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