Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 47 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# Druncord Tanking Bot

Pretty mickey mouse discord bot for "drunk tanking" problem users.
Druncord's in-house bot that handles role management for problematic users, and handles some logging for actions.

Drunk tanking is where a custom role is given that blocks access to most channels for a set period of time for them to cool off.

Relies on Discord.js v12 -

# How to use

To run with the existing config, use:
* npm run testserver - connects to test server using test.js config
* npm run production - connects to druncord using druncord.js config
* ALTERNATE - set the appropriate configuration file in main.js line 7 to the filename without extension that should be used if hosted in an environment that cannot issue 'npm run', and set main.js as the start script.

Fill in a config file and pop it into the config folder. pass the name of the file as a command line argument like so:

Expand All @@ -18,41 +21,54 @@ Will load the app with config file located in ./config/configfilename.js

Behaviour:
The Bot will react to the following events:
* a member is given the drunktankRole
* a member has the drunktankRole taken away
* a member joining - if they have been drunk tanked in the past, it will update the state so their are reflected as tanked in .checktank. NB: This will NOT grant the role back itself, it expects a Role Persistence bot to do this for us. Role persistence may be incorporated at a later date.
* a member leaving - it will mark the member as archived but still tanked, so they do not appear in checktank whilst they are not a member of the server.
* inviteCreate - Stores the invite information into the database for comparing and tracking invite usage. Announces to invitesChannel with invite code and inviter's mention handle, current user.tag, and userID.
* inviteDelete - Deleted invites are not removed from the database, but instead have their db-stored deleted flag flipped to true for posterity. Announces to invitesChannel that the invite was deleted, its use count, and the creator's mention/tag/ID.
* userUpdate - used only to track username changes. passed to handler for guildMemberUpdate to reduce redundant code.
* guildMemberUpdate - Catches all role changes and nickname changes, and receives userUpdate username changes. Special consideration for drunktankRole being given or taken and passes information found in corresponding audit log entry for executing user (passed to drunktankService). All other roles given/taken are logged to rolesChannel with executor mention/tag/ID gave/took role.mention to/from target mention/tag/ID (currently will log all roles except drunktankRole given/taken by the bot when tanked/untanked). Username and Nickname changes are logged to namesChannel with mention/tag/ID and old/new name, specially marked for username change and nickname added/changed/removed.
* guildMemberAdd - Caught when a user joins/rejoins the server. Checks if the user has an active tank record, then assigns the drunktankRole to them if they still have time to server, otherwise gives their old roles back and closes the record with "time served". Checks the invite they used to join, incrememnts its use and logs the usage to invitesChannel and references the inviter and invite code.
* guildMemberRemove - logs their invite used (and inviter if known), date they joined the server and date their account was created (along with days/hours count from trigger), and determines if the user was kicked, banned, or left on their own. If a GuildBanAdd triggers the guildMemberRemove event whilst the user had an active tank record, drunktankService gets called to close the record with "Banned" as the reason.
* message - handles all messages the bot can see. Necessary to catch all commands and responses. Currently ignores messages created by users listed in bypassGMU (includes itself for good reason), and retorts back a random message found in the DB retorts table when DM'd (to be reworked if/when responses are expected from a DM).

The Bot will respond to the following commands:
* .tank - drunk tanks a user. usage: .tank @user reason.
* .tank - drunk tanks a user. usage: .tank @user [duration] reason. -- if duration is not valid, it is part of the reason
* .checktank - Checks the current users in the tank.
* .untank - Untank a user. usage: .untank @user reason.
* .tankstats - Stats for fun.
* .synctank - sync up the 2drunk2party role with the Bot tank log.
* .sipstats - List the stats of all the counted strings
* .help - Sends this help message
* .untank - Untank a user. usage: .untank @user [reason]. -- reason has default "Default - assume time served"
* .tankstats - Stats for fun. Shows top 5 tanked user, tankers, and untankers. Pass a second argument of userID/mention to filter results by involved user (currently nonworking).
* .sipstats - List the stats of all the counted strings (such as "sip") by showing the top 5. -- this command is not listed in help.
* .config - display or edit server configuration details - requires botMasterRole or Administrator privilege.
* .help - Sends the help message of the current commands

