diff --git a/configs/sourceirc.cfg b/configs/sourceirc.cfg new file mode 100755 index 0000000..76d1873 --- /dev/null +++ b/configs/sourceirc.cfg @@ -0,0 +1,64 @@ +"SourceIRC" +{ + "Access" + { + "Hostmasks" + { + // Put hostmasks followed by SourceMod admin permissions here, Just like you would in IRC, * and ? wildcards are supported. + "Yournick!YourIdent@yourdomain.com" "z" + } + } + "Server" + { + "server" "irc.gamesurge.net" // Server to connect to + "port" "6667" + "nickname" "SourceIRC" + "realname" "SourceIRC" + "channels" + { + "#sourceirc" + { + "relay" "1" // Tell the RelayAll module to relay messages to this channel + "cmd_prefix" "!" // Ontop of calling the bot by name, you can also use "!" in this channel + } + "#sourceirc-admin" + { + "ticket" "1" // Tell the ticket module to send tickets to this channel + } + } + } + "Settings" + { + "msg-rate" "1.0" // Seconds to wait between sending messages, set to 0 to disable. Setting this too low can cause the bot to be kicked from the server for "Excess flood". + // Team colors, use -1 to set no color. + "teamcolor-0" "15" // Unassigned + "teamcolor-1" "15" // Spectators + "teamcolor-2" "04" // Red / Terrorist + "teamcolor-3" "12" // Blue / Counter Terrorist + "server-domain" "" // Servers domain/external IP. Leave blank for autodetect. Good if your server is running behind a router and has a local IP or you use a domain. This is only used for cosmetic purposes. + } + "Ticket" + { + "Menu" + { + "Abusive" "Abusive" + "Racism" "Racism" + "General cheating/exploits" "General cheating/exploits" + "Wallhack" "Wallhack" + "Aimbot" "Aimbot" + "Speedhacking" "Speedhacking" + "Mic spamming" "Mic spamming" + "Admin disrepect" "Admin disrepect" + "Camping" "Camping" + "Team killing" "Team killing" + "Unacceptable Spray" "Unacceptable Spray" + "Breaking Server Rules" "Breaking Server Rules" + "Other" "Other" + } + "Settings" + { + "spray_url" "" // If you have a URL where sprays can be downloaded, put it here and the bot will link to it in bad spray reports. Use {SPRAY} to insert the ID of the spray that's being reported. Leave blank to disable. + "custom_msg" "" // Send this message when a ticket is submitted, good for mass highlighting everyone when a ticket is received. + } + } +} diff --git a/scripting/SourceIRC/sourceirc-ban.sp b/scripting/SourceIRC/sourceirc-ban.sp new file mode 100644 index 0000000..ed3dcac --- /dev/null +++ b/scripting/SourceIRC/sourceirc-ban.sp @@ -0,0 +1,133 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#undef REQUIRE_PLUGIN +#include + +#pragma semicolon 1 +#pragma dynamic 65535 + + +public Plugin:myinfo = { + name = "SourceIRC -> Ban", + author = "Azelphur", + description = "Adds ban command to SourceIRC", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +public OnPluginStart() { + LoadTranslations("common.phrases"); + LoadTranslations("plugin.basecommands"); +} + +public OnAllPluginsLoaded() { + if (LibraryExists("sourceirc")) + IRC_Loaded(); +} + +public OnLibraryAdded(const String:name[]) { + if (StrEqual(name, "sourceirc")) + IRC_Loaded(); +} + +IRC_Loaded() { + IRC_CleanUp(); // Call IRC_CleanUp as this function can be called more than once. + IRC_RegAdminCmd("ban", Command_Ban, ADMFLAG_BAN, "ban <#userid|name> [reason] - Bans a player from the server"); +} + +public Action:Command_Ban(const String:nick[], args) { + // Blatently borrowed code from basebans/bans + if (args < 2) + { + IRC_ReplyToCommand(nick, "Usage: ban <#userid|name> [reason]"); + return Plugin_Handled; + } + + decl len, next_len; + decl String:Arguments[256]; + IRC_GetCmdArgString(Arguments, sizeof(Arguments)); + + decl String:arg[65]; + len = BreakString(Arguments, arg, sizeof(arg)); + + new target = IRC_FindTarget(nick, arg, true); + if (target == -1) + { + return Plugin_Handled; + } + + decl String:s_time[12]; + if ((next_len = BreakString(Arguments[len], s_time, sizeof(s_time))) != -1) + { + len += next_len; + } + else + { + len = 0; + Arguments[0] = '\0'; + } + + new time = StringToInt(s_time); + + PrepareBan(nick, target, time, Arguments[len]); + + return Plugin_Handled; +} + +PrepareBan(const String:nick[], target, time, const String:reason[]) +{ + decl String:authid[64], String:name[32]; + GetClientAuthString(target, authid, sizeof(authid)); + GetClientName(target, name, sizeof(name)); + + if (!time) + { + if (reason[0] == '\0') + { + IRC_ReplyToCommand(nick, "%t", "Permabanned player", name); + } else { + IRC_ReplyToCommand(nick, "%t", "Permabanned player reason", name, reason); + } + } else { + if (reason[0] == '\0') + { + IRC_ReplyToCommand(nick, "%t", "Banned player", name, time); + } else { + IRC_ReplyToCommand(nick, "%t", "Banned player reason", name, time, reason); + } + } + + decl String:hostmask[IRC_MAXLEN]; + IRC_GetHostMask(hostmask, sizeof(hostmask)); + LogAction(-1, target, "\"%s\" banned \"%L\" (minutes \"%d\") (reason \"%s\")", hostmask, target, time, reason); + + if (reason[0] == '\0') + { + BanClient(target, time, BANFLAG_AUTO, "Banned", "Banned", "sourceirc_ban", 0); + } + else + { + BanClient(target, time, BANFLAG_AUTO, reason, reason, "sourceirc_ban", 0); + } +} + +public OnPluginEnd() { + IRC_CleanUp(); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc-changemap.sp b/scripting/SourceIRC/sourceirc-changemap.sp new file mode 100644 index 0000000..d2d9ded --- /dev/null +++ b/scripting/SourceIRC/sourceirc-changemap.sp @@ -0,0 +1,92 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#undef REQUIRE_PLUGIN +#include + +#pragma semicolon 1 +#pragma dynamic 65535 + +public Plugin:myinfo = { + name = "SourceIRC -> Change Map", + author = "Azelphur", + description = "Adds a changemap command to SourceIRC", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +public OnPluginStart() { + LoadTranslations("sourceirc.phrases"); +} + +public OnAllPluginsLoaded() { + if (LibraryExists("sourceirc")) + IRC_Loaded(); +} + +public OnLibraryAdded(const String:name[]) { + if (StrEqual(name, "sourceirc")) + IRC_Loaded(); +} + +IRC_Loaded() { + IRC_CleanUp(); // Call IRC_CleanUp as this function can be called more than once. + IRC_RegAdminCmd("changemap", Command_ChangeMap, ADMFLAG_CHANGEMAP, "changemap - Changes the current map, you can use a partial map name."); +} + +public Action:Command_ChangeMap(const String:nick[], args) { + decl String:text[IRC_MAXLEN]; + IRC_GetCmdArgString(text, sizeof(text)); + if (IsMapValid(text)) { + IRC_ReplyToCommand(nick, "%t", "Changing Map", text); + ForceChangeLevel(text, "Requested from IRC"); + } + else { + decl String:storedmap[64], String:map[64]; + new Handle:maps = CreateArray(64); + ReadMapList(maps); + new bool:foundmatch = false; + for (new i = 0; i < GetArraySize(maps); i++) { + GetArrayString(maps, i, storedmap, sizeof(storedmap)); + if (StrContains(storedmap, text, false) != -1) { + if (!foundmatch) { + strcopy(map, sizeof(map), storedmap); + foundmatch = true; + } + else { + IRC_ReplyToCommand(nick, "%t", "Multiple Maps", text); + return Plugin_Handled; + } + } + } + if (foundmatch) { + IRC_ReplyToCommand(nick, "%t", "Changing Map", map); + ForceChangeLevel(map, "Requested from IRC"); + return Plugin_Handled; + } + else { + IRC_ReplyToCommand(nick, "%t", "Invalid Map", text); + } + } + return Plugin_Handled; +} + +public OnPluginEnd() { + IRC_CleanUp(); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc-hostmasks.sp b/scripting/SourceIRC/sourceirc-hostmasks.sp new file mode 100644 index 0000000..12b1677 --- /dev/null +++ b/scripting/SourceIRC/sourceirc-hostmasks.sp @@ -0,0 +1,60 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#undef REQUIRE_PLUGIN +#include + +public Plugin:myinfo = { + name = "SourceIRC -> Hostmasks", + author = "Azelphur", + description = "Provides access based on hostmask", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +new Handle:kv; + +public OnConfigsExecuted() { + kv = CreateKeyValues("SourceIRC"); + decl String:file[512]; + BuildPath(Path_SM, file, sizeof(file), "configs/sourceirc.cfg"); + FileToKeyValues(kv, file); +} + +public IRC_RetrieveUserFlagBits(const String:hostmask[], &flagbits) { + if (!KvJumpToKey(kv, "Access")) return; + if (!KvJumpToKey(kv, "Hostmasks")) return; + if (!KvGotoFirstSubKey(kv, false)) return; + decl String:key[64], String:value[64]; + new AdminFlag:tempflag; + do + { + KvGetSectionName(kv, key, sizeof(key)); + if (IsWildCardMatch(hostmask, key)) { + KvGetString(kv, NULL_STRING, value, sizeof(value)); + for (new i = 0; i <= strlen(value); i++) { + if (FindFlagByChar(value[i], tempflag)) { + flagbits |= 1<<_:tempflag; + } + } + } + } while (KvGotoNextKey(kv, false)); + + KvRewind(kv); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc-kick.sp b/scripting/SourceIRC/sourceirc-kick.sp new file mode 100644 index 0000000..47018ac --- /dev/null +++ b/scripting/SourceIRC/sourceirc-kick.sp @@ -0,0 +1,147 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#undef REQUIRE_PLUGIN +#include + +#pragma semicolon 1 +#pragma dynamic 65535 + + +public Plugin:myinfo = { + name = "SourceIRC -> Kick", + author = "Azelphur", + description = "Adds kick command to SourceIRC", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +public OnPluginStart() { + LoadTranslations("common.phrases"); + LoadTranslations("plugin.basecommands"); +} + +public OnAllPluginsLoaded() { + if (LibraryExists("sourceirc")) + IRC_Loaded(); +} + +public OnLibraryAdded(const String:name[]) { + if (StrEqual(name, "sourceirc")) + IRC_Loaded(); +} + +IRC_Loaded() { + IRC_CleanUp(); // Call IRC_CleanUp as this function can be called more than once. + IRC_RegAdminCmd("kick", Command_Kick, ADMFLAG_KICK, "kick <#userid|name> [reason] - Kicks a player from the server"); +} + +public Action:Command_Kick(const String:nick[], args) { + // Blatently borrowed code from basecommands/kick + if (args < 1) + { + IRC_ReplyToCommand(nick, "Usage: kick <#userid|name> [reason]"); + return Plugin_Handled; + } + + decl String:Arguments[256]; + IRC_GetCmdArgString(Arguments, sizeof(Arguments)); + + decl String:arg[65]; + new len = BreakString(Arguments, arg, sizeof(arg)); + + if (len == -1) + { + /* Safely null terminate */ + len = 0; + Arguments[0] = '\0'; + } + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if ((target_count = ProcessTargetString( + arg, + 0, + target_list, + MAXPLAYERS, + COMMAND_FILTER_CONNECTED, + target_name, + sizeof(target_name), + tn_is_ml)) > 0) + { + decl String:reason[64]; + Format(reason, sizeof(reason), Arguments[len]); + + if (tn_is_ml) + { + if (reason[0] == '\0') + { + IRC_ReplyToCommand(nick, "%t", "Kicked target", target_name); + } + else + { + IRC_ReplyToCommand(nick, "%t", "Kicked target reason", target_name, reason); + } + } + else + { + if (reason[0] == '\0') + { + IRC_ReplyToCommand(nick, "Kicked target", "_s", target_name); + } + else + { + IRC_ReplyToCommand(nick, "Kicked target reason", "_s", target_name, reason); + } + } + + decl String:hostmask[IRC_MAXLEN]; + IRC_GetHostMask(hostmask, sizeof(hostmask)); + + for (new i = 0; i < target_count; i++) + { + PerformKick(hostmask, target_list[i], reason); + } + } + else + { + IRC_ReplyToTargetError(nick, target_count); + } + + return Plugin_Handled; +} + +PerformKick(const String:hostmask[], target, const String:reason[]) +{ + LogAction(-1, target, "\"%s\" kicked \"%L\" (reason \"%s\")", hostmask, target, reason); + + if (reason[0] == '\0') + { + KickClient(target, "%t", "Kicked by admin"); + } + else + { + KickClient(target, "%s", reason); + } +} + +public OnPluginEnd() { + IRC_CleanUp(); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc-rcon.sp b/scripting/SourceIRC/sourceirc-rcon.sp new file mode 100644 index 0000000..df9541b --- /dev/null +++ b/scripting/SourceIRC/sourceirc-rcon.sp @@ -0,0 +1,152 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#include +#undef REQUIRE_PLUGIN +#include + +#pragma semicolon 1 + +#define SERVERDATA_EXECCOMMAND 2 +#define SERVERDATA_AUTH 3 + +new Handle:gsocket = INVALID_HANDLE; +new REQUESTID = 0; +new bool:busy = false; +new String:greplynick[64]; +new String:gcommand[256]; + +public Plugin:myinfo = { + name = "SourceIRC -> RCON", + author = "Azelphur", + description = "Allows you to run RCON commands", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +public OnAllPluginsLoaded() { + if (LibraryExists("sourceirc")) + IRC_Loaded(); +} + +public OnLibraryAdded(const String:name[]) { + if (StrEqual(name, "sourceirc")) + IRC_Loaded(); +} + +IRC_Loaded() { + IRC_CleanUp(); // Call IRC_CleanUp as this function can be called more than once. + IRC_RegAdminCmd("rcon", Command_RCON, ADMFLAG_RCON, "rcon - Run an rcon command on the server."); +} + +public Action:Command_RCON(const String:nick[], args) { + if (busy) + IRC_ReplyToCommand(nick, "%t", "RCON Busy"); + else { + IRC_GetCmdArgString(gcommand, sizeof(gcommand)); + strcopy(greplynick, sizeof(greplynick), nick); + Connect(); + } + return Plugin_Handled; +} + +Connect() { + decl String:ServerIp[16]; + new iIp = GetConVarInt(FindConVar("hostip")); + Format(ServerIp, sizeof(ServerIp), "%i.%i.%i.%i", (iIp >> 24) & 0x000000FF, + (iIp >> 16) & 0x000000FF, + (iIp >> 8) & 0x000000FF, + iIp & 0x000000FF); + new ServerPort = GetConVarInt(FindConVar("hostport")); + gsocket = SocketCreate(SOCKET_TCP, OnSocketError); + SocketConnect(gsocket, OnSocketConnect, OnSocketReceive, OnSocketDisconnected, ServerIp, ServerPort); +} + +public OnSocketConnect(Handle:socket, any:arg) { + decl String:rcon_password[256]; + GetConVarString(FindConVar("rcon_password"), rcon_password, sizeof(rcon_password)); + if (StrEqual(rcon_password, "")) + SetFailState("You need to enable RCON to use this plugin"); + ReplaceString(rcon_password, sizeof(rcon_password), "%", "%%"); // Escape out any percent symbols that should happen to be in the password + Send(SERVERDATA_AUTH, rcon_password); +} + +public OnSocketReceive(Handle:socket, String:receiveData[], const dataSize, any:hFile) { + new i = 0; + while (i < dataSize) { + new packetlen = ReadByte(receiveData[i]); + new requestid = ReadByte(receiveData[i+4]); + new serverdata = ReadByte(receiveData[i+8]); + if (serverdata == 2) { + if (requestid == 1) + Send(SERVERDATA_EXECCOMMAND, gcommand); + else + IRC_ReplyToCommand(greplynick, "Unable to connect to RCON"); + } + if (serverdata == 0 && requestid > 1) { + decl String:lines[64][256]; + new linecount = ExplodeString(receiveData[i+12], "\n", lines, sizeof(lines), sizeof(lines[])); + for (new l = 0; l <= linecount; l++) { + IRC_ReplyToCommand(greplynick, "%s", lines[l]); + } + busy = false; + SocketDisconnect(gsocket); + REQUESTID = 0; + CloseHandle(socket); + } + i += packetlen+4; + } +} + +public OnSocketDisconnected(Handle:socket, any:hFile) { + REQUESTID = 0; + CloseHandle(socket); +} + +public OnSocketError(Handle:socket, const errorType, const errorNum, any:hFile) { + LogError("socket error %d (errno %d)", errorType, errorNum); + CloseHandle(socket); +} + +ReadByte(String:recieveData[]) { + new numbers[4]; + for (new i = 0; i <= 3; i++) { + numbers[i] = recieveData[i]; + } + new number = 0; + number += numbers[0]; + number += numbers[1]<<8; + number += numbers[2]<<16; + number += numbers[3]<<24; + return number; +} + +Send(type, const String:format[], any:...) { + REQUESTID++; + decl String:packet[1024], String:command[1014]; + VFormat(command, sizeof(command), format, 2); + new num = strlen(command)+10; + Format(packet, sizeof(packet), "%c%c%c%c%c%c%c%c%c%c%c%c%s\x00\x00", num&0xFF, num>>8&0xFF, num>>16&0xFF, num>>24&0xFF, REQUESTID&0xFF, REQUESTID>>8&0xFF, REQUESTID>>16&0xFF, REQUESTID>>24&0xFF, type&0xFF, type>>8&0xFF, type>>16&0xFF, type>>24&0xFF, command); + SocketSend(gsocket, packet, strlen(command)+14); + return; +} + +public OnPluginEnd() { + IRC_CleanUp(); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc-relayall.sp b/scripting/SourceIRC/sourceirc-relayall.sp new file mode 100644 index 0000000..7769ce4 --- /dev/null +++ b/scripting/SourceIRC/sourceirc-relayall.sp @@ -0,0 +1,208 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#include +#undef REQUIRE_PLUGIN +#include + +new g_userid = 0; + +new bool:g_isteam = false; + +public Plugin:myinfo = { + name = "SourceIRC -> Relay All", + author = "Azelphur", + description = "Relays various game events", + version = IRC_VERSION, + url = "http://azelphur.com/" +}; + +public OnPluginStart() { + RegConsoleCmd("me", Command_Me); + HookEvent("player_disconnect", Event_PlayerDisconnect, EventHookMode_Post); + HookEvent("player_changename", Event_PlayerChangeName, EventHookMode_Post); + HookEvent("player_say", Event_PlayerSay, EventHookMode_Post); + HookEvent("player_chat", Event_PlayerSay, EventHookMode_Post); + + RegConsoleCmd("say", Command_Say); + RegConsoleCmd("say2", Command_Say); + RegConsoleCmd("say_team", Command_SayTeam); + + LoadTranslations("sourceirc.phrases"); +} + +public OnAllPluginsLoaded() { + if (LibraryExists("sourceirc")) + IRC_Loaded(); +} + +public OnLibraryAdded(const String:name[]) { + if (StrEqual(name, "sourceirc")) + IRC_Loaded(); +} + +IRC_Loaded() { + IRC_CleanUp(); // Call IRC_CleanUp as this function can be called more than once. + IRC_HookEvent("PRIVMSG", Event_PRIVMSG); +} + +public Action:Command_Say(client, args) { + g_isteam = false; // Ugly hack to get around player_chat event not working. +} + +public Action:Command_SayTeam(client, args) { + g_isteam = true; // Ugly hack to get around player_chat event not working. +} + +public Action:Event_PlayerSay(Handle:event, const String:name[], bool:dontBroadcast) +{ + new userid = GetEventInt(event, "userid"); + new client = GetClientOfUserId(userid); + + decl String:result[IRC_MAXLEN], String:message[256]; + result[0] = '\0'; + GetEventString(event, "text", message, sizeof(message)); + if (!IsPlayerAlive(client)) + StrCat(result, sizeof(result), "*DEAD* "); + if (g_isteam) + StrCat(result, sizeof(result), "(TEAM) "); + + new team + if (client != 0) + team = IRC_GetTeamColor(GetClientTeam(client)); + else + team = 0; + if (team == -1) + Format(result, sizeof(result), "%s%N: %s", result, client, message); + else + Format(result, sizeof(result), "%s\x03%02d%N\x03: %s", result, team, client, message); + + IRC_MsgFlaggedChannels("relay", result); +} + + +public OnClientAuthorized(client, const String:auth[]) { // We are hooking this instead of the player_connect event as we want the steamid + new userid = GetClientUserId(client); + if (userid <= g_userid) // Ugly hack to get around mass connects on map change + return true; + g_userid = userid; + decl String:playername[MAX_NAME_LENGTH], String:result[IRC_MAXLEN]; + GetClientName(client, playername, sizeof(playername)); + Format(result, sizeof(result), "%t", "Player Connected", playername, auth, userid); + if (!StrEqual(result, "")) + IRC_MsgFlaggedChannels("relay", result); + return true; +} + +public Action:Event_PlayerDisconnect(Handle:event, const String:name[], bool:dontBroadcast) +{ + new userid = GetEventInt(event, "userid"); + new client = GetClientOfUserId(userid); + if (client != 0) { + decl String:reason[128], String:playername[MAX_NAME_LENGTH], String:auth[64], String:result[IRC_MAXLEN]; + GetEventString(event, "reason", reason, sizeof(reason)); + GetClientName(client, playername, sizeof(playername)); + GetClientAuthString(client, auth, sizeof(auth)); + for (new i = 0; i <= strlen(reason); i++) { // For some reason, certain disconnect reasons have \n in them, so i'm stripping them. Silly valve. + if (reason[i] == '\n') + RemoveChar(reason, sizeof(reason), i); + } + Format(result, sizeof(result), "%t", "Player Disconnected", playername, auth, userid, reason); + if (!StrEqual(result, "")) + IRC_MsgFlaggedChannels("relay", result); + } +} + +public Action:Event_PlayerChangeName(Handle:event, const String:name[], bool:dontBroadcast) +{ + new userid = GetEventInt(event, "userid"); + new client = GetClientOfUserId(userid); + if (client != 0) { + decl String:oldname[128], String:newname[MAX_NAME_LENGTH], String:auth[64], String:result[IRC_MAXLEN]; + GetEventString(event, "oldname", oldname, sizeof(oldname)); + GetEventString(event, "newname", newname, sizeof(newname)); + GetClientAuthString(client, auth, sizeof(auth)); + Format(result, sizeof(result), "%t", "Changed Name", oldname, auth, userid, newname); + if (!StrEqual(result, "")) + IRC_MsgFlaggedChannels("relay", result); + } +} + +public OnMapEnd() { + IRC_MsgFlaggedChannels("relay", "%t", "Map Changing"); +} + +public OnMapStart() { + decl String:map[128]; + GetCurrentMap(map, sizeof(map)); + IRC_MsgFlaggedChannels("relay", "%t", "Map Changed", map); +} + +public Action:Command_Me(client, args) { + decl String:Args[256], String:name[64], String:auth[64], String:text[512]; + GetCmdArgString(Args, sizeof(Args)); + GetClientName(client, name, sizeof(name)); + GetClientAuthString(client, auth, sizeof(auth)); + new team = IRC_GetTeamColor(GetClientTeam(client)); + if (team == -1) + IRC_MsgFlaggedChannels("relay", "* %s %s", name, Args); + else + IRC_MsgFlaggedChannels("relay", "* \x03%02d%s\x03 %s", team, name, Args); + Format(text, sizeof(text), "\x01* \x03%s\x01 %s", name, Args); + SayText2All(client, text); + return Plugin_Handled; +} + +public Action:Event_PRIVMSG(const String:hostmask[], args) { + decl String:channel[64]; + IRC_GetEventArg(1, channel, sizeof(channel)); + if (IRC_ChannelHasFlag(channel, "relay")) { + decl String:nick[IRC_NICK_MAXLEN], String:text[IRC_MAXLEN]; + IRC_GetNickFromHostMask(hostmask, nick, sizeof(nick)); + IRC_GetEventArg(2, text, sizeof(text)); + if (!strncmp(text, "\x01ACTION ", 8) && text[strlen(text)-1] == '\x01') { + text[strlen(text)-1] = '\x00'; + IRC_Strip(text, sizeof(text)); // Strip IRC Color Codes + IRC_StripGame(text, sizeof(text)); // Strip Game color codes + PrintToChatAll("\x01[\x04IRC\x01] * %s %s", nick, text[7]); + } + else { + IRC_Strip(text, sizeof(text)); // Strip IRC Color Codes + IRC_StripGame(text, sizeof(text)); // Strip Game color codes + PrintToChatAll("\x01[\x04IRC\x01] %s : %s", nick, text); + } + } +} + +stock SayText2All(clientid4team, const String:message[]) +{ + new Handle:hBf; + hBf = StartMessageAll("SayText2"); + if (hBf != INVALID_HANDLE) + { + BfWriteByte(hBf, clientid4team); + BfWriteByte(hBf, 0); + BfWriteString(hBf, message); + EndMessage(); + } +} + +public OnPluginEnd() { + IRC_CleanUp(); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc-status.sp b/scripting/SourceIRC/sourceirc-status.sp new file mode 100644 index 0000000..90f0fdc --- /dev/null +++ b/scripting/SourceIRC/sourceirc-status.sp @@ -0,0 +1,141 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#undef REQUIRE_PLUGIN +#include + +#pragma semicolon 1 +#pragma dynamic 65535 + + +public Plugin:myinfo = { + name = "SourceIRC -> Status", + author = "Azelphur", + description = "Adds status and gameinfo commands show server status and who's online.", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +public OnAllPluginsLoaded() { + if (LibraryExists("sourceirc")) + IRC_Loaded(); +} + +public OnLibraryAdded(const String:name[]) { + if (StrEqual(name, "sourceirc")) + IRC_Loaded(); +} + +IRC_Loaded() { + IRC_CleanUp(); // Call IRC_CleanUp as this function can be called more than once. + IRC_RegCmd("status", Command_Status, "status - Shows the server name, map, nextmap, and players who are online."); + IRC_RegCmd("gameinfo", Command_GameInfo, "gameinfo - Shows the server name, ip, map, nextmap, how many players are online and timeleft (If supported)."); +} + +public Action:Command_GameInfo(const String:nick[], args) { + decl String:hostname[256], String:serverdomain[128], String:map[64], String:nextmap[64], String:hostmask[512]; + IRC_GetHostMask(hostmask, sizeof(hostmask)); + + GetClientName(0, hostname, sizeof(hostname)); + IRC_ReplyToCommand(nick, "hostname: %s", hostname); + + IRC_GetServerDomain(serverdomain, sizeof(serverdomain)); + IRC_ReplyToCommand(nick, "udp/ip : %s", serverdomain); + + GetCurrentMap(map, sizeof(map)); + IRC_ReplyToCommand(nick, "map : %s", map); + + GetNextMap(nextmap, sizeof(nextmap)); + IRC_ReplyToCommand(nick, "nextmap : %s", nextmap); + + IRC_ReplyToCommand(nick, "players : %d (%d max)", GetClientCount(), GetMaxClients()); + + new timeleft; + + if (GetMapTimeLeft(timeleft)) { + if (timeleft >= 0) + IRC_ReplyToCommand(nick, "timeleft: %d:%02d", timeleft / 60, timeleft % 60); + else + IRC_ReplyToCommand(nick, "timeleft: N/A"); + } + return Plugin_Handled; +} + +public Action:Command_Status(const String:nick[], args) { + decl String:hostname[256], String:serverdomain[128], String:map[64], String:hostmask[512], String:auth[64], String:ip[32], String:states[32], time, mins, secs, latency, loss; + IRC_GetHostMask(hostmask, sizeof(hostmask)); + new bool:isadmin = IRC_GetAdminFlag(hostmask, AdminFlag:ADMFLAG_GENERIC); + + GetClientName(0, hostname, sizeof(hostname)); + IRC_ReplyToCommand(nick, "hostname: %s", hostname); + + IRC_GetServerDomain(serverdomain, sizeof(serverdomain)); + IRC_ReplyToCommand(nick, "udp/ip : %s", serverdomain); + + GetCurrentMap(map, sizeof(map)); + IRC_ReplyToCommand(nick, "map : %s", map); + + IRC_ReplyToCommand(nick, "players : %d (%d max)", GetClientCount(), GetMaxClients()); + + decl String:line[IRC_MAXLEN]; + strcopy(line, sizeof(line), "# userid name uniqueid connected ping loss state"); + if (isadmin) + StrCat(line, sizeof(line), " adr"); + IRC_ReplyToCommand(nick, line); + + for (new i = 1; i <= GetMaxClients(); i++) { + if (IsClientConnected(i)) { + if (IsClientAuthorized(i)) + GetClientAuthString(i, auth, sizeof(auth)); + else + strcopy(auth, sizeof(auth), "N/A"); + + if (IsClientInGame(i) && !IsFakeClient(i)) { + time = RoundToFloor(GetClientTime(i)); + mins = time / 60; + secs = time % 60; + latency = RoundToFloor(GetClientAvgLatency(i, NetFlow_Both)*1000.0); + loss = RoundToFloor(GetClientAvgLoss(i, NetFlow_Both)*100.0); + } + else { + mins = 0; + secs = 0; + latency = -1; + loss = -1; + } + + if (IsClientInGame(i)) + strcopy(states, sizeof(states), "active"); + else + strcopy(states, sizeof(states), "spawning"); + GetClientIP(i, ip, sizeof(ip), false); + if (isadmin) + Format(line, sizeof(line), "# %d \"%N\" %s %d:%02d %d %d %s %s", GetClientUserId(i), i, auth, mins, secs, latency, loss, states, ip); + else + Format(line, sizeof(line), "# %d \"%N\" %s %d:%02d %d %d %s", GetClientUserId(i), i, auth, mins, secs, latency, loss, states); + IRC_ReplyToCommand(nick, line); + } + } + + return Plugin_Handled; +} + +public OnPluginEnd() { + IRC_CleanUp(); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc-ticket.sp b/scripting/SourceIRC/sourceirc-ticket.sp new file mode 100644 index 0000000..c4827a0 --- /dev/null +++ b/scripting/SourceIRC/sourceirc-ticket.sp @@ -0,0 +1,258 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#include +#undef REQUIRE_PLUGIN +#include + +#pragma semicolon 1 + +new Float:SprayLocation[MAXPLAYERS+1][3]; +new String:ReportString[MAXPLAYERS+1][512]; + +new Handle:kv; + +public Plugin:myinfo = { + name = "SourceIRC -> Ticket", + author = "Azelphur", + description = "Adds a report command in game for players to report problems to staff in an IRC channel", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +public OnPluginStart() { + LoadTranslations("common.phrases"); + RegConsoleCmd("report", Command_Support); + RegConsoleCmd("reply", Command_Reply); + AddTempEntHook("Player Decal", PlayerSpray); + kv = CreateKeyValues("SourceIRC"); + decl String:file[512]; + BuildPath(Path_SM, file, sizeof(file), "configs/sourceirc.cfg"); + FileToKeyValues(kv, file); +} + +public OnAllPluginsLoaded() { + if (LibraryExists("sourceirc")) + IRC_Loaded(); +} + +public OnLibraryAdded(const String:name[]) { + if (StrEqual(name, "sourceirc")) + IRC_Loaded(); +} + +IRC_Loaded() { + IRC_CleanUp(); // Call IRC_CleanUp as this function can be called more than once. + IRC_RegAdminCmd("to", Command_To, ADMFLAG_CHAT, "to - Send a message to a player"); +} + +public Action:Command_Reply(client, args) { + decl String:Args[256], String:name[64], String:auth[64]; + GetCmdArgString(Args, sizeof(Args)); + if (StrEqual(Args, "")) + return Plugin_Handled; + GetClientName(client, name, sizeof(name)); + GetClientAuthString(client, auth, sizeof(auth)); + IRC_MsgFlaggedChannels("ticket", "%s (%s) : %s", name, auth, Args); + PrintToChat(client, "To ADMIN : %s", Args); + return Plugin_Handled; +} + +public Action:Command_To(const String:nick[], args) { + decl String:destination[64], String:text[IRC_MAXLEN]; + IRC_GetCmdArgString(text, sizeof(text)); + new startpos = BreakString(text, destination, sizeof(destination)); + new target = FindTarget(0, destination, true, false); + if (target != -1) { + PrintToChat(target, "\x01[\x04IRC\x01] \x03(ADMIN) %s\x01 : %s", nick, text[startpos]); + } + else { + IRC_ReplyToCommand(nick, "Unable to find %s", destination); + } + return Plugin_Handled; +} + +public Action:PlayerSpray(const String:te_name[],const clients[],client_count,Float:delay) { + new client=TE_ReadNum("m_nPlayer"); + TE_ReadVector("m_vecOrigin", SprayLocation[client]); +} + +TraceSpray(client) { + new Float:pos[3]; + if(GetPlayerEye(client, pos) >= 1){ + new Float:MaxDis = 50.0; + for(new i = 1; i<= MAXPLAYERS; i++) { + if(GetVectorDistance(pos, SprayLocation[i]) <= MaxDis) + return i; + } + } + return 0; +} + +stock GetPlayerEye(client, Float:pos[3]) { + new Float:vAngles[3], Float:vOrigin[3]; + GetClientEyePosition(client,vOrigin); + GetClientEyeAngles(client, vAngles); + + new Handle:trace = TR_TraceRayFilterEx(vOrigin, vAngles, MASK_SHOT, RayType_Infinite, TraceEntityFilterPlayer); + + if(TR_DidHit(trace)) { + TR_GetEndPosition(pos, trace); + if(GetVectorDistance(pos, vOrigin) <= 128.0) + return 2; + return 1; + } + return 0; +} + +public bool:TraceEntityFilterPlayer(entity, contentsMask) { + new String:classname[64]; + GetEntityNetClass(entity, classname, 64); + return !StrEqual(classname, "CTFPlayer"); +} + +public Action:Command_Support(client, args) { + new Handle:hMenu=CreateMenu(MenuHandler_Report); + SetMenuTitle(hMenu,"What do you want to report for?"); + if (!KvJumpToKey(kv, "Ticket")) return; + if (!KvJumpToKey(kv, "Menu")) return; + if (!KvGotoFirstSubKey(kv, false)) return; + decl String:key[64], String:value[64]; + do + { + KvGetSectionName(kv, key, sizeof(key)); + KvGetString(kv, NULL_STRING, value, sizeof(value)); + AddMenuItem(hMenu, key, value); + } while (KvGotoNextKey(kv, false)); + + KvRewind(kv); + + DisplayMenu(hMenu, client, MENU_TIME_FOREVER); +} + +public MenuHandler_Report(Handle:hMenu, MenuAction:action, param1, param2) { + if(action==MenuAction_Select) { + GetMenuItem(hMenu, param2, ReportString[param1], sizeof(ReportString[])); + if (StrEqual(ReportString[param1], "{Special:Spray}")) + SprayMenu(param1); + else + ShowPlayerList(param1); + } +} + +SprayMenu(client) { + new Handle:hMenu = CreateMenu(MenuHandler_SprayMenu); + SetMenuTitle(hMenu, "Aim at the spray you wish to report, then press ok."); + AddMenuItem(hMenu, "Ok", "Ok"); + SetMenuExitBackButton(hMenu, true); + DisplayMenu(hMenu, client, MENU_TIME_FOREVER); +} + +public MenuHandler_SprayMenu(Handle:menu, MenuAction:action, param1, param2) +{ + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { + Command_Support(param1, 0); + } + else if (action == MenuAction_Select) { + new target = TraceSpray(param1); + if (!target) { + PrintToChat(param1, "No spray found where you are looking, try getting closer!"); + SprayMenu(param1); + } + else { + decl String:decalfile[256]; + GetPlayerDecalFile(target, decalfile, sizeof(decalfile)); + decl String:sprayurl[128]; + sprayurl[0] = '\x00'; + Format(ReportString[param1], sizeof(ReportString[]), "Bad spray"); + if ((KvJumpToKey(kv, "Ticket")) && (KvJumpToKey(kv, "Settings"))) { + KvGetString(kv, "spray_url", sprayurl, sizeof(sprayurl), ""); + if (!StrEqual(sprayurl, "")) { + ReplaceString(sprayurl, sizeof(sprayurl), "{SPRAY}", decalfile); + StrCat(ReportString[param1], sizeof(ReportString), " "); + StrCat(ReportString[param1], sizeof(ReportString), sprayurl); + } + } + KvRewind(kv); + + Report(param1, target, ReportString[param1]); + } + } +} + +ShowPlayerList(client) { + new Handle:hMenu = CreateMenu(MenuHandler_PlayerList); + decl String:title[256]; + Format(title, sizeof(title), "Who do you want to report for %s", ReportString[client]); + SetMenuTitle(hMenu, title); + SetMenuExitBackButton(hMenu, true); + new maxclients = GetMaxClients(); + decl String:disp[64], String:info[64]; + for (new i = 1; i <= maxclients; i++) { + if (IsClientConnected(i) && !IsFakeClient(i)) { + GetClientName(i, disp, sizeof(disp)); + IntToString(GetClientUserId(i), info, sizeof(info)); + AddMenuItem(hMenu, info, disp); + } + } + DisplayMenu(hMenu, client, MENU_TIME_FOREVER); +} + +public MenuHandler_PlayerList(Handle:menu, MenuAction:action, param1, param2) +{ + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) { + Command_Support(param1, 0); + } + else if (action == MenuAction_Select) { + decl String:info[32]; + GetMenuItem(menu, param2, info, sizeof(info)); + new client = GetClientOfUserId(StringToInt(info)); + + if (!client) { + PrintToChat(param1, "Player disconnected, sorry!"); + } + else { + Report(param1, client, ReportString[param1]); + } + } +} + +Report(client, target, String:info[]) { + decl String:name[64], String:auth[64], String:targetname[64], String:targetauth[64], String:mynick[64]; + GetClientName(client, name, sizeof(name)); + GetClientAuthString(client, auth, sizeof(auth)); + GetClientName(target, targetname, sizeof(targetname)); + GetClientAuthString(target, targetauth, sizeof(targetauth)); + IRC_GetNick(mynick, sizeof(mynick)); + if ((KvJumpToKey(kv, "Ticket")) && (KvJumpToKey(kv, "Settings"))) { + decl String:custom_msg[IRC_MAXLEN]; + KvGetString(kv, "custom_msg", custom_msg, sizeof(custom_msg), ""); + if (!StrEqual(custom_msg, "")) { + IRC_MsgFlaggedChannels("ticket", custom_msg); + } + } + KvRewind(kv); + IRC_MsgFlaggedChannels("ticket", "%s (%s) has reported %s (%s) for %s", name, auth, targetname, targetauth, info); + IRC_MsgFlaggedChannels("ticket", "use %s to #%d - To reply", mynick, GetClientUserId(client)); + PrintToChat(client, "\x01Your report has been sent. Type \x04/reply your text here\x01 to chat with the admins."); +} + +public OnPluginEnd() { + IRC_CleanUp(); +} + +// http://bit.ly/defcon diff --git a/scripting/SourceIRC/sourceirc.sp b/scripting/SourceIRC/sourceirc.sp new file mode 100644 index 0000000..1f8eccb --- /dev/null +++ b/scripting/SourceIRC/sourceirc.sp @@ -0,0 +1,679 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#pragma semicolon 1 + +#include +#include + +// Global socket handle for the IRC connection +new Handle:gsocket = INVALID_HANDLE; + +// Global keyvalues handle for the config file +new Handle:kv; + +// Command registry for plugins using IRC_Reg*Cmd +new Handle:CommandPlugins; +new Handle:Commands; +new Handle:CommandCallbacks; +new Handle:CommandDescriptions; +new Handle:CommandFlags; +new Handle:CommandPermissions; + +// Event registry for plugins using IRC_HookEvent +new Handle:EventPlugins; +new Handle:Events; +new Handle:EventCallbacks; + +// Queue for rate limiting +new Handle:messagequeue; +new Handle:messagetimer = INVALID_HANDLE; +new Float:messagerate = 0.0; + +// Temporary storage for command and event arguments +new Handle:cmdargs; +new String:cmdargstring[IRC_MAXLEN]; +new String:cmdhostmask[IRC_MAXLEN]; + +// Are we connected yet? +new bool:g_connected; + +// My nickname +new String:g_nick[IRC_NICK_MAXLEN]; + +// IRC can break messages into more than one packet, so this is temporary storage for "Broken" packets +new String:brokenline[IRC_MAXLEN]; + +public Plugin:myinfo = { + name = "SourceIRC", + author = "Azelphur", + description = "An easy to use API to the IRC protocol", + version = IRC_VERSION, + url = "http://Azelphur.com/project/sourceirc" +}; + +public OnPluginStart() { + RegPluginLibrary("sourceirc"); + + CreateConVar("sourceirc_version", IRC_VERSION, "Current version of SourceIRC", FCVAR_PLUGIN|FCVAR_REPLICATED|FCVAR_SPONLY|FCVAR_NOTIFY); + LoadTranslations("sourceirc.phrases"); + + CommandPlugins = CreateArray(); + Commands = CreateArray(IRC_CMD_MAXLEN); + CommandCallbacks = CreateArray(); + CommandDescriptions = CreateArray(256); + CommandFlags = CreateArray(); + CommandPermissions = CreateArray(); + + EventPlugins = CreateArray(); + Events = CreateArray(IRC_MAXLEN); + EventCallbacks = CreateArray(); + + messagequeue = CreateArray(IRC_MAXLEN); + + cmdargs = CreateArray(IRC_MAXLEN); + + g_connected = false; + RegAdminCmd("irc_send", Command_Send, ADMFLAG_RCON, "irc_send "); +} + +public OnAllPluginsLoaded() { + IRC_RegCmd("help", Command_Help, "help - Shows a list of commands available to you"); + IRC_HookEvent("433", Event_RAW433); + IRC_HookEvent("NICK", Event_NICK); +} + +public Action:Event_RAW433(const String:hostmask[], args) { + if (!g_connected) { + decl String:nick[IRC_NICK_MAXLEN]; + IRC_GetNick(nick, sizeof(nick)); + LogError("Nickname %s is already in use, trying %s_", nick, nick); + StrCat(nick, sizeof(nick), "_"); + IRC_Send("NICK %s", nick); + strcopy(g_nick, sizeof(g_nick), nick); + } +} + +public Action:Event_NICK(const String:hostmask[], args) { + decl String:newnick[64], String:oldnick[IRC_NICK_MAXLEN]; + IRC_GetNickFromHostMask(hostmask, oldnick, sizeof(oldnick)); + if (StrEqual(oldnick, g_nick)) { + IRC_GetEventArg(1, newnick, sizeof(newnick)); + strcopy(g_nick, sizeof(g_nick), newnick); + } +} + +public Action:Command_Help(const String:nick[], args) { + decl String:description[256]; + decl String:hostmask[IRC_MAXLEN]; + IRC_GetHostMask(hostmask, sizeof(hostmask)); + for (new i = 0; i < GetArraySize(Commands); i++) { + if (IRC_GetAdminFlag(hostmask, GetArrayCell(CommandPermissions, i))) { + GetArrayString(CommandDescriptions, i, description, sizeof(description)); + IRC_ReplyToCommand(nick, "%s", description); + } + } + return Plugin_Handled; +} + +public Action:Command_Send(client, args) { + if (g_connected) { + decl String:buffer[IRC_MAXLEN]; + GetCmdArgString(buffer, sizeof(buffer)); + IRC_Send(buffer); + } + else { + ReplyToCommand(client, "%t", "Not Connected"); + } +} + +public OnConfigsExecuted() { + if (gsocket == INVALID_HANDLE) { + LoadConfigs(); + Connect(); + } +} + +LoadConfigs() { + kv = CreateKeyValues("SourceIRC"); + decl String:file[512]; + BuildPath(Path_SM, file, sizeof(file), "configs/sourceirc.cfg"); + FileToKeyValues(kv, file); + KvJumpToKey(kv, "Settings"); + messagerate = KvGetFloat(kv, "msg-rate", 2.0); + KvRewind(kv); +} + +Connect() { + decl String:server[256]; + KvJumpToKey(kv, "Server"); + KvGetString(kv, "server", server, sizeof(server), ""); + if (StrEqual(server, "")) + SetFailState("No server defined in sourceirc.cfg"); + new port = KvGetNum(kv, "port", 6667); + KvRewind(kv); + gsocket = SocketCreate(SOCKET_TCP, OnSocketError); + SocketConnect(gsocket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, server, port); +} + +public OnSocketConnected(Handle:socket, any:arg) { + decl String:hostname[256], String:realname[64], String:ServerIp[16]; + KvJumpToKey(kv, "Server"); + KvGetString(kv, "nickname", g_nick, sizeof(g_nick), "SourceIRC"); + KvGetString(kv, "realname", realname, sizeof(realname), "SourceIRC - http://Azelphur.com/project/sourceirc"); + KvRewind(kv); + SocketGetHostName(hostname, sizeof(hostname)); + + new iIp = GetConVarInt(FindConVar("hostip")); + Format(ServerIp, sizeof(ServerIp), "%i.%i.%i.%i", (iIp >> 24) & 0x000000FF, + (iIp >> 16) & 0x000000FF, + (iIp >> 8) & 0x000000FF, + iIp & 0x000000FF); + IRC_Send("NICK %s", g_nick); + IRC_Send("USER %s %s %s :%s", g_nick, hostname, ServerIp, realname); +} + +public OnSocketReceive(Handle:socket, String:receiveData[], const dataSize, any:hFile) { + new startpos = 0; + decl String:line[IRC_MAXLEN]; + decl String:prefix[IRC_MAXLEN]; + decl String:trailing[IRC_MAXLEN]; + + static Handle:args = INVALID_HANDLE; + if (args == INVALID_HANDLE) { + args = CreateArray(IRC_MAXLEN); + } + + while (startpos < dataSize) { + startpos += SplitString(receiveData[startpos], "\n", line, sizeof(line)); + if (receiveData[startpos-1] != '\n') { // is this the first part of a "Broken" packet? + strcopy(brokenline, sizeof(brokenline), line); + break; + } + if (!StrEqual(brokenline, "")) { // Is this the latter half of a "Broken" packet? Stick it back together again. + decl String:originalline[IRC_MAXLEN]; + strcopy(originalline, sizeof(originalline), line); + strcopy(line, sizeof(line), brokenline); + StrCat(line, sizeof(line), originalline); + brokenline[0] = '\x00'; + } + if (line[strlen(line)-1] == '\r') + line[strlen(line)-1] = '\x00'; + prefix[0] = '\x00'; + if (line[0] == ':') + Split(line[1], " ", prefix, sizeof(prefix), line, sizeof(line)); + if (StrContains(line, " :") != -1) { + Split(line, " :", line, sizeof(line), trailing, sizeof(trailing)); + ExplodeString_Array(line, " ", args, IRC_MAXLEN); + PushArrayString(args, trailing); + } + else { + ExplodeString_Array(line, " ", args, IRC_MAXLEN); + } + HandleLine(prefix, args); // packet has been parsed, time to send it off to HandleLine. + ClearArray(args); + } +} + +Split(const String:source[], const String:split[], String:dest1_[], dest1maxlen, String:dest2_[], dest2maxlen) { + decl String:dest1[dest1maxlen]; + decl String:dest2[dest2maxlen]; + new bool:beforesplit = true; + new strpos = 0; + for (new i = 0; i <= strlen(source); i++) { + if (beforesplit == true) { + if (!strncmp(source[i], split, strlen(split))) { + strpos = 0; + dest1[i] = '\x00'; + beforesplit = false; + i += strlen(split); + } + } + if (beforesplit && strpos < dest1maxlen) + dest1[strpos] = source[i]; + if (!beforesplit && strpos < dest2maxlen) + dest2[strpos] = source[i]; + strpos++; + } + dest2[strpos] = '\x00'; + strcopy(dest1_, dest1maxlen, dest1); + strcopy(dest2_, dest2maxlen, dest2); +} + +HandleLine(String:prefix[], Handle:args) { + decl String:command[IRC_MAXLEN], String:ev[IRC_MAXLEN]; + GetArrayString(args, 0, command, sizeof(command)); + if (StrEqual(command, "PRIVMSG")) { // Is it a privmsg? check if it's a command and then run the command. + decl String:message[IRC_MAXLEN], String:channel[IRC_CHANNEL_MAXLEN]; + GetArrayString(args, 1, channel, sizeof(channel)); + GetArrayString(args, 2, message, sizeof(message)); + if ((message[0] == '\x01') && (message[strlen(message)-1] == '\x01')) { // CTCP Handling + message[strlen(message)-1] = '\x00'; + decl String:nick[IRC_NICK_MAXLEN]; + IRC_GetNickFromHostMask(prefix, nick, sizeof(nick)); + if (StrEqual(message[1], "VERSION", false)) { + IRC_Send("NOTICE %s :\x01VERSION SourceIRC v%s - IRC Relay for source engine servers. http://azelphur.com/project/sourceirc\x01", nick, IRC_VERSION); + } + } + new argpos = IsTrigger(channel, message); + if (argpos != -1) { + RunCommand(prefix, message[argpos]); + return; + } + } + else if (StrEqual(command, "PING", false)) { // Reply to PING + decl String:reply[IRC_MAXLEN]; + GetArrayString(args, 1, reply, sizeof(reply)); + IRC_Send("PONG %s", reply); + } + else if (!g_connected & (StrEqual(command, "004") || StrEqual(command, "376"))) { // Recieved RAW 004 or RAW 376? We're connected. Yay! + g_connected = true; + ServerCommand("exec sourcemod/irc-connected.cfg"); + new Handle:connected = CreateGlobalForward("IRC_Connected", ET_Ignore); + Call_StartForward(connected); + Call_Finish(); + CloseHandle(connected); + } + for (new i = 0; i < GetArraySize(Events); i++) { // Push events to plugins that have hooked them. + GetArrayString(Events, i, ev, sizeof(ev)); + if (StrEqual(command, ev, false)) { + new Action:result; + cmdargs = args; + new Handle:f = CreateForward(ET_Event, Param_String, Param_Cell); + AddToForward(f, GetArrayCell(EventPlugins, i), Function:GetArrayCell(EventCallbacks, i)); + Call_StartForward(f); + Call_PushString(prefix); + Call_PushCell(GetArraySize(cmdargs)-1); + Call_Finish(_:result); + CloseHandle(f); + if (result == Plugin_Stop) + return; + } + } + ClearArray(cmdargs); +} + +IsTrigger(const String:channel[], const String:message[]) { + decl String:arg1[IRC_MAXLEN], String:cmd_prefix[64]; + if (!KvJumpToKey(kv, "Server") || !KvJumpToKey(kv, "channels") || !KvJumpToKey(kv, channel)) + cmd_prefix[0] = '\x00'; + else + KvGetString(kv, "cmd_prefix", cmd_prefix, sizeof(cmd_prefix), ""); + KvRewind(kv); + for (new i = 0; i <= strlen(message); i++) { + if (message[i] == ' ') { + arg1[i] = '\x00'; + break; + } + arg1[i] = message[i]; + } + new startpos = -1; + if (StrEqual(channel, g_nick, false)) + startpos = 0; + if (!strncmp(arg1, g_nick, strlen(g_nick), false) && !(strlen(arg1)-strlen(g_nick) > 1)) + startpos = strlen(arg1); + else if (!StrEqual(cmd_prefix, "") && !strncmp(arg1, cmd_prefix, strlen(cmd_prefix))) + startpos = strlen(cmd_prefix); + else { + decl String:cmd[IRC_CMD_MAXLEN]; + for (new i = 0; i < GetArraySize(CommandFlags); i++) { + if (GetArrayCell(CommandFlags, i) == IRC_CMDFLAG_NOPREFIX) { + GetArrayString(Commands, i, cmd, sizeof(cmd)); + if (!strncmp(arg1, cmd, strlen(cmd), false)) { + startpos = 0; + break; + } + } + } + } + if (startpos != -1) { + for (new i = startpos; i <= strlen(message); i++) { + if (message[i] != ' ') + break; + startpos++; + } + } + return startpos; +} + +RunCommand(const String:hostmask[], const String:message[]) { + decl String:command[IRC_CMD_MAXLEN], String:savedcommand[IRC_CMD_MAXLEN], String:arg[IRC_MAXLEN]; + new newpos = 0; + new pos = BreakString(message, command, sizeof(command)); + strcopy(cmdargstring, sizeof(cmdargstring), message[pos]); + strcopy(cmdhostmask, sizeof(cmdhostmask), hostmask); + while (pos != -1) { + pos = BreakString(message[newpos], arg, sizeof(arg)); + newpos += pos; + PushArrayString(cmdargs, arg); + } + decl String:nick[IRC_NICK_MAXLEN]; + IRC_GetNickFromHostMask(hostmask, nick, sizeof(nick)); + new arraysize = GetArraySize(Commands); + new bool:IsPlugin_Handled = false; + for (new i = 0; i < arraysize; i++) { + GetArrayString(Commands, i, savedcommand, sizeof(savedcommand)); + if (StrEqual(command, savedcommand, false)) { + if (IRC_GetAdminFlag(hostmask, GetArrayCell(CommandPermissions, i))) { + new Action:result; + new Handle:f = CreateForward(ET_Event, Param_String, Param_Cell); + AddToForward(f, GetArrayCell(CommandPlugins, i), Function:GetArrayCell(CommandCallbacks, i)); + Call_StartForward(f); + Call_PushString(nick); + Call_PushCell(GetArraySize(cmdargs)-1); + Call_Finish(_:result); + CloseHandle(f); + ClearArray(cmdargs); + if (result == Plugin_Handled) + IsPlugin_Handled = true; + if (result == Plugin_Stop) + return; + } + else { + IRC_ReplyToCommand(nick, "%t", "Access Denied", command); + return; + } + } + } + if (!IsPlugin_Handled) + IRC_ReplyToCommand(nick, "%t", "Unknown Command", command); +} + +public IRC_Connected() { + if (!KvJumpToKey(kv, "Server") || !KvJumpToKey(kv, "channels") || !KvGotoFirstSubKey(kv)) { + LogError("No channels defined in sourceirc.cfg"); + } + else { + decl String:channel[IRC_CHANNEL_MAXLEN]; + do + { + KvGetSectionName(kv, channel, sizeof(channel)); + IRC_Send("JOIN %s", channel); + } while (KvGotoNextKey(kv)); + } + KvRewind(kv); +} + +public OnSocketDisconnected(Handle:socket, any:hFile) { + g_connected = false; + CreateTimer(5.0, ReConnect); + CloseHandle(socket); +} + +public Action:ReConnect(Handle:timer) { + Connect(); +} + +public OnSocketError(Handle:socket, const errorType, const errorNum, any:hFile) { + g_connected = false; + CreateTimer(5.0, ReConnect); + LogError("socket error %d (errno %d)", errorType, errorNum); + CloseHandle(socket); +} + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) { + // Create all the magical natives + CreateNative("IRC_RegCmd", N_IRC_RegCmd); + CreateNative("IRC_RegAdminCmd", N_IRC_RegAdminCmd); + CreateNative("IRC_ReplyToCommand", N_IRC_ReplyToCommand); + CreateNative("IRC_GetCmdArgString", N_IRC_GetCmdArgString); + CreateNative("IRC_GetCmdArg", N_IRC_GetCmdArg); + CreateNative("IRC_GetEventArg", N_IRC_GetCmdArg); // Not a mistake, they both do the same thing for now. + + CreateNative("IRC_GetServerDomain", N_IRC_GetServerDomain); + CreateNative("IRC_HookEvent", N_IRC_HookEvent); + CreateNative("IRC_GetTeamColor", N_IRC_GetTeamColor); + + CreateNative("IRC_GetHostMask", N_IRC_GetHostMask); + CreateNative("IRC_CleanUp", N_IRC_CleanUp); + CreateNative("IRC_ChannelHasFlag", N_IRC_ChannelHasFlag); + CreateNative("IRC_Send", N_IRC_Send); + CreateNative("IRC_GetUserFlagBits", N_IRC_GetUserFlagBits); + CreateNative("IRC_GetAdminFlag", N_IRC_GetAdminFlag); + CreateNative("IRC_MsgFlaggedChannels", N_IRC_MsgFlaggedChannels); + CreateNative("IRC_GetCommandArrays", N_IRC_GetCommandArrays); + CreateNative("IRC_GetNick", N_IRC_GetNick); + return APLRes_Success; +} + +public N_IRC_GetServerDomain(Handle:plugin, numParams) { + decl String:AutoIP[32], String:ServerDomain[128]; + new iIp = GetConVarInt(FindConVar("hostip")); + Format(AutoIP, sizeof(AutoIP), "%i.%i.%i.%i:%d", (iIp >> 24) & 0x000000FF, + (iIp >> 16) & 0x000000FF, + (iIp >> 8) & 0x000000FF, + iIp & 0x000000FF, + GetConVarInt(FindConVar("hostport"))); + if (!KvJumpToKey(kv, "Settings")) { + SetNativeString(1, AutoIP, GetNativeCell(2)); + return; + } + KvGetString(kv, "server-domain", ServerDomain, sizeof(ServerDomain), ""); + if (StrEqual(ServerDomain, "")) { + SetNativeString(1, AutoIP, GetNativeCell(2)); + return; + } + + SetNativeString(1, ServerDomain, GetNativeCell(2)); + KvRewind(kv); +} + +public N_IRC_GetTeamColor(Handle:plugin, numParams) { + new team = GetNativeCell(1); + if (!KvJumpToKey(kv, "Settings")) return -1; + decl String:key[16]; + Format(key, sizeof(key), "teamcolor-%d", team); + new color = KvGetNum(kv, key, -1); + KvRewind(kv); + return color; +} + +public N_IRC_GetHostMask(Handle:plugin, numParams) { + SetNativeString(1, cmdhostmask, GetNativeCell(2)); + return strlen(cmdhostmask); +} + +public N_IRC_GetCmdArgString(Handle:plugin, numParams) { + SetNativeString(1, cmdargstring, GetNativeCell(2)); + return strlen(cmdargstring); +} + +public N_IRC_GetCmdArg(Handle:plugin, numParams) { + decl String:str[IRC_MAXLEN]; + GetArrayString(cmdargs, GetNativeCell(1), str, sizeof(str)); + SetNativeString(2, str, GetNativeCell(3)); + return strlen(str); +} + +public N_IRC_ReplyToCommand(Handle:plugin, numParams) { + decl String:buffer[512], String:nick[64], written; + GetNativeString(1, nick, sizeof(nick)); + FormatNativeString(0, 2, 3, sizeof(buffer), written, buffer); + IRC_Send("NOTICE %s :%s", nick, buffer); +} + +public N_IRC_GetNick(Handle:plugin, numParams) { + new maxlen = GetNativeCell(2); + SetNativeString(1, g_nick, maxlen); +} + +public N_IRC_GetCommandArrays(Handle:plugin, numParams) { + new Handle:CommandsArg = GetNativeCell(1); + new Handle:CommandPluginsArg = GetNativeCell(2); + new Handle:CommandCallbacksArg = GetNativeCell(3); + new Handle:CommandDescriptionsArg = GetNativeCell(4); + decl String:command[64], String:description[256]; + for (new i = 0; i < GetArraySize(CommandPlugins); i++) { + GetArrayString(Commands, i, command, sizeof(command)); + GetArrayString(CommandDescriptions, i, description, sizeof(description)); + + PushArrayString(CommandsArg, command); + PushArrayCell(CommandPluginsArg, GetArrayCell(CommandPlugins, i)); + PushArrayCell(CommandCallbacksArg, GetArrayCell(CommandCallbacks, i)); + PushArrayString(CommandDescriptionsArg, description); + } +} + +public N_IRC_HookEvent(Handle:plugin, numParams) { + decl String:ev[IRC_MAXLEN]; + GetNativeString(1, ev, sizeof(ev)); + + PushArrayCell(EventPlugins, plugin); + PushArrayString(Events, ev); + PushArrayCell(EventCallbacks, GetNativeCell(2)); +} + +public N_IRC_RegCmd(Handle:plugin, numParams) { + decl String:command[IRC_CMD_MAXLEN], String:description[256]; + GetNativeString(1, command, sizeof(command)); + GetNativeString(3, description, sizeof(description)); + PushArrayCell(CommandPlugins, plugin); + PushArrayString(Commands, command); + PushArrayCell(CommandCallbacks, GetNativeCell(2)); + PushArrayCell(CommandPermissions, 0); + PushArrayCell(CommandFlags, GetNativeCell(4)); + PushArrayString(CommandDescriptions, description); +} + +public N_IRC_RegAdminCmd(Handle:plugin, numParams) { + decl String:command[IRC_CMD_MAXLEN], String:description[256]; + GetNativeString(1, command, sizeof(command)); + GetNativeString(4, description, sizeof(description)); + PushArrayCell(CommandPlugins, plugin); + PushArrayString(Commands, command); + PushArrayCell(CommandCallbacks, GetNativeCell(2)); + PushArrayCell(CommandPermissions, GetNativeCell(3)); + PushArrayCell(CommandFlags, GetNativeCell(5)); + PushArrayString(CommandDescriptions, description); +} + +public N_IRC_CleanUp(Handle:plugin, numParams) { + for (new i = 0; i < GetArraySize(CommandPlugins); i++) { + if (plugin == GetArrayCell(CommandPlugins, i)) { + RemoveFromArray(CommandPlugins, i); + RemoveFromArray(Commands, i); + RemoveFromArray(CommandCallbacks, i); + RemoveFromArray(CommandPermissions, i); + RemoveFromArray(CommandDescriptions, i); + RemoveFromArray(CommandFlags, i); + i--; + } + } + for (new i = 0; i < GetArraySize(EventPlugins); i++) { + if (plugin == GetArrayCell(EventPlugins, i)) { + RemoveFromArray(EventPlugins, i); + RemoveFromArray(Events, i); + RemoveFromArray(EventCallbacks, i); + i--; + } + } +} + +public N_IRC_ChannelHasFlag(Handle:plugin, numParams) { + new String:flag[64], String:channel[IRC_CHANNEL_MAXLEN]; + GetNativeString(1, channel, sizeof(channel)); + GetNativeString(2, flag, sizeof(flag)); + if (!KvJumpToKey(kv, "Server") || !KvJumpToKey(kv, "channels") || !KvJumpToKey(kv, channel)) { + KvRewind(kv); + return 0; + } + new result = KvGetNum(kv, flag, 0); + KvRewind(kv); + return result; +} + +public N_IRC_GetAdminFlag(Handle:plugin, numParams) { + decl String:hostmask[512]; + new flag = GetNativeCell(2); + if (flag == 0) + return true; + GetNativeString(1, hostmask, sizeof(hostmask)); + new userflag = IRC_GetUserFlagBits(hostmask); + if (userflag & ADMFLAG_ROOT) + return true; + if (userflag & flag) + return true; + return false; +} + +public N_IRC_GetUserFlagBits(Handle:plugin, numParams) { + decl String:hostmask[512]; + GetNativeString(1, hostmask, sizeof(hostmask)); + new resultflag = 0; + new Handle:f = CreateGlobalForward("IRC_RetrieveUserFlagBits", ET_Ignore, Param_String, Param_CellByRef); + Call_StartForward(f); + Call_PushString(hostmask); + Call_PushCellRef(resultflag); + Call_Finish(); + CloseHandle(f); + return _:resultflag; +} + +public N_IRC_Send(Handle:plugin, numParams) { + new String:buffer[IRC_MAXLEN], written; + FormatNativeString(0, 1, 2, sizeof(buffer), written, buffer); + if (StrContains(buffer, "\n") != -1 || StrContains(buffer, "\r") != -1) { + ThrowNativeError(1, "String contains \n or \r"); + return; + } + + if ((g_connected) && (messagerate != 0.0)) { + if (messagetimer != INVALID_HANDLE) { + PushArrayString(messagequeue, buffer); + return; + } + messagetimer = CreateTimer(messagerate, MessageTimerCB); + } + Format(buffer, sizeof(buffer), "%s\r\n", buffer); + SocketSend(gsocket, buffer); +} + +public Action:MessageTimerCB(Handle:timer) { + messagetimer = INVALID_HANDLE; + decl String:buffer[IRC_MAXLEN]; + if (GetArraySize(messagequeue) > 0) { + GetArrayString(messagequeue, 0, buffer, sizeof(buffer)); + IRC_Send(buffer); + RemoveFromArray(messagequeue, 0); + } +} + +public N_IRC_MsgFlaggedChannels(Handle:plugin, numParams) { + if (!g_connected) + return false; + decl String:flag[64], String:text[IRC_MAXLEN]; + new written; + GetNativeString(1, flag, sizeof(flag)); + FormatNativeString(0, 2, 3, sizeof(text), written, text); + if (!KvJumpToKey(kv, "Server") || !KvJumpToKey(kv, "channels") || !KvGotoFirstSubKey(kv)) { + LogError("No channels defined in sourceirc.cfg"); + } + else { + decl String:channel[IRC_CHANNEL_MAXLEN]; + do + { + KvGetSectionName(kv, channel, sizeof(channel)); + if (KvGetNum(kv, flag, 0)) { + IRC_Send("PRIVMSG %s :%s", channel, text); + } + } while (KvGotoNextKey(kv)); + } + KvRewind(kv); + return true; +} + +// http://bit.ly/defcon diff --git a/scripting/include/sourceirc.inc b/scripting/include/sourceirc.inc new file mode 100755 index 0000000..51e046d --- /dev/null +++ b/scripting/include/sourceirc.inc @@ -0,0 +1,516 @@ +/* + This file is part of SourceIRC. + + SourceIRC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SourceIRC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SourceIRC. If not, see . +*/ + +#define IRC_VERSION "0.1.14" + +#define IRC_CMD_MAXLEN 64 // The maximum length of a command +#define IRC_NICK_MAXLEN 64 // The maximum length of a nickname +#define IRC_CHANNEL_MAXLEN 64 // The maximum length of a channel name +#define IRC_MAXLEN 512 // The maximum length of a line from the server. IRC RFC says maximum line length is 512, there again it also says a nicknames max length is 9. This might not be right. + +#define IRC_CMDFLAG_NOPREFIX 1<<0 // Allow command to be triggered even without a prefix. For example instead of typing "SourceIRC: command args" you could just type "command args" + +public SharedPlugin:__pl_sourceirc = +{ + name = "sourceirc", + file = "sourceirc.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +public __pl_sourceirc_SetNTVOptional() +{ + MarkNativeAsOptional("IRC_RegCmd"); + MarkNativeAsOptional("IRC_RegAdminCmd"); + MarkNativeAsOptional("IRC_Send"); + MarkNativeAsOptional("IRC_ReplyToCommand"); + MarkNativeAsOptional("IRC_GetCmdArgString"); + MarkNativeAsOptional("IRC_GetCmdArg"); + MarkNativeAsOptional("IRC_GetEventArg"); + MarkNativeAsOptional("IRC_CleanUp"); + MarkNativeAsOptional("IRC_GetHostMask"); + MarkNativeAsOptional("IRC_HookEvent"); + MarkNativeAsOptional("IRC_GetNick"); + MarkNativeAsOptional("IRC_ChannelHasFlag"); + MarkNativeAsOptional("IRC_GetUserFlagBits"); + MarkNativeAsOptional("IRC_GetAdminFlag"); + MarkNativeAsOptional("IRC_GetTeamColor"); +} + +/** + * Called when an IRC command is invoked. + * + * @param nick The nickname of the user who invoked hte command + * @param args Number of arguments that were in the argument string. + * @return An Action value. Not handling the command + * means that SourceIRC will report it as "Unknown Command." + */ +functag IRCCmd Action:public(const String:nick[], args); + +/** + * Called when an IRC event is fired. + * + * @param prefix This is the hostmask for IRC events that are triggered by a user, otherwise blank. + * @param args Number of arguments that are in the argument string. + * @return Only Plugin_Stop has any effect here, as this is a post hook. + */ + +functag IRCEvent Action:public(const String:prefix[], args); + +/** + * Registers a command people can use in IRC + * + * @param cmd String containing command to register + * @param callback A Function to use as a callback for when the command is invoked + * @param description Optional description to use for help. + * @param flags Optional command flags. + * @noreturn + */ + +native IRC_RegCmd(const String:cmd[], IRCCmd:callback, const String:description[]="", flags=0); + +/** + * Creates an IRC command as an administrative command. + * When this command is invoked, the access rights of the user are + * automatically checked before allowing it to continue. + * + * @param cmd String containing command to register. + * @param callback A function to use as a callback for when the command is invoked. + * @param adminflags Administrative flags (bitstring) to use for permissions. + * @param description Optional description to use for help. + * @param flags Optional command flags. + * @noreturn + */ + +native IRC_RegAdminCmd(const String:cmd[], IRCCmd:callback, adminflags, const String:description[]="", flags=0); + +/** + * Sends a packet to the IRC Server. \r\n is automatically appended for you. + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error String contains \r or \n. + */ + +native IRC_Send(const String:format[], any:...); + +/** + * Replys to a message (Using a notice) + * + * @param nick Nickname of the user to message + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ + +native IRC_ReplyToCommand(const String:nick[], const String:format[], any:...); + +/** + * Retrieves the entire command argument string in one lump from the current + * IRC command. + * + * @param buffer Buffer to use for storing the string. + * @param maxlength Maximum length of the buffer. + * @return Length of string written to buffer. + */ + +native IRC_GetCmdArgString(String:buffer[], maxlength); + +/** + * Retrieves a command argument given its index, from the current IRC command. + * @note Argument indexes start at 1; 0 retrieves the command name. + * + * @param argnum Argument number to retrieve. + * @param buffer Buffer to use for storing the string. + * @param maxlength Maximum length of the buffer. + * @return Length of string written to buffer. + */ + +native IRC_GetCmdArg(argnum, String:buffer[], maxlength); + + +/** + * Gets an event argument, note that this is different to SourceMods GetEvent* + * function, and works more like GetCmdArg. + * + * @param argnum Argument number to retrieve. + * @param buffer Buffer to use for storing the string. + * @param maxlength Maximum length of the buffer. + * @return Length of string written to buffer. + */ + +native IRC_GetEventArg(argnum, String:buffer[], maxlength); + +/** + * Sends a message to all channels with the given flag. + * + * @param flag The flag channels must have to recieve this message. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ + +native IRC_MsgFlaggedChannels(const String:flag[], const String:format[], any:...); + +/** + * If your plugin calls any of the IRC_Reg* functions + * you must call this function upon unload otherwise you will cause errors + * in the core. + * + * @noreturn + */ + +native IRC_CleanUp(); + +/** + * Gets the hostmask (eg Nick!ident@address.com) for the user executing + * a command, only valid inside a IRCCmd callback. + * + * @param hostmask String to store the hostmask in + * @param maxlength Maximum length of the buffer. + * @noreturn + */ + +native IRC_GetHostMask(String:hostmask[], maxlength); + +/** + * Creates a hook for when an IRC event is fired. + * + * @param name Name of event. + * @param callback An IRCEvent function pointer. + * @noreturn + */ + +native IRC_HookEvent(const String:event[], IRCEvent:callback); + +/** + * Gets the bots current nickname + * + * @param mynick String to store the bots name in. + * @param maxlength Maximum length of the buffer. + * @noreturn + */ + +native IRC_GetNick(String:mynick[], maxlength); + +/** + * Checks if a channel has a flag. + * + * @param channel Channel to check. + * @param flag Flag to check. + * @return True if channel has flag, otherwise false. + */ + +native IRC_ChannelHasFlag(const String:channel[], const String:flag[]); + +/** + * Returns users access flags. If the client is not an admin, + * the result is always 0. + * + * @param hostmask hostmask of the user to check. + * @return Flags + */ + +native IRC_GetUserFlagBits(const String:hostmask[]); + +/** + * Returns whether or not a flag is enabled on an admin. + * + * @param hostmask hostmask of the user to check. + * @param flag Admin flag to use. + * @return True if enabled, false otherwise. + */ + +native bool:IRC_GetAdminFlag(const String:hostmask[], AdminFlag:flag); + +/** + * Gets the IRC color number for a team as specified in sourceirc.cfg + * + * @param team Team index + * @return Color code if available, otherwise -1. + */ + +native IRC_GetTeamColor(team); + +/** + * Gets the servers domain/external IP as set in sourceirc.cfg + * + * @param domain String to store domain in + * @param maxlength Maximum length of the buffer. + * + */ + +native IRC_GetServerDomain(String:domain[], maxlength); + +forward IRC_Connected(); + +forward IRC_RetrieveUserFlagBits(const String:hostmask[], &flagbits); + + + + + +/** + * Performs a standard IRC Like wildcard match, useful for hostmasks. + * + * @param str String to check + * @param wildcard Wildcard to check against string + * @return true if match, false otherwise. + */ + +stock bool:IsWildCardMatch(const String:str[], const String:wildcard[]) { + new wildpos = 0; + for (new a = 0; a <= strlen(str); a++) { + if (wildcard[wildpos] == '*') { + if (wildpos == strlen(wildcard)) + return true; + if (CharToLower(str[a]) == CharToLower(wildcard[wildpos+1])) + wildpos += 2; + } + else if (wildcard[wildpos] == '?') { + wildpos++; + } + else if (CharToLower(str[a]) == CharToLower(wildcard[wildpos])) { + wildpos++; + } + else { + return false; + } + } + if (wildpos == strlen(wildcard)) + return false; + return true; +} + +/** + * Breaks a string into pieces and stores each piece into an adt_array of buffers. + * + * @param text The string to split. + * @param split The string to use as a split delimiter. + * @param adt_array An adt_array of string buffers. + * @param maxlength Maximum length of each string buffer. + * @return Number of strings retrieved. + */ + +stock ExplodeString_Array(const String:source[], const String:split[], Handle:adt_array, maxlength) { + ClearArray(adt_array); + decl String:arg[maxlength]; + new strpos = 0; + for (new i = 0; i <= strlen(source); i++) { + if (!strncmp(source[i], split, strlen(split))) { + arg[strpos] = '\x00'; + PushArrayString(adt_array, arg); + + strpos = 0; + i += strlen(split); + } + if (strpos < maxlength) + arg[strpos] = source[i]; + strpos++; + } + arg[strpos] = '\x00'; + PushArrayString(adt_array, arg); + return GetArraySize(adt_array); +} + +/** + * Wraps ProcessTargetString() and handles producing error messages for + * bad targets. + * + * @param client Client who issued command + * @param target Client's target argument + * @param nobots Optional. Set to true if bots should NOT be targetted + * @param immunity Optional. Set to false to ignore target immunity. + * @return Index of target client, or -1 on error. + */ +stock IRC_FindTarget(const String:nick[], const String:target[], bool:nobots = false, bool:immunity = true) +{ + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[1], target_count, bool:tn_is_ml; + + new flags = COMMAND_FILTER_NO_MULTI; + if (nobots) + { + flags |= COMMAND_FILTER_NO_BOTS; + } + if (!immunity) + { + flags |= COMMAND_FILTER_NO_IMMUNITY; + } + + if ((target_count = ProcessTargetString( + target, + 0, + target_list, + 1, + flags, + target_name, + sizeof(target_name), + tn_is_ml)) > 0) + { + return target_list[0]; + } + else + { + IRC_ReplyToTargetError(nick, target_count); + return -1; + } +} + +/** + * Replies to a client with a given message describing a targetting + * failure reason. + * + * Note: The translation phrases are found in common.phrases.txt. + * + * @param client Client index, or 0 for server. + * @param reason COMMAND_TARGET reason. + * @noreturn + */ +stock IRC_ReplyToTargetError(const String:nick[], reason) { + switch (reason) + { + case COMMAND_TARGET_NONE: + { + IRC_ReplyToCommand(nick, "[SM] %t", "No matching client"); + } + case COMMAND_TARGET_NOT_ALIVE: + { + IRC_ReplyToCommand(nick, "[SM] %t", "Target must be alive"); + } + case COMMAND_TARGET_NOT_DEAD: + { + IRC_ReplyToCommand(nick, "[SM] %t", "Target must be dead"); + } + case COMMAND_TARGET_NOT_IN_GAME: + { + IRC_ReplyToCommand(nick, "[SM] %t", "Target is not in game"); + } + case COMMAND_TARGET_IMMUNE: + { + IRC_ReplyToCommand(nick, "[SM] %t", "Unable to target"); + } + case COMMAND_TARGET_EMPTY_FILTER: + { + IRC_ReplyToCommand(nick, "[SM] %t", "No matching clients"); + } + case COMMAND_TARGET_NOT_HUMAN: + { + IRC_ReplyToCommand(nick, "[SM] %t", "Cannot target bot"); + } + case COMMAND_TARGET_AMBIGUOUS: + { + IRC_ReplyToCommand(nick, "[SM] %t", "More than one client matched"); + } + } +} + +/** + * Extracts a nickname from a hostmask. + * + * @param hostmask Hostmask to get the nickname from. + * @param nick String to store the nickname in. + * @param maxlength Maximum length of the nickname. + * @noreturn + */ + +stock IRC_GetNickFromHostMask(const String:hostmask[], String:nick[], maxlength) { + for (new i = 0; i <= maxlength; i++) { + if (hostmask[i] == '!') { + nick[i] = '\x00'; + break; + } + nick[i] = hostmask[i]; + } +} + +/** + * Strips IRC Color codes from a string + * + * @param str String to strip + * @param maxlength maximum length of str + * @noreturn + */ + +stock IRC_Strip(String:str[], maxlength) { + for (new i = 0; i <= strlen(str); i++) { + // Underline Reverse Color codes off Bold + if (str[i] == '\x1F' || str[i] == '\x16' || str[i] == '\x0f' || str[i] == '\x02') + RemoveChar(str, maxlength, i); + // Strip color codes + if (str[i] == '\x03') { + RemoveChar(str, maxlength, i); + new ignorelast = false; + + if (str[i] > 47 && str[i] < 58) { + RemoveChar(str, maxlength, i); + if ((str[i] > 47 && str[i] < 58) || str[i] == ',') { + if (str[i] == ',') + ignorelast = true; + RemoveChar(str, maxlength, i); + if ((str[i] > 47 && str[i] < 58) || str[i] == ',') { + RemoveChar(str, maxlength, i); + if (str[i] > 47 && str[i] < 58) { + RemoveChar(str, maxlength, i); + if (str[i] > 47 && str[i] < 58 && !ignorelast) { + RemoveChar(str, maxlength, i); + } + } + } + } + } + i--; + } + } +} + +/** + * Removes a character from a string. + * + * @param str String to strip. + * @param maxlength maximum length of str. + * @param c character index to remove. + * @noreturn + */ + +stock RemoveChar(String:str[], maxlen, c) { + for (new i = c; i < maxlen-1; i++) { + str[i] = str[i+1]; + } + str[maxlen-1] = '\0'; +} + + +/** + * Strips Game Color codes from a string + * + * @param str String to strip + * @param maxlength maximum length of str + * @noreturn + */ + +stock IRC_StripGame(String:str[], maxlen) { + for (new i = 0; i <= strlen(str); i++) { + // Default Team/LightGreen Green Olive + if (str[i] == '\x01' || str[i] == '\x03' || str[i] == '\x04' || str[i] == '\x05') + RemoveChar(str, maxlen, i); + } +} diff --git a/translations/sourceirc.phrases.txt b/translations/sourceirc.phrases.txt new file mode 100644 index 0000000..457edd9 --- /dev/null +++ b/translations/sourceirc.phrases.txt @@ -0,0 +1,79 @@ +"Phrases" +{ + // Core + "Hi" + { + "#format" "{1:s}" + "en" "Hi, i'm {1}. Try typing {1} help for information on how to use me." + } + "Unknown Command" + { + "#format" "{1:s}" + "en" "Unknown command: {1}" + } + "Access Denied" + { + "#format" "{1:s}" + "en" "Access Denied: {1}" + } + "Not Connected" + { + "en" "Error: Not connected to IRC yet" + } + + // Changemap + "Changing Map" + { + "#format" "{1:s}" + "en" "Changing map to {1}" + } + "Multiple Maps" + { + "#format" "{1:s}" + "en" "{1} matches more than one map" + } + "Invalid Map" + { + "#format" "{1:s}" + "en" "Map {1} does not exist" + } + + // RelayAll - You can use this section for theming relay all, which is why there are alot of extra unused parameters you can use. You can also set the translation string to "" to completely disable an event. + "Player Connected" + { + "#format" "{1:s},{2:s},{3:d}" // {1} Name, {2} SteamID, {3} UserID + "en" "{1} connected." + } + "Player Disconnected" + { + "#format" "{1:s},{2:s},{3:d},{4:s}" // {1} Name, {2} SteamID, {3} UserID, {4} Disconnect reason + "en" "{1} disconnected ({4})." + } + "Changed Name" + { + "#format" "{1:s},{2:s},{3:d},{4:s}" // {1} Old Name, {2} SteamID, {3} UserID, {4} New name + "en" "{1} changed name to {4}" + } + "Map Changing" + { + "en" "-- Map Changing --" + } + "Map Changed" + { + "#format" "{1:s}" + "en" "-- Map Changed to {1} --" + } + + // rcon + "RCON Busy" + { + "en" "RCON is busy, please try again" + } + + // Ban + "Invalid Player" + { + "#format" "{1:s}" + "en" "Invalid player: {1}" + } +}