]>
Commit | Line | Data |
---|---|---|
d5c004ba GB |
1 | #include <stdio.h> |
2 | #include <stdarg.h> | |
3 | #include "../lib/version.h" | |
4 | #include "../dbapi2/dbapi2.h" | |
5 | #include "../core/error.h" | |
6 | #include "../core/hooks.h" | |
7 | #include "../core/schedule.h" | |
8 | #include "../control/control.h" | |
9 | #include "../irc/irc.h" | |
10 | #include "../lua/lua.h" | |
11 | ||
12 | MODULE_VERSION(""); | |
13 | ||
14 | DBAPIConn *a4statsdb; | |
15 | ||
16 | static int a4stats_connectdb(void) { | |
17 | if(!a4statsdb) { | |
18 | a4statsdb = dbapi2open(NULL, "a4stats"); | |
19 | if(!a4statsdb) { | |
20 | Error("a4stats", ERR_WARNING, "Unable to connect to db -- not loaded."); | |
21 | return 0; | |
22 | } | |
23 | } | |
24 | ||
25 | a4statsdb->createtable(a4statsdb, NULL, NULL, | |
c2a4bd5a | 26 | "CREATE TABLE ? (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(64) UNIQUE, timestamp INT, active INT DEFAULT 1, deleted INT DEFAULT 0, privacy INT DEFAULT 1)", "T", "channels"); |
d5c004ba | 27 | |
f79faea1 GB |
28 | a4statsdb->createtable(a4statsdb, NULL, NULL, |
29 | "CREATE TABLE ? (channelid INT, kicker VARCHAR(64), kickerid INT, victim VARCHAR(64), victimid INT, timestamp INT, reason VARCHAR(256))", "T", "kicks"); | |
30 | ||
31 | a4statsdb->squery(a4statsdb, "CREATE INDEX ? ON kicks (channelid)", "T", "kicks_channelid_index"); | |
d5c004ba GB |
32 | |
33 | a4statsdb->createtable(a4statsdb, NULL, NULL, | |
f79faea1 | 34 | "CREATE TABLE ? (channelid INT, setby VARCHAR(64), setbyid INT, timestamp INT, topic VARCHAR(512))", "T", "topics"); |
d5c004ba | 35 | |
f79faea1 | 36 | a4statsdb->squery(a4statsdb, "CREATE INDEX ? ON topics (channelid)", "T", "topics_channelid_index"); |
d5c004ba GB |
37 | |
38 | a4statsdb->createtable(a4statsdb, NULL, NULL, | |
f79faea1 GB |
39 | "CREATE TABLE ? (channelid INT, account VARCHAR(64), accountid INT, seen INT DEFAULT 0, rating INT DEFAULT 0, lines INT DEFAULT 0, chars INT DEFAULT 0, words INT DEFAULT 0, " |
40 | "h0 INT DEFAULT 0, h1 INT DEFAULT 0, h2 INT DEFAULT 0, h3 INT DEFAULT 0, h4 INT DEFAULT 0, h5 INT DEFAULT 0, " | |
41 | "h6 INT DEFAULT 0, h7 INT DEFAULT 0, h8 INT DEFAULT 0, h9 INT DEFAULT 0, h10 INT DEFAULT 0, h11 INT DEFAULT 0, " | |
42 | "h12 INT DEFAULT 0, h13 INT DEFAULT 0, h14 INT DEFAULT 0, h15 INT DEFAULT 0, h16 INT DEFAULT 0, h17 INT DEFAULT 0, " | |
43 | "h18 INT DEFAULT 0, h19 INT DEFAULT 0, h20 INT DEFAULT 0, h21 INT DEFAULT 0, h22 INT DEFAULT 0, h23 INT DEFAULT 0, " | |
d5c004ba GB |
44 | "last VARCHAR(512), quote VARCHAR(512), quotereset INT, mood_happy INT DEFAULT 0, mood_sad INT DEFAULT 0, questions INT DEFAULT 0, yelling INT DEFAULT 0, caps INT DEFAULT 0, " |
45 | "slaps INT DEFAULT 0, slapped INT DEFAULT 0, highlights INT DEFAULT 0, kicks INT DEFAULT 0, kicked INT DEFAULT 0, ops INT DEFAULT 0, deops INT DEFAULT 0, actions INT DEFAULT 0, skitzo INT DEFAULT 0, foul INT DEFAULT 0, " | |
46 | "firstseen INT DEFAULT 0, curnick VARCHAR(16))", "T", "users"); | |
47 | ||
f79faea1 GB |
48 | a4statsdb->squery(a4statsdb, "CREATE INDEX ? ON users (channelid)", "T", "users_channelid_index"); |
49 | a4statsdb->squery(a4statsdb, "CREATE UNIQUE INDEX ? ON users (channelid, account, accountid)", "T", "users_channelid_account_accountid_index"); | |
a190db93 | 50 | a4statsdb->squery(a4statsdb, "CREATE INDEX ? ON users (channelid, lines)", "T", "users_channelid_lines_index"); |
d5c004ba GB |
51 | |
52 | return 1; | |
53 | } | |
54 | ||
55 | static void a4stats_closedb(void) { | |
56 | if(!a4statsdb) | |
57 | return; | |
58 | ||
59 | a4statsdb->close(a4statsdb); | |
60 | a4statsdb = NULL; | |
61 | } | |
62 | ||
63 | static int a4stats_lua_escape_string(lua_State *ps) { | |
64 | const char *input; | |
65 | char *buf, *buf2; | |
66 | size_t len, i, o; | |
67 | ||
68 | if (!lua_isstring(ps, 1)) | |
69 | LUA_RETURN(ps, LUA_FAIL); | |
70 | ||
71 | input = lua_tostring(ps, 1); | |
72 | len = strlen(input); | |
73 | ||
74 | buf = malloc(len * 2 + 1); | |
75 | ||
76 | if (!buf) | |
77 | LUA_RETURN(ps, LUA_FAIL); | |
78 | ||
79 | a4statsdb->escapestring(a4statsdb, buf, input, len); | |
80 | ||
81 | buf2 = malloc(len * 4 + 1); | |
82 | ||
83 | if (!buf2) { | |
84 | free(buf); | |
85 | LUA_RETURN(ps, LUA_FAIL); | |
86 | } | |
87 | ||
88 | /* escape "?" */ | |
89 | o = 0; | |
90 | for (i = 0; buf[i]; i++) { | |
91 | if (buf[i] == '?') { | |
92 | buf2[i + o] = '\\'; | |
93 | o++; | |
94 | } | |
95 | ||
96 | buf2[i + o] = buf[i]; | |
97 | } | |
98 | buf2[i + o] = '\0'; | |
99 | ||
100 | free(buf); | |
101 | ||
102 | lua_pushstring(ps, buf2); | |
103 | ||
104 | free(buf2); | |
105 | ||
106 | return 1; | |
107 | } | |
108 | ||
f79faea1 GB |
109 | typedef struct db_callback_info { |
110 | struct db_callback_info *next; | |
d5c004ba GB |
111 | |
112 | lua_State *interp; | |
113 | char callback[64]; | |
114 | int uarg_index; | |
f79faea1 GB |
115 | } db_callback_info; |
116 | ||
117 | static db_callback_info *dci_head; | |
118 | ||
119 | static void a4stats_delete_dci(db_callback_info *dci) { | |
120 | db_callback_info **pnext; | |
d5c004ba | 121 | |
f79faea1 GB |
122 | for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) { |
123 | if (*pnext == dci) { | |
124 | *pnext = dci->next; | |
125 | break; | |
126 | } | |
127 | } | |
128 | ||
129 | free(dci); | |
130 | } | |
d5c004ba GB |
131 | |
132 | static void a4stats_fetch_user_cb(const struct DBAPIResult *result, void *uarg) { | |
f79faea1 | 133 | db_callback_info *dci = uarg; |
d5c004ba GB |
134 | time_t seen = 0, quotereset = 0; |
135 | ||
136 | if (result) { | |
137 | if (result->success) { | |
138 | while (result->next(result)) { | |
139 | seen = (result->get(result, 0)) ? (time_t)strtoul(result->get(result, 0), NULL, 10) : 0; | |
140 | quotereset = (result->get(result, 1)) ? (time_t)strtoul(result->get(result, 1), NULL, 10) : 0; | |
141 | } | |
142 | } | |
143 | ||
144 | result->clear(result); | |
145 | } | |
146 | ||
f79faea1 GB |
147 | if (dci->interp) { |
148 | lua_vpcall(dci->interp, dci->callback, "llR", seen, quotereset, dci->uarg_index); | |
149 | luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index); | |
d5c004ba GB |
150 | } |
151 | ||
f79faea1 | 152 | a4stats_delete_dci(dci); |
d5c004ba GB |
153 | } |
154 | ||
155 | static int a4stats_lua_fetch_user(lua_State *ps) { | |
f79faea1 GB |
156 | const char *account, *callback; |
157 | unsigned long channelid, accountid; | |
158 | db_callback_info *dci; | |
d5c004ba | 159 | |
f79faea1 | 160 | if (!lua_islong(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4)) |
d5c004ba GB |
161 | LUA_RETURN(ps, LUA_FAIL); |
162 | ||
f79faea1 | 163 | channelid = lua_tonumber(ps, 1); |
d5c004ba GB |
164 | account = lua_tostring(ps, 2); |
165 | accountid = lua_tonumber(ps, 3); | |
166 | callback = lua_tostring(ps, 4); | |
167 | ||
f79faea1 GB |
168 | dci = malloc(sizeof(*dci)); |
169 | dci->interp = ps; | |
d5c004ba | 170 | |
f79faea1 GB |
171 | strncpy(dci->callback, callback, sizeof(dci->callback)); |
172 | dci->callback[sizeof(dci->callback) - 1] = '\0'; | |
d5c004ba GB |
173 | |
174 | lua_pushvalue(ps, 5); | |
f79faea1 | 175 | dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX); |
d5c004ba | 176 | |
f79faea1 | 177 | a4statsdb->query(a4statsdb, a4stats_fetch_user_cb, dci, "SELECT seen, quotereset FROM ? WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)", "TUUs", "users", channelid, accountid, account); |
d5c004ba GB |
178 | |
179 | LUA_RETURN(ps, LUA_OK); | |
180 | } | |
181 | ||
182 | typedef struct user_update_info { | |
183 | int stage; | |
184 | char *update; | |
f79faea1 | 185 | unsigned long channelid; |
d5c004ba GB |
186 | char *account; |
187 | unsigned long accountid; | |
188 | } user_update_info; | |
189 | ||
190 | static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) { | |
191 | user_update_info *uui = uarg; | |
192 | ||
193 | uui->stage++; | |
194 | ||
195 | if (uui->stage == 1 || uui->stage == 3) | |
f79faea1 | 196 | a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account); |
d5c004ba GB |
197 | else { |
198 | if (result->affected > 0 || uui->stage == 4) { | |
199 | if (result->affected == 0 && uui->stage == 4) | |
200 | Error("a4stats", ERR_WARNING, "Unable to update user."); | |
201 | ||
202 | free(uui->update); | |
d5c004ba GB |
203 | free(uui->account); |
204 | free(uui); | |
205 | return; | |
206 | } | |
207 | ||
f79faea1 | 208 | a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, "INSERT INTO ? (channelid, account, accountid, firstseen) VALUES (?, ?, ?, ?)", "TUsUt", "users", uui->channelid, uui->account, uui->accountid, time(NULL)); |
d5c004ba GB |
209 | } |
210 | } | |
211 | ||
212 | static int a4stats_lua_update_user(lua_State *ps) { | |
f79faea1 GB |
213 | const char *account; |
214 | unsigned long channelid, accountid; | |
d5c004ba GB |
215 | char query[4096]; |
216 | int first = 1; | |
217 | user_update_info *uui; | |
218 | ||
f79faea1 | 219 | if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3)) |
d5c004ba GB |
220 | LUA_RETURN(ps, LUA_FAIL); |
221 | ||
f79faea1 | 222 | channelid = lua_tonumber(ps, 1); |
d5c004ba GB |
223 | account = lua_tostring(ps, 2); |
224 | accountid = lua_tonumber(ps, 3); | |
225 | ||
226 | strcpy(query, "UPDATE ? SET "); | |
227 | ||
228 | lua_pushvalue(ps, 4); | |
229 | lua_pushnil(ps); | |
230 | ||
231 | while (lua_next(ps, -2)) { | |
232 | const char *value = lua_tostring(ps, -1); | |
233 | ||
234 | if (first) | |
235 | first = 0; | |
236 | else | |
237 | strcat(query, ", "); | |
238 | ||
239 | strcat(query, value); | |
240 | ||
241 | lua_pop(ps, 1); | |
242 | } | |
243 | ||
244 | lua_pop(ps, 1); | |
245 | ||
f79faea1 | 246 | strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)"); |
d5c004ba GB |
247 | |
248 | uui = malloc(sizeof(*uui)); | |
249 | uui->stage = 0; | |
250 | uui->update = strdup(query); | |
f79faea1 | 251 | uui->channelid = channelid; |
d5c004ba GB |
252 | uui->account = strdup(account); |
253 | uui->accountid = accountid; | |
254 | ||
255 | a4stats_update_user_cb(NULL, uui); | |
256 | ||
257 | LUA_RETURN(ps, LUA_OK); | |
258 | } | |
259 | ||
f79faea1 GB |
260 | static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) { |
261 | db_callback_info *dci = uarg; | |
262 | unsigned long channelid; | |
263 | int active; | |
264 | char *channel; | |
265 | ||
266 | if (result) { | |
267 | if (result->success) { | |
268 | while (result->next(result)) { | |
269 | channelid = strtoul(result->get(result, 0), NULL, 10); | |
270 | channel = result->get(result, 1); | |
271 | active = atoi(result->get(result, 2)); | |
272 | ||
273 | if (dci->interp) | |
274 | lua_vpcall(dci->interp, dci->callback, "lslR", channelid, channel, active, dci->uarg_index); | |
275 | } | |
276 | } | |
277 | ||
278 | result->clear(result); | |
279 | } | |
280 | ||
281 | if (dci->interp) | |
282 | luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index); | |
283 | ||
284 | a4stats_delete_dci(dci); | |
285 | } | |
286 | ||
287 | static int a4stats_lua_fetch_channels(lua_State *ps) { | |
288 | const char *callback; | |
289 | db_callback_info *dci; | |
290 | ||
291 | if (!lua_isstring(ps, 1)) | |
292 | LUA_RETURN(ps, LUA_FAIL); | |
293 | ||
294 | callback = lua_tostring(ps, 1); | |
295 | ||
296 | dci = malloc(sizeof(*dci)); | |
297 | dci->interp = ps; | |
298 | ||
299 | strncpy(dci->callback, callback, sizeof(dci->callback)); | |
300 | dci->callback[sizeof(dci->callback) - 1] = '\0'; | |
301 | ||
302 | lua_pushvalue(ps, 2); | |
303 | dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX); | |
304 | ||
305 | a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels"); | |
306 | ||
307 | LUA_RETURN(ps, LUA_OK); | |
308 | } | |
309 | ||
5bf3ab9d | 310 | static int a4stats_lua_enable_channel(lua_State *ps) { |
f79faea1 GB |
311 | if (!lua_isstring(ps, 1)) |
312 | LUA_RETURN(ps, LUA_FAIL); | |
313 | ||
899f2ee4 | 314 | a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL)); |
c2a4bd5a | 315 | a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps, 1)); |
5bf3ab9d GB |
316 | |
317 | LUA_RETURN(ps, LUA_OK); | |
318 | } | |
319 | ||
320 | static int a4stats_lua_disable_channel(lua_State *ps) { | |
321 | if (!lua_isstring(ps, 1)) | |
322 | LUA_RETURN(ps, LUA_FAIL); | |
323 | ||
c2a4bd5a | 324 | a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL), lua_tostring(ps, 1)); |
f79faea1 GB |
325 | |
326 | LUA_RETURN(ps, LUA_OK); | |
327 | } | |
328 | ||
d5c004ba | 329 | static int a4stats_lua_add_kick(lua_State *ps) { |
f79faea1 GB |
330 | unsigned long channelid, kickerid, victimid; |
331 | const char *kicker, *victim, *reason; | |
332 | ||
333 | if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4) || !lua_isnumber(ps, 5) || !lua_isstring(ps, 6)) | |
d5c004ba GB |
334 | LUA_RETURN(ps, LUA_FAIL); |
335 | ||
f79faea1 GB |
336 | channelid = lua_tonumber(ps, 1); |
337 | kicker = lua_tostring(ps, 2); | |
338 | kickerid = lua_tonumber(ps, 3); | |
339 | victim = lua_tostring(ps, 4); | |
340 | victimid = lua_tonumber(ps, 5); | |
341 | reason = lua_tostring(ps, 6); | |
342 | ||
343 | a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts", | |
344 | "kicks", channelid, kicker, kickerid, victim, victimid, time(NULL), reason); | |
d5c004ba GB |
345 | |
346 | LUA_RETURN(ps, LUA_OK); | |
347 | } | |
348 | ||
349 | static int a4stats_lua_add_topic(lua_State *ps) { | |
f79faea1 GB |
350 | unsigned long channelid, setbyid; |
351 | const char *topic, *setby; | |
352 | ||
353 | if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4)) | |
d5c004ba GB |
354 | LUA_RETURN(ps, LUA_FAIL); |
355 | ||
f79faea1 GB |
356 | channelid = lua_tonumber(ps, 1); |
357 | topic = lua_tostring(ps, 2); | |
358 | setby = lua_tostring(ps, 3); | |
359 | setbyid = lua_tonumber(ps, 4); | |
360 | ||
361 | a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU", | |
362 | "topics", channelid, topic, time(NULL), setby, setbyid); | |
d5c004ba GB |
363 | |
364 | LUA_RETURN(ps, LUA_OK); | |
365 | } | |
366 | ||
367 | static void a4stats_hook_loadscript(int hooknum, void *arg) { | |
368 | void **args = arg; | |
369 | lua_State *l = args[1]; | |
370 | ||
5bf3ab9d GB |
371 | lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel); |
372 | lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel); | |
f79faea1 | 373 | lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels); |
d5c004ba GB |
374 | lua_register(l, "a4_add_kick", a4stats_lua_add_kick); |
375 | lua_register(l, "a4_add_topic", a4stats_lua_add_topic); | |
376 | lua_register(l, "a4_fetch_user", a4stats_lua_fetch_user); | |
377 | lua_register(l, "a4_update_user", a4stats_lua_update_user); | |
378 | lua_register(l, "a4_escape_string", a4stats_lua_escape_string); | |
379 | } | |
380 | ||
381 | #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n)) | |
382 | ||
383 | static void a4stats_hook_unloadscript(int hooknum, void *arg) { | |
f79faea1 | 384 | db_callback_info **pnext, *dci; |
899f2ee4 | 385 | lua_State *l = arg; |
d5c004ba | 386 | |
f79faea1 GB |
387 | for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) { |
388 | dci = *pnext; | |
899f2ee4 | 389 | if (dci->interp == l) { |
f79faea1 GB |
390 | *pnext = dci->next; |
391 | free(dci); | |
d5c004ba GB |
392 | } |
393 | } | |
d5c004ba GB |
394 | } |
395 | ||
396 | void _init(void) { | |
397 | lua_list *l; | |
398 | void *args[2]; | |
399 | ||
400 | a4stats_connectdb(); | |
401 | ||
402 | registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript); | |
403 | registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript); | |
404 | ||
405 | args[0] = NULL; | |
406 | for (l = lua_head; l;l = l->next) { | |
407 | args[1] = l->l; | |
408 | a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args); | |
409 | } | |
410 | } | |
411 | ||
412 | void _fini(void) { | |
f79faea1 GB |
413 | lua_list *l; |
414 | ||
d5c004ba GB |
415 | a4stats_closedb(); |
416 | ||
f79faea1 GB |
417 | for (l = lua_head; l;l = l->next) { |
418 | a4stats_hook_loadscript(HOOK_LUA_UNLOADSCRIPT, l->l); | |
899f2ee4 GB |
419 | |
420 | lua_unregister(l->l, "a4_enable_channel"); | |
421 | lua_unregister(l->l, "a4_disable_channel"); | |
422 | lua_unregister(l->l, "a4_fetch_channels"); | |
423 | lua_unregister(l->l, "a4_add_kick"); | |
424 | lua_unregister(l->l, "a4_add_topic"); | |
425 | lua_unregister(l->l, "a4_fetch_user"); | |
426 | lua_unregister(l->l, "a4_update_user"); | |
427 | lua_unregister(l->l, "a4_escape_string"); | |
f79faea1 GB |
428 | } |
429 | ||
d5c004ba GB |
430 | deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript); |
431 | deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript); | |
432 | } | |
433 |