Promover servidor

Initial release 1.0.0

Browse Source
1.0.0 - Initial release.
Daemon666 committed commit 0c31446a200bf36ef82d3c9fa0c879bc5eab3fca
4 changed files with 282 additions and 0 deletions
+3 -0 CHANGELOG.md A
@@ -0,0 +1,3 @@
1 +## 1.0.0 (2026-05-26)
2 +
3 +1.0.0 - Initial release.
+1 -0 README.md A
@@ -0,0 +1 @@
1 +<p><strong>CSB Discord Relay</strong> forwards server events - map start/end, player connects and disconnects, and optionally public chat - to a Discord channel. Discord webhooks are HTTPS-only, so the plugin posts a small JSON body (<code>{"content": "..."}</code>) over plain HTTP to a local relay defined by <code>csb_discord_host/port/path</code> that forwards it to your webhook URL; any tiny reverse proxy or a ten-line script handles the TLS.</p><p>Messages sit in a queue and are sent one every <code>csb_discord_rate</code> seconds over a non-blocking socket, so a slow or dead relay never stalls the game thread.</p><h2>Features</h2><ul><li>16-slot queue; overflow drops instead of blocking.</li><li>Rate-limited sending, in-flight timeout.</li><li>Optional chat relay (off by default).</li></ul><h2>Installation</h2><ol><li>Enable the <code>sockets</code> module.</li><li>Point the <code>csb_discord_*</code> cvars at your local relay.</li><li>Compile, install, add to <code>plugins.ini</code>, change map.</li></ol>
+227 -0 addons/amxmodx/scripting/csb_discord_webhook.sma A
@@ -0,0 +1,227 @@
1 +/*
2 + * CSB Discord Relay
3 + * Copyright (C) 2026 counter-strike-boost.com
4 + *
5 + * Relays server events - map changes, player connects/disconnects and
6 + * optionally public chat - to Discord. Discord webhooks are HTTPS-only, so
7 + * the plugin posts a small JSON body over plain HTTP to a local relay
8 + * (csb_discord_host/port/path) that forwards it to your webhook URL; any
9 + * tiny reverse proxy or the companion relay script can do the TLS part.
10 + * Messages are queued and sent one every csb_discord_rate seconds on a
11 + * non-blocking socket, so the game thread never stalls on the HTTP call.
12 + *
13 + * This program is free software: you can redistribute it and/or modify it under
14 + * the terms of the GNU General Public License as published by the Free Software
15 + * Foundation, either version 3 of the License, or (at your option) any later
16 + * version. It is distributed in the hope that it will be useful, but WITHOUT
17 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 + * FOR A PARTICULAR PURPOSE. See <https://www.gnu.org/licenses/> for details.
19 + */
20 +
21 +#include <amxmodx>
22 +#include <sockets>
23 +
24 +new const PLUGIN[] = "CSB Discord Relay"
25 +new const VERSION[] = "1.0.0"
26 +new const AUTHOR[] = "counter-strike-boost.com"
27 +
28 +#define MAX_QUEUE 16
29 +#define TASK_PUMP 7200
30 +
31 +new g_szQueue[MAX_QUEUE][192]
32 +new g_iQueueHead, g_iQueueTail
33 +
34 +new g_iSock = -1
35 +new Float:g_fDeadline
36 +
37 +new g_pHost, g_pPort, g_pPath, g_pRate, g_pChat
38 +
39 +public plugin_init()
40 +{
41 + register_plugin(PLUGIN, VERSION, AUTHOR)
42 +
43 + g_pHost = register_cvar("csb_discord_host", "127.0.0.1")
44 + g_pPort = register_cvar("csb_discord_port", "8080")
45 + g_pPath = register_cvar("csb_discord_path", "/relay")
46 + g_pRate = register_cvar("csb_discord_rate", "2.0")
47 + g_pChat = register_cvar("csb_discord_chat", "0")
48 +
49 + register_clcmd("say", "hookSay")
50 + register_clcmd("say_team", "hookSay")
51 +
52 + new map[32], msg[160]
53 + get_mapname(map, charsmax(map))
54 + formatex(msg, charsmax(msg), "Map started: %s", map)
55 + enqueue(msg)
56 +
57 + set_task(1.0, "taskPump", TASK_PUMP, _, _, "b")
58 +}
59 +
60 +public plugin_end()
61 +{
62 + new map[32], msg[160]
63 + get_mapname(map, charsmax(map))
64 + formatex(msg, charsmax(msg), "Map ended: %s", map)
65 + enqueue(msg)
66 +
67 + /* try to flush immediately; the socket gets a moment from the engine */
68 + pump(true)
69 +
70 + if (g_iSock != -1)
71 + {
72 + socket_close(g_iSock)
73 + g_iSock = -1
74 + }
75 +}
76 +
77 +public client_putinserver(id)
78 +{
79 + if (is_user_bot(id))
80 + return
81 +
82 + new name[32], msg[160]
83 + get_user_name(id, name, charsmax(name))
84 + formatex(msg, charsmax(msg), "%s connected.", name)
85 + enqueue(msg)
86 +}
87 +
88 +public client_disconnect(id)
89 +{
90 + if (is_user_bot(id))
91 + return
92 +
93 + new name[32], msg[160]
94 + get_user_name(id, name, charsmax(name))
95 + formatex(msg, charsmax(msg), "%s disconnected.", name)
96 + enqueue(msg)
97 +}
98 +
99 +public hookSay(id)
100 +{
101 + if (get_pcvar_num(g_pChat) && is_user_connected(id))
102 + {
103 + new said[160]
104 + read_args(said, charsmax(said))
105 + remove_quotes(said)
106 + trim(said)
107 +
108 + if (said[0] && said[0] != '/')
109 + {
110 + new name[32], msg[192]
111 + get_user_name(id, name, charsmax(name))
112 + formatex(msg, charsmax(msg), "%s: %s", name, said)
113 + enqueue(msg)
114 + }
115 + }
116 +
117 + return PLUGIN_CONTINUE
118 +}
119 +
120 +enqueue(const msg[])
121 +{
122 + new next = (g_iQueueTail + 1) % MAX_QUEUE
123 +
124 + if (next == g_iQueueHead)
125 + return /* queue full: drop rather than block */
126 +
127 + copy(g_szQueue[g_iQueueTail], charsmax(g_szQueue[]), msg)
128 + g_iQueueTail = next
129 +}
130 +
131 +public taskPump()
132 +{
133 + pump(false)
134 +}
135 +
136 +pump(bool:flush)
137 +{
138 + static Float:nextSend = 0.0
139 + new Float:now = get_gametime()
140 +
141 + /* state machine for an in-flight request */
142 + if (g_iSock != -1)
143 + {
144 + if (socket_is_readable(g_iSock, 0))
145 + {
146 + static buf[256]
147 + socket_recv(g_iSock, buf, charsmax(buf))
148 + socket_close(g_iSock)
149 + g_iSock = -1
150 + }
151 + else if (now > g_fDeadline)
152 + {
153 + socket_close(g_iSock)
154 + g_iSock = -1
155 + }
156 + else
157 + {
158 + return
159 + }
160 + }
161 +
162 + if (!flush && now < nextSend)
163 + return
164 +
165 + if (g_iQueueHead == g_iQueueTail)
166 + return
167 +
168 + new host[64], path[96]
169 + get_pcvar_string(g_pHost, host, charsmax(host))
170 + get_pcvar_string(g_pPath, path, charsmax(path))
171 + new port = get_pcvar_num(g_pPort)
172 +
173 + static msg[192]
174 + copy(msg, charsmax(msg), g_szQueue[g_iQueueHead])
175 + g_iQueueHead = (g_iQueueHead + 1) % MAX_QUEUE
176 +
177 + /* escape the message into a JSON string literal */
178 + static json[256]
179 + new j = 0
180 + json[j++] = '"'
181 +
182 + for (new i = 0; msg[i] && j < charsmax(json) - 8; i++)
183 + {
184 + new c = msg[i]
185 +
186 + if (c == '"' || c == 92)
187 + json[j++] = 92
188 +
189 + json[j++] = c
190 + }
191 +
192 + json[j++] = '"'
193 + json[j] = 0
194 +
195 + static body[320]
196 + formatex(body, charsmax(body), "{^"content^": %s}", json)
197 +
198 + static request[512]
199 + new reqlen = formatex(request, charsmax(request),
200 + "POST %s HTTP/1.1^nHost: %s^nContent-Type: application/json^nContent-Length: %d^nConnection: close^n^n%s",
201 + path, host, strlen(body), body)
202 +
203 + new err
204 + g_iSock = socket_open(host, port, SOCKET_TCP, err, SOCK_NON_BLOCKING)
205 +
206 + if (err != SOCK_ERROR_OK || g_iSock == -1)
207 + {
208 + log_amx("[CSB Discord] socket_open failed (%d)", err)
209 + g_iSock = -1
210 + return
211 + }
212 +
213 + /* non-blocking connect: wait until writable, then send */
214 + if (socket_is_writable(g_iSock, 100000))
215 + {
216 + socket_send(g_iSock, request, reqlen)
217 + g_fDeadline = now + 4.0
218 + }
219 + else
220 + {
221 + socket_close(g_iSock)
222 + g_iSock = -1
223 + log_amx("[CSB Discord] relay not reachable at %s:%d", host, port)
224 + }
225 +
226 + nextSend = now + get_pcvar_float(g_pRate)
227 +}
+51 -0 csb.json A
@@ -0,0 +1,51 @@
1 +{
2 + "name": "CSB Discord Relay",
3 + "category": "utility",
4 + "tags": "discord, webhook, relay",
5 + "image": "",
6 + "original_author": "",
7 + "original_url": "",
8 + "license": "GPL-3.0",
9 + "title": "CSB Discord Relay",
10 + "short_desc": "Relays map changes, connects and optional chat to Discord via a queued, rate-limited local HTTP relay.",
11 + "version": "1.0.0",
12 + "amxx_min": "1.9",
13 + "amxx_compile_default": "1.9",
14 + "rehlds_ok": true,
15 + "hlds_ok": true,
16 + "regamedll_required": false,
17 + "metamod_flavor": "any",
18 + "reapi_required": false,
19 + "modules": "sockets",
20 + "install_notes": "1. Enable the sockets module in addons/amxmodx/configs/modules.ini\n2. Run a local HTTP->HTTPS relay for your Discord webhook and set the csb_discord_* cvars\n3. Compile, install, add to plugins.ini, change map",
21 + "known_conflicts": "",
22 + "cvars": [
23 + {
24 + "name": "csb_discord_host",
25 + "default": "127.0.0.1",
26 + "description": "Relay host."
27 + },
28 + {
29 + "name": "csb_discord_port",
30 + "default": "8080",
31 + "description": "Relay port."
32 + },
33 + {
34 + "name": "csb_discord_path",
35 + "default": "/relay",
36 + "description": "Relay URL path."
37 + },
38 + {
39 + "name": "csb_discord_rate",
40 + "default": "2.0",
41 + "description": "Minimum seconds between sent messages."
42 + },
43 + {
44 + "name": "csb_discord_chat",
45 + "default": "0",
46 + "description": "Also relay public chat (1) or not (0)."
47 + }
48 + ],
49 + "commands": [],
50 + "main": "addons/amxmodx/scripting/csb_discord_webhook.sma"
51 +}
0 52