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"