discord-lfs/dlfs.js

165 lines
5.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
process.on("unhandledRejection", (reason) => {
console.error(`An error occured: ${reason}`);
process.exit(1);
});
var argv = process.argv.slice(2);
var command = argv[0];
var input = argv.slice(1).join(' ');
switch (command) {
case "upload":
if (!input) { console.error(`input a file path u derp`); process.exit(1); }
let stat = require("fs").statSync(input);
if (!stat.isFile()) {
if (stat.isDirectory()) console.log(`Directories are not supported; archive it with tar or something first.`);
else console.log(`wtf is dis\ngive me regular file!`);
process.exit(1);
}
uploadFile(input);
break;
case "download":
if (!input) { console.error(`download what bruh`); process.exit(1); }
downloadFile(input);
break;
case "delete":
if (!input) { console.error(`bruh`); process.exit(1); }
deleteFile(input);
break;
case undefined:
console.log(
`Discord Large-File-Storing Utility Thing\n` +
`To upload a file: ${process.argv.slice(0,2).join(' ')} upload <path to any file>\n` +
`To download a file: ${process.argv.slice(0,2).join(' ')} download <http url or path to .dlfs.gz file>\n` +
`To delete a file you uploaded: ${process.argv.slice(0,2).join(' ')} <http url or path to .dlfs.gz file>`
);
process.exit();
break;
default:
console.log("bruh");
process.exit(1);
}
const DATA_GUILD_NAME = "datastore";
const DATA_CHANNEL_NAME = "data";
const CHUNK_SIZE = 8e6;
async function prepareClient() {
require("dotenv").config();
var client = new (require("discord.js")).Client({restRequestTimeout: 6e4});
console.log("Logging in to Discord…");
await client.login(process.env.TOKEN).catch(error => {
console.error(`Login failed: ${error.message}\nMake sure to set the TOKEN environment variable to a valid bot token.`);
process.exit(1);
});
await new Promise(r => client.on("ready", r));
client.dataguild = client.guilds.cache.find(x => x.name == DATA_GUILD_NAME && x.ownerID == client.user.id);
if (!client.dataguild) {
console.log("Creating new data guild…");
client.dataguild = await client.guilds.create(DATA_GUILD_NAME, {
channels: [Array(500).fill({name: DATA_CHANNEL_NAME})]
});
} else console.log("Found data guild");
//console.debug(`ID: ${dg.id} Invite: ${(await dg.channels.cache.first().createInvite()).code}`);
return client;
}
async function uploadFile(filepath) {
var readstream = require("fs").createReadStream(filepath);
var client = await prepareClient();
var datachannels = client.dataguild.channels.cache.filter(c => c.name == DATA_CHANNEL_NAME).array();
var filename = require("path").basename(filepath);
var metadata = [JSON.stringify({filename})]; // still not sure what to call this
var index = 0;
while (true) {
let choncc = readstream.read(CHUNK_SIZE);
if (!choncc) {
if (readstream.readableEnded) break;
else {
await new Promise(r => setTimeout(r, 100));
continue;
}
}
console.log(`Uploading chunk #${index}`);
let message;
while (true) {
try {
message = await datachannels[index].send(new (require("discord.js")).MessageAttachment(choncc, `${filename}.${index}`));
break;
} catch(error) {
console.error("bruh:", error.message, "retrying…");
}
}
let attachment = message.attachments.first();
metadata.push(`${message.channel.id},${message.id},${attachment.id},${attachment.name}`);
index++;
}
console.log("All chunks uploaded; saving metadata…");
metadata = metadata.join(';');
metadata = require("zlib").gzipSync(metadata);
var metadatafilename = `${filename}.dlfs.gz`;
while (require("fs").existsSync(metadatafilename)) metadatafilename += '_';
require("fs").writeFileSync(metadatafilename, metadata);
console.log(`Metadata file written to ${metadatafilename}. Anyone with this metadata file can download the original file using this program.`);
client.destroy();
}
async function downloadFile(x) {
var {metadata, filename} = await loadMetadata(x);
while (require("fs").existsSync(filename)) filename += '_';
var writestream = require("fs").createWriteStream(filename);
var index = 0;
while (true) {
let dat = metadata[index];
if (!dat) break;
let channel_id = dat[0], attachment_id = dat[2], attachment_name = dat[3];
let attachment_url = `https://cdn.discordapp.com/attachments/${channel_id}/${attachment_id}/${attachment_name}`;
console.log(`Downloading chunk #${index}`);
let data = (await require("node-fetch")(attachment_url)).body;
data.pipe(writestream, {end: false});
await new Promise(resolve => data.on("end", resolve));
index++;
}
writestream.end();
console.log(`Done! Downloaded to "${filename}"`);
}
async function deleteFile(x) {
var {metadata} = await loadMetadata(x);
var client = await prepareClient();
for (let i = 0; i < metadata.length; i++) {
try {
console.log(`Deleting chunk ${i}`);
let channel_id = metadata[i][0], message_id = metadata[i][1];
let channel = await client.dataguild.channels.resolve(channel_id);
//console.debug(`Found channel ${channel_id}`);
let message = await channel.messages.fetch(message_id);
//console.debug(`Found message ${message_id}`);
await message.delete();
//console.debug(`Deleted message ${message_id}`);
} catch (error) {
console.error(`${i}: ${error.message}`);
}
}
console.log(`Done`);
client.destroy();
}
async function loadMetadata(urlorpath){
console.log("Loading metadata file…");
if (/^https?:\/\//.test(urlorpath)) {
var metadata = (await require("node-fetch")(urlorpath)).body;
} else {
var metadata = require("fs").readFileSync(urlorpath);
}
metadata = require("zlib").gunzipSync(metadata).toString();
metadata = metadata.split(';');
var metametadata = JSON.parse(metadata.shift());
metadata = metadata.map(x => x.split(','));
metadata.sort((a,b) => {
let ai = Number(a[3].split('.').pop());
let bi = Number(b[3].split('.').pop());
return ai - bi;
});
return {filename: metametadata.filename, metadata}
}