Files
SMScripts/scripting/dcr-server.sp
2025-04-15 22:27:20 -04:00

363 lines
9.2 KiB
SourcePawn

#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <discord>
#include <socket>
#include <chatcolors>
#undef REQUIRE_PLUGIN
#include <chat-processor>
#pragma newdecls required
// Socket Handles
Handle gH_Socket = INVALID_HANDLE;
// Clients
ArrayList gAL_Clients;
// Discord Bot
DiscordBot gDB_Bot = view_as<DiscordBot>(INVALID_HANDLE);
// ConVars
ConVar gCV_DiscordChatChannel;
ConVar gCV_DiscordEnable;
ConVar gCV_SocketEnable;
ConVar gCV_SocketPort;
ConVar gCV_ServerMessageTag;
ConVar gCV_DiscordMessageTag;
char gS_BotToken[128];
char gS_ServerTag[128];
char gS_DiscordTag[128];
bool gB_ChatProcessor;
bool gB_ListeningToDiscord = false;
public Plugin myinfo =
{
name = "Discord Chat Relay - Server",
author = "PaxPlay, Credits to Ryan \"FLOOR_MASTER\" Mannion, shavit and Deathknife",
description = "Chat relay between a sourcemod server and Discord.",
version = "1.0.0",
url = ""
};
public void OnPluginStart()
{
gCV_DiscordChatChannel = CreateConVar("sm_discord_text_channel_id", "", "The Discord text channel id.");
gCV_DiscordEnable = CreateConVar("sm_discord_relay_enable", "1", "Enable the chat relay with Discord.", 0, true, 0.0, true, 1.0);
gCV_SocketEnable = CreateConVar("sm_discord_socket_enable", "0", "Enable the cross server chat relay.", 0, true, 0.0, true, 1.0);
gCV_SocketPort = CreateConVar("sm_discord_socket_port", "13370", "Port for the cross server chat relay socket.");
gCV_ServerMessageTag = CreateConVar("sm_discord_chat_prefix_server", "{grey}[{green}SERVER{grey}]", "Chat Tag for messages from the server.");
gCV_DiscordMessageTag = CreateConVar("sm_discord_chat_prefix_discord", "{grey}[{blue}DISCORD{grey}]", "Chat Tag for messages from discord.");
gCV_ServerMessageTag.AddChangeHook(OnConVarChanged);
gCV_DiscordMessageTag.AddChangeHook(OnConVarChanged);
AutoExecConfig(true, "discord-chat-relay-server", "sourcemod");
gB_ChatProcessor = LibraryExists("chat-processor");
}
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
UpdateCvars();
}
void UpdateCvars()
{
char buffer[128];
gCV_ServerMessageTag.GetString(buffer, sizeof(buffer));
SCC_ReplaceColors(buffer, sizeof(buffer));
FormatEx(gS_ServerTag, sizeof(gS_ServerTag), "%s", buffer); // Update Gameserver Chat Tag
gCV_DiscordMessageTag.GetString(buffer, sizeof(buffer));
SCC_ReplaceColors(buffer, sizeof(buffer));
FormatEx(gS_DiscordTag, sizeof(gS_DiscordTag), "%s", buffer); // Update Discord Chat Tag
}
bool LoadConfig()
{
char[] sPath = new char[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "configs/dcr.cfg");
if (!FileExists(sPath))
{
File hFile = OpenFile(sPath, "w");
WriteFileLine(hFile, "\"discord-chat-relay\"");
WriteFileLine(hFile, "{");
WriteFileLine(hFile, "\t\"bot-token\"\t\"<insert-bot-token>\"");
WriteFileLine(hFile, "}");
CloseHandle(hFile);
LogError("[DCR] \"%s\" not found, creating!", sPath);
return false;
}
KeyValues kv = new KeyValues("discord-chat-relay");
if(!kv.ImportFromFile(sPath))
{
delete kv;
LogError("[DCR] Couldnt import KeyValues from \"%s\"!", sPath);
return false;
}
kv.GetString("bot-token", gS_BotToken, sizeof(gS_BotToken));
LogMessage("Loaded the BotToken.");
delete kv;
return true;
}
public void OnAllPluginsLoaded()
{
if(gDB_Bot != view_as<DiscordBot>(INVALID_HANDLE))
return;
if (LoadConfig())
gDB_Bot = new DiscordBot(gS_BotToken);
else
LogError("Couldnt load the dcr config.");
}
public void OnConfigsExecuted()
{
UpdateCvars();
if (gCV_DiscordEnable.BoolValue && gDB_Bot != view_as<DiscordBot>(INVALID_HANDLE))
{
if(!gB_ListeningToDiscord)
gDB_Bot.GetGuilds(GuildList, INVALID_FUNCTION);
}
if(gCV_SocketEnable.BoolValue && gH_Socket == INVALID_HANDLE)
{
char ip[24];
int port = gCV_SocketPort.IntValue;
GetServerIP(ip, sizeof(ip));
gH_Socket = SocketCreate(SOCKET_TCP, OnSocketError);
SocketBind(gH_Socket, ip, port);
SocketListen(gH_Socket, OnSocketIncoming);
gAL_Clients = CreateArray();
LogMessage("%s chat-processor. DCR %s be able to send messages.", gB_ChatProcessor ? "Found" : "Couldn\'t find", gB_ChatProcessor ? "will" : "won\'t");
LogMessage("[DCR] Started Server Chat Relay server on port %d", port);
}
}
public void GuildList(DiscordBot bot, char[] id, char[] name, char[] icon, bool owner, int permissions, any data)
{
gDB_Bot.GetGuildChannels(id, ChannelList, INVALID_FUNCTION);
}
public void ChannelList(DiscordBot bot, char[] guild, DiscordChannel Channel, any data)
{
if(Channel.IsText) {
char id[32];
Channel.GetID(id, sizeof(id));
char sChannelID[64];
gCV_DiscordChatChannel.GetString(sChannelID, sizeof(sChannelID));
if(StrEqual(id, sChannelID) && !gB_ListeningToDiscord)
{
gDB_Bot.StopListening();
char name[32];
Channel.GetName(name, sizeof(name));
gDB_Bot.StartListeningToChannel(Channel, OnMessage);
LogMessage("[DCR] Started listening to channel %s (%s)", name, id);
gB_ListeningToDiscord = true;
}
}
}
public void OnMessage(DiscordBot Bot, DiscordChannel Channel, DiscordMessage message)
{
if (message.GetAuthor().IsBot())
return;
char sMessage[2048];
message.GetContent(sMessage, sizeof(sMessage));
char sAuthor[128];
message.GetAuthor().GetUsername(sAuthor, sizeof(sAuthor));
Format(sMessage, sizeof(sMessage), "%s \x01%s: %s", gS_DiscordTag, sAuthor, sMessage);
PrintToChatAll(" %s", sMessage);
Broadcast(INVALID_HANDLE, sMessage, sizeof(sMessage));
}
public void CP_OnChatMessagePost(int author, ArrayList recipients, const char[] flagstring, const char[] formatstring, const char[] name, const char[] message, bool processcolors, bool removecolors)
{
if(message[0] == '!' || message[1] == '!') // remove chat commands
return;
char sMessage[512];
Format(sMessage, sizeof(sMessage), "%s %s: %s", gS_ServerTag, name, message);
Broadcast(INVALID_HANDLE, sMessage, sizeof(sMessage));
SendToDiscord(sMessage ,sizeof(sMessage));
}
public void OnMessageSent(DiscordBot bot, char[] channel, DiscordMessage message, any data)
{
char sMessage[2048];
message.GetContent(sMessage, sizeof(sMessage));
LogMessage("[SM] Message sent to discord: \"%s\".", sMessage);
}
void GetServerIP(char[] ip, int length)
{
int hostip = FindConVar("hostip").IntValue;
Format(ip, length, "%d.%d.%d.%d",
(hostip >> 24 & 0xFF),
(hostip >> 16 & 0xFF),
(hostip >> 8 & 0xFF),
(hostip & 0xFF)
);
}
bool Broadcast(Handle socket, const char[] message, int maxlength)
{
if(!gCV_SocketEnable.BoolValue)
return false;
if(gAL_Clients == INVALID_HANDLE)
{
LogError("In Broadcast, gAL_Clients was invalid. This should never happen!");
return false;
}
int size = gAL_Clients.Length;
Handle dest_socket = INVALID_HANDLE;
for (int i = 0; i < size; i++)
{
dest_socket = gAL_Clients.Get(i);
if (dest_socket != socket) // Prevent sending back to the same server.
{
SocketSend(dest_socket, message, maxlength);
}
}
if (socket != INVALID_HANDLE) // Prevent printing to the server chat, if message is by the server.
{
PrintToChatAll(" %s", message);
}
return true;
}
void CloseSocket()
{
if(gH_Socket != INVALID_HANDLE)
{
CloseHandle(gH_Socket);
gH_Socket = INVALID_HANDLE;
LogMessage("Closed local Server Chat Relay socket");
}
if(gAL_Clients != INVALID_HANDLE)
{
CloseHandle(gAL_Clients);
}
}
void RemoveClient(Handle client)
{
if (gAL_Clients == INVALID_HANDLE)
{
LogError("Attempted to remove client while g_clients was invalid. This should never happen!");
return;
}
int size = gAL_Clients.Length;
for (int i = 0; i < size; i++)
{
if(gAL_Clients.Get(i) == client)
{
gAL_Clients.Erase(i);
return;
}
}
LogError("Could not find client in RemoveClient. This should never happen!");
}
public int OnSocketIncoming(Handle socket, Handle newSocket, char[] remoteIP, int remotePort, any arg)
{
if (gAL_Clients == INVALID_HANDLE) {
LogError("In OnSocketIncoming, gAL_Clients was invalid. This should never happen!");
}
else {
PushArrayCell(gAL_Clients, newSocket);
}
SocketSetReceiveCallback(newSocket, OnChildSocketReceive);
SocketSetDisconnectCallback(newSocket, OnChildSocketDisconnected);
SocketSetErrorCallback(newSocket, OnChildSocketError);
}
public int OnSocketDisconnected(Handle socket, any arg)
{
CloseSocket();
}
public int OnSocketError(Handle socket, const int errorType, const int errorNum, any arg)
{
LogError("Socket error %d (errno %d)", errorType, errorNum);
CloseSocket();
}
public int OnChildSocketReceive(Handle socket, char[] receiveData, const int dataSize, any arg)
{
Broadcast(socket, receiveData, dataSize);
SendToDiscord(receiveData, dataSize);
}
public int OnChildSocketDisconnected(Handle socket, any arg) {
RemoveClient(socket);
CloseHandle(socket);
}
public int OnChildSocketError(Handle socket, const int errorType, const int errorNum, any arg)
{
LogError("Child socket error %d (errno %d)", errorType, errorNum);
RemoveClient(socket);
CloseHandle(socket);
}
void SendToDiscord(const char[] message, int maxlength)
{
char[] sMessage = new char[maxlength];
FormatEx(sMessage, maxlength, "%s", message);
SCC_RemoveColors(sMessage, maxlength);
char sChannelID[64];
gCV_DiscordChatChannel.GetString(sChannelID, sizeof(sChannelID));
gDB_Bot.SendMessageToChannelID(sChannelID, sMessage, OnMessageSent);
}