commit b0a6214374887e21833c01f256c99397b235cdbb
parent 48d106ac535492f024a044c46fcab1b806d89574
Author: Demonstrandum <moi@knutsen.co>
Date:   Tue, 17 Mar 2020 19:59:00 +0000
Youtube scraper...
Diffstat:
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"