diff --git a/flake.nix b/flake.nix index df941f9..9b8d655 100644 --- a/flake.nix +++ b/flake.nix @@ -37,7 +37,7 @@ shellHook = '' ${config.pre-commit.installationScript} ''; - packages = with pkgs; [nodePackages.pnpm]; + packages = with pkgs; [nodePackages.pnpm redis]; }; formatter = pkgs.alejandra; }; diff --git a/package.json b/package.json index 1fd726d..5ec4f53 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "discord.js": "14.14.1", "just-random": "3.2.0", "kleur": "4.1.5", + "redis": "4.6.10", "tsx": "4.1.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e48f8ba..dd97a44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: kleur: specifier: 4.1.5 version: 4.1.5 + redis: + specifier: 4.6.10 + version: 4.6.10 tsx: specifier: 4.1.1 version: 4.1.1 @@ -403,6 +406,55 @@ packages: fastq: 1.15.0 dev: true + /@redis/bloom@1.2.0(@redis/client@1.5.11): + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.11 + dev: false + + /@redis/client@1.5.11: + resolution: {integrity: sha512-cV7yHcOAtNQ5x/yQl7Yw1xf53kO0FNDTdDU6bFIMbW6ljB7U7ns0YRM+QIkpoqTAt6zK5k9Fq0QWlUbLcq9AvA==} + engines: {node: '>=14'} + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + dev: false + + /@redis/graph@1.1.0(@redis/client@1.5.11): + resolution: {integrity: sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.11 + dev: false + + /@redis/json@1.0.6(@redis/client@1.5.11): + resolution: {integrity: sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.11 + dev: false + + /@redis/search@1.1.5(@redis/client@1.5.11): + resolution: {integrity: sha512-hPP8w7GfGsbtYEJdn4n7nXa6xt6hVZnnDktKW4ArMaFQ/m/aR7eFvsLQmG/mn1Upq99btPJk+F27IQ2dYpCoUg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.11 + dev: false + + /@redis/time-series@1.0.5(@redis/client@1.5.11): + resolution: {integrity: sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.11 + dev: false + /@sapphire/async-queue@1.5.0: resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -665,6 +717,11 @@ packages: supports-color: 7.2.0 dev: true + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + dev: false + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -966,6 +1023,11 @@ packages: dev: false optional: true + /generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + dev: false + /get-tsconfig@4.7.2: resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} dependencies: @@ -1281,6 +1343,17 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /redis@4.6.10: + resolution: {integrity: sha512-mmbyhuKgDiJ5TWUhiKhBssz+mjsuSI/lSZNPI9QvZOYzWvYGejtb+W3RlDDf8LD6Bdl5/mZeG8O1feUGhXTxEg==} + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.5.11) + '@redis/client': 1.5.11 + '@redis/graph': 1.1.0(@redis/client@1.5.11) + '@redis/json': 1.0.6(@redis/client@1.5.11) + '@redis/search': 1.1.5(@redis/client@1.5.11) + '@redis/time-series': 1.0.5(@redis/client@1.5.11) + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1482,7 +1555,6 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} diff --git a/src/index.ts b/src/index.ts index e33afd3..f3f344b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,11 @@ import { Events, } from 'discord.js'; import { reuploadCommands } from './_reupload'; +import { + connect as connectStorage, + isUserPlural, + storeUserPlurality, +} from './storage'; import * as BuildConfig from './constants'; import { parseLog } from './logs'; @@ -26,7 +31,11 @@ import { sayCommand } from './commands/say'; import random from 'just-random'; import { green, bold, yellow, cyan } from 'kleur/colors'; import 'dotenv/config'; -import { proxied } from './utils/pluralKit'; +import { + fetchPluralKitMessage, + isMessageProxied, + pkDelay, +} from './utils/pluralKit'; const client = new Client({ intents: [ @@ -89,7 +98,16 @@ client.once('ready', async () => { if (e.author === client.user) return; - if (await proxied(e)) return; + if (e.webhookId !== null) { + // defer PK detection + setTimeout(async () => { + const pkMessage = await fetchPluralKitMessage(e); + if (pkMessage !== null) storeUserPlurality(pkMessage.sender); + }, pkDelay); + } + + if ((await isUserPlural(e.author.id)) && (await isMessageProxied(e))) + return; if (e.cleanContent.match(BuildConfig.ETA_REGEX)) { await e.reply( @@ -196,6 +214,7 @@ client.on(Events.ThreadCreate, async (channel) => { reuploadCommands() .then(() => { + connectStorage(); client.login(process.env.DISCORD_TOKEN); }) .catch((e) => { diff --git a/src/storage.ts b/src/storage.ts new file mode 100644 index 0000000..c3b40c9 --- /dev/null +++ b/src/storage.ts @@ -0,0 +1,22 @@ +import { createClient } from 'redis'; + +export const client = createClient({ + url: process.env.REDIS_URL || 'redis://localhost:6379', +}); + +export const storeUserPlurality = async (userId: string) => { + // Just store some value. We only care about the presence of this key + await client + .multi() + .set(`user:${userId}:pk`, '0') + .expire(`user:${userId}:pk`, 7 * 24 * 60 * 60) + .exec(); +}; + +export const isUserPlural = async (userId: string) => { + return (await client.exists(`user:${userId}:pk`)) > 0; +}; + +export const connect = () => { + client.connect(); +}; diff --git a/src/utils/pluralKit.ts b/src/utils/pluralKit.ts index 7753a95..cb2f28c 100644 --- a/src/utils/pluralKit.ts +++ b/src/utils/pluralKit.ts @@ -1,10 +1,22 @@ -import { Message } from "discord.js"; +import { Message } from 'discord.js'; -export async function proxied(message: Message): Promise { - if (message.webhookId !== null) - return false; - - await new Promise(resolve => setTimeout(resolve, 300)); - const response = await fetch(`https://api.pluralkit.me/v2/messages/${message.id}`); - return response.ok; +interface PkMessage { + sender: string; +} + +export const pkDelay = 500; + +export async function fetchPluralKitMessage(message: Message) { + const response = await fetch( + `https://api.pluralkit.me/v2/messages/${message.id}` + ); + + if (!response.ok) return null; + + return (await response.json()) as PkMessage; +} + +export async function isMessageProxied(message: Message) { + await new Promise((resolve) => setTimeout(resolve, pkDelay)); + return (await fetchPluralKitMessage(message)) !== null; }