2022-12-29 17:12:44 -08:00
|
|
|
/*
|
|
|
|
i really should split this up into different modules
|
|
|
|
*/
|
|
|
|
|
2022-09-26 22:09:38 -07:00
|
|
|
import bodyParser from "body-parser"
|
2022-09-27 16:39:25 -07:00
|
|
|
import multer, {memoryStorage} from "multer"
|
2022-12-29 13:03:33 -08:00
|
|
|
import Discord, { IntentsBitField, Client } from "discord.js"
|
2022-09-26 22:09:38 -07:00
|
|
|
import express from "express"
|
|
|
|
import fs from "fs"
|
2022-12-26 20:59:16 -08:00
|
|
|
import axios, { AxiosResponse } from "axios"
|
2022-09-26 22:09:38 -07:00
|
|
|
|
|
|
|
require('dotenv').config()
|
2022-12-27 11:52:11 -08:00
|
|
|
let pkg = require(`${process.cwd()}/package.json`)
|
2022-09-26 22:09:38 -07:00
|
|
|
|
|
|
|
let app = express()
|
|
|
|
|
2022-09-27 16:39:25 -07:00
|
|
|
const multerSetup = multer({storage:memoryStorage()})
|
|
|
|
|
2022-12-27 15:09:47 -08:00
|
|
|
let config = require(`${process.cwd()}/config.json`)
|
|
|
|
app.use("/static",express.static("assets"))
|
2022-12-26 20:59:16 -08:00
|
|
|
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
|
2022-09-26 22:09:38 -07:00
|
|
|
let files:{[key:string]:{filename:string,mime:string,messageids:string[]}} = {}
|
|
|
|
|
|
|
|
// funcs
|
|
|
|
|
|
|
|
function ThrowError(response:express.Response,code:number,errorMessage:string) {
|
|
|
|
fs.readFile(__dirname+"/../pages/error.html",(err,buf) => {
|
|
|
|
if (err) {response.sendStatus(500);console.log(err);return}
|
|
|
|
response.status(code)
|
2022-12-27 11:52:11 -08:00
|
|
|
response.send(buf.toString().replace(/\$ErrorCode/g,code.toString()).replace(/\$ErrorMessage/g,errorMessage).replace(/\$Version/g,pkg.version))
|
2022-09-26 22:09:38 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// init data
|
|
|
|
|
|
|
|
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
|
|
|
|
|
|
|
|
fs.readFile(__dirname+"/../.data/files.json",(err,buf) => {
|
|
|
|
if (err) {console.log(err);return}
|
|
|
|
files = JSON.parse(buf.toString() || "{}")
|
|
|
|
})
|
|
|
|
|
|
|
|
// discord
|
|
|
|
|
|
|
|
let client = new Client({intents:[
|
2022-12-29 13:03:33 -08:00
|
|
|
IntentsBitField.Flags.GuildMessages,
|
|
|
|
IntentsBitField.Flags.MessageContent
|
|
|
|
],rest:{timeout:config.requestTimeout}})
|
2022-09-26 22:09:38 -07:00
|
|
|
|
|
|
|
let uploadChannel:Discord.TextBasedChannel
|
|
|
|
|
2022-12-26 20:28:33 -08:00
|
|
|
interface FileUploadSettings {
|
|
|
|
name?: string,
|
|
|
|
mime: string,
|
2022-12-29 12:43:16 -08:00
|
|
|
uploadId?: string
|
2022-12-26 20:28:33 -08:00
|
|
|
}
|
2022-09-26 22:09:38 -07:00
|
|
|
|
2022-12-26 20:28:33 -08:00
|
|
|
let uploadFile = (settings:FileUploadSettings,fBuffer:Buffer) => {
|
|
|
|
return new Promise<string>(async (resolve,reject) => {
|
|
|
|
if (!settings.name || !settings.mime) {reject({status:400,message:"missing name/mime"});return}
|
2022-09-27 16:39:25 -07:00
|
|
|
|
2022-12-29 17:12:44 -08:00
|
|
|
let uploadId = (settings.uploadId || Math.random().toString().slice(2)).toString();
|
|
|
|
|
|
|
|
if ((uploadId.match(/[A-Za-z0-9_\-]+/)||[])[0] != uploadId || uploadId.length > 30) {reject({status:400,message:"invalid id"});return}
|
2022-09-27 16:10:23 -07:00
|
|
|
|
2022-12-29 17:12:44 -08:00
|
|
|
if (files[uploadId]) {reject({status:400,message:"a file with this id already exists"});return}
|
2022-12-26 20:28:33 -08:00
|
|
|
if (settings.name.length > 128) {reject({status:400,message:"name too long"}); return}
|
|
|
|
if (settings.name.length > 128) {reject({status:400,message:"mime too long"}); return}
|
2022-09-27 16:10:23 -07:00
|
|
|
|
|
|
|
// get buffer
|
2022-12-26 20:28:33 -08:00
|
|
|
if (fBuffer.byteLength >= (config.maxDiscordFileSize*config.maxDiscordFiles)) {reject({status:400,message:"file too large"}); return}
|
2022-09-27 16:10:23 -07:00
|
|
|
|
|
|
|
// generate buffers to upload
|
|
|
|
let toUpload = []
|
|
|
|
for (let i = 0; i < Math.ceil(fBuffer.byteLength/config.maxDiscordFileSize); i++) {
|
|
|
|
toUpload.push(fBuffer.subarray(i*config.maxDiscordFileSize,Math.min(fBuffer.byteLength,(i+1)*config.maxDiscordFileSize)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// begin uploading
|
2022-12-29 13:03:33 -08:00
|
|
|
let uploadTmplt:Discord.AttachmentBuilder[] = toUpload.map((e) => {return new Discord.AttachmentBuilder(e).setName(Math.random().toString().slice(2))})
|
2022-09-27 16:10:23 -07:00
|
|
|
let uploadGroups = []
|
|
|
|
for (let i = 0; i < Math.ceil(uploadTmplt.length/10); i++) {
|
|
|
|
uploadGroups.push(uploadTmplt.slice(i*10,((i+1)*10)))
|
|
|
|
}
|
|
|
|
|
|
|
|
let msgIds = []
|
|
|
|
|
|
|
|
for (let i = 0; i < uploadGroups.length; i++) {
|
|
|
|
let ms = await uploadChannel.send({files:uploadGroups[i]}).catch((e) => {console.error(e)})
|
|
|
|
if (ms) {
|
|
|
|
msgIds.push(ms.id)
|
|
|
|
} else {
|
2022-12-26 20:28:33 -08:00
|
|
|
reject({status:500,message:"please try again"}); return
|
2022-09-27 16:10:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// save
|
|
|
|
|
|
|
|
files[uploadId] = {
|
2022-12-26 20:28:33 -08:00
|
|
|
filename:settings.name,
|
2022-09-27 16:10:23 -07:00
|
|
|
messageids:msgIds,
|
2022-12-26 20:28:33 -08:00
|
|
|
mime:settings.mime
|
2022-09-27 16:10:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fs.writeFile(__dirname+"/../.data/files.json",JSON.stringify(files),(err) => {
|
2022-12-26 20:28:33 -08:00
|
|
|
if (err) {reject({status:500,message:"please try again"}); delete files[uploadId];return}
|
|
|
|
resolve(uploadId)
|
2022-09-27 16:10:23 -07:00
|
|
|
})
|
2022-12-26 20:28:33 -08:00
|
|
|
})
|
|
|
|
}
|
2022-09-27 16:10:23 -07:00
|
|
|
|
2022-12-26 20:28:33 -08:00
|
|
|
app.get("/", function(req,res) {
|
2022-12-29 17:12:44 -08:00
|
|
|
fs.readFile(__dirname+"/../pages/base.html",(err,buf) => {
|
2022-12-26 20:28:33 -08:00
|
|
|
if (err) {res.sendStatus(500);console.log(err);return}
|
2022-12-29 17:12:44 -08:00
|
|
|
res.send(
|
|
|
|
buf.toString()
|
|
|
|
.replace("$MaxInstanceFilesize",`${(config.maxDiscordFileSize*config.maxDiscordFiles)/1048576}MB`)
|
|
|
|
.replace(/\$Version/g,pkg.version)
|
|
|
|
.replace(/\$Handler/g,"upload_file")
|
|
|
|
.replace(/\$UploadButtonText/g,"Upload file")
|
|
|
|
.replace(/\$otherPath/g,"/clone")
|
|
|
|
.replace(/\$otherText/g,"clone from url...")
|
|
|
|
)
|
2022-12-26 20:28:33 -08:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-12-26 21:13:55 -08:00
|
|
|
app.get("/clone", function(req,res) {
|
2022-12-29 17:12:44 -08:00
|
|
|
fs.readFile(__dirname+"/../pages/base.html",(err,buf) => {
|
2022-12-26 21:13:55 -08:00
|
|
|
if (err) {res.sendStatus(500);console.log(err);return}
|
2022-12-29 17:12:44 -08:00
|
|
|
res.send(
|
|
|
|
buf.toString()
|
|
|
|
.replace("$MaxInstanceFilesize",`${(config.maxDiscordFileSize*config.maxDiscordFiles)/1048576}MB`)
|
|
|
|
.replace(/\$Version/g,pkg.version)
|
|
|
|
.replace(/\$Handler/g,"clone_file")
|
|
|
|
.replace(/\$UploadButtonText/g,"Input a URL")
|
|
|
|
.replace(/\$otherPath/g,"/")
|
|
|
|
.replace(/\$otherText/g,"upload file...")
|
|
|
|
)
|
2022-12-26 21:13:55 -08:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-12-26 20:28:33 -08:00
|
|
|
app.post("/upload",multerSetup.single('file'),async (req,res) => {
|
|
|
|
if (req.file) {
|
2022-12-29 17:12:44 -08:00
|
|
|
try {
|
|
|
|
uploadFile({name:req.file.originalname,mime:req.file.mimetype,uploadId:req.header("monofile-upload-id")},req.file.buffer)
|
|
|
|
.then((uID) => res.send(uID))
|
|
|
|
.catch((stat) => {res.status(stat.status);res.send(`[err] ${stat.message}`)})
|
|
|
|
} catch {
|
|
|
|
res.status(400)
|
|
|
|
res.send("[err] bad request")
|
|
|
|
}
|
2022-09-27 16:10:23 -07:00
|
|
|
} else {
|
|
|
|
res.status(400)
|
|
|
|
res.send("[err] bad request")
|
|
|
|
}
|
2022-09-26 22:09:38 -07:00
|
|
|
})
|
|
|
|
|
2022-12-26 20:28:33 -08:00
|
|
|
app.post("/clone",(req,res) => {
|
2022-12-29 17:12:44 -08:00
|
|
|
try {
|
|
|
|
let j = JSON.parse(req.body)
|
|
|
|
if (!j.url) {
|
|
|
|
res.status(400)
|
|
|
|
res.send("[err] invalid url")
|
|
|
|
}
|
|
|
|
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
|
|
|
|
uploadFile({name:req.body.split("/")[req.body.split("/").length-1] || "generic",mime:data.headers["content-type"],uploadId:j.uploadId},Buffer.from(data.data))
|
|
|
|
.then((uID) => res.send(uID))
|
|
|
|
.catch((stat) => {res.status(stat.status);res.send(`[err] ${stat.message}`)})
|
|
|
|
}).catch((err) => {
|
|
|
|
res.status(400)
|
|
|
|
res.send(`[err] failed to fetch data`)
|
|
|
|
})
|
|
|
|
} catch {
|
|
|
|
res.status(500)
|
|
|
|
res.send("[err] an error occured")
|
|
|
|
}
|
2022-12-26 20:28:33 -08:00
|
|
|
})
|
|
|
|
|
2022-09-26 22:09:38 -07:00
|
|
|
app.get("/download/:fileId",(req,res) => {
|
|
|
|
if (files[req.params.fileId]) {
|
|
|
|
let file = files[req.params.fileId]
|
|
|
|
|
|
|
|
fs.readFile(__dirname+"/../pages/download.html",(err,buf) => {
|
|
|
|
if (err) {res.sendStatus(500);console.log(err);return}
|
2022-12-27 11:52:11 -08:00
|
|
|
res.send(buf.toString().replace(/\$FileName/g,file.filename).replace(/\$FileId/g,req.params.fileId).replace(/\$Version/g,pkg.version))
|
2022-09-26 22:09:38 -07:00
|
|
|
})
|
|
|
|
} else {
|
2022-12-27 15:09:47 -08:00
|
|
|
ThrowError(res,404,"File not found.")
|
2022-09-26 22:09:38 -07:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
app.get("/file/:fileId",async (req,res) => {
|
|
|
|
if (files[req.params.fileId]) {
|
|
|
|
let file = files[req.params.fileId]
|
2022-09-27 16:10:23 -07:00
|
|
|
let bufToCombine = []
|
2022-09-26 22:09:38 -07:00
|
|
|
|
|
|
|
for (let i = 0; i < file.messageids.length; i++) {
|
|
|
|
let msg = await uploadChannel.messages.fetch(file.messageids[i]).catch(() => {return null})
|
|
|
|
if (msg?.attachments) {
|
2022-09-27 16:10:23 -07:00
|
|
|
let attach = Array.from(msg.attachments.values())
|
|
|
|
for (let i = 0; i < attach.length; i++) {
|
2022-09-27 22:56:24 -07:00
|
|
|
let d = await axios.get(attach[i].url,{responseType:"arraybuffer"}).catch((e:Error) => {console.error(e)})
|
2022-09-27 16:10:23 -07:00
|
|
|
if (d) {
|
2022-09-27 16:39:25 -07:00
|
|
|
bufToCombine.push(d.data)
|
2022-09-27 16:10:23 -07:00
|
|
|
} else {
|
|
|
|
res.sendStatus(500);return
|
|
|
|
}
|
|
|
|
}
|
2022-09-26 22:09:38 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-27 22:56:24 -07:00
|
|
|
let nb:Buffer|null = Buffer.concat(bufToCombine)
|
2022-09-27 16:10:23 -07:00
|
|
|
|
|
|
|
res.setHeader('Content-Type',file.mime)
|
|
|
|
res.send(nb)
|
|
|
|
|
2022-09-27 22:56:24 -07:00
|
|
|
nb = null
|
|
|
|
|
2022-09-26 22:09:38 -07:00
|
|
|
} else {
|
|
|
|
res.sendStatus(404)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-12-27 15:09:47 -08:00
|
|
|
app.get("/server",(req,res) => {
|
|
|
|
res.send(JSON.stringify({...config,version:pkg.version}))
|
|
|
|
})
|
|
|
|
|
2022-09-26 22:09:38 -07:00
|
|
|
app.get("*",(req,res) => {
|
2022-12-27 15:09:47 -08:00
|
|
|
ThrowError(res,404,"Page not found.")
|
2022-09-26 22:09:38 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
client.on("ready",() => {
|
|
|
|
console.log("Discord OK!")
|
|
|
|
|
|
|
|
client.guilds.fetch(config.targetGuild).then((g) => {
|
|
|
|
g.channels.fetch(config.targetChannel).then((a) => {
|
2022-12-29 13:03:33 -08:00
|
|
|
if (a?.isTextBased()) {
|
2022-09-26 22:09:38 -07:00
|
|
|
uploadChannel = a
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
app.listen(3000,function() {
|
|
|
|
console.log("Web OK!")
|
|
|
|
})
|
|
|
|
|
|
|
|
client.login(process.env.TOKEN)
|