Daemon666 / CSB Admin Logger Public

Logs every admin command (kick/ban/gag/slay/slap/map) with SteamID, target, map and time to MySQL via threaded SQLx, with a flat-file fallback and a csb_log_action native.

v1.0.0 162 téléchargements GPL-3.0 Dernière mise à jour Aug 24, 2026
csb_admin_logger.sma v1.0.0 201 lignes · 5.8 KB Brut
1 /*
2 * CSB Admin Logger
3 * Copyright (C) 2026 counter-strike-boost.com
4 *
5 * Logs every admin command (kick, ban, gag, slay, map change and any action
6 * pushed through the csb_log_action native) with the admin's SteamID, name, the
7 * target/args, the current map and a timestamp into a MySQL table via threaded
8 * SQLx. If the database is unreachable it falls back to a flat log file.
9 *
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 3 of the License, or (at your option) any later
13 * version. It is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See <https://www.gnu.org/licenses/> for details.
16 */
17
18 #include <amxmodx>
19 #include <amxmisc>
20 #include <sqlx>
21
22 new const PLUGIN[] = "CSB Admin Logger"
23 new const VERSION[] = "1.0.0"
24 new const AUTHOR[] = "counter-strike-boost.com"
25
26 #define FALLBACK_FILE "addons/amxmodx/logs/csb_admin_actions.log"
27
28 new const g_watched[][] = { "amx_kick", "amx_ban", "amx_banip", "amx_gag", "amx_slay", "amx_slap", "amx_map" }
29
30 new Handle:g_hTuple = Empty_Handle
31 new bool:g_bDbReady = false
32 new bool:g_bTableChecked = false
33
34 new g_pHost, g_pUser, g_pPass, g_pDb
35
36 public plugin_init()
37 {
38 register_plugin(PLUGIN, VERSION, AUTHOR)
39
40 g_pHost = register_cvar("csb_log_sql_host", "127.0.0.1")
41 g_pUser = register_cvar("csb_log_sql_user", "root")
42 g_pPass = register_cvar("csb_log_sql_pass", "")
43 g_pDb = register_cvar("csb_log_sql_db", "amxx")
44
45 for (new i = 0; i < sizeof(g_watched); i++)
46 register_clcmd(g_watched[i], "cmdWatch")
47 }
48
49 public plugin_natives()
50 {
51 register_native("csb_log_action", "native_log_action", 0)
52 }
53
54 public plugin_cfg()
55 {
56 new host[64], user[32], pass[32], db[32]
57 get_pcvar_string(g_pHost, host, charsmax(host))
58 get_pcvar_string(g_pUser, user, charsmax(user))
59 get_pcvar_string(g_pPass, pass, charsmax(pass))
60 get_pcvar_string(g_pDb, db, charsmax(db))
61
62 if (!host[0] || !user[0] || !db[0])
63 {
64 log_amx("[CSB Logger] SQL not configured; using flat-file fallback only.")
65 return
66 }
67
68 g_hTuple = SQL_MakeDbTuple(host, user, pass, db)
69 g_bDbReady = true
70
71 new query[256]
72 formatex(query, charsmax(query),
73 "CREATE TABLE IF NOT EXISTS csb_admin_log (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, \
74 stamp DATETIME, map VARCHAR(64), admin_name VARCHAR(64), admin_auth VARCHAR(48), \
75 target VARCHAR(64), action VARCHAR(255))")
76
77 SQL_ThreadQuery(g_hTuple, "queryCreate", query)
78 }
79
80 public plugin_end()
81 {
82 if (g_hTuple != Empty_Handle)
83 SQL_FreeHandle(g_hTuple)
84 }
85
86 public queryCreate(failstate, Handle:query, error[], errcode, data[], size, Float:queuetime)
87 {
88 if (failstate != TQUERY_SUCCESS)
89 {
90 log_amx("[CSB Logger] table check failed (%d): %s - falling back to file.", errcode, error)
91 g_bDbReady = false
92 return
93 }
94
95 g_bTableChecked = true
96 }
97
98 /* native: csb_log_action(const admin_auth[], const admin_name[], const target[], const action[]) */
99 public native_log_action(plugin, params)
100 {
101 new adminAuth[48], adminName[64], target[64], action[128]
102 get_string(1, adminAuth, charsmax(adminAuth))
103 get_string(2, adminName, charsmax(adminName))
104 get_string(3, target, charsmax(target))
105 get_string(4, action, charsmax(action))
106
107 storeAction(adminName, adminAuth, target, action)
108 return 1
109 }
110
111 public cmdWatch(id)
112 {
113 new cmd[32]
114 read_argv(0, cmd, charsmax(cmd))
115
116 new target[64]
117 read_argv(1, target, charsmax(target))
118
119 new args[128]
120 read_args(args, charsmax(args))
121 remove_quotes(args)
122
123 new adminName[64], adminAuth[48]
124 getName(id, adminName, charsmax(adminName))
125 if (id)
126 get_user_authid(id, adminAuth, charsmax(adminAuth))
127 else
128 copy(adminAuth, charsmax(adminAuth), "CONSOLE")
129
130 new action[160]
131 formatex(action, charsmax(action), "%s %s", cmd, args)
132
133 storeAction(adminName, adminAuth, target, action)
134 return PLUGIN_CONTINUE
135 }
136
137 storeAction(const adminName[], const adminAuth[], const target[], const action[])
138 {
139 new sName[64], sAuth[48], sTarget[64], sAction[160]
140 sanitize(sName, charsmax(sName), adminName)
141 sanitize(sAuth, charsmax(sAuth), adminAuth)
142 sanitize(sTarget, charsmax(sTarget), target)
143 sanitize(sAction, charsmax(sAction), action)
144
145 new map[64]
146 get_mapname(map, charsmax(map))
147
148 if (g_bDbReady && g_hTuple != Empty_Handle)
149 {
150 new query[512]
151 formatex(query, charsmax(query),
152 "INSERT INTO csb_admin_log (stamp, map, admin_name, admin_auth, target, action) \
153 VALUES (NOW(), '%s', '%s', '%s', '%s', '%s')",
154 map, sName, sAuth, sTarget, sAction)
155
156 SQL_ThreadQuery(g_hTuple, "queryInsert", query)
157 }
158 else
159 {
160 writeFallback(map, sName, sAuth, sTarget, sAction)
161 }
162 }
163
164 public queryInsert(failstate, Handle:query, error[], errcode, data[], size, Float:queuetime)
165 {
166 if (failstate != TQUERY_SUCCESS)
167 {
168 log_amx("[CSB Logger] insert failed (%d): %s", errcode, error)
169 g_bDbReady = false
170 }
171 }
172
173 writeFallback(const map[], const name[], const auth[], const target[], const action[])
174 {
175 new stamp[32]
176 get_time("%Y-%m-%d %H:%M:%S", stamp, charsmax(stamp))
177
178 new line[320]
179 formatex(line, charsmax(line), "[%s] map=%s admin=%s (%s) target=%s action=%s",
180 stamp, map, name, auth, target, action)
181
182 write_file(FALLBACK_FILE, line)
183 }
184
185 /* strip characters that would break the single-quoted SQL literal */
186 sanitize(out[], len, const in[])
187 {
188 copy(out, len, in)
189 replace_all(out, len, "'", " ")
190 replace_all(out, len, "\", " ")
191 replace_all(out, len, "^n", " ")
192 }
193
194 getName(id, name[], len)
195 {
196 if (id)
197 get_user_name(id, name, len)
198 else
199 copy(name, len, "CONSOLE")
200 }
201