Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

690
LINES

< > BotCompany Repo | #1030356 // MKLab FM Bot with JDA 4.2 [LIVE, fixing status & playback for shards]

JavaX source code (Dynamic Module) [tags: use-pretranspiled] - run with: Stefan's OS

Uses 39550K of libraries. Click here for Pure Java version (24143L/127K).

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

Author comment

Began life as a copy of #1025354

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt

No comments. add comment

Snippet ID: #1030356
Snippet name: MKLab FM Bot with JDA 4.2 [LIVE, fixing status & playback for shards]
Eternal ID of this version: #1030356/44
Text MD5: 735cb7cecb1ec2115fa56ba4168807f2
Transpilation MD5: e750597f5761de1568c76d92bcfe3f03
Author: stefan
Category: javax
Type: JavaX source code (Dynamic Module)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-05-05 14:44:52
Source code size: 24942 bytes / 690 lines
Pitched / IR pitched: No / No
Views / Downloads: 261 / 993
Version history: 43 change(s)
Referenced in: [show references]