%%% lists.sl Lists utilities for SLirc. %%% Copyright (C) 1998,1999 Stanley J. Brooks irc_log("[Lists.sl] Version 0.10.6 BETA loading."); irc_update_display(); %{{{ variable resolver,resolv_busy = 1; resolver = open_resolver(); if (resolver != NULL) resolv_busy = 0; variable resolv_list = make_list(); define send_resolv_req(host) { variable ahost,l,rq; ahost = "xxxxyyyy" + host; l = strlen(ahost); rq = Char_Type[l]; init_char_array(rq,ahost); resolv_busy = 1; () = vf_write(resolver,rq); } define Hresolver(vf,i) { variable q,p,n,r,host,rhost,ip; q = resolv_list.f; p = q.v; variable ar = Char_Type[1024]; r = vf_read(vf,ar); if (r >= 8) { ip = array_get_u32(ar,0); rhost = array_get_string(ar,8); (,host) = v_split(p.v.uhost,"@"); if (rhost == host) p.v.hip = ip; else irc_Logf("Hresolver: %s,%s is [%s]",rhost,host,inet_ntoa(ip)); } forever { s_del(q); q = resolv_list.f; if (q == NULL) break; p = q.v; if (p.v.hip != NULL) continue; % delete unnecessary requests (,host) = v_split(p.v.uhost,"@"); send_resolv_req(host); return; } () = set_action(vf,0,NULL); resolv_busy = 0; } % we should probably keep a list of know host->ip translations % to avoid redundant lookups define do_resolve(p) { variable host,ip,q; if (resolver == NULL) return; % this is easy way to turn it off. (,host) = v_split(p.v.uhost,"@"); ip = inet_addr(host); if (ip != -1) { p.v.hip = ip; return; } () = s_putlast(resolv_list,p); if (resolv_busy) return; send_resolv_req(host); () = set_action(resolver,0,"Hresolver"); } %}}} % for /ftp get/put: variable ftp_list = make_list(); % for /dcc get/send: variable dcc_list = make_list(); variable dcc_pending = make_list(); % for /dcc chat: variable chat_list = make_list(); variable chat_pending = make_list(); % a string consisting of ' ' delimited names of channels which we are on: variable on_chans = NullString; % a string of ' ' delimited names of pending channels which we have /join variable join_chans = NullString; define addto_strl(s,e,d) { !if (is_list_element(s,e,d[0])) { if (int(s)) e = e + d; s = e + s; } return s; } % nick_chan's fields are: % 0) (s) nick % 1) (s) user@host % 2) (s) numeric host-ip, if known % 3) (s) space-delimited list of channels the nick is known to be on % nick_chan list maintained in reverse order of JOIN % (most recent join is 1st in list) typedef struct{nick,uhost,hip,rbl,chans} NickChan_Type; variable nick_chan = make_list(); % nick_act's fields are: % 0) (s) nick % 1) (s) action ( a short encoding of what happened) % 2) (i) when (unix_time) typedef struct{nick,act,tim} NickAct_Type; variable nick_act = make_list(); variable naj_ptr = NULL; % this points to 1st JOIN/JNICK in nick_act (or 0 if no) % commandlist: list of commands which used for command-completions variable commandlist = make_list(); define CC_cmp_pi(v,pfx) { return is_prefix(v,pfx); } define cmnd_completions(prefix) { variable p, i = 0; variable name = prefix; p = s_find_n1(commandlist.f,&CC_cmp_pi,prefix); if (p != NULL) { i++; name = p.v; } return(name, i); } define add_cmnd(cmnd) { () = s_putlast(commandlist,cmnd); } % this nick_act stuff will replace the irc_user_blah LRU stuff in 'C' % for the moment, it's at least used for nick-completion define irc_nick_action(nick,what) { variable p0,p,pn; !if (int(nick)) return; % ignore NullString variable na = @NickAct_Type; na.nick = nick; na.act = what; na.tim = unix_time(); if ( int(what) != 'J' ) { % looksee if an identical 'what' already exists for this nick... % if so, we just update the time & move it to head of the list p0 = nick_act.f; p = p0; do { p = s_find_n1(p,&NC0_cmp,nick); if (p == NULL) break; pn = p.n; % yes! if (what == p.v.act) { p.v.tim = na.tim; % update the time p = s_move(p0,-1,p); % move it to head of list return; } p = pn; } while (p != p0); p = s_put1st(nick_act,na); if (naj_ptr != NULL) if (s_ord(naj_ptr) > 30) s_del(naj_ptr.p); }else{ % 'what' is JOIN or JNICK if (naj_ptr != NULL) naj_ptr = s_put(naj_ptr,-1,na); else naj_ptr = s_putlast(nick_act,na); } if (nick_act.ct > 50) s_del(nick_act.f.p); } define NC1_cmp_p(v,pfx) { return is_prefix(v.act,pfx); } define nick_act_del(nick) { variable p0,p; p0 = nick_act.f; if (p0 == NULL) return; NULL; % top of stack mark p = p0; do { p = s_find_n1(p,&NC0_cmp,nick); if (p == NULL) break; p; % push it for below p = p.n; } while (p != p0); while (p = (), p != NULL) s_del(p); % use compare: case-sensitive prefix naj_ptr = s_find_n1(nick_act.f,&NC1_cmp_p,"J"); } % this command is only for debugging: % it lists the data in nick_act list add_cmnd("act"); private variable cmds_act_ct; private define cmds_act(p,i) { sprintf("%s %s [%d].",p.v.nick,p.v.act,unix_time() - p.v.tim); cmds_act_ct++; } define command_ACT(s) { cmds_act_ct = 0; "\n"; s_dofunc(nick_act,&cmds_act); if(cmds_act_ct) { variable items = create_delimited_string(cmds_act_ct); irc_create_popup("NICK_ACTION_LIST", items, ""); return; } s = (); } % [sjb] -- moved this into lists.sl in anticipation of % making it more intelligent, hence more tangled in with the lists % (1) it SHOULD track recent nick-changes % (2) it MIGHT give a warning when the best completion is someone % who recently split or left define nick_completions(prefix) { variable i, p; variable name = prefix; variable count = 0; if (int(prefix) == '=') { % dcc chat target p = s_find_n1(chat_list.f,&NC0_cmp_p,chop_1st(prefix)); if (p != NULL) { ++count; name = "=" + p.v.nick; } }else{ p = s_find_n1(nick_act.f,&NC0_cmp_p,prefix); if (p == NULL) p = s_find_n1(nick_chan.f,&NC0_cmp_p,prefix); if (p != NULL) { ++count; name = p.v.nick; } } return(name, count); } % list of ignores, (string pattern, int until-time) typedef struct{pat,tim} Ignores_Type; variable ignores = make_list(); add_cmnd("who"); variable Wwho = "*"; define command_WHO(s) { Wwho = irc_target; s = strcompress(s," "); !if(strlen(s)) s = irc_target; sprintf("WHO %s\r\n", s); irc_send_string(); } variable igh_nicks = NullString; define igh_who_tail(nick,uhost) { variable s,usr,host; igh_nicks = cut_list_element(igh_nicks,nick,' '); (usr,host) = v_split(uhost,"@"); s = sprintf(".*!.*@%s",host); variable ii = @Ignores_Type; ii.pat = s; ii.tim = unix_time() + 600; () = s_put1st(ignores, ii); irc_Inff("Ignoring %s (%s) for %d seconds",Rpms[7],s,600); } % /igh nick[,nick]* % ignores the HOST associated with nick(s) for default time add_cmnd("igh"); define command_IGH(params) { variable b,p,nick,user,host; b = strchop(params,',',0); foreach (b) { nick = (); !if (strlen(nick)) continue; p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p == NULL) return; (user,host) = v_split(p.v.uhost,"@"); if (strlen(host)) { variable ii = @Ignores_Type; ii.pat = sprintf(".*!.*@%s",host); ii.tim = unix_time() + 600; () = s_put1st(ignores,ii); irc_Inff("Ignoring %s (%s) for %d seconds",nick,ii.pat,600); }else{ % generate a /WHO request which will complete the /igh % once it gets the necessary host info igh_nicks = addto_strl(igh_nicks,strlow(nick)," "); % irc_Logf("/WHO %s",nick); command_WHO(nick); } } } #ifdef ENEMIES variable enemies = Assoc_Type[Integer_Type,0]; define add_enemy(uhost) { %(user,host) = v_split(uhost,"@"); !if (assoc_key_exists(enemies,uhost)) { irc_Inff("New Enemy (%s)",uhost); } enemies[uhost] = enemies[uhost] + 1; } % PUenemies *does* need to be public :-/ define PUenemies(key,n,s) { irc_destroy_popup(); return; } add_cmnd("enemies"); define command_ENEMIES(params) { variable p,s; variable keys = assoc_get_keys(enemies); variable k,n,l; l = length(keys); if (l) { "\n"; for (n = 0; n < l; n++) { k = keys[n]; sprintf("%4d %s", enemies[k], k); % on stack } variable items = create_delimited_string(l); irc_create_popup("ENEMIES", items, "PUenemies"); } } #endif % this PUignore *does* need to be public :-/ define PUignore(key,n,s) { variable p; if (key == 0x0d) { p = s_nth(ignores, n); if (p != NULL) { irc_Inff("Unignoring %s",p.v.pat); s_del(p); } } irc_destroy_popup(); return; } % /ignore list current ignore regex's on popup menu, % allow unignoring selected one. % /ignore regex add the slang-regex to the ignore list % /ignore +regex UNIGNORE regex added to beginning of list % note: SLang regex's don't seem to support the usual '|' operator ?! add_cmnd("ignore"); define command_IGNORE(params) { variable p,s; if (int(params)) { variable nick,uhost; (nick,uhost) = v_split(params,"!"); nick = strlow(nick); !if (int(uhost)) uhost = ".*"; variable ii = @Ignores_Type; ii.pat = sprintf("%s!%s",nick,uhost); ii.tim = unix_time() + 600; () = s_put1st(ignores,ii); return; } variable p0 = ignores.f; if (p0 != NULL) { variable pn,now,pat,ign_time,n = 0; p = p0; now = unix_time(); "\n"; do { pn = p.n; if (p.v.tim - now > 0) { n++; sprintf("%4d %s", p.v.tim - now, p.v.pat); % on stack } else { s_del(p); if (p == p0) { % we deleted the 1st -- be careful p0 = ignores.f; if (p0 == NULL) break; p = p0; continue; } } p = pn; } while (p != p0); if (n) { variable items = create_delimited_string(n); irc_create_popup("UNIGNORE", items, "PUignore"); return; } n = (); } } % rx_ignored returns non-zero if sender (nick!user@host) should be ignored private define rx_ignored(sender) { variable p0 = ignores.f; if (p0 == NULL) return 0; variable who,r,p,now,pat; variable nick,uhost; (nick,uhost) = v_split(sender,"!"); nick = strlow(nick); who = sprintf("%s!%s",nick,uhost); now = unix_time(); p = p0; do { pat = p.v.pat; if (p.v.tim - now > 0) { r = 1; if (int(pat) == '+') { pat = chop_1st(pat); r = 0; } pat = sprintf("^%s$",pat); % irc_Inff("Try pattern (%s) on sender (%s)",pat,who); if (string_match(who,pat,1)) return r; } p = p.n; } while (p != p0); % irc_Inff("sender (%s) not ignored",who); return 0; } % variable nca_lookup_time = unix_time; define nick_chan_add_uhost(p,uhost) { !if (int(uhost)) return; if (uhost != p.v.uhost) { if (int(p.v.uhost)) irc_Logf("Huh? multiple uhosts: %s (%s,%s)",p.v.nick,uhost,p.v.uhost); p.v.uhost = uhost; } if (p.v.hip == NULL) do_resolve(p); } % chan should already be strlow'd here: define nick_chan_add_chan(p,chan) { if (int(chan)) { variable chans = p.v.chans; !if (is_list_element(chans,chan,' ')) { if (int(chans)) chan = strcat(chan," "); chans = strcat(chan,chans); p.v.chans = chans; } } } define nick_chan_add_chans(nick,chans) { variable p,chan,b; % irc_Logf("add_chans (%s) (%s)",nick,chans); p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p != NULL) { b = strchopr(chans,' ',0); foreach (b) { chan = (); !if (int(chan)) continue; if (strspn(chan,"@+")) chan = chop_1st(chan); chan = strlow(chan); if (is_list_element(on_chans,chan,' ')) nick_chan_add_chan(p,chan); } } } define nick_chan_del_chan(p,chan) { variable chans,chnd,ret; chans = p.v.chans; chnd = cut_list_element(chans,chan,' '); ret = strcmp(chans,chnd); if (ret) if (int(chnd)) p.v.chans = chnd; else { nick_act_del(p.v.nick); s_del(p); } return ret; } % uhost will be nullstring if info is from RPL_NAMREPLY define nick_chan_add(nick,uhost,ichan) { variable m,p,chans; variable chan = strlow(ichan); variable cpfx = NullString; if (strspn(nick,"@+")) { cpfx = substr(nick,1,1); nick = chop_1st(nick); } p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p == NULL) { variable nc = @NickChan_Type; nc.nick = nick; nc.uhost = NullString; nc.chans = NullString; if (int(uhost)) p = s_put1st(nick_chan,nc); else p = s_putlast(nick_chan,nc); if (p == NULL) { irc_logu("Crap! p=NULL still"); return; } } nick_chan_add_uhost(p,uhost); if (int(chan)) if (is_list_element(on_chans,chan,' ')) nick_chan_add_chan(p,chan); } define nick_chan_del(nick,ichan) { variable p0,p; variable chan = strlow(ichan); if (strspn(nick,"@+")) nick = chop_1st(nick); p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p == NULL) { irc_Logf("Huh? dunno abt departing %s",nick); return; } % irc_Logf("Del nick %s from %s",nick,chan); if (chan == "all") { s_del(p); nick_act_del(nick); return; } !if (nick_chan_del_chan(p,chan)) irc_Logf("Huh? Didn't know departing nick %s WAS on %s",nick,chan); !if (streq_i(irc_nickname,nick)) return; % WE left channel, so need to purge more p0 = nick_chan.f; p = p0; if (p != NULL) { NULL; do { if (is_list_element(p.v.chans,chan,' ')) p; p = p.n; } while (p != p0); while (p = (), p != NULL) () = nick_chan_del_chan(p,chan); } } % ATM, we only use 1st param to nick_chan_check, but maybe later % the other two will be used define nick_chan_check(sender,rcpt,isnotice) { variable nick, uhost, p, r = -1; if (int(sender)) { (nick,uhost) = v_split(sender,"!"); if(uhost == NullString) (uhost,nick) = (nick,uhost); if (uhost != irc_servername) { % better not ignore ppl on the server p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p != NULL) { nick_chan_add_uhost(p,uhost); r = 1 - rx_ignored(sender); }else{ irc_Logf("Ignoring msg/ctcp from unknown source (%s)",sender); r = 0; } } } return r; } % the handling of nick-changes will be made 'smarter' RSN, maybe. % we should probably have another list (nick_aka) to deal with % automatically handling recent nickchanges for nick-completion, etc. define nick_chan_mod(nick,uhost,newnick) { variable p,p0; if (strspn(nick,"@+")) nick = chop_1st(nick); p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p != NULL) p.v.nick = newnick; else irc_Logf("Huh? old nick %s not found",nick); % now apply nickchange to the nick_act list too p0 = nick_act.f; p = p0; while (p != NULL) { p = s_find_n1(p,&NC0_cmp,nick); if (p == NULL) break; p.v.nick = newnick; p = p.n; if (p == p0) break; } irc_nick_action(newnick,sprintf("JNICK was %s",nick)); } % refer to maintained list of known users on channels for % hostname, hostip associated with the nick. define host_of_nick(nick) { variable p,host; p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p == NULL) return (NULL,NullString); (,host) = v_split(p.v.uhost,"@"); if (host == NullString) return (NULL,NullString); if (p.v.hip == NULL) p.v.hip = gethostbyname(host); return (p.v.hip,host); } define chans_of_nick(nick) { variable p,chans,b,n,chan; p = s_find_n1(nick_chan.f,&NC0_cmp,nick); if (p == NULL) return NullString; chans = p.v.chans; if (chans != NullString) { b = strchopr(chans,' ',0); foreach (b) { chan = (); !if (is_list_element(on_chans,chan,' ')) chans = cut_list_element(chans,chan,' '); } b = strchop(chans,' ',0); chans = strjoin(b,","); } return chans; } %% this one is only for testing host_of_nick: add_cmnd("hostof"); define command_HOSTOF(params) { variable ip,host,s = ""; (ip,host) = host_of_nick(params); if (ip != NULL) s = inet_ntoa(ip); irc_Logf("host of %s is %s [%s]",params,host,s); } define irc_logl(ls) { variable p0,p; p0 = ls.f; if (p0 != NULL) { p = p0; do { irc_log(p.v); p = p.n; }while (p != p0); } } % this command is really just for debugging: private variable cmds_nicky_pm = NullString; private variable cmds_nicky_ct; private define cmds_nicky(p,i) { variable s = ""; if (cmds_nicky_pm != NullString) { if (cmds_nicky_pm == "#"){ if (p.v.hip == NULL) return; }else if (cmds_nicky_pm == "*"){ if (p.v.uhost == NullString) return; }else{ !if (is_prefix_i(p.v.nick,cmds_nicky_pm)) return; } } if (p.v.hip != NULL) { s = "?"; if (p.v.hip != -1) s = inet_ntoa(p.v.hip); } sprintf("%s (%s) [%s] on:%s", p.v.nick, p.v.uhost, s ,p.v.chans); cmds_nicky_ct++; } add_cmnd("nicky"); define command_NICKY(params) { params = strtrim(params); cmds_nicky_pm = params; cmds_nicky_ct = 0; "\n"; s_dofunc(nick_chan,&cmds_nicky); if(cmds_nicky_ct) { variable items = create_delimited_string(cmds_nicky_ct); irc_create_popup("NICK_CHAN_LIST", items, ""); return; } params = (); } % another debug command: list the LRU list add_cmnd("lru"); define command_LRU(params) { variable i, n = irc_user_list(); if (n > 10) n = 10; variable nick,uhost; variable s = "LRU:"; s; for (i = 0; i < n ; i++) { (nick,uhost) = v_split(Rpms[i],"!"); strcat(" ",nick); strcat(); } irc_log; } define is_channel(channel) { return strspn(channel, "#&"); } define on_channel(channel) { if(is_channel(channel)) return is_list_element(on_chans, strlow(channel), ' '); return 0; }