· 6 years ago · Aug 13, 2019, 01:10 AM
1// Installation:
2// npm install lodash tmi.js
3
4const tmi = require('tmi.js');
5const _ = require('lodash');
6
7// Define configuration options
8const opts = {
9 identity: {
10 username: "cheerupbot",
11 password: "<OAUTH_TOKEN>"
12 },
13 channels: [
14 "cheerupbeer"
15 ]
16};
17
18// These map be edited
19let shorthands = {
20 Australia: ['aus', 'straya'],
21 Bangladesh: ['bangla'],
22 Bhutan: ['bootan'],
23 Botswana: ['bots', 'wana'],
24 Brazil: ['brazzers'],
25 'Brunei Darussalam': ['brunei'],
26 Cambodia: ['khmer'],
27 Estonia: ['eesti', 'thegreen'],
28 Germany: ['blurmany'],
29 Guatemala: ['guat'],
30 Indonesia: ['indo'],
31 Israel: ['palestine', 'israel/palestine', 'west bank', 'ps'],
32 Japan: ['nihon', 'nippon'],
33 Kazakhstan: ['kazakh'],
34 'South Korea': ['korea'],
35 Kyrgyzstan: ['kyrgyz'],
36 Laos: ['lao', 'lao pdr'],
37 Latvia: ['lat'],
38 Lithuania: ['lith'],
39 Luxembourg: ['lux'],
40 'North Macedonia': ['macedonia'],
41 Malaysia: ['malay'],
42 Malta: ['rocks'],
43 Mexico: ['mex'],
44 Netherlands: ['holland'],
45 'Papua New Guinea': ['png'],
46 Philippines: ['phils'],
47 Poland: ['poleland'],
48 Romania: ['rom'],
49 Singapore: ['singapura'],
50 'Sri Lanka': ['sri', 'lanka'],
51 Taiwan: ['numba1'],
52 Thailand: ['thai'],
53 Turkey: ['turk'],
54 'United Arab Emirates': ['uae'],
55 'United Kingdom': ['uk'],
56 'United States': ['usa'],
57 'Vietnam': ['viet'],
58};
59
60// autogenerated, don't edit this
61let countryList = [
62 ["Afghanistan", "AF", 0],
63 ["Albania", "AL", 0],
64 ["Algeria", "DZ", 0],
65 ["Andorra", "AD", 0],
66 ["Angola", "AO", 0],
67 ["Antigua and Barbuda", "AG", 0],
68 ["Argentina", "AR", 0],
69 ["Armenia", "AM", 0],
70 ["Australia", "AU", ["Christmas Island", "CX", "Cocos Islands", "CC", "Heard Island and McDonald Islands", "HM", "Norfolk Island", "NF"]],
71 ["Austria", "AT", 0],
72 ["Azerbaijan", "AZ", 0],
73 ["Bahamas", "BS", 0],
74 ["Bahrain", "BH", 0],
75 ["Bangladesh", "BD", 0],
76 ["Barbados", "BB", 0],
77 ["Belarus", "BY", 0],
78 ["Belgium", "BE", 0],
79 ["Belize", "BZ", 0],
80 ["Benin", "BJ", 0],
81 ["Bhutan", "BT", 0],
82 ["Bolivia", "BO", 0],
83 ["Bosnia and Herzegovina", "BA", 0],
84 ["Botswana", "BW", 0],
85 ["Brazil", "BR", 0],
86 ["Brunei Darussalam", "BN", 0],
87 ["Bulgaria", "BG", 0],
88 ["Burkina Faso", "BF", 0],
89 ["Burundi", "BI", 0],
90 ["Cabo Verde", "CV", 0],
91 ["Cambodia", "KH", 0],
92 ["Cameroon", "CM", 0],
93 ["Canada", "CA", 0],
94 ["Central African Republic", "CF", 0],
95 ["Chad", "TD", 0],
96 ["Chile", "CL", 0],
97 ["China", "CN", 0],
98 ["Colombia", "CO", 0],
99 ["Comoros", "KM", 0],
100 ["Congo", "CG", 0],
101 ["Costa Rica", "CR", 0],
102 ["C\u00f4te d'Ivoire", "CI", 0],
103 ["Croatia", "HR", 0],
104 ["Cuba", "CU", 0],
105 ["Cyprus", "CY", 0],
106 ["Czechia", "CZ", 0],
107 ["Denmark", "DK", ["Faroe Islands", "FO", "Greenland", "GL"]],
108 ["Djibouti", "DJ", 0],
109 ["Dominica", "DM", 0],
110 ["Dominican Republic", "DO", 0],
111 ["Ecuador", "EC", 0],
112 ["Egypt", "EG", 0],
113 ["El Salvador", "SV", 0],
114 ["Equatorial Guinea", "GQ", 0],
115 ["Eritrea", "ER", 0],
116 ["Estonia", "EE", 0],
117 ["Eswatini", "SZ", 0],
118 ["Ethiopia", "ET", 0],
119 ["Fiji", "FJ", 0],
120 ["Finland", "FI", ["\u00c5land Islands", "AX"]],
121 ["France", "FR", ["French Guiana", "GF", "French Polynesia", "PF", "French Southern Territories", "TF", "Guadeloupe", "GP", "Martinique", "MQ", "Mayotte", "YT", "New Caledonia", "NC", "R\u00e9union", "RE", "Saint Barth\u00e9lemy", "BL", "Saint Martin", "MF", "Saint Pierre and Miquelon", "PM", "Wallis and Futuna", "WF"]],
122 ["Gabon", "GA", 0],
123 ["Gambia", "GM", 0],
124 ["Georgia", "GE", 0],
125 ["Germany", "DE", 0],
126 ["Ghana", "GH", 0],
127 ["Greece", "GR", 0],
128 ["Grenada", "GD", 0],
129 ["Guatemala", "GT", 0],
130 ["Guinea", "GN", 0],
131 ["Guinea-Bissau", "GW", 0],
132 ["Guyana", "GY", 0],
133 ["Haiti", "HT", 0],
134 ["Vatican City", "VA", 0],
135 ["Honduras", "HN", 0],
136 ["Hong Kong", "HK", 0],
137 ["Hungary", "HU", 0],
138 ["Iceland", "IS", 0],
139 ["India", "IN", 0],
140 ["Indonesia", "ID", 0],
141 ["Iran", "IR", 0],
142 ["Iraq", "IQ", 0],
143 ["Ireland", "IE", 0],
144 ["Israel", "IL", 0],
145 ["Italy", "IT", 0],
146 ["Jamaica", "JM", 0],
147 ["Japan", "JP", 0],
148 ["Jordan", "JO", 0],
149 ["Kazakhstan", "KZ", 0],
150 ["Kenya", "KE", 0],
151 ["Kiribati", "KI", 0],
152 ["North Korea", "KP", 0],
153 ["South Korea", "KR", 0],
154 ["Kuwait", "KW", 0],
155 ["Kyrgyzstan", "KG", 0],
156 ["Laos", "LA", 0],
157 ["Latvia", "LV", 0],
158 ["Lebanon", "LB", 0],
159 ["Lesotho", "LS", 0],
160 ["Liberia", "LR", 0],
161 ["Libya", "LY", 0],
162 ["Liechtenstein", "LI", 0],
163 ["Lithuania", "LT", 0],
164 ["Luxembourg", "LU", 0],
165 ["Macao", "MO", 0],
166 ["North Macedonia", "MK", 0],
167 ["Madagascar", "MG", 0],
168 ["Malawi", "MW", 0],
169 ["Malaysia", "MY", 0],
170 ["Maldives", "MV", 0],
171 ["Mali", "ML", 0],
172 ["Malta", "MT", 0],
173 ["Marshall Islands", "MH", 0],
174 ["Mauritania", "MR", 0],
175 ["Mauritius", "MU", 0],
176 ["Mexico", "MX", 0],
177 ["Micronesia", "FM", 0],
178 ["South Korea", "MD", 0],
179 ["Monaco", "MC", 0],
180 ["Mongolia", "MN", 0],
181 ["Montenegro", "ME", 0],
182 ["Morocco", "MA", 0],
183 ["Mozambique", "MZ", 0],
184 ["Myanmar", "MM", 0],
185 ["Namibia", "NA", 0],
186 ["Nauru", "NR", 0],
187 ["Nepal", "NP", 0],
188 ["Netherlands", "NL", ["Aruba", "AW", "Bonaire Sint Eustatius Saba", "BQ", "Cura\u00e7ao", "CW", "Sint Maarten", "SX"]],
189 ["New Zealand", "NZ", ["Cook Islands", "CK", "Niue", "NU", "Tokelau", "TK"]],
190 ["Nicaragua", "NI", 0],
191 ["Niger", "NE", 0],
192 ["Nigeria", "NG", 0],
193 ["Norway", "NO", ["Bouvet Island", "BV", "Svalbard Jan Mayen", "SJ"]],
194 ["Oman", "OM", 0],
195 ["Pakistan", "PK", 0],
196 ["Palau", "PW", 0],
197 ["Palestine, State of", "PS", 0],
198 ["Panama", "PA", 0],
199 ["Papua New Guinea", "PG", 0],
200 ["Paraguay", "PY", 0],
201 ["Peru", "PE", 0],
202 ["Philippines", "PH", 0],
203 ["Poland", "PL", 0],
204 ["Portugal", "PT", 0],
205 ["Qatar", "QA", 0],
206 ["Romania", "RO", 0],
207 ["Russia", "RU", 0],
208 ["Rwanda", "RW", 0],
209 ["Saint Kitts and Nevis", "KN", 0],
210 ["Saint Lucia", "LC", 0],
211 ["Saint Vincent and the Grenadines", "VC", 0],
212 ["Samoa", "WS", 0],
213 ["San Marino", "SM", 0],
214 ["Sao Tome and Principe", "ST", 0],
215 ["Saudi Arabia", "SA", 0],
216 ["Senegal", "SN", 0],
217 ["Serbia", "RS", 0],
218 ["Seychelles", "SC", 0],
219 ["Sierra Leone", "SL", 0],
220 ["Singapore", "SG", 0],
221 ["Slovakia", "SK", 0],
222 ["Slovenia", "SI", 0],
223 ["Solomon Islands", "SB", 0],
224 ["Somalia", "SO", 0],
225 ["South Africa", "ZA", 0],
226 ["South Sudan", "SS", 0],
227 ["Spain", "ES", 0],
228 ["Sri Lanka", "LK", 0],
229 ["Sudan", "SD", 0],
230 ["Suriname", "SR", 0],
231 ["Sweden", "SE", 0],
232 ["Switzerland", "CH", 0],
233 ["Syria", "SY", 0],
234 ["Taiwan", "TW", 0],
235 ["Tajikistan", "TJ", 0],
236 ["Tanzania", "TZ", 0],
237 ["Thailand", "TH", 0],
238 ["Timor-Leste", "TL", 0],
239 ["Togo", "TG", 0],
240 ["Tonga", "TO", 0],
241 ["Trinidad and Tobago", "TT", 0],
242 ["Tunisia", "TN", 0],
243 ["Turkey", "TR", 0],
244 ["Turkmenistan", "TM", 0],
245 ["Tuvalu", "TV", 0],
246 ["Uganda", "UG", 0],
247 ["Ukraine", "UA", 0],
248 ["United Arab Emirates", "AE", 0],
249 ["United Kingdom", "GB", ["Anguilla", "AI", "Bermuda", "BM", "British Indian Ocean Territory", "IO", "Cayman Islands", "KY", "Falkland Islands", "FK", "Gibraltar", "GI", "Guernsey", "GG", "Isle of Man", "IM", "Jersey", "JE", "Montserrat", "MS", "Pitcairn", "PN", "Saint Helena Ascension Island Tristan da Cunha", "SH", "South Georgia and the South Sandwich Islands", "GS", "Turks and Caicos Islands", "TC"]],
250 ["United States", "US", ["American Samoa", "AS", "Guam", "GU", "Northern Mariana Islands", "MP", "Puerto Rico", "PR"]],
251 ["Uruguay", "UY", 0],
252 ["Uzbekistan", "UZ", 0],
253 ["Vanuatu", "VU", 0],
254 ["Venezuela", "VE", 0],
255 ["Vietnam", "VN", 0],
256 ["Yemen", "YE", 0],
257 ["Zambia", "ZM", 0],
258 ["Zimbabwe", "ZW", 0],
259]; // autogenerated, don't edit this
260
261
262let namesToCountries = {};
263for (let [name, iso, alts] of countryList) {
264 namesToCountries[name.toLowerCase()] = name;
265 namesToCountries[iso.toLowerCase()] = name;
266 if (alts) {
267 for (let alt of alts) {
268 namesToCountries[alt.toLowerCase()] = name;
269 }
270 }
271}
272for (let key in shorthands) {
273 for (let value in shorthands[key]) {
274 namesToCountries[value.toLowerCase()] = key;
275 }
276}
277console.log(namesToCountries);
278
279function countryMatches(a, b) {
280 a = a.toLowerCase().trim();
281 b = b.toLowerCase().trim();
282
283 if (a === b && !!a) return true;
284
285 let a1 = namesToCountries[a];
286 let b1 = namesToCountries[b];
287
288 return a1 === b1 && !!a1;
289}
290
291function countryExists(a) {
292 return !!namesToCountries(a.toLowerCase().trim());
293}
294
295class Round {
296 constructor(parent) {
297 this.parent = parent;
298 this.guesses = new Map();
299 }
300 guess(player, country) {
301 this.guesses.set(player, country);
302 }
303 playersWhoSelected(answer) {
304 let result = [];
305 for (let [player, options] of this.guesses) {
306 if (countryMatches(options.answer, guess)) {
307 result.push(options.displayname);
308 }
309 }
310 return result;
311 }
312}
313
314class Game {
315 constructor(parent) {
316 this.parent = parent;
317 this.streaks = new Map();
318 }
319 addWinner(displayname) {
320 let obj = this.streaks.get(displayname);
321 if (!obj) {
322 obj = { streak: 0 };
323 this.streaks.set(displayname, obj);
324 }
325 obj.streak += 1;
326 }
327 sortedWinners() {
328 let winners = [];
329 for (let [player, obj] of this.streaks) {
330 winner.push([player, obj.streak]);
331 }
332 winner = _.orderBy(winner, x => x[1], 'desc');
333 return winner;
334 }
335}
336
337class Controller {
338 constructor(client) {
339 this.client = client;
340 this.newRound();
341 this.newGame();
342 }
343 newRound() {
344 this.round = new Round(this);
345 }
346 newGame() {
347 this.game = new Game(this);
348 }
349
350 recv(channel, userstate, msg, self, isWhisper) {
351 const displayname = userstate['display-name'];
352 const username = userstate['username'];
353 const userkey = username['user-id'];
354
355 if (username === 'cheerupbeer') {
356 if (/^!answer\b/i.test(msg)) {
357 let answer = /^![a-z]+\s([^\n]+)/i.exec(msg)[1];
358 if (answer) {
359 answer = answer.trim();
360 let winners = this.round.playersWhoSelected(answer);
361 for (let winner of winners) {
362 this.game.addWinner(winner);
363 }
364 this.client.say(target, `The winners were ${winners.map(w => '@'+w).join(', ')}`);
365 }
366 }
367 }
368 if (isWhisper && /^!g(?:uess)?\b/i.test(msg)) {
369 let answer = /^![a-z]+\s([^\n]+)/i.exec(msg)[1];
370 if (answer) {
371 answer = answer.trim();
372 if (countryExists(answer)) {
373 this.round.set(userkey, {
374 displayname, answer
375 });
376 } else {
377 this.client.say(target, `@${displayname} I'm sorry, I don't know the country you guessed!`);
378 }
379 }
380 }
381 if (/^!leaderboard\b/i.test(msg)) {
382 let sortedWinners = this.game.sortedWinners();
383 let strs = [''];
384 for (let [displayname, streak] of sortedWinners) {
385 let str1 = strs[strs.length - 1];
386 let str2 = `${streak} ${displayname}`;
387 let suffix = ' ; ' + str2;
388 // Limit message lengths to 500
389 if ((str1 + suffix).length >= 500) {
390 strs.push(str2);
391 } else {
392 strs[strs.length - 1] += suffix;
393 }
394 }
395 for (let str of strs) {
396 this.client.say(str);
397 }
398 }
399 }
400}
401
402// Create a client with our options
403const client = new tmi.client(opts);
404
405// Register our event handlers (defined below)
406client.on('message', onMessageHandler);
407client.on('whisper', onWhisperHandler);
408client.on('connected', onConnectedHandler);
409
410// Connect to Twitch:
411client.connect();
412
413const controller = new Controller(client);
414
415// https://github.com/tmijs/docs/blob/gh-pages/_posts/v1.4.2/2019-03-03-Events.md#message
416
417// Called every time a message comes in
418function onMessageHandler (channel, userstate, message, self) {
419 if (self) { return; } // Ignore messages from the bot
420
421 // If the command is known, let's execute it
422 controller.recv(channel, userstate, msg.trim(), self, false);
423}
424
425function onWhisperHandler (channel, userstate, message, self) {
426 if (self) { return; } // Ignore messages from the bot
427
428 // If the command is known, let's execute it
429 controller.recv(channel, userstate, msg.trim(), self, true);
430}
431
432// Called every time the bot connects to Twitch chat
433function onConnectedHandler (addr, port) {
434 console.log(`* Connected to ${addr}:${port}`);
435}