onequestionmark-chat/Onequestionmark.hx

484 lines
18 KiB
Haxe
Raw Permalink Normal View History

2023-02-26 14:46:18 -06:00
import hxdiscord.DiscordClient;
import hxdiscord.types.*;
import hxdiscord.endpoints.Endpoints;
2023-02-27 20:28:19 -06:00
import haxe.MainLoop;
2023-02-26 14:46:18 -06:00
import haxe.Json;
import sys.io.File;
import sys.FileSystem;
2023-02-26 17:53:53 -06:00
using StringTools;
2023-02-26 14:46:18 -06:00
class Onequestionmark {
2023-03-02 12:57:23 -06:00
// Initialize vars
static var Bot:DiscordClient;
2023-02-26 14:46:18 -06:00
static var settings:Dynamic;
2023-02-26 19:00:02 -06:00
static var botInfo:Dynamic;
static var iceRegex:EReg = ~/\bicc?ed?\b/i;
2023-09-03 00:00:11 -05:00
2023-03-02 12:09:41 -06:00
static var hugDB:Array<String>;
2023-04-17 21:55:46 -05:00
static var motdDB:haxe.DynamicAccess<Dynamic> = {};
2023-03-02 22:14:13 -06:00
static var yesnoDB:Dynamic;
2023-09-03 00:00:11 -05:00
static var echoboxDB:haxe.DynamicAccess<Dynamic> = {};
2023-02-26 14:46:18 -06:00
2023-09-02 18:54:33 -05:00
static var saveTimer = Date.now().getMinutes();
static var saveQueue:Array<String> = [];
2023-02-26 14:46:18 -06:00
static function main() {
2023-02-26 17:53:53 -06:00
Sys.println('[${timestamp()}] Starting onequestionmark-chat');
2023-02-26 14:46:18 -06:00
2023-03-02 12:57:23 -06:00
// Load files
2023-02-26 14:46:18 -06:00
if (FileSystem.exists("settings.json")) {
2023-02-26 17:53:53 -06:00
Sys.println('[${timestamp()}] Found settings.json, loading');
2023-02-26 14:46:18 -06:00
settings = Json.parse(File.getContent("settings.json"));
} else {
2023-02-26 17:53:53 -06:00
Sys.println('[${timestamp()}] Did not find settings.json, exiting');
2023-02-26 14:46:18 -06:00
Sys.exit(0);
}
2023-03-02 12:09:41 -06:00
if (FileSystem.exists("hug-db.json")) {
Sys.println('[${timestamp()}] Found hug-db.json, loading');
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
}
2023-03-02 12:57:23 -06:00
if (FileSystem.exists("motd-db.json")) {
Sys.println('[${timestamp()}] Found motd-db.json, loading');
motdDB = Json.parse(File.getContent("motd-db.json"));
} else {
Sys.println('[${timestamp()}] Did not find motd-db.json, skipping');
2023-04-17 21:55:46 -05:00
motdDB = Json.parse('{"sunday": [],"monday": [],"tuesday": [],"wednesday": [],"thursday": [],"friday": [],"saturday": []}'); // Fallback
2023-03-02 12:57:23 -06:00
}
2023-03-02 22:14:13 -06:00
if (FileSystem.exists("yesno-db.json")) {
Sys.println('[${timestamp()}] Found yesno-db.json, loading');
yesnoDB = Json.parse(File.getContent("yesno-db.json"));
} else {
Sys.println('[${timestamp()}] Did not find yesno-db.json, skipping');
yesnoDB = {"yes": [],"no": []}; // Fallback
}
2023-09-03 00:00:11 -05:00
if (FileSystem.exists("echobox-db.json")) {
Sys.println('[${timestamp()}] Found echobox-db.json, loading');
2023-09-03 00:14:46 -05:00
echoboxDB = Json.parse(File.getContent("echobox-db.json"));
2023-09-03 00:00:11 -05:00
} else {
Sys.println('[${timestamp()}] Did not find echobox-db.json, generating');
File.saveContent("echobox-db.json", Json.stringify(echoboxDB, "\t"));
}
2023-02-26 14:46:18 -06:00
// Start the bot
Bot = new DiscordClient(settings.token, [3276799], settings.debug);
Bot.onReady = onReady;
Bot.onMessageCreate = onMessageCreate;
Bot.connect();
2023-02-26 14:46:18 -06:00
}
public static function onReady() {
2023-09-03 00:14:46 -05:00
Sys.sleep(1);
2023-02-26 17:53:53 -06:00
Sys.println('[${timestamp()}] Bot is online');
// Join message from program args
if (Sys.args().length > 0) {
Endpoints.sendMessage(settings.devchannel, {content:Sys.args().join(" ")}, null, false);
}
botInfo = Endpoints.getCurrentUser();
2023-09-02 18:54:33 -05:00
MainLoop.add(motd);
MainLoop.add(saveSystem);
2023-02-26 14:46:18 -06:00
}
public static function onMessageCreate(m:Message) {
2023-02-26 16:11:00 -06:00
// DevMode check
if ((!settings.devmode) || (m.guild_id == settings.devserver)) {
var msg = m.content;
2023-09-04 18:44:46 -05:00
var sender:String;
if (m.getMember().nick == null) {sender = m.author.global_name;} else {sender = m.getMember().nick;}
2023-02-26 16:11:00 -06:00
// Command processor
if (msg.charAt(0) == "?") {
var command = "";
2023-09-04 18:44:46 -05:00
var args:Array<String> = [];
2023-02-26 16:11:00 -06:00
if (msg.indexOf(" ") != -1) {
// Separate command from rest of message, if necessary
command = msg.substring(1, msg.indexOf(" ")).toLowerCase();
msg = msg.substring(msg.indexOf(" ")+1);
} else {
// Otherwise, remove msg
command = msg.substring(1).toLowerCase();
msg = "";
}
2023-09-04 18:44:46 -05:00
// Separate arguments from command
if (command.indexOf(".") != -1) {
args = command.substring(command.indexOf(".")+1).toLowerCase().split(".");
command = command.substring(0, command.indexOf(".")).toLowerCase();
}
2023-02-26 16:11:00 -06:00
switch (command) {
// Commands that require authorization
case "quit":
if (m.author.id == settings.botowner) {
m.react('');
2023-02-26 17:53:53 -06:00
Sys.println('[${timestamp()}] Shutdown command received from botowner, exiting');
2023-02-26 17:41:40 -06:00
shutdown();
2023-02-26 16:11:00 -06:00
}
2023-02-26 17:43:36 -06:00
case "devmode":
if (m.author.id == settings.botowner) {
if (!settings.devmode) {
settings.devmode = true;
m.reply({content:"DevMode enabled."}, false);
} else {
settings.devmode = false;
m.reply({content:"DevMode disabled."}, false);
}
}
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);
} else {
settings.motd.channels.remove(m.channel_id);
m.reply({content:'MOTD has been disabled for <#${m.channel_id}>'}, false);
}
2023-09-02 18:54:33 -05:00
saveQueue.push("settings");
2023-02-26 17:43:36 -06:00
}
2023-02-26 16:11:00 -06:00
// System for WIP commands that only work in testing server
case "wipcommand":
if (m.guild_id == settings.devserver) {
m.reply({content:"Dev Server response."}, true);
}
// Basic response commands
2023-03-01 14:26:52 -06:00
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,
"url": "https://discord.com",
"author": {
"name": "onequestionmark help",
"url": "https://discord.com",
"icon_url": "https://cdn.discordapp.com/avatars/1071524991084015756/d623b45d33e8119599b29c5cf5ed532e.png"
},
"thumbnail": {
"url": "https://cdn.discordapp.com/emojis/1071560243051511808.png"
},
"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:",
"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:",
"inline": false
},
{
"name": "**Auth-Requiring Commands:**",
"value": "`?motd`: Enables MOTD for current channel\n`?devmode`: Toggles Dev Mode\n`?quit`: Shutdown command",
"inline": true
},
{
"name": "**Non-command bot functions:**",
"value": "**MOTD**: *Meme of the Day* is posted in enabled channels.\n**Icce**: Bot provides users with ice cuboids. :ice_cube:",
"inline": true
}
]
}
]});
2023-02-26 16:11:00 -06:00
case "chk":
m.reply({content:'<@${m.author.id}>: ack'}, false);
case "mrkrabs":
m.reply({content:'<@${m.author.id}>: ack ack ack ack ack'}, false);
2023-02-26 16:11:00 -06:00
case "slap":
if (msg.length != 0) {m.reply({content:'*onequestionmark slaps ${msg} around a bit with a large trout*'}, false);}
2023-09-04 18:44:46 -05:00
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);
// Gaming functions
case "coin":
if ((msg.length == 0) || (Std.parseInt(msg) == null) || (Std.parseInt(msg) == 1)) {
var coin = randInt(0,1);
if (coin == 1) {
m.reply({content:'${sender} flipped a coin and got heads.'}, false);
} else {
m.reply({content:'${sender} flipped a coin and got tails.'}, false);
}
} else {
if ((Std.parseInt(msg) > 1) && (Std.parseInt(msg) < 101)) {
var heads:Int = 0;
for (i in 1...(Std.parseInt(msg)+1)) {
heads += randInt(0,1);
}
m.reply({content:'${sender} flipped ${Std.parseInt(msg)} coins.\n**Heads:** ${heads}\n**Tails:** ${Std.parseInt(msg)-heads}'}, false);
} else {
m.reply({content:'Please enter a value between 1 and 100.'}, false);
}
}
case "dice":
var sides:Int = 6;
// Set number of sides
if (args.length != 0) {
if (Std.parseInt(args[0]) != null) {
sides = Std.parseInt(args[0]);
}
}
// Validate sides
if (sides == 1) {
m.reply({content:'${sender} rolled a ball or something, idk.'}, false);
} else if (sides == 2) {
m.reply({content:'${sender} tried to roll a 2-sided die (this is called a coin btw).'}, false);
} else if ((sides < 1) || (sides > 100)) {
m.reply({content:'Please enter a value between 1 and 100.'}, false);
} else {
// Roll a single die
if ((msg.length == 0) || (Std.parseInt(msg) == null) || (Std.parseInt(msg) == 1)) {
m.reply({content:'${sender} rolled a ${sides}-sided die and got ${randInt(1,sides)}.'}, false);
} else {
// Roll multiple dice
if ((Std.parseInt(msg) > 1) && (Std.parseInt(msg) < 101)) {
var results:Array<Int> = [];
for (i in 1...(Std.parseInt(msg)+1)) {
results.push(randInt(1,sides));
}
// Sort results high-to-low
results.sort((a, b) -> b - a);
m.reply({content:'${sender} rolled a ${sides}-sided die ${Std.parseInt(msg)} times.\n**Results:** ${results.toString()}'}, false);
} else {
m.reply({content:'Please enter a value between 1 and 100.'}, false);
}
}
}
case "8ball":
var response:Array<String> = ["It is certain.","It is decidedly so.","Without a doubt.","Yes definitely.","You may rely on it.","As I see it, yes.","Most likely.","Outlook good.","Yes.","Signs point to yes.","Reply hazy, try again.","Ask again later.","Better not tell you now.","Cannot predict now.","Concentrate and ask again.","Don't count on it.","My reply is no.","My sources say no.","Outlook not so good.","Very doubtful."];
m.reply({content:'🎱 ${response[randInt(0,19)]} 🎱'}, false);
// Basic aliases
2023-02-26 16:11:00 -06:00
case "angery":
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1079518504413311108/angery.jpg"}, false);
case "subway":
m.reply({content:"https://www.youtube.com/watch?v=y3VRXVvr6XU"}, false);
case "illuminati":
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147204253039984671/illuminati.gif"}, false);
case "cube":
m.reply({content:"https://cdn.discordapp.com/attachments/270113422232911883/502690458779123722/the_cube.jpg"}, false);
case "coolsville":
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147583765212835921/coolsville.gif"}, false);
case "communism":
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147590229960691742/communism.gif"}, false);
2023-09-04 18:44:46 -05:00
case "opinions":
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147983375701921892/opinions.jpg"}, false);
2023-02-26 17:41:40 -06:00
// Image database commands
case "hug":
2023-03-02 12:09:41 -06:00
m.reply({content:'🫂 *hugs ${msg}*\n${hugDB[randInt(0, hugDB.length-1)]}'}, false);
2023-03-02 22:14:13 -06:00
case "yes":
var yes = yesnoDB.yes;
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);
2023-09-03 00:00:11 -05:00
// Echobox
case "echobox":
// Create Echobox array for the server if missing
if (!echoboxDB.exists('server${m.guild_id}')) {
echoboxDB.set('server${m.guild_id}', []);
}
var echobox:Array<String> = echoboxDB.get('server${m.guild_id}');
var quote:String = "";
// Echobox response
if ((!m.mention_everyone) && (m.mentions.length == 0) && (m.mention_roles.length == 0)) {
2023-09-03 00:00:11 -05:00
if (echobox.length > 0) {
var ebchoice = randInt(0, echobox.length-1);
quote = '**Echobox, quote #${ebchoice+1}:** ${echobox[ebchoice]}';
} else {
quote = '**Echobox:** There are no quotes in this server\'s Echobox yet.';
}
// Add quote to Echobox
if (msg.length != 0) {
if (!echobox.contains(msg)) {
echobox.push(msg);
echoboxDB.set('server${m.guild_id}', echobox);
saveQueue.push("echobox");
m.reply({content:quote}, false);
} else {
m.reply({content:'**Echobox:** This quote is already in the database.'}, false);
}
} else {
m.reply({content:quote}, false);
}
} else {
m.reply({content:'**Echobox:** You cannot add mentions to the Echobox.'}, false);
2023-09-03 00:00:11 -05:00
}
2023-02-26 16:11:00 -06:00
}
}
// Non-command responses
if ((msg.charAt(0) == ".") && (msg.length == 1)) {m.reply({content:"omg a meteor"}, true);}
2023-02-26 19:00:02 -06:00
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);}
2023-02-26 16:11:00 -06:00
}
2023-02-26 14:46:18 -06:00
}
2023-02-26 17:41:40 -06:00
2023-02-27 20:28:19 -06:00
// Time-related functions
2023-02-26 17:53:53 -06:00
public static function timestamp() {
return '${StringTools.lpad(Std.string(Date.now().getHours()), "0", 2)}:${StringTools.lpad(Std.string(Date.now().getMinutes()), "0", 2)}';
}
2023-02-27 20:28:19 -06:00
public static function datestamp() {
return '${StringTools.lpad(Std.string(Date.now().getMonth()+1), "0", 2)}-${StringTools.lpad(Std.string(Date.now().getDate()), "0", 2)}';
}
2023-04-17 21:55:46 -05:00
// 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";
}
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;
}
//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()}';
}
2023-02-27 20:28:19 -06:00
public static function motd() {
if (settings.motd.date != datestamp()) {
settings.motd.date = datestamp();
settings.motd.posted = false;
}
if ((settings.motd.posted == false) && (Date.now().getHours() >= settings.motd.time)) {
settings.motd.posted = true;
2023-09-02 18:54:33 -05:00
saveQueue.push("settings");
2023-03-02 12:09:41 -06:00
2023-02-27 20:28:19 -06:00
var msg = "";
2023-03-02 12:57:23 -06:00
var day:Array<String> = [];
2023-04-17 21:55:46 -05:00
// Check for exact date
if (motdDB.exists('${monthdate()}-${Date.now().getFullYear()}')) {
day = motdDB.get('${monthdate()}-${Date.now().getFullYear()}');
}
// Else, check for special date
else if (motdDB.exists(monthdate())) {
2023-04-17 21:55:46 -05:00
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());
2023-02-27 20:28:19 -06:00
}
2023-03-02 12:57:23 -06:00
if (day.length > 0) {msg = day[randInt(0, day.length-1)];}
2023-02-27 20:28:19 -06:00
var channels:Array<String> = settings.motd.channels; // Because it won't let me do this directly
2023-03-02 12:57:23 -06:00
if (channels.length > 0) {for (i in channels) {if (msg.length > 0) {Endpoints.sendMessage(i, {content:msg}, null, false);}}}
2023-02-27 20:28:19 -06:00
}
}
2023-09-02 18:54:33 -05:00
// 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
switch (saveQueue.shift()) {
case "settings":
saveSettings();
Sys.println('[${timestamp()}] Saved settings.json');
2023-09-03 00:00:11 -05:00
case "echobox":
saveEchoboxDB();
Sys.println('[${timestamp()}] Saved echobox-db.json');
2023-09-02 18:54:33 -05:00
}
}
saveTimer = Date.now().getMinutes(); // Update timer
}
2023-02-27 20:28:19 -06:00
}
2023-02-26 17:41:40 -06:00
public static function saveSettings() {
File.saveContent("settings.json", Json.stringify(settings, "\t"));
}
2023-09-03 00:00:11 -05:00
public static function saveEchoboxDB() {
File.saveContent("echobox-db.json", Json.stringify(echoboxDB, "\t"));
}
2023-09-02 18:54:33 -05:00
// 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;
}
2023-02-26 17:41:40 -06:00
public static function shutdown() {
saveSettings();
2023-09-03 00:00:11 -05:00
saveEchoboxDB();
2023-02-26 17:41:40 -06:00
Sys.exit(0);
}
2023-02-26 14:46:18 -06:00
}