Compare commits
10 Commits
2035710bf7
...
1ec9c93bda
Author | SHA1 | Date | |
---|---|---|---|
1ec9c93bda | |||
7be7970194 | |||
8da2c94dcd | |||
3e36b3839b | |||
be2d5661d4 | |||
193f7f2bf8 | |||
ea5c2da9ef | |||
22342d954f | |||
a36c40daf6 | |||
4c4013612e |
52
CHANGELOG.md
Normal file
52
CHANGELOG.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Release Version 1.2
|
||||||
|
- Added support for MOTDs matching only the date
|
||||||
|
- General purpose functions have been split off into IzzComLib
|
||||||
|
- Improved fallbacks for file-reading commands
|
||||||
|
- Data files for the official instance have been split into a separate repo
|
||||||
|
|
||||||
|
## Release Version 1.1
|
||||||
|
- Echobox
|
||||||
|
- Checks whether the message is already in the database
|
||||||
|
- Makes sure it doesn't say what was just added
|
||||||
|
- Prevents adding `@everyone` or other mentions to the echobox
|
||||||
|
- Changed shutdown message to reaction
|
||||||
|
- Updated MOTD to support exact dates
|
||||||
|
- Optional startup message supplied by program args
|
||||||
|
- New aliases
|
||||||
|
- `?illuminati`
|
||||||
|
- `?communism`
|
||||||
|
- `?tufac` lol
|
||||||
|
- `?mrkrabs` (ack ack ack ack ack)
|
||||||
|
- `?coolsville`
|
||||||
|
- `?cube`
|
||||||
|
- `?opinions`
|
||||||
|
- Respond to `@everyone` with the Caesar pic
|
||||||
|
- Gaming functions:
|
||||||
|
- `?dice` - Rolls a 6-sided die (default) or takes input on size
|
||||||
|
- `?coin` - Flips a coin (default) or takes input on number of coins to flip
|
||||||
|
- `?8ball` - Standard "fortune telling"
|
||||||
|
- Fixed issue if user doesn't have a nickname set
|
||||||
|
- Added new file saving queue system
|
||||||
|
- Updated `?help` with new features, using Embed Builder for nicer-looking display
|
||||||
|
|
||||||
|
## Release Version 1.0 (Minimum Viable Product)
|
||||||
|
- Save / Load settings from file
|
||||||
|
- Git repo that excludes settings file and export / reference folders
|
||||||
|
- Safe shutdown of bot
|
||||||
|
- Dev Mode (OQM ignores everything outside of the testing server)
|
||||||
|
- A way to set up which channels get posted to
|
||||||
|
- Command processor
|
||||||
|
- System for WIP commands that only work in testing server
|
||||||
|
- `?help` command which lists full bot functionality
|
||||||
|
- `?chk` (ack)
|
||||||
|
- `?devmode` toggle
|
||||||
|
- `?hug` image database
|
||||||
|
- `?yes` database
|
||||||
|
- `?no` database
|
||||||
|
- `?slap` command from mIRC
|
||||||
|
- `?angery`
|
||||||
|
- `?subway`
|
||||||
|
- MOTD framework and image database
|
||||||
|
- Automatic *"ICE"* response
|
@ -1,10 +1,11 @@
|
|||||||
import hxdiscord.DiscordClient;
|
import hxdiscord.DiscordClient;
|
||||||
import hxdiscord.types.*;
|
import hxdiscord.types.*;
|
||||||
import hxdiscord.endpoints.Endpoints;
|
import hxdiscord.endpoints.Endpoints;
|
||||||
import haxe.MainLoop;
|
import haxe.Timer;
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
import sys.io.File;
|
import sys.io.File;
|
||||||
import sys.FileSystem;
|
import sys.FileSystem;
|
||||||
|
import izzcomlib.IzzComLib.*;
|
||||||
using StringTools;
|
using StringTools;
|
||||||
|
|
||||||
class Onequestionmark {
|
class Onequestionmark {
|
||||||
@ -19,7 +20,8 @@ class Onequestionmark {
|
|||||||
static var yesnoDB:Dynamic;
|
static var yesnoDB:Dynamic;
|
||||||
static var echoboxDB:haxe.DynamicAccess<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 saveQueue:Array<String> = [];
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ class Onequestionmark {
|
|||||||
hugDB = Json.parse(File.getContent("hug-db.json"));
|
hugDB = Json.parse(File.getContent("hug-db.json"));
|
||||||
} else {
|
} else {
|
||||||
Sys.println('[${timestamp()}] Did not find hug-db.json, skipping');
|
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")) {
|
if (FileSystem.exists("motd-db.json")) {
|
||||||
@ -49,7 +51,7 @@ class Onequestionmark {
|
|||||||
motdDB = Json.parse(File.getContent("motd-db.json"));
|
motdDB = Json.parse(File.getContent("motd-db.json"));
|
||||||
} else {
|
} else {
|
||||||
Sys.println('[${timestamp()}] Did not find motd-db.json, skipping');
|
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")) {
|
if (FileSystem.exists("yesno-db.json")) {
|
||||||
@ -75,24 +77,40 @@ class Onequestionmark {
|
|||||||
Bot.connect();
|
Bot.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `onReady()` function is run upon a successful `Bot.connect()`
|
||||||
|
*/
|
||||||
public static function onReady() {
|
public static function onReady() {
|
||||||
Sys.sleep(1);
|
Sys.sleep(1);
|
||||||
Sys.println('[${timestamp()}] Bot is online');
|
Sys.println('[${timestamp()}] Bot is online');
|
||||||
//Endpoints.sendMessage(settings.devchannel, {content:"onequestionmark-chat alpha test is now running"}, null, false);
|
|
||||||
|
// Join message from program args
|
||||||
|
if (Sys.args().length > 0) {
|
||||||
|
Endpoints.sendMessage(settings.devchannel, {content:Sys.args().join(" ")}, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
botInfo = Endpoints.getCurrentUser();
|
botInfo = Endpoints.getCurrentUser();
|
||||||
|
|
||||||
MainLoop.add(motd);
|
startMotd();
|
||||||
MainLoop.add(saveSystem);
|
saveSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public static function onMessageCreate(m:Message) {
|
||||||
// DevMode check
|
// DevMode check
|
||||||
if ((!settings.devmode) || (m.guild_id == settings.devserver)) {
|
if ((!settings.devmode) || (m.guild_id == settings.devserver)) {
|
||||||
var msg = m.content;
|
var msg = m.content;
|
||||||
|
var sender:String;
|
||||||
|
if (m.getMember().nick == null) {sender = m.author.global_name;} else {sender = m.getMember().nick;}
|
||||||
|
|
||||||
// Command processor
|
// Command processor
|
||||||
if (msg.charAt(0) == "?") {
|
if (msg.charAt(0) == "?") {
|
||||||
var command = "";
|
var command = "";
|
||||||
|
var args:Array<String> = [];
|
||||||
|
|
||||||
if (msg.indexOf(" ") != -1) {
|
if (msg.indexOf(" ") != -1) {
|
||||||
// Separate command from rest of message, if necessary
|
// Separate command from rest of message, if necessary
|
||||||
@ -104,6 +122,12 @@ class Onequestionmark {
|
|||||||
msg = "";
|
msg = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Separate arguments from command
|
||||||
|
if (command.indexOf(".") != -1) {
|
||||||
|
args = command.substring(command.indexOf(".")+1).toLowerCase().split(".");
|
||||||
|
command = command.substring(0, command.indexOf(".")).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
// Commands that require authorization
|
// Commands that require authorization
|
||||||
case "quit":
|
case "quit":
|
||||||
@ -140,14 +164,110 @@ class Onequestionmark {
|
|||||||
}
|
}
|
||||||
// Basic response commands
|
// Basic response commands
|
||||||
case "help":
|
case "help":
|
||||||
m.reply({content:'
|
m.reply({embeds: [
|
||||||
**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);
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]});
|
||||||
case "chk":
|
case "chk":
|
||||||
m.reply({content:'<@${m.author.id}>: ack'}, false);
|
m.reply({content:'<@${m.author.id}>: ack'}, false);
|
||||||
case "mrkrabs":
|
case "mrkrabs":
|
||||||
m.reply({content:'<@${m.author.id}>: ack ack ack ack ack'}, false);
|
m.reply({content:'<@${m.author.id}>: ack ack ack ack ack'}, false);
|
||||||
case "slap":
|
case "slap":
|
||||||
if (msg.length != 0) {m.reply({content:'*onequestionmark slaps ${msg} around a bit with a large trout*'}, false);}
|
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);
|
||||||
|
// 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
|
// Basic aliases
|
||||||
case "angery":
|
case "angery":
|
||||||
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1079518504413311108/angery.jpg"}, false);
|
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1079518504413311108/angery.jpg"}, false);
|
||||||
@ -159,19 +279,25 @@ class Onequestionmark {
|
|||||||
m.reply({content:"https://cdn.discordapp.com/attachments/270113422232911883/502690458779123722/the_cube.jpg"}, false);
|
m.reply({content:"https://cdn.discordapp.com/attachments/270113422232911883/502690458779123722/the_cube.jpg"}, false);
|
||||||
case "coolsville":
|
case "coolsville":
|
||||||
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147583765212835921/coolsville.gif"}, false);
|
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147583765212835921/coolsville.gif"}, 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 "communism":
|
case "communism":
|
||||||
m.reply({content:"https://cdn.discordapp.com/attachments/1071547517847732305/1147590229960691742/communism.gif"}, false);
|
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);
|
||||||
// Image database commands
|
// Image database commands
|
||||||
case "hug":
|
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":
|
case "yes":
|
||||||
var yes = yesnoDB.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":
|
case "no":
|
||||||
var no = yesnoDB.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
|
// Echobox
|
||||||
case "echobox":
|
case "echobox":
|
||||||
// Create Echobox array for the server if missing
|
// Create Echobox array for the server if missing
|
||||||
@ -183,13 +309,11 @@ class Onequestionmark {
|
|||||||
var quote:String = "";
|
var quote:String = "";
|
||||||
|
|
||||||
// Echobox response
|
// Echobox response
|
||||||
if (!m.mention_everyone) {
|
if ((!m.mention_everyone) && (m.mentions.length == 0) && (m.mention_roles.length == 0)) {
|
||||||
if (echobox.length > 0) {
|
if (echobox.length > 0) {
|
||||||
var ebchoice = randInt(0, echobox.length-1);
|
var ebchoice = randInt(0, echobox.length-1);
|
||||||
//m.reply({content:'**Echobox, quote #${ebchoice+1}:** ${echobox[ebchoice]}'}, false);
|
|
||||||
quote = '**Echobox, quote #${ebchoice+1}:** ${echobox[ebchoice]}';
|
quote = '**Echobox, quote #${ebchoice+1}:** ${echobox[ebchoice]}';
|
||||||
} else {
|
} else {
|
||||||
//m.reply({content:'**Echobox:** There are no quotes in this server\'s Echobox yet.'}, false);
|
|
||||||
quote = '**Echobox:** There are no quotes in this server\'s Echobox yet.';
|
quote = '**Echobox:** There are no quotes in this server\'s Echobox yet.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +331,7 @@ class Onequestionmark {
|
|||||||
m.reply({content:quote}, false);
|
m.reply({content:quote}, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.reply({content:'**Echobox:** You cannot add `@everyone` to the Echobox.'}, false);
|
m.reply({content:'**Echobox:** You cannot add mentions to the Echobox.'}, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,125 +343,96 @@ class Onequestionmark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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";
|
|
||||||
}
|
|
||||||
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()}';
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
* 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.
|
||||||
saveQueue.push("settings");
|
*/
|
||||||
|
public static function startMotd() {
|
||||||
var msg = "";
|
var now = Date.now();
|
||||||
var day:Array<String> = [];
|
var nextPost = new Date(now.getFullYear(), now.getMonth(), now.getDate(), settings.motd.time, 0, 0);
|
||||||
|
if (now.getHours() >= settings.motd.time) {
|
||||||
// Check for exact date
|
if (settings.motd.date != datestamp()) {
|
||||||
if (motdDB.exists('${monthdate()}-${Date.now().getFullYear()}')) {
|
settings.motd.date = datestamp();
|
||||||
day = motdDB.get('${monthdate()}-${Date.now().getFullYear()}');
|
saveQueue.push("settings");
|
||||||
|
postMotd();
|
||||||
}
|
}
|
||||||
// Else, check for special date
|
nextPost = DateTools.delta(nextPost, 86400000); // Add one day in milliseconds
|
||||||
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);}}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate delay for timers
|
||||||
|
var delay = Std.int(nextPost.getTime() - now.getTime());
|
||||||
|
|
||||||
|
Timer.delay(function(){
|
||||||
|
settings.motd.date = datestamp();
|
||||||
|
saveQueue.push("settings");
|
||||||
|
postMotd();
|
||||||
|
|
||||||
|
// Start the looped MOTD timer
|
||||||
|
motdTimer = new Timer(86400000);
|
||||||
|
motdTimer.run = function() {
|
||||||
|
settings.motd.date = datestamp();
|
||||||
|
saveQueue.push("settings");
|
||||||
|
postMotd();
|
||||||
|
}
|
||||||
|
}, 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();
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public static function getMotd() {
|
||||||
|
var msg = "";
|
||||||
|
var day:Array<String> = [];
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day.length > 0) {msg = day[randInt(0, day.length-1)];}
|
||||||
|
|
||||||
|
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()) {
|
switch (saveQueue.shift()) {
|
||||||
case "settings":
|
case "settings":
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@ -345,27 +440,32 @@ class Onequestionmark {
|
|||||||
case "echobox":
|
case "echobox":
|
||||||
saveEchoboxDB();
|
saveEchoboxDB();
|
||||||
Sys.println('[${timestamp()}] Saved echobox-db.json');
|
Sys.println('[${timestamp()}] Saved echobox-db.json');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, (60-Date.now().getSeconds())*1000);
|
||||||
saveTimer = Date.now().getMinutes(); // Update timer
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the modified `settings.json` from memory back to the drive.
|
||||||
|
*/
|
||||||
public static function saveSettings() {
|
public static function saveSettings() {
|
||||||
File.saveContent("settings.json", Json.stringify(settings, "\t"));
|
File.saveContent("settings.json", Json.stringify(settings, "\t"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the modified `echobox-db.json` from memory back to the drive.
|
||||||
|
*/
|
||||||
public static function saveEchoboxDB() {
|
public static function saveEchoboxDB() {
|
||||||
File.saveContent("echobox-db.json", Json.stringify(echoboxDB, "\t"));
|
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() {
|
public static function shutdown() {
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveEchoboxDB();
|
saveEchoboxDB();
|
||||||
|
62
README.md
Normal file
62
README.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# onequestionmark-chat
|
||||||
|
|
||||||
|
**onequestionmark** is a Discord bot written in Haxe.
|
||||||
|
|
||||||
|
## Name origin
|
||||||
|
Following the established naming scheme of Maralis and Derxwna's fivequestionmarks and Kuschelyagi's [tenquestionmarks](https://forge.ds8.zone/elsanctum/tenquestionmarks), onequestionmark is named after the glitch Pokémon known only as ["?"](https://bulbapedia.bulbagarden.net/wiki/%3F_(glitch_Pokémon)) (which amusingly has *two* question marks as its sprite).
|
||||||
|
|
||||||
|
"onequestionmark" is never capitalized.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
A `settings.json` file is required to run the bot. An example is provided below:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"motd": {
|
||||||
|
"date": "",
|
||||||
|
"time": 8,
|
||||||
|
"channels": []
|
||||||
|
},
|
||||||
|
"debug": false,
|
||||||
|
"devmode": false,
|
||||||
|
"devchannel": "000000000000000000",
|
||||||
|
"botowner": "000000000000000000",
|
||||||
|
"devserver": "000000000000000000",
|
||||||
|
"token": "fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- onequestionmark will handle most of the `motd` values itself.
|
||||||
|
- `date` is modified by the bot during normal operations.
|
||||||
|
- `time` determines the hour at which the MOTD is posted (24-hour notation, local time).
|
||||||
|
- Using the `?motd` command adds the current channel to the `channels` array.
|
||||||
|
- `debug` determines whether HxDiscord's console debug messages are enabled.
|
||||||
|
- `devmode` restricts the bot's responses to the specified dev server.
|
||||||
|
- `devchannel`, `devserver`, and `botowner` must be filled with the appropriate numerical values. These values can be seen easily in Discord's URLs.
|
||||||
|
- `token` is your Discord bot app token. Don't share it.
|
||||||
|
|
||||||
|
An `echobox-db.json` file will also be generated by onequestionmark upon starting if it fails to find one.
|
||||||
|
|
||||||
|
The `motd-db.json`, `yesno-db.json`, and `hug-db.json` files provided in this repo are examples that will need to be replaced with correct data. Any of these three files can also be removed. If onequestionmark cannot find one of these files at startup, the bot will simply disable the corresponding function.
|
||||||
|
|
||||||
|
`settings.json` and `echobox-db.json` are the only files modified by the bot during operation. As they can contain sensitive data, they are excluded from Git tracking.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
### Requirements
|
||||||
|
- [Haxe Programming Language](https://haxe.org/)
|
||||||
|
- [HxDiscord](https://github.com/furretpaws/hxdiscord) by FurretPaws
|
||||||
|
- [IzzComLib](https://forge.ds8.zone/Izwzyzx/IzzComLib) by Izwzyzx
|
||||||
|
|
||||||
|
Install the libraries [from their git repositories](https://lib.haxe.org/documentation/using-haxelib/#git).
|
||||||
|
|
||||||
|
### Compiling
|
||||||
|
Navigate to the project folder in a terminal and run `haxe hxbuild.hxml`. This will generate `onequestionmark.n` in the export folder.
|
||||||
|
|
||||||
|
The provided `hxbuild.hxml` file is set up to immediately run the bot after compiling.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
`neko onequestionmark.n`
|
||||||
|
|
||||||
|
onequestionmark is designed and tested to run in [NekoVM](https://nekovm.org/download/) (included with Haxe). Other Haxe targets not guaranteed to work (see HxDiscord library's compatibility).
|
||||||
|
|
||||||
|
`onequestionmark.n` should be placed in the same directory as its JSON files.
|
||||||
|
|
||||||
|
It is preferable to stop the bot via its own `?stop` command in Discord, so it can save its settings file before exiting.
|
4
hug-db.json
Normal file
4
hug-db.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[
|
||||||
|
"Insert \"hug\" image URLs here.",
|
||||||
|
"The script will randomly choose between multiple entries."
|
||||||
|
]
|
@ -1,5 +1,6 @@
|
|||||||
-m Onequestionmark
|
-m Onequestionmark
|
||||||
-L hxdiscord
|
-L hxdiscord
|
||||||
|
-L IzzComLib
|
||||||
# hxdiscord uses deprecated functions
|
# hxdiscord uses deprecated functions
|
||||||
-D no-deprecation-warnings
|
-D no-deprecation-warnings
|
||||||
--neko export/onequestionmark.n
|
--neko export/onequestionmark.n
|
||||||
|
37
motd-db.json
Normal file
37
motd-db.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"Sun": [
|
||||||
|
"Sunday Event",
|
||||||
|
"For any day, the script will randomly choose between multiple entries, if provided."
|
||||||
|
],
|
||||||
|
"Mon": [
|
||||||
|
"Monday Event"
|
||||||
|
],
|
||||||
|
"Tue": [
|
||||||
|
"Tuesday Event"
|
||||||
|
],
|
||||||
|
"Wed": [
|
||||||
|
"Wednesday Event"
|
||||||
|
],
|
||||||
|
"Thu": [
|
||||||
|
"Thursday Event"
|
||||||
|
],
|
||||||
|
"Fri": [
|
||||||
|
"Friday Event"
|
||||||
|
],
|
||||||
|
"Sat": [
|
||||||
|
"Saturday Event"
|
||||||
|
],
|
||||||
|
"01": [
|
||||||
|
"Example event to be triggered on the 1st of any month."
|
||||||
|
],
|
||||||
|
"Mon01": [
|
||||||
|
"Example event to be triggered on any Monday that is also the 1st."
|
||||||
|
],
|
||||||
|
"Jan01": [
|
||||||
|
"Example event to be triggered on January 1st."
|
||||||
|
],
|
||||||
|
"2000-01-01": [
|
||||||
|
"Example event to be triggered specifically on January 1st, 2000. (YYYY-MM-DD)",
|
||||||
|
"Entries that are more specific take precedence over less-specific entries."
|
||||||
|
]
|
||||||
|
}
|
10
yesno-db.json
Normal file
10
yesno-db.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"yes": [
|
||||||
|
"Insert \"Yes\" image URLs here.",
|
||||||
|
"The script will randomly choose between multiple entries."
|
||||||
|
],
|
||||||
|
"no": [
|
||||||
|
"Insert \"No\" image URLs here.",
|
||||||
|
"The script will randomly choose between multiple entries."
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user