Overhauls discord verification system (#53289)

This completely replaces the previous verification system, for one that
will interoperate with a discord redbot instance that uses the cogs
located at you github.com/optimumtact/orangescogs

This cuts out several steps in the system, but it also leaves alone the
existing notify system (which just uses a file list of discord ids) as a
record of who to notify

SQL changes required for the new database system

Version 5.10, 7 August 2020, by oranges

Changes how the discord verification process works.
Adds the discord_links table, and migrates discord id entries from
player table to the discord links table in a once off operation and then
removes the discord id on the player table

The user connects to any tg server, and uses the "Verify Discord
Account" verb, this generates a six word one time use token, with a 4
hour time validity period (defined as 4 hours from the timestamp value)
in the discord links table.

This one time token, and the ckey of the user are stored in
discord_links

At this point the entire DM side is done, this is all it does
This commit is contained in:
oranges
2020-08-31 13:24:23 +12:00
committed by GitHub
parent 42e8ddea42
commit 6c9d88a4cd
15 changed files with 327 additions and 252 deletions

View File

@@ -1,15 +1,42 @@
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.9; The query to update the schema revision table is:
The latest database version is 5.10; The query to update the schema revision table is:
INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 9);
INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 10);
or
INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 9);
INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 10);
In any query remember to add a prefix to the table names if you use one.
-----------------------------------------------------
Version 5.10, 7 August 2020, by oranges
Changes how the discord verification process works.
Adds the discord_links table, and migrates discord id entries from player table to the discord links table in a once off operation and then removes the discord id
on the player table
START TRANSACTION;
DROP TABLE IF EXISTS `discord_links`;
CREATE TABLE `discord_links` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ckey` VARCHAR(32) NOT NULL,
`discord_id` BIGINT(20) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`one_time_token` VARCHAR(100) NOT NULL,
`valid` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `discord_links` (`ckey`, `discord_id`, `one_time_token`, `valid`) SELECT `ckey`, `discord_id`, CONCAT("presync_from_player_table_", `ckey`), TRUE FROM `player` WHERE discord_id IS NOT NULL;
ALTER TABLE `player` DROP COLUMN `discord_id`;
COMMIT;
-----------------------------------------------------
Version 5.9, 19 April 2020, by Jordie0608
Updates and improvements to poll handling.
Added the `deleted` column to tables 'poll_option', 'poll_textreply' and 'poll_vote' and the columns `created_datetime`, `subtitle`, `allow_revoting` and `deleted` to 'poll_question'.

View File

@@ -322,7 +322,6 @@ 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`)
@@ -574,6 +573,20 @@ END
$$
DELIMITER ;
--
-- Table structure for table `discord_links`
--
DROP TABLE IF EXISTS `discord_links`;
CREATE TABLE `discord_links` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ckey` VARCHAR(32) NOT NULL,
`discord_id` BIGINT(20) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`one_time_token` VARCHAR(100) NOT NULL,
`valid` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`id`)
) 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

@@ -322,7 +322,6 @@ 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`)
@@ -574,6 +573,20 @@ END
$$
DELIMITER ;
--
-- Table structure for table `discord_links`
--
DROP TABLE IF EXISTS `SS13_discord_links`;
CREATE TABLE `SS13_discord_links` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ckey` VARCHAR(32) NOT NULL,
`discord_id` BIGINT(20) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`one_time_token` VARCHAR(100) NOT NULL,
`valid` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`id`)
) 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

@@ -20,7 +20,7 @@
*
* make sure you add an update to the schema_version stable in the db changelog
*/
#define DB_MINOR_VERSION 9
#define DB_MINOR_VERSION 10
//! ## Timing subsystem

View File

@@ -295,6 +295,21 @@
return copytext(text, 1, i + 1)
return ""
/**
* Truncate a string to the given length
*
* Will only truncate if the string is larger than the length and *ignores unicode concerns*
*
* This exists soley because trim does other stuff too.
*
* Arguments:
* * text - String
* * max_length - integer length to truncate at
*/
/proc/truncate(text, max_length)
if(length(text) > max_length)
return copytext(text, 1, max_length)
//Returns a string with reserved characters and spaces before the first word and after the last word removed.
/proc/trim(text, max_length)
if(max_length)

View File

@@ -44,8 +44,6 @@ GLOBAL_VAR(tgui_log)
GLOBAL_PROTECT(tgui_log)
GLOBAL_VAR(world_shuttle_log)
GLOBAL_PROTECT(world_shuttle_log)
GLOBAL_VAR(discord_api_log)
GLOBAL_PROTECT(discord_api_log)
GLOBAL_VAR(demo_log)
GLOBAL_PROTECT(demo_log)

View File

@@ -495,15 +495,3 @@
/datum/config_entry/string/centcom_source_whitelist
// DISCORD ROLE STUFFS
// Using strings for everything because BYOND does not like numbers this big
// (exception to the above is required living hours haha)
/datum/config_entry/flag/enable_discord_autorole
/datum/config_entry/number/required_living_hours
/datum/config_entry/string/discord_token
/datum/config_entry/string/discord_guildid
/datum/config_entry/string/discord_roleid

View File

@@ -1,30 +1,33 @@
/*
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 wasn't properly shutdown
It only writes to the disk every 5 minutes, and it won't 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 hasn't fired at end round
This is an absolute clusterfuck, but its my clusterfuck -aa07
*/
/**
* # Discord Subsystem
*
* This subsystem handles some integrations with discord
*
*
* NOTES:
* * There is a DB table to track ckeys and associated discord IDs. (discord_link)
* * This system REQUIRES TGS for notifying users at end of the round
* * The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown
* * It only writes to the disk every 5 minutes, and it won't 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 NOTIFYING 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 hasn't fired at end round
*
* This is an absolute clusterfuck, but its my clusterfuck -aa07
*/
SUBSYSTEM_DEF(discord)
name = "Discord"
wait = 3000
@@ -36,15 +39,22 @@ SUBSYSTEM_DEF(discord)
var/list/notify_members_cache = list()
/// People to notify on roundstart
var/list/people_to_notify = list()
/// List that holds accounts to link, used in conjunction with TGS
var/list/account_link_cache = list()
/// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown
var/list/reverify_cache = list()
/// People who have tried to verify this round already
var/list/reverify_cache
/// Common words list, used to generate one time tokens
var/list/common_words
/// The file where notification status is saved
var/notify_file = file("data/notify.json")
/// Is TGS enabled (If not we won't fire because otherwise this is useless)
var/enabled = FALSE
/datum/controller/subsystem/discord/Initialize(start_timeofday)
common_words = world.file2list("strings/1000_most_common.txt")
reverify_cache = list()
// Check for if we are using TGS, otherwise return and disables firing
if(world.TgsAvailable())
enabled = TRUE // Allows other procs to use this (Account linking, etc)
@@ -81,70 +91,186 @@ SUBSYSTEM_DEF(discord)
WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file
notify_members_cache = notify_members // Updates the cache list
// Returns ID from ckey
/**
* Given a ckey, look up the discord user id attached to the user, if any
*
* This gets the most recent entry from the discord link table that is associated with the given ckey
*
* Arguments:
* * lookup_ckey A string representing the ckey to search on
*/
/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey)
//We cast the discord ID to varchar to prevent BYOND mangling
//it into it's scientific notation
var/datum/db_query/query_get_discord_id = SSdbcore.NewQuery(
"SELECT CAST(discord_id AS CHAR(25)) FROM [format_table_name("player")] WHERE ckey = :ckey",
list("ckey" = 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)
var/datum/discord_link_record/link = find_discord_link_by_ckey(lookup_ckey)
if(link)
return link.discord_id
// Returns ckey from ID
/**
* Given a discord id as a string, look up the ckey attached to that account, if any
*
* This gets the most recent entry from the discord_link table that is associated with this discord id snowflake
*
* Arguments:
* * lookup_id The discord id as a string
*/
/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id)
var/datum/db_query/query_get_discord_ckey = SSdbcore.NewQuery(
"SELECT ckey FROM [format_table_name("player")] WHERE discord_id = :discord_id",
list("discord_id" = lookup_id)
var/datum/discord_link_record/link = find_discord_link_by_discord_id(lookup_id)
if(link)
return link.ckey
/datum/controller/subsystem/discord/proc/get_or_generate_one_time_token_for_ckey(ckey)
// Is there an existing valid one time token
var/datum/discord_link_record/link = find_discord_link_by_ckey(ckey, timebound = TRUE)
if(link)
return link.one_time_token
// Otherwise we make one
return generate_one_time_token(ckey)
/**
* Generate a timebound token for discord verification
*
* This uses the common word list to generate a six word random token, this token can then be fed to a discord bot that has access
* to the same database, and it can use it to link a ckey to a discord id, with minimal user effort
*
* It returns the token to the calling proc, after inserting an entry into the discord_link table of the following form
*
* ```
* (unique_id, ckey, null, the current time, the one time token generated)
* the null value will be filled out with the discord id by the integrated discord bot when a user verifies
* ```
*
* Notes:
* * The token is guaranteed to unique during it's validity period
* * The validity period is currently set at 4 hours
* * a token may not be unique outside it's validity window (to reduce conflicts)
*
* Arguments:
* * ckey_for a string representing the ckey this token is for
*
* Returns a string representing the one time token
*/
/datum/controller/subsystem/discord/proc/generate_one_time_token(ckey_for)
var/not_unique = TRUE
var/one_time_token = ""
// While there's a collision in the token, generate a new one (should rarely happen)
while(not_unique)
//Column is varchar 100, so we trim just in case someone does us the dirty later
one_time_token = trim("[pick(common_words)]-[pick(common_words)]-[pick(common_words)]-[pick(common_words)]-[pick(common_words)]-[pick(common_words)]", 100)
not_unique = find_discord_link_by_token(one_time_token, timebound = TRUE)
// Insert into the table, null in the discord id, id and timestamp and valid fields so the db fills them out where needed
var/datum/db_query/query_insert_link_record = SSdbcore.NewQuery(
"INSERT INTO [format_table_name("discord_links")] (ckey, one_time_token) VALUES(:ckey, :token)",
list("ckey" = ckey_for, "token" = one_time_token)
)
if(!query_get_discord_ckey.Execute())
qdel(query_get_discord_ckey)
if(!query_insert_link_record.Execute())
qdel(query_insert_link_record)
return ""
//Cleanup
qdel(query_insert_link_record)
return one_time_token
/**
* Find discord link entry by the passed in user token
*
* This will look into the discord link table and return the *first* entry that matches the given one time token
*
* Remember, multiple entries can exist, as they are only guaranteed to be unique for their validity period
*
* Arguments:
* * one_time_token the string of words representing the one time token
* * timebound A boolean flag, that specifies if it should only look for entries within the last 4 hours, off by default
*
* Returns a [/datum/discord_link_record]
*/
/datum/controller/subsystem/discord/proc/find_discord_link_by_token(one_time_token, timebound = FALSE)
var/timeboundsql = ""
if(timebound)
timeboundsql = "AND timestamp >= Now() - INTERVAL 4 HOUR"
var/query = "SELECT CAST(discord_id AS CHAR(25)), ckey, MAX(timestamp), one_time_token FROM [format_table_name("discord_links")] WHERE one_time_token = :one_time_token [timeboundsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1"
var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery(
query,
list("one_time_token" = one_time_token)
)
if(!query_get_discord_link_record.Execute())
qdel(query_get_discord_link_record)
return
if(query_get_discord_ckey.NextRow())
. = query_get_discord_ckey.item[1]
qdel(query_get_discord_ckey)
if(query_get_discord_link_record.NextRow())
var/result = query_get_discord_link_record.item
. = new /datum/discord_link_record(result[2], result[1], result[4], result[3])
// Finalises link
/datum/controller/subsystem/discord/proc/link_account(ckey)
var/datum/db_query/link_account = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET discord_id = :discord_id WHERE ckey = :ckey",
list("discord_id" = account_link_cache[ckey], "ckey" = ckey)
)
link_account.Execute()
qdel(link_account)
account_link_cache -= ckey
//Make sure we clean up the query
qdel(query_get_discord_link_record)
// Unlink account (Admin verb used)
/datum/controller/subsystem/discord/proc/unlink_account(ckey)
var/datum/db_query/unlink_account = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = :ckey",
/**
* Find discord link entry by the passed in user ckey
*
* This will look into the discord link table and return the *first* entry that matches the given ckey
*
* Remember, multiple entries can exist
*
* Arguments:
* * ckey the users ckey as a string
* * timebound should we search only in the last 4 hours
*
* Returns a [/datum/discord_link_record]
*/
/datum/controller/subsystem/discord/proc/find_discord_link_by_ckey(ckey, timebound = FALSE)
var/timeboundsql = ""
if(timebound)
timeboundsql = "AND timestamp >= Now() - INTERVAL 4 HOUR"
var/query = "SELECT CAST(discord_id AS CHAR(25)), ckey, MAX(timestamp), one_time_token FROM [format_table_name("discord_links")] WHERE ckey = :ckey [timeboundsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1"
var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery(
query,
list("ckey" = 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, "")
/datum/controller/subsystem/discord/proc/grant_role(id)
// Ignore this shit if config isn't enabled for it
if(!CONFIG_GET(flag/enable_discord_autorole))
if(!query_get_discord_link_record.Execute())
qdel(query_get_discord_link_record)
return
var/url = "https://discord.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]"
if(query_get_discord_link_record.NextRow())
var/result = query_get_discord_link_record.item
. = new /datum/discord_link_record(result[2], result[1], result[4], result[3])
// Make the request
var/datum/http_request/req = new()
req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]"))
req.begin_async()
UNTIL(req.is_complete())
var/datum/http_response/res = req.into_response()
//Make sure we clean up the query
qdel(query_get_discord_link_record)
WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]")
/**
* Find discord link entry by the passed in user ckey
*
* This will look into the discord link table and return the *first* entry that matches the given ckey
*
* Remember, multiple entries can exist
*
* Arguments:
* * discord_id The users discord id (string)
* * timebound should we search only in the last 4 hours
*
* Returns a [/datum/discord_link_record]
*/
/datum/controller/subsystem/discord/proc/find_discord_link_by_discord_id(discord_id, timebound = FALSE)
var/timeboundsql = ""
if(timebound)
timeboundsql = "AND timestamp >= Now() - INTERVAL 4 HOUR"
var/query = "SELECT CAST(discord_id AS CHAR(25)), ckey, MAX(timestamp), one_time_token FROM [format_table_name("discord_links")] WHERE discord_id = :discord_id [timeboundsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1"
var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery(
query,
list("discord_id" = discord_id)
)
if(!query_get_discord_link_record.Execute())
qdel(query_get_discord_link_record)
return
if(query_get_discord_link_record.NextRow())
var/result = query_get_discord_link_record.item
. = new /datum/discord_link_record(result[2], result[1], result[4], result[3])
//Make sure we clean up the query
qdel(query_get_discord_link_record)

View File

@@ -144,7 +144,6 @@ GLOBAL_VAR(restart_counter)
GLOB.world_paper_log = "[GLOB.log_directory]/paper.log"
GLOB.tgui_log = "[GLOB.log_directory]/tgui.log"
GLOB.world_shuttle_log = "[GLOB.log_directory]/shuttle.log"
GLOB.discord_api_log = "[GLOB.log_directory]/discord_api_log.log"
GLOB.demo_log = "[GLOB.log_directory]/demo.log"
@@ -164,7 +163,6 @@ GLOBAL_VAR(restart_counter)
start_log(GLOB.world_job_debug_log)
start_log(GLOB.tgui_log)
start_log(GLOB.world_shuttle_log)
start_log(GLOB.discord_api_log)
GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently
if(fexists(GLOB.config_error_log))

View File

@@ -75,7 +75,6 @@ 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

@@ -1,93 +1,32 @@
// Verb to link discord accounts to BYOND accounts
/client/verb/linkdiscord()
set category = "OOC"
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://tgstation13.org/wiki/How_to_find_your_Discord_User_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 Discord to successfully 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")
src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_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 Discord to successfully 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
// IF you have linked your account, this will trigger a verify of the user
/client/verb/verify_in_discord()
set category = "OOC"
set name = "Verify Discord Account"
set desc = "Verify or reverify your discord account against your linked ckey"
set desc = "Verify your discord account with 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
// ss is still starting
if(!SSdiscord)
to_chat(src, "<span class='notice'>The server is still starting up. Please wait before attempting to link your account!</span>")
if(!SSdiscord && !SSdiscord.reverify_cache)
to_chat(src, "<span class='warning'>Wait for the Discord subsystem to finish initialising</span>")
return
var/message = ""
// Simple sanity check to prevent a user doing this too often
var/cached_one_time_token = SSdiscord.reverify_cache[usr.ckey]
if(cached_one_time_token && cached_one_time_token != "")
message = "You already generated your one time token, it is [cached_one_time_token], if you need a new one, you will have to wait until the round ends, or switch to another server, try verifying yourself in discord by using the command <span class=\"warning\">\".verify [cached_one_time_token]\"</span>"
// check that tgs is alive and well
if(!SSdiscord.enabled)
to_chat(src, "<span class='warning'>This feature requires the server is running on the TGS toolkit.</span>")
return
// check that this is not an IDIOT mistaking us for an attack vector
if(SSdiscord.reverify_cache[usr.ckey] == TRUE)
to_chat(src, "<span class='warning'>Thou can only do this once a round, if you're stuck seek help.</span>")
return
SSdiscord.reverify_cache[usr.ckey] = TRUE
else
// Will generate one if an expired one doesn't exist already, otherwise will grab existing token
var/one_time_token = SSdiscord.get_or_generate_one_time_token_for_ckey(ckey)
SSdiscord.reverify_cache[usr.ckey] = one_time_token
message = "Your one time token is: [one_time_token], Assuming you have the required living minutes in game, you can now verify yourself in discord by using the command <span class=\"warning\">\".verify [one_time_token]\"</span>"
// check that account is linked with discord
var/stored_id = SSdiscord.lookup_id(usr.ckey)
if(!stored_id) // Account is not linked
to_chat(usr, "Link your discord account via the linkdiscord verb in the OOC tab first");
return
//Now give them a browse window so they can't miss whatever we told them
var/datum/browser/window = new/datum/browser(usr, "discordverification", "Discord verification")
window.set_content("<span>[message]</span>")
window.open()
// check for living hours requirement
var/required_living_minutes = CONFIG_GET(number/required_living_hours) * 60
var/living_minutes = usr.client ? usr.client.get_exp_living(TRUE) : 0
if(required_living_minutes <= 0)
CRASH("The discord verification system is setup to require zero hours or less, this is likely a configuration bug")
if(living_minutes < required_living_minutes)
to_chat(usr, "<span class='warning'>You must have at least [required_living_minutes] minutes of living " \
+ "playtime in a round to verify. You have [living_minutes] minutes. Play more!</span>")
return
// honey its time for your role flattening
to_chat(usr, "<span class='notice'>Discord verified</span>")
SSdiscord.grant_role(stored_id)

View File

@@ -0,0 +1,24 @@
/// Represents a record from the discord link table in a nicer format
/datum/discord_link_record
var/ckey
var/discord_id
var/one_time_token
var/timestamp
/**
* Generate a discord link datum from the values
*
* This is only used by SSdiscord wrapper functions for now, so you can reference the fields
* slightly easier
*
* Arguments:
* * ckey Ckey as a string
* * discord_id Discord id as a string
* * one_time_token as a string
* * timestamp as a string
*/
/datum/discord_link_record/New(ckey, discord_id, one_time_token, timestamp)
src.ckey = ckey
src.discord_id = discord_id
src.one_time_token = one_time_token
src.timestamp = timestamp

View File

@@ -1,36 +0,0 @@
// 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|null
var/returned_ckey = SSdiscord.lookup_ckey(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|null
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

@@ -15,32 +15,3 @@
// If we got here, they arent in the list. Chuck 'em in!
SSdiscord.notify_members += sender.mention
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
var/discordid = SSdiscord.id_clean(sender.mention)
if(SSdiscord.account_link_cache[lowerparams]) // First if they are in the list, then if the ckey matches
if(SSdiscord.account_link_cache[lowerparams] == discordid) // If the associated ID is the correct one
// Link the account in the DB table
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"
/// Gets the discord user's Discord UserID
/datum/tgs_chat_command/myuserid
name = "myuserid"
help_text = "Returns your userid"
/datum/tgs_chat_command/myuserid/Run(datum/tgs_chat_user/sender, params)
var/discordid = SSdiscord.id_clean(sender.mention)
return "<@[discordid]> Your Discord UserID is [discordid]"

View File

@@ -1821,7 +1821,7 @@
#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\discord_link_record.dm"
#include "code\modules\discord\tgs_commands.dm"
#include "code\modules\discord\toggle_notify.dm"
#include "code\modules\economy\_economy.dm"