When a user is tanked, the bot will automatically remove all of their other roles (provided it has permissions to), excluding any roles listed in rolesToIgnore, and will apply the drunktankRole. It will re-grant the old roles when a user is untanked. This will work either via the commands or the handled events.

When a user is tanked, the bot will automatically remove all of their other roles (provided it has permissions to). It will re-grant these roles when a user is untanked. This will work either via the commands or the handled events.
# Maintenance Access

If you host and maintain a copy of the bot, or just happen to have your user ID held in the developers array in /services/dmService.js, you can DM the bot to execute a command found in the /developer directory. Currently, there is one command found in this directory: reloadcommands.js. executing that command will forcibly reload all JS files in /services, /events, /helpers, /managers, /commands, and /developer, replacing previously existing modules with altered files of the same name. This effectively allows a bot maintainer to hotload any changes in those directories whilst keeping the bot online. Do keep in mind that you could crash your bot if your changes contain any fatal errors (future update planned to test changes of new files prior to overwriting or including them).

Config:
* drunktankRole = Role ID for the role that locks a user out of the other channels
* tankChannel = Channel ID for the drunk-tank channel to message the tankee
* logChannel = Channel ID to log tank events to.
* botMasterRole = Role ID for the role that will be able to execute bot commands. No one without this role should be able to use. Disclaimer: Not tested and probably easily hackable.
* tankUOM = hours, minutes or days
* tankDuration = default duration for people to remain in the tank
* commandPrefix = the bot command prefix
* access_key = Your discord bot access key. Keep this in .env and access via process.env
* json_path = Path to a local json file that the app can use for storage.
* bot_name = Give your bot a name. Call it whatever you please.
* bypassGMU = Array of ID's for events the bot should ignore
* defaultStaffChat = channel id of the staff channel. Not used at present.
* writeMessageToDrunkTank = True to message the drunk tank channel telling the user why, false if not.
* warnAuthorizedUsage = True to warn users for trying to command it with authorization, false if not.
* countedStrings = a list of strings that when the bot detects them as a message, keeps a count of the times the user has said the phrase
* countedJsonPath = Path to a local JSON file for the string counts (sips)

NB: Be careful with your access key. Keep it out of source control. I keep mine in .env and have it gitignored.

Configuration is held in 3 areas - .env, config/filename.js, and the config table in the database. .env contains sensitive information for the bot, such as the access_key for the bot_user, and the database connection details (including the DB user and password). The config file used will load the settings from .env and set the bot name for main.js. The remaining configuration settings have been migrated to be stored in the database on a per-server instance. The Discord Server configurations are:

* commandPrefix - the character to be accepted as the command prefix. Limited to 1 character, default is "."
* botMasterRole - role ID that can control all aspects of the bot, primarily configuration. Users with Administrator privileges are considered bot Masters. Currently limited to one role.
* botUserRole - role ID that the bot will allow to use all commands except botMasterRole commands. Currently limited to one role.
* drunktankRole - the role ID to use for tanking a user.
* tankChannel - the channel ID that a tanked user can see - used for blocking sipCommand triggers and sending tankees a tank message.
* logChannel - channel ID where to log tank/untank commands.
* invitesChannel - channel ID where to log all created/deleted/used invites as well as invite and account information of a user that has left.
* namesChannel - channel ID where to log all username and nickname changes.
* modlogChannel - channel ID where to log all upper-level event logs (currently only includes role changes not involving drunktankRole).
* bypassGMU - list of user/role IDs that the bot will ignore messages from - cannot command the bot at all. The bot itself does not need to be included, it already checks for itself.
* rolesToIgnore - list of role IDs that will not be given or taken by the bot - default config adds the server Booster role to this list, just to avoid errors on attempts.
* rolesICannotTank - list of role IDs the bot is explicitly forbidden from being able to tank/untank. botUserRole and botMasterRole cannot be tanked/untanked, no need to add them.
* tankUOM - the default unit of measure - 'days', 'hours', or 'minutes' - for tanking when a duration is not specified with the tank command. Default is 'hours'.
* tankDuration - the default amount of time to tank when a duration is not specified with the tank command. Default is 12.
* writeMessageToDrunkTank - Whether or not to send an extra message to the tanked user in the drunktank about who tanked them, for how long, and why (if given). Default is false.
* warnAuthorizedUsage - Whether the bot will respond, insultingly, to an unauthorized user's attempt to use a command. Default is false.
* startServer - Boolean flag that determines if the bot is ready to respond to anything other than "configure" commands. Default is false.

