Page MenuHomedesp's stash

No OneTemporary

diff --git a/src/me/despawningbone/discordbot/DiscordBot.java b/src/me/despawningbone/discordbot/DiscordBot.java
index b7f2370..513354f 100644
--- a/src/me/despawningbone/discordbot/DiscordBot.java
+++ b/src/me/despawningbone/discordbot/DiscordBot.java
@@ -1,292 +1,286 @@
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
+ 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<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()));
}
}
}
diff --git a/src/me/despawningbone/discordbot/command/info/Calculator.java b/src/me/despawningbone/discordbot/command/info/Calculator.java
index b0fe808..1ab2247 100644
--- a/src/me/despawningbone/discordbot/command/info/Calculator.java
+++ b/src/me/despawningbone/discordbot/command/info/Calculator.java
@@ -1,218 +1,227 @@
package me.despawningbone.discordbot.command.info;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EmptyStackException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.math3.special.Gamma;
import org.json.JSONObject;
import org.json.JSONTokener;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
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.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import net.objecthunter.exp4j.function.Function;
import net.objecthunter.exp4j.operator.Operator;
public class Calculator extends Command {
public Calculator() {
this.alias = Arrays.asList("calc");
this.desc = "Do some calculations!";
this.usage = "[-w] <operation>";
this.remarks = Arrays.asList("This calculator is using exp4j.",
"You can get the built in operators and functions at:", "http://projects.congrace.de/exp4j/",
"`!` - factorials", "`logx(base, num)` - logarithm (base x)", "are supported too.",
"\nYou can also query [Wolfram Alpha](https://www.wolframalpha.com/) using the `-w` switch.");
- this.examples = Arrays.asList("3*4-2", "4 * (sin(3 - 5)) + 5!", "log(e) + logx(10, 100)");
+ this.examples = Arrays.asList("3*4-2", "4 * (sin(3 - 5)) + 5!", "log(e) + logx(10, 100)", " -w laurent series log(x) about x=0");
}
Function logb = new Function("logx", 2) {
@Override
public double apply(double... args) {
return Math.log(args[1]) / Math.log(args[0]);
}
};
Function digamma = new Function("digamma", 1) {
@Override
public double apply(double... args) {
return Gamma.digamma(args[0]);
}
};
Operator factorial = new Operator("!", 2, true, Operator.PRECEDENCE_POWER + 1) {
@Override
public double apply(double... args) {
final long arg = (long) args[0];
if ((double) arg != args[0]) {
throw new IllegalArgumentException("Operand for factorial has to be an integer");
}
if (arg < 0) {
throw new IllegalArgumentException("The operand of the factorial can not " + "be " + "less than zero");
}
double result = 1;
for (int i = 1; i <= arg; i++) {
result *= i;
}
return result;
}
};
@Override
public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) {
List<String> params = new ArrayList<>(Arrays.asList(args));
if(params.removeAll(Collections.singleton("-w"))) {
return queryWolfram(String.join(" ", params), channel);
} else {
if (args.length < 1) {
return new CommandResult(CommandResultType.INVALIDARGS, "Please enter an operation.");
}
String splitted = String.join(" ", args);
if (msg.getContentDisplay().toLowerCase().contains("the meaning of life, the universe, and everything")) { // easter egg
channel.sendMessage("The answer is: `42`").queue();
return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg");
}
if (splitted.equals("9+10") || splitted.equals("9 + 10")) { // easter egg
channel.sendMessage("The answer is: `21`\n\n *i am smart ;)*").queue();
return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg");
}
if (splitted.equals("666") || splitted.equals("333")) { // easter egg
channel.sendMessage("No you don't").queue();
return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg");
} else {
String operation = String.join("", args);
if (operation.contains("!")) {
int index = operation.indexOf("!");
while (index >= 0) { // indexOf returns -1 if no match found
operation = new StringBuilder(operation).insert(index + 1, "(1)").toString();
index = operation.indexOf("!", index + 1);
}
}
DecimalFormat format = new DecimalFormat();
format.setDecimalSeparatorAlwaysShown(false);
// System.out.println(operation);
Expression e = null;
String ans = null;
double planckConstant = 6.62607004 * Math.pow(10, -34);
double eulerMascheroni = 0.57721566490153286060651209008240243104215933593992;
try {
e = new ExpressionBuilder(operation).variable("h").variable("γ").function(digamma).function(logb).operator(factorial).build()
.setVariable("h", planckConstant).setVariable("γ", eulerMascheroni);
ans = Double.toString(e.evaluate());
// String ans = format.format(e.evaluate());
} catch (EmptyStackException e1) {
return new CommandResult(CommandResultType.INVALIDARGS, "You have imbalanced parentheses.");
} catch (ArithmeticException e1) {
return new CommandResult(CommandResultType.INVALIDARGS, "You cannot divide by zero.");
} catch (IllegalArgumentException e1) {
return new CommandResult(CommandResultType.FAILURE, "An error has occured: " + e1.getMessage());
}
if (ans.equals("NaN")) {
ans = "Undefined";
}
channel.sendMessage("The answer is: `" + ans + "`").queue();
return new CommandResult(CommandResultType.SUCCESS);
}
}
}
public CommandResult queryWolfram(String operation, TextChannel channel) {
+ channel.sendTyping().queue();
try {
CompletableFuture<CommandResult> result = new CompletableFuture<>();
WebSocket socket = new WebSocketFactory().createSocket("wss://www.wolframalpha.com/n/v1/api/fetcher/results");
ArrayList<Integer> pos = new ArrayList<>(); //prevent duplicate
socket.addListener(new WebSocketAdapter() {
@Override
public void onTextMessage(WebSocket websocket, String message) {
try {
//System.out.println(message);
JSONObject resp = new JSONObject(new JSONTokener(message));
switch(resp.getString("type")) {
case "pods":
for(Object obj : resp.getJSONArray("pods")) { //pods might have multiple values
JSONObject pod = (JSONObject) obj;
+ if(pod.getBoolean("error")) continue; //skip errors
+
if(!pos.contains(pod.getInt("position"))) { //check dupe
//build big image for each pods
ArrayList<BufferedImage> images = new ArrayList<>();
int width = 0, height = 0;
if(!pod.has("subpods")) continue; //ignore empty pods that doesnt have image, usually fixed somewhere else
for(Object subobj : pod.getJSONArray("subpods")) {
JSONObject subpodImg = ((JSONObject) subobj).getJSONObject("img");
images.add(ImageIO.read(new URL(subpodImg.getString("src"))));
if(subpodImg.getInt("width") > width) width = subpodImg.getInt("width"); //get widest image and use it as final width
height += subpodImg.getInt("height"); //add all images
}
//create final image
BufferedImage podImg = new BufferedImage(width + 20, height + 20, BufferedImage.TYPE_INT_RGB); //padding
Graphics g = podImg.getGraphics();
g.setColor(new Color(255, 255, 255)); //fill as white first
g.fillRect(0, 0, width + 20, height + 20);
int y = 10;
for(BufferedImage img : images) {
g.drawImage(img, 10, y, null);
y += img.getHeight();
}
//send each pod as an individual message
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(podImg, "png", os);
channel.sendMessage(pod.getString("title") + ":")
.addFile(new ByteArrayInputStream(os.toByteArray()), "result.png").queue();
pos.add(pod.getInt("position")); //update to prevent dupes
}
}
break;
+ case "futureTopic":
+ result.complete(new CommandResult(CommandResultType.FAILURE, "This query was identified under the topic of `" + resp.getJSONObject("futureTopic").getString("topic") + "`; Development of this topic is under investigation..."));
+ break;
case "didyoumean":
- result.complete(new CommandResult(CommandResultType.INVALIDARGS, "No results found :cry:\nDid you mean `" + resp.getJSONArray("didyoumean").getJSONObject(0).getString("val") + "`?"));
+ result.complete(new CommandResult(CommandResultType.INVALIDARGS, "No good results found :cry:\nDid you mean `" + resp.getJSONArray("didyoumean").getJSONObject(0).getString("val") + "`?"));
+ break;
+ case "noResult":
+ result.complete(new CommandResult(CommandResultType.NORESULT));
break;
- case "queryComplete":
+ case "queryComplete": //might provide no output for certain queries, but theres no practical way to detect with this listener rn
websocket.disconnect();
result.complete(new CommandResult(CommandResultType.SUCCESS)); //if its preceded by anything it wouldnt update; thats how complete() works
break;
}
} catch(Exception e) {
result.complete(new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)));
}
}
});
//System.out.println("{\"type\":\"init\",\"lang\":\"en\",\"exp\":" + System.currentTimeMillis() + ",\"displayDebuggingInfo\":false,\"messages\":[{\"type\":\"newQuery\",\"locationId\":\"hipuj\",\"language\":\"en\",\"displayDebuggingInfo\":false,\"yellowIsError\":false,\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}],\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}");
socket.connect();
socket.sendText("{\"type\":\"init\",\"lang\":\"en\",\"exp\":" + System.currentTimeMillis() + ",\"displayDebuggingInfo\":false,\"messages\":[{\"type\":\"newQuery\",\"locationId\":\"hipuj\",\"language\":\"en\",\"displayDebuggingInfo\":false,\"yellowIsError\":false,\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}],\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}");
return result.get();
} catch (IOException | WebSocketException | InterruptedException | ExecutionException e) {
return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
}
}
}
diff --git a/src/me/despawningbone/discordbot/command/music/Music.java b/src/me/despawningbone/discordbot/command/music/Music.java
index 6492a88..81f74cc 100644
--- a/src/me/despawningbone/discordbot/command/music/Music.java
+++ b/src/me/despawningbone/discordbot/command/music/Music.java
@@ -1,678 +1,679 @@
package me.despawningbone.discordbot.command.music;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.safety.Whitelist;
import org.jsoup.select.Elements;
import com.google.common.base.Splitter;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
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 me.despawningbone.discordbot.command.music.AudioTrackHandler.TrackData;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
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.managers.AudioManager;
@SuppressWarnings("deprecation")
public class Music extends Command {
private AudioTrackHandler handler;
public Music() {
this.alias = Arrays.asList("m");
this.desc = "The music sub-bot!";
this.usage = "<subcommands>";
this.handler = new AudioTrackHandler();
//DONE merge p, pl and ps?
registerSubCommand("play", Arrays.asList("p"), (c, u, m, a) -> {
List<String> params = new ArrayList<>(Arrays.asList(a));
String type = params.removeAll(Collections.singleton("-s")) ? "soundcloud" : "youtube " + (params.removeAll(Collections.singleton("-l")) ? "playlist" : "video");
if(a.length < 1) return new CommandResult(CommandResultType.INVALIDARGS, "Please enter something to search or an URL to play.");
try {
return handler.searchAndPlay(String.join(" ", params), type, c.getGuild().getMember(u), c).get();
} catch (NoSuchElementException e) {
return new CommandResult(CommandResultType.NORESULT);
} catch (InterruptedException | ExecutionException e) {
return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
}
}, "[-s/-l] <URL/search words>", Arrays.asList("despacito", "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "-l tpz sega", "-s asymmetry reol"), "Queue some music to play!", Arrays.asList(
" * this also supports all opus streams and common audio format as long as you provide the url.",
" * you can specify `-s` when searching to search soundcloud instead of youtube,",
" * or `-l` to search for youtube playlists instead."));
//TODO merge? i dont think it can be though
+ //TODO guild config for non restricted loop/autoplay
registerSubCommand("autoplay", Arrays.asList("ap"), (c, u, m, a) -> {
VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
if (vc.getMembers().size() <= 2
|| DiscordBot.ModID.contains(u.getId())) {
c.sendMessage("Autoplay mode toggled "
+ (handler.toggleLoopQueue(c.getGuild(), "autoplay") ? "on.\nIt will end once someone joins the music channel." : "off."))
.queue();
return new CommandResult(CommandResultType.SUCCESS);
} else {
return new CommandResult(CommandResultType.INVALIDARGS, "You are currently not alone in the music channel, therefore this feature is disabled.");
}
}, "", null, "Auto-queues related track of the last track when you are alone!",
Arrays.asList(" Note: it will autoplay indefinitely until you toggle it again or someone joins."));
registerSubCommand("loop", Arrays.asList("l", "loopqueue"), (c, u, m, a) -> {
VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
if (vc.getMembers().size() <= 2
|| DiscordBot.ModID.contains(u.getId())) {
c.sendMessage("Looping mode toggled "
+ (handler.toggleLoopQueue(c.getGuild(), "loop") ? "on.\nIt will end once someone joins the music channel." : "off."))
.queue();
return new CommandResult(CommandResultType.SUCCESS);
} else {
return new CommandResult(CommandResultType.INVALIDARGS, "You are currently not alone in the music channel, therefore this feature is disabled.");
}
}, "", null, "Loop the queue indefinitely if you are alone!",
Arrays.asList(" Note: skipping tracks will remove it from the loop too."));
registerSubCommand("approve", Arrays.asList("a"), (c, u, m, a) -> {
GuildMusicManager mm = handler.getGuildMusicManager(c.getGuild());
if (mm.pending == null) {
return new CommandResult(CommandResultType.FAILURE, "There is currently no pending playlists/livestreams.");
}
Member requester = c.getGuild().getMemberByTag(mm.pending.get(0).getUserData(TrackData.class).getUserWithDiscriminator());
VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
if(vc == null) vc = requester.getVoiceState().getChannel(); //fallback to requester, usually due to bot not joined yet (first track needs approval)
if(vc == null) vc = c.getGuild().getMember(u).getVoiceState().getChannel(); //fallback to approver in case requester left voice channel
try {
int req = (int) Math.ceil(vc.getMembers().stream().filter(mem -> !mem.getUser().isBot()).count() / 2.0);
List<AudioTrack> tracks = mm.pending; //have to place before vote or else it gets wiped
int votes = mm.vote(u, "tracks", req);
if (votes == -1) { //automatically cleaned up in guildmusicmanager already
c.sendMessage("Pending playlist/livestream approved. Queuing...").queue();
handler.queueTracks(tracks, requester);
} else {
c.sendMessage("You voted for the pending playlist! (" + votes + "/"+ req + ")").queue();
}
return new CommandResult(CommandResultType.SUCCESS);
} catch(UnsupportedOperationException e) {
return new CommandResult(CommandResultType.FAILURE, e.getMessage());
}
}, "", null, "Approve the pending playlist or livestream.", null);
registerSubCommand("skip", Arrays.asList("s"), (c, u, m, a) -> {
TrackData track = handler.getGuildMusicManager(c.getGuild()).player.getPlayingTrack().getUserData(TrackData.class);
if(!u.getAsTag().equals(track.getUserWithDiscriminator())) {
VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
int req = (int) Math.ceil(vc.getMembers().stream().filter(mem -> !mem.getUser().isBot()).count() / 2.0);
try {
int votes = track.voteSkip(u, req);
if (votes != -1) {
c.sendMessage("You voted to skip the current track! (" + votes + "/" + req + ")").queue();
return new CommandResult(CommandResultType.SUCCESS);
}
} catch (UnsupportedOperationException e) {
return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
}
}
//else skips track if no need votes or vote passed
String next = handler.skipTrack(c.getGuild());
if(next != null) {
c.sendMessage("Skipped to the next track: `" + next + "`.").queue();
} else {
c.sendMessage("No tracks found after this one. Stopping the player...").queue();
}
return new CommandResult(CommandResultType.SUCCESS);
}, "", null, "Vote to skip the currently playing track.",
Arrays.asList(" * this is equivalent to `forceskip` if you are the one who requested the track."));
//TODO merge? i dont think i can either though
registerSubCommand("forceskip", Arrays.asList("fs"), (c, u, m, a) -> {
String next = handler.skipTrack(c.getGuild());
if(next != null) {
c.sendMessage("Skipped to the next track: `" + next + "`.").queue();
} else {
c.sendMessage("No tracks found after this one. Stopping the player...").queue();
}
return new CommandResult(CommandResultType.SUCCESS);
}, "", null, "Skip the currently playing track without voting", null,
EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
registerSubCommand("info", Arrays.asList("i", "trackinfo", "track", "nowplaying", "np", "current", "c"), (c, u, m, a) -> {
try {
c.sendMessage(handler.getTrackInfo(c.getGuild(), a.length > 0 ? Integer.parseInt(a[0]) : 1).build()).queue();
return new CommandResult(CommandResultType.SUCCESS);
} catch(NumberFormatException | NullPointerException e) {
return new CommandResult(CommandResultType.INVALIDARGS, "Invalid track index specified.");
}
}, "[index]", Arrays.asList("", "2"), "See the info about a track in the queue.",
Arrays.asList(" * If you did not specify an index, it will return info about the current track playing."));
registerSubCommand("queue", Arrays.asList("q", "page", "list"), (c, u, m, a) -> {
try {
c.sendMessage(handler.queueCheck(c.getGuild(), a.length > 0 ? Integer.parseInt(a[0]) : 1).build()).queue();
return new CommandResult(CommandResultType.SUCCESS);
} catch(NumberFormatException e) {
return new CommandResult(CommandResultType.INVALIDARGS, "Invalid page index specified.");
} catch(IllegalArgumentException e) {
return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
}
}, "[page]", Arrays.asList("", "2"), "See the queue of tracks to play.",
Arrays.asList(" * If you did not specify an page number, it will return the first page."));
//TODO allow pausing when alone? unpause when people join; or vote system?
//TODO yet another merge lmao but this time idk coz rythm has them as seperate commands
registerSubCommand("pause", Arrays.asList("pa"), (c, u, m, a) -> {
try {
handler.togglePause(c.getGuild(), true);
c.sendMessage("Successfully paused the player.").queue();
return new CommandResult(CommandResultType.SUCCESS);
} catch(IllegalStateException e) {
return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
}
}, "", null, "Pauses the music player.", null, EnumSet.of(Permission.VOICE_MOVE_OTHERS), BotUserLevel.DEFAULT.ordinal());
registerSubCommand("resume", Arrays.asList("re", "unpause"), (c, u, m, a) -> {
try {
handler.togglePause(c.getGuild(), false);
c.sendMessage("Successfully resumed the player.").queue();
return new CommandResult(CommandResultType.SUCCESS);
} catch(IllegalStateException e) {
return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
}
}, "", null, "Resumes the music player.", null, EnumSet.of(Permission.VOICE_MOVE_OTHERS), BotUserLevel.DEFAULT.ordinal());
registerSubCommand("setposition", Arrays.asList("set", "setpos", "seek"), (c, u, m, a) -> {
try {
String[] s = a[0].split(":");
int index = a.length > 2 ? 1 : 0;
String t = handler.setTrackPosition(c.getGuild(), index == 1 ? Long.parseLong(s[0]) : 0 , Long.parseLong(s[index]), Long.parseLong(s[index + 1]));
c.sendMessage("Successfully set the timestamp to `" + t + "`.").queue();
return new CommandResult(CommandResultType.SUCCESS);
} catch (IndexOutOfBoundsException | NumberFormatException e) {
e.printStackTrace();
return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a valid timestamp with colons.");
} catch (IllegalArgumentException e) {
return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
}
}, "[hr:]<min:sec>", Arrays.asList("4:52:21", "2:00"), "Set the playing position in the current track!",
Arrays.asList(" * the person who requested the current track can always set the position, regardless of perms."),
EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
//TODO merge with skip?
registerSubCommand("removetrack", Arrays.asList("rt", "r", "skiptrack", "st"), (c, u, m, a) -> {
if(a.length > 0) {
try {
int num = Integer.parseInt(a[0]);
if(num == 1) return new CommandResult(CommandResultType.INVALIDARGS, "You cannot remove the current track from the queue with this command. Use !desp music skip instead.");
if(num < 1) return new CommandResult(CommandResultType.INVALIDARGS, "The index you entered is invalid.");
AudioTrack removed = handler.getGuildMusicManager(c.getGuild()).scheduler.removeTrack(num - 1);
if(removed != null) {
c.sendMessage("Removed track `" + removed.getInfo().title + "` requested by `" + removed.getUserData(TrackData.class).getUserWithDiscriminator() + "`.").queue();
return new CommandResult(CommandResultType.SUCCESS);
} else {
return new CommandResult(CommandResultType.INVALIDARGS, "The track does not exist.");
}
} catch(NumberFormatException e) {
return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a valid index number.");
}
} else {
return new CommandResult(CommandResultType.INVALIDARGS, "Please enter an index.");
}
}, "<index>", null, "Remove a track at the specified index of the queue.",
Arrays.asList(" * the person who requested the track which is to be removed can always execute the command, regardless of perms."),
EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
registerSubCommand("move", Arrays.asList("m", "movetrack"), (c, u, m, a) -> {
try {
if(a[0].equals("1") || a[1].equals("1")) return new CommandResult(CommandResultType.INVALIDARGS, "You cannot move the currently playing track.");
AudioTrack track = handler.getGuildMusicManager(c.getGuild()).scheduler.moveTrack(Integer.parseInt(a[0]) - 2, Integer.parseInt(a[1]) - 2);
c.sendMessage("Successfully moved `" + track.getInfo().title + "` to position `" + a[1] + "`.").queue();
return new CommandResult(CommandResultType.SUCCESS);
} catch(IndexOutOfBoundsException | NumberFormatException | NullPointerException e) {
return new CommandResult(CommandResultType.INVALIDARGS, "Please valid indices.");
}
}, "<fromindex> <toindex>", Arrays.asList("2 3"), "Move a track to the specified index.", Arrays.asList(" * you can also run this command regardless of perms if you are alone with the bot.")
, EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
//TODO make vote system for this
registerSubCommand("shuffle", null, (c, u, m, a) -> {
VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
if (vc.getMembers().size() <= 2
|| DiscordBot.ModID.contains(u.getId())) {
handler.getGuildMusicManager(c.getGuild()).scheduler.shuffleQueue();
c.sendMessage("Successfully shuffled the queue.").queue();
return new CommandResult(CommandResultType.SUCCESS);
} else {
return new CommandResult(CommandResultType.INVALIDARGS, "You are currently not alone in the music channel, therefore this feature is disabled.");
}
}, "", null, "Shuffle the tracks in the queue when you are alone!", null);
//TODO make vote system for this
registerSubCommand("clear", Arrays.asList("disconnect", "dc", "stop", "clearqueue"), (c, u, m, a) -> {
handler.stopAndClearQueue(c.getGuild());
c.sendMessage("The queue has been cleared.").queue();
return new CommandResult(CommandResultType.SUCCESS);
}, "", null, "Clear the queue and stop the player.", Arrays.asList(" * you can also run this command regardless of perms if you are alone with the bot.")
, EnumSet.of(Permission.VOICE_MUTE_OTHERS, Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
registerSubCommand("lyrics", Arrays.asList("ly"), (c, u, m, a) -> {
c.sendTyping().queue();
try {
String search = a.length > 0 ? String.join(" ", a) : handler.getGuildMusicManager(c.getGuild()).player.getPlayingTrack().getInfo().title
.split("ft.")[0].replaceAll("\\(.*?\\)", "")
.replaceAll("\\[.*?\\]", "")
.replaceAll("\\【.*?\\】", "")
.replaceAll("-", "").trim();
getLyrics(search).forEach(em -> c.sendMessage(em).queue());
return new CommandResult(CommandResultType.SUCCESS);
} catch(IllegalArgumentException | UnsupportedOperationException e) {
String erMsg = e.getMessage();
//String rMsg = null;
if(e instanceof UnsupportedOperationException) {
String[] erSplit = erMsg.split(" \\| ");
erMsg = erSplit[0];
//rMsg = "Lyrics too long; " + erSplit[1];
}
return new CommandResult(CommandResultType.FAILURE, erMsg);
} catch(NullPointerException e) {
return new CommandResult(CommandResultType.INVALIDARGS, "There is nothing playing currently! Please specify a song title to search the lyrics up.");
}
}, "[search words]", Arrays.asList(""), "Search some lyrics up!", Arrays.asList(" * if you did not specify any search words, the bot will try to fetch the lyrics of the current track playing, if any."));
//TODO equalizer persistence?
//make this premium?
registerSubCommand("equalizer", Arrays.asList("eq"), (c, u, m, a) -> {
GuildMusicManager mm = handler.getGuildMusicManager(c.getGuild());
Float[] vals;
if(a.length == 0) {
vals = mm.getCurrentGain();
} else {
try {
try {
if(a.length != 15) throw new NumberFormatException();
vals = new Float[15];
for(int i = 0; i < a.length; i++) {
vals[i] = Float.parseFloat(a[i]);
}
mm.setGain(0, vals);
} catch(NumberFormatException e) {
vals = mm.setPresetGain(a[0]);
}
} catch(IllegalArgumentException e2) {
return new CommandResult(CommandResultType.INVALIDARGS, e2.getMessage());
}
}
//formatting
DecimalFormat df = new DecimalFormat("0.00");
df.setPositivePrefix("+");
EmbedBuilder eb = new EmbedBuilder();
eb.setTitle("Current equalizer graph");
eb.appendDescription("```\n");
for(double line = 0.25; line >= -0.25; line -= 0.05) {
eb.appendDescription(df.format(line) + " ");
for(int band = 0; band < 15; band++) {
if(Math.abs(0.05 * Math.round(vals[band] / 0.05) - line) < 1E-7) eb.appendDescription("🔘");
else eb.appendDescription(" ❘ ");
}
eb.appendDescription("\n");
}
eb.appendDescription("```");
eb.addField("Actual values", Arrays.asList(vals).stream().map(f -> df.format(f)).collect(Collectors.joining(" ")), false);
c.sendMessage(eb.build()).queue();
return new CommandResult(CommandResultType.SUCCESS);
}, "[bandvalues/presetname]", Arrays.asList("", "0.09 0.07 0.07 0.01 0 0 -0.02 -0.02 0.03 0.03 0.05 0.07 0.09 0.1 0.1", "bassboost"), "Sets the equalizer for the music player in this guild!",
Arrays.asList("Accepts 15 bands with values ranging from -0.25 to 0.25, where -0.25 is muted and 0.25 is double volume.", "* presets include: `bassboost`, `default`, `rock`.", "Input nothing to return the current settings.", "",
"Note: you might experience some audio cracking for band values >0.1, since amplifying volumes remotely does not work well.", "It is recommended to use values from -0.25 to 0.1, and turning discord volume up instead.")
, EnumSet.of(Permission.VOICE_MUTE_OTHERS, Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
registerSubCommand("shutdown", null, (c, u, m, a) -> {
handler.shutdown();
handler = null;
c.sendMessage("Successfully destroyed the music player instance.").queue();
return new CommandResult(CommandResultType.SUCCESS);
}, "", null, "Shuts down the music player sub-bot.", null, EnumSet.noneOf(Permission.class), BotUserLevel.BOT_OWNER.ordinal());
registerSubCommand("startup", Arrays.asList("start"), (c, u, m, a) -> {
if(handler != null) return new CommandResult(CommandResultType.FAILURE, "An instance is already running. Please shut it down first.");
else handler = new AudioTrackHandler();
c.sendMessage("Successfully created the music player instance.").queue();
return new CommandResult(CommandResultType.SUCCESS);
}, "", null, "Boots up the music player sub-bot.", null, EnumSet.noneOf(Permission.class), BotUserLevel.BOT_OWNER.ordinal());
registerSubCommand("restart", Arrays.asList("reboot"), (c, u, m, a) -> {
handler.shutdown();
handler = new AudioTrackHandler();
c.sendMessage("Successfully rebooted the music player instance.").queue();
return new CommandResult(CommandResultType.SUCCESS);
}, "", null, "Reboots the music player sub-bot.", null, EnumSet.noneOf(Permission.class), BotUserLevel.BOT_OWNER.ordinal());
}
public AudioTrackHandler getAudioTrackHandler() {
return handler;
}
//TODO make a skipuntil command that requires voting? //prob not
//TODO make clear accessible on vote?
@Override
public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) {
DiscordBot.lastMusicCmd.put(channel.getGuild().getId(), channel);
Command sub = args.length > 0 ? getSubCommand(args[0].toLowerCase()) : null;
if(sub != null) { //only run if not null, else hand to normal sub command handler
//if handler is null it means the music player is shut down
if(!sub.getName().equals("startup")) {
if(handler == null) return new CommandResult(CommandResultType.FAILURE, "The music bot is in maintenance right now!");
//pre check to block out all music commands if the player is not running
List<String> exception = Arrays.asList("play", "approve", "shutdown", "lyrics", "equalizer");
GuildMusicManager mm = handler.getGuildMusicManager(channel.getGuild());
if(mm.player.getPlayingTrack() == null &&
!exception.contains(sub.getName()))
return new CommandResult(CommandResultType.INVALIDARGS, "There are no tracks playing currently.");
else {
//else block out all users that are not in the same voice channel
exception = Arrays.asList("lyrics", "queue", "info");
AudioManager man = channel.getGuild().getAudioManager();
if(man.isConnected() && !man.getConnectedChannel().getMembers().contains(channel.getGuild().getMember(author))
&& !(exception.contains(sub.getName()) || DiscordBot.ModID.contains(author.getId()))) {
return new CommandResult(CommandResultType.INVALIDARGS, "You are not in the same channel as the bot.");
}
}
//perms overriding for queuer
if(Arrays.asList("forceskip", "skip", "setposition").contains(sub.getName())) {
if(author.getAsTag().equals(mm.player.getPlayingTrack().getUserData(TrackData.class).getUserWithDiscriminator()))
return sub.execute(channel, author, msg, Arrays.copyOfRange(args, 1, args.length));
} else if(sub.getName().equals("removetrack")) {
try {
if(author.getAsTag().equals(mm.scheduler.findTracks(Integer.parseInt(args[1]) - 1, 1).get(0).getUserData(TrackData.class).getUserWithDiscriminator()))
return sub.execute(channel, author, msg, Arrays.copyOfRange(args, 1, args.length));
} catch(Exception ignored) {
ignored.printStackTrace();
}
}
//perms overriding for when alone
if(Arrays.asList("move", "clear").contains(sub.getName())) { //should always be connected to vc
if(channel.getGuild().getAudioManager().getConnectedChannel().getMembers().size() == 2) { //alone; no need to check if the other member is requester, since its checked before
return sub.execute(channel, author, msg, Arrays.copyOfRange(args, 1, args.length));
}
}
}
}
CommandResult res = super.execute(channel, author, msg, args); //pass to subcommand handler for perms checking
if(res.getResultType() == CommandResultType.NOPERMS && Arrays.asList("forceskip", "skip", "setposition", "removetrack").contains(sub.getName())) { //add back info to command result
res = new CommandResult(CommandResultType.INVALIDARGS, res.getMessage().getContentRaw().split(" to execute")[0] + ", or have requested the track to execute this command.");
}
return res;
}
private final String geniusAuth = DiscordBot.tokens.getProperty("genius");
private List<String> whitelist = Arrays.asList("https://genius.com/Noma-jpn-brain-power-annotated");
//private ExecutorService executor = Executors.newCachedThreadPool(); //unfortunately i need to finish the api search to get the url to start the html scrape, which means i cannot use future for multithread here
public List<MessageEmbed> getLyrics(String search) { //DONE dont splice words between embeds; make whole sentence to be spliced instead
//System.out.println(search);
List<MessageEmbed> em = new ArrayList<MessageEmbed>();
try {
URLConnection searchCon = new URL("https://api.genius.com/search?access_token=" + geniusAuth + "&q=" + URLEncoder.encode(search, "UTF-8").replaceAll("\\+", "%20")).openConnection();
//URLConnection searchCon = new URL("https://api.genius.com/search?q=eden%20-%20end%20credits%20%28feat.%20leah%20kelly%29").openConnection();
searchCon.addRequestProperty("User-Agent", "Mozilla/4.0");
//searchCon.addRequestProperty("Authorization", "Bearer " + geniusAuth);
InputStream searchStream = searchCon.getInputStream();
JSONTokener searchResult = new JSONTokener(searchStream);
JSONArray list = new JSONObject(searchResult).getJSONObject("response").getJSONArray("hits");
JSONObject main = null;
for(int i = 0; i < list.length(); i++) {
JSONObject check = list.getJSONObject(i).getJSONObject("result");
if(!check.getString("url").endsWith("lyrics") && !whitelist.contains(check.getString("url"))) {
System.out.println(" " + check.getString("url"));
continue;
}
main = check;
break;
}
if(main == null) {
throw new IllegalArgumentException("There were no results unfortunately :cry:");
}
searchStream.close();
String url = main.getString("url");
Element href;
try {
Document document = Jsoup.connect(url).userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0").get();
href = document.body();
} catch (IOException e1) {
e1.printStackTrace();
throw new IllegalArgumentException("Something went wrong.");
}
Element el = href.selectFirst("div[class=\"lyrics\"]");
String s;
if(el == null) {
el = href.selectFirst("div[class*=\"Lyrics__Root\"]"); //second version of the page
el.select("div[class*=\"Ad__Container\"]").remove();
el.select("a, span, i, div[class*=\"Lyrics__Container\"]").unwrap();
s = el.html().replaceAll("\\\\n", "\n");
} else {
el.select("br").append("\\n");
el.select("p").prepend("\\n\\n");
s = el.html().replaceAll("\\\\n", "\n");
}
String lyrics = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false));
List<String> lyList = new ArrayList<String>(Splitter.fixedLength(2000).splitToList(
lyrics.trim().replaceAll("&amp;", "&").replaceAll("&nbsp;", " ")
.replaceAll("\n ", "\n"))); //retain this formatting? it looks more clean yet more jumbled at the same time
if(lyList.size() > 5) {
throw new UnsupportedOperationException("The lyrics is too long for a normal song xD | URL: " + url);
}
EmbedBuilder eb = new EmbedBuilder();
String auImg = main.getJSONObject("primary_artist").getString("header_image_url");
eb.setAuthor(main.getString("full_title"), url, auImg.contains("https://assets.genius.com/images/default_avatar_300.png") ? null : auImg);
String alImg = main.getString("song_art_image_thumbnail_url");
eb.setThumbnail(alImg.contains("https://assets.genius.com/images/default_cover_image.png") ? null : alImg);
formatLyrics(eb, "**Lyrics**\n\n " + (lyList.size() > 0 ? lyList.get(0).trim() : "N/A"), lyList, 0);
eb.setColor(new Color(255, 255, 100));
if(lyList.size() > 1) {
em.add(eb.build());
for(int i = 1; i < lyList.size(); i++) {
EmbedBuilder loopEm = new EmbedBuilder();
loopEm.setColor(new Color(255, 255, 100));
formatLyrics(loopEm, lyList.get(i), lyList, i);
if(i == lyList.size() - 1) {
setFinalLyricsEmbed(loopEm, main, href);
}
em.add(loopEm.build());
}
} else {
setFinalLyricsEmbed(eb, main, href);
em = Arrays.asList(eb.build());
}
//System.out.println(lyrics);
} catch (JSONException | IOException | ArrayIndexOutOfBoundsException e) {
if(e instanceof JSONException || e instanceof ArrayIndexOutOfBoundsException) {
e.printStackTrace();
throw new IllegalArgumentException("There were no results unfortunately :cry:");
} else {
e.printStackTrace();
throw new IllegalArgumentException("Something went wrong.");
}
}
return em;
}
private void formatLyrics(EmbedBuilder eb, String segment, List<String> lyList, int i) {
if(segment.length() < 2000) {
eb.setDescription(segment.trim());
return;
}
String includeSeg = segment.length() > 2000 ? segment.substring(0, 2000) : segment;
int index = includeSeg.lastIndexOf("\n");
//System.out.println(segment);
//System.out.println(segment.substring(0, index));
eb.setDescription(segment.substring(0, index));
String move = segment.substring(includeSeg.lastIndexOf("\n"));
try {
if(!move.isEmpty()) lyList.set(i + 1, move + lyList.get(i + 1));
} catch (IndexOutOfBoundsException e) {
lyList.add(move);
}
}
private void setFinalLyricsEmbed(EmbedBuilder eb, JSONObject main, Element href) { //change the format for the supplementary info in artists and albums from italic to sth else?
//System.out.println("test");
try {
String name = main.getJSONObject("primary_artist").getString("name");
//name = "[" + name + "](https://genius.com/artists/" + URLEncoder.encode(name, "UTF-8").replaceAll("\\+", "%20") + ")";
/*StringBuilder sb = new StringBuilder();
data.getJSONArray("artists").forEach(obj -> {
String n = obj.toString();
if(!n.equals(name)) {
sb.append(obj.toString() + " ");
}
});
String others = sb.toString();*/
try {
String others = href.select("script:containsData(_sf_async_config)").html().split("_sf_async_config.authors = '")[1].split("';")[0].replace(",", ", ");
eb.addField("Artists", new StringBuffer(others).insert(others.indexOf(name) + name.length() + 2, "\n*").toString() + "*", true);
} catch (IndexOutOfBoundsException e) {
eb.addField("Artist", name, true);
}
//eb.addField("Artist" + (others.isEmpty() ? "" : "s"), name + (others.isEmpty() ? "" : ",\n*" + others.trim().replaceAll(" ", ", ") + "*"), true); //add link to it?
} catch (JSONException e) {
e.printStackTrace();
eb.addField("Artist", "Unknown", true);
}/* catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}*/
if(!href.select("div[class=\"lyrics\"]").isEmpty()) {
Elements buffer = href.select("span:contains(Album) ~ span[class=\"metadata_unit-info\"] a");
if(buffer.size() > 0) {
String album = buffer.get(0).ownText();
StringBuilder sb = new StringBuilder();
JSONObject data = new JSONObject(href.select("script[type=\"application/ld+json\"]").get(0).html());
data.getJSONArray("inAlbum").forEach(obj -> {
String n = ((JSONObject) obj).getString("name");
if(!n.equals(album)) {
sb.append("*" + n.trim() + "*\n");
}
});
String others = sb.toString();
eb.addField("Album" + (others.isEmpty() ? "" : "s"), album + (others.isEmpty() ? "" : "\n" + others), true); //add link?
}
buffer = href.select("span:contains(Release Date) ~ span[class*=\"metadata_unit-info\"]");
if(buffer.size() > 0) eb.addField("Release Date", buffer.get(0).ownText(), true);
try {
String sbuff = URLDecoder.decode(href.select("img[src*=\"page-genres=\"]").first().absUrl("src").split("page-genres=")[1].split("&page-type")[0].replaceAll("\\+", " ").split("&")[0], "UTF-8");
if(!sbuff.isEmpty()) {
eb.addField("Tags", sbuff.trim().replaceAll(",", ", "), false);
}
} catch (UnsupportedEncodingException | NullPointerException e) {
e.printStackTrace();
}
buffer = href.select("div[class=\"annotation_label\"] ~ div[class=\"rich_text_formatting\"]");
if(buffer.size() > 0) {
String bgInfo = buffer.get(0).text();
if(!bgInfo.trim().isEmpty()) {
if(bgInfo.length() > 1024) {
bgInfo = bgInfo.substring(0, 1021) + "...";
}
eb.addField("Background info", bgInfo, false);
}
}
} else { //second version -- actually has a lot more info, use?
System.out.println("Second ver");
String temp = href.select("script:containsData(__PRELOADED_STATE__)").html().split("JSON.parse\\(\'", 2)[1].split("\'\\);\n", 2)[0];
JSONObject preload = new JSONObject(StringEscapeUtils.unescapeJson(temp));
JSONObject song = preload.getJSONObject("entities").getJSONObject("songs").getJSONObject(Integer.toString(preload.getJSONObject("songPage").getInt("song")));
StringBuilder sb = new StringBuilder();
try {
String album = IntStream.range(0, song.getJSONArray("trackingData").length()).mapToObj(i -> song.getJSONArray("trackingData").getJSONObject(i)).filter(obj -> obj.getString("key").equals("Primary Album")).findFirst().get().getString("value").trim();
song.getJSONArray("albums").forEach(obj -> {
String n = ((JSONObject) obj).getString("name");
if(!n.trim().equals(album)) {
sb.append("*" + n.trim() + "*\n");
}
});
String others = sb.toString();
eb.addField("Album" + (others.isEmpty() ? "" : "s"), album + (others.isEmpty() ? "" : "\n" + others), true); //add link?
} catch (JSONException ignored) {
//no albums; value is null
}
eb.addField("Release Date", song.getString("releaseDateForDisplay"), true);
ArrayList<String> tags = new ArrayList<>();
for(int i = 0; i < song.getJSONArray("tags").length(); i++) {
song.getJSONArray("tags").getJSONObject(i).getString("name");
}
if(!tags.isEmpty()) eb.addField("Background info", String.join(", ", tags), false);
String bgInfo = song.getJSONObject("description").getString("markdown");
if(bgInfo.length() > 1024) {
bgInfo = bgInfo.substring(0, 1021) + "...";
}
if(!bgInfo.equals("?")) eb.addField("Background info", bgInfo, false); //apparently its ? for empty descs lol
}
eb.setFooter((main.getJSONObject("stats").has("pageviews") ? NumberFormat.getIntegerInstance().format(main.getJSONObject("stats").getInt("pageviews")) + " views | " : "") + "Powered by Genius",
"https://yt3.ggpht.com/a/AATXAJzPOKLs0x9W_yNpTUPvwg-zeSnJaxqzf2CU0g=s900-c-k-c0xffffffff-no-rj-mo");
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Apr 15, 8:18 PM (6 h, 35 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e4/ce/d5a456933578370d6b1b5b11f143

Event Timeline