diff --git a/.gitignore b/.gitignore index 13f2d81..eb331cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ bin/ target/ logs/ .metadata/ tokens.properties +eastereggs.properties testAnimePicCache.bin data.db hs_err_pid*.log diff --git a/src/me/despawningbone/discordbot/DiscordBot.java b/src/me/despawningbone/discordbot/DiscordBot.java index 513354f..a37ec66 100644 --- a/src/me/despawningbone/discordbot/DiscordBot.java +++ b/src/me/despawningbone/discordbot/DiscordBot.java @@ -1,286 +1,290 @@ package me.despawningbone.discordbot; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.StringJoiner; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import javax.security.auth.login.LoginException; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.PropertyConfigurator; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; //import com.sedmelluq.discord.lavaplayer.jdaudp.NativeAudioSendFactory; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import me.despawningbone.discordbot.command.Command; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.requests.GatewayIntent; public class DiscordBot { // TODO move to appropriate locations //public static List BannedID = new ArrayList(); //DONE store in DB public static List ModID = new ArrayList(); public static List logExcemptID = new ArrayList(); //TODO synchronize below or change to concurrent //but why? its thread safe since its read only public static HashMap commands = new HashMap(); public static HashMap aliases = new HashMap(); //TODO temporary solution public static TreeMap> catCmds = new TreeMap>(); //order base on categories? public static ConcurrentHashMap lastMusicCmd = new ConcurrentHashMap(); //TODO store in guild configs //nah its fine //public static HashMap guildMemberPresence = new HashMap(); public static final String prefix = "!desp "; //DONE allow guild change prefix? public static JDA mainJDA = null; - public static final String BotID = "311086271642599424"; - public static final String OwnerID = "237881229876133888"; + public static String BotID; + public static String OwnerID; public static Properties tokens = new Properties(); static final Logger logger = LoggerFactory.getLogger(DiscordBot.class); //package private public static HikariDataSource db; /*public static FirefoxDriver driver;*/ // DONE SQLite integration; check if program termination will screw up the connection // TODO SHARDING // DONE on shutdown alert those playing music or await? //alerted public static void main(String[] args) { // initiate Selenium /*System.setProperty("webdriver.gecko.driver", System.getProperty("user.dir") + File.separator + "geckodriver.exe"); FirefoxOptions options = new FirefoxOptions(); options.setHeadless(true); driver = new FirefoxDriver(options);*/ PropertyConfigurator.configure(System.getProperty("user.dir") + File.separator + "log4j.properties"); try(FileInputStream in = new FileInputStream(new File(System.getProperty("user.dir") + File.separator + "tokens.properties"))){ tokens.load(in); } catch (IOException e) { e.printStackTrace(); return; } initCmds(); initDB(); // login try { JDA jda = JDABuilder.create(tokens.getProperty("bot"), GatewayIntent.getIntents(GatewayIntent.ALL_INTENTS)) //.setAudioSendFactory(new NativeAudioSendFactory()) //segfaults frequently somehow, disabling .addEventListeners(new me.despawningbone.discordbot.EventListener()).build(); jda.getPresence().setActivity(Activity.watching("Ping me for info!")); // for user bots, see // https://discordapp.com/developers/docs/topics/gateway#update-status // and // https://gist.github.com/MrPowerGamerBR/b8acccb9951b0d78a5115024e3ee0d03 + + //find owner id from bot owner + BotID = jda.getSelfUser().getId(); + OwnerID = jda.retrieveApplicationInfo().complete().getOwner().getId(); } catch (LoginException e) { e.printStackTrace(); return; } // initiate logs //handled by log4j? File directory = new File(System.getProperty("user.dir") + File.separator + "logs" + File.separator); if (!directory.exists()) { directory.mkdir(); } //initiate test anime pics cache for repost check /*try { FileInputStream in = new FileInputStream(System.getProperty("user.dir") + File.separator + "testAnimePicCache.bin"); ObjectInputStream ois = new ObjectInputStream(in); EventListener.testAnimePicCache = (Multimap) ois.readObject(); ois.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> { //no need to be a global variable since i dont need to access it try { FileOutputStream out = new FileOutputStream(System.getProperty("user.dir") + File.separator + "testAnimePicCache.bin"); ObjectOutputStream oos = new ObjectOutputStream(out); oos.writeObject(EventListener.testAnimePicCache); oos.flush(); oos.close(); } catch (IOException e) { e.printStackTrace(); } }, 1, 5, TimeUnit.MINUTES);*/ // DEPRECATED //or is it? make unmodifiable instead for(String id : tokens.getProperty("botmod").split(",")) { ModID.add(id); } ModID.add(OwnerID); logExcemptID.add(BotID); } private static void initDB() { //init guild settings when do !desp settings for the first time? //dont really need coz im doing upsert for all values anyways HikariConfig dbConf = new HikariConfig(); dbConf.setJdbcUrl("jdbc:sqlite:data.db"); dbConf.setIdleTimeout(45000); dbConf.setMaxLifetime(60000); dbConf.setMaximumPoolSize(10); //dbConf.setMaximumPoolSize(25); //for deployment in server db = new HikariDataSource(dbConf); try (Connection con = db.getConnection()){ Statement s = con.createStatement(); s.execute("CREATE TABLE IF NOT EXISTS settings" + "(id INTEGER PRIMARY KEY," //performance problem using text; integer can handle long anyways + "prefix TEXT DEFAULT '!desp '," + "premium INTEGER DEFAULT 0," + "mchannel TEXT," //allow people to set this manually? By default, use last music cmd place + "locale TEXT DEFAULT 'EN'," //or int? //user specific or guild specific, or user override guild? + "shortcuts TEXT," //use another method? + "votepct INTEGER DEFAULT 50," + "looplimit INTEGER DEFAULT 1," + "volume INTEGER DEFAULT 100," //premium?, is default actually 100? + "helpdm INTEGER DEFAULT 0);"); //send help to dm or not, excluding cmd help(?) //add logexempt? PreparedStatement pragma = con.prepareStatement("SELECT name FROM pragma_table_info('perms_' || ?);"); //NOTE: sqlite does not support changing default values, beware when adding new commands for(Entry> entry : catCmds.entrySet()) { pragma.setString(1, entry.getKey().toLowerCase()); ResultSet rs = pragma.executeQuery(); boolean isEmpty = true; HashMap> nodes = new HashMap<>(); Stack tree = new Stack<>(); for(Command cmd : entry.getValue()) tree.push(cmd); //populate stack coz there is no root node while(!tree.empty()) { Command cmd = tree.pop(); String name = cmd.getName(); for(Command iterate = cmd.getParent(); iterate != null; iterate = iterate.getParent()) { name = iterate.getName() + "." + name; } nodes.put(name, cmd.getDefaultPerms()); for(String sub : cmd.getSubCommandNames()) { tree.push(cmd.getSubCommand(sub)); } } while(rs.next()) { isEmpty = false; nodes.remove(rs.getString("name")); //remove already added node names } if(isEmpty) { String create = "CREATE TABLE perms_" + entry.getKey().toLowerCase() + " (id INTEGER PRIMARY KEY, _GLOBAL_ TEXT DEFAULT '0\n', "; //perms for a category are always default no restraints StringJoiner join = new StringJoiner(", "); for(Entry> node : nodes.entrySet()) { join.add("\"" + node.getKey() + "\" TEXT DEFAULT '" + ((0L << 32) | (Permission.getRaw(node.getValue()) & 0xffffffffL)) + "\n'"); //initialize with no permission deny override, permissions allow //needs to be text so that channel overrides delimiter can be stored } create += join.toString() + ");"; s.execute(create); //DONE? create table } else { if(!nodes.isEmpty()) { //which means there is new subCmd/cmd for(Entry> node : nodes.entrySet()) { s.execute("ALTER TABLE perms_" + entry.getKey().toLowerCase() + " ADD COLUMN \"" + node.getKey() + "\" TEXT DEFAULT '" + ((0L << 32) | (Permission.getRaw(node.getValue()) & 0xffffffffL)) + "\n';"); //not able to prep statement coz its column name s.close(); } } } } pragma.close(); //DONE TEST generate the nodes, retrieve default from fields? //what about additional commands added, use ALTER TABLE ADD COLUMN DEFAULT? I dont need to sort it according to category at all, i am not and will not be bulk printing permissions anywhere //actually i might be coz of list edited perms //fit subnodes into sub tables, split by category so its more balanced (category as table name)? //fields are gonna be sth like :| etc? //DONE store the permissions with long merged; <32bit = deny, >32bit = allow s.execute("CREATE TABLE IF NOT EXISTS users" + "(id INTEGER PRIMARY KEY," + "reports TEXT DEFAULT ''," + "game TEXT);"); //disposable, i can change it anytime //add botmod field? if so, i can deprecate the hard coded list, but it would require querying to this table on perm checks //i need to query it to check if the user is banned anyways //subcmd need to query again, so i didnt do it; besides the modlist is gonna be too small to be significant anyways //add user specific locale? might be confusing s.close(); } catch (SQLException e1) { e1.printStackTrace(); } } private static void initCmds() { Reflections reflections = new Reflections("me.despawningbone.discordbot.command"); Set> classes = reflections.getSubTypesOf(Command.class); for (Class s : classes) { try { if (Modifier.isAbstract(s.getModifiers())) { continue; } String pkName = s.getPackage().getName(); String cat = StringUtils.capitalize(pkName.substring(pkName.lastIndexOf(".") + 1)); //if(cat.equals("Osu")) cat = "osu!"; Command c = s.getConstructor().newInstance(); //DONE use constructor to put name and cat instead if (c.isDisabled()) { continue; } try { //init name and cat Field nameF = Command.class.getDeclaredField("name"); nameF.setAccessible(true); if(nameF.get(c) == null) nameF.set(c, s.getSimpleName().toLowerCase()); //allow overrides Field catF = Command.class.getDeclaredField("cat"); catF.setAccessible(true); catF.set(c, cat); } catch (NoSuchFieldException e) { //should never throw e.printStackTrace(); } // check disable whole command category? List cmds = catCmds.get(cat); if(cmds == null) { cmds = new ArrayList(Arrays.asList(c)); } else { cmds.add(c); } catCmds.put(cat, cmds); // set command category? commands.put(c.getName(), c); List l = c.getAliases(); if(l != null) { for(int i = 0; i < l.size(); i++) { aliases.put(l.get(i), c); } } } catch (ReflectiveOperationException e) { e.printStackTrace(); } } for(Entry> entry : catCmds.entrySet()) { Collections.sort(entry.getValue(), (a, b) -> a.getName().compareTo(b.getName())); } } } diff --git a/src/me/despawningbone/discordbot/EventListener.java b/src/me/despawningbone/discordbot/EventListener.java index 88e9434..96b5091 100644 --- a/src/me/despawningbone/discordbot/EventListener.java +++ b/src/me/despawningbone/discordbot/EventListener.java @@ -1,441 +1,416 @@ package me.despawningbone.discordbot; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Properties; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.exception.ExceptionUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import me.despawningbone.discordbot.command.music.AudioTrackHandler; import me.despawningbone.discordbot.command.music.GuildMusicManager; import me.despawningbone.discordbot.command.music.Music; import me.despawningbone.discordbot.utils.MiscUtils; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.MessageBuilder; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.GuildVoiceState; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.VoiceChannel; import net.dv8tion.jda.api.entities.Message.Attachment; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMoveEvent; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent; import net.dv8tion.jda.api.events.user.UserActivityStartEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; public class EventListener extends ListenerAdapter { public static Multimap testAnimePicCache = ArrayListMultimap.create(); @Override public void onGuildMessageReceived(GuildMessageReceivedEvent event) { DiscordBot.mainJDA = event.getJDA(); // refresh it? //no need, everything's getJDA() is the same jda basis but audioplayer's still using it; need to migrate to store in guildmusicmanager //String[] words = event.getMessage().getContentDisplay().split(" "); User author = event.getAuthor(); TextChannel channel = event.getChannel(); Message msg = event.getMessage(); //checkAnimePics(event); //DONE use log4j //logging if (!DiscordBot.logExcemptID.contains(author.getId())) { String guildinfo = "[" + event.getGuild().getName() + " #" + channel.getName() + "]"; String payload = "[INFO] " + guildinfo + System.lineSeparator() + " " + msg + System.lineSeparator() + " Full msg: " + msg.getContentDisplay(); List att = event.getMessage().getAttachments(); if (!att.isEmpty()) { payload += System.lineSeparator() + " Attachments:"; for (int i = 0; i < att.size(); i++) { payload += System.lineSeparator() + " " + att.get(i).getUrl(); } } DiscordBot.logger.trace(payload); //TODO log embeds and add user id too? } /*if (String.join("", words).equalsIgnoreCase("!despacito")) { channel.sendMessage("https://www.youtube.com/watch?v=W3GrSMYbkBE").queue(); return; }*/ CommandResult result = null; try (Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()){ String prefix = MiscUtils.getPrefix(s, event.getGuild().getId()); if(msg.getContentDisplay().toLowerCase().startsWith(prefix.toLowerCase())) { String msgStripped = msg.getContentDisplay().substring(prefix.length()).replaceAll("\\s\\s+", " "); //merges space String[] args = msgStripped.split(" "); // base on command length? Command cmd = DiscordBot.commands.get(args[0].toLowerCase()); cmd = cmd == null ? DiscordBot.aliases.get(args[0].toLowerCase()) : cmd; if (cmd != null) { ResultSet uRs = s.executeQuery("SELECT reports FROM users WHERE id = " + author.getId() + ";"); if(uRs.next()) { //checks null at the same time if(uRs.getString(1).split("\n").length >= 5) { channel.sendMessage("You are banned from using the bot.").queue(); DiscordBot.logger.info("[WARN] " + author.getName() + " (" + author.getId() + ") tried to execute " + msg.getContentDisplay() + " but was banned."); return; } } uRs.close(); long perm = MiscUtils.getActivePerms(s, channel, cmd); //result = cmd.execute(channel, author, msg, args); String perms = cmd.hasSubCommand() ? null : MiscUtils.getMissingPerms(perm, cmd.getRequiredBotUserLevel(), event.getMember(), channel); //pass it to the subcommand handler to handle instead if(perms == null || event.getAuthor().getId().equals(DiscordBot.OwnerID)) { //owner overrides perms for convenience /*if(cmd.isDisabled(channel.getGuild().getId())) { channel.sendMessage("This command is disabled!").queue(); result = new CommandResult(CommandResultType.FAILURE, "Disabled command"); } else {*/ cmd.executeAsync(channel, author, msg, Arrays.copyOfRange(args, 1, args.length), r -> { //catch all exceptions? //should have actually DiscordBot.logger.info("[" + r.getResultType() + "] " + author.getName() + " (" + author.getId() + ") executed " + msg.getContentDisplay() + (r.getRemarks() == null ? "." : ". (" + r.getRemarks() + ")")); //logging has to be before sendMessage, or else if no permission it will just quit if(r.getMessage() != null) channel.sendMessage(r.getMessage()).queue(); }); //dont know if async will screw anything up //wont, TODO log date and which server executed the command also? return; //} } else if(perms.equals("DISABLED") || cmd.isDisabled()) { msg.addReaction("❎").queue(); result = new CommandResult(CommandResultType.DISABLED); } else { result = new CommandResult(CommandResultType.NOPERMS, perms); channel.sendMessage(result.getMessage()).queue(); } } else { result = new CommandResult(CommandResultType.FAILURE, "Invalid command"); //do more stuff? } } else if(msg.getContentRaw().matches("<@!?" + DiscordBot.BotID + ">")) result = greet(channel, author, prefix); } catch (SQLException e) { result = new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } if(result != null) DiscordBot.logger.info("[" + result.getResultType() + "] " + author.getName() + " (" + author.getId() + ") executed " + msg.getContentDisplay() + (result.getRemarks() == null ? "." : ". (" + result.getRemarks() + ")")); } @SuppressWarnings("unused") private void checkAnimePics(GuildMessageReceivedEvent event) { //TESTING CompletableFuture.runAsync(() -> { User author = event.getAuthor(); TextChannel channel = event.getChannel(); Message msg = event.getMessage(); List pics = new ArrayList<>(); if(channel.getId().equals("615396635383562272") && !DiscordBot.BotID.equals(author.getId())) { for(Attachment att : msg.getAttachments()) { if(att.isImage()) pics.add(att.getUrl()); } /*if(msg.getContentDisplay().contains("http")) { for(String s : msg.getContentDisplay().split("http")) { try { new URL("http" + s); pics.add("http" + s); } catch(MalformedURLException e) { ; } } }*/ try { Thread.sleep(1500); msg = channel.retrieveMessageById(msg.getId()).complete(); } catch (InterruptedException e2) { e2.printStackTrace(); } for(MessageEmbed em : msg.getEmbeds()) { //System.out.println(em.toJSONObject()); if(em.getThumbnail() != null) { System.out.println("thumb"); if(em.getSiteProvider() != null && em.getSiteProvider().getName().equals("pixiv")) { //pixiv doesnt show whole pic in embed pics.add(em.getUrl()); } else { pics.add(em.getThumbnail().getUrl()); } } else if(em.getImage() != null && !author.isBot()) { System.out.println("img"); pics.add(em.getImage().getUrl()); } else { System.out.println("url"); pics.add(em.getUrl()); } } //System.out.println(pics); //System.out.println(testAnimePicCache); for(String pic : pics) { //handle first since these mustnt be the same urls, therefore cache saucenao sauce instead String origPic = pic; if(!testAnimePicCache.containsValue(pic)) { Element saucenao; try { saucenao = Jsoup.connect("https://saucenao.com/search.php?url=" + pic).get().body(); Element result = saucenao.selectFirst(".resulttable"); try { //normal pixiv/deviantart handling if(result.parent().attr("class").equals("result hidden")) throw new NullPointerException(); Element source = result.selectFirst("strong:contains(ID:)").nextElementSibling(); pic = source.attr("href"); //will not run if the cache has the value || if the link specified aint a pic } catch (NullPointerException e1) { //weird saucenao card formatting (eg episode info), or no result; we dont handle these ; } } catch (IOException e) { e.printStackTrace(); } } System.out.println("next" + testAnimePicCache); if(testAnimePicCache.containsValue(pic)) { try { Multimap temp = ArrayListMultimap.create(); System.out.println("temp"); Message m = channel.retrieveMessageById(Multimaps.invertFrom(testAnimePicCache, temp).get(pic).toArray(new String[1])[0]).complete(); EmbedBuilder eb = new EmbedBuilder(); eb.setTitle("Repost detected!"); eb.setDescription("[This image](" + origPic + ")" + (origPic.equals(pic) ? "" : "([source](" + pic + "))") + " is a repost of [this message](" + m.getJumpUrl() + ") by `" + m.getAuthor().getName() + "#" + m.getAuthor().getDiscriminator() + "` at **" + m.getTimeCreated().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "**.\n"); eb.setThumbnail(origPic); channel.sendMessage(eb.build()).queue(); continue; } catch(NullPointerException e) { //if the message is deleted e.printStackTrace(); } } else { System.out.println("put"); testAnimePicCache.put(msg.getId(), pic); } } } }).whenComplete((r, t) -> {if(t != null) t.printStackTrace();}); } + private Properties greetEasterEggs = new Properties(); + { + try(FileInputStream in = new FileInputStream(new File(System.getProperty("user.dir") + File.separator + "eastereggs.properties"))){ + greetEasterEggs.load(in); + } catch (IOException e) { + e.printStackTrace(); + } + } + private CommandResult greet(TextChannel channel, User author, String prefix) { MessageBuilder smsg = new MessageBuilder(); String nick = channel.getGuild().getMemberById(author.getId()).getNickname(); if (nick != null) { smsg.append("Yo " + nick + "!\n"); } else { smsg.append("Yo " + author.getName() + "!\n"); } - if (author.getId().equals("237881229876133888")) { // easter eggs - smsg.append("How art thou, my creator?"); - } - if (author.getId().equals("179824176847257600")) { - smsg.append("Still stalking as usual, huh? :smirk:"); - smsg.append("\n\"go fix your potatoes\" - pugger"); - } - if (author.getId().equals("165403578133905408")) { - smsg.append( - "You're probably still dead :D (your status), either that or you are playing with your doodle :P"); - } - if (author.getId().equals("187714189672841216")) { - smsg.append("Hello, fearsome coder named Morgan :wink:"); - } - if (author.getId().equals("201768560345743360")) { - smsg.append("Still need help to save you from the school? :D"); - } - if (author.getId().equals("257660112703979521")) { - smsg.append("I like how you only uses me for `!desp roll 1`. :P"); - } - if (author.getId().equals("272712701988569090")) { - smsg.append("This guy is generous :smile:"); - } - if (author.getId().equals("203861130995695616")) { - smsg.append("He asked my owner to change it so here it is :smile:"); - } - if (author.getId().equals("206038522971422721")) { - smsg.append("Appearently he loves !desp idiot :face_palm:"); - } - if (author.getId().equals("218377806994866176")) { - smsg.append("Your potatoes seems better than Dank's :smirk:"); - } - if (author.getId().equals("139316803582033920")) { - smsg.append("That ironic name tho :stuck_out_tongue:"); - } - if (author.getId().equals("237058431272484864")) { - smsg.append(":b:oi"); - } - if (author.getId().equals("338258756762730496")) { - smsg.append("**a e s t h e t i c**"); + String easterEgg = greetEasterEggs.getProperty(author.getId()); + if(easterEgg != null) { + smsg.append(easterEgg.replace("\\n", "\n") + "\n"); } EmbedBuilder eb = new EmbedBuilder(); eb.setColor(0x051153); //TODO version info based on git commits - eb.appendDescription("This bot is running **despbot v1.5.0**, Shard `" + DiscordBot.mainJDA.getShardInfo().getShardString() + "`. ([invite me!](https://discordapp.com/oauth2/authorize?&client_id=311086271642599424&scope=bot&permissions=0))\n"); + eb.appendDescription("This bot is running **despbot v1.5.0**, Shard `" + DiscordBot.mainJDA.getShardInfo().getShardString() + "`. ([invite me!](https://discordapp.com/oauth2/authorize?&client_id=" + DiscordBot.BotID + "&scope=bot&permissions=0))\n"); eb.appendDescription("Connected guilds: `" + DiscordBot.mainJDA.getGuildCache().size() + (DiscordBot.mainJDA.getShardManager() == null ? "" : "/" + DiscordBot.mainJDA.getShardManager().getShards().stream().mapToLong(jda -> jda.getGuildCache().size()).sum()) + "`; "); eb.appendDescription("Total members (cached): `" + DiscordBot.mainJDA.getUserCache().size() + (DiscordBot.mainJDA.getShardManager() == null ? "" : "/" + DiscordBot.mainJDA.getShardManager().getShards().stream().mapToLong(jda -> jda.getUserCache().size()).sum()) + "`\n"); - eb.appendDescription("DM `despawningbone#4078` if you have any questions!\n"); + eb.appendDescription("DM `" + DiscordBot.mainJDA.getUserById(DiscordBot.OwnerID).getAsTag() + "` if you have any questions!\n"); eb.appendDescription("To get a list of commands, do `" + prefix + "help`."); smsg.setEmbed(eb.build()); Message fmsg = smsg.build(); channel.sendMessage(fmsg).queue(); return new CommandResult(CommandResultType.SUCCESS, null); } @Override public void onGuildMessageUpdate(GuildMessageUpdateEvent event) { //System.out.println("edit"); //debug Message msg = event.getMessage(); User author = event.getAuthor(); TextChannel channel = event.getChannel(); if (!DiscordBot.logExcemptID.contains(author.getId())) { String guildinfo = "[" + event.getGuild().getName() + " #" + channel.getName() + "]"; DiscordBot.logger.trace("[EDIT] " + guildinfo + System.lineSeparator() + " " + msg + System.lineSeparator() + " Full edited msg: " + msg.getContentDisplay()); } } @Override public void onUserActivityStart(UserActivityStartEvent event) { //store presence for checking osu pp Activity osu = event.getNewActivity(); if (osu != null && osu.getName().equals("osu!") && osu.isRich() //if need to include other games and details, just remove this if clause and change sGame below && osu.asRichPresence().getDetails() != null) { String toolTip = osu.asRichPresence().getLargeImage().getText(); //DiscordBot.guildMemberPresence.put(event.getUser().getId(), game); //so that game update to nothing wont be logged /*System.out.println(event.getGuild().getName()); System.out.println(event.getUser().getName()); System.out.println(game.getName()); System.out.println(game.getUrl()); System.out.println(game.getType()); System.out.println(game.isRich() ? game.asRichPresence().getDetails() : "no rich"); System.out.println(game.isRich() ? game.asRichPresence().getState() : "no rich");*/ //OffsetDateTime timesent = OffsetDateTime.now(); //System.out.println(toolTip); String sGame = (toolTip.lastIndexOf(" (") != -1 ? toolTip.substring(0, toolTip.lastIndexOf(" (")) : toolTip) + "||" + osu.asRichPresence().getDetails(); //sGame = sGame.replaceAll("'", "''"); /*OffsetDateTime timeReceived = OffsetDateTime.now(); long ms = Math.abs(timesent.until(timeReceived, ChronoUnit.MILLIS)); System.out.println("Time taken: " + ms + "ms");*/ try (Connection con = DiscordBot.db.getConnection()) { //DONE do i need to close the statement? PreparedStatement s = con.prepareStatement("INSERT INTO users(id, game) VALUES (" + event.getUser().getId() + ", ?) ON CONFLICT(id) DO UPDATE SET game = ? WHERE game <> ?;" ); //prevent blank updates s.setString(1, sGame); s.setString(2, sGame); s.setString(3, sGame); s.execute(); //con.createStatement().execute("INSERT INTO users(id, game) VALUES (" + event.getUser().getId() + ", '" + sGame + "') ON CONFLICT(id) DO UPDATE SET game = '" + sGame + "' WHERE game <> '" + sGame + "';" ); //prevent blank updates } catch (SQLException e) { //FIXED if i make this not osu only, be aware of SQL injections through sGame (probably being paranoid tho) e.printStackTrace(); } /*timeReceived = OffsetDateTime.now(); ms = Math.abs(timesent.until(timeReceived, ChronoUnit.MILLIS)); System.out.println("Time taken: " + ms + "ms");*/ } } //TODO only update when not paused? //TODO update on member deafen? private void waitActivity(Guild guild) { AudioTrackHandler ap = ((Music) DiscordBot.commands.get("music")).getAudioTrackHandler(); ap.getGuildMusicManager(guild).player.setPaused(true); ap.getGuildMusicManager(guild).clearQueueCleanup = ap.ex.schedule(() -> { ap.stopAndClearQueue(guild); DiscordBot.lastMusicCmd.get(guild.getId()).sendMessage("The queue has been cleared.").queue(); ap.getGuildMusicManager(guild).clearQueueCleanup = null; }, 1, TimeUnit.MINUTES); } private String updateActivity(Guild guild, VoiceChannel vc) { AudioTrackHandler ap = ((Music) DiscordBot.commands.get("music")).getAudioTrackHandler(); GuildMusicManager mm = ap.getGuildMusicManager(guild); if(mm.clearQueueCleanup != null) { mm.player.setPaused(false); mm.clearQueueCleanup.cancel(true); mm.clearQueueCleanup = null; } String type = mm.scheduler.loop; //mm cannot be null if (type != null && vc.getMembers().size() > 2 && vc.getMembers().contains(guild.getMemberById(DiscordBot.OwnerID))) { ap.toggleLoopQueue(guild, type); return type; } return null; } @Override public void onGuildVoiceLeave(GuildVoiceLeaveEvent event) { // theoretically i dont need to check if lastMusicCmd has the entry or not, as it must have one to trigger this GuildVoiceState vs = event.getGuild().getMemberById(DiscordBot.BotID).getVoiceState(); if(!event.getMember().getUser().getId().equals(DiscordBot.BotID)) { if (vs.inVoiceChannel()) { if (vs.getChannel().equals(event.getChannelLeft()) && event.getChannelLeft().getMembers().size() < 2) { TextChannel channel = DiscordBot.lastMusicCmd.get(event.getGuild().getId()); channel.sendMessage( "All users have left the music channel, the player is now paused.\nThe queue will be cleared in 1 minute if there is no activity.") .queue(); waitActivity(event.getGuild()); } } } else { //got kicked AudioTrackHandler ap = ((Music) DiscordBot.commands.get("music")).getAudioTrackHandler(); if(ap != null) { //if not destroyed ap.stopAndClearQueue(event.getGuild()); //can double fire on normal end; shouldnt be too big of a problem } } } @Override public void onGuildVoiceMove(GuildVoiceMoveEvent event) { // theoretically i dont need to check if lastMusicCmd has the entry or not, as it must have one to trigger this GuildVoiceState vs = event.getGuild().getMemberById(DiscordBot.BotID).getVoiceState(); if (vs.inVoiceChannel()) { String id = event.getGuild().getId(); TextChannel channel = DiscordBot.lastMusicCmd.get(id); if (!event.getMember().getUser().getId().equals(DiscordBot.BotID)) { if (vs.getChannel().equals(event.getChannelLeft())) { if (!event.getMember().getUser().getId().equals(DiscordBot.BotID) && event.getChannelLeft().getMembers().size() < 2) { channel.sendMessage( "All users have left the music channel, the player is now paused.\nThe queue will be cleared in 1 minute if there is no activity.") .queue(); waitActivity(event.getGuild()); } } else if (vs.getChannel().equals(event.getChannelJoined())) { String type = updateActivity(event.getGuild(), event.getChannelJoined()); if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue(); } } else { //moved bot to empty channel if(event.getChannelJoined().getMembers().size() < 2) { channel.sendMessage( "The bot has been moved to an empty channel, the player is now paused.\nThe queue will be cleared in 1 minute if there is no activity.") .queue(); waitActivity(event.getGuild()); } else { //moved bot to channel with ppl event.getGuild().getAudioManager().openAudioConnection(event.getChannelJoined()); //seems to need explicit reconnect on bot move, it gets stuck on attempting to reconnect otherwise; probably would be fixed soon but hot patch for now String type = updateActivity(event.getGuild(), event.getChannelJoined()); if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue(); } } } } @Override public void onGuildVoiceJoin(GuildVoiceJoinEvent event) { GuildVoiceState vs = event.getGuild().getMemberById(DiscordBot.BotID).getVoiceState(); String id = event.getGuild().getId(); TextChannel channel = DiscordBot.lastMusicCmd.get(id); if(channel != null) { if (vs.inVoiceChannel() && !event.getMember().getUser().getId().equals(DiscordBot.BotID)) { if (vs.getChannel().equals(event.getChannelJoined())) { String type = updateActivity(event.getGuild(), event.getChannelJoined()); if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue(); } } } } } diff --git a/src/me/despawningbone/discordbot/command/admin/UserReport.java b/src/me/despawningbone/discordbot/command/admin/UserReport.java index d36f349..f9f846c 100644 --- a/src/me/despawningbone/discordbot/command/admin/UserReport.java +++ b/src/me/despawningbone/discordbot/command/admin/UserReport.java @@ -1,183 +1,183 @@ package me.despawningbone.discordbot.command.admin; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.exception.ExceptionUtils; import me.despawningbone.discordbot.DiscordBot; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class UserReport extends Command { public UserReport() { this.alias = Arrays.asList("ureport"); this.desc = "Report a user if they are abusing the bot!"; this.usage = ""; this.remarks = Arrays.asList("Note: 5 global reports will result in a ban from the bot."); - this.examples = Arrays.asList(DiscordBot.OwnerID, "despawningbone"); + this.examples = Arrays.asList("311086271642599424", "despawningbone"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if (args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Please specify which user you want to report."); } else { String SID = null; boolean success = true; String pname = String.join(" ", args); long ID = 0; try { ID = Long.parseLong(pname); } catch (NumberFormatException e) { success = false; // System.out.println(pname); //debug List pm = channel.getGuild().getMembersByEffectiveName(pname, true); if (pm.size() <= 0) { return new CommandResult(CommandResultType.FAILURE, "There is no such user."); } else if (pm.size() > 1) { return new CommandResult(CommandResultType.FAILURE, "Theres more than 1 user with the same name. Please use !desp ID to get the ID of the user you want to report."); } else { SID = pm.get(0).getUser().getId(); ID = Long.parseLong(SID); } } if (success == true) { SID = Long.toString(ID); } if (author.getId().equals(SID)) { return new CommandResult(CommandResultType.FAILURE, "You cannot report yourself you dumbo :stuck_out_tongue:"); } else if (DiscordBot.ModID.contains(SID) || SID.equals(DiscordBot.BotID)) { return new CommandResult(CommandResultType.FAILURE, "That user cannot be reported."); } try (Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()) { String reports = ""; ResultSet uRs = s.executeQuery("SELECT reports FROM users WHERE id = " + SID + ";"); if(uRs.next()) { reports = uRs.getString(1); if(reports.contains(author.getId())) { return new CommandResult(CommandResultType.FAILURE, "You cannot report a user more than once."); //make it so that its toggling the reports instead? } else if(reports.split("\n").length < 5){ reports += author.getId() + "\n"; } else { return new CommandResult(CommandResultType.FAILURE, "The user has already been banned!"); } } else { reports = author.getId() + "\n"; } PreparedStatement set = con.prepareStatement("INSERT INTO users(id, reports) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET reports = ?" ); set.setString(1, SID); set.setString(2, reports); set.setString(3, reports); int update = set.executeUpdate(); if(update != 0) { int times = reports.split("\n").length; channel.sendMessage("You have successfully reported <@!" + ID + ">.\nThe user has been reported for " + times + "/5 times.\n").queue(); if(times >= 5) { channel.sendMessage("The user <@!" + ID + "> is now banned from using the bot.").queue(); } return new CommandResult(CommandResultType.SUCCESS); } else { throw new IllegalArgumentException("This should never happen"); } } catch (SQLException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } /*for (int i = 0; i < DiscordBot.reportedlist.size(); i++) { String[] randrp = DiscordBot.reportedlist.get(i).split(" "); if (randrp[0].contains(author.getId()) && randrp[1].contains(SID)) { return new CommandResult(CommandResultType.FAILURE, "You cannot report a user more than once."); } } boolean banned = false; boolean contains = false; for (int i = 0; i < DiscordBot.playerreportlist.size(); i++) { if (DiscordBot.playerreportlist.get(i).contains(SID)) { contains = true; } } if (contains == false) { DiscordBot.playerreportlist.add(SID + " " + "1"); times = 1; FileWriter fileWriter = null; BufferedWriter bufferedWriter; try { fileWriter = new FileWriter(DiscordBot.pfile, true); } catch (IOException e1) { e1.printStackTrace(); } try { bufferedWriter = new BufferedWriter(fileWriter); bufferedWriter.write(SID + " " + "1" + "\r\n"); bufferedWriter.close(); } catch (IOException e) { e.printStackTrace(); } } else { // System.out.println("Got record");//debug for (int i = 0; i < DiscordBot.playerreportlist.size(); i++) { String[] pandt = DiscordBot.playerreportlist.get(i).split(" "); if (pandt[0].contains(SID)) { times = Long.parseLong(pandt[1]); // System.out.println(times); //debug if (times < 5) { times = times + (long) 1; // System.out.println(times); //debug if (times == 5) { DiscordBot.BannedID.add(Long.toString(ID)); banned = true; } DiscordBot.playerreportlist.set(i, SID + " " + Long.toString(times)); Path fp = Paths.get(System.getProperty("user.dir"), "PlayerReports.txt"); try { List fileContent = new ArrayList<>( Files.readAllLines(fp, StandardCharsets.UTF_8)); for (int f = 0; f < fileContent.size(); f++) { if (fileContent.get(f).startsWith(SID)) { fileContent.set(f, SID + " " + Long.toString(times)); break; } } Files.write(fp, fileContent, StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); } break; } else { if (!DiscordBot.BannedID.contains(String.valueOf(ID))) { DiscordBot.BannedID.add(Long.toString(ID)); } return new CommandResult(CommandResultType.FAILURE, "The user <@!" + ID + "> had enough reports therefore he is banned from using this bot."); } } } } DiscordBot.reportedlist.add(author.getId() + " " + SID); channel.sendMessage("You have successfully reported <@!" + ID + ">.\nThe user has been reported for " + times + "/5 times.\n").queue(); String addinfo = null; if (banned == true) { channel.sendMessage("The user <@!" + ID + "> is now banned from using the bot.").queue(); addinfo = channel.getGuild().getMemberById(SID).getEffectiveName() + " (" + SID + ") is banned from the bot."; } return new CommandResult(CommandResultType.SUCCESS, addinfo);*/ } } }