[READY] SSdiscord and Round Notifications (#44231)

* Notify System

* V2

* Adds a hint

* Stoned fixes round 1

* Use grammar wells I can

* This didnt work

* I wish you could test on TGS without committing

* Jordie fixes round 1

* oops

* This took way longer than it should have taken

* Adds in endnotify for serverops

* Spacing
This commit is contained in:
AffectedArc07
2019-06-29 15:43:25 +01:00
committed by Jordie
parent dc2d666a5b
commit c4e75bc40b
14 changed files with 285 additions and 13 deletions

View File

@@ -1,15 +1,23 @@
Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255.
The latest database version is 5.1; The query to update the schema revision table is:
The latest database version is 5.2; The query to update the schema revision table is:
INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 1);
INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 2);
or
INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 1);
INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 2);
In any query remember to add a prefix to the table names if you use one.
----------------------------------------------------
Version 5.2, 30 May 2019, by AffectedArc07
Added a field to the `player` table to track ckey and discord ID relationships
ALTER TABLE `player`
ADD COLUMN `discord_id` BIGINT NULL DEFAULT NULL AFTER `flags`;
----------------------------------------------------
Version 5.1, 25 Feb 2018, by MrStonedOne
Added four tables to enable storing of stickybans in the database since byond can lose them, and to enable disabling stickybans for a round without depending on a crash free round. Existing stickybans are automagically imported to the tables.

View File

@@ -320,6 +320,7 @@ CREATE TABLE `player` (
`lastadminrank` varchar(32) NOT NULL DEFAULT 'Player',
`accountjoindate` DATE DEFAULT NULL,
`flags` smallint(5) unsigned DEFAULT '0' NOT NULL,
`discord_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`ckey`),
KEY `idx_player_cid_ckey` (`computerid`,`ckey`),
KEY `idx_player_ip_ckey` (`ip`,`ckey`)
@@ -513,7 +514,6 @@ CREATE TABLE `stickyban_matched_cid` (
PRIMARY KEY (`stickyban`, `matched_cid`)
) ENGINE=InnoDB;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;

View File

@@ -320,6 +320,7 @@ CREATE TABLE `SS13_player` (
`lastadminrank` varchar(32) NOT NULL DEFAULT 'Player',
`accountjoindate` DATE DEFAULT NULL,
`flags` smallint(5) unsigned DEFAULT '0' NOT NULL,
`discord_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`ckey`),
KEY `idx_player_cid_ckey` (`computerid`,`ckey`),
KEY `idx_player_ip_ckey` (`ip`,`ckey`)
@@ -513,8 +514,6 @@ CREATE TABLE `SS13_stickyban_matched_cid` (
PRIMARY KEY (`stickyban`, `matched_cid`)
) ENGINE=InnoDB;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;

View File

@@ -1,7 +1,7 @@
//Update this whenever the db schema changes
//make sure you add an update to the schema_version stable in the db changelog
#define DB_MAJOR_VERSION 5
#define DB_MINOR_VERSION 1
#define DB_MINOR_VERSION 2
//Timing subsystem
@@ -78,6 +78,7 @@
#define INIT_ORDER_SHUTTLE -21
#define INIT_ORDER_MINOR_MAPPING -40
#define INIT_ORDER_PATH -50
#define INIT_ORDER_DISCORD -60
#define INIT_ORDER_PERSISTENCE -100
// Subsystem fire priority, from lowest to highest priority

View File

@@ -164,8 +164,6 @@
to_chat(world, "<BR><BR><BR><span class='big bold'>The round has ended.</span>")
log_game("The round has ended.")
if(LAZYLEN(GLOB.round_end_notifiees))
send2irc("Notice", "[GLOB.round_end_notifiees.Join(", ")] the round has ended.")
for(var/I in round_end_events)
var/datum/callback/cb = I

View File

