From 98a49346b822d71ac3bb5bd2c56139a50fcee5a4 Mon Sep 17 00:00:00 2001 From: conner Date: Wed, 16 Sep 2020 19:16:33 -0400 Subject: [PATCH 1/5] attempt --- src/bot.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/bot.js b/src/bot.js index f87b82a..4faa6f7 100644 --- a/src/bot.js +++ b/src/bot.js @@ -129,11 +129,27 @@ async function createSubmission(db, challengeId, ownerId, trackUrl) { ) } +function listDropboxFiles(dropbox, path) { + async function list(build, cursor) { + let response + if (cursor) { + response = await dropbox.filesListFolderContinue({ path }) + } else { + response = await dropbox.filesListFolder({ path }) + } + let { entries, has_more: hasMore, cursor } = response + if (hasMore) { + return list(build.concat(entries), cursor) + } + } + return list([]) +} + async function getRandomSample(dropbox) { try { - const entries = await dropbox.filesListFolder({ path: SAMPLE_PATH }) - const sample = entries[Math.floor(Math.random() * entries.length)] - const link = await dropbox.sharingCreateSharedLinkWithSettings({ + let entries = await listDropboxFiles(dropbox, SAMPLE_PATH) + let sample = entries[Math.floor(Math.random() * entries.length)] + let link = await dropbox.sharingCreateSharedLinkWithSettings({ path: sample.path_lower, short_url: true, }) @@ -307,7 +323,7 @@ function setupDiscord(dropbox, db) { execute: async (message) => { try { await message.react('👍') - const { url } = await getRandomSample(dropbox) + let { url } = await getRandomSample(dropbox) await message.reply(`done. ${url}`) } catch (err) { return message.react('❓') From 500f63735d7df1695df7a02dbc37aacdd7e1a24a Mon Sep 17 00:00:00 2001 From: conner Date: Wed, 16 Sep 2020 19:20:31 -0400 Subject: [PATCH 2/5] fix --- src/bot.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bot.js b/src/bot.js index 4faa6f7..4363656 100644 --- a/src/bot.js +++ b/src/bot.js @@ -133,13 +133,16 @@ function listDropboxFiles(dropbox, path) { async function list(build, cursor) { let response if (cursor) { - response = await dropbox.filesListFolderContinue({ path }) + response = await dropbox.filesListFolderContinue({ cursor }) } else { response = await dropbox.filesListFolder({ path }) } - let { entries, has_more: hasMore, cursor } = response + let { entries, has_more: hasMore, cursor: newCursor } = response + let allEntries = build.concat(entries) if (hasMore) { - return list(build.concat(entries), cursor) + return list(allEntries, newCursor) + } else { + return allEntries } } return list([]) From 3d618096a6dc493e3c627fbbecc9474b7125f195 Mon Sep 17 00:00:00 2001 From: conner Date: Wed, 16 Sep 2020 19:21:20 -0400 Subject: [PATCH 3/5] fix --- src/bot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot.js b/src/bot.js index 4363656..4a61764 100644 --- a/src/bot.js +++ b/src/bot.js @@ -152,7 +152,7 @@ async function getRandomSample(dropbox) { try { let entries = await listDropboxFiles(dropbox, SAMPLE_PATH) let sample = entries[Math.floor(Math.random() * entries.length)] - let link = await dropbox.sharingCreateSharedLinkWithSettings({ + let link = await dropbox.sharingCreateSharedLink({ path: sample.path_lower, short_url: true, }) From 0192bf47f60fe1c4ebbc1f1dff7dbb611ee7f6aa Mon Sep 17 00:00:00 2001 From: conner bryan Date: Wed, 16 Sep 2020 20:13:35 -0400 Subject: [PATCH 4/5] typescript --- package-lock.json | 131 ++++++++++++++++++++++++++++ package.json | 14 ++- procfile | 1 - src/{bot.js => bot.ts} | 150 ++++++++++++++++++++------------ src/tests/bot_test.js | 166 ----------------------------------- src/tests/bot_test.ts | 193 +++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 16 ++++ 7 files changed, 448 insertions(+), 223 deletions(-) delete mode 100644 procfile rename src/{bot.js => bot.ts} (73%) delete mode 100644 src/tests/bot_test.js create mode 100644 src/tests/bot_test.ts create mode 100644 tsconfig.json diff --git a/package-lock.json b/package-lock.json index 9e2ec2e..4c77764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,18 +142,85 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/chai": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", + "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "dev": true + }, + "@types/chai-as-promised": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz", + "integrity": "sha512-FQnh1ohPXJELpKhzjuDkPLR2BZCAqed+a6xV4MI/T3XzHfd2FlarfUGUdZYgqYe8oxkYn0fchHEeHfHqdZ96sg==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/fluent-ffmpeg": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.15.tgz", + "integrity": "sha512-bz52DnmQHWFJpV+O0ijpqtWpTfomtQSU5gDKG7FABDckLhGJXyqc+8hYvfjQ4FMOqjZLFHX1LCqs4NeZOyorLQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/node": { + "version": "14.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.3.tgz", + "integrity": "sha512-zdN0hor7TLkjAdKTnYW+Y22oIhUUpil5ZD1V1OFq0CR0CLKw+NdR6dkziTfkWRLo6sKzisayoj/GNpNbe4LY9Q==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sinon": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.5.tgz", + "integrity": "sha512-4CnkGdM/5/FXDGqL32JQ1ttVrGvhOoesLLF7VnTh4KdjK5N5VQOtxaylFqqTjnHx55MnD9O02Nbk5c1ELC8wlQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", + "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -266,6 +333,12 @@ } } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1135,6 +1208,17 @@ "which": "^1.1.1" } }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fs-minipass": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", @@ -1690,6 +1774,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "mime-db": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", @@ -2678,6 +2768,22 @@ } } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2903,6 +3009,19 @@ "utf8-byte-length": "^1.0.1" } }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, "tunnel-ssh": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-4.1.4.tgz", @@ -2945,6 +3064,12 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -3345,6 +3470,12 @@ } } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "ytdl-core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-3.2.0.tgz", diff --git a/package.json b/package.json index 59821b5..3057b76 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start:bot": "node src/bot.js", "deploy": "git push heroku master", - "test": "mocha src/tests" + "test": "mocha -r ts-node/register src/tests/**/*.ts" }, "husky": { "hooks": { @@ -31,6 +31,14 @@ }, "devDependencies": { "@sinonjs/referee": "^6.0.1", + "@types/chai": "^4.2.12", + "@types/chai-as-promised": "^7.1.3", + "@types/fluent-ffmpeg": "^2.1.15", + "@types/minimist": "^1.2.0", + "@types/mocha": "^8.0.3", + "@types/node": "^14.10.3", + "@types/node-fetch": "^2.5.7", + "@types/sinon": "^9.0.5", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "db-migrate": "^0.11.11", @@ -39,6 +47,8 @@ "husky": "^4.3.0", "mocha": "^8.1.1", "prettier": "^2.0.5", - "sinon": "^9.0.2" + "sinon": "^9.0.2", + "ts-node": "^9.0.0", + "typescript": "^4.0.2" } } diff --git a/procfile b/procfile deleted file mode 100644 index 94d75ea..0000000 --- a/procfile +++ /dev/null @@ -1 +0,0 @@ -worker: node src/bot.js \ No newline at end of file diff --git a/src/bot.js b/src/bot.ts similarity index 73% rename from src/bot.js rename to src/bot.ts index 4a61764..899797c 100644 --- a/src/bot.js +++ b/src/bot.ts @@ -1,22 +1,25 @@ require('dotenv').config() -let fs = require('fs') -let urlParse = require('url') -let { Dropbox } = require('dropbox') -let path = require('path') -let ytdl = require('ytdl-core') -let ffmpeg = require('fluent-ffmpeg') -let sanitizeFilename = require('sanitize-filename') -let Discord = require('discord.js') -let parseArgs = require('minimist') -let sqlite3 = require('sqlite3') -let sqlite = require('sqlite') -let tempfile = require('tempfile') +import * as fs from 'fs' +import { parse as parseUrl } from 'url' +import { Dropbox, files as dropboxFiles } from 'dropbox' +import * as path from 'path' +import ytdl from 'ytdl-core' +import ffmpeg from 'fluent-ffmpeg' +import sanitizeFilename from 'sanitize-filename' +import * as Discord from 'discord.js' +import parseArgs, { ParsedArgs } from 'minimist' +import sqlite3 from 'sqlite3' +import sqlite, { Database } from 'sqlite' +import tempfile from 'tempfile' +import { stringify } from 'querystring' let SAMPLE_PATH = '/samples' let CHALLENGES_PATH = '/challenges' -function youtubeSampleSource(url) { - return async (format) => { +type AudioFormat = 'wav' | 'mp3' + +function youtubeSampleSource(url: string) { + return async (format: AudioFormat) => { let info = await ytdl.getInfo(url) let filepath = tempfile(`.${format}`) await new Promise((resolve, reject) => @@ -36,7 +39,12 @@ function youtubeSampleSource(url) { } } -async function addYoutubeSample(url, args, message, dropbox) { +export async function addYoutubeSample( + url: string, + args: { format: AudioFormat }, + message: Discord.Message, + dropbox: Dropbox, +): ReturnType { let allowedFormats = ['mp3', 'wav'] let allowedHosts = [ 'youtube.com', @@ -44,16 +52,16 @@ async function addYoutubeSample(url, args, message, dropbox) { 'www.youtube.com', 'music.youtube.com', ] - let defaultFormat = 'wav' + let defaultFormat: AudioFormat = 'wav' let format = args.format || defaultFormat if (!allowedFormats.includes(format)) { await message.reply(`invalid format, sorry`) - return + throw new Error(`invalid format ${format}`) } - let { hostname } = urlParse.parse(url) - if (allowedHosts.includes(hostname)) { + let { hostname } = parseUrl(url) + if (hostname && allowedHosts.includes(hostname)) { await message.react('👍') let link = await uploadSample( youtubeSampleSource(url), @@ -66,7 +74,13 @@ async function addYoutubeSample(url, args, message, dropbox) { } } -async function uploadSample(source, format, dropbox) { +type Source = (fmt: AudioFormat) => Promise<{ title: string; data: any }> + +async function uploadSample( + source: Source, + format: AudioFormat, + dropbox: Dropbox, +) { let { title, data } = await source(format) let uploadPath = path.join( SAMPLE_PATH, @@ -83,27 +97,31 @@ async function uploadSample(source, format, dropbox) { return link } -async function getCurrentChallenge(db) { +async function getCurrentChallenge(db: Database) { let actives = await db.all('select * from challenges where active = ?', true) if (actives.length > 0) { return actives[0] } } -async function existingChallenge(db) { +async function existingChallenge(db: Database) { let currentChallenge = await getCurrentChallenge(db) return !!currentChallenge } -async function endChallenge(db, id) { +async function endChallenge(db: Database, id: string) { return db.run('update challenges set active = ? WHERE id = ?', false, id) } -async function getSubmissions(db, challengeId) { +async function getSubmissions(db: Database, challengeId: string) { return db.all('select * from submissions where challenge_id = ?', challengeId) } -async function createChallenge(db, ownerId, sampleUrl) { +async function createChallenge( + db: Database, + ownerId: string, + sampleUrl: string, +) { if (await existingChallenge(db)) { return false } @@ -118,7 +136,12 @@ async function createChallenge(db, ownerId, sampleUrl) { ) } -async function createSubmission(db, challengeId, ownerId, trackUrl) { +async function createSubmission( + db: Database, + challengeId: string, + ownerId: string, + trackUrl: string, +) { await db.run( 'insert into submissions(owner_id, challenge_id, track_url) VALUES(:ownerId, :challengeId, :trackUrl) on conflict (challenge_id, owner_id) do update set track_url = excluded.track_url', { @@ -129,8 +152,16 @@ async function createSubmission(db, challengeId, ownerId, trackUrl) { ) } -function listDropboxFiles(dropbox, path) { - async function list(build, cursor) { +type DropboxEntry = dropboxFiles.ListFolderResult['entries'][0] + +function listDropboxFiles( + dropbox: Dropbox, + path: string, +): Promise { + async function list( + build: DropboxEntry[], + cursor: string | undefined, + ): Promise { let response if (cursor) { response = await dropbox.filesListFolderContinue({ cursor }) @@ -145,15 +176,15 @@ function listDropboxFiles(dropbox, path) { return allEntries } } - return list([]) + return list([], undefined) } -async function getRandomSample(dropbox) { +export async function getRandomSample(dropbox: Dropbox) { try { let entries = await listDropboxFiles(dropbox, SAMPLE_PATH) let sample = entries[Math.floor(Math.random() * entries.length)] let link = await dropbox.sharingCreateSharedLink({ - path: sample.path_lower, + path: sample.path_lower!, short_url: true, }) @@ -164,14 +195,18 @@ async function getRandomSample(dropbox) { } } -function setupDiscord(dropbox, db) { +interface CommandDef { + execute: (message: Discord.Message, args: ParsedArgs) => Promise +} + +function setupDiscord(dropbox: Dropbox, db: Database) { let client = new Discord.Client() - client.commands = new Discord.Collection() + let commands = new Discord.Collection() let prefix = 'sb!' - client.commands.set('help', { + commands.set('help', { execute: async (message) => { - let available = client.commands + let available = commands .keyArray() .map((k) => `\`${prefix}${k}\``) .join(', ') @@ -179,7 +214,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('challenge', { + commands.set('challenge', { execute: async (message) => { let currentChallenge = await getCurrentChallenge(db) if (currentChallenge) { @@ -195,7 +230,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('challenge.submissions', { + commands.set('challenge.submissions', { execute: async (message) => { let currentChallenge = await getCurrentChallenge(db) if (currentChallenge) { @@ -214,7 +249,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('challenge.submit', { + commands.set('challenge.submit', { execute: async (message, args) => { let currentChallenge = await getCurrentChallenge(db) if (!currentChallenge) { @@ -236,7 +271,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('challenges', { + commands.set('challenges', { execute: async (message) => { let link = await dropbox.sharingCreateSharedLink({ path: CHALLENGES_PATH, @@ -246,7 +281,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('challenge.end', { + commands.set('challenge.end', { execute: async (message) => { let currentChallenge = await getCurrentChallenge(db) if (!currentChallenge) { @@ -268,7 +303,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('challenge.start', { + commands.set('challenge.start', { execute: async (message, args) => { let currentChallenge = await getCurrentChallenge(db) if (currentChallenge) { @@ -284,7 +319,12 @@ function setupDiscord(dropbox, db) { let url = args._[0] try { - let link = await addYoutubeSample(url, args, message, dropbox) + let link = await addYoutubeSample( + url, + { format: args.format as AudioFormat }, + message, + dropbox, + ) await createChallenge(db, message.author.id, link.url) await message.reply(`challenge started! sample: ${link.url}`) @@ -295,7 +335,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('samples', { + commands.set('samples', { execute: async (message) => { let link = await dropbox.sharingCreateSharedLink({ path: SAMPLE_PATH, @@ -305,7 +345,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('samples.add', { + commands.set('samples.add', { execute: async (message, args) => { if (args._.length === 0) { return message.react('❓') @@ -313,7 +353,12 @@ function setupDiscord(dropbox, db) { let url = args._[0] try { - let link = await addYoutubeSample(url, args, message, dropbox) + let link = await addYoutubeSample( + url, + { format: args.format as AudioFormat }, + message, + dropbox, + ) await message.reply(`done. ${link.url}`) } catch (err) { console.error(`Failed to add a sample: ${err}`) @@ -322,7 +367,7 @@ function setupDiscord(dropbox, db) { }, }) - client.commands.set('samples.random', { + commands.set('samples.random', { execute: async (message) => { try { await message.react('👍') @@ -347,10 +392,13 @@ function setupDiscord(dropbox, db) { for (let i = args._.length; i >= 0; i--) { let command = args._.slice(0, i).join('.') - if (client.commands.has(command)) { + if (commands.has(command)) { try { args._ = args._.slice(i) - await client.commands.get(command).execute(message, args) + let execCmd = commands.get(command) + if (execCmd) { + await execCmd.execute(message, args) + } } catch (error) { console.error(error) await message.reply(`that didn't work`) @@ -383,9 +431,3 @@ async function run() { if (process.env.NODE_ENV !== 'test') { run().catch((e) => console.error(e)) } - -module.exports = { - youtubeSampleSource, - addYoutubeSample, - getRandomSample, -} diff --git a/src/tests/bot_test.js b/src/tests/bot_test.js deleted file mode 100644 index d5fa665..0000000 --- a/src/tests/bot_test.js +++ /dev/null @@ -1,166 +0,0 @@ -let { addYoutubeSample, getRandomSample } = require('../bot') -let { describe, it } = require('mocha') -let { expect } = require('chai') -let chai = require('chai') -var chaiAsPromised = require('chai-as-promised') -let { Message } = require('discord.js') -let sinon = require('sinon') -let { Dropbox } = require('dropbox') -const { assert } = require('@sinonjs/referee') -const fetch = require('node-fetch') - -chai.use(chaiAsPromised) - -describe('#addYoutubeSample', function () { - this.timeout(15 * 1000) - it('should throw an error when invalid url provided', async function () { - let dropbox = new Dropbox({ fetch }) - sinon.stub(dropbox, 'filesUpload').returns('') - sinon.stub(dropbox, 'sharingCreateSharedLink').returns('example-url') - - let messageStub = sinon.createStubInstance(Message) - - return await expect( - addYoutubeSample( - 'invalid', - { - format: 'mp3', - }, - messageStub, - dropbox, - ), - ).to.be.rejectedWith(Error) - }) - - it('should work for music.youtube.com links', function () { - let dropbox = new Dropbox({ fetch }) - sinon.stub(dropbox, 'filesUpload').returns('') - sinon.stub(dropbox, 'sharingCreateSharedLink').returns('example-url') - - let messageStub = sinon.createStubInstance(Message) - - return addYoutubeSample( - 'https://music.youtube.com/watch?v=NaMmX7OCyCA&feature=share', - { - format: 'mp3', - }, - messageStub, - dropbox, - ).then((link) => { - expect(link).to.equal('example-url') - assert.isTrue(messageStub.react.calledWith('👍')) - expect(dropbox) - }) - }) - - it('should work for youtube.com links', function () { - let dropbox = new Dropbox({ fetch }) - sinon.stub(dropbox, 'filesUpload').returns('') - sinon.stub(dropbox, 'sharingCreateSharedLink').returns('example-url') - - let messageStub = sinon.createStubInstance(Message) - - return addYoutubeSample( - 'https://youtube.com/watch?v=NaMmX7OCyCA&feature=share', - { - format: 'mp3', - }, - messageStub, - dropbox, - ).then((link) => { - expect(link).to.equal('example-url') - assert.isTrue(messageStub.react.calledWith('👍')) - expect(dropbox) - }) - }) - - it('should work with a song with the highest quality', function () { - let dropbox = new Dropbox({ fetch }) - sinon.stub(dropbox, 'filesUpload').returns('') - sinon.stub(dropbox, 'sharingCreateSharedLink').returns('example-url') - - let messageStub = sinon.createStubInstance(Message) - - return addYoutubeSample( - 'https://music.youtube.com/watch?v=rW9VsxK2HPE', - { - format: 'mp3', - }, - messageStub, - dropbox, - ).then((link) => { - expect(link).to.equal('example-url') - assert.isTrue(dropbox.filesUpload.called) - assert.isTrue(messageStub.react.calledWith('👍')) - expect(dropbox) - }) - }) - - it('should work for www.youtube.com links', function () { - let dropbox = new Dropbox({ fetch }) - sinon.stub(dropbox, 'filesUpload').returns('') - sinon.stub(dropbox, 'sharingCreateSharedLink').returns('example-url') - - let messageStub = sinon.createStubInstance(Message) - - return addYoutubeSample( - 'https://www.youtube.com/watch?v=NaMmX7OCyCA&feature=share', - { - format: 'mp3', - }, - messageStub, - dropbox, - ).then((link) => { - expect(link).to.equal('example-url') - assert.isTrue(dropbox.filesUpload.called) - assert.isTrue(messageStub.react.calledWith('👍')) - expect(dropbox) - }) - }) - - it('should work for youtu.be links', function () { - let dropbox = new Dropbox({ fetch }) - sinon.stub(dropbox, 'filesUpload').returns('') - sinon.stub(dropbox, 'sharingCreateSharedLink').returns('example-url') - - let messageStub = sinon.createStubInstance(Message) - - return addYoutubeSample( - 'https://youtu.be/NaMmX7OCyCA&feature=share', - { - format: 'mp3', - }, - messageStub, - dropbox, - ).then((link) => { - expect(link).to.equal('example-url') - assert.isTrue(messageStub.react.calledWith('👍')) - expect(dropbox) - }) - }) -}) - -describe('#getRandomSample', () => { - it('should get a random link from dropbox', () => { - const dropbox = new Dropbox({ fetch }) - sinon.stub(dropbox, 'filesListFolder').returns([ - { - path_lower: '/samples/sample1.mp3', - }, - { - path_lower: '/samples/sample2.mp3', - }, - { - path_lower: '/samples/sample3.mp3', - }, - ]) - - sinon - .stub(dropbox, 'sharingCreateSharedLinkWithSettings') - .returns('example-url') - - return getRandomSample(dropbox).then((link) => { - expect(link).to.eq('example-url') - }) - }) -}) diff --git a/src/tests/bot_test.ts b/src/tests/bot_test.ts new file mode 100644 index 0000000..295780c --- /dev/null +++ b/src/tests/bot_test.ts @@ -0,0 +1,193 @@ +import { addYoutubeSample, getRandomSample } from '../bot' +import { describe, it } from 'mocha' +import chai, { expect } from 'chai' +import { Message } from 'discord.js' +import sinon from 'sinon' +import { Dropbox, sharing } from 'dropbox' +import fetch from 'node-fetch' + +chai.use(require('chai-as-promised')) + +let sharedLink: sharing.PathLinkMetadata = { + path: 'example-url', + url: '', + visibility: { '.tag': 'public' }, +} + +let uploaded = {} as any + +describe('#addYoutubeSample', function () { + this.timeout(15 * 1000) + it('should throw an error when invalid url provided', async function () { + let dropbox = new Dropbox({ fetch }) + sinon.stub(dropbox, 'filesUpload').returns(Promise.resolve(uploaded)) + sinon + .stub(dropbox, 'sharingCreateSharedLink') + .returns(Promise.resolve(sharedLink)) + + let messageStub = sinon.createStubInstance(Message) + + return await expect( + addYoutubeSample( + 'invalid', + { + format: 'mp3', + }, + (messageStub as unknown) as Message, + dropbox, + ), + ).to.be.rejectedWith(Error) + }) + + it('should work for music.youtube.com links', function () { + let dropbox = new Dropbox({ fetch }) + sinon.stub(dropbox, 'filesUpload').returns(Promise.resolve(uploaded)) + sinon + .stub(dropbox, 'sharingCreateSharedLink') + .returns(Promise.resolve(sharedLink)) + + let messageStub = sinon.createStubInstance(Message) + + return addYoutubeSample( + 'https://music.youtube.com/watch?v=NaMmX7OCyCA&feature=share', + { + format: 'mp3', + }, + (messageStub as unknown) as Message, + dropbox, + ).then((link) => { + expect(link.path).to.equal('example-url') + expect(messageStub.react.calledWith('👍')).to.eq(true) + expect(dropbox) + }) + }) + + it('should work for youtube.com links', function () { + let dropbox = new Dropbox({ fetch }) + sinon.stub(dropbox, 'filesUpload').returns(Promise.resolve(uploaded)) + sinon + .stub(dropbox, 'sharingCreateSharedLink') + .returns(Promise.resolve(sharedLink)) + + let messageStub = sinon.createStubInstance(Message) + + return addYoutubeSample( + 'https://youtube.com/watch?v=NaMmX7OCyCA&feature=share', + { + format: 'mp3', + }, + (messageStub as unknown) as Message, + dropbox, + ).then((link) => { + expect(link.path).to.equal('example-url') + expect(messageStub.react.calledWith('👍')).to.eq(true) + expect(dropbox) + }) + }) + + it('should work with a song with the highest quality', function () { + let dropbox = new Dropbox({ fetch }) + let uploadStub = sinon + .stub(dropbox, 'filesUpload') + .returns(Promise.resolve(uploaded)) + sinon + .stub(dropbox, 'sharingCreateSharedLink') + .returns(Promise.resolve(sharedLink)) + + let messageStub = sinon.createStubInstance(Message) + + return addYoutubeSample( + 'https://music.youtube.com/watch?v=rW9VsxK2HPE', + { + format: 'mp3', + }, + (messageStub as unknown) as Message, + dropbox, + ).then((link) => { + expect(link.path).to.equal('example-url') + expect(uploadStub.called).to.eq(true) + expect(messageStub.react.calledWith('👍')).to.eq(true) + expect(dropbox) + }) + }) + + it('should work for www.youtube.com links', function () { + let dropbox = new Dropbox({ fetch }) + let uploadStub = sinon + .stub(dropbox, 'filesUpload') + .returns(Promise.resolve(uploaded)) + sinon + .stub(dropbox, 'sharingCreateSharedLink') + .returns(Promise.resolve(sharedLink)) + + let messageStub = sinon.createStubInstance(Message) + + return addYoutubeSample( + 'https://www.youtube.com/watch?v=NaMmX7OCyCA&feature=share', + { + format: 'mp3', + }, + (messageStub as unknown) as Message, + dropbox, + ).then((link) => { + expect(link.path).to.equal('example-url') + expect(uploadStub.called).to.eq(true) + expect(messageStub.react.calledWith('👍')).to.eq(true) + expect(dropbox) + }) + }) + + it('should work for youtu.be links', function () { + let dropbox = new Dropbox({ fetch }) + sinon.stub(dropbox, 'filesUpload').returns(Promise.resolve(uploaded)) + sinon + .stub(dropbox, 'sharingCreateSharedLink') + .returns(Promise.resolve(sharedLink)) + + let messageStub = sinon.createStubInstance(Message) + + return addYoutubeSample( + 'https://youtu.be/NaMmX7OCyCA&feature=share', + { + format: 'mp3', + }, + (messageStub as unknown) as Message, + dropbox, + ).then((link) => { + expect(link.path).to.equal('example-url') + expect(messageStub.react.calledWith('👍')).to.eq(true) + expect(dropbox) + }) + }) +}) + +describe('#getRandomSample', () => { + it('should get a random link from dropbox', () => { + const dropbox = new Dropbox({ fetch }) + sinon.stub(dropbox, 'filesListFolder').returns( + Promise.resolve({ + cursor: '', + has_more: false, + entries: [ + { + path_lower: '/samples/sample1.mp3', + } as any, + { + path_lower: '/samples/sample2.mp3', + } as any, + { + path_lower: '/samples/sample3.mp3', + } as any, + ], + }), + ) + + sinon + .stub(dropbox, 'sharingCreateSharedLink') + .returns(Promise.resolve(sharedLink)) + + return getRandomSample(dropbox).then((link) => { + expect(link.path).to.eq('example-url') + }) + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fc6b02c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2017", + "lib": ["esnext"], + "strict": true, + "moduleResolution": "node", + "module": "commonjs", + "sourceMap": true, + "rootDir": "src", + "outDir": "dist", + "experimentalDecorators": false, + "esModuleInterop": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} From ec54f0601989cf10178b0c2d35314079da1499e5 Mon Sep 17 00:00:00 2001 From: conner bryan Date: Wed, 16 Sep 2020 20:21:21 -0400 Subject: [PATCH 5/5] update start --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 3057b76..638c994 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "start:bot": "node src/bot.js", - "deploy": "git push heroku master", + "start:bot": "ts-node src/bot.ts", "test": "mocha -r ts-node/register src/tests/**/*.ts" }, "husky": {