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