@@ -0,0 +1,115 @@
/*
NOTES:
There is a DB table to track ckeys and associated discord IDs.
This system REQUIRES TGS, and will auto-disable if TGS is not present.
The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasnt properly shutdown
It only writes to the disk every 5 minutes, and it wont write to disk if the file is the same as it was the last time it was written. This is to save on disk writes
The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server.
##################
# HOW THIS WORKS #
##################
ROUNDSTART:
1] The file is loaded and the discord IDs are extracted
2] A ping is sent to the discord with the IDs of people who wished to be notified
3] The file is emptied
MIDROUND:
1] Someone usees the notify verb, it adds their discord ID to the list.
2] On fire, it will write that to the disk, as long as conditions above are correct
END ROUND:
1] The file is force-saved, incase it hasnt fired at end round
This is an absolute clusterfuck, but its my clusterfuck -aa07
*/
SUBSYSTEM_DEF(discord)
name = "Discord"
wait = 3000
init_order = INIT_ORDER_DISCORD
var/list/notify_members = list() // People to save to notify file
var/list/notify_members_cache = list() // Copy of previous list, so the SS doesnt have to fire if no new members have been added
var/list/people_to_notify = list() // People to notify on roundstart
var/list/account_link_cache = list() // List that holds accounts to link, used in conjunction with TGS
var/notify_file = file("data/notify.json")
var/enabled = 0 // Is TGS enabled (If not we wont fire because otherwise this is useless)
/datum/controller/subsystem/discord/Initialize(start_timeofday)
// Check for if we are using TGS, otherwise return and disabless firing
if(world.TgsAvailable())
enabled = 1 // Allows other procs to use this (Account linking, etc)
else
can_fire = 0 // We dont want excess firing
return ..() // Cancel
try
people_to_notify = json_decode(file2text(notify_file))
catch
pass() // The list can just stay as its defualt (blank). Pass() exists because it needs a catch
var/notifymsg = ""
for(var/id in people_to_notify)
// I would use jointext here, but I dont think you can two-side glue with it, and I would have to strip characters otherwise
notifymsg += "<@[id]> " // 22 charaters per notify, 90 notifies per message, so I am not making a failsafe because 90 people arent going to notify at once
if(notifymsg)
send2chat("[notifymsg]", CONFIG_GET(string/chat_announce_new_game)) // Sends the message to the discord, using same config option as the roundstart notification
fdel(notify_file) // Deletes the file
return ..()
/datum/controller/subsystem/discord/fire()
if(!enabled)
return // Dont do shit if its disabled
if(notify_members == notify_members_cache)
return // Dont re-write the file
// If we are all clear
write_notify_file()
/datum/controller/subsystem/discord/Shutdown()
write_notify_file() // Guaranteed force-write on server close
/datum/controller/subsystem/discord/proc/write_notify_file()
if(!enabled) // Dont do shit if its disabled
return
fdel(notify_file) // Deletes the file first to make sure it writes properly
WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file
notify_members_cache = notify_members // Updates the cache list
// Returns ID from ckey
/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey)
var/datum/DBQuery/query_get_discord_id = SSdbcore.NewQuery("SELECT discord_id FROM [format_table_name("player")] WHERE ckey = '[sanitizeSQL(lookup_ckey)]'")
if(!query_get_discord_id.Execute())
qdel(query_get_discord_id)
return
if(query_get_discord_id.NextRow())
. = query_get_discord_id.item[1]
qdel(query_get_discord_id)
// Returns ckey from ID
/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id)
var/datum/DBQuery/query_get_discord_ckey = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE discord_id = '[sanitizeSQL(lookup_id)]'")
if(!query_get_discord_ckey.Execute())
qdel(query_get_discord_ckey)
return
if(query_get_discord_ckey.NextRow())
. = query_get_discord_ckey.item[1]
qdel(query_get_discord_ckey)
// Finalises link
/datum/controller/subsystem/discord/proc/link_account(ckey)
var/datum/DBQuery/link_account = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET discord_id = '[sanitizeSQL(account_link_cache[ckey])]' WHERE ckey = '[sanitizeSQL(ckey)]'")
link_account.Execute()
qdel(link_account)
account_link_cache -= ckey
// Unlink account (Admin verb used)
/datum/controller/subsystem/discord/proc/unlink_account(ckey)
var/datum/DBQuery/unlink_account = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = '[sanitizeSQL(ckey)]'")
unlink_account.Execute()
qdel(unlink_account)
// Clean up a discord account mention
/datum/controller/subsystem/discord/proc/id_clean(input)
var/regex/num_only = regex("\[^0-9\]", "g")
return num_only.Replace(input, "")

View File

@@ -72,6 +72,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/resetasaycolor,
/client/proc/toggleadminhelpsound,
/client/proc/respawn_character,
/client/proc/discord_id_manipulation,
/datum/admins/proc/open_borgopanel
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel))

