Uses 11217K of libraries. Click here for Pure Java version (30435L/185K).
1 | !7 |
2 | |
3 | set flag JDA40. |
4 | set flag DynModule. |
5 | |
6 | import java.util.function.Consumer; |
7 | import net.dv8tion.jda.api.requests.restaction.*; |
8 | |
9 | // Note: Bot is always started & logs lines, |
10 | // but doesn't respond when enabled is false. |
11 | |
12 | cm DiscordBot > DynPrintLogAndEnabled { |
13 | transient JDA bot; |
14 | transient new Set<Long> msgsReactedTo; |
15 | transient double deleteDelay = 60.0; |
16 | transient bool doDelete; |
17 | transient int maxMonologue = 5; |
18 | transient bool selfTalk = false; |
19 | transient Color imageEmbedMysteriousLineColor = colorFromHex("36393f"); |
20 | |
21 | transient new InheritableThreadLocal<MessageChannel> currentChannel; |
22 | transient new InheritableThreadLocal<Message> respondingTo; |
23 | |
24 | transient new L<Long> lastPosted; |
25 | transient GazelleContextCache_v2 contextCache; |
26 | |
27 | transient Set<S> acceptablePurposes = litciset(""); |
28 | |
29 | switchable S gazoogleDefaultCountry = "us"; |
30 | |
31 | switchable S powerWordURL = "https://gazelle.rocks/beaHTML/274191"; |
32 | |
33 | transient LS voice9speakers = tok_splitAtComma("p225, p226, p227, p228, p229, p230, p231, p232, p233, p234, p236, p237, p238, p239, p240, p241, p243, p244, p245, p246, p247, p248, p249, p250, p251, p252, p253, p254, p255, p256, p257, p258, p259, p260, p261, p262, p263, p264, p265, p266, p267, p268, p269, p270, p271, p272, p273, p274, p275, p276, p277, p278, p279, p280, p281, p282, p283, p284, p285, p286, p287, p288, p292, p293, p294, p295, p297, p298, p299, p300, p301, p302, p303, p304, p305, p306, p307, p308, p310, p311, p312, p313, p314, p316, p317, p318, p323, p326, p329, p330, p333, p334, p335, p336, p339, p340, p341, p343, p345, p347, p351, p360, p361, p362, p363, p364, p374, p376"); |
34 | |
35 | transient Consumer<Throwable> onQueueError = error -> { |
36 | temp enter(); |
37 | printStackTrace(error); |
38 | }; |
39 | |
40 | // map user command message id to bot message id |
41 | // (for editing) |
42 | Map<Long> cmdToResponse = syncMap(); |
43 | |
44 | start-thread { |
45 | vm_cleanPrints(); |
46 | logModuleOutput(); |
47 | dm_useLocalMechListCopies(); |
48 | |
49 | print("Making cache"); |
50 | time "Made cache" { |
51 | contextCache = new GazelleContextCache_v2(me()); |
52 | ownResource(contextCache.listenToMessages()); |
53 | contextCache.fill(); |
54 | } |
55 | |
56 | // TODO: log JDA output |
57 | bot = discordBot40(new ListenerAdapter { |
58 | public void onMessageUpdate(MessageUpdateEvent e) pcall { |
59 | temp enter(); |
60 | ret if !enabled || !licensed(); |
61 | |
62 | long msgID = e.getMessage().getIdLong(); |
63 | S content = e.getMessage().getContentRaw(); |
64 | print(content := quote(content)); |
65 | O lineConcept = dm_discord_lineForMsgID_unimported(msgID); |
66 | S rendered = msgID + ": " + content; |
67 | if (lineConcept == null) |
68 | ret with print("Weird: Message author, but not found: " + rendered); |
69 | call(lineConcept, '_setField, editedText := content); |
70 | print("Message edited: " + rendered); |
71 | |
72 | handleMessage(e, e.getAuthor(), e.getMember(), e.getMessage(), false); |
73 | } |
74 | |
75 | public void onMessageReceived(MessageReceivedEvent e) pcall { |
76 | temp enter(); |
77 | ret if !licensed(); |
78 | |
79 | handleMessage(e, e.getAuthor(), e.getMember(), e.getMessage(), true); |
80 | } |
81 | |
82 | void handleMessage(GenericMessageEvent e, User author, Member member, Message message, bool newMsg) { |
83 | bool bot = author.isBot(); |
84 | long msgID = message.getIdLong(); |
85 | long userID = author.getIdLong(); |
86 | S userName = member == null ? null : member.getNickname(); // the changeable nick name - null sometimes? |
87 | if (userName == null && member != null) userName = member.getEffectiveName(); |
88 | if (userName == null) userName = author.getName(); |
89 | |
90 | final Message msg = message; |
91 | |
92 | print("Channel type: " + e.getChannelType()); |
93 | bool isPrivate = e.getChannelType() == ChannelType.PRIVATE; |
94 | S content = trim(backtickUnquote(trim(msg.getContentRaw()))); |
95 | print("Msg from " + userName + ": " + content); |
96 | if (empty(content)) ret; |
97 | fS originalContent = content; |
98 | |
99 | O user = userConcept(author); |
100 | |
101 | if (newMsg) { |
102 | S crud = dm_gazelle_linesCRUD(); |
103 | O channel = dm_call(crud, 'uniqChannel, msg.getChannel().getIdLong()); |
104 | dm_call(crud, 'cset, channel, litobjectarray(name := msg.getChannel().getName())); |
105 | |
106 | O lineConcept = dm_call(crud, 'uniqConcept, litObjectArrayAsObject(+msgID)); |
107 | dm_call(crud, 'cset, lineConcept, litobjectarray( |
108 | text := content, |
109 | +bot, +isPrivate, |
110 | author := user, +channel)); |
111 | vmBus_send('newDiscordLine, lineConcept); |
112 | } |
113 | |
114 | ret if !enabled; |
115 | |
116 | final MessageChannel ch = e.getChannel(); |
117 | long channelID = ch.getIdLong(); |
118 | L<GazelleLine> linesInChannel = dm_discord_linesInChannel(channelID); |
119 | |
120 | // monologue prevention |
121 | |
122 | if (bot) { |
123 | int monologueLength = cast dm_call(dm_gazelle_linesCRUD(), 'monologueLength, channelID); |
124 | if (monologueLength >= maxMonologue) ret; |
125 | //Long last = get(lastPosted, l(lastPosted)-3); |
126 | //if (last != null && now() < last+10000) ret; |
127 | } |
128 | |
129 | temp tempSetTL(currentChannel, ch); |
130 | temp tempSetTL(respondingTo, msg); |
131 | |
132 | // module meta ("gazelle stop" etc.) |
133 | |
134 | S userForModulesManager = "discord user " + userID; |
135 | |
136 | if (!bot) { |
137 | for (T3S t : dm_gazelle_allRulesWithPurpose("moduleMeta")) { |
138 | PairS p = splitAtDoubleArrow_pair(t.a); |
139 | if (eqic(p.b, "stop module") && nempty(p.a) && matchX2(p.a, content)) { |
140 | print("Stopping modules"); |
141 | postText((S) dm_call(dm_gazelle_modulesManager(), 'deleteAllModulesForUser, userForModulesManager, silentIfEmpty := true)); |
142 | } |
143 | } |
144 | } |
145 | |
146 | new Matches m; |
147 | |
148 | try { |
149 | content = fixNewLines(content); |
150 | content = replacePrefix("!eval\n", "!eval ", content); |
151 | |
152 | content = regexpReplace_direct(content, "^=" + regexpLookahead("[\\.\\w0-9\\s\\]\\{]"), "!eval "); |
153 | |
154 | // g me |
155 | if (eqic(content, "g me")) |
156 | ret with postInChannel(ch, newMsg, msgID, author.getId()); |
157 | |
158 | // G command - power word search |
159 | |
160 | if (regexpFindIC("^g\\b", content)) { |
161 | S answer = loadPageWithParams(powerWordURL, text := content, user := userName); |
162 | if (nempty(answer)) |
163 | ret with postInChannel(ch, newMsg, msgID, answer); |
164 | } |
165 | |
166 | // GAZOOGLE command - TODO: find out when it's just a sentence starting with "gazoogle" |
167 | |
168 | S googleQuery = regexpFirstGroupIC("^gazoogle\\b[,:.!]*\\s*(.*)", content); |
169 | if (nempty(googleQuery)) { |
170 | /*if (newMsg)*/ sendTyping(); |
171 | MapSO urlParams = litmap(gl := gazoogleDefaultCountry); |
172 | ret with postInChannel(ch, newMsg, msgID, or2_rev("Gazoogle doesn't know :(", lines(map(dm_webSearch_all(googleQuery, urlParams), |
173 | result -> result! + " <" + result.url() + ">")))); |
174 | } |
175 | |
176 | // GAZTUBE/GAZELLETUBE command (YouTube search) |
177 | |
178 | S ytQuery = regexpFirstGroupIC("^gaz\\w*tube\\b[,:.!]*\\s*(.*)", content); |
179 | if (nempty(ytQuery)) { |
180 | /*if (newMsg)*/ sendTyping(); |
181 | ret with postInChannel(ch, newMsg, msgID, |
182 | or2_rev("GazelleTube is showing a black screen :(", |
183 | mapSingle(dm_youTubeSearch(ytQuery), |
184 | result -> result.url()))); |
185 | } |
186 | |
187 | // GPIC/GAZPIC command (Pixabay search) |
188 | |
189 | S picQuery = regexpFirstGroupIC("(?:gpic|gazpic)\\b[,:.!]*\\s*(.*)", content); |
190 | if (nempty(picQuery)) { |
191 | sendTyping(); |
192 | S imgURL = cast mapGet(first(pixabaySearch(picQuery)), "webformatURL"); |
193 | ret with postInChannel(ch, newMsg, msgID, |
194 | or2(imgURL, "No image found on Pixabay")); |
195 | } |
196 | |
197 | // gsay / gazsay / gazellesay / !say |
198 | S toSay = regexpFirstGroupIC("(?:!say|gsay|gazsay|gazellesay)\\b[,:.!]*\\s*(.*)", content); |
199 | if (nempty(toSay)) { |
200 | sendTyping(); |
201 | S fileName = "speech.mp3"; |
202 | |
203 | toSay = shorten(trim(toSay), 500); |
204 | if (eqic(toSay, "voices")) |
205 | ret with postInChannel(ch, newMsg, msgID, "Voice list: https://mouth.gazelle.rocks/beaHTML/22"); |
206 | |
207 | LS groups = regexpFirstGroupsIC("voice\\s*(\\d+)\\b[,:.!]*\\s*(.*)", toSay); |
208 | S voiceNr = null, speaker = null; |
209 | if (groups != null) { |
210 | voiceNr = first(groups); |
211 | toSay = second(groups); |
212 | |
213 | groups = regexpFirstGroupsIC("speaker\\s*(\\S+)\\b[,:.!]*\\s*(.*)", toSay); |
214 | if (groups != null) { |
215 | speaker = first(groups); |
216 | if (eqic(speaker, "random")) { |
217 | speaker = random(voice9speakers); |
218 | fileName = "voice-9-" + speaker + ".mp3"; |
219 | } |
220 | toSay = second(groups); |
221 | } |
222 | } |
223 | |
224 | File f = programFile(fileName); |
225 | postBinaryPageToFile(f, "https://mouth.gazelle.rocks/beaHTML/16", |
226 | page := 0, +voiceNr, +speaker, text := toSay); |
227 | ret with uploadFileInChannel(f); |
228 | } |
229 | |
230 | S evalHelp = regexpFirstGroupIC("^\\!eval[\\s:\\-]*help\\s+(.*)", content); |
231 | if (evalHelp != null) |
232 | ret with postInChannel(ch, newMsg, msgID, |
233 | htmlDemo(javaxSourceToHTML(evalHelp))); |
234 | |
235 | if (swicOneOf(content, "!eval ", "!fresh ", "!real-eval ", "!safe-eval ") || eqic(content, "!fresh")) { |
236 | O safetyCheck = null; |
237 | bool authed = dm_discord_userCanEval(userID); |
238 | if (swic_trim(content, "!safe-eval ", m)) { |
239 | content = "!eval " + m.rest(); |
240 | authed = false; |
241 | } |
242 | |
243 | if (regexpMatches("![a-z\\-]+\\s+again", content)) { |
244 | GazelleLine last = dm_discord_nextToLastEvalByUser(userID); |
245 | if (last == null) ret with postInChannel(ch, newMsg, msgID, "No last eval found"); |
246 | content = replaceSuffix("again", afterSpace(last.text), content); |
247 | } |
248 | |
249 | if (!authed) |
250 | safetyCheck = botEval_strictSafetyCheck(); |
251 | |
252 | /*if (newMsg)*/ sendTyping(); |
253 | |
254 | var post = voidfunc(S s) { |
255 | if (s != null) |
256 | postInChannel(ch, newMsg, msgID, shorten(ifEmpty(s, [[""]]), 1000)) |
257 | }; |
258 | |
259 | if (startsWith(content, "!eval ", m)) |
260 | ret with dm_bot_execFreshRealEval(post, m.rest(), +safetyCheck); |
261 | else |
262 | ret with dm_bot_execEvalCmd(post, content, +safetyCheck, alwaysFresh := true); |
263 | } |
264 | |
265 | if (eqic(content, "!rule")) { |
266 | LS lines = linesInChannelBy(channelID, userID); |
267 | if (contains(nextToLast(lines), "=>")) |
268 | content = "!rule " + nextToLast(lines); |
269 | else { |
270 | if (l(lines) < 3) fail("Too few lines"); |
271 | content = "!rule " + nextToNextToLast(lines) + " => " + nextToLast(lines); |
272 | } |
273 | } |
274 | |
275 | if (swicOneOf(content, m, "!rule ", "!rule\n")) { |
276 | S s = trim(m.rest()); |
277 | print("Processing rule: " + s); |
278 | S opt = leadingSquareBracketStuff(s); |
279 | s = dropActuallyLeadingSquareBracketStuff(s); |
280 | if (startsWith(s, "=>")) |
281 | s = assertNotNull("No last line in channel", nextToLast(linesInChannelBy(channelID, userID))) + "\n" + s; |
282 | LS comments = ll("made by user", "discord", "tokenize out with javaTok"); |
283 | if (cic(pairB(tok_splitAtDoubleArrow_pair(s)), userName)) { |
284 | s = optCurly(userName) + " says: " + s; |
285 | comments.add("with user name"); |
286 | } |
287 | |
288 | gazelle_parsePublicRuleOptions(opt, comments); |
289 | s = replace(s, " + ", "\n+ "); |
290 | s = jreplace1(s, "=>", "\n=>"); |
291 | s = gazelle_processSquareBracketAnnotations(s, comments); |
292 | temp tempSetTL(dm_gazelle_addRuleWithComment_renderWithoutComments, true); |
293 | dm_gazelle_addRuleWithComment(s, lines_rtrim(comments)); |
294 | ret with postText(dm_gazelle_addRuleWithComment_msg!); |
295 | } |
296 | |
297 | if (swic_trim(content, "!phrase ", m)) { |
298 | gazelle_createRuleForPhrase(m.rest()); |
299 | ret with postText(dm_gazelle_addRuleWithComment_msg!); |
300 | } |
301 | |
302 | // Go into normal reasoning |
303 | |
304 | if (bot && !selfTalk) ret; |
305 | |
306 | S purpose = null; |
307 | bool debug = false, skipBad = true; |
308 | Set<S> restrictToRules = null; |
309 | |
310 | bool change; |
311 | do { |
312 | change = false; |
313 | if (swic_trim(content, "!withBad ", m)) { |
314 | skipBad = false; |
315 | content = m.rest(); set change; |
316 | } |
317 | |
318 | if (swic_trim(content, "!preprocess ", m)) { |
319 | purpose = 'preprocess; |
320 | content = m.rest(); set change; |
321 | } |
322 | |
323 | if (swic_trim(content, "!debug ", m)) { |
324 | set debug; |
325 | content = m.rest(); set change; |
326 | } |
327 | |
328 | if (swic(content, "!using[", m)) { |
329 | int i = indexOf(m.rest(), ']'); |
330 | restrictToRules = litset(substring(m.rest(), 0, i)); |
331 | content = trimSubstring(m.rest(), i+1); |
332 | } |
333 | } while (change); |
334 | |
335 | print("debug=" + debug + ", content=" + content); |
336 | |
337 | LS preContext = takeLast(2, dropLast(collect text(linesInChannel))); |
338 | print("preContext=" + preContext); |
339 | |
340 | GazelleContextCache_v2 contextCache = DiscordBot.this.contextCache; |
341 | if (restrictToRules != null) { |
342 | contextCache = gazelle_cloneContextCache(contextCache); |
343 | Set<S> _restrictToRules = restrictToRules; |
344 | print("restrictToRules=" + restrictToRules); |
345 | contextCache.cachedCtx.engine.dropRulesWhere(r -> !contains(_restrictToRules, r.globalID)); |
346 | print("Using rules: " + collect globalID(contextCache.cachedCtx.engine.rules)); |
347 | } |
348 | |
349 | O[] params = litmapparams(+userName, +skipBad, +preContext, |
350 | badComments := mechCISet("Knock-out rule comments"), |
351 | acceptablePurposes := nempty(purpose) |
352 | ? litciset(purpose) |
353 | : acceptablePurposes, |
354 | respondingToHuman := !bot, |
355 | +debug, |
356 | +userID, |
357 | contextMaker := contextCache, |
358 | debugPreprocessing := true, |
359 | sendToModules := func(S input) -> L<GazelleTree> { |
360 | gazelle_wrapLinesAsTree(gazelle_answersFromModules(userForModulesManager, input)) |
361 | }); |
362 | |
363 | if (eq(purpose, 'preprocess)) |
364 | ret with postText(lines_rtrim(takeFirst(10, gazelle_preprocess(content, params)))); |
365 | |
366 | L<GazelleTree> l; |
367 | if (nempty(purpose)) |
368 | l = gazelle_postprocess(dm_gazelle_reasonAboutChatInput_v2(userName, content, params)); |
369 | else |
370 | l = gazelle_reason_repeat(content, params); |
371 | |
372 | int idx = 0; |
373 | for (final GazelleTree t : l) { |
374 | final int _idx = idx++; |
375 | |
376 | if (eqic(t.lineType, "temporary fact")) { |
377 | if (dm_gazelle_hasTempFact(t.line)) continue; |
378 | print("Saving temp fact: " + t.line); |
379 | dm_gazelle_addTempFact(t.line, "discord msg " + msgID); |
380 | } |
381 | |
382 | if (eqic(t.lineType, "eval")) { |
383 | S code = t.rule().out; |
384 | if (!mechSet("Gazelle | Allowed Evals").contains(code)) |
385 | print("Eval not allowed: " + code); |
386 | else { |
387 | /*if (newMsg)*/ sendTyping(); |
388 | S out = strOrNull(dm_javaEvalOrInterpret(code)); |
389 | if (nempty(out)) |
390 | postInChannelWithDelete(ch, out, voidfunc(Message msg2) { |
391 | Gazelle_ReasoningForLine reasoning = nu(Gazelle_ReasoningForLine, |
392 | outMsgID := msg2.getIdLong(), |
393 | outText := out, |
394 | inMsgID := msg.getIdLong(), |
395 | inUserID := userID, |
396 | inChannelID := channelID, |
397 | inText := originalContent, |
398 | tree := t, |
399 | treeIndex := _idx); |
400 | dm_gazelle_saveReasoningForLine(reasoning); |
401 | }); |
402 | } |
403 | continue; |
404 | } |
405 | |
406 | // Now that we actually post, store recently used mapping |
407 | gazelle_storeRecentlyUsedMappings(ll(t), |
408 | context := "discord channel " + channelID); |
409 | |
410 | fS out = tok_dropCurlyBrackets(t.line); |
411 | print(">> " + t); |
412 | print("POSTING: " + out); |
413 | |
414 | postInChannelWithDelete(ch, out, voidfunc(Message msg2) { |
415 | Gazelle_ReasoningForLine reasoning = nu(Gazelle_ReasoningForLine, |
416 | outMsgID := msg2.getIdLong(), |
417 | outText := out, |
418 | inMsgID := msg.getIdLong(), |
419 | inUserID := userID, |
420 | inChannelID := channelID, |
421 | inText := originalContent, |
422 | tree := t, |
423 | treeIndex := _idx); |
424 | dm_gazelle_saveReasoningForLine(reasoning); |
425 | }); |
426 | } |
427 | } catch print error { |
428 | postInChannel(ch, newMsg, msgID, exceptionToStringShort(error)); |
429 | } |
430 | } |
431 | |
432 | public void onMessageReactionAdd(MessageReactionAddEvent e) pcall { |
433 | temp enter(); |
434 | ret if !enabled || !licensed(); |
435 | MessageReaction r = e.getReaction(); |
436 | bool bot = e.getUser().isBot(); |
437 | long msgID = r.getMessageIdLong(); |
438 | add(msgsReactedTo, msgID); |
439 | print("User " + e.getUser() + (bot ? " (bot)" : "") + " reacted to message " + msgID + " with " + r.getReactionEmote()); |
440 | if (bot) ret; |
441 | |
442 | S crud = dm_gazelle_linesCRUD(); |
443 | O lineConcept = dm_call(crud, 'uniqConcept, litObjectArrayAsObject(+msgID)); |
444 | L reactions = cast get(lineConcept, 'reactions); |
445 | print("lineConcept=" + lineConcept); |
446 | print("reactions=" + reactions); |
447 | O reaction = dm_call(crud, 'nuReaction, litObjectArrayAsObject( |
448 | user := userConcept(e.getUser()), |
449 | emoji := r.getReactionEmote().getName(), |
450 | created := now())); |
451 | print("reaction=" + reaction); |
452 | |
453 | dm_call(crud, 'cset, lineConcept, litobjectarray( |
454 | reactions := listPlus(reactions, reaction))); |
455 | |
456 | dm_discord_gatherFeedbackFromLine(dm_discord_importLine(lineConcept)); |
457 | } |
458 | }); |
459 | |
460 | dm_registerAs('discordBot); |
461 | print("Started bot"); |
462 | } |
463 | |
464 | void cleanMeUp { |
465 | if (bot != null) pcall { |
466 | print("Shutting down bot"); |
467 | bot.shutdown(); |
468 | print("Bot shut down"); |
469 | } |
470 | bot = null; |
471 | } |
472 | |
473 | O userConcept(User user) { |
474 | S crud = dm_gazelle_linesCRUD(); |
475 | O userConcept = dm_call(crud, 'uniqUser, user.getIdLong()); |
476 | dm_call(crud, 'cset, userConcept, litobjectarray(name := user.getName())); |
477 | ret userConcept; |
478 | } |
479 | |
480 | // API |
481 | |
482 | MessageChannel getChannel(long channelID) { |
483 | ret bot.getTextChannelById(channelID); |
484 | } |
485 | |
486 | /*void postInChannel(long channelID, S msg) { |
487 | postInChannel(getChannel(channelID), msg); |
488 | }*/ |
489 | |
490 | void postInChannel(MessageChannel channel, S msg) { |
491 | postInChannel(channel, true, 0, msg); |
492 | } |
493 | |
494 | void postInChannel(MessageChannel channel, bool newMsg, long userMsgID, S msg) { |
495 | if (!newMsg) { |
496 | Long responseID = cmdToResponse.get(userMsgID); |
497 | if (responseID != null) { |
498 | editMessage(channel.getIdLong(), responseID, msg); |
499 | ret; |
500 | } |
501 | } |
502 | |
503 | S postID = cast dm_call(gazelle_postedLinesCRUD(), 'postingLine, channel.getId(), msg); |
504 | channel.sendMessage(msg).queue(m -> { |
505 | dm_call(gazelle_postedLinesCRUD(), 'donePosting, postID, "discord msg " + m.getId()); |
506 | cmdToResponse.put(userMsgID, m.getIdLong()); |
507 | change(); |
508 | }); |
509 | if (l(lastPosted) > 5) popFirst(lastPosted); |
510 | lastPosted.add(now()); |
511 | } |
512 | |
513 | /*void postInChannel(S channel, S msg) { |
514 | long id = dm_discord_channelID(channel); |
515 | if (id == 0) fail("Channel not found: " + channel); |
516 | postInChannel(id, msg); |
517 | }*/ |
518 | |
519 | void postInChannelWithDelete(MessageChannel channel, S msg, VF1<Message> onPost) { |
520 | S postID = cast dm_call(gazelle_postedLinesCRUD(), 'postingLine, channel.getId(), msg); |
521 | channel.sendMessage(msg).queue(msg2 -> { |
522 | dm_pcall(gazelle_postedLinesCRUD(), 'donePosting, postID, "discord msg " + msg2.getId()); |
523 | pcallF(onPost, msg2); |
524 | final long msgId = msg2.getIdLong(); |
525 | print("I sent msg: " + msgId); |
526 | if (doDelete) doLater(deleteDelay, r { |
527 | ret if contains(msgsReactedTo, msgId); |
528 | print("Deleting msg " + msgId); |
529 | msg2.delete().queue(); |
530 | }); |
531 | }, error -> _handleException(error)); |
532 | } |
533 | |
534 | void postText(S text) { |
535 | postInChannel(currentChannel!, text); |
536 | } |
537 | |
538 | void postImage(S url) { |
539 | postImage(currentChannel!, url); |
540 | } |
541 | |
542 | void postImage(S url, S title) { |
543 | postImage(currentChannel!, url, title); |
544 | } |
545 | |
546 | void postImage(MessageChannel channel, S url) { |
547 | postImage(channel, url, ""); |
548 | } |
549 | |
550 | void postImage(MessageChannel channel, S url, S description) { |
551 | channel.sendMessage( |
552 | new EmbedBuilder() |
553 | .setImage(absoluteURL(url)) |
554 | //.setTitle(unnull(title)) |
555 | .setDescription(unnull(description)) |
556 | .setColor(imageEmbedMysteriousLineColor) |
557 | .build()).queue(); // TODO: posted lines |
558 | } |
559 | |
560 | LS linesInChannelBy(long channelID, long userID) { |
561 | ret collect text(dm_discord_linesInChannelBy(channelID, userID)); |
562 | } |
563 | |
564 | MessageChannel currentChannel() { ret currentChannel!; } |
565 | Message respondingTo() { ret respondingTo!; } |
566 | |
567 | void sendTyping() { |
568 | currentChannel().sendTyping().queue(); |
569 | } |
570 | |
571 | // for testing, outdated |
572 | L<GazelleTree> reasonAbout(S line, O... _) { |
573 | ret gazelle_reasonAbout(line, paramsPlus(_, ctx := contextCache!)); |
574 | } |
575 | |
576 | void rebuildCache { |
577 | contextCache.fill(); |
578 | } |
579 | |
580 | void editMessage(long channelID, long msgID, S text) { |
581 | getChannel(channelID).editMessageById(str(msgID), text).queue(); |
582 | } |
583 | |
584 | S respondingToUserID() { |
585 | ret respondingTo! == null ? null : "discord user " + respondingTo->getAuthor().getIdLong(); |
586 | } |
587 | |
588 | void uploadFileInChannel(File file) { |
589 | uploadFileInChannel(file, "", null); |
590 | } |
591 | |
592 | void uploadFileInChannel(File file, S msg, IVF1<Message> onPost) { |
593 | if (!isFile(file)) fail("Not a file: " + file); |
594 | |
595 | var channel = currentChannel!; |
596 | S fileName = fileName(file); |
597 | MessageAction a = empty(msg) |
598 | ? channel.sendFile(file, fileName) |
599 | : channel.sendMessage(msg).addFile(file, fileName); |
600 | a.queue(onPost == null ?: m -> onPost.get(m), onQueueError); |
601 | } |
602 | } |
Began life as a copy of #1022193
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, cfunsshuasjs, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1022488 |
Snippet name: | Discord Gazelle [v13, LIVE] |
Eternal ID of this version: | #1022488/86 |
Text MD5: | a28f0c0a35fa58bcc4b2bec42eb22242 |
Transpilation MD5: | 80ab60721d94bd0f686bba617bfdb592 |
Author: | stefan |
Category: | javax / discord |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-10-18 08:31:12 |
Source code size: | 23860 bytes / 602 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 556 / 26162 |
Version history: | 85 change(s) |
Referenced in: | [show references] |