]> jfr.im git - irc/quakenet/newserv.git/blame - a4stats/a4stats_db.c
a4stats: Added DB queries for the search bar.
[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
12MODULE_VERSION("");
13
14DBAPIConn *a4statsdb;
15
16static int a4stats_connectdb(void) {
17 if(!a4statsdb) {
cf5ac373 18 a4statsdb = dbapi2open("pqsql", "a4stats");
d5c004ba
GB
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,
cf5ac373 26 "CREATE TABLE ? (id SERIAL, name VARCHAR(256) UNIQUE, timestamp INT DEFAULT 0, active INT DEFAULT 1, deleted INT DEFAULT 0, privacy INT DEFAULT 1, "
92898969
GB
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");
d5c004ba 31
f79faea1 32 a4statsdb->createtable(a4statsdb, NULL, NULL,
cf5ac373 33 "CREATE TABLE ? (channelid INT, kicker VARCHAR(128), kickerid INT, victim VARCHAR(128), victimid INT, timestamp INT, reason VARCHAR(256))", "T", "kicks");
f79faea1 34
cf5ac373
GB
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");
d5c004ba
GB
37
38 a4statsdb->createtable(a4statsdb, NULL, NULL,
cf5ac373 39 "CREATE TABLE ? (channelid INT, setby VARCHAR(128), setbyid INT, timestamp INT, topic VARCHAR(512))", "T", "topics");
d5c004ba 40
cf5ac373 41 a4statsdb->squery(a4statsdb, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
d5c004ba
GB
42
43 a4statsdb->createtable(a4statsdb, NULL, NULL,
cf5ac373 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, "
f79faea1
GB
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, "
e0311e96 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, "
d5c004ba
GB
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
cf5ac373
GB
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");
d5c004ba
GB
58
59 return 1;
60}
61
62static void a4stats_closedb(void) {
63 if(!a4statsdb)
64 return;
65
66 a4statsdb->close(a4statsdb);
67 a4statsdb = NULL;
68}
69
70static int a4stats_lua_escape_string(lua_State *ps) {
71 const char *input;
72 char *buf, *buf2;
73 size_t len, i, o;
74
75 if (!lua_isstring(ps, 1))
76 LUA_RETURN(ps, LUA_FAIL);
77
78 input = lua_tostring(ps, 1);
79 len = strlen(input);
80
81 buf = malloc(len * 2 + 1);
82
83 if (!buf)
84 LUA_RETURN(ps, LUA_FAIL);
85
86 a4statsdb->escapestring(a4statsdb, buf, input, len);
87
88 buf2 = malloc(len * 4 + 1);
89
90 if (!buf2) {
91 free(buf);
92 LUA_RETURN(ps, LUA_FAIL);
93 }
94
95 /* escape "?" */
96 o = 0;
97 for (i = 0; buf[i]; i++) {
98 if (buf[i] == '?') {
99 buf2[i + o] = '\\';
100 o++;
101 }
102
103 buf2[i + o] = buf[i];
104 }
105 buf2[i + o] = '\0';
106
107 free(buf);
108
109 lua_pushstring(ps, buf2);
110
111 free(buf2);
112
113 return 1;
114}
115
f79faea1
GB
116typedef struct db_callback_info {
117 struct db_callback_info *next;
d5c004ba
GB
118
119 lua_State *interp;
120 char callback[64];
121 int uarg_index;
f79faea1
GB
122} db_callback_info;
123
124static db_callback_info *dci_head;
125
126static void a4stats_delete_dci(db_callback_info *dci) {
127db_callback_info **pnext;
d5c004ba 128
f79faea1
GB
129 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
130 if (*pnext == dci) {
131 *pnext = dci->next;
132 break;
133 }
134 }
135
136 free(dci);
137}
d5c004ba
GB
138
139static void a4stats_fetch_user_cb(const struct DBAPIResult *result, void *uarg) {
f79faea1 140 db_callback_info *dci = uarg;
d5c004ba
GB
141 time_t seen = 0, quotereset = 0;
142
143 if (result) {
144 if (result->success) {
145 while (result->next(result)) {
146 seen = (result->get(result, 0)) ? (time_t)strtoul(result->get(result, 0), NULL, 10) : 0;
147 quotereset = (result->get(result, 1)) ? (time_t)strtoul(result->get(result, 1), NULL, 10) : 0;
148 }
149 }
150
151 result->clear(result);
152 }
153
f79faea1 154 if (dci->interp) {
cf5ac373 155 lua_vpcall(dci->interp, dci->callback, "llR", (long)seen, (long)quotereset, dci->uarg_index);
f79faea1 156 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
d5c004ba
GB
157 }
158
f79faea1 159 a4stats_delete_dci(dci);
d5c004ba
GB
160}
161
162static int a4stats_lua_fetch_user(lua_State *ps) {
f79faea1
GB
163 const char *account, *callback;
164 unsigned long channelid, accountid;
165 db_callback_info *dci;
d5c004ba 166
f79faea1 167 if (!lua_islong(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3) || !lua_isstring(ps, 4))
d5c004ba
GB
168 LUA_RETURN(ps, LUA_FAIL);
169
f79faea1 170 channelid = lua_tonumber(ps, 1);
d5c004ba
GB
171 account = lua_tostring(ps, 2);
172 accountid = lua_tonumber(ps, 3);
173 callback = lua_tostring(ps, 4);
174
f79faea1
GB
175 dci = malloc(sizeof(*dci));
176 dci->interp = ps;
d5c004ba 177
f79faea1
GB
178 strncpy(dci->callback, callback, sizeof(dci->callback));
179 dci->callback[sizeof(dci->callback) - 1] = '\0';
d5c004ba
GB
180
181 lua_pushvalue(ps, 5);
f79faea1 182 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
d5c004ba 183
f79faea1 184 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
185
186 LUA_RETURN(ps, LUA_OK);
187}
188
189typedef struct user_update_info {
190 int stage;
191 char *update;
f79faea1 192 unsigned long channelid;
d5c004ba
GB
193 char *account;
194 unsigned long accountid;
195} user_update_info;
196
197static void a4stats_update_user_cb(const struct DBAPIResult *result, void *uarg) {
198 user_update_info *uui = uarg;
199
200 uui->stage++;
201
e6a4eec1 202 if (uui->stage == 1 || (result != NULL && uui->stage == 3))
f79faea1 203 a4statsdb->query(a4statsdb, a4stats_update_user_cb, uui, uui->update, "TUUs", "users", uui->channelid, uui->accountid, uui->account);
d5c004ba 204 else {
e6a4eec1 205 if (result == NULL || result->affected > 0 || uui->stage == 4) {
424b0e83 206 if (result == NULL || (result->affected == 0 && uui->stage == 4))
d5c004ba
GB
207 Error("a4stats", ERR_WARNING, "Unable to update user.");
208
209 free(uui->update);
d5c004ba
GB
210 free(uui->account);
211 free(uui);
212 return;
213 }
214
f79faea1 215 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
216 }
217}
218
219static int a4stats_lua_update_user(lua_State *ps) {
f79faea1
GB
220 const char *account;
221 unsigned long channelid, accountid;
d5c004ba
GB
222 char query[4096];
223 int first = 1;
224 user_update_info *uui;
225
f79faea1 226 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isnumber(ps, 3))
d5c004ba
GB
227 LUA_RETURN(ps, LUA_FAIL);
228
f79faea1 229 channelid = lua_tonumber(ps, 1);
d5c004ba
GB
230 account = lua_tostring(ps, 2);
231 accountid = lua_tonumber(ps, 3);
232
233 strcpy(query, "UPDATE ? SET ");
234
235 lua_pushvalue(ps, 4);
236 lua_pushnil(ps);
237
238 while (lua_next(ps, -2)) {
239 const char *value = lua_tostring(ps, -1);
240
241 if (first)
242 first = 0;
243 else
244 strcat(query, ", ");
245
246 strcat(query, value);
247
248 lua_pop(ps, 1);
249 }
250
251 lua_pop(ps, 1);
252
f79faea1 253 strcat(query, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
d5c004ba
GB
254
255 uui = malloc(sizeof(*uui));
256 uui->stage = 0;
257 uui->update = strdup(query);
f79faea1 258 uui->channelid = channelid;
d5c004ba
GB
259 uui->account = strdup(account);
260 uui->accountid = accountid;
261
262 a4stats_update_user_cb(NULL, uui);
263
264 LUA_RETURN(ps, LUA_OK);
265}
266
92898969
GB
267static int a4stats_lua_add_line(lua_State *ps) {
268 char query[256];
269 const char *channel;
270 int hour;
271
272 if (!lua_isstring(ps, 1) || !lua_isnumber(ps, 2))
273 LUA_RETURN(ps, LUA_FAIL);
274
275 channel = lua_tostring(ps, 1);
276 hour = lua_tonumber(ps, 2);
277
278 snprintf(query, sizeof(query), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour, hour);
279
280 a4statsdb->squery(a4statsdb, query, "Ts", "channels", channel);
281
282 LUA_RETURN(ps, LUA_OK);
283}
284
f79faea1
GB
285static void a4stats_fetch_channels_cb(const struct DBAPIResult *result, void *uarg) {
286 db_callback_info *dci = uarg;
287 unsigned long channelid;
288 int active;
289 char *channel;
290
291 if (result) {
292 if (result->success) {
293 while (result->next(result)) {
294 channelid = strtoul(result->get(result, 0), NULL, 10);
295 channel = result->get(result, 1);
296 active = atoi(result->get(result, 2));
297
298 if (dci->interp)
cf5ac373 299 lua_vpcall(dci->interp, dci->callback, "lsiR", channelid, channel, active, dci->uarg_index);
f79faea1
GB
300 }
301 }
302
303 result->clear(result);
304 }
305
306 if (dci->interp)
307 luaL_unref(dci->interp, LUA_REGISTRYINDEX, dci->uarg_index);
308
309 a4stats_delete_dci(dci);
310}
311
312static int a4stats_lua_fetch_channels(lua_State *ps) {
313 const char *callback;
314 db_callback_info *dci;
315
316 if (!lua_isstring(ps, 1))
317 LUA_RETURN(ps, LUA_FAIL);
318
319 callback = lua_tostring(ps, 1);
320
321 dci = malloc(sizeof(*dci));
322 dci->interp = ps;
323
324 strncpy(dci->callback, callback, sizeof(dci->callback));
325 dci->callback[sizeof(dci->callback) - 1] = '\0';
326
327 lua_pushvalue(ps, 2);
328 dci->uarg_index = luaL_ref(ps, LUA_REGISTRYINDEX);
329
330 a4statsdb->query(a4statsdb, a4stats_fetch_channels_cb, dci, "SELECT id, name, active FROM ?", "T", "channels");
331
332 LUA_RETURN(ps, LUA_OK);
333}
334
5bf3ab9d 335static int a4stats_lua_enable_channel(lua_State *ps) {
f79faea1
GB
336 if (!lua_isstring(ps, 1))
337 LUA_RETURN(ps, LUA_FAIL);
338
899f2ee4 339 a4statsdb->squery(a4statsdb, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps, 1), time(NULL));
c2a4bd5a 340 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps, 1));
5bf3ab9d
GB
341
342 LUA_RETURN(ps, LUA_OK);
343}
344
345static int a4stats_lua_disable_channel(lua_State *ps) {
346 if (!lua_isstring(ps, 1))
347 LUA_RETURN(ps, LUA_FAIL);
348
c2a4bd5a 349 a4statsdb->squery(a4statsdb, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL), lua_tostring(ps, 1));
f79faea1
GB
350
351 LUA_RETURN(ps, LUA_OK);
352}
353
d5c004ba 354static int a4stats_lua_add_kick(lua_State *ps) {
f79faea1
GB
355 unsigned long channelid, kickerid, victimid;
356 const char *kicker, *victim, *reason;
357
358 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
359 LUA_RETURN(ps, LUA_FAIL);
360
f79faea1
GB
361 channelid = lua_tonumber(ps, 1);
362 kicker = lua_tostring(ps, 2);
363 kickerid = lua_tonumber(ps, 3);
364 victim = lua_tostring(ps, 4);
365 victimid = lua_tonumber(ps, 5);
366 reason = lua_tostring(ps, 6);
367
368 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, kicker, kickerid, victim, victimid, timestamp, reason) VALUES (?, ?, ?, ?, ?, ?, ?)", "TUsUsUts",
369 "kicks", channelid, kicker, kickerid, victim, victimid, time(NULL), reason);
d5c004ba
GB
370
371 LUA_RETURN(ps, LUA_OK);
372}
373
374static int a4stats_lua_add_topic(lua_State *ps) {
f79faea1
GB
375 unsigned long channelid, setbyid;
376 const char *topic, *setby;
377
378 if (!lua_isnumber(ps, 1) || !lua_isstring(ps, 2) || !lua_isstring(ps, 3) || !lua_isnumber(ps, 4))
d5c004ba
GB
379 LUA_RETURN(ps, LUA_FAIL);
380
f79faea1
GB
381 channelid = lua_tonumber(ps, 1);
382 topic = lua_tostring(ps, 2);
383 setby = lua_tostring(ps, 3);
384 setbyid = lua_tonumber(ps, 4);
385
386 a4statsdb->squery(a4statsdb, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
387 "topics", channelid, topic, time(NULL), setby, setbyid);
d5c004ba
GB
388
389 LUA_RETURN(ps, LUA_OK);
390}
391
392static void a4stats_hook_loadscript(int hooknum, void *arg) {
393 void **args = arg;
394 lua_State *l = args[1];
395
5bf3ab9d
GB
396 lua_register(l, "a4_enable_channel", a4stats_lua_enable_channel);
397 lua_register(l, "a4_disable_channel", a4stats_lua_disable_channel);
f79faea1 398 lua_register(l, "a4_fetch_channels", a4stats_lua_fetch_channels);
d5c004ba
GB
399 lua_register(l, "a4_add_kick", a4stats_lua_add_kick);
400 lua_register(l, "a4_add_topic", a4stats_lua_add_topic);
92898969 401 lua_register(l, "a4_add_line", a4stats_lua_add_line);
d5c004ba
GB
402 lua_register(l, "a4_fetch_user", a4stats_lua_fetch_user);
403 lua_register(l, "a4_update_user", a4stats_lua_update_user);
404 lua_register(l, "a4_escape_string", a4stats_lua_escape_string);
405}
406
407#define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
408
409static void a4stats_hook_unloadscript(int hooknum, void *arg) {
f79faea1 410 db_callback_info **pnext, *dci;
899f2ee4 411 lua_State *l = arg;
d5c004ba 412
f79faea1
GB
413 for (pnext = &dci_head; *pnext; pnext = &((*pnext)->next)) {
414 dci = *pnext;
899f2ee4 415 if (dci->interp == l) {
f79faea1
GB
416 *pnext = dci->next;
417 free(dci);
d5c004ba
GB
418 }
419 }
d5c004ba
GB
420}
421
422void _init(void) {
423 lua_list *l;
424 void *args[2];
425
426 a4stats_connectdb();
427
428 registerhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
429 registerhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
430
431 args[0] = NULL;
432 for (l = lua_head; l;l = l->next) {
433 args[1] = l->l;
434 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT, args);
435 }
436}
437
438void _fini(void) {
f79faea1
GB
439 lua_list *l;
440
d5c004ba
GB
441 a4stats_closedb();
442
f79faea1
GB
443 for (l = lua_head; l;l = l->next) {
444 a4stats_hook_loadscript(HOOK_LUA_UNLOADSCRIPT, l->l);
899f2ee4
GB
445
446 lua_unregister(l->l, "a4_enable_channel");
447 lua_unregister(l->l, "a4_disable_channel");
448 lua_unregister(l->l, "a4_fetch_channels");
449 lua_unregister(l->l, "a4_add_kick");
450 lua_unregister(l->l, "a4_add_topic");
92898969 451 lua_unregister(l->l, "a4_add_line");
899f2ee4
GB
452 lua_unregister(l->l, "a4_fetch_user");
453 lua_unregister(l->l, "a4_update_user");
454 lua_unregister(l->l, "a4_escape_string");
f79faea1
GB
455 }
456
d5c004ba
GB
457 deregisterhook(HOOK_LUA_LOADSCRIPT, a4stats_hook_loadscript);
458 deregisterhook(HOOK_LUA_UNLOADSCRIPT, a4stats_hook_unloadscript);
459}
460