mirror of
https://github.com/fulpstation/fulpstation.git
synced 2025-12-09 07:54:14 +00:00
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:
@@ -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'.
|
||||
|
||||
@@ -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 */;
|
||||
|
||||
@@ -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 */;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
24
code/modules/discord/discord_link_record.dm
Normal file
24
code/modules/discord/discord_link_record.dm
Normal 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
|
||||
@@ -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><@[returned_id]></b></span>") // < and > print < > in HTML without using them as tags
|
||||
@@ -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]"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user