diff --git a/cfg/sourcemod/Discord-Utilities.cfg b/cfg/sourcemod/Discord-Utilities.cfg new file mode 100644 index 0000000..7ecf0e1 --- /dev/null +++ b/cfg/sourcemod/Discord-Utilities.cfg @@ -0,0 +1,330 @@ +// This file was auto-generated by AutoExecConfig v0.1.5 (https://forums.alliedmods.net/showthread.php?t=204254) +// ConVars for plugin "discord_utilities.smx" + + +// Webhook for calladmin reports and report handled print. Blank to disable. +// - +// Default: "" +sm_du_calladmin_webhook "" + +// BotName for calladmin. Blank to use webhook name. +// - +// Default: "Discord Utilities" +sm_du_calladmin_botname "" + +// Avatar link for calladmin bot. Blank to use webhook avatar. +// - +// Default: "" +sm_du_calladmin_avatar "" + +// Color for embed message of calladmin. +// - +// Default: "#ff9911" +sm_du_calladmin_color "#ff9911" + +// Content for embed message of calladmin. Blank to disable. +// - +// Default: "When in-game type !calladmin_handle in chat to handle this report." +sm_du_calladmin_content "When in-game type !calladmin_handle in chat to handle this report." + +// Link to footer icon for calladmin. Blank for no footer icon. +// - +// Default: "" +sm_du_calladmin_footericon "" + +// Webhook for bugreport reports. Blank to disable. +// - +// Default: "" +sm_du_bugreport_webhook "" + +// BotName for bugreport. Blank to use webhook name. +// - +// Default: "Discord Utilities" +sm_du_bugreport_botname "Discord Utilities" + +// Avatar link for bugreport bot. Blank to use webhook avatar. +// - +// Default: "" +sm_du_bugreport_avatar "" + +// Color for embed message of bugreport. +// - +// Default: "#ff9911" +sm_du_bugreport_color "#ff9911" + +// Content for embed message of bugreport. Blank to disable. +// - +// Default: "" +sm_du_bugreport_content "" + +// Link to footer icon for bugreport. Blank for no footer icon. +// - +// Default: "" +sm_du_bugreport_footericon "" + +// Webhook for sourcebans. Blank to disable. +// - +// Default: "" +sm_du_sourcebans_webhook "" + +// BotName for sourcebans. Blank to use webhook name. +// - +// Default: "Discord Utilities" +sm_du_sourcebans_botname "Discord Utilities" + +// Avatar link for sourcebans bot. Blank to use webhook avatar. +// - +// Default: "" +sm_du_sourcebans_avatar "" + +// Color for embed message of sourcebans. +// - +// Default: "#0E40E6" +sm_du_sourcebans_color "#0E40E6" + +// Color for embed message of sourcebans when permanent banned. +// - +// Default: "#f00000" +sm_du_sourcebans_perma_color "#f00000" + +// Content for embed message of sourcebans. Blank to disable. +// - +// Default: "" +sm_du_sourcebans_content "" + +// Link to footer icon for sourcebans. Blank for no footer icon. +// - +// Default: "" +sm_du_sourcebans_footericon "" + +// Webhook for sourcecomms. Blank to disable. +// - +// Default: "" +sm_du_sourcecomms_webhook "" + +// BotName for sourcecomms. Blank to use webhook name. +// - +// Default: "Discord Utilities" +sm_du_sourcecomms_botname "Discord Utilities" + +// Avatar link for sourcecomms bot. Blank to use webhook avatar. +// - +// Default: "" +sm_du_sourcecomms_avatar "" + +// Color for embed message of sourcecomms. +// - +// Default: "#FF69B4" +sm_du_sourcecomms_color "#FF69B4" + +// Color for embed message of sourcecomms when permanent banned. +// - +// Default: "#f00000" +sm_du_sourcecomms_perma_color "#f00000" + +// Content for embed message of sourcecomms. Blank to disable. +// - +// Default: "" +sm_du_sourcecomms_content "" + +// Link to footer icon for sourcecomms. Blank for no footer icon. +// - +// Default: "" +sm_du_sourcecomms_footericon "" + +// Webhook for map notification. Blank to disable. +// - +// Default: "" +sm_du_map_webhook "" + +// BotName for map notification. Blank to use webhook name. +// - +// Default: "Discord Utilities" +sm_du_map_botname "Discord Utilities" + +// Avatar link for map notification bot. Blank to use webhook avatar. +// - +// Default: "" +sm_du_map_avatar "" + +// Color for embed message of map notification. +// - +// Default: "#6a0dad" +sm_du_map_color "#6a0dad" + +// Content for embed message of map notification. Blank to disable. +// - +// Default: "" +sm_du_map_content "" + +// Seconds to wait after mapstart to send the map notification webhook. 0 for no delay. +// - +// Default: "25" +sm_du_map_delay "25" + +// Thumbnail link for map notification. Make sure "MAPNAME" is added in the link just like the default value. Blank for none. +// - +// Default: "https://image.gametracker.com/images/maps/160x120/csgo/MAPNAME.jpg" +sm_du_map_thumbnail "https://image.gametracker.com/images/maps/160x120/csgo/MAPNAME.jpg" + +// Webhook for game server => discord server chat messages. Blank to disable. +// - +// Default: "" +sm_du_chat_webhook "" + +// Text that shouldn't appear in gameserver => discord server chat messages. Separate it with ", " +// - +// Default: "rtv, nominate" +sm_du_chat_blocklist "rtv, nominate" + +// Webhook for game server => discord server chat messages where chat messages are to admins (say_team with @ / sm_chat). Blank to disable. +// - +// Default: "" +sm_du_adminchat_webhook "" + +// Text that shouldn't appear in gameserver => discord server where chat messages are to admin. Separate it with ", " +// - +// Default: "rtv, nominate" +sm_du_adminchat_blocklist "rtv, nominate" + +// Webhook for channel where all admin commands are logged. Blank to disable. +// - +// Default: "" +sm_du_adminlog_webhook "" + +// Log with this string will be ignored. Separate it with ", " +// - +// Default: "slapped, firebombed" +sm_du_adminlog_blocklist "" + +// Channel ID for verfication. Blank to disable. +// - +// Default: "" +sm_du_verfication_channelid "" + +// Channel ID for discord server => game server messages. Blank to disable. +// - +// Default: "" +sm_du_chat_channelid "" + +// Guild ID of your discord server. Blank to disable. Needed for verification module. +// - +// Default: "" +sm_du_verification_guildid "" + +// Role ID to give to user when user is verified. Blank to give no role. Verification module needs to be running. +// - +// Default: "" +sm_du_verification_roleid "826136130202107934" + +// Steam API Key (https://steamcommunity.com/dev/apikey). Needed for gameserver => discord server relay and/or admin chat relay and/or Admin logs. Blank will show default author icon of discord. +// - +// Default: "" +sm_du_apikey "" + +// Bot Token. Needed for discord server => gameserver and/or verification module. +// - +// Default: "" +sm_du_bottoken "" + +// DNS IP address of your game server. Blank to use real IP. +// - +// Default: "" +sm_du_dns_ip "" + +// Time in seconds between verifying accounts. 0 for no check. +// - +// Default: "300" +sm_du_accounts_check_interval "300" + +// Use SWGM config file for restricting commands. +// - +// Default: "0" +sm_du_use_swgm_file "0" + +// Display timestamps? Used in gameserver => discord server relay AND AdminLog +// - +// Default: "0" +sm_du_display_timestamps "0" + +// Increase this with every server you put this plugin in. Prevents multiple replies from the bot in verfication channel. +// - +// Default: "1" +sm_du_server_id "1" + +// Is this the primary server in the verification channel? Only this server will respond to generic queries. +// - +// Default: "1" +sm_du_server_primary "1" + +// Command to use in text channel. +// - +// Default: "!link" +sm_du_link_command "!link" + +// Command to view id. +// - +// Default: "sm_viewid" +sm_du_viewid_command "sm_viewid" + +// Command to unlink. +// - +// Default: "sm_unlink" +sm_du_unlink_command "sm_unlink" + +// Invite link of your discord server. +// - +// Default: "https://discord.gg/83g5xcE" +sm_du_link "https://discord.gg/83g5xcE" + +// Prefix for discord messages. +// - +// Default: "[{lightgreen}Discord{default}]" +sm_du_discord_prefix "{lightgreen}[Discord]{default}" + +// Prefix for chat messages. +// - +// Default: "[{lightgreen}Discord-Utilities{default}]" +sm_du_server_prefix "{lightgreen}[Discord-Utilities]{default}" + +// Section name in databases.cfg. +// - +// Default: "du" +sm_du_database_name "du" + +// Table Name. +// - +// Default: "du_users" +sm_du_table_name "du_users" + +// Prune database with players whose last connect is X DAYS and he is not member of discord server. 0 to disable. +// - +// Default: "60" +sm_du_prune_days "60" + +// Enable log for revoke? +// - +// Default: "1" +sm_du_logrevoke_enabled" "1" + +// Enable to create logs/dsmembers.json? +// - +// Default: "1" +sm_du_dsmembersfile_enabled" "1" + +// Thumbnail link for map notification. Make sure "MAPNAME" is added in the link just like the default value. Blank for none. +// - +// Default: "" +https://image.gametracker.com/images/maps/160x120/csgo/MAPNAME.jpg "" + +// 0 - Only "say_team with @ / sm_chat" +// 0b - "say_team with @ / sm_chat" with discord to game server chat to admin. +// Any admin flag - Show messages of specific flag in channel. +// - +// Default: "0b" +sm_du_adminchat_mode "0b" + +// Channel ID for discord server => game server messages only of admins. Blank to disable. +// - +// Default: "" +sm_du_adminchat_channelid "" diff --git a/discord_api.smx b/discord_api.smx deleted file mode 100644 index c61fdcd..0000000 Binary files a/discord_api.smx and /dev/null differ diff --git a/discord_utilities.smx b/discord_utilities.smx deleted file mode 100644 index adf28c7..0000000 Binary files a/discord_utilities.smx and /dev/null differ diff --git a/include/discord/DiscordRequest.sp b/scripting/discord/DiscordRequest.sp similarity index 100% rename from include/discord/DiscordRequest.sp rename to scripting/discord/DiscordRequest.sp diff --git a/include/discord/GetGuildChannels.sp b/scripting/discord/GetGuildChannels.sp similarity index 100% rename from include/discord/GetGuildChannels.sp rename to scripting/discord/GetGuildChannels.sp diff --git a/include/discord/GetGuilds.sp b/scripting/discord/GetGuilds.sp similarity index 100% rename from include/discord/GetGuilds.sp rename to scripting/discord/GetGuilds.sp diff --git a/include/discord/GuildMember.inc b/scripting/discord/GuildMember.inc similarity index 100% rename from include/discord/GuildMember.inc rename to scripting/discord/GuildMember.inc diff --git a/include/discord/GuildMembers.sp b/scripting/discord/GuildMembers.sp similarity index 100% rename from include/discord/GuildMembers.sp rename to scripting/discord/GuildMembers.sp diff --git a/include/discord/GuildRole.sp b/scripting/discord/GuildRole.sp similarity index 100% rename from include/discord/GuildRole.sp rename to scripting/discord/GuildRole.sp diff --git a/include/discord/ListenToChannel.sp b/scripting/discord/ListenToChannel.sp similarity index 100% rename from include/discord/ListenToChannel.sp rename to scripting/discord/ListenToChannel.sp diff --git a/include/discord/MessageObject.sp b/scripting/discord/MessageObject.sp similarity index 100% rename from include/discord/MessageObject.sp rename to scripting/discord/MessageObject.sp diff --git a/include/discord/SendMessage.sp b/scripting/discord/SendMessage.sp similarity index 100% rename from include/discord/SendMessage.sp rename to scripting/discord/SendMessage.sp diff --git a/include/discord/SendWebHook.sp b/scripting/discord/SendWebHook.sp similarity index 100% rename from include/discord/SendWebHook.sp rename to scripting/discord/SendWebHook.sp diff --git a/include/discord/UserObject.sp b/scripting/discord/UserObject.sp similarity index 100% rename from include/discord/UserObject.sp rename to scripting/discord/UserObject.sp diff --git a/include/discord/bot.inc b/scripting/discord/bot.inc similarity index 100% rename from include/discord/bot.inc rename to scripting/discord/bot.inc diff --git a/include/discord/channel.inc b/scripting/discord/channel.inc similarity index 100% rename from include/discord/channel.inc rename to scripting/discord/channel.inc diff --git a/include/discord/deletemessage.sp b/scripting/discord/deletemessage.sp similarity index 100% rename from include/discord/deletemessage.sp rename to scripting/discord/deletemessage.sp diff --git a/include/discord/message.inc b/scripting/discord/message.inc similarity index 100% rename from include/discord/message.inc rename to scripting/discord/message.inc diff --git a/include/discord/message_embed.inc b/scripting/discord/message_embed.inc similarity index 100% rename from include/discord/message_embed.inc rename to scripting/discord/message_embed.inc diff --git a/include/discord/reactions.sp b/scripting/discord/reactions.sp similarity index 100% rename from include/discord/reactions.sp rename to scripting/discord/reactions.sp diff --git a/include/discord/stocks.inc b/scripting/discord/stocks.inc similarity index 100% rename from include/discord/stocks.inc rename to scripting/discord/stocks.inc diff --git a/include/discord/user.inc b/scripting/discord/user.inc similarity index 100% rename from include/discord/user.inc rename to scripting/discord/user.inc diff --git a/include/discord/webhook.inc b/scripting/discord/webhook.inc similarity index 100% rename from include/discord/webhook.inc rename to scripting/discord/webhook.inc diff --git a/discord_api.sp b/scripting/discord_api.sp similarity index 100% rename from discord_api.sp rename to scripting/discord_api.sp diff --git a/discord_utilities.sp b/scripting/discord_utilities.sp similarity index 98% rename from discord_utilities.sp rename to scripting/discord_utilities.sp index 1402873..d43574c 100644 --- a/discord_utilities.sp +++ b/scripting/discord_utilities.sp @@ -60,7 +60,6 @@ public void OnPluginStart() CreateCvars(); - RegConsoleCmd("sm_unlink", Cmd_Unlink); RegConsoleCmd("sm_unverify", Cmd_Unlink); LoadTranslations("Discord-Utilities.phrases"); diff --git a/include/discord_utilities/discordrequest.sp b/scripting/discord_utilities/discordrequest.sp similarity index 95% rename from include/discord_utilities/discordrequest.sp rename to scripting/discord_utilities/discordrequest.sp index 92cf0c0..adad41d 100644 --- a/include/discord_utilities/discordrequest.sp +++ b/scripting/discord_utilities/discordrequest.sp @@ -1,187 +1,187 @@ -methodmap DiscordRequest < Handle -{ - public DiscordRequest(char[] url, EHTTPMethod method) - { - Handle request = SteamWorks_CreateHTTPRequest(method, url); - return view_as(request); - } - - public void SetJsonBody(Handle hJson) - { - static char stringJson[16384]; - stringJson[0] = '\0'; - if(hJson != null) - { - json_dump(hJson, stringJson, sizeof(stringJson), 0, true); - } - SteamWorks_SetHTTPRequestRawPostBody(this, "application/json; charset=UTF-8", stringJson, strlen(stringJson)); - if(hJson != null) delete hJson; - } - - public void SetJsonBodyEx(Handle hJson) - { - static char stringJson[16384]; - stringJson[0] = '\0'; - if(hJson != null) - { - json_dump(hJson, stringJson, sizeof(stringJson), 0, true); - } - SteamWorks_SetHTTPRequestRawPostBody(this, "application/json; charset=UTF-8", stringJson, strlen(stringJson)); - } - - property int Timeout - { - public set(int timeout) - { - SteamWorks_SetHTTPRequestNetworkActivityTimeout(this, timeout); - } - } - - public void SetCallbacks(SteamWorksHTTPRequestCompleted OnComplete, SteamWorksHTTPDataReceived DataReceived) - { - SteamWorks_SetHTTPCallbacks(this, OnComplete, HeadersReceived, DataReceived); - } - - public void SetContentSize() - { - SteamWorks_SetHTTPRequestHeaderValue(this, "Content-Length", "0"); - } - - public void SetContextValue(any data1, any data2) - { - SteamWorks_SetHTTPRequestContextValue(this, data1, data2); - } - - public void SetData(any data1, char[] route) - { - SteamWorks_SetHTTPRequestContextValue(this, data1, UrlToDP(route)); - } - - public void SetBot(DiscordBot bawt) - { - BuildAuthHeader(this, bawt); - } - - public void Send(char[] route) - { - DiscordSendRequest(this, route); - } -} - -public int HeadersReceived(Handle request, bool failure, any data, any datapack) -{ - DataPack dp = view_as(datapack); - if(failure) - { - delete dp; - return; - } - - char xRateLimit[16]; - char xRateLeft[16]; - char xRateReset[32]; - - bool exists = false; - - exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Limit", xRateLimit, sizeof(xRateLimit)); - exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Remaining", xRateLeft, sizeof(xRateLeft)); - exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Reset", xRateReset, sizeof(xRateReset)); - - //Get url - char route[128]; - ResetPack(dp); - ReadPackString(dp, route, sizeof(route)); - delete dp; - - int reset = StringToInt(xRateReset); - if(reset > GetTime() + 3) - { - reset = GetTime() + 3; - } - - if(exists) - { - SetTrieValue(hRateReset, route, reset); - SetTrieValue(hRateLeft, route, StringToInt(xRateLeft)); - SetTrieValue(hRateLimit, route, StringToInt(xRateLimit)); - } - else - { - SetTrieValue(hRateReset, route, -1); - SetTrieValue(hRateLeft, route, -1); - SetTrieValue(hRateLimit, route, -1); - } -} - -public Handle UrlToDP(char[] url) -{ - DataPack dp = new DataPack(); - WritePackString(dp, url); - return dp; -} - -stock void BuildAuthHeader(Handle request, DiscordBot bawt) -{ - static char buffer[256]; - static char token[196]; - JsonObjectGetString(bawt, "token", token, sizeof(token)); - FormatEx(buffer, sizeof(buffer), "Bot %s", token); - SteamWorks_SetHTTPRequestHeaderValue(request, "Authorization", buffer); - //g_bIsBotLoaded = true; -} - -public void DiscordSendRequest(Handle request, const char[] route) -{ - //Check for reset - int time = GetTime(); - int resetTime; - - int defLimit = 0; - if(!GetTrieValue(hRateLimit, route, defLimit)) - { - defLimit = 1; - } - - bool exists = GetTrieValue(hRateReset, route, resetTime); - - if(!exists) - { - SetTrieValue(hRateReset, route, GetTime() + 5); - SetTrieValue(hRateLeft, route, defLimit - 1); - SteamWorks_SendHTTPRequest(request); - return; - } - - if(time == -1) - { - //No x-rate-limit send - SteamWorks_SendHTTPRequest(request); - return; - } - - if(time > resetTime) - { - SetTrieValue(hRateLeft, route, defLimit - 1); - SteamWorks_SendHTTPRequest(request); - return; - } - else - { - int left; - GetTrieValue(hRateLeft, route, left); - if(left == 0) - { - float remaining = float(resetTime) - float(time) + 1.0; - Handle dp = new DataPack(); - WritePackCell(dp, request); - WritePackString(dp, route); - CreateTimer(remaining, SendRequestAgain, dp); - } - else - { - left--; - SetTrieValue(hRateLeft, route, left); - SteamWorks_SendHTTPRequest(request); - } - } +methodmap DiscordRequest < Handle +{ + public DiscordRequest(char[] url, EHTTPMethod method) + { + Handle request = SteamWorks_CreateHTTPRequest(method, url); + return view_as(request); + } + + public void SetJsonBody(Handle hJson) + { + static char stringJson[16384]; + stringJson[0] = '\0'; + if(hJson != null) + { + json_dump(hJson, stringJson, sizeof(stringJson), 0, true); + } + SteamWorks_SetHTTPRequestRawPostBody(this, "application/json; charset=UTF-8", stringJson, strlen(stringJson)); + if(hJson != null) delete hJson; + } + + public void SetJsonBodyEx(Handle hJson) + { + static char stringJson[16384]; + stringJson[0] = '\0'; + if(hJson != null) + { + json_dump(hJson, stringJson, sizeof(stringJson), 0, true); + } + SteamWorks_SetHTTPRequestRawPostBody(this, "application/json; charset=UTF-8", stringJson, strlen(stringJson)); + } + + property int Timeout + { + public set(int timeout) + { + SteamWorks_SetHTTPRequestNetworkActivityTimeout(this, timeout); + } + } + + public void SetCallbacks(SteamWorksHTTPRequestCompleted OnComplete, SteamWorksHTTPDataReceived DataReceived) + { + SteamWorks_SetHTTPCallbacks(this, OnComplete, HeadersReceived, DataReceived); + } + + public void SetContentSize() + { + SteamWorks_SetHTTPRequestHeaderValue(this, "Content-Length", "0"); + } + + public void SetContextValue(any data1, any data2) + { + SteamWorks_SetHTTPRequestContextValue(this, data1, data2); + } + + public void SetData(any data1, char[] route) + { + SteamWorks_SetHTTPRequestContextValue(this, data1, UrlToDP(route)); + } + + public void SetBot(DiscordBot bawt) + { + BuildAuthHeader(this, bawt); + } + + public void Send(char[] route) + { + DiscordSendRequest(this, route); + } +} + +public int HeadersReceived(Handle request, bool failure, any data, any datapack) +{ + DataPack dp = view_as(datapack); + if(failure) + { + delete dp; + return; + } + + char xRateLimit[16]; + char xRateLeft[16]; + char xRateReset[32]; + + bool exists = false; + + exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Limit", xRateLimit, sizeof(xRateLimit)); + exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Remaining", xRateLeft, sizeof(xRateLeft)); + exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Reset", xRateReset, sizeof(xRateReset)); + + //Get url + char route[128]; + ResetPack(dp); + ReadPackString(dp, route, sizeof(route)); + delete dp; + + int reset = StringToInt(xRateReset); + if(reset > GetTime() + 3) + { + reset = GetTime() + 3; + } + + if(exists) + { + SetTrieValue(hRateReset, route, reset); + SetTrieValue(hRateLeft, route, StringToInt(xRateLeft)); + SetTrieValue(hRateLimit, route, StringToInt(xRateLimit)); + } + else + { + SetTrieValue(hRateReset, route, -1); + SetTrieValue(hRateLeft, route, -1); + SetTrieValue(hRateLimit, route, -1); + } +} + +public Handle UrlToDP(char[] url) +{ + DataPack dp = new DataPack(); + WritePackString(dp, url); + return dp; +} + +stock void BuildAuthHeader(Handle request, DiscordBot bawt) +{ + static char buffer[256]; + static char token[196]; + JsonObjectGetString(bawt, "token", token, sizeof(token)); + FormatEx(buffer, sizeof(buffer), "Bot %s", token); + SteamWorks_SetHTTPRequestHeaderValue(request, "Authorization", buffer); + //g_bIsBotLoaded = true; +} + +public void DiscordSendRequest(Handle request, const char[] route) +{ + //Check for reset + int time = GetTime(); + int resetTime; + + int defLimit = 0; + if(!GetTrieValue(hRateLimit, route, defLimit)) + { + defLimit = 1; + } + + bool exists = GetTrieValue(hRateReset, route, resetTime); + + if(!exists) + { + SetTrieValue(hRateReset, route, GetTime() + 5); + SetTrieValue(hRateLeft, route, defLimit - 1); + SteamWorks_SendHTTPRequest(request); + return; + } + + if(time == -1) + { + //No x-rate-limit send + SteamWorks_SendHTTPRequest(request); + return; + } + + if(time > resetTime) + { + SetTrieValue(hRateLeft, route, defLimit - 1); + SteamWorks_SendHTTPRequest(request); + return; + } + else + { + int left; + GetTrieValue(hRateLeft, route, left); + if(left == 0) + { + float remaining = float(resetTime) - float(time) + 1.0; + Handle dp = new DataPack(); + WritePackCell(dp, request); + WritePackString(dp, route); + CreateTimer(remaining, SendRequestAgain, dp); + } + else + { + left--; + SetTrieValue(hRateLeft, route, left); + SteamWorks_SendHTTPRequest(request); + } + } } \ No newline at end of file diff --git a/include/discord_utilities/forwards.sp b/scripting/discord_utilities/forwards.sp similarity index 78% rename from include/discord_utilities/forwards.sp rename to scripting/discord_utilities/forwards.sp index b0d5113..25dab26 100644 --- a/include/discord_utilities/forwards.sp +++ b/scripting/discord_utilities/forwards.sp @@ -1,247 +1,259 @@ -public void OnPluginEnd() -{ - KillBot(); -} - -public void OnLibraryAdded(const char[] szLibrary) -{ - if(StrEqual(szLibrary, "shavit")) g_bShavit = true; -} - -public void OnLibraryRemoved(const char[] szLibrary) -{ - if(StrEqual(szLibrary, "shavit")) g_bShavit = false; -} - -public void OnAllPluginsLoaded() -{ - if(!LibraryExists("discord-api")) - { - SetFailState("[Discord-Utilities] This plugin is fully dependant on \"Discord-API\" by Deathknife. (https://github.com/Deathknife/sourcemod-discord)"); - } - - g_bShavit = LibraryExists("shavit"); -} - -public void OnConfigsExecuted() -{ - LoadCvars(); - - if(Bot == view_as(INVALID_HANDLE)) - { - if(!CommandExists(g_sViewIDCommand)) - { - RegConsoleCmd(g_sViewIDCommand, Command_ViewId); - RegConsoleCmd("sm_verify", Command_ViewId); - } - CreateBot(); - } - - LoadCommands(); - - char sDTB[32]; - g_cDatabaseName.GetString(sDTB, sizeof(sDTB)); - g_cTableName.GetString(g_sTableName, sizeof(g_sTableName)); - SQL_TConnect(SQLQuery_Connect, sDTB); -} - -public void OnMapEnd() -{ - KillBot(); -} - -public void OnMapStart() -{ - if(g_cCheckInterval.FloatValue) - { - CreateTimer(g_cCheckInterval.FloatValue, VerifyAccounts, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } -} - -public void OnClientDisconnect(int client) -{ - if(g_hDB == null) - { - return; - } - UpdatePlayer(client); -} - -public void OnClientPutInServer(int client) -{ - g_bChecked[client] = false; - g_bMember[client] = false; - g_sUniqueCode[client][0] = '\0'; - g_sUserID[client][0] = '\0'; - g_bRoleGiven[client] = false; -} - -public Action OnClientPreAdminCheck(int client) -{ - if(IsFakeClient(client) || g_hDB == null) - { - return; - } - - char szQuery[512], szSteamId[32]; - GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); - if(g_bIsMySQl) - { - g_hDB.Format(szQuery, sizeof(szQuery), "SELECT userid, member FROM %s WHERE steamid = '%s';", g_sTableName, szSteamId); - } - else - { - g_hDB.Format(szQuery, sizeof(szQuery), "SELECT userid, member FROM %s WHERE steamid = '%s'", g_sTableName, szSteamId); - } - SQL_TQuery(g_hDB, SQLQuery_GetUserData, szQuery, GetClientUserId(client)); -} - -public int OnSettingsChanged(ConVar convar, const char[] oldVal, const char[] newVal) -{ - if(StrEqual(oldVal, newVal, true)) - { - return; - } - if(convar == g_cVerificationChannelID) - { - strcopy(g_sVerificationChannelID, sizeof(g_sVerificationChannelID), newVal); - } - else if(convar == g_cGuildID) - { - strcopy(g_sGuildID, sizeof(g_sGuildID), newVal); - } - else if(convar == g_cRoleID) - { - strcopy(g_sRoleID, sizeof(g_sRoleID), newVal); - } - else if(convar == g_cBotToken) - { - strcopy(g_sBotToken, sizeof(g_sBotToken), newVal); - } - else if(convar == g_cLinkCommand) - { - strcopy(g_sLinkCommand, sizeof(g_sLinkCommand), newVal); - } - else if(convar == g_cViewIDCommand) - { - strcopy(g_sViewIDCommand, sizeof(g_sViewIDCommand), newVal); - } - else if(convar == g_cInviteLink) - { - strcopy(g_sInviteLink, sizeof(g_sInviteLink), newVal); - } - else if(convar == g_cServerPrefix) - { - strcopy(g_sServerPrefix, sizeof(g_sServerPrefix), newVal); - } - else if(convar == g_cTableName) - { - strcopy(g_sTableName, sizeof(g_sTableName), newVal); - char dtbname[32]; - g_cDatabaseName.GetString(dtbname, sizeof(dtbname)); - SQL_TConnect(SQLQuery_Connect, dtbname); - RefreshClients(); - } -} - -public void GuildList(DiscordBot bawt, char[] id, char[] name, char[] icon, bool owner, int permissions, const bool listen) -{ - Bot.GetGuildChannels(id, ChannelList, INVALID_FUNCTION, listen); -} - -public void ChannelList(DiscordBot bawt, const char[] guild, DiscordChannel Channel, const bool listen) -{ - if(StrEqual(g_sBotToken, "") || StrEqual(g_sVerificationChannelID, "")) - { - return; - } - if(Channel == null || Bot == null) - { - return; - } - if(Bot.IsListeningToChannel(Channel)) - { - //Bot.StopListeningToChannel(Channel); - return; - } - - - char id[20], name[32]; - Channel.GetID(id, sizeof(id)); - Channel.GetName(name, sizeof(name)); - - if(strlen(g_sVerificationChannelID) > 10) - { - if(StrEqual(id, g_sVerificationChannelID)) - { - g_sVerificationChannelName = name; - if(listen) - { - PrintToServer("******** STARTING TO LISTEN ***********"); - Bot.StartListeningToChannel(Channel, OnMessageReceived); - //Bot.PrintChannels(); - //Bot.PrintChannels(); - } - } - } -} - -/* -public Action smpc(int client, int args) -{ - Bot.PrintChannels(); -} -*/ - -public Action Command_ViewId(int client, int args) -{ - if(!client || StrEqual(g_sVerificationChannelID, "")) - { - return Plugin_Handled; - } - if(!g_bChecked[client]) - { - CReplyToCommand(client, "%s %T", g_sServerPrefix, "TryAgainLater", client); - return Plugin_Handled; - } - - //CPrintToChat(client, "%s %T", g_sServerPrefix, "LinkYourID", client, g_sUniqueCode[client]); - if(!g_bMember[client]) - { - CPrintToChat(client, "%s %T", g_sServerPrefix, "LinkConnect", client); - CPrintToChat(client, "%s {blue}%s", g_sServerPrefix, g_sInviteLink); - - CPrintToChat(client, "%s %T", g_sServerPrefix, "LinkUsage", client, g_sLinkCommand, g_sUniqueCode[client]); - CPrintToChat(client, "%s %T", g_sServerPrefix, "LinkUsage2", client, g_sVerificationChannelName); - CPrintToChat(client, "%s You can also {yellow}copy-paste{default} from {green}console{default} :)", g_sServerPrefix); - - PrintToConsole(client, "************************************"); - PrintToConsole(client, "[Discord] %T", "LinkConnect", client); - PrintToConsole(client, "[Discord] %s", g_sInviteLink); - PrintToConsole(client, "[Discord] Use %s %s", g_sLinkCommand, g_sUniqueCode[client]); - PrintToConsole(client, "************************************"); - } - else - { - CPrintToChat(client, "%s - You are already verified. Enjoy your benefits :)", g_sServerPrefix); - CPrintToChat(client, "%s - You can change your verified discord account using {LIME}!unverify{DEFAULT} and {LIME}!verify{DEFAULT} again", g_sServerPrefix); - } - - - - return Plugin_Handled; -} - -public Action Check(int client, const char[] command, int args) -{ - if(!client || client > MaxClients) - { - return Plugin_Continue; - } - if(!g_bMember[client]) - { - CPrintToChat(client, "%s %T", g_sServerPrefix, "MustVerify", client, ChangePartsInString(g_sViewIDCommand, "sm_", "!")); - return Plugin_Stop; - } - return Plugin_Continue; -} +public void OnPluginEnd() +{ + KillBot(); +} + +public void OnLibraryAdded(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "shavit")) g_bShavit = true; +} + +public void OnLibraryRemoved(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "shavit")) g_bShavit = false; +} + +public void OnAllPluginsLoaded() +{ + if(!LibraryExists("discord-api")) + { + SetFailState("[Discord-Utilities] This plugin is fully dependant on \"Discord-API\" by Deathknife. (https://github.com/Deathknife/sourcemod-discord)"); + } + + g_bShavit = LibraryExists("shavit"); +} + +public void OnConfigsExecuted() +{ + LoadCvars(); + + if(Bot == view_as(INVALID_HANDLE)) + { + if(!CommandExists(g_sViewIDCommand)) + { + RegConsoleCmd(g_sViewIDCommand, Command_ViewId); + RegConsoleCmd(g_sUnLinkCommand, Cmd_Unlink); + RegConsoleCmd("sm_verify", Command_ViewId); + } + CreateBot(); + } + + LoadCommands(); + + char sDTB[32]; + g_cDatabaseName.GetString(sDTB, sizeof(sDTB)); + g_cTableName.GetString(g_sTableName, sizeof(g_sTableName)); + SQL_TConnect(SQLQuery_Connect, sDTB); +} + +public void OnMapEnd() +{ + KillBot(); +} + +public void OnMapStart() +{ + if(g_cCheckInterval.FloatValue) + { + CreateTimer(g_cCheckInterval.FloatValue, VerifyAccounts, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } +} + +public void OnClientDisconnect(int client) +{ + if(g_hDB == null) + { + return; + } + UpdatePlayer(client); +} + +public void OnClientPutInServer(int client) +{ + g_bChecked[client] = false; + g_bMember[client] = false; + g_sUniqueCode[client][0] = '\0'; + g_sUserID[client][0] = '\0'; + g_bRoleGiven[client] = false; +} + +public Action OnClientPreAdminCheck(int client) +{ + if(IsFakeClient(client) || g_hDB == null) + { + return; + } + + char szQuery[512], szSteamId[32]; + GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); + if(g_bIsMySQl) + { + g_hDB.Format(szQuery, sizeof(szQuery), "SELECT userid, member FROM %s WHERE steamid = '%s';", g_sTableName, szSteamId); + } + else + { + g_hDB.Format(szQuery, sizeof(szQuery), "SELECT userid, member FROM %s WHERE steamid = '%s'", g_sTableName, szSteamId); + } + SQL_TQuery(g_hDB, SQLQuery_GetUserData, szQuery, GetClientUserId(client)); +} + +public int OnSettingsChanged(ConVar convar, const char[] oldVal, const char[] newVal) +{ + if(StrEqual(oldVal, newVal, true)) + { + return; + } + if(convar == g_cVerificationChannelID) + { + strcopy(g_sVerificationChannelID, sizeof(g_sVerificationChannelID), newVal); + } + else if(convar == g_cGuildID) + { + strcopy(g_sGuildID, sizeof(g_sGuildID), newVal); + } + else if(convar == g_cRoleID) + { + strcopy(g_sRoleID, sizeof(g_sRoleID), newVal); + } + else if(convar == g_cBotToken) + { + strcopy(g_sBotToken, sizeof(g_sBotToken), newVal); + } + else if(convar == g_cLinkCommand) + { + strcopy(g_sLinkCommand, sizeof(g_sLinkCommand), newVal); + } + else if(convar == g_cViewIDCommand) + { + strcopy(g_sViewIDCommand, sizeof(g_sViewIDCommand), newVal); + } + else if(convar == g_cUnLinkCommand) + { + strcopy(g_sUnLinkCommand, sizeof(g_sUnLinkCommand), newVal); + } + else if(convar == g_cInviteLink) + { + strcopy(g_sInviteLink, sizeof(g_sInviteLink), newVal); + } + else if(convar == g_cServerPrefix) + { + strcopy(g_sServerPrefix, sizeof(g_sServerPrefix), newVal); + } + else if(convar == g_cTableName) + { + strcopy(g_sTableName, sizeof(g_sTableName), newVal); + char dtbname[32]; + g_cDatabaseName.GetString(dtbname, sizeof(dtbname)); + SQL_TConnect(SQLQuery_Connect, dtbname); + RefreshClients(); + } +} + +public void GuildList(DiscordBot bawt, char[] id, char[] name, char[] icon, bool owner, int permissions, const bool listen) +{ + Bot.GetGuildChannels(id, ChannelList, INVALID_FUNCTION, listen); +} + +public void ChannelList(DiscordBot bawt, const char[] guild, DiscordChannel Channel, const bool listen) +{ + if(StrEqual(g_sBotToken, "") || StrEqual(g_sVerificationChannelID, "")) + { + return; + } + if(Channel == null || Bot == null) + { + return; + } + if(Bot.IsListeningToChannel(Channel)) + { + //Bot.StopListeningToChannel(Channel); + return; + } + + + char id[20], name[32]; + Channel.GetID(id, sizeof(id)); + Channel.GetName(name, sizeof(name)); + + if(strlen(g_sVerificationChannelID) > 10) + { + if(StrEqual(id, g_sVerificationChannelID)) + { + g_sVerificationChannelName = name; + if(listen) + { + PrintToServer("******** STARTING TO LISTEN ***********"); + Bot.StartListeningToChannel(Channel, OnMessageReceived); + //Bot.PrintChannels(); + //Bot.PrintChannels(); + } + } + } +} + +/* +public Action smpc(int client, int args) +{ + Bot.PrintChannels(); +} +*/ + +public Action Command_ViewId(int client, int args) +{ + if(!client || StrEqual(g_sVerificationChannelID, "")) + { + return Plugin_Handled; + } + if(!g_bChecked[client]) + { + CReplyToCommand(client, "%s %T", g_sServerPrefix, "TryAgainLater", client); + return Plugin_Handled; + } + + //CPrintToChat(client, "%s %T", g_sServerPrefix, "LinkYourID", client, g_sUniqueCode[client]); + if(!g_bMember[client]) + { + CPrintToChat(client, "%s %T", g_sServerPrefix, "LinkConnect", client); + CPrintToChat(client, "%s {blue}%s", g_sServerPrefix, g_sInviteLink); + + CPrintToChat(client, "%s %T", g_sServerPrefix, "LinkUsage", client, g_sLinkCommand, g_sUniqueCode[client], g_sVerificationChannelName); + CPrintToChat(client, "%s %T", g_sServerPrefix, "CopyPasteFromConsole", client); + + char buf[128], g_sServerPrefix2[128]; + Format(g_sServerPrefix2, sizeof(g_sServerPrefix2), g_sServerPrefix); + for(int i = 0; i < sizeof(C_Tag); i++) + { + ReplaceString(g_sServerPrefix2, sizeof(g_sServerPrefix2), C_Tag[i], ""); + } + + PrintToConsole(client, "*****************************************************"); + PrintToConsole(client, "%s %T", g_sServerPrefix2, "LinkConnect", client, g_sInviteLink); + PrintToConsole(client, "%s %s", g_sServerPrefix2, g_sInviteLink); + Format(buf, sizeof(buf), "%T", "LinkUsage", client, g_sLinkCommand, g_sUniqueCode[client], g_sVerificationChannelName); + for(int i = 0; i < sizeof(C_Tag); i++) + { + ReplaceString(buf, sizeof(buf), C_Tag[i], ""); + } + PrintToConsole(client, "%s %s", g_sServerPrefix2, buf); + PrintToConsole(client, "*****************************************************"); + } + else + { + CPrintToChat(client, "%s - %T", g_sServerPrefix, "AlreadyVerified", client); + CPrintToChat(client, "%s - %T", g_sServerPrefix, "CanChange", client, ChangePartsInString(g_sUnLinkCommand, "sm_", "!"), ChangePartsInString(g_sViewIDCommand, "sm_", "!")); + } + + + + return Plugin_Handled; +} + +public Action Check(int client, const char[] command, int args) +{ + if(IsValidClient(client) && !g_bMember[client]) + { + CPrintToChat(client, "%s %T", g_sServerPrefix, "MustVerify", client, ChangePartsInString(g_sViewIDCommand, "sm_", "!")); + return Plugin_Stop; + } + return Plugin_Continue; +} \ No newline at end of file diff --git a/include/discord_utilities/globals.sp b/scripting/discord_utilities/globals.sp similarity index 71% rename from include/discord_utilities/globals.sp rename to scripting/discord_utilities/globals.sp index f9d5e6d..9ce1ac8 100644 --- a/include/discord_utilities/globals.sp +++ b/scripting/discord_utilities/globals.sp @@ -1,49 +1,50 @@ -#define PLUGIN_VERSION "2.4-betafixslow" - -#define PLUGIN_NAME "Discord Utilities" -#define PLUGIN_AUTHOR "Cruze" -#define PLUGIN_DESC "Utilities that can be used to integrate gameserver to discord server I guess?" -#define PLUGIN_URL "https://github.com/Cruze03/discord-utilities | http://www.steamcommunity.com/profiles/76561198132924835" - -//#define USE_AutoExecConfig - - -ConVar g_cVerificationChannelID, g_cGuildID, g_cRoleID; -ConVar g_cBotToken, g_cCheckInterval, g_cUseSWGM, g_cServerID; -ConVar g_cLinkCommand, g_cViewIDCommand, g_cInviteLink; -ConVar g_cDiscordPrefix, g_cServerPrefix; -ConVar g_cDatabaseName, g_cTableName, g_cPruneDays; -ConVar g_cPrimaryServer; - -char g_sVerificationChannelID[20], g_sGuildID[20], g_sRoleID[20]; -char g_sBotToken[60]; -char g_sLinkCommand[20], g_sViewIDCommand[20], g_sInviteLink[30]; -char g_sDiscordPrefix[128], g_sServerPrefix[128]; -char g_sTableName[32]; - -char g_sVerificationChannelName[32]; - -bool g_bShavit; - -bool g_bChecked[MAXPLAYERS+1]; -bool g_bMember[MAXPLAYERS+1]; -bool g_bRoleGiven[MAXPLAYERS+1]; -char g_sUserID[MAXPLAYERS+1][20]; -char g_sUniqueCode[MAXPLAYERS+1][36]; - -Handle g_hOnCheckedAccounts, g_hOnLinkedAccount, g_hOnAccountRevoked, g_hOnMemberDataDumped; - -DiscordBot Bot; - -Database g_hDB; - -bool g_bIsMySQl; - -bool g_bLateLoad = false; -//bool g_bIsBotLoaded = false; - -Handle hRateLimit = null; -Handle hRateReset = null; -Handle hRateLeft = null; - +#define PLUGIN_VERSION "2.6" + +#define PLUGIN_NAME "Discord Utilities" +#define PLUGIN_AUTHOR "Cruze & xSlow & AiDN™" +#define PLUGIN_DESC "Utilities that can be used to integrate gameserver to discord server I guess?" +#define PLUGIN_URL "https://github.com/Cruze03/discord-utilities | http://www.steamcommunity.com/profiles/76561198132924835 & http://steamcommunity.com/profiles/76561198192410833 & http://steamcommunity.com/profiles/76561198069218105" + +//#define USE_AutoExecConfig + + +ConVar g_cVerificationChannelID, g_cGuildID, g_cRoleID; +ConVar g_cBotToken, g_cCheckInterval, g_cUseSWGM, g_cServerID; +ConVar g_cLinkCommand, g_cViewIDCommand, g_cUnLinkCommand, g_cInviteLink; +ConVar g_cDiscordPrefix, g_cServerPrefix; +ConVar g_cDatabaseName, g_cTableName, g_cPruneDays; +ConVar g_cPrimaryServer; +ConVar g_cLogRevokeEnabled, g_cDsMembersEnabled; + +char g_sVerificationChannelID[20], g_sGuildID[20], g_sRoleID[20]; +char g_sBotToken[60]; +char g_sLinkCommand[20], g_sViewIDCommand[20], g_sUnLinkCommand[20], g_sInviteLink[30]; +char g_sDiscordPrefix[128], g_sServerPrefix[128]; +char g_sTableName[32]; + +char g_sVerificationChannelName[32]; + +bool g_bShavit; + +bool g_bChecked[MAXPLAYERS+1]; +bool g_bMember[MAXPLAYERS+1]; +bool g_bRoleGiven[MAXPLAYERS+1]; +char g_sUserID[MAXPLAYERS+1][20]; +char g_sUniqueCode[MAXPLAYERS+1][36]; + +Handle g_hOnCheckedAccounts, g_hOnLinkedAccount, g_hOnAccountRevoked, g_hOnMemberDataDumped; + +DiscordBot Bot; + +Database g_hDB; + +bool g_bIsMySQl; + +bool g_bLateLoad = false; +//bool g_bIsBotLoaded = false; + +Handle hRateLimit = null; +Handle hRateReset = null; +Handle hRateLeft = null; + Handle hFinalMemberList; \ No newline at end of file diff --git a/include/discord_utilities/helpers.sp b/scripting/discord_utilities/helpers.sp similarity index 94% rename from include/discord_utilities/helpers.sp rename to scripting/discord_utilities/helpers.sp index 532b629..7c2068a 100644 --- a/include/discord_utilities/helpers.sp +++ b/scripting/discord_utilities/helpers.sp @@ -1,680 +1,690 @@ -void AccountsCheck() -{ - Action action = Plugin_Continue; - Call_StartForward(g_hOnCheckedAccounts); - Call_PushString(g_sBotToken); - Call_PushString(g_sGuildID); - Call_PushString(g_sTableName); - Call_Finish(action); - - if(action >= Plugin_Handled) - { - return; - } - /* - if(g_hDB == null) - { - CreateTimer(5.0, Timer_Query_AccountCheck, _, TIMER_FLAG_NO_MAPCHANGE); - } - else - { - char Query[256]; - g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s", g_sTableName); - SQL_TQuery(g_hDB, SQLQuery_AccountCheck, Query); - } - */ - delete hFinalMemberList; - Handle hData = json_object(); - hFinalMemberList = json_array(); - json_object_set_new(hData, "limit", json_integer(1000)); - json_object_set_new(hData, "after", json_string("")); - GetMembers(hData); - //PrintToChatAll("AccountsCheck();"); -} - -void GetMembers(Handle hData = INVALID_HANDLE) -{ - if(StrEqual(g_sGuildID, "") && !StrEqual(g_sVerificationChannelID, "")) - { - LogError("[Discord-Utilities] GuildID is not provided. GetMember won't work!"); - delete hData; - delete hFinalMemberList; - return; - } - if(Bot == null) - { - delete hData; - delete hFinalMemberList; - return; - } - int limit = JsonObjectGetInt(hData, "limit"); - char after[32]; - JsonObjectGetString(hData, "after", after, sizeof(after)); - - char url[256]; - if(StrEqual(after, "")) - { - FormatEx(url, sizeof(url), "https://discord.com/api/guilds/%s/members?limit=%i", g_sGuildID, limit); - } - else - { - FormatEx(url, sizeof(url), "https://discord.com/api/guilds/%s/members?limit=%i&after=%s", g_sGuildID, limit, after); - } - - char route[128]; - FormatEx(route, sizeof(route), "guild/%s/members", g_sGuildID); - - DiscordRequest request = new DiscordRequest(url, k_EHTTPMethodGET); - if(request == null) - { - CreateTimer(2.0, SendGetMembers, hData); - return; - } - request.SetCallbacks(HTTPCompleted, MembersDataReceive); - request.SetBot(Bot); - request.SetData(hData, route); - request.Send(route); -} - -public int HTTPCompleted(Handle request, bool failure, bool requestSuccessful, EHTTPStatusCode statuscode, any data, any data2) -{ -} - -public void MembersDataReceive(Handle request, bool failure, int offset, int statuscode, any dp) -{ - if(failure || (statuscode != 200)) - { - if(statuscode == 429 || statuscode == 500) - { - GetMembers(dp); - //delete view_as(dp); - delete request; - return; - } - delete hFinalMemberList; - delete request; - delete view_as(dp); - return; - } - SteamWorks_GetHTTPResponseBodyCallback(request, GetMembersData, dp); - delete request; -} - -public int GetMembersData(const char[] data, any dp) -{ - //PrintToChatAll("GetMembersData();"); - Handle hJson = json_load(data); - //bool returned = json_array_extend(hFinalMemberList, hJson); - json_array_extend(hFinalMemberList, hJson); - //PrintToChatAll("returned %d", returned); - Handle hData = view_as(dp); - - int size = json_array_size(hJson); - int limit = JsonObjectGetInt(hData, "limit"); - //PrintToChatAll("size %d | limit %d", size, limit); - - if(limit == size) - { - char userid[32]; - DiscordGuildUser GuildUser; - DiscordUser user; - - GuildUser = view_as(json_array_get(hJson, limit - 1)); - user = GuildUser.GetUser(); - user.GetID(userid, sizeof(userid)); - delete GuildUser; - delete user; - //PrintToChatAll("userID %s", userid); - - delete hJson; - - json_object_set_new(hData, "after", json_string(userid)); - GetMembers(hData); - return; - } - - OnGetMembersAll(hFinalMemberList); - - delete hJson; - delete hData; - delete hFinalMemberList; -} - -public void OnGetMembersAll(Handle hMemberList) -{ - //DeleteFile("addons/sourcemod/logs/dsmembers.json") - json_dump_file(hMemberList, "addons/sourcemod/logs/dsmembers.json"); - //LogToFile("addons/sourcemod/logs/dsmembers.json", "OnGetMembersAll size %d", json_array_size(hMemberList)); - - Call_StartForward(g_hOnMemberDataDumped); - Call_Finish(); - - char userid[20]; - DiscordGuildUser GuildUser; - DiscordUser user; - bool found; - char Query[256]; - bool[] bUpdate = new bool[MaxClients+1]; - - for(int x = 1; x <= MaxClients; x++) - { - if(!IsClientInGame(x)) - { - continue; - } - if(!g_bMember[x]) - { - continue; - } - found = false; - for(int i = 0; i < json_array_size(hMemberList); i++) - { - GuildUser = view_as(json_array_get(hMemberList, i)); - user = GuildUser.GetUser(); - user.GetID(userid, sizeof(userid)); - if(strcmp(userid, g_sUserID[x]) == 0) - { - found = true; - delete user; - delete GuildUser; - break; - } - delete user; - delete GuildUser; - } - delete user; - delete GuildUser; - if(!found) - { - char steamid[32]; - GetClientAuthId(x, AuthId_Steam2, steamid, sizeof(steamid)); - if(g_bIsMySQl) - { - g_hDB.Format(Query, sizeof(Query), "UPDATE `%s` SET `userid` = '%s', member = '0' WHERE `steamid` = '%s';", g_sTableName, NULL_STRING, steamid); - } - else - { - g_hDB.Format(Query, sizeof(Query), "UPDATE %s SET userid = '%s', member = '0' WHERE steamid = '%s';", g_sTableName, NULL_STRING, steamid); - } - SQL_TQuery(g_hDB, SQLQuery_UpdatePlayer, Query); - bUpdate[x] = true; - CPrintToChat(x, "%s %T", g_sServerPrefix, "DiscordRevoked", x); - - LogToFile("addons/sourcemod/logs/dsmembers_revoke.log", "Player %L got revoked. Memberlist json size: %d", x, json_array_size(hMemberList)); - - Call_StartForward(g_hOnAccountRevoked); - Call_PushCell(x); - Call_PushString(g_sUserID[x]); - Call_Finish(); - } - else - { - if(strlen(g_sRoleID) > 5 && !g_bRoleGiven[x]) - { - ManagingRole(g_sUserID[x], g_sRoleID, k_EHTTPMethodPUT); - g_bRoleGiven[x] = true; - } - } - } - - for(int i = 1; i <= MaxClients; i++) - { - if(!IsClientInGame(i)) - { - continue; - } - if(!bUpdate[i]) - { - continue; - } - OnClientPutInServer(i); - OnClientPreAdminCheck(i); - } -} - -/* -void GetGuildMember(char[] userid) -{ - Handle hData = json_object(); - json_object_set_new(hData, "userID", json_string(userid[0])); - GetMembers(hData); -} -*/ - -void UpdatePlayer(int client) -{ - char steamid[32], szQuery[512]; - - GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid)); - if(g_bIsMySQl) - { - g_hDB.Format(szQuery, sizeof(szQuery), "UPDATE `%s` SET last_accountuse = '%d' WHERE `steamid` = '%s';", g_sTableName, GetTime(), steamid); - } - else - { - g_hDB.Format(szQuery, sizeof(szQuery), "UPDATE %s SET last_accountuse = '%d' WHERE steamid = '%s'", g_sTableName, GetTime(), steamid); - } - SQL_TQuery(g_hDB, SQLQuery_UpdatePlayer, szQuery, GetClientUserId(client)); -} - -stock void RemoveColors(char[] text, int size) -{ - if(g_bShavit) - { - for(int i = 0; i < sizeof(gS_GlobalColorNames); i++) - { - ReplaceString(text, size, gS_GlobalColorNames[i], ""); - } - for(int i = 0; i < sizeof(gS_GlobalColors); i++) - { - ReplaceString(text, size, gS_GlobalColors[i], ""); - } - for(int i = 0; i < sizeof(gS_CSGOColorNames); i++) - { - ReplaceString(text, size, gS_CSGOColorNames[i], ""); - } - for(int i = 0; i < sizeof(gS_CSGOColors); i++) - { - ReplaceString(text, size, gS_CSGOColors[i], ""); - } - } - else - { - for(int i = 0; i < sizeof(C_Tag); i++) - { - ReplaceString(text, size, C_Tag[i], ""); - } - for(int i = 0; i < sizeof(C_TagCode); i++) - { - ReplaceString(text, size, C_TagCode[i], ""); - } - } -} - -void CreateCvars() -{ - #if defined USE_AutoExecConfig - AutoExecConfig_SetFile("Discord-Utilities"); - AutoExecConfig_SetCreateFile(true); - - g_cVerificationChannelID = AutoExecConfig_CreateConVar("sm_du_verfication_channelid", "", "Channel ID for verfication. Blank to disable."); - g_cGuildID = AutoExecConfig_CreateConVar("sm_du_verification_guildid", "", "Guild ID of your discord server. Blank to disable. Needed for verification module."); - g_cRoleID = AutoExecConfig_CreateConVar("sm_du_verification_roleid", "", "Role ID to give to user when user is verified. Blank to give no role. Verification module needs to be running."); - - g_cBotToken = AutoExecConfig_CreateConVar("sm_du_bottoken", "", "Bot Token. Needed for discord server => gameserver and/or verification module.", FCVAR_PROTECTED); - g_cCheckInterval = AutoExecConfig_CreateConVar("sm_du_accounts_check_interval", "300", "Time in seconds between verifying accounts."); - g_cUseSWGM = AutoExecConfig_CreateConVar("sm_du_use_swgm_file", "0", "Use SWGM config file for restricting commands."); - g_cServerID = AutoExecConfig_CreateConVar("sm_du_server_id", "1", "Increase this with every server you put this plugin in. Prevents multiple replies from the bot in verfication channel."); - g_cPrimaryServer = AutoExecConfig_CreateConVar("sm_du_server_primary", "0", "Is this the primary server in the verification channel? Only this server will respond to generic queries so atleast 1 server should have this 1."); - - g_cLinkCommand = AutoExecConfig_CreateConVar("sm_du_link_command", "!link", "Command to use in text channel."); - g_cViewIDCommand = AutoExecConfig_CreateConVar("sm_du_viewid_command", "sm_viewid", "Command to view id."); - g_cInviteLink = AutoExecConfig_CreateConVar("sm_du_link", "https://discord.gg/83g5xcE", "Invite link of your discord server."); - - g_cDiscordPrefix = AutoExecConfig_CreateConVar("sm_du_discord_prefix", "[{lightgreen}Discord{default}]", "Prefix for discord messages."); - g_cServerPrefix = AutoExecConfig_CreateConVar("sm_du_server_prefix", "[{lightgreen}Discord-Utilities{default}]", "Prefix for chat messages."); - - g_cDatabaseName = AutoExecConfig_CreateConVar("sm_du_database_name", "du", "Section name in databases.cfg."); - g_cTableName = AutoExecConfig_CreateConVar("sm_du_table_name", "du_users", "Table Name."); - g_cPruneDays = AutoExecConfig_CreateConVar("sm_du_prune_days", "60", "Prune database with players whose last connect is X DAYS and he is not member of discord server. 0 to disable."); - - AutoExecConfig_ExecuteFile(); - AutoExecConfig_CleanFile(); - - #else - g_cVerificationChannelID = CreateConVar("sm_du_verfication_channelid", "", "Channel ID for verfication. Blank to disable."); - g_cGuildID = CreateConVar("sm_du_verification_guildid", "", "Guild ID of your discord server. Blank to disable. Needed for verification module."); - g_cRoleID = CreateConVar("sm_du_verification_roleid", "", "Role ID to give to user when user is verified. Blank to give no role. Verification module needs to be running."); - - g_cBotToken = CreateConVar("sm_du_bottoken", "", "Bot Token. Needed for discord server => gameserver and/or verification module.", FCVAR_PROTECTED); - g_cCheckInterval = CreateConVar("sm_du_accounts_check_interval", "300", "Time in seconds between verifying accounts."); - g_cUseSWGM = CreateConVar("sm_du_use_swgm_file", "0", "Use SWGM config file for restricting commands."); - g_cServerID = CreateConVar("sm_du_server_id", "1", "Increase this with every server you put this plugin in. Prevents multiple replies from the bot in verfication channel."); - g_cPrimaryServer = CreateConVar("sm_du_server_primary", "0", "Is this the primary server in the verification channel? Only this server will respond to generic queries so atleast 1 server should have this 1."); - - g_cLinkCommand = CreateConVar("sm_du_link_command", "!link", "Command to use in text channel."); - g_cViewIDCommand = CreateConVar("sm_du_viewid_command", "sm_viewid", "Command to view id."); - g_cInviteLink = CreateConVar("sm_du_link", "https://discord.gg/83g5xcE", "Invite link of your discord server."); - - g_cDiscordPrefix = CreateConVar("sm_du_discord_prefix", "[{lightgreen}Discord{default}]", "Prefix for discord messages."); - g_cServerPrefix = CreateConVar("sm_du_server_prefix", "[{lightgreen}Discord-Utilities{default}]", "Prefix for chat messages."); - - g_cDatabaseName = CreateConVar("sm_du_database_name", "du", "Section name in databases.cfg."); - g_cTableName = CreateConVar("sm_du_table_name", "du_users", "Table Name."); - g_cPruneDays = CreateConVar("sm_du_prune_days", "60", "Prune database with players whose last connect is X DAYS and he is not member of discord server. 0 to disable."); - - AutoExecConfig(true, "Discord-Utilities"); - #endif - - HookConVarChange(g_cVerificationChannelID, OnSettingsChanged); - HookConVarChange(g_cGuildID, OnSettingsChanged); - HookConVarChange(g_cRoleID, OnSettingsChanged); - - HookConVarChange(g_cBotToken, OnSettingsChanged); - - HookConVarChange(g_cLinkCommand, OnSettingsChanged); - HookConVarChange(g_cViewIDCommand, OnSettingsChanged); - HookConVarChange(g_cInviteLink, OnSettingsChanged); - - HookConVarChange(g_cDiscordPrefix, OnSettingsChanged); - HookConVarChange(g_cServerPrefix, OnSettingsChanged); -} - -void LoadCvars() -{ - g_cVerificationChannelID.GetString(g_sVerificationChannelID, sizeof(g_sVerificationChannelID)); - g_cGuildID.GetString(g_sGuildID, sizeof(g_sGuildID)); - g_cRoleID.GetString(g_sRoleID, sizeof(g_sRoleID)); - - g_cBotToken.GetString(g_sBotToken, sizeof(g_sBotToken)); - - g_cLinkCommand.GetString(g_sLinkCommand, sizeof(g_sLinkCommand)); - g_cViewIDCommand.GetString(g_sViewIDCommand, sizeof(g_sViewIDCommand)); - g_cInviteLink.GetString(g_sInviteLink, sizeof(g_sInviteLink)); - - g_cDiscordPrefix.GetString(g_sDiscordPrefix, sizeof(g_sDiscordPrefix)); - g_cServerPrefix.GetString(g_sServerPrefix, sizeof(g_sServerPrefix)); -} - -void ManagingRole(char[] userid, char[] roleid, EHTTPMethod method) -{ - Handle hData = json_object(); - json_object_set_new(hData, "userid", json_string(userid)); - json_object_set_new(hData, "roleid", json_string(roleid)); - json_object_set_new(hData, "method", json_integer(view_as(method))); - ManageRole(hData); -} - -void ManageRole(Handle hData) -{ - if(StrEqual(g_sGuildID, "")) - { - LogError("[Discord-Utilities] GuildID is not provided. Role cannot be provided!"); - delete hData; - return; - } - char userid[128]; - if (!JsonObjectGetString(hData, "userid", userid, sizeof(userid))) - { - LogError("JsonObjectGetString \"userid\" failed"); - delete hData; - return; - } - char roleid[128]; - if (!JsonObjectGetString(hData, "roleid", roleid, sizeof(roleid))) - { - LogError("JsonObjectGetString \"roleid\" failed"); - delete hData; - return; - } - EHTTPMethod method = view_as(JsonObjectGetInt(hData, "method")); - char url[1024]; - FormatEx(url, sizeof(url), "https://discord.com/api/guilds/%s/members/%s/roles/%s", g_sGuildID, userid, roleid); - char route[512]; - FormatEx(route, sizeof(route), "guild/%s/members", g_sGuildID); - DiscordRequest request = new DiscordRequest(url, method); - if (request == null) - { - CreateTimer(2.0, SendManageRole, hData, TIMER_FLAG_NO_MAPCHANGE); - return; - } - request.SetCallbacks(HTTPCompleted, OnManageRoleSent); - request.SetContentSize(); - request.SetBot(Bot); - request.SetData(hData, route); - request.Send(route); -} - - -public void OnManageRoleSent(Handle request, bool failure, int offset, int statuscode, any dp) -{ - if(failure || (statuscode != 200)) - { - if(statuscode == 429 || statuscode == 500) - { - ManageRole(dp); - //delete view_as(dp); - delete request; - //LogError("OnManageRoleSent: Error code %d | Retrying to Managerole(dp)", statuscode); - return; - } - delete request; - delete view_as(dp); - //LogError("OnManageRoleSent: Error code %d | Deleting everything and returning", statuscode); - return; - } - delete request - delete view_as(dp); -} - - -stock void RefreshClients() -{ - for(int i = 1; i <= MaxClients; i++) if(IsClientInGame(i)) - { - OnClientPreAdminCheck(i); - } -} - -void LoadCommands() -{ - char sBuffer[256]; - if(g_cUseSWGM.IntValue == 1) - { - KeyValues kv = new KeyValues("Command_Listener"); - BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "configs/swgm/command_listener.ini"); - if(!FileToKeyValues(kv, sBuffer)) - { - SetFailState("[Discord-Utilities] Missing config file %s. If you don't use SWGM, then change 'sm_du_use_swgm_file' value to 0.", sBuffer); - } - if(kv.GotoFirstSubKey()) - { - do - { - if(kv.GetSectionName(sBuffer, sizeof(sBuffer))) - { - AddCommandListener(Check, sBuffer); - } - } - while (kv.GotoNextKey()); - } - delete kv; - return; - } - BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "configs/du/command_listener.ini"); - - File fFile = OpenFile(sBuffer, "r"); - - if(!FileExists(sBuffer)) - { - fFile.Close(); - fFile = OpenFile(sBuffer, "w+"); - fFile.WriteLine("// Separate each commands with separate lines. DON'T USE SPACE INFRONT OF COMMANDS. Example:"); - fFile.WriteLine("//sm_shop"); - fFile.WriteLine("//sm_store"); - fFile.WriteLine("//Use it without \"//\""); - fFile.Close(); - LogError("[Discord-Utilities] %s file is empty. Add commands to restrict them!", sBuffer); - return; - } - char sReadBuffer[PLATFORM_MAX_PATH]; - - int len; - while(!fFile.EndOfFile() && fFile.ReadLine(sReadBuffer, sizeof(sReadBuffer))) - { - if (sReadBuffer[0] == '/' && sReadBuffer[1] == '/' || IsCharSpace(sReadBuffer[0])) - { - continue; - } - - ReplaceString(sReadBuffer, sizeof(sReadBuffer), "\n", ""); - ReplaceString(sReadBuffer, sizeof(sReadBuffer), "\r", ""); - ReplaceString(sReadBuffer, sizeof(sReadBuffer), "\t", ""); - - len = strlen(sReadBuffer); - - if (len < 3) - { - continue; - } - - AddCommandListener(Check, sReadBuffer); - } - - fFile.Close(); -} - -stock void CreateBot(bool guilds = true, bool listen = true) -{ - if(StrEqual(g_sBotToken, "") || StrEqual(g_sVerificationChannelID, "")) - { - delete Bot; - return; - } - - delete Bot; - - //if(!g_bIsBotLoaded) - //{ - Bot = new DiscordBot(g_sBotToken); - //} - - if(guilds) - { - //if(g_bIsBotLoaded) - //{ - Bot.GetGuilds(GuildList, _, listen); - //} - //else - //{ - // CreateBot(); - //} - } -} - -stock void KillBot() -{ - if(Bot) - { - Bot.StopListeningToChannels(); - Bot.StopListening(); - } - delete Bot; -} - -stock int GetClientFromUniqueCode(const char[] unique) -{ - for(int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i)) continue; - if (StrEqual(g_sUniqueCode[i], unique)) return i; - } - return -1; -} - -stock char ChangePartsInString(char[] input, const char[] from, const char[] to) -{ - char output[64]; - ReplaceString(input, sizeof(output), from, to); - strcopy(output, sizeof(output), input); - return output; -} - -/* -stock void GetGuilds(bool listen = true) -{ - Bot.GetGuilds(GuildList, _, listen); -} -*/ - -stock void Discord_EscapeString(char[] string, int maxlen, bool name = false) -{ - if(name) - { - ReplaceString(string, maxlen, "everyone", "everyone"); - ReplaceString(string, maxlen, "here", "here"); - ReplaceString(string, maxlen, "discordtag", "discordtag"); - } - ReplaceString(string, maxlen, "#", "#"); - ReplaceString(string, maxlen, "@", "@"); - //ReplaceString(string, maxlen, ":", ""); - ReplaceString(string, maxlen, "_", "ˍ"); - ReplaceString(string, maxlen, "'", "'"); - ReplaceString(string, maxlen, "`", "'"); - ReplaceString(string, maxlen, "~", "∽"); - ReplaceString(string, maxlen, "\"", """); -} - -/* TIMERS */ - -public Action VerifyAccounts(Handle timer) -{ - AccountsCheck(); -} - -public Action Cmd_Unlink(int client, int args) -{ - if(g_bMember[client]) - { - char Query[256]; - - char szSteamId[32]; - GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); - - g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s WHERE steamid = '%s'", g_sTableName, szSteamId); - SQL_TQuery(g_hDB, SQLQuery_UnlinkAccount, Query, GetClientUserId(client)); - } -} -/* -public Action Timer_Query_AccountCheck(Handle timer) -{ - if(g_hDB == null) - { - CreateTimer(5.0, Timer_Query_AccountCheck, _, TIMER_FLAG_NO_MAPCHANGE); - return; - } - char Query[256]; - g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s", g_sTableName); - SQL_TQuery(g_hDB, SQLQuery_AccountCheck, Query); -} -*/ - -public Action SendGetMembers(Handle timer, any data) -{ - GetMembers(view_as(data)); -} - -public Action SendManageRole(Handle timer, any data) -{ - ManageRole(view_as(data)); -} - -public Action SendRequestAgain(Handle timer, DataPack dp) -{ - ResetPack(dp, false); - Handle request = ReadPackCell(dp); - char route[512]; - ReadPackString(dp, route, sizeof(route)); - delete dp; - DiscordSendRequest(request, route); -} - -public Action Timer_RefreshClients(Handle timer) -{ - RefreshClients(); -} - -stock void DU_DeleteMessageID(DiscordMessage discordmessage) -{ - char channelid[64], msgid[64]; - - discordmessage.GetChannelID(channelid, sizeof(channelid)); - discordmessage.GetID(msgid, sizeof(msgid)); - - Bot.DeleteMessageID(channelid, msgid); -} - -stock bool IsClientValid(int client) -{ - return (0 < client <= MaxClients) && IsClientInGame(client) && !IsFakeClient(client); -} +void AccountsCheck() +{ + Action action = Plugin_Continue; + Call_StartForward(g_hOnCheckedAccounts); + Call_PushString(g_sBotToken); + Call_PushString(g_sGuildID); + Call_PushString(g_sTableName); + Call_Finish(action); + + if(action >= Plugin_Handled) + { + return; + } + /* + if(g_hDB == null) + { + CreateTimer(5.0, Timer_Query_AccountCheck, _, TIMER_FLAG_NO_MAPCHANGE); + } + else + { + char Query[256]; + g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s", g_sTableName); + SQL_TQuery(g_hDB, SQLQuery_AccountCheck, Query); + } + */ + delete hFinalMemberList; + Handle hData = json_object(); + hFinalMemberList = json_array(); + json_object_set_new(hData, "limit", json_integer(1000)); + json_object_set_new(hData, "after", json_string("")); + GetMembers(hData); + //PrintToChatAll("AccountsCheck();"); +} + +void GetMembers(Handle hData = INVALID_HANDLE) +{ + if(StrEqual(g_sGuildID, "") && !StrEqual(g_sVerificationChannelID, "")) + { + LogError("[Discord-Utilities] GuildID is not provided. GetMember won't work!"); + delete hData; + delete hFinalMemberList; + return; + } + if(Bot == null) + { + delete hData; + delete hFinalMemberList; + return; + } + int limit = JsonObjectGetInt(hData, "limit"); + char after[32]; + JsonObjectGetString(hData, "after", after, sizeof(after)); + + char url[256]; + if(StrEqual(after, "")) + { + FormatEx(url, sizeof(url), "https://discord.com/api/guilds/%s/members?limit=%i", g_sGuildID, limit); + } + else + { + FormatEx(url, sizeof(url), "https://discord.com/api/guilds/%s/members?limit=%i&after=%s", g_sGuildID, limit, after); + } + + char route[128]; + FormatEx(route, sizeof(route), "guild/%s/members", g_sGuildID); + + DiscordRequest request = new DiscordRequest(url, k_EHTTPMethodGET); + if(request == null) + { + CreateTimer(2.0, SendGetMembers, hData); + return; + } + request.SetCallbacks(HTTPCompleted, MembersDataReceive); + request.SetBot(Bot); + request.SetData(hData, route); + request.Send(route); +} + +public int HTTPCompleted(Handle request, bool failure, bool requestSuccessful, EHTTPStatusCode statuscode, any data, any data2) +{ +} + +public void MembersDataReceive(Handle request, bool failure, int offset, int statuscode, any dp) +{ + if(failure || (statuscode != 200)) + { + if(statuscode == 429 || statuscode == 500) + { + GetMembers(dp); + //delete view_as(dp); + delete request; + return; + } + delete hFinalMemberList; + delete request; + delete view_as(dp); + return; + } + SteamWorks_GetHTTPResponseBodyCallback(request, GetMembersData, dp); + delete request; +} + +public int GetMembersData(const char[] data, any dp) +{ + //PrintToChatAll("GetMembersData();"); + Handle hJson = json_load(data); + //bool returned = json_array_extend(hFinalMemberList, hJson); + json_array_extend(hFinalMemberList, hJson); + //PrintToChatAll("returned %d", returned); + Handle hData = view_as(dp); + + int size = json_array_size(hJson); + int limit = JsonObjectGetInt(hData, "limit"); + //PrintToChatAll("size %d | limit %d", size, limit); + + if(limit == size) + { + char userid[32]; + DiscordGuildUser GuildUser; + DiscordUser user; + + GuildUser = view_as(json_array_get(hJson, limit - 1)); + user = GuildUser.GetUser(); + user.GetID(userid, sizeof(userid)); + delete GuildUser; + delete user; + //PrintToChatAll("userID %s", userid); + + delete hJson; + + json_object_set_new(hData, "after", json_string(userid)); + GetMembers(hData); + return; + } + + OnGetMembersAll(hFinalMemberList); + + delete hJson; + delete hData; + delete hFinalMemberList; +} + +public void OnGetMembersAll(Handle hMemberList) +{ + //DeleteFile("addons/sourcemod/logs/dsmembers.json") + if(g_cDsMembersEnabled.IntValue ==1) json_dump_file(hMemberList, "addons/sourcemod/logs/dsmembers.json"); + //LogToFile("addons/sourcemod/logs/dsmembers.json", "OnGetMembersAll size %d", json_array_size(hMemberList)); + + Call_StartForward(g_hOnMemberDataDumped); + Call_Finish(); + + char userid[20]; + DiscordGuildUser GuildUser; + DiscordUser user; + bool found; + char Query[256]; + bool[] bUpdate = new bool[MaxClients+1]; + + for(int x = 1; x <= MaxClients; x++) + { + if(!IsClientInGame(x)) + { + continue; + } + if(!g_bMember[x]) + { + continue; + } + found = false; + for(int i = 0; i < json_array_size(hMemberList); i++) + { + GuildUser = view_as(json_array_get(hMemberList, i)); + user = GuildUser.GetUser(); + user.GetID(userid, sizeof(userid)); + if(strcmp(userid, g_sUserID[x]) == 0) + { + found = true; + delete user; + delete GuildUser; + break; + } + delete user; + delete GuildUser; + } + delete user; + delete GuildUser; + if(!found) + { + char steamid[32]; + GetClientAuthId(x, AuthId_Steam2, steamid, sizeof(steamid)); + if(g_bIsMySQl) + { + g_hDB.Format(Query, sizeof(Query), "UPDATE `%s` SET `userid` = '%s', member = '0' WHERE `steamid` = '%s';", g_sTableName, NULL_STRING, steamid); + } + else + { + g_hDB.Format(Query, sizeof(Query), "UPDATE %s SET userid = '%s', member = '0' WHERE steamid = '%s';", g_sTableName, NULL_STRING, steamid); + } + SQL_TQuery(g_hDB, SQLQuery_UpdatePlayer, Query); + bUpdate[x] = true; + CPrintToChat(x, "%s %T", g_sServerPrefix, "DiscordRevoked", x); + + if(g_cLogRevokeEnabled.IntValue == 1) LogToFile("addons/sourcemod/logs/dsmembers_revoke.log", "Player %L got revoked. Memberlist json size: %d", x, json_array_size(hMemberList)); + + Call_StartForward(g_hOnAccountRevoked); + Call_PushCell(x); + Call_PushString(g_sUserID[x]); + Call_Finish(); + } + else + { + if(strlen(g_sRoleID) > 5 && !g_bRoleGiven[x]) + { + ManagingRole(g_sUserID[x], g_sRoleID, k_EHTTPMethodPUT); + g_bRoleGiven[x] = true; + } + } + } + + for(int i = 1; i <= MaxClients; i++) + { + if(!IsClientInGame(i)) + { + continue; + } + if(!bUpdate[i]) + { + continue; + } + OnClientPutInServer(i); + OnClientPreAdminCheck(i); + } +} + +/* +void GetGuildMember(char[] userid) +{ + Handle hData = json_object(); + json_object_set_new(hData, "userID", json_string(userid[0])); + GetMembers(hData); +} +*/ + +void UpdatePlayer(int client) +{ + char steamid[32], szQuery[512]; + + GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid)); + if(g_bIsMySQl) + { + g_hDB.Format(szQuery, sizeof(szQuery), "UPDATE `%s` SET last_accountuse = '%d' WHERE `steamid` = '%s';", g_sTableName, GetTime(), steamid); + } + else + { + g_hDB.Format(szQuery, sizeof(szQuery), "UPDATE %s SET last_accountuse = '%d' WHERE steamid = '%s'", g_sTableName, GetTime(), steamid); + } + SQL_TQuery(g_hDB, SQLQuery_UpdatePlayer, szQuery, GetClientUserId(client)); +} + +stock void RemoveColors(char[] text, int size) +{ + if(g_bShavit) + { + for(int i = 0; i < sizeof(gS_GlobalColorNames); i++) + { + ReplaceString(text, size, gS_GlobalColorNames[i], ""); + } + for(int i = 0; i < sizeof(gS_GlobalColors); i++) + { + ReplaceString(text, size, gS_GlobalColors[i], ""); + } + for(int i = 0; i < sizeof(gS_CSGOColorNames); i++) + { + ReplaceString(text, size, gS_CSGOColorNames[i], ""); + } + for(int i = 0; i < sizeof(gS_CSGOColors); i++) + { + ReplaceString(text, size, gS_CSGOColors[i], ""); + } + } + else + { + for(int i = 0; i < sizeof(C_Tag); i++) + { + ReplaceString(text, size, C_Tag[i], ""); + } + for(int i = 0; i < sizeof(C_TagCode); i++) + { + ReplaceString(text, size, C_TagCode[i], ""); + } + } +} + +void CreateCvars() +{ + #if defined USE_AutoExecConfig + AutoExecConfig_SetFile("Discord-Utilities"); + AutoExecConfig_SetCreateFile(true); + + g_cVerificationChannelID = AutoExecConfig_CreateConVar("sm_du_verfication_channelid", "", "Channel ID for verfication. Blank to disable."); + g_cGuildID = AutoExecConfig_CreateConVar("sm_du_verification_guildid", "", "Guild ID of your discord server. Blank to disable. Needed for verification module."); + g_cRoleID = AutoExecConfig_CreateConVar("sm_du_verification_roleid", "", "Role ID to give to user when user is verified. Blank to give no role. Verification module needs to be running."); + + g_cBotToken = AutoExecConfig_CreateConVar("sm_du_bottoken", "", "Bot Token. Needed for discord server => gameserver and/or verification module.", FCVAR_PROTECTED); + g_cCheckInterval = AutoExecConfig_CreateConVar("sm_du_accounts_check_interval", "300", "Time in seconds between verifying accounts."); + g_cUseSWGM = AutoExecConfig_CreateConVar("sm_du_use_swgm_file", "0", "Use SWGM config file for restricting commands."); + g_cServerID = AutoExecConfig_CreateConVar("sm_du_server_id", "1", "Increase this with every server you put this plugin in. Prevents multiple replies from the bot in verfication channel."); + g_cPrimaryServer = AutoExecConfig_CreateConVar("sm_du_server_primary", "0", "Is this the primary server in the verification channel? Only this server will respond to generic queries so atleast 1 server should have this 1."); + + g_cLinkCommand = AutoExecConfig_CreateConVar("sm_du_link_command", "!link", "Command to use in text channel."); + g_cViewIDCommand = AutoExecConfig_CreateConVar("sm_du_viewid_command", "sm_viewid", "Command to view id."); + g_cUnLinkCommand = AutoExecConfig_CreateConVar("sm_du_unlink_command", "sm_unlink", "Command to unlink."); + g_cInviteLink = AutoExecConfig_CreateConVar("sm_du_link", "https://discord.gg/83g5xcE", "Invite link of your discord server."); + + g_cDiscordPrefix = AutoExecConfig_CreateConVar("sm_du_discord_prefix", "[{lightgreen}Discord{default}]", "Prefix for discord messages."); + g_cServerPrefix = AutoExecConfig_CreateConVar("sm_du_server_prefix", "[{lightgreen}Discord-Utilities{default}]", "Prefix for chat messages."); + + g_cDatabaseName = AutoExecConfig_CreateConVar("sm_du_database_name", "du", "Section name in databases.cfg."); + g_cTableName = AutoExecConfig_CreateConVar("sm_du_table_name", "du_users", "Table Name."); + g_cPruneDays = AutoExecConfig_CreateConVar("sm_du_prune_days", "60", "Prune database with players whose last connect is X DAYS and he is not member of discord server. 0 to disable."); + + g_cLogRevokeEnabled = AutoExecConfig_CreateConVar("sm_du_logrevoke_enabled", "1", "Enable log for revoke?"); + g_cDsMembersEnabled = AutoExecConfig_CreateConVar("sm_du_dsmembersfile_enabled", "1", "Enable to create logs/dsmembers.json?"); + + AutoExecConfig_ExecuteFile(); + AutoExecConfig_CleanFile(); + + #else + g_cVerificationChannelID = CreateConVar("sm_du_verfication_channelid", "", "Channel ID for verfication. Blank to disable."); + g_cGuildID = CreateConVar("sm_du_verification_guildid", "", "Guild ID of your discord server. Blank to disable. Needed for verification module."); + g_cRoleID = CreateConVar("sm_du_verification_roleid", "", "Role ID to give to user when user is verified. Blank to give no role. Verification module needs to be running."); + + g_cBotToken = CreateConVar("sm_du_bottoken", "", "Bot Token. Needed for discord server => gameserver and/or verification module.", FCVAR_PROTECTED); + g_cCheckInterval = CreateConVar("sm_du_accounts_check_interval", "300", "Time in seconds between verifying accounts."); + g_cUseSWGM = CreateConVar("sm_du_use_swgm_file", "0", "Use SWGM config file for restricting commands."); + g_cServerID = CreateConVar("sm_du_server_id", "1", "Increase this with every server you put this plugin in. Prevents multiple replies from the bot in verfication channel."); + g_cPrimaryServer = CreateConVar("sm_du_server_primary", "0", "Is this the primary server in the verification channel? Only this server will respond to generic queries so atleast 1 server should have this 1."); + + g_cLinkCommand = CreateConVar("sm_du_link_command", "!link", "Command to use in text channel."); + g_cViewIDCommand = CreateConVar("sm_du_viewid_command", "sm_viewid", "Command to view id."); + g_cUnLinkCommand = CreateConVar("sm_du_unlink_command", "sm_unlink", "Command to unlink."); + g_cInviteLink = CreateConVar("sm_du_link", "https://discord.gg/83g5xcE", "Invite link of your discord server."); + + g_cDiscordPrefix = CreateConVar("sm_du_discord_prefix", "[{lightgreen}Discord{default}]", "Prefix for discord messages."); + g_cServerPrefix = CreateConVar("sm_du_server_prefix", "[{lightgreen}Discord-Utilities{default}]", "Prefix for chat messages."); + + g_cDatabaseName = CreateConVar("sm_du_database_name", "du", "Section name in databases.cfg."); + g_cTableName = CreateConVar("sm_du_table_name", "du_users", "Table Name."); + g_cPruneDays = CreateConVar("sm_du_prune_days", "60", "Prune database with players whose last connect is X DAYS and he is not member of discord server. 0 to disable."); + + g_cLogRevokeEnabled = CreateConVar("sm_du_logrevoke_enabled", "1", "Enable log for revoke?"); + g_cDsMembersEnabled = CreateConVar("sm_du_dsmembersfile_enabled", "1", "Enable to create logs/dsmembers.json?"); + + AutoExecConfig(true, "Discord-Utilities"); + #endif + + HookConVarChange(g_cVerificationChannelID, OnSettingsChanged); + HookConVarChange(g_cGuildID, OnSettingsChanged); + HookConVarChange(g_cRoleID, OnSettingsChanged); + + HookConVarChange(g_cBotToken, OnSettingsChanged); + + HookConVarChange(g_cLinkCommand, OnSettingsChanged); + HookConVarChange(g_cViewIDCommand, OnSettingsChanged); + HookConVarChange(g_cUnLinkCommand, OnSettingsChanged); + HookConVarChange(g_cInviteLink, OnSettingsChanged); + + HookConVarChange(g_cDiscordPrefix, OnSettingsChanged); + HookConVarChange(g_cServerPrefix, OnSettingsChanged); +} + +void LoadCvars() +{ + g_cVerificationChannelID.GetString(g_sVerificationChannelID, sizeof(g_sVerificationChannelID)); + g_cGuildID.GetString(g_sGuildID, sizeof(g_sGuildID)); + g_cRoleID.GetString(g_sRoleID, sizeof(g_sRoleID)); + + g_cBotToken.GetString(g_sBotToken, sizeof(g_sBotToken)); + + g_cLinkCommand.GetString(g_sLinkCommand, sizeof(g_sLinkCommand)); + g_cViewIDCommand.GetString(g_sViewIDCommand, sizeof(g_sViewIDCommand)); + g_cUnLinkCommand.GetString(g_sUnLinkCommand, sizeof(g_sUnLinkCommand)); + g_cInviteLink.GetString(g_sInviteLink, sizeof(g_sInviteLink)); + + g_cDiscordPrefix.GetString(g_sDiscordPrefix, sizeof(g_sDiscordPrefix)); + g_cServerPrefix.GetString(g_sServerPrefix, sizeof(g_sServerPrefix)); +} + +void ManagingRole(char[] userid, char[] roleid, EHTTPMethod method) +{ + Handle hData = json_object(); + json_object_set_new(hData, "userid", json_string(userid)); + json_object_set_new(hData, "roleid", json_string(roleid)); + json_object_set_new(hData, "method", json_integer(view_as(method))); + ManageRole(hData); +} + +void ManageRole(Handle hData) +{ + if(StrEqual(g_sGuildID, "")) + { + LogError("[Discord-Utilities] GuildID is not provided. Role cannot be provided!"); + delete hData; + return; + } + char userid[128]; + if (!JsonObjectGetString(hData, "userid", userid, sizeof(userid))) + { + LogError("JsonObjectGetString \"userid\" failed"); + delete hData; + return; + } + char roleid[128]; + if (!JsonObjectGetString(hData, "roleid", roleid, sizeof(roleid))) + { + LogError("JsonObjectGetString \"roleid\" failed"); + delete hData; + return; + } + EHTTPMethod method = view_as(JsonObjectGetInt(hData, "method")); + char url[1024]; + FormatEx(url, sizeof(url), "https://discord.com/api/guilds/%s/members/%s/roles/%s", g_sGuildID, userid, roleid); + char route[512]; + FormatEx(route, sizeof(route), "guild/%s/members", g_sGuildID); + DiscordRequest request = new DiscordRequest(url, method); + if (request == null) + { + CreateTimer(2.0, SendManageRole, hData, TIMER_FLAG_NO_MAPCHANGE); + return; + } + request.SetCallbacks(HTTPCompleted, OnManageRoleSent); + request.SetContentSize(); + request.SetBot(Bot); + request.SetData(hData, route); + request.Send(route); +} + + +public void OnManageRoleSent(Handle request, bool failure, int offset, int statuscode, any dp) +{ + if(failure || (statuscode != 200)) + { + if(statuscode == 429 || statuscode == 500) + { + ManageRole(dp); + //delete view_as(dp); + delete request; + //LogError("OnManageRoleSent: Error code %d | Retrying to Managerole(dp)", statuscode); + return; + } + delete request; + delete view_as(dp); + //LogError("OnManageRoleSent: Error code %d | Deleting everything and returning", statuscode); + return; + } + delete request + delete view_as(dp); +} + + +stock void RefreshClients() +{ + for(int i = 1; i <= MaxClients; i++) if(IsClientInGame(i)) + { + OnClientPreAdminCheck(i); + } +} + +void LoadCommands() +{ + char sBuffer[256]; + if(g_cUseSWGM.IntValue == 1) + { + KeyValues kv = new KeyValues("Command_Listener"); + BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "configs/swgm/command_listener.ini"); + if(!FileToKeyValues(kv, sBuffer)) + { + SetFailState("[Discord-Utilities] Missing config file %s. If you don't use SWGM, then change 'sm_du_use_swgm_file' value to 0.", sBuffer); + } + if(kv.GotoFirstSubKey()) + { + do + { + if(kv.GetSectionName(sBuffer, sizeof(sBuffer))) + { + AddCommandListener(Check, sBuffer); + } + } + while (kv.GotoNextKey()); + } + delete kv; + return; + } + BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "configs/du/command_listener.ini"); + + File fFile = OpenFile(sBuffer, "r"); + + if(!FileExists(sBuffer)) + { + fFile.Close(); + fFile = OpenFile(sBuffer, "w+"); + fFile.WriteLine("// Separate each commands with separate lines. DON'T USE SPACE INFRONT OF COMMANDS. Example:"); + fFile.WriteLine("//sm_shop"); + fFile.WriteLine("//sm_store"); + fFile.WriteLine("//Use it without \"//\""); + fFile.Close(); + LogError("[Discord-Utilities] %s file is empty. Add commands to restrict them!", sBuffer); + return; + } + char sReadBuffer[PLATFORM_MAX_PATH]; + + int len; + while(!fFile.EndOfFile() && fFile.ReadLine(sReadBuffer, sizeof(sReadBuffer))) + { + if (sReadBuffer[0] == '/' && sReadBuffer[1] == '/' || IsCharSpace(sReadBuffer[0])) + { + continue; + } + + ReplaceString(sReadBuffer, sizeof(sReadBuffer), "\n", ""); + ReplaceString(sReadBuffer, sizeof(sReadBuffer), "\r", ""); + ReplaceString(sReadBuffer, sizeof(sReadBuffer), "\t", ""); + + len = strlen(sReadBuffer); + + if (len < 3) + { + continue; + } + + AddCommandListener(Check, sReadBuffer); + } + + fFile.Close(); +} + +stock void CreateBot(bool guilds = true, bool listen = true) +{ + if(StrEqual(g_sBotToken, "") || StrEqual(g_sVerificationChannelID, "")) + { + delete Bot; + return; + } + + delete Bot; + + //if(!g_bIsBotLoaded) + //{ + Bot = new DiscordBot(g_sBotToken); + //} + + if(guilds) + { + //if(g_bIsBotLoaded) + //{ + Bot.GetGuilds(GuildList, _, listen); + //} + //else + //{ + // CreateBot(); + //} + } +} + +stock void KillBot() +{ + if(Bot) + { + Bot.StopListeningToChannels(); + Bot.StopListening(); + } + delete Bot; +} + +stock int GetClientFromUniqueCode(const char[] unique) +{ + for(int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i)) continue; + if (StrEqual(g_sUniqueCode[i], unique)) return i; + } + return -1; +} + +stock char ChangePartsInString(char[] input, const char[] from, const char[] to) +{ + char output[64]; + ReplaceString(input, sizeof(output), from, to); + strcopy(output, sizeof(output), input); + return output; +} + +/* +stock void GetGuilds(bool listen = true) +{ + Bot.GetGuilds(GuildList, _, listen); +} +*/ + +stock void Discord_EscapeString(char[] string, int maxlen, bool name = false) +{ + if(name) + { + ReplaceString(string, maxlen, "everyone", "everyone"); + ReplaceString(string, maxlen, "here", "here"); + ReplaceString(string, maxlen, "discordtag", "discordtag"); + } + ReplaceString(string, maxlen, "#", "#"); + ReplaceString(string, maxlen, "@", "@"); + //ReplaceString(string, maxlen, ":", ""); + ReplaceString(string, maxlen, "_", "ˍ"); + ReplaceString(string, maxlen, "'", "'"); + ReplaceString(string, maxlen, "`", "'"); + ReplaceString(string, maxlen, "~", "∽"); + ReplaceString(string, maxlen, "\"", """); +} + +/* TIMERS */ + +public Action VerifyAccounts(Handle timer) +{ + AccountsCheck(); +} + +public Action Cmd_Unlink(int client, int args) +{ + if(g_bMember[client]) + { + char Query[256]; + + char szSteamId[32]; + GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); + + g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s WHERE steamid = '%s'", g_sTableName, szSteamId); + SQL_TQuery(g_hDB, SQLQuery_UnlinkAccount, Query, GetClientUserId(client)); + } +} +/* +public Action Timer_Query_AccountCheck(Handle timer) +{ + if(g_hDB == null) + { + CreateTimer(5.0, Timer_Query_AccountCheck, _, TIMER_FLAG_NO_MAPCHANGE); + return; + } + char Query[256]; + g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s", g_sTableName); + SQL_TQuery(g_hDB, SQLQuery_AccountCheck, Query); +} +*/ + +public Action SendGetMembers(Handle timer, any data) +{ + GetMembers(view_as(data)); +} + +public Action SendManageRole(Handle timer, any data) +{ + ManageRole(view_as(data)); +} + +public Action SendRequestAgain(Handle timer, DataPack dp) +{ + ResetPack(dp, false); + Handle request = ReadPackCell(dp); + char route[512]; + ReadPackString(dp, route, sizeof(route)); + delete dp; + DiscordSendRequest(request, route); +} + +public Action Timer_RefreshClients(Handle timer) +{ + RefreshClients(); +} + +stock void DU_DeleteMessageID(DiscordMessage discordmessage) +{ + char channelid[64], msgid[64]; + + discordmessage.GetChannelID(channelid, sizeof(channelid)); + discordmessage.GetID(msgid, sizeof(msgid)); + + Bot.DeleteMessageID(channelid, msgid); +} + +stock bool IsClientValid(int client) +{ + return (0 < client <= MaxClients) && IsClientInGame(client) && !IsFakeClient(client); +} diff --git a/include/discord_utilities/modules.sp b/scripting/discord_utilities/modules.sp similarity index 96% rename from include/discord_utilities/modules.sp rename to scripting/discord_utilities/modules.sp index e0ea489..a3f319e 100644 --- a/include/discord_utilities/modules.sp +++ b/scripting/discord_utilities/modules.sp @@ -1,122 +1,122 @@ -public void ChatRelayReceived(DiscordBot bawt, DiscordChannel channel, DiscordMessage discordmessage) -{ - DiscordUser author = discordmessage.GetAuthor(); - if(author.IsBot()) - { - delete author; - return; - } - - char message[512]; - char userName[32], discriminator[6]; - discordmessage.GetContent(message, sizeof(message)); - author.GetUsername(userName, sizeof(userName)); - author.GetDiscriminator(discriminator, sizeof(discriminator)); - delete author; - - CPrintToChatAll("%s %T", g_sDiscordPrefix, "ChatRelayFormat", LANG_SERVER, userName, discriminator, message); -} - -public void OnMessageReceived(DiscordBot bawt, DiscordChannel channel, DiscordMessage discordmessage) -{ - DiscordUser author = discordmessage.GetAuthor(); - if(author.IsBot()) - { - delete author; - return; - } - - char szValues[2][99]; - char szReply[512]; - char message[512]; - char userID[20], userName[32], discriminator[6]; - - discordmessage.GetContent(message, sizeof(message)); - author.GetUsername(userName, sizeof(userName)); - author.GetDiscriminator(discriminator, sizeof(discriminator)); - author.GetID(userID, sizeof(userID)); - delete author; - - int retrieved1 = ExplodeString(message, " ", szValues, sizeof(szValues), sizeof(szValues[])); - TrimString(szValues[1]); - - char _szValues[3][75]; - int retrieved2 = ExplodeString(szValues[1], "-", _szValues, sizeof(_szValues), sizeof(_szValues[])); - - bool bIsPrimary = g_cPrimaryServer.BoolValue; - - if(StrEqual(szValues[0], g_sLinkCommand)) - { - if (retrieved1 < 2) - { - //Prevent multiple replies, only allow the primary server to respond - if (bIsPrimary) - { - Format(szReply, sizeof(szReply), "%T", "DiscordMissingParameters", LANG_SERVER, userID); - Bot.SendMessage(channel, szReply); - DU_DeleteMessageID(discordmessage); - } - return; - } - else if (retrieved2 != 3) - { - if (bIsPrimary) - { - Format(szReply, sizeof(szReply), "%T", "DiscordInvalidID", LANG_SERVER, userID, g_sViewIDCommand); - Bot.SendMessage(channel, szReply); - DU_DeleteMessageID(discordmessage); - } - return; - } - - if(StringToInt(_szValues[0]) != g_cServerID.IntValue) - { - return; //Prevent multiple replies from the bot (for e.g. the plugin is installed on more than 1 server and they're using the same bot & channel) - } - - int client = GetClientFromUniqueCode(szValues[1]); - if(client <= 0) - { - Format(szReply, sizeof(szReply), "%T", "DiscordInvalid", LANG_SERVER, userID); - Bot.SendMessage(channel, szReply); - } - else if (!g_bMember[client]) - { - DataPack datapack = new DataPack(); - datapack.WriteCell(client); - datapack.WriteString(userID); - datapack.WriteString(userName); - datapack.WriteString(discriminator); - //datapack.WriteString(messageID); - - char szSteamId[32]; - GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); - - char Query[512]; - g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s WHERE steamid = '%s'", g_sTableName, szSteamId); - SQL_TQuery(g_hDB, SQLQuery_CheckUserData, Query, datapack); - - //Security addition - renew unique code in case another user copies it before query returns (?) - GetClientAuthId(client, AuthId_SteamID64, szSteamId, sizeof(szSteamId)); - int uniqueNum = GetRandomInt(100000, 999999); - Format(g_sUniqueCode[client], sizeof(g_sUniqueCode), "%i-%i-%s", g_cServerID.IntValue, uniqueNum, szSteamId); - - return; //Dont delete this message so user has positive confirmation - } - else - { - //Don't bother querying the DB if user is already a member - Format(szReply, sizeof(szReply), "%T", "DiscordAlreadyLinked", LANG_SERVER, userID); - Bot.SendMessage(channel, szReply); - } - } - else - { - if (bIsPrimary) - { - Format(szReply, sizeof(szReply), "%T", "DiscordInfo", LANG_SERVER, userID, g_sLinkCommand); - Bot.SendMessage(channel, szReply); - } - } - DU_DeleteMessageID(discordmessage); +public void ChatRelayReceived(DiscordBot bawt, DiscordChannel channel, DiscordMessage discordmessage) +{ + DiscordUser author = discordmessage.GetAuthor(); + if(author.IsBot()) + { + delete author; + return; + } + + char message[512]; + char userName[32], discriminator[6]; + discordmessage.GetContent(message, sizeof(message)); + author.GetUsername(userName, sizeof(userName)); + author.GetDiscriminator(discriminator, sizeof(discriminator)); + delete author; + + CPrintToChatAll("%s %T", g_sDiscordPrefix, "ChatRelayFormat", LANG_SERVER, userName, discriminator, message); +} + +public void OnMessageReceived(DiscordBot bawt, DiscordChannel channel, DiscordMessage discordmessage) +{ + DiscordUser author = discordmessage.GetAuthor(); + if(author.IsBot()) + { + delete author; + return; + } + + char szValues[2][99]; + char szReply[512]; + char message[512]; + char userID[20], userName[32], discriminator[6]; + + discordmessage.GetContent(message, sizeof(message)); + author.GetUsername(userName, sizeof(userName)); + author.GetDiscriminator(discriminator, sizeof(discriminator)); + author.GetID(userID, sizeof(userID)); + delete author; + + int retrieved1 = ExplodeString(message, " ", szValues, sizeof(szValues), sizeof(szValues[])); + TrimString(szValues[1]); + + char _szValues[3][75]; + int retrieved2 = ExplodeString(szValues[1], "-", _szValues, sizeof(_szValues), sizeof(_szValues[])); + + bool bIsPrimary = g_cPrimaryServer.BoolValue; + + if(StrEqual(szValues[0], g_sLinkCommand)) + { + if (retrieved1 < 2) + { + //Prevent multiple replies, only allow the primary server to respond + if (bIsPrimary) + { + Format(szReply, sizeof(szReply), "%T", "DiscordMissingParameters", LANG_SERVER, userID); + Bot.SendMessage(channel, szReply); + DU_DeleteMessageID(discordmessage); + } + return; + } + else if (retrieved2 != 3) + { + if (bIsPrimary) + { + Format(szReply, sizeof(szReply), "%T", "DiscordInvalidID", LANG_SERVER, userID, g_sViewIDCommand); + Bot.SendMessage(channel, szReply); + DU_DeleteMessageID(discordmessage); + } + return; + } + + if(StringToInt(_szValues[0]) != g_cServerID.IntValue) + { + return; //Prevent multiple replies from the bot (for e.g. the plugin is installed on more than 1 server and they're using the same bot & channel) + } + + int client = GetClientFromUniqueCode(szValues[1]); + if(client <= 0) + { + Format(szReply, sizeof(szReply), "%T", "DiscordInvalid", LANG_SERVER, userID); + Bot.SendMessage(channel, szReply); + } + else if (!g_bMember[client]) + { + DataPack datapack = new DataPack(); + datapack.WriteCell(client); + datapack.WriteString(userID); + datapack.WriteString(userName); + datapack.WriteString(discriminator); + //datapack.WriteString(messageID); + + char szSteamId[32]; + GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); + + char Query[512]; + g_hDB.Format(Query, sizeof(Query), "SELECT userid FROM %s WHERE steamid = '%s'", g_sTableName, szSteamId); + SQL_TQuery(g_hDB, SQLQuery_CheckUserData, Query, datapack); + + //Security addition - renew unique code in case another user copies it before query returns (?) + GetClientAuthId(client, AuthId_SteamID64, szSteamId, sizeof(szSteamId)); + int uniqueNum = GetRandomInt(100000, 999999); + Format(g_sUniqueCode[client], sizeof(g_sUniqueCode), "%i-%i-%s", g_cServerID.IntValue, uniqueNum, szSteamId); + + return; //Dont delete this message so user has positive confirmation + } + else + { + //Don't bother querying the DB if user is already a member + Format(szReply, sizeof(szReply), "%T", "DiscordAlreadyLinked", LANG_SERVER, userID); + Bot.SendMessage(channel, szReply); + } + } + else + { + if (bIsPrimary) + { + Format(szReply, sizeof(szReply), "%T", "DiscordInfo", LANG_SERVER, userID, g_sLinkCommand); + Bot.SendMessage(channel, szReply); + } + } + DU_DeleteMessageID(discordmessage); } \ No newline at end of file diff --git a/include/discord_utilities/natives.sp b/scripting/discord_utilities/natives.sp similarity index 96% rename from include/discord_utilities/natives.sp rename to scripting/discord_utilities/natives.sp index 5c1f8cc..bf3ab35 100644 --- a/include/discord_utilities/natives.sp +++ b/scripting/discord_utilities/natives.sp @@ -1,80 +1,80 @@ -public int Native_IsChecked(Handle plugin, int numparams) -{ - return g_bChecked[GetNativeCell(1)]; -} - -public int Native_IsDiscordMember(Handle plugin, int numparams) -{ - if(!g_bChecked[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); - } - return g_bMember[GetNativeCell(1)]; -} - -public int Native_GetUserId(Handle plugin, int numparams) -{ - if(!g_bChecked[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); - } - if(!g_bMember[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N isn't verified.", GetNativeCell(1)); - } - - SetNativeString(2, g_sUserID[GetNativeCell(1)], GetNativeCell(3)); - return 0; -} - -public int Native_RefreshClients(Handle plugin, int numparams) -{ - if(!g_bChecked[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); - } - RefreshClients(); - return 0; -} - -public int Native_AddRole(Handle plugin, int numparams) -{ - if(!g_bChecked[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); - } - if(!g_bMember[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N isn't verified.", GetNativeCell(1)); - } - if(g_sUserID[GetNativeCell(1)][0] == '\0') - { - return ThrowNativeError(25, "[Discord-Utilities] %N's userid doesn't exist in database.", GetNativeCell(1)); - } - int client = GetNativeCell(1); - char roleid[128]; - GetNativeString(2, roleid, sizeof(roleid)); - ManagingRole(g_sUserID[client], roleid, k_EHTTPMethodPUT); - return 0; -} - -public int Native_DeleteRole(Handle plugin, int numparams) -{ - if(!g_bChecked[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); - } - if(!g_bMember[GetNativeCell(1)]) - { - return ThrowNativeError(25, "[Discord-Utilities] %N isn't verified.", GetNativeCell(1)); - } - if(g_sUserID[GetNativeCell(1)][0] == '\0') - { - return ThrowNativeError(25, "[Discord-Utilities] %N's userid doesn't exist in database.", GetNativeCell(1)); - } - int client = GetNativeCell(1); - char roleid[128]; - GetNativeString(2, roleid, sizeof(roleid)); - ManagingRole(g_sUserID[client], roleid, k_EHTTPMethodDELETE); - return 0; +public int Native_IsChecked(Handle plugin, int numparams) +{ + return g_bChecked[GetNativeCell(1)]; +} + +public int Native_IsDiscordMember(Handle plugin, int numparams) +{ + if(!g_bChecked[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); + } + return g_bMember[GetNativeCell(1)]; +} + +public int Native_GetUserId(Handle plugin, int numparams) +{ + if(!g_bChecked[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); + } + if(!g_bMember[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N isn't verified.", GetNativeCell(1)); + } + + SetNativeString(2, g_sUserID[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public int Native_RefreshClients(Handle plugin, int numparams) +{ + if(!g_bChecked[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); + } + RefreshClients(); + return 0; +} + +public int Native_AddRole(Handle plugin, int numparams) +{ + if(!g_bChecked[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); + } + if(!g_bMember[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N isn't verified.", GetNativeCell(1)); + } + if(g_sUserID[GetNativeCell(1)][0] == '\0') + { + return ThrowNativeError(25, "[Discord-Utilities] %N's userid doesn't exist in database.", GetNativeCell(1)); + } + int client = GetNativeCell(1); + char roleid[128]; + GetNativeString(2, roleid, sizeof(roleid)); + ManagingRole(g_sUserID[client], roleid, k_EHTTPMethodPUT); + return 0; +} + +public int Native_DeleteRole(Handle plugin, int numparams) +{ + if(!g_bChecked[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N hasn't been checked. Call this in OnClientPostAdminCheck.", GetNativeCell(1)); + } + if(!g_bMember[GetNativeCell(1)]) + { + return ThrowNativeError(25, "[Discord-Utilities] %N isn't verified.", GetNativeCell(1)); + } + if(g_sUserID[GetNativeCell(1)][0] == '\0') + { + return ThrowNativeError(25, "[Discord-Utilities] %N's userid doesn't exist in database.", GetNativeCell(1)); + } + int client = GetNativeCell(1); + char roleid[128]; + GetNativeString(2, roleid, sizeof(roleid)); + ManagingRole(g_sUserID[client], roleid, k_EHTTPMethodDELETE); + return 0; } \ No newline at end of file diff --git a/include/discord_utilities/sql.sp b/scripting/discord_utilities/sql.sp similarity index 94% rename from include/discord_utilities/sql.sp rename to scripting/discord_utilities/sql.sp index c58d07b..4da82c6 100644 --- a/include/discord_utilities/sql.sp +++ b/scripting/discord_utilities/sql.sp @@ -1,312 +1,312 @@ -public int SQLQuery_Connect(Handle owner, Handle hndl, char[] error, any data) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-Connect] Database failure: %s", error); - SetFailState("[Discord Utilities] Failed to connect to database"); - } - else - { - delete g_hDB; - - g_hDB = view_as(hndl); - - char Ident[4096]; - SQL_GetDriverIdent(SQL_ReadDriver(g_hDB), Ident, sizeof(Ident)); - g_bIsMySQl = StrEqual(Ident, "mysql", false) ? true : false; - - if(g_bIsMySQl) - { - g_hDB.Format(Ident, sizeof(Ident), "CREATE TABLE IF NOT EXISTS `%s` (`ID` bigint(20) NOT NULL AUTO_INCREMENT, `userid` varchar(20) COLLATE utf8_bin NOT NULL, `steamid` varchar(20) COLLATE utf8_bin NOT NULL, `member` int(20) NOT NULL, `last_accountuse` int(64) NOT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `steamid` (`steamid`) ) ENGINE = InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;", g_sTableName); - } - else - { - g_hDB.Format(Ident, sizeof(Ident), "CREATE TABLE IF NOT EXISTS %s (userid varchar(20) NOT NULL, steamid varchar(20) PRIMARY KEY NOT NULL, member int(20) NOT NULL, last_accountuse INTEGER)", g_sTableName); - SQL_SetCharset(g_hDB, "utf8"); - } - SQL_TQuery(g_hDB, SQLQuery_ConnectCallback, Ident); - PruneDatabase(); - } - - //For late load - for (int client = 1; client <= MaxClients; client++) - { - if (IsClientInGame(client) && !g_bChecked[client]) - { - OnClientPreAdminCheck(client); - } - } -} - -public int SQLQuery_ConnectCallback(Handle owner, Handle hndl, char[] error, any data) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-ConnectCallback] Database failure: %s", error); - } -} - -public void PruneDatabase() -{ - if(g_hDB == INVALID_HANDLE) - { - LogError("[DU-PruneDatabaseStart] Prune Database cannot connect to database."); - return; - } - if(g_cPruneDays.IntValue <= 0) - { - return; - } - - int maxlastaccuse = GetTime() - (g_cPruneDays.IntValue * 86400); - - char buffer[1024]; - - if(g_bIsMySQl) - g_hDB.Format(buffer, sizeof(buffer), "DELETE FROM `%s` WHERE `last_accountuse`<'%d' AND `last_accountuse`>'0' AND `member` = 0;", g_sTableName, maxlastaccuse); - else - g_hDB.Format(buffer, sizeof(buffer), "DELETE FROM %s WHERE last_accountuse<'%d' AND last_accountuse>'0' AND member = 0;", g_sTableName, maxlastaccuse); - - SQL_TQuery(g_hDB, SQLQuery_PruneDatabase, buffer); -} - -public int SQLQuery_PruneDatabase(Handle owner, Handle hndl, char [] error, any data) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-PruneDatabase] Query failure: %s", error); - } -} - -public int SQLQuery_GetUserData(Handle owner, Handle hndl, char [] error, any data) -{ - int client; - - /* Make sure the client didn't disconnect while the thread was running */ - - if((client = GetClientOfUserId(data)) == 0) - { - return; - } - - if(hndl == INVALID_HANDLE) - { - LogError("[DU-GetUserData] Query failure: %s", error); - return; - } - if(!SQL_GetRowCount(hndl)) - { - char szSteamId[32]; - char Query[256]; - GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); - if(g_bIsMySQl) - { - g_hDB.Format(Query, sizeof(Query), "INSERT INTO `%s`(ID, userid, steamid, member, last_accountuse) VALUES(NULL, '%s', '%s', '0', '0');", g_sTableName, NULL_STRING, szSteamId); - } - else - { - g_hDB.Format(Query, sizeof(Query), "INSERT INTO %s(userid, steamid, member, last_accountuse) VALUES('%s', '%s', '0', '0');", g_sTableName, NULL_STRING, szSteamId); - } - SQL_TQuery(g_hDB, SQLQuery_InsertNewPlayer, Query); - OnClientPreAdminCheck(client); - return; - } - while(SQL_FetchRow(hndl)) - { - SQL_FetchString(hndl, 0, g_sUserID[client], sizeof(g_sUserID)); - g_bMember[client] = !!SQL_FetchInt(hndl, 1); - } - /* - if(g_bMember[client]) - { - if(strlen(g_sRoleID) > 5) - { - ManagingRole(g_sUserID[client], g_sRoleID, k_EHTTPMethodPUT); - } - } - */ - char steamid[32]; - GetClientAuthId(client, AuthId_SteamID64, steamid, sizeof(steamid)); - int uniqueNum = GetRandomInt(100000, 999999); - Format(g_sUniqueCode[client], sizeof(g_sUniqueCode), "%i-%i-%s", g_cServerID.IntValue, uniqueNum, steamid); - g_bChecked[client] = true; -} - -public int SQLQuery_InsertNewPlayer(Handle owner, Handle hndl, char[] error, any data) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-InsertNewPlayer] Query failure: %s", error); - } -} - -/* -public int SQLQuery_AccountCheck(Handle owner, Handle hndl, char [] error, DataPack pack) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-AccountsCheck] Query failure: %s", error); - return; - } - char szUserIdDB[80]; - while (SQL_FetchRow(hndl)) - { - SQL_FetchString(hndl, 0, szUserIdDB, sizeof(szUserIdDB)); - if (strlen(szUserIdDB) > 15) - { - GetGuildMember(szUserIdDB); - } - } -} -*/ - -public int SQLQuery_CheckUserData(Handle owner, Handle hndl, char [] error, DataPack pack) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-CheckUserData] Query failure: %s", error); - return; - } - char szUserIdDB[20]; - while (SQL_FetchRow(hndl)) - { - SQL_FetchString(hndl, 0, szUserIdDB, sizeof(szUserIdDB)); - } - char szUserId[20], szUserName[32], szDiscriminator[6]; - pack.Reset(); - int client = pack.ReadCell(); - pack.ReadString(szUserId, sizeof(szUserId)); - pack.ReadString(szUserName, sizeof(szUserName)); - pack.ReadString(szDiscriminator, sizeof(szDiscriminator)); - delete pack; - - char szReply[512]; - if(!StrEqual(szUserIdDB, szUserId)) - { - if(strlen(g_sRoleID) > 5) - { - ManagingRole(szUserId, g_sRoleID, k_EHTTPMethodPUT); - } - - CPrintToChat(client, "%s %T", g_sServerPrefix, "DiscordVerified", client, szUserName, szDiscriminator); - g_bMember[client] = true; - - Format(g_sUserID[client], sizeof(g_sUserID), szUserId); - Format(szReply, sizeof(szReply), "%T", "DiscordLinked", LANG_SERVER, szUserId); - Bot.SendMessageToChannelID(g_sVerificationChannelID, szReply); - - char szSteamId[20]; - GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); - - char Query[512]; - if(g_bIsMySQl) - { - g_hDB.Format(Query, sizeof(Query), "UPDATE `%s` SET `userid` = '%s', member = 1 WHERE `steamid` = '%s';", g_sTableName, szUserId, szSteamId); - } - else - { - g_hDB.Format(Query, sizeof(Query), "UPDATE %s SET userid = '%s', member = 1 WHERE steamid = '%s'", g_sTableName, szUserId, szSteamId); - } - SQL_TQuery(g_hDB, SQLQuery_LinkedAccount, Query); - - Call_StartForward(g_hOnLinkedAccount); - Call_PushCell(client); - Call_PushString(szUserId); - Call_PushString(szUserName); - Call_PushString(szDiscriminator); - Call_Finish(); - } - else - { - Format(szReply, sizeof(szReply), "%T", "DiscordAlreadyLinked", LANG_SERVER, szUserId); - Bot.SendMessageToChannelID(g_sVerificationChannelID, szReply); - } -} - -public int SQLQuery_LinkedAccount(Handle owner, Handle hndl, char [] error, any data) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-LinkedAccount] Query failure: %s", error); - return; - } -} - - -public int SQLQuery_UnlinkAccount(Handle owner, Handle hndl, char [] error, int userid) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-CheckUserData] Query failure: %s", error); - return; - } - - int client = GetClientOfUserId(userid); - - if(!IsClientValid(client)) - { - return; - } - - char szUserIdDB[20]; - while (SQL_FetchRow(hndl)) - { - SQL_FetchString(hndl, 0, szUserIdDB, sizeof(szUserIdDB)); - } - - if(strlen(g_sRoleID) > 5) - { - ManagingRole(szUserIdDB, g_sRoleID, k_EHTTPMethodDELETE); - } - - char szSteamId[20]; - GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); - - char Query[512]; - if(g_bIsMySQl) - { - g_hDB.Format(Query, sizeof(Query), "UPDATE `%s` SET `userid` = '', member = 0 WHERE `steamid` = '%s';", g_sTableName, szSteamId); - } - else - { - g_hDB.Format(Query, sizeof(Query), "UPDATE %s SET userid = '', member = 0 WHERE steamid = '%s'", g_sTableName, szSteamId); - } - SQL_TQuery(g_hDB, SQLQuery_UnlinkAccount_Callback, Query, userid); -} - -public int SQLQuery_UnlinkAccount_Callback(Handle owner, Handle hndl, char [] error, int userid) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-UnlinkAccount] Query failure: %s", error); - return; - } - - int client = GetClientOfUserId(userid); - - if(!IsClientValid(client)) - { - return; - } - - g_bMember[client] = false; - - Call_StartForward(g_hOnAccountRevoked); - Call_PushCell(client); - Call_PushString(g_sUserID[client]); - Call_Finish(); - - g_sUserID[client][0] = '\0'; - - LogToFile("addons/sourcemod/logs/dsmembers_revoke.log", "Player %L unverified himself.", client); - CPrintToChat(client, "%s - You succesfully unlinked your account.", g_sServerPrefix); -} - -public int SQLQuery_UpdatePlayer(Handle owner, Handle hndl, char [] error, any data) -{ - if(hndl == INVALID_HANDLE) - { - LogError("[DU-UpdatePlayer] Query failure: %s", error); - return; - } -} +public int SQLQuery_Connect(Handle owner, Handle hndl, char[] error, any data) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-Connect] Database failure: %s", error); + SetFailState("[Discord Utilities] Failed to connect to database"); + } + else + { + delete g_hDB; + + g_hDB = view_as(hndl); + + char Ident[4096]; + SQL_GetDriverIdent(SQL_ReadDriver(g_hDB), Ident, sizeof(Ident)); + g_bIsMySQl = StrEqual(Ident, "mysql", false) ? true : false; + + if(g_bIsMySQl) + { + g_hDB.Format(Ident, sizeof(Ident), "CREATE TABLE IF NOT EXISTS `%s` (`ID` bigint(20) NOT NULL AUTO_INCREMENT, `userid` varchar(20) COLLATE utf8_bin NOT NULL, `steamid` varchar(20) COLLATE utf8_bin NOT NULL, `member` int(20) NOT NULL, `last_accountuse` int(64) NOT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `steamid` (`steamid`) ) ENGINE = InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;", g_sTableName); + } + else + { + g_hDB.Format(Ident, sizeof(Ident), "CREATE TABLE IF NOT EXISTS %s (userid varchar(20) NOT NULL, steamid varchar(20) PRIMARY KEY NOT NULL, member int(20) NOT NULL, last_accountuse INTEGER)", g_sTableName); + SQL_SetCharset(g_hDB, "utf8"); + } + SQL_TQuery(g_hDB, SQLQuery_ConnectCallback, Ident); + PruneDatabase(); + } + + //For late load + for (int client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client) && !g_bChecked[client]) + { + OnClientPreAdminCheck(client); + } + } +} + +public int SQLQuery_ConnectCallback(Handle owner, Handle hndl, char[] error, any data) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-ConnectCallback] Database failure: %s", error); + } +} + +public void PruneDatabase() +{ + if(g_hDB == INVALID_HANDLE) + { + LogError("[DU-PruneDatabaseStart] Prune Database cannot connect to database."); + return; + } + if(g_cPruneDays.IntValue <= 0) + { + return; + } + + int maxlastaccuse = GetTime() - (g_cPruneDays.IntValue * 86400); + + char buffer[1024]; + + if(g_bIsMySQl) + g_hDB.Format(buffer, sizeof(buffer), "DELETE FROM `%s` WHERE `last_accountuse`<'%d' AND `last_accountuse`>'0' AND `member` = 0;", g_sTableName, maxlastaccuse); + else + g_hDB.Format(buffer, sizeof(buffer), "DELETE FROM %s WHERE last_accountuse<'%d' AND last_accountuse>'0' AND member = 0;", g_sTableName, maxlastaccuse); + + SQL_TQuery(g_hDB, SQLQuery_PruneDatabase, buffer); +} + +public int SQLQuery_PruneDatabase(Handle owner, Handle hndl, char [] error, any data) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-PruneDatabase] Query failure: %s", error); + } +} + +public int SQLQuery_GetUserData(Handle owner, Handle hndl, char [] error, any data) +{ + int client; + + /* Make sure the client didn't disconnect while the thread was running */ + + if((client = GetClientOfUserId(data)) == 0) + { + return; + } + + if(hndl == INVALID_HANDLE) + { + LogError("[DU-GetUserData] Query failure: %s", error); + return; + } + if(!SQL_GetRowCount(hndl)) + { + char szSteamId[32]; + char Query[256]; + GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); + if(g_bIsMySQl) + { + g_hDB.Format(Query, sizeof(Query), "INSERT INTO `%s`(ID, userid, steamid, member, last_accountuse) VALUES(NULL, '%s', '%s', '0', '0');", g_sTableName, NULL_STRING, szSteamId); + } + else + { + g_hDB.Format(Query, sizeof(Query), "INSERT INTO %s(userid, steamid, member, last_accountuse) VALUES('%s', '%s', '0', '0');", g_sTableName, NULL_STRING, szSteamId); + } + SQL_TQuery(g_hDB, SQLQuery_InsertNewPlayer, Query); + OnClientPreAdminCheck(client); + return; + } + while(SQL_FetchRow(hndl)) + { + SQL_FetchString(hndl, 0, g_sUserID[client], sizeof(g_sUserID)); + g_bMember[client] = !!SQL_FetchInt(hndl, 1); + } + /* + if(g_bMember[client]) + { + if(strlen(g_sRoleID) > 5) + { + ManagingRole(g_sUserID[client], g_sRoleID, k_EHTTPMethodPUT); + } + } + */ + char steamid[32]; + GetClientAuthId(client, AuthId_SteamID64, steamid, sizeof(steamid)); + int uniqueNum = GetRandomInt(100000, 999999); + Format(g_sUniqueCode[client], sizeof(g_sUniqueCode), "%i-%i-%s", g_cServerID.IntValue, uniqueNum, steamid); + g_bChecked[client] = true; +} + +public int SQLQuery_InsertNewPlayer(Handle owner, Handle hndl, char[] error, any data) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-InsertNewPlayer] Query failure: %s", error); + } +} + +/* +public int SQLQuery_AccountCheck(Handle owner, Handle hndl, char [] error, DataPack pack) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-AccountsCheck] Query failure: %s", error); + return; + } + char szUserIdDB[80]; + while (SQL_FetchRow(hndl)) + { + SQL_FetchString(hndl, 0, szUserIdDB, sizeof(szUserIdDB)); + if (strlen(szUserIdDB) > 15) + { + GetGuildMember(szUserIdDB); + } + } +} +*/ + +public int SQLQuery_CheckUserData(Handle owner, Handle hndl, char [] error, DataPack pack) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-CheckUserData] Query failure: %s", error); + return; + } + char szUserIdDB[20]; + while (SQL_FetchRow(hndl)) + { + SQL_FetchString(hndl, 0, szUserIdDB, sizeof(szUserIdDB)); + } + char szUserId[20], szUserName[32], szDiscriminator[6]; + pack.Reset(); + int client = pack.ReadCell(); + pack.ReadString(szUserId, sizeof(szUserId)); + pack.ReadString(szUserName, sizeof(szUserName)); + pack.ReadString(szDiscriminator, sizeof(szDiscriminator)); + delete pack; + + char szReply[512]; + if(!StrEqual(szUserIdDB, szUserId)) + { + if(strlen(g_sRoleID) > 5) + { + ManagingRole(szUserId, g_sRoleID, k_EHTTPMethodPUT); + } + + CPrintToChat(client, "%s %T", g_sServerPrefix, "DiscordVerified", client, szUserName, szDiscriminator); + g_bMember[client] = true; + + Format(g_sUserID[client], sizeof(g_sUserID), szUserId); + Format(szReply, sizeof(szReply), "%T", "DiscordLinked", LANG_SERVER, szUserId); + Bot.SendMessageToChannelID(g_sVerificationChannelID, szReply); + + char szSteamId[20]; + GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); + + char Query[512]; + if(g_bIsMySQl) + { + g_hDB.Format(Query, sizeof(Query), "UPDATE `%s` SET `userid` = '%s', member = 1 WHERE `steamid` = '%s';", g_sTableName, szUserId, szSteamId); + } + else + { + g_hDB.Format(Query, sizeof(Query), "UPDATE %s SET userid = '%s', member = 1 WHERE steamid = '%s'", g_sTableName, szUserId, szSteamId); + } + SQL_TQuery(g_hDB, SQLQuery_LinkedAccount, Query); + + Call_StartForward(g_hOnLinkedAccount); + Call_PushCell(client); + Call_PushString(szUserId); + Call_PushString(szUserName); + Call_PushString(szDiscriminator); + Call_Finish(); + } + else + { + Format(szReply, sizeof(szReply), "%T", "DiscordAlreadyLinked", LANG_SERVER, szUserId); + Bot.SendMessageToChannelID(g_sVerificationChannelID, szReply); + } +} + +public int SQLQuery_LinkedAccount(Handle owner, Handle hndl, char [] error, any data) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-LinkedAccount] Query failure: %s", error); + return; + } +} + + +public int SQLQuery_UnlinkAccount(Handle owner, Handle hndl, char [] error, int userid) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-CheckUserData] Query failure: %s", error); + return; + } + + int client = GetClientOfUserId(userid); + + if(!IsClientValid(client)) + { + return; + } + + char szUserIdDB[20]; + while (SQL_FetchRow(hndl)) + { + SQL_FetchString(hndl, 0, szUserIdDB, sizeof(szUserIdDB)); + } + + if(strlen(g_sRoleID) > 5) + { + ManagingRole(szUserIdDB, g_sRoleID, k_EHTTPMethodDELETE); + } + + char szSteamId[20]; + GetClientAuthId(client, AuthId_Steam2, szSteamId, sizeof(szSteamId)); + + char Query[512]; + if(g_bIsMySQl) + { + g_hDB.Format(Query, sizeof(Query), "UPDATE `%s` SET `userid` = '', member = 0 WHERE `steamid` = '%s';", g_sTableName, szSteamId); + } + else + { + g_hDB.Format(Query, sizeof(Query), "UPDATE %s SET userid = '', member = 0 WHERE steamid = '%s'", g_sTableName, szSteamId); + } + SQL_TQuery(g_hDB, SQLQuery_UnlinkAccount_Callback, Query, userid); +} + +public int SQLQuery_UnlinkAccount_Callback(Handle owner, Handle hndl, char [] error, int userid) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-UnlinkAccount] Query failure: %s", error); + return; + } + + int client = GetClientOfUserId(userid); + + if(!IsClientValid(client)) + { + return; + } + + g_bMember[client] = false; + + Call_StartForward(g_hOnAccountRevoked); + Call_PushCell(client); + Call_PushString(g_sUserID[client]); + Call_Finish(); + + g_sUserID[client][0] = '\0'; + + if(g_cLogRevokeEnabled.IntValue == 1) LogToFile("addons/sourcemod/logs/dsmembers_revoke.log", "Player %L unverified himself.", client); + CPrintToChat(client, "%s - %T", g_sServerPrefix, "SuccessfullyUnlink", client); +} + +public int SQLQuery_UpdatePlayer(Handle owner, Handle hndl, char [] error, any data) +{ + if(hndl == INVALID_HANDLE) + { + LogError("[DU-UpdatePlayer] Query failure: %s", error); + return; + } +} diff --git a/scripting/du_bugreport.sp b/scripting/du_bugreport.sp new file mode 100644 index 0000000..0264868 --- /dev/null +++ b/scripting/du_bugreport.sp @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include + +#include + +#pragma dynamic 500000 +#pragma newdecls required +#pragma semicolon 1 + +ConVar g_cBugReport_Webhook, g_cBugReport_BotName, g_cBugReport_BotAvatar, g_cBugReport_Color, g_cBugReport_Content, g_cBugReport_FooterIcon, g_cDNSServerIP; + +char g_sBugReport_Webhook[128], g_sBugReport_BotName[32], g_sBugReport_BotAvatar[128], g_sBugReport_Color[8], g_sBugReport_Content[256], g_sBugReport_FooterIcon[128], g_sServerIP[128]; + +char g_sServerName[128]; + +bool g_bBugReport; + +#define REPORTER_CONSOLE 1679124 +#define DEFAULT_COLOR "#00FF00" +#define USE_AutoExecConfig + +public Plugin myinfo = +{ + name = "Discord Utilities - Bugreport module", + author = "AiDN™ & Cruze03", + description = "Bugreport module for the Discord Utilities, code from Cruze03", + version = "1.0", + url = "https://steamcommunity.com/id/originalaidn & https://github.com/Cruze03/discord-utilities" +}; + +public void OnPluginStart() +{ + #if defined USE_AutoExecConfig + AutoExecConfig_SetFile("Discord-Utilities"); + AutoExecConfig_SetCreateFile(true); + + g_cBugReport_Webhook = AutoExecConfig_CreateConVar("sm_du_bugreport_webhook", "", "Webhook for bugreport reports. Blank to disable.", FCVAR_PROTECTED); + g_cBugReport_BotName = AutoExecConfig_CreateConVar("sm_du_bugreport_botname", "Discord Utilities", "BotName for bugreport. Blank to use webhook name."); + g_cBugReport_BotAvatar = AutoExecConfig_CreateConVar("sm_du_bugreport_avatar", "", "Avatar link for bugreport bot. Blank to use webhook avatar."); + g_cBugReport_Color = AutoExecConfig_CreateConVar("sm_du_bugreport_color", "#ff9911", "Color for embed message of bugreport."); + g_cBugReport_Content = AutoExecConfig_CreateConVar("sm_du_bugreport_content", "", "Content for embed message of bugreport. Blank to disable."); + g_cBugReport_FooterIcon = AutoExecConfig_CreateConVar("sm_du_bugreport_footericon", "", "Link to footer icon for bugreport. Blank for no footer icon."); + + g_cDNSServerIP = AutoExecConfig_CreateConVar("sm_du_dns_ip", "", "DNS IP address of your game server. Blank to use real IP."); + + #else + g_cBugReport_Webhook = CreateConVar("sm_du_bugreport_webhook", "", "Webhook for bugreport reports. Blank to disable.", FCVAR_PROTECTED); + g_cBugReport_BotName = CreateConVar("sm_du_bugreport_botname", "Discord Utilities", "BotName for bugreport. Blank to use webhook name."); + g_cBugReport_BotAvatar = CreateConVar("sm_du_bugreport_avatar", "", "Avatar link for bugreport bot. Blank to use webhook avatar."); + g_cBugReport_Color = CreateConVar("sm_du_bugreport_color", "#ff9911", "Color for embed message of bugreport."); + g_cBugReport_Content = CreateConVar("sm_du_bugreport_content", "", "Content for embed message of bugreport. Blank to disable."); + g_cBugReport_FooterIcon = CreateConVar("sm_du_bugreport_footericon", "", "Link to footer icon for bugreport. Blank for no footer icon."); + + g_cDNSServerIP = CreateConVar("sm_du_dns_ip", "", "DNS IP address of your game server. Blank to use real IP."); + + AutoExecConfig(true, "Discord-Utilities"); + #endif + + HookConVarChange(g_cBugReport_Webhook, OnSettingsChanged); + HookConVarChange(g_cBugReport_BotName, OnSettingsChanged); + HookConVarChange(g_cBugReport_BotAvatar, OnSettingsChanged); + HookConVarChange(g_cBugReport_Color, OnSettingsChanged); + HookConVarChange(g_cBugReport_Content, OnSettingsChanged); + HookConVarChange(g_cBugReport_FooterIcon, OnSettingsChanged); + + LoadTranslations("Discord-Utilities.phrases"); +} + +public void OnLibraryAdded(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "bugreport")) g_bBugReport = true; +} + +public void OnLibraryRemoved(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "bugreport")) g_bBugReport = false; +} +public void OnAllPluginsLoaded() +{ + if(!LibraryExists("discord-api")) + { + SetFailState("[Discord-Utilities] This plugin is fully dependant on \"Discord-API\" by Deathknife. (https://github.com/Deathknife/sourcemod-discord)"); + } + + g_bBugReport = LibraryExists("bugreport"); +} + +public void OnConfigsExecuted() +{ + LoadCvars(); + FindConVar("hostname").GetString(g_sServerName, sizeof(g_sServerName)); +} + +void LoadCvars() +{ + g_cBugReport_Webhook.GetString(g_sBugReport_Webhook, sizeof(g_sBugReport_Webhook)); + g_cBugReport_BotName.GetString(g_sBugReport_BotName, sizeof(g_sBugReport_BotName)); + g_cBugReport_BotAvatar.GetString(g_sBugReport_BotAvatar, sizeof(g_sBugReport_BotAvatar)); + g_cBugReport_Color.GetString(g_sBugReport_Color, sizeof(g_sBugReport_Color)); + g_cBugReport_Content.GetString(g_sBugReport_Content, sizeof(g_sBugReport_Content)); + g_cBugReport_FooterIcon.GetString(g_sBugReport_FooterIcon, sizeof(g_sBugReport_FooterIcon)); + + g_cDNSServerIP.GetString(g_sServerIP, sizeof(g_sServerIP)); + ServerIP(g_sServerIP, sizeof(g_sServerIP)); +} + +public int OnSettingsChanged(ConVar convar, const char[] oldVal, const char[] newVal) +{ + if(StrEqual(oldVal, newVal, true)) + { + return; + } + if(convar == g_cBugReport_Webhook) + { + strcopy(g_sBugReport_Webhook, sizeof(g_sBugReport_Webhook), newVal); + } + else if(convar == g_cBugReport_BotName) + { + strcopy(g_sBugReport_BotName, sizeof(g_sBugReport_BotName), newVal); + } + else if(convar == g_cBugReport_BotAvatar) + { + strcopy(g_sBugReport_BotAvatar, sizeof(g_sBugReport_BotAvatar), newVal); + } + else if(convar == g_cBugReport_Color) + { + strcopy(g_sBugReport_Color, sizeof(g_sBugReport_Color), newVal); + } + else if(convar == g_cBugReport_Content) + { + strcopy(g_sBugReport_Content, sizeof(g_sBugReport_Content), newVal); + } + else if(convar == g_cBugReport_FooterIcon) + { + strcopy(g_sBugReport_FooterIcon, sizeof(g_sBugReport_FooterIcon), newVal); + } + else if(convar == g_cDNSServerIP) + { + strcopy(g_sServerIP, sizeof(g_sServerIP), newVal); + ServerIP(g_sServerIP, sizeof(g_sServerIP)); + } +} + +public void BugReport_OnReportPost(int client, const char[] map, const char[] reason, ArrayList array) +{ + if(StrEqual(g_sBugReport_Webhook, "")) + { + return; + } + + if(!g_bBugReport) + { + return; + } + + char sReason[(REASON_MAX_LENGTH + 1) * 2]; + strcopy(sReason, sizeof(sReason), reason); + int index = array.FindString(sReason); + + if(index != -1) + { + LogError("Duplicate Reason. Skipping."); + return; + } + + Discord_EscapeString(sReason, sizeof(sReason)); + + char clientAuth[21]; + char clientAuth2[21]; + char clientName[(MAX_NAME_LENGTH + 1) * 2]; + + if (client == REPORTER_CONSOLE) + { + Format(clientName, sizeof(clientName), "%T", "SERVER", LANG_SERVER); + Format(clientAuth, sizeof(clientAuth), "%T", "CONSOLE", LANG_SERVER); + } + else + { + GetClientAuthId(client, AuthId_SteamID64, clientAuth, sizeof(clientAuth)); + GetClientAuthId(client, AuthId_Steam2, clientAuth2, sizeof(clientAuth2)); + GetClientName(client, clientName, sizeof(clientName)); + Discord_EscapeString(clientName, sizeof(clientName)); + } + + DiscordWebHook hook = new DiscordWebHook( g_sBugReport_Webhook ); + hook.SlackMode = true; + if(g_sBugReport_BotName[0]) + { + hook.SetUsername( g_sBugReport_BotName ); + } + + if(g_sBugReport_BotAvatar[0]) + { + hook.SetAvatar( g_sBugReport_BotAvatar ); + } + + MessageEmbed embed = new MessageEmbed(); + + if(StrContains(g_sBugReport_Color, "#") != -1) + { + embed.SetColor(g_sBugReport_Color); + } + else + { + LogError("[Discord-Utilities] BugReport is using default color as you've set invalid BugReport color."); + embed.SetColor(DEFAULT_COLOR); + } + + char buffer[512], trans[64]; + Format( trans, sizeof( trans ), "%T", "BugReportTitle", LANG_SERVER); + embed.SetTitle( buffer ); + + if (client != REPORTER_CONSOLE) + { + Format( buffer, sizeof( buffer ), "[%s](http://www.steamcommunity.com/profiles/%s) (%s)", clientName, clientAuth, clientAuth2 ); + } + else + { + Format( buffer, sizeof( buffer ), "%s", clientName ); + } + Format( trans, sizeof( trans ), "%T", "ReporterField", LANG_SERVER); + embed.AddField( trans, buffer, true ); + + Format( trans, sizeof( trans ), "%T", "MapField", LANG_SERVER); + embed.AddField( trans, map, true ); + + Format( trans, sizeof( trans ), "%T", "ReasonField", LANG_SERVER); + embed.AddField( trans, sReason, false ); + + Format( trans, sizeof( trans ), "%T", "DirectConnectField", LANG_SERVER); + Format( buffer, sizeof( buffer ), "steam://connect/%s", g_sServerIP ); + embed.AddField( trans, buffer, false ); + + if(g_sBugReport_FooterIcon[0]) + { + embed.SetFooterIcon( g_sBugReport_FooterIcon ); + } + Format( buffer, sizeof( buffer ), "%T", "ServerField", LANG_SERVER, g_sServerName); + embed.SetFooter( buffer ); + + if(g_sBugReport_Content[0]) + { + hook.SetContent(g_sBugReport_Content); + } + + hook.Embed( embed ); + hook.Send(); + delete hook; +} + +stock void Discord_EscapeString(char[] string, int maxlen, bool name = false) +{ + if(name) + { + ReplaceString(string, maxlen, "everyone", "everyone"); + ReplaceString(string, maxlen, "here", "here"); + ReplaceString(string, maxlen, "discordtag", "discordtag"); + } + ReplaceString(string, maxlen, "#", "#"); + ReplaceString(string, maxlen, "@", "@"); + //ReplaceString(string, maxlen, ":", ""); + ReplaceString(string, maxlen, "_", "ˍ"); + ReplaceString(string, maxlen, "'", "'"); + ReplaceString(string, maxlen, "`", "'"); + ReplaceString(string, maxlen, "~", "∽"); + ReplaceString(string, maxlen, "\"", """); +} + +void ServerIP(char[] sIP, int size) +{ + if(sIP[0]) + { + return; + } + int ip[4]; + int iServerPort = FindConVar("hostport").IntValue; + SteamWorks_GetPublicIP(ip); + if(SteamWorks_GetPublicIP(ip)) + { + Format(sIP, size, "%d.%d.%d.%d:%d", ip[0], ip[1], ip[2], ip[3], iServerPort); + } + else + { + int iServerIP = FindConVar("hostip").IntValue; + Format(sIP, size, "%d.%d.%d.%d:%d", iServerIP >> 24 & 0x000000FF, iServerIP >> 16 & 0x000000FF, iServerIP >> 8 & 0x000000FF, iServerIP & 0x000000FF, iServerPort); + } +} \ No newline at end of file diff --git a/scripting/du_calladmin.sp b/scripting/du_calladmin.sp new file mode 100644 index 0000000..dc6778c --- /dev/null +++ b/scripting/du_calladmin.sp @@ -0,0 +1,396 @@ +#include +#include +#include +#include +#include + +#include +#include + +#pragma dynamic 500000 +#pragma newdecls required +#pragma semicolon 1 + +#define DEFAULT_COLOR "#00FF00" +#define USE_AutoExecConfig + +ConVar g_cCallAdmin_Webhook, g_cCallAdmin_BotName, g_cCallAdmin_BotAvatar, g_cCallAdmin_Color, g_cCallAdmin_Content, g_cCallAdmin_FooterIcon, g_cDNSServerIP; + +char g_sCallAdmin_Webhook[128], g_sCallAdmin_BotName[32], g_sCallAdmin_BotAvatar[128], g_sCallAdmin_Color[8], g_sCallAdmin_Content[256], g_sCallAdmin_FooterIcon[128], g_sServerIP[128]; + +int g_iLastReportID; + +char g_sServerName[128]; + +ArrayList g_aCallAdmin_ReportedList; + +bool g_bCallAdmin; + +public Plugin myinfo = +{ + name = "Discord Utilities - Calladmin module", + author = "AiDN™ & Cruze03", + description = "Calladmin module for the Discord Utilities, code from Cruze03", + version = "1.0", + url = "https://steamcommunity.com/id/originalaidn & https://github.com/Cruze03/discord-utilities" +}; + +public void OnLibraryAdded(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "calladmin")) g_bCallAdmin = true; +} + +public void OnLibraryRemoved(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "calladmin")) g_bCallAdmin = false; +} +public void OnAllPluginsLoaded() +{ + if(!LibraryExists("discord-api")) + { + SetFailState("[Discord-Utilities] This plugin is fully dependant on \"Discord-API\" by Deathknife. (https://github.com/Deathknife/sourcemod-discord)"); + } + + g_bCallAdmin = LibraryExists("calladmin"); +} + +public void OnConfigsExecuted() +{ + LoadCvars(); + if(g_bCallAdmin) + { + CallAdmin_GetHostName(g_sServerName, sizeof(g_sServerName)); + g_aCallAdmin_ReportedList = new ArrayList(64); + } + else + { + FindConVar("hostname").GetString(g_sServerName, sizeof(g_sServerName)); + } +} + +public int OnSettingsChanged(ConVar convar, const char[] oldVal, const char[] newVal) +{ + if(StrEqual(oldVal, newVal, true)) + { + return; + } + if(convar == g_cCallAdmin_Webhook) + { + strcopy(g_sCallAdmin_Webhook, sizeof(g_sCallAdmin_Webhook), newVal); + } + else if(convar == g_cCallAdmin_BotName) + { + strcopy(g_sCallAdmin_BotName, sizeof(g_sCallAdmin_BotName), newVal); + } + else if(convar == g_cCallAdmin_BotAvatar) + { + strcopy(g_sCallAdmin_BotAvatar, sizeof(g_sCallAdmin_BotAvatar), newVal); + } + else if(convar == g_cCallAdmin_Color) + { + strcopy(g_sCallAdmin_Color, sizeof(g_sCallAdmin_Color), newVal); + } + else if(convar == g_cCallAdmin_Content) + { + strcopy(g_sCallAdmin_Content, sizeof(g_sCallAdmin_Content), newVal); + } + else if(convar == g_cCallAdmin_FooterIcon) + { + strcopy(g_sCallAdmin_FooterIcon, sizeof(g_sCallAdmin_FooterIcon), newVal); + } + else if(convar == g_cDNSServerIP) + { + strcopy(g_sServerIP, sizeof(g_sServerIP), newVal); + ServerIP(g_sServerIP, sizeof(g_sServerIP)); + } +} + +public void CallAdmin_OnServerDataChanged(ConVar convar, ServerData type, const char[] oldVal, const char[] newVal) +{ + if (type == ServerData_HostName) + CallAdmin_GetHostName(g_sServerName, sizeof(g_sServerName)); +} + +public void OnPluginStart() +{ + #if defined USE_AutoExecConfig + AutoExecConfig_SetFile("Discord-Utilities"); + AutoExecConfig_SetCreateFile(true); + + g_cCallAdmin_Webhook = AutoExecConfig_CreateConVar("sm_du_calladmin_webhook", "", "Webhook for calladmin reports and report handled print. Blank to disable.", FCVAR_PROTECTED); + g_cCallAdmin_BotName = AutoExecConfig_CreateConVar("sm_du_calladmin_botname", "Discord Utilities", "BotName for calladmin. Blank to use webhook name."); + g_cCallAdmin_BotAvatar = AutoExecConfig_CreateConVar("sm_du_calladmin_avatar", "", "Avatar link for calladmin bot. Blank to use webhook avatar."); + g_cCallAdmin_Color = AutoExecConfig_CreateConVar("sm_du_calladmin_color", "#ff9911", "Color for embed message of calladmin."); + g_cCallAdmin_Content = AutoExecConfig_CreateConVar("sm_du_calladmin_content", "When in-game type !calladmin_handle in chat to handle this report.", "Content for embed message of calladmin. Blank to disable."); + g_cCallAdmin_FooterIcon = AutoExecConfig_CreateConVar("sm_du_calladmin_footericon", "", "Link to footer icon for calladmin. Blank for no footer icon."); + + g_cDNSServerIP = AutoExecConfig_CreateConVar("sm_du_dns_ip", "", "DNS IP address of your game server. Blank to use real IP."); + + #else + g_cCallAdmin_Webhook = CreateConVar("sm_du_calladmin_webhook", "", "Webhook for calladmin reports and report handled print. Blank to disable.", FCVAR_PROTECTED); + g_cCallAdmin_BotName = CreateConVar("sm_du_calladmin_botname", "Discord Utilities", "BotName for calladmin. Blank to use webhook name."); + g_cCallAdmin_BotAvatar = CreateConVar("sm_du_calladmin_avatar", "", "Avatar link for calladmin bot. Blank to use webhook avatar."); + g_cCallAdmin_Color = CreateConVar("sm_du_calladmin_color", "#ff9911", "Color for embed message of calladmin."); + g_cCallAdmin_Content = CreateConVar("sm_du_calladmin_content", "When in-game type !calladmin_handle in chat to handle this report.", "Content for embed message of calladmin. Blank to disable."); + g_cCallAdmin_FooterIcon = CreateConVar("sm_du_calladmin_footericon", "", "Link to footer icon for calladmin. Blank for no footer icon."); + + g_cDNSServerIP = CreateConVar("sm_du_dns_ip", "", "DNS IP address of your game server. Blank to use real IP."); + + AutoExecConfig(true, "Discord-Utilities"); + #endif + + HookConVarChange(g_cCallAdmin_Webhook, OnSettingsChanged); + HookConVarChange(g_cCallAdmin_BotName, OnSettingsChanged); + HookConVarChange(g_cCallAdmin_BotAvatar, OnSettingsChanged); + HookConVarChange(g_cCallAdmin_Color, OnSettingsChanged); + HookConVarChange(g_cCallAdmin_Content, OnSettingsChanged); + HookConVarChange(g_cCallAdmin_FooterIcon, OnSettingsChanged); + + LoadTranslations("Discord-Utilities.phrases"); +} + +void LoadCvars() + { + g_cCallAdmin_Webhook.GetString(g_sCallAdmin_Webhook, sizeof(g_sCallAdmin_Webhook)); + g_cCallAdmin_BotName.GetString(g_sCallAdmin_BotName, sizeof(g_sCallAdmin_BotName)); + g_cCallAdmin_BotAvatar.GetString(g_sCallAdmin_BotAvatar, sizeof(g_sCallAdmin_BotAvatar)); + g_cCallAdmin_Color.GetString(g_sCallAdmin_Color, sizeof(g_sCallAdmin_Color)); + g_cCallAdmin_Content.GetString(g_sCallAdmin_Content, sizeof(g_sCallAdmin_Content)); + g_cCallAdmin_FooterIcon.GetString(g_sCallAdmin_FooterIcon, sizeof(g_sCallAdmin_FooterIcon)); + + g_cDNSServerIP.GetString(g_sServerIP, sizeof(g_sServerIP)); + ServerIP(g_sServerIP, sizeof(g_sServerIP)); + } + +public void CallAdmin_OnReportHandled(int client, int id) +{ + if(StrEqual(g_sCallAdmin_Webhook, "")) + { + return; + } + if(!g_bCallAdmin) + { + return; + } + if (id != g_iLastReportID) + { + return; + } + + char clientName[MAX_NAME_LENGTH], clientAuth[32], clientAuth2[32]; + GetClientName(client, clientName, sizeof(clientName)); + GetClientAuthId(client, AuthId_SteamID64, clientAuth, sizeof(clientAuth)); + GetClientAuthId(client, AuthId_Steam2, clientAuth2, sizeof(clientAuth2)); + Discord_EscapeString(clientName, sizeof(clientName)); + + DiscordWebHook hook = new DiscordWebHook( g_sCallAdmin_Webhook ); + hook.SlackMode = true; + if(g_sCallAdmin_BotName[0]) + { + hook.SetUsername( g_sCallAdmin_BotName ); + } + if(g_sCallAdmin_BotAvatar[0]) + { + hook.SetAvatar( g_sCallAdmin_BotAvatar ); + } + + MessageEmbed embed = new MessageEmbed(); + + if(StrContains(g_sCallAdmin_Color, "#") != -1) + { + embed.SetColor(g_sCallAdmin_Color); + } + else + { + LogError("[Discord-Utilities] CallAdmin ReportHandled is using default color as you've set invalid CallAdmin ReportHandled color."); + embed.SetColor(DEFAULT_COLOR); + } + + char buffer[512], trans[64]; + Format( trans, sizeof( trans ), "%T", "CallAdminReportHandledTitle", LANG_SERVER); + embed.SetTitle( trans ); + + Format( trans, sizeof( trans ), "%T", "CallAdminReportHandlerName", LANG_SERVER); + Format( buffer, sizeof( buffer ), "[%s](http://www.steamcommunity.com/profiles/%s)(%s)", clientName, clientAuth, clientAuth2 ); + embed.AddField( trans, buffer, true ); + + Format( trans, sizeof( trans ), "%T", "CallAdminReportIDField", LANG_SERVER); + Format(buffer, sizeof(buffer), "%d", g_iLastReportID); + embed.AddField( trans, buffer, true ); + + Format( trans, sizeof( trans ), "%T", "DirectConnectField", LANG_SERVER); + Format( buffer, sizeof( buffer ), "steam://connect/%s", g_sServerIP ); + embed.AddField( trans, buffer, false ); + + if(g_sCallAdmin_FooterIcon[0]) + { + embed.SetFooterIcon( g_sCallAdmin_FooterIcon ); + } + Format( buffer, sizeof( buffer ), "%T", "ServerField", LANG_SERVER, g_sServerName ); + embed.SetFooter( buffer ); + + hook.Embed( embed ); + hook.Send(); + delete hook; +} + + +public void CallAdmin_OnReportPost(int client, int target, const char[] reason) +{ + if(StrEqual(g_sCallAdmin_Webhook, "")) + { + return; + } + if(!g_bCallAdmin) + { + return; + } + char sReason[(REASON_MAX_LENGTH + 1) * 2]; + strcopy(sReason, sizeof(sReason), reason); + Discord_EscapeString(sReason, sizeof(sReason)); + + char clientAuth[21]; + char clientAuth2[21]; + char clientName[(MAX_NAME_LENGTH + 1) * 2]; + + if (client == REPORTER_CONSOLE) + { + Format(clientName, sizeof(clientName), "%T", "SERVER", LANG_SERVER); + Format(clientAuth, sizeof(clientAuth), "%T", "CONSOLE", LANG_SERVER); + } + else + { + GetClientAuthId(client, AuthId_SteamID64, clientAuth, sizeof(clientAuth)); + GetClientAuthId(client, AuthId_Steam2, clientAuth2, sizeof(clientAuth2)); + GetClientName(client, clientName, sizeof(clientName)); + Discord_EscapeString(clientName, sizeof(clientName)); + } + + char targetAuth[21]; + char targetAuth2[21]; + char targetName[(MAX_NAME_LENGTH + 1) * 2]; + + GetClientAuthId(target, AuthId_SteamID64, targetAuth, sizeof(targetAuth)); + GetClientAuthId(target, AuthId_Steam2, targetAuth2, sizeof(targetAuth2)); + GetClientName(target, targetName, sizeof(targetName)); + Discord_EscapeString(targetName, sizeof(targetName)); + + int index = g_aCallAdmin_ReportedList.FindString(targetAuth); + + if(index != -1) + { + return; + } + + g_aCallAdmin_ReportedList.PushString(targetAuth); + + DiscordWebHook hook = new DiscordWebHook( g_sCallAdmin_Webhook ); + hook.SlackMode = true; + if(g_sCallAdmin_BotName[0]) + { + hook.SetUsername( g_sCallAdmin_BotName ); + } + if(g_sCallAdmin_BotAvatar[0]) + { + hook.SetAvatar( g_sCallAdmin_BotAvatar ); + } + + MessageEmbed embed = new MessageEmbed(); + + if(StrContains(g_sCallAdmin_Color, "#") != -1) + { + embed.SetColor(g_sCallAdmin_Color); + } + else + { + LogError("[Discord-Utilities] CallAdmin ReportPost is using default color as you've set invalid CallAdmin ReportPost color."); + embed.SetColor(DEFAULT_COLOR); + } + + g_iLastReportID = CallAdmin_GetReportID(); + + char buffer[512], trans[64]; + Format( trans, sizeof( trans ), "%T", "CallAdminReportTitle", LANG_SERVER); + embed.SetTitle( buffer ); + + if (client != REPORTER_CONSOLE) + { + Format( buffer, sizeof( buffer ), "[%s](http://www.steamcommunity.com/profiles/%s) (%s)", clientName, clientAuth, clientAuth2 ); + } + else + { + Format( buffer, sizeof( buffer ), "%s", clientName ); + } + Format(trans, sizeof(trans), "%T", "ReporterField", LANG_SERVER); + embed.AddField( trans, buffer, true ); + + Format(trans, sizeof(trans), "%T", "TargetField", LANG_SERVER); + Format( buffer, sizeof( buffer ), "[%s](http://www.steamcommunity.com/profiles/%s) (%s)", targetName, targetAuth, targetAuth2 ); + embed.AddField( trans, buffer, true ); + + Format(trans, sizeof(trans), "%T", "ReasonField", LANG_SERVER); + embed.AddField( trans, sReason, true ); + + Format(trans, sizeof(trans), "%T", "CallAdminReportIDField", LANG_SERVER); + Format(buffer, sizeof(buffer), "%d", g_iLastReportID); + + embed.AddField( trans, buffer, false ); + + Format(trans, sizeof(trans), "%T", "DirectConnectField", LANG_SERVER); + Format( buffer, sizeof( buffer ), "steam://connect/%s", g_sServerIP ); + embed.AddField( trans, buffer, true ); + + if(g_sCallAdmin_FooterIcon[0]) + { + embed.SetFooterIcon( g_sCallAdmin_FooterIcon ); + } + Format( buffer, sizeof( buffer ), "%T", "ServerField", LANG_SERVER, g_sServerName ); + embed.SetFooter( buffer ); + + if(g_sCallAdmin_Content[0]) + { + hook.SetContent( g_sCallAdmin_Content ); + } + + hook.Embed( embed ); + hook.Send(); + delete hook; +} + +stock void Discord_EscapeString(char[] string, int maxlen, bool name = false) +{ + if(name) + { + ReplaceString(string, maxlen, "everyone", "everyone"); + ReplaceString(string, maxlen, "here", "here"); + ReplaceString(string, maxlen, "discordtag", "discordtag"); + } + ReplaceString(string, maxlen, "#", "#"); + ReplaceString(string, maxlen, "@", "@"); + //ReplaceString(string, maxlen, ":", ""); + ReplaceString(string, maxlen, "_", "ˍ"); + ReplaceString(string, maxlen, "'", "'"); + ReplaceString(string, maxlen, "`", "'"); + ReplaceString(string, maxlen, "~", "∽"); + ReplaceString(string, maxlen, "\"", """); +} + +void ServerIP(char[] sIP, int size) +{ + if(sIP[0]) + { + return; + } + int ip[4]; + int iServerPort = FindConVar("hostport").IntValue; + SteamWorks_GetPublicIP(ip); + if(SteamWorks_GetPublicIP(ip)) + { + Format(sIP, size, "%d.%d.%d.%d:%d", ip[0], ip[1], ip[2], ip[3], iServerPort); + } + else + { + int iServerIP = FindConVar("hostip").IntValue; + Format(sIP, size, "%d.%d.%d.%d:%d", iServerIP >> 24 & 0x000000FF, iServerIP >> 16 & 0x000000FF, iServerIP >> 8 & 0x000000FF, iServerIP & 0x000000FF, iServerPort); + } +} diff --git a/scripting/du_chatrelay.sp b/scripting/du_chatrelay.sp new file mode 100644 index 0000000..6efe134 --- /dev/null +++ b/scripting/du_chatrelay.sp @@ -0,0 +1,746 @@ +#include +#include +#include +#include +#include + +#include +#include + +#pragma dynamic 500000 +#pragma newdecls required +#pragma semicolon 1 + +#define MAX_BLOCKLIST_LIMIT 20 +#define USE_AutoExecConfig + +ConVar g_cChatRelay_Webhook, g_cChatRelay_BlockList, g_cAdminChatRelay_Mode, g_cAdminChatRelay_Webhook, g_cAdminChatRelay_BlockList, g_cAdminLog_Webhook, g_cAdminLog_BlockList; +ConVar g_cChatRelayChannelID, g_cAdminChatRelayChannelID; +ConVar g_cBotToken, g_cTimeStamps, g_cDiscordPrefix, g_cAPIKey; + +char g_sAvatarURL[MAXPLAYERS+1][128]; + +char g_sChatRelay_Webhook[128], g_sChatRelay_BlockList[MAX_BLOCKLIST_LIMIT][64], g_sAdminChatRelay_Mode[16], g_sAdminChatRelay_Webhook[128], g_sAdminChatRelay_BlockList[MAX_BLOCKLIST_LIMIT][64], g_sAdminLog_Webhook[128], g_sAdminLog_BlockList[MAX_BLOCKLIST_LIMIT][64]; +char g_sVerificationChannelID[20], g_sChatRelayChannelID[20], g_sAdminChatRelayChannelID[20]; +char g_sBotToken[60], g_sAPIKey[64]; + +char g_sDiscordPrefix[128]; + +char g_sServerName[128]; + +bool g_bBaseComm, g_bShavit; + +DiscordBot Bot; + +char gS_GlobalColorNames[][] = +{ + "{default}", + "{team}", + "{green}" +}; + +char gS_GlobalColors[][] = +{ + "\x01", + "\x03", + "\x04" +}; + +char gS_CSGOColorNames[][] = +{ + "{blue}", + "{bluegrey}", + "{darkblue}", + "{darkred}", + "{gold}", + "{grey}", + "{grey2}", + "{lightgreen}", + "{lightred}", + "{lime}", + "{orchid}", + "{yellow}", + "{palered}" +}; + +char gS_CSGOColors[][] = +{ + "\x0B", + "\x0A", + "\x0C", + "\x02", + "\x10", + "\x08", + "\x0D", + "\x05", + "\x0F", + "\x06", + "\x0E", + "\x09", + "\x07" +}; + +public Plugin myinfo = +{ + name = "Discord Utilities - Chatrelay module", + author = "AiDN™ & Cruze03", + description = "Chatrelay module for the Discord Utilities, code from Cruze03", + version = "1.1", + url = "https://steamcommunity.com/id/originalaidn & https://github.com/Cruze03/discord-utilities" +}; + +public void OnPluginStart() +{ + AddCommandListener(Command_AdminChat, "sm_chat"); + + #if defined USE_AutoExecConfig + AutoExecConfig_SetFile("Discord-Utilities"); + AutoExecConfig_SetCreateFile(true); + + g_cChatRelay_Webhook = AutoExecConfig_CreateConVar("sm_du_chat_webhook", "", "Webhook for game server => discord server chat messages. Blank to disable.", FCVAR_PROTECTED); + g_cChatRelay_BlockList = AutoExecConfig_CreateConVar("sm_du_chat_blocklist", "rtv, nominate", "Text that shouldn't appear in gameserver => discord server chat messages. Separate it with \", \""); + g_cAdminChatRelay_Mode = AutoExecConfig_CreateConVar("sm_du_adminchat_mode", "0b", "0 - Only \"say_team with @ / sm_chat\"\n0b - \"say_team with @ / sm_chat\" with discord to game server chat to admin.\nAny admin flag - Show messages of specific flag in channel."); + g_cAdminChatRelay_Webhook = AutoExecConfig_CreateConVar("sm_du_adminchat_webhook", "", "Webhook for game server => discord server chat messages where chat messages are to (say_team with @ / sm_chat) / are of admins. Blank to disable.", FCVAR_PROTECTED); + g_cAdminChatRelay_BlockList = AutoExecConfig_CreateConVar("sm_du_adminchat_blocklist", "rtv, nominate", "Text that shouldn't appear in gameserver => discord server where chat messages are to admin. Separate it with \", \""); + g_cAdminLog_Webhook = AutoExecConfig_CreateConVar("sm_du_adminlog_webhook", "", "Webhook for channel where all admin commands are logged. Blank to disable.", FCVAR_PROTECTED); + g_cAdminLog_BlockList = AutoExecConfig_CreateConVar("sm_du_adminlog_blocklist", "slapped, firebombed", "Log with this string will be ignored. Separate it with \", \""); + + g_cChatRelayChannelID = AutoExecConfig_CreateConVar("sm_du_chat_channelid", "", "Channel ID for discord server => game server messages. Blank to disable."); + g_cAdminChatRelayChannelID = AutoExecConfig_CreateConVar("sm_du_adminchat_channelid", "", "Channel ID for discord server => game server messages only of admins. Blank to disable."); + + g_cBotToken = AutoExecConfig_CreateConVar("sm_du_bottoken", "", "Bot Token. Needed for discord server => gameserver and/or verification module.", FCVAR_PROTECTED); + + g_cDiscordPrefix = AutoExecConfig_CreateConVar("sm_du_discord_prefix", "[{lightgreen}Discord{default}]", "Prefix for discord messages."); + + g_cTimeStamps = AutoExecConfig_CreateConVar("sm_du_display_timestamps", "0", "Display timestamps? Used in gameserver => discord server relay AND AdminLog"); + + g_cAPIKey = AutoExecConfig_CreateConVar("sm_du_apikey", "", "Steam API Key (https://steamcommunity.com/dev/apikey). Needed for gameserver => discord server relay and/or admin chat relay and/or Admin logs. Blank will show default author icon of discord.", FCVAR_PROTECTED); + + #else + g_cChatRelay_Webhook = CreateConVar("sm_du_chat_webhook", "", "Webhook for game server => discord server chat messages. Blank to disable.", FCVAR_PROTECTED); + g_cChatRelay_BlockList = CreateConVar("sm_du_chat_blocklist", "rtv, nominate", "Text that shouldn't appear in gameserver => discord server chat messages. Separate it with \", \""); + g_cAdminChatRelay_Mode = CreateConVar("sm_du_adminchat_mode", "0b", "0 - Only \"say_team with @ / sm_chat\"\n0b - \"say_team with @ / sm_chat\" with discord to game server chat to admin.\nAny admin flag - Show messages of specific flag in channel."); + g_cAdminChatRelay_Webhook = CreateConVar("sm_du_adminchat_webhook", "", "Webhook for game server => discord server chat messages where chat messages are to (say_team with @ / sm_chat) / are of admins. Blank to disable.", FCVAR_PROTECTED); + g_cAdminChatRelay_BlockList = CreateConVar("sm_du_adminchat_blocklist", "rtv, nominate", "Text that shouldn't appear in gameserver => discord server where chat messages are to admin. Separate it with \", \""); + g_cAdminLog_Webhook = CreateConVar("sm_du_adminlog_webhook", "", "Webhook for channel where all admin commands are logged. Blank to disable.", FCVAR_PROTECTED); + g_cAdminLog_BlockList = CreateConVar("sm_du_adminlog_blocklist", "slapped, firebombed", "Log with this string will be ignored. Separate it with \", \""); + + g_cChatRelayChannelID = CreateConVar("sm_du_chat_channelid", "", "Channel ID for discord server => game server messages. Blank to disable."); + g_cAdminChatRelayChannelID = CreateConVar("sm_du_adminchat_channelid", "", "Channel ID for discord server => game server messages only of admins. Blank to disable."); + + g_cBotToken = CreateConVar("sm_du_bottoken", "", "Bot Token. Needed for discord server => gameserver and/or verification module.", FCVAR_PROTECTED); + + g_cDiscordPrefix = CreateConVar("sm_du_discord_prefix", "[{lightgreen}Discord{default}]", "Prefix for discord messages."); + + g_cTimeStamps = CreateConVar("sm_du_display_timestamps", "0", "Display timestamps? Used in gameserver => discord server relay AND AdminLog"); + + g_cAPIKey = CreateConVar("sm_du_apikey", "", "Steam API Key (https://steamcommunity.com/dev/apikey). Needed for gameserver => discord server relay and/or admin chat relay and/or Admin logs. Blank will show default author icon of discord.", FCVAR_PROTECTED); + + AutoExecConfig(true, "Discord-Utilities"); + #endif + + HookConVarChange(g_cChatRelay_Webhook, OnSettingsChanged); + HookConVarChange(g_cChatRelay_BlockList, OnSettingsChanged); + HookConVarChange(g_cAdminChatRelay_Mode, OnSettingsChanged); + HookConVarChange(g_cAdminChatRelay_Webhook, OnSettingsChanged); + HookConVarChange(g_cAdminChatRelay_BlockList, OnSettingsChanged); + HookConVarChange(g_cAdminLog_Webhook, OnSettingsChanged); + HookConVarChange(g_cAdminLog_BlockList, OnSettingsChanged); + HookConVarChange(g_cChatRelayChannelID, OnSettingsChanged); + HookConVarChange(g_cAdminChatRelayChannelID, OnSettingsChanged); + + HookConVarChange(g_cBotToken, OnSettingsChanged); + + HookConVarChange(g_cDiscordPrefix, OnSettingsChanged); + + HookConVarChange(g_cAPIKey, OnSettingsChanged); + + LoadTranslations("Discord-Utilities.phrases"); +} + +public void OnLibraryAdded(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "basecomm")) g_bBaseComm = true; + else if(StrEqual(szLibrary, "shavit")) g_bShavit = true; +} + +public void OnLibraryRemoved(const char[] szLibrary) +{ + if(StrEqual(szLibrary, "basecomm")) g_bBaseComm = false; + else if(StrEqual(szLibrary, "shavit")) g_bShavit = false; +} + +public void OnAllPluginsLoaded() +{ + if(!LibraryExists("discord-api")) + { + SetFailState("[Discord-Utilities] This plugin is fully dependant on \"Discord-API\" by Deathknife. (https://github.com/Deathknife/sourcemod-discord)"); + } + + g_bShavit = LibraryExists("shavit"); + g_bBaseComm = LibraryExists("basecomm"); +} + +public void OnConfigsExecuted() +{ + LoadCvars(); + + if(!StrEqual(g_sBotToken, "")) + { + if(Bot == view_as(INVALID_HANDLE)) + { + CreateBot(); + } + } + + FindConVar("hostname").GetString(g_sServerName, sizeof(g_sServerName)); +} + +void LoadCvars() +{ + char sBlockList[PLATFORM_MAX_PATH]; + g_cChatRelay_Webhook.GetString(g_sChatRelay_Webhook, sizeof(g_sChatRelay_Webhook)); + g_cChatRelay_BlockList.GetString(sBlockList, sizeof(sBlockList)); + ExplodeString(sBlockList, ", ", g_sChatRelay_BlockList, MAX_BLOCKLIST_LIMIT, 64); + g_cAdminChatRelay_Mode.GetString(g_sAdminChatRelay_Mode, sizeof(g_sAdminChatRelay_Mode)); + g_cAdminChatRelay_Webhook.GetString(g_sAdminChatRelay_Webhook, sizeof(g_sAdminChatRelay_Webhook)); + g_cAdminChatRelay_BlockList.GetString(sBlockList, sizeof(sBlockList)); + ExplodeString(sBlockList, ", ", g_sAdminChatRelay_BlockList, MAX_BLOCKLIST_LIMIT, 64); + g_cAdminLog_Webhook.GetString(g_sAdminLog_Webhook, sizeof(g_sAdminLog_Webhook)); + g_cAdminLog_BlockList.GetString(sBlockList, sizeof(sBlockList)); + ExplodeString(sBlockList, ", ", g_sAdminLog_BlockList, MAX_BLOCKLIST_LIMIT, 64); + + g_cChatRelayChannelID.GetString(g_sChatRelayChannelID, sizeof(g_sChatRelayChannelID)); + g_cAdminChatRelayChannelID.GetString(g_sAdminChatRelayChannelID, sizeof(g_sAdminChatRelayChannelID)); + + g_cBotToken.GetString(g_sBotToken, sizeof(g_sBotToken)); + + g_cDiscordPrefix.GetString(g_sDiscordPrefix, sizeof(g_sDiscordPrefix)); + + g_cAPIKey.GetString(g_sAPIKey, sizeof(g_sAPIKey)); +} + +public int OnSettingsChanged(ConVar convar, const char[] oldVal, const char[] newVal) +{ + if(StrEqual(oldVal, newVal, true)) + { + return; + } + if(convar == g_cChatRelay_Webhook) + { + strcopy(g_sChatRelay_Webhook, sizeof(g_sChatRelay_Webhook), newVal); + } + else if(convar == g_cChatRelay_BlockList) + { + ExplodeString(newVal, ", ", g_sChatRelay_BlockList, MAX_BLOCKLIST_LIMIT, 64); + } + else if(convar == g_cAdminChatRelay_Webhook) + { + strcopy(g_sAdminChatRelay_Webhook, sizeof(g_sAdminChatRelay_Webhook), newVal); + } + else if(convar == g_cAdminChatRelay_BlockList) + { + ExplodeString(newVal, ", ", g_sAdminChatRelay_BlockList, MAX_BLOCKLIST_LIMIT, 64); + } + else if(convar == g_cChatRelayChannelID) + { + strcopy(g_sChatRelayChannelID, sizeof(g_sChatRelayChannelID), newVal); + } + else if(convar == g_cBotToken) + { + strcopy(g_sBotToken, sizeof(g_sBotToken), newVal); + } + else if(convar == g_cDiscordPrefix) + { + strcopy(g_sDiscordPrefix, sizeof(g_sDiscordPrefix), newVal); + } +} + +public void OnPluginEnd() +{ + KillBot(); +} + +public void OnMapEnd() +{ + KillBot(); +} + +public void OnClientPutInServer(int client) +{ + g_sAvatarURL[client][0] = '\0'; +} + +public Action Command_AdminChat(int client, const char[] command, int argc) +{ + if(g_sAdminChatRelay_Webhook[0] == '\0' || g_sAdminChatRelay_Mode[0] != '0') + { + return Plugin_Continue; + } + if(IsValidClient(client) && g_bBaseComm && BaseComm_IsClientGagged(client)) + { + return Plugin_Continue; + } + if(1 <= client <= MaxClients) + { + char sMessage[256]; + GetCmdArgString(sMessage, sizeof(sMessage)); + SendChatRelay(client, sMessage, g_sAdminChatRelay_Webhook); + } + return Plugin_Continue; +} + +public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) +{ + if(StrEqual(g_sChatRelay_Webhook, "") && StrEqual(g_sAdminChatRelay_Webhook, "")) + { + return Plugin_Continue; + } + if(IsValidClient(client) && g_bBaseComm && BaseComm_IsClientGagged(client)) + { + return Plugin_Continue; + } + if(1 <= client <= MaxClients) + { + if(strcmp(command, "say") != 0 && strcmp(command, "say_team") != 0) + { + return Plugin_Continue; + } + if(IsChatTrigger()) + { + return Plugin_Continue; + } + if(strcmp(command, "say_team") == 0 && sArgs[0] == '@') + { + bool bAdmin = CheckCommandAccess(client, "", ADMFLAG_GENERIC); + if(g_sAdminChatRelay_Mode[0] == '0') + { + SendChatRelay(client, sArgs[1], g_sAdminChatRelay_Webhook, bAdmin); + } + return Plugin_Continue; + } + if(strcmp(command, "say") == 0 && sArgs[0] == '@') + { + if(g_sChatRelay_Webhook[0]) + { + SendChatRelay(client, sArgs[1], g_sChatRelay_Webhook, true, true); + } + return Plugin_Continue; + } + if(!StrEqual(g_sAdminChatRelay_Mode, "0", false) && !StrEqual(g_sAdminChatRelay_Mode, "", false) && g_sAdminChatRelay_Mode[1] == '\0') + { + if(g_sAdminChatRelay_Webhook[0] && CheckAdminFlags(client, ReadFlagString(g_sAdminChatRelay_Mode))) + SendChatRelay(client, sArgs, g_sAdminChatRelay_Webhook); + } + else if(g_sChatRelay_Webhook[0]) + { + SendChatRelay(client, sArgs, g_sChatRelay_Webhook); + } + } + return Plugin_Continue; +} + +public Action OnLogAction(Handle hSource, Identity ident, int client, int target, const char[] sMsg) +{ + if(StrEqual(g_sAdminLog_Webhook, "")) + { + delete hSource; + return Plugin_Continue; + } + + if(client <= 0) + { + delete hSource; + return Plugin_Continue; + } + + if(StrContains(sMsg, "sm_chat", false) != -1) + { + delete hSource; + return Plugin_Continue;// dont log sm_chat because it's already being showed in admin chat relay channel. + } + + SendAdminLog(client, sMsg); + delete hSource; + + return Plugin_Continue; +} + +public void GuildList(DiscordBot bawt, char[] id, char[] name, char[] icon, bool owner, int permissions, const bool listen) +{ + Bot.GetGuildChannels(id, ChannelList, INVALID_FUNCTION, listen); +} + +public void ChannelList(DiscordBot bawt, const char[] guild, DiscordChannel Channel, const bool listen) +{ + if(StrEqual(g_sBotToken, "") || (StrEqual(g_sChatRelayChannelID, "") && StrEqual(g_sVerificationChannelID, "") && StrEqual(g_sAdminChatRelayChannelID, ""))) + { + return; + } + if(Bot == null || Channel == null) + { + return; + } + if(Bot.IsListeningToChannel(Channel)) + { + //Bot.StopListeningToChannel(Channel); + return; + } + char id[20], name[32]; + Channel.GetID(id, sizeof(id)); + Channel.GetName(name, sizeof(name)); + if(strlen(g_sChatRelayChannelID) > 10) //ChannelID size is around 18-20 char + { + if(StrEqual(id, g_sChatRelayChannelID)) + { + Bot.StartListeningToChannel(Channel, ChatRelayReceived); + } + } + if(strlen(g_sAdminChatRelayChannelID) > 10) + { + if(StrEqual(id, g_sAdminChatRelayChannelID) && !StrEqual(g_sAdminChatRelay_Mode, "", false) && (g_sAdminChatRelay_Mode[0] == '0' && g_sAdminChatRelay_Mode[1] != '\0') || (g_sAdminChatRelay_Mode[0] != '0' && g_sAdminChatRelay_Mode[0] != '\0')) + { + Bot.StartListeningToChannel(Channel, AdminChatRelayReceived); + } + } +} + +public void AdminChatRelayReceived(DiscordBot bawt, DiscordChannel channel, DiscordMessage discordmessage) +{ + if((g_sAdminChatRelay_Mode[0] == '0' && g_sAdminChatRelay_Mode[1] == '\0') || (g_sAdminChatRelay_Mode[0] != '\0' && g_sAdminChatRelay_Mode[0] != '0' && g_sAdminChatRelay_Mode[1] != '\0')) + { + return; + } + DiscordUser author = discordmessage.GetAuthor(); + if(author.IsBot()) + { + delete author; + return; + } + + char message[512]; + char userName[32], discriminator[6]; + discordmessage.GetContent(message, sizeof(message)); + author.GetUsername(userName, sizeof(userName)); + author.GetDiscriminator(discriminator, sizeof(discriminator)); + delete author; + + char sFlag[5]; + if(g_sAdminChatRelay_Mode[0] == '0' && g_sAdminChatRelay_Mode[1] != '\0') + { + sFlag[0] = g_sAdminChatRelay_Mode[1]; + } + else + { + sFlag[0] = g_sAdminChatRelay_Mode[0]; + } + for(int i = 1; i <= MaxClients; i++) if(IsClientInGame(i) && !IsFakeClient(i) && CheckAdminFlags(i, ReadFlagString(sFlag))) + { + CPrintToChat(i, "%s %T", g_sDiscordPrefix, "AdminChatRelayFormat", i, userName, discriminator, message); + } +} + +public void ChatRelayReceived(DiscordBot bawt, DiscordChannel channel, DiscordMessage discordmessage) +{ + DiscordUser author = discordmessage.GetAuthor(); + if(author.IsBot()) + { + delete author; + return; + } + + char message[512]; + char userName[32], discriminator[6]; + discordmessage.GetContent(message, sizeof(message)); + author.GetUsername(userName, sizeof(userName)); + author.GetDiscriminator(discriminator, sizeof(discriminator)); + delete author; + + CPrintToChatAll("%s %T", g_sDiscordPrefix, "ChatRelayFormat", LANG_SERVER, userName, discriminator, message); +} + +void SendChatRelay(int client, const char[] sArgs, char[] url, bool bAdmin = true, bool bAllChat = false) +{ + if(strcmp(url, g_sChatRelay_Webhook) == 0) + { + for(int i = 0; i < sizeof(g_sChatRelay_BlockList); i++) + { + if(strcmp(sArgs, g_sChatRelay_BlockList[i], false) == 0) + { + return; + } + } + } + else if(strcmp(url, g_sAdminChatRelay_Webhook) == 0) + { + for(int i = 0; i < sizeof(g_sAdminChatRelay_BlockList); i++) + { + if(strcmp(sArgs, g_sAdminChatRelay_BlockList[i], false) == 0) + { + return; + } + } + } + char name[MAX_NAME_LENGTH+1], timestamp[32], sMessage[256]; + GetClientName(client, name, sizeof(name)); + TrimString(name); + Discord_EscapeString(name, sizeof(name), true); + + char auth[32]; + if(!GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth))) + { + return; + } + Format(name, sizeof(name), "%s [%s]", name, auth); + + FormatEx(sMessage, sizeof(sMessage), sArgs); + Discord_EscapeString(sMessage, sizeof(sMessage)); + + RemoveColors(sMessage, sizeof(sMessage)); + + if(g_cTimeStamps.BoolValue) + { + FormatTime(timestamp, sizeof(timestamp), "[%I:%M:%S %p] ", GetTime()); + } + + DiscordWebHook hook = new DiscordWebHook( url ); + hook.SlackMode = true; + hook.SetUsername( name ); + if(g_sAvatarURL[client][0]) + { + hook.SetAvatar(g_sAvatarURL[client]); + } + char sPrivateToAdmins[32], sAllChat[32]; + Format(sPrivateToAdmins, sizeof(sPrivateToAdmins), "%T", "ChatRelayPrivateToAdmins", LANG_SERVER); + Format(sAllChat, sizeof(sAllChat), "%T", "ChatRelayAllChat", LANG_SERVER); + if(strcmp(url, g_sAdminChatRelay_Webhook) == 0) + { + Format(sMessage, sizeof(sMessage), "%T", "AdminChatFormat", LANG_SERVER, timestamp, g_sServerName, bAdmin ? "" : sPrivateToAdmins, sMessage); + } + else + { + Format(sMessage, sizeof(sMessage), "%s%s%s", timestamp, bAllChat ? sAllChat : "", sMessage); + } + hook.SetContent(sMessage); + hook.Send(); + + delete hook; +} + +void SendAdminLog(int client, const char[] sArgs) +{ + char name[MAX_NAME_LENGTH+1], timestamp[32], sMessage[256], map[PLATFORM_MAX_PATH], mapdisplay[64]; + GetClientName(client, name, sizeof(name)); + TrimString(name); + Discord_EscapeString(name, sizeof(name), true); + + char auth[32]; + if(!GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth))) + { + return; + } + Format(name, sizeof(name), "%s [%s]", name, auth); + + GetCurrentMap(map, sizeof(map)); + GetMapDisplayName(map, mapdisplay, sizeof(mapdisplay)); + + FormatEx(sMessage, sizeof(sMessage), sArgs); + Discord_EscapeString(sMessage, sizeof(sMessage)); + + + RemoveColors(name, sizeof(name)); + RemoveColors(sMessage, sizeof(sMessage)); + + if(g_cTimeStamps.BoolValue) + { + FormatTime(timestamp, sizeof(timestamp), "[%I:%M:%S %p] ", GetTime()); + } + + DiscordWebHook hook = new DiscordWebHook( g_sAdminLog_Webhook ); + hook.SlackMode = true; + hook.SetUsername( name ); + if(g_sAvatarURL[client][0]) + { + hook.SetAvatar(g_sAvatarURL[client]); + } + Format(sMessage, sizeof(sMessage), "%T", "AdminLogFormat", LANG_SERVER, timestamp, g_sServerName, mapdisplay, sMessage); + hook.SetContent(sMessage); + + hook.Send(); + + delete hook; +} + +stock void RemoveColors(char[] text, int size) +{ + if(g_bShavit) + { + for(int i = 0; i < sizeof(gS_GlobalColorNames); i++) + { + ReplaceString(text, size, gS_GlobalColorNames[i], ""); + } + for(int i = 0; i < sizeof(gS_GlobalColors); i++) + { + ReplaceString(text, size, gS_GlobalColors[i], ""); + } + for(int i = 0; i < sizeof(gS_CSGOColorNames); i++) + { + ReplaceString(text, size, gS_CSGOColorNames[i], ""); + } + for(int i = 0; i < sizeof(gS_CSGOColors); i++) + { + ReplaceString(text, size, gS_CSGOColors[i], ""); + } + } + else + { + for(int i = 0; i < sizeof(C_Tag); i++) + { + ReplaceString(text, size, C_Tag[i], ""); + } + for(int i = 0; i < sizeof(C_TagCode); i++) + { + ReplaceString(text, size, C_TagCode[i], ""); + } + } +} + +stock void Discord_EscapeString(char[] string, int maxlen, bool name = false) +{ + if(name) + { + ReplaceString(string, maxlen, "everyone", "everyone"); + ReplaceString(string, maxlen, "here", "here"); + ReplaceString(string, maxlen, "discordtag", "discordtag"); + } + ReplaceString(string, maxlen, "#", "#"); + ReplaceString(string, maxlen, "@", "@"); + //ReplaceString(string, maxlen, ":", ""); + ReplaceString(string, maxlen, "_", "ˍ"); + ReplaceString(string, maxlen, "'", "'"); + ReplaceString(string, maxlen, "`", "'"); + ReplaceString(string, maxlen, "~", "∽"); + ReplaceString(string, maxlen, "\"", """); +} + +stock void CreateBot() +{ + if(StrEqual(g_sBotToken, "") || StrEqual(g_sChatRelayChannelID, "") && StrEqual(g_sVerificationChannelID, "")) + { + return; + } + KillBot(); + Bot = new DiscordBot(g_sBotToken); + CreateTimer(5.0, Timer_GuildList, _, TIMER_FLAG_NO_MAPCHANGE); +} + +stock void KillBot() +{ + if(Bot) + { + Bot.StopListeningToChannels(); + Bot.StopListening(); + } + delete Bot; +} + +bool CheckAdminFlags(int client, int iFlag) +{ + int iUserFlags = GetUserFlagBits(client); + return (iUserFlags & ADMFLAG_ROOT || (iUserFlags & iFlag) == iFlag); +} + +public Action Timer_GuildList(Handle timer) +{ + GetGuilds(); +} + +public Action OnClientPreAdminCheck(int client) +{ + if(IsFakeClient(client)) + { + return; + } + + + if(StrEqual(g_sAPIKey, "")) + { + return; + } + + char szSteamID64[32]; + if(!GetClientAuthId(client, AuthId_SteamID64, szSteamID64, sizeof(szSteamID64))) + { + return; + } + + static char sRequest[256]; + FormatEx(sRequest, sizeof(sRequest), "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=%s&steamids=%s&format=vdf", g_sAPIKey, szSteamID64); + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest); + if(!hRequest || !SteamWorks_SetHTTPRequestContextValue(hRequest, client) || !SteamWorks_SetHTTPCallbacks(hRequest, OnTransferCompleted) || !SteamWorks_SendHTTPRequest(hRequest)) + { + delete hRequest; + } +} + +public int OnTransferCompleted(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, int client) +{ + if (bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) + { + LogError("SteamAPI HTTP Response failed: %d", eStatusCode); + delete hRequest; + return; + } + + int iBodyLength; + SteamWorks_GetHTTPResponseBodySize(hRequest, iBodyLength); + + char[] sData = new char[iBodyLength]; + SteamWorks_GetHTTPResponseBodyData(hRequest, sData, iBodyLength); + + delete hRequest; + + APIWebResponse(sData, client); +} + +public void APIWebResponse(const char[] sData, int client) +{ + KeyValues kvResponse = new KeyValues("SteamAPIResponse"); + + if (!kvResponse.ImportFromString(sData, "SteamAPIResponse")) + { + LogError("kvResponse.ImportFromString(\"SteamAPIResponse\") in APIWebResponse failed. Try updating your steamworks extension."); + + delete kvResponse; + return; + } + + if (!kvResponse.JumpToKey("players")) + { + LogError("kvResponse.JumpToKey(\"players\") in APIWebResponse failed. Try updating your steamworks extension."); + + delete kvResponse; + return; + } + + if (!kvResponse.GotoFirstSubKey()) + { + LogError("kvResponse.GotoFirstSubKey() in APIWebResponse failed. Try updating your steamworks extension."); + + delete kvResponse; + return; + } + + kvResponse.GetString("avatarfull", g_sAvatarURL[client], sizeof(g_sAvatarURL[])); + delete kvResponse; +} + +stock void GetGuilds() +{ + Bot.GetGuilds(GuildList, _, true); +} + +stock bool IsValidClient(int client) +{ + return (1 <= client <= MaxClients && IsClientInGame(client)); +} \ No newline at end of file diff --git a/scripting/include/bugreport.inc b/scripting/include/bugreport.inc new file mode 100644 index 0000000..0817cbf --- /dev/null +++ b/scripting/include/bugreport.inc @@ -0,0 +1,42 @@ +#if defined _bugreport_included + #endinput +#endif +#define _bugreport_included + + +#define REASON_MAX_LENGTH 128 + +#define COOLDOWN 300 //in seconds (default: 300[5 minutes]) + +/** + * Called after a client (or module) has reported a bug for a map. + * Be sure to check that client != 0 if you expect a valid client index. + * + * @param client Client index of the caller. + * @param map DisplayName of map when client reported the bug. + * @param reason Reason selected by the client for the report. + * @param array Array with reasons that have already been reported in the current map. + * @noreturn + */ +forward void BugReport_OnReportPost(int client, const char[] map, const char[] reason, ArrayList array); + + +public SharedPlugin __pl_bugreport = +{ + name = "bugreport", + file = "bugreport.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + + + + +#if !defined REQUIRE_PLUGIN +public __pl_bugreport_SetNTVOptional() +{ +} +#endif diff --git a/scripting/include/calladmin.inc b/scripting/include/calladmin.inc new file mode 100644 index 0000000..1af2c58 --- /dev/null +++ b/scripting/include/calladmin.inc @@ -0,0 +1,292 @@ +/** Include guard */ +#if defined _calladmin_included + #endinput +#endif +#define _calladmin_included + + + + +/** Global calladmin version, do not change */ +#define CALLADMIN_VERSION "0.1.8" + + + +/** Pass this as a clientindex to the ReportPlayer native if you don't have a client, eg report from server automatically */ +#define REPORTER_CONSOLE 1679124 + + +/** Maximum size a reason string can be in length */ +#define REASON_MAX_LENGTH 128 + + + +/** + * Called when the main CallAdmin client selection menu is about to be drawn for a client. + * Note: CallAdmin will not notify players why or if their menu was blocked. + * + * @param client Client index of the caller. + * @return Plugin_Continue to allow, Plugin_Handled otherwise. + */ +forward Action CallAdmin_OnDrawMenu(int client); + + + + +/** + * Called when the own reason selection is enabled and the select item for it is about to be drawn for a client. + * + * @param client Client index of the caller. + * @return Plugin_Continue to allow, Plugin_Handled otherwise. + */ +forward Action CallAdmin_OnDrawOwnReason(int client); + + + + +/** + * Called when a target is about to be drawn to the target selection menu for a client. + * Note: Called *n-1 times for the client selection menu where n is the amount of valid targets including the caller. + * + * @param client Client index of the caller. + * @param target Client index of the target about to be drawn. + * @return Plugin_Continue to allow the target to be drawn, Plugin_Handled otherwise. + */ +forward Action CallAdmin_OnDrawTarget(int client, int target); + + + + +/** + * Called when the tracker count was changed. + * + * @param oldVal Tracker count before update. + * @param newVal Tracker count after update. + * @noreturn + */ +forward void CallAdmin_OnTrackerCountChanged(int oldVal, int newVal); + + + + +/** + * Called before a client (or module) has reported another client. + * Be sure to check that client != REPORTER_CONSOLE if you expect a valid client index. + * Note: CallAdmin will not notify players why or if their report was blocked. + * + * @param client Client index of the caller. + * @param target Client index of the target. + * @param reason Reason selected by the client for the report. + * @return Plugin_Continue to allow, Plugin_Handled otherwise. + */ +forward Action CallAdmin_OnReportPre(int client, int target, const char[] reason); + + + + +/** + * Called after a client (or module) has reported another client. + * Be sure to check that client != REPORTER_CONSOLE if you expect a valid client index. + * + * @param client Client index of the caller. + * @param target Client index of the target. + * @param reason Reason selected by the client for the report. + * @noreturn + */ +forward void CallAdmin_OnReportPost(int client, int target, const char[] reason); + + + + +/** + * Initiates a report call. + * If you report automatically (via a module for example) set the client index to REPORTER_CONSOLE. + * + * @param client Client index of the caller. + * @param target Client index of the target. + * @param reason Reason for the report. + * @return True if target could be reported, false otherwise. + */ +native bool CallAdmin_ReportClient(int client, int target, const char[] reason); + + + + +/** + * Called when an admin is about to be added to the in-game admin count. + * + * @param client Client index of the admin. + * @return Plugin_Continue to allow, Plugin_Handled otherwise. + */ +forward Action CallAdmin_OnAddToAdminCount(int client); + + + + +/** + * Returns the cached count of current trackers. + * + * @return Count of current trackers. + */ +native int CallAdmin_GetTrackersCount(); + + + + +/** + * Requests a forced refresh of the trackers count. + * Note that most modules work asynchronous and only return their own cached count. + * + * @noreturn + */ +native void CallAdmin_RequestTrackersCountRefresh(); + + + + +/** + * Called when the trackercount of a module is requested. + * This is either called periodically via the base calladmin, or when RequestTrackersCountRefresh is invoked. + * + * @param trackers By ref value of your trackers. + * @noreturn + */ +forward void CallAdmin_OnRequestTrackersCountRefresh(int &trackers); + + + + +enum ServerData +{ + ServerData_HostName, /**< This is the hostname of the server, gathered from the `hostname` convar */ + ServerData_HostIP, /**< This is the hostip of the server, gathered and converted from the `hostip` convar */ + ServerData_HostPort /**< This is the hostport of the server, gathered from the `hostport` convar */ +}; + + + + +/** + * Called when the serverdata data is changed. + * + * @param convar Handle to the convar which was changed. + * @param type Type of data which was changed. + * @param oldVal Value of data before change. + * @param newVal Value of data after change. + * @noreturn + */ +forward void CallAdmin_OnServerDataChanged(ConVar convar, ServerData type, const char[] oldVal, const char[] newVal); + + + + +/** + * Returns the server's hostname. + * + * @param buffer String to copy hostname to. + * @param max_size Maximum size of buffer. + * @noreturn + */ +native void CallAdmin_GetHostName(char[] buffer, int max_size); + + + + +/** + * Returns the server's Ip String. + * + * @param buffer String to copy hostip to. + * @param max_size Maximum size of buffer. + * @noreturn + */ +native void CallAdmin_GetHostIP(char[] buffer, int max_size); + + + + +/** + * Returns the server's port. + * + * @return Port of the server. + */ +native int CallAdmin_GetHostPort(); + + + + +/** + * Logs a message to the calladmin logfile. + * The message has this format "[Pluginname] Message", where the plugin name is detected automatically. + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ +native void CallAdmin_LogMessage(const char[] format, any ...); + + + + +/** + * Called when a message was logged to the calladmin logfile. + * + * @param plugin Handle to the plugin which logged the message. + * @param message Message that was logged. + * @noreturn + */ +forward void CallAdmin_OnLogMessage(Handle plugin, const char[] message); + + + + +/** + * Returns the server's current report id. + * This is a temporary value and is increased with each successfull report. + * + * @return Current report id of the server. + */ +native int CallAdmin_GetReportID(); + + + + +/** + * Called when a report was handled. + * + * @param client Admin who handled the report. + * @param id Id of the handled report. + * @noreturn + */ +forward void CallAdmin_OnReportHandled(int client, int id); + + + + +/* Do not edit below this line */ +public SharedPlugin __pl_calladmin = +{ + name = "calladmin", + file = "calladmin.smx", +#if defined REQUIRE_PLUGIN + required = 0, +#else + required = 0, +#endif +}; + + + + +#if !defined REQUIRE_PLUGIN +public __pl_calladmin_SetNTVOptional() +{ + MarkNativeAsOptional("CallAdmin_GetTrackersCount"); + MarkNativeAsOptional("CallAdmin_RequestTrackersCountRefresh"); + MarkNativeAsOptional("CallAdmin_GetHostName"); + MarkNativeAsOptional("CallAdmin_GetHostIP"); + MarkNativeAsOptional("CallAdmin_GetHostPort"); + MarkNativeAsOptional("CallAdmin_ReportClient"); + MarkNativeAsOptional("CallAdmin_LogMessage"); + MarkNativeAsOptional("CallAdmin_GetReportID"); +} +#endif diff --git a/include/discord.inc b/scripting/include/discord.inc similarity index 100% rename from include/discord.inc rename to scripting/include/discord.inc diff --git a/include/discord_utilities.inc b/scripting/include/discord_utilities.inc similarity index 96% rename from include/discord_utilities.inc rename to scripting/include/discord_utilities.inc index 5ba8212..9680384 100644 --- a/include/discord_utilities.inc +++ b/scripting/include/discord_utilities.inc @@ -1,124 +1,124 @@ -#if defined _discord_utilities_included - #endinput -#endif -#define _discord_utilities_included - -/** - * Called after a client has verfied his discord account. - * - * @param client Client index of the verified client. - * @param userid Discord userid of the client. - * @param username Discord username of the client. - * @param discriminator Discord discriminator of the client. - * @noreturn - */ -forward void DU_OnLinkedAccount(int client, const char[] userid, const char[] username, const char[] discriminator); - -/** - * Called after a client's member status has been revoked. - * - * @param client Client index. - * @param userid Discord userid of the client. - * @noreturn - */ -forward void DU_OnAccountRevoked(int client, const char[] userid); - -/** - * Called before checking accounts. return Plugin_Handled to block checking. - * - * @param bottoken Token of bot which will be checking accounts. - * @param userid Guildid of your discord server. - * @param tablename Table name of database. - * @noreturn - */ -forward void DU_OnCheckedAccounts(const char[] bottoken, const char[] guildid, const char[] tablename); - - -/** - * Called after member data is dumped (as json) - * - * @noreturn - */ -forward void DU_OnMemberDataDumped(); - - - -/** - * Refreshing every client online in server. Can be used for grasping steam avatar of client and re-checking client in database. - * - * @noreturn - */ -native void DU_RefreshClients(); - -/** - * Discord Database Check - * - * @returns true if client was already checked for member status. - */ -native bool DU_IsChecked(int client); - -/** - * Discord Member Check - * - * @returns true if client is member of discord server else false. - */ -native bool DU_IsMember(int client); - -/** - * Retrieve client's discord userid. - * @param client Client index to get userid from. - * @param output String to store userid in. - * @param maxsize Max size of string. - * @Throws native error if Is_Member == false or client userid is not retrieved. - */ -native char DU_GetUserId(int client, char[] output, int maxsize); - -/** - * Retrieve DNS ip / real ip of server according to sm_du_dns_ip. (If cvar is blank then real ip else DNS ip) - * @param output String to store sererip in. - * @param maxsize Max size of string. - * @noreturn - */ -native void DU_GetIP(char[] output, int maxsize); - -/** - * Assigns role to client according to roleid - * @param client Client index to give role to. - * @param roleid String containing roleid. - * @param maxsize Max size of string. - * @noreturn - */ -native void DU_AddRole(int client, char[] roleid); - -/** - * Deletes role from client according to roleid - * @param client Client index to delete role from. - * @param roleid String containing roleid. - * @param maxsize Max size of string. - * @noreturn - */ -native void DU_DeleteRole(int client, char[] roleid); - -public SharedPlugin __pl_discord_utilities = -{ - name = "DiscordUtilities", - file = "discord_utilities.smx", - #if defined REQUIRE_PLUGIN - required = 1, - #else - required = 0, - #endif -}; - -#if !defined REQUIRE_PLUGIN -public void __pl_discord_utilities_SetNTVOptional() -{ - MarkNativeAsOptional("DU_IsChecked"); - MarkNativeAsOptional("DU_IsMember"); - MarkNativeAsOptional("DU_GetUserId"); - MarkNativeAsOptional("DU_RefreshClients"); - MarkNativeAsOptional("DU_GetIP"); - MarkNativeAsOptional("DU_AddRole"); - MarkNativeAsOptional("DU_DeleteRole"); -} -#endif +#if defined _discord_utilities_included + #endinput +#endif +#define _discord_utilities_included + +/** + * Called after a client has verfied his discord account. + * + * @param client Client index of the verified client. + * @param userid Discord userid of the client. + * @param username Discord username of the client. + * @param discriminator Discord discriminator of the client. + * @noreturn + */ +forward void DU_OnLinkedAccount(int client, const char[] userid, const char[] username, const char[] discriminator); + +/** + * Called after a client's member status has been revoked. + * + * @param client Client index. + * @param userid Discord userid of the client. + * @noreturn + */ +forward void DU_OnAccountRevoked(int client, const char[] userid); + +/** + * Called before checking accounts. return Plugin_Handled to block checking. + * + * @param bottoken Token of bot which will be checking accounts. + * @param userid Guildid of your discord server. + * @param tablename Table name of database. + * @noreturn + */ +forward void DU_OnCheckedAccounts(const char[] bottoken, const char[] guildid, const char[] tablename); + + +/** + * Called after member data is dumped (as json) + * + * @noreturn + */ +forward void DU_OnMemberDataDumped(); + + + +/** + * Refreshing every client online in server. Can be used for grasping steam avatar of client and re-checking client in database. + * + * @noreturn + */ +native void DU_RefreshClients(); + +/** + * Discord Database Check + * + * @returns true if client was already checked for member status. + */ +native bool DU_IsChecked(int client); + +/** + * Discord Member Check + * + * @returns true if client is member of discord server else false. + */ +native bool DU_IsMember(int client); + +/** + * Retrieve client's discord userid. + * @param client Client index to get userid from. + * @param output String to store userid in. + * @param maxsize Max size of string. + * @Throws native error if Is_Member == false or client userid is not retrieved. + */ +native char DU_GetUserId(int client, char[] output, int maxsize); + +/** + * Retrieve DNS ip / real ip of server according to sm_du_dns_ip. (If cvar is blank then real ip else DNS ip) + * @param output String to store sererip in. + * @param maxsize Max size of string. + * @noreturn + */ +native void DU_GetIP(char[] output, int maxsize); + +/** + * Assigns role to client according to roleid + * @param client Client index to give role to. + * @param roleid String containing roleid. + * @param maxsize Max size of string. + * @noreturn + */ +native void DU_AddRole(int client, char[] roleid); + +/** + * Deletes role from client according to roleid + * @param client Client index to delete role from. + * @param roleid String containing roleid. + * @param maxsize Max size of string. + * @noreturn + */ +native void DU_DeleteRole(int client, char[] roleid); + +public SharedPlugin __pl_discord_utilities = +{ + name = "DiscordUtilities", + file = "discord_utilities.smx", + #if defined REQUIRE_PLUGIN + required = 1, + #else + required = 0, + #endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_discord_utilities_SetNTVOptional() +{ + MarkNativeAsOptional("DU_IsChecked"); + MarkNativeAsOptional("DU_IsMember"); + MarkNativeAsOptional("DU_GetUserId"); + MarkNativeAsOptional("DU_RefreshClients"); + MarkNativeAsOptional("DU_GetIP"); + MarkNativeAsOptional("DU_AddRole"); + MarkNativeAsOptional("DU_DeleteRole"); +} +#endif diff --git a/scripting/include/smjansson.inc b/scripting/include/smjansson.inc new file mode 100644 index 0000000..e1936b8 --- /dev/null +++ b/scripting/include/smjansson.inc @@ -0,0 +1,1328 @@ +#if defined _jansson_included_ + #endinput +#endif +#define _jansson_included_ + + +/** + * --- Type + * + * The JSON specification (RFC 4627) defines the following data types: + * object, array, string, number, boolean, and null. + * JSON types are used dynamically; arrays and objects can hold any + * other data type, including themselves. For this reason, Jansson�s + * type system is also dynamic in nature. There�s one Handle type to + * represent all JSON values, and the referenced structure knows the + * type of the JSON value it holds. + * + */ +enum json_type { + JSON_OBJECT, + JSON_ARRAY, + JSON_STRING, + JSON_INTEGER, + JSON_REAL, + JSON_TRUE, + JSON_FALSE, + JSON_NULL +} + +/** + * Return the type of the JSON value. + * + * @param hObj Handle to the JSON value + * + * @return json_type of the value. + */ +native json_type:json_typeof(Handle:hObj); + +/** + * The type of a JSON value is queried and tested using these macros + * + * @param %1 Handle to the JSON value + * + * @return True if the value has the correct type. + */ +#define json_is_object(%1) ( json_typeof(%1) == JSON_OBJECT ) +#define json_is_array(%1) ( json_typeof(%1) == JSON_ARRAY ) +#define json_is_string(%1) ( json_typeof(%1) == JSON_STRING ) +#define json_is_integer(%1) ( json_typeof(%1) == JSON_INTEGER ) +#define json_is_real(%1) ( json_typeof(%1) == JSON_REAL ) +#define json_is_true(%1) ( json_typeof(%1) == JSON_TRUE ) +#define json_is_false(%1) ( json_typeof(%1) == JSON_FALSE ) +#define json_is_null(%1) ( json_typeof(%1) == JSON_NULL ) +#define json_is_number(%1) ( json_typeof(%1) == JSON_INTEGER || json_typeof(%1) == JSON_REAL ) +#define json_is_boolean(%1) ( json_typeof(%1) == JSON_TRUE || json_typeof(%1) == JSON_FALSE ) + +/** + * Saves json_type as a String in output + * + * @param input json_type value to convert to string + * @param output Buffer to store the json_type value + * @param maxlength Maximum length of string buffer. + * + * @return False if the type does not exist. + */ +stock bool:Stringify_json_type(json_type:input, String:output[], maxlength) { + switch(input) { + case JSON_OBJECT: strcopy(output, maxlength, "Object"); + case JSON_ARRAY: strcopy(output, maxlength, "Array"); + case JSON_STRING: strcopy(output, maxlength, "String"); + case JSON_INTEGER: strcopy(output, maxlength, "Integer"); + case JSON_REAL: strcopy(output, maxlength, "Real"); + case JSON_TRUE: strcopy(output, maxlength, "True"); + case JSON_FALSE: strcopy(output, maxlength, "False"); + case JSON_NULL: strcopy(output, maxlength, "Null"); + default: return false; + } + + return true; +} + + + +/** + * --- Equality + * + * - Two integer or real values are equal if their contained numeric + * values are equal. An integer value is never equal to a real value, + * though. + * - Two strings are equal if their contained UTF-8 strings are equal, + * byte by byte. Unicode comparison algorithms are not implemented. + * - Two arrays are equal if they have the same number of elements and + * each element in the first array is equal to the corresponding + * element in the second array. + * - Two objects are equal if they have exactly the same keys and the + * value for each key in the first object is equal to the value of + * the corresponding key in the second object. + * - Two true, false or null values have no "contents", so they are + * equal if their types are equal. + * + */ + +/** + * Test whether two JSON values are equal. + * + * @param hObj Handle to the first JSON object + * @param hOther Handle to the second JSON object + * + * @return Returns false if they are inequal or one + * or both of the pointers are NULL. + */ +native bool:json_equal(Handle:hObj, Handle:hOther); + + + + +/** + * --- Copying + * + * Jansson supports two kinds of copying: shallow and deep. There is + * a difference between these methods only for arrays and objects. + * + * Shallow copying only copies the first level value (array or object) + * and uses the same child values in the copied value. + * + * Deep copying makes a fresh copy of the child values, too. Moreover, + * all the child values are deep copied in a recursive fashion. + * + */ + +/** + * Get a shallow copy of the passed object + * + * @param hObj Handle to JSON object to be copied + * + * @return Returns a shallow copy of the object, + * or INVALID_HANDLE on error. + */ +native Handle:json_copy(Handle:hObj); + +/** + * Get a deep copy of the passed object + * + * @param hObj Handle to JSON object to be copied + * + * @return Returns a deep copy of the object, + * or INVALID_HANDLE on error. + */ +native Handle:json_deep_copy(Handle:hObj); + + + + +/** + * --- Objects + * + * A JSON object is a dictionary of key-value pairs, where the + * key is a Unicode string and the value is any JSON value. + * + */ + +/** + * Returns a handle to a new JSON object, or INVALID_HANDLE on error. + * Initially, the object is empty. + * + * @return Handle to a new JSON object. + */ +native Handle:json_object(); + +/** + * Returns the number of elements in hObj + * + * @param hObj Handle to JSON object + * + * @return Number of elements in hObj, + * or 0 if hObj is not a JSON object. + */ +native json_object_size(Handle:hObj); + +/** + * Get a value corresponding to sKey from hObj + * + * @param hObj Handle to JSON object to get a value from + * @param sKey Key to retrieve + * + * @return Handle to a the JSON object or + * INVALID_HANDLE on error. + */ +native Handle:json_object_get(Handle:hObj, const String:sKey[]); + +/** + * Set the value of sKey to hValue in hObj. + * If there already is a value for key, it is replaced by the new value. + * + * @param hObj Handle to JSON object to set a value on + * @param sKey Key to store in the object + * Must be a valid null terminated UTF-8 encoded + * Unicode string. + * @param hValue Value to store in the object + * + * @return True on success. + */ +native bool:json_object_set(Handle:hObj, const String:sKey[], Handle:hValue); + +/** + * Set the value of sKey to hValue in hObj. + * If there already is a value for key, it is replaced by the new value. + * This function automatically closes the Handle to the value object. + * + * @param hObj Handle to JSON object to set a value on + * @param sKey Key to store in the object + * Must be a valid null terminated UTF-8 encoded + * Unicode string. + * @param hValue Value to store in the object + * + * @return True on success. + */ +native bool:json_object_set_new(Handle:hObj, const String:sKey[], Handle:hValue); + +/** + * Delete sKey from hObj if it exists. + * + * @param hObj Handle to JSON object to delete a key from + * @param sKey Key to delete + * + * @return True on success. + */ +native bool:json_object_del(Handle:hObj, const String:sKey[]); + +/** + * Remove all elements from hObj. + * + * @param hObj Handle to JSON object to remove all + * elements from. + * + * @return True on success. + */ +native bool:json_object_clear(Handle:hObj); + +/** + * Update hObj with the key-value pairs from hOther, overwriting + * existing keys. + * + * @param hObj Handle to JSON object to update + * @param hOther Handle to JSON object to get update + * keys/values from. + * + * @return True on success. + */ +native bool:json_object_update(Handle:hObj, Handle:hOther); + +/** + * Like json_object_update(), but only the values of existing keys + * are updated. No new keys are created. + * + * @param hObj Handle to JSON object to update + * @param hOther Handle to JSON object to get update + * keys/values from. + * + * @return True on success. + */ +native bool:json_object_update_existing(Handle:hObj, Handle:hOther); + +/** + * Like json_object_update(), but only new keys are created. + * The value of any existing key is not changed. + * + * @param hObj Handle to JSON object to update + * @param hOther Handle to JSON object to get update + * keys/values from. + * + * @return True on success. + */ +native bool:json_object_update_missing(Handle:hObj, Handle:hOther); + + + + +/** + * Object iteration + * + * Example code: + * - We assume hObj is a Handle to a valid JSON object. + * + * + * new Handle:hIterator = json_object_iter(hObj); + * while(hIterator != INVALID_HANDLE) + * { + * new String:sKey[128]; + * json_object_iter_key(hIterator, sKey, sizeof(sKey)); + * + * new Handle:hValue = json_object_iter_value(hIterator); + * + * // Do something with sKey and hValue + * + * CloseHandle(hValue); + * + * hIterator = json_object_iter_next(hObj, hIterator); + * } + * + */ + +/** + * Returns a handle to an iterator which can be used to iterate over + * all key-value pairs in hObj. + * If you are not iterating to the end of hObj make sure to close the + * handle to the iterator manually. + * + * @param hObj Handle to JSON object to get an iterator + * for. + * + * @return Handle to JSON object iterator, + * or INVALID_HANDLE on error. + */ +native Handle:json_object_iter(Handle:hObj); + +/** + * Like json_object_iter(), but returns an iterator to the key-value + * pair in object whose key is equal to key. + * Iterating forward to the end of object only yields all key-value + * pairs of the object if key happens to be the first key in the + * underlying hash table. + * + * @param hObj Handle to JSON object to get an iterator + * for. + * @param sKey Start key for the iterator + * + * @return Handle to JSON object iterator, + * or INVALID_HANDLE on error. + */ +native Handle:json_object_iter_at(Handle:hObj, const String:key[]); + +/** + * Returns an iterator pointing to the next key-value pair in object. + * This automatically closes the Handle to the iterator hIter. + * + * @param hObj Handle to JSON object. + * @param hIter Handle to JSON object iterator. + * + * @return Handle to JSON object iterator, + * or INVALID_HANDLE on error, or if the + * whole object has been iterated through. + */ +native Handle:json_object_iter_next(Handle:hObj, Handle:hIter); + +/** + * Extracts the associated key of hIter as a null terminated UTF-8 + * encoded string in the passed buffer. + * + * @param hIter Handle to the JSON String object + * @param sKeyBuffer Buffer to store the value of the String. + * @param maxlength Maximum length of string buffer. + * @error Invalid JSON Object Iterator. + * @return Length of the returned string or -1 on error. + */ +native json_object_iter_key(Handle:hIter, String:sKeyBuffer[], maxlength); + +/** + * Returns a handle to the value hIter is pointing at. + * + * @param hIter Handle to JSON object iterator. + * + * @return Handle to value or INVALID_HANDLE on error. + */ +native Handle:json_object_iter_value(Handle:hIter); + +/** + * Set the value of the key-value pair in hObj, that is pointed to + * by hIter, to hValue. + * + * @param hObj Handle to JSON object. + * @param hIter Handle to JSON object iterator. + * @param hValue Handle to JSON value. + * + * @return True on success. + */ +native bool:json_object_iter_set(Handle:hObj, Handle:hIter, Handle:hValue); + +/** + * Set the value of the key-value pair in hObj, that is pointed to + * by hIter, to hValue. + * This function automatically closes the Handle to the value object. + * + * @param hObj Handle to JSON object. + * @param hIter Handle to JSON object iterator. + * @param hValue Handle to JSON value. + * + * @return True on success. + */ +native bool:json_object_iter_set_new(Handle:hObj, Handle:hIter, Handle:hValue); + + + + +/** + * Arrays + * + * A JSON array is an ordered collection of other JSON values. + * + */ + +/** + * Returns a handle to a new JSON array, or INVALID_HANDLE on error. + * + * @return Handle to the new JSON array + */ +native Handle:json_array(); + +/** + * Returns the number of elements in hArray + * + * @param hObj Handle to JSON array + * + * @return Number of elements in hArray, + * or 0 if hObj is not a JSON array. + */ +native json_array_size(Handle:hArray); + +/** + * Returns the element in hArray at position iIndex. + * + * @param hArray Handle to JSON array to get a value from + * @param iIndex Position to retrieve + * + * @return Handle to a the JSON object or + * INVALID_HANDLE on error. + */ +native Handle:json_array_get(Handle:hArray, iIndex); + +/** + * Replaces the element in array at position iIndex with hValue. + * The valid range for iIndex is from 0 to the return value of + * json_array_size() minus 1. + * + * @param hArray Handle to JSON array + * @param iIndex Position to replace + * @param hValue Value to store in the array + * + * @return True on success. + */ +native bool:json_array_set(Handle:hArray, iIndex, Handle:hValue); + +/** + * Replaces the element in array at position iIndex with hValue. + * The valid range for iIndex is from 0 to the return value of + * json_array_size() minus 1. + * This function automatically closes the Handle to the value object. + * + * @param hArray Handle to JSON array + * @param iIndex Position to replace + * @param hValue Value to store in the array + * + * @return True on success. + */ +native bool:json_array_set_new(Handle:hArray, iIndex, Handle:hValue); + +/** + * Appends value to the end of array, growing the size of array by 1. + * + * @param hArray Handle to JSON array + * @param hValue Value to append to the array + * + * @return True on success. + */ +native bool:json_array_append(Handle:hArray, Handle:hValue); + +/** + * Appends value to the end of array, growing the size of array by 1. + * This function automatically closes the Handle to the value object. + * + * @param hArray Handle to JSON array + * @param hValue Value to append to the array + * + * @return True on success. + */ +native bool:json_array_append_new(Handle:hArray, Handle:hValue); + +/** + * Inserts value to hArray at position iIndex, shifting the elements at + * iIndex and after it one position towards the end of the array. + * + * @param hArray Handle to JSON array + * @param iIndex Position to insert at + * @param hValue Value to store in the array + * + * @return True on success. + */ +native bool:json_array_insert(Handle:hArray, iIndex, Handle:hValue); + +/** + * Inserts value to hArray at position iIndex, shifting the elements at + * iIndex and after it one position towards the end of the array. + * This function automatically closes the Handle to the value object. + * + * @param hArray Handle to JSON array + * @param iIndex Position to insert at + * @param hValue Value to store in the array + * + * @return True on success. + */ +native bool:json_array_insert_new(Handle:hArray, iIndex, Handle:hValue); + +/** + * Removes the element in hArray at position iIndex, shifting the + * elements after iIndex one position towards the start of the array. + * + * @param hArray Handle to JSON array + * @param iIndex Position to insert at + * + * @return True on success. + */ +native bool:json_array_remove(Handle:hArray, iIndex); + +/** + * Removes all elements from hArray. + * + * @param hArray Handle to JSON array + * + * @return True on success. + */ +native bool:json_array_clear(Handle:hArray); + +/** + * Appends all elements in hOther to the end of hArray. + * + * @param hArray Handle to JSON array to be extended + * @param hOther Handle to JSON array, source to copy from + * + * @return True on success. + */ +native bool:json_array_extend(Handle:hArray, Handle:hOther); + + + + +/** + * Booleans & NULL + * + */ + +/** + * Returns a handle to a new JSON Boolean with value true, + * or INVALID_HANDLE on error. + * + * @return Handle to the new Boolean object + */ +native Handle:json_true(); + +/** + * Returns a handle to a new JSON Boolean with value false, + * or INVALID_HANDLE on error. + * + * @return Handle to the new Boolean object + */ +native Handle:json_false(); + +/** + * Returns a handle to a new JSON Boolean with the value passed + * in bState or INVALID_HANDLE on error. + * + * @param bState Value for the new Boolean object + * @return Handle to the new Boolean object + */ +native Handle:json_boolean(bool:bState); + +/** + * Returns a handle to a new JSON NULL or INVALID_HANDLE on error. + * + * @return Handle to the new NULL object + */ +native Handle:json_null(); + + + + +/** + * Strings + * + * Jansson uses UTF-8 as the character encoding. All JSON strings must + * be valid UTF-8 (or ASCII, as it�s a subset of UTF-8). Normal null + * terminated C strings are used, so JSON strings may not contain + * embedded null characters. + * + */ + +/** + * Returns a handle to a new JSON string, or INVALID_HANDLE on error. + * + * @param sValue Value for the new String object + * Must be a valid UTF-8 encoded Unicode string. + * @return Handle to the new String object + */ +native Handle:json_string(const String:sValue[]); + +/** + * Saves the associated value of hString as a null terminated UTF-8 + * encoded string in the passed buffer. + * + * @param hString Handle to the JSON String object + * @param sValueBuffer Buffer to store the value of the String. + * @param maxlength Maximum length of string buffer. + * @error Invalid JSON String Object. + * @return Length of the returned string or -1 on error. + */ +native json_string_value(Handle:hString, String:sValueBuffer[], maxlength); + +/** + * Sets the associated value of JSON String object to value. + * + * @param hString Handle to the JSON String object + * @param sValue Value to set the object to. + * Must be a valid UTF-8 encoded Unicode string. + * @error Invalid JSON String Object. + * @return True on success. + */ +native bool:json_string_set(Handle:hString, String:sValue[]); + + + + +/** + * Numbers + * + * The JSON specification only contains one numeric type, 'number'. + * The C (and Pawn) programming language has distinct types for integer + * and floating-point numbers, so for practical reasons Jansson also has + * distinct types for the two. They are called 'integer' and 'real', + * respectively. (Whereas 'real' is a 'Float' for Pawn). + * Therefore a number is represented by either a value of the type + * JSON_INTEGER or of the type JSON_REAL. + * + */ + +/** + * Returns a handle to a new JSON integer, or INVALID_HANDLE on error. + * + * @param iValue Value for the new Integer object + * @return Handle to the new Integer object + */ +native Handle:json_integer(iValue); + +/** + * Returns the associated value of a JSON Integer Object. + * + * @param hInteger Handle to the JSON Integer object + * @error Invalid JSON Integer Object. + * @return Value of the hInteger, + * or 0 if hInteger is not a JSON integer. + */ +native json_integer_value(Handle:hInteger); + +/** + * Sets the associated value of JSON Integer to value. + * + * @param hInteger Handle to the JSON Integer object + * @param iValue Value to set the object to. + * @error Invalid JSON Integer Object. + * @return True on success. + */ +native bool:json_integer_set(Handle:hInteger, iValue); + +/** + * Returns a handle to a new JSON real, or INVALID_HANDLE on error. + * + * @param fValue Value for the new Real object + * @return Handle to the new String object + */ +native Handle:json_real(Float:fValue); + +/** + * Returns the associated value of a JSON Real. + * + * @param hReal Handle to the JSON Real object + * @error Invalid JSON Real Object. + * @return Float value of hReal, + * or 0.0 if hReal is not a JSON Real. + */ +native Float:json_real_value(Handle:hReal); + +/** + * Sets the associated value of JSON Real to fValue. + * + * @param hReal Handle to the JSON Integer object + * @param fValue Value to set the object to. + * @error Invalid JSON Real handle. + * @return True on success. + */ +native bool:json_real_set(Handle:hReal, Float:value); + +/** + * Returns the associated value of a JSON integer or a + * JSON Real, cast to Float regardless of the actual type. + * + * @param hNumber Handle to the JSON Number + * @error Not a JSON Real or JSON Integer + * @return Float value of hNumber, + * or 0.0 on error. + */ +native Float:json_number_value(Handle:hNumber); + + + + +/** + * Decoding + * + * This sections describes the functions that can be used to decode JSON text + * to the Jansson representation of JSON data. The JSON specification requires + * that a JSON text is either a serialized array or object, and this + * requirement is also enforced with the following functions. In other words, + * the top level value in the JSON text being decoded must be either array or + * object. + * + */ + +/** + * Decodes the JSON string sJSON and returns the array or object it contains. + * Errors while decoding can be found in the sourcemod error log. + * + * @param sJSON String containing valid JSON + + * @return Handle to JSON object or array. + * or INVALID_HANDLE on error. + */ +native Handle:json_load(const String:sJSON[]); + +/** + * Decodes the JSON string sJSON and returns the array or object it contains. + * This function provides additional error feedback and does not log errors + * to the sourcemod error log. + * + * @param sJSON String containing valid JSON + * @param sErrorText This buffer will be filled with the error + * message. + * @param maxlen Size of the buffer + * @param iLine This int will contain the line of the error + * @param iColumn This int will contain the column of the error + * + * @return Handle to JSON object or array. + * or INVALID_HANDLE on error. + */ +native Handle:json_load_ex(const String:sJSON[], String:sErrorText[], maxlen, &iLine, &iColumn); + +/** + * Decodes the JSON text in file sFilePath and returns the array or object + * it contains. + * Errors while decoding can be found in the sourcemod error log. + * + * @param sFilePath Path to a file containing pure JSON + * + * @return Handle to JSON object or array. + * or INVALID_HANDLE on error. + */ +native Handle:json_load_file(const String:sFilePath[PLATFORM_MAX_PATH]); + +/** + * Decodes the JSON text in file sFilePath and returns the array or object + * it contains. + * This function provides additional error feedback and does not log errors + * to the sourcemod error log. + * + * @param sFilePath Path to a file containing pure JSON + * @param sErrorText This buffer will be filled with the error + * message. + * @param maxlen Size of the buffer + * @param iLine This int will contain the line of the error + * @param iColumn This int will contain the column of the error + * + * @return Handle to JSON object or array. + * or INVALID_HANDLE on error. + */ +native Handle:json_load_file_ex(const String:sFilePath[PLATFORM_MAX_PATH], String:sErrorText[], maxlen, &iLine, &iColumn); + + + +/** + * Encoding + * + * This sections describes the functions that can be used to encode values + * to JSON. By default, only objects and arrays can be encoded directly, + * since they are the only valid root values of a JSON text. + * + */ + +/** + * Saves the JSON representation of hObject in sJSON. + * + * @param hObject String containing valid JSON + * @param sJSON Buffer to store the created JSON string. + * @param maxlength Maximum length of string buffer. + * @param iIndentWidth Indenting with iIndentWidth spaces. + * The valid range for this is between 0 and 31 (inclusive), + * other values result in an undefined output. If this is set + * to 0, no newlines are inserted between array and object items. + * @param bEnsureAscii If this is set, the output is guaranteed + * to consist only of ASCII characters. This is achieved + * by escaping all Unicode characters outside the ASCII range. + * @param bSortKeys If this flag is used, all the objects in output are sorted + * by key. This is useful e.g. if two JSON texts are diffed + * or visually compared. + * @param bPreserveOrder If this flag is used, object keys in the output are sorted + * into the same order in which they were first inserted to + * the object. For example, decoding a JSON text and then + * encoding with this flag preserves the order of object keys. + * @return Length of the returned string or -1 on error. + */ +native json_dump(Handle:hObject, String:sJSON[], maxlength, iIndentWidth = 4, bool:bEnsureAscii = false, bool:bSortKeys = false, bool:bPreserveOrder = false); + +/** + * Write the JSON representation of hObject to the file sFilePath. + * If sFilePath already exists, it is overwritten. + * + * @param hObject String containing valid JSON + * @param sFilePath Buffer to store the created JSON string. + * @param iIndentWidth Indenting with iIndentWidth spaces. + * The valid range for this is between 0 and 31 (inclusive), + * other values result in an undefined output. If this is set + * to 0, no newlines are inserted between array and object items. + * @param bEnsureAscii If this is set, the output is guaranteed + * to consist only of ASCII characters. This is achieved + * by escaping all Unicode characters outside the ASCII range. + * @param bSortKeys If this flag is used, all the objects in output are sorted + * by key. This is useful e.g. if two JSON texts are diffed + * or visually compared. + * @param bPreserveOrder If this flag is used, object keys in the output are sorted + * into the same order in which they were first inserted to + * the object. For example, decoding a JSON text and then + * encoding with this flag preserves the order of object keys. + * @return Length of the returned string or -1 on error. + */ +native bool:json_dump_file(Handle:hObject, const String:sFilePath[], iIndentWidth = 4, bool:bEnsureAscii = false, bool:bSortKeys = false, bool:bPreserveOrder = false); + + + +/** + * Convenience stocks + * + * These are some custom functions to ease the development using this + * extension. + * + */ + +/** + * Returns a handle to a new JSON string, or INVALID_HANDLE on error. + * Formats the string according to the SourceMod format rules. + * The result must be a valid UTF-8 encoded Unicode string. + * + * @param sFormat Formatting rules. + * @param ... Variable number of format parameters. + * @return Handle to the new String object + */ +stock Handle:json_string_format(const String:sFormat[], any:...) { + new String:sTmp[4096]; + VFormat(sTmp, sizeof(sTmp), sFormat, 2); + + return json_string(sTmp); +} + +/** + * Returns a handle to a new JSON string, or INVALID_HANDLE on error. + * This stock allows to specify the size of the temporary buffer used + * to create the string. Use this if the default of 4096 is not enough + * for your string. + * Formats the string according to the SourceMod format rules. + * The result must be a valid UTF-8 encoded Unicode string. + * + * @param tmpBufferLength Size of the temporary buffer + * @param sFormat Formatting rules. + * @param ... Variable number of format parameters. + * @return Handle to the new String object + */ +stock Handle:json_string_format_ex(tmpBufferLength, const String:sFormat[], any:...) { + new String:sTmp[tmpBufferLength]; + VFormat(sTmp, sizeof(sTmp), sFormat, 3); + + return json_string(sTmp); +} + + +/** + * Returns the boolean value of the element in hArray at position iIndex. + * + * @param hArray Handle to JSON array to get a value from + * @param iIndex Position to retrieve + * + * @return True if it's a boolean and TRUE, + * false otherwise. + */ +stock bool:json_array_get_bool(Handle:hArray, iIndex) { + new Handle:hElement = json_array_get(hArray, iIndex); + + new bool:bResult = (json_is_true(hElement) ? true : false); + + CloseHandle(hElement); + return bResult; +} + +/** + * Returns the float value of the element in hArray at position iIndex. + * + * @param hArray Handle to JSON array to get a value from + * @param iIndex Position to retrieve + * + * @return Float value, + * or 0.0 if element is not a JSON Real. + */ +stock Float:json_array_get_float(Handle:hArray, iIndex) { + new Handle:hElement = json_array_get(hArray, iIndex); + + new Float:fResult = (json_is_number(hElement) ? json_number_value(hElement) : 0.0); + + CloseHandle(hElement); + return fResult; +} + +/** + * Returns the integer value of the element in hArray at position iIndex. + * + * @param hArray Handle to JSON array to get a value from + * @param iIndex Position to retrieve + * + * @return Integer value, + * or 0 if element is not a JSON Integer. + */ +stock json_array_get_int(Handle:hArray, iIndex) { + new Handle:hElement = json_array_get(hArray, iIndex); + + new iResult = (json_is_integer(hElement) ? json_integer_value(hElement) : 0); + + CloseHandle(hElement); + return iResult; +} + +/** + * Saves the associated value of the element in hArray at position iIndex + * as a null terminated UTF-8 encoded string in the passed buffer. + * + * @param hArray Handle to JSON array to get a value from + * @param iIndex Position to retrieve + * @param sBuffer Buffer to store the value of the String. + * @param maxlength Maximum length of string buffer. + * + * @error Element is not a JSON String. + * @return Length of the returned string or -1 on error. + */ +stock json_array_get_string(Handle:hArray, iIndex, String:sBuffer[], maxlength) { + new Handle:hElement = json_array_get(hArray, iIndex); + + new iResult = -1; + if(json_is_string(hElement)) { + iResult = json_string_value(hElement, sBuffer, maxlength); + } + CloseHandle(hElement); + + return iResult; +} + +/** + * Returns the boolean value of the element in hObj at entry sKey. + * + * @param hObj Handle to JSON object to get a value from + * @param sKey Entry to retrieve + * + * @return True if it's a boolean and TRUE, + * false otherwise. + */ +stock bool:json_object_get_bool(Handle:hObj, const String:sKey[]) { + new Handle:hElement = json_object_get(hObj, sKey); + + new bool:bResult = (json_is_true(hElement) ? true : false); + + CloseHandle(hElement); + return bResult; +} + +/** + * Returns the float value of the element in hObj at entry sKey. + * + * @param hObj Handle to JSON object to get a value from + * @param sKey Position to retrieve + * + * @return Float value, + * or 0.0 if element is not a JSON Real. + */ +stock Float:json_object_get_float(Handle:hObj, const String:sKey[]) { + new Handle:hElement = json_object_get(hObj, sKey); + + new Float:fResult = (json_is_number(hElement) ? json_number_value(hElement) : 0.0); + + CloseHandle(hElement); + return fResult; +} + +/** + * Returns the integer value of the element in hObj at entry sKey. + * + * @param hObj Handle to JSON object to get a value from + * @param sKey Position to retrieve + * + * @return Integer value, + * or 0 if element is not a JSON Integer. + */ +stock json_object_get_int(Handle:hObj, const String:sKey[]) { + new Handle:hElement = json_object_get(hObj, sKey); + + new iResult = (json_is_integer(hElement) ? json_integer_value(hElement) : 0); + + CloseHandle(hElement); + return iResult; +} + +/** + * Saves the associated value of the element in hObj at entry sKey + * as a null terminated UTF-8 encoded string in the passed buffer. + * + * @param hObj Handle to JSON object to get a value from + * @param sKey Entry to retrieve + * @param sBuffer Buffer to store the value of the String. + * @param maxlength Maximum length of string buffer. + * + * @error Element is not a JSON String. + * @return Length of the returned string or -1 on error. + */ +stock json_object_get_string(Handle:hObj, const String:sKey[], String:sBuffer[], maxlength) { + new Handle:hElement = json_object_get(hObj, sKey); + + new iResult = -1; + if(json_is_string(hElement)) { + iResult = json_string_value(hElement, sBuffer, maxlength); + } + CloseHandle(hElement); + + return iResult; +} + + + +/** + * Pack String Rules + * + * Here�s the full list of format characters: + * n Output a JSON null value. No argument is consumed. + * s Output a JSON string, consuming one argument. + * b Output a JSON bool value, consuming one argument. + * i Output a JSON integer value, consuming one argument. + * f Output a JSON real value, consuming one argument. + * r Output a JSON real value, consuming one argument. + * [] Build an array with contents from the inner format string, + * recursive value building is supported. + * No argument is consumed. + * {} Build an array with contents from the inner format string. + * The first, third, etc. format character represent a key, + * and must be s (as object keys are always strings). The + * second, fourth, etc. format character represent a value. + * Recursive value building is supported. + * No argument is consumed. + * + */ + +/** + * This method can be used to create json objects/arrays directly + * without having to create the structure. + * See 'Pack String Rules' for more details. + * + * @param sPackString Pack string similiar to Format()s fmt. + * See 'Pack String Rules'. + * @param hParams ADT Array containing all keys and values + * in the order they appear in the pack string. + * + * @error Invalid pack string or pack string and + * ADT Array don't match up regarding type + * or size. + * @return Handle to JSON element. + */ +stock Handle:json_pack(const String:sPackString[], Handle:hParams) { + new iPos = 0; + return json_pack_element_(sPackString, iPos, hParams); +} + + + + + +/** +* Internal stocks used by json_pack(). Don't use these directly! +* +*/ +stock Handle:json_pack_array_(const String:sFormat[], &iPos, Handle:hParams) { + new Handle:hObj = json_array(); + new iStrLen = strlen(sFormat); + for(; iPos < iStrLen;) { + new this_char = sFormat[iPos]; + + if(this_char == 32 || this_char == 58 || this_char == 44) { + // Skip whitespace, ',' and ':' + iPos++; + continue; + } + + if(this_char == 93) { + // array end + iPos++; + break; + } + + // Get the next entry as value + // This automatically increments the position! + new Handle:hValue = json_pack_element_(sFormat, iPos, hParams); + + // Append the value to the array. + json_array_append_new(hObj, hValue); + } + + return hObj; +} + +stock Handle:json_pack_object_(const String:sFormat[], &iPos, Handle:hParams) { + new Handle:hObj = json_object(); + new iStrLen = strlen(sFormat); + for(; iPos < iStrLen;) { + new this_char = sFormat[iPos]; + + if(this_char == 32 || this_char == 58 || this_char == 44) { + // Skip whitespace, ',' and ':' + iPos++; + continue; + } + + if(this_char == 125) { + // } --> object end + iPos++; + break; + } + + if(this_char != 115) { + LogError("Object keys must be strings at %d.", iPos); + return INVALID_HANDLE; + } + + // Get the key string for this object from + // the hParams array. + decl String:sKey[255]; + GetArrayString(hParams, 0, sKey, sizeof(sKey)); + RemoveFromArray(hParams, 0); + + // Advance one character in the pack string, + // because we've just read the Key string for + // this object. + iPos++; + + // Get the next entry as value + // This automatically increments the position! + new Handle:hValue = json_pack_element_(sFormat, iPos, hParams); + + // Insert into object + json_object_set_new(hObj, sKey, hValue); + } + + return hObj; +} + +stock Handle:json_pack_element_(const String:sFormat[], &iPos, Handle:hParams) { + new this_char = sFormat[iPos]; + while(this_char == 32 || this_char == 58 || this_char == 44) { + iPos++; + this_char = sFormat[iPos]; + } + + // Advance one character in the pack string + iPos++; + + switch(this_char) { + case 91: { + // { --> Array + return json_pack_array_(sFormat, iPos, hParams); + } + + case 123: { + // { --> Object + return json_pack_object_(sFormat, iPos, hParams); + + } + + case 98: { + // b --> Boolean + new iValue = GetArrayCell(hParams, 0); + RemoveFromArray(hParams, 0); + + return json_boolean(bool:iValue); + } + + case 102, 114: { + // r,f --> Real (Float) + new Float:iValue = GetArrayCell(hParams, 0); + RemoveFromArray(hParams, 0); + + return json_real(iValue); + } + + case 110: { + // n --> NULL + return json_null(); + } + + case 115: { + // s --> String + decl String:sKey[255]; + GetArrayString(hParams, 0, sKey, sizeof(sKey)); + RemoveFromArray(hParams, 0); + + return json_string(sKey); + } + + case 105: { + // i --> Integer + new iValue = GetArrayCell(hParams, 0); + RemoveFromArray(hParams, 0); + + return json_integer(iValue); + } + } + + SetFailState("Invalid pack String '%s'. Type '%s' not supported at %i", sFormat, this_char, iPos); + return json_null(); +} + + + + + +/** + * Not yet implemented + * + * native json_object_foreach(Handle:hObj, ForEachCallback:cb); + * native Handle:json_unpack(const String:sFormat[], ...); + * + */ + + + + + + +/** + * Do not edit below this line! + */ +public Extension:__ext_smjansson = +{ + name = "SMJansson", + file = "smjansson.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_smjansson_SetNTVOptional() +{ + MarkNativeAsOptional("json_typeof"); + MarkNativeAsOptional("json_equal"); + + MarkNativeAsOptional("json_copy"); + MarkNativeAsOptional("json_deep_copy"); + + MarkNativeAsOptional("json_object"); + MarkNativeAsOptional("json_object_size"); + MarkNativeAsOptional("json_object_get"); + MarkNativeAsOptional("json_object_set"); + MarkNativeAsOptional("json_object_set_new"); + MarkNativeAsOptional("json_object_del"); + MarkNativeAsOptional("json_object_clear"); + MarkNativeAsOptional("json_object_update"); + MarkNativeAsOptional("json_object_update_existing"); + MarkNativeAsOptional("json_object_update_missing"); + + MarkNativeAsOptional("json_object_iter"); + MarkNativeAsOptional("json_object_iter_at"); + MarkNativeAsOptional("json_object_iter_next"); + MarkNativeAsOptional("json_object_iter_key"); + MarkNativeAsOptional("json_object_iter_value"); + MarkNativeAsOptional("json_object_iter_set"); + MarkNativeAsOptional("json_object_iter_set_new"); + + MarkNativeAsOptional("json_array"); + MarkNativeAsOptional("json_array_size"); + MarkNativeAsOptional("json_array_get"); + MarkNativeAsOptional("json_array_set"); + MarkNativeAsOptional("json_array_set_new"); + MarkNativeAsOptional("json_array_append"); + MarkNativeAsOptional("json_array_append_new"); + MarkNativeAsOptional("json_array_insert"); + MarkNativeAsOptional("json_array_insert_new"); + MarkNativeAsOptional("json_array_remove"); + MarkNativeAsOptional("json_array_clear"); + MarkNativeAsOptional("json_array_extend"); + + MarkNativeAsOptional("json_string"); + MarkNativeAsOptional("json_string_value"); + MarkNativeAsOptional("json_string_set"); + + MarkNativeAsOptional("json_integer"); + MarkNativeAsOptional("json_integer_value"); + MarkNativeAsOptional("json_integer_set"); + + MarkNativeAsOptional("json_real"); + MarkNativeAsOptional("json_real_value"); + MarkNativeAsOptional("json_real_set"); + MarkNativeAsOptional("json_number_value"); + + MarkNativeAsOptional("json_boolean"); + MarkNativeAsOptional("json_true"); + MarkNativeAsOptional("json_false"); + MarkNativeAsOptional("json_null"); + + MarkNativeAsOptional("json_load"); + MarkNativeAsOptional("json_load_file"); + + MarkNativeAsOptional("json_dump"); + MarkNativeAsOptional("json_dump_file"); +} +#endif \ No newline at end of file diff --git a/Discord-Utilities.phrases.txt b/translations/Discord-Utilities.phrases.txt similarity index 93% rename from Discord-Utilities.phrases.txt rename to translations/Discord-Utilities.phrases.txt index 51fe04a..1983ecf 100644 --- a/Discord-Utilities.phrases.txt +++ b/translations/Discord-Utilities.phrases.txt @@ -183,26 +183,15 @@ "LinkUsage" { - "#format" "{1:s},{2:s}" - "en" "Use {yellow}{1} {2}" - "hu" "Használd a {yellow}{1} {2}" - "fr" "Utiliser {yellow}{1} {2}" - "nl" "Gebruik {yellow}{1} {2}" - "lv" "Izmanto {yellow}{1} {2}" - "ru" "использование {yellow}{1} {2}" - "ro" "Folosește {yellow}{1} {2}" + "#format" "{1:s},{2:s},{3:s}" + "en" "Type {lime}{1} {2} {default}in {orange}#{3}" + "hu" "Használd a {lime}{1} {2} {default}parancsot a {orange}#{3} {default}szobában" } - - "LinkUsage2" + + "CopyPasteFromConsole" { - "#format" "{1:s}" - "en" "in channel {purple}#{1} {default}to get verified!" - "hu" "parancsot a {purple}#{1} {default}szobában a megerősítéshez!" - "fr" "dans le canal {purple}#{1} {default}pour se faire vérifier!" - "nl" "in kanaal {purple}#{1} {default}om geverifieerd te worden!" - "lv" "verifikācijas kodu kanālā {purple}#{1} {default}lai verificētu profilu!" - "ru" "в канале {purple}#{1} {default}пройти проверку!" - "ro" "În chanalul {purple}#{1} {default}pentru a te verifica!" + "en" "You can also {lime}copy-paste{default} from {orange}console{default} :)" + "hu" "Ki is {lime}másolhatod {default}a {orange}konzolból{default} :)" } "CallAdminReportHandledTitle" @@ -475,4 +464,20 @@ "ru" "Вы должны подтвердить свой {blue}Discord {default}учетная запись! Тип {orange}{1}" "ro" "Trebuie să îti asociezi Discord-ul tău! Tastează {orange}{1}" } + + "AlreadyVerified" + { + "en" "You are already verified. Enjoy your benefits :)" + } + + "CanChange" + { + "#format" "{1:s},{2:s}" + "en" "You can change your verified discord account using {LIME}{1}{DEFAULT} and {LIME}{2}{DEFAULT} again." + } + + "SuccessfullyUnlink" + { + "en" "You successfully unlinked your account." + } }