]> jfr.im git - irc/quakenet/newserv.git/blame - a4stats/a4stats_db.c
a4stats: Remove a4stats_fetch_user function.
[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 12#define CLEANUP_KEEP 10 /* keep this many topics and kicks per channel around */
ef90464f 13#define CLEANUP_INTERVAL 86400 /* db cleanup interval (in seconds) */
82645134 14#define CLEANUP_INACTIVE_DAYS 30 /* disable channels where nothing happened for this many days */
5af6ffcf 15#define CLEANUP_DELETE_DAYS 5 /* delete data for channels that have been disabled for this many days */
5905e45f 16
d5c004ba
GB
17MODULE_VERSION("");
18
19DBAPIConn *a4statsdb;
20
21static int a4stats_connectdb(void) {
22 if(!a4statsdb) {
cf5ac373 23 a4statsdb = dbapi2open("pqsql", "a4stats");
d5c004ba
GB
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,
53c0b4a2 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, "
92898969
GB
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");
d5c004ba 36
f79faea1 37 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
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");
f79faea1 40
cf5ac373
GB
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");
d5c004ba
GB
43
44 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
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");
d5c004ba 47
cf5ac373 48 a4statsdb->squery(a4statsdb, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
d5c004ba
GB
49
50 a4statsdb->createtable(a4statsdb, NULL, NULL,
cf5ac373 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, "
f79faea1
GB
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, "
e0311e96 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, "
d5c004ba 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, "
5af6ffcf 58 "firstseen INT DEFAULT 0, curnick VARCHAR(16), FOREIGN KEY (channelid) REFERENCES ? (id) ON DELETE CASCADE)", "TT", "users", "channels");
d5c004ba 59
cf5ac373
GB
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");
d5c004ba 65
28d6a377 66 a4statsdb->createtable(a4statsdb, NULL, NULL,
5af6ffcf
TS
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");
28d6a377 69
8d6e0508
GB
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
d5c004ba
GB
74 return 1;
75}
76
77static void a4stats_closedb(void) {
78 if(!a4statsdb)
79 return;
80
81 a4statsdb->close(a4statsdb);
82 a4statsdb = NULL;
83}
84
85static 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
f79faea1
GB
131typedef struct db_callback_info {
132 struct db_callback_info *next;
d5c004ba
GB
133
134 lua_State *interp;
135 char callback[64];
136 int uarg_index;
f79faea1
GB
137} db_callback_info;
138
139static db_callback_info *dci_head;
140
141static void a4stats_delete_dci(db_callback_info *dci) {
5905e45f 142 db_callback_info **pnext;
d5c004ba 143
f79faea1
GB
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}
d5c004ba 153
d5c004ba
GB
154typedef struct user_update_info {
155 int stage;
156 char *update;
f79faea1 157 unsigned long channelid;
d5c004ba
GB
158 char *account;
159 unsigned long accountid;
160} user_update_info;
161
162static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
163 user_update_info *uui = uarg;
164
165 uui->stage++;
166
e6a4eec1 167 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
f79faea1 168 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
d5c004ba 169 else {
e6a4eec1 170 if (result == NULL || result->affected > 0 || uui->stage == 4) {
424b0e83 171 if (result == NULL || (result->affected == 0 && uui->stage == 4))
d5c004ba
GB
172 Error("a4stats", ERR_WARNING, "Unable to update user.");
173
174 free(uui->update);
d5c004ba
GB
175 free(uui->account);
176 free(uui);
316ff6c6 177 goto a4_uuc_return;
d5c004ba
GB
178 }
179
f79faea1 180 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 181 }
316ff6c6
TS
182
183a4_uuc_return:
184 if (result)
185 result->clear(result);
d5c004ba
GB
186}
187
188static int a4stats_lua_update_user(lua_State *ps) {
f79faea1
GB
189 const char *account;
190 unsigned long channelid, accountid;
d5c004ba
GB
191 char query[4096];
192 int first = 1;
193 user_update_info *uui;
194
f79faea1 195 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
d5c004ba
GB
196 LUA_RETURN(ps, LUA_FAIL);
197
f79faea1 198 channelid = lua_tonumber(ps, 1);
d5c004ba
GB
199 account = lua_tostring(ps, 2);
200 accountid = lua_tonumber(ps, 3);
201
202 strcpy(query, "UPDATE ? SET ");
203
204 lua_pushvalue(ps, 4);
205 lua_pushnil(ps);
206
207 while (lua_next(ps, -2)) {
208 const char *value = lua_tostring(ps, -1);
209
210 if (first)
211 first = 0;
212 else
213 strcat(query, ", ");
214
215 strcat(query, value);
216
217 lua_pop(ps, 1);
218 }
219
220 lua_pop(ps, 1);
221
f79faea1 222 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
d5c004ba
GB
223
224 uui = malloc(sizeof(*uui));
225 uui->stage = 0;
226 uui->update = strdup(query);
f79faea1 227 uui->channelid = channelid;
d5c004ba
GB
228 uui->account = strdup(account);
229 uui->accountid = accountid;
230
231 a4stats_update_user_cb(NULL, uui);
232
233 LUA_RETURN(ps, LUA_OK);
234}
235
28d6a377
GB
236typedef struct relation_update_info {
237 int stage;
238 unsigned long channelid;
239 char *first;
240 unsigned long firstid;
241 char *second;
242 unsigned long secondid;
243} relation_update_info;
244
245static void a4stats_update_relation_cb(const struct DBAPIResult *result, void *uarg) {
246 relation_update_info *rui = uarg;
247
248 rui->stage++;
249
250 if (rui->stage == 1) {
251 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "UPDATE ? SET score = score + 1, seen = ? "
252 "WHERE channelid = ? AND first = ? AND firstid = ? AND second = ? AND secondid = ?",
253 "TtUsUsU", "relations", time(NULL), rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid);
316ff6c6 254 goto a4_urc_return;
28d6a377
GB
255 } else if (rui->stage == 2 && result && result->affected == 0) {
256 a4statsdb->query(a4statsdb, a4stats_update_relation_cb, rui, "INSERT INTO ? (channelid, first, firstid, second, secondid, seen) VALUES (?, ?, ?, ?, ?, ?)",
257 "TUsUsUt", "relations", rui->channelid, rui->first, rui->firstid, rui->second, rui->secondid, time(NULL));
316ff6c6 258 goto a4_urc_return;
28d6a377
GB
259 }
260
261 if (!result || result->affected == 0)
262 Error("a4stats", ERR_WARNING, "Unable to update relation.");
263
264 free(rui->first);
265 free(rui->second);
266 free(rui);
316ff6c6
TS
267
268a4_urc_return:
269 if (result)
270 result->clear(result);
28d6a377
GB
271}
272
273static int a4stats_lua_update_relation(lua_State *ps) {
274 const char *user1, *user2;
275 unsigned long channelid, user1id, user2id;
276 relation_update_info *rui;
277
278 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4) || !lua_isnumber(ps, 5))
279 LUA_RETURN(ps, LUA_FAIL);
280
281 channelid = lua_tonumber(ps, 1);
282 user1 = lua_tostring(ps, 2);
283 user1id = lua_tonumber(ps, 3);
284 user2 = lua_tostring(ps, 4);
285 user2id = lua_tonumber(ps, 5);
286
287 rui = malloc(sizeof(*rui));
288 rui->stage = 0;
289 rui->channelid = channelid;
290
291 if (user1id < user2id || (user1id == user2id && strcmp(user1, user2) <= 0)) {
292 rui->first = strdup(user1);
293 rui->firstid = user1id;
294 rui->second = strdup(user2);
295 rui->secondid = user2id;
296 } else {
297 rui->first = strdup(user2);
298 rui->firstid = user2id;
299 rui->second = strdup(user1);
300 rui->secondid = user1id;
301 }
302
303 a4stats_update_relation_cb(NULL, rui);
304
305 LUA_RETURN(ps, LUA_OK);
306}
307
92898969
GB
308static int a4stats_lua_add_line(lua_State *ps) {
309 char query[256];
310 const char *channel;
311 int hour;
312
313 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
314 LUA_RETURN(ps, LUA_FAIL);
315
316 channel = lua_tostring(ps, 1);
317 hour = lua_tonumber(ps, 2);
318
319 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour, hour);
320
321 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
322
323 LUA_RETURN(ps, LUA_OK);
324}
325
5af6ffcf
TS
326static struct {
327 time_t start;
82645134
TS
328 unsigned long pending;
329 unsigned long topicskicks;
330 unsigned long disabled;
5af6ffcf 331 unsigned long deleted;
82645134
TS
332} cleanupdata;
333
5af6ffcf
TS
334static void a4stats_cleanupdb_got_result(void) {
335 if (!--cleanupdata.pending) {
23c149d9 336 controlwall(NO_OPER, NL_CLEANUP, "CLEANUPA4STATS: Deleted %lu old topics and kicks. Disabled %lu inactive channels. Deleted data for %lu channels.",
5af6ffcf
TS
337 cleanupdata.topicskicks, cleanupdata.disabled, cleanupdata.deleted);
338 cleanupdata.start = 0;
339 }
82645134
TS
340}
341
5af6ffcf
TS
342static void a4stats_cleanupdb_cb_countrows(const struct DBAPIResult *result, void *arg) {
343 unsigned long *counter = arg;
82645134 344
8a12ea21 345 if (result) {
5af6ffcf 346 *counter += result->affected;
8a12ea21
TS
347 result->clear(result);
348 }
82645134 349
5af6ffcf 350 a4stats_cleanupdb_got_result();
5905e45f
TS
351}
352
5af6ffcf 353static void a4stats_cleanupdb_cb_active(const struct DBAPIResult *result, void *null) {
82645134
TS
354 unsigned long channelid;
355 time_t seen;
8a12ea21
TS
356
357 if (!result)
358 return;
359
360 if (result->success) {
5905e45f
TS
361 while (result->next(result)) {
362 channelid = strtoul(result->get(result, 0), NULL, 10);
82645134
TS
363 seen = (time_t)strtoul(result->get(result, 2), NULL, 10);
364 /* use channel enabling timestamp if there was never any event */
365 if (!seen)
366 seen = (time_t)strtoul(result->get(result, 1), NULL, 10);
367
5af6ffcf
TS
368 if (seen < cleanupdata.start - CLEANUP_INACTIVE_DAYS * 86400) {
369 /* disable inactive channels */
370 cleanupdata.pending++;
371 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.disabled,
82645134 372 "UPDATE ? SET active = 0, deleted = ? WHERE id = ? AND active = 1",
5af6ffcf 373 "TtU", "channels", cleanupdata.start, channelid);
82645134
TS
374 } else {
375 /* cleanup old kicks/topics */
5af6ffcf
TS
376 cleanupdata.pending++;
377 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
378 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
379 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
380 "TUTUU", "kicks", channelid, "kicks", channelid, (unsigned long)CLEANUP_KEEP);
5af6ffcf
TS
381 cleanupdata.pending++;
382 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.topicskicks,
82645134
TS
383 "DELETE FROM ? WHERE channelid = ? AND timestamp <= "
384 "(SELECT timestamp FROM ? WHERE channelid = ? ORDER BY timestamp DESC OFFSET ? LIMIT 1)",
385 "TUTUU", "topics", channelid, "topics", channelid, (unsigned long)CLEANUP_KEEP);
386 }
5905e45f
TS
387 }
388 }
8a12ea21 389 result->clear(result);
5905e45f
TS
390}
391
392static void a4stats_cleanupdb(void *null) {
5af6ffcf 393 controlwall(NO_OPER, NL_CLEANUP, "Starting a4stats_db cleanup.");
82645134 394
5af6ffcf
TS
395 if (cleanupdata.start != 0) {
396 controlwall(NO_OPER, NL_CLEANUP, "a4stats cleanup already in progress.");
397 return;
398 }
5905e45f 399
5af6ffcf
TS
400 cleanupdata.start = time(NULL);
401 cleanupdata.pending = 0;
402 cleanupdata.topicskicks = 0;
403 cleanupdata.disabled = 0;
404 cleanupdata.deleted = 0;
405
406 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_active, NULL,
82645134
TS
407 "SELECT id, timestamp, MAX(users.seen) FROM ? LEFT JOIN ? AS users ON id = users.channelid WHERE active = 1 GROUP BY id",
408 "TT", "channels", "users");
5af6ffcf
TS
409 cleanupdata.pending++;
410 a4statsdb->query(a4statsdb, a4stats_cleanupdb_cb_countrows, &cleanupdata.deleted,
411 "DELETE FROM ? WHERE active = 0 AND deleted < ?", "Tt", "channels", (time_t)(cleanupdata.start - CLEANUP_DELETE_DAYS * 84600));
5905e45f
TS
412}
413
f79faea1
GB
414static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
415 db_callback_info *dci = uarg;
416 unsigned long channelid;
417 int active;
418 char *channel;
419
420 if (result) {
421 if (result->success) {
422 while (result->next(result)) {
423 channelid = strtoul(result->get(result, 0), NULL, 10);
424 channel = result->get(result, 1);
425 active = atoi(result->get(result, 2));
426
427 if (dci->interp)
cf5ac373 428 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
f79faea1
GB
429 }
430 }
431
432 result->clear(result);
433 }
434
435 if (dci->interp)
436 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
437
438 a4stats_delete_dci(dci);
439}
440
441static int a4stats_lua_fetch_channels(lua_State *ps) {
442 const char *callback;
443 db_callback_info *dci;
444
445 if (!lua_isstring(ps, 1))
446 LUA_RETURN(ps, LUA_FAIL);
447
448 callback = lua_tostring(ps, 1);
449
450 dci = malloc(sizeof(*dci));
451 dci->interp = ps;
452
453 strncpy(dci->callback, callback, sizeof(dci->callback));
454 dci->callback[sizeof(dci->callback) - 1] = '\0';
455
456 lua_pushvalue(ps, 2);
457 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
458
459 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
460
461 LUA_RETURN(ps, LUA_OK);
462}
463
5bf3ab9d 464static int a4stats_lua_enable_channel(lua_State *ps) {
f79faea1
GB
465 if (!lua_isstring(ps, 1))
466 LUA_RETURN(ps, LUA_FAIL);
467
899f2ee4 468 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
c2a4bd5a 469 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps, 1));
5bf3ab9d
GB
470
471 LUA_RETURN(ps, LUA_OK);
472}
473
474static int a4stats_lua_disable_channel(lua_State *ps) {
475 if (!lua_isstring(ps, 1))
476 LUA_RETURN(ps, LUA_FAIL);
477
c2a4bd5a 478 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL), lua_tostring(ps, 1));
f79faea1
GB
479
480 LUA_RETURN(ps, LUA_OK);
481}
482
d5c004ba 483static int a4stats_lua_add_kick(lua_State *ps) {
f79faea1
GB
484 unsigned long channelid, kickerid, victimid;
485 const char *kicker, *victim, *reason;
486
487 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
488 LUA_RETURN(ps, LUA_FAIL);
489
f79faea1
GB
490 channelid = lua_tonumber(ps, 1);
491 kicker = lua_tostring(ps, 2);
492 kickerid = lua_tonumber(ps, 3);
493 victim = lua_tostring(ps, 4);
494 victimid = lua_tonumber(ps, 5);
495 reason = lua_tostring(ps, 6);
496
497 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts",
498 "kicks", channelid, kicker, kickerid, victim, victimid, time(NULL), reason);
d5c004ba
GB
499
500 LUA_RETURN(ps, LUA_OK);
501}
502
503static int a4stats_lua_add_topic(lua_State *ps) {
f79faea1
GB
504 unsigned long channelid, setbyid;
505 const char *topic, *setby;
506
507 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
d5c004ba
GB
508 LUA_RETURN(ps, LUA_FAIL);
509
f79faea1
GB
510 channelid = lua_tonumber(ps, 1);
511 topic = lua_tostring(ps, 2);
512 setby = lua_tostring(ps, 3);
513 setbyid = lua_tonumber(ps, 4);
514
515 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
516 "topics", channelid, topic, time(NULL), setby, setbyid);
d5c004ba
GB
517
518 LUA_RETURN(ps, LUA_OK);
519}
520
521static void a4stats_hook_loadscript(int hooknum, void *arg) {
522 void **args = arg;
523 lua_State *l = args[1];
524
5bf3ab9d
GB
525 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
526 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
f79faea1 527 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
d5c004ba
GB
528 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
529 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
92898969 530 lua_register(l, "a4_add_line", a4stats_lua_add_line);
d5c004ba 531 lua_register(l, "a4_update_user", a4stats_lua_update_user);
28d6a377 532 lua_register(l, "a4_update_relation", a4stats_lua_update_relation);
d5c004ba
GB
533 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
534}
535
536#define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
537
538static void a4stats_hook_unloadscript(int hooknum, void *arg) {
f79faea1 539 db_callback_info **pnext, *dci;
899f2ee4 540 lua_State *l = arg;
d5c004ba 541
f79faea1
GB
542 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
543 dci = *pnext;
899f2ee4 544 if (dci->interp == l) {
f79faea1
GB
545 *pnext = dci->next;
546 free(dci);
d5c004ba
GB
547 }
548 }
d5c004ba
GB
549}
550
551void _init(void) {
552 lua_list *l;
553 void *args[2];
554
555 a4stats_connectdb();
556
557 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
558 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
5905e45f 559 schedulerecurring(time(NULL), 0, CLEANUP_INTERVAL, a4stats_cleanupdb, NULL);
d5c004ba
GB
560
561 args[0] = NULL;
562 for (l = lua_head; l;l = l->next) {
563 args[1] = l->l;
564 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
565 }
566}
567
568void _fini(void) {
f79faea1
GB
569 lua_list *l;
570
5905e45f 571 deleteschedule(NULL, a4stats_cleanupdb, NULL);
d5c004ba
GB
572 a4stats_closedb();
573
f79faea1 574 for (l = lua_head; l;l = l->next) {
064db467 575 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
899f2ee4
GB
576
577 lua_unregister(l->l, "a4_enable_channel");
578 lua_unregister(l->l, "a4_disable_channel");
579 lua_unregister(l->l, "a4_fetch_channels");
580 lua_unregister(l->l, "a4_add_kick");
581 lua_unregister(l->l, "a4_add_topic");
92898969 582 lua_unregister(l->l, "a4_add_line");
899f2ee4
GB
583 lua_unregister(l->l, "a4_fetch_user");
584 lua_unregister(l->l, "a4_update_user");
28d6a377 585 lua_unregister(l->l, "a4_update_relation");
899f2ee4 586 lua_unregister(l->l, "a4_escape_string");
f79faea1
GB
587 }
588
d5c004ba
GB
589 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
590 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
591}
592