fc00afacc25c162b11d9af2397cb155c8b53fe22
[NeonServV5.git] / src / modules / NeonServ.mod / cmd_neonserv_unvisited.c
1 /* cmd_neonserv_unvisited.c - NeonServ v5.6
2  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License 
15  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
16  */
17
18 #include "cmd_neonserv.h"
19 #include "../botid.h"
20
21 struct neonserv_cmd_unvisited_cache {
22     struct ClientSocket *client, *textclient;
23     struct UserNode *user;
24     int duration, unregister_matches;
25     int who_count, matches;
26 };
27
28 static USERLIST_CALLBACK(neonserv_cmd_unvisited_userlist_lookup);
29 static void neonserv_check_unvisited(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, int duration, int unregister_matches);
30 static int neonserv_cmd_unvisited_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, int duration, int unregister_matches);
31 static void neonserv_cmd_unvisited_async2(struct neonserv_cmd_unvisited_cache *cache);
32 static void neonserv_cmd_unvisited_unreg(struct ClientSocket *client, char *channel);
33 static TIMEQ_CALLBACK(neonserv_check_unvisited_timer);
34
35 CMD_BIND(neonserv_cmd_unvisited) {
36     int duration = (argc ? strToTime(user, argv[0]) : 60*60*24*7*3);
37     reply(textclient, user, "NS_SEARCH_HEADER");
38     int unreg = 0;
39     if(argc > 1 && !stricmp(argv[1], "unregister")) 
40         unreg = 1;
41     neonserv_check_unvisited(client, textclient, user, duration, unreg);
42 }
43
44 void neonserv_cmd_unvisited_init() {
45     if(!timeq_name_exists("neonserv_unvisited"))
46         timeq_add_name("neonserv_unvisited", 1200, module_id, neonserv_check_unvisited_timer, NULL);
47 }
48
49 static TIMEQ_CALLBACK(neonserv_check_unvisited_timer) {
50     char tmp[200];
51     char *modname = get_module_name(module_id);
52     sprintf(tmp, "modules.%s.chan_expire_freq", modname);
53     char *check_freq_str = get_string_field(tmp);
54     int check_freq;
55     if(!check_freq_str || (check_freq = strToTime(NULL, check_freq_str)) < (60*60)) {
56         timeq_add_name("neonserv_unvisited", 1800, module_id, neonserv_check_unvisited_timer, NULL);
57         return;
58     }
59     sprintf(tmp, "modules.%s.chan_expire_delay", modname);
60     char *check_expire_str = get_string_field(tmp);
61     int duration;
62     if(!check_expire_str || (duration = strToTime(NULL, check_expire_str)) < 60*60*24*7) return;
63     neonserv_check_unvisited(NULL, NULL, NULL, duration, 1);
64 }
65
66 static void neonserv_check_unvisited(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, int duration, int unregister_matches) {
67     MYSQL_RES *res, *res2;
68     MYSQL_ROW row, row2;
69     struct ChanNode *channel;
70     struct neonserv_cmd_unvisited_cache *cache = malloc(sizeof(*cache));
71     if (!cache) {
72         perror("malloc() failed");
73         return;
74     }
75     cache->client = client;
76     cache->textclient = textclient;
77     cache->user = user;
78     cache->duration = duration;
79     cache->unregister_matches = unregister_matches;
80     cache->who_count = 1; /* small fake to prevent the cache to be freed too early */
81     cache->matches = 0;
82     int botid;
83     if(client)
84         botid = client->botid;
85     else
86         botid = NEONSERV_BOTID;
87     printf_mysql_query("SELECT `channel_id`, `channel_name`, `channel_nodelete` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` LEFT JOIN `users` ON `channel_registrator` = `user_id` WHERE `botid` = '%d'", botid);
88     res = mysql_use();
89     while ((row = mysql_fetch_row(res)) != NULL) {
90         if(!strcmp(row[2], "1")) continue;
91         printf_mysql_query("SELECT `chanuser_seen` FROM `chanusers` WHERE `chanuser_cid` = '%s' AND `chanuser_access` >= 300 ORDER BY `chanuser_seen` DESC LIMIT 1", row[0]);
92         res2 = mysql_use();
93         row2 = mysql_fetch_row(res2);
94         if(row2 && atol(row2[0]) > (time(0) - duration)) continue;
95         channel = getChanByName(row[1]);
96         if(channel) {
97             cache->who_count++;
98             channel->channel_id = atoi(row[0]);
99             get_userlist_with_invisible(channel, module_id, neonserv_cmd_unvisited_userlist_lookup, cache);
100         } else {
101             if(textclient)
102                 reply(textclient, user, "%s", row[1]);
103             if(unregister_matches)
104                 neonserv_cmd_unvisited_unreg(client, row[1]);
105             cache->matches++;
106         }   
107     }
108     cache->who_count--; //see fix on line 78
109     if(cache->who_count == 0) {
110         neonserv_cmd_unvisited_async2(cache);
111     }
112 }
113
114 static USERLIST_CALLBACK(neonserv_cmd_unvisited_userlist_lookup) {
115     struct neonserv_cmd_unvisited_cache *cache = data;
116     if(neonserv_cmd_unvisited_async1(cache->client, cache->textclient, cache->user, chan, cache->duration, cache->unregister_matches))
117         cache->matches++;
118     cache->who_count--;
119     if(cache->who_count == 0) {
120         neonserv_cmd_unvisited_async2(cache);
121     }
122 }
123
124 static int neonserv_cmd_unvisited_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, int duration, int unregister_matches) {
125     struct ChanUser *chanuser;
126     MYSQL_RES *res2;
127     MYSQL_ROW row2;
128     int active = 0;
129     for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
130         if(isBot(chanuser->user)) continue;
131         if((chanuser->user->flags & USERFLAG_ISAUTHED)) {
132             printf_mysql_query("SELECT `chanuser_id`, `chanuser_access` FROM `chanusers` LEFT JOIN `users` ON `user_id` = `chanuser_uid` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(chanuser->user->auth));
133             res2 = mysql_use();
134             row2 = mysql_fetch_row(res2);
135             if(row2 && atoi(row2[1]) >= 300) {
136                 printf_mysql_query("UPDATE `chanusers` SET `chanuser_seen` = UNIX_TIMESTAMP() WHERE `chanuser_id` = '%s'", row2[0]);
137                 active = 1;
138             }
139         }
140     }
141     if(!active) {
142         if(textclient)
143             reply(textclient, user, "%s", chan->name);
144         if(unregister_matches)
145             neonserv_cmd_unvisited_unreg(client, chan->name);
146     }
147     return !active;
148 }
149
150 static void neonserv_cmd_unvisited_async2(struct neonserv_cmd_unvisited_cache *cache) {
151     if(cache->textclient)
152         reply(cache->textclient, cache->user, "NS_TABLE_COUNT", cache->matches);
153     free(cache);
154 }
155
156 static void neonserv_cmd_unvisited_unreg(struct ClientSocket *client, char *channel) {
157     MYSQL_RES *res;
158     MYSQL_ROW row;
159     int sync_neonspam_unreg = get_int_field("General.sync_neonspam_unreg");
160     int botid;
161     if(client)
162         botid = client->botid;
163     else
164         botid = NEONSERV_BOTID;
165     printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended`, `channels`.`channel_nodelete` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), botid);
166     res = mysql_use();
167     if ((row = mysql_fetch_row(res)) == NULL) {
168         return;
169     }
170     int clientid = atoi(row[0]);
171     struct ClientSocket *bot;
172     for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
173         if(bot->clientid == clientid)
174             break;
175     }
176     if(!strcmp(row[3], "1")) return;
177     printf_mysql_query("DELETE FROM `bot_channels` WHERE `id` = '%s'", row[1]);
178     if(bot && strcmp(row[2], "1")) {
179         putsock(bot, "PART %s :Channel unregistered.", channel);
180     }
181     if(botid == NEONSERV_BOTID && sync_neonspam_unreg) {
182         botid = NEONSPAM_BOTID;
183         printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), botid);
184         res = mysql_use();
185         if ((row = mysql_fetch_row(res)) == NULL) {
186             return;
187         }
188         clientid = atoi(row[0]);
189         for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
190             if(bot->clientid == clientid)
191                 break;
192         }
193         printf_mysql_query("DELETE FROM `bot_channels` WHERE `id` = '%s'", row[1]);
194         if(bot && strcmp(row[2], "1")) {
195             putsock(bot, "PART %s :Channel unregistered.", channel);
196         }
197     }
198     if(botid == NEONSERV_BOTID) {
199         char setting[128];
200         sprintf(setting, "modules.%s.auto_backup_unregister", get_module_name(module_id));
201         if(get_int_field(setting))
202             module_global_cmd_unregister_neonbackup(channel);
203     }
204     char *alertchan = get_string_field("General.alertchan");
205     if(alertchan) {
206         struct ChanNode *alertchan_chan = getChanByName(alertchan);
207         struct ClientSocket *alertclient;
208         if(alertchan_chan && (alertclient = getChannelBot(alertchan_chan, 0)) != NULL) {
209             putsock(alertclient, "PRIVMSG %s :Unregistered %s (unvisited)", alertchan_chan->name, channel);
210         }
211     }
212 }