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 1, "
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, 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");
62 static void a4stats_closedb(void) {
66 a4statsdb
->close(a4statsdb
);
70 static int a4stats_lua_escape_string(lua_State
*ps
) {
75 if (!lua_isstring(ps
, 1))
76 LUA_RETURN(ps
, LUA_FAIL
);
78 input
= lua_tostring(ps
, 1);
81 buf
= malloc(len
* 2 + 1);
84 LUA_RETURN(ps
, LUA_FAIL
);
86 a4statsdb
->escapestring(a4statsdb
, buf
, input
, len
);
88 buf2
= malloc(len
* 4 + 1);
92 LUA_RETURN(ps
, LUA_FAIL
);
97 for (i
= 0; buf
[i
]; i
++) {
103 buf2
[i
+ o
] = buf
[i
];
109 lua_pushstring(ps
, buf2
);
116 typedef struct db_callback_info
{
117 struct db_callback_info
*next
;
124 static db_callback_info
*dci_head
;
126 static void a4stats_delete_dci(db_callback_info
*dci
) {
127 db_callback_info
**pnext
;
129 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
139 static void a4stats_fetch_user_cb(const struct DBAPIResult
*result
, void *uarg
) {
140 db_callback_info
*dci
= uarg
;
141 time_t seen
= 0, quotereset
= 0;
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;
151 result
->clear(result
);
155 lua_vpcall(dci
->interp
, dci
->callback
, "llR", (long)seen
, (long)quotereset
, dci
->uarg_index
);
156 luaL_unref(dci
->interp
, LUA_REGISTRYINDEX
, dci
->uarg_index
);
159 a4stats_delete_dci(dci
);
162 static int a4stats_lua_fetch_user(lua_State
*ps
) {
163 const char *account
, *callback
;
164 unsigned long channelid
, accountid
;
165 db_callback_info
*dci
;
167 if (!lua_islong(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3) || !lua_isstring(ps
, 4))
168 LUA_RETURN(ps
, LUA_FAIL
);
170 channelid
= lua_tonumber(ps
, 1);
171 account
= lua_tostring(ps
, 2);
172 accountid
= lua_tonumber(ps
, 3);
173 callback
= lua_tostring(ps
, 4);
175 dci
= malloc(sizeof(*dci
));
178 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
179 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
181 lua_pushvalue(ps
, 5);
182 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
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
);
186 LUA_RETURN(ps
, LUA_OK
);
189 typedef struct user_update_info
{
192 unsigned long channelid
;
194 unsigned long accountid
;
197 static void a4stats_update_user_cb(const struct DBAPIResult
*result
, void *uarg
) {
198 user_update_info
*uui
= uarg
;
202 if (uui
->stage
== 1 || (result
!= NULL
&& uui
->stage
== 3))
203 a4statsdb
->query(a4statsdb
, a4stats_update_user_cb
, uui
, uui
->update
, "TUUs", "users", uui
->channelid
, uui
->accountid
, uui
->account
);
205 if (result
== NULL
|| result
->affected
> 0 || uui
->stage
== 4) {
206 if (result
== NULL
|| (result
->affected
== 0 && uui
->stage
== 4))
207 Error("a4stats", ERR_WARNING
, "Unable to update user.");
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
));
219 static int a4stats_lua_update_user(lua_State
*ps
) {
221 unsigned long channelid
, accountid
;
224 user_update_info
*uui
;
226 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isnumber(ps
, 3))
227 LUA_RETURN(ps
, LUA_FAIL
);
229 channelid
= lua_tonumber(ps
, 1);
230 account
= lua_tostring(ps
, 2);
231 accountid
= lua_tonumber(ps
, 3);
233 strcpy(query
, "UPDATE ? SET ");
235 lua_pushvalue(ps
, 4);
238 while (lua_next(ps
, -2)) {
239 const char *value
= lua_tostring(ps
, -1);
246 strcat(query
, value
);
253 strcat(query
, " WHERE channelid = ? AND (accountid != 0 AND accountid = ? OR accountid = 0 AND account = ?)");
255 uui
= malloc(sizeof(*uui
));
257 uui
->update
= strdup(query
);
258 uui
->channelid
= channelid
;
259 uui
->account
= strdup(account
);
260 uui
->accountid
= accountid
;
262 a4stats_update_user_cb(NULL
, uui
);
264 LUA_RETURN(ps
, LUA_OK
);
267 static int a4stats_lua_add_line(lua_State
*ps
) {
272 if (!lua_isstring(ps
, 1) || !lua_isnumber(ps
, 2))
273 LUA_RETURN(ps
, LUA_FAIL
);
275 channel
= lua_tostring(ps
, 1);
276 hour
= lua_tonumber(ps
, 2);
278 snprintf(query
, sizeof(query
), "UPDATE ? SET h%d = h%d + 1 WHERE name = ?", hour
, hour
);
280 a4statsdb
->squery(a4statsdb
, query
, "Ts", "channels", channel
);
282 LUA_RETURN(ps
, LUA_OK
);
285 static void a4stats_fetch_channels_cb(const struct DBAPIResult
*result
, void *uarg
) {
286 db_callback_info
*dci
= uarg
;
287 unsigned long channelid
;
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));
299 lua_vpcall(dci
->interp
, dci
->callback
, "lsiR", channelid
, channel
, active
, dci
->uarg_index
);
303 result
->clear(result
);
307 luaL_unref(dci
->interp
, LUA_REGISTRYINDEX
, dci
->uarg_index
);
309 a4stats_delete_dci(dci
);
312 static int a4stats_lua_fetch_channels(lua_State
*ps
) {
313 const char *callback
;
314 db_callback_info
*dci
;
316 if (!lua_isstring(ps
, 1))
317 LUA_RETURN(ps
, LUA_FAIL
);
319 callback
= lua_tostring(ps
, 1);
321 dci
= malloc(sizeof(*dci
));
324 strncpy(dci
->callback
, callback
, sizeof(dci
->callback
));
325 dci
->callback
[sizeof(dci
->callback
) - 1] = '\0';
327 lua_pushvalue(ps
, 2);
328 dci
->uarg_index
= luaL_ref(ps
, LUA_REGISTRYINDEX
);
330 a4statsdb
->query(a4statsdb
, a4stats_fetch_channels_cb
, dci
, "SELECT id, name, active FROM ?", "T", "channels");
332 LUA_RETURN(ps
, LUA_OK
);
335 static int a4stats_lua_enable_channel(lua_State
*ps
) {
336 if (!lua_isstring(ps
, 1))
337 LUA_RETURN(ps
, LUA_FAIL
);
339 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (name, timestamp) VALUES (?, ?)", "Tst", "channels", lua_tostring(ps
, 1), time(NULL
));
340 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET active = 1, deleted = 0 WHERE name = ?", "Ts", "channels", lua_tostring(ps
, 1));
342 LUA_RETURN(ps
, LUA_OK
);
345 static int a4stats_lua_disable_channel(lua_State
*ps
) {
346 if (!lua_isstring(ps
, 1))
347 LUA_RETURN(ps
, LUA_FAIL
);
349 a4statsdb
->squery(a4statsdb
, "UPDATE ? SET active = 0, deleted = ? WHERE name = ?", "Tts", "channels", time(NULL
), lua_tostring(ps
, 1));
351 LUA_RETURN(ps
, LUA_OK
);
354 static int a4stats_lua_add_kick(lua_State
*ps
) {
355 unsigned long channelid
, kickerid
, victimid
;
356 const char *kicker
, *victim
, *reason
;
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))
359 LUA_RETURN(ps
, LUA_FAIL
);
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);
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
);
371 LUA_RETURN(ps
, LUA_OK
);
374 static int a4stats_lua_add_topic(lua_State
*ps
) {
375 unsigned long channelid
, setbyid
;
376 const char *topic
, *setby
;
378 if (!lua_isnumber(ps
, 1) || !lua_isstring(ps
, 2) || !lua_isstring(ps
, 3) || !lua_isnumber(ps
, 4))
379 LUA_RETURN(ps
, LUA_FAIL
);
381 channelid
= lua_tonumber(ps
, 1);
382 topic
= lua_tostring(ps
, 2);
383 setby
= lua_tostring(ps
, 3);
384 setbyid
= lua_tonumber(ps
, 4);
386 a4statsdb
->squery(a4statsdb
, "INSERT INTO ? (channelid, topic, timestamp, setby, setbyid) VALUES (?, ?, ?, ?, ?)", "TUstsU",
387 "topics", channelid
, topic
, time(NULL
), setby
, setbyid
);
389 LUA_RETURN(ps
, LUA_OK
);
392 static void a4stats_hook_loadscript(int hooknum
, void *arg
) {
394 lua_State
*l
= args
[1];
396 lua_register(l
, "a4_enable_channel", a4stats_lua_enable_channel
);
397 lua_register(l
, "a4_disable_channel", a4stats_lua_disable_channel
);
398 lua_register(l
, "a4_fetch_channels", a4stats_lua_fetch_channels
);
399 lua_register(l
, "a4_add_kick", a4stats_lua_add_kick
);
400 lua_register(l
, "a4_add_topic", a4stats_lua_add_topic
);
401 lua_register(l
, "a4_add_line", a4stats_lua_add_line
);
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
);
407 #define lua_unregister(L, n) (lua_pushnil(L), lua_setglobal(L, n))
409 static void a4stats_hook_unloadscript(int hooknum
, void *arg
) {
410 db_callback_info
**pnext
, *dci
;
413 for (pnext
= &dci_head
; *pnext
; pnext
= &((*pnext
)->next
)) {
415 if (dci
->interp
== l
) {
428 registerhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
429 registerhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);
432 for (l
= lua_head
; l
;l
= l
->next
) {
434 a4stats_hook_loadscript(HOOK_LUA_LOADSCRIPT
, args
);
443 for (l
= lua_head
; l
;l
= l
->next
) {
444 a4stats_hook_loadscript(HOOK_LUA_UNLOADSCRIPT
, l
->l
);
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");
451 lua_unregister(l
->l
, "a4_add_line");
452 lua_unregister(l
->l
, "a4_fetch_user");
453 lua_unregister(l
->l
, "a4_update_user");
454 lua_unregister(l
->l
, "a4_escape_string");
457 deregisterhook(HOOK_LUA_LOADSCRIPT
, a4stats_hook_loadscript
);
458 deregisterhook(HOOK_LUA_UNLOADSCRIPT
, a4stats_hook_unloadscript
);