diff --git a/code/controllers/subsystem/discord.dm b/code/controllers/subsystem/discord.dm index 7efdbfcda6a55..822b7857301eb 100644 --- a/code/controllers/subsystem/discord.dm +++ b/code/controllers/subsystem/discord.dm @@ -119,7 +119,7 @@ SUBSYSTEM_DEF(discord) /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) + var/datum/discord_link_record/link = find_discord_link_by_ckey(ckey, timebound = TRUE, only_valid = TRUE) //SPLURT EDIT - Lookup only valid links if(link) return link.one_time_token @@ -191,7 +191,7 @@ SUBSYSTEM_DEF(discord) 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/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 ORDER BY timestamp DESC LIMIT 1" //SPLURT EDIT - Order by timestamp var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery( query, list("one_time_token" = one_time_token) @@ -227,7 +227,7 @@ SUBSYSTEM_DEF(discord) if(only_valid) validsql = "AND valid = 1" - 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] [validsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1" + 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] [validsql] GROUP BY ckey, discord_id, one_time_token ORDER BY timestamp DESC LIMIT 1" //SPLURT EDIT - Order by timestamp var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery( query, list("ckey" = ckey) @@ -265,7 +265,7 @@ SUBSYSTEM_DEF(discord) if(only_valid) validsql = "AND valid = 1" - 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] [validsql] GROUP BY ckey, discord_id, one_time_token LIMIT 1" + 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] [validsql] GROUP BY ckey, discord_id, one_time_token ORDER BY timestamp DESC LIMIT 1" //SPLURT EDIT - Order by timestamp var/datum/db_query/query_get_discord_link_record = SSdbcore.NewQuery( query, list("discord_id" = discord_id) diff --git a/code/modules/discord/accountlink.dm b/code/modules/discord/accountlink.dm index 5ff80d34e471e..e0db1bdb607d6 100644 --- a/code/modules/discord/accountlink.dm +++ b/code/modules/discord/accountlink.dm @@ -25,12 +25,22 @@ 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 on Discord by using the command: [prefix]verify [one_time_token]" + //SPLURT EDIT - Only one linked account + var/datum/discord_link_record/existing_link = SSdiscord.find_discord_link_by_ckey(usr?.ckey, only_valid = TRUE) + //Do not create a new entry if they already have a linked account + if(existing_link?.discord_id) + message = "You already have a linked account with discord ID ([existing_link.discord_id]) linked on [existing_link.timestamp]. If you desire to change your account please contact staff." + 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 on Discord by using the command: [prefix]verify [one_time_token]" + //SPLURT EDIT END //Now give them a browse window so they can't miss whatever we told them + //SPLURT EDIT - Notify if they must stay in our discord server + if(CONFIG_GET(flag/forced_discord_stay)) + message += span_warning("Remember that to mantain verification you MUST stay in the discord server") var/datum/browser/window = new/datum/browser(usr, "discordverification", "Discord Verification") window.set_content("
[message]
") window.open() diff --git a/config/config.txt b/config/config.txt index 2bafd3d37e776..28370d5825557 100644 --- a/config/config.txt +++ b/config/config.txt @@ -11,6 +11,9 @@ $include interviews.txt $include lua.txt $include auxtools.txt +# SPLURT configs +$include splurt/discord.txt + # You can use the @ character at the beginning of a config option to lock it from being edited in-game # Example usage: # @SERVERNAME tgstation diff --git a/config/splurt/discord.txt b/config/splurt/discord.txt new file mode 100644 index 0000000000000..2caf282a4078a --- /dev/null +++ b/config/splurt/discord.txt @@ -0,0 +1,20 @@ +# Discord verification channel name +## This config has no actual function, it's only aesthetic lol ## +## Use the actual name of the channel ## +#VERIFICATION_CHANNEL verification + +# Discord verification command name +## The name of the command you use for verification, only in case you've set up an alias for it ## +## Defaults to "verify" ## +#VERIFICATION_COMMAND verify + +# Need discord account to join +## Uncomment to make players need to link their discord accounts to ckeys in order to join the game_mode ## +## The discord verification system makes use of https://github.com/optimumtact/orangescogs ## +## Needs a working database with the station's current schema ## +#DISCORD_BUNKER + +# Discord forcing mode +## Uncomment this if your players need to remain in the discord to keep verification ## +## Note, this only produces aesthetic changes, the real magic happens with the redbot modules +#FORCED_DISCORD_STAY diff --git a/modular_zzplurt/code/_globalvars/mobs.dm b/modular_zzplurt/code/_globalvars/mobs.dm new file mode 100644 index 0000000000000..92a3c83791f14 --- /dev/null +++ b/modular_zzplurt/code/_globalvars/mobs.dm @@ -0,0 +1 @@ +GLOBAL_LIST_EMPTY(discord_passthrough) diff --git a/modular_zzplurt/code/controllers/configuration/entries/discord.dm b/modular_zzplurt/code/controllers/configuration/entries/discord.dm new file mode 100644 index 0000000000000..29b302fe8aa96 --- /dev/null +++ b/modular_zzplurt/code/controllers/configuration/entries/discord.dm @@ -0,0 +1,9 @@ +/datum/config_entry/string/verification_channel + default = "verification" + +/datum/config_entry/string/verification_command + default = "verify" + +/datum/config_entry/flag/discord_bunker + +/datum/config_entry/flag/forced_discord_stay diff --git a/modular_zzplurt/code/controllers/subsystem/discord.dm b/modular_zzplurt/code/controllers/subsystem/discord.dm new file mode 100644 index 0000000000000..14ede844bea1c --- /dev/null +++ b/modular_zzplurt/code/controllers/subsystem/discord.dm @@ -0,0 +1,33 @@ +/datum/controller/subsystem/discord/Initialize() + . = ..() + delete_nulls() + + +/datum/controller/subsystem/discord/proc/check_login(mob/dead/new_player/player) + . = TRUE + if(!(SSdbcore.IsConnected() && CONFIG_GET(flag/discord_bunker) && CONFIG_GET(string/discordbotcommandprefix))) //If not configured/using DB + return TRUE + if(!player.client) //Safety check + return FALSE + if(player.client?.ckey in GLOB.discord_passthrough) //If they have bypass + return TRUE + + var/datum/discord_link_record/player_link = find_discord_link_by_ckey(player.client?.ckey, only_valid = TRUE) + + if(!(player_link && player_link?.discord_id)) + return FALSE + +/datum/controller/subsystem/discord/proc/delete_nulls() + var/query = "DELETE FROM [format_table_name("discord_links")] WHERE discord_id IS NULL" + var/datum/db_query/query_delete_nulls = SSdbcore.NewQuery( + query + ) + if(!query_delete_nulls.Execute()) + log_runtime("DATABASE: There was an error while deleting NULL discord IDs") // This codebase doesn't have a subsystem_log() + message_admins(span_warning("There was an error while deleting NULL IDs, please delete them manually using the Delete Null Discords verb")) + send2adminchat("Discord Subsystem", "There was an error while deleting NULL IDs, please delete them manually using `!tgs discordnulls`") + qdel(query_delete_nulls) + return FALSE + + qdel(query_delete_nulls) + return TRUE diff --git a/modular_zzplurt/code/modules/client/verbs/looc.dm b/modular_zzplurt/code/modules/client/verbs/looc.dm new file mode 100644 index 0000000000000..6a01d74a0dd28 --- /dev/null +++ b/modular_zzplurt/code/modules/client/verbs/looc.dm @@ -0,0 +1,10 @@ +/client/looc(msg as text) + var/vibe_check = SSdiscord?.check_login(usr) + if(isnull(vibe_check)) + to_chat(usr, span_notice("The server is still starting up. Please wait... ")) + return + else if(!vibe_check) //Dirty but I guess we gotta tell when the subsystem hasn't started + to_chat(usr, span_warning("You must link your discord account to your ckey in order to join the game. Join our discord and use the [CONFIG_GET(string/discordbotcommandprefix)][CONFIG_GET(string/verification_command)] command [CONFIG_GET(string/verification_channel) ? "as indicated in #[CONFIG_GET(string/verification_channel)] " : ""]. It won't take you more than two minutes :)
Ahelp or ask staff in the discord if this is an error.")) + return + + . = ..() diff --git a/modular_zzplurt/code/modules/client/verbs/ooc.dm b/modular_zzplurt/code/modules/client/verbs/ooc.dm new file mode 100644 index 0000000000000..55aeb17848052 --- /dev/null +++ b/modular_zzplurt/code/modules/client/verbs/ooc.dm @@ -0,0 +1,10 @@ +/client/ooc(msg as text) + var/vibe_check = SSdiscord?.check_login(usr) + if(isnull(vibe_check)) + to_chat(usr, span_notice("The server is still starting up. Please wait... ")) + return + else if(!vibe_check) //Dirty but I guess we gotta tell when the subsystem hasn't started + to_chat(usr, span_warning("You must link your discord account to your ckey in order to join the game. Join our discord and use the [CONFIG_GET(string/discordbotcommandprefix)][CONFIG_GET(string/verification_command)] command [CONFIG_GET(string/verification_channel) ? "as indicated in #[CONFIG_GET(string/verification_channel)] " : ""]. It won't take you more than two minutes :)
Ahelp or ask staff in the discord if this is an error.")) + return + + . = ..() diff --git a/modular_zzplurt/code/modules/discord/tgs_commands.dm b/modular_zzplurt/code/modules/discord/tgs_commands.dm new file mode 100644 index 0000000000000..1bd9c583910c3 --- /dev/null +++ b/modular_zzplurt/code/modules/discord/tgs_commands.dm @@ -0,0 +1,46 @@ +/datum/tgs_chat_command/adddiscordpass + name = "adddiscordpass" + help_text = "adddiscordpass | Add someone to the discord bunker bypass list" + admin_only = TRUE + +/datum/tgs_chat_command/adddiscordpass/Run(datum/tgs_chat_user/sender, params) + if(!SSdbcore.IsConnected()) + return "The Database is not connected!" + if(!SSdiscord) + return "The discord subsystem hasn't initialized yet!" + if(!CONFIG_GET(flag/discord_bunker)) + return "The Discord Bunker is deactivated!" + + GLOB.discord_passthrough |= ckey(params) + GLOB.discord_passthrough[ckey(params)] = world.realtime + log_admin("[sender.friendly_name] has added [params] to the current round's discord bypass list.") + message_admins("[sender.friendly_name] has added [params] to the current round's discord bypass list.") + return "[params] has been added to the current round's bunker bypass list." + +/datum/tgs_chat_command/revdiscordpass + name = "revdiscordpass" + help_text = "revdiscordpass | Remove someone from the discord bypass list" + admin_only = TRUE + +/datum/tgs_chat_command/revdiscordpass/Run(datum/tgs_chat_user/sender, params) + if(!SSdbcore.IsConnected()) + return "The Database is not connected!" + if(!SSdiscord) + return "The discord subsystem hasn't initialized yet!" + if(!CONFIG_GET(flag/discord_bunker)) + return "The Discord Bunker is deactivated!" + + GLOB.discord_passthrough -= ckey(params) + log_admin("[sender.friendly_name] has removed [params] from the current round's discord bypass list.") + message_admins("[sender.friendly_name] has removed [params] from the current round's discord bypass list.") + return "[params] has been removed from the current round's discord bypass list." + +/datum/tgs_chat_command/discordnulls + name = "discordnulls" + help_text = "Deletes all rows in the database where discord_id is NULL." + admin_only = TRUE + +/datum/tgs_chat_command/discordnulls/Run(datum/tgs_chat_user/sender, params) + log_admin("[sender.friendly_name] has attempted to delete the NULLs from the discord database.") + message_admins("[sender.friendly_name] has attempted to delete the NULLs from the discord database.") + return "[SSdiscord.delete_nulls() ? "NULL rows deleted successfully" : "There was a problem while deleting NULLs"]" diff --git a/modular_zzplurt/code/modules/discord/verbs.dm b/modular_zzplurt/code/modules/discord/verbs.dm new file mode 100644 index 0000000000000..da04e3c428bd8 --- /dev/null +++ b/modular_zzplurt/code/modules/discord/verbs.dm @@ -0,0 +1,45 @@ +ADMIN_VERB(discordbunker, R_SERVER, "Toggle Discord Bunker", "Toggles the Discord Bunker on or off.", ADMIN_CATEGORY_SERVER) + if(!SSdbcore.IsConnected()) + to_chat(user, span_adminnotice("The Database is not connected/enabled!")) + return + + var/new_dbun = !CONFIG_GET(flag/discord_bunker) + CONFIG_SET(flag/discord_bunker, new_dbun) + + log_admin("[key_name(user)] has toggled the Discord Bunker, it is now [new_dbun ? "on" : "off"]") + message_admins("[key_name_admin(user)] has toggled the Discord Bunker, it is now [new_dbun ? "enabled" : "disabled"].") + SSblackbox.record_feedback("nested tally", "discord_toggle", 1, list("Toggle Discord Bunker", "[new_dbun ? "Enabled" : "Disabled"]")) + send2adminchat("Discord Bunker", "[key_name(user)] has toggled the Discord Bunker, it is now [new_dbun ? "enabled" : "disabled"].") + +ADMIN_VERB(adddiscordbypass, R_SERVER, "Add Discord Bypass", "Allows a given ckey to connect through the discord bunker for the round even if they haven't verified yet.", ADMIN_CATEGORY_SERVER, ckeytobypass as text) + if(!SSdbcore.IsConnected()) + to_chat(user, span_adminnotice("The Database is not connected!")) + return + if(!SSdiscord) + to_chat(user, span_adminnotice("The discord subsystem hasn't initialized yet!")) + return + if(!CONFIG_GET(flag/discord_bunker)) + to_chat(user, span_adminnotice("The Discord Bunker is deactivated!")) + return + + GLOB.discord_passthrough |= ckey(ckeytobypass) + GLOB.discord_passthrough[ckey(ckeytobypass)] = world.realtime + log_admin("[key_name(user)] has added [ckeytobypass] to the current round's discord bypass list.") + message_admins("[key_name_admin(user)] has added [ckeytobypass] to the current round's discord bypass list.") + send2adminchat("Discord Bunker", "[key_name(user)] has added [ckeytobypass] to the current round's discord bypass list.") + +ADMIN_VERB(revokediscordbypass, R_SERVER, "Revoke Discord Bypass", "Revoke's a ckey's permission to bypass the discord bunker for a given round.", ADMIN_CATEGORY_SERVER, ckeytobypass as text) + if(!SSdbcore.IsConnected()) + to_chat(user, span_adminnotice("The Database is not connected!")) + return + if(!SSdiscord) + to_chat(user, span_adminnotice("The discord subsystem hasn't initialized yet!")) + return + if(!CONFIG_GET(flag/discord_bunker)) + to_chat(user, span_adminnotice("The Discord Bunker is deactivated!")) + return + + GLOB.discord_passthrough -= ckey(ckeytobypass) + log_admin("[key_name(user)] has removed [ckeytobypass] from the current round's discord bypass list.") + message_admins("[key_name_admin(user)] has removed [ckeytobypass] from the current round's discord bypass list.") + send2adminchat("Discord Bunker", "[key_name(user)] has removed [ckeytobypass] from the current round's discord bypass list.") diff --git a/modular_zzplurt/code/modules/mob/dead/new_player/new_player.dm b/modular_zzplurt/code/modules/mob/dead/new_player/new_player.dm new file mode 100644 index 0000000000000..44db66ca92d58 --- /dev/null +++ b/modular_zzplurt/code/modules/mob/dead/new_player/new_player.dm @@ -0,0 +1,10 @@ +/mob/dead/new_player/Topic(href, list/href_list) + var/vibe_check = SSdiscord?.check_login(src) + if(isnull(vibe_check)) + to_chat(usr, span_notice("The server is still starting up. Please wait... ")) + return + if(href_list["observe"] || href_list["toggle_ready"] || href_list["late_join"]) + if(!vibe_check) + to_chat(src, span_warning("You must link your discord account to your ckey in order to join the game. Join our discord and use the [CONFIG_GET(string/discordbotcommandprefix)][CONFIG_GET(string/verification_command)] command [CONFIG_GET(string/verification_channel) ? "as indicated in #[CONFIG_GET(string/verification_channel)] " : ""]. It won't take you more than two minutes :)
Ahelp or ask staff in the discord if this is an error.")) + return + . = ..() diff --git a/tgstation.dme b/tgstation.dme index c7a5299eeae60..bab184dcf7891 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -9074,4 +9074,12 @@ #include "modular_zubbers\master_files\skyrat\modules\cortical_borer\code\cortical_borer_antag.dm" #include "modular_zubbers\master_files\skyrat\modules\opposing_force\code\opposing_force_subsystem.dm" #include "modular_zubbers\master_files\skyrat\modules\verbs\code\subtle.dm" +#include "modular_zzplurt\code\_globalvars\mobs.dm" +#include "modular_zzplurt\code\controllers\configuration\entries\discord.dm" +#include "modular_zzplurt\code\controllers\subsystem\discord.dm" +#include "modular_zzplurt\code\modules\client\verbs\looc.dm" +#include "modular_zzplurt\code\modules\client\verbs\ooc.dm" +#include "modular_zzplurt\code\modules\discord\tgs_commands.dm" +#include "modular_zzplurt\code\modules\discord\verbs.dm" +#include "modular_zzplurt\code\modules\mob\dead\new_player\new_player.dm" // END_INCLUDE