From c31ea7874dac7b3e4f1ddf29efcb77c1bdad9f25 Mon Sep 17 00:00:00 2001 From: Mladen Brankovic Date: Fri, 6 Oct 2023 20:29:44 +0200 Subject: [PATCH] chore: delete deprecated TypeScript source --- ts/.env.example | 6 - ts/.gitignore | 199 --------- ts/.vscode/launch.json | 19 - ts/.vscode/settings.json | 7 - ts/deno.json | 9 - ts/denon.json | 6 - ts/docker-compose.yml | 11 - ts/dockerfile | 8 - ts/paths.json | 17 - ts/src/bot.ts | 13 - ts/src/commands/about.ts | 61 --- ts/src/commands/config.ts | 69 --- ts/src/commands/contribute.ts | 21 - ts/src/commands/help.ts | 23 - ts/src/commands/ping.ts | 22 - ts/src/commands/ranked.ts | 468 --------------------- ts/src/commands/rtp.ts | 90 ---- ts/src/commands/tracking.ts | 68 --- ts/src/env.ts | 39 -- ts/src/error/http-error.ts | 13 - ts/src/error/invocation-error.ts | 9 - ts/src/error/pattern-error.ts | 9 - ts/src/error/unimplemented-error.ts | 9 - ts/src/events/interaction-create.ts | 29 -- ts/src/events/presence-update.ts | 28 -- ts/src/events/reaction-add.ts | 76 ---- ts/src/events/reaction-remove.ts | 39 -- ts/src/events/ready.ts | 22 - ts/src/interfaces/activity-tracker-data.ts | 8 - ts/src/interfaces/command.ts | 27 -- ts/src/interfaces/discord-bot.ts | 15 - ts/src/interfaces/discord-user.ts | 41 -- ts/src/interfaces/interaction-reply.ts | 8 - ts/src/interfaces/lunaro-match.ts | 47 --- ts/src/interfaces/lunaro-player.ts | 20 - ts/src/interfaces/pending-match.ts | 37 -- ts/src/main.ts | 24 -- ts/src/util/activity-tracking.ts | 78 ---- ts/src/util/commands.ts | 26 -- ts/src/util/creators.ts | 24 -- ts/src/util/data.ts | 170 -------- ts/src/util/interactions.ts | 24 -- ts/src/util/logger.ts | 77 ---- ts/src/util/match-approval.ts | 145 ------- ts/src/util/messages.ts | 34 -- ts/src/util/rank-api.ts | 213 ---------- ts/src/util/rtp.ts | 33 -- ts/src/util/time.ts | 13 - 48 files changed, 2454 deletions(-) delete mode 100644 ts/.env.example delete mode 100644 ts/.gitignore delete mode 100644 ts/.vscode/launch.json delete mode 100644 ts/.vscode/settings.json delete mode 100644 ts/deno.json delete mode 100644 ts/denon.json delete mode 100644 ts/docker-compose.yml delete mode 100644 ts/dockerfile delete mode 100644 ts/paths.json delete mode 100644 ts/src/bot.ts delete mode 100644 ts/src/commands/about.ts delete mode 100644 ts/src/commands/config.ts delete mode 100644 ts/src/commands/contribute.ts delete mode 100644 ts/src/commands/help.ts delete mode 100644 ts/src/commands/ping.ts delete mode 100644 ts/src/commands/ranked.ts delete mode 100644 ts/src/commands/rtp.ts delete mode 100644 ts/src/commands/tracking.ts delete mode 100644 ts/src/env.ts delete mode 100644 ts/src/error/http-error.ts delete mode 100644 ts/src/error/invocation-error.ts delete mode 100644 ts/src/error/pattern-error.ts delete mode 100644 ts/src/error/unimplemented-error.ts delete mode 100644 ts/src/events/interaction-create.ts delete mode 100644 ts/src/events/presence-update.ts delete mode 100644 ts/src/events/reaction-add.ts delete mode 100644 ts/src/events/reaction-remove.ts delete mode 100644 ts/src/events/ready.ts delete mode 100644 ts/src/interfaces/activity-tracker-data.ts delete mode 100644 ts/src/interfaces/command.ts delete mode 100644 ts/src/interfaces/discord-bot.ts delete mode 100644 ts/src/interfaces/discord-user.ts delete mode 100644 ts/src/interfaces/interaction-reply.ts delete mode 100644 ts/src/interfaces/lunaro-match.ts delete mode 100644 ts/src/interfaces/lunaro-player.ts delete mode 100644 ts/src/interfaces/pending-match.ts delete mode 100644 ts/src/main.ts delete mode 100644 ts/src/util/activity-tracking.ts delete mode 100644 ts/src/util/commands.ts delete mode 100644 ts/src/util/creators.ts delete mode 100644 ts/src/util/data.ts delete mode 100644 ts/src/util/interactions.ts delete mode 100644 ts/src/util/logger.ts delete mode 100644 ts/src/util/match-approval.ts delete mode 100644 ts/src/util/messages.ts delete mode 100644 ts/src/util/rank-api.ts delete mode 100644 ts/src/util/rtp.ts delete mode 100644 ts/src/util/time.ts diff --git a/ts/.env.example b/ts/.env.example deleted file mode 100644 index f9a6e55..0000000 --- a/ts/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -DISCORD_TOKEN=U1Tr4.53cR3t-T0k3N -BOT_ID=1234567890987654321 -HOME_GUILD_ID=1234567890987654321 -RTP_ROLE_ID=1234567890987654321 -RANK_API_URL=https://example.com -RANK_API_TOKEN=U1Tr4.53cR3t-T0k3 diff --git a/ts/.gitignore b/ts/.gitignore deleted file mode 100644 index 8e19b61..0000000 --- a/ts/.gitignore +++ /dev/null @@ -1,199 +0,0 @@ -# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig - -# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,dotenv,node -# Edit at https://www.toptal.com/developers/gitignore?templates=windows,visualstudiocode,dotenv,node - -### dotenv ### -.env - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -### Node Patch ### -# Serverless Webpack directories -.webpack/ - -# Optional stylelint cache - -# SvelteKit build / generate output -.svelte-kit - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - -# Support for Project snippet scope - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# End of https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,dotenv,node - -# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) - -data diff --git a/ts/.vscode/launch.json b/ts/.vscode/launch.json deleted file mode 100644 index 7401a90..0000000 --- a/ts/.vscode/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "request": "launch", - "name": "Deno: Debug", - "type": "node", - "program": "${workspaceFolder}/src/main.ts", - "cwd": "${workspaceFolder}", - "runtimeExecutable": "deno", - "runtimeArgs": [ - "run", - "--inspect-brk", - "--allow-all" - ], - "attachSimplePort": 9229 - } - ] -} diff --git a/ts/.vscode/settings.json b/ts/.vscode/settings.json deleted file mode 100644 index 8fc5e9e..0000000 --- a/ts/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "deno.enable": true, - "deno.lint": true, - "deno.unstable": true, - "deno.suggest.autoImports": true, - "deno.importMap": "paths.json" -} diff --git a/ts/deno.json b/ts/deno.json deleted file mode 100644 index 16e34e9..0000000 --- a/ts/deno.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "importMap": "paths.json", - - "tasks": { - "cache": "deno cache src/**/*.ts", - "start": "deno run --allow-all --unstable src/main.ts", - "start:watch": "denon run --allow-all --unstable src/main.ts" - } -} diff --git a/ts/denon.json b/ts/denon.json deleted file mode 100644 index e6bf1d7..0000000 --- a/ts/denon.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "watcher": { - "legacy": true, - "exts": ["ts"] - } -} diff --git a/ts/docker-compose.yml b/ts/docker-compose.yml deleted file mode 100644 index 0b302a5..0000000 --- a/ts/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: "3" - -services: - lunaro-manager: - image: "lunaro-manager" - build: "." - hostname: "lunaro-manager" - container_name: "lunaro-manager" - restart: "unless-stopped" - volumes: - - "./data:/mnt/app/data" diff --git a/ts/dockerfile b/ts/dockerfile deleted file mode 100644 index 3543fda..0000000 --- a/ts/dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM denoland/deno:1.21.2 - -WORKDIR /mnt/app - -COPY src src -COPY deno.json paths.json .env ./ - -CMD [ "task", "start" ] diff --git a/ts/paths.json b/ts/paths.json deleted file mode 100644 index 049e2be..0000000 --- a/ts/paths.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "imports": { - "/": "./", - "./": "./", - - ":src/": "./src/", - ":commands/": "./src/commands/", - ":events/": "./src/events/", - ":interfaces/": "./src/interfaces/", - ":util/": "./src/util/", - ":error/": "./src/error/", - - "discordeno": "https://deno.land/x/discordeno@13.0.0-rc39/mod.ts", - "dotenv": "https://deno.land/x/dotenv@v3.2.0/mod.ts", - "date-fns": "https://esm.sh/date-fns@2.29.1" - } -} diff --git a/ts/src/bot.ts b/ts/src/bot.ts deleted file mode 100644 index ebeff7a..0000000 --- a/ts/src/bot.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BOT_ID, DISCORD_TOKEN } from ':src/env.ts'; -import { createDiscordBot } from ':util/creators.ts'; -import { Collection } from 'discordeno'; - -/** Primary instance of the bot, shared across the project. */ -export const bot = createDiscordBot({ - botId: BOT_ID, - token: DISCORD_TOKEN, - intents: ['Guilds', 'GuildMembers', 'GuildPresences', 'GuildMessageReactions'], - - commands: new Collection(), - events: {}, -}); diff --git a/ts/src/commands/about.ts b/ts/src/commands/about.ts deleted file mode 100644 index 6f5ae2c..0000000 --- a/ts/src/commands/about.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { readyTimestamp } from ':events/ready.ts'; -import { bot } from ':src/bot.ts'; -import { HOME_GUILD_ID } from ':src/env.ts'; -import { createCommand } from ':util/creators.ts'; -import { readActivityTrackingConfig } from ':util/data.ts'; -import { replyToInteraction } from ':util/interactions.ts'; - -import { formatDistanceToNow, formatDuration, intervalToDuration } from 'date-fns'; -import { ApplicationCommandTypes, DISCORDENO_VERSION } from 'discordeno'; - -createCommand({ - name: 'about', - description: '๐Ÿ’ก View details about Lunaro Manager', - type: ApplicationCommandTypes.ChatInput, - - run: async (interaction) => { - const trackingData = readActivityTrackingConfig(); - const members = await bot.helpers.getMembers(HOME_GUILD_ID, {}); - const trackedMemberCount = members.size - trackingData.blocklist.length; - - const trackingString = trackingData.enabled - ? `๐Ÿ”Ž Tracking activity of ${trackedMemberCount} members` - : '๐Ÿ›‘ Activity tracking disabled'; - - const engineString = - `โš™ Deno v${Deno.version.deno}` + - ` + TypeScript v${Deno.version.typescript}` + - ` + discordeno v${DISCORDENO_VERSION}`; - - const commits = await fetch( - 'https://api.github.com/repos/imatpot/lunaro-manager/commits?per_page=1' - ).then((commits) => commits.json()); - - const lastUpdatedString = `๐Ÿ›  Last updated ${formatDistanceToNow( - Date.parse(commits[0].commit.committer.date), - { - addSuffix: true, - } - )}`; - - const uptimeString = `โฑ Running for ${formatDuration( - intervalToDuration({ - start: readyTimestamp, - end: Date.now(), - }) - )}`; - - const aboutString = [ - trackingString, - '', - engineString, - lastUpdatedString, - '', - uptimeString, - ].join('\n'); - - await replyToInteraction(interaction, { - content: aboutString, - }); - }, -}); diff --git a/ts/src/commands/config.ts b/ts/src/commands/config.ts deleted file mode 100644 index cb891a1..0000000 --- a/ts/src/commands/config.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { InvocationError } from ':error/invocation-error.ts'; -import { SubcommandMap } from ':interfaces/command.ts'; -import { disableActivityTracking, enableActivityTracking } from ':util/activity-tracking.ts'; -import { getSubcommand } from ':util/commands.ts'; -import { createCommand } from ':util/creators.ts'; -import { replyToInteraction } from ':util/interactions.ts'; -import { ApplicationCommandOptionTypes, ApplicationCommandTypes, Interaction } from 'discordeno'; - -createCommand({ - name: 'config', - description: '๐Ÿค” Configure Lunaro Manager', - type: ApplicationCommandTypes.ChatInput, - - options: [ - { - name: 'activity-tracking', - description: '๐Ÿ”Ž Enable or disable activity tracking', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - options: [ - { - name: 'enabled', - description: 'Whether tracking should be enabled', - type: ApplicationCommandOptionTypes.Boolean, - required: true, - }, - ], - }, - ], - - run: async (interaction) => { - const subcommand = getSubcommand(interaction); - - if (!subcommand) { - throw new InvocationError('Cannot execute /config without a subcommand'); - } - - const subcommands: SubcommandMap = { - 'activity-tracking': configActivityTracking, - }; - - await subcommands[subcommand](interaction); - }, -}); - -/** Function for `/config activity-tracking`. */ -const configActivityTracking = async (interaction: Interaction) => { - const shouldEnable = interaction.data?.options - ?.find((option) => option.name === 'activity-tracking') - ?.options?.find((option) => option.name === 'enabled')?.value as boolean; - - if (shouldEnable === undefined) { - throw new InvocationError('Missing option `enabled`'); - } - - if (shouldEnable) { - enableActivityTracking(); - - await replyToInteraction(interaction, { - content: `๐Ÿ”Ž Activity tracking is now enabled`, - }); - } else { - disableActivityTracking(); - - await replyToInteraction(interaction, { - content: `๐Ÿ›‘ Activity tracking is now disabled`, - }); - } -}; diff --git a/ts/src/commands/contribute.ts b/ts/src/commands/contribute.ts deleted file mode 100644 index 00abd6b..0000000 --- a/ts/src/commands/contribute.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createCommand } from ':util/creators.ts'; -import { replyToInteraction } from ':util/interactions.ts'; - -import { ApplicationCommandTypes } from 'discordeno'; - -createCommand({ - name: 'contribute', - description: "๐Ÿค Let's work together", - type: ApplicationCommandTypes.ChatInput, - - run: async (interaction) => { - const contributionString = [ - `๐Ÿค Feel like helping out? Create an issue or pull request on GitHub:`, - 'https://github.com/imatpot/lunaro-manager', - ].join('\n'); - - await replyToInteraction(interaction, { - content: contributionString, - }); - }, -}); diff --git a/ts/src/commands/help.ts b/ts/src/commands/help.ts deleted file mode 100644 index 39d9a2a..0000000 --- a/ts/src/commands/help.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createCommand } from ':util/creators.ts'; -import { replyToInteraction } from ':util/interactions.ts'; - -import { ApplicationCommandTypes } from 'discordeno'; - -createCommand({ - name: 'help', - description: 'โ“ Learn how to use Lunaro Manager', - type: ApplicationCommandTypes.ChatInput, - - run: async (interaction) => { - const helpString = [ - "Swazdo-lah, surah! I'm the Lunaro Manager, and my job is to help you with all things Lunaro.", - '', - 'Type `/` and use the sidebar to explore my capabilites, or check out the *Usage* section on my GitHub page:', - 'https://github.com/imatpot/lunaro-manager#usage', - ].join('\n'); - - await replyToInteraction(interaction, { - content: helpString, - }); - }, -}); diff --git a/ts/src/commands/ping.ts b/ts/src/commands/ping.ts deleted file mode 100644 index 95c9063..0000000 --- a/ts/src/commands/ping.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createCommand } from ':util/creators.ts'; -import { replyToInteraction } from ':util/interactions.ts'; -import { log } from ':util/logger.ts'; -import { snowflakeToTimestamp } from ':util/time.ts'; - -import { ApplicationCommandTypes } from 'discordeno'; - -createCommand({ - name: 'ping', - description: '๐Ÿ“ Check connection latency', - type: ApplicationCommandTypes.ChatInput, - - run: async (interaction) => { - const ping = Date.now() - snowflakeToTimestamp(interaction.id); - - await replyToInteraction(interaction, { - content: `๐Ÿ“ Current ping is ${ping}ms`, - }); - - log(`Ping is ${ping}ms`); - }, -}); diff --git a/ts/src/commands/ranked.ts b/ts/src/commands/ranked.ts deleted file mode 100644 index 8526d6b..0000000 --- a/ts/src/commands/ranked.ts +++ /dev/null @@ -1,468 +0,0 @@ -import { HttpError } from ':error/http-error.ts'; -import { InvocationError } from ':error/invocation-error.ts'; -import { SubcommandMap } from ':interfaces/command.ts'; -import { parseDiscordUsername } from ':interfaces/discord-user.ts'; -import { NewLunaroMatch } from ':interfaces/lunaro-match.ts'; -import { NewLunaroPlayer } from ':interfaces/lunaro-player.ts'; -import { PendingMatch } from ':interfaces/pending-match.ts'; -import { bot } from ':src/bot.ts'; -import { HOME_GUILD_ID } from ':src/env.ts'; -import { getSubcommand } from ':util/commands.ts'; -import { createCommand } from ':util/creators.ts'; -import { addPendingMatch } from ':util/data.ts'; -import { replyToInteraction } from ':util/interactions.ts'; -import { pendingMatchApprovalMessage } from ':util/match-approval.ts'; -import { sendMessageInChannel } from ':util/messages.ts'; -import { - createPlayer, - getAllPlayers, - getPlayerByNameOrId, - leagueNameToRank, - rankToLeagueName -} from ':util/rank-api.ts'; -import { - ApplicationCommandOptionTypes, - ApplicationCommandTypes, - Attachment, - Interaction -} from 'discordeno'; - -createCommand({ - name: 'ranked', - description: '๐Ÿ† Submit and view ranking data', - type: ApplicationCommandTypes.ChatInput, - - options: [ - { - name: 'view', - description: "๐Ÿ… View a player's ranking", - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - options: [ - { - name: 'player', - description: 'The player whose ranking you want to view. Defaults to yourself', - type: ApplicationCommandOptionTypes.String, - required: false, - }, - ], - }, - { - name: 'top', - description: '๐Ÿ† View the top rankings', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - options: [ - { - name: 'players', - description: 'The amount of players to show. Defaults to 10, maxes out at 30', - type: ApplicationCommandOptionTypes.Number, - required: false, - }, - { - name: 'offset', - description: 'The amount of players to skip. Defaults to 0', - type: ApplicationCommandOptionTypes.Number, - required: false, - }, - ], - }, - { - name: 'register', - description: 'โœ Register for ranked matches', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - }, - { - name: 'submit', - description: '๐Ÿฅ Submit a ranked match', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - options: [ - { - name: 'player-a', - description: 'Player A', - type: ApplicationCommandOptionTypes.User, - required: true, - }, - { - name: 'player-b', - description: 'Player B', - type: ApplicationCommandOptionTypes.User, - required: true, - }, - { - name: 'player-a-ping', - description: 'Average ping of player A. For host, enter 0', - type: ApplicationCommandOptionTypes.Number, - required: true, - }, - { - name: 'player-b-ping', - description: 'Average ping of player B. For host, enter 0', - type: ApplicationCommandOptionTypes.Number, - required: true, - }, - { - name: 'player-a-score', - description: 'Scored points of the host', - type: ApplicationCommandOptionTypes.Number, - required: true, - }, - { - name: 'player-b-score', - description: 'Scored points of the client', - type: ApplicationCommandOptionTypes.Number, - required: true, - }, - { - name: 'evidence', - description: 'Screenshot of the match result', - type: ApplicationCommandOptionTypes.Attachment, - required: false, - }, - ], - }, - ], - - run: async (interaction) => { - const subcommand = getSubcommand(interaction); - - if (!subcommand) { - throw new InvocationError('Cannot execute /rtp without a subcommand'); - } - - const subcommands: SubcommandMap = { - view: rankedView, - top: rankedTop, - register: rankedRegister, - submit: rankedSubmit, - }; - - await subcommands[subcommand](interaction); - }, -}); - -/** - * Generates a string containing a medal and a placement number. - * - * @param placement to be generated - * @param spaces between the medal and the placement - * @returns the genenerated string - */ -const generatePlacementString = (placement: number, spaces = 1): string => { - const medals: { - [place: number]: string; - } = { - 1: '๐Ÿฅ‡', - 2: '๐Ÿฅˆ', - 3: '๐Ÿฅ‰', - }; - - let gap = ''; - - for (let i = 0; i < spaces; i++) { - gap += ' '; - } - - const medal: string = medals[placement] || '๐Ÿ…'; - const placementString = [1, 2, 3].includes(placement) - ? `${medal}` - : `${medal}${gap}#${placement}`; - - return placementString; -}; - -/** Function for `/ranked view`. */ -const rankedView = async (interaction: Interaction) => { - let username = interaction.data?.options - ?.find((option) => option.name === 'view') - ?.options?.find((option) => option.name === 'player')?.value as string; - - if (username === undefined) { - const player = await bot.helpers.getMember(HOME_GUILD_ID, interaction.user.id); - const nick = player.nick || interaction.user.username; - const user = parseDiscordUsername(nick); - - username = user.username; - } - - const playerData = await getPlayerByNameOrId(username); - const allPlayerData = await getAllPlayers(); - - const locationInAllPlayerData = allPlayerData.find((a) => a.name == playerData.name)!; - const index = allPlayerData.indexOf(locationInAllPlayerData); - - const placement = generatePlacementString(index + 1, 2); - placement.replace('#0', 'Unknown placement'); - - await replyToInteraction(interaction, { - content: [ - `๐Ÿ‘ค ${playerData.name}`, - `๐Ÿ† ${rankToLeagueName(playerData.rank)}`, - `${placement} with ${playerData.rank} points`, - ].join('\n'), - }); -}; - -/** Function for `/ranked top`. */ -const rankedTop = async (interaction: Interaction) => { - const playerCount = - (interaction.data?.options - ?.find((option) => option.name === 'top') - ?.options?.find((option) => option.name === 'players')?.value as number) || 10; - - const offset = - (interaction.data?.options - ?.find((option) => option.name === 'top') - ?.options?.find((option) => option.name === 'offset')?.value as number) || 0; - - if (playerCount < 1) { - throw new RangeError('Parameter `players` must be at least 1'); - } - - if (playerCount > 30) { - throw new RangeError('Parameter `players` must be less than 30'); - } - - if (offset < 0) { - throw new RangeError('Parameter `offset` cannot be negative'); - } - - const allPlayers = await getAllPlayers(); - - allPlayers.splice(0, offset); - - const topPlayers = allPlayers.splice(0, playerCount); - - if (!allPlayers) { - throw new InvocationError('No list of players match given criteria'); - } - - const output: string[] = []; - - for (const [index, player] of topPlayers.entries()) { - const placement = index + offset + 1; - const placementString = generatePlacementString(placement); - - output.push(`${placementString} ${player.name} with ${player.rank} points`); - - if (placement === 3) { - output.push(''); - } - } - - await replyToInteraction(interaction, { - content: output.join('\n'), - }); -}; - -/** Function for `/ranked register`. */ -const rankedRegister = async (interaction: Interaction) => { - const user = await bot.helpers.getMember(HOME_GUILD_ID, interaction.user.id); - const discordUser = parseDiscordUsername(user.nick || interaction.user.username); - const username = discordUser.username; - - let exists = true; - - try { - await getPlayerByNameOrId(username); - } catch (error) { - if (error instanceof HttpError && error.code === 404) { - exists = false; - } else { - throw new InvocationError( - `Failed to check for existing players with the name "${username}"` - ); - } - } - - if (exists) { - await replyToInteraction(interaction, { - content: '๐Ÿ† You are already signed up for ranked gameplay', - ephemeral: true, - }); - - return; - } - - let points = -1; - - const guild = await bot.helpers.getGuild(HOME_GUILD_ID); - - for (const roleId of user.roles) { - const role = guild.roles.get(roleId); - const rolePoints = leagueNameToRank(role?.name || ''); - - if (rolePoints > points) { - points = rolePoints; - } - } - - if (points === -1) { - points = leagueNameToRank('neophyte'); - } - - const newPlayer: NewLunaroPlayer = { - name: username, - rank: points, - }; - - await createPlayer(newPlayer); - - await replyToInteraction(interaction, { - content: '๐Ÿ† Successfully signed you up to ranked gameplay', - }); -}; - -/** Function for `/ranked submit`. */ -const rankedSubmit = async (interaction: Interaction) => { - const playerAId = interaction.data?.options - ?.find((option) => option.name === 'submit') - ?.options?.find((option) => option.name === 'player-a')?.value as string; - - const playerBId = interaction.data?.options - ?.find((option) => option.name === 'submit') - ?.options?.find((option) => option.name === 'player-b')?.value as string; - - const playerAPing = interaction.data?.options - ?.find((option) => option.name === 'submit') - ?.options?.find((option) => option.name === 'player-a-ping')?.value as number; - - const playerBPing = interaction.data?.options - ?.find((option) => option.name === 'submit') - ?.options?.find((option) => option.name === 'player-b-ping')?.value as number; - - const playerAScore = interaction.data?.options - ?.find((option) => option.name === 'submit') - ?.options?.find((option) => option.name === 'player-a-score')?.value as number; - - const playerBScore = interaction.data?.options - ?.find((option) => option.name === 'submit') - ?.options?.find((option) => option.name === 'player-b-score')?.value as number; - - const evidenceId = interaction.data?.options - ?.find((option) => option.name === 'submit') - ?.options?.find((option) => option.name === 'evidence')?.value as string; - - if (playerAId === undefined) { - throw new InvocationError('Missing option `player-a`'); - } - - if (playerBId === undefined) { - throw new InvocationError('Missing option `player-b`'); - } - - if (playerAPing === undefined) { - throw new InvocationError('Missing option `player-a-ping`'); - } - - if (playerBPing === undefined) { - throw new InvocationError('Missing option `player-b-ping`'); - } - - if (playerAScore === undefined) { - throw new InvocationError('Missing option `player-a-score`'); - } - - if (playerBScore === undefined) { - throw new InvocationError('Missing option `player-b-score`'); - } - - if (playerAId === playerBId) { - throw new InvocationError('A match must be played between two distinct players'); - } - - if (playerAPing === 0 && playerBPing === 0) { - throw new RangeError('A match cannot have two hosts (ping = 0)'); - } - - if (playerAPing < 0 || playerAPing < 0) { - throw new RangeError('A player cannot negative ping'); - } - - if (playerAScore === 0 || playerBScore === 0) { - throw new RangeError( - 'Due to limitations of the way ranking is calculated, you unfortunately cannot submit a match result where either player scored zero points' - ); - } - - if (playerAScore < 0 || playerAScore < 0) { - throw new RangeError('A player cannot score a negative amount of points'); - } - - let evidence: Attachment | undefined; - - if (evidenceId !== undefined) { - evidence = interaction.data?.resolved?.attachments?.get(BigInt(evidenceId)); - - if (evidence === undefined) { - throw new InvocationError('Failed to fetch submitted attachment'); - } - } - - await replyToInteraction(interaction, { - content: 'โณ Preparing data for approval, please wait...', - ephemeral: true, - }); - - const userA = await bot.helpers.getMember(HOME_GUILD_ID, BigInt(playerAId)); - const userB = await bot.helpers.getMember(HOME_GUILD_ID, BigInt(playerBId)); - - const playerA = parseDiscordUsername(userA.nick); - const playerB = parseDiscordUsername(userB.nick); - - const match: NewLunaroMatch = { - player_a: playerA.username, - ping_a: playerAPing, - score_a: playerAScore, - - player_b: playerB.username, - ping_b: playerBPing, - score_b: playerBScore, - }; - - const playerAPingMessage = - match.ping_a === 0 ? 'as host' : `with around ${match.ping_a}ms ping`; - const playerBPingMessage = - match.ping_b === 0 ? 'as host' : `with around ${match.ping_b}ms ping`; - - const message = await sendMessageInChannel(interaction.channelId!, { - content: [ - '๐Ÿ† Ranked match summary', - '', - `<@${userA.id}> scored ${match.score_a} points ${playerAPingMessage}`, - `<@${userB.id}> scored ${match.score_b} points ${playerBPingMessage}`, - '', - pendingMatchApprovalMessage, - ].join('\n'), - - file: !evidence - ? undefined - : { - name: evidence.filename, - blob: await fetch(evidence.url).then((res) => res.blob()), - }, - }); - - await bot.helpers.addReaction(message.channelId, message.id, 'โœ…'); - await bot.helpers.addReaction(message.channelId, message.id, 'โŒ'); - - const pendingMatch: PendingMatch = { - status: { - required: [playerAId, playerBId], - approved: [], - boycotted: [], - }, - message: { - id: message.id.toString(), - channelId: message.channelId.toString(), - }, - submitter: interaction.user.id.toString(), - match, - }; - - addPendingMatch(pendingMatch); - - // TODO: delete ephemeral info about preparing data -}; diff --git a/ts/src/commands/rtp.ts b/ts/src/commands/rtp.ts deleted file mode 100644 index e7b13c0..0000000 --- a/ts/src/commands/rtp.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { InvocationError } from ':error/invocation-error.ts'; -import { SubcommandMap } from ':interfaces/command.ts'; -import { getSubcommand } from ':util/commands.ts'; -import { createCommand } from ':util/creators.ts'; -import { replyToInteraction } from ':util/interactions.ts'; -import { log } from ':util/logger.ts'; -import { addMemberToRTP, getRTPMembers, removeMemberFromRTP } from ':util/rtp.ts'; -import { ApplicationCommandOptionTypes, ApplicationCommandTypes, Interaction } from 'discordeno'; - -createCommand({ - name: 'rtp', - description: '๐Ÿฅ Manage RTP status', - type: ApplicationCommandTypes.ChatInput, - - options: [ - { - name: 'join', - description: '๐ŸŸข Join RTP', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - }, - { - name: 'leave', - description: 'โญ• Leave RTP', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - }, - { - name: 'info', - description: "๐Ÿ‘€ Check out who's available", - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - }, - ], - - run: async (interaction) => { - const subcommand = getSubcommand(interaction); - - if (!subcommand) { - throw new InvocationError('Cannot execute /rtp without a subcommand'); - } - - const subcommands: SubcommandMap = { - join: rtpJoin, - leave: rtpLeave, - info: rtpInfo, - }; - - await subcommands[subcommand](interaction); - }, -}); - -/** Function for `/rtp join`. */ -const rtpJoin = async (interaction: Interaction) => { - const member = interaction.member!; - await addMemberToRTP(member); - - const name = member.nick || interaction.user.username; - - await replyToInteraction(interaction, { - content: `๐ŸŸข ${name} is now available for Lunaro`, - }); -}; - -/** Function for `/rtp leave`. */ -const rtpLeave = async (interaction: Interaction) => { - const member = interaction.member!; - await removeMemberFromRTP(member); - - const name = member.nick || interaction.user.username; - - await replyToInteraction(interaction, { - content: `โญ• ${name} is no longer available for Lunaro`, - }); -}; - -/** Function for `/rtp info`. */ -const rtpInfo = async (interaction: Interaction) => { - const rtpMembers = await getRTPMembers(); - const rtpMemberCount = rtpMembers.length; - - const rtpMemberCountString = - rtpMemberCount === 1 ? 'There is 1 member' : `There are ${rtpMemberCount} members`; - - await replyToInteraction(interaction, { - content: `๐Ÿ‘€ ${rtpMemberCountString} available for Lunaro`, - }); - - log(`${rtpMemberCountString} with the RTP role`); -}; diff --git a/ts/src/commands/tracking.ts b/ts/src/commands/tracking.ts deleted file mode 100644 index 7a41fb0..0000000 --- a/ts/src/commands/tracking.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { InvocationError } from ':error/invocation-error.ts'; -import { SubcommandMap } from ':interfaces/command.ts'; -import { - addMemberToTrackingBlocklist, - removeMemberFromTrackingBlocklist -} from ':util/activity-tracking.ts'; -import { getSubcommand } from ':util/commands.ts'; -import { createCommand } from ':util/creators.ts'; -import { replyToInteraction } from ':util/interactions.ts'; -import { ApplicationCommandOptionTypes, ApplicationCommandTypes, Interaction } from 'discordeno'; - -createCommand({ - name: 'tracking', - description: '๐Ÿ”Ž Manage your tracking permissions', - type: ApplicationCommandTypes.ChatInput, - - options: [ - { - name: 'pause', - description: 'โ›” Pause activity tracking on your account', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - }, - { - name: 'resume', - description: 'โšก Resume activity tracking on your account', - type: ApplicationCommandOptionTypes.SubCommand, - required: false, - }, - ], - - run: async (interaction) => { - const subcommand = getSubcommand(interaction); - - if (!subcommand) { - throw new InvocationError('Cannot execute /rtp without a subcommand'); - } - - const subcommands: SubcommandMap = { - pause: trackingPause, - resume: trackingResume, - }; - - await subcommands[subcommand](interaction); - }, -}); - -/** Function for `/tracking pause`. */ -const trackingPause = async (interaction: Interaction) => { - const member = interaction.member!; - addMemberToTrackingBlocklist(member); - - await replyToInteraction(interaction, { - content: 'โ›” Paused activity tracking for your account', - ephemeral: true, - }); -}; - -/** Function for `/tracking resume`. */ -const trackingResume = async (interaction: Interaction) => { - const member = interaction.member!; - removeMemberFromTrackingBlocklist(member); - - await replyToInteraction(interaction, { - content: 'โšก Resumed activity tracking for your account', - ephemeral: true, - }); -}; diff --git a/ts/src/env.ts b/ts/src/env.ts deleted file mode 100644 index 8919ca3..0000000 --- a/ts/src/env.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { config } from 'dotenv'; - -const environment = config(); - -/** - * Token for connecting to the Discord API and client. - * Contains the value of the `DISCORD_TOKEN` environment variable. - */ -export const DISCORD_TOKEN: string = environment.DISCORD_TOKEN; - -/** - * ID of the bot's client account. - * Contains the value of the `BOT_ID` environment variable. - */ -export const BOT_ID = BigInt(environment.BOT_ID); - -/** - * ID of the guild in which the bot is supposed to be active. - * Contains the value of the `HOME_GUILD_ID` environment variable. - */ -export const HOME_GUILD_ID = BigInt(environment.HOME_GUILD_ID); - -/** - * ID of the role to give to users who enter the RTP (ready-to-play) state. - * Contains the value of the `RTP_ROLE_ID` environment variable. - */ -export const RTP_ROLE_ID = BigInt(environment.RTP_ROLE_ID); - -/** - * URL of the Lunaro ranking API. - * Contains the value of the `RANK_API_URL` environment variable. - */ -export const RANK_API_URL = environment.RANK_API_URL; - -/** - * Token to access POST-endpoints in the Lunaro ranking API. - * Contains the value of the `RANK_API_TOKEN` environment variable. - */ -export const RANK_API_TOKEN = environment.RANK_API_TOKEN; diff --git a/ts/src/error/http-error.ts b/ts/src/error/http-error.ts deleted file mode 100644 index 3984206..0000000 --- a/ts/src/error/http-error.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** Represents an HTTP error. */ -export class HttpError extends Error { - /** HTTP error code. */ - code: number; - - constructor(code: number, message: string) { - super(); - this.message = message; - this.code = code; - } - - toString = () => `HttpError: ${this.code}: ${this.message}`; -} diff --git a/ts/src/error/invocation-error.ts b/ts/src/error/invocation-error.ts deleted file mode 100644 index 3659f3d..0000000 --- a/ts/src/error/invocation-error.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** Represents an error while invoking a bot command. */ -export class InvocationError extends Error { - constructor(message: string) { - super(); - this.message = message; - } - - toString = () => `InvocationError: ${this.message}`; -} diff --git a/ts/src/error/pattern-error.ts b/ts/src/error/pattern-error.ts deleted file mode 100644 index 5d94376..0000000 --- a/ts/src/error/pattern-error.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** Represents an error in a given pattern. */ -export class PatternError extends Error { - constructor(message: string) { - super(); - this.message = message; - } - - toString = () => `PatternError: ${this.message}`; -} diff --git a/ts/src/error/unimplemented-error.ts b/ts/src/error/unimplemented-error.ts deleted file mode 100644 index e6c4300..0000000 --- a/ts/src/error/unimplemented-error.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** Represents an HTTP error. */ -export class UnimplementedError extends Error { - constructor(message: string) { - super(); - this.message = message; - } - - toString = () => `UnimplementedError: ${this.message}`; -} diff --git a/ts/src/events/interaction-create.ts b/ts/src/events/interaction-create.ts deleted file mode 100644 index 506aba5..0000000 --- a/ts/src/events/interaction-create.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { getSubcommand } from ':util/commands.ts'; -import { replyToInteraction } from ':util/interactions.ts'; -import { error, event } from ':util/logger.ts'; -import { InteractionTypes } from 'discordeno'; - -bot.events.interactionCreate = async (_, interaction) => { - if (!interaction.data) { - return; - } - - if (interaction.type === InteractionTypes.ApplicationCommand) { - const commandName = interaction.data.name; - const subCommandName = getSubcommand(interaction); - - event(`Member ran /${commandName} ${subCommandName || ''}`); - - try { - await bot.commands.get(commandName)?.run(interaction); - } catch (err) { - await replyToInteraction(interaction, { - content: 'โŒ Sorry, something went wrong.\n```\n' + err + '\n```', - ephemeral: true, - }); - - error(err.stack || err.message); - } - } -}; diff --git a/ts/src/events/presence-update.ts b/ts/src/events/presence-update.ts deleted file mode 100644 index 54d412b..0000000 --- a/ts/src/events/presence-update.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { RTP_ROLE_ID } from ':src/env.ts'; -import { doActivitiesIncludeLunaro } from ':util/activity-tracking.ts'; -import { readActivityTrackingConfig } from ':util/data.ts'; -import { event } from ':util/logger.ts'; -import { addMemberToRTP, removeMemberFromRTP } from ':util/rtp.ts'; - -bot.events.presenceUpdate = async (_, presence) => { - const activityTrackerData = readActivityTrackingConfig(); - - if (!activityTrackerData.enabled) { - return; - } - - if (activityTrackerData.blocklist.includes(presence.user.id.toString())) { - return; - } - - const member = await bot.helpers.getMember(presence.guildId, presence.user.id); - - if (doActivitiesIncludeLunaro(presence.activities)) { - event('Member started playing Lunaro'); - await addMemberToRTP(member); - } else if (member.roles.includes(RTP_ROLE_ID)) { - event('Member stopped playing Lunaro'); - await removeMemberFromRTP(member); - } -}; diff --git a/ts/src/events/reaction-add.ts b/ts/src/events/reaction-add.ts deleted file mode 100644 index 2688756..0000000 --- a/ts/src/events/reaction-add.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { BOT_ID } from ':src/env.ts'; -import { event } from ':util/logger.ts'; -import { - addApproval, - addBoycott, - cancelSubmission, - finalizeSubmission, - pendingMatchOfMessage -} from ':util/match-approval.ts'; - -bot.events.reactionAdd = async (_, reaction) => { - if (reaction.userId === BOT_ID) { - // Ignore reactions the bot adds - return; - } - - const message = await bot.helpers.getMessage(reaction.channelId, reaction.messageId); - const messageAuthor = message.authorId; - - if (messageAuthor !== BOT_ID) { - // Ignore reactions to other users' messages - return; - } - - event(`Reaction ${reaction.emoji.name} added to ${reaction.messageId}`); - - const linkedMatch = pendingMatchOfMessage( - reaction.channelId.toString(), - reaction.messageId.toString() - ); - - if (!linkedMatch) { - // No match linked to this message - return; - } - - if ( - !linkedMatch.status.required.includes(reaction.userId.toString()) && - reaction.userId.toString() !== linkedMatch.submitter - ) { - // Remove and ignore all reactions from people who are not involved - bot.helpers.removeReaction(reaction.channelId, reaction.messageId, reaction.emoji.name!, { - userId: reaction.userId, - }); - - return; - } - - if (reaction.emoji.name === 'โœ…') { - const approvedMatch = addApproval(linkedMatch, reaction.userId.toString()); - - if ( - approvedMatch.status.required.every((requiredApproval) => - approvedMatch.status.approved.includes(requiredApproval) - ) - ) { - // Received all required approvals - await finalizeSubmission(approvedMatch); - } - } - - if (reaction.emoji.name === 'โŒ') { - const boycottedMatch = addBoycott(linkedMatch, reaction.userId.toString()); - - if ( - reaction.userId.toString() === linkedMatch.submitter || - boycottedMatch.status.required.every((requiredApproval) => - boycottedMatch.status.boycotted.includes(requiredApproval) - ) - ) { - // Submitter cancelled or all required approvals boycotted - await cancelSubmission(linkedMatch); - } - } -}; diff --git a/ts/src/events/reaction-remove.ts b/ts/src/events/reaction-remove.ts deleted file mode 100644 index fed377e..0000000 --- a/ts/src/events/reaction-remove.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { BOT_ID } from ':src/env.ts'; -import { event } from ':util/logger.ts'; -import { pendingMatchOfMessage, removeApproval, removeBoycott } from ':util/match-approval.ts'; - -bot.events.reactionRemove = async (_, reaction) => { - if (reaction.userId === BOT_ID) { - // Ignore reactions the bot removes - return; - } - - const message = await bot.helpers.getMessage(reaction.channelId, reaction.messageId); - const messageAuthor = message.authorId; - - if (messageAuthor != BOT_ID) { - // Ignore reactions to other users' messages - return; - } - - event(`Reaction ${reaction.emoji.name} removed from ${reaction.messageId}`); - - const linkedMatch = pendingMatchOfMessage( - reaction.channelId.toString(), - reaction.messageId.toString() - ); - - if (!linkedMatch) { - // No match linked to this message - return; - } - - if (reaction.emoji.name === 'โœ…') { - removeApproval(linkedMatch, reaction.userId.toString()); - } else if (reaction.emoji.name === 'โŒ') { - removeBoycott(linkedMatch, reaction.userId.toString()); - } - - await null; -}; diff --git a/ts/src/events/ready.ts b/ts/src/events/ready.ts deleted file mode 100644 index cf7c343..0000000 --- a/ts/src/events/ready.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { event } from ':util/logger.ts'; -import { ActivityTypes } from 'discordeno'; - -export let readyTimestamp: number; - -bot.events.ready = (_, __) => { - readyTimestamp = Date.now(); - - bot.helpers.editBotStatus({ - status: 'online', - activities: [ - { - type: ActivityTypes.Competing, - createdAt: Date.now(), - name: 'Lunaro', - }, - ], - }); - - event('Lunaro Manager is online'); -}; diff --git a/ts/src/interfaces/activity-tracker-data.ts b/ts/src/interfaces/activity-tracker-data.ts deleted file mode 100644 index d833f85..0000000 --- a/ts/src/interfaces/activity-tracker-data.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** Configuration options for activity tracking. */ -export interface ActivityTrackingConfig { - /** Whether to track activities. */ - enabled: boolean; - - /** IDs of users who paused tracking for their account. */ - blocklist: string[]; -} diff --git a/ts/src/interfaces/command.ts b/ts/src/interfaces/command.ts deleted file mode 100644 index f03c4f9..0000000 --- a/ts/src/interfaces/command.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ApplicationCommandOption, ApplicationCommandTypes, Interaction } from 'discordeno'; - -/** Configuration for a slash command. */ -export interface Command { - /** The name of the command. */ - name: string; - - /** The description of the command. */ - description: string; - - /** The type of the command. */ - type: ApplicationCommandTypes; - - /** The options of the command. */ - options?: ApplicationCommandOption[]; - - /** - * The function to be executed when the command is called. - * @param interaction the command invocation - */ - run: (interaction: Interaction) => Promise; -} - -/** Maps subcommand names to corresponding functions. */ -export type SubcommandMap = { - [subcommand: string]: (interaction: Interaction) => Promise; -}; diff --git a/ts/src/interfaces/discord-bot.ts b/ts/src/interfaces/discord-bot.ts deleted file mode 100644 index eccc76f..0000000 --- a/ts/src/interfaces/discord-bot.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Command } from ':interfaces/command.ts'; -import { Bot, Collection, CreateBotOptions } from 'discordeno'; - -/** Extension of discordeno's `Bot` class with a collection of commands. */ -export interface DiscordBot extends Bot { - commands: Collection; -} - -/** - * Extension of discordeno's `CreateBotOptions` class with a collection of - * commands. - */ -export interface DiscordBotOptions extends CreateBotOptions { - commands: Collection; -} diff --git a/ts/src/interfaces/discord-user.ts b/ts/src/interfaces/discord-user.ts deleted file mode 100644 index a7d7caf..0000000 --- a/ts/src/interfaces/discord-user.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { PatternError } from ':error/pattern-error.ts'; -import { log } from ':util/logger.ts'; - -/** Regex matching a Lunaro Revival Server username (formatted as ` []`). */ -const usernamePattern = /([A-Za-z0-9_.-]{4,24})\s\[(PC|XB1|PS4|SWI)\]/; - -/** Represents a platform Warframe can be played on. */ -type Platform = 'PC' | 'XB1' | 'PS4' | 'SWI'; - -/** A Lunaro Revival Server Discord user. */ -export interface DiscordUser { - /** The username / in-game name. */ - username: string; - - /** The player's primary platform. */ - platform: Platform; -} - -/** - * Parses a Lunaro Revival Server username. - * - * @param discordName the name to be parsed - * @returns the corresponding user or null if the name cannot be parsed - * @throws if the name cannot be parsed - */ -export const parseDiscordUsername = (name?: string): DiscordUser => { - log(`Parsing username "${name}"`); - - const matchGroups = name?.match(usernamePattern); - const username = matchGroups?.[1]; - const platform = matchGroups?.[2]; - - if (!username || !platform) { - throw new PatternError(`Could not infer in-game username from display name "${name}"`); - } - - return { - username, - platform: platform as Platform, - }; -}; diff --git a/ts/src/interfaces/interaction-reply.ts b/ts/src/interfaces/interaction-reply.ts deleted file mode 100644 index 32c3805..0000000 --- a/ts/src/interfaces/interaction-reply.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** Data to reply to an interaction with. */ -export interface InteractionReply { - /** The content of the text message. */ - content: string; - - /** Whether the message is ephemeral. */ - ephemeral?: boolean; -} diff --git a/ts/src/interfaces/lunaro-match.ts b/ts/src/interfaces/lunaro-match.ts deleted file mode 100644 index b28d06d..0000000 --- a/ts/src/interfaces/lunaro-match.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** Ranked Lunaro match from the rank API. */ -export interface LunaroMatch { - /** The unique identifier. */ - id: number; - - /** Submission timestamp. */ - epoch: number; - - /** Unique identifier of player A. */ - player_a: number; - - /** Final score of player A. */ - score_a: number; - - /** Rank delta of player A. */ - delta_a: number; - - /** Unique identifier of player B. */ - player_b: number; - - /** Final score of player B. */ - score_b: number; - - /** Rank delta of player B. */ - delta_b: number; -} - -/** Request body for creating a new ranked Lunaro player in the rank API. */ -export interface NewLunaroMatch { - /** Username of player A. */ - player_a: string; - - /** Final score of player A. */ - score_a: number; - - /** Ping of player A. A ping of `0` means the player was the host of the match. */ - ping_a: number; - - /** Username of player B. */ - player_b: string; - - /** Final score of player B. */ - score_b: number; - - /** Ping of player B. A ping of `0` means the player was the host of the match. */ - ping_b: number; -} diff --git a/ts/src/interfaces/lunaro-player.ts b/ts/src/interfaces/lunaro-player.ts deleted file mode 100644 index 33f15a6..0000000 --- a/ts/src/interfaces/lunaro-player.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** Ranked Lunaro player from the rank API. */ -export interface LunaroPlayer { - /** The unique identifier. */ - id: number; - - /** The username. */ - name: string; - - /** The rank. */ - rank: number; -} - -/** Request body for creating a new ranked Lunaro player in the rank API. */ -export interface NewLunaroPlayer { - /** The username. */ - name: string; - - /** The rank. */ - rank: number; -} diff --git a/ts/src/interfaces/pending-match.ts b/ts/src/interfaces/pending-match.ts deleted file mode 100644 index 4fe88d7..0000000 --- a/ts/src/interfaces/pending-match.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NewLunaroMatch } from ':interfaces/lunaro-match.ts'; - -/** Reference to a bot's Discord message. */ -interface MessageReference { - /** ID of the message. */ - id: string; - - /** ID of the message's channel. */ - channelId: string; -} - -/** Tracks the approval of users. */ -interface ApprovalStatus { - /** User IDs which must approve. */ - required: string[]; - - /** User IDs which have approved. */ - approved: string[]; - - /** User IDs which have boycotted. */ - boycotted: string[]; -} - -/** Represents a pending match which has not been fully approved yet. */ -export interface PendingMatch { - /** The message containing the match details. */ - message: MessageReference; - - /** The approval progress. */ - status: ApprovalStatus; - - /** The pending match. */ - match: NewLunaroMatch; - - /** User ID of the person who submitted the match. */ - submitter: string; -} diff --git a/ts/src/main.ts b/ts/src/main.ts deleted file mode 100644 index 4ba4509..0000000 --- a/ts/src/main.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { HOME_GUILD_ID } from ':src/env.ts'; -import { log } from ':util/logger.ts'; -import { startBot } from 'discordeno'; - -for await (const entry of Deno.readDir('src/events')) { - if (entry.isFile && entry.name.endsWith('.ts')) { - log(`Loading file src/events/${entry.name}`); - await import(`:events/${entry.name}`); - } -} - -for await (const entry of Deno.readDir('src/commands')) { - if (entry.isFile && entry.name.endsWith('.ts')) { - log(`Loading file src/commands/${entry.name}`); - await import(`:commands/${entry.name}`); - } -} - -log('Deploying application commands'); -await bot.helpers.upsertApplicationCommands(bot.commands.array(), HOME_GUILD_ID); - -log('Logging in'); -await startBot(bot); diff --git a/ts/src/util/activity-tracking.ts b/ts/src/util/activity-tracking.ts deleted file mode 100644 index de54855..0000000 --- a/ts/src/util/activity-tracking.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { readActivityTrackingConfig, writeActivityTrackingConfig } from ':util/data.ts'; -import { log } from ':util/logger.ts'; -import { Activity, Member } from 'discordeno'; - -/** - * Checks for Lunaro in a list of activities. Looks for both Warframe itself and - * [PyLunaroRPC](https://github.com/kozabrada123/PyLunaroRPC) - * - * @param activities to be checked - * @returns whether any of the activities is Lunaro - */ -export const doActivitiesIncludeLunaro = (activities: Activity[]) => { - const localizedLunaroName = ['lunaro', 'ะปัƒะฝะฐั€ะพ', '๋ฃจ๋‚˜๋กœ']; - - const lunaroActivities = activities.filter((activity) => { - const isWarframeLunaro = - activity.name.toLowerCase() === 'warframe' && - localizedLunaroName.includes(activity.details?.toLowerCase() || ''); - - const isPyLunaroRPC = activity.name.toLowerCase() === 'warframe: lunaro'; - - return isWarframeLunaro || isPyLunaroRPC; - }); - - return lunaroActivities.length > 0; -}; - -/** Updates the activity tracking config to allow tracking globally. */ -export const enableActivityTracking = () => { - const data = readActivityTrackingConfig(); - data.enabled = true; - writeActivityTrackingConfig(data); - - log('Enabled activity tracker'); -}; - -/** Updates the activity tracking config to deny tracking globally. */ -export const disableActivityTracking = () => { - const data = readActivityTrackingConfig(); - data.enabled = false; - writeActivityTrackingConfig(data); - - log('Disabled activity tracker'); -}; - -/** - * Adds a member to the activity tracking blocklist to disable tracking for - * their account's activity. - * - * @param member to be blocklisted - */ -export const addMemberToTrackingBlocklist = (member: Member) => { - const data = readActivityTrackingConfig(); - - if (!data.blocklist.includes(member.id.toString())) { - data.blocklist.push(member.id.toString()); - writeActivityTrackingConfig(data); - - log('Member added to activity tracking blocklist'); - } -}; - -/** - * Removes a member to the activity tracking blocklist to resume tracking for - * their account's activity. - * - * @param member to be blocklisted - */ -export const removeMemberFromTrackingBlocklist = (member: Member) => { - const data = readActivityTrackingConfig(); - - if (data.blocklist.includes(member.id.toString())) { - data.blocklist = data.blocklist.filter((entry) => entry !== member.id.toString()); - writeActivityTrackingConfig(data); - - log('Member removed from activity tracking blocklist'); - } -}; diff --git a/ts/src/util/commands.ts b/ts/src/util/commands.ts deleted file mode 100644 index 2ea5f2c..0000000 --- a/ts/src/util/commands.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ApplicationCommandOptionTypes, Interaction } from 'discordeno'; - -/** - * Extracts the name of a subcommand from an interaction. - * @param interaction of which the subcommand should be extraced. - * @returns the name of the subcommand, or `null` if no subcommand is specified - */ -export const getSubcommand = (interaction: Interaction): string | null => { - if (!interaction.data) { - return null; - } - - if (!interaction.data.options) { - return null; - } - - if (!interaction.data.options[0]) { - return null; - } - - if (interaction.data.options[0]?.type !== ApplicationCommandOptionTypes.SubCommand) { - return null; - } - - return interaction.data.options[0].name; -}; diff --git a/ts/src/util/creators.ts b/ts/src/util/creators.ts deleted file mode 100644 index 633a062..0000000 --- a/ts/src/util/creators.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Command } from ':interfaces/command.ts'; -import { DiscordBot, DiscordBotOptions } from ':interfaces/discord-bot.ts'; -import { bot } from ':src/bot.ts'; -import { createBot } from 'discordeno'; - -/** - * Creates and configures a Discord bot instance. - * @param options for bot configuration - * @returns the created instance - */ -export const createDiscordBot = (options: DiscordBotOptions): DiscordBot => { - const newBot = createBot(options) as DiscordBot; - newBot.commands = options.commands; - - return newBot; -}; - -/** - * Adds a slash command to the global primary bot instance. - * @param command to be created - */ -export const createCommand = (command: Command) => { - bot.commands.set(command.name, command); -}; diff --git a/ts/src/util/data.ts b/ts/src/util/data.ts deleted file mode 100644 index 5e2ce27..0000000 --- a/ts/src/util/data.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { ActivityTrackingConfig } from ':interfaces/activity-tracker-data.ts'; -import { PendingMatch } from ':interfaces/pending-match.ts'; -import { log } from ':util/logger.ts'; - -const dataDirPath = 'data'; -const activityTrackerDataFile = dataDirPath + '/activity-tracker.json'; -const pendingMatchesFile = dataDirPath + '/pending-matches.json'; - -/** Creates a `projectRoot/data/` directory if it doesn't exist. */ -const createDataDirIfNotExists = () => { - try { - Deno.statSync(dataDirPath); - } catch { - log(`Creating directory ${dataDirPath}`); - Deno.mkdirSync(dataDirPath); - Deno.chmod(dataDirPath, 0o777); - } -}; - -/** Creates a `projectRoot/data/activity-tracker.json` file if it doesn't exist. */ -const createActivityTrackerConfigIfNotExists = () => { - try { - Deno.statSync(activityTrackerDataFile); - } catch { - const emptyData: ActivityTrackingConfig = { - enabled: false, - blocklist: [], - }; - - log(`Creating file ${activityTrackerDataFile}`); - - Deno.writeTextFileSync(activityTrackerDataFile, JSON.stringify(emptyData, null, 2)); - } -}; - -/** Creates a `projectRoot/data/pending-matches.json` file if it doesn't exist. */ -const createPendingMatchesIfNotExists = () => { - try { - Deno.statSync(pendingMatchesFile); - } catch { - const emptyData: PendingMatch[] = []; - - log(`Creating file ${pendingMatchesFile}`); - - Deno.writeTextFileSync(pendingMatchesFile, JSON.stringify(emptyData, null, 2)); - } -}; - -/** Creates all necessary directories and files to store the bot's data. */ -const initializeData = () => { - createDataDirIfNotExists(); - createActivityTrackerConfigIfNotExists(); - createPendingMatchesIfNotExists(); -}; - -/** - * Reads the activity tracking configuration from the disk. - * @returns the configuration - */ -export const readActivityTrackingConfig = (): ActivityTrackingConfig => { - initializeData(); - - log(`Reading file ${activityTrackerDataFile}`); - - const fileContents = Deno.readTextFileSync(activityTrackerDataFile); - const activityTrackerData: ActivityTrackingConfig = JSON.parse(fileContents); - - return activityTrackerData; -}; - -/** - * Saves the activity tracking configuration to the disk. - * @param config to be saved - */ -export const writeActivityTrackingConfig = (config: ActivityTrackingConfig) => { - initializeData(); - - log(`Writing file ${activityTrackerDataFile}`); - - Deno.writeTextFileSync(activityTrackerDataFile, JSON.stringify(config, null, 2)); -}; - -/** - * Reads the pending matches from the disk. - * @returns the pending matches - */ -export const readPendingMatches = (): PendingMatch[] => { - initializeData(); - - log(`Reading file ${pendingMatchesFile}`); - - const fileContents = Deno.readTextFileSync(pendingMatchesFile); - const pendingMatches: PendingMatch[] = JSON.parse(fileContents); - - return pendingMatches; -}; - -/** - * Saves the pending matches to the disk. - * @param matches to be saved - */ -const writePendingMatches = (matches: PendingMatch[]) => { - initializeData(); - - log(`Writing file ${pendingMatchesFile}`); - - Deno.writeTextFileSync(pendingMatchesFile, JSON.stringify(matches, null, 2)); -}; - -/** - * Add a pending match. - * @param match to be added - */ -export const addPendingMatch = (match: PendingMatch) => { - const pendingMatches = readPendingMatches(); - let exists = false; - - for (const pendingMatch of pendingMatches) { - if ( - pendingMatch.message.channelId === match.message.channelId && - pendingMatch.message.id === match.message.id - ) { - exists = true; - } - } - - if (!exists) { - pendingMatches.push(match); - writePendingMatches(pendingMatches); - } -}; - -/** - * Updates a pending match. - * @param match to be updated - */ -export const updatePendingMatch = (match: PendingMatch) => { - const pendingMatches = readPendingMatches(); - let exists = false; - - for (const pendingMatch of pendingMatches) { - if ( - pendingMatch.message.channelId === match.message.channelId && - pendingMatch.message.id === match.message.id - ) { - exists = true; - } - } - - if (exists) { - removePendingMatch(match); - addPendingMatch(match); - } -}; - -/** - * Remove a pending match. - * @param match to be removed - */ -export const removePendingMatch = (match: PendingMatch) => { - const pendingMatches = readPendingMatches(); - - const newMatches = pendingMatches.filter( - (pendingMatch) => - pendingMatch.message.channelId !== match.message.channelId && - pendingMatch.message.id !== match.message.id - ); - - writePendingMatches(newMatches); -}; diff --git a/ts/src/util/interactions.ts b/ts/src/util/interactions.ts deleted file mode 100644 index deb9c38..0000000 --- a/ts/src/util/interactions.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { InteractionReply } from ':interfaces/interaction-reply.ts'; -import { bot } from ':src/bot.ts'; -import { log } from ':util/logger.ts'; -import { Interaction, InteractionResponseTypes, Message } from 'discordeno'; - -/** - * Replies to an interaction with a text message. - * @param interaction to be replied to - * @param reply to be replied with - * @returns the sent message - */ -export const replyToInteraction = async (interaction: Interaction, reply: InteractionReply): Promise => { - log(`Replying to interaction ${interaction.id}`); - - return await bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { - type: InteractionResponseTypes.ChannelMessageWithSource, - data: { - content: reply.content, - - // https://discord.com/developers/docs/change-log#march-5-2021 - flags: reply.ephemeral ? 64 : undefined, - }, - }); -}; diff --git a/ts/src/util/logger.ts b/ts/src/util/logger.ts deleted file mode 100644 index 99b1456..0000000 --- a/ts/src/util/logger.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Turns text red. - * @param content to be turned red. - * @returns the same text, but red. - */ -const red = (content: string): string => { - return `\x1b[31m${content}\x1b[0m`; -}; - -/** - * Turns text green. - * @param content to be turned green. - * @returns the same text, but green. - */ -const green = (content: string): string => { - return `\x1b[32m${content}\x1b[0m`; -}; - -/** - * Turns text yellow. - * @param content to be turned yellow. - * @returns the same text, but yellow. - */ -const yellow = (content: string): string => { - return `\x1b[33m${content}\x1b[0m`; -}; - -/** - * Prints a string with timestamp in the following format: `hh:mm:ss | string`. - * @param content to be printed - * @param color is a function which returns a colored string - */ -const print = (content: string, color: (content: string) => string) => { - const now = new Date(); - - const hh = doubleDigit(now.getHours()); - const mm = doubleDigit(now.getMinutes()); - const ss = doubleDigit(now.getSeconds()); - - const time = [hh, mm, ss].join(':'); - const timestamp = color(`${time}`); - - console.log(`${timestamp} | ${content}`); -}; - -/** - * Convert a number to a string with two digits, adding a leading `0`. - * @param num to be converted - * @returns the number as a two-digit string - */ -const doubleDigit = (num: number): string => { - return num.toString().padStart(2, '0'); -}; - -/** - * Log a string to the console. - * @param content to be logged. - */ -export const log = (content: string) => { - print(content, green); -}; - -/** - * Log a string to the console, and format it as an event. - * @param content to be logged. - */ -export const event = (content: string) => { - print(content, yellow); -}; - -/** - * Log a string to the console, and format it as an error. - * @param content to be logged. - */ -export const error = (content: string) => { - print(content, red); -}; diff --git a/ts/src/util/match-approval.ts b/ts/src/util/match-approval.ts deleted file mode 100644 index 47c114c..0000000 --- a/ts/src/util/match-approval.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { PendingMatch } from ':interfaces/pending-match.ts'; -import { bot } from ':src/bot.ts'; -import { readPendingMatches, removePendingMatch, updatePendingMatch } from ':util/data.ts'; -import { editMessageInChannel } from ':util/messages.ts'; -import { createMatch } from ':util/rank-api.ts'; - -export const matchApprovedMessage = 'โœ… This match has been approved'; -export const matchCancelledMessage = 'โŒ This match has been cancelled'; -export const pendingMatchApprovalMessage = - 'โณ This match is pending approval. Both players are requested to react with โœ… to approve and finalize this match submission, or ract with โŒ to boycott or cancel it'; - -/** - * Checks for a pending match linked to a given message. - * - * @param channelId the message's channel's ID - * @param messageId the message's ID - * @returns the linked pending match or `undefined` if no match is linked - */ -export const pendingMatchOfMessage = ( - channelId: string, - messageId: string -): PendingMatch | undefined => { - const pendingMatches = readPendingMatches(); - - return pendingMatches.find( - (match) => match.message.channelId === channelId && match.message.id === messageId - ); -}; - -/** - * Adds an user's approval to a match. - * - * @param match to be approved - * @param approverId who approved the match - */ -export const addApproval = (match: PendingMatch, approverId: string): PendingMatch => { - match.status.approved.push(approverId); - - updatePendingMatch(match); - - return match; -}; - -/** - * Removes an user's approval from a match. - * - * @param match to be approved - * @param approverId who approved the match - */ -export const removeApproval = (match: PendingMatch, approverId: string): PendingMatch => { - match.status.approved = match.status.approved.filter((approver) => approver !== approverId); - - updatePendingMatch(match); - - return match; -}; - -/** - * Adds an user's boycott to a match. - * - * @param match to be approved - * @param boycotterId who boycotted the match - */ -export const addBoycott = (match: PendingMatch, boycotterId: string): PendingMatch => { - match.status.boycotted.push(boycotterId); - - updatePendingMatch(match); - - return match; -}; - -/** - * Removes an user's boycott from a match. - * - * @param match to be approved - * @param boycotterId who approved the match - */ -export const removeBoycott = (match: PendingMatch, boycotterId: string): PendingMatch => { - match.status.boycotted = match.status.boycotted.filter( - (boycotter) => boycotter !== boycotterId - ); - - updatePendingMatch(match); - - return match; -}; - -/** - * Finalized a submission and uploads the match to the API when all players - * approve the match. - * - * @param match to be submitted - */ -export const finalizeSubmission = async (match: PendingMatch): Promise => { - const createdMatch = await createMatch(match.match); - - const message = await bot.helpers.getMessage( - BigInt(match.message.channelId), - BigInt(match.message.id) - ); - - let content = message.content.replace(pendingMatchApprovalMessage, matchApprovedMessage); - - content += [ - '\n', - generateDeltaString(match.match.player_a, createdMatch.delta_a), - generateDeltaString(match.match.player_b, createdMatch.delta_b), - ].join('\n'); - - await editMessageInChannel(BigInt(match.message.channelId), BigInt(match.message.id), { - content, - }); - - removePendingMatch(match); -}; - -/** - * Cancel a submission. - * @param match to be cancelled - */ -export const cancelSubmission = async (match: PendingMatch): Promise => { - const message = await bot.helpers.getMessage( - BigInt(match.message.channelId), - BigInt(match.message.id) - ); - - const content = message.content.replace(pendingMatchApprovalMessage, matchCancelledMessage); - - await editMessageInChannel(BigInt(match.message.channelId), BigInt(match.message.id), { - content, - }); - - removePendingMatch(match); -}; - -const generateDeltaString = (player: string, delta: number): string => { - if (delta > 0) { - return `๐Ÿ”บ ${player} gained ${delta} points`; - } else if (delta < 0) { - return `๐Ÿ”ป ${player} lost ${-delta} points`; - } else { - const possessiveS = player.endsWith('s') ? "'" : "'s"; - return `๐Ÿ”ธ ${player}${possessiveS} rank did not change`; - } -}; diff --git a/ts/src/util/messages.ts b/ts/src/util/messages.ts deleted file mode 100644 index 1767c20..0000000 --- a/ts/src/util/messages.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { log } from ':util/logger.ts'; -import { CreateMessage, EditMessage, Message } from 'discordeno'; - -/** - * Sends a text message in a given channel. - * @param channelId of the channel the message should be sent in - * @param message to be sent - * @returns the sent message - */ -export const sendMessageInChannel = async ( - channelId: string | bigint, - message: CreateMessage -): Promise => { - log(`Sending message in channel ${channelId}`); - - return await bot.helpers.sendMessage(BigInt(channelId), message); -}; - -/** - * Sends a text message in a given channel. - * @param channelId of the channel the message should be sent in - * @param message to be sent - * @returns the sent message - */ -export const editMessageInChannel = async ( - channelId: string | bigint, - messageId: string | bigint, - message: EditMessage -): Promise => { - log(`Editing message in channel ${channelId}`); - - return await bot.helpers.editMessage(BigInt(channelId), BigInt(messageId), message); -}; diff --git a/ts/src/util/rank-api.ts b/ts/src/util/rank-api.ts deleted file mode 100644 index 9aef92a..0000000 --- a/ts/src/util/rank-api.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { HttpError } from ':error/http-error.ts'; -import { LunaroMatch, NewLunaroMatch } from ':interfaces/lunaro-match.ts'; -import { LunaroPlayer, NewLunaroPlayer } from ':interfaces/lunaro-player.ts'; -import { RANK_API_TOKEN, RANK_API_URL } from ':src/env.ts'; -import { log } from ':util/logger.ts'; - -/** - * Sorts a list of players first by their rank, and then by their name. - * - * @param players unsorted list - * @returns a sorted list - */ -const sortByRankAndName = (players: LunaroPlayer[]): LunaroPlayer[] => { - const compareRank = (a: LunaroPlayer, b: LunaroPlayer) => (a.rank < b.rank ? 1 : -1); - - const compareName = (a: LunaroPlayer, b: LunaroPlayer) => - a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1; - - return players.sort((a, b) => { - return a.rank == b.rank ? compareName(a, b) : compareRank(a, b); - }); -}; - -/** Converts a number to a league name. */ -export const rankToLeagueName = (rank: number): string => { - if (rank < 1500) { - return 'Neophyte'; - } - - if (rank < 1750) { - return 'Padawan'; - } - - if (rank < 2000) { - return 'Amateur'; - } - - if (rank < 2250) { - return 'Skilled'; - } - - if (rank < 2500) { - return 'Pro'; - } - - if (rank < 2750) { - return 'Master'; - } - - return 'Champion'; -}; - -/** Converts a leagze name to a number. */ -export const leagueNameToRank = (league: string): number => { - switch (league.toLowerCase()) { - case 'champion': - return 2875; - case 'master': - return 2625; - case 'pro': - return 2375; - case 'skilled': - return 2125; - case 'amateur': - return 1875; - case 'padawan': - return 1625; - case 'neophyte': - return 1250; - default: - return -1; - } -}; - -/** - * Fetches all players. - * @returns the list of players - * @throws if the request results in an error - */ -export const getAllPlayers = async (): Promise => { - const resource = RANK_API_URL + '/api/players'; - log(`GET ${resource}`); - - const response = await fetch(resource); - - if (response.status !== 200) { - throw new HttpError(response.status, await response.text()); - } - - const players: LunaroPlayer[] = JSON.parse(await response.text()); - - return sortByRankAndName(players); -}; - -/** - * Fetches the player with the given username or ID. - * - * @param nameOrId username or ID of the player - * @returns the requested player - * @throws if the request results in an error - */ -export const getPlayerByNameOrId = async (nameOrId: string): Promise => { - const resource = `${RANK_API_URL}/api/players/${nameOrId}`; - log(`GET ${resource}`); - - const response = await fetch(resource); - - if (response.status !== 200) { - throw new HttpError(response.status, await response.text()); - } - - const player: LunaroPlayer = JSON.parse(await response.text()); - - return player; -}; - -/** - * Fetches all matches. - * @returns the list of matches - * @throws if the request results in an error - */ -export const getAllMatches = async (): Promise => { - const resource = RANK_API_URL + '/api/matches'; - log(`GET ${resource}`); - - const response = await fetch(resource); - - if (response.status !== 200) { - throw new HttpError(response.status, await response.text()); - } - - const matches: LunaroMatch[] = JSON.parse(await response.text()); - - return matches; -}; - -/** - * Fetches the match with the given username or ID. - * - * @param id ID of the match - * @returns the requested match - * @throws if the request results in an error - */ -export const getMatchById = async (id: string): Promise => { - const resource = `${RANK_API_URL}/api/matches/${id}`; - log(`GET ${resource}`); - - const response = await fetch(resource); - - if (response.status !== 200) { - throw new HttpError(response.status, await response.text()); - } - - const match: LunaroMatch = JSON.parse(await response.text()); - - return match; -}; - -/** - * Creates a player in the database. - * - * @param player player to be created - * @returns the created player - * @throws if the request results in an error - */ -export const createPlayer = async (player: NewLunaroPlayer): Promise => { - const resource = RANK_API_URL + '/api/players/add'; - log(`POST ${resource} ${JSON.stringify({ ...player, token: '***' }, null, 2)}`); - - const body = { ...player, token: RANK_API_TOKEN }; - - const response = await fetch(resource, { - method: 'POST', - body: JSON.stringify(body), - }); - - log(JSON.stringify(response, null, 2)) - - if (response.status !== 201) { - throw new HttpError(response.status, await response.text()); - } - - const createdPlayer: LunaroPlayer = JSON.parse(await response.text()); - - return createdPlayer; -}; - -/** - * Creates a match in the database. - * - * @param match match to be created - * @returns the created match - * @throws if the request results in an error - */ -export const createMatch = async (match: NewLunaroMatch): Promise => { - const resource = RANK_API_URL + '/api/matches/add'; - log(`POST ${resource} ${JSON.stringify({ ...match, token: '***' }, null, 2)}`); - - const body = { ...match, token: RANK_API_TOKEN }; - - const response = await fetch(resource, { - method: 'POST', - body: JSON.stringify(body), - }); - - if (response.status !== 201) { - throw new HttpError(response.status, await response.text()); - } - - const createdMatch: LunaroMatch = JSON.parse(await response.text()); - - return createdMatch; -}; diff --git a/ts/src/util/rtp.ts b/ts/src/util/rtp.ts deleted file mode 100644 index f6a4e3f..0000000 --- a/ts/src/util/rtp.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { bot } from ':src/bot.ts'; -import { HOME_GUILD_ID, RTP_ROLE_ID } from ':src/env.ts'; -import { log } from ':util/logger.ts'; -import { Member } from 'discordeno'; - -/** - * Checks for all home guild's members with the configured ready-to-play role. - * @returns an array of all members with the role - */ -export const getRTPMembers = async (): Promise => { - const allMembers = await bot.helpers.getMembers(HOME_GUILD_ID, {}); - const rtpMembers = allMembers.array().filter((member) => member.roles.includes(RTP_ROLE_ID)); - - return rtpMembers; -}; - -/** - * Adds the configured ready-to-play role to a given member. - * @param member to add the role to - */ -export const addMemberToRTP = async (member: Member) => { - await bot.helpers.addRole(HOME_GUILD_ID, member.id, RTP_ROLE_ID); - log('Added member to RTP'); -}; - -/** - * Removes the configured ready-to-play role from a given member. - * @param member to remove the role from - */ -export const removeMemberFromRTP = async (member: Member) => { - await bot.helpers.removeRole(HOME_GUILD_ID, member.id, RTP_ROLE_ID); - log('Removed member from RTP'); -}; diff --git a/ts/src/util/time.ts b/ts/src/util/time.ts deleted file mode 100644 index 7405701..0000000 --- a/ts/src/util/time.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Extracts the unix timestamp from a snowflake. - * Snowflake spec: https://discord.com/developers/docs/reference#snowflakes - * - * @param snowflake containing the timestamp - * @returns the unix timestamp - */ -export const snowflakeToTimestamp = (snowflake: bigint): number => { - const discordEpoch = 1420070400000; - const millisecondsSinceDiscordEpoch = Number(snowflake >> 22n); - - return discordEpoch + millisecondsSinceDiscordEpoch; -};