]> jfr.im git - irc/quakenet/newserv.git/blob - a4stats/a4stats_db.c
4376dd7916407a610217a4221629ad9b7d420e4f
[irc/quakenet/newserv.git] / a4stats / a4stats_db.c
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 #define CLEANUP_KEEP 10 /* keep this many topics and kicks per channel around */
13 #define CLEANUP_INTERVAL 86400 /* db cleanup interval (in seconds) */
14 #define CLEANUP_INACTIVE_DAYS 30 /* disable channels where nothing happened for this many days */
15 #define CLEANUP_DELETE_DAYS 5 /* delete data for channels that have been disabled for this many days */
16
17 MODULE_VERSION("");
18
19 DBAPIConn *a4statsdb;
20
21 static int a4stats_connectdb(void) {
22 if(!a4statsdb) {
23 a4statsdb = dbapi2open("pqsql", "a4stats");
24 if(!a4statsdb) {
25 Error("a4stats", ERR_WARNING, "Unable to connect to db -- not loaded.");
26 return 0;
27 }
28 }
29
30 a4statsdb->createtable(a4statsdb, NULL, NULL,
31 "CREATE TABLE ? (id SERIAL PRIMARY KEY, name VARCHAR(256) UNIQUE, timestamp INT DEFAULT 0, active INT DEFAULT 1, deleted INT DEFAULT 0, privacy INT DEFAULT 1, "
32 "h0 INT DEFAULT 0, h1 INT DEFAULT 0, h2 INT DEFAULT 0, h3 INT DEFAULT 0, h4 INT DEFAULT 0, h5 INT DEFAULT 0, "
33 "h6 INT DEFAULT 0, h7 INT DEFAULT 0, h8 INT DEFAULT 0, h9 INT DEFAULT 0, h10 INT DEFAULT 0, h11 INT DEFAULT 0, "
34 "h12 INT DEFAULT 0, h13 INT DEFAULT 0, h14 INT DEFAULT 0, h15 INT DEFAULT 0, h16 INT DEFAULT 0, h17 INT DEFAULT 0, "
35 "h18 INT DEFAULT 0, h19 INT DEFAULT 0, h20 INT DEFAULT 0, h21 INT DEFAULT 0, h22 INT DEFAULT 0, h23 INT DEFAULT 0)", "T", "channels");
36
37 a4statsdb->createtable(a4statsdb, NULL, NULL,
38 "CREATE TABLE ? (channelid INT, kicker VARCHAR(128), kickerid INT, victim VARCHAR(128), victimid INT, timestamp INT, reason VARCHAR(256),"
39 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "kicks", "channels");
40
41 a4statsdb->squery(a4statsdb, "CREATE INDEX kicks_channelid_index ON ? (channelid)", "T", "kicks");
42 a4statsdb->squery(a4statsdb, "CREATE INDEX kicks_timestamp_index ON ? (timestamp)", "T", "kicks");
43
44 a4statsdb->createtable(a4statsdb, NULL, NULL,
45 "CREATE TABLE ? (channelid INT, setby VARCHAR(128), setbyid INT, timestamp INT, topic VARCHAR(512),"
46 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "topics", "channels");
47
48 a4statsdb->squery(a4statsdb, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
49
50 a4statsdb->createtable(a4statsdb, NULL, NULL,
51 "CREATE TABLE ? (channelid INT, account VARCHAR(128), accountid INT, seen INT DEFAULT 0, rating INT DEFAULT 0, lines INT DEFAULT 0, chars INT DEFAULT 0, words INT DEFAULT 0, "
52 "h0 INT DEFAULT 0, h1 INT DEFAULT 0, h2 INT DEFAULT 0, h3 INT DEFAULT 0, h4 INT DEFAULT 0, h5 INT DEFAULT 0, "
53 "h6 INT DEFAULT 0, h7 INT DEFAULT 0, h8 INT DEFAULT 0, h9 INT DEFAULT 0, h10 INT DEFAULT 0, h11 INT DEFAULT 0, "
54 "h12 INT DEFAULT 0, h13 INT DEFAULT 0, h14 INT DEFAULT 0, h15 INT DEFAULT 0, h16 INT DEFAULT 0, h17 INT DEFAULT 0, "
55 "h18 INT DEFAULT 0, h19 INT DEFAULT 0, h20 INT DEFAULT 0, h21 INT DEFAULT 0, h22 INT DEFAULT 0, h23 INT DEFAULT 0, "
56 "last VARCHAR(512), quote VARCHAR(512), quotereset INT DEFAULT 0, mood_happy INT DEFAULT 0, mood_sad INT DEFAULT 0, questions INT DEFAULT 0, yelling INT DEFAULT 0, caps INT DEFAULT 0, "
57 "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, "
58 "firstseen INT DEFAULT 0, curnick VARCHAR(16), FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "users", "channels");
59
60 a4statsdb->squery(a4statsdb, "CREATE INDEX users_account_index ON ? (account)", "T", "users");
61 a4statsdb->squery(a4statsdb, "CREATE INDEX users_accountid_index ON ? (accountid)", "T", "users");
62 a4statsdb->squery(a4statsdb, "CREATE INDEX users_channelid_index ON ? (channelid)", "T", "users");
63 a4statsdb->squery(a4statsdb, "CREATE UNIQUE INDEX users_channelid_account_accountid_index ON ? (channelid, account, accountid)", "T", "users");
64 a4statsdb->squery(a4statsdb, "CREATE INDEX users_channelid_lines_index ON ? (channelid, lines)", "T", "users");
65
66 a4statsdb->createtable(a4statsdb, NULL, NULL,
67 "CREATE TABLE ? (channelid INT, first VARCHAR(128), firstid INT, second VARCHAR(128), secondid INT, seen INT, score INT DEFAULT 1,"
68 "FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "relations", "channels");
69
70 a4statsdb->squery(a4statsdb, "CREATE INDEX relations_channelid_index ON ? (channelid)", "T", "relations");
71 a4statsdb->squery(a4statsdb, "CREATE INDEX relations_score_index ON ? (score)", "T", "relations");
72
73
74 return 1;
75 }
76
77 static void a4stats_closedb(void) {
78 if(!a4statsdb)
79 return;
80
81 a4statsdb->close(a4statsdb);
82 a4statsdb = NULL;
83 }
84
85 static int a4stats_lua_escape_string(lua_State *ps) {
86 const char *input;
87 char *buf, *buf2;
88 size_t len, i, o;
89
90 if (!lua_isstring(ps, 1))
91 LUA_RETURN(ps, LUA_FAIL);
92
93 input = lua_tostring(ps, 1);
94 len = strlen(input);
95
96 buf = malloc(len * 2 + 1);
97
98 if (!buf)
99 LUA_RETURN(ps, LUA_FAIL);
100
101 a4statsdb->escapestring(a4statsdb, buf, input, len);
102
103 buf2 = malloc(len * 4 + 1);
104
105 if (!buf2) {
106 free(buf);
107 LUA_RETURN(ps, LUA_FAIL);
108 }
109
110 /* escape "?" */
111 o = 0;
112 for (i = 0; buf[i]; i++) {
113 if (buf[i] == '?') {
114 buf2[i + o] = '\\';
115 o++;
116 }
117
118 buf2[i + o] = buf[i];
119 }
120 buf2[i + o] = '\0';
121
122 free(buf);
123
124 lua_pushstring(ps, buf2);
125
126 free(buf2);
127
128 return 1;
129 }
130
131 typedef struct db_callback_info {
132 struct db_callback_info *next;
133
134 lua_State *interp;
135 char callback[64];
136 int uarg_index;
137 } db_callback_info;
138
139 static db_callback_info *dci_head;
140
141 static void a4stats_delete_dci(db_callback_info *dci) {
142 db_callback_info **pnext;
143
144 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
145 if (*pnext == dci) {
146 *pnext = dci->next;
147 break;
148 }
149 }
150
151 free(dci);
152 }
153
154 static void a4stats_fetch_user_cb(const struct DBAPIResult *result, void *uarg) {
155 db_callback_info *dci = uarg;
156 time_t seen = 0, quotereset = 0;
157
158 if (result) {
159 if (result->success) {
160 while (result->next(result)) {
161 seen = (result->get(result, 0)) ? (time_t)strtoul(result->get(result, 0), NULL, 10) : 0;
162 quotereset = (result->get(result, 1)) ? (time_t)strtoul(result->get(result, 1), NULL, 10) : 0;
163 }
164 }
165
166 result->clear(result);
167 }
168
169 if (dci->interp) {
170 lua_vpcall(dci->interp, dci->callback, "llR", (long)seen, (long)quotereset, dci->uarg_index);
171 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
172 }
173
174 a4stats_delete_dci(dci);
175 }
176
177 static int a4stats_lua_fetch_user(lua_State *ps) {
178 const char *account, *callback;
179 unsigned long channelid, accountid;
180 db_callback_info *dci;
181
182 if (!lua_islong(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4))
183 LUA_RETURN(ps, LUA_FAIL);
184
185 channelid = lua_tonumber(ps, 1);
186 account = lua_tostring(ps, 2);
187 accountid = lua_tonumber(ps, 3);
188 callback = lua_tostring(ps, 4);
189
190 dci = malloc(sizeof(*dci));
191 dci->interp = ps;
192
193 strncpy(dci->callback, callback, sizeof(dci->callback));
194 dci->callback[sizeof(dci->callback) - 1] = '\0';
195
196 lua_pushvalue(ps, 5);
197 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
198
199 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);
200
201 LUA_RETURN(ps, LUA_OK);
202 }
203
204 typedef struct user_update_info {
205 int stage;
206 char *update;
207 unsigned long channelid;
208 char *account;
209 unsigned long accountid;
210 } user_update_info;
211
212 static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
213 user_update_info *uui = uarg;
214
215 uui->stage++;
216
217 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
218 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
219 else {
220 if (result == NULL || result->affected > 0 || uui->stage == 4) {
221 if (result == NULL || (result->affected == 0 && uui->stage == 4))
222 Error("a4stats", ERR_WARNING, "Unable to update user.");
223
224 free(uui->update);
225 free(uui->account);
226 free(uui);
227 goto a4_uuc_return;
228 }
229
230 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));
231 }
232
233 a4_uuc_return:
234 if (result)
235 result->clear(result);
236 }
237
238 static int a4stats_lua_update_user(lua_State *ps) {
239 const char *account;
240 unsigned long channelid, accountid;
241 char query[4096];
242 int first = 1;
243 user_update_info *uui;
244
245 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
246 LUA_RETURN(ps, LUA_FAIL);
247
248 channelid = lua_tonumber(ps, 1);
249 account = lua_tostring(ps, 2);
250 accountid = lua_tonumber(ps, 3);
251
252 strcpy(query, "UPDATE ? SET ");
253
254 lua_pushvalue(ps, 4);
255 lua_pushnil(ps);
256
257 while (lua_next(ps, -2)) {
258 const char *value = lua_tostring(ps, -1);
259
260 if (first)
261 first = 0;
262 else
263 strcat(query, ", ");
264
265 strcat(query, value);
266
267 lua_pop(ps, 1);
268 }
269
270 lua_pop(ps, 1);
271
272 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
273
274 uui = malloc(sizeof(*uui));
275 uui->stage = 0;
276 uui->update = strdup(query);
277 uui->channelid = channelid;
278 uui->account = strdup(account);
279 uui->accountid = accountid;
280
281 a4stats_update_user_cb(NULL, uui);
282
283 LUA_RETURN(ps, LUA_OK);
284 }
285
286 typedef struct relation_update_info {
287 int stage;
288 unsigned long channelid;
289 char *first;
290 unsigned long firstid;
291 char *second;
292 unsigned long secondid;
293 } relation_update_info;
294
295 static void a4stats_update_relation_cb(const struct DBAPIResult *result, void *uarg) {
296 relation_update_info *rui = uarg;
297
298 rui->stage++;
299
300 if (rui->stage == 1) {
301 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "UPDATE ? SET score = score + 1, seen = ? "
302 "WHERE channelid = ? AND first = ? AND firstid = ? AND second = ? AND secondid = ?",
303 "TtUsUsU", "relations", time(NULL), rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid);
304 goto a4_urc_return;
305 } else if (rui->stage == 2 && result && result->affected == 0) {
306 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "INSERT INTO ? (channelid, first, firstid, second, secondid, seen) VALUES (?, ?, ?, ?, ?, ?)",
307 "TUsUsUt", "relations", rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid, time(NULL));
308 goto a4_urc_return;
309 }
310
311 if (!result || result->affected == 0)
312 Error("a4stats", ERR_WARNING, "Unable to update relation.");
313
314 free(rui->first);
315 free(rui->second);
316 free(rui);
317
318 a4_urc_return:
319 if (result)
320 result->clear(result);
321 }
322
323 static int a4stats_lua_update_relation(lua_State *ps) {
324 const char *user1, *user2;
325 unsigned long channelid, user1id, user2id;
326 relation_update_info *rui;
327
328 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4) || !lua_isnumber(ps, 5))
329 LUA_RETURN(ps, LUA_FAIL);
330
331 channelid = lua_tonumber(ps, 1);
332 user1 = lua_tostring(ps, 2);
333 user1id = lua_tonumber(ps, 3);
334 user2 = lua_tostring(ps, 4);
335 user2id = lua_tonumber(ps, 5);
336
337 rui = malloc(sizeof(*rui));
338 rui->stage = 0;
339 rui->channelid = channelid;
340
341 if (user1id < user2id || (user1id == user2id && strcmp(user1, user2) <= 0)) {
342 rui->first = strdup(user1);
343 rui->firstid = user1id;
344 rui->second = strdup(user2);
345 rui->secondid = user2id;
346 } else {
347 rui->first = strdup(user2);
348 rui->firstid = user2id;
349 rui->second = strdup(user1);
350 rui->secondid = user1id;
351 }
352
353 a4stats_update_relation_cb(NULL, rui);
354
355 LUA_RETURN(ps, LUA_OK);
356 }
357
358 static int a4stats_lua_add_line(lua_State *ps) {
359 char query[256];
360 const char *channel;
361 int hour;
362
363 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
364 LUA_RETURN(ps, LUA_FAIL);
365
366 channel = lua_tostring(ps, 1);
367 hour = lua_tonumber(ps, 2);
368
369 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour, hour);
370
371 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
372
373 LUA_RETURN(ps, LUA_OK);
374 }
375
376 static struct {
377 time_t start;
378 unsigned long pending;
379 unsigned long topicskicks;
380 unsigned long disabled;
381 unsigned long deleted;
382 } cleanupdata;
383
384 static void a4stats_cleanupdb_got_result(void) {
385 if (!--cleanupdata.pending) {
386 controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
387 cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted);
388 cleanupdata.start = 0;
389 }
390 }
391
392 static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) {
393 unsigned long *counter = arg;
394
395 if (result) {
396 *counter += result->affected;
397 result->clear(result);
398 }
399
400 a4stats_cleanupdb_got_result();
401 }
402
403 static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) {
404 unsigned long channelid;
405 time_t seen;
406
407 if (!result)
408 return;
409
410 if (result->success) {
411 while (result->next(result)) {
412 channelid = strtoul(result->get(result, 0), NULL, 10);
413 seen = (time_t)strtoul(result->get(result, 2), NULL, 10);
414 /* use channel enabling timestamp if there was never any event */
415 if (!seen)
416 seen = (time_t)strtoul(result->get(result, 1), NULL, 10);
417
418 if (seen < cleanupdata.start - CLEANUP_INACTIVE_DAYS * 86400) {
419 /* disable inactive channels */
420 cleanupdata.pending++;
421 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.disabled,
422 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
423 "TtU", "channels", cleanupdata.start, channelid);
424 } else {
425 /* cleanup old kicks/topics */
426 cleanupdata.pending++;
427 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
428 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
429 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
430 "TUTUU", "kicks", channelid, "kicks", channelid, (unsigned long)CLEANUP_KEEP);
431 cleanupdata.pending++;
432 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
433 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
434 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
435 "TUTUU", "topics", channelid, "topics", channelid, (unsigned long)CLEANUP_KEEP);
436 }
437 }
438 }
439 result->clear(result);
440 }
441
442 static void a4stats_cleanupdb(void *null) {
443 controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup.");
444
445 if (cleanupdata.start != 0) {
446 controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress.");
447 return;
448 }
449
450 cleanupdata.start = time(NULL);
451 cleanupdata.pending = 0;
452 cleanupdata.topicskicks = 0;
453 cleanupdata.disabled = 0;
454 cleanupdata.deleted = 0;
455
456 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_active, NULL,
457 "SELECT id, timestamp, MAX(users.seen) FROM ? LEFT JOIN ? AS users ON id = users.channelid WHERE active = 1 GROUP BY id",
458 "TT", "channels", "users");
459 cleanupdata.pending++;
460 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.deleted,
461 "DELETE FROM ? WHERE active = 0 AND deleted < ?", "Tt", "channels", (time_t)(cleanupdata.start - CLEANUP_DELETE_DAYS * 84600));
462 }
463
464 static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
465 db_callback_info *dci = uarg;
466 unsigned long channelid;
467 int active;
468 char *channel;
469
470 if (result) {
471 if (result->success) {
472 while (result->next(result)) {
473 channelid = strtoul(result->get(result, 0), NULL, 10);
474 channel = result->get(result, 1);
475 active = atoi(result->get(result, 2));
476
477 if (dci->interp)
478 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
479 }
480 }
481
482 result->clear(result);
483 }
484
485 if (dci->interp)
486 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
487
488 a4stats_delete_dci(dci);
489 }
490
491 static int a4stats_lua_fetch_channels(lua_State *ps) {
492 const char *callback;
493 db_callback_info *dci;
494
495 if (!lua_isstring(ps, 1))
496 LUA_RETURN(ps, LUA_FAIL);
497
498 callback = lua_tostring(ps, 1);
499
500 dci = malloc(sizeof(*dci));
501 dci->interp = ps;
502
503 strncpy(dci->callback, callback, sizeof(dci->callback));
504 dci->callback[sizeof(dci->callback) - 1] = '\0';
505
506 lua_pushvalue(ps, 2);
507 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
508
509 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
510
511 LUA_RETURN(ps, LUA_OK);
512 }
513
514 static int a4stats_lua_enable_channel(lua_State *ps) {
515 if (!lua_isstring(ps, 1))
516 LUA_RETURN(ps, LUA_FAIL);
517
518 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
519 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps, 1));
520
521 LUA_RETURN(ps, LUA_OK);
522 }
523
524 static int a4stats_lua_disable_channel(lua_State *ps) {
525 if (!lua_isstring(ps, 1))
526 LUA_RETURN(ps, LUA_FAIL);
527
528 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL), lua_tostring(ps, 1));
529
530 LUA_RETURN(ps, LUA_OK);
531 }
532
533 static int a4stats_lua_add_kick(lua_State *ps) {
534 unsigned long channelid, kickerid, victimid;
535 const char *kicker, *victim, *reason;
536
537 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))
538 LUA_RETURN(ps, LUA_FAIL);
539
540 channelid = lua_tonumber(ps, 1);
541 kicker = lua_tostring(ps, 2);
542 kickerid = lua_tonumber(ps, 3);
543 victim = lua_tostring(ps, 4);
544 victimid = lua_tonumber(ps, 5);
545 reason = lua_tostring(ps, 6);
546
547 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts",
548 "kicks", channelid, kicker, kickerid, victim, victimid, time(NULL), reason);
549
550 LUA_RETURN(ps, LUA_OK);
551 }
552
553 static int a4stats_lua_add_topic(lua_State *ps) {
554 unsigned long channelid, setbyid;
555 const char *topic, *setby;
556
557 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
558 LUA_RETURN(ps, LUA_FAIL);
559
560 channelid = lua_tonumber(ps, 1);
561 topic = lua_tostring(ps, 2);
562 setby = lua_tostring(ps, 3);
563 setbyid = lua_tonumber(ps, 4);
564
565 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
566 "topics", channelid, topic, time(NULL), setby, setbyid);
567
568 LUA_RETURN(ps, LUA_OK);
569 }
570
571 static void a4stats_hook_loadscript(int hooknum, void *arg) {
572 void **args = arg;
573 lua_State *l = args[1];
574
575 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
576 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
577 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
578 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
579 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
580 lua_register(l, "a4_add_line", a4stats_lua_add_line);
581 lua_register(l, "a4_fetch_user", a4stats_lua_fetch_user);
582 lua_register(l, "a4_update_user", a4stats_lua_update_user);
583 lua_register(l, "a4_update_relation", a4stats_lua_update_relation);
584 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
585 }
586
587 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
588
589 static void a4stats_hook_unloadscript(int hooknum, void *arg) {
590 db_callback_info **pnext, *dci;
591 lua_State *l = arg;
592
593 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
594 dci = *pnext;
595 if (dci->interp == l) {
596 *pnext = dci->next;
597 free(dci);
598 }
599 }
600 }
601
602 void _init(void) {
603 lua_list *l;
604 void *args[2];
605
606 a4stats_connectdb();
607
608 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
609 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
610 schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL);
611
612 args[0] = NULL;
613 for (l = lua_head; l;l = l->next) {
614 args[1] = l->l;
615 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
616 }
617 }
618
619 void _fini(void) {
620 lua_list *l;
621
622 deleteschedule(NULL, a4stats_cleanupdb, NULL);
623 a4stats_closedb();
624
625 for (l = lua_head; l;l = l->next) {
626 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
627
628 lua_unregister(l->l, "a4_enable_channel");
629 lua_unregister(l->l, "a4_disable_channel");
630 lua_unregister(l->l, "a4_fetch_channels");
631 lua_unregister(l->l, "a4_add_kick");
632 lua_unregister(l->l, "a4_add_topic");
633 lua_unregister(l->l, "a4_add_line");
634 lua_unregister(l->l, "a4_fetch_user");
635 lua_unregister(l->l, "a4_update_user");
636 lua_unregister(l->l, "a4_update_relation");
637 lua_unregister(l->l, "a4_escape_string");
638 }
639
640 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
641 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
642 }
643