From 229dfb47ff482d027bebbc85c6a08077abe63d05 Mon Sep 17 00:00:00 2001 From: Reed Davenport Date: Sat, 26 Feb 2022 22:11:35 -0500 Subject: [PATCH 1/5] Refactoring checks --- src/checks/common.check.ts | 80 +++++++++++++++++++++++--------------- src/checks/game.check.ts | 10 ++--- src/checks/stream.check.ts | 47 +++++++++++----------- 3 files changed, 78 insertions(+), 59 deletions(-) diff --git a/src/checks/common.check.ts b/src/checks/common.check.ts index c08b17d..63f888e 100644 --- a/src/checks/common.check.ts +++ b/src/checks/common.check.ts @@ -15,6 +15,52 @@ const config: HTCheckConfig = { sendToTwitter: true } +const messages = { + // Discord specific + discordEmbedContent: 'Hear ye hear ye, new API', + discordEmbedTitle: (hostname: string) => `${hostname} Hash Changed`, + discordEmbedBody: (hostname: string) => `\`${hostname}\`'s hash has changed!`, + discordOldRevision: 'Old Hash', + discordNewRevision: 'New Hash', + + // Twitter / Telegram stuff + revisionChanged: (hostname: string, oldRevision: string, newRevision: string) => `${hostname}'s hash has changed!\n\n${oldRevision} => ${newRevision}` +} + +async function socials (hostname: string, oldRevision: string, newRevision: string) { + if (config.sendToDiscord) { + await client.send({ + content: messages.discordEmbedContent, + embeds: [{ + title: messages.discordEmbedTitle(hostname), + description: messages.discordEmbedBody(hostname), + color: 'RANDOM', + fields: [ + { + name: messages.discordOldRevision, + value: `\`${oldRevision}\``, + inline: true + }, + { + name: messages.discordNewRevision, + value: `\`${newRevision}\``, + inline: true + } + ], + timestamp: new Date() + }] + }) + } + + if (config.sendToTelegram) { + await tg(messages.revisionChanged(hostname, oldRevision, newRevision)) + } + + if (config.sendToTwitter) { + await tweet(messages.revisionChanged(hostname, oldRevision, newRevision)) + } +} + /** * Splits a hostname into an HTHost object. * @param {string} hostname Hostname returned from an axios.head() request. @@ -77,37 +123,7 @@ async function check(apiHost: string, friendlyHostname: string) { // If we get an undefined value here, there is nothing in the revision history and we've probably hit a new hash. d('The localRev and remoteRev do not match, and there is no match in the revisionHistory.') - if (config.sendToDiscord) { - await client.send({ - content: 'Hear ye hear ye, new API', - embeds: [{ - title: `${friendlyHostname} Changed`, - description: `\`${friendlyHostname}\`'s revision has changed! This could indicate a scale-up or new API changes.`, - color: 'RANDOM', - fields: [ - { - name: 'Old Revision', - value: `\`${localRev}\``, - inline: true - }, - { - name: 'New Revision', - value: `\`${rev}\``, - inline: true - } - ], - timestamp: new Date() - }] - }) - } - - if (config.sendToTelegram) { - await tg(`${friendlyHostname}'s revision has changed! This could indicate a scale-up or new API changes.\n\nOld Revision: ${localRev}\nNew Revision: ${rev}\nDate: ${new Date()}`) - } - - if (config.sendToTwitter) { - await tweet(`🛠️ I've detected that ${friendlyHostname}'s revision has changed. This could indicate an API change or a scale-up.\n\nOld revision: ${localRev}\nNew revision: ${rev}`) - } + await socials(friendlyHostname, localRev, rev) // To ensure we don't send out duplicates, create a new HTRevisionHistory object and append it to the revHistory. // Then we write this back. @@ -116,7 +132,7 @@ async function check(apiHost: string, friendlyHostname: string) { rev } - // See note at L77-78 for the reason the non-null assertion operator is used here. + // See note at L118-119 for the reason the non-null assertion operator is used here. revHistory!.push(newHistoryEntry) await set('revisionHistory', revHistory) diff --git a/src/checks/game.check.ts b/src/checks/game.check.ts index 4380b16..1fdc84e 100644 --- a/src/checks/game.check.ts +++ b/src/checks/game.check.ts @@ -1,7 +1,7 @@ import axios from 'axios' import debug from '../utils/debug.js' import { tweet } from '../utils/twitter.js' -import { client } from '../utils/discord.js' +import { postToDiscord } from '../utils/discord.js' import { tg } from '../utils/telegram.js' import { get, set } from '../utils/db2.js' @@ -16,15 +16,15 @@ const config: HTCheckConfig = { } const messages = { - gameLive: `An HQ game is active. (ts: ${+new Date()})`, - gameOver: `HQ is no longer active. (ts: ${+new Date()})` + gameLive: () => `An HQ game is active. (ts: ${+new Date()})`, + gameOver: () => `HQ is no longer active. (ts: ${+new Date()})` } async function social (gameLive: boolean) { - let text = gameLive ? messages.gameLive : messages.gameOver + let text = gameLive ? messages.gameLive() : messages.gameOver() if (config.sendToDiscord) { - await client.send(text) + await postToDiscord(text) } if (config.sendToTwitter) { diff --git a/src/checks/stream.check.ts b/src/checks/stream.check.ts index c5066f6..a16f29e 100644 --- a/src/checks/stream.check.ts +++ b/src/checks/stream.check.ts @@ -15,6 +15,29 @@ const config: HTCheckConfig = { sendToTwitter: true } +const messages = { + streamLive: (playlist: string) => `The HQ stream is live @ https://hls.prod.hype.space/${playlist}.m3u8 (ts: ${Date.now()})`, + streamDown: (playlist: string) => `The HQ stream (${playlist}) is now down. (ts: ${Date.now()})` +} + +async function socials (playlist: string, streamLive: boolean) { + const text = streamLive + ? messages.streamLive(playlist) + : messages.streamDown(playlist) + + if (config.sendToDiscord) { + await postToDiscord(text) + } + + if (config.sendToTwitter) { + await tweet(text) + } + + if (config.sendToTelegram) { + await tg(text) + } +} + async function check(playlist: string) { const d = debug.extend(playlist) @@ -35,17 +58,7 @@ async function check(playlist: string) { // Set streamLive for this playlist. await set(`streamLive_${playlist}`, true) - if (config.sendToDiscord) { - await postToDiscord(`The HQ stream is live @ https://hls.prod.hype.space/${playlist}.m3u8 (ts: ${Date.now()})`) - } - - if (config.sendToTelegram) { - await tg(`The HQ stream is live @ https://hls.prod.hype.space/${playlist}.m3u8 (ts: ${Date.now()})`) - } - - if (config.sendToTwitter) { - await tweet(`The HQ stream is live @ https://hls.prod.hype.space/${playlist}.m3u8 (ts: ${Date.now()})`) - } + await socials(playlist, true) } catch (error: any) { d('%s is not live!', playlist) @@ -55,17 +68,7 @@ async function check(playlist: string) { // The stream has gone offline since our last check. d('%s went offline since last check!', playlist) - if (config.sendToDiscord) { - await postToDiscord(`The HQ stream (${playlist}) is now down. (ts: ${Date.now()})`) - } - - if (config.sendToTelegram) { - await tg(`The HQ stream (${playlist}) is now down. (ts: ${Date.now()})`) - } - - if (config.sendToTwitter) { - await tweet(`The HQ stream (${playlist}) is now down. (ts: ${Date.now()})`) - } + await socials(playlist, false) } return From 7aeda162265dc00c855432e4b049f492aa6907c9 Mon Sep 17 00:00:00 2001 From: Reed Davenport Date: Sat, 26 Feb 2022 22:18:33 -0500 Subject: [PATCH 2/5] slight code cleanup --- src/checks/common.check.ts | 2 +- src/checks/stream.check.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/checks/common.check.ts b/src/checks/common.check.ts index 63f888e..69328bf 100644 --- a/src/checks/common.check.ts +++ b/src/checks/common.check.ts @@ -3,8 +3,8 @@ import debug from '../utils/debug.js' import { tweet } from '../utils/twitter.js' import { client } from '../utils/discord.js' import { tg } from '../utils/telegram.js' -// import { Message } from '@cryb/mesa' import { get, set } from '../utils/db2.js' + import type { HTCheckConfig } from '../types/HTCheckConfig.type.js' import type { HTHost } from '../types/HTHost.type.js' import type { HTRevisionHistory } from '../types/HTRevisionHistory.type.js' diff --git a/src/checks/stream.check.ts b/src/checks/stream.check.ts index a16f29e..84876d2 100644 --- a/src/checks/stream.check.ts +++ b/src/checks/stream.check.ts @@ -1,5 +1,8 @@ -import dotenv from 'dotenv' -dotenv.config() +// TODO: Why is this here? +// IIRC this was because of some weirdness with the Twitter utils script not finding its keys. +// But game/common works without this. +// import dotenv from 'dotenv' +// dotenv.config() import axios from 'axios' import debug from '../utils/debug.js' From 1e4cd0f1677d11778ab37c0ec99780f6482809ec Mon Sep 17 00:00:00 2001 From: Reed Davenport Date: Sat, 26 Feb 2022 22:45:20 -0500 Subject: [PATCH 3/5] initial writing checks documentation --- notes/writing_checks.md | 145 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 notes/writing_checks.md diff --git a/notes/writing_checks.md b/notes/writing_checks.md new file mode 100644 index 0000000..933ae4a --- /dev/null +++ b/notes/writing_checks.md @@ -0,0 +1,145 @@ +# Writing HypeTrack Checks + +HypeTrack checks are built to be as simple as possible to implement. + +## Import libraries + +In most cases, you'll need these at the top of your new check: +```ts +import axios from 'axios' +import debug from '../utils/debug.js' +import { tweet } from '../utils/twitter.js' +import { postToDiscord } from '../utils/discord.js' +import { tg } from '../utils/telegram.js' +import { get, set } from '../utils/db2.js' +import type { HTCheckConfig } from '../types/HTCheckConfig.type.js' +``` + +Explanation for each import: +* `axios` + * Used for HTTP requests. (This will be changed eventually when Node.js gets native fetch support.) +* `../utils/debug.js` + * Used for visual debugging. This is the script that handles the base namespace for tracker's debug logs. +* `../utils/twitter.js` + * Helper script for tweeting. Handles the checks for tweets being too long as well. +* `../utils/discord.js` + * Helper script for posting to Discord. +* `../utils/telegram.js` + * Helper script for posting to Telegram. +* `../utils/db2.js` + * This one is critical! It handles writing and fetching data from the in-memory database (DB2.) +* `../types/HTCheckConfig.type.js` + * Used as a type for the `config` constant. + +## Set up config + +The `HTCheckConfig` type has types for telling the `socials` function where to post messages. + +You write the config like this: +```ts +const config: HTCheckConfig { + sendToDiscord: true, + sendToTelegram: true, + sendToTwitter: true +} +``` + +If you don't want the check to initiate a message to a specific service, change the specific value to `false`. + +In the event you need to do a one-off config option, you can handle it by making an inline [intersection type](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types). +```ts +const config: HTCheckConfig & { sendToMastodon: boolean } { + sendToDiscord: true, + sendToTelegram: true, + sendToTwitter: true, + sendToMastodon: true +} +``` + +## Set up messages + +While not strictly required, first-party HypeTrack checks use this construct to make it easier to change what is posted in certain places. + +It's easy, just make a constant called `messages` and set up your messages like this: +```ts +const messages = { + stringA: "Didn't plan to never land", + stringB: (arbitraryParam: string) => `Just never thought that we could ${arbitraryParam}` +} +``` + +For stringA, you'd reference it as `messages.stringA`. For stringB, you'll have to pass a value for arbitraryParam, so this would work: `messages.stringB("drown")`. + +> Note that because `arbitraryParam` is strictly typed as a string, you can only pass strings to it. This means that `messages.stringB(42)` wouldn't work. + +## Define socials function + +The `socials` function should be an asynchronous function that returns nothing. So the definition of it should be something like this: +```ts +async function socials (anyParametersNeededHere: any): void { + // ... +} +``` + +You'd use the options defined in `config` to determine what the socials function needs to do. + +### Posting to Discord + +You'd normally use the `postToDiscord` function exported from `../utils/discord.js`, but the full Discord.js client is exported as `client` in the event that embeds need to be made. + +```ts +if (config.sendToDiscord) { + await postToDiscord('This will be posted to Discord.') + + // Alternatively: + await client.send('This will be posted to Discord.') +} +``` + +### Posting to Telegram + +Use the `tg` function. + +```ts +if (config.sendToTelegram) { + await tg('This will be posted to Telegram.') +} +``` + +### Posting to Twitter + +Use the `tweet` function. + +> Note that you don't have to check how long the text you're tweeting is. The `tweet` function will throw if your text is too long. + +```ts +if (config.sendToTwitter) { + await tweet('This will be posted to Twitter.') +} +``` + +## Defining the check function + +The check function is asynchronous and returns nothing, so you'd do this: +```ts +async function check (anyParametersNeeded: any): void { + // ... +} +``` + +### Checking if a key exists in DB2 + +Most tracker checks use this pattern to check if a key exists in DB2: +```ts +const data = await get('someKeyHere') + +if (typeof data === 'undefined') { + // Write a default value. + await set('someKeyHere', 'abc') + + // Return and come back next time + return +} +``` + +The `` part of the statement tells DB2 how to get and write the data to DB2. \ No newline at end of file From 342437f62a74e4afd44964efebdc969d35f9390d Mon Sep 17 00:00:00 2001 From: Reed Davenport Date: Sat, 26 Feb 2022 22:54:07 -0500 Subject: [PATCH 4/5] changes to writing_checks docs --- notes/writing_checks.md | 5 +++++ start.sh | 2 ++ 2 files changed, 7 insertions(+) diff --git a/notes/writing_checks.md b/notes/writing_checks.md index 933ae4a..d1d37b0 100644 --- a/notes/writing_checks.md +++ b/notes/writing_checks.md @@ -127,6 +127,11 @@ async function check (anyParametersNeeded: any): void { } ``` +A couple things you should do in your checks: +* **Extend out the debug scope and assign it to a constant called `d`.** + * `const d = debug.extend("check")` +* Wrap your code in a try-catch block so you can handle errors. + ### Checking if a key exists in DB2 Most tracker checks use this pattern to check if a key exists in DB2: diff --git a/start.sh b/start.sh index 1c47b17..7092cf5 100755 --- a/start.sh +++ b/start.sh @@ -7,4 +7,6 @@ if [ ! -d "$BUILD_DIR/" ]; then exit 1 fi +# Alternatively, you can remove the -db2 from this argument list. +# The only reason it is here is because DB2 is especially noisy. DEBUG=*,-follow-redirects,-telegraf:client,-db2 node dist/index From 0fc65da5f899c6a5bd9a02ef1d99693168b77335 Mon Sep 17 00:00:00 2001 From: Reed Davenport Date: Thu, 17 Mar 2022 17:12:48 -0400 Subject: [PATCH 5/5] Add key generator for stream check --- src/checks/stream.check.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/checks/stream.check.ts b/src/checks/stream.check.ts index 84876d2..3d481f4 100644 --- a/src/checks/stream.check.ts +++ b/src/checks/stream.check.ts @@ -12,6 +12,8 @@ import { tg } from '../utils/telegram.js' import { get, set } from '../utils/db2.js' import type { HTCheckConfig } from '../types/HTCheckConfig.type.js' +const key = (playlist: string) => `streamLive_${playlist}` + const config: HTCheckConfig = { sendToDiscord: true, sendToTelegram: true, @@ -43,9 +45,10 @@ async function socials (playlist: string, streamLive: boolean) { async function check(playlist: string) { const d = debug.extend(playlist) + const db2Key = key(playlist) // Get the already existing streamLive entry in our in-memory database. - const streamLive = await get(`streamLive_${playlist}`) + const streamLive = await get(db2Key) try { d('Attempting request to %s.m3u8...', playlist) @@ -59,13 +62,13 @@ async function check(playlist: string) { } // Set streamLive for this playlist. - await set(`streamLive_${playlist}`, true) + await set(db2Key, true) await socials(playlist, true) } catch (error: any) { d('%s is not live!', playlist) - await set(`streamLive_${playlist}`, false) + await set(db2Key, false) if (streamLive === true) { // The stream has gone offline since our last check.