Skip to content

Commit 9a1a1ce

Browse files
committed
Misc: Add 'wikipedia' command
1 parent 5c6ce52 commit 9a1a1ce

File tree

3 files changed

+343
-3
lines changed

3 files changed

+343
-3
lines changed

src/bot-modules/misc/botmodule.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
"randpoke": {
1616
"group": "voice"
1717
},
18-
"link": {
19-
"group": "mod"
18+
"wikipedia": {
19+
"group": "voice"
2020
}
2121
},
2222
"langfiles": [
2323
"./commands/fun.translations",
24-
"./commands/randomanswer.translations"
24+
"./commands/randomanswer.translations",
25+
"./commands/wiki.translations"
2526
],
2627
"description": "Misc Commands Plugins"
2728
}
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/**
2+
* Commands File
3+
*
4+
* wiki: Displays a overview of a topic from Wikipedia
5+
*/
6+
7+
'use strict';
8+
9+
const Path = require('path');
10+
const HTTPS = require('https');
11+
12+
const BufferCache = Tools('cache').BufferCache;
13+
14+
const Text = Tools('text');
15+
16+
const Lang_File = Path.resolve(__dirname, 'wiki.translations');
17+
18+
const FAKE_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.3";
19+
20+
const wgetCache = new BufferCache(512, 5 * 60 * 1000);
21+
22+
function wget(url, callback) {
23+
if (wgetCache.has(url)) {
24+
return callback(wgetCache.get(url));
25+
}
26+
HTTPS.get(url, {
27+
headers: {
28+
"User-Agent": FAKE_USER_AGENT,
29+
},
30+
}, response => {
31+
if (response.statusCode !== 200) {
32+
if (response.statusCode === 404) {
33+
return callback(null, new Error("404 - Not found"));
34+
} else {
35+
return callback(null, new Error("" + response.statusCode));
36+
}
37+
}
38+
let data = '';
39+
response.on('data', chunk => {
40+
data += chunk;
41+
});
42+
response.on('end', () => {
43+
wgetCache.cache(url, data);
44+
callback(data);
45+
});
46+
response.on('error', err => {
47+
callback(null, err);
48+
});
49+
}).on('error', err => {
50+
callback(null, err);
51+
});
52+
}
53+
54+
const NO_RESULTS_ERROR = "No results found";
55+
56+
function findWikiPage(topic, locale, callback) {
57+
const url = 'https://' + locale +
58+
'.wikipedia.org/w/api.php?action=query&format=json&list=search&srprop=&srlimit=1&srsearch=' +
59+
encodeURIComponent(topic) + '&srinfo=suggestion';
60+
61+
wget(url, function (data, err) {
62+
if (err) {
63+
return callback(null, err);
64+
}
65+
66+
let title;
67+
let pageId;
68+
69+
try {
70+
const results = JSON.parse(data).query.search;
71+
72+
if (!Array.isArray(results)) {
73+
throw new Error("Search results is not an array");
74+
}
75+
76+
if (results.length === 0) {
77+
return callback(null, new Error(NO_RESULTS_ERROR));
78+
}
79+
80+
title = results[0].title;
81+
82+
if (typeof title !== "string") {
83+
throw new Error("Title is not a string");
84+
}
85+
86+
pageId = results[0].pageid;
87+
88+
if (typeof pageId !== "string" && typeof pageId !== "number") {
89+
throw new Error("PageId is not a number or a string");
90+
}
91+
} catch (ex) {
92+
return callback(null, new Error("Invalid response from Wikipedia API"));
93+
}
94+
95+
return callback({
96+
title,
97+
pageId,
98+
});
99+
});
100+
}
101+
102+
function isValidHttpsUrl(url) {
103+
try {
104+
const u = new URL(url);
105+
106+
return u.protocol === "https:";
107+
} catch (ex) {
108+
return false;
109+
}
110+
}
111+
112+
function getWikiSummary(locale, title, pageId, callback) {
113+
const url = 'https://' + locale +
114+
'.wikipedia.org/w/api.php?action=query&prop=extracts|pageimages&pithumbsize=100&format=json&explaintext=&exintro=&titles=' +
115+
encodeURIComponent(title);
116+
117+
wget(url, function (data, err) {
118+
if (err) {
119+
return callback(null, err);
120+
}
121+
122+
let summary = "";
123+
let image = "";
124+
let imageWidth = 100;
125+
let imageHeight = 100;
126+
127+
try {
128+
const pages = JSON.parseNoPrototype(data).query.pages;
129+
130+
const page = pages[pageId];
131+
132+
if (!page || typeof page !== "object") {
133+
throw new Error("Unexpected response: No page object");
134+
}
135+
136+
summary = page.extract;
137+
138+
if (typeof summary !== "string") {
139+
throw new Error("Unexpected response: Invalid summary format");
140+
}
141+
142+
if (page.thumbnail && typeof page.thumbnail === "object" &&
143+
typeof page.thumbnail.source === "string" && isValidHttpsUrl(page.thumbnail.source) &&
144+
typeof page.thumbnail.width === "number" && typeof page.thumbnail.height === "number") {
145+
image = page.thumbnail.source;
146+
imageWidth = page.thumbnail.width;
147+
imageHeight = page.thumbnail.height;
148+
}
149+
} catch (ex) {
150+
return callback(null, new Error("Invalid response from Wikipedia API"));
151+
}
152+
153+
return callback({
154+
summary,
155+
image: image ? {
156+
url: image,
157+
width: imageWidth,
158+
height: imageHeight,
159+
} : null,
160+
});
161+
});
162+
}
163+
164+
function botCanHtml(room, App) {
165+
let roomData = App.bot.rooms[room];
166+
let botid = Text.toId(App.bot.getBotNick());
167+
return (roomData && roomData.users[botid] && App.parser.equalOrHigherGroup({ group: roomData.users[botid] }, 'bot'));
168+
}
169+
170+
const downloadingFlag = Object.create(null);
171+
172+
function markDownload(user, b) {
173+
if (b === false) {
174+
if (downloadingFlag[user]) delete downloadingFlag[user];
175+
} else if (b === true) {
176+
downloadingFlag[user] = true;
177+
} else {
178+
return downloadingFlag[user] || false;
179+
}
180+
}
181+
182+
const Available_Languages = ["en", "es"];
183+
184+
const abreviations = {
185+
"english": "en",
186+
"spanish": "es",
187+
};
188+
189+
const MAX_SUMMARY_SIZE = 3000;
190+
191+
module.exports = {
192+
wiki: "wikipedia",
193+
wikipedia: function (App) {
194+
this.setLangFile(Lang_File);
195+
196+
const args = this.args;
197+
198+
if (args.length === 0 || args.length > 2) {
199+
return this.errorReply(this.usage({ desc: this.mlt('topic') }, { desc: this.mlt('lang'), optional: true }));
200+
}
201+
202+
const topic = this.args[0].trim();
203+
204+
if (!topic) {
205+
return this.errorReply(this.usage({ desc: this.mlt('topic') }, { desc: this.mlt('lang'), optional: true }));
206+
}
207+
208+
let lang = Text.toId(args[1] || abreviations[this.lang] || 'en');
209+
if (abreviations[lang]) {
210+
lang = abreviations[lang];
211+
}
212+
213+
if (!Available_Languages.includes(lang)) {
214+
return this.errorReply(this.mlt('unavaillang') + ". " + this.mlt('availlangs') + ": " + Available_Languages.join(", "));
215+
}
216+
217+
let canUseHtmlInRoom = this.getRoomType(this.room) === 'chat' &&
218+
!this.isGroupChat(this.room) && this.can('usagedata', this.room) &&
219+
botCanHtml(this.room, App);
220+
221+
if (markDownload(this.byIdent.id)) return this.errorReply(this.mlt('busy'));
222+
223+
markDownload(this.byIdent.id, true);
224+
225+
findWikiPage(topic, lang, (page, errFindPage) => {
226+
if (errFindPage) {
227+
markDownload(this.byIdent.id, false);
228+
return this.errorReply(this.mlt('errfind') + ": " + (errFindPage.message === NO_RESULTS_ERROR ? this.mlt('notfound') : errFindPage.message));
229+
}
230+
231+
if (page === null) {
232+
markDownload(this.byIdent.id, false);
233+
return this.errorReply(this.mlt('errfind') + ": " + this.mlt('notfound'));
234+
}
235+
236+
const title = page.title;
237+
const pageId = page.pageId + "";
238+
239+
const fullLink = 'https://' + lang + '.wikipedia.org/wiki/' + encodeURIComponent(title);
240+
241+
getWikiSummary(lang, title, pageId, (pageInfo, err) => {
242+
markDownload(this.byIdent.id, false);
243+
244+
if (errFindPage) {
245+
return this.errorReply(this.mlt('errfind') + ": " + err.message);
246+
}
247+
248+
if (!pageInfo || !pageInfo.summary) {
249+
return this.errorReply(this.mlt('errfind') + ": " + this.mlt('notfound'));
250+
}
251+
252+
let summary = pageInfo.summary;
253+
254+
if (summary.length > MAX_SUMMARY_SIZE) {
255+
summary = summary.substring(0, MAX_SUMMARY_SIZE).trim();
256+
257+
if (!summary.endsWith(".")) {
258+
summary += "...";
259+
}
260+
}
261+
262+
const image = pageInfo.image;
263+
264+
const code = "!code " + title + "\n\n" + summary + "\n\n" + fullLink;
265+
266+
let html = '';
267+
268+
html += '<table>';
269+
270+
html += '<tr>';
271+
272+
if (image) {
273+
html += '<td style="padding: 8px;">';
274+
html += '<img src="' + Text.escapeHTML(image.url) + '" width="' + Text.escapeHTML(image.width) + '" height="' + Text.escapeHTML(image.height) + '">';
275+
html += '</td>';
276+
}
277+
278+
html += '<td style="padding: 8px;">';
279+
280+
html += '<p style="font-size: large; font-weight: bold; text-decoration:underline;">' + Text.escapeHTML(title) + '</p>';
281+
282+
if (canUseHtmlInRoom) {
283+
html += '<p>' + Text.escapeHTML(summary).replace(/\n/g, "<br>") + '</p>';
284+
285+
html += '<p><a href="' + Text.escapeHTML(fullLink) + '" target="_blank">' + Text.escapeHTML(fullLink) + '</a></p>';
286+
}
287+
288+
html += '</td>';
289+
290+
html += '</tr>';
291+
292+
if (!canUseHtmlInRoom) {
293+
html += '<tr>';
294+
html += '<td colspan="2">';
295+
296+
html += '<p>' + Text.escapeHTML(summary).replace(/\n/g, "<br>") + '</p>';
297+
298+
html += '<p><a href="' + Text.escapeHTML(fullLink) + '" target="_blank">' + Text.escapeHTML(fullLink) + '</a></p>';
299+
300+
html += '</td>';
301+
html += '</tr>';
302+
}
303+
304+
html += '</table>';
305+
306+
this.htmlRestrictReply(html, "wikipedia", code);
307+
});
308+
});
309+
},
310+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Translations file
2+
3+
@ misc-wiki
4+
5+
%english
6+
7+
$topic = Topic
8+
$lang = Language
9+
10+
$busy = Currently downloading the data, try again in a few seconds
11+
12+
$unavaillang = Unavailable language
13+
$availlangs = Available languages
14+
15+
$errfind = Could not find the Wikipedia page
16+
$notfound = Page not found
17+
18+
%spanish
19+
20+
$topic = Tema
21+
$lang = Idioma
22+
23+
$busy = Ahora mismo estoy descargando los datos, inténtalo de nuevo en unos segundos
24+
25+
$unavaillang = Idioma no disponible
26+
$availlangs = Idiomas disponibles
27+
28+
$errfind = No se pudo encontrar la página en Wikipedia
29+
$notfound = Página no encontrada

0 commit comments

Comments
 (0)