Skip to content

Commit 2d21a9e

Browse files
committed
MDN docs command
1 parent 5896707 commit 2d21a9e

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
"discord.js": "^12.5.1",
1616
"discord.js-docs": "^0.1.2",
1717
"dotenv": "^10.0.0",
18+
"fast-xml-parser": "^4.0.0-beta.6",
19+
"flexsearch": "^0.7.21",
1820
"node-fetch": "^2.6.6",
1921
"pm2": "^5.1.2"
2022
},
2123
"devDependencies": {
24+
"@types/flexsearch": "^0.7.2",
2225
"@types/ms": "^0.7.31",
2326
"@types/node": "^14.14.19",
2427
"@types/node-fetch": "^2.5.7",

src/commands/docs/mdn.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Command } from "discord-akairo";
2+
import { Message } from "discord.js";
3+
import { gunzipSync } from "zlib";
4+
import { XMLParser } from "fast-xml-parser";
5+
import BotClient from "../../client/client";
6+
import fetch from "node-fetch";
7+
import flexsearch from "flexsearch";
8+
9+
interface SitemapEntry<T extends string | number> {
10+
loc: string;
11+
lastmod: T;
12+
}
13+
type Sitemap<T extends string | number> = SitemapEntry<T>[];
14+
15+
const baseURL = "https://developer.mozilla.org/en-US/docs/" as const;
16+
let sources = {
17+
index: null as flexsearch.Index,
18+
sitemap: null as Sitemap<number>,
19+
lastUpdated: null as number,
20+
};
21+
22+
async function getSources(): Promise<typeof sources> {
23+
if (sources.lastUpdated && Date.now() - sources.lastUpdated < 43200000 /* 12 hours */) return sources;
24+
25+
const res = await fetch("https://developer.mozilla.org/sitemaps/en-us/sitemap.xml.gz");
26+
if (!res.ok) return sources; // Fallback to old sources if the new ones are not available for any reason
27+
28+
const sitemap: Sitemap<number> = new XMLParser()
29+
.parse(gunzipSync(await res.buffer()).toString()).urlset.url
30+
.map((entry: SitemapEntry<string>) => ({
31+
loc: entry.loc.slice(baseURL.length),
32+
lastmod: new Date(entry.lastmod).valueOf()
33+
}));
34+
35+
const index = new flexsearch.Index();
36+
sitemap.forEach((entry, idx) => index.add(idx, entry.loc));
37+
38+
sources = { index, sitemap, lastUpdated: Date.now() };
39+
return sources;
40+
}
41+
42+
export default class MdnCommand extends Command {
43+
public client: BotClient;
44+
private MDN_BLUE_COLOR = 0x83BFFF as const;
45+
private MDN_ICON_URL = "https://i.imgur.com/1P4wotC.png" as const;
46+
47+
public constructor() {
48+
super("mdn-docs", {
49+
aliases: ["mdn", "mdndocs"],
50+
description: {
51+
content: "Searches MDN documentation.",
52+
usage: "<query>",
53+
examples: ['TODO']
54+
},
55+
channel: "guild",
56+
clientPermissions: ["EMBED_LINKS"],
57+
ratelimit: 2,
58+
args: [
59+
{
60+
id: "query",
61+
match: "rest",
62+
type: "string",
63+
prompt: {
64+
start: "```\n" + "Enter the phrase you'd like to search for.\n" + "Example: Array.filter" + "```",
65+
retry: "Not a valid search phrase.",
66+
},
67+
}
68+
],
69+
});
70+
}
71+
72+
public async exec(message: Message, { query }: { query: string }): Promise<Message | Message[]> {
73+
const { index, sitemap } = await getSources();
74+
const search: string[] = index.search(query, { limit: 10 }).map((id) => sitemap[<number>id].loc);
75+
const embed = this.client.util
76+
.embed()
77+
.setColor(this.MDN_BLUE_COLOR)
78+
.setAuthor("MDN Documentation", this.MDN_ICON_URL)
79+
.setTitle(`Search for: ${query}`);
80+
81+
if (!search.length) {
82+
embed.setColor(0xFF0000).setDescription("No results found...");
83+
return message.util.send(embed);
84+
}
85+
86+
if (search.length === 1) {
87+
const res = await fetch(`${baseURL + search[0]}/index.json`);
88+
const doc: MdnDoc = (await res.json()).doc;
89+
const docEmbed = this.client.util
90+
.embed()
91+
.setColor(0xFFFFFF)
92+
.setTitle(doc.pageTitle)
93+
.setURL(`https://developer.mozilla.org/${doc.mdn_url}`)
94+
.setThumbnail(this.MDN_ICON_URL)
95+
.setDescription(doc.summary);
96+
return message.util.send(docEmbed);
97+
}
98+
99+
let results = search.map((path) => `**• [${path.replace(/_|-/g, " ")}](${baseURL}${path})**`);
100+
embed.setDescription(results.join("\n"));
101+
return message.util.send(embed);
102+
}
103+
}
104+
105+
interface MdnDoc {
106+
isMarkdown: boolean,
107+
isTranslated: boolean,
108+
isActive: boolean,
109+
flaws: {},
110+
title: string,
111+
mdn_url: string,
112+
locale: string,
113+
native: string,
114+
sidebarHTML: string,
115+
body: ({
116+
type: "prose" | "specifications" | "browser_compatibility",
117+
value: {
118+
id: string | null,
119+
title: string | null,
120+
isH3: boolean,
121+
// type:prose
122+
content?: string,
123+
// type:specifications
124+
specifications?: ({ bcdSpecificationURL: string, title: string, shortTitle: string })[],
125+
// type:browser_compatibility
126+
dataURL?: string,
127+
// type:specifications | type:browser_compatibility
128+
query?: string,
129+
}
130+
})[],
131+
toc: ({ text: string, id: string })[],
132+
summary: string,
133+
popularity: number,
134+
modified: string, // ISO Date String
135+
other_translations: ({ title: string, locale: string, native: string })[],
136+
source: {
137+
folder: string,
138+
github_url: string,
139+
last_commit_url: string,
140+
filename: string
141+
},
142+
parents: ({ uri: string, title: string })[],
143+
pageTitle: string,
144+
noIndexing: boolean
145+
}

