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"
16 static int a4stats_connectdb(void) {
18 a4statsdb
= dbapi2open("pqsql", "a4stats");
20 Error("a4stats", ERR_WARNING
, "Unable to connect to db -- not loaded.");
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");
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");
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");
38 a4statsdb
->createtable(a4statsdb
, NULL
, NULL
,
39 "CREATE TABLE ? (channelid INT, setby VARCHAR(128), setbyid INT, timestamp INT, topic VARCHAR(512))", "T", "topics");
41 a4statsdb
->squery(a4statsdb
, "CREATE INDEX topics_channelid_index ON ? (channelid)", "T", "topics");
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");
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");
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");
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");
69 static void a4stats_closedb(void) {
73 a4statsdb
->close(a4statsdb
);
77 static int a4stats_lua_escape_string(lua_State
*ps
) {
82 if (!lua_isstring(ps
, 1))
83 LUA_RETURN(ps
, LUA_FAIL
);
85 input
= lua_tostring(ps
, 1);
88 buf
= malloc(len
* 2 + 1);
91 LUA_RETURN(ps
, LUA_FAIL
);
93 a4statsdb
->escapestring(a4statsdb
, buf
, input
, len
);
95 buf2
= malloc(len
* 4 + 1);
99 LUA_RETURN(ps
, LUA_FAIL
);
104 for (i
= 0; buf
[i
]; i
++) {
110 buf2
[i
+ o
] = buf
[i
];
116 lua_pushstring(ps
, buf2
);
123 typedef struct db_callback_info
{
124 struct db_callback_info
*next
;
131 static db_callback_info
*dci_head
;
133 static void a4stats_delete_dci(db_callback_info
*dci
) {
134 db_callback_info
**pnext
;
136 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
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;
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;
158 result
->clear(result
);
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
);
166 a4stats_delete_dci(dci
);
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
;
174 if (!lua_islong(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3) || !lua_isstring(ps
, 4))
175 LUA_RETURN(ps
, LUA_FAIL
);
177 channelid
= lua_tonumber(ps
, 1);
178 account
= lua_tostring(ps
, 2);
179 accountid
= lua_tonumber(ps
, 3);
180 callback
= lua_tostring(ps
, 4);
182 dci
= malloc(sizeof(*dci
));
185 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
186 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
188 lua_pushvalue(ps
, 5);
189 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
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
);
193 LUA_RETURN(ps
, LUA_OK
);
196 typedef struct user_update_info
{
199 unsigned long channelid
;
201 unsigned long accountid
;
204 static void a4stats_update_user_cb(const struct DBAPIResult
*result
, void *uarg
) {
205 user_update_info
*uui
= uarg
;
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
);
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.");
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
));
226 static int a4stats_lua_update_user(lua_State
*ps
) {
228 unsigned long channelid
, accountid
;
231 user_update_info
*uui
;
233 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3))
234 LUA_RETURN(ps
, LUA_FAIL
);
236 channelid
= lua_tonumber(ps
, 1);
237 account
= lua_tostring(ps
, 2);
238 accountid
= lua_tonumber(ps
, 3);
240 strcpy(query
, "UPDATE ? SET ");
242 lua_pushvalue(ps
, 4);
245 while (lua_next(ps
, -2)) {
246 const char *value
= lua_tostring(ps
, -1);
253 strcat(query
, value
);
260 strcat(query
, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
262 uui
= malloc(sizeof(*uui
));
264 uui
->update
= strdup(query
);
265 uui
->channelid
= channelid
;
266 uui
->account
= strdup(account
);
267 uui
->accountid
= accountid
;
269 a4stats_update_user_cb(NULL
, uui
);
271 LUA_RETURN(ps
, LUA_OK
);
274 typedef struct relation_update_info
{
276 unsigned long channelid
;
278 unsigned long firstid
;
280 unsigned long secondid
;
281 } relation_update_info
;
283 static void a4stats_update_relation_cb(const struct DBAPIResult
*result
, void *uarg
) {
284 relation_update_info
*rui
= uarg
;
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
);
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
));
299 if (!result
|| result
->affected
== 0)
300 Error("a4stats", ERR_WARNING
, "Unable to update relation.");
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
;
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
);
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);
321 rui
= malloc(sizeof(*rui
));
323 rui
->channelid
= channelid
;
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
;
331 rui
->first
= strdup(user2
);
332 rui
->firstid
= user2id
;
333 rui
->second
= strdup(user1
);
334 rui
->secondid
= user1id
;
337 a4stats_update_relation_cb(NULL
, rui
);
339 LUA_RETURN(ps
, LUA_OK
);
342 static int a4stats_lua_add_line(lua_State
*ps
) {
347 if (!lua_isstring(ps
, 1) || !lua_isnumber(ps
, 2))
348 LUA_RETURN(ps
, LUA_FAIL
);
350 channel
= lua_tostring(ps
, 1);
351 hour
= lua_tonumber(ps
, 2);
353 snprintf(query
, sizeof(query
), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour
, hour
);
355 a4statsdb
->squery(a4statsdb
, query
, "Ts", "channels", channel
);
357 LUA_RETURN(ps
, LUA_OK
);
360 static void a4stats_fetch_channels_cb(const struct DBAPIResult
*result
, void *uarg
) {
361 db_callback_info
*dci
= uarg
;
362 unsigned long channelid
;
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));
374 lua_vpcall(dci
->interp
, dci
->callback
, "lsiR", channelid
, channel
, active
, dci
->uarg_index
);
378 result
->clear(result
);
382 luaL_unref(dci
->interp
, LUA_REGISTRYINDEX
, dci
->uarg_index
);
384 a4stats_delete_dci(dci
);
387 static int a4stats_lua_fetch_channels(lua_State
*ps
) {
388 const char *callback
;
389 db_callback_info
*dci
;
391 if (!lua_isstring(ps
, 1))
392 LUA_RETURN(ps
, LUA_FAIL
);
394 callback
= lua_tostring(ps
, 1);
396 dci
= malloc(sizeof(*dci
));
399 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
400 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
402 lua_pushvalue(ps
, 2);
403 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
405 a4statsdb
->query(a4statsdb
, a4stats_fetch_channels_cb
, dci
, "SELECT id, name, active FROM ?", "T", "channels");
407 LUA_RETURN(ps
, LUA_OK
);
410 static int a4stats_lua_enable_channel(lua_State
*ps
) {
411 if (!lua_isstring(ps
, 1))
412 LUA_RETURN(ps
, LUA_FAIL
);
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));
417 LUA_RETURN(ps
, LUA_OK
);
420 static int a4stats_lua_disable_channel(lua_State
*ps
) {
421 if (!lua_isstring(ps
, 1))
422 LUA_RETURN(ps
, LUA_FAIL
);
424 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL
), lua_tostring(ps
, 1));
426 LUA_RETURN(ps
, LUA_OK
);
429 static int a4stats_lua_add_kick(lua_State
*ps
) {
430 unsigned long channelid
, kickerid
, victimid
;
431 const char *kicker
, *victim
, *reason
;
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
);
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);
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
);
446 LUA_RETURN(ps
, LUA_OK
);
449 static int a4stats_lua_add_topic(lua_State
*ps
) {
450 unsigned long channelid
, setbyid
;
451 const char *topic
, *setby
;
453 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isstring(ps
, 3) || !lua_isnumber(ps
, 4))
454 LUA_RETURN(ps
, LUA_FAIL
);
456 channelid
= lua_tonumber(ps
, 1);
457 topic
= lua_tostring(ps
, 2);
458 setby
= lua_tostring(ps
, 3);
459 setbyid
= lua_tonumber(ps
, 4);
461 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
462 "topics", channelid
, topic
, time(NULL
), setby
, setbyid
);
464 LUA_RETURN(ps
, LUA_OK
);
467 static void a4stats_hook_loadscript(int hooknum
, void *arg
) {
469 lua_State
*l
= args
[1];
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
);
483 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
485 static void a4stats_hook_unloadscript(int hooknum
, void *arg
) {
486 db_callback_info
**pnext
, *dci
;
489 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
491 if (dci
->interp
== l
) {
504 registerhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
505 registerhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);
508 for (l
= lua_head
; l
;l
= l
->next
) {
510 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT
, args
);
519 for (l
= lua_head
; l
;l
= l
->next
) {
520 a4stats_hook_unloadscript(HOOK_LUA_UNLOADSCRIPT
, l
->l
);
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");
534 deregisterhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
535 deregisterhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);