mirror of
https://github.com/xHyroM/bun-discord-bot.git
synced 2024-11-14 02:48:07 +01:00
feat: mdn command
This commit is contained in:
parent
97ecf6fa0f
commit
f051c142b0
3 changed files with 160 additions and 2 deletions
|
@ -5,6 +5,7 @@ import {
|
||||||
} from "@lilybird/jsx";
|
} from "@lilybird/jsx";
|
||||||
import { ApplicationCommand } from "@lilybird/handlers";
|
import { ApplicationCommand } from "@lilybird/handlers";
|
||||||
import algoliasearch from "algoliasearch";
|
import algoliasearch from "algoliasearch";
|
||||||
|
import { safeSlice } from "src/util.ts";
|
||||||
|
|
||||||
// @ts-expect-error It is callable, but algolia for some reason has a namespace with the same name
|
// @ts-expect-error It is callable, but algolia for some reason has a namespace with the same name
|
||||||
const algoliaClient = algoliasearch(
|
const algoliaClient = algoliasearch(
|
||||||
|
@ -37,8 +38,8 @@ export default {
|
||||||
const name = getHitName(hit);
|
const name = getHitName(hit);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: name.full.length > 100 ? name.full.slice(0, 100) : name.full,
|
name: safeSlice(name.full, 100),
|
||||||
value: name.name.length > 100 ? name.name.slice(0, 100) : name.name,
|
value: safeSlice(name.name, 100),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
154
src/commands/mdn.tsx
Normal file
154
src/commands/mdn.tsx
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
// https://github.com/discordjs/discord-utils-bot/blob/main/src/functions/autocomplete/mdnAutoComplete.ts#L23-L47 thanks
|
||||||
|
// https://github.com/discordjs/discord-utils-bot/blob/main/src/functions/mdn.ts#L59C1-L78C3 thanks
|
||||||
|
|
||||||
|
import {
|
||||||
|
BooleanOption,
|
||||||
|
ApplicationCommand as JSXApplicationCommand,
|
||||||
|
StringOption,
|
||||||
|
UserOption,
|
||||||
|
} from "@lilybird/jsx";
|
||||||
|
import { ApplicationCommand } from "@lilybird/handlers";
|
||||||
|
import { MDN_API, MDN_DISCORD_EMOJI } from "src/constants.ts";
|
||||||
|
import { safeSlice, silently } from "src/util.ts";
|
||||||
|
|
||||||
|
type MDNIndexEntry = {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type APIResult = {
|
||||||
|
doc: Document;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Document = {
|
||||||
|
archived: boolean;
|
||||||
|
highlight: Highlight;
|
||||||
|
locale: string;
|
||||||
|
mdn_url: string;
|
||||||
|
popularity: number;
|
||||||
|
score: number;
|
||||||
|
slug: string;
|
||||||
|
summary: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Highlight = {
|
||||||
|
body: string[];
|
||||||
|
title: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const MDN_DATA = (await (
|
||||||
|
await fetch(`${MDN_API}/en-US/search-index.json`)
|
||||||
|
).json()) as MDNIndexEntry[];
|
||||||
|
|
||||||
|
const cache = new Map<string, Document>();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
post: "GLOBAL",
|
||||||
|
data: (
|
||||||
|
<JSXApplicationCommand
|
||||||
|
name="mdn"
|
||||||
|
description="Search the Mozilla Developer Network documentation"
|
||||||
|
>
|
||||||
|
<StringOption
|
||||||
|
name="query"
|
||||||
|
description="Class or method to search for"
|
||||||
|
required
|
||||||
|
autocomplete
|
||||||
|
max_length={100}
|
||||||
|
/>
|
||||||
|
<UserOption name="target" description="User to mention" />
|
||||||
|
<BooleanOption name="hide" description="Show this message only for you" />
|
||||||
|
</JSXApplicationCommand>
|
||||||
|
),
|
||||||
|
run: async (interaction) => {
|
||||||
|
const hide = interaction.data.getBoolean("hide") ?? false;
|
||||||
|
|
||||||
|
await interaction.deferReply(hide);
|
||||||
|
|
||||||
|
const query = interaction.data.getString("query", true).trim();
|
||||||
|
const target = interaction.data.getUser("target");
|
||||||
|
|
||||||
|
const key = `${MDN_API}/${query}/index.json`;
|
||||||
|
|
||||||
|
let hit = cache.get(key);
|
||||||
|
if (!hit) {
|
||||||
|
try {
|
||||||
|
const result = (await fetch(key).then(async (response) =>
|
||||||
|
response.json()
|
||||||
|
)) as APIResult;
|
||||||
|
hit = result.doc;
|
||||||
|
} catch {
|
||||||
|
interaction.editReply({
|
||||||
|
content: `❌ Invalid result. Make sure to select an entry from the autocomplete.`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = MDN_API + hit.mdn_url;
|
||||||
|
|
||||||
|
const linkReplaceRegex = /\[(.+?)]\((.+?)\)/g;
|
||||||
|
const boldCodeBlockRegex = /`\*\*(.*)\*\*`/g;
|
||||||
|
|
||||||
|
const intro = escape(hit.summary)
|
||||||
|
.replaceAll(/\s+/g, " ")
|
||||||
|
.replaceAll(linkReplaceRegex, `[$1](<${MDN_API}$2>)`)
|
||||||
|
.replaceAll(boldCodeBlockRegex, `**\`$1\``);
|
||||||
|
|
||||||
|
const parts = [
|
||||||
|
`<:${MDN_DISCORD_EMOJI}:> __**[${escape(hit.title)}](<${url}>)**__`,
|
||||||
|
intro,
|
||||||
|
];
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
content: [
|
||||||
|
target ? `*Suggestion for <@${target}>:*\n` : "",
|
||||||
|
parts.join("\n"),
|
||||||
|
].join("\n"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
autocomplete: async (interaction) => {
|
||||||
|
const query = interaction.data.getFocused<string>().value;
|
||||||
|
|
||||||
|
const parts = query.split(/\.|#/).map((part) => part.toLowerCase());
|
||||||
|
const candidates = [];
|
||||||
|
|
||||||
|
for (const entry of MDN_DATA) {
|
||||||
|
const lowerTitle = entry.title.toLowerCase();
|
||||||
|
const matches = parts.filter((phrase) => lowerTitle.includes(phrase));
|
||||||
|
if (matches.length) {
|
||||||
|
candidates.push({
|
||||||
|
entry,
|
||||||
|
matches,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedCandidates = candidates.sort((one, other) => {
|
||||||
|
if (one.matches.length !== other.matches.length) {
|
||||||
|
return other.matches.length - one.matches.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aMatches = one.matches.join("").length;
|
||||||
|
const bMatches = other.matches.join("").length;
|
||||||
|
return bMatches - aMatches;
|
||||||
|
});
|
||||||
|
|
||||||
|
await silently(
|
||||||
|
interaction.showChoices(
|
||||||
|
safeSlice(
|
||||||
|
sortedCandidates.map((candidate) => ({
|
||||||
|
name: candidate.entry.title,
|
||||||
|
value: candidate.entry.url,
|
||||||
|
})),
|
||||||
|
25
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
} satisfies ApplicationCommand;
|
||||||
|
|
||||||
|
function escape(text: string) {
|
||||||
|
return text.replaceAll("||", "|\u200B|").replaceAll("*", "\\*");
|
||||||
|
}
|
|
@ -47,3 +47,6 @@ export const BUN_EMOJIS = [
|
||||||
{ name: "bunpet", id: "1172808445737574591", animated: true },
|
{ name: "bunpet", id: "1172808445737574591", animated: true },
|
||||||
{ name: "bunsegfault", id: "1175208306533486632", animated: true },
|
{ name: "bunsegfault", id: "1175208306533486632", animated: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const MDN_API = "https://developer.mozilla.org";
|
||||||
|
export const MDN_DISCORD_EMOJI = "mdn:1236028636826566758";
|
||||||
|
|
Loading…
Reference in a new issue