X-Git-Url: http://git.pk910.de/?a=blobdiff_plain;f=src%2Fbot_NeonHelp.c;h=5e0e605076c9e3409f240ea6ef65536c7e9de80b;hb=f90d21daf31f8d69e24406678be696afa8cae962;hp=aa75426c4743b7a363674b6010cdcc18cf3e26d4;hpb=9d77ca8f63040a3945f634090be990fdeb05c2f3;p=NeonServV5.git diff --git a/src/bot_NeonHelp.c b/src/bot_NeonHelp.c index aa75426..5e0e605 100644 --- a/src/bot_NeonHelp.c +++ b/src/bot_NeonHelp.c @@ -1,5 +1,5 @@ -/* bot_HelpServ.c - NeonServ v5.2 - * Copyright (C) 2011 Philipp Kreil (pk910) +/* bot_NeonHelp.c - NeonServ v5.3 + * Copyright (C) 2011-2012 Philipp Kreil (pk910) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ #include "bot_NeonHelp.h" #include "modcmd.h" #include "cmd_neonhelp.h" +#include "lang.h" #include "mysqlConn.h" #include "ClientSocket.h" #include "UserNode.h" @@ -27,10 +28,41 @@ #include "IRCParser.h" #include "bots.h" #include "DBHelper.h" +#include "WHOHandler.h" #define BOTID 4 #define BOTALIAS "NeonHelp" +static const struct default_language_entry msgtab[] = { + {"NH_NOT_ON_CHAN_1", "You cannot open this request as you are not in %s."}, /* {ARGS: "#test"} */ + {"NH_NOT_ON_CHAN_2", "You cannot open this request as you are not in %s or %s."}, /* {ARGS: "test", "#test-support"} */ + {"NH_REQUEST_RECORDED", "Your message has been recorded and assigned request ID#%d A helper should contact you shortly."}, /* {ARGS: 5} */ + {"NH_REQUEST_OTHERS_0", "There are no other unhandled requests."}, + {"NH_REQUEST_OTHERS_1", "There is 1 other unhandled request."}, + {"NH_REQUEST_OTHERS_2", "There are %d other unhandled requests."}, /* {ARGS: 1337} */ + {"NH_REQUEST_FOOTER_1", "Everything you tell me until you are helped (or you leave %1$s) will be recorded. If you part %1$s, your request will be lost."}, /* {ARGS: "#test"} */ + {"NH_REQUEST_FOOTER_2", "Everything you tell me until you are helped (or you leave %1$s or %2$s) will be recorded. If you part %1$s or %2$s, your request will be lost."}, /* {ARGS: "#test", "#test-support"} */ + {"NH_NEW_REQUEST", "New request #%d by %s: %s"}, /* {ARGS: 5, "pk910", "Help, I've fallen and I can't get up!"} */ + {"NH_NEXT_NONE", "No more requests."}, + {"NH_NEXT_NOT_FOUND", "No request found."}, + {"NH_NEXT_HEADER", "$bNext request: #%d %s$b"}, /* {ARGS: 5, "pk910"} */ + {"NH_NEXT_HELPER", "Your helper for request ID#%d is %s (Current nick: %s)."}, /* {ARGS: 5, "pk910", "Skynet"} */ + {"NH_NEXT_JOIN", "Please /join %s now."}, /* {ARGS: "#test-support"} */ + {"NH_DELETED", "Your request ID#%d has been deleted."}, /* {ARGS: 5} */ + {"NH_DELETED_STAFF", "Request deleted: #%d (%s)"}, /* {ARGS: 5, "pk910"} */ + {"NH_REMIND_OPEN_REQUESTS_1", "There is %d unhandled request!"}, /* {ARGS: 1} */ + {"NH_REMIND_OPEN_REQUESTS_2", "There are %d unhandled requests!"}, /* {ARGS: 4} */ + {"NH_REQUESTS_HEADER_ID", "ID"}, + {"NH_REQUESTS_HEADER_STATUS", "State"}, + {"NH_REQUESTS_HEADER_NICK", "Nick"}, + {"NH_REQUESTS_HEADER_TIME", "Time"}, + {"NH_REQUESTS_HEADER_REQUEST", "Question"}, + {"NH_REQUESTS_STATE_ACTIVE", "active"}, + {"NH_REQUESTS_STATE_PENDING", "pending"}, + {"NH_REQUESTS_STATE_ERROR", "ERROR"}, + {NULL, NULL} +}; + static void neonhelp_bot_ready(struct ClientSocket *client) { MYSQL_RES *res; MYSQL_ROW row; @@ -49,14 +81,24 @@ static void neonhelp_bot_ready(struct ClientSocket *client) { } } -static void neonhelp_trigger_callback(struct ChanNode *chan, char *trigger) { +static void neonhelp_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) { MYSQL_RES *res; MYSQL_ROW row; loadChannelSettings(chan); - printf_mysql_query("SELECT `trigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, BOTID); + if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) { + strcpy(trigger, "!"); + return; + } + printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, BOTID); res = mysql_use(); - row = mysql_fetch_row(res); - strcpy(trigger, (strlen(row[0]) ? row[0] : "!")); + if(!(row = mysql_fetch_row(res))) { + strcpy(trigger, "!"); + return; + } + if(row[0] && *row[0]) + strcpy(trigger, row[0]); + else + strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "!")); } static void start_bots() { @@ -72,12 +114,15 @@ static void start_bots() { client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0); client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0); client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0); + client->flags |= SOCKET_FLAG_SILENT; client->botid = BOTID; client->clientid = atoi(row[7]); connect_socket(client); + //close old, still opened requests + printf_mysql_query("UPDATE `helpserv_requests` SET `status` = '2' WHERE `botid` = '%d'", client->clientid); } - printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID); + printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID); res2 = mysql_use(); while ((row = mysql_fetch_row(res2)) != NULL) { if(bind_cmd_to_command(BOTID, row[0], row[1])) { @@ -90,11 +135,375 @@ static void start_bots() { if(row[4]) { bind_set_channel_access(BOTID, row[0], row[4]); } + if(strcmp(row[5], "0")) + bind_set_bind_flags(BOTID, row[0], atoi(row[5])); } } bind_unbound_required_functions(BOTID); } +static void neonhelp_event_privmsg_async(struct ClientSocket *client, struct UserNode *user, struct UserNode *target, char *message); +static USERAUTH_CALLBACK(neonhelp_event_privmsg_nick_lookup); +struct neonhelp_event_privmsg_cache { + struct ClientSocket *client; + struct UserNode *user, *target; + char *message; +}; + +static void neonhelp_event_privmsg(struct UserNode *user, struct UserNode *target, char *message) { + struct ClientSocket *client; + for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) { + if(client->user == target) { + if(client->botid != BOTID) return; + break; + } + } + if(!client) return; //we got the message but we have no client that could receive it??? + if(user->flags & USERFLAG_ISAUTHED) { + neonhelp_event_privmsg_async(client, user, target, message); + } else { + struct neonhelp_event_privmsg_cache *cache = malloc(sizeof(*cache)); + if(!cache) return; + cache->client = client; + cache->user = user; + cache->target = target; + cache->message = strdup(message); + get_userauth(user, neonhelp_event_privmsg_nick_lookup, cache); + } +} + +static USERAUTH_CALLBACK(neonhelp_event_privmsg_nick_lookup) { + struct neonhelp_event_privmsg_cache *cache = data; + neonhelp_event_privmsg_async(cache->client, cache->user, cache->target, cache->message); + free(cache->message); + free(cache); +} + +static TIMEQ_CALLBACK(neonhelp_remind_open_requests); + +static void neonhelp_event_privmsg_async(struct ClientSocket *client, struct UserNode *user, struct UserNode *target, char *message) { + MYSQL_RES *res; + MYSQL_ROW row, row2; + printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern`, `helpserv_intern_announce` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid); + res = mysql_use(); + if (!(row = mysql_fetch_row(res))) return; + //check if the user is a supporter (access in the support channel) + if((user->flags & USERFLAG_ISAUTHED)) { + int caccess = 0; + int userid; + if(user->flags & USERFLAG_HAS_USERID) + userid = user->user_id; + else { + printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth)); + res = mysql_use(); + if ((row2 = mysql_fetch_row(res)) != NULL) { + userid = atoi(row2[0]); + user->user_id = userid; + user->flags |= USERFLAG_HAS_USERID; + } else + userid = 0; + } + printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d' AND `channel_name` = '%s'", userid, escape_string(row[0])); + res = mysql_use(); + if ((row2 = mysql_fetch_row(res)) != NULL) { + int cflags = atoi(row2[1]); + if(!(cflags & DB_CHANUSER_SUSPENDED)) + caccess = atoi(row2[0]); + } + if(caccess) return; //ignore messages from supporters + } + //check if the user is in one of the bot's channels + struct ChanUser *chanuser; + struct ChanNode *chan; + for(chanuser = getUserChannels(target, NULL); chanuser; chanuser = getUserChannels(target, chanuser)) { + chan = chanuser->chan; + if((!stricmp(chan->name, row[0]) || (row[1] && !stricmp(chan->name, row[1]))) && isUserOnChan(user, chan)) + break; + } + if(!chanuser) { + if(row[1]) + reply(client, user, "NH_NOT_ON_CHAN_2", row[0], row[1]); + else + reply(client, user, "NH_NOT_ON_CHAN_1", row[0]); + return; + } + //check if there is already a support request + int others = 0; + if(client->flags & SOCKET_HAVE_HELPNODE) { + struct NeonHelpNode *helpnode; + for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) { + if(helpnode->user == user) { + //simply append the message to the database + printf_mysql_query("SELECT `text` FROM `helpserv_requests` WHERE `id` = %d", helpnode->suppid); + res = mysql_use(); + if ((row2 = mysql_fetch_row(res)) != NULL) { + char *old_msg = escape_string(row2[0]); + char *new_msg = escape_string(message); + printf_long_mysql_query(1024 + strlen(old_msg) + strlen(new_msg), "UPDATE `helpserv_requests` SET `text` = '%s\n%s' WHERE `id` = %d", old_msg, new_msg, helpnode->suppid); + } + return; + } + others++; + } + } + //add new request + struct NeonHelpNode *helpnode = malloc(sizeof(*helpnode)); + if(!helpnode) return; + helpnode->user = user; + helpnode->logchan = getChanByName(row[0]); + helpnode->status = 0; + helpnode->announce = (row[2] && strcmp(row[3], "0") ? 1 : 0); + if(helpnode->announce) { + char nameBuf[30]; + sprintf(nameBuf, "neonhelp_%d", client->clientid); + if(!timeq_name_exists(nameBuf)) { + int *cidptr = malloc(sizeof(int)); + *cidptr = client->clientid; + timeq_add_name(nameBuf, 300, neonhelp_remind_open_requests, cidptr); + } + } + printf_mysql_query("INSERT INTO `helpserv_requests` (`botid`, `host`, `hand`, `nick`, `status`, `supporter`, `time`, `text`) VALUES ('%d', '%s@%s', '%s', '%s', '0', '-1', UNIX_TIMESTAMP(), '%s')", client->clientid, escape_string(user->ident), escape_string(user->host), ((user->flags & USERFLAG_ISAUTHED) ? escape_string(user->auth) : "*"), escape_string(user->nick), escape_string(message)); + helpnode->suppid = (int) mysql_insert_id(get_mysql_conn()); + helpnode->log = NULL; + helpnode->next = ((client->flags & SOCKET_HAVE_HELPNODE) ? client->botclass_helpnode : NULL); + client->botclass_helpnode = helpnode; + client->flags |= SOCKET_HAVE_HELPNODE; + //build the user reply... + char user_reply[MAXLEN]; + int user_reply_pos = 0; + char reply_buff[MAXLEN]; + //1st part: NH_REQUEST_RECORDED + strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_RECORDED", helpnode->suppid)); + user_reply_pos += strlen(reply_buff); + //2nd part: NH_REQUEST_OTHERS_0 / NH_REQUEST_OTHERS_1 / NH_REQUEST_OTHERS_2 + user_reply[user_reply_pos++] = ' '; + if(others <= 1) + strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, (others ? "NH_REQUEST_OTHERS_1" : "NH_REQUEST_OTHERS_0"))); + else + strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_OTHERS_2", others)); + user_reply_pos += strlen(reply_buff); + //3th part: NH_REQUEST_FOOTER_1 / NH_REQUEST_FOOTER_2 + user_reply[user_reply_pos++] = ' '; + if(row[1]) + strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_FOOTER_2", row[0], row[1])); + else + strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_FOOTER_1", row[0])); + user_reply_pos += strlen(reply_buff); + reply(client, user, "%s", user_reply); + //sent a message to the internal channel / onotice to supp channel + build_language_string(user, reply_buff, "NH_NEW_REQUEST", helpnode->suppid, user->nick, message); + if(row[2]) { + putsock(client, "PRIVMSG %s :%s", row[2], reply_buff); + } else { + putsock(client, "NOTICE @%s :%s", row[0], reply_buff); + } +} + +static TIMEQ_CALLBACK(neonhelp_remind_open_requests) { + int clientid = *((int*)data); + MYSQL_RES *res; + MYSQL_ROW row; + printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern`, `helpserv_intern_announce` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", clientid); + res = mysql_use(); + if (!(row = mysql_fetch_row(res)) || !row[2]) { + free(data); + return; + } + struct ClientSocket *client; + for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) { + if(client->clientid == clientid) + break; + } + if(!client) { + free(data); + return; + } + //count open requests + int requests = 0; + struct NeonHelpNode *helpnode; + if(client->flags & SOCKET_HAVE_HELPNODE) { + for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) { + if(helpnode->status == 0) { + requests++; + } + } + } + if(requests) { + char nameBuf[30]; + sprintf(nameBuf, "neonhelp_%d", client->clientid); + if(!timeq_name_exists(nameBuf)) { + timeq_add_name(nameBuf, 300, neonhelp_remind_open_requests, data); + } + char replybuf[MAXLEN]; + build_language_string(NULL, replybuf, (requests == 1 ? "NH_REMIND_OPEN_REQUESTS_1" : "NH_REMIND_OPEN_REQUESTS_2"), requests); + putsock(client, "PRIVMSG %s :%s", row[2], replybuf); + } else + free(data); +} + +static void neonhelp_event_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) { + char logline[MAXLEN]; + sprintf(logline, "<%s> %s", user->nick, message); + struct ClientSocket *client; + for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) { + if(client->botid == BOTID) { + struct NeonHelpNode *helpnode; + if(client->flags & SOCKET_HAVE_HELPNODE) { + for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) { + if(helpnode->logchan == chan && helpnode->status == 1) { + if(!helpnode->log) { + helpnode->log = calloc(LOGBUFFERLINES, sizeof(char*)); + if(!helpnode->log) return; + } + int i; + for(i = 0; i < LOGBUFFERLINES; i++) { + if(!helpnode->log[i]) { + helpnode->log[i] = strdup(logline); + break; + } + } + if(i == LOGBUFFERLINES) { + //write buffer to database + char logbuff[MAXLEN * LOGBUFFERLINES]; + int len = 0; + for(i = 0; i < LOGBUFFERLINES; i++) { + len += sprintf(logbuff + len, "%s\n", helpnode->log[i]); + free(helpnode->log[i]); + helpnode->log[i] = NULL; + } + printf_long_mysql_query(1024 + len, "UPDATE `helpserv_requests` SET `log` = CONCAT(`log`, '%s') WHERE `id` = %d", escape_string(logbuff), helpnode->suppid); + } + break; + } + } + } + } + } +} + +static void destroy_support_request(struct ClientSocket *client, struct NeonHelpNode *helpnode, int do_reply) { + //write buffer to database + char logbuff[MAXLEN * LOGBUFFERLINES]; + int len = 0; + int i; + if(helpnode->log) { + for(i = 0; i < LOGBUFFERLINES; i++) { + if(!helpnode->log[i]) break; + len += sprintf(logbuff + len, "%s\n", helpnode->log[i]); + free(helpnode->log[i]); + helpnode->log[i] = NULL; + } + free(helpnode->log); + } else + logbuff[0] = '\0'; + printf_long_mysql_query(1024 + len, "UPDATE `helpserv_requests` SET `status`='2', `log` = CONCAT(`log`, '%s') WHERE `id` = %d", escape_string(logbuff), helpnode->suppid); + if(do_reply) { + reply(client, helpnode->user, "NH_DELETED", helpnode->suppid); + } + free(helpnode); +} + +static void neonhelp_event_kick(struct UserNode *user, struct ChanUser *target, char *reason) { + struct ClientSocket *client; + MYSQL_RES *res; + MYSQL_ROW row; + struct ChanNode *support, *public; + int userHasRequest; + for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) { + if(client->botid == BOTID && isUserOnChan(client->user, target->chan)) { + userHasRequest = 0; + struct NeonHelpNode *helpnode, *prev_helpnode = NULL; + if(client->flags & SOCKET_HAVE_HELPNODE) { + for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) { + if(helpnode->user == target->user) { + userHasRequest = 1; + break; + } else + prev_helpnode = helpnode; + } + } + if(!userHasRequest) continue; + printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid); + res = mysql_use(); + if (!(row = mysql_fetch_row(res))) continue; + support = getChanByName(row[0]); + public = (row[1] ? getChanByName(row[1]) : NULL); + if(target->chan == support || !((support && isUserOnChan(target->user, support)) || (public && isUserOnChan(target->user, public)))) { + //free the user's support request + if(prev_helpnode) + prev_helpnode->next = helpnode->next; + else + client->botclass_helpnode = helpnode->next; + destroy_support_request(client, helpnode, 1); + } + } + } +} + +static void neonhelp_event_part(struct ChanUser *target, char *reason) { + struct ClientSocket *client; + MYSQL_RES *res; + MYSQL_ROW row; + struct ChanNode *support, *public; + int userHasRequest; + for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) { + if(client->botid == BOTID && isUserOnChan(client->user, target->chan)) { + userHasRequest = 0; + struct NeonHelpNode *helpnode, *prev_helpnode = NULL; + if(client->flags & SOCKET_HAVE_HELPNODE) { + for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) { + if(helpnode->user == target->user) { + userHasRequest = 1; + break; + } else + prev_helpnode = helpnode; + } + } + if(!userHasRequest) continue; + printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid); + res = mysql_use(); + if (!(row = mysql_fetch_row(res))) continue; + support = getChanByName(row[0]); + public = (row[1] ? getChanByName(row[1]) : NULL); + if(target->chan == support || !((support && isUserOnChan(target->user, support)) || (public && isUserOnChan(target->user, public)))) { + //free the user's support request + if(prev_helpnode) + prev_helpnode->next = helpnode->next; + else + client->botclass_helpnode = helpnode->next; + destroy_support_request(client, helpnode, 1); + } + } + } +} + +static void neonhelp_event_quit(struct UserNode *target, char *reason) { + struct ClientSocket *client; + int userHasRequest; + for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) { + if(client->botid == BOTID) { + userHasRequest = 0; + struct NeonHelpNode *helpnode, *prev_helpnode = NULL; + if(client->flags & SOCKET_HAVE_HELPNODE) { + for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) { + if(helpnode->user == target) { + userHasRequest = 1; + break; + } else + prev_helpnode = helpnode; + } + } + if(!userHasRequest) continue; + //free the user's support request + if(prev_helpnode) + prev_helpnode->next = helpnode->next; + else + client->botclass_helpnode = helpnode->next; + destroy_support_request(client, helpnode, 0); + } + } +} + void init_NeonHelp() { set_bot_alias(BOTID, BOTALIAS); @@ -102,8 +511,15 @@ void init_NeonHelp() { //register events bind_bot_ready(neonhelp_bot_ready); + bind_privmsg(neonhelp_event_privmsg); + bind_chanmsg(neonhelp_event_chanmsg); + bind_part(neonhelp_event_part); + bind_kick(neonhelp_event_kick); + bind_quit(neonhelp_event_quit); set_trigger_callback(BOTID, neonhelp_trigger_callback); + + register_default_language_table(msgtab); } void loop_NeonHelp() {