From d3c3b65db6b6675c2ff5ddaba4b603d134a2aa46 Mon Sep 17 00:00:00 2001 From: Dragory Date: Tue, 15 Jan 2019 03:04:47 +0200 Subject: [PATCH] Post: add support for !posting files --- package-lock.json | 19 +++++++++++++++++++ package.json | 2 ++ src/plugins/Post.ts | 37 ++++++++++++++++++++++++++++++------- src/utils.ts | 43 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9fe8d994..1728a3d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,12 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==" }, + "@types/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -2932,6 +2938,11 @@ } } }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -3726,6 +3737,14 @@ "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.203.1.tgz", "integrity": "sha512-7MUlYyGJ6rSitEZ3r1Q1QNV8uSIzapS8SmmhSusBuIc7uIxPPwsKllEP0GRp1NS6Ik6F+fRZvnjDWm3ecv2hDw==" }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", diff --git a/package.json b/package.json index 7ae4ea30..2fae008f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "mysql": "^2.16.0", "reflect-metadata": "^0.1.12", "tlds": "^1.203.1", + "tmp": "0.0.33", "ts-node": "^3.3.0", "typeorm": "^0.2.8", "typescript": "^3.2.2", @@ -50,6 +51,7 @@ }, "devDependencies": { "@types/node": "^10.12.0", + "@types/tmp": "0.0.33", "husky": "^0.14.3", "lint-staged": "^7.2.0", "nodemon": "^1.17.5", diff --git a/src/plugins/Post.ts b/src/plugins/Post.ts index 22f81f51..c17931ac 100644 --- a/src/plugins/Post.ts +++ b/src/plugins/Post.ts @@ -1,11 +1,13 @@ import { Plugin, decorators as d } from "knub"; import { Channel, Message, TextChannel } from "eris"; -import { errorMessage } from "../utils"; +import { errorMessage, downloadFile } from "../utils"; import { GuildSavedMessages } from "../data/GuildSavedMessages"; -import { ISavedMessageData } from "../data/entities/SavedMessage"; + +import fs from "fs"; +const fsp = fs.promises; export class PostPlugin extends Plugin { - public static pluginName = 'post'; + public static pluginName = "post"; protected savedMessages: GuildSavedMessages; @@ -33,18 +35,39 @@ export class PostPlugin extends Plugin { } /** - * Post a message as the bot to the specified channel + * COMMAND: Post a message as the bot to the specified channel */ - @d.command("post", " ") + @d.command("post", " [content:string$]") @d.permission("post") - async postCmd(msg: Message, args: { channel: Channel; content: string }) { + async postCmd(msg: Message, args: { channel: Channel; content?: string }) { if (!(args.channel instanceof TextChannel)) { msg.channel.createMessage(errorMessage("Channel is not a text channel")); return; } - const createdMsg = await args.channel.createMessage(args.content); + const content = args.content || undefined; + let downloadedAttachment; + let file; + + if (msg.attachments.length) { + downloadedAttachment = await downloadFile(msg.attachments[0].url); + file = { + name: msg.attachments[0].filename, + file: await fsp.readFile(downloadedAttachment.path) + }; + } + + if (content == null && file == null) { + msg.channel.createMessage(errorMessage("Text content or attachment required")); + return; + } + + const createdMsg = await args.channel.createMessage(content, file); await this.savedMessages.setPermanent(createdMsg.id); + + if (downloadedAttachment) { + downloadedAttachment.deleteFn(); + } } /** diff --git a/src/utils.ts b/src/utils.ts index 8e952805..f58df0c0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,15 @@ -import at = require("lodash.at"); +import at from "lodash.at"; import { Emoji, Guild, GuildAuditLogEntry, TextableChannel } from "eris"; import url from "url"; import tlds from "tlds"; import emojiRegex from "emoji-regex"; +import fs from "fs"; +const fsp = fs.promises; + +import https from "https"; +import tmp from "tmp"; + /** * Turns a "delay string" such as "1h30m" to milliseconds * @param {String} str @@ -287,6 +293,41 @@ export async function createChunkedMessage(channel: TextableChannel, messageText } } +/** + * Downloads the file from the given URL to a temporary file, with retry support + */ +export function downloadFile(attachmentUrl: string, retries = 3): Promise<{ path: string; deleteFn: () => void }> { + return new Promise(resolve => { + tmp.file((err, path, fd, deleteFn) => { + if (err) throw err; + + const writeStream = fs.createWriteStream(path); + + https + .get(attachmentUrl, res => { + res.pipe(writeStream); + writeStream.on("finish", () => { + writeStream.end(); + resolve({ + path, + deleteFn + }); + }); + }) + .on("error", httpsErr => { + fsp.unlink(path); + + if (retries === 0) { + throw httpsErr; + } else { + console.warn("File download failed, retrying. Error given:", httpsErr.message); + resolve(downloadFile(attachmentUrl, retries - 1)); + } + }); + }); + }); +} + export function noop() { // IT'S LITERALLY NOTHING }