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