1 | !7 |
2 | |
3 | /*static MapSO _renameClasses = litmap( |
4 | "DynDiscordHopper" := "DynShardedDiscordHopper", |
5 | "DynServerAwareDiscordBot" := "DynShardedServerAwareDiscordBot", |
6 | "DynTalkBot2" := "DynShardedTalkBot2");*/ |
7 | |
8 | set flag JDA40. |
9 | |
10 | !include once #1034054 // DynTalkBot2 with sharding |
11 | !include once #1034056 // DynServerAwareDiscordBot |
12 | !include once #1034057 // DynDiscordHopper |
13 | |
14 | // TODO: on restart, check if we actually are in vc with voiceChannelID |
15 | // (if permissions have changed) |
16 | |
17 | // Changes in this version: |
18 | // uploadImagesAsFiles |
19 | // !!!listeners fixed |
20 | // check if embed fields are longer than 1k |
21 | |
22 | // Note: dm owners only takes ONE LINE |
23 | // TODO: send dms only once when owner has two guilds |
24 | |
25 | !include once #1030357 // Discord Audio |
26 | |
27 | cmodule MKLabFM extends DynTalkBot2<.ByServer> { |
28 | switchable S radioURL = "http://streamlive2.hearthis.at:8080/26450.ogg"; |
29 | switchable S currentSongURL = "https://widgets.autopo.st/widgets/public/MKLabFM/nowplaying.php"; |
30 | switchable S coverArtURL = "http://95.154.196.33:26687/playingart?sid=1&rand=<RNDINT>"; |
31 | switchable bool enableShadowStop = true; |
32 | switchable bool putSongInStatus = true; |
33 | transient double bufferSeconds = 30.0; |
34 | transient int grabInterval = 15; // try to grab frames quicker to see if it fixes the jumps |
35 | transient double considerDownInterval = 60.0; |
36 | |
37 | // restart stream when no frames received for this long |
38 | transient double autoRestartInterval = 20.0; |
39 | |
40 | switchable S madeBy = trim([[ |
41 | MKLab: <https://tinyurl.com/musikcolabradiofm> |
42 | Support channel: <https://discord.gg/u6E4muY> |
43 | Bot by: <https://BotCompany.de> |
44 | You like this bot? Donate: <https://paypal.me/BotCompany> |
45 | ]]); |
46 | |
47 | switchable S voteLink = "\n\nPlease help us grow by voting here: <https://discordbots.org/bot/625377645982384128>"; |
48 | |
49 | switchable S npMsg = "Now playing: <track> - Listen at }{ https://tinyurl.com/mklab-live-fm }{ #edm #music #radio #ibiza #dance #chillout #psy #live #miami #memes #detroit #techno #dj #synthwave #housemusic #deephouse #onair"; |
50 | |
51 | switchable bool uploadImagesAsFiles = true; |
52 | |
53 | transient SimpleCircularBuffer<byte[]> buffer; |
54 | transient AudioPlayer player; |
55 | switchable transient bool simulateStreamDown, restartNow; |
56 | transient long lastFrameReceived, lastActualFrameReceived; // sys time |
57 | transient long moduleStarted; // sys time |
58 | transient NotTooOften postRestarting = onlyEveryHour(); |
59 | transient S currentSong; |
60 | |
61 | // stats |
62 | transient new AtomicLong framesReceived; |
63 | transient new AtomicLong canProvideCalls; |
64 | transient new AtomicLong provideCalls; |
65 | transient new AtomicLong dataSent; |
66 | |
67 | O _getReloadData() { ret currentSong; } |
68 | void _setReloadData(S data) { currentSong = data; } |
69 | |
70 | void init { |
71 | super.init(); |
72 | myName = "!!!"; |
73 | makeByServer = () -> new ByServer; |
74 | dropPunctuation = false; |
75 | preprocessAtSelfToMyName = true; |
76 | ngbCommands = false; |
77 | sendToPostedLinesCRUD = false; |
78 | |
79 | // First thing, start the stream, so we can restart the radios |
80 | initStream(); |
81 | |
82 | onDiscordStarted(r { |
83 | temp enter(); |
84 | print("Bot back up"); |
85 | for (Long id, ByServer bs : cloneMap(dataByServer)) { pcall { |
86 | bs.restart(discord.getGuildById(id)); |
87 | }} |
88 | |
89 | postInChannel(nextGenBotsTestRoomID(), "Rebooted " + computerID() + "/" + dm_moduleID()); |
90 | }); |
91 | |
92 | onDiscordEvent(e -> { |
93 | if (e cast GenericGuildVoiceEvent) |
94 | getByServer(e.getGuild()).handleVoiceUpdateEvent(e); |
95 | }); |
96 | |
97 | doEveryAndNow(20.0, r grabSong); |
98 | doEvery(60.0, r updateVoiceChannelCount_allServers); |
99 | } |
100 | |
101 | void initStream { |
102 | buffer = new SimpleCircularBuffer(iround(bufferSeconds*50)); |
103 | player = lavaPlayer_createPlayer(); |
104 | lavaPlayer_printPlayerEvents(player); |
105 | print("Have player"); |
106 | |
107 | doEvery(grabInterval, r { |
108 | temp enter(); |
109 | if (!simulateStreamDown) { |
110 | AudioFrame frame = player.provide(); |
111 | if (frame != null) { |
112 | buffer.add(frame.getData()); |
113 | inc(framesReceived); |
114 | lastFrameReceived = lastActualFrameReceived = sysNow(); |
115 | } |
116 | } |
117 | }); |
118 | |
119 | doEvery(1.0, r { |
120 | temp enter(); |
121 | if (discord == null) ret; |
122 | if (restartNow || elapsedSeconds(lastFrameReceived) >= autoRestartInterval) { |
123 | if (postRestarting.yo()) |
124 | postInChannel(nextGenBotsTestRoomID(), "Restarting radio stream"); |
125 | simulateStreamDown = restartNow = false; |
126 | lastFrameReceived = sysNow(); |
127 | loadTrack(); |
128 | lastFrameReceived = sysNow(); |
129 | } |
130 | }); |
131 | } |
132 | |
133 | class ByServer extends DynTalkBot2.ByServer { |
134 | bool shouldBePlaying; |
135 | long voiceChannelID, lastVoiceChannelID; |
136 | long songChannelID; |
137 | |
138 | transient S voiceChannelName; |
139 | transient VoiceChannel vc; |
140 | transient MyAudioSendHandler sendHandler; |
141 | transient Set<Long> usersInVoiceChannel = synchroSet(); |
142 | |
143 | synchronized S processSimplifiedLine(S s, O... _) enter { |
144 | try { |
145 | ret processSimplifiedLine2(s, _); |
146 | } catch print e { |
147 | if (cic(str(e), "InsufficientPermission")) |
148 | ret "There were insufficient permissions to perform this action."; |
149 | ret "Sorry, an error occurred trying to perform this action."; |
150 | } |
151 | } |
152 | |
153 | synchronized S processSimplifiedLine2(S s, O... _) { |
154 | try answer super.processSimplifiedLine(s, _); // add authorized users etc. |
155 | new Matches m; |
156 | |
157 | print("myPrefix", quote(myPrefix())); |
158 | s = dropMyPrefixOrNull(s); |
159 | print("dropped", quote(s)); |
160 | if (s == null) null; |
161 | optPar Message msg; |
162 | Guild guild = msg.getGuild(); |
163 | Member member = msg == null ? null : msg.getMember(); |
164 | |
165 | if (eqic(s, "help")) { |
166 | optPar MessageChannel channel; |
167 | |
168 | S syntax = trim([[ |
169 | `@me play | start` - start playing |
170 | `@me stop | pause` - stop playing |
171 | `@me leave` - leave voice channel |
172 | `@me help` - show this help |
173 | `@me np` - show currently playing song |
174 | `@me join` - join any voice channel |
175 | `@me set channel` - post now playing track names in current channel |
176 | `@me stop posting` - stop posting track names |
177 | [MORE] |
178 | ]].replace("\n[MORE]", renderChannelListIfNotOne(guild)) |
179 | .replace("@me ", myPrefix())) |
180 | + voteLink; |
181 | |
182 | new EmbedBuilder eb; |
183 | eb.setColor(Color.yellow); |
184 | eb.setAuthor("MKLab FM Bot", null, null); |
185 | eb.setTitle("Purpose", null); |
186 | eb.setDescription("I play MKLab FM, the best Electronic Dance Music Radio, in a voice channel of your choice."); |
187 | discordEmbed_addField(eb, "Syntax", syntax); |
188 | |
189 | if (authed(_)) |
190 | eb.addField("Admin commands", rtrim([[ |
191 | `@me guild count/guild names` - show stats |
192 | `@me reboot` - reboot the whole bot (if music stream is completely broken for some reason) |
193 | `@me dm owners: <message>` - DM all guild owners where radio is playing |
194 | `@me stream url http...` - set stream URL. current: $STREAMURL |
195 | `@me np url http...` - set current song URL. current: $NPURL |
196 | `@me cover url http...` - set cover art URL. current: $COVERURL |
197 | `@me cover url -` - disable cover art |
198 | `@me np msg Playing <track>...` - set nowplaying message. current: $NPMSG |
199 | `@me listeners` - full list of listeners |
200 | ]]) |
201 | .replace("@me ", myPrefix()) |
202 | .replace("$STREAMURL", backtickQuote(radioURL)) |
203 | .replace("$NPURL", backtickQuote(currentSongURL)) |
204 | .replace("$NPMSG", backtickQuote(npMsg)) |
205 | .replace("$COVERURL", backtickQuote(coverArtURL)), false); |
206 | |
207 | eb.addField("Made by", madeBy, false); |
208 | |
209 | eb.setThumbnail(imageSnippetURL(#1102722)); |
210 | channel.sendMessage(eb.build()).queue(); |
211 | null; |
212 | } |
213 | |
214 | if (guild == null) null; |
215 | |
216 | if (authed(_)) { |
217 | /*if "restart radios" |
218 | ret "OK" with restartRadios();*/ |
219 | if "reboot" { |
220 | doAfter(2.0, rEnter dm_reload); |
221 | ret "OK, rebooting bot"; |
222 | } |
223 | if "simulate stream down" { |
224 | simulateStreamDown = true; |
225 | ret "OK"; |
226 | } |
227 | if (swic_trim(s, "dm owners:", m)) |
228 | ret dmOwners($1); |
229 | |
230 | if (swic_trim(s, "stream url ", m) && isWebURL(m.rest())) { |
231 | if (!MKLabFM.this.setField(radioURL := m.rest())) ret "No change"; |
232 | restartNow = true; |
233 | ret "OK, restarting with radio URL " + radioURL; |
234 | } |
235 | |
236 | if (swic_trim(s, "np url ", m) && isWebURL(m.rest())) { |
237 | if (!MKLabFM.this.setField(currentSongURL := m.rest())) ret "No change"; |
238 | ret "Current song URL changed to " + currentSongURL; |
239 | } |
240 | |
241 | if (swic_trim(s, "cover url ", m)) { |
242 | if (!MKLabFM.this.setField(coverArtURL := m.rest())) ret "No change"; |
243 | ret "Cover art URL changed to " + coverArtURL; |
244 | } |
245 | |
246 | if (swic_trim(s, "np msg ", m)) { |
247 | if (!MKLabFM.this.setField(npMsg := m.rest())) ret "No change"; |
248 | ret "Nowplaying message changed to " + npMsg; |
249 | } |
250 | |
251 | if "listeners" |
252 | ret or2(listeningUsers_full(), "No listeners"); |
253 | } |
254 | |
255 | if (matchX2("play ...|start...|radio|radio on", s)) |
256 | ret action_play(guild, member); |
257 | |
258 | if (matchX2("stop ...|pause ...|quiet|silence|shut up|... off", s) && !match("stop posting", s)) |
259 | ret action_stop(guild); |
260 | |
261 | if (matchX2("leave|leave channel|leave voice channel", s)) |
262 | ret action_leave(guild); |
263 | |
264 | if (matchX2("join|join channel|join voice channel", s)) |
265 | ret action_join(guild, member); |
266 | |
267 | if (matchX2("... channel *", s, m) && isInteger($1)) |
268 | ret action_selectChannel(guild, parseInt($1)); |
269 | |
270 | if (matchX2("np|n p|... song ...|... playing ...", s)) |
271 | ret "MKLab FM is currently playing: " + (nempty(currentSong) ? backtickQuote(currentSong) : "Hmm. Not sure, I think something went wrong."); |
272 | |
273 | if (matchX2("set channel|post here|set np", s)) { |
274 | long channelID = longPar channelID(_); |
275 | if (channelID == 0) ret "Please do this in a channel"; |
276 | if (songChannelID != channelID) { |
277 | songChannelID = channelID; |
278 | change(); |
279 | } |
280 | doAfter(2.0, r postSong); |
281 | ret "OK, I will post the currently playing tracks in this channel. Type `" + atSelfOrMyName_space() + "stop posting` to stop."; |
282 | } |
283 | |
284 | if (matchX2("stop posting|no channel", s)) { |
285 | songChannelID = 0; |
286 | change(); |
287 | ret "OK"; |
288 | } |
289 | |
290 | if "users in channel" |
291 | ret "Users in voice channel: " + n2(usersInVoiceChannel); |
292 | |
293 | if "internal stats" |
294 | ret "Frames received: " + n2(framesReceived!) |
295 | + ", canProvide calls: " + n2(canProvideCalls!) |
296 | + ", provide calls: " + n2(provideCalls!) |
297 | + ", bytes sent: " + n2(dataSent!); |
298 | |
299 | null; |
300 | } |
301 | |
302 | S renderGuildCount() { |
303 | L<Guild> guilds = discord.getGuilds(); |
304 | int shouldPlayCount = 0, shadowCount = 0; |
305 | for (Guild guild : guilds) { |
306 | ByServer bs = getByServer(guild.getIdLong(), true); |
307 | if (bs != null) { |
308 | //if (bs.playing) ++playCount; |
309 | if (bs.shouldBePlaying) { |
310 | ++shouldPlayCount; |
311 | if (bs.shadowStopped()) ++shadowCount; |
312 | } |
313 | } |
314 | } |
315 | ret super.renderGuildCount() + ". " + nRadios(shouldPlayCount-shadowCount) + " playing to " + n2(listeningUsers(), "listener") + ", " + shadowCount + " playing without listeners"; |
316 | } |
317 | |
318 | void joinVoiceChannel(Guild guild) { |
319 | vc = guild.getVoiceChannelById(voiceChannelID); |
320 | guild.getAudioManager().openAudioConnection(vc); |
321 | voiceChannelName = vc.getName(); |
322 | updateVoiceChannelCount(); |
323 | } |
324 | |
325 | void updateVoiceChannelCount { |
326 | if (vc == null) ret with usersInVoiceChannel.clear(); |
327 | L<Member> members = vc.getMembers(); |
328 | int n = l(usersInVoiceChannel); |
329 | replaceCollection(usersInVoiceChannel, |
330 | mapNonNulls_pcall(members, m -> { |
331 | User user = m.getUser(); |
332 | ret user.isBot() ? null : user.getIdLong(); |
333 | })); |
334 | int n2 = l(usersInVoiceChannel); |
335 | if (n != n2) print(guildID + " voice channel count corrected: " + n + " -> " + n2); |
336 | } |
337 | |
338 | bool shadowStopped() { |
339 | ret enableShadowStop && empty(usersInVoiceChannel); |
340 | } |
341 | |
342 | class MyAudioSendHandler implements AudioSendHandler { |
343 | long counter = -1; |
344 | byte[] data; |
345 | |
346 | public bool canProvide() enter { |
347 | inc(canProvideCalls); |
348 | if (!shouldBePlaying) false; |
349 | if (shadowStopped()) { |
350 | counter = -1; |
351 | false; |
352 | } |
353 | |
354 | synchronized(buffer) { |
355 | if (!buffer.isFull()) false; |
356 | |
357 | // out of range? go back to middle |
358 | data = buffer.get(counter); |
359 | if (data == null) { |
360 | long oldCounter = counter; |
361 | counter = buffer.getBase()+buffer.size()/2; |
362 | if (oldCounter >= 0) printWithTime("RESETTING " + oldCounter + " => " + counter |
363 | + " (delta=" + (counter-oldCounter) + ")"); |
364 | data = buffer.get(counter); |
365 | } |
366 | ++counter; |
367 | true; |
368 | } |
369 | } |
370 | |
371 | public java.nio.ByteBuffer provide20MsAudio() { |
372 | inc(provideCalls); |
373 | inc(dataSent, l(data)); |
374 | java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(data); |
375 | ret buf; |
376 | } |
377 | |
378 | public bool isOpus() { true; } |
379 | } |
380 | |
381 | // when already in channel |
382 | S startPlaying(Guild guild) { |
383 | AudioManager am = guild.getAudioManager(); |
384 | sendHandler = new MyAudioSendHandler; |
385 | am.setSendingHandler(sendHandler); |
386 | |
387 | ret "Playing radio in channel " + quote(voiceChannelName); |
388 | } |
389 | |
390 | // try to select the best channel |
391 | // -the one the user is in, or |
392 | // -the one that was used before, or |
393 | // -just any channel. |
394 | // returns error message or null |
395 | S selectAVoiceChannel(Guild guild, Member member) { |
396 | print("selectAVoiceChannel member=" + member); |
397 | if (member != null) { |
398 | GuildVoiceState vs = member.getVoiceState(); |
399 | VoiceChannel vc = vs.getChannel(); |
400 | print("selectAVoiceChannel vc=" + vc); |
401 | if (vc != null) { |
402 | voiceChannelID = lastVoiceChannelID = vc.getIdLong(); |
403 | ret null with change(); |
404 | } |
405 | } |
406 | |
407 | if (voiceChannelID != 0) null; |
408 | L<VoiceChannel> allChannels = guild.getVoiceChannels(); |
409 | L<VoiceChannel> channels = filter(allChannels, c -> isJoinableVoiceChannel(guild, c)); |
410 | |
411 | if (empty(channels)) |
412 | if (empty(allChannels)) |
413 | ret "Please create a voice channel."; |
414 | else |
415 | ret "Sorry, I am not allowed to enter any of your voice channels. Maybe my invite link was wrong?"; // TODO: show better link |
416 | |
417 | // Check if lastVoiceChannelID still exists |
418 | if (lastVoiceChannelID != 0 && !hasWhereMethodReturns getIdLong(channels, lastVoiceChannelID)) { |
419 | lastVoiceChannelID = 0; change(); |
420 | } |
421 | |
422 | voiceChannelID = lastVoiceChannelID != 0 ? lastVoiceChannelID |
423 | : first(channels).getIdLong(); |
424 | change(); |
425 | null; |
426 | } |
427 | |
428 | S action_play(Guild guild, Member member) { |
429 | if (shouldBePlaying && sendHandler != null) ret "I am playing already!"; |
430 | try answer selectAVoiceChannel(guild, member); |
431 | joinVoiceChannel(guild); |
432 | shouldBePlaying = true; change(); |
433 | ret startPlaying(guild); |
434 | } |
435 | |
436 | S action_stop(Guild guild) { |
437 | if (shouldBePlaying) { |
438 | shouldBePlaying = false; change(); |
439 | ret "OK, radio switched off"; |
440 | } |
441 | ret "I am stopped, I think. No?"; |
442 | } |
443 | |
444 | S action_leave(Guild guild) { |
445 | voiceChannelID = 0; change(); |
446 | this.vc = null; |
447 | GuildVoiceState state = getSelfMember(guild).getVoiceState(); |
448 | VoiceChannel vc = state.getChannel(); |
449 | if (vc == null) ret "I don't think I am in any voice channel"; |
450 | guild.getAudioManager().closeAudioConnection(); |
451 | ret "OK, leaving channel " + quote(vc.getName()); |
452 | } |
453 | |
454 | S action_join(Guild guild, Member member) { |
455 | GuildVoiceState state = getSelfMember(guild).getVoiceState(); |
456 | VoiceChannel vc = state.getChannel(); |
457 | try answer selectAVoiceChannel(guild, member); |
458 | joinVoiceChannel(guild); |
459 | ret "OK, joined voice channel " + quote(voiceChannelName); |
460 | } |
461 | |
462 | S action_selectChannel(Guild guild, int i) { |
463 | L<VoiceChannel> channels = guild.getVoiceChannels(); |
464 | channels = filter(channels, c -> isJoinableVoiceChannel(guild, c)); |
465 | VoiceChannel vc = _get(channels, i-1); |
466 | if (vc == null) ret "Sorry, I count only " + nChannels(channels); |
467 | voiceChannelID = lastVoiceChannelID = vc.getIdLong(); |
468 | change(); |
469 | joinVoiceChannel(guild); |
470 | ret "OK, joined voice channel " + quote(voiceChannelName); |
471 | } |
472 | |
473 | S renderChannelListIfNotOne(Guild guild) { |
474 | if (guild == null) ret ""; |
475 | L<VoiceChannel> channels = guild.getVoiceChannels(); |
476 | channels = filter(channels, c -> isJoinableVoiceChannel(guild, c)); |
477 | if (empty(channels)) ret "\nNote: You don't have any voice channels."; |
478 | if (l(channels) == 1) ret ""; |
479 | new LS out; |
480 | for i over channels: |
481 | out.add("`@me join channel " + (i+1) + "` - join channel " + quote(channels.get(i).getName())); |
482 | ret "\n" + lines_rtrim(out); |
483 | } |
484 | |
485 | bool isJoinableVoiceChannel(Guild guild, VoiceChannel vc) { |
486 | if (vc == null || guild == null) false; |
487 | try { |
488 | ret guild.getSelfMember().hasPermission(vc, Permission.VOICE_CONNECT); |
489 | } catch print e { ret false; } |
490 | } |
491 | |
492 | // normal restart (on bot load) |
493 | void restart(Guild guild) { |
494 | if (guild == null) ret; // no longer in guild |
495 | if (shouldBePlaying) { |
496 | print("RESTARTING RADIO in " + guild); |
497 | print(action_play(guild, null)); |
498 | } else if (voiceChannelID != 0) { |
499 | print("Rejoining voice channel in " + guild); |
500 | print(action_join(guild, null)); |
501 | } |
502 | } |
503 | |
504 | // restart with stop (after stream interruption) - no longer needed |
505 | /*void hardRestart(Guild guild) { |
506 | restart(guild); |
507 | }*/ |
508 | |
509 | void postSong { |
510 | if (songChannelID == 0) ret; |
511 | postInChannel(songChannelID, npMsg.replace("<track>", backtickQuote(currentSong))); |
512 | if (isWebURL(coverArtURL)) { |
513 | S imageURL = appendQueryToURL(coverArtURL, rand := randomInt()); |
514 | if (uploadImagesAsFiles) |
515 | uploadFileInChannel(songChannelID, loadBinaryPage(imageURL), "cover-art.jpg", "", null); |
516 | else |
517 | postImage(getChannel(songChannelID), imageURL, ""); |
518 | } |
519 | } |
520 | |
521 | void handleUserJoined(long userID, VoiceChannel joined) { |
522 | if (joined != null && joined.getIdLong() == voiceChannelID) { |
523 | usersInVoiceChannel.add(userID); |
524 | print(guildID + " adding user to voice channel: " + userID + " (n=" + l(usersInVoiceChannel)); |
525 | } |
526 | } |
527 | |
528 | void handleVoiceUpdateEvent(GenericGuildVoiceEvent e) { |
529 | User user = ((GenericGuildVoiceEvent) e).getMember().getUser(); |
530 | if (user.isBot()) ret; |
531 | long userID = user.getIdLong(); |
532 | if (e cast GuildVoiceUpdateEvent) { |
533 | VoiceChannel left = e.getChannelLeft(); |
534 | if (e.getChannelLeft().getIdLong() == voiceChannelID) { |
535 | usersInVoiceChannel.remove(userID); |
536 | print(guildID + " removed user from voice channel: " + userID + " (n=" + l(usersInVoiceChannel)); |
537 | } |
538 | } |
539 | if (e cast GuildVoiceMoveEvent) |
540 | handleUserJoined(userID, e.getChannelJoined()); |
541 | if (e cast GuildVoiceJoinEvent) |
542 | handleUserJoined(userID, e.getChannelJoined()); |
543 | } |
544 | } // end of ByServer |
545 | |
546 | /*void restartRadios { |
547 | L<Guild> guilds = discord.getGuilds(); |
548 | for (Guild guild : guilds) pcall { |
549 | ByServer bs = getByServer(guild); |
550 | if (bs != null) |
551 | bs.hardRestart(guild); |
552 | } |
553 | }*/ |
554 | |
555 | S dmOwners(S msg) { |
556 | L<Guild> guilds = discord.getGuilds(); |
557 | int n = 0; |
558 | for (Guild guild : guilds) pcall { |
559 | ByServer bs = getByServer(guild.getIdLong(), true); |
560 | if (bs != null && bs.shouldBePlaying) { |
561 | gazelle_dmGuildOwner(guild, msg); |
562 | ++n; |
563 | } |
564 | } |
565 | ret n2(n, "guild owner") + " contacted."; |
566 | } |
567 | |
568 | void loadTrack { |
569 | if (!isURL(radioURL)) ret; |
570 | lavaPlayer_loadItem(module(), lavaPlayer_playerManager(), player, radioURL.replace("<RNDINT>", str(randomInt()))); |
571 | } |
572 | |
573 | S currentSong() { |
574 | temp tempSetTL(loadPage_sizeLimit, 128*1024L); |
575 | if (l(currentSongURL) <= 1) ret ""; |
576 | if (cic(currentSongURL, "autopo.st/")) |
577 | ret trackTitleFromAutoPostWidget(loadPageWithTimeout(currentSongURL, 10.0)); |
578 | if (cic(currentSongURL, "caster.fm/")) |
579 | ret (S) mapGet(jsonDecodeMap(loadPageWithHeaders(currentSongURL, |
580 | "X-Requested-With", "XMLHttpRequest")), "playing"); |
581 | if (endsWith(currentSongURL, "status.xsl")) |
582 | ret trackTitleFromIceCastStatusXSL(loadPageWithTimeout(currentSongURL, 10.0)); |
583 | ret loadPageWithTimeout(currentSongURL, 10.0); |
584 | } |
585 | |
586 | void grabSong enter { |
587 | if (discord == null) ret; |
588 | S song = currentSong(); |
589 | bool post = nempty(currentSong) && nempty(song) && neq(currentSong, song); |
590 | currentSong = song; |
591 | if (post) |
592 | postSong_allServers(); |
593 | if (putSongInStatus) { |
594 | S s = currentSong; |
595 | if (elapsedSeconds(lastActualFrameReceived) >= considerDownInterval && elapsedSeconds(moduleStarted) >= considerDownInterval) |
596 | s = "Stream currently down"; |
597 | if (nempty(s)) |
598 | gazelle_discord_playingGame(s); |
599 | } |
600 | } |
601 | |
602 | void postSong_allServers { |
603 | L<Guild> guilds = discord.getGuilds(); |
604 | for (Guild guild : guilds) pcall { |
605 | ByServer bs = getByServer(guild); |
606 | if (bs != null) |
607 | bs.postSong(); |
608 | } |
609 | } |
610 | |
611 | void updateVoiceChannelCount_allServers { |
612 | if (discord == null) ret; |
613 | for (ByServer bs : cloneValues(dataByServer)) |
614 | bs.updateVoiceChannelCount(); |
615 | } |
616 | |
617 | int listeningUsers() { |
618 | int n = 0; |
619 | for (Guild guild : discord.getGuilds()) { |
620 | ByServer bs = getByServer(guild.getIdLong(), true); |
621 | if (bs != null && bs.shouldBePlaying) |
622 | n += l(bs.usersInVoiceChannel); |
623 | } |
624 | ret n; |
625 | } |
626 | |
627 | S listeningUsers_full() { |
628 | new LS out; |
629 | for (Guild guild : discord.getGuilds()) { |
630 | ByServer bs = getByServer(guild.getIdLong(), true); |
631 | if (bs != null && bs.shouldBePlaying) { |
632 | L<Long> users = cloneList(bs.usersInVoiceChannel); |
633 | if (empty(users)) continue; |
634 | out.add(guild.getName() + ": playing to " + joinWithComma(map(users, |
635 | id -> or2(discord_memberIDToEffectiveName(guild, id), str(id))))); |
636 | } |
637 | } |
638 | ret lines(out); |
639 | } |
640 | } |
Began life as a copy of #1030356
download show line numbers debug dex old transpilations
Travelled to 2 computer(s): bhatertpkbcr, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1034058 |
Snippet name: | MKLab FM Bot with JDA 4.2 [using renamed sharded classes] |
Eternal ID of this version: | #1034058/1 |
Text MD5: | 600c28bfec582627dddc1eca5ea430b0 |
Author: | stefan |
Category: | javax |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-01-19 17:27:47 |
Source code size: | 23008 bytes / 640 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 128 / 138 |
Referenced in: | [show references] |