1 | import net.dv8tion.jda.api.events.guild.member.*; |
2 | import net.dv8tion.jda.api.Permission; |
3 | import net.dv8tion.jda.api.requests.restaction.MessageAction; |
4 | import net.dv8tion.jda.api.requests.RestAction; |
5 | import java.util.function.Consumer; |
6 | |
7 | switchable S discordToken; |
8 | |
9 | S discordBotName; |
10 | long discordBotID; |
11 | |
12 | bool reactToBots = true; |
13 | transient bool sendToPostedLinesCRUD = true; |
14 | transient bool escapeAtEveryone; |
15 | transient bool membersIntent; // is members intent needed (reacting to user join/leave) |
16 | |
17 | transient ShardManager discord; |
18 | transient Color discordImageEmbedMysteriousLineColor = colorFromHex("36393f"); |
19 | transient L<IVF2<MessageChannel, S>> onPostedInChannel = syncList(); |
20 | //transient bool debugPosting; |
21 | |
22 | // when a discord action failed |
23 | transient Consumer<Throwable> onQueueError = error -> { |
24 | temp enter(); |
25 | printStackTrace(error); |
26 | }; |
27 | |
28 | void startDiscord { |
29 | if (!discordEnabled()) ret with print("Not enabled"); |
30 | |
31 | vm_cleanPrints(); // avoid Swing Unicode problem |
32 | logModuleOutput(); // good idea for chat bots |
33 | |
34 | // TODO: log JDA output going to System.out/err |
35 | discord = shardedDiscordBot40(new ListenerAdapter { |
36 | @Override |
37 | public void onMessageUpdate(MessageUpdateEvent e) pcall { |
38 | temp enter(); |
39 | ret if !discordEnabled() || !licensed(); |
40 | |
41 | Message msg = e.getMessage(); |
42 | long msgID = msg.getIdLong(); |
43 | User user = e.getAuthor(); |
44 | long userID = user == null ? 0 : user.getIdLong(); |
45 | S content = e.getMessage().getContentRaw(); |
46 | MessageChannel channel = e.getChannel(); |
47 | long channelID = channel.getIdLong(); |
48 | S rendered = msgID + ": " + content; |
49 | //print("Message edited: " + rendered); |
50 | |
51 | vmBus_send editedDiscordMessage( |
52 | litmapparams(event := e, module := dm_me(), +msgID, +msg, +content, |
53 | +channelID, +channel, +user, +userID)); |
54 | |
55 | O lineConcept = dm_discord_lineForMsgID_unimported(msgID); |
56 | if (lineConcept == null) ret; // logging module not enabled |
57 | call(lineConcept, '_setField, editedText := content); |
58 | } |
59 | |
60 | @Override |
61 | public void onMessageReceived(MessageReceivedEvent e) pcall { |
62 | temp enter(); |
63 | ret if !discordEnabled() || !licensed(); |
64 | |
65 | // send out raw event |
66 | vmBus_send discord_onMessageReceived(module(), e); |
67 | |
68 | User user = e.getAuthor(); |
69 | if (!reactToUser(user)) ret with print("Not reacting to user " + user); |
70 | bool bot = user.isBot(); |
71 | |
72 | long msgID = e.getMessage().getIdLong(); |
73 | long userID = user.getIdLong(); |
74 | Guild guild = e.isFromType(ChannelType.TEXT) ? e.getGuild() : null; |
75 | long guildID = toLong(call(guild, 'getIdLong); |
76 | Member member = e.getMember(); |
77 | S userName = member == null ? null : member.getNickname(); // the changeable nick name - null sometimes? |
78 | if (userName == null && member != null) userName = member.getEffectiveName(); |
79 | if (userName == null) userName = e.getAuthor().getName(); |
80 | |
81 | final Message msg = e.getMessage(); |
82 | |
83 | MessageChannel channel = e.getChannel(); |
84 | long channelID = channel.getIdLong(); |
85 | //print("Channel type: " + e.getChannelType()); |
86 | bool isPrivate = e.getChannelType() == ChannelType.PRIVATE; |
87 | S content = trim(msg.getContentRaw()); |
88 | //print("Msg from " + userName + ": " + content); |
89 | |
90 | vmBus_send incomingDiscordMessage( |
91 | litmapparams(fromBot := bot, module := dm_me(), +msgID, +userID, +userName, event := e, +guildID, +guild, +member, |
92 | +msg, |
93 | +content, +isPrivate, +channelID, +channel)); |
94 | } |
95 | |
96 | @Override |
97 | public void onMessageReactionAdd(MessageReactionAddEvent e) pcall { |
98 | temp enter(); |
99 | ret if !discordEnabled() || !licensed(); |
100 | MessageReaction r = e.getReaction(); |
101 | |
102 | User user = e.getUser(); |
103 | if (!reactToUser(user)) ret; |
104 | bool bot = user.isBot(); |
105 | |
106 | long msgID = r.getMessageIdLong(); |
107 | S emoji = r.getReactionEmote().getName(); |
108 | |
109 | vmBus_send('incomingDiscordReaction, litmapparams( |
110 | reaction := r, |
111 | fromBot := bot, +user, userID := user.getIdLong(), |
112 | module := dm_me(), +msgID, +emoji |
113 | )); |
114 | } |
115 | |
116 | @Override |
117 | public void onMessageReactionRemove(MessageReactionRemoveEvent e) pcall { |
118 | temp enter(); |
119 | ret if !discordEnabled() || !licensed(); |
120 | MessageReaction r = e.getReaction(); |
121 | |
122 | User user = e.getUser(); |
123 | if (!reactToUser(user)) ret; |
124 | bool bot = user.isBot(); |
125 | |
126 | long msgID = r.getMessageIdLong(); |
127 | S emoji = r.getReactionEmote().getName(); |
128 | |
129 | vmBus_send('discordReactionRemoved, litmapparams( |
130 | reaction := r, |
131 | fromBot := bot, +user, userID := user.getIdLong(), |
132 | module := dm_me(), +msgID, +emoji |
133 | )); |
134 | } |
135 | |
136 | @Override |
137 | public void onMessageReactionRemoveAll(MessageReactionRemoveAllEvent e) pcall { |
138 | temp enter(); |
139 | print("TODO: onMessageReactionRemoveAll"); |
140 | } |
141 | |
142 | @Override |
143 | public void onGuildMemberJoin(GuildMemberJoinEvent event) pcall { |
144 | temp enter(); |
145 | print("Got guild join"); |
146 | ret if !discordEnabled() || !licensed(); |
147 | |
148 | print("Join >> getting user ID"); |
149 | long userID = rcall_long getIdLong(rcall getUser(event.getMember())); |
150 | print("Join >> sending"); |
151 | vmBus_send('discordGuildJoin, litmapparams( |
152 | module := dm_me(), +event, +userID, guildID := discord_guildIDFromEvent_gen(event) |
153 | )); |
154 | } |
155 | |
156 | @Override |
157 | public void onGuildMemberLeave(GuildMemberLeaveEvent event) pcall { |
158 | temp enter(); |
159 | print("Got guild leave"); |
160 | ret if !discordEnabled() || !licensed(); |
161 | |
162 | long userID = rcall_long getIdLong(rcall getUser(event.getMember())); |
163 | vmBus_send('discordGuildLeave, litmapparams( |
164 | module := dm_me(), +event, +userID |
165 | )); |
166 | } // end of JDA listener |
167 | }, token := discordToken, +membersIntent); |
168 | |
169 | dm_registerAs('liveDiscordModule); |
170 | dm_vmBus_answerToMessage activeDiscordTokens(func -> LS { ll(discordToken) }); |
171 | |
172 | int safety = 0; |
173 | int nShards = nShards(); |
174 | while (licensed() && ++safety < 60) { |
175 | int connected = nShardsConnected(); |
176 | print(connected + " of " + n2(nShards, "shard") + " connected"); |
177 | if (connected >= nShards) break; |
178 | sleepSeconds(1); |
179 | } |
180 | |
181 | User selfUser = getSelfUser(); |
182 | if (selfUser == null) fail("No shards connected after 20 seconds"); |
183 | |
184 | setField(discordBotName := selfUser.getName()); |
185 | setField(discordBotID := selfUser.getIdLong()); |
186 | print("Bot name: " + discordBotName); |
187 | } |
188 | |
189 | int nShards() { |
190 | ret l(discord.getShards()); |
191 | } |
192 | |
193 | public int nShardsConnected() { |
194 | int n = 0; |
195 | for (JDA shard : discord.getShards()) { |
196 | if (shard.getStatus().equals(JDA.Status.CONNECTED)) |
197 | n++; |
198 | } |
199 | ret n; |
200 | } |
201 | |
202 | public SelfUser getSelfUser() { |
203 | for (JDA shard : discord.getShards()) { |
204 | if (shard.getStatus().equals(JDA.Status.CONNECTED)) |
205 | ret shard.getSelfUser(); |
206 | } |
207 | null; |
208 | } |
209 | |
210 | void cleanMeUp { |
211 | if (discord != null) pcall { |
212 | print("Shutting down discord"); |
213 | discord.shutdown(); |
214 | print("Bot shut down"); |
215 | } |
216 | discord = null; |
217 | } |
218 | |
219 | O userConcept(User user) { |
220 | S crud = dm_gazelle_linesCRUD(); |
221 | O userConcept = dm_call(crud, 'uniqUser, user.getIdLong()); |
222 | dm_call(crud, 'cset, userConcept, litobjectarray(name := user.getName())); |
223 | ret userConcept; |
224 | } |
225 | |
226 | // API |
227 | |
228 | MessageChannel getChannel(long channelID) { |
229 | ret discord.getTextChannelById(channelID); |
230 | } |
231 | |
232 | |
233 | void postInChannel(long channelID, S msg) { |
234 | postInChannel(channelID, msg, null); |
235 | } |
236 | |
237 | void postInChannel(long channelID, S msg, IVF1<Message> onPost) { |
238 | if (channelID == 0) ret; |
239 | postInChannel(getChannel(channelID), msg, onPost); |
240 | } |
241 | |
242 | // returns null for your convenience |
243 | S uploadFileInChannel(long channelID, File file, S fileName, S msg, IVF1<Message> onPost) { |
244 | if (channelID == 0) null; |
245 | ret uploadFileInChannel(getChannel(channelID), file, fileName, msg, onPost); |
246 | } |
247 | |
248 | void postInChannel(MessageChannel channel, S msg, IVF1<Message> onPost) { |
249 | S originalMsg = msg; |
250 | msg = ecapeAndShortenForDiscord(msg); |
251 | if (empty(msg)) ret; |
252 | |
253 | S postID = !sendToPostedLinesCRUD ? null : (S) dm_pcall(gazelle_postedLinesCRUD(), 'postingLine, channel.getId(), msg); |
254 | |
255 | print("Posting in channel " + channel + ": " + msg); |
256 | pcallFAll(onPostedInChannel, channel, msg); |
257 | MessageAction a = channel.sendMessage(msg); |
258 | if (msg != originalMsg) |
259 | a.addFile(toUtf8(originalMsg), genericTextFileName()); |
260 | a.queue(m -> msgPosted(m, onPost, postID), onQueueError); |
261 | } |
262 | |
263 | S genericTextFileName() { |
264 | ret "full-output-" + ymd_minus_hms() + ".txt"; |
265 | } |
266 | |
267 | void msgPosted(Message m, IVF1<Message> onPost, S postID) enter { |
268 | if (nempty(postID)) |
269 | dm_call(gazelle_postedLinesCRUD(), 'donePosting, postID, "discord msg " + m.getId()); |
270 | long msgId = m.getIdLong(); |
271 | print("I sent msg: " + msgId); |
272 | pcallF(onPost, m); |
273 | } |
274 | |
275 | void postInChannel(S channel, S msg) { |
276 | long id = dm_discord_channelID(channel); |
277 | if (id == 0) fail("Channel not found: " + channel); |
278 | postInChannel(id, msg); |
279 | } |
280 | |
281 | void postInChannel(MessageChannel channel, S msg) { |
282 | postInChannel(channel, msg, null); |
283 | } |
284 | |
285 | void postImage(Map msgMap, S url) { |
286 | postImage(msgMap, url, ""); |
287 | } |
288 | |
289 | void postImage(Map msgMap, S url, S description) { |
290 | postImage((MessageChannel) get channel(msgMap), url, description); |
291 | } |
292 | |
293 | void postImage(MessageChannel channel, S url, S description) { |
294 | print("Posting image: " + url); |
295 | channel.sendMessage( |
296 | new EmbedBuilder() |
297 | .setImage(absoluteURL(url)) |
298 | //.setTitle(unnull(title)) |
299 | .setDescription(unnull(description)) |
300 | .setColor(discordImageEmbedMysteriousLineColor) |
301 | .build()).queue(null, onQueueError); // TODO: posted lines |
302 | } |
303 | |
304 | void editMessage(long channelID, long msgID, S text) { |
305 | getChannel(channelID).editMessageById(str(msgID), text).queue(null, onQueueError); |
306 | } |
307 | |
308 | void sendPM(long userID, S _text) { |
309 | S text = ecapeAndShortenForDiscord(_text); |
310 | if (empty(text)) ret; |
311 | print("Sending PM to " + userID + ": " + text); |
312 | discord.getUserById(userID).openPrivateChannel().queue(channel -> { |
313 | channel.sendMessage(text).queue(null, onQueueError); |
314 | }, onQueueError); |
315 | print("PM queued"); |
316 | } |
317 | |
318 | void reply(Map msgMap, S text) { |
319 | reply(msgMap, text, null); |
320 | } |
321 | |
322 | void reply(Map msgMap, S text, O onPost) { |
323 | if (empty(text = trim(text))) ret; |
324 | postInChannel((MessageChannel) msgMap.get('channel), text, toIVF1(onPost)); |
325 | } |
326 | |
327 | void iAmTyping(Map msgMap) pcall { |
328 | ((MessageChannel) msgMap.get('channel)).sendTyping().queue(null, onQueueError); |
329 | } |
330 | |
331 | S startKeepAliveModule() enter { |
332 | ret dm_discord_startKeepAliveModule(module()); |
333 | } |
334 | |
335 | bool reactToUser(User user) { |
336 | if (user == null) false; |
337 | |
338 | bool bot = user.isBot(); |
339 | |
340 | // don't react to other bots |
341 | if (bot && !reactToBots) false; |
342 | |
343 | // don't read msgs from myself |
344 | if (user.getIdLong() == discordBotID) false; |
345 | |
346 | true; |
347 | } |
348 | |
349 | S ecapeAndShortenForDiscord(S s) { |
350 | if (escapeAtEveryone) s = escapeAtEveryone(s); |
351 | ret shortenForDiscord(s); |
352 | } |
353 | |
354 | // returns null for your convenience |
355 | S uploadFileInChannel(MessageChannel channel, File file, S fileName, S msg, IVF1<Message> onPost) { |
356 | msg = ecapeAndShortenForDiscord(msg); |
357 | MessageAction a = empty(msg) |
358 | ? channel.sendFile(file, fileName) |
359 | : channel.sendMessage(msg).addFile(file, fileName); |
360 | a.queue(m -> msgPosted(m, onPost, null), onQueueError); |
361 | null; |
362 | } |
363 | |
364 | // returns null... you know the drill |
365 | S uploadFileInChannel(MessageChannel channel, byte[] data, S fileName, S msg, IVF1<Message> onPost) { |
366 | msg = ecapeAndShortenForDiscord(msg); |
367 | MessageAction a = empty(msg) |
368 | ? channel.sendFile(data, fileName) |
369 | : channel.sendMessage(msg).addFile(data, fileName); |
370 | a.queue(m -> msgPosted(m, onPost, null), onQueueError); |
371 | null; |
372 | } |
373 | |
374 | S uploadFileInChannel(long channelID, byte[] data, S fileName, S msg, IVF1<Message> onPost) { |
375 | ret uploadFileInChannel(getChannel(channelID), data, fileName, msg, onPost); |
376 | } |
377 | |
378 | void <A> queue(RestAction<A> action) { |
379 | action.queue(null, onQueueError); |
380 | } |
Began life as a copy of #1026292
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1034049 |
Snippet name: | Sharded Discord Include [JDA 4.x] |
Eternal ID of this version: | #1034049/11 |
Text MD5: | b97256dbd4740fe472cfb03f3efdc6df |
Author: | stefan |
Category: | javax / discord |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-01-19 18:58:58 |
Source code size: | 12273 bytes / 380 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 111 / 177 |
Version history: | 10 change(s) |
Referenced in: | [show references] |