|
|
|
@@ -1,14 +1,16 @@
|
|
|
|
|
import hxdiscord.DiscordClient;
|
|
|
|
|
import hxdiscord.types.*;
|
|
|
|
|
import hxdiscord.endpoints.Endpoints;
|
|
|
|
|
import haxe.MainLoop;
|
|
|
|
|
import htmlparser.HtmlDocument;
|
|
|
|
|
import haxe.Timer;
|
|
|
|
|
import haxe.Json;
|
|
|
|
|
import sys.io.File;
|
|
|
|
|
import sys.FileSystem;
|
|
|
|
|
import izzcomlib.IzzComLib.*;
|
|
|
|
|
using StringTools;
|
|
|
|
|
|
|
|
|
|
class Onequestionmark {
|
|
|
|
|
// Initialize vars
|
|
|
|
|
// Initialize vars
|
|
|
|
|
static var Bot:DiscordClient;
|
|
|
|
|
static var settings:Dynamic;
|
|
|
|
|
static var botInfo:Dynamic;
|
|
|
|
@@ -19,11 +21,13 @@ class Onequestionmark {
|
|
|
|
|
static var yesnoDB:Dynamic;
|
|
|
|
|
static var echoboxDB:haxe.DynamicAccess<Dynamic> = {};
|
|
|
|
|
|
|
|
|
|
static var saveTimer = Date.now().getMinutes();
|
|
|
|
|
static var motdTimer:Timer;
|
|
|
|
|
static var saveTimer:Timer;
|
|
|
|
|
static var saveQueue:Array<String> = [];
|
|
|
|
|
static var timerInit:Bool = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static function main() {
|
|
|
|
|
static function main() {
|
|
|
|
|
|
|
|
|
|
Sys.println('[${timestamp()}] Starting onequestionmark-chat');
|
|
|
|
|
|
|
|
|
@@ -41,7 +45,7 @@ class Onequestionmark {
|
|
|
|
|
hugDB = Json.parse(File.getContent("hug-db.json"));
|
|
|
|
|
} else {
|
|
|
|
|
Sys.println('[${timestamp()}] Did not find hug-db.json, skipping');
|
|
|
|
|
hugDB = ["https://cdn.discordapp.com/attachments/270113422232911883/445026464623099907/brohugbump.gif"]; // Fallback
|
|
|
|
|
hugDB = []; // Fallback
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FileSystem.exists("motd-db.json")) {
|
|
|
|
@@ -49,7 +53,7 @@ class Onequestionmark {
|
|
|
|
|
motdDB = Json.parse(File.getContent("motd-db.json"));
|
|
|
|
|
} else {
|
|
|
|
|
Sys.println('[${timestamp()}] Did not find motd-db.json, skipping');
|
|
|
|
|
motdDB = Json.parse('{"sunday": [],"monday": [],"tuesday": [],"wednesday": [],"thursday": [],"friday": [],"saturday": []}'); // Fallback
|
|
|
|
|
motdDB = Json.parse('{}'); // Fallback
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FileSystem.exists("yesno-db.json")) {
|
|
|
|
@@ -67,15 +71,24 @@ class Onequestionmark {
|
|
|
|
|
Sys.println('[${timestamp()}] Did not find echobox-db.json, generating');
|
|
|
|
|
File.saveContent("echobox-db.json", Json.stringify(echoboxDB, "\t"));
|
|
|
|
|
}
|
|
|
|
|
if (!FileSystem.exists("export")) {
|
|
|
|
|
FileSystem.createDirectory("export");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveSystem();
|
|
|
|
|
|
|
|
|
|
// Start the bot
|
|
|
|
|
Bot = new DiscordClient(settings.token, [3276799], settings.debug);
|
|
|
|
|
Bot.onReady = onReady;
|
|
|
|
|
Bot.onMessageCreate = onMessageCreate;
|
|
|
|
|
Bot = new DiscordClient(settings.token, [3276799], settings.debug);
|
|
|
|
|
Bot.onReady = onReady;
|
|
|
|
|
Bot.onMessageCreate = onMessageCreate;
|
|
|
|
|
Bot.connect();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function onReady() {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The `onReady()` function is run upon a successful `Bot.connect()`
|
|
|
|
|
*/
|
|
|
|
|
public static function onReady() {
|
|
|
|
|
Sys.sleep(1);
|
|
|
|
|
Sys.println('[${timestamp()}] Bot is online');
|
|
|
|
|
|
|
|
|
@@ -86,13 +99,20 @@ class Onequestionmark {
|
|
|
|
|
|
|
|
|
|
botInfo = Endpoints.getCurrentUser();
|
|
|
|
|
|
|
|
|
|
MainLoop.add(motd);
|
|
|
|
|
MainLoop.add(saveSystem);
|
|
|
|
|
}
|
|
|
|
|
if (timerInit == false) {
|
|
|
|
|
timerInit = true;
|
|
|
|
|
startMotd();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function onMessageCreate(m:Message) {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The `onMessageCreate()` event provides the bulk of onequestionmark's functionality.
|
|
|
|
|
* This is what triggers when a new Discord message is received, and contains the bot's command processor.
|
|
|
|
|
*/
|
|
|
|
|
public static function onMessageCreate(m:Message) {
|
|
|
|
|
// DevMode check
|
|
|
|
|
if ((!settings.devmode) || (m.guild_id == settings.devserver)) {
|
|
|
|
|
if ((!settings.devmode) || (m.guild_id == settings.devserver)) {
|
|
|
|
|
var msg = m.content;
|
|
|
|
|
var sender:String;
|
|
|
|
|
if (m.getMember().nick == null) {sender = m.author.global_name;} else {sender = m.getMember().nick;}
|
|
|
|
@@ -138,14 +158,29 @@ class Onequestionmark {
|
|
|
|
|
}
|
|
|
|
|
case "motd":
|
|
|
|
|
if (m.author.id == settings.botowner) {
|
|
|
|
|
if (!settings.motd.channels.contains(m.channel_id)) {
|
|
|
|
|
settings.motd.channels.push(m.channel_id);
|
|
|
|
|
m.reply({content:'MOTD has been enabled for <#${m.channel_id}>'}, false);
|
|
|
|
|
if (args.length != 0) {
|
|
|
|
|
if (args[0] == "force") {
|
|
|
|
|
m.reply({content:'${getMotd(true)}'}, false);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
settings.motd.channels.remove(m.channel_id);
|
|
|
|
|
m.reply({content:'MOTD has been disabled for <#${m.channel_id}>'}, false);
|
|
|
|
|
if (!settings.motd.channels.contains(m.channel_id)) {
|
|
|
|
|
settings.motd.channels.push(m.channel_id);
|
|
|
|
|
m.reply({content:'MOTD has been enabled for <#${m.channel_id}>'}, false);
|
|
|
|
|
} else {
|
|
|
|
|
settings.motd.channels.remove(m.channel_id);
|
|
|
|
|
m.reply({content:'MOTD has been disabled for <#${m.channel_id}>'}, false);
|
|
|
|
|
}
|
|
|
|
|
saveQueue.push("settings");
|
|
|
|
|
}
|
|
|
|
|
saveQueue.push("settings");
|
|
|
|
|
}
|
|
|
|
|
case "trace":
|
|
|
|
|
if (m.author.id == settings.botowner) {
|
|
|
|
|
if (m.referenced_message == null) {
|
|
|
|
|
Sys.println('[${timestamp()}] trace: ${Json.stringify(getMessage(m.channel_id, m.id))}');
|
|
|
|
|
} else {
|
|
|
|
|
Sys.println('[${timestamp()}] trace: ${Json.stringify(getMessage(m.referenced_message.channel_id, m.referenced_message.id))}');
|
|
|
|
|
}
|
|
|
|
|
m.react('✅');
|
|
|
|
|
}
|
|
|
|
|
// System for WIP commands that only work in testing server
|
|
|
|
|
case "wipcommand":
|
|
|
|
@@ -154,8 +189,6 @@ class Onequestionmark {
|
|
|
|
|
}
|
|
|
|
|
// Basic response commands
|
|
|
|
|
case "help":
|
|
|
|
|
/*m.reply({content:'
|
|
|
|
|
**onequestionmark bot commands**\n`?chk`: ack\n`?slap <target>`: The classic mIRC troutslap.\n`?hug <target (optional)>`: Posts randomized hug image.\n`?yes` and `?no`: Posts randomized yes/no.\n`?angery`\n`?subway`\n`?motd`: Enables MOTD for current channel (requires auth)\n`?devmode`: Toggles Dev Mode (requires auth)\n`?quit`: Shutdown command (requires auth)\n**Non-command bot functions:**\n*MOTD*: Bot will post a randomized *Meme of the Day* in enabled channels.\n*Icce*: Bot provides users with ice cuboids.\n*Meteor*: Bot reacts to falling rocks in the chat.'}, false);*/
|
|
|
|
|
m.reply({embeds: [
|
|
|
|
|
{
|
|
|
|
|
"color": 13733022,
|
|
|
|
@@ -171,17 +204,22 @@ class Onequestionmark {
|
|
|
|
|
"fields": [
|
|
|
|
|
{
|
|
|
|
|
"name": "**Bot commands:**",
|
|
|
|
|
"value": "`?help`: Posts this help document. :book:\n`?chk`: Replies with \"ack\". :speaking_head:\n`?slap <target>`: The classic mIRC troutslap. :fish:\n`?hug <target (optional)>`: Posts randomized hug image. :people_hugging:\n`?yes`: Posts randomized \"yes\" image or video. :thumbsup:\n`?no`: Posts randomized \"no\" image or video. :no_entry_sign:\n`?mrkrabs`: Similar to `?chk`. :crab:\n`?tufac`: The official Tufac Theme Song. :busts_in_silhouette:\n`?coin <count (optional)>`: Flips between 1 and 100 coins. :coin:\n`?dice.<arg> <num>`: Rolls <arg>-sided die <num> times :game_die:\n`?8ball`: Answers your yes or no questions. :8ball:\n`?echobox`: Posts randomized quote from the quote database. :loudspeaker:\n`?echobox <quote>`: Adds provided quote to the database. :writing_hand:",
|
|
|
|
|
"value": "`?help`: Posts this help document. :book:\n`?chk`: Replies with \"ack\". :speaking_head:\n`?slap <target>`: The classic mIRC troutslap. :fish:\n`?hug <target (optional)>`: Posts randomized hug image. :people_hugging:\n`?yes`: Posts randomized \"yes\" image or video. :thumbsup:\n`?no`: Posts randomized \"no\" image or video. :no_entry_sign:\n`?mrkrabs`: Similar to `?chk`. :crab:\n`?tufac`: The official Tufac Theme Song. :busts_in_silhouette:\n`?coin <count (optional)>`: Flips between 1 and 100 coins. :coin:\n`?dice.<arg> <num>`: Rolls <arg>-sided die <num> times :game_die:\n`?8ball`: Answers your yes or no questions. :8ball:\n`?echobox`: Posts randomized quote from the quote database. :loudspeaker:\n`?echobox <quote>`: Adds provided quote to the database. :writing_hand:\n`?youtube <query>`: Return first result of a YouTube search. :cinema:",
|
|
|
|
|
"inline": false
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "**Pirate vs. Ninja:**",
|
|
|
|
|
"value": "`?pirate <name>`: Look up stats for a pirate. :pirate_flag:\n`?ninja <name>`: Look up stats for a ninja. :ninja:\n`?pvn <pirate> | <ninja>`: Let them fight! :crossed_swords:\n`?nvp <ninja> | <pirate>`: Same as above but reversed order.",
|
|
|
|
|
"inline": false
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "**Basic Response Aliases:**",
|
|
|
|
|
"value": "`?angery`: *I taste a vegetal.* :rage:\n`?subway`: Arin's infamous Subway rant. :sandwich:\n`?illuminati`: Always watching. :eye:\n`?cube`: *The Cube*\n`?coolsville`: Population: Us :sunglasses:\n`?communism`: The old 3D rotating gif.\n`?opinions`: Here they come. :scream:",
|
|
|
|
|
"value": "`?angery`: *I taste a vegetal.* :rage:\n`?subway`: Arin's infamous Subway rant. :sandwich:\n`?illuminati`: Always watching. :eye:\n`?cube`: *The Cube*\n`?coolsville`: Population: Us :sunglasses:\n`?communism`: The old 3D rotating gif.\n`?opinions`: Here they come. :scream:\n`?fineart`: The great masterpiece. :art:\n`?artishard`: Art *is* hard. :corn:\n`?florida`: :carpentry_saw::rabbit:\n`?stop`: :stop_sign: :smiley:\n`?indeed`",
|
|
|
|
|
"inline": false
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "**Auth-Requiring Commands:**",
|
|
|
|
|
"value": "`?motd`: Enables MOTD for current channel\n`?devmode`: Toggles Dev Mode\n`?quit`: Shutdown command",
|
|
|
|
|
"value": "`?motd`: Enables MOTD for current channel.\n`?devmode`: Toggles Dev Mode.\n`?trace`: Debugging tool.\n`?quit`: Shutdown command.",
|
|
|
|
|
"inline": true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
@@ -200,6 +238,26 @@ class Onequestionmark {
|
|
|
|
|
if (msg.length != 0) {m.reply({content:'*onequestionmark slaps ${msg} around a bit with a large trout*'}, false);}
|
|
|
|
|
case "tufac":
|
|
|
|
|
m.reply({content:"*Not Teh Face, but better,*\n*Tufac to the letter!*\n*Always two faces, never one,*\n*Tufac has you on the run!*"}, false);
|
|
|
|
|
case "youtube":
|
|
|
|
|
if (msg.length > 0) {ytlookup(m,msg);}
|
|
|
|
|
case "ytdebug":
|
|
|
|
|
if (FileSystem.exists("export/ytlookup_result.txt")) {
|
|
|
|
|
var debugRename = 'export/${datestamp()}_${StringTools.lpad(Std.string(Date.now().getHours()), "0", 2)}-${StringTools.lpad(Std.string(Date.now().getMinutes()), "0", 2)}-${StringTools.lpad(Std.string(Date.now().getSeconds()), "0", 2)}_ytdebug.txt';
|
|
|
|
|
FileSystem.rename("export/ytlookup_result.txt", debugRename);
|
|
|
|
|
Sys.println('[${timestamp()}] ytdebug: Created debug file ${debugRename}');
|
|
|
|
|
m.react('✅');
|
|
|
|
|
} else {
|
|
|
|
|
Sys.println('[${timestamp()}] ytdebug: No result to debug');
|
|
|
|
|
m.reply({content:"ytdebug: No result to debug"}, false);
|
|
|
|
|
}
|
|
|
|
|
case "pirate":
|
|
|
|
|
if (msg.length > 0) {pvn("p",msg,m);}
|
|
|
|
|
case "ninja":
|
|
|
|
|
if (msg.length > 0) {pvn("n",msg,m);}
|
|
|
|
|
case "pvn":
|
|
|
|
|
if (msg.length > 0) {pvn("pvn",msg,m);}
|
|
|
|
|
case "nvp":
|
|
|
|
|
if (msg.length > 0) {pvn("nvp",msg,m);}
|
|
|
|
|
// Gaming functions
|
|
|
|
|
case "coin":
|
|
|
|
|
if ((msg.length == 0) || (Std.parseInt(msg) == null) || (Std.parseInt(msg) == 1)) {
|
|
|
|
@@ -275,15 +333,31 @@ class Onequestionmark {
|
|
|
|
|
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147590229960691742/communism.gif"}, false);
|
|
|
|
|
case "opinions":
|
|
|
|
|
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147983375701921892/opinions.jpg"}, false);
|
|
|
|
|
case "fineart":
|
|
|
|
|
m.reply({content:"https://cdn.discordapp.com/attachments/270113422232911883/423323629116325898/unknown.png"}, false);
|
|
|
|
|
case "artishard":
|
|
|
|
|
m.reply({content:"https://cdn.discordapp.com/attachments/270113422232911883/356638565834424330/tumblr_nrxhzoF0KM1t75ioqo2_250.jpg"}, false);
|
|
|
|
|
case "indeed":
|
|
|
|
|
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1305252892491649145/indeed.gif"}, false);
|
|
|
|
|
case "florida":
|
|
|
|
|
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1305253566348529696/florida.gif"}, false);
|
|
|
|
|
case "stop":
|
|
|
|
|
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1305253985036275712/stop.jpg"}, false);
|
|
|
|
|
// Image database commands
|
|
|
|
|
case "hug":
|
|
|
|
|
m.reply({content:'🫂 *hugs ${msg}*\n${hugDB[randInt(0, hugDB.length-1)]}'}, false);
|
|
|
|
|
if (hugDB.length > 0) {
|
|
|
|
|
m.reply({content:'🫂 *hugs ${msg}*\n${hugDB[randInt(0, hugDB.length-1)]}'}, false);
|
|
|
|
|
}
|
|
|
|
|
case "yes":
|
|
|
|
|
var yes = yesnoDB.yes;
|
|
|
|
|
m.reply({content:'${yes[randInt(0, yes.length-1)]}'}, false);
|
|
|
|
|
if (yes.length > 0) {
|
|
|
|
|
m.reply({content:'${yes[randInt(0, yes.length-1)]}'}, false);
|
|
|
|
|
}
|
|
|
|
|
case "no":
|
|
|
|
|
var no = yesnoDB.no;
|
|
|
|
|
m.reply({content:'${no[randInt(0, no.length-1)]}'}, false);
|
|
|
|
|
if (no.length > 0) {
|
|
|
|
|
m.reply({content:'${no[randInt(0, no.length-1)]}'}, false);
|
|
|
|
|
}
|
|
|
|
|
// Echobox
|
|
|
|
|
case "echobox":
|
|
|
|
|
// Create Echobox array for the server if missing
|
|
|
|
@@ -327,127 +401,244 @@ class Onequestionmark {
|
|
|
|
|
if ((iceRegex.match(msg)) && (m.author.id != botInfo.id)) {m.reply({content:"Did some carbon-based lifeform just say **I C E**?"});}
|
|
|
|
|
if ((m.mention_everyone == true) && (msg.charAt(0) != "?")) {m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147598637241741343/at_everyone.jpg"}, true);}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Time-related functions
|
|
|
|
|
public static function timestamp() {
|
|
|
|
|
return '${StringTools.lpad(Std.string(Date.now().getHours()), "0", 2)}:${StringTools.lpad(Std.string(Date.now().getMinutes()), "0", 2)}';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function datestamp() {
|
|
|
|
|
return '${StringTools.lpad(Std.string(Date.now().getMonth()+1), "0", 2)}-${StringTools.lpad(Std.string(Date.now().getDate()), "0", 2)}';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean print day of week
|
|
|
|
|
public static function checkDay() {
|
|
|
|
|
var day = "";
|
|
|
|
|
switch (Date.now().getDay()+1) {
|
|
|
|
|
case 1:
|
|
|
|
|
day = "sun";
|
|
|
|
|
case 2:
|
|
|
|
|
day = "mon";
|
|
|
|
|
case 3:
|
|
|
|
|
day = "tues";
|
|
|
|
|
case 4:
|
|
|
|
|
day = "wed";
|
|
|
|
|
case 5:
|
|
|
|
|
day = "thurs";
|
|
|
|
|
case 6:
|
|
|
|
|
day = "fri";
|
|
|
|
|
case 7:
|
|
|
|
|
day = "sat";
|
|
|
|
|
/**
|
|
|
|
|
* This function starts the "Meme of the Day" system.
|
|
|
|
|
* A temporary timer is set that counts down to the next MOTD event, at which point the main timer is started on a 24 hour loop.
|
|
|
|
|
*/
|
|
|
|
|
public static function startMotd() {
|
|
|
|
|
var now = Date.now();
|
|
|
|
|
var nextPost = new Date(now.getFullYear(), now.getMonth(), now.getDate(), settings.motd.time, 0, 0);
|
|
|
|
|
if (now.getHours() >= settings.motd.time) {
|
|
|
|
|
if (settings.motd.date != datestamp()) {
|
|
|
|
|
settings.motd.date = datestamp();
|
|
|
|
|
postMotd();
|
|
|
|
|
saveQueue.push("settings");
|
|
|
|
|
}
|
|
|
|
|
nextPost = DateTools.delta(nextPost, 86400000); // Add one day in milliseconds
|
|
|
|
|
}
|
|
|
|
|
return day;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean print month
|
|
|
|
|
public static function checkMonth() {
|
|
|
|
|
var month = "";
|
|
|
|
|
switch (Date.now().getMonth()+1) {
|
|
|
|
|
case 1:
|
|
|
|
|
month = "jan";
|
|
|
|
|
case 2:
|
|
|
|
|
month = "feb";
|
|
|
|
|
case 3:
|
|
|
|
|
month = "mar";
|
|
|
|
|
case 4:
|
|
|
|
|
month = "apr";
|
|
|
|
|
case 5:
|
|
|
|
|
month = "may";
|
|
|
|
|
case 6:
|
|
|
|
|
month = "jun";
|
|
|
|
|
case 7:
|
|
|
|
|
month = "jul";
|
|
|
|
|
case 8:
|
|
|
|
|
month = "aug";
|
|
|
|
|
case 9:
|
|
|
|
|
month = "sep";
|
|
|
|
|
case 10:
|
|
|
|
|
month = "oct";
|
|
|
|
|
case 11:
|
|
|
|
|
month = "nov";
|
|
|
|
|
case 12:
|
|
|
|
|
month = "dec";
|
|
|
|
|
}
|
|
|
|
|
return month;
|
|
|
|
|
}
|
|
|
|
|
// Calculate delay for timers
|
|
|
|
|
var delay = Std.int(nextPost.getTime() - now.getTime());
|
|
|
|
|
|
|
|
|
|
//Get current day of month, padded
|
|
|
|
|
public static function checkDate() {
|
|
|
|
|
return StringTools.lpad(Std.string(Date.now().getDate()), "0", 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function monthdate() {
|
|
|
|
|
return '${checkMonth()}${checkDate()}';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function weekdate() {
|
|
|
|
|
return '${checkDay()}${checkDate()}';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function motd() {
|
|
|
|
|
if (settings.motd.date != datestamp()) {
|
|
|
|
|
Timer.delay(function(){
|
|
|
|
|
settings.motd.date = datestamp();
|
|
|
|
|
settings.motd.posted = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((settings.motd.posted == false) && (Date.now().getHours() >= settings.motd.time)) {
|
|
|
|
|
settings.motd.posted = true;
|
|
|
|
|
postMotd();
|
|
|
|
|
saveQueue.push("settings");
|
|
|
|
|
|
|
|
|
|
var msg = "";
|
|
|
|
|
var day:Array<String> = [];
|
|
|
|
|
|
|
|
|
|
// Check for exact date
|
|
|
|
|
if (motdDB.exists('${monthdate()}-${Date.now().getFullYear()}')) {
|
|
|
|
|
day = motdDB.get('${monthdate()}-${Date.now().getFullYear()}');
|
|
|
|
|
// Start the looped MOTD timer
|
|
|
|
|
motdTimer = new Timer(86400000);
|
|
|
|
|
motdTimer.run = function() {
|
|
|
|
|
settings.motd.date = datestamp();
|
|
|
|
|
postMotd();
|
|
|
|
|
saveQueue.push("settings");
|
|
|
|
|
}
|
|
|
|
|
// Else, check for special date
|
|
|
|
|
else if (motdDB.exists(monthdate())) {
|
|
|
|
|
day = motdDB.get(monthdate());
|
|
|
|
|
}
|
|
|
|
|
// Else, check for special day/date combo
|
|
|
|
|
else if (motdDB.exists(weekdate())) {
|
|
|
|
|
day = motdDB.get(weekdate());
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, post daily meme
|
|
|
|
|
else if (motdDB.exists(checkDay())) {
|
|
|
|
|
day = motdDB.get(checkDay());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (day.length > 0) {msg = day[randInt(0, day.length-1)];}
|
|
|
|
|
|
|
|
|
|
var channels:Array<String> = settings.motd.channels; // Because it won't let me do this directly
|
|
|
|
|
|
|
|
|
|
if (channels.length > 0) {for (i in channels) {if (msg.length > 0) {Endpoints.sendMessage(i, {content:msg}, null, false);}}}
|
|
|
|
|
}
|
|
|
|
|
}, delay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Filesystem operations
|
|
|
|
|
public static function saveSystem() {
|
|
|
|
|
if (saveTimer != Date.now().getMinutes()) { // We only want to run this once a minute
|
|
|
|
|
|
|
|
|
|
if (saveQueue.length > 0) { // See if there's anything in the queue
|
|
|
|
|
/**
|
|
|
|
|
* Sends the MOTD to the channels specified in the bot settings.
|
|
|
|
|
* May just move this code into the places it's needed instead of keeping this function.
|
|
|
|
|
*/
|
|
|
|
|
public static function postMotd() {
|
|
|
|
|
var msg = getMotd(true);
|
|
|
|
|
var channels:Array<String> = settings.motd.channels; // Because it won't let me do this directly
|
|
|
|
|
|
|
|
|
|
if (channels.length > 0) {for (i in channels) {if (msg.length > 0) {Endpoints.sendMessage(i, {content:msg}, null, false);}}}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function checks the MOTD database to find the entry most relevant to the current date and returns the appropriate data.
|
|
|
|
|
*
|
|
|
|
|
* @param update Whether the result should be stored to exclude from the next run.
|
|
|
|
|
*/
|
|
|
|
|
public static function getMotd(update:Bool = false) {
|
|
|
|
|
var msg = "";
|
|
|
|
|
var day:Array<String> = [];
|
|
|
|
|
var special = true;
|
|
|
|
|
|
|
|
|
|
// Check for exact date
|
|
|
|
|
if (motdDB.exists('${datestamp()}')) {
|
|
|
|
|
day = motdDB.get('${datestamp()}');
|
|
|
|
|
}
|
|
|
|
|
// Else, check for special date
|
|
|
|
|
else if (motdDB.exists('${printMonth().substr(0,3)}${printDate()}')) {
|
|
|
|
|
day = motdDB.get('${printMonth().substr(0,3)}${printDate()}');
|
|
|
|
|
}
|
|
|
|
|
// Else, check for special day/date combo
|
|
|
|
|
else if (motdDB.exists('${printDay().substr(0,3)}${printDate()}')) {
|
|
|
|
|
day = motdDB.get('${printDay().substr(0,3)}${printDate()}');
|
|
|
|
|
}
|
|
|
|
|
// Else, check for just the date
|
|
|
|
|
else if (motdDB.exists('${printDate()}')) {
|
|
|
|
|
day = motdDB.get('${printDate()}');
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, post daily meme
|
|
|
|
|
else if (motdDB.exists(printDay().substr(0,3))) {
|
|
|
|
|
day = motdDB.get(printDay().substr(0,3));
|
|
|
|
|
special = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (day.length > 0) {
|
|
|
|
|
if (update && !special && (day.length > 2)) {
|
|
|
|
|
var today = Date.now().getDay();
|
|
|
|
|
var previous:Array<Int> = settings.motd.previous;
|
|
|
|
|
|
|
|
|
|
var choice = randIntExclude(0, day.length-1,[previous[today]]);
|
|
|
|
|
|
|
|
|
|
previous[today] = choice;
|
|
|
|
|
settings.motd.previous = previous;
|
|
|
|
|
|
|
|
|
|
msg = day[choice];
|
|
|
|
|
} else {
|
|
|
|
|
msg = day[randInt(0, day.length-1)];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function scrapes PVN and replies with the appropriate results.
|
|
|
|
|
* @param choice `p`, `n`, `pvn`, or `nvp`.
|
|
|
|
|
* @param query The string to search.
|
|
|
|
|
*/
|
|
|
|
|
public static function pvn(choice:String,query:String,m:Message) {
|
|
|
|
|
if (choice.length > 1) {
|
|
|
|
|
if (query.contains(" | ")) {
|
|
|
|
|
var splitQuery = query.split(" | ");
|
|
|
|
|
query = '${splitQuery[0].urlEncode()}&n2=${splitQuery[1].urlEncode()}';
|
|
|
|
|
} else {
|
|
|
|
|
m.reply({content:'$choice: Must choose two combatants.'}, false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
query = query.urlEncode();
|
|
|
|
|
}
|
|
|
|
|
var req = new haxe.Http('https://majcher.com/project/pvn/pvn.cgi?a=${choice.urlEncode()}&n1=${query}');
|
|
|
|
|
|
|
|
|
|
req.onData = function (request) {
|
|
|
|
|
var data = new HtmlDocument(request);
|
|
|
|
|
var search = data.find("table>tr>td");
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
m.reply({content:'## ${search[0].innerText}\n${search[2].innerText} ${search[3].innerText}\n${search[4].innerText} ${search[5].innerText}\n${search[6].innerText} ${search[7].innerText}\n${search[8].innerText} ${search[9].innerText}\n**${search[10].innerText} ${search[11].innerText}**'}, false);
|
|
|
|
|
|
|
|
|
|
if (choice.length > 1) {
|
|
|
|
|
var result = data.find('div#result');
|
|
|
|
|
|
|
|
|
|
m.reply({content:'## ${search[12].innerText}\n${search[14].innerText} ${search[15].innerText}\n${search[16].innerText} ${search[17].innerText}\n${search[18].innerText} ${search[19].innerText}\n${search[20].innerText} ${search[21].innerText}\n**${search[22].innerText} ${search[23].innerText}**'}, false);
|
|
|
|
|
|
|
|
|
|
m.reply({content:'## ${result[0].innerText}'}, false);
|
|
|
|
|
}
|
|
|
|
|
} catch(e) {
|
|
|
|
|
m.reply({content:'$choice: Error - $e'}, false);
|
|
|
|
|
Sys.println('[${timestamp()}] $choice: Error - $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req.onError = function (error) {
|
|
|
|
|
m.reply({content:'Error in PVN: $error'}, false);
|
|
|
|
|
Sys.println('[${timestamp()}] $choice: Error - $error, request was $query');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req.request();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function performs a YouTube search and replies with the first result.
|
|
|
|
|
* @param m The message data.
|
|
|
|
|
* @param query The string to search.
|
|
|
|
|
*/
|
|
|
|
|
public static function ytlookup(m:Message,query:String) {
|
|
|
|
|
var req = new haxe.Http('https://www.youtube.com/results?search_query=${query.urlEncode()}');
|
|
|
|
|
Sys.println('[${timestamp()}] ytlookup: URL - https://www.youtube.com/results?search_query=${query.urlEncode()}');
|
|
|
|
|
|
|
|
|
|
req.onData = function (request) {
|
|
|
|
|
File.saveContent("export/ytlookup_result.txt", request);
|
|
|
|
|
var data = new HtmlDocument(request);
|
|
|
|
|
var search = data.find("script"); // YouTube obfuscates everything into JS garbage so we have to check every <script>
|
|
|
|
|
|
|
|
|
|
var gotcha = "";
|
|
|
|
|
var result = "Error: Unable to parse YouTube search result.";
|
|
|
|
|
|
|
|
|
|
for (i in search) {
|
|
|
|
|
if (i.toString().contains("\"videoRenderer\":{\"videoId\":\"") || i.toString().contains("\"reelWatchEndpoint\":{\"videoId\":\"")) {
|
|
|
|
|
gotcha = i.toString();
|
|
|
|
|
if ((i.toString().contains("\"reelWatchEndpoint\":{\"videoId\":\"")) && (gotcha.indexOf("\"reelWatchEndpoint\":{\"videoId\":\"") < gotcha.indexOf("\"videoRenderer\":{\"videoId\":\""))) {
|
|
|
|
|
// Top result is a Short
|
|
|
|
|
result = "https://youtu.be/" + gotcha.substring(gotcha.indexOf("\"reelWatchEndpoint\":{\"videoId\":\"")+32, gotcha.indexOf("\"", gotcha.indexOf("\"reelWatchEndpoint\":{\"videoId\":\"")+32));
|
|
|
|
|
|
|
|
|
|
Sys.println('[${timestamp()}] ytlookup: Result - $result (Short)');
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
// Top result is a normal video
|
|
|
|
|
result = "https://youtu.be/" + gotcha.substring(gotcha.indexOf("\"videoRenderer\":{\"videoId\":\"")+28, gotcha.indexOf("\"", gotcha.indexOf("\"videoRenderer\":{\"videoId\":\"")+28));
|
|
|
|
|
|
|
|
|
|
Sys.println('[${timestamp()}] ytlookup: Result - $result (Normal)');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m.reply({content:result}, false);
|
|
|
|
|
m.reply({content:"-# YouTube Lookup is still in beta. If the result seems inaccurate, please use the command `?ytdebug` so the developer can review the data that was received."}, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req.onError = function (error) {
|
|
|
|
|
m.reply({content:'Error in YouTube Lookup: $error'}, false);
|
|
|
|
|
Sys.println('[${timestamp()}] ytlookup: Error - $error, request was $query');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req.request();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retrieves a specific message in the channel and returns it as a JSON object.
|
|
|
|
|
* @param channel_id Numerical ID of the channel.
|
|
|
|
|
* @param m_id Numerical ID of the message.
|
|
|
|
|
*/
|
|
|
|
|
public static function getMessage(channel_id:String, m_id:String) {
|
|
|
|
|
var req:hxdiscord.utils.Http = new hxdiscord.utils.Http("https://discord.com/api/v"+hxdiscord.gateway.Gateway.API_VERSION+"/channels/"+channel_id+"/messages/"+m_id);
|
|
|
|
|
|
|
|
|
|
req.addHeader("User-Agent", "hxdiscord (https://github.com/FurretDev/hxdiscord)");
|
|
|
|
|
req.addHeader("Authorization", DiscordClient.authHeader);
|
|
|
|
|
req.setMethod("GET");
|
|
|
|
|
|
|
|
|
|
var msg:Dynamic = null;
|
|
|
|
|
|
|
|
|
|
req.onData = function(data:String) {
|
|
|
|
|
msg = haxe.Json.parse(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req.onError = function(error) {
|
|
|
|
|
Sys.println('[${timestamp()}] getMessage: Error - $error');
|
|
|
|
|
Sys.println('[${timestamp()}] getMessage: ${req.responseData}');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req.send();
|
|
|
|
|
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The `saveSystem()` function handles all filesystem writes. When the bot needs to save a file, it pushes data to the `saveQueue` array.
|
|
|
|
|
* The save system checks the queue regularly and operates on the first entry provided.
|
|
|
|
|
* This throttles filesystem access, to prevent multiple commands from writing to the drive at the same time.
|
|
|
|
|
*/
|
|
|
|
|
public static function saveSystem() {
|
|
|
|
|
Timer.delay(function() {
|
|
|
|
|
saveTimer = new Timer(60*1000);
|
|
|
|
|
saveTimer.run = function() {
|
|
|
|
|
if (saveQueue.length > 0) { // See if there's anything in the queue
|
|
|
|
|
switch (saveQueue.shift()) {
|
|
|
|
|
case "settings":
|
|
|
|
|
saveSettings();
|
|
|
|
@@ -455,27 +646,32 @@ class Onequestionmark {
|
|
|
|
|
case "echobox":
|
|
|
|
|
saveEchoboxDB();
|
|
|
|
|
Sys.println('[${timestamp()}] Saved echobox-db.json');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveTimer = Date.now().getMinutes(); // Update timer
|
|
|
|
|
}
|
|
|
|
|
}, (60-Date.now().getSeconds())*1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Writes the modified `settings.json` from memory back to the drive.
|
|
|
|
|
*/
|
|
|
|
|
public static function saveSettings() {
|
|
|
|
|
File.saveContent("settings.json", Json.stringify(settings, "\t"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Writes the modified `echobox-db.json` from memory back to the drive.
|
|
|
|
|
*/
|
|
|
|
|
public static function saveEchoboxDB() {
|
|
|
|
|
File.saveContent("echobox-db.json", Json.stringify(echoboxDB, "\t"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// General functions
|
|
|
|
|
public static function randInt(x, y) {
|
|
|
|
|
// Return a random integer between x and y, both inclusive
|
|
|
|
|
return Std.random((y+1)-x)+x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Performs a clean shutdown of the bot, after saving relevant files to the disk.
|
|
|
|
|
*/
|
|
|
|
|
public static function shutdown() {
|
|
|
|
|
saveSettings();
|
|
|
|
|
saveEchoboxDB();
|
|
|
|
|