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

636
LINES

< > BotCompany Repo | #1034055 // MKLab FM Bot with JDA 4.2 [using sharded classes]

JavaX source code (Dynamic Module) - run with: Stefan's OS

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

Author comment

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: #1034055
Snippet name: MKLab FM Bot with JDA 4.2 [using sharded classes]
Eternal ID of this version: #1034055/1
Text MD5: 514ccbc82ecb6fedfbde596d2596b4ac
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:08:06
Source code size: 22867 bytes / 636 lines
Pitched / IR pitched: No / No
Views / Downloads: 44 / 56
Referenced in: [show references]