- String res = Jsoup.connect("https://mywaifulist.moe/api/waifu/search") //advancedsearch is no longer a thing
- .userAgent("Mozilla/4.0")
- .header("Cookie", cookie)
- .header("X-CSRF-Token", csrf)
+ //Mozilla/4.0 actually bypasses the need for CSRF, but breaks /api/search/all since it requires X-Requested-With (/browse?name= still works but doesn't handle multiple types at once)
+ Response con = Jsoup.connect("https://mywaifulist.moe/").userAgent("Mozilla/5.0").execute();
+ String res = Jsoup.connect("https://mywaifulist.moe/api/search/all") //advancedsearch is no longer a thing
+ .header("X-XSRF-TOKEN", URLDecoder.decode(con.cookie("XSRF-TOKEN"), "UTF-8")) //base64 might be encoded - if it is, then laravel will return 419 page expired
+ .header("Content-Type", "application/json") //needed or the endpoint can't parse the query json
+ .header("Referer", "https://mywaifulist.moe/") //needed to avoid internal server error for some reason
.sorted((a, b) -> wholeWord.matcher(a.getString("name")).matches() && !wholeWord.matcher(b.getString("name")).matches() ? -1 : 0) //move whole word matches up only if last one was not matched
.collect(Collectors.toList());
//fetch
- main = new JSONObject(new JSONTokener(Jsoup.connect("https://mywaifulist.moe/api/waifu/" + arr.get(index).getInt("id"))
+ props = new JSONObject(new JSONTokener(Jsoup.connect("https://mywaifulist.moe/waifu/" + arr.get(index).getString("slug"))
+ (!jobj.isNull("description") ? (" \"" + jobj.getString("description").substring(0, Math.min(jobj.getString("description").length(), avg)).replaceAll("\"", "”")) //trim desc to max length, replacing double quotes since it will interfere with markdown
+ (jobj.getString("description").length() > avg ? "..." : "") + "\")" : ")")) //append ... if its not finished (only if desc is non null will desc be printed)
List<String> amend = new ArrayList<String>(Arrays.asList(words));
int wParamIndex = amend.indexOf("-w");
//parse weight param and uid; doesnt work if -w is immediately followed by pp params like 100x etc
String uid = null;
boolean weight = wParamIndex != -1;
if(weight) {
try {
int wParamLength = 1;
uid = amend.get(wParamIndex + 1);
if(!uid.contains("osu.ppy.sh/u") && (uid.startsWith("http") && uid.contains("://"))) { //url thats not user url means its most likely a beatmap, aka no username param
return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
}
channel.sendMessage("Trying to retrieve map from discord status...").queue(m -> msgId.complete(m.getId())); //DONE custom status masks presence now, will not be as effective; any way to get manually? //JDA 4 API change fixed this
channel.sendTyping().queue();
//parse map name and search
if (details != null) { //TODO if name is sth like `Feryquitous - (S).0ngs//---::compilation.[TQR-f3] [-[//mission:#FC.0011-excindell.defer.abferibus]-]` it breaks (but reasonable break tbh); also breaks if difficulty has " in it
try {
String title = URLEncoder.encode(details.substring(details.indexOf(" - ") + 3, details.lastIndexOf("[")).trim(), "UTF-8");
JSONTokener tokener = new JSONTokener(stream.getInputStream());
initmap = "https://osu.ppy.sh/beatmaps/" + new JSONObject(tokener).getJSONArray("beatmaps").getJSONObject(0).getInt("beatmap_id");
} catch (IOException e) {
return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
} catch (JSONException e) {
e.printStackTrace();
System.out.println(details);
return new CommandResult(CommandResultType.NORESULT); //returns if osusearch isnt updated fast enough
}
} else {
return new CommandResult(CommandResultType.FAILURE, "There is no account of your rich presence, therefore I cannot get the beatmap from your status.");
}
}
//if still dud aka no presence nor url
if(initmap.equals("null")) { //shouldnt throw at all
return new CommandResult(CommandResultType.FAILURE, "You haven't played any maps I can recognize yet!");
channel.sendMessage("For " + uid + " to actually gain " + df.format(targetPP) + "pp in " + modes[modeId] + ", they have to play a map worth approximately **" + df.format(mid) + "pp** raw.").queue();
return new CommandResult(CommandResultType.SUCCESS);
} else {
return new CommandResult(CommandResultType.INVALIDARGS, "You can't really achieve such large pp jumps without people thinking you are hacking :P");
}
} catch (NumberFormatException e) {
return new CommandResult(CommandResultType.INVALIDARGS, "Invalid target pp specified.");
List<String> params = new ArrayList<>(Arrays.asList(words));
int modeId = getMode(params);
if(modeId > 1) modeId = 0; //ignore unsupported modes and default to standard
boolean passOnly = params.removeAll(Collections.singleton("-p")); //no need space, as spaced ones are split (if a player has a name with spaced -p it might be a problem)
return new CommandResult(CommandResultType.FAILURE, "Only the 50 most recent plays can be recalled!");
}
//fetch recent plays
JSONArray array;
String name;
try {
JSONObject res = getPlayerData("get_user_recent", search, modeId, user);
if(!res.has("result"))
return new CommandResult(CommandResultType.INVALIDARGS, "Unknown player `" + res.getString("search") + "` or the player has not been playing in the last 24h.");
array = res.getJSONArray("result");
if(array.length() == 0)
return new CommandResult(CommandResultType.FAILURE, "You have no recent plays in this 24h!");
//set name according to supported formats
name = res.getBoolean("isId") ? //isId might return true on cases inputted as https://osu.ppy.sh/users/despawningbone for example, which would make the fetching redundant but still works
for (int i = n; (n + 3 <= fobj.size() ? i < n + 3 : i < fobj.size()); i++) {
JSONObject json = fobj.get(i);
em.addField("Difficulty", "[" + json.getString("version") + "](https://osu.ppy.sh/b/" + json.getString("beatmap_id") + ")" + " ([Preview](https://jmir.xyz/osu/#" + json.getString("beatmap_id") + "))", false); //bloodcat is no longer a thing but someone hosted a copy for previewing here
if(map.isEmpty()) return new CommandResult(CommandResultType.FAILURE, "There are no recent map/score cards to compare with in the past 100 messages!");
//if name or id is provided in the command use it else fallback to requester
return new CommandResult(CommandResultType.SUCCESS);
} catch (IOException e) {
return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
} catch (JSONException e) {
e.printStackTrace();
return new CommandResult(CommandResultType.INVALIDARGS, "Unknown player `" + uid + "` or the player has no scores on this map.");
}
}, "[username] [| index]", null, "Compare your score with the most recent map/score card in the channel!", Arrays.asList(" * Specify the index to skip to the nth recent score card in the channel."));
//get new top pp list, replacing old score if needed
for(int i = 0; i < userBest.length(); i++) { //something like a treemap might work more elegantly, but i need to add pp to the list anyways so its fine i guess
JSONObject obj = userBest.getJSONObject(i);
double v = obj.getDouble("pp");
String bid = obj.getString("beatmap_id");
if(bid.equals(ibid)) {
if(v >= pp) {
return -1;
} else {
change = v;
}
}
if(v <= pp && !pps.contains(pp)) { //insert on first smaller occurence
for(int i = pps.indexOf(pp); i < pps.size(); i++) {
double c = pps.get(i);
double w = c*(Math.pow(0.95, i));
if(i == pps.indexOf(pp)) {
net += w;
} else {
net += c*(Math.pow(0.95, i - 1)) * (0.95 - 1);
}
}
net -= last; //because the last one is completely not counted, not just shifted to a lower value
} else {
int old = pps.indexOf(change) - 1;
pps.remove(change);
for(int i = pps.indexOf(pp); i <= old; i++) {
double c = pps.get(i);
double w = c*(Math.pow(0.95, i));
if(c == pp) {
net += w - change*(Math.pow(0.95, old));
} else {
net += w * (0.95 - 1);
}
}
}
return net;
}
//note: stripped ids are considered usernames - if you are sure that its an id (eg result from api) then wrap it in an url
private static JSONObject getPlayerData(String api, String input, int modeId, User requester) throws IOException {
//check if user url provided, if not assume username
String id = "";
boolean noturl = false;
try {
new URL(input);
id = input.substring(input.lastIndexOf("/") + 1);
} catch (MalformedURLException e) {
noturl = true;
id = input;
}
//if empty fallback to own
if(id.isEmpty()) {
id = getPlayer(requester);
}
//set params and fetch
String addparam = "";
if (noturl) { //search as usernames
addparam = "&type=string";
}
if (modeId != 0) { //change mode
addparam += ("&m=" + modeId);
}
//limit either gets ignored or truncated to highest - is fine if i leave it as 100 (highest of any apis i use)
JSONArray a = new JSONArray(new JSONTokener(new URL("https://osu.ppy.sh/api/" + api + "?k=" + osuAPI + "&u=" + URLEncoder.encode(id, "UTF-8") + addparam + "&limit=100").openStream()));
//always return the val we did the search with, and only result if it exists
JSONObject wrap = new JSONObject("{\"search\": \"" + id + "\", \"isId\": " + !noturl + "}");
if (a.length() > 0) {
wrap.put("result", a);
}
return wrap;
}
//TODO find a way to parallelize map fetching and meta fetching if possible?
private static JSONObject computePP(Map beatmap, String bid, int mods, int n50, int n100, int n300, int miss, int combo) throws IllegalArgumentException, IOException {
//set osu-wide beatmap params (non mode specific)
PlayParameters p = new Koohii.PlayParameters();
p.beatmap = beatmap;
p.n50 = n50;
p.n100 = n100;
p.n300 = n300;
p.nmiss = miss;
p.nobjects = p.n300 + p.n100 + p.n50 + p.nmiss;
p.mods = mods;
if (combo > 0) {
p.combo = combo;
} else {
p.combo = p.beatmap.max_combo();
}
//get beatmap metadata
String addparam = "";
if(p.beatmap.mode == 1) { //these mods alter metadata that taiko needs
+ //www.yahoo.com changed its endpoint - there's no longer a AJAX API for weather info, and the search autocomplete is basically just a prefix search which is way inferior; so we use ca.news.yahoo.com instead
+ countryShort = wQualifiedName.substring(wQualifiedName.lastIndexOf(",") + 1); //the display name from yahoo is not always consistent with Java's Locale display name, so we extract from qualified name instead
region = wQualifiedName.split(",")[wQualifiedName.split(",").length - 2]; //get second highest level, highest should always be country code
String imgUrl = info.getJSONArray("photos").getJSONObject(0).getJSONArray("resolutions").getJSONObject(0).getString("url"); //seems to have dead urls, how fix
footer = "Weather info last updated: " + OffsetDateTime.parse(obs.getJSONObject("observationTime").getString("timestamp")).format(DateTimeFormatter.RFC_1123_DATE_TIME)
return new CommandResult(CommandResultType.FAILURE, "Unfortunately, the bot is missing the permission `MESSAGE_EMBED_LINKS` which is required for this command to work.");
}
return new CommandResult(CommandResultType.SUCCESS);
} catch (Exception e) {
return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
}
}
}
private String fToC(int f) { //fucking no metric ree
String wiki = init[0].replaceAll("[^\\p{L}\\p{N} ]+", "").replaceAll(" ", "-").toLowerCase();
- //the wikia domain is still in use; no need to swap to fandom.com for now
- //alternative search endpoint (more of an autocomplete only but much faster): "https://" + wiki + ".wikia.com/wikia.php?controller=LinkSuggest&method=getLinkSuggestions&format=json&query=" + search
- url = (HttpURLConnection) new URL("https://" + wiki + ".wikia.com/api.php?action=query&format=json&list=search&srsearch=" + search).openConnection();
+ //newer wikis does not have an entry under wikia.com anymore
+ //alternative search endpoint (more of an autocomplete only but much faster): "https://" + wiki + ".fandom.com/wikia.php?controller=UnifiedSearchSuggestions&method=getSuggestions&format=json&scope=internal&query=" + search
+ url = (HttpURLConnection) new URL("https://" + wiki + ".fandom.com/api.php?action=query&format=json&list=search&srsearch=" + search).openConnection(); //sometimes this has no results (new wikis?)
if(url.getResponseCode() == 404) {
return new CommandResult(CommandResultType.FAILURE, "Unknown wiki name!"); //404 means unknown wiki now
}
//get result
int id;
try {
JSONObject result = new JSONObject(new JSONTokener(url.getInputStream()));
id = result.getJSONObject("query").getJSONArray("search").getJSONObject(num).getInt("pageid");
} catch(JSONException e) {
return new CommandResult(CommandResultType.NORESULT, "it in the " + init[0] + " wiki");
}
//fetch details about page; way worse formatting than AsSimpleJson but hey its gone what can i do
- JSONObject details = new JSONObject(new JSONTokener(new URL("https://" + wiki + ".wikia.com/api/v1/Articles/Details?abstract=500&ids=" + id).openStream()));
+ JSONObject details = new JSONObject(new JSONTokener(new URL("https://" + wiki + ".fandom.com/api/v1/Articles/Details?abstract=500&ids=" + id).openStream()));
JSONObject info = details.getJSONObject("items").getJSONObject(String.valueOf(id)); //TODO make async
//only use until the last full stop before table of content or end for slightly better formatting
//there might be false positives for table of content detection since its just checking 1 after full stop, but honestly rarely less details > commonly being ugly af
String desc = info.getString("abstract").replaceAll("^(?:(.*?\\.) ?1 .*|(.*\\.) .*?)$", "$1$2"); //greedy if table of content is present, else lazy to get the last
eb.setDescription(desc.matches(".*\\.$") ? desc : (desc + "...")); //if everything fails (aka last char aint a full stop) give it the good ol ... treatment
if(!info.isNull("thumbnail")) eb.setThumbnail(info.getString("thumbnail").substring(0, info.getString("thumbnail").indexOf("/revision/"))); //get full img by trimming revision path
eb.setFooter("Last edited by " + info.getJSONObject("revision").getString("user"), null);
this.remarks = Arrays.asList("You can search a specific language's Wikipedia with the parameter, as long as there is a valid subdomain for that language,", "e.g. `en.wikipedia.org` or `ja.wikipedia.org`.");
JSONObject s = new JSONObject(new JSONTokener(new URL("https://" + lang + ".wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=" + search).openStream()));
title = URLEncoder.encode(s.getJSONObject("query").getJSONArray("search").getJSONObject(index).getString("title"), "UTF-8").replaceAll("\\+", "%20");
} catch(IOException e) { //usually caused by wrong language wiki
return new CommandResult(CommandResultType.INVALIDARGS, "Unknown wiki language version specified.");
} catch(JSONException e) {
return new CommandResult(CommandResultType.INVALIDARGS, "There are not enough results for your specified index!");
}
JSONObject result = new JSONObject(new JSONTokener(new URL("https://" + lang + ".wikipedia.org/api/rest_v1/page/summary/" + title).openStream()));
//TODO do something with type = disambiguition?
EmbedBuilder eb = new EmbedBuilder();
eb.setAuthor(new Locale(result.getString("lang")).getDisplayName(Locale.ENGLISH) + " Wikipedia"); //DONE add support for other languages?