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