Page MenuHomedesp's stash

DiscordBot.java
No OneTemporary

DiscordBot.java

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<String> BannedID = new ArrayList<String>(); //DONE store in DB
public static List<String> ModID = new ArrayList<String>();
public static List<String> logExcemptID = new ArrayList<String>();
//TODO synchronize below or change to concurrent //but why? its thread safe since its read only
public static HashMap<String, Command> commands = new HashMap<String, Command>();
public static HashMap<String, Command> aliases = new HashMap<String, Command>(); //TODO temporary solution
public static TreeMap<String, List<Command>> catCmds = new TreeMap<String, List<Command>>(); //order base on categories?
public static ConcurrentHashMap<String, TextChannel> lastMusicCmd = new ConcurrentHashMap<String, TextChannel>(); //TODO store in guild configs //nah its fine
//public static HashMap<String, Game> guildMemberPresence = new HashMap<String, Game>();
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 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
} 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<String, String>) 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
ModID.add("165403578133905408");
ModID.add("187714189672841216");
ModID.add("204587986724192257"); // hyper
ModID.add("194112515859283968"); // bond
ModID.add("214270819793108993"); // emma
ModID.add("209883960522702848"); // M4
ModID.add("237546287388426242"); // haoiscoll
ModID.add("264612287048843264"); // kanade
ModID.add("254509414600409088"); // shii
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<String, List<Command>> entry : catCmds.entrySet()) {
pragma.setString(1, entry.getKey().toLowerCase());
ResultSet rs = pragma.executeQuery();
boolean isEmpty = true;
HashMap<String, EnumSet<Permission>> nodes = new HashMap<>();
Stack<Command> 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<String, EnumSet<Permission>> 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<String, EnumSet<Permission>> 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 <channelid>:<perm>|<perm> 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<Class<? extends Command>> classes = reflections.getSubTypesOf(Command.class);
for (Class<? extends Command> 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<Command> cmds = catCmds.get(cat);
if(cmds == null) {
cmds = new ArrayList<Command>(Arrays.asList(c));
} else {
cmds.add(c);
}
catCmds.put(cat, cmds);
// set command category?
commands.put(c.getName(), c);
List<String> 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<String, List<Command>> entry : catCmds.entrySet()) {
Collections.sort(entry.getValue(), (a, b) -> a.getName().compareTo(b.getName()));
}
}
}

File Metadata

Mime Type
text/x-c
Expires
Sat, Mar 15, 6:40 PM (1 d, 8 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
26/f0/2e968d02d649a94ef07d5bbb7e82

Event Timeline