diff options
author | Luca Matei Pintilie <lucafulger@gmail.com> | 2022-08-11 18:19:31 +0000 |
---|---|---|
committer | Luca Matei Pintilie <lucafulger@gmail.com> | 2022-08-11 18:19:31 +0000 |
commit | e563a37a70c06d4362b22117c4a1eb8147f9972d (patch) | |
tree | 37a120e986d1b4542104332e8bba6c60464ee1b8 | |
parent | f6985bd082a9eb07146af5503a7a77b2e081fea0 (diff) | |
download | speedrunbot-slash-e563a37a70c06d4362b22117c4a1eb8147f9972d.tar speedrunbot-slash-e563a37a70c06d4362b22117c4a1eb8147f9972d.tar.gz speedrunbot-slash-e563a37a70c06d4362b22117c4a1eb8147f9972d.tar.bz2 speedrunbot-slash-e563a37a70c06d4362b22117c4a1eb8147f9972d.tar.lz speedrunbot-slash-e563a37a70c06d4362b22117c4a1eb8147f9972d.tar.xz speedrunbot-slash-e563a37a70c06d4362b22117c4a1eb8147f9972d.tar.zst speedrunbot-slash-e563a37a70c06d4362b22117c4a1eb8147f9972d.zip |
Better search for autocomplete. Credits go to @YummyBacon5 for reverse engineerig speedrun.com's internal endpoints
-rw-r--r-- | src/srcom/slash_commands.ts | 57 | ||||
-rw-r--r-- | src/srcom/types.d.ts | 3 | ||||
-rwxr-xr-x | src/srcom/utils.ts | 109 |
3 files changed, 139 insertions, 30 deletions
diff --git a/src/srcom/slash_commands.ts b/src/srcom/slash_commands.ts index 88abf44..2b75c74 100644 --- a/src/srcom/slash_commands.ts +++ b/src/srcom/slash_commands.ts @@ -23,7 +23,14 @@ import { categoriesPlayed } from "./categories_played.ts"; import { podiums } from "./podiums.ts"; import { modCount } from "./mod_count.ts"; import { runsCount } from "./runs_count.ts"; -import { CommandError, getGame, SRC_API } from "./utils.ts"; +import { + CommandError, + getGame, + searchGames, + searchUsers, + SRC_API, + SRC_URL, +} from "./utils.ts"; import { runInfo } from "./run_info.ts"; import { levelInfo } from "./level_info.ts"; import { categoryInfo } from "./category_info.ts"; @@ -49,7 +56,20 @@ const srcStatus: ApplicationCommandOption = { name: "status", description: "The status of a run.", type: SlashCommandOptionType.STRING, - autocomplete: true, + choices: [ + { + name: "Verified", + value: "verified", + }, + { + name: "Rejected", + value: "rejected", + }, + { + name: "Pending", + value: "new", + }, + ], }; const srcExaminer: ApplicationCommandOption = { name: "examiner", @@ -374,23 +394,19 @@ export class SpeedrunCom extends ApplicationCommandsModule { static async #userCompletions( d: AutocompleteInteraction, ): Promise<ApplicationCommandChoice[]> { - const res = await fetch(`${SRC_API}/users?name=${d.focusedOption.value}`); - const body = await res.json(); - return (body.data as ISpeedrunCom.User[]).map((user) => ({ - name: user.names.international, - value: user.names.international, + const users = await searchUsers(d.focusedOption.value); + + return users.map((user) => ({ + name: user.name, + value: user.name, })); } static async #gamesCompletions( d: AutocompleteInteraction, ): Promise<ApplicationCommandChoice[]> { - const res = await fetch(`${SRC_API}/games?name=${d.focusedOption.value}`); - const body = await res.json(); - return (body.data as ISpeedrunCom.Game[]).map((game) => ({ - name: game.names.international, - value: game.abbreviation, - })); + const games = await searchGames(d.focusedOption.value); + return games.map((game) => ({ name: game.name, value: game.abbreviation })); } static async #levelCompletions( @@ -471,21 +487,6 @@ export class SpeedrunCom extends ApplicationCommandsModule { completions.push(...await SpeedrunCom.#levelCompletions(d)); } else if (d.focusedOption.name.includes("category")) { completions.push(...await SpeedrunCom.#categoryCompletion(d)); - } else if (d.focusedOption.name.includes("status")) { - completions.push(...[ - { - name: "Approvals", - value: "verified", - }, - { - name: "Rejections", - value: "rejected", - }, - { - name: "Pending", - value: "new", - }, - ]); } return d.autocomplete( completions, diff --git a/src/srcom/types.d.ts b/src/srcom/types.d.ts index 1569f66..010bb88 100644 --- a/src/srcom/types.d.ts +++ b/src/srcom/types.d.ts @@ -2,7 +2,8 @@ export namespace SpeedrunCom { export interface Names { international: string; - japanese?: string; + japanese: string | null; + twitch?: string; } export interface ColorFrom { diff --git a/src/srcom/utils.ts b/src/srcom/utils.ts index 760fabe..b6f2784 100755 --- a/src/srcom/utils.ts +++ b/src/srcom/utils.ts @@ -2,7 +2,8 @@ import type { Format, MarkupType } from "./fmt.ts"; import type { SpeedrunCom, SpeedrunComUnofficial } from "./types.d.ts"; import { delay, TimeDelta } from "../../deps_general.ts"; -export const SRC_API = "https://www.speedrun.com/api/v1"; +export const SRC_URL = "https://www.speedrun.com"; +export const SRC_API = `${SRC_URL}/api/v1`; export interface Opts { outputType?: MarkupType; @@ -271,3 +272,109 @@ export function getUsersGamesExaminers( getUsers((examiner ? examiner.split(",") : []).filter((a) => !!a)), ]); } + +interface GameBulk { + id: string; + names: SpeedrunCom.Names; + "abbreviation": "sms"; + "weblink": "https://www.speedrun.com/sms"; +} + +export async function searchGames(name: string): Promise<{ + name: string; + abbreviation: string; +}[]> { + // This is super un official way and can break at any time + // Which is why we fall back on the normal API + try { + const gamesRes = await fetch( + `${SRC_URL}/ajax_search.php?type=games&showall=true&term=${ + encodeURIComponent(name) + }`, + ); + if (!gamesRes.ok) { + throw new Error(`Got an unexpected status: ${gamesRes.status}`); + } + const games = await gamesRes.json() as { + label: string; + url: string; + category: string; + }[]; + return games.map((game) => ({ + name: game.label, + abbreviation: game.url, + })); + } catch (err: unknown) { + console.error(err); + const gamesRes = await fetch(`${SRC_API}/games?name=${name}&_bulk=true`); + if (!gamesRes.ok) { + throw new Error(`Got an unexpected status: ${gamesRes.status}`); + } + const games = (await gamesRes.json()).data as { + id: string; + names: SpeedrunCom.Names; + abbreviation: string; + weblink: string; + }[]; + return games.map((game) => ({ + name: game.names.international, + abbreviation: game.abbreviation, + })); + } +} + +export async function searchUsers(name: string): Promise<{ + name: string; +}[]> { + if (!name) return []; + const output: { name: string }[] = []; + let shortName: Promise<{ name: string } | false> | false = false; + + if (name.length <= 3) { + shortName = fetch(`${SRC_API}/users?lookup=${encodeURIComponent(name)}`) + .then((res) => res.json()).then((user: { data: SpeedrunCom.User[] }) => ({ + name: user.data[0].names.international, + }), (_) => false); + } + + // This is super un official way and can break at any time + // Which is why we fall back on the normal API + try { + if (name.length <= 1) throw new Error("Short name"); + const usersRes = await fetch( + `${SRC_URL}/ajax_search.php?type=users&showall=true&term=${ + encodeURIComponent(name) + }`, + ); + if (!usersRes.ok) { + throw new Error(`Got an unexpected status: ${usersRes.status}`); + } + const users = await usersRes.json() as { + label: string; + url: string; + category: string; + }[]; + + output.push(...users.map((user) => ({ + name: user.label, + }))); + } catch (err: unknown) { + if (!(err instanceof Error && err.message === "Short name")) { + console.error(err); + } + const usersRes = await fetch(`${SRC_API}/users?name=${name}`); + if (!usersRes.ok) { + throw new Error(`Got an unexpected status: ${usersRes.status}`); + } + const users = (await usersRes.json()).data as SpeedrunCom.User[]; + output.push(...users.map((user) => ({ + name: user.names.international, + }))); + } + if (shortName) { + const name = await shortName; + if (name) output.splice(0, 0, name); + } + + return output; +} |