yarn.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@
175175
"resolved" "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz"
176176
"version" "1.1.2"
177177

178+
"@types/flexsearch@^0.7.2":
179+
"integrity" "sha512-Nq0CSpOCyUhaF7tAXSvMtoyBMPGlhNyF+uElhIrrgSiXDmX/bnn9jUX7Us3l81Hzowb9rcgNISke0Nj+3xhd3g=="
180+
"resolved" "https://registry.npmjs.org/@types/flexsearch/-/flexsearch-0.7.2.tgz"
181+
"version" "0.7.2"
182+
178183
"@types/json-schema@^7.0.7":
179184
"integrity" "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ=="
180185
"resolved" "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz"
@@ -959,6 +964,13 @@
959964
"resolved" "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
960965
"version" "2.0.6"
961966

967+
"fast-xml-parser@^4.0.0-beta.6":
968+
"integrity" "sha512-rICUdpYy7HtUxu8WYTvZ8AswOZSZ/L25nJ/Ba51sdLVRdDRotbf05bSeZhQAsspATGSU/Qb6IssYaOFu1ZVw9A=="
969+
"resolved" "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.0-beta.6.tgz"
970+
"version" "4.0.0-beta.6"
971+
dependencies:
972+
"strnum" "^1.0.5"
973+
962974
"fastq@^1.6.0":
963975
"integrity" "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw=="
964976
"resolved" "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz"
@@ -1003,6 +1015,11 @@
10031015
"resolved" "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz"
10041016
"version" "3.2.4"
10051017

1018+
"flexsearch@^0.7.21":
1019+
"integrity" "sha512-W7cHV7Hrwjid6lWmy0IhsWDFQboWSng25U3VVywpHOTJnnAZNPScog67G+cVpeX9f7yDD21ih0WDrMMT+JoaYg=="
1020+
"resolved" "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.21.tgz"
1021+
"version" "0.7.21"
1022+
10061023
"follow-redirects@^1.14.0":
10071024
"integrity" "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA=="
10081025
"resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz"
@@ -1992,6 +2009,11 @@
19922009
"resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
19932010
"version" "3.1.1"
19942011

2012+
"strnum@^1.0.5":
2013+
"integrity" "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
2014+
"resolved" "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz"
2015+
"version" "1.0.5"
2016+
19952017
"supports-color@^5.3.0":
19962018
"integrity" "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="
19972019
"resolved" "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"

0 commit comments

Comments
 (0)