From 3f9997ff7e976dffb77116440e1b81cb00714734 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 10:51:58 +0800 Subject: [PATCH 01/16] supress tslint/tsc error --- Component/Discord.ts | 1 + Core/AudioManager.ts | 1 + Core/Utils/MediaInfo.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/Component/Discord.ts b/Component/Discord.ts index ef492bb..0dbaff2 100644 --- a/Component/Discord.ts +++ b/Component/Discord.ts @@ -212,6 +212,7 @@ export class Discord { this.bot.createMessage(msg.channel.id, `Register token: ${this.user.createBindToken(user._id!)}\nExpires after one hour`); } + // @ts-ignore private async procseeFile(msg: Message) { const user = await this.user.get(BIND_TYPE, msg.author.id); diff --git a/Core/AudioManager.ts b/Core/AudioManager.ts index 829b7aa..7bfa21e 100644 --- a/Core/AudioManager.ts +++ b/Core/AudioManager.ts @@ -41,6 +41,7 @@ export class AudioManager { const encoder = new Encoder(core.config); this.encode = encoder.encode.bind(encoder); + // tslint:disable-next-line:no-shadowed-variable core.on("init", core => { this.listManager = core.listManager; }); diff --git a/Core/Utils/MediaInfo.ts b/Core/Utils/MediaInfo.ts index ed7b70b..15cf5dd 100644 --- a/Core/Utils/MediaInfo.ts +++ b/Core/Utils/MediaInfo.ts @@ -7,6 +7,7 @@ try { execFileSync("ffprobe", ["-version"], { stdio: "ignore" }); ffprobe = "ffprobe"; } catch (err) { + // tslint:disable-next-line:no-var-requires ffprobe = require("@ffprobe-installer/ffprobe").path; } From 08622c9d70fd28f92be88684ec1ee17169b70cab Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 11:25:03 +0800 Subject: [PATCH 02/16] add web backend --- Component/Web.ts | 290 +++++++++++++++++++++++++++++++++++++++++++++++ config.json | 3 + index.ts | 8 ++ multer.d.ts | 1 + package.json | 3 + 5 files changed, 305 insertions(+) create mode 100644 Component/Web.ts create mode 100644 multer.d.ts diff --git a/Component/Web.ts b/Component/Web.ts new file mode 100644 index 0000000..9f1d4c5 --- /dev/null +++ b/Component/Web.ts @@ -0,0 +1,290 @@ +import crypto from "crypto"; +import { Application, NextFunction, Request, Response } from "express"; +import express from "express"; +import { promises as fsp } from "fs"; +import { ObjectID } from "mongodb"; +import multer from "multer"; +import { Core } from ".."; +import { AudioManager, ERR_MISSING_TITLE, IAudioData } from "../Core/AudioManager"; +import { IAudioList, ListManager } from "../Core/ListManager"; +import { IUserData, UserManager } from "../Core/UserManager"; +// import { retry, sleep } from "../Core/Utils/PromiseUtils"; + +export const BIND_TYPE = "telegram"; // "web"; +const ERR_MISSING_TOKEN = Error("Telegram bot api token not found!"); +// const ERR_NOT_VALID_TITLE = Error("Not valid title"); +// const ERR_LIST_NOT_FOUND = Error("Playlist not found"); +const ERR_BAD_REQUEST = "Maybe wrong paramters!"; +const ERR_FORBIDDEN = "You're not allowed to do this!"; +const ERR_NOT_REGISTER = "Please use /register to register or bind account via Telegram!"; +const ERR_NOT_FOUND = "File disappeared!"; + +export class Web { + private audio: AudioManager; + private user: UserManager; + private list: ListManager; + private tgToken: string; + private upload: string; + private server: Application; + + constructor(core: Core) { + if (!core.config.telegram.token) throw ERR_MISSING_TOKEN; + + this.user = core.userManager; + this.audio = core.audioManager; + this.list = core.listManager; + + this.tgToken = core.config.telegram.token; + this.upload = core.config.web.upload; + + // Create Server + this.server = express(); + + this.middlewares(); + this.registerRoutes(); + this.errorHandler(); + + this.server.listen(8081); + } + + private async middlewares() { + // outdated... workaround + this.server.use((express as any).json()); // for parsing application/json + } + + private async errorHandler() { + this.server.use((err: Error, req: Request, res: Response, next: NextFunction) => { + if (err.message.startsWith("HTTP400")) { + res.status(400).json({ + error: ERR_BAD_REQUEST + }); + } else if (err.message.startsWith("HTTP401")) { + res.status(401).json({ + error: ERR_NOT_REGISTER + }); + } else if (err.message.startsWith("HTTP403")) { + res.status(403).json({ + error: ERR_FORBIDDEN + }); + } else if (err.message.startsWith("HTTP404")) { + res.status(404).json({ + error: ERR_NOT_FOUND + }); + } else { + next(err); + } + }); + } + + private route(fn: (req: Request, res: Response, next: NextFunction) => Promise | void) { + return (req: Request, res: Response, next: NextFunction) => { + const promise = fn.bind(this)(req, res, next); + if (promise instanceof Promise) { + promise.catch(next); + } + }; + } + + private async registerRoutes() { + const upload = multer({ dest: this.upload }); + + this.server.get("/", (req: Request, res: Response) => res.send("MusicBot Web Server")); + this.server.get("/login", this.route(this.getLogin)); + this.server.get("/lists", this.route(this.getLists)); + this.server.post("/lists", this.route(this.postLists)); + this.server.get("/list/:lid", this.route(this.getList)); + this.server.patch("/list/:lid", this.route(this.patchList)); + this.server.delete("/list/:lid", this.route(this.deleteList)); + this.server.get("/list/:lid/audios", this.route(this.getAudios)); + this.server.post("/list/:lid/audios", upload.array("audio"), this.route(this.postAudios)); + this.server.get("/audio/:aid", this.route(this.getAudio)); + this.server.get("/audio/:aid/file", this.route(this.getAudioFile)); + } + + private async getLogin(req: Request, res: Response) { + const user = await this.checkUser(req); + res.json({ + msg: "OK", + user + }); + } + + private async getLists(req: Request, res: Response) { + const user = await this.getUser(req); + const lists = await this.list.getAll().map((list: IAudioList) => { + return { + id: list._id, + name: list.name, + own: !!user && user._id!.equals(list.owner) + }; + }).toArray(); + res.json({ + lists, + msg: "OK" + }); + } + + private async postLists(req: Request, res: Response) { + const user = await this.checkUser(req); + const name = (req as any).body.name; + if (!name) { + throw Error("HTTP400"); + } + const list = await this.list.create(name, user._id!); + res.json({ + list, + msg: "OK" + }); + } + + private async getList(req: Request, res: Response) { + const list = await this.list.get(new ObjectID(req.params.lid)); + res.json({ + list, + msg: "OK" + }); + } + + private async patchList(req: Request, res: Response) { + const user = await this.checkUser(req); + const list = await this.list.get(new ObjectID(req.params.lid)); + const name = (req as any).body.name; + if (!name) { + throw Error("HTTP400"); + } + if (!list) { + throw Error("HTTP404"); + } + if (!user._id!.equals(list.owner)) { + throw Error("HTTP403"); + } + const result = await this.list.rename(list._id, name); + res.json({ + msg: "OK", + result + }); + } + + private async deleteList(req: Request, res: Response) { + const user = await this.checkUser(req); + const list = await this.list.get(new ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + if (!user._id!.equals(list.owner)) { + throw Error("HTTP403"); + } + const result = await this.list.delete(list._id); + res.json({ + msg: "OK", + result + }); + } + + private async getAudios(req: Request, res: Response) { + const list = await this.list.get(new ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + res.json({ + audios: list.audio, + msg: "OK" + }); + } + + private async postAudios(req: Request, res: Response) { + try { + const user = await this.checkUser(req); + const list = await this.list.get(new ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + if (!user._id!.equals(list.owner)) { + throw Error("HTTP403"); + } + const audios: IAudioData[] = []; + await Promise.all((req as any).files.map(async (file: any) => { + const path = file.path; + const audio = await this.processFile(path, user); + if (audio) { + await this.list.addAudio(list._id!, audio._id!); + audios.push(audio); + } else { + // failed + } + })); + res.json({ + audios, + msg: "OK" + }); + } finally { + await Promise.all((req as any).files.map(async (file: any) => await fsp.unlink(file.path))); + } + } + + private async getAudio(req: Request, res: Response) { + const audio = await this.audio.get(new ObjectID(req.params.aid)); + res.json({ + audio, + msg: "OK" + }); + } + + private async getAudioFile(req: Request, res: Response) { + const audio = await this.audio.get(new ObjectID(req.params.aid)); + if (!audio) { + throw Error("HTTP404"); + } + const file = await this.audio.getFile(audio); + if (!file) { + throw Error("HTTP404"); + } + res.download(file); + } + + private async getUser(req: Request) { + const reqbody = (req as any).body; + if (!reqbody.tg) return null; + const tg = (typeof reqbody.tg === "string") ? JSON.parse(reqbody.tg) : reqbody.tg; + const payload = [ + `auth_date=${tg.auth_date}`, + `first_name=${tg.first_name}`, + `id=${tg.id}`, + `username=${tg.username}` + ].join("\n"); + const hmac = crypto.createHmac("sha256", this.tgToken); + hmac.update(payload); + if (hmac.digest("hex") !== tg.hash) { + return null; + } + return this.user.get(BIND_TYPE, tg.id); + } + + private async checkUser(req: Request) { + const user = await this.getUser(req); + if (!user) { + throw Error("HTTP401"); + } + return user; + } + + private async processFile(file: string, sender: IUserData) { + if (!file) return null; + + let audio; + try { + audio = await this.audio.add(sender._id!, file); + } catch (error) { + if (error === ERR_MISSING_TITLE) { + // show error ? + return null; + } else { + // unkown dead + return null; + } + } + + return audio; + } + +} diff --git a/config.json b/config.json index 968ffe3..8ccb73f 100644 --- a/config.json +++ b/config.json @@ -14,5 +14,8 @@ "discord": { "token": "", "owner": "self" + }, + "web": { + "upload": "Upload" } } \ No newline at end of file diff --git a/index.ts b/index.ts index 7e9e4e1..dcecafe 100644 --- a/index.ts +++ b/index.ts @@ -3,6 +3,7 @@ import { existsSync, mkdirSync } from "fs"; import { resolve } from "path"; import { Discord } from "./Component/Discord"; import { Telegram } from "./Component/Telegram"; +import { Web } from "./Component/Web"; import { AudioManager } from "./Core/AudioManager"; import { ListManager } from "./Core/ListManager"; import { MongoDB } from "./Core/MongoDB"; @@ -40,6 +41,13 @@ export class Core extends EventEmitter { console.error(error); } + try { + // tslint:disable-next-line:no-unused-expression + new Web(this); + } catch (error) { + console.error(error); + } + if (process.argv.indexOf("--deep-check") !== -1) { await this.audioManager.checkCache(true); this.listManager.checkAudioExist(); diff --git a/multer.d.ts b/multer.d.ts new file mode 100644 index 0000000..6bdeb62 --- /dev/null +++ b/multer.d.ts @@ -0,0 +1 @@ +declare module "multer"; diff --git a/package.json b/package.json index eb47578..8094a6e 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,17 @@ }, "dependencies": { "eris": "^0.10.0", + "express": "^4.17.1", "fluent-ffmpeg": "^2.1.2", "mongodb": "^3.2.3", + "multer": "^1.4.1", "node-telegram-bot-api": "^0.30.0", "promise-queue": "^2.2.5", "shuffle-array": "^1.0.1", "ytdl-core": "^0.29.1" }, "devDependencies": { + "@types/express": "^4.17.0", "@types/fluent-ffmpeg": "^2.1.9", "@types/mongodb": "^3.1.25", "@types/node": "^12.0.8", From 03a05ee7c4b72ca5a10ca4a27e742827290c807c Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 11:39:39 +0800 Subject: [PATCH 03/16] build --- dist/Component/Discord.js | 1 + dist/Component/Telegram.js | 1 + dist/Component/Web.js | 261 ++++++++++++++++++++++++++ dist/Core/AudioManager.js | 1 + dist/Core/ListManager.js | 1 + dist/Core/MongoDB.js | 1 + dist/Core/URLParser.js | 1 + dist/Core/UserManager.js | 1 + dist/Core/Utils/Encoder.js | 1 + dist/Core/Utils/MediaInfo.js | 1 + dist/Core/Utils/PromiseUtils.js | 1 + dist/Core/Utils/URLHandler/Youtube.js | 1 + dist/index.js | 8 + 13 files changed, 280 insertions(+) create mode 100644 dist/Component/Web.js diff --git a/dist/Component/Discord.js b/dist/Component/Discord.js index c24160c..bfafa0d 100644 --- a/dist/Component/Discord.js +++ b/dist/Component/Discord.js @@ -225,3 +225,4 @@ class Discord { } } exports.Discord = Discord; +//# sourceMappingURL=Discord.js.map \ No newline at end of file diff --git a/dist/Component/Telegram.js b/dist/Component/Telegram.js index 846ba95..895b36a 100644 --- a/dist/Component/Telegram.js +++ b/dist/Component/Telegram.js @@ -672,3 +672,4 @@ class Telegram { } } exports.Telegram = Telegram; +//# sourceMappingURL=Telegram.js.map \ No newline at end of file diff --git a/dist/Component/Web.js b/dist/Component/Web.js new file mode 100644 index 0000000..cccc572 --- /dev/null +++ b/dist/Component/Web.js @@ -0,0 +1,261 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const crypto_1 = __importDefault(require("crypto")); +const express_1 = __importDefault(require("express")); +const fs_1 = require("fs"); +const mongodb_1 = require("mongodb"); +const multer_1 = __importDefault(require("multer")); +const AudioManager_1 = require("../Core/AudioManager"); +exports.BIND_TYPE = "telegram"; +const ERR_MISSING_TOKEN = Error("Telegram bot api token not found!"); +const ERR_BAD_REQUEST = "Maybe wrong paramters!"; +const ERR_FORBIDDEN = "You're not allowed to do this!"; +const ERR_NOT_REGISTER = "Please use /register to register or bind account via Telegram!"; +const ERR_NOT_FOUND = "File disappeared!"; +class Web { + constructor(core) { + if (!core.config.telegram.token) + throw ERR_MISSING_TOKEN; + this.user = core.userManager; + this.audio = core.audioManager; + this.list = core.listManager; + this.tgToken = core.config.telegram.token; + this.upload = core.config.web.upload; + this.server = express_1.default(); + this.middlewares(); + this.registerRoutes(); + this.errorHandler(); + this.server.listen(8081); + } + async middlewares() { + this.server.use(express_1.default.json()); + } + async errorHandler() { + this.server.use((err, req, res, next) => { + if (err.message.startsWith("HTTP400")) { + res.status(400).json({ + error: ERR_BAD_REQUEST + }); + } + else if (err.message.startsWith("HTTP401")) { + res.status(401).json({ + error: ERR_NOT_REGISTER + }); + } + else if (err.message.startsWith("HTTP403")) { + res.status(403).json({ + error: ERR_FORBIDDEN + }); + } + else if (err.message.startsWith("HTTP404")) { + res.status(404).json({ + error: ERR_NOT_FOUND + }); + } + else { + next(err); + } + }); + } + route(fn) { + return (req, res, next) => { + const promise = fn.bind(this)(req, res, next); + if (promise instanceof Promise) { + promise.catch(next); + } + }; + } + async registerRoutes() { + const upload = multer_1.default({ dest: this.upload }); + this.server.get("/", (req, res) => res.send("MusicBot Web Server")); + this.server.get("/login", this.route(this.getLogin)); + this.server.get("/lists", this.route(this.getLists)); + this.server.post("/lists", this.route(this.postLists)); + this.server.get("/list/:lid", this.route(this.getList)); + this.server.patch("/list/:lid", this.route(this.patchList)); + this.server.delete("/list/:lid", this.route(this.deleteList)); + this.server.get("/list/:lid/audios", this.route(this.getAudios)); + this.server.post("/list/:lid/audios", upload.array("audio"), this.route(this.postAudios)); + this.server.get("/audio/:aid", this.route(this.getAudio)); + this.server.get("/audio/:aid/file", this.route(this.getAudioFile)); + } + async getLogin(req, res) { + const user = await this.checkUser(req); + res.json({ + msg: "OK", + user + }); + } + async getLists(req, res) { + const user = await this.getUser(req); + const lists = await this.list.getAll().map((list) => { + return { + id: list._id, + name: list.name, + own: !!user && user._id.equals(list.owner) + }; + }).toArray(); + res.json({ + lists, + msg: "OK" + }); + } + async postLists(req, res) { + const user = await this.checkUser(req); + const name = req.body.name; + if (!name) { + throw Error("HTTP400"); + } + const list = await this.list.create(name, user._id); + res.json({ + list, + msg: "OK" + }); + } + async getList(req, res) { + const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); + res.json({ + list, + msg: "OK" + }); + } + async patchList(req, res) { + const user = await this.checkUser(req); + const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); + const name = req.body.name; + if (!name) { + throw Error("HTTP400"); + } + if (!list) { + throw Error("HTTP404"); + } + if (!user._id.equals(list.owner)) { + throw Error("HTTP403"); + } + const result = await this.list.rename(list._id, name); + res.json({ + msg: "OK", + result + }); + } + async deleteList(req, res) { + const user = await this.checkUser(req); + const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + if (!user._id.equals(list.owner)) { + throw Error("HTTP403"); + } + const result = await this.list.delete(list._id); + res.json({ + msg: "OK", + result + }); + } + async getAudios(req, res) { + const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + res.json({ + audios: list.audio, + msg: "OK" + }); + } + async postAudios(req, res) { + try { + const user = await this.checkUser(req); + const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + if (!user._id.equals(list.owner)) { + throw Error("HTTP403"); + } + const audios = []; + await Promise.all(req.files.map(async (file) => { + const path = file.path; + const audio = await this.processFile(path, user); + if (audio) { + await this.list.addAudio(list._id, audio._id); + audios.push(audio); + } + else { + } + })); + res.json({ + audios, + msg: "OK" + }); + } + finally { + await Promise.all(req.files.map(async (file) => await fs_1.promises.unlink(file.path))); + } + } + async getAudio(req, res) { + const audio = await this.audio.get(new mongodb_1.ObjectID(req.params.aid)); + res.json({ + audio, + msg: "OK" + }); + } + async getAudioFile(req, res) { + const audio = await this.audio.get(new mongodb_1.ObjectID(req.params.aid)); + if (!audio) { + throw Error("HTTP404"); + } + const file = await this.audio.getFile(audio); + if (!file) { + throw Error("HTTP404"); + } + res.download(file); + } + async getUser(req) { + const reqbody = req.body; + if (!reqbody.tg) + return null; + const tg = (typeof reqbody.tg === "string") ? JSON.parse(reqbody.tg) : reqbody.tg; + const payload = [ + `auth_date=${tg.auth_date}`, + `first_name=${tg.first_name}`, + `id=${tg.id}`, + `username=${tg.username}` + ].join("\n"); + const hmac = crypto_1.default.createHmac("sha256", this.tgToken); + hmac.update(payload); + if (hmac.digest("hex") !== tg.hash) { + return null; + } + return this.user.get(exports.BIND_TYPE, tg.id); + } + async checkUser(req) { + const user = await this.getUser(req); + if (!user) { + throw Error("HTTP401"); + } + return user; + } + async processFile(file, sender) { + if (!file) + return null; + let audio; + try { + audio = await this.audio.add(sender._id, file); + } + catch (error) { + if (error === AudioManager_1.ERR_MISSING_TITLE) { + return null; + } + else { + return null; + } + } + return audio; + } +} +exports.Web = Web; +//# sourceMappingURL=Web.js.map \ No newline at end of file diff --git a/dist/Core/AudioManager.js b/dist/Core/AudioManager.js index 2c52552..ecb5682 100644 --- a/dist/Core/AudioManager.js +++ b/dist/Core/AudioManager.js @@ -174,3 +174,4 @@ class AudioManager { } } exports.AudioManager = AudioManager; +//# sourceMappingURL=AudioManager.js.map \ No newline at end of file diff --git a/dist/Core/ListManager.js b/dist/Core/ListManager.js index 1ad455d..304e072 100644 --- a/dist/Core/ListManager.js +++ b/dist/Core/ListManager.js @@ -80,3 +80,4 @@ class ListManager { } } exports.ListManager = ListManager; +//# sourceMappingURL=ListManager.js.map \ No newline at end of file diff --git a/dist/Core/MongoDB.js b/dist/Core/MongoDB.js index 0a1530b..63baf04 100644 --- a/dist/Core/MongoDB.js +++ b/dist/Core/MongoDB.js @@ -15,3 +15,4 @@ class MongoDB extends events_1.EventEmitter { } } exports.MongoDB = MongoDB; +//# sourceMappingURL=MongoDB.js.map \ No newline at end of file diff --git a/dist/Core/URLParser.js b/dist/Core/URLParser.js index aef6d05..7efb5af 100644 --- a/dist/Core/URLParser.js +++ b/dist/Core/URLParser.js @@ -34,3 +34,4 @@ class UrlParser { } } exports.UrlParser = UrlParser; +//# sourceMappingURL=URLParser.js.map \ No newline at end of file diff --git a/dist/Core/UserManager.js b/dist/Core/UserManager.js index 9e8f7a6..156bc5b 100644 --- a/dist/Core/UserManager.js +++ b/dist/Core/UserManager.js @@ -56,3 +56,4 @@ class UserManager { } } exports.UserManager = UserManager; +//# sourceMappingURL=UserManager.js.map \ No newline at end of file diff --git a/dist/Core/Utils/Encoder.js b/dist/Core/Utils/Encoder.js index 055719b..dc57a50 100644 --- a/dist/Core/Utils/Encoder.js +++ b/dist/Core/Utils/Encoder.js @@ -70,3 +70,4 @@ class Encoder { } } exports.Encoder = Encoder; +//# sourceMappingURL=Encoder.js.map \ No newline at end of file diff --git a/dist/Core/Utils/MediaInfo.js b/dist/Core/Utils/MediaInfo.js index 9c2d19f..117af0a 100644 --- a/dist/Core/Utils/MediaInfo.js +++ b/dist/Core/Utils/MediaInfo.js @@ -44,3 +44,4 @@ async function getMediaInfo(file) { }); } exports.getMediaInfo = getMediaInfo; +//# sourceMappingURL=MediaInfo.js.map \ No newline at end of file diff --git a/dist/Core/Utils/PromiseUtils.js b/dist/Core/Utils/PromiseUtils.js index ebca773..ceb61f4 100644 --- a/dist/Core/Utils/PromiseUtils.js +++ b/dist/Core/Utils/PromiseUtils.js @@ -21,3 +21,4 @@ function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); } exports.sleep = sleep; +//# sourceMappingURL=PromiseUtils.js.map \ No newline at end of file diff --git a/dist/Core/Utils/URLHandler/Youtube.js b/dist/Core/Utils/URLHandler/Youtube.js index 36261ca..e0e9df5 100644 --- a/dist/Core/Utils/URLHandler/Youtube.js +++ b/dist/Core/Utils/URLHandler/Youtube.js @@ -31,3 +31,4 @@ class Youtube { } } exports.Youtube = Youtube; +//# sourceMappingURL=Youtube.js.map \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 8090a77..efb59d3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5,6 +5,7 @@ const fs_1 = require("fs"); const path_1 = require("path"); const Discord_1 = require("./Component/Discord"); const Telegram_1 = require("./Component/Telegram"); +const Web_1 = require("./Component/Web"); const AudioManager_1 = require("./Core/AudioManager"); const ListManager_1 = require("./Core/ListManager"); const MongoDB_1 = require("./Core/MongoDB"); @@ -34,6 +35,12 @@ class Core extends events_1.EventEmitter { catch (error) { console.error(error); } + try { + new Web_1.Web(this); + } + catch (error) { + console.error(error); + } if (process.argv.indexOf("--deep-check") !== -1) { await this.audioManager.checkCache(true); this.listManager.checkAudioExist(); @@ -46,3 +53,4 @@ class Core extends events_1.EventEmitter { } exports.Core = Core; new Core(); +//# sourceMappingURL=index.js.map \ No newline at end of file From c0cb589ae9d5fcc1948bf22f089d1116afca7017 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 11:58:30 +0800 Subject: [PATCH 04/16] add list delAudio api --- Component/Web.ts | 27 ++++++++++++++++++++++----- dist/Component/Web.js | 25 ++++++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index 9f1d4c5..719475b 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -95,8 +95,9 @@ export class Web { this.server.get("/list/:lid", this.route(this.getList)); this.server.patch("/list/:lid", this.route(this.patchList)); this.server.delete("/list/:lid", this.route(this.deleteList)); - this.server.get("/list/:lid/audios", this.route(this.getAudios)); - this.server.post("/list/:lid/audios", upload.array("audio"), this.route(this.postAudios)); + this.server.get("/list/:lid/audios", this.route(this.getListAudios)); + this.server.post("/list/:lid/audios", upload.array("audio"), this.route(this.postListAudios)); + this.server.delete("/list/:lid/audio/:aid", this.route(this.deleteListAudio)); this.server.get("/audio/:aid", this.route(this.getAudio)); this.server.get("/audio/:aid/file", this.route(this.getAudioFile)); } @@ -181,7 +182,7 @@ export class Web { }); } - private async getAudios(req: Request, res: Response) { + private async getListAudios(req: Request, res: Response) { const list = await this.list.get(new ObjectID(req.params.lid)); if (!list) { throw Error("HTTP404"); @@ -192,7 +193,7 @@ export class Web { }); } - private async postAudios(req: Request, res: Response) { + private async postListAudios(req: Request, res: Response) { try { const user = await this.checkUser(req); const list = await this.list.get(new ObjectID(req.params.lid)); @@ -222,6 +223,22 @@ export class Web { } } + private async deleteListAudio(req: Request, res: Response) { + const user = await this.checkUser(req); + const list = await this.list.get(new ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + if (!user._id!.equals(list.owner)) { + throw Error("HTTP403"); + } + const result = await this.list.delAudio(list._id, new ObjectID(req.params.aid)); + res.json({ + msg: "OK", + result + }); + } + private async getAudio(req: Request, res: Response) { const audio = await this.audio.get(new ObjectID(req.params.aid)); res.json({ @@ -255,7 +272,7 @@ export class Web { const hmac = crypto.createHmac("sha256", this.tgToken); hmac.update(payload); if (hmac.digest("hex") !== tg.hash) { - return null; + // return null; } return this.user.get(BIND_TYPE, tg.id); } diff --git a/dist/Component/Web.js b/dist/Component/Web.js index cccc572..7f6502a 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -77,8 +77,9 @@ class Web { this.server.get("/list/:lid", this.route(this.getList)); this.server.patch("/list/:lid", this.route(this.patchList)); this.server.delete("/list/:lid", this.route(this.deleteList)); - this.server.get("/list/:lid/audios", this.route(this.getAudios)); - this.server.post("/list/:lid/audios", upload.array("audio"), this.route(this.postAudios)); + this.server.get("/list/:lid/audios", this.route(this.getListAudios)); + this.server.post("/list/:lid/audios", upload.array("audio"), this.route(this.postListAudios)); + this.server.delete("/list/:lid/audio/:aid", this.route(this.deleteListAudio)); this.server.get("/audio/:aid", this.route(this.getAudio)); this.server.get("/audio/:aid/file", this.route(this.getAudioFile)); } @@ -156,7 +157,7 @@ class Web { result }); } - async getAudios(req, res) { + async getListAudios(req, res) { const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); if (!list) { throw Error("HTTP404"); @@ -166,7 +167,7 @@ class Web { msg: "OK" }); } - async postAudios(req, res) { + async postListAudios(req, res) { try { const user = await this.checkUser(req); const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); @@ -196,6 +197,21 @@ class Web { await Promise.all(req.files.map(async (file) => await fs_1.promises.unlink(file.path))); } } + async deleteListAudio(req, res) { + const user = await this.checkUser(req); + const list = await this.list.get(new mongodb_1.ObjectID(req.params.lid)); + if (!list) { + throw Error("HTTP404"); + } + if (!user._id.equals(list.owner)) { + throw Error("HTTP403"); + } + const result = await this.list.delAudio(list._id, new mongodb_1.ObjectID(req.params.aid)); + res.json({ + msg: "OK", + result + }); + } async getAudio(req, res) { const audio = await this.audio.get(new mongodb_1.ObjectID(req.params.aid)); res.json({ @@ -228,7 +244,6 @@ class Web { const hmac = crypto_1.default.createHmac("sha256", this.tgToken); hmac.update(payload); if (hmac.digest("hex") !== tg.hash) { - return null; } return this.user.get(exports.BIND_TYPE, tg.id); } From fb986686353b1cc4ead0e8cc0b2cdcf0e1d58b3d Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 20:40:16 +0800 Subject: [PATCH 05/16] get auth token from header instead --- Component/Web.ts | 6 +++--- dist/Component/Web.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index 719475b..a20fbe6 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -260,9 +260,9 @@ export class Web { } private async getUser(req: Request) { - const reqbody = (req as any).body; - if (!reqbody.tg) return null; - const tg = (typeof reqbody.tg === "string") ? JSON.parse(reqbody.tg) : reqbody.tg; + const tgStr = req.get("X-Auth"); + if (!tgStr) return null; + const tg = JSON.parse(tgStr); const payload = [ `auth_date=${tg.auth_date}`, `first_name=${tg.first_name}`, diff --git a/dist/Component/Web.js b/dist/Component/Web.js index 7f6502a..f23ed85 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -231,10 +231,10 @@ class Web { res.download(file); } async getUser(req) { - const reqbody = req.body; - if (!reqbody.tg) + const tgStr = req.get("X-Auth"); + if (!tgStr) return null; - const tg = (typeof reqbody.tg === "string") ? JSON.parse(reqbody.tg) : reqbody.tg; + const tg = JSON.parse(tgStr); const payload = [ `auth_date=${tg.auth_date}`, `first_name=${tg.first_name}`, From 46ff8a560fc260f5c5806cd46ba55518b449c044 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 21:07:06 +0800 Subject: [PATCH 06/16] arrange owned lists first --- Component/Web.ts | 11 ++++++++++- dist/Component/Web.js | 12 +++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index a20fbe6..8235a99 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -119,8 +119,17 @@ export class Web { own: !!user && user._id!.equals(list.owner) }; }).toArray(); + const own: any[] = []; + const other: any[] = []; + lists.forEach(list => { + if (list.own) { + own.push(list); + } else { + other.push(list); + } + }); res.json({ - lists, + lists: [...own, ...other], msg: "OK" }); } diff --git a/dist/Component/Web.js b/dist/Component/Web.js index f23ed85..2c19655 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -99,8 +99,18 @@ class Web { own: !!user && user._id.equals(list.owner) }; }).toArray(); + const own = []; + const other = []; + lists.forEach(list => { + if (list.own) { + own.push(list); + } + else { + other.push(list); + } + }); res.json({ - lists, + lists: [...own, ...other], msg: "OK" }); } From 422a20689d43c375a7d59711e94c9587f6fdaf41 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 21:34:34 +0800 Subject: [PATCH 07/16] list audios with queried audio data --- Component/Web.ts | 7 ++++++- dist/Component/Web.js | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index 8235a99..815ff74 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -196,8 +196,13 @@ export class Web { if (!list) { throw Error("HTTP404"); } + const audios = await this.audio.search({ + _id: { + $in: list.audio.map(id => new ObjectID(id)) + } + }).toArray(); res.json({ - audios: list.audio, + audios, msg: "OK" }); } diff --git a/dist/Component/Web.js b/dist/Component/Web.js index 2c19655..e03200c 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -172,8 +172,13 @@ class Web { if (!list) { throw Error("HTTP404"); } + const audios = await this.audio.search({ + _id: { + $in: list.audio.map(id => new mongodb_1.ObjectID(id)) + } + }).toArray(); res.json({ - audios: list.audio, + audios, msg: "OK" }); } From fd5709578e1928a8f2b62a8cfbf19c8334b1de6c Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 15 Jun 2019 23:01:54 +0800 Subject: [PATCH 08/16] fix tg digest check --- Component/Web.ts | 21 ++++++++++----------- dist/Component/Web.js | 18 +++++++++--------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index 815ff74..deb06e4 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -23,7 +23,7 @@ export class Web { private audio: AudioManager; private user: UserManager; private list: ListManager; - private tgToken: string; + private digest: string; private upload: string; private server: Application; @@ -34,7 +34,7 @@ export class Web { this.audio = core.audioManager; this.list = core.listManager; - this.tgToken = core.config.telegram.token; + this.digest = crypto.createHash("sha256").update(core.config.telegram.token).digest(); this.upload = core.config.web.upload; // Create Server @@ -277,16 +277,15 @@ export class Web { const tgStr = req.get("X-Auth"); if (!tgStr) return null; const tg = JSON.parse(tgStr); - const payload = [ - `auth_date=${tg.auth_date}`, - `first_name=${tg.first_name}`, - `id=${tg.id}`, - `username=${tg.username}` - ].join("\n"); - const hmac = crypto.createHmac("sha256", this.tgToken); + const hash = tg.hash; + delete tg.hash; + const payload = Object.keys(tg).sort().map(key => { + return `${key}=${tg[key]}`; + }).join("\n"); + const hmac = crypto.createHmac("sha256", this.digest); hmac.update(payload); - if (hmac.digest("hex") !== tg.hash) { - // return null; + if (hmac.digest("hex") !== hash) { + return null; } return this.user.get(BIND_TYPE, tg.id); } diff --git a/dist/Component/Web.js b/dist/Component/Web.js index e03200c..8b3e709 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -22,7 +22,7 @@ class Web { this.user = core.userManager; this.audio = core.audioManager; this.list = core.listManager; - this.tgToken = core.config.telegram.token; + this.digest = crypto_1.default.createHash("sha256").update(core.config.telegram.token).digest(); this.upload = core.config.web.upload; this.server = express_1.default(); this.middlewares(); @@ -250,15 +250,15 @@ class Web { if (!tgStr) return null; const tg = JSON.parse(tgStr); - const payload = [ - `auth_date=${tg.auth_date}`, - `first_name=${tg.first_name}`, - `id=${tg.id}`, - `username=${tg.username}` - ].join("\n"); - const hmac = crypto_1.default.createHmac("sha256", this.tgToken); + const hash = tg.hash; + delete tg.hash; + const payload = Object.keys(tg).sort().map(key => { + return `${key}=${tg[key]}`; + }).join("\n"); + const hmac = crypto_1.default.createHmac("sha256", this.digest); hmac.update(payload); - if (hmac.digest("hex") !== tg.hash) { + if (hmac.digest("hex") !== hash) { + return null; } return this.user.get(exports.BIND_TYPE, tg.id); } From 1e1adf62a372be8f5e106405c38cd48629fcf121 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sun, 16 Jun 2019 02:45:07 +0800 Subject: [PATCH 09/16] upload audio with uri --- Component/Web.ts | 30 +++++++++++++++++++++++++----- dist/Component/Web.js | 27 ++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index deb06e4..0364476 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -5,7 +5,7 @@ import { promises as fsp } from "fs"; import { ObjectID } from "mongodb"; import multer from "multer"; import { Core } from ".."; -import { AudioManager, ERR_MISSING_TITLE, IAudioData } from "../Core/AudioManager"; +import { AudioManager, ERR_MISSING_TITLE, ERR_NOT_AUDIO, IAudioData } from "../Core/AudioManager"; import { IAudioList, ListManager } from "../Core/ListManager"; import { IUserData, UserManager } from "../Core/UserManager"; // import { retry, sleep } from "../Core/Utils/PromiseUtils"; @@ -23,7 +23,7 @@ export class Web { private audio: AudioManager; private user: UserManager; private list: ListManager; - private digest: string; + private digest: Buffer; private upload: string; private server: Application; @@ -218,8 +218,23 @@ export class Web { throw Error("HTTP403"); } const audios: IAudioData[] = []; - await Promise.all((req as any).files.map(async (file: any) => { - const path = file.path; + const paths: string[] = []; + if ((req as any).files) { + (req as any).files.forEach((file: any) => { + paths.push(file.path); + }); + } + if ((req as any).body) { + const body = (req as any).body; + if (body.uris) { + if (typeof body.uris === "string") { + paths.push(...JSON.parse(body.uris)); + } else { + paths.push(...body.uris); + } + } + } + await Promise.all(paths.map(async (path) => { const audio = await this.processFile(path, user); if (audio) { await this.list.addAudio(list._id!, audio._id!); @@ -233,7 +248,9 @@ export class Web { msg: "OK" }); } finally { - await Promise.all((req as any).files.map(async (file: any) => await fsp.unlink(file.path))); + if ((req as any).files) { + await Promise.all((req as any).files.map(async (file: any) => await fsp.unlink(file.path))); + } } } @@ -308,6 +325,9 @@ export class Web { if (error === ERR_MISSING_TITLE) { // show error ? return null; + } else if (error === ERR_NOT_AUDIO) { + // not audio file + return null; } else { // unkown dead return null; diff --git a/dist/Component/Web.js b/dist/Component/Web.js index 8b3e709..a84f638 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -193,8 +193,24 @@ class Web { throw Error("HTTP403"); } const audios = []; - await Promise.all(req.files.map(async (file) => { - const path = file.path; + const paths = []; + if (req.files) { + req.files.forEach((file) => { + paths.push(file.path); + }); + } + if (req.body) { + const body = req.body; + if (body.uris) { + if (typeof body.uris === "string") { + paths.push(...JSON.parse(body.uris)); + } + else { + paths.push(...body.uris); + } + } + } + await Promise.all(paths.map(async (path) => { const audio = await this.processFile(path, user); if (audio) { await this.list.addAudio(list._id, audio._id); @@ -209,7 +225,9 @@ class Web { }); } finally { - await Promise.all(req.files.map(async (file) => await fs_1.promises.unlink(file.path))); + if (req.files) { + await Promise.all(req.files.map(async (file) => await fs_1.promises.unlink(file.path))); + } } } async deleteListAudio(req, res) { @@ -280,6 +298,9 @@ class Web { if (error === AudioManager_1.ERR_MISSING_TITLE) { return null; } + else if (error === AudioManager_1.ERR_NOT_AUDIO) { + return null; + } else { return null; } From 5d4d2f88c6cdea6eab97cd035386e682a3a42e59 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sun, 16 Jun 2019 05:09:59 +0800 Subject: [PATCH 10/16] add front-end --- Component/Web.ts | 2 +- dist/web/main.js | 369 +++++++++++++++++++++++++++++++++++++++++++++++ web/index.html | 132 +++++++++++++++++ web/main.js | 363 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 865 insertions(+), 1 deletion(-) create mode 100644 dist/web/main.js create mode 100644 web/index.html create mode 100644 web/main.js diff --git a/Component/Web.ts b/Component/Web.ts index 0364476..dffbbcc 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -234,7 +234,7 @@ export class Web { } } } - await Promise.all(paths.map(async (path) => { + await Promise.all(paths.map(async path => { const audio = await this.processFile(path, user); if (audio) { await this.list.addAudio(list._id!, audio._id!); diff --git a/dist/web/main.js b/dist/web/main.js new file mode 100644 index 0000000..76c8843 --- /dev/null +++ b/dist/web/main.js @@ -0,0 +1,369 @@ +"use strict"; +const config = { + base: `${location.pathname}api/`, + tgBotName: "test_oktw_musicbot_bot", + urlRegex: /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i, +}; +Vue.use(VuetifyUploadButton); +const app = new Vue({ + el: "#app", + components: { + vueTelegramLogin, + }, + data() { + return { + tg: {}, + isMobile: false, + drop: { + in: false, + overlay: false, + timer: 0, + }, + upload: { + uploading: false, + total: 1, + done: 0, + current: "", + encoding: false, + file: true, + single: false, + }, + showDrawer: false, + itemActing: {}, + itemDelete: { + act: "", + cb: null, + dialog: false, + }, + listModify: { + act: "", + dialog: false, + name: "", + nameRules: [ + v => !!v || "請輸入名稱", + v => { + if (this.listModify.act === "edit") { + return this.itemActing.name !== v || "請修改名稱"; + } + else { + return true; + } + }, + ], + valid: false, + }, + lists: [], + listSelected: 0, + listsHeaders: [ + { + align: "left", + sortable: false, + text: "清單名稱", + value: "name", + }, + { + align: "center", + sortable: false, + text: "操作", + value: "act", + width: "72px", + }, + ], + audios: [], + audioSelected: 0, + audiosHeaders: [ + { + text: "歌名", + value: "title", + width: "50%", + }, + { + text: "歌手", + value: "artist", + width: "40%", + }, + { + text: "長度", + value: "duration", + width: "5%", + }, + { + text: "操作", + value: "act", + width: "5%", + }, + ], + }; + }, + created() { + this.isMobile = window.innerWidth < 769 ? true : false; + if (!this.isMobile) { + this.showDrawer = true; + } + this.refresh(); + }, + computed: { + tgBotName() { + return config.tgBotName; + }, + loggedIn() { + return Object.keys(this.tg).length > 0; + }, + uploadTotalProgress() { + const base = this.upload.done * 100 / this.upload.total; + const seg = 100.0 / this.upload.total; + let add = 0; + if (this.upload.file && this.upload.encoding) { + add = seg; + } + else if (this.upload.single) { + add = seg * this.upload.single / 100; + } + return base + add; + }, + list() { + const list = this.lists[this.listSelected]; + return list || {}; + }, + }, + watch: { + list() { + this.getAudios(); + }, + lists(newVal, oldVal) { + const oldList = oldVal[this.listSelected]; + if (!oldList) { + return; + } + let newIndex = 0; + this.lists.forEach((list, idx) => { + if (list.id === oldList.id) { + newIndex = idx; + } + }); + this.listSelected = newIndex; + }, + }, + methods: { + onAuth(tg) { + this.tg = tg; + this.api("get", "login").then(json => { + this.refresh(); + console.log(json); + }, json => { + this.tg = {}; + }); + }, + onDragOver(evt) { + if (this.loggedIn && !this.upload.uploading) { + clearTimeout(this.drop.timer); + this.drop.timer = setTimeout(() => { + this.drop.in = false; + clearTimeout(this.drop.timer); + this.drop.overlay = false; + }, 100); + if (!this.drop.in) { + this.drop.in = true; + this.drop.overlay = true; + } + } + }, + onDrop(evt) { + if (this.loggedIn && !this.upload.uploading) { + this.audioAdd(evt.dataTransfer.items || evt.dataTransfer.files); + } + }, + onPaste(evt) { + if (this.loggedIn && !this.upload.uploading) { + this.audioAdd(evt.clipboardData.items); + } + }, + refresh() { + this.getLists(); + }, + api(act, path, body, uploadProgress) { + const ep = `${config.base}${path}`; + const conf = { + headers: { + "X-Auth": JSON.stringify(this.tg), + }, + uploadProgress, + }; + const params = body ? [ep, body, conf] : [ep, conf]; + return new Promise((resolve, reject) => { + const handle = res => { + res.json().then(resolve, reject); + }; + this.$http[act](...params).then(handle, handle); + }); + }, + itemDeleteCall() { + if (this.itemDelete.cb) { + this.itemDelete.cb(); + } + }, + getLists() { + this.api("get", "lists").then(json => { + this.lists = json.lists.map((list, idx) => { + list.index = idx; + return list; + }); + }); + }, + listDeleteConfirm(list) { + this.itemActing = list; + this.itemDelete.dialog = true; + this.itemDelete.cb = this.listDeleteCall.bind(this); + }, + listDeleteCall() { + this.api("delete", `list/${this.itemActing.id}`).then(json => { + this.itemDelete.dialog = false; + this.getLists(); + console.log(json); + }); + }, + listNewInput() { + this.listModify.act = "new"; + this.listModify.name = ""; + this.listModify.dialog = true; + }, + listEditInput(list) { + this.itemActing = list; + this.listModify.act = "edit"; + this.listModify.name = list.name; + this.listModify.dialog = true; + }, + listModifyCall() { + if (this.$refs.listModify.validate()) { + switch (this.listModify.act) { + case "new": + this.api("post", "lists", { + name: this.listModify.name, + }).then(json => { + this.listModify.dialog = false; + this.getLists(); + console.log(json); + }); + break; + case "edit": + this.api("patch", `list/${this.itemActing.id}`, { + name: this.listModify.name, + }).then(json => { + this.listModify.dialog = false; + this.getLists(); + console.log(json); + }); + break; + } + } + }, + getAudios() { + this.api("get", `list/${this.list.id}/audios`).then(json => { + this.audios = json.audios.map((audio, idx) => { + audio.index = idx; + return audio; + }); + }); + }, + audioDeleteConfirm(audio) { + this.itemActing = audio; + this.itemDelete.dialog = true; + this.itemDelete.cb = this.audioDeleteCall.bind(this); + }, + audioDeleteCall() { + this.api("delete", `list/${this.list.id}/audio/${this.itemActing._id}`).then(json => { + this.itemDelete.dialog = false; + this.getAudios(); + console.log(json); + }); + }, + async audioAdd(files) { + this.upload.uploading = true; + const arr = []; + await Promise.all(Array.from(files).map(ele => { + if (ele instanceof DataTransferItem) { + if (ele.kind === "file") { + arr.push(ele.getAsFile()); + } + else if (ele.kind === "string") { + if (/^text\/uri-list/.exec(ele.type) || /^text\/plain/.exec(ele.type)) { + return new Promise((resolve, reject) => { + ele.getAsString((str) => { + str.split("\n").forEach(l => { + if (config.urlRegex.exec(l)) { + try { + const url = new URL(l); + arr.push(url.href); + return; + } + catch (e) { + } + } + }); + resolve(); + }); + }); + } + } + } + else if (ele instanceof File) { + arr.push(ele); + } + })); + this.upload.total = arr.length; + const consume = result => new Promise((resolve, reject) => { + const ele = arr.pop(); + if (ele) { + let data; + if (ele instanceof File) { + data = new FormData(); + data.append("audio", ele); + this.upload.current = ele.name; + this.upload.file = true; + } + else { + data = { + uris: [ele], + }; + this.upload.current = ele; + this.upload.file = false; + } + this.upload.done = this.upload.total - arr.length - 1; + const handle = json => { + this.upload.encoding = false; + result.push({ + ele, + json, + }); + resolve(consume(result)); + }; + const upload = evt => { + if (evt.lengthComputable) { + const progress = evt.loaded * 100 / evt.total; + if (progress > 99) { + this.upload.single = false; + this.upload.encoding = true; + } + else { + this.upload.single = progress; + } + } + else { + this.upload.single = false; + } + }; + this.api("post", `list/${this.list.id}/audios`, data, upload).then(handle, handle); + } + else { + resolve(result); + } + }); + consume([]).then(result => { + this.upload.uploading = false; + console.log(result); + this.getAudios(); + }); + }, + }, +}); +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..868f13f --- /dev/null +++ b/web/index.html @@ -0,0 +1,132 @@ + + + + + + + OKTW MusicBot Web + + + + + + + + + + +
+ + + 將檔案拖曳至此處以上傳至歌單 + + + + 上傳中 + + 已完成 {{ upload.done }} / 總共 {{ upload.total }} 個檔案 + + 正在{{ upload.encoding ? "處理" : "上傳"}} {{ upload.current }} + + + + + + + OKTW MusicBot + + + + + + 播放清單 + + + add + + + + + + + + + 新增播放清單 + + + + + + + + 取消 + 確定 + + + + + + 確認刪除 + 確定要刪除{{ itemActing.name || itemActing.title }}嗎? + + + 取消 + 刪除 + + + + + + {{ list.name }} + + + + + + + + + + +
+ + + diff --git a/web/main.js b/web/main.js new file mode 100644 index 0000000..939ca14 --- /dev/null +++ b/web/main.js @@ -0,0 +1,363 @@ +const config = { + base: `${location.pathname}api/`, + tgBotName: "test_oktw_musicbot_bot", + // https://gist.github.com/dperini/729294 + urlRegex: /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i, +}; + +Vue.use(VuetifyUploadButton); + +const app = new Vue({ + el: "#app", + components: { + vueTelegramLogin, + }, + data() { + return { + tg: {}, + isMobile: false, + drop: { + in: false, + overlay: false, + timer: 0, + }, + upload: { + uploading: false, + total: 1, + done: 0, + current: "", + encoding: false, + file: true, + single: false, + }, + showDrawer: false, + itemActing: {}, + itemDelete: { + act: "", + cb: null, + dialog: false, + }, + listModify: { + act: "", + dialog: false, + name: "", + nameRules: [ + v => !!v || "請輸入名稱", + v => { + if (this.listModify.act === "edit") { + return this.itemActing.name !== v || "請修改名稱"; + } else { + return true; + } + }, + ], + valid: false, + }, + lists: [], + listSelected: 0, + listsHeaders: [ + { + align: "left", + sortable: false, + text: "清單名稱", + value: "name", + }, + { + align: "center", + sortable: false, + text: "操作", + value: "act", + width: "72px", + }, + ], + audios: [], + audioSelected: 0, + audiosHeaders: [ + { + text: "歌名", + value: "title", + width: "50%", + }, + { + text: "歌手", + value: "artist", + width: "40%", + }, + { + text: "長度", + value: "duration", + width: "5%", + }, + { + text: "操作", + value: "act", + width: "5%", + }, + ], + }; + }, + created() { + this.isMobile = window.innerWidth < 769 ? true : false; + if (!this.isMobile) { this.showDrawer = true; } + this.refresh(); + }, + computed: { + tgBotName() { + return config.tgBotName; + }, + loggedIn() { + return Object.keys(this.tg).length > 0; + }, + uploadTotalProgress() { + const base = this.upload.done * 100 / this.upload.total; + const seg = 100.0 / this.upload.total; + let add = 0; + if (this.upload.file && this.upload.encoding) { + add = seg; + } else if (this.upload.single) { + add = seg * this.upload.single / 100; + } + return base + add; + }, + list() { + const list = this.lists[this.listSelected]; + return list || {}; + }, + }, + watch: { + list() { + this.getAudios(); + }, + lists(newVal, oldVal) { + const oldList = oldVal[this.listSelected]; + if (!oldList) { return; } + let newIndex = 0; + this.lists.forEach((list, idx) => { + if (list.id === oldList.id) { + newIndex = idx; + } + }); + this.listSelected = newIndex; + }, + }, + methods: { + onAuth(tg) { + this.tg = tg; // X-Auth header for login api request + this.api("get", "login").then(json => { + this.refresh(); + console.log(json); + }, json => { + // chear tg object if login failed + this.tg = {}; + }); + }, + onDragOver(evt) { + if (this.loggedIn && !this.upload.uploading) { + clearTimeout(this.drop.timer); + this.drop.timer = setTimeout(() => { + this.drop.in = false; + clearTimeout(this.drop.timer); + + this.drop.overlay = false; + }, 100); + + if (!this.drop.in) { + this.drop.in = true; + + this.drop.overlay = true; + } + } + }, + onDrop(evt) { + if (this.loggedIn && !this.upload.uploading) { + this.audioAdd(evt.dataTransfer.items || evt.dataTransfer.files); + } + }, + onPaste(evt) { + if (this.loggedIn && !this.upload.uploading) { + this.audioAdd(evt.clipboardData.items); + } + }, + refresh() { + this.getLists(); + }, + api(act, path, body, uploadProgress) { + const ep = `${config.base}${path}`; + const conf = { + headers: { + "X-Auth": JSON.stringify(this.tg), + }, + uploadProgress, + }; + const params = body ? [ep, body, conf] : [ep, conf]; + return new Promise((resolve, reject) => { + const handle = res => { + res.json().then(resolve, reject); + }; + this.$http[act](...params).then(handle, handle); + }); + }, + itemDeleteCall() { + if (this.itemDelete.cb) { + this.itemDelete.cb(); + } + }, + getLists() { + this.api("get", "lists").then(json => { + this.lists = json.lists.map((list, idx) => { + list.index = idx; + return list; + }); + }); + }, + listDeleteConfirm(list) { + this.itemActing = list; + this.itemDelete.dialog = true; + this.itemDelete.cb = this.listDeleteCall.bind(this); + }, + listDeleteCall() { + this.api("delete", `list/${this.itemActing.id}`).then(json => { + this.itemDelete.dialog = false; + this.getLists(); + console.log(json); + }); + }, + listNewInput() { + this.listModify.act = "new"; + this.listModify.name = ""; + this.listModify.dialog = true; + }, + listEditInput(list) { + this.itemActing = list; + this.listModify.act = "edit"; + this.listModify.name = list.name; + this.listModify.dialog = true; + }, + listModifyCall() { + if (this.$refs.listModify.validate()) { + switch (this.listModify.act) { + case "new": + this.api("post", "lists", { + name: this.listModify.name, + }).then(json => { + this.listModify.dialog = false; + this.getLists(); + console.log(json); + }); + break; + case "edit": + this.api("patch", `list/${this.itemActing.id}`, { + name: this.listModify.name, + }).then(json => { + this.listModify.dialog = false; + this.getLists(); + console.log(json); + }); + break; + } + } + }, + getAudios() { + this.api("get", `list/${this.list.id}/audios`).then(json => { + this.audios = json.audios.map((audio, idx) => { + audio.index = idx; + return audio; + }); + }); + }, + audioDeleteConfirm(audio) { + this.itemActing = audio; + this.itemDelete.dialog = true; + this.itemDelete.cb = this.audioDeleteCall.bind(this); + }, + audioDeleteCall() { + this.api("delete", `list/${this.list.id}/audio/${this.itemActing._id}`).then(json => { + this.itemDelete.dialog = false; + this.getAudios(); + console.log(json); + }); + }, + async audioAdd(files) { + this.upload.uploading = true; + const arr = []; + await Promise.all(Array.from(files).map(ele => { + if (ele instanceof DataTransferItem) { + if (ele.kind === "file") { + arr.push(ele.getAsFile()); + } else if (ele.kind === "string") { + if (/^text\/uri-list/.exec(ele.type) || /^text\/plain/.exec(ele.type)) { + return new Promise((resolve, reject) => { + ele.getAsString((str) => { + // try parse uri + str.split("\n").forEach(l => { + if (config.urlRegex.exec(l)) { + try { + const url = new URL(l); + arr.push(url.href); + return; + } catch (e) { + } + } + }); + resolve(); + }); + }); + } + } + } else if (ele instanceof File) { + arr.push(ele); + } + })); + this.upload.total = arr.length; + const consume = result => new Promise((resolve, reject) => { + const ele = arr.pop(); + if (ele) { + let data; + if (ele instanceof File) { + data = new FormData(); + data.append("audio", ele); + this.upload.current = ele.name; + this.upload.file = true; + } else { + data = { + uris: [ele], + }; + this.upload.current = ele; + this.upload.file = false; + } + // this must assign after file judgement + this.upload.done = this.upload.total - arr.length - 1; + const handle = json => { + this.upload.encoding = false; + result.push({ + ele, + json, + }); + resolve(consume(result)); + }; + const upload = evt => { + if (evt.lengthComputable) { + const progress = evt.loaded * 100 / evt.total; + if (progress > 99) { + this.upload.single = false; + this.upload.encoding = true; + } else { + this.upload.single = progress; + } + } else { + this.upload.single = false; + } + }; + this.api("post", `list/${this.list.id}/audios`, data, upload).then(handle, handle); + } else { + resolve(result); + } + }); + consume([]).then(result => { + this.upload.uploading = false; + console.log(result); + this.getAudios(); + }); + }, + }, +}); From 5d9cf2e0a8d6c7dfae5e927e2242250b87d0392f Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sun, 16 Jun 2019 05:25:56 +0800 Subject: [PATCH 11/16] use vue prod js instead --- web/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/index.html b/web/index.html index 868f13f..dc985ea 100644 --- a/web/index.html +++ b/web/index.html @@ -5,7 +5,7 @@ OKTW MusicBot Web - + From a56eabbbc943b061ab91f8c80562da5a569e896b Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sun, 16 Jun 2019 16:54:21 +0800 Subject: [PATCH 12/16] build with prod instead --- dist/Component/Discord.js | 1 - dist/Component/Telegram.js | 1 - dist/Component/Web.js | 1 - dist/Core/AudioManager.js | 1 - dist/Core/ListManager.js | 1 - dist/Core/MongoDB.js | 1 - dist/Core/URLParser.js | 1 - dist/Core/UserManager.js | 1 - dist/Core/Utils/Encoder.js | 1 - dist/Core/Utils/MediaInfo.js | 1 - dist/Core/Utils/PromiseUtils.js | 1 - dist/Core/Utils/URLHandler/Youtube.js | 1 - dist/index.js | 1 - dist/web/main.js | 1 - 14 files changed, 14 deletions(-) diff --git a/dist/Component/Discord.js b/dist/Component/Discord.js index bfafa0d..c24160c 100644 --- a/dist/Component/Discord.js +++ b/dist/Component/Discord.js @@ -225,4 +225,3 @@ class Discord { } } exports.Discord = Discord; -//# sourceMappingURL=Discord.js.map \ No newline at end of file diff --git a/dist/Component/Telegram.js b/dist/Component/Telegram.js index 895b36a..846ba95 100644 --- a/dist/Component/Telegram.js +++ b/dist/Component/Telegram.js @@ -672,4 +672,3 @@ class Telegram { } } exports.Telegram = Telegram; -//# sourceMappingURL=Telegram.js.map \ No newline at end of file diff --git a/dist/Component/Web.js b/dist/Component/Web.js index a84f638..9913f15 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -309,4 +309,3 @@ class Web { } } exports.Web = Web; -//# sourceMappingURL=Web.js.map \ No newline at end of file diff --git a/dist/Core/AudioManager.js b/dist/Core/AudioManager.js index ecb5682..2c52552 100644 --- a/dist/Core/AudioManager.js +++ b/dist/Core/AudioManager.js @@ -174,4 +174,3 @@ class AudioManager { } } exports.AudioManager = AudioManager; -//# sourceMappingURL=AudioManager.js.map \ No newline at end of file diff --git a/dist/Core/ListManager.js b/dist/Core/ListManager.js index 304e072..1ad455d 100644 --- a/dist/Core/ListManager.js +++ b/dist/Core/ListManager.js @@ -80,4 +80,3 @@ class ListManager { } } exports.ListManager = ListManager; -//# sourceMappingURL=ListManager.js.map \ No newline at end of file diff --git a/dist/Core/MongoDB.js b/dist/Core/MongoDB.js index 63baf04..0a1530b 100644 --- a/dist/Core/MongoDB.js +++ b/dist/Core/MongoDB.js @@ -15,4 +15,3 @@ class MongoDB extends events_1.EventEmitter { } } exports.MongoDB = MongoDB; -//# sourceMappingURL=MongoDB.js.map \ No newline at end of file diff --git a/dist/Core/URLParser.js b/dist/Core/URLParser.js index 7efb5af..aef6d05 100644 --- a/dist/Core/URLParser.js +++ b/dist/Core/URLParser.js @@ -34,4 +34,3 @@ class UrlParser { } } exports.UrlParser = UrlParser; -//# sourceMappingURL=URLParser.js.map \ No newline at end of file diff --git a/dist/Core/UserManager.js b/dist/Core/UserManager.js index 156bc5b..9e8f7a6 100644 --- a/dist/Core/UserManager.js +++ b/dist/Core/UserManager.js @@ -56,4 +56,3 @@ class UserManager { } } exports.UserManager = UserManager; -//# sourceMappingURL=UserManager.js.map \ No newline at end of file diff --git a/dist/Core/Utils/Encoder.js b/dist/Core/Utils/Encoder.js index dc57a50..055719b 100644 --- a/dist/Core/Utils/Encoder.js +++ b/dist/Core/Utils/Encoder.js @@ -70,4 +70,3 @@ class Encoder { } } exports.Encoder = Encoder; -//# sourceMappingURL=Encoder.js.map \ No newline at end of file diff --git a/dist/Core/Utils/MediaInfo.js b/dist/Core/Utils/MediaInfo.js index 117af0a..9c2d19f 100644 --- a/dist/Core/Utils/MediaInfo.js +++ b/dist/Core/Utils/MediaInfo.js @@ -44,4 +44,3 @@ async function getMediaInfo(file) { }); } exports.getMediaInfo = getMediaInfo; -//# sourceMappingURL=MediaInfo.js.map \ No newline at end of file diff --git a/dist/Core/Utils/PromiseUtils.js b/dist/Core/Utils/PromiseUtils.js index ceb61f4..ebca773 100644 --- a/dist/Core/Utils/PromiseUtils.js +++ b/dist/Core/Utils/PromiseUtils.js @@ -21,4 +21,3 @@ function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); } exports.sleep = sleep; -//# sourceMappingURL=PromiseUtils.js.map \ No newline at end of file diff --git a/dist/Core/Utils/URLHandler/Youtube.js b/dist/Core/Utils/URLHandler/Youtube.js index e0e9df5..36261ca 100644 --- a/dist/Core/Utils/URLHandler/Youtube.js +++ b/dist/Core/Utils/URLHandler/Youtube.js @@ -31,4 +31,3 @@ class Youtube { } } exports.Youtube = Youtube; -//# sourceMappingURL=Youtube.js.map \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index efb59d3..dfea930 100644 --- a/dist/index.js +++ b/dist/index.js @@ -53,4 +53,3 @@ class Core extends events_1.EventEmitter { } exports.Core = Core; new Core(); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/web/main.js b/dist/web/main.js index 76c8843..de48c99 100644 --- a/dist/web/main.js +++ b/dist/web/main.js @@ -366,4 +366,3 @@ const app = new Vue({ }, }, }); -//# sourceMappingURL=main.js.map \ No newline at end of file From ac908577cba20ac62ef247439fedf2c315927038 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sun, 16 Jun 2019 17:02:49 +0800 Subject: [PATCH 13/16] fix as ts types updated --- Component/Web.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index dffbbcc..ef71d56 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -48,8 +48,7 @@ export class Web { } private async middlewares() { - // outdated... workaround - this.server.use((express as any).json()); // for parsing application/json + this.server.use(express.json()); // for parsing application/json } private async errorHandler() { From 5418d9adb4361fdbc957458e79aa0b55dabe40c2 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 3 Aug 2019 22:38:51 +0800 Subject: [PATCH 14/16] move title & bot name into config.json --- Component/Web.ts | 12 +++++++++++- config.json | 8 +++++--- dist/Component/Web.js | 8 ++++++++ dist/web/main.js | 15 +++++++++++---- web/index.html | 6 +++--- web/main.js | 16 ++++++++++++---- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/Component/Web.ts b/Component/Web.ts index ef71d56..43e43ab 100644 --- a/Component/Web.ts +++ b/Component/Web.ts @@ -25,6 +25,7 @@ export class Web { private list: ListManager; private digest: Buffer; private upload: string; + private webConfig: any; private server: Application; constructor(core: Core) { @@ -36,6 +37,10 @@ export class Web { this.digest = crypto.createHash("sha256").update(core.config.telegram.token).digest(); this.upload = core.config.web.upload; + this.webConfig = { + tgBotName: core.config.telegram.botname, + title: core.config.web.title, + }; // Create Server this.server = express(); @@ -88,6 +93,7 @@ export class Web { const upload = multer({ dest: this.upload }); this.server.get("/", (req: Request, res: Response) => res.send("MusicBot Web Server")); + this.server.get("/config", this.route(this.getConfig)); this.server.get("/login", this.route(this.getLogin)); this.server.get("/lists", this.route(this.getLists)); this.server.post("/lists", this.route(this.postLists)); @@ -101,6 +107,10 @@ export class Web { this.server.get("/audio/:aid/file", this.route(this.getAudioFile)); } + private async getConfig(req: Request, res: Response) { + res.json(this.webConfig); + } + private async getLogin(req: Request, res: Response) { const user = await this.checkUser(req); res.json({ @@ -322,7 +332,7 @@ export class Web { audio = await this.audio.add(sender._id!, file); } catch (error) { if (error === ERR_MISSING_TITLE) { - // show error ? + // TODO show error return null; } else if (error === ERR_NOT_AUDIO) { // not audio file diff --git a/config.json b/config.json index 8ccb73f..e458625 100644 --- a/config.json +++ b/config.json @@ -9,13 +9,15 @@ "name": "musicbot" }, "telegram": { - "token": "" + "token": "", + "botname": "" }, "discord": { "token": "", "owner": "self" }, "web": { - "upload": "Upload" + "upload": "Upload", + "title": "MusicBot" } -} \ No newline at end of file +} diff --git a/dist/Component/Web.js b/dist/Component/Web.js index 9913f15..860f8df 100644 --- a/dist/Component/Web.js +++ b/dist/Component/Web.js @@ -24,6 +24,10 @@ class Web { this.list = core.listManager; this.digest = crypto_1.default.createHash("sha256").update(core.config.telegram.token).digest(); this.upload = core.config.web.upload; + this.webConfig = { + tgBotName: core.config.telegram.botname, + title: core.config.web.title, + }; this.server = express_1.default(); this.middlewares(); this.registerRoutes(); @@ -71,6 +75,7 @@ class Web { async registerRoutes() { const upload = multer_1.default({ dest: this.upload }); this.server.get("/", (req, res) => res.send("MusicBot Web Server")); + this.server.get("/config", this.route(this.getConfig)); this.server.get("/login", this.route(this.getLogin)); this.server.get("/lists", this.route(this.getLists)); this.server.post("/lists", this.route(this.postLists)); @@ -83,6 +88,9 @@ class Web { this.server.get("/audio/:aid", this.route(this.getAudio)); this.server.get("/audio/:aid/file", this.route(this.getAudioFile)); } + async getConfig(req, res) { + res.json(this.webConfig); + } async getLogin(req, res) { const user = await this.checkUser(req); res.json({ diff --git a/dist/web/main.js b/dist/web/main.js index de48c99..c0ac719 100644 --- a/dist/web/main.js +++ b/dist/web/main.js @@ -1,7 +1,6 @@ "use strict"; const config = { base: `${location.pathname}api/`, - tgBotName: "test_oktw_musicbot_bot", urlRegex: /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i, }; Vue.use(VuetifyUploadButton); @@ -14,6 +13,9 @@ const app = new Vue({ return { tg: {}, isMobile: false, + configLoaded: false, + title: "", + tgBotName: "", drop: { in: false, overlay: false, @@ -100,12 +102,10 @@ const app = new Vue({ if (!this.isMobile) { this.showDrawer = true; } + this.loadConfig(); this.refresh(); }, computed: { - tgBotName() { - return config.tgBotName; - }, loggedIn() { return Object.keys(this.tg).length > 0; }, @@ -178,6 +178,13 @@ const app = new Vue({ this.audioAdd(evt.clipboardData.items); } }, + loadConfig() { + this.api("get", "config").then(json => { + this.title = json.title; + this.tgBotName = json.tgBotName; + this.configLoaded = true; + }); + }, refresh() { this.getLists(); }, diff --git a/web/index.html b/web/index.html index dc985ea..e4b78ae 100644 --- a/web/index.html +++ b/web/index.html @@ -4,7 +4,7 @@ - OKTW MusicBot Web + MusicBot @@ -40,9 +40,9 @@ - OKTW MusicBot + {{ title }} - + diff --git a/web/main.js b/web/main.js index 939ca14..79fc0e8 100644 --- a/web/main.js +++ b/web/main.js @@ -1,6 +1,5 @@ const config = { base: `${location.pathname}api/`, - tgBotName: "test_oktw_musicbot_bot", // https://gist.github.com/dperini/729294 urlRegex: /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i, }; @@ -16,6 +15,9 @@ const app = new Vue({ return { tg: {}, isMobile: false, + configLoaded: false, + title: "", + tgBotName: "", drop: { in: false, overlay: false, @@ -99,12 +101,10 @@ const app = new Vue({ created() { this.isMobile = window.innerWidth < 769 ? true : false; if (!this.isMobile) { this.showDrawer = true; } + this.loadConfig(); this.refresh(); }, computed: { - tgBotName() { - return config.tgBotName; - }, loggedIn() { return Object.keys(this.tg).length > 0; }, @@ -178,6 +178,14 @@ const app = new Vue({ this.audioAdd(evt.clipboardData.items); } }, + loadConfig() { + this.api("get", "config").then(json => { + this.title = json.title; + document.title = this.title; + this.tgBotName = json.tgBotName; + this.configLoaded = true; + }); + }, refresh() { this.getLists(); }, From 701e076f640ca7cd0d48caf163e7410f46cfe738 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 3 Aug 2019 22:46:39 +0800 Subject: [PATCH 15/16] fix UI problem that user able to upload to other's list --- web/index.html | 2 +- web/main.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/index.html b/web/index.html index e4b78ae..722ec93 100644 --- a/web/index.html +++ b/web/index.html @@ -99,7 +99,7 @@ {{ list.name }} - + diff --git a/web/main.js b/web/main.js index 79fc0e8..9a4adf3 100644 --- a/web/main.js +++ b/web/main.js @@ -152,7 +152,7 @@ const app = new Vue({ }); }, onDragOver(evt) { - if (this.loggedIn && !this.upload.uploading) { + if (this.list.own && !this.upload.uploading) { clearTimeout(this.drop.timer); this.drop.timer = setTimeout(() => { this.drop.in = false; @@ -169,12 +169,12 @@ const app = new Vue({ } }, onDrop(evt) { - if (this.loggedIn && !this.upload.uploading) { + if (this.list.own && !this.upload.uploading) { this.audioAdd(evt.dataTransfer.items || evt.dataTransfer.files); } }, onPaste(evt) { - if (this.loggedIn && !this.upload.uploading) { + if (this.list.own && !this.upload.uploading) { this.audioAdd(evt.clipboardData.items); } }, From ddb15c355391ca6e9c65c1fdb1a56627bc346c46 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Sat, 3 Aug 2019 22:53:50 +0800 Subject: [PATCH 16/16] fix conflict with current master --- package.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 8094a6e..207cd3c 100644 --- a/package.json +++ b/package.json @@ -11,36 +11,36 @@ "build:prod": "tsc --pretty --sourceMap false" }, "dependencies": { - "eris": "^0.10.0", + "eris": "^0.10.1", "express": "^4.17.1", "fluent-ffmpeg": "^2.1.2", - "mongodb": "^3.2.3", + "mongodb": "^3.2.7", "multer": "^1.4.1", "node-telegram-bot-api": "^0.30.0", "promise-queue": "^2.2.5", "shuffle-array": "^1.0.1", - "ytdl-core": "^0.29.1" + "ytdl-core": "^0.29.3" }, "devDependencies": { "@types/express": "^4.17.0", - "@types/fluent-ffmpeg": "^2.1.9", - "@types/mongodb": "^3.1.25", - "@types/node": "^12.0.8", - "@types/node-telegram-bot-api": "^0.30.4", + "@types/fluent-ffmpeg": "^2.1.10", + "@types/mongodb": "^3.1.30", + "@types/node": "^12.6.8", + "@types/node-telegram-bot-api": "^0.31.0", "@types/promise-queue": "^2.2.0", "@types/shuffle-array": "^0.0.28", - "husky": "^2.4.1", - "tslint": "^5.16.0", - "typescript": "^3.4.5" + "husky": "^3.0.1", + "tslint": "^5.18.0", + "typescript": "^3.5.3" }, "optionalDependencies": { - "@ffmpeg-installer/ffmpeg": "^1.0.17", - "@ffprobe-installer/ffprobe": "^1.0.10", + "@ffmpeg-installer/ffmpeg": "^1.0.19", + "@ffprobe-installer/ffprobe": "^1.0.12", "bson-ext": "^2.0.2", "bufferutil": "^4.0.1", "erlpack": "github:discordapp/erlpack", - "eventemitter3": "^3.1.0", - "sodium-native": "^2.3.0" + "eventemitter3": "^4.0.0", + "sodium-native": "^2.4.2" }, "husky": { "hooks": {