· 8 years ago · Mar 06, 2018, 04:11 AM
1package DiscordBot;
2
3import java.awt.Color;
4import java.awt.image.BufferedImage;
5import java.io.File;
6import java.io.IOException;
7import java.math.BigDecimal;
8import java.math.RoundingMode;
9import java.net.URL;
10import java.net.URLConnection;
11import java.sql.Connection;
12import java.sql.DriverManager;
13import java.sql.Statement;
14import java.text.DateFormat;
15import java.text.ParseException;
16import java.text.SimpleDateFormat;
17import java.time.LocalDateTime;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Calendar;
21import java.util.Collections;
22import java.util.Comparator;
23import java.util.Date;
24import java.util.HashMap;
25import java.util.HashSet;
26import java.util.LinkedHashMap;
27import java.util.LinkedList;
28import java.util.List;
29import java.util.Map;
30import java.util.Objects;
31import java.util.Optional;
32import java.util.Queue;
33import java.util.Random;
34import java.util.Scanner;
35import java.util.Set;
36import java.util.TreeMap;
37import java.util.concurrent.ConcurrentLinkedQueue;
38import java.util.concurrent.Executors;
39import java.util.concurrent.ScheduledExecutorService;
40import java.util.concurrent.ThreadLocalRandom;
41import java.util.concurrent.TimeUnit;
42import java.util.function.Consumer;
43import java.util.function.Function;
44import java.util.stream.Collectors;
45
46import javax.imageio.ImageIO;
47import javax.security.auth.login.LoginException;
48
49import org.jsoup.Jsoup;
50import org.jsoup.nodes.Document;
51
52import com.google.gson.Gson;
53
54import RealmeyeIO.RECharacter;
55import RealmeyeIO.REGuild;
56import RealmeyeIO.REName;
57import RealmeyeIO.RENameHistory;
58import RealmeyeIO.Realmeye;
59import WhoRecogniser.Recognise;
60import net.dv8tion.jda.core.AccountType;
61import net.dv8tion.jda.core.EmbedBuilder;
62import net.dv8tion.jda.core.JDA;
63import net.dv8tion.jda.core.JDABuilder;
64import net.dv8tion.jda.core.entities.AudioChannel;
65import net.dv8tion.jda.core.entities.Channel;
66import net.dv8tion.jda.core.entities.ChannelType;
67import net.dv8tion.jda.core.entities.Emote;
68import net.dv8tion.jda.core.entities.Game;
69import net.dv8tion.jda.core.entities.Guild;
70import net.dv8tion.jda.core.entities.Member;
71import net.dv8tion.jda.core.entities.Message;
72import net.dv8tion.jda.core.entities.Message.Attachment;
73import net.dv8tion.jda.core.entities.MessageChannel;
74import net.dv8tion.jda.core.entities.MessageEmbed;
75import net.dv8tion.jda.core.entities.MessageHistory;
76import net.dv8tion.jda.core.entities.MessageReaction;
77import net.dv8tion.jda.core.entities.MessageReaction.ReactionEmote;
78import net.dv8tion.jda.core.entities.Role;
79import net.dv8tion.jda.core.entities.TextChannel;
80import net.dv8tion.jda.core.entities.User;
81import net.dv8tion.jda.core.entities.VoiceChannel;
82import net.dv8tion.jda.core.entities.VoiceState;
83import net.dv8tion.jda.core.events.Event;
84import net.dv8tion.jda.core.events.ReadyEvent;
85import net.dv8tion.jda.core.events.guild.GuildBanEvent;
86import net.dv8tion.jda.core.events.guild.GuildLeaveEvent;
87import net.dv8tion.jda.core.events.guild.member.GuildMemberLeaveEvent;
88import net.dv8tion.jda.core.events.guild.member.GuildMemberNickChangeEvent;
89import net.dv8tion.jda.core.events.guild.member.GuildMemberRoleAddEvent;
90import net.dv8tion.jda.core.events.guild.voice.GuildVoiceJoinEvent;
91import net.dv8tion.jda.core.events.guild.voice.GuildVoiceLeaveEvent;
92import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent;
93import net.dv8tion.jda.core.events.message.MessageDeleteEvent;
94import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
95import net.dv8tion.jda.core.events.message.guild.GuildMessageDeleteEvent;
96import net.dv8tion.jda.core.events.message.react.MessageReactionAddEvent;
97import net.dv8tion.jda.core.events.user.UserNameUpdateEvent;
98import net.dv8tion.jda.core.hooks.ListenerAdapter;
99import net.dv8tion.jda.core.managers.ChannelManager;
100import net.dv8tion.jda.core.managers.GuildController;
101import net.dv8tion.jda.core.requests.Request;
102import net.dv8tion.jda.core.requests.restaction.pagination.ReactionPaginationAction;
103
104//import static Database.DBFunctions.*;
105import static DiscordBot.BotUtils.*;
106import static Database.DBFunctions.*;
107
108@SuppressWarnings("unused")
109public class CommandHandler extends ListenerAdapter {
110
111 //Embeds
112 public static MessageEmbed embed;
113 public static EmbedBuilder embedBuilder;
114
115 // Requirements
116 private static final int REQ_BASE_FAME = 5000;
117 private static final int REQ_MAXED_CHARS = 3;
118
119 // User IDs
120 static final String AUTHOR = "216008405758771200"; //tybug's id
121 // Voice channels
122 private static final String VC_QUEUE = "342858485387755524"; //**
123 private static final String VC_AFK = "342845468826402816"; //**
124 // Roles
125 public static final String ROLE_RAID = "342844963845046282"; //**
126 public static final String ROLE_VIAL = "342841048852201492"; //**
127 public static final String ROLE_MOD = "342842671418703872"; //**
128 public static final String RAID_LEADER = "349678092219842571"; //**
129 public static final String ROLE_ADMIN = "342840802449424384"; //**
130
131
132 // Text channels
133 private static final String STDOUTPUT = "350043042834808832"; //**
134 private static final String LEADERVOTE = "357226117985468427"; //**
135 private static final String REGISTER_CHANNEL = "417352100733779969"; //**
136
137 private static final String BOT_STAFF = "350081735930806283"; //**
138 private static final String BOT_CHANNEL = "350043042834808832"; //**
139 private static final String BOT_SANDBOX = "360949070174355460"; //**
140 private static final String BOT_MOD = "386487696895508483"; //**
141 private static final String BOT_NAMEHISTORY = "390817290318249984"; //**
142 private static final String BOT_SPAM = "342842121239396352"; //**
143 private static final String PENDING_CHANNEL = "417354217901260801"; //**
144
145
146 private static final List<String> whitelist = new ArrayList<>(Arrays.asList(BOT_STAFF, BOT_CHANNEL, BOT_SANDBOX, BOT_MOD, BOT_NAMEHISTORY));
147 private static final HashMap<String, String> verifiedNames = new HashMap<>();
148 private static final String SBC = "342836970873159680"; //**
149
150 // List of unnamed players to filter out of name history
151 private static final List<String> unnamed = Arrays.asList("Darq", "Deyst", "Drac", "Eango", "Eashy", "Eati",
152 "Eendi", "Ehoni", "Gharr", "Iatho", "Iawa", "Idrae", "Iri", "Issz", "Itani", "Laen", "Lauk", "Lorz",
153 "Oalei", "Odaru", "Oeti", "Orothi", "Oshyu", "Queq", "Radph", "Rayr", "Ril", "Rilr", "Risrr", "Saylt",
154 "Scheev", "Sek", "Serl", "Seus", "Tal", "Tiar", "Uoro", "Urake", "Utanu", "Vorck", "Vorv", "Yangu", "Yimi", "Zhiar", "N/A");
155
156 // List of users for which verification is in progress
157 // private static final Object verificationLock = new Object();
158 // private static List<User> verification = new ArrayList<>();
159
160 //User id :: Vericode
161 private static Map<String, String> vericodes = new HashMap<String, String>();
162
163 // Message id :: Member id
164 private static Map<String, String> pendingReactions = new HashMap<String, String>();
165 // Message id :: IGN
166 private static Map<String, String> pendingNames = new HashMap<String, String>();
167
168 private static final String GUILD_CHECK = "416431177319055371"; //**
169 private static final String GUILD_X = "417354845670998017"; //**
170
171
172
173 private static boolean paused = false;
174 private static final Queue<Member> queue = new ConcurrentLinkedQueue<>();
175 private static final Object queueLock = new Object();
176 private volatile static boolean finishOngoing = false;
177 private volatile static boolean splitOngoing = false;
178 private static final Object splitLock = new Object();
179
180 // Raid specific
181 private static final String RAIDCHAT = "342846622176378880"; //**
182 private static final String GROUP_FIGHTING = "342841893559730197"; //**
183 private static final String VC_CULTIST = "349668599700652032"; //**
184 private static final Object vialQueueLock = new Object();
185 private static List<Member> vialQueue = new LinkedList<>();
186 private static List<Member> dodgers = new LinkedList<>();
187 private static final List<Raid> raids = new LinkedList<>();
188
189 //TODO
190 //audio
191
192
193 // Used for recognition of /who images
194 private static Recognise recognise;
195
196 private static final String AUDIO_PATH = "res/AFK_check.mp3";
197
198 // A flag to indicate that multiple matches were found when looking for an exact match
199 private static boolean multipleMatchFlag;
200
201 // A lock for the message ID list
202 private static final Object msgIDLock = new Object();
203 // A list of message IDs which got deleted by the bot in #register-here. If a message ID is in this list, it won't clear the entire history from the user.
204 // This is used to delete duplicate Realmeye links without clearing someone's entire register-here message history.
205 private static final List<Long> msgIDs = new ArrayList<>();
206
207 // A map from Long to User with timestamps of events
208 private static TreeMap<Long, User> commandTimes = new TreeMap<>();
209
210 static Message calloutMessage;
211
212 //Used for checkQueue() sysos and possibly more laters
213 private static boolean debug = false;
214
215 public static void main(String[] args){
216 JDA jda = null;
217 try {
218 jda = new JDABuilder(AccountType.BOT)
219 // .setToken("NDEwMjM5OTI4Njk0MjEwNTYz.DVqROA.etOH7v5KeNmnuOjqCb1iBeTILwA") // SBC Test JDA Bot
220 .setToken("MzQ5OTYwNTk5OTYwMjg5Mjkx.DP8bbA.3mIy51H4hv6kmzPQkle4btiGh3E") //SBC bot
221 .addEventListener(new CommandHandler())
222 // .setGame(new Game("with Spooky Boi"))
223 .buildBlocking();
224
225 } catch (LoginException e) {
226 e.printStackTrace();
227 } catch (InterruptedException e) {
228 e.printStackTrace();
229 }
230
231 recognise = new Recognise();
232 Raid baseRaid = new Raid(
233 jda.getTextChannelById(RAIDCHAT),
234 jda.getVoiceChannelById(GROUP_FIGHTING),
235 jda.getVoiceChannelById(VC_CULTIST));
236 raids.add(baseRaid);
237 System.out.println("Client is ready!");
238 // Play with Spooky Boi
239 // Check if we can move anyone in from the queue
240 checkQueue();
241 startAutomatedActivity(jda);
242 }
243
244
245
246
247
248
249
250 public void onMessageReceived(MessageReceivedEvent event)
251 {
252 //If the author is a bot, ignore. Bad recursion and ratelimit issues can happen here.
253 if(event.getAuthor().isBot()) {
254 return;
255 }
256
257
258 //PM the author for their own curiosity, then ignore if the message is a pm to the bot
259 JDA jda = event.getJDA();
260 if(event.isFromType(ChannelType.PRIVATE)) {
261 if(event.getAuthor().getId().equals(AUTHOR)) {
262 for(User u : event.getMessage().getMentionedUsers()) {
263 String[] parts = event.getMessage().getContentDisplay().split(" ");
264 String send = "";
265 for(int i = 1; i < parts.length; i++) {
266 send += parts[i] + " ";
267 }
268 sendPrivateMessage(u, send);
269 sendPrivateMessage(jda.getUserById(AUTHOR), send);
270 }
271 }
272
273
274 String start = event.getAuthor().getName() + "/" + jda.getGuildById(SBC).getMember(event.getAuthor()).getEffectiveName() + "/" +
275 jda.getGuildById(SBC).getMember(event.getAuthor()).getAsMention() + ": \n`";
276 sendPrivateMessage(jda.getUserById(AUTHOR), start + event.getMessage().getContentRaw() + "`");
277 for(Attachment a : event.getMessage().getAttachments()) {
278 sendPrivateMessage(jda.getUserById(AUTHOR), start + a.getUrl() + "`");
279 }
280 return;
281 }
282 //Should never happen, but if it's anything else but a text channel, return
283 if(!event.isFromType(ChannelType.TEXT)) {
284 return;
285 }
286
287 User author = event.getAuthor();
288 Message message = event.getMessage();
289 if(message == null) {
290 return;
291 }
292 String msg = message.getContentDisplay().toLowerCase();
293 String[] argArray = event.getMessage().getContentRaw().split(" ");
294
295 Guild guild = event.getGuild();
296 TextChannel textChannel = event.getTextChannel();
297 Member member = event.getMember();
298 GuildController gc = guild.getController();
299 ChannelManager gm = textChannel.getManager();
300
301 /*
302 0 = tybug (Whoever's ID is in AUTHOR)
303 1 = admin
304 2 = mod
305 3 = RL
306 4 = TRL (Currently doesn't exist, but space is left if it ever gets added)
307 5 = Vial (Currently unused, but easy way to distinguish between member and vial
308 6 = member
309 */
310 int clearance = checkPerms(member);
311
312
313 if(msg.equals(BOT_PREFIX + "queue")) {
314 synchronized (queueLock) {
315 List<Member> queueList = new ArrayList<>(queue);
316 List<String> names = getIGNs(queueList, guild);
317 sendMessage(textChannel, "Current queue: " + names);
318 }
319 }
320
321 if(msg.startsWith(BOT_PREFIX + "finish") && clearance < 4) {
322 new Thread(() -> {
323 String[] parts = msg.split(" ");
324
325 final Raid raid = getRaidFinal(raids, member, textChannel);
326
327 // Check if there's already an AFK check ongoing
328 if (finishOngoing) {
329 sendMessage(textChannel, "An AFK check is already happening! Please wait until "
330 + "the current AFK check is done before starting a new one.");
331 return;
332 }
333 raid.setSplit(false);
334 finishOngoing = true;
335 // Reset the temporary Raid Leaders to normal
336 for (Member m : raid.getTempRL().keySet()) {
337
338 // Mute the user
339 gc.setMute(m, true).queue();
340
341 // Reset the nickname
342 gc.setNickname(m, raid.getTempRL().get(m)).queue();
343
344 // Launch a leader vote for them
345 Message m2 = guild.getTextChannelById(LEADERVOTE).sendMessage("@here " + m.getAsMention() +
346 ", was leading group " + getGroup(m, raid.getGroupA())).complete();
347
348 m2.addReaction("\u2B06").complete();
349 m2.addReaction("\u2B07").complete();
350 m2.addReaction("\uD83E\uDD37").complete();
351
352 }
353 raid.getTempRL().clear();
354
355 // Remove vial role from all those excluded in the bot_split command
356 if (raid.getExcl() != null && raid.getExcl().size() > 0) {
357 Role role = guild.getRoleById(ROLE_VIAL);
358
359 for (Member m : raid.getExcl()) {
360 try {
361 gc.removeSingleRoleFromMember(m, role).queue();
362 } catch (Exception e) {
363 System.err.println("Exception caught while trying to remove vial role from user " + m.getEffectiveName());
364 e.printStackTrace();
365 }
366 }
367 raid.setExcl(null);
368 }
369
370 // If `bot_finish end` is done, don't do the AFK check.
371 if (parts.length >= 2 && parts[0].toLowerCase().equals("end")) {
372 raid.setQueue_blocked(false);
373 finishOngoing = false;
374 checkQueue();
375 return;
376 }
377
378 // Do AFK check
379 Thread t = new Thread(() -> {
380 // Get the raid-callout channel
381 TextChannel raidCallouts = guild.getTextChannelsByName("raid-callouts", true).get(0);
382 // Get the emojis
383 Emote emoji = guild.getEmotesByName("spookywhale", true).get(0);
384 Emote cultistEmoji = guild.getEmotesByName("vial", true).get(0);
385
386 String tempMessage = "@here Starting new raid soon! But first... an AFK check.\n" +
387 "If you're in **Fighting #" + raid.getId() + "** or the **Queue**, "
388 + "respond with " + emoji.getAsMention() + " or you will be moved to AFSB! "
389 + "Additionally, respond with " + cultistEmoji.getAsMention() + " "
390 + "to join the next Cultist run.\n Time remaining: ";
391 String sendMessage = tempMessage + "60 seconds.";
392
393 System.out.println("Sending AFK check message");
394 // Block the queue, get the voice channels
395 raid.setQueue_blocked(true);
396
397
398 calloutMessage = raidCallouts.sendMessage(sendMessage).complete();
399
400 raid.setAnnounceMessage(calloutMessage);
401 System.out.println("Getting voice channels");
402 VoiceChannel fightingChannel = raid.getFightingVoiceChannel();
403 VoiceChannel afkChannel = guild.getVoiceChannelById(VC_AFK);
404 VoiceChannel queueChannel = guild.getVoiceChannelById(VC_QUEUE);
405 System.out.println("Adding reactions");
406
407 // Add the AFK check emojis
408 if(calloutMessage == null) {
409 sendMessage(textChannel, "Callout message is null!");
410 }
411 if(emoji == null) {
412 sendMessage(textChannel, "emoji is null!");
413 }
414 calloutMessage.addReaction(emoji).queue();
415 calloutMessage.addReaction(cultistEmoji).queue();
416
417
418
419 System.out.println("Playing queue sound");
420 //playQueueSound(guild);
421 System.out.println("Counting down");
422
423
424 // Count down from 60 and move AFKs
425 for (int i = 1; i < 13; i++) {
426 try {
427 Thread.sleep(5000);
428 } catch (InterruptedException e) {
429 e.printStackTrace();
430 }
431 final int temp = 60 - 5 * i;
432
433 // After 10 seconds, disconnect from the queue
434 // if (i == 2) {
435 // System.out.println("Leaving queue channel.");
436 // RequestBuffer.request(queueChannel::leave).get();
437 // // Prepare the bot for the next AFK check
438 // System.out.println("Making track clone.");
439 // track = track.makeClone();
440 // }
441 //
442
443
444
445 // Edit the message
446 raid.getAnnounceMessage().editMessage(tempMessage + temp + " seconds").queue();
447
448 }
449
450 //Fetch the message again to get reactions
451 for(MessageReaction mr : raid.getAnnounceMessage().getTextChannel()
452 .getMessageById(raid.getAnnounceMessage().getId()).complete().getReactions()) {
453
454 if(mr.getReactionEmote().getName().equalsIgnoreCase("spookywhale")) { //If they reacted with the whale
455 moveAFKUsers(fightingChannel, afkChannel, mr.getUsers().complete()); //Move afk users
456 moveAFKUsers(queueChannel, afkChannel, mr.getUsers().complete());
457 }
458
459 if(mr.getReactionEmote().getName().equalsIgnoreCase("vial")) { //If they reacted with the vial
460 raid.setCultists(usersToMembers(mr.getUsers().complete(), guild));
461
462 }
463
464 }
465
466 raid.getAnnounceMessage().editMessage("@here Starting new raid soon! Join the "
467 + "queue to get into the next run!").queue();
468 });
469 t.start();
470 try {
471 t.join();
472 } catch (InterruptedException e) {
473 e.printStackTrace();
474 }
475
476 checkQueue();
477
478 // Reset the `finishOngoing` flag and unlock the queue
479 finishOngoing = false;
480 raid.setQueue_blocked(false);
481 }).start();
482 }
483
484
485
486
487
488
489 if(msg.equals(BOT_PREFIX + "guild_info") && clearance < 1) {
490 log(member, "guild_info");
491
492 List<Role> roles = guild.getRoles();
493 StringBuilder sb = new StringBuilder();
494 sb.append("```\n");
495
496 for (Role r : roles) {
497 sb.append(r.getName() + ": " + r.getId() + "\n");
498 }
499
500 sb.append("```");
501 sendMessage(textChannel, sb.toString());
502
503 }
504
505 if(msg.equals(BOT_PREFIX + "autododge") && clearance < 4) {
506 new Thread(() -> {
507
508 Raid raid = getRaidFinal(raids, member, textChannel);
509 // if (raid.getPreviousSplit() == null || raid.getVialRun() == null) {
510 // sendMessage(textChannel, "There is no previous run in this Raid!");
511 // return;
512 // }
513 // Check if an image is attached
514 if (message.getAttachments().size() == 0) {
515 sendMessage(textChannel, "You must attach an image!");
516 return;
517 }
518 // Download the image
519 BufferedImage image;
520 try {
521 image = getFile(message.getAttachments().get(0).getUrl());
522 } catch (IOException e) {
523 e.printStackTrace();
524 sendMessage(textChannel, "Failed to download image! Please try again later.");
525 return;
526 }
527 sendMessage(textChannel, "Automatically dodging players...");
528 // Parse the /who
529 List<String> igns = recognise.recognise(image);
530 if (igns.size() == 1 && igns.get(0).length() > 10) {
531 // error
532 sendMessage(textChannel, "Failed to recognise the image! Reason:\n\n" + igns.get(0) + "\n\n" +
533 "Please try again later.");
534 } else {
535 // Check if any non-fatal errors occurred
536 String error = igns.get(igns.size() - 1);
537 if (error.length() > 10) {
538 igns.remove(error);
539 sendMessage(textChannel, "__**The recognition was completed with the following errors:"
540 + " **__\n" + error + "\nThe results may be incorrect.");
541 }
542 // Print out image recognition info.
543
544 // The people in the /who in the order they appear in the screenshot
545 sendMessage(textChannel, "**List of IGNs in the dungeon (parsed order)**: `" + igns + "`");
546 igns.sort(String::compareTo);
547 // Alphabetical order
548 sendMessage(textChannel, "**List of IGNs in the dungeon (alphabetical)**: `" + igns + "`");
549
550
551 // Get the users from SBC in the run, matching by IGN (see getUserByNameExact).
552 System.out.println("Getting users from IGNs");
553
554
555 List<Member> userList = igns.stream().map(s -> getMemberByNameExact(s, guild, true))
556 .filter(Objects::nonNull).collect(Collectors.toList());
557 System.out.println("Getting users in Raid");
558 // List of users from SBC who are in vc and the dungeon
559 List<Member> inRaid = userList.stream().filter(u -> {
560 Raid u_raid = getRaid(raids, u);
561 return (u_raid != null && u_raid.equals(raid)) || raid.getVialRun().contains(u);
562 }).collect(Collectors.toList());
563
564
565 sendMessage(textChannel, "**People from the Raid in the run**: `"
566 + getIGNs(sortByName(inRaid, guild), guild) + "`");
567
568 // List of users from SBC who are not in vc but are in the dungeon
569 System.out.println("Getting Crashers in SBC");
570
571
572 List<Member> crashersInSBC = userList.stream().filter(u -> !inRaid.contains(u)).collect(Collectors.toList());
573 sendMessage(textChannel, "**Crashers from SBC**: "
574 + getMentions(sortByName(crashersInSBC, guild)));
575
576 System.out.println("Getting crashers");
577 // List of users in the dungeon who are not in sbc
578 List<String> crashers = igns.stream().filter(s -> {
579 Member memb = getMemberByNameExact(s, guild, false);
580 if ((memb == null) && multipleMatchFlag) {
581 Role raidRole = guild.getRoleById(ROLE_RAID);
582 List<String> names = getPermutations(s);
583
584 // Get the list of users who match one of the permutations of the name
585 List<Member> matches = guild.getMembersWithRoles(raidRole).stream().filter(m -> {
586 String[] list = m.getEffectiveName().split("[^a-zA-Z]");
587 for (String string : list) {
588 // Check if this IGN is equal to the given name.
589 String ign = string.replaceAll("[^a-zA-Z]", "");
590 // First, try the parsed IGN. If this is correct, we're done.
591 if (ign.equalsIgnoreCase(string)) {
592 return true;
593 }
594 // If there's no exact match, try each permutation of the name.
595 for (String n : names) {
596 if (ign.equalsIgnoreCase(n)) {
597 return true;
598 }
599 }
600 }
601 // If no suitable match was found, return false.
602 return false;
603 }).collect(Collectors.toList());
604 sendMessage(textChannel, "User " + s + " has been found multiple times in SBC: " + matches);
605 }
606 return memb == null || crashersInSBC.contains(memb);
607 }).sorted(String::compareTo).collect(Collectors.toList());
608
609 sendMessage(textChannel, "**Crashers**: `" + crashers + "`");
610 System.out.println("Getting list of people who dodged the cultist run");
611 // List of users who missed the dungeon
612 List<Member> missedRun = raid.getVialRun().stream().filter(m -> !inRaid.contains(m)).collect(Collectors.toList());
613 sendMessage(textChannel, "**People who dodged the cultist run**: " + getMentions(sortByName(missedRun, guild)));
614
615
616 // Dodge all the people who missed the run.
617 missedRun.forEach(m -> {
618 if (!dodgers.contains(m)) dodgers.add(m);
619 });
620 // Decrement activity for these users
621 missedRun.forEach(m -> decrementActivity(m.getUser().getIdLong(), raid.getPreviousRaidDate()));
622 sendMessage(textChannel, "Added all users who dodged the cultist run to the dodge list!");
623 }
624 }).start();
625 }
626
627
628 if(msg.startsWith(BOT_PREFIX + "split") && clearance < 4) {
629 new Thread(() -> {
630
631 final Raid raid = getRaidFinal(raids, member, textChannel);
632 // Check if there's already a split ongoing
633 synchronized (splitLock) {
634 if (splitOngoing) {
635 sendMessage(textChannel, "`bot_split` is already happening! Please wait until the current split "
636 + "is done before starting a new one.");
637 return;
638 }
639 splitOngoing = true;
640 }
641 // Check if an image is attached
642 if (message.getAttachments().size() == 0) {
643 sendMessage(textChannel, "You must attach an image!");
644 splitOngoing = false;
645 return;
646 }
647 // Check for @everyone/@here
648 if (message.mentionsEveryone()) {
649 sendMessage(textChannel, "You're not allowed to use `@everyone` or `@here` in this command!");
650 splitOngoing = false;
651 return;
652 }
653 // Check if number after split can be parsed
654 int splitNumber;
655 boolean splitNum = false;
656 // Check arguments
657 // NOTE: splitNum is an optional argument. Because of this, indices for later arguments depend on the splitNum boolean.
658 String[] args = msg.split(" ");
659 if (args.length == 1) {
660 // The default amount of people selected for cultist is 20% of the raid's size.
661 splitNumber = raid.getSize() / 5;
662 sendMessage(textChannel, "No argument given. Defaulted to " + splitNumber + " users selected for Cultist.");
663 } else {
664 try {
665 // -all is the argument for full cultist, or any number above the raid's size
666 if (args[1].toLowerCase().equals("-all")) {
667 splitNumber = 9001;
668 } else {
669 splitNumber = Integer.parseInt(args[1]);
670 }
671 splitNum = true;
672 } catch (NumberFormatException e) {
673 splitNumber = raid.getSize() / 5;
674 sendMessage(textChannel, "No split number given. Defaulted to " + splitNumber + " users selected for Cultist.");
675 }
676 }
677 // Get the date of this run and save it (used in `undoall`)
678 Date dateObj = new Date();
679 DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
680 String date = format.format(dateObj);
681 // Save the date for possible use in `undoall` to undo activity
682 raid.setPreviousRaidDate(date);
683
684 // Lock queue and start run
685 raid.setQueue_blocked(true);
686 raid.setSplit(true);
687 // Get the relevant channels
688 VoiceChannel vc_fighting = raid.getFightingVoiceChannel();
689 TextChannel raidChat = raid.getRaidChannel();
690 TextChannel botStaff = guild.getTextChannelById(BOT_STAFF);
691 // Get the users in the fighting channel
692 List<Member> users = vc_fighting.getMembers();
693 // Get the users who get excluded.
694 if (message.getMentionedMembers().size() > 0) {
695 // Everyone who got mentioned gets excluded
696 raid.setExcl(message.getMentionedMembers());
697 } else {
698 // Initialise
699 raid.setExcl(new LinkedList<>());
700 if (args.length > 1) {
701 // If the optional split number argument is left out, start at index 0. Otherwise start at 1
702 for (int i = (splitNum ? 2 : 1); i < args.length; i++) {
703 // Get the user by name
704 Member memb = getUserByName(args[i], textChannel);
705
706 // Error message printed in getUserByName
707 if (memb == null) {
708 continue;
709 }
710
711 // Add the user to the list of excluded users
712 raid.getExcl().add(memb);
713
714 }
715 }
716 }
717 String sTemp = "Users excluded: [";
718 boolean looped = false;
719 for(Member m : raid.getExcl()) {
720 sTemp += m.getAsMention() + ", ";
721 looped = true;
722 }
723 if(looped) {
724 sTemp = sTemp.substring(0, sTemp.length() - 2) + "]";
725 } else {
726 sTemp += "]";
727 }
728 sendMessage(textChannel, sTemp);
729
730 // Download the image
731 BufferedImage image;
732 try {
733 image = getFile(message.getAttachments().get(0).getUrl());
734 } catch (IOException e) {
735 e.printStackTrace();
736 sendMessage(textChannel, "Failed to download image! Please try again later.");
737 splitOngoing = false;
738 return;
739 }
740
741 // Filter out the excluded users, and put them in a separate set if they're in the vc
742 // Essentially checks which of the users excluded are *actually* in the vc and ignores the rest
743 Set<Member> excludedInChannel = new HashSet<>();
744 for (Member m : raid.getExcl()) {
745 if(raid.getFightingVoiceChannel().equals(m.getVoiceState().getChannel())) {
746 excludedInChannel.add(m);
747 }
748 }
749 // Divide users into vial/void. See CommandHandler#getCultistUsers for more info on how exactly.
750 // This division is only preliminary, because the actual split will happen later on, when the image recognition is done.
751 // Saves this list in Raid#getVialRun()
752 // PREVIOUSLY_A_TODO: Safely remove, this is now hidden (caused confusion rather than being helpful). No longer shown to users.
753 raid.setVialRun(sortByName(getCultistUsers(users, splitNumber, guild, excludedInChannel, raid), guild));
754
755 // Divide all uses into group A/B. Save this in Raid#getGroupA()
756 raid.setGroupA(sortByName(users, guild).subList(0, users.size() / 2));
757 List<String> messages = new ArrayList<>();
758 // Print out groups A and B. Preliminary groups because they're decided *before* image recognition.
759 // Makes sure the messages aren't too long.
760 StringBuilder sb = new StringBuilder("Preliminary Group **A**:");
761 for (Member m : raid.getGroupA()) {
762 if (sb.length() + m.getAsMention().length() > 2000) {
763 messages.add(message.toString());
764 sb = new StringBuilder("Preliminary Group **A** (continued):");
765 }
766 sb.append("\n\t").append(m.getAsMention());
767 }
768 messages.add(sb.toString());
769 // Sends the messages in the right groups. Saves them so they can be edited later.
770 List<List<Message>> groupAMessages = sendgroupMessage(messages, botStaff, raidChat, raid);
771
772 // Idem for group B.
773 messages.clear();
774 sb = new StringBuilder("Preliminary Group **B**:");
775 for (Member m : sortByName(users.stream().filter(user -> getGroup(user, raid.getGroupA()) == 'B')
776 .collect(Collectors.toList()), guild)) {
777 if (sb.length() + m.getAsMention().length() > 2000) {
778 messages.add(sb.toString());
779 sb = new StringBuilder("Preliminary Group **B** (continued):");
780 }
781 sb.append("\n\t").append(m.getAsMention());
782 }
783 messages.add(sb.toString());
784 List<List<Message>> groupBMessages = sendgroupMessage(messages, botStaff, raidChat, raid);
785 messages.clear();
786
787 // Idem for Cultist group, except they aren't shown to the users.
788
789 sb = new StringBuilder("Preliminary users selected for **Cultist**:");
790 for (Member m : raid.getVialRun()) {
791 if (sb.length() + m.getAsMention().length() > 2000) {
792 messages.add("Users selected for **Cultist**:\n\tTo be determined");
793 sb = new StringBuilder("Preliminary users selected for **Cultist** (continued):");
794 }
795 sb.append("\n\t").append(m.getAsMention());
796 }
797 messages.add("Users selected for **Cultist**: \n\tTo be determined");
798 List<List<Message>> cultistMessages = sendgroupMessage(messages, botStaff, raidChat, raid);
799 messages.clear();
800
801 // Parse the /who screenshot
802 List<String> igns = recognise.recognise(image);
803 List<Member> inRaid;
804 if (igns.size() == 1 && igns.get(0).length() > 10) {
805 // A fatal error occurred in the image recognition
806 sendMessage(textChannel, "Failed to recognise the image! Reason:\n\n" + igns.get(0) + "\n\n" +
807 "Falling back to old implementation of `bot_split`.");
808 inRaid = users;
809 } else {
810 // Check for non-fatal errors in the image recognition
811 String error = igns.get(igns.size() - 1);
812 if (error.length() > 10) {
813 // IGNs have a maximum size of 10 characters, so anything above this is an error.
814 igns.remove(error);
815 sendMessage(textChannel, "__**The recognition was completed with the following errors:"
816 + " **__\n" + error + "\nThe results may be incorrect.");
817 }
818 System.out.println("Sending parsed IGNs");
819 // Print out parsed IGNs (UI + order in screenshot)
820 sendMessage(textChannel, "**List of IGNs in the dungeon (parsed order)**: `" + igns + "`");
821 igns.sort(String::compareTo);
822 System.out.println("Sending alphabetical IGNs");
823 // Print out alphabetical order
824 sendMessage(textChannel, "**List of IGNs in the dungeon (alphabetical)**: `" + igns + "`");
825 // Get the users from SBC in the run.
826 System.out.println("Getting users from IGNs");
827 List<Member> memberList = igns.stream().map(s -> getMemberByNameExact(s, guild, true))
828 .filter(Objects::nonNull).collect(Collectors.toList());
829 System.out.println("Got users from IGNs! Getting users in Raid");
830 // List of users from SBC who are in vc and the dungeon
831 inRaid = memberList.stream().filter(m -> {
832 Raid u_raid = getRaid(raids, m);
833 return u_raid != null && u_raid.equals(raid);
834 }).collect(Collectors.toList());
835
836 System.out.println("Got users in the Raid! Sending message");
837 sendMessage(textChannel, "**People from the Raid in the run**: "
838 + "`" + getIGNs(sortByName(inRaid, guild), guild) + "`");
839
840 // List of users from SBC who are not in vc but are in the dungeon
841 System.out.println("Sent message! Getting Crashers in SBC");
842 List<Member> crashersInSBC = memberList.stream().filter(m -> !inRaid.contains(m)).collect(Collectors.toList());
843
844 System.out.println("Got crashers in SBC! Sending message");
845 sendMessage(textChannel, "**Crashers from SBC**: [" + getMentions(sortByName(crashersInSBC, guild)) + "]");
846 System.out.println("Sent message! Getting crashers");
847
848 // List of users in the dungeon who are not in sbc.
849 List<String> crashers = igns.stream().filter(s -> {
850 Member memb = getMemberByNameExact(s, guild, false);
851 if ((memb == null) && multipleMatchFlag) {
852 Role raidRole = guild.getRoleById(ROLE_RAID);
853 List<String> names = getPermutations(s);
854 // Get the list of users who match one of the permutations of the name
855 List<Member> matches = guild.getMembersWithRoles(raidRole).stream().filter(m -> {
856 String[] list = m.getEffectiveName().split("[^a-zA-Z]");
857 for (String string : list) {
858 // Check if this IGN is equal to the given name.
859 String ign = string.replaceAll("[^a-zA-Z]", "");
860 // First, try the parsed IGN. If this is correct, we're done.
861 if (ign.equalsIgnoreCase(string)) {
862 return true;
863 }
864 // If there's no exact match, try each permutation of the name.
865 for (String n : names) {
866 if (ign.equalsIgnoreCase(n)) {
867 return true;
868 }
869 }
870 }
871 // If no suitable match was found, return false.
872 return false;
873 }).collect(Collectors.toList());
874 sendMessage(textChannel, "User " + s + " has been found multiple times in SBC: " + matches);
875 }
876 return memb == null || crashersInSBC.contains(memb);
877 }).sorted(String::compareTo).collect(Collectors.toList());
878
879 System.out.println("Got crashers! Sending message");
880 sendMessage(textChannel, "**Crashers**: `" + crashers + "`");
881 System.out.println("Sent message! Getting list of people who missed the run");
882 // List of users who missed the dungeon
883 List<Member> missedRun = users.stream().filter(m -> !inRaid.contains(m)).collect(Collectors.toList());
884 System.out.println("Got people who missed the run! Sending message");
885 sendMessage(textChannel, "**People who missed the run**: `"
886 + getIGNs(sortByName(missedRun, guild), guild) + "`");
887 System.out.println("Sent message! Moving people out");
888 // Get the queue voice channel
889 VoiceChannel queueChannel = guild.getVoiceChannelById(VC_QUEUE);
890 // Move all users who missed the run out to the queue
891 for (Member m : missedRun) {
892 String s = "You have been moved to the **Queue**. This happened because you missed the dungeon."
893 + " Keep an eye out for the next AFK check so you don't miss it next time.";
894 // Move to queue
895 try {
896 gc.moveVoiceMember(m, queueChannel).queue();
897 } catch (Exception e) {
898 System.out.println("Exception caught while splitting the raid, likely because " + m.getEffectiveName() +
899 "left the voice channel before I could move them!");
900 }
901
902 // Send PM saying why
903 sendPrivateMessage(m.getUser(), s);
904 }
905 }
906
907 // Set the raid's previous split. Used in `undoall` to be able to undo points.
908 raid.setPreviousSplit(new ArrayList<>());
909 raid.getPreviousSplit().addAll(inRaid);
910
911 // Select people for Cultist Hideout.
912 raid.setVialRun(sortByName(getCultistUsers(inRaid, splitNumber, guild, excludedInChannel, raid), guild));
913
914 // If this happened, the cultist/void split failed. Don't change points or dodge/cultist lists.
915 if (raid.getVialRun() == null) {
916 sendMessage(textChannel, "A database error occurred while running bot_split! Please try again!");
917 splitOngoing = false;
918 return;
919 }
920
921 // Remove all removed dodgers from the dodge list
922 dodgers.removeAll(raid.getDodgeRemoved());
923 // Remove and re-add people that are on the persistent cultist list.
924 // This is done to make sure everyone has a fair chance,
925 // instead of the first 15 people to do `bot_cultist` always getting selected for cultist.
926 synchronized (vialQueueLock) {
927 for (Member m : raid.getReQueue()) {
928 // Removes the entry
929 vialQueue.remove(m);
930 // Adds the entry back at the end of the list
931 vialQueue.add(m);
932 }
933 }
934
935 // Divide all uses into group A/B
936 raid.setGroupA(sortByName(inRaid, guild).subList(0, users.size() / 2));
937
938 messages = new ArrayList<>();
939 // Print out groups A and B
940 sb = new StringBuilder("Group **A**:");
941 for (Member m : raid.getGroupA()) {
942 if (sb.length() + m.getAsMention().length() > 2000) {
943 messages.add(sb.toString());
944 sb = new StringBuilder("Group **A** (continued):");
945 }
946 sb.append("\n\t").append(m.getAsMention());
947 }
948 messages.add(sb.toString());
949 // Edits the messages saved earlier
950 editGroupMessage(messages, groupAMessages);
951 // Make sure you don't re-send the same messages again
952 messages.clear();
953
954 // Idem for group B
955 sb = new StringBuilder("Group **B**:");
956 for (Member m : sortByName(inRaid.stream().filter(user -> getGroup(user, raid.getGroupA()) == 'B')
957 .collect(Collectors.toList()), guild)) {
958 if (sb.length() + m.getAsMention().length() > 2000) {
959 messages.add(sb.toString());
960 sb = new StringBuilder("Group **B** (continued):");
961 }
962 sb.append("\n\t").append(m.getAsMention());
963 }
964 messages.add(sb.toString());
965 editGroupMessage(messages, groupBMessages);
966 messages.clear();
967
968 // Idem for cultist list
969 sb = new StringBuilder("Users selected for **Cultist**:");
970 for (Member m : raid.getVialRun()) {
971 if (sb.length() + m.getAsMention().length() > 2000) {
972 messages.add(sb.toString());
973 sb = new StringBuilder("Users selected for **Cultist** (continued):");
974 }
975 sb.append("\n\t").append(m.getAsMention());
976 }
977 messages.add(sb.toString());
978 editGroupMessage(messages, cultistMessages);
979
980 // Send the PMs to the people selected for Cultist Hideout
981 for (Member m : raid.getVialRun()) {
982 String vialMessage = "You have been selected to do a **Cultist** run. This means you will do Cultist."
983 + " You are currently on 0 points.\nYou are in group **" + getGroup(m, raid.getGroupA()) + "**.";
984 sendPrivateMessage(m.getUser(), vialMessage);
985 }
986 // Send the PMs to the people selected for Void
987 for (Member m : inRaid.stream().filter(m -> !raid.getVialRun().contains(m)).collect(Collectors.toList())) {
988 String voidMessage = "You have been selected to do a **Void** run. You will help those who will go Cultist"
989 + " get all the flames, then you'll go for the Void. You are currently on " + (getPoints(m.getUser().getIdLong()) + 1)
990 + " point(s).\nYou are in group **" + getGroup(m, raid.getGroupA()) + "**.";
991 sendPrivateMessage(m.getUser(), voidMessage);
992 }
993
994 // Send the PMs to Mod+ about people on the screenshot list.
995 List<Long> screenList = screen_list();
996 if (screenList == null) {
997 // Send message in #bot_staff and skip step.
998 sendMessage(textChannel, "**ERROR**: A database error occurred while getting the screenshot list! Skipping this step.");
999 } else {
1000 // Get the list of users who are on the screenshot list.
1001 List<Member> screenListInRun = sortByName(inRaid.stream().filter(m -> screenList.contains(m.getUser().getIdLong()))
1002 .collect(Collectors.toList()), guild);
1003 String screenMessage = "Users in the current run who are on the screenshot list: ";
1004 // Mention them and add their IGN for ease of use.
1005 for (Member m : screenListInRun) {
1006 screenMessage = screenMessage.concat(m.getAsMention() + " (" + m.getEffectiveName() + "), ");
1007 }
1008 // remove the last ", "
1009 screenMessage = screenMessage.substring(0, screenMessage.length() - 2);
1010 final String sendMessage = screenMessage;
1011 if (screenListInRun.size() > 0) {
1012 // Send the message to each Mod which is in the current run
1013
1014 for(Member m : inRaid.stream().filter(m -> m.getRoles().contains(guild.getRoleById(ROLE_MOD))).collect(Collectors.toList())){
1015 sendPrivateMessage(m.getUser(), sendMessage);
1016 }
1017 }
1018 }
1019 System.out.println("Incrementing activity");
1020 // Increment activity for each person in the raid
1021 inRaid.forEach(m -> incrementActivity(m.getUser().getIdLong(), date));
1022 System.out.println("Change points for users for Cultist");
1023 // Reset points to 0 for everyone selected for Cultist Hideout
1024 for (Member m : raid.getVialRun()) {
1025 resetPoints(m.getUser().getIdLong());
1026 }
1027 System.out.println("Change points for users for Void");
1028 // Increment points for everyone selected for Void
1029 for (Member m : inRaid.stream().filter(m -> !raid.getVialRun().contains(m)).collect(Collectors.toList())) {
1030 incrementPoints(m.getUser().getIdLong());
1031 }
1032 // Reset the split ongoing flag so `split` can be done again
1033 splitOngoing = false;
1034 }).start();
1035 }
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053 if(msg.startsWith(BOT_PREFIX + "dodge") && clearance < 4) {
1054
1055 List<String> args = getArgs("dodge", msg);
1056
1057 // Disallow @everyone/@here. Happened once, wasn't fun.
1058 if (event.getMessage().mentionsEveryone()) {
1059 sendMessage(textChannel, "You are not allowed to user `@everyone` or `@here` in this command!");
1060 return;
1061 }
1062 // Get the mentioned users
1063 List<Member> mentions = message.getMentionedMembers();
1064 // Get the date for which to decrement activity
1065 Date dateObj = new Date();
1066 DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
1067 String date = format.format(dateObj);
1068
1069 if (mentions.size() > 0) {
1070 // Add all dodgers to the dodge list
1071 mentions.forEach(u -> {
1072 if (!dodgers.contains(u)) dodgers.add(u);
1073 });
1074 // Decrement activity for each user. This may not properly decrement activity if it's done around midnight.
1075 mentions.forEach(m -> decrementActivity(m.getUser().getIdLong(), date));
1076 sendMessage(textChannel, "Added all mentioned users to the dodger list!");
1077 } else if (args.size() > 0) {
1078 // Get the list of cultist dodgers
1079 if (args.size() == 1 && args.get(0).toLowerCase().equals("list")) {
1080 sendMessage(textChannel, "List of Cultist dodgers: " + listToString(sortByName(dodgers, guild)));
1081 return;
1082 }
1083 // Get the people to add from the arguments (`bot_dodge IGN...`)
1084 for (String arg : args) {
1085 Member memb = getUserByName(arg, textChannel);
1086 if(memb == null) {
1087 continue;
1088 }
1089 if (dodgers.contains(memb)) {
1090 sendMessage(textChannel, memb.getAsMention() + " is already on the dodger list!");
1091 } else {
1092 dodgers.add(memb);
1093 decrementActivity(memb.getUser().getIdLong(), date);
1094 sendMessage(textChannel, "Added " + memb.getAsMention() + " to dodger list!");
1095 }
1096 }
1097 } else {
1098 // Send error message
1099 sendMessage(textChannel, "Usage: `bot_dodge user/list`\n" +
1100 "Example: `bot_dodge @user...`\n" +
1101 " This will add all the mentioned users to the list of dodgers.\n" +
1102 "Alternative: `bot_dodge IGN...`\n" +
1103 " This will add all the users named to the list of dodgers.\n" +
1104 "Alternative: `bot_dodge list`\n" +
1105 " This will show the list of people who dodged a run, and haven't done a vial run since.");
1106 }
1107 }
1108
1109
1110
1111
1112
1113 if(msg.startsWith(BOT_PREFIX + "undodge") && clearance < 4) {
1114 List<String> args = getArgs("undodge", msg);
1115
1116 // Disallow @everyone/@here. Happened once, wasn't fun.
1117 if (event.getMessage().mentionsEveryone()) {
1118 sendMessage(textChannel, "You are not allowed to user `@everyone` or `@here` in this command!");
1119 return;
1120 }
1121 // Get the date for which to decrement activity
1122 Date dateObj = new Date();
1123 DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
1124 String date = format.format(dateObj);
1125
1126 if (message.getMentionedMembers().size() > 0) {
1127 for (Member m : message.getMentionedMembers()) {
1128 // Remove the user from the dodge list
1129 dodgers.remove(m);
1130 // increment activity for the given date (decremented at `autododge` or `dodge`, this is the undo command.
1131 incrementActivity(m.getUser().getIdLong(), date);
1132 sendMessage(textChannel, "Removed " + m.getAsMention() + " from dodger list!");
1133 }
1134 } else if (args.size() > 0) {
1135 // `bot_undodge IGN...` is done
1136 for (String arg : args) {
1137 // Get the user with the IGN given in the arguments
1138 Member memb = getUserByName(arg, textChannel);
1139 // If no valid user is found, an error message is printed in getUserByName.
1140 if (memb == null) continue;
1141 // Can't undodge a user who wasn't on the dodge list.
1142 if (!dodgers.contains(memb)) {
1143 sendMessage(textChannel, "User wasn't on the dodger list!");
1144 continue;
1145 }
1146 // Increment activity
1147 incrementActivity(memb.getUser().getIdLong(), date);
1148 // Remove from the dodge list
1149 dodgers.remove(memb);
1150 sendMessage(textChannel, "Removed " + memb.getAsMention() + " from the dodger list!");
1151 }
1152 } else {
1153 // Send error message
1154 sendMessage(textChannel, "Usage: `bot_undodge user/list`\n" +
1155 "Example: `bot_undodge @user...`\n" +
1156 " This will remove all the mentioned users from the list of dodgers.\n" +
1157 "Alternative: `bot_undodge IGN...`\n" +
1158 " This will remove all the users named from the list of dodgers.");
1159 }
1160 }
1161
1162
1163 if(msg.startsWith(BOT_PREFIX + "score")) {
1164 List<String> args = getArgs("score", msg);
1165
1166 List<Member> mention = message.getMentionedMembers();
1167 if (args.size() == 0) {
1168 // Get your own score
1169 // Check if the user is in the database
1170 if (userExists(member.getUser().getIdLong())) {
1171 sendMessage(textChannel, member.getAsMention() + ", your score is " + getPoints(member.getUser().getIdLong()));
1172 } else {
1173 sendMessage(textChannel, member.getAsMention() + ", your score is 0");
1174 }
1175 } else if (mention.size() == 1) {
1176 // Get the mentioned user's score, if the author has Mod role.
1177 if (clearance < 3) {
1178 Member memb = mention.get(0);
1179 // Check if the user is in the database
1180 if (userExists(memb.getUser().getIdLong())) {
1181 sendMessage(textChannel, memb.getAsMention() + "'s score is " + getPoints(memb.getUser().getIdLong()));
1182 } else {
1183 sendMessage(textChannel, memb.getAsMention() + "'s score is 0");
1184 }
1185 }
1186 } else if (args.size() == 1) {
1187 if (clearance < 3) {
1188 // Get the user through their IGN (i.e. `bot_score Botkiller`)
1189 Member memb = getUserByName(args.get(0), textChannel);
1190 // Error message sent in getUserByName if user == null
1191 if (memb != null) {
1192 // Check if the user exists (should always be the case)
1193 if (userExists(memb.getUser().getIdLong())) {
1194 sendMessage(textChannel, memb.getAsMention() + "'s score is " + getPoints(memb.getUser().getIdLong()));
1195 } else {
1196 sendMessage(textChannel, memb.getAsMention() + "'s score is 0.");
1197 }
1198 }
1199 }
1200 } else {
1201 // Send error message
1202 String s = "Usage: bot_score";
1203 if (clearance < 3) {
1204 s += " [User]\n" +
1205 "Example: bot_score\n" +
1206 "Example: bot_score Botkiller\n" +
1207 "Example: bot_score " + jda.getSelfUser().getAsMention();
1208 }
1209 sendMessage(textChannel, s);
1210 }
1211 }
1212
1213
1214
1215 if(msg.equals(BOT_PREFIX + "count")) {
1216 if(!textChannel.getId().equals(BOT_CHANNEL)) {
1217 Message m = textChannel.sendMessage("This command can only be run in <#" + BOT_CHANNEL + ">!").complete();
1218 m.delete().queueAfter(3, TimeUnit.SECONDS);
1219 return;
1220 }
1221
1222 final Raid raid = getRaidFinal(raids, member, textChannel);
1223
1224 sendMessage(textChannel, "Users in raid #" + raid.getId() + ": " + raid.getSize());
1225 }
1226
1227
1228
1229
1230 if(msg.equals(BOT_PREFIX + "undoall") && clearance < 4) {
1231 final Raid raid = getRaidFinal(raids, member, textChannel);
1232
1233 // Check if there was a previous raid. If either of these are null, it's a newly constructed Raid
1234 // (see `raid add` or a bot restart happened recently)
1235 if (raid.getPreviousSplit() == null || raid.getVialRun() == null) {
1236 sendMessage(textChannel, "No run was done in this raid yet!");
1237 return;
1238 }
1239 sendMessages(raid.getPreviousSplit(), ((Member m) -> {
1240 undo(m);
1241 decrementActivity(m.getUser().getIdLong(), raid.getPreviousRaidDate());
1242 return "\n\t" + m.getAsMention() + " - Old: " + getPrev(m.getUser().getIdLong()) + ", new: " + getPoints(m.getUser().getIdLong()) + ".";
1243 }), "Raid #" + raid.getId() + ": Change undone for the following list of people", textChannel, false);
1244 // Re-add all dodgers who got removed who aren't on the dodge list
1245 raid.getDodgeRemoved().forEach(u -> {
1246 if (!dodgers.contains(u)) dodgers.add(u);
1247 });
1248 // Note that the cultist list is ignored here, it's too much of a bother to undo that + it doesn't really matter that much anyway
1249 sendMessage(textChannel, "Raid #" + raid.getId() + ": The following people were added "
1250 + "back to the dodge list: " + raid.getDodgeRemoved());
1251 }
1252
1253
1254
1255 if(msg.startsWith(BOT_PREFIX + "cultist")) {
1256
1257 List<String> args = getArgs("cultist", msg);
1258
1259 // Add yourself to the cultist list
1260 if (args.size() == 0) {
1261 // Make sure we don't read outdated data or write to an outdated list.
1262 synchronized (vialQueueLock) {
1263 if (vialQueue.contains(member)) {
1264 // Report that the user is on the list and where
1265 sendMessage(textChannel, member.getAsMention() + ", you were already in the cultist list! Position in the cultist list: "
1266 + (vialQueue.indexOf(member) + 1));
1267 } else {
1268 // Add the user to the list and report where exactly
1269 vialQueue.add(member);
1270 sendMessage(textChannel, member.getAsMention() + ", you've been added to the cultist list! Position in the cultist list: "
1271 + (vialQueue.indexOf(member) + 1));
1272 }
1273 return;
1274 }
1275 }
1276 // Remove yourself from the cultist list
1277 if (args.get(0).equals("remove")) {
1278 // Again, make sure we don't read outdated data or write to an outdated list
1279 synchronized (vialQueueLock) {
1280 // Remove the user and report.
1281 vialQueue.remove(member);
1282 sendMessage(textChannel, member.getAsMention() + ", you've been removed from the cultist list!");
1283 }
1284 return;
1285 }
1286
1287 // Print out the list of users selected for cultist.
1288 if (args.get(0).equals("list") && clearance < 3) {
1289 // Make sure we synchronize to not get outdated data
1290 synchronized (vialQueueLock) {
1291 sendMessages(vialQueue, (m -> "\n\t" + m.getEffectiveName()), "Cultist List", textChannel, false);
1292 }
1293 return;
1294 }
1295 sendMessage(textChannel, "This command has up to 1 argument. \n" +
1296 "Usage: `bot_cultist remove? / list?`");
1297 }
1298
1299
1300
1301
1302
1303
1304 if(msg.startsWith(BOT_PREFIX + "vial") && clearance < 4) {
1305
1306 List<String> args = getArgs("vial", msg);
1307
1308
1309 Raid raid = getRaid(raids, member);
1310 if (raid == null) {
1311 sendMessage(textChannel, "You must be in a Raid voice chat to run this command!");
1312 return;
1313 }
1314 // Check arguments
1315 if (args.size() != 1) {
1316 sendMessage(textChannel, "This command has 1 argument. \n" +
1317 "Usage: `bot_vial`\n" +
1318 "Alternative: `bot_vial split/merge`");
1319 return;
1320 }
1321 VoiceChannel cultist = raid.getCultistVoiceChannel();
1322 switch (args.get(0)) {
1323 case "split":
1324 if (raid.getVialRun() == null) {
1325 sendMessage(textChannel, "There is no ongoing run! You either didn't do a split, or the bot restarted recently.");
1326 return;
1327 }
1328 // Move people selected for cultist to the cultist voice channel and unmute them
1329 for (Member m : raid.getVialRun()) {
1330 try {
1331 gc.moveVoiceMember(m, cultist).queue();
1332 } catch (Exception e) {
1333 System.out.println("Exception caught while checking the queue, likely because " + m.getEffectiveName()
1334 + " left the voice channel before I could move them!");
1335 }
1336 gc.setMute(m, false).queue();
1337 }
1338 break;
1339 case "merge":
1340 // Move people from the cultist voice channel back into the main channel.
1341 VoiceChannel fighting = raid.getFightingVoiceChannel();
1342 System.out.println("Merge command, moving users back to fighting");
1343 for (Member m : cultist.getMembers()) {
1344 System.out.println("Moving " + m.getEffectiveName() + " to the fighting voice channel from the cultist voice channel!");
1345 try {
1346 gc.moveVoiceMember(m, fighting).queue();
1347 } catch (Exception e) {
1348 System.out.println("Caught exception for bot_merge - I suspect because " + m.getEffectiveName() + " left the voice "
1349 + "channel before I moved them.");
1350 }
1351 }
1352 break;
1353 default:
1354 // Send error message
1355 sendMessage(textChannel, "This command has up to 1 argument. \n" +
1356 "Usage: `bot_vial`\n" +
1357 "Alternative: `bot_vial split/merge`");
1358 break;
1359 }
1360
1361 }
1362
1363
1364
1365 if(msg.startsWith(BOT_PREFIX + "lock") && clearance < 4) {
1366
1367 List<String> args = getArgs("lock", msg);
1368
1369
1370 Raid raid = getRaid(raids, member);
1371 if (raid == null) {
1372 sendMessage(textChannel, "Assuming Raid #1.");
1373 raid = raids.get(0);
1374 }
1375 // Lock the queue
1376 raid.setSplit(false);
1377 raid.setQueue_blocked(true);
1378 sendMessage(textChannel, "Raid #" + raid.getId() + ": Locked the queue! To unlock the queue again, use `bot_unlock`.");
1379 }
1380
1381
1382
1383
1384
1385 if(msg.startsWith(BOT_PREFIX + "unlock") && clearance < 4) {
1386
1387 List<String> args = getArgs("unlock", msg);
1388
1389 Raid raid = getRaid(raids, member);
1390 if (raid == null) {
1391 sendMessage(textChannel, "Assuming Raid #1.");
1392 raid = raids.get(0);
1393 }
1394 // Unlock the queue.
1395 raid.setSplit(true);
1396 raid.setQueue_blocked(false);
1397 sendMessage(textChannel, "Raid #" + raid.getId() + ": Unlocked the queue! To lock the queue again, use `bot_lock`.");
1398 checkQueue();
1399 }
1400
1401
1402
1403 if(msg.startsWith(BOT_PREFIX + "temprl") && clearance < 4) {
1404
1405 List<String> args = getArgs("temprl", msg);
1406
1407 final Raid raid = getRaidFinal(raids, member, textChannel);
1408
1409 String error = "Usage: `bot_tempRL @user`\nAlternative: `bot_tempRL IGN`";
1410 // Check arguments
1411 if (args.size() != 1) {
1412 sendMessage(textChannel, error);
1413 return;
1414 }
1415 if (message.getMentionedMembers().size() == 1) {
1416 Member m = message.getMentionedMembers().get(0);
1417 String name = m.getEffectiveName();
1418 // Save their nickname to restore later on (at `finish`)
1419 raid.getTempRL().put(m, name);
1420 // Unmute them and change their nickname
1421
1422 gc.setMute(m, false).queue();
1423 gc.setNickname(m, "." + name).queue();
1424
1425 sendMessage(textChannel, "Raid #" + raid.getId() + ": Unmuted user " + m.getAsMention() + ". "
1426 + "User will be muted again when `bot_finish` is run.");
1427 } else if (args.size() == 1) {
1428 // Get the user by IGN
1429 Member m = getUserByName(args.get(0), textChannel);
1430 // If user == null, the error message was printed in getUserByName
1431 if (m != null) {
1432 String name = m.getEffectiveName();
1433 // Save their nickname to restore later on (at `finish`)
1434 raid.getTempRL().put(m, name);
1435 // Unmute + change nickname
1436
1437 gc.setMute(m, false).queue();
1438 gc.setNickname(m, "." + name).queue();
1439 sendMessage(textChannel, "Raid #" + raid.getId() + ": Unmuted user " + m.getAsMention() +
1440 ". User will be muted again when `bot_finish` is run.");
1441 }
1442 } else {
1443 // Send error message
1444 sendMessage(textChannel, error);
1445 }
1446 }
1447
1448
1449
1450
1451 if(msg.startsWith(BOT_PREFIX + "give") && clearance < 4) {
1452
1453 List<String> args = getArgs("give", msg);
1454
1455 // Check arguments
1456 if (args.size() == 2 && args.get(0).toLowerCase().equals("vial")) {
1457 if (message.getMentionedMembers().size() == 1) {
1458 Member m = message.getMentionedMembers().get(0);
1459 // Give user vial role
1460 gc.addSingleRoleToMember(m, guild.getRoleById(ROLE_VIAL)).complete();
1461 } else {
1462 // Get user by name
1463 Member m = getUserByName(args.get(1), textChannel);
1464 // Error printed at getUserByName if user == null
1465 if (m != null) {
1466 // Give user vial role
1467
1468 gc.addSingleRoleToMember(m, guild.getRoleById(ROLE_VIAL)).complete();
1469 sendMessage(textChannel, "Added vial role to user " + m.getAsMention());
1470 }
1471 }
1472 } else {
1473 String error = "Usage: `bot_give vial @user`\n" +
1474 "Alternative: `bot_give vial IGN`";
1475 // Send error message
1476 sendMessage(textChannel, error);
1477 }
1478 }
1479
1480
1481
1482
1483 if(msg.startsWith(BOT_PREFIX + "remove") && clearance < 4) {
1484 List<String> args = getArgs("remove", msg);
1485
1486 // Check arguments
1487 if (args.size() == 2 && args.get(0).toLowerCase().equals("vial")) {
1488 if (message.getMentionedMembers().size() == 1) {
1489 Member m = message.getMentionedMembers().get(0);
1490 // Remove vial role
1491 gc.removeSingleRoleFromMember(m, guild.getRoleById(ROLE_VIAL)).queue();
1492 sendMessage(textChannel, "Removed vial role from user " + m.getAsMention());
1493 } else {
1494 // Get user by name
1495 Member m = getUserByName(args.get(1), textChannel);
1496 // Error message printed in getUserByName if u == null
1497 if (m != null) {
1498 // Remove role
1499
1500 gc.removeSingleRoleFromMember(m, guild.getRoleById(ROLE_VIAL)).queue();
1501 sendMessage(textChannel, "Removed vial role from user " + m.getAsMention());
1502 }
1503 }
1504 } else {
1505 String error = "Usage: `bot_remove vial @user`\n" +
1506 "Alternative: `bot_remove vial IGN`";
1507 // Send error message
1508 sendMessage(textChannel, error);
1509 }
1510 }
1511
1512
1513
1514 if(msg.startsWith(BOT_PREFIX + "list") && clearance < 4) {
1515 List<String> args = getArgs("list", msg);
1516 Raid raid = getRaid(raids, member);
1517 if (raid == null) {
1518 sendMessage(textChannel, "Assuming Raid #1.");
1519 return;
1520 }
1521 // Get the list of users from the previous run
1522 List<String> messages = new ArrayList<>();
1523 List<String> igns = getIGNs(sortByName(raid.getPreviousSplit(), guild), guild);
1524 if (igns == null || igns.size() == 0) {
1525 sendMessage(textChannel, "Raid #" + raid.getId() + ": There is no previous run!");
1526 return;
1527 }
1528 // Send a message with all IGNs of the users in the previous run
1529 // Slightly fucks up by adding ", " at the front of every new message after the first, but w/e
1530 String s = igns.remove(0);
1531 sendMessages(igns, (string -> ", " + string), "Raid #" + raid.getId() + ": Users in the previous run: " + igns.get(0), textChannel, true);
1532
1533 }
1534
1535
1536 if(msg.startsWith(BOT_PREFIX + "rlvote") && clearance < 3) {
1537 List<String> args = getArgs("rlvote", msg);
1538 Raid raid = getRaid(raids, member);
1539 if (raid == null) {
1540 sendMessage(textChannel, "Assuming Raid #1..");
1541 raid = raids.get(0);
1542 }
1543 Member m;
1544 List<Member> mention = message.getMentionedMembers();
1545 // Get the user by mention or by matching IGN.
1546 if (mention.size() == 1) {
1547 m = mention.get(0);
1548 } else if (args.size() == 1) {
1549 m = getUserByName(args.get(0), textChannel);
1550 } else {
1551 String s = "Usage: `bot_rlvote @user/IGN`\n" +
1552 "Example: `bot_rlvote`\n" +
1553 "Example: `bot_rlvote Botkiller`\n" +
1554 "Example: `bot_rlvote" + jda.getSelfUser().getAsMention() + "`";
1555 sendMessage(textChannel, s);
1556 return;
1557 }
1558 // Error message printed in getUserByName
1559 if (m == null) {
1560 return;
1561 }
1562 // Check if the targeted user is a Raid Leader
1563 if (checkPerms(m) > 3) {
1564 sendMessage(textChannel, "This command can only be used on Raid Leaders! To get a vote for a tempRL,"
1565 + " use `bot_tempRL @user/IGN` and then `bot_finish end?`.");
1566 return;
1567 }
1568 // Check if the user was in the previous run in this raid
1569 if (!raid.getPreviousSplit().contains(m)) {
1570 sendMessage(textChannel, "This user wasn't in Raid #" + raid.getId() + "!");
1571 return;
1572 }
1573 // Send a Raid Leader vote
1574 TextChannel leaderVote = guild.getTextChannelById(LEADERVOTE);
1575 Message tempMessage = leaderVote.sendMessage("@here " + m.getAsMention() +
1576 ", was leading group " + getGroup(m, raid.getGroupA()) + "in Raid #" + raid.getId()).complete();
1577 tempMessage.addReaction("\u2B06").queue();
1578 tempMessage.addReaction("\u2B07").queue();
1579 tempMessage.addReaction("\uD83E\uDD37").queue();
1580
1581 }
1582
1583
1584 if(msg.startsWith(BOT_PREFIX + "sync") && clearance < 1) {
1585 List<String> args = getArgs("sync", msg);
1586 Map<Long, Integer> all = getAll();
1587 if (all == null) {
1588 sendMessage(textChannel, "Unable to read point data!");
1589 return;
1590 }
1591 Set<Long> userIDs = all.keySet();
1592 Set<Long> toRemove = new HashSet<>();
1593 for (long l : userIDs) {
1594 if (guild.getMemberById(l) != null) {
1595 continue;
1596 }
1597 if (!userExists(l)) {
1598 continue;
1599 }
1600 Member m = guild.getMemberById(l);
1601 sendMessage(jda.getTextChannelById(BOT_SPAM), "Removed user " + m.getAsMention() + " from the database.");
1602 remove_user(l);
1603 removeVerificationEntry(l);
1604 toRemove.add(l);
1605 if (m != null) {
1606 dodgers.remove(m);
1607 vialQueue.remove(m);
1608 }
1609 }
1610
1611 sendMessage(textChannel, "Removed " + toRemove.size() + " people from the database. See #bot-spam for the full list.");
1612 }
1613
1614
1615 if(msg.startsWith(BOT_PREFIX + "raid") && clearance < 4) {
1616 List<String> args = getArgs("raid", msg);
1617 // Check arguments
1618 if (args.size() != 1 && args.size() != 2) {
1619 sendMessage(textChannel, "Must give one argument: `bot_raid add` or `bot_raid remove`." +
1620 " Note that you need to be in the raid you're trying to remove.\n" +
1621 "Example: `bot_raid remove` to remove Raid 2.");
1622 return;
1623 }
1624
1625 if (args.size() == 1 && args.get(0).equals("add")) {
1626 // Create a new Raid and add it to the list of raids
1627 Raid raid = new Raid(guild, raids.size() + 1);
1628 raids.add(raid);
1629 } else if (args.size() == 1 && args.get(0).equals("remove")) {
1630 // Get the raid to remove
1631 Raid toRemove = getRaid(raids, member);
1632 // Error messages are clear enough
1633 if (toRemove == null) {
1634 sendMessage(textChannel, "You must be in the raid you want to remove before running this command!");
1635 return;
1636 }
1637 if (toRemove.inRun()) {
1638 sendMessage(textChannel, "Raid is in a run right now! Do `bot_finish end` before deleting the raid group!");
1639 return;
1640 }
1641 if (toRemove.getId() == 1) {
1642 sendMessage(textChannel, "You can not remove the first raid!");
1643 return;
1644 }
1645 // Set the raid so it's inactive
1646 raids.remove(toRemove);
1647 // Decrement the ID of lower raids (if Raid #2 gets deleted, Raid #3 becomes Raid #2, Raid #4 becomes Raid #3 etc)
1648 for (int i = toRemove.getId() - 1; i < raids.size(); i++) {
1649 raids.get(i).decrement();
1650 }
1651 System.out.println("Removed raid " + toRemove + " from the list of raids!");
1652 // From here on out, the voice channels of the deleted raid become a sort of Queue channel.
1653 // The users will be moved into a different raid **before** the people in the actual Queue.
1654
1655 // Get the list of people in fighting and cultist voice channels in the raid
1656 List<Member> members = toRemove.getFightingVoiceChannel().getMembers();
1657 members.addAll(toRemove.getCultistVoiceChannel().getMembers());
1658
1659 // Shuffle the list for fairness
1660 Collections.shuffle(members);
1661 // Add all the people in the raid to the **front** of the queue.
1662 synchronized (queueLock) {
1663 VoiceChannel queueChannel = guild.getVoiceChannelById(VC_QUEUE);
1664 for(Member m : members) {
1665 try {
1666 gc.moveVoiceMember(m, queueChannel).queue();
1667 } catch (Exception e) {
1668 System.out.println("Exception caught while checking the queue, likely because " + m.getEffectiveName()
1669 + " left the voice channel before I could move them!");
1670 }
1671 }
1672 Queue<Member> temp = new ConcurrentLinkedQueue<>();
1673 while (queue.size() > 0) {
1674 temp.add(queue.remove());
1675 }
1676 // Add all users to the queue
1677 queue.addAll(members);
1678 queue.addAll(temp);
1679 }
1680 toRemove.delete();
1681 // Check the Queue to see if anyone can be moved.
1682 checkQueue();
1683 }
1684 }
1685
1686
1687 if(msg.startsWith(BOT_PREFIX + "forecast")) {
1688 List<String> args = getArgs("forecast", msg);
1689 int vialCount;
1690 // Get the raid
1691 Raid raid = getRaid(raids, member);
1692 if (raid == null || !raid.getFightingVoiceChannel().getMembers().contains(member)) {
1693 sendMessage(textChannel, "Assuming Raid #1."
1694 + " Chances are calculated assuming you are in the voice channel."); // TODO check if it really returns the right value, if not,
1695 //revert back to needing to be in the vc
1696// vialCount = (raid.getSize() + 1) / 5;
1697 //Can't really do this because getCultistForecast algorithm relies on the member being in a group. Would have to rewrite that
1698 return;
1699 }
1700 // Check split number
1701 else if (args.size() != 1) {
1702 vialCount = raid.getSize() / 5;
1703 sendMessage(textChannel, "No argument given. Defaulted to " + vialCount + " users selected for Cultist.");
1704 } else {
1705 try {
1706 vialCount = Integer.parseInt(args.get(0));
1707 } catch (NumberFormatException e) {
1708 sendMessage(textChannel, "Could not parse `" + args.get(0) + "` as an `integer`!");
1709 return;
1710 }
1711 }
1712 // Get the chance you'll be selected for cultist
1713
1714 double chance = getCultistForecast(raid.getFightingVoiceChannel().getMembers(), vialCount, guild, raid, member);
1715 if (chance == -1) {
1716 sendMessage(textChannel, member.getAsMention() + ", an error occurred! Please try again later.");
1717 return;
1718 }
1719 // Display chance in percentage, rounded to 2 decimals.
1720 sendMessage(textChannel, member.getAsMention() + ", you have a " +
1721 new BigDecimal(chance * 100).setScale(2, RoundingMode.HALF_UP).doubleValue()
1722 + "% chance to be selected for Cultist next run.");
1723 }
1724
1725
1726
1727 if(msg.startsWith(BOT_PREFIX + "screen") && clearance < 3) {
1728 List<String> args = getArgs("screen", msg);
1729 if(!textChannel.getId().equals(BOT_MOD)) {
1730 Message m = textChannel.sendMessage("This command must be used in <#" + BOT_MOD + ">!").complete();
1731 m.delete().queueAfter(3, TimeUnit.SECONDS);
1732 return;
1733 }
1734
1735
1736 String error = "This command takes at least 1 argument. \n**Format: `bot_screen add/remove/list @User.../IGN...`**\n" +
1737 "Example: bot_screen add Botkiller Dash";
1738 // Check arguments
1739 if (args.size() < 1) {
1740 sendMessage(textChannel, error);
1741 return;
1742 }
1743 // Get the screenshot list
1744 List<Long> screenList = screen_list();
1745 if (screenList != null) {
1746 // Check for each entry if they're still in the guild, remove them if not
1747 screenList.forEach(l -> {
1748 if (guild.getMemberById(l) == null) screen_remove(l);
1749 });
1750 } else {
1751 sendMessage(textChannel, "Failed to retrieve screen list!");
1752 return;
1753 }
1754 switch (args.get(0)) {
1755 case "add":
1756 // Add a user to the screenshot list
1757 // Allows mentions or IGN. If you want documentation on that, check out `dodge` for comments on the general structure.
1758 if (message.getMentionedMembers().size() > 0) {
1759 message.getMentionedMembers().forEach(m -> {
1760 if (!screenList.contains(m.getUser().getIdLong())) {
1761 screen_add(m.getUser().getIdLong());
1762 sendMessage(textChannel, "Added " + m.getAsMention() + " to the screenshot list!");
1763 } else {
1764 sendMessage(textChannel, "User " + m.getAsMention() + " is already on the screenshot list!");
1765 }
1766 });
1767 } else {
1768 if (args.size() == 1) {
1769 sendMessage(textChannel, "No user specified! Usage: `bot_screen add IGN...`");
1770 return;
1771 }
1772 for (int i = 1; i < args.size(); i++) {
1773 Member m = getUserByName(args.get(i), textChannel);
1774 if (m == null) {
1775 continue;
1776 }
1777 if (screenList.contains(m.getUser().getIdLong())) {
1778 sendMessage(textChannel, "User " + m.getAsMention() + " is already on the screenshot list!");
1779 return;
1780 }
1781 screen_add(m.getUser().getIdLong());
1782 sendMessage(textChannel, "Added " + m.getAsMention() + " to the screenshot list!");
1783 }
1784 }
1785 break;
1786 case "remove":
1787 // Remove a user from the screenshot list.
1788 // If you want documentation about mention/ign parse structure, check out `dodge`.
1789 if (message.getMentionedMembers().size() > 0) {
1790 // Remove from screenshot list
1791 message.getMentionedMembers().forEach(m -> screen_remove(m.getUser().getIdLong()));
1792 sendMessage(textChannel, "Removed all mentioned users from the screenshot list!");
1793 } else {
1794 if (args.size() == 1) {
1795 sendMessage(textChannel, "No user specified! Usage: `bot_screen remove IGN...`");
1796 return;
1797 }
1798 for (int i = 1; i < args.size(); i++) {
1799 Member m = getUserByName(args.get(i), textChannel);
1800 if (m == null) {
1801 continue;
1802 }
1803 screen_remove(m.getUser().getIdLong());
1804 sendMessage(textChannel, "Removed " + m.getAsMention() + " from the screenshot list!");
1805 }
1806 }
1807 break;
1808 case "list":
1809 // Get the screenshot list and convert to User objects
1810 List<Member> list = new ArrayList<>();
1811 screenList.forEach(l -> {
1812 Member m = guild.getMemberById(l);
1813 if (m != null) {
1814 list.add(m);
1815 }
1816 });
1817 // Sort the list by name
1818 List<Member> sorted = sortByName(list, guild);
1819 sendMessages(sorted, (m -> ", " + m.getAsMention()), "Screenshot list", textChannel, true);
1820 sendMessage(textChannel, "Amount of people on the screenshot list: **"
1821 + sorted.size() + "**");
1822 break;
1823 default:
1824 // Send error message
1825 sendMessage(textChannel, error);
1826 }
1827 }
1828
1829 if(msg.startsWith(BOT_PREFIX + "balance") && clearance < 4) {
1830 List<String> args = getArgs("balance", msg);
1831 Raid raid = getRaid(raids, member);
1832 if (raid == null) {
1833 sendMessage(textChannel, "You need to be in a raid before running this command!");
1834 return;
1835 }
1836 // Make sure they're not in a run
1837 if (raid.inRun()) {
1838 sendMessage(textChannel, "This raid is still in a run! Run `bot_finish` first!");
1839 return;
1840 }
1841 // Get the size for each raid and start puzzling
1842 for (Member m : raid.getFightingVoiceChannel().getMembers()) {
1843 // The smallest raid is the raid with the least amount of users in it.
1844 Raid smallest = getSmallestRaid(raids);
1845 // If the smallest raid is this raid, we're done.
1846 if (raid.equals(smallest) || smallest == null) {
1847 break;
1848 }
1849 // Move the next user to the smallest raid.
1850 moveToRaid(m, smallest);
1851 }
1852 }
1853
1854
1855 if(msg.startsWith(BOT_PREFIX + "checkloc") && clearance < 1) {
1856 List<String> args = getArgs("checkloc", msg);
1857
1858 new Thread(() -> {
1859 sendMessage(textChannel, "Checking peoples' realmeyes to see if their last seen location is private.");
1860 // Get raid role
1861 Role raid = guild.getRoleById(ROLE_RAID);
1862 int count = 1;
1863 // Get all users with Raid role
1864 List<Member> raidMembers = guild.getMembersWithRoles(raid);
1865 Set<Member> publicLoc = new HashSet<>();
1866 Set<Member> error = new HashSet<>();
1867 for (int i = 0; i < raidMembers.size(); i++) {
1868 System.out.println("Progress: " + count + " / " + raidMembers.size());
1869 count++;
1870 final int index = i;
1871 // Get the IGN
1872 String ignString = raidMembers.get(index).getEffectiveName();
1873 // Filter IGN by alphabetical symbols
1874 String ign = ignString.toLowerCase().replaceAll("\\P{Alpha}", "");
1875 if (!ignString.equalsIgnoreCase(ign) || ign.length() > 10 || ign.length() == 0 || ignString.split(" ").length > 1) {
1876 // Add user to the error list. This happens to people with 2 IGNs in their name.
1877 System.out.println("User " + ignString + " has an invalid IGN!");
1878 error.add(raidMembers.get(i));
1879 continue;
1880 }
1881 // Check the requirements for this user
1882 Set<String> reqs = hasRequirements(ign);
1883 // Check if their Realmeye is public and location is public
1884 if (reqs != null && !reqs.contains("realmeye") && reqs.contains("location")) {
1885 System.out.println(ign + " has a public last seen location.");
1886 publicLoc.add(raidMembers.get(i));
1887 }
1888 // If their realmeye is private or an error occurred while getting the requirements, this happens
1889 if (reqs == null || reqs.contains("realmeye")) {
1890 System.out.println("An error occurred while fetching requirements for user " + ign + "!");
1891 System.out.println("reqs: " + reqs);
1892 error.add(raidMembers.get(i));
1893 }
1894 if (reqs != null && reqs.contains("api")) {
1895 System.out.println("Internal API error for user " + ign + "!");
1896 error.add(raidMembers.get(i));
1897 }
1898 }
1899 System.out.println("List of users in SBC with Raid role with public location: " + publicLoc);
1900 System.out.println("Users which had an error: " + error);
1901 Role RL = guild.getRoleById(RAID_LEADER);
1902 // Print out all the users who have their last seen location set to public on Realmeye
1903 String tempMessage = "**People with public realmeye location**: ";
1904 for (Member m : publicLoc) {
1905 if (tempMessage.length() + m.getAsMention().length() + 2 > 2000) {
1906 final String send = tempMessage;
1907 sendMessage(textChannel, send);
1908 tempMessage = "**People with public realmeye location (continued)**: ";
1909 }
1910 tempMessage = tempMessage.concat("\n\t" + m.getAsMention());
1911 }
1912 final String s = tempMessage;
1913 sendMessage(textChannel, s);
1914 // Print out all trhe users for which the bot couldn't check requirements
1915 tempMessage = "**People the bot couldn't check requirements for**: ";
1916 for (Member m : error) {
1917 // If the user has RL role, just skip em
1918 if (m.getRoles().contains(RL)) {
1919 System.out.println("User " + m.getEffectiveName() + " has RL role, skipping.");
1920 continue;
1921 }
1922 if (tempMessage.length() + m.getAsMention().length() + 2 > 2000) {
1923 final String send = tempMessage;
1924 sendMessage(textChannel, send);
1925 tempMessage = "**People the bot couldn't check requirements for (continued)**: ";
1926 }
1927 tempMessage = tempMessage.concat("\n\t" + m.getAsMention());
1928 }
1929 final String s2 = tempMessage;
1930 sendMessage(textChannel, s2);
1931 }).start();
1932 }
1933
1934
1935 if(msg.startsWith(BOT_PREFIX + "cancelfinish") && clearance < 4) {
1936 List<String> args = getArgs("cancelfinish", msg);
1937
1938 finishOngoing = false;
1939 sendMessage(textChannel, "Reset the AFK check ongoing flag, so you should be able to do `bot_finish` again.");
1940 }
1941
1942 if(msg.startsWith(BOT_PREFIX + "cancelsplit") && clearance < 4) {
1943 List<String> args = getArgs("cancelsplit", msg);
1944
1945 splitOngoing = false;
1946 sendMessage(textChannel, "Reset the split ongoing flag, so you should be able to do `bot_split` again.");
1947 }
1948
1949
1950 if(msg.startsWith(BOT_PREFIX + "leaderboard")) {
1951 List<String> args = getArgs("leaderboard", msg);
1952
1953 if(textChannel.equals(jda.getTextChannelById(BOT_STAFF))){
1954 Message m = textChannel.sendMessage("Please do not use this command in <#" + BOT_STAFF + ">!").complete();
1955 m.delete().queueAfter(3, TimeUnit.SECONDS);
1956 return;
1957 }
1958 if (args.size() == 0) {
1959 // Get the leaderboard stats
1960 Map<Long, Integer> leaderboard = getLeaderboardTotal();
1961 // Get the rank of the user who ran the command
1962 long rank = getTotalRank(member.getUser().getIdLong());
1963 // Get the three numbers to display on the leaderboard: <@ID>, Rank and Number of halls done.
1964 long[] userRank = new long[]{member.getUser().getIdLong(), rank, getTotalActivity(member.getUser().getIdLong())};
1965 // An error occurred
1966 if (leaderboard == null) {
1967 sendMessage(textChannel, "Failed to retrieve leaderboard data! Please try again later.");
1968 return;
1969 }
1970 // Create the leaderboard embed
1971 embed = createLeaderboardEmbed(leaderboard, userRank, false);
1972 // Send the leaderboard embed
1973 textChannel.sendMessage(embed).queue();
1974 return;
1975 } else if (args.get(0).equalsIgnoreCase("monthly")) {
1976 // Get the monthly leaderboard stats
1977 Map<Long, Integer> leaderboard = getLeaderboardMonthly();
1978 // Get the rank of the user who ran the command
1979 long rank = getMonthlyRank(member.getUser().getIdLong());
1980 // Get the three numbers to display on the leaderboard: <@ID>, Rank and Number of halls done.
1981 long[] userRank = new long[]{member.getUser().getIdLong(), rank, getActivity(member.getUser().getIdLong())};
1982 // An error occurred
1983 if (leaderboard == null) {
1984 sendMessage(textChannel, "Failed to retrieve leaderboard data! Please try again later.");
1985 return;
1986 }
1987 // Create the leaderboard embed
1988 embed = createLeaderboardEmbed(leaderboard, userRank, true);
1989 // Send the embed
1990 textChannel.sendMessage(embed).queue();
1991 return;
1992 }
1993 // Send error message
1994 sendMessage(textChannel, "Usage: `bot_leaderboard monthly?`\n" +
1995 "Example: `bot_leaderboard monthly`\n" +
1996 "Example: `bot_leaderboard`");
1997 }
1998
1999
2000 if(msg.startsWith(BOT_PREFIX + "get") && clearance < 3) {
2001 List<String> args = getArgs("get", msg);
2002
2003 // Check arguments
2004 if (args.size() == 0) {
2005 sendMessage(textChannel, "Usage: `bot_get IGN...`");
2006 return;
2007 }
2008 // First, split the args properly.
2009 List<String> actualArgs = new ArrayList<>();
2010 for (String s : args) {
2011 // Split on any non-alphabetical character, create a list of those.
2012 actualArgs.addAll(Arrays.asList(s.split("\\P{Alpha}")));
2013 }
2014 // Make sure the igns are each >1 character long
2015 actualArgs = actualArgs.stream().filter(s -> s.length() > 0).collect(Collectors.toList());
2016 for (String s : actualArgs) {
2017 // Get the user by IGN
2018 Member m = getMemberByNameExact(s, guild, false);
2019 if (m != null) {
2020 // User found! Mention them.
2021
2022 sendMessage(textChannel, "User found - " + s + "! " + m.getAsMention());
2023 }
2024 }
2025 sendMessage(textChannel, "Done checking! Checked " + actualArgs.size() + " names!");
2026 }
2027
2028
2029 if(msg.startsWith(BOT_PREFIX + "getguild") && clearance < 3) {
2030 List<String> args = getArgs("getguild", msg);
2031
2032 // Check arguments
2033 if (args.size() != 1) {
2034 sendMessage(textChannel, "This command requires 1 argument: Guild name!");
2035 }
2036 // Get the guild's members.
2037 Gson gson = new Gson();
2038 StringBuilder json = new StringBuilder();
2039 try {
2040 // Connect to the URL
2041 URL url = new URL("http://tiffit.net:8080/RealmInfo/api/guild?g=" + args.get(0) + "&f=d;");
2042 Scanner s = new Scanner(url.openStream());
2043 while (s.hasNext()) {
2044 // Add to the json
2045 json.append(s.next());
2046 }
2047 s.close();
2048 } catch (IOException e) {
2049 e.printStackTrace();
2050 return;
2051 }
2052 if (json.toString().equals("")) {
2053 return;
2054 }
2055 // Parse the json
2056 REGuild re = gson.fromJson(json.toString(), REGuild.class);
2057 // Get the list of names from the guild
2058 List<String> memberNames = re.members.stream().filter(m -> !m.name.equals("Private")).map(m -> m.name).collect(Collectors.toList());
2059 for (String s : memberNames) {
2060 // Check if the user is in the discord
2061 Member m = getMemberByNameExact(s, guild, false);
2062 if (m != null) {
2063 // Match found! Mention them.
2064 sendMessage(textChannel, "User found - " + s + "! " + m.getAsMention());
2065 }
2066 }
2067 sendMessage(textChannel, "Checked " + memberNames.size() + " names!");
2068 }
2069
2070
2071 if(msg.startsWith(BOT_PREFIX + "paluu") && clearance < 2) {
2072 List<String> args = getArgs("paluu", msg);
2073
2074 if (args.size() != 2) return;
2075 boolean b = Boolean.parseBoolean(args.get(1));
2076 TextChannel channel = message.getMentionedChannels().get(0);
2077 message.delete().queue();
2078 if (b) {
2079 String[] messages = new String[]{
2080 "~~̶:̶̶n̶̶n̶̶~~l̶É̶d̶̶~~s̶̶l̶~~ÇÇ~~ɟ̶~~:̶ ",
2081 "~~:~~̶̶f̶̶~~e̶~~̶e̶̶l̶̶~~s̶~~̶p̶̶~~a̶̶~~l̶̶u̶̶~~u̶̶:̶~~",
2082 "<:feelspaluu:372135342293254144>"
2083 };
2084 for (String s : messages) {
2085 sendMessage(channel, s);
2086 }
2087 } else {
2088 String send = "~~̶:̶̶n̶̶n̶̶~~l̶É̶d̶̶~~s̶̶l̶~~ÇÇ~~ɟ̶~~:̶ ~~:̶̶f̶̶~~e̶̶e̶̶l̶̶~~s̶̶p̶̶~~a̶̶~~l̶̶u̶̶~~u̶̶:̶ <:feelspaluu:372135342293254144>";
2089 sendMessage(textChannel, send);
2090 }
2091 }
2092
2093
2094
2095
2096 //If sent to #register-here and is bot_
2097 if(msg.equals(BOT_PREFIX + "verify") && textChannel.getId().equals(REGISTER_CHANNEL)){
2098 String vericode = getAndLogVerificationCode(member);
2099 sendPrivateMessage(member.getUser(), "Your Realmeye verification code is: **" + vericode + "**. \n" +
2100 "Put this code inside your Realmeye description, **wait 30 seconds** and then post your realmeye link in <#"
2101 + REGISTER_CHANNEL + ">.");
2102 message.delete().queue();
2103 }
2104
2105
2106
2107
2108
2109 if(textChannel.getId().equals(REGISTER_CHANNEL)) {
2110
2111
2112
2113 if(msg.startsWith("https://www.realmeye.com/settings-of/")) {
2114 sendPrivateMessage(author, "Remember to link to the main page of your realmeye"
2115 + " page and not the settings page!" + "\n" + "You cannot be verified unless you give it the correct link.");
2116 message.delete().queue();
2117 return;
2118 }
2119
2120 if(!(msg.startsWith("https://www.realmeye.com/player") || msg.startsWith(BOT_PREFIX)) && checkPerms(member) > 3) {
2121 message.delete().queue();
2122 return;
2123 }
2124
2125
2126 String parts[] = message.getContentRaw().split("player/");
2127 //if "player/" isn't present
2128 if(parts.length == 1) {
2129 return;
2130 }
2131 String ign = parts[1];
2132
2133 //---------------------------------------------------------------------------------------
2134 //For testing TODO remove this
2135 if(member.getUser().getId().equals(AUTHOR)) {
2136 sendPendingMessage(member, ign);
2137 message.delete().queue();
2138 return;
2139 }
2140 //---------------------------------------------------------------------------------------
2141
2142 //Get ign but filter out anything that isn't a letter
2143 ign = ign.replaceAll("[^a-zA-Z]", "");
2144 //If they haven't gotten a vericode yet
2145 if(!vericodes.containsKey(member.getUser().getId())) {
2146 sendPrivateMessage(member.getUser(), "You do not have a verification code yet! Please be sure to type bot_verify in <#"
2147 + REGISTER_CHANNEL + "> before posting your realmeye link.");
2148 message.delete().queue();
2149 return;
2150 }
2151
2152
2153
2154
2155
2156 Set<String> missingReqs = hasRequirements(ign);
2157
2158 //true if they have the correct code, false otherwise
2159 if(verifyRealmeye(member.getUser().getId(), ign) == false) {
2160 sendPrivateMessage(member.getUser(), "I couldn't find the code in your realmeye description!"
2161 + " Your code is: **" + vericodes.get(member.getUser().getId()) + ".** \n" +
2162 "Put this code inside your Realmeye description, **wait 30 seconds** and then send your realmeye in <#"
2163 + REGISTER_CHANNEL + ">.");
2164 message.delete().queue();
2165 return;
2166 }
2167
2168 if (missingReqs == null) {
2169 sendPrivateMessage(member.getUser(), "The bot failed to check if you meet" +
2170 " requirements! Please re-post your Realmeye link. If this message persists, contant <@" + AUTHOR + ">.");
2171 message.delete().queue();
2172 return;
2173 }
2174 if (missingReqs.equals(Collections.singleton("api"))) {
2175 sendMessage(textChannel, "Interal API error occurred for user " + ign + "!");
2176 message.delete().queue();
2177 return;
2178 }
2179 // If the user does meet requirements
2180
2181
2182 if (missingReqs.size() == 0) {
2183 if (isInactive(member.getUser().getIdLong())) {
2184
2185 // Remove from inactivity database
2186 removeInactiveUser(member.getUser().getIdLong());
2187 sendMessage(jda.getTextChannelById("342842121239396352"), "Verified " + member.getAsMention() + " (Previously inactive)");
2188 register(member, ign);
2189 message.delete().queue();
2190 return;
2191 } else {
2192 if(pendingNames.containsValue(ign)){
2193 sendPrivateMessage(member.getUser(), "You have already registered under that name! Please wait for a Moderator to "
2194 + "review your account. This can "
2195 + "take up to a week. **Please do not PM mods about this.**");
2196 return;
2197 }
2198 sendPrivateMessage(member.getUser(), "Your Realmeye is verified! Next up, " +
2199 "a Moderator will quickly review your account (to see if you're a known cheater). " +
2200 "After this, you'll be given Raid role. This can take up to a week. **Please do not PM Mods about this.**");
2201
2202 sendPendingMessage(member, ign);
2203
2204 message.delete().queue();
2205 return;
2206 }
2207 }
2208
2209
2210 //If they got here, they don't meet reqs.
2211
2212 String toSend = "**" + ign + "**, " +
2213 "you don't currently meet the requirements of Spooky Boi Central. " +
2214 "The requirements are: \n" +
2215 (missingReqs.contains("realmeye") ? "**" : "") +
2216 "- Public Realmeye page" +
2217 (missingReqs.contains("realmeye") ? "**" : "") + "\n" +
2218 (missingReqs.contains("charsHidden") ? "**" : "") +
2219 "- Characters are visible" +
2220 (missingReqs.contains("charsHidden") ? "**" : "") + "\n" +
2221 (missingReqs.contains("char") ? "**" : "") +
2222 "- 3 8/8s" +
2223 (missingReqs.contains("char") ? "**" : "") + "\n" +
2224 (missingReqs.contains("fame") ? "**" : "") +
2225 "- " + REQ_BASE_FAME + " base fame (alive, across all visible characters on Realmeye)" +
2226 (missingReqs.contains("fame") ? "**" : "") + "\n" +
2227 (missingReqs.contains("namehistory") ? "**" : "") +
2228 "- Public name history " +
2229 (missingReqs.contains("namehistory") ? "**" : "") + "\n" +
2230 (missingReqs.contains("location") ? "**" : "") +
2231 "- Last seen location should be private" +
2232 (missingReqs.contains("location") ? "**" : "") + "\n" +
2233 "- Public Guild History\n\n" +
2234 "The requirements you're missing are indicated in bold text. " +
2235 "If you have 3 8/8+ chars, but the line is bolded, make sure your characters aren't hidden.\n" +
2236 "Please make sure you meet the requirements before sending your realmeye link again.\n\n" +
2237 "**NOTE**: This message was automatically generated. Please don't respond to this message.";
2238
2239 sendPrivateMessage(member.getUser(), toSend);
2240 message.delete().queue();
2241 }
2242
2243
2244 if(msg.startsWith(BOT_PREFIX + "register") && clearance < 2) {
2245 List<String> args = getArgs("register", msg);
2246 String ign = args.get(1);
2247 if(message.getMentionedMembers().size() != 1) {
2248 sendMessage(textChannel, "Please mention exactly one user!");
2249 return;
2250 }
2251 try {
2252 register(message.getMentionedMembers().get(0), ign);
2253 } catch (Exception e) {
2254 sendMessage(textChannel, "Error while registering; this was likely because you did not structure your arguments properly."
2255 + " Please send it in the form \n`"
2256 + BOT_PREFIX + "register @user IGN`\n\nand only mention one user per command.\n\nIf"
2257 + " your command was fine, make sure the user has a one word nickname on the server - set their nickname to"
2258 + " \"temp\" if necessary. It will be set correctly when they are successfully registered via the command.");
2259 return;
2260 }
2261 check(message);
2262 }
2263
2264
2265 //bot_blacklist add/remove ign
2266 if(msg.startsWith(BOT_PREFIX + "blacklist") && clearance < 3) {
2267 String error = "Usage: " + BOT_PREFIX + "blacklist add/remove/get ign";
2268 List<String> args = getArgs("blacklist", msg);
2269 String arg = args.get(0);
2270 args.remove(0);
2271 String ignTest = null;
2272 if(!arg.equals("get")) {
2273 try {
2274 ignTest = args.get(0 );
2275 } catch(Exception e) {
2276 sendMessage(textChannel, error);
2277 }
2278 }
2279 switch(arg) {
2280 case "add":
2281 // if(clearance > 1) {
2282 // return;
2283 // }
2284 for(String ign : args) {
2285 if(userBlacklisted(ign)) {
2286 sendMessage(textChannel, "That user is already in the blacklist!");
2287 break;
2288 }
2289 blacklist_add(ign);
2290 }
2291 check(message);
2292 break;
2293 case "remove":
2294 if(clearance > 1) {
2295 return;
2296 }
2297 if(!userBlacklisted(ignTest)) {
2298 sendMessage(textChannel, "That user is not in the blacklist!");
2299 break;
2300 }
2301 blacklist_remove(ignTest);
2302 check(message);
2303 break;
2304 case "get":
2305 String blacklist = listToString(getUserBlacklist());
2306 if(blacklist.equals("")) {
2307 sendMessage(textChannel, "The blacklist is currently empty!");
2308 break;
2309 }
2310 sendMessage(textChannel, listToString(getUserBlacklist()));
2311 break;
2312 }
2313
2314
2315 }
2316
2317
2318
2319 if(msg.startsWith(BOT_PREFIX + "isBlacklisted") && clearance < 3) {
2320 List<String> args = getArgs("blacklist", msg);
2321 String ign = args.get(0);
2322 if(userBlacklisted(ign)) {
2323 check(message);
2324 } else {
2325 ex(message);
2326 }
2327
2328 }
2329
2330 if(msg.startsWith(BOT_PREFIX + "name") && clearance < 2) {
2331
2332 List<String> args = getArgs("name", msg);
2333
2334 String name = args.get(1);
2335
2336 if(message.getMentionedMembers().size() != 1) {
2337 sendMessage(textChannel, "Please mention exactly one user!");
2338 return;
2339 }
2340
2341 try {
2342 verifiedNames.put(message.getMentionedMembers().get(0).getUser().getId(), name);
2343 } catch (Exception e) {
2344 sendMessage(textChannel, "Error while setting the user's name; this was likely because you did not structure your arguments properly."
2345 + " Please send it in the form \n`"
2346 + BOT_PREFIX + "name @user IGN`\n\nand only mention one user per command.");
2347 return;
2348 }
2349
2350 check(message);
2351 }
2352
2353 if(msg.startsWith(BOT_PREFIX + "get") && clearance < 2) {
2354 String switchS = getArgs("get", msg).get(0);
2355 Raid r = getRaidFinal(raids, member, textChannel);
2356
2357 switch (switchS) {
2358 case "id":
2359 sendMessage(textChannel, "Raid id:" + r.getId());
2360 break;
2361 case "queueBlocked":
2362 if(r.isQueue_blocked()) {
2363 sendMessage(textChannel,"Raid #" + r.getId() + " is queue blocked");
2364 } else {
2365 sendMessage(textChannel,"Raid #" + r.getId() + " isn't queue blocked");
2366 }
2367 break;
2368 case "split":
2369 if(r.isSplit()) {
2370 sendMessage(textChannel, "Raid #" + r.getId() + " is currently split.");
2371 } else {
2372 sendMessage(textChannel, "Raid #" + r.getId() + " is not split.");
2373 }
2374 case "announcementMessage":
2375 sendMessage(textChannel, "`" + r.getAnnounceMessage() + "`");
2376 break;
2377
2378
2379 default:
2380 sendMessage(textChannel, "Usage: ");
2381// TODO;
2382
2383 }
2384 }
2385
2386
2387
2388
2389
2390
2391 }
2392
2393 private void ex(Message message) {
2394 message.addReaction(message.getGuild().getEmoteById(GUILD_X)).queue(); //add the checkmark
2395 }
2396
2397
2398 public static void check(Message message) {
2399 //:check:
2400 message.addReaction(message.getGuild().getEmoteById(GUILD_CHECK)).queue(); //add the checkmark
2401 }
2402
2403
2404
2405
2406
2407
2408
2409 /**
2410 * Returns the raid the member is currently in, or the default raid if they are not in any raid voice channel.
2411 * @param raids2
2412 * @param member
2413 * @param guild
2414 * @param textChannel
2415 * @return The raid the member is a part of, or the default raid if they are not part of any raid
2416 */
2417 private Raid getRaidFinal(List<Raid> raids, Member m, TextChannel textChannel) {
2418
2419 if (m == null) {
2420 System.out.println("Member is null");
2421 return null;
2422 }
2423
2424 // Check which voice channel the user is in
2425
2426 VoiceChannel vc = m.getVoiceState().getChannel();
2427
2428 // Check which Raid the voice channel is part of, return that Raid
2429 if(vc != null) {
2430 for (Raid raid : raids) {
2431 if (raid.isVoiceChat(vc)) {
2432 return raid;
2433 }
2434 }
2435 }
2436 // If there's no raid, return the default raid and say so.
2437 sendMessage(textChannel, "Assuming Raid #1.");
2438 return raids.get(0);
2439 }
2440
2441
2442
2443
2444
2445
2446
2447 private String getMentions(List<Member> passed) {
2448 String ret = "";
2449 for(Member m : passed) {
2450 ret += m.getAsMention() +", ";
2451 }
2452 if(ret.equals("")) {
2453 return ret;
2454 }
2455 return ret.substring(0, ret.length() - 2);
2456 }
2457
2458
2459
2460
2461
2462
2463
2464 private static void register(Member member, String ign) {
2465 Guild guild = member.getGuild();
2466 User user = member.getUser();
2467 sendPrivateMessage(user, "You've been successfully registered!" +
2468 " You can now join Raids by joining the Queue Voice channel, listed under the Raid category." +
2469 " Keep an eye on <#342864077367803905> to see when we're about to start raids.\n\n" +
2470 "Also remember to read <#342841231686107136> if you haven't already. If you don't" +
2471 " read it carefully, you may find yourself getting banned for doing something you didn't know was not allowed.");
2472 Date date = new Date();
2473 DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
2474 setActivityOnRoleChange(user.getIdLong(), format.format(date));
2475
2476 GuildController gc = guild.getController();
2477 gc.addSingleRoleToMember(member, guild.getRoleById(ROLE_RAID)).complete();
2478 gc.setNickname(member, ign).queue();
2479 verifiedNames.put(user.getId(), ign);
2480
2481 if (userExists(user.getIdLong())) {
2482 return;
2483 }
2484 RENameHistory nh = getNameHistory(ign);
2485 for(MessageEmbed emb : makeNameHistoryEmbed(nh, member, ign)) {
2486 member.getJDA().getTextChannelById(BOT_NAMEHISTORY).sendMessage(emb).queue();
2487 }
2488
2489
2490
2491 add_user(user.getIdLong());
2492 }
2493
2494 /**
2495 * Formats a String as given by tiffit's api and turns it into a more readable String
2496 *
2497 * @param input The input String (format: yyyy-MM-ddTHH:mm:ssZ)
2498 * @return The output String (format: yyyy-MM-dd)
2499 */
2500 private static String getDateString(String input) {
2501 if (input.length() == 0) {
2502 return input;
2503 }
2504 try {
2505 DateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
2506 Date date = parser.parse(input);
2507 DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
2508 return format.format(date);
2509 } catch (ParseException e) {
2510 return "";
2511 }
2512 }
2513
2514
2515 /**
2516 * Creates a name history embed.
2517 *
2518 * @param nameHistory The Name History of the user
2519 * @param user The user
2520 * @param ign The IGN the user has currently
2521 * @return A List of EmbedObjects containing a graphical representation of the Name history
2522 * @author Botkiller
2523 */
2524 private static List<MessageEmbed> makeNameHistoryEmbed(RENameHistory nameHistory, Member member, String ign) {
2525 System.out.println("Entering makeNameHistoryEmbed for user " + ign);
2526 List<MessageEmbed> embeds = new ArrayList<>();
2527 EmbedBuilder builder = new EmbedBuilder().setTitle("Name history of user " + ign).setDescription("Discord: " + member.getAsMention());
2528 // If the user has never changed their name, this can happen
2529 if (nameHistory.names.size() == 0) {
2530 builder.addField(ign, "To: \nFrom: ", false);
2531 } else {
2532 int count = 0;
2533 for (REName reName : nameHistory.names) {
2534 // If the IGN is an unnamed account, skip it
2535 if (unnamed.contains(reName.name)) continue;
2536 count++;
2537 // If the embed has 20 fields, create a new one (max limit is 25 per embed, we do 20 to be sure)
2538 if (count == 20) {
2539 count = 0;
2540 embeds.add(builder.build());
2541 builder = new EmbedBuilder().setTitle("Name history of user " + ign + " (continued)")
2542 .setDescription("Discord: " + member.getAsMention());
2543 }
2544 // Add next IGN to the embed
2545 System.out.println("\tChecking reName " + reName.name + "\n\tfrom: " + reName.from + "\n\tto: " + reName.to);
2546 builder.addField(reName.name, "To: " + getDateString(reName.to) + "\nFrom: " + getDateString(reName.from), false);
2547 }
2548 }
2549 embeds.add(builder.build());
2550 return embeds;
2551 }
2552
2553 private static RENameHistory getNameHistory(String ign) {
2554 // Get the name history json of this user
2555 Gson gson = new Gson();
2556 StringBuilder json = new StringBuilder();
2557 try {
2558 URL url = new URL("http://tiffit.net:8080/RealmInfo/api/name-history-of-player?u=" + ign);
2559 Scanner s = new Scanner(url.openStream());
2560 while (s.hasNext()) {
2561 json.append(s.next());
2562 }
2563 s.close();
2564 } catch (IOException e) {
2565 e.printStackTrace();
2566 return null;
2567 }
2568 if (json.toString().equals("")) {
2569 return null;
2570 }
2571 // Parse the json
2572 RENameHistory nameHistory = gson.fromJson(json.toString(), RENameHistory.class);
2573 return nameHistory;
2574 }
2575
2576
2577
2578 private static void sendPendingMessage(Member member, String ign) {
2579
2580 MessageEmbed embed;
2581 EmbedBuilder builder = new EmbedBuilder();
2582
2583 Set<String> missingReqs = new HashSet<>();
2584 Gson gson = new Gson();
2585 StringBuilder json = new StringBuilder();
2586 StringBuilder json2 = new StringBuilder();
2587 // Get the json from tiffit's API
2588 try {
2589 URL mainPage = new URL("http://tiffit.net:8080/RealmInfo/api/user?u=" + ign + "&f=d;");
2590 URL nameHistory = new URL("http://tiffit.net:8080/RealmInfo/api/name-history-of-player?u=" + ign);
2591 Scanner s = new Scanner(mainPage.openStream());
2592 Scanner s2 = new Scanner(nameHistory.openStream());
2593 while (s.hasNext()) {
2594 json.append(s.next());
2595 }
2596 s.close();
2597
2598 while (s2.hasNext()) {
2599 json2.append(s2.next());
2600 }
2601 s2.close();
2602 } catch (IOException e) {
2603 e.printStackTrace();
2604 return;
2605 }
2606 if (json.toString().equals("") || json2.toString().equals("")) {
2607 return;
2608 }
2609
2610 Realmeye re = gson.fromJson(json.toString(), Realmeye.class);
2611 RENameHistory nameHistory = gson.fromJson(json2.toString(), RENameHistory.class);
2612 HashMap<String, String> warnings = new HashMap<String, String>();
2613 if(userBlacklisted(ign)) {
2614 warnings.put("Blacklisted", ign);
2615 }
2616
2617 for(REName name : nameHistory.names) {
2618 if(userBlacklisted(name.name)) {
2619 System.out.println("User is on blacklist; (" + name.name + ")");
2620 warnings.put("Blacklisted", name.name);
2621 }
2622 }
2623
2624 builder.setTitle(re.name + "(" + member.getAsMention() + ")");
2625 builder.setDescription("**Characters: **" + re.characterCount + "**\nFame: **" + re.fame + "**\nStars: **" + re.rank +
2626 "**\nAccount Fame: **" + re.account_fame
2627 + "**\nGuild: **" + re.guild + "**\nRank: **" + re.guild_rank + "**\nCreated: **" + re.created);
2628
2629 String fieldDesc = "";
2630 for(String warning : warnings.keySet()) {
2631 System.out.println("Key: " + warning);
2632 switch (warning) {
2633 case "Blacklisted":
2634 System.out.println("Case: Blacklisted");
2635 fieldDesc += ign + " is on the user blacklist!\n";
2636 break;
2637 case "guildBlacklisted":
2638 System.out.println("Case: guildBlacklisted");
2639 // TODO add guild
2640 case "newUser":
2641 System.out.println("Case: newUser");
2642 fieldDesc += "This discord account is a new user (TODO hours old).\n";
2643 break;
2644 default:
2645 System.out.println("Case: Default");
2646 //Should never happen, but just ignore
2647 System.out.println("Encountered default case - while not program threatening (probably), please tell tybug about this");
2648 }
2649 }
2650
2651 if(fieldDesc != "") {
2652 member.getJDA().getTextChannelById(PENDING_CHANNEL).sendMessage(builder.build()).complete();
2653
2654
2655 builder.clear();
2656 builder.addField("Warnings", fieldDesc, false).setColor(Color.RED);
2657
2658 Message m = member.getJDA().getTextChannelById(PENDING_CHANNEL).sendMessage(builder.build()).complete();
2659 m.addReaction(m.getGuild().getEmoteById(GUILD_CHECK)).queue(); // Check
2660 m.addReaction(m.getGuild().getEmoteById(GUILD_X)).queue(); // X
2661
2662 pendingNames.put(m.getId(), ign);
2663 pendingReactions.put(m.getId(), member.getUser().getId());
2664 return;
2665 }
2666
2667 builder.addField("Realmeye Link", "https://realmeye.com/player/" + ign ,false);
2668 Message m = member.getJDA().getTextChannelById(PENDING_CHANNEL).sendMessage(builder.build()).complete();
2669 m.addReaction(m.getGuild().getEmoteById(GUILD_CHECK)).queue(); // Check
2670 m.addReaction(m.getGuild().getEmoteById(GUILD_X)).queue(); // X
2671
2672 pendingNames.put(m.getId(), ign);
2673 pendingReactions.put(m.getId(), member.getUser().getId());
2674 }
2675
2676
2677
2678 /**
2679 * Checks a user's Realmeye page to confirm the code they used is correct. Also reports to the user if it's not correct.
2680 *
2681 * @param id The user to get the vericode of
2682 * @param ign The name of the user to check the realmeye page of
2683 * @return true if they used the right code, false otherwise.
2684 */
2685 private static boolean verifyRealmeye(String id, String ign) {
2686
2687 // Check if the Realmeye description contains the verification code.
2688 String description;
2689 try {
2690 Document doc = Jsoup.connect("https://www.realmeye.com/player/" + ign).validateTLSCertificates(false).get();
2691 description = doc.getElementsByClass("well description").first().html(); // String
2692 } catch (IOException e) {
2693 System.err.println("IOException in verifyRealmeye!");
2694 e.printStackTrace();
2695 return false;
2696 }
2697 // If the description has the code
2698 if (description.contains(vericodes.get(id))) {
2699 return true;
2700 }
2701 // The description doesn't have the code.
2702 return false;
2703 }
2704
2705
2706 /**
2707 * Creates a randomised String for the user to put on their Realmeye description and saves this code to the database.
2708 *
2709 * @param member The user to get a verification code for
2710 * @param ign The IGN of the user
2711 */
2712 private static void sendVerificationCode(Member member, String ign) {
2713 // Get the verification data for this user
2714 String[] res = getVerificationData(member.getUser().getIdLong());
2715 if (res != null) {
2716 sendPrivateMessage(member.getUser(), "You already have a verification code for the folllowing IGN: " +
2717 "**" + res[0] + "**! Your Realmeye verification code is: **" + res[1] + "**\n" +
2718 "Put this code inside your Realmeye description, **wait 30 seconds** and then type `bot_verify` in <#" +
2719 REGISTER_CHANNEL + ">.\n" +
2720 "If this is not your IGN, do `bot_cancel` in <#" + REGISTER_CHANNEL + "> and re-post your Realmeye link.");
2721 } else {
2722 Random generator = new Random();
2723 // Get a random number between 1000 and 9999
2724 String random = "SBC" + (1000 + generator.nextInt(9000));
2725 // Add a verification entry for the given user
2726 addVerificationEntry(member.getUser().getIdLong(), ign, random);
2727 // Send the verification code to the user
2728 sendPrivateMessage(member.getUser(), "Your Realmeye verification code is: **" + random + "**. \n" +
2729 "Put this code inside your Realmeye description, **wait 30 seconds** and then type `bot_verify` in <#"
2730 + REGISTER_CHANNEL + ">.\n\n" +
2731 "The IGN to verify is **" + ign + "**. If this is not your IGN, do `bot_cancel` in <#" + REGISTER_CHANNEL
2732 + "> and re-post your Realmeye link.\n" +
2733 "Note that if you don't verify your Realmeye within a day, your message will be deleted and you will " +
2734 "have to restart the verification process.");
2735 }
2736 }
2737
2738
2739 public static String getAndLogVerificationCode(Member member) {
2740 Random generator = new Random();
2741 // Get a random number between 1000 and 9999
2742 String random = "SBC" + (1000 + generator.nextInt(9000));
2743 vericodes.put(member.getUser().getId(), random);
2744 return random;
2745 }
2746
2747
2748
2749 private String listToString(List<Member> members) {
2750 String ret = "";
2751 for(Member m : members) {
2752 ret += m.getAsMention() + ", ";
2753 }
2754 ret = ret.substring(0, ret.length() - 2);
2755 return ret;
2756 }
2757
2758 private String listToString(ArrayList<String> arr) {
2759 String ret = "";
2760 for(String s : arr) {
2761 ret += s + ", ";
2762 }
2763 if(ret.equals("")) {
2764 return ret;
2765 }
2766 ret = ret.substring(0, ret.length() - 2);
2767 return ret;
2768 }
2769
2770
2771
2772 private List<String> getArgs(String command, String msg) {
2773 String[] argsTemp = msg.split(" ");
2774 List<String> args = new ArrayList<>();
2775
2776 for(String s : argsTemp) {
2777 if(!s.equals(BOT_PREFIX + command)) {
2778 args.add(s);
2779 }
2780 }
2781
2782 return args;
2783 }
2784
2785
2786
2787 private List<Member> usersToMembers(List<User> users, Guild guild) {
2788 List<Member> ret = new ArrayList<>();
2789 for(User u : users) {
2790 ret.add(guild.getMemberById(u.getId()));
2791 }
2792 return ret;
2793 }
2794
2795
2796
2797 /**
2798 * Checks if there's someone in the queue who should be moved to a different voice channel.
2799 * @author Botkiller
2800 */
2801 private static void checkQueue() {
2802 synchronized (queueLock) {
2803
2804 // if there's no raid to move people to, we're done.
2805 if (raids.size() == 0) return;
2806
2807 // If there's more people in the Queue VC than the queue, add the extras. This happens when the bot is restarted.
2808
2809 VoiceChannel queueChannel = raids.get(0).getFightingVoiceChannel().getGuild().getVoiceChannelById(VC_QUEUE);
2810
2811 List<Member> members = queueChannel.getMembers();
2812 for (Member m : queue) {
2813 if(debug) System.out.println("Vc didn't contain a member in the queue, removing " + m.getEffectiveName() +"from queue");
2814 if (!members.contains(m)) queue.remove(m);
2815 }
2816 for (Member m : members) {
2817 if(debug) System.out.println("Queue didn't contain a member in the vc, adding " + m.getEffectiveName() + "to queue");
2818 if (!queue.contains(m)) queue.add(m);
2819 }
2820
2821 // -----NOTE: The next algorithm may seem like wizardry at first. Read through it carefully!-----
2822
2823
2824 // If the queue is empty, we're done.
2825 if (queue.size() == 0) {
2826 if(debug) System.out.println("Queue is empty, returning");
2827 return;
2828 }
2829
2830 // Get size of raids
2831 Map<Raid, Integer> raidToSize = new HashMap<>();
2832 for (Raid r : raids) {
2833 if(debug) System.out.println("Mapping raid #" + r.getId() + " to size " + r.getSize());
2834 raidToSize.put(r, r.getSize());
2835 }
2836
2837 // Get the queue in reversed order
2838 List<Member> queueList = new ArrayList<>(queue);
2839 Collections.reverse(queueList);
2840 if(debug) System.out.println("Reversed queue order");
2841 // From the back of the queue, start assigning people to raids until the raids are equally sized or the queue is empty.
2842 Raid smallest = getSmallestRaid(raids);
2843 if(smallest != null) {
2844 if(debug) System.out.println("Got smallest raid; it is #" + smallest.getId());
2845 }
2846 int count = 0;
2847 while (queueList.size() > 0 && smallest != null) {
2848 count++;
2849 if(debug) System.out.println("Starting iteration: " + count);
2850 // Remove the last user in the queue
2851 queueList.remove(0);
2852 // Assign said user to the smallest raid
2853 raidToSize.replace(smallest, raidToSize.get(smallest) + 1);
2854 // Check if the map is empty to avoid a NoSuchElementException. Should never happen, but we can never be too sure.
2855 if (raidToSize.isEmpty()) {
2856 System.err.println("Failed to check Queue: raidToSize is empty! No raids are initialized.");
2857 return;
2858 }
2859 // Get smallest raid
2860 // We can safely disregard the "Optional .get() without .isPresent() check":
2861 //we check isPresent() by checking raidToSize.isEmpty() above.
2862 Raid r = raidToSize.entrySet().stream().min(Comparator.comparing(Map.Entry<Raid, Integer>::getValue)).get().getKey();
2863 // Check if there's no other smallest raids
2864 if (raidToSize.values().stream().filter(i -> i.equals(raidToSize.get(r))).count() > 1) {
2865 // There are other raids with the same amount of people in it
2866 if(debug) System.out.println("There is no longer a smallest raid, setting smallest to null and breaking out of the while loop");
2867 smallest = null;
2868 } else {
2869 smallest = r;
2870 }
2871 }
2872
2873 // If everyone was assigned, the raids are too unequal.
2874 // Move the first person in the queue to the smallest raid, if it's unlocked.
2875 if (queueList.size() == 0) {
2876 if(debug) System.out.println("QueueList is empty");
2877 // Move the first person in the queue to the smallest raid, if it's unlocked. Otherwise, wait for now.
2878 smallest = getSmallestRaid(raids);
2879 if (smallest == null) {
2880 if(debug) System.out.println("Smallest is null");
2881 return;
2882 }
2883 if (smallest.isQueue_blocked()) {
2884 if(debug) System.out.println("Smallest is queue blocked");
2885 return;
2886 }
2887 if (smallest.isFull()) {
2888 if(debug) System.out.println("Smallest is full");
2889 return;
2890 }
2891 moveToRaid(queue.remove(), smallest);
2892 } else {
2893 // Move the first person in the queue to any non-full, non-locked raid.
2894 for (Raid r : raids) {
2895 if(debug) System.out.println("Checking raid #" + r.getId());
2896 if (r.isQueue_blocked()) {
2897 if(debug) System.out.println("Raid is queue blocked, moving on to the next one");
2898 continue;
2899 }
2900 if (r.isFull()) {
2901 if(debug) System.out.println("Raid is full, moving on to the next one");
2902 continue;
2903 }
2904 Member m = queue.remove();
2905 moveToRaid(m, r);
2906 if(debug) System.out.println("Moved member to raid, breaking out of for loop");
2907 break;
2908 }
2909 }
2910
2911
2912
2913 }
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923 }
2924
2925
2926
2927
2928 /**
2929 * Moves the users that are not in the users List, but are connected to the oldChannel.
2930 *
2931 * @param oldChannel The channel which users are in
2932 * @param newChannel The channel which AFK users should be moved to
2933 * @param users The users which aren't AFK.
2934 * @author Botkiller
2935 */
2936 private static void moveAFKUsers(VoiceChannel oldChannel, VoiceChannel newChannel, List<User> users) {
2937 Guild guild = oldChannel.getGuild();
2938 GuildController gc = guild.getController();
2939 List<Member> toMove1 = oldChannel.getMembers();
2940 List<Member> toMove = new ArrayList<>();
2941 List<Member> toMoveToRemove = new ArrayList<>();
2942
2943
2944
2945 for(Member m : toMove1) {
2946 toMove.add(m);
2947 }
2948
2949 List<Member> toRemove = new ArrayList<>();
2950
2951 for(User u : users) {
2952 toRemove.add(guild.getMember(u));
2953 }
2954 for(Member m : toMove) {
2955 if(toRemove.contains(m)) {
2956 toMoveToRemove.add(m);
2957 }
2958 }
2959
2960 toMove.removeAll(toMoveToRemove);
2961
2962 for (Member m : toMove) {
2963 try {
2964 gc.moveVoiceMember(m, newChannel).queue();
2965 } catch (Exception e) {
2966 System.out.println("Exception caught while checking the queue, likely because " + m.getEffectiveName()
2967 + " left the voice channel before I could move them!");
2968 }
2969 }
2970 String sendMessage;
2971 if(oldChannel.equals(guild.getVoiceChannelById(VC_QUEUE))) {
2972 sendMessage = "Raid --> AFK: [";
2973 } else {
2974 sendMessage = "Queue --> AFK: [";
2975 }
2976
2977 boolean flag = false;
2978 for(Member m : sortByName(toMove, oldChannel.getGuild())) {
2979 sendMessage += m.getAsMention() +", ";
2980 flag = true;
2981 }
2982 if(flag) {
2983 sendMessage = sendMessage.substring(0, sendMessage.length() - 2);
2984 }
2985 sendMessage += "]";
2986 sendMessage(guild.getTextChannelById(BOT_STAFF), sendMessage);
2987 }
2988
2989
2990
2991
2992
2993 /**
2994 * Sorts a given list by the display name for the given guild.
2995 *
2996 * @param list The list of users which should be sorted
2997 * @param guild The guild which the users are in.
2998 * @return A List of Members, sorted by their display name for the given guild.
2999 */
3000 private static List<Member> sortByName(List<Member> list, Guild guild) {
3001 if (list == null) return null;
3002 // Copy the list first.
3003 List<Member> clone = new ArrayList<>(list.size());
3004 clone.addAll(list);
3005 clone.sort((o1, o2) -> {
3006 // get the display names
3007 String o1_name = o1.getEffectiveName();
3008 String o2_name = o2.getEffectiveName();
3009 // Replace each non-alphabetical character with "" and then compare the names.
3010 return o1_name.toLowerCase().replaceAll("\\P{Alpha}", "").compareTo(o2_name.toLowerCase().replaceAll("\\P{Alpha}", ""));
3011 });
3012 return clone;
3013 }
3014
3015
3016
3017 /**
3018 * Gets the raid with the least amount of users in it.
3019 *
3020 * @param raids A list of raids
3021 * @return The Raid with the least amount of users connected to it.
3022 * @author Botkiller
3023 */
3024 private static Raid getSmallestRaid(List<Raid> raids) {
3025 Raid smallest = null;
3026 int min = Integer.MAX_VALUE;
3027 int count = 0;
3028 for (Raid r : raids) {
3029 // If this raid is smaller than the smallest raid we found, this is the new smallest raid.
3030 if (r.getSize() < min) {
3031 smallest = r;
3032 min = r.getSize();
3033 count = 1;
3034 } else if (r.getSize() == min) {
3035 // If this raid is tied for the smallest raid, increment the count variable.
3036 count++;
3037 }
3038 }
3039 // If all raids are equally small, there's no 'smallest' raid, so return null.
3040 // Otherwise, return the first raid found which is smallest.
3041 return count == raids.size() ? null : smallest;
3042 }
3043
3044 /**
3045 * Moves the specified user to the fighting channel in a specified raid.
3046 *
3047 * @param u The user to be moved
3048 * @param r The raid to move to
3049 * @author Botkiller
3050 */
3051 private static void moveToRaid(Member m, Raid r) {
3052 try {
3053 m.getGuild().getController().moveVoiceMember(m, r.getFightingVoiceChannel()).queue();
3054 } catch (Exception e) {
3055 System.out.println("Exception caught while checking the queue, likely because " + m.getEffectiveName()
3056 + " left the voice channel before I could move them!");
3057 }
3058
3059 }
3060
3061
3062
3063 /**
3064 * Converts an User list into a String list containing the display name for the given guild.
3065 *
3066 * @param users The users which should be in the resulting list
3067 * @param guild The guild which the names should be used from
3068 * @return A List containing the display name for guild for each user in users.
3069 */
3070 private static List<String> getIGNs(List<Member> members, Guild guild) {
3071 if (members == null) {
3072 return null;
3073 }
3074 List<String> list = new ArrayList<String>();
3075 // Get the display name for the given users
3076 for(Member m : members) {
3077 list.add(m.getEffectiveName());
3078 }
3079 return list;
3080 }
3081
3082
3083
3084 /**
3085 * Logs a command with the current date and time.
3086 *
3087 * @param author The person who ran the command
3088 * @param command The message which was sent
3089 */
3090 private static void log(Member m, String command) {
3091 DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
3092 Date date = new Date();
3093
3094 String log = df.format(date) + " - user " + m.getUser().getName() + " ("
3095 + m.getEffectiveName() +") executed a command: " + command;
3096
3097 sendMessage(m.getJDA().getTextChannelById("342842121239396352"), log);
3098 }
3099
3100
3101 /**
3102 * Sorts a map based on their value in descending order.
3103 *
3104 * @param map The map to be sorted
3105 * @param <K> The keys
3106 * @param <V> The entries
3107 * @return The Map object, sorted by the value in descending order.
3108 * @author Botkiller
3109 */
3110 private static <K, V extends Comparable<? super V>> Map<K, V> sortByValueDesc(Map<K, V> map) {
3111 return map.entrySet() // Get the entries
3112 .stream() // Convert into stream
3113 .sorted(Map.Entry.comparingByValue(Collections.reverseOrder())) // Sort them by their value in reverse order (highest first)
3114 .collect(Collectors.toMap(
3115 Map.Entry::getKey,
3116 Map.Entry::getValue,
3117 (e1, e2) -> e1,
3118 LinkedHashMap::new
3119 )); // Convert to map again, keeping the order.
3120 }
3121
3122
3123 /**
3124 * Checks which raid a given user is in, based on which voice channel they're in.
3125 *
3126 * @param raids The list of raids
3127 * @param user The user to get the Raid for
3128 * @return The Raid the user is in, or null if the user isn't connected to a Raid voice channel.
3129 * @author Botkiller
3130 */
3131 private static Raid getRaid(List<Raid> raids, Member m) {
3132 if (m == null) {
3133 System.out.println("Member is null");
3134 return null;
3135 }
3136
3137
3138 // Check which voice channel the user is in
3139 VoiceState vs = m.getVoiceState();
3140 if (vs == null) {
3141 System.out.println("Voice State is null");
3142 return null;
3143 }
3144 VoiceChannel vc = m.getVoiceState().getChannel();
3145 // to work with the raid#isVoiceChat
3146 if (vc == null) {
3147 System.out.println("Voice Channel is null");
3148 return null;
3149 }
3150 // Check which Raid the voice channel is part of, return that Raid
3151 for (Raid raid : raids) {
3152 if (raid.isVoiceChat(vc)) {
3153 return raid;
3154 }
3155 }
3156 // If there's no raid, return null.
3157 System.out.println("Returning null by default");
3158 return null;
3159 }
3160
3161
3162 /**
3163 * Checks which group a user is in.
3164 *
3165 * @param u The user
3166 * @param a The list of users in group A
3167 * @return 'A' if the user is in the list, 'B' otherwise.
3168 * @author Botkiller
3169 */
3170 private static char getGroup(Member m, List<Member> a) {
3171 return a.contains(m) ? 'A' : 'B';
3172
3173 //Kinda weird, why not just make a nicer function in Raid to do this (getGroup(user u))
3174 //TODO
3175 }
3176
3177
3178 /**
3179 * Get all exact matches with a given username. The username should either be exactly the same as a nickname on discord,
3180 * or be a separate subpart of a name (separated by a non-alphabetical character)
3181 *
3182 * @param name The given IGN to match on
3183 * @param guild The IGuild from which to get the nicknames from
3184 * @return The User object of the given user, or `null` if no User was found.
3185 * @author Botkiller
3186 */
3187 private static Member getMemberByNameExact(String name, Guild guild, boolean feedback) {
3188
3189 Role raidRole = guild.getRoleById(ROLE_RAID);
3190
3191 // Get all possible variations of I/l in this name (i.e. Botkiller, BotkiIIer, BotkiIler, BotkilIer)
3192 List<String> names = getPermutations(name);
3193 // Get the list of users which have raid role and filter
3194 List<Member> matches = guild.getMembersWithRoles(raidRole).stream().filter(member -> {
3195 // Get all IGNs in the user's display name (i.e. "Hmm | Orb" -> ["Hmm", "", "", "Orb"])
3196 String[] list = member.getEffectiveName().split("\\P{Alpha}");
3197 for (String s : list) {
3198 // Skip the zero-length ones
3199 if (s.length() == 0) continue;
3200 // First, try the IGN. If this is correct, we're done.
3201 if (s.equalsIgnoreCase(name)) {
3202 return true;
3203 }
3204 // If there's no exact match, try each permutation of the name.
3205 for (String n : names) {
3206 if (s.equalsIgnoreCase(n)) {
3207 // Give feedback to the recognition if the 'feedback' flag is set to true.
3208 if (feedback) recognise.giveFeedback(name, n);
3209 return true;
3210 }
3211 }
3212
3213 }
3214 // If no suitable match was found, return false.
3215 return false;
3216 }).collect(Collectors.toList());
3217 if (matches.size() == 0) {
3218 // Will happen if no match was found
3219 multipleMatchFlag = false;
3220 return null;
3221 } else if (matches.size() == 1) {
3222 // Will happen if one match was found
3223 return matches.get(0);
3224 } else {
3225 // Will happen if there are multiple people with Raid role registered under the same name,
3226 // or there are people registered with variations of the same name (i.e. Botkiller and BotkiIIer)
3227 // TODO: Maybe fix bug where multiple 'exact' matches are found if there are multiple users with similar names (see above comments)
3228 System.err.println("Multiple exact matches found for given name " + name + "!");
3229 multipleMatchFlag = true;
3230 return null;
3231 }
3232 }
3233
3234 /**
3235 * Returns a list of Strings with each permutation of the given nickname, replacing I with l and vice versa.
3236 *
3237 * @param ign The input IGN to give permutations of
3238 * @return A List of Strings containing permutations of the given ign.
3239 * @author Botkiller
3240 */
3241 private static List<String> getPermutations(String ign) {
3242 int count = ign.replaceAll("[^Il]", "").length();
3243 if (count == 0) return Collections.singletonList(ign);
3244 // We need: An array of indices for I/l for the given String
3245 int[] indices = new int[count];
3246 int index = 0;
3247 // Construct an array of indices for the letters which should change
3248 for (int i = 0; i < ign.length(); i++) {
3249 if (ign.charAt(i) == 'I' || ign.charAt(i) == 'l') {
3250 indices[index] = i;
3251 index++;
3252 }
3253 }
3254 List<char[]> res = new ArrayList<>();
3255 // We now have an array containing the indices which need to be changed.
3256 // We should now iterate over all possible permutations.
3257
3258 // Some parts of the name are static; those are put in the following array:
3259 char[][] stChars = new char[count + 1][];
3260 if (stChars.length > 0) {
3261 stChars[0] = ign.substring(0, indices[0]).toCharArray();
3262
3263 for (int i = 0; i < count - 1; i++) {
3264 stChars[i + 1] = ign.substring(indices[i] + 1, indices[i + 1]).toCharArray();
3265 }
3266 stChars[count] = ign.substring(indices[count - 1] + 1, ign.length()).toCharArray();
3267 }
3268 for (int i = 0; i < pow(count); i++) {
3269 char[] characters = new char[count];
3270 for (int x = 0; x < count; x++) {
3271 characters[x] = ((i & pow(x)) > 0 ? 'I' : 'l');
3272 }
3273 char[] ignChars = new char[ign.length()];
3274 index = 0;
3275 // First, add the first part of the name
3276 while (index < stChars[0].length) {
3277 ignChars[index] = stChars[0][index];
3278 index++;
3279 }
3280 // Then, add the next i/l and the part between i/l and next i/l
3281 for (int j = 1; j <= count; j++) {
3282 ignChars[index] = characters[j - 1];
3283 index++;
3284 System.arraycopy(stChars[j], 0, ignChars, index, stChars[j].length);
3285 index += stChars[j].length;
3286 }
3287 System.arraycopy(stChars[count], 0, ignChars, index, ign.length() - index);
3288 // Add the found name to the array
3289 res.add(ignChars);
3290 }
3291 return res.stream().map(String::valueOf).collect(Collectors.toList());
3292 }
3293
3294 /**
3295 * Calculates 2**x
3296 *
3297 * @param x the exponent
3298 * @return 2 to the power of x
3299 * @author Botkiller
3300 */
3301 private static int pow(int x) {
3302 return (int) Math.pow(2, x);
3303 }
3304
3305 /**
3306 * Downloads an image from a given URL.
3307 *
3308 * @param urlString The URL to download the image from
3309 * @return the BufferedImage representation of the image.
3310 * @throws IOException if an error occurs while downloading the image or writing it to input.png.
3311 * @author Botkiller
3312 */
3313 private static BufferedImage getFile(String urlString) throws IOException {
3314 URL url = new URL(urlString);
3315 URLConnection connection = url.openConnection();
3316 connection.setRequestProperty("User-Agent",
3317 "Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0");
3318 connection.connect();
3319 BufferedImage capture = ImageIO.read(connection.getInputStream());
3320 System.out.println("Writing image to output");
3321 ImageIO.write(capture, "png", new File("res/images/Test/input.png"));
3322 return capture;
3323 }
3324
3325
3326 /**
3327 * Finds a user based on their name.
3328 *
3329 * @param name The name of the user which should be found
3330 * @param out The channel which output should be printed to. Used to find the guild.
3331 * @return The User object, if a match is found. Returns null if no suitable match is found.
3332 */
3333 private static Member getUserByName(String name, TextChannel out) {
3334 Guild guild = out.getGuild();
3335 Role raidRole = guild.getRoleById(ROLE_RAID);
3336 // Matches contains each user in this guild with the Raid role of which the display name contains the 'name' variable.
3337 List<Member> matches = guild.getMembersWithRoles(raidRole)
3338 .stream().filter(m -> m.getEffectiveName().toLowerCase()
3339 .contains(name.toLowerCase())).collect(Collectors.toList());
3340
3341 if (matches.size() > 1) {
3342 String similar = "";
3343 for(Member m : matches) {
3344 similar += m.getAsMention() + ", ";
3345 }
3346 similar = similar.substring(0, similar.length() - 2);
3347
3348 // String message = "There are multiple users who match \"" + name + "\". Trying to find exact match. List of users: ";
3349 // for(Member m : matches) {
3350 // message += m.getAsMention() + ", ";
3351 // }
3352 //
3353 // message = message.substring(0, message.length() - 2);
3354 // // If the message is too long, just print out the amount of matches instead of the list of matches.
3355 // if (message.length() >= 2000) {
3356 // sendMessage(out, "There are multiple users who match \"" + name + "\". Not printing full"
3357 // + " list because it is over 2k characters.\n"
3358 // + " Amount of matches: " + matches.size());
3359 // } else {
3360 // sendMessage(out, message);
3361 // }
3362
3363 // Attempt to find an exact match
3364 for (Member m : matches) {
3365 String[] list = m.getEffectiveName().split("\\W");
3366 for (String s : list) {
3367 // Get the IGN, remove any non-alphabetical character
3368 String ign = s.replaceAll("\\P{Alpha}", "");
3369 // If the ign matches the given name, we've found an exact match.
3370 if (ign.equalsIgnoreCase(name)) {
3371 // sendMessage(out, "Exact match found! " + m.getAsMention());
3372 return m;
3373 }
3374 }
3375 }
3376 // If there was no match found, report this and return null.
3377
3378 if(matches.size() == 0) {
3379 sendMessage(out, "There are no users who match \"" + name + "\". Perhpas you're looking for one of these? \n" + similar);
3380 return null;
3381 }
3382 } else {
3383 // Return the exact match we found.
3384 return matches.get(0);
3385 }
3386 return null;
3387 }
3388
3389 /**
3390 *
3391 * @param user The user to send the message to
3392 * @param content The message to send
3393 */
3394 public static void sendPrivateMessage(User user, String content)
3395 {
3396
3397 user.openPrivateChannel().queue((channel) ->
3398 {
3399 channel.sendMessage(content).queue();
3400
3401 });
3402 }
3403
3404
3405 /**
3406 * Gets the list of users to be sent to a Cultist run.
3407 *
3408 * @param originalUsers The list from which the Cultist group should be selected.
3409 * @param splitNumber The amount of people which should be put in the Cultist run
3410 * @param guild The guild which the users are in.
3411 * @param excluded The users which should not be selected for the Cultist run.
3412 * @param raid The Raid which the users are in.
3413 * @return A list of users which are selected for the next Cultist run.
3414 */
3415 private static List<Member> getCultistUsers(List<Member> originalUsers, int splitNumber, Guild guild, Set<Member> excluded, Raid raid) {
3416 List<Member> members = new LinkedList<>();
3417 members.addAll(originalUsers);
3418 // Shuffle the users to get a more random result (shuffles between users with the same points)
3419 Collections.shuffle(members);
3420 // The users who are selected for Cultist
3421 List<Member> res = new LinkedList<>();
3422 // Add the user to the database if they are not yet in the database
3423 members.stream().filter(m -> !userExists(m.getUser().getIdLong())).forEach(m -> add_user(m.getUser().getIdLong()));
3424 // Don't select the excluded people for Cultist Hideout
3425 members.removeAll(excluded);
3426
3427 // Select the people from the dodge list who are in the current run
3428
3429 // Reset the dodge removed list for the Raid.
3430 raid.setDodgeRemoved(new HashSet<>());
3431 // For each user in the dodger list who is in the current run
3432 for (Member m : dodgers.stream().filter(members::contains).collect(Collectors.toList())) {
3433 // if there are still more people to select for cultist, select this user.
3434 if (res.size() < splitNumber) {
3435 // Add them to the people selected for Cultist
3436 res.add(m);
3437 // Indicate they should be removed from the dodge list
3438 raid.getDodgeRemoved().add(m);
3439 // Remove them from the remaining users
3440 members.remove(m);
3441 } else {
3442 break;
3443 }
3444 }
3445
3446 // Quick check to see if we're done
3447 if (res.size() == splitNumber) return res;
3448
3449 // Add people who noted cultist preference through `bot_cultist`
3450 raid.setReQueue(new ArrayList<>());
3451
3452 synchronized (vialQueueLock) {
3453 // For each user in the `cultist list` who are in the current run
3454 for (Member m : vialQueue.stream().filter(members::contains).collect(Collectors.toList())) {
3455 // If there are more people to add to the Cultist group, add this person.
3456 if (res.size() < splitNumber) {
3457 res.add(m);
3458 members.remove(m);
3459 raid.getReQueue().add(m);
3460 } else {
3461 break;
3462 }
3463 }
3464 }
3465
3466 // Quick check to see if we're done
3467 if (res.size() == splitNumber) return res;
3468
3469 // Add people who noted cultist preference through :vial: emoji, if any.
3470 List<Member> cultists = raid.getCultists();
3471 if (cultists != null) {
3472 // Remove each person who reacted with :vial: but isn't in the current run
3473 cultists = cultists.stream().filter(members::contains).collect(Collectors.toList());
3474 // Shuffle them for fairness
3475 Collections.shuffle(cultists);
3476
3477 for (Member m : cultists) {
3478 // If there are more people to add, add this user
3479 if (res.size() < splitNumber) {
3480 res.add(m);
3481 members.remove(m);
3482 } else {
3483 break;
3484 }
3485 }
3486 }
3487
3488 // Quick check to see if we're done
3489 if (res.size() == splitNumber) return res;
3490
3491 // Get all users and their points
3492 Map<Long, Integer> all = getAll();
3493 // Check for database error
3494 if (all == null) return null;
3495
3496 // Get the user IDs of all users in the current run
3497 // List<Long> userLongs = members.stream().map(Member::getUser).collect(Collectors.toList()); //This is what it previously was
3498 List<Long> userLongs = new ArrayList<>();
3499 for(Member m : members) {
3500 userLongs.add(m.getUser().getIdLong());
3501 }
3502
3503 // Get the user IDs of all users which are in the database and are in the current run
3504 // Should be identical to the list above, might be redundant.
3505 List<Long> shuffledKey = all.keySet().stream().filter(userLongs::contains).collect(Collectors.toList());
3506 // Shuffle this list for fairness
3507 Collections.shuffle(shuffledKey);
3508 // add the user ID and the user's points to shuffleMap
3509 Map<Long, Integer> shuffleMap = new LinkedHashMap<>();
3510 shuffledKey.forEach(k -> shuffleMap.put(k, all.get(k)));
3511 // Sort the map by the points in descending order
3512 Map<Long, Integer> iterate = sortByValueDesc(shuffleMap);
3513
3514 // Select users for vial run from the people in the vc.
3515 for (Long l : iterate.keySet()) {
3516 // Get the User object
3517 Member m = guild.getMemberById(l);
3518 // If there are more people to select, add this user
3519 if (res.size() < splitNumber) {
3520 res.add(m);
3521 members.remove(m);
3522 } else {
3523 break;
3524 }
3525 }
3526 // Return the users selected for Cultist Hideout
3527 return res;
3528 }
3529
3530
3531 /**
3532 * Sends a message to the #bot_staff and #raiding channels with the given group.
3533 *
3534 * @param messages The messages to be sent
3535 * @param botStaff The bot_staff channel
3536 * @param raidChat The raiding channel
3537 * @param raid The raid
3538 * @return An List of Lists containing Messages. Will always have length 2.
3539 * The first element of the array is a list of the messages sent in #bot_staff, the second element is the list of messages sent in #raiding.
3540 */
3541 private static List<List<Message>> sendgroupMessage(List<String> messages, TextChannel botStaff, TextChannel raidChat, Raid raid) {
3542 List<Message> bot_staff = new ArrayList<>();
3543 List<Message> raiding = new ArrayList<>();
3544 for (String m : messages) {
3545 // Send message in #bot_staff
3546 bot_staff.add(botStaff.sendMessage("Raid " + raid.getId() + ": " + m).complete());
3547
3548 // Send message in #raiding
3549 raiding.add(raidChat.sendMessage(m).complete());
3550 }
3551 // Create a list with as elements the list
3552 List<List<Message>> res = new ArrayList<>(2);
3553 res.add(bot_staff);
3554 res.add(raiding);
3555 // Return both messages to edit later
3556 return res;
3557 }
3558
3559 /**
3560 * Edits a previously sent message to contain new content.
3561 *
3562 * @param messages The new messages
3563 * @param sentMessages The old messages
3564 */
3565 private static void editGroupMessage(List<String> messages, List<List<Message>> sentMessages) {
3566 // Messages in #bot_staff
3567 List<Message> bs = sentMessages.get(0);
3568 // Messages in #raiding
3569 List<Message> ra = sentMessages.get(1);
3570 for (int i = 0; i < messages.size(); i++) {
3571 // Get the message from #bot_staff
3572 Message mbs = bs.get(i);
3573 // Get the message from #raiding
3574 Message mra = ra.get(i);
3575 // Get the content to replace the messages by
3576 String s = messages.get(i);
3577 // Edit the messages
3578 mbs.editMessage(s).queue();
3579 mra.editMessage(s).queue();
3580 }
3581 // Delete the extra "People selected for Cultist(continued):" messages
3582 if (messages.size() < bs.size()) {
3583 for (int i = messages.size(); i < sentMessages.get(0).size(); i++) {
3584 Message mbs = bs.get(i);
3585 Message mra = ra.get(i);
3586 mbs.delete().queue();
3587 mra.delete().queue();
3588 }
3589 }
3590 }
3591
3592
3593 /**
3594 * Sends messages, using a Function to create the content of the message.
3595 *
3596 * @param <T> The type of the list
3597 * @param test The list of T to create messages of
3598 * @param f The method from T to String which creates the message
3599 * @param messageStart The start of each message to be sent
3600 * @param channel The channel to send the message to
3601 * @param removeFirst Whether or not to remove the first two characters from the function on the first element (i.e. ", " or "\n\t")
3602 */
3603 private static <T> void sendMessages(List<T> test, Function<T, String> f, String messageStart, TextChannel channel, boolean removeFirst) {
3604 List<String> messages = new ArrayList<>();
3605 StringBuilder message = new StringBuilder(messageStart + ": ");
3606 if (removeFirst && test.size() > 0) {
3607 message.append(f.apply(test.remove(0)).substring(2));
3608 }
3609 for (T u : test) {
3610 String next = f.apply(u);
3611 if (message.length() + next.length() > 2000) {
3612 messages.add(message.toString());
3613 message = new StringBuilder(messageStart + " (continued): " + (removeFirst ? next.substring(2) : removeFirst));
3614 } else {
3615 message.append(next);
3616 }
3617 }
3618 messages.add(message.toString());
3619 for (String s : messages) {
3620 sendMessage(channel, s);
3621 }
3622 }
3623
3624 /**
3625 * Undoes a point change for a given user.
3626 *
3627 * @param u The user for which the point change should be undone
3628 */
3629 private static void undo(Member m) {
3630 undoChange(m.getUser().getIdLong());
3631 }
3632
3633
3634
3635 /**
3636 * Gives the chance for a given user to be selected for cultist.
3637 *
3638 * @param originalUsers The users in the voice channel
3639 * @param splitNumber The amount of people to send to cultist hideout
3640 * @param guild The guild the bot is in
3641 * @param raid The raid the user is in
3642 * @param m The member
3643 * @return A double between 0 and 1 indicating the chance that the user will be selected for Cultist. 0 is 0% chance,
3644 * 1 is 100% chance. Returns -1 if a database error occurs.
3645 */
3646 private static double getCultistForecast(List<Member> originalUsers, int splitNumber, Guild guild, Raid raid, Member m) {
3647 // remaining is the amount of users yet to be selected for cultist
3648 int remaining = splitNumber;
3649
3650 // Count indicates the next group to check points for.
3651 // First: Dodgers
3652 // Second: `bot_cultist list`
3653 // Third: :vial: list
3654 // Fourth: highest points
3655 // Fifth: second-highest points
3656 // Sixth: third-highest points
3657 // etc.
3658 // TODO: Fix edge case where users are counted twice if they're in multiple of these lists (i.e. dodgers, `cultist`
3659 // list, :vial: list, highest points)
3660 int count = 0;
3661 List<Long> originalUserLongs = new ArrayList<>();
3662 for(Member m2 : originalUsers) {
3663 originalUserLongs.add(m2.getUser().getIdLong());
3664 }
3665 while (remaining > 0) {
3666 List<Member> nextGroup;
3667 switch (count) {
3668 case 0:
3669 // The first case is the people on the dodge list.
3670 nextGroup = dodgers.stream().filter(originalUsers::contains).collect(Collectors.toList());
3671 break;
3672 case 1:
3673 // The second case is the people on the `cultist` list
3674 synchronized (vialQueueLock) {
3675 nextGroup = vialQueue.stream().filter(originalUsers::contains).collect(Collectors.toList());
3676 }
3677 break;
3678 case 2:
3679 // The third case is the people who indicated cultist preference through :vial: emoji.
3680 if (raid.getCultists() == null) {
3681 count++;
3682 continue;
3683 }
3684 nextGroup = raid.getCultists().stream().filter(originalUsers::contains).collect(Collectors.toList());
3685 break;
3686 default:
3687 // The other cases are the users with the highest points.
3688
3689 // Get all users' points
3690 Map<Long, Integer> all = getAll();
3691 // Database error
3692 if (all == null) return -1;
3693 // Filter by users in the current run
3694 List<Long> usersInRun = all.keySet().stream().filter(originalUserLongs::contains).collect(Collectors.toList());
3695 // Convert back to map
3696 Map<Long, Integer> userMap = new LinkedHashMap<>();
3697 usersInRun.forEach(k -> userMap.put(k, all.get(k)));
3698 // Sort by value descending
3699 Map<Long, Integer> usersInRunSorted = sortByValueDesc(userMap);
3700 // Get the amount of times to go through the while loop
3701 int pointCount = (count - 2);
3702 while (true) {
3703 pointCount--;
3704 // From the map, get the highest points in the map.
3705 int max = Collections.max(usersInRunSorted.entrySet(), Comparator.comparingInt(Map.Entry::getValue)).getValue();
3706 // Then, get all entries which have this count
3707 List<Long> entries = usersInRunSorted.entrySet().stream().filter(e -> e.getValue() == max).
3708 map(Map.Entry::getKey).collect(Collectors.toList());
3709 // If pointCount == 0, this is the group we need to check next.
3710 if (pointCount == 0) {
3711 // Get the Member objects for each entry
3712 nextGroup = entries.stream().map(l -> guild.getMemberById(l)).collect(Collectors.toList());
3713 break;
3714 }
3715 // If this wasn't the next group to check, remove the group and look for the next highest points.
3716 entries.forEach(usersInRunSorted::remove);
3717 }
3718 }
3719 // If there's nobody in the next group to check, we're done
3720 if (nextGroup.size() == 0) {
3721 count++;
3722 continue;
3723 }
3724 // If the next group has more people than the amount of remaining people to select for cultist:
3725 if (nextGroup.size() >= remaining) {
3726 // If the user isn't in the group, there's no chance for him to be selected.
3727 if (!nextGroup.contains(m)) return 0;
3728 // Otherwise, the chance depends on the amount of people chosen from this group.
3729 return ((double) remaining) / nextGroup.size();
3730 } else {
3731 // The amount of people to select is less than the amount of people who need to be added.
3732 // If the user is part of this group, they'll definitely be chosen.
3733 if (nextGroup.contains(m)) return 1;
3734 // Otherwise, select all these people for cultist and move on to the next group.
3735 remaining -= nextGroup.size();
3736 }
3737 // Go to the next group
3738 count++;
3739 }
3740 // If there's nobody remaining to check, but the user still hasn't been chosen, there's no chance he'll be selected.
3741 return 0;
3742 }
3743
3744
3745
3746
3747
3748
3749 /**
3750 * Checks the requirements of the user given by their username
3751 *
3752 * @param username The ign of the user which requirements should be checked of
3753 * @return The requirements the user is missing.
3754 */
3755 private static Set<String> hasRequirements(String username) {
3756 Set<String> missingReqs = new HashSet<>();
3757 Gson gson = new Gson();
3758 StringBuilder json = new StringBuilder();
3759 // Get the json from tiffit's API
3760 try {
3761 URL url = new URL("http://tiffit.net:8080/RealmInfo/api/user?u=" + username + "&f=d;");
3762 Scanner s = new Scanner(url.openStream());
3763 while (s.hasNext()) {
3764 json.append(s.next());
3765 }
3766 s.close();
3767 } catch (IOException e) {
3768 e.printStackTrace();
3769 return null;
3770 }
3771 if (json.toString().equals("")) {
3772 return null;
3773 }
3774 // Parse the json
3775 Realmeye re = gson.fromJson(json.toString(), Realmeye.class);
3776 // If the Realmeye is hidden
3777 if (re.error != null) {
3778 if (re.error.equals("Internal API error!")) {
3779 return Collections.singleton("api");
3780 }
3781 missingReqs.add("realmeye");
3782 }
3783 // If the fame is too low
3784 if (re.fame < REQ_BASE_FAME) {
3785 missingReqs.add("fame");
3786 }
3787 // If the characters are hidden
3788 if (re.characterCount == -1) {
3789 missingReqs.add("charactersHidden");
3790 }
3791 // If the last seen location is not hidden
3792 if (re.last_seen == null || !re.last_seen.equals("hidden")) {
3793 missingReqs.add("location");
3794 }
3795 // Count the amount of 8/8s this user has
3796 int count = 0;
3797 for (RECharacter c : re.characters) {
3798 switch (c.stats_maxed) {
3799 case "8/8":
3800 count++;
3801 default:
3802 break;
3803 }
3804 if (count >= REQ_MAXED_CHARS) {
3805 break;
3806 }
3807 }
3808 // If this user has less than 3 8/8s
3809 if (count < REQ_MAXED_CHARS) {
3810 missingReqs.add("char");
3811 }
3812
3813 // Get the name history json of this user
3814 json = new StringBuilder();
3815 try {
3816 URL url = new URL("http://tiffit.net:8080/RealmInfo/api/name-history-of-player?u=" + username);
3817 Scanner s = new Scanner(url.openStream());
3818 while (s.hasNext()) {
3819 json.append(s.next());
3820 }
3821 s.close();
3822 } catch (IOException e) {
3823 e.printStackTrace();
3824 return null;
3825 }
3826 if (json.toString().equals("")) {
3827 return null;
3828 }
3829 // Parse the json
3830 RENameHistory nameHistory = gson.fromJson(json.toString(), RENameHistory.class);
3831 // If the name history is hidden
3832 // TODO: FIX INTERNAL API ERROR BUG ON REALMEYE LINKS WITH DONATOR CROWN (i.e.
3833 // http://www.realmeye.com/player/Kyle - http://tiffit.net:8080/RealmInfo/api/name-history-of-player?u=Kyle)
3834 if (re.error != null || nameHistory.error != null) {
3835 missingReqs.add("namehistory");
3836 }
3837 return missingReqs;
3838 }
3839
3840
3841
3842
3843
3844 /**
3845 * Creates an EmbedObject representing the leaderboard as given in the given map.
3846 *
3847 * @param leaderboard The map which contains the leaderboard data
3848 * @param userRank The user's rank
3849 * @param isMonthly Whether this leaderboard is monthly or total
3850 * @return An EmbedObject representing the visual representation of the leaderboard
3851 */
3852
3853 private static MessageEmbed createLeaderboardEmbed(Map<Long, Integer> leaderboard, long[] userRank, boolean isMonthly) {
3854 EmbedBuilder builder = new EmbedBuilder();
3855 builder.setTitle("Leaderboard - " + (isMonthly ? "past 31 days" : "total"));
3856 builder.setDescription("The " + (isMonthly ? "monthly " : "") + "leaderboard of Lost Halls done in SBC runs.");
3857 int count = 0;
3858 int actualCount = 0;
3859 int prevCount = -1;
3860 // Get the leaderboard data and sort them by the amount of halls done
3861 Map<Long, Integer> entries = sortByValueDesc(leaderboard);
3862 // Get the entries
3863 for (Map.Entry<Long, Integer> a : entries.entrySet()) {
3864 // Correct the rank if it's incorrect. If two players are tied for 2nd place, they'll both be displayed as 2nd.
3865 // If the 4th is then not tied, they'll be displayed as 4th again instead of 3rd.
3866 actualCount++;
3867 if (a.getValue() != prevCount) count = actualCount;
3868 prevCount = a.getValue();
3869 // Add this entry to the leaderboard entry
3870 builder.addField("#" + count, "<@" + a.getKey() + "> - **" + prevCount + "**", false);
3871 }
3872 // Add user rank
3873 builder.addField("Your rank: #" + userRank[1], "<@" + userRank[0] + "> - **" + userRank[2] + "**", false);
3874 return builder.build();
3875 }
3876
3877
3878
3879
3880 /**
3881 * Returns true if the user has their last seen location hidden.
3882 *
3883 * @param user The user to check the last seen location for
3884 * @param guild The guild to get the display name from
3885 * @return true if the user has their last seen location hidden, false otherwise.
3886 */
3887 private static boolean checkLoc(Member member, Guild guild) {
3888 // Check if the user's location is private
3889 String privateLoc = isLocPrivate(member, guild);
3890 if (!privateLoc.equalsIgnoreCase("hidden")) {
3891 // The user's last seen location isn't hidden
3892 VoiceChannel afk = guild.getVoiceChannelById(VC_AFK);
3893 try {
3894 guild.getController().moveVoiceMember(member, afk).queue();
3895 } catch (Exception e) {
3896 System.out.println("Exception caught while checking the queue, likely because " + member.getEffectiveName()
3897 + " left the voice channel before I could move them!");
3898 }
3899 final String sendMessage;
3900 switch (privateLoc) {
3901 case "noName":
3902 sendMessage = "You've been moved to the AFK channel.\n\n" +
3903 "Unable to find a valid IGN in your display name for SBC! Make sure to have your" +
3904 " nickname in Spooky Boi Central set to your in-game name.\n\n" +
3905 "If there is in fact a valid IGN in your nickname, contact Tybug.";
3906 break;
3907 case "error":
3908 sendMessage = "You've been moved to the AFK channel.\n\n" +
3909 "An error occurred while trying to obtain your last seen location on realmeye. " +
3910 "Please try to join the queue again. If this problem persists, contact Tybug.";
3911 break;
3912 default:
3913 sendMessage = "You've been moved to the AFK channel.\n\n" +
3914 "The last seen location of " + privateLoc + " is public! Please set " + privateLoc + "'s" +
3915 " last seen location to **private** before rejoining the queue. \n" +
3916 "Note that doing \"/tell mreyeball hide me\" does not count as a private location.";
3917 }
3918 // Send the message
3919 sendPrivateMessage(member.getUser(), sendMessage);
3920 System.out.println("Sending PM to user " + member.getEffectiveName() + ": " + sendMessage);
3921 return false;
3922 }
3923 return true;
3924 }
3925
3926
3927
3928
3929
3930 /**
3931 * Checks whether a user's last seen location on Realmeye is hidden or not.
3932 *
3933 * @param user The user to check for
3934 * @param guild The guild to get the display name from
3935 * @return "noName" if no name was found for the given user, "hidden" if their last seen
3936 * location was public, the user's IGN if their location is public on Realmeye.
3937 */
3938 private static String isLocPrivate(Member member, Guild guild) {
3939 // Get user's display name, and split by IGN
3940 List<String> igns = new ArrayList<>();
3941 // Get all names in the user's display name.
3942 String[] names = member.getEffectiveName().split("\\P{Alpha}+?");
3943 for (String s : names) {
3944 // Take out any special characters
3945 String ign = s.replaceAll("\\P{Alpha}", "");
3946 // Check if the character length is valid
3947 if (ign.length() > 0 && ign.length() <= 10) {
3948 // Valid IGN. Add to the list of IGNs for this user.
3949 igns.add(ign);
3950 }
3951 }
3952 // There's no valid IGN in the user's display name
3953 if (igns.size() == 0) {
3954 System.err.println("No IGN found for user " + member.getEffectiveName() + "!");
3955 return "noName";
3956 }
3957 for (String username : igns) {
3958 // Get the json for this user
3959 Gson gson = new Gson();
3960 StringBuilder json = new StringBuilder();
3961 try {
3962 URL url = new URL("http://tiffit.net:8080/RealmInfo/api/user?u=" + username + "&f=d;");
3963 Scanner s = new Scanner(url.openStream());
3964 while (s.hasNext()) {
3965 json.append(s.next());
3966 }
3967 s.close();
3968 } catch (IOException e) {
3969 e.printStackTrace();
3970 return "error";
3971 }
3972 if (json.toString().equals("")) {
3973 return "error";
3974 }
3975 // Parse the json
3976 Realmeye re = gson.fromJson(json.toString(), Realmeye.class);
3977 // If the last seen is not equal to hidden, the last seen location is public.
3978 // If the Realmeye is hidden, re.last_seen == null.
3979 if (re.last_seen != null && !re.last_seen.equalsIgnoreCase("hidden")) return re.name;
3980 }
3981 return "hidden";
3982 }
3983
3984
3985
3986 /**
3987 * Gets the raid a given voice channel belongs to.
3988 *
3989 * @param raids The map of all raids
3990 * @param vc The voice channel to check for
3991 * @return The raid the voice channel is part of, or null if the voice channel isn't part of any raid.
3992 */
3993 private static Raid getRaid(List<Raid> raids, VoiceChannel vc) {
3994 // Check which raid the voice channel is part of
3995 if (vc == null) {
3996 return null;
3997 }
3998 for (Raid r : raids) {
3999 if (r.isVoiceChat(vc)) {
4000 return r;
4001 }
4002 }
4003 // Return null if the vc isn't part of any raid.
4004 return null;
4005 }
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016 @Override
4017 public void onUserNameUpdate(UserNameUpdateEvent event) { //Reverts name changes in sbc
4018 Guild guild = event.getJDA().getGuildById(SBC);
4019 GuildController gc = guild.getController();
4020 User user = event.getUser();
4021 Member member = guild.getMember(user);
4022
4023 // if(verifiedNames.containsKey(user.getId())) {
4024 // if(userExists(user.getIdLong())) {
4025 // gc.setNickname(member, verifiedNames.get(user.getId())).queue();
4026 // sendMessage(guild.getTextChannelById(BOT_SPAM), "Changed user " + member.getAsMention() + "'s nickname to "
4027 // + verifiedNames.get(user.getId()));
4028 // }
4029 // }
4030
4031
4032
4033
4034
4035 // String nameOld = event.getOldName();
4036 // String nameNew = guild.getMember(user).getEffectiveName();
4037 //
4038 // // If their nickname didn't change, ignore.
4039 // if (nameOld.equals(nameNew)) {
4040 // System.out.println("Names are the same, ignoring.");
4041 // return;
4042 // }
4043 //
4044 // // If the user is registered and their display name changed, change it back to what it was.
4045 // if (userExists(user.getIdLong())) {
4046 // gc.setNickname(member, nameOld).queue();
4047 //
4048 // // Log this in #bot-spam
4049 // sendMessage(guild.getTextChannelById(BOT_SPAM), "Changed user " + member.getAsMention() + "'s nickname to " + nameOld);
4050 // }
4051
4052
4053
4054
4055 }
4056
4057
4058
4059
4060
4061
4062 @Override
4063 public void onGuildMemberLeave(GuildMemberLeaveEvent event) {
4064 JDA jda = event.getJDA();
4065 Guild guild = event.getGuild();
4066 Member member = event.getMember();
4067 User user = event.getUser();
4068 // Log to #bot-spam
4069 sendMessage(guild.getTextChannelById(BOT_SPAM), "User " + user.getAsMention() + " left SBC!");
4070 System.out.println("User left SBC!");
4071
4072 // Remove from dodge/cultist lists
4073 dodgers.remove(member);
4074 vialQueue.remove(member);
4075 if (userExists(user.getIdLong())) {
4076 // Log to #bot-spam that they're removed from the database.
4077 sendMessage(guild.getTextChannelById(BOT_SPAM), "Removed user " + event.getUser().getAsMention() + " from the database.");
4078 }
4079 // Remove user from the database
4080 remove_user(user.getIdLong());
4081 // Remove the user's verification entry
4082 removeVerificationEntry(user.getIdLong());
4083 System.out.println("Done handling UserLeaveEvent.");
4084 }
4085
4086
4087 @Override
4088 public void onGuildBan(GuildBanEvent event) {
4089
4090 User user = event.getUser();
4091 Guild guild = event.getGuild();
4092
4093 // Log to #bot-spam that they were banned
4094 sendMessage(guild.getTextChannelById(BOT_SPAM), "User " + user.getAsMention() + " was banned from SBC!");
4095
4096 // Remove from dodge/cultist lists
4097 dodgers.remove(guild.getMember(event.getUser()));
4098 vialQueue.remove(guild.getMember(event.getUser()));
4099 // Remove user from the database
4100 remove_user(user.getIdLong());
4101 // Remove verification entry from the database
4102 removeVerificationEntry(user.getIdLong());
4103 // Log to #bot-spam that they got removed from the database
4104
4105 sendMessage(guild.getTextChannelById(BOT_SPAM), "Removed user " + event.getUser() + " from the database.");
4106 System.out.println("Done handling UserLeaveEvent.");
4107 }
4108
4109
4110
4111
4112
4113 @Override
4114 public void onGuildVoiceLeave(GuildVoiceLeaveEvent event) {
4115 // If the user was in the queue, remove them from the queue.
4116 Member member = event.getMember();
4117 Guild guild = event.getGuild();
4118 VoiceChannel vc = event.getChannelLeft();
4119 synchronized (queueLock) {
4120 if (queue.contains(member)) queue.remove(member);
4121 }
4122 // Get the raid
4123 Raid raid = getRaid(raids, vc);
4124
4125 // If the user was in a raid, the raid has not yet done `split` but the queue is locked, and there is no AFK check ongoing:
4126 // (This happens when the RLs are about to call the location, or have just called the location: )
4127 if (raid != null && raid.isVoiceChat(vc) && !raid.isSplit() && raid.isQueue_blocked() && !finishOngoing) {
4128 // Log that the user left the voice channel early. Useful for detecting crashers.
4129
4130 sendMessage(guild.getTextChannelById(BOT_STAFF), "Raid #" + raid.getId() + ": " + member.getEffectiveName() +
4131 " left the voice channel after bot_lock but before bot_split or bot_unlock.");
4132 }
4133
4134 // Check if we can move anyone in from the queue
4135 checkQueue();
4136 }
4137
4138
4139 @Override
4140 public void onGuildVoiceMove(GuildVoiceMoveEvent event) {
4141
4142 Member member = event.getMember();
4143 Guild guild = event.getGuild();
4144
4145 // If the user was in the queue, remove them. They're no longer in it.
4146 if(event.getChannelLeft().getId().equals(VC_QUEUE)) {
4147 synchronized (queueLock) {
4148 if (queue.contains(member)) {
4149 queue.remove(member);
4150 }
4151 }
4152 }
4153
4154 //TODO maybe wrap this in a new thread
4155 // If the user moved to the queue:
4156 if (event.getChannelJoined().getId().equals(VC_QUEUE)) {
4157 // If the user's location is private:
4158 if (checkLoc(member, guild)) {
4159 // Add the user to the queue and log this.
4160 synchronized (queueLock) {
4161 // sendMessage(guild.getTextChannelById(STDOUTPUT), member.getEffectiveName() + " joined the queue! Queue length: " + queue.size());
4162 if (member.getRoles().contains(guild.getRoleById(ROLE_RAID)) && event.getChannelJoined().getMembers().contains(member)) {
4163 queue.add(member);
4164 }
4165 }
4166 }
4167 }
4168
4169 // Get the raid for the user
4170 Raid raid = getRaid(raids, event.getChannelLeft());
4171
4172 // If the user was in a raid, the raid has not yet done `split` but the queue is locked, and there is no AFK check ongoing:
4173 // (This happens when the RLs are about to call the location, or have just called the location: )
4174 if (raid != null && raid.getFightingVoiceChannel().equals(event.getChannelLeft()) && !raid.isSplit() && raid.isQueue_blocked()
4175 && !finishOngoing) {
4176 // Log that the user left the vc
4177
4178 sendMessage(guild.getTextChannelById(BOT_STAFF), "Raid #" + raid.getId() + ": " + member.getEffectiveName() +
4179 " left the voice channel after bot_lock but before bot_split or bot_unlock.");
4180 }
4181
4182 // Check if we can move anyone from the queue
4183 checkQueue();
4184 }
4185
4186
4187 @Override
4188 public void onGuildVoiceJoin(GuildVoiceJoinEvent event) {
4189 if (event.getJDA() == null) {
4190 return; // check if shard is ready
4191 }
4192 new Thread(() -> {
4193 // If the user joins the queue
4194
4195 if (event.getChannelJoined().getId().equals(VC_QUEUE)) {
4196 // If their location is private
4197 if (checkLoc(event.getMember(), event.getGuild())) {
4198 // Add them to the queue and log it.
4199 synchronized (queueLock) {
4200 queue.add(event.getMember());
4201 // sendMessage(event.getGuild().getTextChannelById(STDOUTPUT), event.getMember().getEffectiveName() +
4202 // " joined the queue! Position in the queue: " + queue.size());
4203 }
4204 }
4205 }
4206
4207 // Check if we can move anyone in from the queue.
4208 checkQueue();
4209 }).start();
4210 }
4211
4212
4213
4214 @Override
4215 public void onMessageReactionAdd(MessageReactionAddEvent event) {
4216
4217 Member member = event.getMember();
4218 String mId = event.getMessageId();
4219 if(!pendingReactions.containsKey(mId) || checkPerms(member) > 3) {
4220 return;
4221 }
4222
4223 JDA jda = event.getJDA();
4224 Guild guild = event.getGuild();
4225 GuildController gc = guild.getController();
4226 Message m = event.getTextChannel().getMessageById(mId).complete();
4227
4228 new Thread(() -> {
4229 int xCount = 0;
4230 int checkCount = 0;
4231 for(MessageReaction mr : m.getReactions()) {
4232 if(mr.getReactionEmote().getId().equals(GUILD_CHECK)) {
4233 checkCount = mr.getCount();
4234 }
4235 if(mr.getReactionEmote().getId().equals(GUILD_X)) {
4236 xCount = mr.getCount();
4237 }
4238 }
4239 try {
4240 Thread.sleep(5000);
4241 } catch (InterruptedException e) {
4242 e.printStackTrace();
4243 }
4244 Message message = event.getTextChannel().getMessageById(m.getId()).complete();
4245 int checkCount2 = 0;
4246 int xCount2 = 0;
4247 for(MessageReaction mr : message.getReactions()) {
4248 if(mr.getReactionEmote().getId().equals(GUILD_CHECK)) {
4249 checkCount2 = mr.getCount();
4250 }
4251 if(mr.getReactionEmote().getId().equals(GUILD_X)) {
4252 xCount2 = mr.getCount();
4253 }
4254 }
4255
4256 if((checkCount2 != checkCount) || (xCount2 != xCount)) {
4257 return;
4258 }
4259
4260
4261 //If they are a mod
4262 if(event.getReactionEmote().getId().equals(GUILD_CHECK)) {
4263
4264
4265 Member m2 = guild.getMemberById(pendingReactions.get(mId));
4266 //#bot-spam
4267 for(MessageEmbed embed : message.getEmbeds()) {
4268 Message toEdit = jda.getTextChannelById("342842121239396352").sendMessage(embed).complete();
4269 toEdit.editMessage("Accepted by " + event.getMember().getAsMention());
4270 }
4271 m.delete().queue();
4272 String ign = pendingNames.get(mId);
4273 register(m2, ign);
4274 pendingReactions.remove(mId);
4275 pendingNames.remove(mId);
4276 }
4277
4278 //If it's the x, remove anyway
4279 else if(event.getReactionEmote().getId().equals(GUILD_X)) {
4280 //#register-log in testing discord
4281 for(MessageEmbed embed : m.getEmbeds()) {
4282 Message toEdit = jda.getTextChannelById("342842121239396352").sendMessage(embed).complete();
4283 toEdit.editMessage("Denied by " + event.getMember().getAsMention());
4284 }
4285 m.delete().queue();
4286 pendingReactions.remove(mId);
4287 pendingNames.remove(mId);
4288 }
4289 }).start();
4290
4291 }
4292
4293 @Override
4294 public void onGuildMemberRoleAdd(GuildMemberRoleAddEvent event) {
4295 JDA jda = event.getJDA();
4296 sendPrivateMessage(jda.getUserById(AUTHOR), "Member name (effective): " + event.getMember().getEffectiveName() +
4297 "\nUser name: " + event.getUser().getName() + "\nRole name: "
4298 + event.getRoles().get(0).getName() + "Role id: " + event.getRoles().get(0).getId());
4299
4300 }
4301
4302
4303
4304 private static void startAutomatedActivity(JDA jda) {
4305 // AUTOMATED ACTIVITY CHECK EVERY DAY
4306 // Get the delay until next check
4307
4308 // Set the date of the next inactivity check.
4309 Calendar reset = Calendar.getInstance();
4310 DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
4311 reset.set(2017, Calendar.DECEMBER, 3, 3, 0, 0);
4312
4313 long resetLong = reset.getTimeInMillis();
4314 long now = System.currentTimeMillis();
4315 // These times are in milliseconds.
4316 // Make sure the next scheduled role reset is scheduled in the future. Add 1 day until this is the case.
4317 while (resetLong < now) {
4318 // Add 1 day to the reset time
4319 resetLong += 1000 /*ms*/
4320 * 60 /*s*/
4321 * 60 /*min*/
4322 * 24 /*hours*/;
4323 }
4324
4325 long timeUntilNextReset = (resetLong - now) /*ms*/;
4326 // I was too lazy to properly print this earlier xd
4327 // Did fix this for AutoBanBot, feel free to copy over implementation from that.
4328 // TODO: Copy over date and time matching from AutoBanBot - that one's a more elegant solution (although this works)
4329 System.out.println("Time until reset in milliseconds: " + timeUntilNextReset);
4330
4331 timeUntilNextReset /= 1000;
4332 System.out.println("The current time is " + df.format(new Date(now)) + ". The next reset occurs at " +
4333 "" + df.format(new Date(resetLong)) + ". That is " + timeUntilNextReset + " seconds from now.");
4334
4335 // Schedules the task every day
4336 ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
4337 scheduler.scheduleAtFixedRate(() -> {
4338 // Check the activity table.
4339 TextChannel out = jda.getTextChannelById(BOT_STAFF);
4340 sendMessage(out, "Doing daily activity check!");
4341 // Update the activity table. Removes outdated entries (>1 month old)
4342 updateActivityTable();
4343 // Gets all users who haven't done a raid in the past 2 weeks.
4344 List<Long> inactiveUserIDs = getInactiveUsersFromActivity();
4345 // Get the guild
4346 Guild guild = jda.getGuildById(SBC);
4347 // If a database error occurs, just exit and try again tomorrow.
4348 if (inactiveUserIDs == null) {
4349 sendMessage(out, "An error occurred while getting the inactive users!");
4350 return;
4351 }
4352 // Get the IUser objects for each inactive user
4353 List<Member> members = inactiveUserIDs.stream().map(id -> guild.getMemberById(id)).collect(Collectors.toList());
4354 // Get the raid role, mod role
4355 Role raidRole = guild.getRoleById(ROLE_RAID);
4356 Role modRole = guild.getRoleById(ROLE_MOD);
4357 // Remove everyone with the mod role. They won't have their Raid role removed for inactivity.
4358 members.removeAll(guild.getMembersWithRoles(modRole));
4359 // Get all users
4360 List<Member> usersWithoutRaidRole = guild.getMembers();
4361 // Remove everyone with raid role
4362 usersWithoutRaidRole.removeAll(guild.getMembersWithRoles(raidRole));
4363 // Get all the users who have been marked as inactive who DO have raid role
4364 members.removeAll(usersWithoutRaidRole);
4365 sendMessage(out, "Users to remove for inactivity: " + members.size());
4366 sendMessages(members, (m -> {
4367 if (m == null) return "";
4368 String nickname = m.getEffectiveName();
4369 System.out.println("Removed user " + m + " (" + nickname + ")'s raid role for inactivity.");
4370 // Add to inactivity database
4371 addToInactiveUsers(m.getUser().getIdLong());
4372 // Send PM about inactivity
4373 sendPrivateMessage(m.getUser(), "Your Raid role has been removed for inactivity.\n\n" +
4374 "You received this message because you have not been in an SBC raid in over 2 weeks," +
4375 " which has resulted in a raid removal. Please re-apply in #register-here if you wish to have Raid role again." +
4376 " You'll be automatically given the Raid role once you've verified your Realmeye again" +
4377 " (and meet our joining requirements).");
4378 // Remove role
4379 guild.getController().removeSingleRoleFromMember(m, raidRole).queue();
4380 return "\n\t" + nickname;
4381 }), "**Users removed for inactivity** ", out, false);
4382 sendMessage(out, "Removed " + members.size() + " users for inactivity.");
4383
4384 }, timeUntilNextReset, 60 * 60 * 24, TimeUnit.SECONDS);
4385 }
4386
4387}