View File

@@ -75,12 +75,12 @@
GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/notify
name = "notify"
/datum/tgs_chat_command/endnotify
name = "endnotify"
help_text = "Pings the invoker when the round ends"
admin_only = TRUE
/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params)
/datum/tgs_chat_command/endnotify/Run(datum/tgs_chat_user/sender, params)
if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted())
return "[sender.mention], the round has already ended!"
LAZYINITLIST(GLOB.round_end_notifiees)

View File

@@ -0,0 +1,45 @@
// Verb to link discord accounts to BYOND accounts
/client/verb/linkdiscord()
set category = "Special Verbs"
set name = "Link Discord Account"
set desc = "Link your discord account to your BYOND account."
// Safety checks
if(!CONFIG_GET(flag/sql_enabled))
to_chat(src, "<span class='warning'>This feature requires the SQL backend to be running.</span>")
return
if(!SSdiscord) // SS is still starting
to_chat(src, "<span class='notice'>The server is still starting up. Please wait before attempting to link your account </span>")
return
if(!SSdiscord.enabled)
to_chat(src, "<span class='warning'>This feature requires the server is running on the TGS toolkit</span>")
return
var/stored_id = SSdiscord.lookup_id(usr.ckey)
if(!stored_id) // Account is not linked
var/know_how = alert("Do you know how to get a discord user ID? This ID is NOT your discord username and numbers! (Pressing NO will open a guide)","Question","Yes","No","Cancel Linking")
if(know_how == "No") // Opens discord support on how to collect IDs
src << link("https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID")
if(know_how == "Cancel Linking")
return
var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null
SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces
alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, follows by \"verify [usr.ckey]\" in the Discord to successfuly verify your account (Example: @Mr_Terry verify [usr.ckey])")
else // Account is already linked
var/choice = alert("You already have the Discord Account [stored_id] linked to [usr.ckey]. Would you like to link a different account?","Already Linked","Yes","No")
if(choice == "Yes")
var/know_how = alert("Do you know how to get a discord user ID? This ID is NOT your discord username and numbers! (Pressing NO will open a guide)","Question","Yes","No", "Cancel Linking")
if(know_how == "No") // Opens discord support on how to collect IDs
src << link("https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID")
if(know_how == "Cancel Linking")
return
var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null
SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces
alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in the Discord to successfuly verify your account (Example: @Mr_Terry verify [usr.ckey])")
// This is so people cant fill the notify list with a fuckload of ckeys
SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer

View File

@@ -0,0 +1,36 @@
// Verb to manipulate IDs and ckeys
/client/proc/discord_id_manipulation()
set name = "Discord Manipulation"
set category = "Admin"
if(!check_rights(R_ADMIN))
return
holder.discord_manipulation()
/datum/admins/proc/discord_manipulation()
if(!usr.client.holder)
return
if(!SSdiscord.enabled)
to_chat(usr, "<span class='warning'>TGS is not enabled</span>")
return
var/lookup_choice = alert(usr, "Do you wish to lookup account by ID or ckey?", "Lookup Type", "ID", "Ckey", "Cancel")
switch(lookup_choice)
if("ID")
var/lookup_id = input(usr,"Enter Discord ID to lookup ckey") as text
var/returned_ckey = SSdiscord.lookup_id(lookup_id)
if(returned_ckey)
var/unlink_choice = alert(usr, "Discord ID [lookup_id] is linked to Ckey [returned_ckey]. Do you wish to unlink or cancel?", "Account Found", "Unlink", "Cancel")
if(unlink_choice == "Unlink")
SSdiscord.unlink_account(returned_ckey)
else
to_chat(usr, "<span class='warning'>Discord ID <b>[lookup_id]</b> has no associated ckey</span>")
if("Ckey")
var/lookup_ckey = input(usr,"Enter Ckey to lookup ID") as text
var/returned_id = SSdiscord.lookup_id(lookup_ckey)
if(returned_id)
to_chat(usr, "<span class='notice'>Ckey <b>[lookup_ckey]</b> is assigned to Discord ID <b>[returned_id]</b></span>")
to_chat(usr, "<span class='notice'>Discord mention format: <b>&lt;@[returned_id]&gt;</b></span>") // &lt; and &gt; print < > in HTML without using them as tags

View File

