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