When setting up a new server initially, the only base command that will be accepted is "configure" (not to be confused with "config"). "configure" can only be run by an Administrator when the server's "start" property is false. When the command with zero additional arguments is given, the bot is triggered to create a new configuration entry in the database as well as creating new tables for the server, initially loaded with default values. IMPORTANT: the channel that the command ".configure" is initially run in will be used for ALL the log channels until they are changed, so it is advised that the configure command is executed in a private channel. Once all the necessary settings have been configured, the ability to set the "start" property to true can be accepted, and the bot will commence.

NB: Be careful with your access key and your database connection information. Keep it out of source control. We keep ours in .env and have it gitignored. There may be other safe ways to store your sensitive connection information, but just be mindful of who can view it and how it could be revealed.

# License

Expand All @@ -62,7 +78,7 @@ You are free to use this as you please, however I take no responsibility for wha

Originally forked from: https://github.com/julianYaman/hello-world-discord-bot

Copyright 2021 Donald Carnegie
Copyright 2021 Donald Carnegie, modified by kmatsumari.
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
68 changes: 0 additions & 68 deletions src/commands/checkTankCommand.js

This file was deleted.

76 changes: 76 additions & 0 deletions src/commands/checktank.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
function help(prefix) {
return prefix + "checktank - Checks the current users in the tank.\r\n";
}

async function handle(message) {
let ts = Date.now();
var json = await SERVICES.persistenceService.getTankedUsers(message.guild.id,true);
var concat = "";
var toSend = [];

// Due to a need to run an async wait command on every found tanked member,
// we are going to iterate the old fashioned way with forLoop.
// i = index = time_tanked; o = tankRecord;
var tankTimes = Object.keys(json);
for (i = 0; i < tankTimes.length; i++) {
let o = json[tankTimes[i]];
var datediff = HELPERS.helpers.getDateDiffString(ts, parseInt(o.time_tanked, 10));
var tanker = message.guild.members.cache.get(o.tanked_by);
if (tanker == undefined) {
tanker = await message.client.users.fetch(o.tanked_by);
tanker = tanker.username + " (" + tanker.tag + ") (" + tanker.id + ")";
} else {
tanker = tanker.displayName + "(" + tanker.user.tag + ") (" + tanker.id + ")";
}
// Check if the tanked user is still in the server
var inServer = message.guild.members.cache.get(o.user_tanked);
var timeServed = ts > o.time_to_untank ? true : false;
if (inServer === undefined) {
// user is not in the server, find out if it was an uncaught ban
var isBanned = await message.guild.fetchBan(o.user_tanked).catch((e)=>{return false;});
if (isBanned) {
// ban exists and was not caught yet. Close out the tank record and move on to the next tank record.
var user = await message.client.users.fetch(o.user_tanked);
await SERVICES.drunkTankService.untankUser(message.guild.id, user, message.client.user, o, "Banned");
continue;
}
inServer = "**(NOT IN SERVER)**";
} else {
inServer = "";
}
if (o.tanked_by == 0) {
msg = "<@" + o.user_tanked + "> was not tanked by me. I learned about them " + datediff + " ago.";
} else {
msg = "(tanked " + datediff + " ago by " + tanker
+ (o.tank_reason != "" ? " for " + o.tank_reason + ")" : ")");
if (timeServed) {
msg = "<@" + o.user_tanked + "> " + inServer + " **has served their time**. " + msg;
}
else {
msg = "<@" + o.user_tanked + "> " + inServer + " still has time to wait. " + msg;
}
}

//beware of the max length for a message
if ((concat + '\r\n' + msg).length >= 2000) {
toSend.push(concat);
concat = msg;
}
else {
concat += '\r\n' + msg;
}
}
if (concat != "") toSend.push(concat);

if (toSend.length == 0) {
message.channel.send("According to my records, the drunk tank is empty!");
}
else {
toSend.forEach((obj) => {
message.channel.send(obj);
});
}
}

exports.handle = handle;
exports.help = help;
Loading