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