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