@@ -0,0 +1,30 @@
// Notify
/datum/tgs_chat_command/notify
name = "notify"
help_text = "Pings the invoker when the round ends"
/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params)
for(var/member in SSdiscord.notify_members) // If they are in the list, take them out
if(member == "[sender.mention]")
SSdiscord.notify_members -= "[SSdiscord.id_clean(sender.mention)]" // The list uses strings because BYOND cannot handle a 17 digit integer
return "You will no longer be notified when the server restarts"
// If we got here, they arent in the list. Chuck 'em in!
SSdiscord.notify_members += "[SSdiscord.id_clean(sender.mention)]" // The list uses strings because BYOND cannot handle a 17 digit integer
return "You will now be notified when the server restarts"
// Verify
/datum/tgs_chat_command/verify
name = "verify"
help_text = "Verifies your discord account and your BYOND account linkage"
/datum/tgs_chat_command/verify/Run(datum/tgs_chat_user/sender, params)
var/lowerparams = replacetext(lowertext(params), " ", "") // Fuck spaces
if(SSdiscord.account_link_cache[lowerparams]) // First if they are in the list, then if the ckey matches
if(SSdiscord.account_link_cache[lowerparams] == "[SSdiscord.id_clean(sender.mention)]") // If the associated ID is the correct one
SSdiscord.link_account(lowerparams)
return "Successfully linked accounts"
else
return "That ckey is not associated to this discord account. If someone has used your ID, please inform an administrator"
else
return "Account not setup for linkage"

View File

@@ -0,0 +1,34 @@
// Verb to toggle restart notifications
/client/verb/notify_restart()
set category = "Special Verbs"
set name = "Notify Restart"
set desc = "Notifies you on Discord when the server restarts."
// Safety checks
if(!CONFIG_GET(flag/sql_enabled))
to_chat(src, "<span class='warning'>This feature requires the SQL backend to be running.</span>")
return
if(!SSdiscord) // SS is still starting
to_chat(src, "<span class='notice'>The server is still starting up. Please wait before attempting to link your account </span>")
return
if(!SSdiscord.enabled)
to_chat(src, "<span class='warning'>This feature requires the server is running on the TGS toolkit</span>")
return
var/stored_id = SSdiscord.lookup_id(usr.ckey)
if(!stored_id) // Account is not linked
to_chat(src, "<span class='warning'>This requires you to link your Discord account with the \"Link Discord Account\" verb.</span>")
return
else // Linked
for(var/member in SSdiscord.notify_members) // If they are in the list, take them out
if(member == "[stored_id]")
SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer
to_chat(src, "<span class='notice'>You will no longer be notified when the server restarts</span>")
return // This is necassary so it doesnt get added again, as it relies on the for loop being unsuccessful to tell us if they are in the list or not
// If we got here, they arent in the list. Chuck 'em in!
to_chat(src, "<span class='notice'>You will now be notified when the server restarts</span>")
SSdiscord.notify_members += "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer

View File

@@ -454,7 +454,7 @@ MINUTE_CLICK_LIMIT 400
## If using TGS4, the string option can be set as a chat channel tag to limit the message to channels of that tag type (case-sensitive)
## i.e. CHAT_ANNOUNCE_NEW_GAME chat_channel_tag
## Send a message with the station name starting a new game
## Send a message with the station name starting a new game. Also required for the notify function
#CHAT_ANNOUNCE_NEW_GAME

View File

@@ -230,6 +230,7 @@
#include "code\controllers\subsystem\communications.dm"
#include "code\controllers\subsystem\dbcore.dm"
#include "code\controllers\subsystem\dcs.dm"
#include "code\controllers\subsystem\discord.dm"
#include "code\controllers\subsystem\disease.dm"
#include "code\controllers\subsystem\economy.dm"
#include "code\controllers\subsystem\events.dm"
@@ -1617,6 +1618,10 @@
#include "code\modules\detectivework\evidence.dm"
#include "code\modules\detectivework\footprints_and_rag.dm"
#include "code\modules\detectivework\scanner.dm"
#include "code\modules\discord\accountlink.dm"
#include "code\modules\discord\manipulation.dm"
#include "code\modules\discord\tgs_commands.dm"
#include "code\modules\discord\toggle_notify.dm"
#include "code\modules\economy\_economy.dm"
#include "code\modules\economy\account.dm"
#include "code\modules\economy\pay_stand.dm"