aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Matei Pintilie <lucafulger@gmail.com>2022-08-11 18:19:31 +0000
committerLuca Matei Pintilie <lucafulger@gmail.com>2022-08-11 18:19:31 +0000
commite563a37a70c06d4362b22117c4a1eb8147f9972d (patch)
tree37a120e986d1b4542104332e8bba6c60464ee1b8
parentf6985bd082a9eb07146af5503a7a77b2e081fea0 (diff)
downloadspeedrunbot-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.ts57
-rw-r--r--src/srcom/types.d.ts3
-rwxr-xr-xsrc/srcom/utils.ts109
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;
+}