added sort function to cmd_nicklist
[NeonServV5.git] / src / modules / NeonServ.mod / cmd_neonserv_nicklist.c
1 /* cmd_neonserv_nicklist.c - NeonServ v5.5
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
20 /*
21 * argv[0]    "force"
22 * argv[1]    (optional) nick mask
23 */
24 static USERLIST_CALLBACK(neonserv_cmd_nicklist_userlist_lookup);
25 static void neonserv_cmd_nicklist_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask, int syncusers);
26 static void neonserv_cmd_nicklist_synchronize_user(struct ChanNode *chan, struct UserNode *user, int access);
27
28 struct neonserv_cmd_nicklist_cache {
29     struct ClientSocket *client, *textclient;
30     struct UserNode *user;
31     struct Event *event;
32     char *nickmask;
33     int syncusers;
34 };
35
36 CMD_BIND(neonserv_cmd_nicklist) {
37     int syncusers = 0;
38     if(argc && !stricmp(argv[0], "sync")) {
39         if(!checkChannelAccess(user, chan, "channel_canadd", 0)) {
40             if(isGodMode(user)) {
41                 event->flags |= CMDFLAG_OPLOG;
42             } else {
43                 reply(textclient, user, "NS_ACCESS_DENIED");
44                 return;
45             }
46         }
47         argv++;
48         argc--;
49         syncusers = 1;
50         event->flags |= CMDFLAG_LOG;
51     }
52     if(argc && !stricmp(argv[0], "nowho") && isGodMode(user)) {
53         argv++;
54         argc--;
55         neonserv_cmd_nicklist_async1(client, textclient, user, chan, event, (argc ? argv[0] : NULL), syncusers);
56         return;
57     }
58     struct neonserv_cmd_nicklist_cache *cache = malloc(sizeof(*cache));
59     if (!cache) {
60         perror("malloc() failed");
61         return;
62     }
63     cache->client = client;
64     cache->textclient = textclient;
65     cache->user = user;
66     cache->event = event;
67     if(argc) {
68         cache->nickmask = strdup(argv[0]);
69     } else
70         cache->nickmask = NULL;
71     cache->syncusers = syncusers;
72     get_userlist_with_invisible(chan, module_id, neonserv_cmd_nicklist_userlist_lookup, cache);
73 }
74
75 static USERLIST_CALLBACK(neonserv_cmd_nicklist_userlist_lookup) {
76     struct neonserv_cmd_nicklist_cache *cache = data;
77     neonserv_cmd_nicklist_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nickmask, cache->syncusers);
78     if(cache->nickmask)
79         free(cache->nickmask);
80     free(cache);
81 }
82
83 static int neonserv_cmd_nicklist_sort_flags[] = { 
84     CHANUSERFLAG_OPPED | CHANUSERFLAG_HALFOPPED | CHANUSERFLAG_VOICED,
85     CHANUSERFLAG_OPPED | CHANUSERFLAG_HALFOPPED,
86     CHANUSERFLAG_OPPED | CHANUSERFLAG_VOICED,
87     CHANUSERFLAG_OPPED,
88     CHANUSERFLAG_HALFOPPED | CHANUSERFLAG_VOICED,
89     CHANUSERFLAG_HALFOPPED,
90     CHANUSERFLAG_VOICED,
91     CHANUSERFLAG_INVISIBLE,
92     0
93 };
94
95 static int neonserv_cmd_nicklist_sort(const void *a, const void *b) {
96     const struct ChanUser *chanuser_a = *((struct ChanUser * const *) a);
97     const struct ChanUser *chanuser_b = *((struct ChanUser * const *) b); 
98     int i_a = 0, i_b = 0;
99     while((chanuser_a->flags & (CHANUSERFLAG_OPPED | CHANUSERFLAG_VOICED | CHANUSERFLAG_INVISIBLE)) != neonserv_cmd_nicklist_sort_flags[i_a] && neonserv_cmd_nicklist_sort_flags[i_a])
100         i_a++;
101     while((chanuser_b->flags & (CHANUSERFLAG_OPPED | CHANUSERFLAG_VOICED | CHANUSERFLAG_INVISIBLE)) != neonserv_cmd_nicklist_sort_flags[i_b] && neonserv_cmd_nicklist_sort_flags[i_b])
102         i_b++;
103     if(i_a == i_b) {
104         return stricmp(chanuser_a->user->nick, chanuser_b->user->nick);
105     } else
106         return i_a - i_b;
107 }
108
109 static void neonserv_cmd_nicklist_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask, int syncusers) {
110     MYSQL_RES *res;
111     MYSQL_ROW row, defaults = NULL;
112     struct Table *table;
113     char *content[3];
114     int userlistlen, i, j;
115     int db_enfops, db_enfvoice;
116     int caccess, synced_user;
117     struct ChanUser *chanusers[chan->usercount];
118     struct ChanUser *chanuser;
119     int chanuser_count;
120     char statebuf[5];
121     char accessbuf[9];
122     int uaccess;
123     
124     table = table_init(3, chan->usercount + 1, 0);
125     content[0] = get_language_string(user, "NS_NICKLIST_NICK");
126     content[1] = get_language_string(user, "NS_NICKLIST_STATE");
127     content[2] = get_language_string(user, "NS_NICKLIST_ACCESS");
128     table_add(table, content);
129     
130     printf_mysql_query("SELECT `chanuser_access`, `user_user`, `chanuser_flags` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d'", chan->channel_id);
131     res = mysql_use();
132     userlistlen = mysql_num_rows(res);
133     i = 0;
134     MYSQL_ROW userlist[userlistlen];
135     while ((row = mysql_fetch_row(res)) != NULL) {
136         userlist[i++] = row;
137     }
138     
139     printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
140     row = mysql_fetch_row(mysql_use());
141     if(row[0] == NULL || row[1] == NULL) {
142         printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_name` = 'defaults'");
143         defaults = mysql_fetch_row(mysql_use());
144     }
145     db_enfops = atoi((row[0] ? row[0] : defaults[0]));
146     db_enfvoice = atoi((row[1] ? row[1] : defaults[1]));
147     
148     chanuser_count = 0;
149     for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
150         if(nickmask && match(nickmask, chanuser->user->nick)) continue;
151         chanusers[chanuser_count++] = chanuser;
152     }
153     qsort(chanusers, chanuser_count, sizeof(struct ChanUser *), neonserv_cmd_nicklist_sort);
154     
155     caccess = getChannelAccess(user, chan);
156     synced_user = 0;
157     for(i = 0; i < chanuser_count; i++) {
158         chanuser = chanusers[i];
159         
160         content[0] = chanuser->user->nick;
161         
162         j = 0;
163         if((chanuser->flags & CHANUSERFLAG_INVISIBLE)) statebuf[j++] = '<';
164         if((chanuser->flags & CHANUSERFLAG_OPPED)) statebuf[j++] = '@';
165         if((chanuser->flags & CHANUSERFLAG_HALFOPPED)) statebuf[j++] = '%';
166         if((chanuser->flags & CHANUSERFLAG_VOICED)) statebuf[j++] = '+';
167         statebuf[j++] = '\0';
168         content[1] = statebuf;
169         
170         uaccess = 0;
171         if(chanuser->user->flags & USERFLAG_ISAUTHED) {
172             for(j = 0; j < userlistlen; j++) {
173                 if(!stricmp(chanuser->user->auth, userlist[j][1])) {
174                     uaccess = atoi(userlist[j][0]);
175                     if((((chanuser->flags & CHANUSERFLAG_OPPED) && uaccess < db_enfops) || ((chanuser->flags & CHANUSERFLAG_VOICED) && uaccess < db_enfvoice)) && !isNetworkService(chanuser->user)) {
176                         if(syncusers) {
177                             if((chanuser->flags & CHANUSERFLAG_OPPED) && (db_enfops < caccess || isGodMode(user))) {
178                                 if(db_enfops >= caccess)
179                                     event->flags |= CMDFLAG_OPLOG;
180                                 uaccess = db_enfops;
181                             } else if((chanuser->flags & CHANUSERFLAG_VOICED) && (caccess < db_enfvoice || isGodMode(user))) {
182                                 if(db_enfvoice >= caccess)
183                                     event->flags |= CMDFLAG_OPLOG;
184                                 uaccess = db_enfvoice;
185                             } else {
186                                 //fail...
187                                 sprintf(accessbuf, "\00307%d\003", uaccess);
188                                 break;
189                             }
190                             neonserv_cmd_nicklist_synchronize_user(chan, chanuser->user, uaccess);
191                             sprintf(accessbuf, "\00309%d\003", uaccess);
192                             synced_user = 1;
193                         } else {
194                             synced_user = 1;
195                             sprintf(accessbuf, "\00307%d\003", uaccess);
196                         }
197                     } else if((uaccess >= db_enfops && !(chanuser->flags & CHANUSERFLAG_OPPED)) || (uaccess >= db_enfvoice && !(chanuser->flags & CHANUSERFLAG_OPPED_OR_VOICED)))
198                         sprintf(accessbuf, "\00303%d\003", uaccess);
199                     else
200                         sprintf(accessbuf, "%d", uaccess);
201                     break;
202                 }
203             }
204         }
205         if(!uaccess && (chanuser->flags & CHANUSERFLAG_OPPED_OR_VOICED) && !isNetworkService(chanuser->user)) {
206             if(syncusers) {
207                 if((chanuser->flags & CHANUSERFLAG_OPPED) && (db_enfops < caccess || isGodMode(user))) {
208                     if(db_enfops >= caccess)
209                         event->flags |= CMDFLAG_OPLOG;
210                     uaccess = db_enfops;
211                 } else if((chanuser->flags & CHANUSERFLAG_VOICED) && (db_enfvoice < caccess || isGodMode(user))) {
212                     if(db_enfvoice >= caccess)
213                         event->flags |= CMDFLAG_OPLOG;
214                     uaccess = db_enfvoice;
215                 } else {
216                     uaccess = 0;
217                     sprintf(accessbuf, "\003040\003");
218                 }
219                 if(uaccess && (chanuser->user->flags & USERFLAG_ISAUTHED)) {
220                     neonserv_cmd_nicklist_synchronize_user(chan, chanuser->user, uaccess);
221                     sprintf(accessbuf, "\00309%d\003", uaccess);
222                     synced_user = 1;
223                 } else if(uaccess) {
224                     sprintf(accessbuf, "\003040\003");
225                 }
226             } else {
227                 synced_user = 1;
228                 sprintf(accessbuf, "\003040\003");
229             }
230         } else if(!uaccess)
231             sprintf(accessbuf, "0");
232         content[2] = accessbuf;
233         
234         table_add(table, content);
235     }
236     
237     //send the table
238     char **table_lines = table_end(table);
239     for(i = 0; i < table->entrys; i++) {
240         reply(textclient, user, table_lines[i]);
241     }
242     if(table->length == 1)
243         reply(textclient, user, "NS_TABLE_NONE");
244     reply(textclient, user, "NS_TABLE_COUNT", table->length - 1);
245     table_free(table);
246     if(synced_user) {
247         if(!syncusers)
248             reply(textclient, user, "NS_NICKLIST_SYNC", db_enfops, db_enfvoice);
249         else
250             logEvent(event);
251     }
252 }
253
254 static void neonserv_cmd_nicklist_synchronize_user(struct ChanNode *chan, struct UserNode *user, int caccess) {
255     if(!(user->flags & USERFLAG_ISAUTHED)) return;
256     MYSQL_RES *res;
257     MYSQL_ROW row;
258     int userid;
259     printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
260     res = mysql_use();
261     if ((row = mysql_fetch_row(res)) != NULL) {
262         userid = atoi(row[0]);
263     } else {
264         printf_mysql_query("INSERT INTO `users` (`user_user`) VALUES ('%s')", escape_string(user->auth));
265         userid = (int) mysql_insert_id(get_mysql_conn());
266     }
267     //check if already added
268     printf_mysql_query("SELECT `chanuser_access`, `chanuser_id`, `chanuser_seen` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
269     res = mysql_use();
270     if ((row = mysql_fetch_row(res)) != NULL) {
271         //clvl
272         if(atoi(row[0]) >= caccess) return;
273         printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = '%d' WHERE `chanuser_id` = '%s'", caccess, row[1]);
274     } else 
275         printf_mysql_query("INSERT INTO `chanusers` (`chanuser_cid`, `chanuser_uid`, `chanuser_access`) VALUES ('%d', '%d', '%d')", chan->channel_id, userid, caccess);
276 }