Simp-O-Matic

Dumb Discord bot in TS.
git clone git://git.knutsen.co/Simp-O-Matic
Log | Files | Refs | README | LICENSE

commit b0a6214374887e21833c01f256c99397b235cdbb
parent 48d106ac535492f024a044c46fcab1b806d89574
Author: Demonstrandum <moi@knutsen.co>
Date:   Tue, 17 Mar 2020 19:59:00 +0000

Youtube scraper...

Diffstat:
MHELP.md | 2++
Alib/api/youtube.ts | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/api/yt_scrape.ts | 25+++++++++++++++++++++++++
Mlib/default.ts | 4++++
Mlib/extensions.ts | 23+++++++++++++++++++++++
Mlib/main.ts | 25++++++++++++++++++++++---
Mpackage.json | 2++
7 files changed, 154 insertions(+), 3 deletions(-)

diff --git a/HELP.md b/HELP.md @@ -15,6 +15,7 @@ - `!help key` — shows how to read the help messages. - `!help source` — shows information about the source code for this bot. - `!help [!command]` — shows help on a certain command. +- `!commands` — Lists out all commands, but with no description. For descriptions, use `!help`. - `!!` — Expands into the previously issued command: - `!!@ [@user-name]` — Expands into the previously issued command by that user. - `!!^` — Expands into the previous message sent (this counts commands as being messages too, since they are). @@ -25,6 +26,7 @@ - `!export` — Exports current configuration, and saves it. - `!prefix [new]` — Changes the prefix for sending this bot commands (default is `!`). Can only be one (1) character/symbol/grapheme/rune long. - `!ping` — Test the response-time/latency of the bot, by observing the time elapsed between the sending of this command, and the subsequent (one-word) response from the bot. +- `!invite` --- Get an invite link (needs admin (`8`) permissions). - `!id <who>` — Print ID of user, or self if no-one is specified. - `!get [accessor]` — Get a runtime configuration variable, using JavaScript object dot-notation. - `!set [accessor] [json-value]` — Set a value in the runtime JavaScript configuration object. diff --git a/lib/api/youtube.ts b/lib/api/youtube.ts @@ -0,0 +1,76 @@ +// FUCK THIS. Max 100 searches a day, and terrible terrible errors +// AND I have to authenticate every hour. +// I'm scraping YouTube instead. + +import { google } from 'googleapis'; +import { oauth2 } from 'googleapis/build/src/apis/oauth2'; +const { OAuth2 } = google.auth; + +import '../extensions'; + +const SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; + +type YTSearch = { + key: string, + query: string +}; + +const web_search = (param: YTSearch) => new Promise((resolve, reject) => { + const yt = google.youtube('v3'); + const auth = new OAuth2({ + clientId: process.env['GOOGLE_OAUTH_ID'], + clientSecret: process.env['GOOGLE_OAUTH_SECRET'], + redirectUri: 'https://google.com/' + }); + // const auth_url = auth.generateAuthUrl({ + // access_type: 'offline', + // scope: SCOPES + // }); + // console.log('Authorize this app by visiting this url: ', auth_url); + auth.getToken(process.env['GOOGLE_PERSONAL_CODE']).then(code => { + auth.setCredentials(code.tokens); + yt.search.list({ + q: param.query, + maxResults: 1, + auth: auth, + part: 'snippet' + }).then(res => { + if (!res.data || !res.data.items || res.data.items.length === 0) + return reject('No such results found.') + + const video = res.data.items[0]; + const id = video.id.videoId; + + yt.videos.list({ + part: 'statistics', id + }).then(vid_res => { + const title = video.snippet.title; + const { viewCount: views, + likeCount: likes, + dislikeCount: dislikes } = vid_res.data.items[0].statistics; + + const url = `https://youtu.be/${id}/`; + const by = video.snippet.channelTitle; + console.log(video); + + return resolve(`${url}\n> ${title} | ${views} views | \ + :+1: ${likes} — :-1: ${dislikes} \nby: ${by}.`.squeeze()); + }).catch(e => + reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); + }).catch(e => + reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); + }).catch(err => { + console.log('Error with code:', err); + reject('Token probably expired, i.e. logged out.\n' + + '```\n' + err.toString() + '\n```'); + }); +}); + +export default web_search; + +web_search({ + key: process.env['GOOGLE_API_KEY'], + query: 'cat videos' +}).then(res => { + console.log(res); +}).catch(console.log); diff --git a/lib/api/yt_scrape.ts b/lib/api/yt_scrape.ts @@ -0,0 +1,25 @@ +const search = require('scrape-youtube'); +import '../extensions'; + +type YTSearch = { + query: string +}; + +const yt_search = (params: YTSearch) => new Promise((resolve, reject) => { + search(params.query, { + limit: 2, + type: 'video' + }).then(res => { + if (!res || res.length === 0) { + return reject('No YouTube results found.'); + } + + const { channel: by, title, views, + upload_date, link: url } = res[0]; + + return resolve(`${url}\n> ${title} | ${views.to_metric()} views | \ + uploaded ${upload_date} | by: ${by}.`.squeeze()); + }); +}); + +export default yt_search; diff --git a/lib/default.ts b/lib/default.ts @@ -17,6 +17,10 @@ export default { 'i': 'image', 'h': 'help', 's': 'search', + 'web': 'search', + 'g': 'search', + 'google': 'search', + 'bing': 'search', 'yt': 'youtube', 'y': 'youtube', 'd': 'define', diff --git a/lib/extensions.ts b/lib/extensions.ts @@ -25,6 +25,7 @@ declare global { interface Number { round_to(dp: number): number + to_metric(figures): string } } @@ -93,6 +94,28 @@ Number.prototype.round_to = function (dp : number) { return Math.round(this.valueOf() * exp) / exp; }; +const SI_EXTENSIONS = [ + { value: 1, symbol: "" }, + { value: 1E3, symbol: "k" }, + { value: 1E6, symbol: "M" }, + { value: 1E9, symbol: "G" }, + { value: 1E12, symbol: "T" }, + { value: 1E15, symbol: "P" }, + { value: 1E18, symbol: "E" } +]; + +Number.prototype.to_metric = function (figures) { + let i = SI_EXTENSIONS.length - 1; + for (; i > 0; --i) + if (this >= SI_EXTENSIONS[i].value) + break; + + return (this.valueOf() / SI_EXTENSIONS[i].value) + .toFixed(figures) + .replace(/\.0+$|(\.[0-9]*[1-9])0+$/, "$1") + + SI_EXTENSIONS[i].symbol; +}; + // Discord Extensions: declare module 'discord.js' { diff --git a/lib/main.ts b/lib/main.ts @@ -29,6 +29,7 @@ import { Channel } from 'discord.js'; import { resolve } from 'dns'; import { TextChannel } from 'discord.js'; import { Collection } from 'discord.js'; +import yt_search from './api/yt_scrape'; // Anything that hasn't been defined in `bot.json` @@ -160,6 +161,15 @@ export class SimpOMatic { + HELP_SECTIONS[help_index].trim()); break; + } case 'commands': { + const p = CONFIG.commands.prefix; + const joined_commands = KNOWN_COMMANDS.slice(0, -1) + .map(c => `\`${p}${c}\``) + .join(', '); + const last_command = `\`${p}${KNOWN_COMMANDS.last()}\``; + message.reply(`All known commands (excluding aliases): \ + ${joined_commands} and ${last_command}`.squeeze()); + break; } case 'id': { if (args[0]) { const matches = args[0].match(/<@(\d+)>/) @@ -306,7 +316,7 @@ export class SimpOMatic { // Man alive, someone else do this please. break; } case 'response': { - + // TODO break } case 'search': { const query = args.join(' ').toLowerCase(); @@ -327,8 +337,14 @@ export class SimpOMatic { query, key: SECRETS.google.api_key, id: SECRETS.google.search_id - }).then((res) => message.answer(res)) - .catch(e => message.answer(e)); + }).then(res => message.answer(res)) + .catch(er => message.answer(er)); + break; + } case 'youtube': { + const query = args.join(' '); + yt_search({ query }) + .then(message.reply.bind(message)) + .catch(message.answer.bind(message)); break; } case 'define': { message.answer('Looking in the Oxford English Dictionary...'); @@ -416,6 +432,9 @@ export class SimpOMatic { } case 'say': { message.answer(`Me-sa says: “${args.join(' ')}”`); break; + } case 'invite': { + message.answer('Invite link: https://discordapp.com/api/oauth2/authorize?client_id=684895962212204748&permissions=8&scope=bot'); + break; } case 'export': { let export_string = export_config(CONFIG, {}); if (export_string.length > 1980) { diff --git a/package.json b/package.json @@ -36,7 +36,9 @@ "@types/ws": "^7.2.2", "deepcopy": "^2.0.0", "discord.js": "11.6.1", + "google-auth-library": "^5.10.1", "googleapis": "^48.0.0", + "scrape-youtube": "^0.0.5", "tslib": "^1.11.1", "typescript": "^3.8.3", "unirest": "^0.6.0"