]>
Commit | Line | Data |
---|---|---|
c86edd1d Q |
1 | #include "chansearch.h" |
2 | #include "../parser/parser.h" | |
3 | #include "../nick/nick.h" | |
4 | #include "../channel/channel.h" | |
5 | #include "../control/control.h" | |
6 | #include "../lib/flags.h" | |
7 | #include "../lib/irc_string.h" | |
87698d77 | 8 | #include "../lib/version.h" |
c86edd1d Q |
9 | |
10 | #include <stdio.h> | |
11 | ||
87698d77 CP |
12 | MODULE_VERSION("$Id") |
13 | ||
c86edd1d Q |
14 | #define MAXTERMS 10 |
15 | #define MAXMATCHES 500 | |
16 | ||
17 | CommandTree *searchfilters; | |
18 | CommandTree *outputfilters; | |
19 | ||
20 | typedef struct modeflags { | |
21 | flag_t setflags; | |
22 | flag_t clearflags; | |
23 | } modeflags; | |
24 | ||
25 | void cs_describe(nick *sender, chanindex *cip); | |
26 | void cs_desctopic(nick *sender, chanindex *cip); | |
27 | void cs_descservices(nick *sender, chanindex *cip); | |
28 | int cs_exists(void *chan, int cargc, char **cargv); | |
29 | int cs_services(void *chan, int cargc, char **cargv); | |
30 | int cs_nick(void *chan, int cargc, char **cargv); | |
31 | int cs_size(void *chan, int cargc, char **cargv); | |
32 | int cs_namelen(void *chan, int cargc, char **cargv); | |
33 | int cs_modes(void *chan, int cargc, char **cargv); | |
34 | int cs_name(void *chan, int cargc, char **cargv); | |
35 | int cs_topic(void *source, int cargc, char **cargv); | |
36 | int cs_oppct(void *source, int cargc, char **cargv); | |
37 | int cs_hostpct(void *source, int cargc, char **cargv); | |
38 | int cs_authedpct(void *source, int cargc, char **cargv); | |
39 | ||
40 | int dochansearch(void *source, int cargc, char **cargv); | |
41 | ||
42 | void _init() { | |
43 | searchfilters=newcommandtree(); | |
44 | outputfilters=newcommandtree(); | |
45 | ||
46 | regchansearchfunc("name", 1, cs_name); | |
47 | regchansearchfunc("exists", 0, cs_exists); | |
48 | regchansearchfunc("services", 1, cs_services); | |
49 | regchansearchfunc("nick", 1, cs_nick); | |
50 | regchansearchfunc("size", 1, cs_size); | |
51 | regchansearchfunc("namelen", 1, cs_namelen); | |
52 | regchansearchfunc("modes", 1, cs_modes); | |
53 | regchansearchfunc("topic", 1, cs_topic); | |
54 | regchansearchfunc("oppercent", 1, cs_oppct); | |
55 | regchansearchfunc("uniquehostpct", 1, cs_hostpct); | |
56 | regchansearchfunc("authedpct", 1, cs_authedpct); | |
57 | ||
58 | regchansearchdisp("default", cs_describe); | |
59 | regchansearchdisp("topic", cs_desctopic); | |
60 | regchansearchdisp("services", cs_descservices); | |
61 | ||
3e3692bf | 62 | registercontrolhelpcmd("chansearch",NO_OPER,19,&dochansearch,"Usage: chansearch <criteria>\nSearches for channels with specified criteria.\nSend chanstats with no arguments for more information."); |
c86edd1d Q |
63 | } |
64 | ||
65 | void _fini() { | |
66 | deregistercontrolcmd("chansearch",&dochansearch); | |
67 | destroycommandtree(searchfilters); | |
68 | destroycommandtree(outputfilters); | |
69 | } | |
70 | ||
71 | void regchansearchfunc(const char *name, int args, CommandHandler handler) { | |
72 | addcommandtotree(searchfilters, name, 0, args, handler); | |
73 | } | |
74 | ||
75 | void unregchansearchfunc(const char *name, CommandHandler handler) { | |
76 | deletecommandfromtree(searchfilters, name, handler); | |
77 | } | |
78 | ||
79 | void regchansearchdisp(const char *name, DisplayFunc handler) { | |
80 | addcommandtotree(outputfilters, name, 0, 0, (CommandHandler)handler); | |
81 | } | |
82 | ||
83 | void unregchansearchdisp(const char *name, DisplayFunc handler) { | |
84 | deletecommandfromtree(outputfilters, name, (CommandHandler)handler); | |
85 | } | |
86 | ||
87 | int dochansearch(void *source, int cargc, char **cargv) { | |
88 | nick *sender=(nick *)source; | |
89 | filter terms[MAXTERMS]; | |
90 | int i,j; | |
91 | int numterms=0; | |
92 | Command *mc; | |
93 | chanindex *cip; | |
94 | int matched=0; | |
95 | int offset; | |
96 | int res; | |
97 | char *ch; | |
98 | int limit=MAXMATCHES; | |
99 | DisplayFunc df=cs_describe; | |
100 | ||
101 | i=0; | |
102 | if (cargc>0 && cargv[0][0]=='-') { | |
103 | /* We have options, parse them */ | |
104 | i++; | |
105 | for (ch=cargv[0];*ch;ch++) { | |
106 | switch(*ch) { | |
107 | case 'l': limit=strtol(cargv[i++],NULL,10); | |
108 | break; | |
109 | ||
110 | case 'd': if ((mc=findcommandintree(outputfilters,cargv[i],1))==NULL) { | |
111 | controlreply(sender, "Invalid display format %s",cargv[i]); | |
112 | return CMD_ERROR; | |
113 | } else { | |
114 | df=(DisplayFunc)(mc->handler); | |
115 | } | |
116 | i++; | |
117 | break; | |
118 | } | |
119 | if (i>=cargc) { | |
120 | break; | |
121 | } | |
122 | } | |
123 | } | |
124 | ||
125 | if (i>=cargc) { | |
126 | Command *cmdlist[100]; | |
4ad1cf7a | 127 | int i,n,bufpos,j; |
c86edd1d Q |
128 | char buf[200]; |
129 | ||
130 | controlreply(sender,"Usage: chansearch [options] (search terms)"); | |
131 | controlreply(sender," Note that all options must be bundled together (-ld 5 default not -l 5 -d default)"); | |
132 | controlreply(sender," Current valid options:"); | |
133 | controlreply(sender," -l <number> Set maximum number of results to return"); | |
134 | controlreply(sender," -d <format> Set output display format"); | |
135 | ||
136 | /* Get list of display formats */ | |
137 | ||
138 | bufpos=0; | |
139 | ||
140 | n=getcommandlist(outputfilters,cmdlist,100); | |
141 | for (i=0;i<n;i++) { | |
142 | for (j=0;j<cmdlist[i]->command->length;j++) { | |
143 | buf[bufpos++]=ToLower(cmdlist[i]->command->content[j]); | |
144 | } | |
145 | if (i<(n-1)) { | |
146 | bufpos+=sprintf(&buf[bufpos],", "); | |
147 | } | |
148 | if (bufpos>180) { | |
149 | bufpos+=sprintf(&buf[bufpos]," ..."); | |
150 | break; | |
151 | } | |
152 | } | |
153 | ||
154 | buf[bufpos]='\0'; | |
155 | ||
156 | controlreply(sender," Valid display formats: %s",buf); | |
157 | ||
158 | /* Get list of search terms */ | |
159 | ||
160 | bufpos=0; | |
161 | ||
162 | n=getcommandlist(searchfilters,cmdlist,100); | |
163 | for (i=0;i<n;i++) { | |
164 | for (j=0;j<cmdlist[i]->command->length;j++) { | |
165 | buf[bufpos++]=ToLower(cmdlist[i]->command->content[j]); | |
166 | } | |
167 | if (i<(n-1)) { | |
168 | bufpos+=sprintf(&buf[bufpos],", "); | |
169 | } | |
170 | if (bufpos>180) { | |
171 | bufpos+=sprintf(&buf[bufpos]," ..."); | |
172 | break; | |
173 | } | |
174 | } | |
175 | buf[bufpos]='\0'; | |
176 | ||
177 | controlreply(sender," Valid search terms: %s",buf); | |
178 | controlreply(sender," Terms can be inverted with !"); | |
179 | return CMD_OK; | |
180 | } | |
181 | ||
182 | for (;i<cargc;) { | |
183 | if (cargv[i][0]=='!') { | |
184 | offset=1; | |
185 | terms[numterms].invert=1; | |
186 | } else { | |
187 | offset=0; | |
188 | terms[numterms].invert=0; | |
189 | } | |
190 | terms[numterms].mallocarg=0; | |
191 | ||
192 | /* Do some sanity checking. Note that the "goto"s allows memory | |
193 | * to be freed if successful filters have already allocated some */ | |
194 | if ((mc=findcommandintree(searchfilters, &cargv[i][offset], 1))==NULL) { | |
195 | controlreply(sender,"Unrecognised search term: %s",cargv[i]); | |
196 | goto abort; | |
197 | } | |
198 | if ((cargc-i-1) < mc->maxparams) { | |
199 | controlreply(sender,"Not enough arguments supplied for %s",cargv[i]); | |
200 | goto abort; | |
201 | } | |
202 | ||
203 | /* Call the setup function. This should fill in the function and argument in the structure */ | |
204 | if ((mc->handler)((void *)&terms[numterms],mc->maxparams,cargv+i+1)) { | |
205 | controlreply(sender,"Error setting up filter: %s",cargv[i]); | |
206 | goto abort; | |
207 | } | |
208 | i+=(mc->maxparams+1); | |
209 | numterms++; | |
210 | ||
211 | if (numterms==MAXTERMS) | |
212 | break; | |
213 | } | |
214 | ||
215 | controlreply(sender,"The following channels match your criteria:"); | |
216 | ||
217 | for(i=0;i<CHANNELHASHSIZE;i++) { | |
218 | for(cip=chantable[i];cip;cip=cip->next) { | |
219 | for(j=0;j<numterms;j++) { | |
220 | res=(terms[j].sf)(cip,terms[j].arg); | |
221 | if (res==0 && terms[j].invert) | |
222 | break; | |
223 | if (res==1 && !terms[j].invert) | |
224 | break; | |
225 | } | |
226 | if (j==numterms) { | |
227 | if (matched == limit) { | |
228 | controlreply(sender,"--- More than %d matches found, truncating list.",limit); | |
229 | } | |
230 | if (matched < limit) { | |
231 | (df)(sender, cip); | |
232 | } | |
233 | matched++; | |
234 | } | |
235 | } | |
236 | } | |
237 | ||
238 | if (matched==0) { | |
239 | controlreply(sender,"--- No matches found."); | |
240 | } else { | |
241 | controlreply(sender,"--- End of list: %d match(es).",matched); | |
242 | } | |
243 | ||
244 | /* GOTO target: here we just free the args if any were malloc()d */ | |
245 | abort: | |
246 | for (i=0;i<numterms;i++) { | |
247 | if (terms[i].mallocarg) { | |
248 | free(terms[i].arg); | |
249 | } | |
250 | } | |
251 | ||
252 | return CMD_OK; | |
253 | } | |
254 | ||
255 | ||
256 | /* Name search execute function: call match2strings */ | |
257 | int cs_nameexe(chanindex *cip, void *arg) { | |
258 | char *pattern=(char *)arg; | |
259 | ||
260 | return !match2strings(pattern, cip->name->content); | |
261 | } | |
262 | ||
263 | /* Name search setup function: fill in the struct */ | |
264 | ||
265 | int cs_name(void *source, int cargc, char **cargv) { | |
266 | filter *thefilter=(filter *)source; | |
267 | ||
268 | thefilter->sf=cs_nameexe; | |
269 | thefilter->arg=(char *)cargv[0]; | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | int cs_topicexe(chanindex *cip, void *arg) { | |
275 | char *pattern=(char *)arg; | |
276 | ||
277 | return ((cip->channel==NULL) || | |
278 | (cip->channel->topic==NULL) || | |
279 | (!match2strings(pattern, cip->channel->topic->content))); | |
280 | } | |
281 | ||
282 | int cs_topic(void *source, int cargc, char **cargv) { | |
283 | filter *thefilter=(filter *)source; | |
284 | ||
285 | thefilter->sf=cs_topicexe; | |
286 | thefilter->arg=(void *)cargv[0]; | |
287 | ||
288 | return 0; | |
289 | } | |
290 | ||
291 | /* services - matches if the specified number of services are in the channel */ | |
292 | ||
293 | int cs_servicesexe(chanindex *cip, void *arg) { | |
294 | nick *np; | |
295 | int i; | |
296 | int count=(int)arg; | |
297 | ||
298 | if (cip->channel==NULL) | |
299 | return 1; | |
300 | ||
301 | for(i=0;i<cip->channel->users->hashsize;i++) { | |
302 | if (cip->channel->users->content[i]==nouser) | |
303 | continue; | |
304 | ||
305 | if (!(np=getnickbynumeric(cip->channel->users->content[i]))) | |
306 | continue; | |
307 | ||
308 | if (IsService(np) && !--count) | |
309 | return 0; | |
310 | } | |
311 | ||
312 | return 1; | |
313 | } | |
314 | ||
315 | int cs_services(void *source, int cargc, char **cargv) { | |
316 | filter *thefilter=(filter *)source; | |
317 | ||
318 | thefilter->sf=cs_servicesexe; | |
319 | thefilter->arg=(void *)strtoul(cargv[0],NULL,10); | |
320 | ||
321 | return 0; | |
322 | } | |
323 | ||
324 | int cs_existsexe(chanindex *cip, void *arg) { | |
325 | return (cip->channel==NULL); | |
326 | } | |
327 | ||
328 | int cs_exists(void *source, int cargc, char **cargv) { | |
329 | filter *thefilter=(filter *)source; | |
330 | ||
331 | thefilter->sf=cs_existsexe; | |
332 | thefilter->arg=NULL; | |
333 | ||
334 | return 0; | |
335 | } | |
336 | ||
337 | int cs_nickexe(chanindex *cip, void *arg) { | |
338 | nick *np=(nick *)arg; | |
339 | ||
340 | return ((cip->channel==NULL) || | |
341 | (getnumerichandlefromchanhash(cip->channel->users, np->numeric)==NULL)); | |
342 | } | |
343 | ||
344 | int cs_nick(void *source, int cargc, char **cargv) { | |
345 | filter *thefilter=(filter *)source; | |
346 | nick *np; | |
347 | ||
348 | if ((np=getnickbynick(cargv[0]))==NULL) { | |
349 | return 1; | |
350 | } | |
351 | ||
352 | thefilter->sf=cs_nickexe; | |
353 | thefilter->arg=(void *)np; | |
354 | ||
355 | return 0; | |
356 | } | |
357 | ||
358 | int cs_sizeexe(chanindex *cip, void *arg) { | |
359 | int lim=(int)arg; | |
360 | ||
361 | return ((cip->channel==NULL) || | |
362 | (cip->channel->users->totalusers < lim)); | |
363 | } | |
364 | ||
365 | int cs_size(void *source, int cargc, char **cargv) { | |
366 | filter *thefilter=(filter *)source; | |
367 | ||
368 | thefilter->sf=cs_sizeexe; | |
369 | thefilter->arg=(void *)strtol(cargv[0],NULL,10); | |
370 | ||
371 | return 0; | |
372 | } | |
373 | ||
374 | int cs_namelenexe(chanindex *cip, void *arg) { | |
375 | int lim=(int)arg; | |
376 | ||
377 | return (cip->name->length < lim); | |
378 | } | |
379 | ||
380 | int cs_namelen(void *source, int cargc, char **cargv) { | |
381 | filter *thefilter=(filter *)source; | |
382 | ||
383 | thefilter->sf=cs_namelenexe; | |
384 | thefilter->arg=(void *)strtol(cargv[0],NULL,10); | |
385 | ||
386 | return 0; | |
387 | } | |
388 | ||
389 | int cs_oppctexe(chanindex *cip, void *arg) { | |
390 | int percent=(int)arg; | |
391 | int nonop; | |
392 | int i; | |
393 | ||
394 | if (cip->channel==NULL) { | |
395 | return 1; | |
396 | } | |
397 | ||
398 | nonop=(cip->channel->users->totalusers)-((cip->channel->users->totalusers*percent)/100); | |
399 | ||
400 | for (i=0;i<cip->channel->users->hashsize;i++) { | |
401 | if (cip->channel->users->content[i]!=nouser) { | |
402 | if (!(cip->channel->users->content[i] & CUMODE_OP)) { | |
403 | if (!nonop--) { | |
404 | return 1; | |
405 | } | |
406 | } | |
407 | } | |
408 | } | |
409 | ||
410 | return 0; | |
411 | } | |
412 | ||
413 | int cs_oppct(void *source, int cargc, char **cargv) { | |
414 | filter *thefilter=(filter *)source; | |
415 | ||
416 | thefilter->sf=cs_oppctexe; | |
417 | thefilter->arg=(void *)strtol(cargv[0],NULL,10); | |
418 | ||
419 | return 0; | |
420 | } | |
421 | ||
422 | int cs_hostpctexe(chanindex *cip, void *arg) { | |
423 | int percent=(int)arg; | |
424 | int hostreq; | |
425 | int i; | |
426 | unsigned int marker; | |
427 | nick *np; | |
428 | ||
429 | if (cip->channel==NULL) { | |
430 | return 1; | |
431 | } | |
432 | ||
433 | marker=nexthostmarker(); | |
434 | ||
435 | hostreq=(cip->channel->users->totalusers * percent) / 100; | |
436 | ||
437 | for (i=0;i<cip->channel->users->hashsize;i++) { | |
438 | if (cip->channel->users->content[i]==nouser) | |
439 | continue; | |
440 | ||
441 | if (!(np=getnickbynumeric(cip->channel->users->content[i]))) | |
442 | continue; | |
443 | ||
444 | if (np->host->marker!=marker) { | |
445 | /* new unique host */ | |
446 | if (--hostreq <= 0) { | |
447 | return 0; | |
448 | } | |
449 | np->host->marker=marker; | |
450 | } | |
451 | } | |
452 | ||
453 | return 1; | |
454 | } | |
455 | ||
456 | int cs_hostpct(void *source, int cargc, char **cargv) { | |
457 | filter *thefilter=source; | |
458 | ||
459 | thefilter->sf=cs_hostpctexe; | |
460 | thefilter->arg=(void *)strtol(cargv[0],NULL,10); | |
461 | ||
462 | return 0; | |
463 | } | |
464 | ||
465 | int cs_authedpctexe(chanindex *cip, void *arg) { | |
466 | int i; | |
467 | int j=0; | |
468 | nick *np; | |
469 | int pct=(int)arg; | |
470 | ||
471 | if (!cip->channel) | |
472 | return 1; | |
473 | ||
474 | for (i=0;i<cip->channel->users->hashsize;i++) { | |
475 | if (cip->channel->users->content[i]==nouser) | |
476 | continue; | |
477 | ||
478 | if ((np=getnickbynumeric(cip->channel->users->content[i])) && IsAccount(np)) | |
479 | j++; | |
480 | } | |
481 | ||
482 | if (((j * 100) / cip->channel->users->totalusers) >= pct) | |
483 | return 0; | |
484 | else | |
485 | return 1; | |
486 | } | |
487 | ||
488 | int cs_authedpct(void *source, int cargc, char **cargv) { | |
489 | filter *thefilter=source; | |
490 | ||
491 | thefilter->sf=cs_authedpctexe; | |
492 | thefilter->arg=(void *)strtol(cargv[0],NULL,10); | |
493 | ||
494 | return 0; | |
495 | } | |
496 | ||
497 | int cs_modesexe(chanindex *cip, void *arg) { | |
498 | struct modeflags *mf=(struct modeflags *)arg; | |
499 | ||
500 | return ((cip->channel == NULL) || | |
501 | ((cip->channel->flags & mf->setflags) != mf->setflags) || | |
502 | (cip->channel->flags & mf->clearflags)); | |
503 | } | |
504 | ||
505 | int cs_modes(void *source, int cargc, char **cargv) { | |
506 | filter *thefilter=(filter *)source; | |
507 | struct modeflags *mf; | |
508 | flag_t flags=0; | |
509 | ||
510 | mf=(void *)malloc(sizeof(struct modeflags)); | |
511 | ||
512 | setflags(&flags, CHANMODE_ALL, cargv[0], cmodeflags, REJECT_NONE); | |
513 | ||
514 | mf->setflags=flags; | |
515 | ||
516 | flags=CHANMODE_ALL; | |
517 | setflags(&flags, CHANMODE_ALL, cargv[0], cmodeflags, REJECT_NONE); | |
518 | ||
519 | mf->clearflags=~flags; | |
520 | ||
521 | thefilter->sf=cs_modesexe; | |
522 | thefilter->arg=(void *)mf; | |
523 | thefilter->mallocarg=1; | |
524 | ||
525 | return 0; | |
526 | } | |
527 | ||
528 | void cs_describe(nick *sender, chanindex *cip) { | |
529 | int i; | |
530 | int op,voice,peon; | |
531 | int oper,service,hosts; | |
532 | nick *np; | |
533 | chanuserhash *cuhp; | |
534 | unsigned int marker; | |
535 | ||
536 | op=voice=peon=oper=service=hosts=0; | |
537 | marker=nexthostmarker(); | |
538 | ||
539 | if (cip->channel==NULL) { | |
540 | controlreply(sender,"[ Channel currently empty ] %s",cip->name->content); | |
541 | } else { | |
542 | cuhp=cip->channel->users; | |
543 | for (i=0;i<cuhp->hashsize;i++) { | |
544 | if (cuhp->content[i]!=nouser) { | |
545 | if (cuhp->content[i]&CUMODE_OP) { | |
546 | op++; | |
547 | } else if (cuhp->content[i]&CUMODE_VOICE) { | |
548 | voice++; | |
549 | } else { | |
550 | peon++; | |
551 | } | |
552 | if ((np=getnickbynumeric(cuhp->content[i]&CU_NUMERICMASK))!=NULL) { | |
553 | if (IsOper(np)) { | |
554 | oper++; | |
555 | } | |
556 | if (IsService(np)) { | |
557 | service++; | |
558 | } | |
559 | if (np->host->marker!=marker) { | |
560 | np->host->marker=marker; | |
561 | hosts++; | |
562 | } | |
563 | } | |
564 | } | |
565 | } | |
566 | controlreply(sender,"[ %4dU %4d@ %4d+ %4d %4d* %4dk %4dH ] %s (%s)",cuhp->totalusers,op,voice,peon,oper,service,hosts, | |
567 | cip->name->content, printflags(cip->channel->flags, cmodeflags)); | |
568 | } | |
569 | } | |
570 | ||
571 | void cs_desctopic(nick *sender, chanindex *cip) { | |
572 | if (cip->channel==NULL) { | |
573 | controlreply(sender,"[ empty ] %-30s",cip->name->content); | |
574 | } else { | |
575 | controlreply(sender,"[%4u users] %s (%s)",cip->channel->users->totalusers,cip->name->content,cip->channel->topic?cip->channel->topic->content:"no topic"); | |
576 | } | |
577 | } | |
578 | ||
579 | void cs_descservices(nick *sender, chanindex *cip) { | |
580 | int i; | |
581 | chanuserhash *cuhp; | |
582 | char servlist[300]; | |
583 | int slpos=0,slfull=0; | |
584 | nick *np; | |
585 | int servs=0; | |
586 | ||
587 | if (cip->channel==NULL) { | |
588 | controlreply(sender,"%-30s empty",cip->name->content); | |
589 | } else { | |
590 | cuhp=cip->channel->users; | |
591 | for (i=0;i<cuhp->hashsize;i++) { | |
592 | if (cuhp->content[i]!=nouser) { | |
593 | if ((np=getnickbynumeric(cuhp->content[i]&CU_NUMERICMASK))) { | |
594 | if (IsService(np)) { | |
595 | servs++; | |
596 | if (!slfull) { | |
597 | if (slpos) { | |
598 | slpos+=sprintf(&servlist[slpos],", "); | |
599 | } | |
600 | slpos+=sprintf(&servlist[slpos],"%s",np->nick); | |
601 | if (slpos>280) { | |
602 | sprintf(&servlist[slpos],", ..."); | |
603 | slfull=1; | |
604 | } | |
605 | } | |
606 | } | |
607 | } | |
608 | } | |
609 | } | |
610 | ||
611 | controlreply(sender,"%-30s %5d user%c %2d service%c %s%s%s",cip->name->content,cuhp->totalusers, | |
612 | cuhp->totalusers>1?'s':' ',servs,(servs==1)?' ':'s',servs?"(":"",slpos?servlist:"",servs?")":""); | |
613 | } | |
614 | } | |
615 |