diff options
Diffstat (limited to 'services/console/lib/purk/scripts')
-rw-r--r-- | services/console/lib/purk/scripts/Makefile.am | 16 | ||||
-rwxr-xr-x | services/console/lib/purk/scripts/alias.py | 60 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/chaninfo.py | 320 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/clicks.py | 146 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/completion.py | 135 | ||||
-rwxr-xr-x | services/console/lib/purk/scripts/console.py | 68 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/history.py | 45 | ||||
-rwxr-xr-x | services/console/lib/purk/scripts/ignore.py | 43 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/irc_script.py | 588 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/keys.py | 70 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/theme.py | 366 | ||||
-rwxr-xr-x | services/console/lib/purk/scripts/timeout.py | 45 | ||||
-rw-r--r-- | services/console/lib/purk/scripts/ui_script.py | 132 |
13 files changed, 2034 insertions, 0 deletions
diff --git a/services/console/lib/purk/scripts/Makefile.am b/services/console/lib/purk/scripts/Makefile.am new file mode 100644 index 0000000..00ffbf8 --- /dev/null +++ b/services/console/lib/purk/scripts/Makefile.am @@ -0,0 +1,16 @@ +sugardir = $(pkgdatadir)/services/console/lib/purk/scripts + +sugar_PYTHON = \ + alias.py \ + chaninfo.py \ + clicks.py \ + completion.py \ + console.py \ + history.py \ + ignore.py \ + irc_script.py \ + keys.py \ + theme.py \ + timeout.py \ + ui_script.py + diff --git a/services/console/lib/purk/scripts/alias.py b/services/console/lib/purk/scripts/alias.py new file mode 100755 index 0000000..ffd213d --- /dev/null +++ b/services/console/lib/purk/scripts/alias.py @@ -0,0 +1,60 @@ +import sys +import os + +from conf import conf + +aliases = conf.get("aliases",{ + 'op':'"mode "+window.id+" +"+"o"*len(args)+" "+" ".join(args)', + 'deop':'"mode "+window.id+" -"+"o"*len(args)+" "+" ".join(args)', + 'voice':'"mode "+window.id+" +"+"v"*len(args)+" "+" ".join(args)', + 'devoice':'"mode "+window.id+" -"+"v"*len(args)+" "+" ".join(args)', + 'umode':'"mode "+network.me+" "+" ".join(args)', + 'clear':'window.output.clear()', + }) + +class CommandHandler: + __slots__ = ["command"] + def __init__(self, command): + self.command = command + def __call__(self, e): + loc = sys.modules.copy() + loc.update(e.__dict__) + result = eval(self.command,loc) + if isinstance(result,basestring): + core.events.run(result,e.window,e.network) + +for name in aliases: + globals()['onCommand'+name.capitalize()] = CommandHandler(aliases[name]) + +def onCommandAlias(e): + if e.args and 'r' in e.switches: + name = e.args[0].lower() + command = aliases[name] + del aliases[name] + conf['aliases'] = aliases + e.window.write("* Deleted alias %s%s (was %s)" % (conf.get('command-prefix','/'),name,command)) + core.events.load(__name__,reloading=True) + elif 'l' in e.switches: + e.window.write("* Current aliases:") + for i in aliases: + e.window.write("* %s%s: %s" % (conf.get('command-prefix','/'),i,aliases[i])) + elif len(e.args) >= 2: + name = e.args[0].lower() + command = ' '.join(e.args[1:]) + aliases[name] = command + conf['aliases'] = aliases + e.window.write("* Created an alias %s%s to %s" % (conf.get('command-prefix','/'),name,command)) + core.events.reload(__name__) + elif len(e.args) == 1: + name = e.args[0].lower() + if name in aliases: + e.window.write("* %s%s is an alias to %s" % (conf.get('command-prefix','/'),name,aliases[name])) + else: + e.window.write("* There is no alias %s%s" % (conf.get('command-prefix','/'),name)) + else: + e.window.write( +"""Usage: + /alias \x02name\x02 \x02expression\x02 to create or replace an alias + /alias \x02name\x02 to look at an alias + /alias -r \x02name\x02 to remove an alias + /alias -l to see a list of aliases""") diff --git a/services/console/lib/purk/scripts/chaninfo.py b/services/console/lib/purk/scripts/chaninfo.py new file mode 100644 index 0000000..e6ff3a0 --- /dev/null +++ b/services/console/lib/purk/scripts/chaninfo.py @@ -0,0 +1,320 @@ +import windows + +def _justprefix(network, channel, nick): + fr, to = network.isupport["PREFIX"][1:].split(")") + + for mode, prefix in zip(fr, to): + if mode in channel.nicks.get(nick, ''): + return prefix + + return '' + +def prefix(network, channelname, nick): + channel = getchan(network, channelname) + + if channel: + nick = '%s%s' % (_justprefix(network, channel, nick), nick) + + return nick + +def escape(string): + for escapes in (('&','&'), ('<','<'), ('>','>')): + string = string.replace(*escapes) + return string + +def sortkey(network, channelname, nick): + chanmodes, dummy = network.isupport["PREFIX"][1:].split(")") + nickmodes = mode(network, channelname, nick) + + return '%s%s' % (''.join(str(int(mode not in nickmodes)) for mode in chanmodes), network.norm_case(nick)) + +def nicklist_add(network, channel, nick): + window = windows.get(windows.ChannelWindow, network, channel.name, core) + #window = core.window + if window: + window.nicklist.append(nick, escape(prefix(network, channel.name, nick)), sortkey(network, channel.name, nick)) + +def nicklist_del(network, channel, nick): + window = windows.get(windows.ChannelWindow, network, channel.name, core) + #window = core.window + if window: + try: + window.nicklist.remove(nick) + except ValueError: + pass + +def setupListRightClick(e): + if isinstance(e.window, windows.ChannelWindow): + #if isinstance(core.window, windows.ChannelWindow): + #if e.data[0] in e.window.network.isupport["PREFIX"].split(")")[1]: + if e.data[0] in core.window.network.isupport["PREFIX"].split(")")[1]: + e.nick = e.data[1:] + else: + e.nick = e.data + +def setupSocketConnect(e): + e.network.channels = {} + +def setdownDisconnect(e): + e.network.channels = {} + +class Channel(object): + def __init__(self, name): + self.name = name + self.nicks = {} + self.normal_nicks = {} # mapping of normal nicks to actual nicks + self.getting_names = False #are we between lines in a /names reply? + self.mode = '' + self.special_mode = {} #for limits, keys, and anything similar + self.topic = '' + self.got_mode = False #did we get at least one mode reply? + self.got_names = False #did we get at least one names reply? + +def getchan(network, channel): + return hasattr(network, 'channels') and network.channels.get(network.norm_case(channel)) + +#return a list of channels you're on on the given network +def channels(network): + if not hasattr(network, 'channels'): + network.channels = {} + + return list(network.channels) + +#return True if you're on the channel +def ischan(network, channel): + return bool(getchan(network, channel)) + +#return True if the nick is on the channel +def ison(network, channel, nickname): + channel = getchan(network, channel) + return channel and network.norm_case(nickname) in channel.normal_nicks + +#return a list of nicks on the given channel +def nicks(network, channel): + channel = getchan(network, channel) + + if channel: + return channel.nicks + else: + return {} + +#return the mode on the given channel +def mode(network, channel, nickname=''): + channel = getchan(network, channel) + + if channel: + if nickname: + realnick = channel.normal_nicks.get(network.norm_case(nickname)) + if realnick: + return channel.nicks[realnick] + + else: + result = channel.mode + for m in channel.mode: + if m in channel.special_mode: + result += ' '+channel.special_mode[m] + return result + + return '' + +#return the topic on the given channel +def topic(network, channel): + channel = getchan(network, channel) + + if channel: + return channel.topic + else: + return '' + +def setupJoin(e): + print e + if e.source == e.network.me: + e.network.channels[e.network.norm_case(e.target)] = Channel(e.target) + e.network.raw('MODE '+e.target) + + #if we wanted to be paranoid, we'd account for not being on the channel + channel = getchan(e.network,e.target) + channel.nicks[e.source] = '' + channel.normal_nicks[e.network.norm_case(e.source)] = e.source + + if e.source == e.network.me: + #If the channel window already existed, and we're joining, then we + #didn't clear out the nicklist when we left. That means we have to clear + #it out now. + window = windows.get(windows.ChannelWindow, e.network, e.target, core) + #window = core.window + #print core + if window: + window.nicklist.clear() + + nicklist_add(e.network, channel, e.source) + +def setdownPart(e): + if e.source == e.network.me: + del e.network.channels[e.network.norm_case(e.target)] + else: + channel = getchan(e.network,e.target) + nicklist_del(e.network, channel, e.source) + del channel.nicks[e.source] + del channel.normal_nicks[e.network.norm_case(e.source)] + +def setdownKick(e): + if e.target == e.network.me: + del e.network.channels[e.network.norm_case(e.channel)] + else: + channel = getchan(e.network,e.channel) + nicklist_del(e.network, channel, e.target) + del channel.nicks[e.target] + del channel.normal_nicks[e.network.norm_case(e.target)] + +def setdownQuit(e): + #if paranoid: check if e.source is me + for channame in channels(e.network): + channel = getchan(e.network,channame) + if e.source in channel.nicks: + nicklist_del(e.network, channel, e.source) + del channel.nicks[e.source] + del channel.normal_nicks[e.network.norm_case(e.source)] + +def setupMode(e): + channel = getchan(e.network,e.channel) + if channel: + user_modes = e.network.isupport['PREFIX'].split(')')[0][1:] + + (list_modes, + always_parm_modes, + set_parm_modes, + normal_modes) = e.network.isupport['CHANMODES'].split(',') + + list_modes += user_modes + + mode_on = True #are we reading a + section or a - section? + params = e.text.split(' ') + + for char in params.pop(0): + if char == '+': + mode_on = True + + elif char == '-': + mode_on = False + + else: + if char in user_modes: + #these are modes like op and voice + nickname = params.pop(0) + nicklist_del(e.network, channel, nickname) + if mode_on: + channel.nicks[nickname] += char + else: + channel.nicks[nickname] = channel.nicks[nickname].replace(char, '') + nicklist_add(e.network, channel, nickname) + + elif char in list_modes: + #things like ban/unban + #FIXME: We don't keep track of those lists here, but we know + # when they're changed and how. Scriptors should be able to + # take advantage of this + params.pop(0) + + elif char in always_parm_modes: + #these always have a parameter + param = params.pop(0) + + if mode_on: + channel.special_mode[char] = param + else: + #account for unsetting modes that aren't set + channel.special_mode.pop(char, None) + + elif char in set_parm_modes: + #these have a parameter only if they're being set + if mode_on: + channel.special_mode[char] = params.pop(0) + else: + #account for unsetting modes that aren't set + channel.special_mode.pop(char, None) + + if char not in list_modes: + if mode_on: + channel.mode = channel.mode.replace(char, '')+char + else: + channel.mode = channel.mode.replace(char, '') + +def setdownNick(e): + for channame in channels(e.network): + channel = getchan(e.network,channame) + if e.source in channel.nicks: + nicklist_del(e.network, channel, e.source) + del channel.normal_nicks[e.network.norm_case(e.source)] + channel.nicks[e.target] = channel.nicks[e.source] + del channel.nicks[e.source] + channel.normal_nicks[e.network.norm_case(e.target)] = e.target + nicklist_add(e.network, channel, e.target) + +def setupTopic(e): + channel = getchan(e.network, e.target) + if channel: + channel.topic = e.text + +def setupRaw(e): + if e.msg[1] == '353': #names reply + channel = getchan(e.network,e.msg[4]) + if channel: + if not channel.getting_names: + channel.nicks.clear() + channel.normal_nicks.clear() + channel.getting_names = True + if not channel.got_names: + e.quiet = True + for nickname in e.msg[5].split(' '): + if nickname: + if not nickname[0].isalpha() and nickname[0] in e.network.prefixes: + n = nickname[1:] + channel.nicks[n] = e.network.prefixes[nickname[0]] + channel.normal_nicks[e.network.norm_case(n)] = n + else: + channel.nicks[nickname] = '' + channel.normal_nicks[e.network.norm_case(nickname)] = nickname + + elif e.msg[1] == '366': #end of names reply + channel = getchan(e.network,e.msg[3]) + if channel: + if not channel.got_names: + e.quiet = True + channel.got_names = True + channel.getting_names = False + + window = windows.get(windows.ChannelWindow, e.network, e.msg[3], core) + if window: + window.nicklist.replace( + (nick, escape(prefix(e.network, channel.name, nick)), sortkey(e.network, channel.name, nick)) for nick in channel.nicks + ) + + elif e.msg[1] == '324': #channel mode is + channel = getchan(e.network,e.msg[3]) + if channel: + if not channel.got_mode: + e.quiet = True + channel.got_mode = True + mode = e.msg[4] + params = e.msg[:4:-1] + list_modes, always_parm_modes, set_parm_modes, normal_modes = \ + e.network.isupport['CHANMODES'].split(',') + parm_modes = always_parm_modes + set_parm_modes + channel.mode = e.msg[4] + channel.special_mode.clear() + for char in channel.mode: + if char in parm_modes: + channel.special_mode[char] = params.pop() + + elif e.msg[1] == '331': #no topic + channel = getchan(e.network,e.msg[3]) + if channel: + channel.topic = '' + + elif e.msg[1] == '332': #channel topic is + channel = getchan(e.network,e.msg[3]) + if channel: + channel.topic = e.text + +#core.events.load(__name__) diff --git a/services/console/lib/purk/scripts/clicks.py b/services/console/lib/purk/scripts/clicks.py new file mode 100644 index 0000000..b2f3f82 --- /dev/null +++ b/services/console/lib/purk/scripts/clicks.py @@ -0,0 +1,146 @@ +import ui +import windows +import chaninfo +from conf import conf + +def set_target(e): + target_l = e.target.lstrip('@+%.(<') + e._target_fr = e.target_fr + len(e.target) - len(target_l) + + target_r = e.target.rstrip('>:,') + e._target_to = e.target_to - len(e.target) + len(target_r) + + if target_r.endswith(')'): + e._target = e.text[e._target_fr:e._target_to] + open_parens = e._target.count('(') - e._target.count(')') + while open_parens < 0 and e.text[e._target_to-1] == ')': + e._target_to -= 1 + open_parens += 1 + + e._target = e.text[e._target_fr:e._target_to] + +def is_nick(e): + return isinstance(e.window, windows.ChannelWindow) and \ + chaninfo.ison(e.window.network, e.window.id, e._target) + +def is_url(e): + def starts(prefix, mindots=1): + def prefix_url(target): + return target.startswith(prefix) and target.count('.') >= mindots + + return prefix_url + + to_check = [starts(*x) for x in [ + ('http://', 1), + ('https://', 1), + ('ftp://', 1), + ('www', 2), + ]] + + for check_url in to_check: + if check_url(e._target): + return True + + return False + +def is_chan(e): + # click on a #channel + return e.window.network and e._target and \ + e._target[0] in e.window.network.isupport.get('CHANTYPES', '&#$+') + +def get_autojoin_list(network): + perform = conf.get('networks',{}).get(network.name,{}).get('perform',()) + channels = set() + for line in perform: + if line.startswith('join ') and ' ' not in line[5:]: + channels.update(line[5:].split(',')) + return channels + +def add_autojoin(network, channel): + if 'networks' not in conf: + conf['networks'] = {} + if network.name not in conf['networks']: + conf['networks'][network.name] = {'server': network.server} + conf['start_networks'] = conf.get('start_networks',[]) + [network.name] + if 'perform' in conf['networks'][network.name]: + perform = conf['networks'][network.name]['perform'] + else: + perform = conf['networks'][network.name]['perform'] = [] + + for n, line in enumerate(perform): + if line.startswith('join ') and ' ' not in line[5:]: + perform[n] = "%s,%s" % (line, channel) + break + else: + perform.append('join %s' % channel) + +def make_nick_menu(e, target): + def query(): + core.events.run('query %s' % target, e.window, e.window.network) + + def whois(): + core.events.run('whois %s' % target, e.window, e.window.network) + + e.menu += [ + ('Query', query), + ('Whois', whois), + (), + ] + +def onHover(e): + set_target(e) + + for is_check in (is_nick, is_url, is_chan): + if is_check(e): + e.tolink.add((e._target_fr, e._target_to)) + break + +def onClick(e): + set_target(e) + + if is_nick(e): + core.events.run('query %s' % e._target, e.window, e.window.network) + + # url of the form http://xxx.xxx or www.xxx.xxx + elif is_url(e): + if e._target.startswith('www'): + e._target = 'http://%s' % e._target + ui.open_file(e._target) + + # click on a #channel + elif is_chan(e): + if not chaninfo.ischan(e.window.network, e._target): + e.window.network.join(e._target) + window = windows.get(windows.ChannelWindow, e.window.network, e._target) + if window: + window.activate() + +def onRightClick(e): + set_target(e) + + # nick on this channel + if is_nick(e): + make_nick_menu(e, e._target) + + elif is_url(e): + if e._target.startswith('www'): + e._target = 'http://%s' % e._target + + def copy_to(): + # copy to clipboard + ui.set_clipboard(e._target) + + e.menu += [('Copy', copy_to)] + + elif is_chan(e): + e.channel = e._target + e.network = e.window.network + core.events.trigger('ChannelMenu', e) + +def onListRightClick(e): + if isinstance(e.window, windows.ChannelWindow): + make_nick_menu(e, e.nick) + +def onListDoubleClick(e): + if isinstance(e.window, windows.ChannelWindow): + core.events.run("query %s" % e.target, e.window, e.window.network) diff --git a/services/console/lib/purk/scripts/completion.py b/services/console/lib/purk/scripts/completion.py new file mode 100644 index 0000000..1719702 --- /dev/null +++ b/services/console/lib/purk/scripts/completion.py @@ -0,0 +1,135 @@ +import windows +import chaninfo +from conf import conf + +def channel_completer(window, left, right, text): + if isinstance(window, windows.ChannelWindow): + yield window.id + + for w in windows.get_with(wclass=windows.ChannelWindow, network=window.network): + if w is not window: + yield w.id + + for w in windows.get_with(wclass=windows.ChannelWindow): + if w.network is not window.network: + yield w.id + +# normal server commands +srv_commands = ('ping', 'join', 'part', 'mode', 'server', 'kick', + 'quit', 'nick', 'privmsg', 'notice', 'topic') + +def command_completer(window, left, right, text): + for c in srv_commands: + yield '/%s' % c + + if 'CMDS' in window.network.isupport: + for c in window.network.isupport['CMDS'].split(','): + yield '/%s' % c.lower() + + for c in core.events.all_events: + if c.startswith('Command') and c != 'Command': + yield '/%s' % c[7:].lower() + +def nick_completer(window, left, right, text): + if type(window) == windows.QueryWindow: + yield window.id + + recent_speakers = getattr(window, 'recent_speakers', ()) + + for nick in recent_speakers: + if chaninfo.ison(window.network, window.id, nick): + yield nick + + for nick in chaninfo.nicks(window.network, window.id): + if nick not in recent_speakers: + yield nick + +def script_completer(window, left, right, text): + return core.events.loaded.iterkeys() + +def network_completer(window, left, right, text): + return conf.get('networks', {}).iterkeys() + +def get_completer_for(window): + input = window.input + + left, right = input.text[:input.cursor], input.text[input.cursor:] + + text = left.split(' ')[-1] + + while True: + suffix = '' + if text and text[0] in window.network.isupport.get('CHANTYPES', '#&+'): + candidates = channel_completer(window, left, right, text) + + elif input.text.startswith('/reload '): + candidates = script_completer(window, left, right, text) + + elif input.text.startswith('/edit '): + candidates = script_completer(window, left, right, text) + + elif input.text.startswith('/server '): + candidates = network_completer(window, left, right, text) + + elif text.startswith('/'): + candidates = command_completer(window, left, right, text) + suffix = ' ' + + else: + candidates = nick_completer(window, left, right, text) + + if left == text: + suffix = ': ' + else: + suffix = ' ' + + if text: + before = left[:-len(text)] + else: + before = left + + insert_text = '%s%s%s%s' % (before, '%s', suffix, right) + cursor_pos = len(before + suffix) + + original = window.input.text, window.input.cursor + + for cand in candidates: + if cand.lower().startswith(text.lower()): + window.input.text, window.input.cursor = insert_text % cand, cursor_pos + len(cand) + yield None + + window.input.text, window.input.cursor = original + yield None + +# generator--use recent_completer.next() to continue cycling through whatever +recent_completer = None + +def onKeyPress(e): + global recent_completer + + if e.key == 'Tab': + if not recent_completer: + recent_completer = get_completer_for(e.window) + + recent_completer.next() + + else: + recent_completer = None + +def onActive(e): + global recent_completer + + recent_completer = None + +def onText(e): + if chaninfo.ischan(e.network, e.target): + if not hasattr(e.window, 'recent_speakers'): + e.window.recent_speakers = [] + + for nick in e.window.recent_speakers: + if nick == e.source or not chaninfo.ison(e.network, e.target, nick): + e.window.recent_speakers.remove(nick) + + e.window.recent_speakers.insert(0, e.source) + +onAction = onText diff --git a/services/console/lib/purk/scripts/console.py b/services/console/lib/purk/scripts/console.py new file mode 100755 index 0000000..bef8e0e --- /dev/null +++ b/services/console/lib/purk/scripts/console.py @@ -0,0 +1,68 @@ +import sys +import traceback +import windows +from conf import conf + +class ConsoleWriter: + __slots__ = ['window'] + def __init__(self, window): + self.window = window + def write(self, text): + try: + self.window.write(text, line_ending='') + except: + self.window.write(traceback.format_exc()) + +class ConsoleWindow(windows.SimpleWindow): + def __init__(self, network, id): + windows.SimpleWindow.__init__(self, network, id) + + writer = ConsoleWriter(self) + + sys.stdout = writer + sys.stderr = writer + + self.globals = {'window': self} + self.locals = {} + +#this prevents problems (and updates an open console window) on reload +#window = None +#for window in manager: +# if type(window).__name__ == "ConsoleWindow": +# window.mutate(ConsoleWindow, window.network, window.id) +#del window + +def onClose(e): + if isinstance(e.window, ConsoleWindow): + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + +def onCommandConsole(e): + windows.new(ConsoleWindow, None, "console").activate() + +def onCommandSay(e): + if isinstance(e.window, ConsoleWindow): + import pydoc #fix nonresponsive help() command + old_pager, pydoc.pager = pydoc.pager, pydoc.plainpager + e.window.globals.update(sys.modules) + text = ' '.join(e.args) + try: + e.window.write(">>> %s" % text) + result = eval(text, e.window.globals, e.window.locals) + if result is not None: + e.window.write(repr(result)) + e.window.globals['_'] = result + except SyntaxError: + try: + exec text in e.window.globals, e.window.locals + except: + traceback.print_exc() + except: + traceback.print_exc() + pydoc.pager = old_pager + else: + raise core.events.CommandError("There's no one here to speak to.") + +def onStart(e): + if conf.get('start-console'): + windows.new(ConsoleWindow, None, "console") diff --git a/services/console/lib/purk/scripts/history.py b/services/console/lib/purk/scripts/history.py new file mode 100644 index 0000000..379ad5e --- /dev/null +++ b/services/console/lib/purk/scripts/history.py @@ -0,0 +1,45 @@ +def onKeyPress(e): + if not hasattr(e.window, 'history'): + e.window.history = [], -1 + + if e.key in ('Up', 'Down'): + h, i = e.window.history + + if i == -1 and e.window.input.text: + h.insert(0, e.window.input.text) + i = 0 + + if e.key == 'Up': + i += 1 + + if i < len(h): + e.window.history = h, i + + e.window.input.text = h[i] + e.window.input.cursor = -1 + + else: # e.key == 'Up' + i -= 1 + + if i > -1: + e.window.history = h, i + + e.window.input.text = h[i] + e.window.input.cursor = -1 + + elif i == -1: + e.window.history = h, i + + e.window.input.text = '' + e.window.input.cursor = -1 + +def onInput(e): + if not hasattr(e.window, 'history'): + e.window.history = [], -1 + + if e.text: + h, i = e.window.history + + h.insert(0, e.text) + + e.window.history = h, -1 diff --git a/services/console/lib/purk/scripts/ignore.py b/services/console/lib/purk/scripts/ignore.py new file mode 100755 index 0000000..98b4eed --- /dev/null +++ b/services/console/lib/purk/scripts/ignore.py @@ -0,0 +1,43 @@ +from conf import conf +import irc + +def preRaw(e): + if e.msg[1] in ('PRIVMSG','NOTICE'): + address = e.network.norm_case('%s!%s' % (e.source, e.address)) + for mask in conf.get('ignore_masks',()): + if irc.match_glob(address, e.network.norm_case(mask)): + core.events.halt() + +def onCommandIgnore(e): + if 'ignore_masks' not in conf: + conf['ignore_masks'] = [] + if 'l' in e.switches: + for i in conf['ignore_masks']: + e.window.write('* %s' % i) + elif 'c' in e.switches: + del conf['ignore_masks'] + e.window.write('* Cleared the ignore list.') + elif e.args: + if '!' in e.args[0] or '*' in e.args[0] or '?' in e.args[0]: + mask = e.args[0] + else: + mask = '%s!*' % e.args[0] + if 'r' in e.switches: + if mask in conf['ignore_masks']: + conf['ignore_masks'].remove(mask) + e.window.write('* Removed %s from the ignore list' % e.args[0]) + else: + raise core.events.CommandError("Couldn't find %s in the ignore list" % e.args[0]) + else: + if mask in conf['ignore_masks']: + e.window.write('* %s is already ignored' % e.args[0]) + else: + conf['ignore_masks'].append(mask) + e.window.write('* Ignoring messages from %s' % e.args[0]) + else: + e.window.write( +"""Usage: + /ignore \x02nick/mask\x02 to ignore a nickname or mask + /ignore -r \x02nick/mask\x02 to stop ignoring a nickname or mask + /ignore -l to view the ignore list + /ignore -c to clear the ignore list""") diff --git a/services/console/lib/purk/scripts/irc_script.py b/services/console/lib/purk/scripts/irc_script.py new file mode 100644 index 0000000..4582d72 --- /dev/null +++ b/services/console/lib/purk/scripts/irc_script.py @@ -0,0 +1,588 @@ +import time + +from conf import conf +import ui +import windows +import irc + +COMMAND_PREFIX = conf.get('command_prefix', '/') + +NICK_SUFFIX = r"`_-\|0123456789" + +_hextochr = dict(('%02x' % i, chr(i)) for i in range(256)) +def unquote(url, rurl=""): + + while '%' in url: + url, char = url.rsplit('%', 1) + + chars = char[:2].lower() + + if chars in _hextochr: + rurl = '%s%s%s' % (_hextochr[chars], char[2:], rurl) + else: + rurl = "%s%s%s" % ('%', char, rurl) + + return url + rurl + +#for getting a list of alternative nicks to try on a network +def _nick_generator(network): + for nick in network.nicks[1:]: + yield nick + if network._nick_error: + nick = 'ircperson' + else: + nick = network.nicks[0] + import itertools + for i in itertools.count(1): + for j in xrange(len(NICK_SUFFIX)**i): + suffix = ''.join(NICK_SUFFIX[(j/(len(NICK_SUFFIX)**x))%len(NICK_SUFFIX)] for x in xrange(i)) + if network._nick_max_length: + yield nick[0:network._nick_max_length-i]+suffix + else: + yield nick+suffix + +def setdownRaw(e): + if not e.done: + if not e.network.got_nick: + if e.msg[1] in ('432','433','436','437'): #nickname unavailable + failednick = e.msg[3] + nicks = list(e.network.nicks) + + if hasattr(e.network,'_nick_generator'): + if len(failednick) < len(e.network._next_nick): + e.network._nick_max_length = len(failednick) + e.network._next_nick = e.network._nick_generator.next() + e.network.raw('NICK %s' % e.network._next_nick) + e.network._nick_error |= (e.msg[1] == '432') + else: + e.network._nick_error = (e.msg[1] == '432') + if len(failednick) < len(e.network.nicks[0]): + e.network._nick_max_length = len(failednick) + else: + e.network._nick_max_length = 0 + e.network._nick_generator = _nick_generator(e.network) + e.network._next_nick = e.network._nick_generator.next() + e.network.raw('NICK %s' % e.network._next_nick) + + elif e.msg[1] == '431': #no nickname given--this shouldn't happen + pass + + elif e.msg[1] == '001': + e.network.got_nick = True + if e.network.me != e.msg[2]: + core.events.trigger( + 'Nick', network=e.network, window=e.window, + source=e.network.me, target=e.msg[2], address='', + text=e.msg[2] + ) + e.network.me = e.msg[2] + if hasattr(e.network,'_nick_generator'): + del e.network._nick_generator, e.network._nick_max_length, e.network._next_nick + + if e.msg[1] == "PING": + e.network.raw("PONG :%s" % e.msg[-1]) + e.done = True + + elif e.msg[1] == "JOIN": + e.channel = e.target + e.requested = e.network.norm_case(e.channel) in e.network.requested_joins + core.events.trigger("Join", e) + e.done = True + + elif e.msg[1] == "PART": + e.channel = e.target + e.requested = e.network.norm_case(e.channel) in e.network.requested_parts + e.text = ' '.join(e.msg[3:]) + core.events.trigger("Part", e) + e.done = True + + elif e.msg[1] in "MODE": + e.channel = e.target + e.text = ' '.join(e.msg[3:]) + core.events.trigger("Mode", e) + e.done = True + + elif e.msg[1] == "QUIT": + core.events.trigger('Quit', e) + e.done = True + + elif e.msg[1] == "KICK": + e.channel = e.msg[2] + e.target = e.msg[3] + core.events.trigger('Kick', e) + e.done = True + + elif e.msg[1] == "NICK": + core.events.trigger('Nick', e) + if e.network.me == e.source: + e.network.me = e.target + + e.done = True + + elif e.msg[1] == "PRIVMSG": + core.events.trigger('Text', e) + e.done = True + + elif e.msg[1] == "NOTICE": + core.events.trigger('Notice', e) + e.done = True + + elif e.msg[1] == "TOPIC": + core.events.trigger('Topic', e) + e.done = True + + elif e.msg[1] in ("376", "422"): #RPL_ENDOFMOTD + if e.network.status == irc.INITIALIZING: + e.network.status = irc.CONNECTED + core.events.trigger('Connect', e) + e.done = True + + elif e.msg[1] == "470": #forwarded from channel X to channel Y + if e.network.norm_case(e.msg[3]) in e.network.requested_joins: + e.network.requested_joins.discard(e.network.norm_case(e.msg[3])) + e.network.requested_joins.add(e.network.norm_case(e.msg[4])) + + elif e.msg[1] == "005": #RPL_ISUPPORT + for arg in e.msg[3:]: + if ' ' not in arg: #ignore "are supported by this server" + if '=' in arg: + name, value = arg.split('=', 1) + if value.isdigit(): + value = int(value) + else: + name, value = arg, '' + + #Workaround for broken servers (bahamut on EnterTheGame) + if name == 'PREFIX' and value[0] != '(': + continue + + #in theory, we're supposed to replace \xHH with the + # corresponding ascii character, but I don't think anyone + # really does this + e.network.isupport[name] = value + + if name == 'PREFIX': + new_prefixes = {} + modes, prefixes = value[1:].split(')') + for mode, prefix in zip(modes, prefixes): + new_prefixes[mode] = prefix + new_prefixes[prefix] = mode + e.network.prefixes = new_prefixes + +def setupSocketConnect(e): + e.network.got_nick = False + e.network.isupport = { + 'NETWORK': e.network.server, + 'PREFIX': '(ohv)@%+', + 'CHANMODES': 'b,k,l,imnpstr', + } + e.network.prefixes = {'o':'@', 'h':'%', 'v':'+', '@':'o', '%':'h', '+':'v'} + e.network.connect_timestamp = time.time() + e.network.requested_joins.clear() + e.network.requested_parts.clear() + e.network.on_channels.clear() + if hasattr(e.network,'_nick_generator'): + del e.network._nick_generator, e.network._nick_max_length, e.network._next_nick + if not e.done: + #this needs to be tested--anyone have a server that uses PASS? + if e.network.password: + e.network.raw("PASS :%s" % e.network.password) + e.network.raw("NICK %s" % e.network.nicks[0]) + e.network.raw("USER %s %s %s :%s" % + (e.network.username, "8", "*", e.network.fullname)) + #per rfc2812 these are username, user mode flags, unused, realname + + #e.network.me = None + e.done = True + +def onDisconnect(e): + if hasattr(e.network,'_reconnect_source'): + e.network._reconnect_source.unregister() + del e.network._reconnect_source + if hasattr(e.network,'connect_timestamp'): + if e.error and conf.get('autoreconnect',True): + delay = time.time() - e.network.connect_timestamp > 30 and 30 or 120 + def do_reconnect(): + if not e.network.status: + server(network=e.network) + def do_announce_reconnect(): + if not e.network.status: + windows.get_default(e.network).write("* Will reconnect in %s seconds.." % delay) + e.network._reconnect_source = ui.register_timer(delay*1000,do_reconnect) + e.network._reconnect_source = ui.register_idle(do_announce_reconnect) + +def onCloseNetwork(e): + e.network.quit() + if hasattr(e.network,'_reconnect_source'): + e.network._reconnect_source.unregister() + del e.network._reconnect_source + +def setdownDisconnect(e): + if hasattr(e.network,'connect_timestamp'): + del e.network.connect_timestamp + +def setupInput(e): + if not e.done: + if e.text.startswith(COMMAND_PREFIX) and not e.ctrl: + command = e.text[len(COMMAND_PREFIX):] + else: + command = 'say - %s' % e.text + + core.events.run(command, e.window, e.network) + + e.done = True + +def onCommandSay(e): + if isinstance(e.window, windows.ChannelWindow) or isinstance(e.window, windows.QueryWindow): + e.network.msg(e.window.id, ' '.join(e.args)) + else: + raise core.events.CommandError("There's no one here to speak to.") + +def onCommandMsg(e): + e.network.msg(e.args[0], ' '.join(e.args[1:])) + +def onCommandNotice(e): + e.network.notice(e.args[0], ' '.join(e.args[1:])) + +def onCommandQuery(e): + windows.new(windows.QueryWindow, e.network, e.args[0], core).activate() + if len(e.args) > 1: + message = ' '.join(e.args[1:]) + if message: #this is false if you do "/query nickname " + e.network.msg(e.args[0], ' '.join(e.args[1:])) + +def setupJoin(e): + if e.source == e.network.me: + chan = e.network.norm_case(e.channel) + e.network.on_channels.add(chan) + e.network.requested_joins.discard(chan) + +def setdownPart(e): + if e.source == e.network.me: + chan = e.network.norm_case(e.channel) + e.network.on_channels.discard(chan) + e.network.requested_parts.discard(chan) + +def setdownKick(e): + if e.target == e.network.me: + chan = e.network.norm_case(e.channel) + e.network.on_channels.discard(chan) + +def ischan(network, channel): + return network.norm_case(channel) in network.on_channels + +# make /nick work offline +def change_nick(network, nick): + if not network.status: + core.events.trigger( + 'Nick', + network=network, window=windows.get_default(network), + source=network.me, target=nick, address='', text=nick + ) + network.nicks[0] = nick + network.me = nick + else: + network.raw('NICK :%s' % nick) + +def onCommandNick(e): + default_nick = irc.default_nicks()[0] + if 't' not in e.switches and e.network.me == default_nick: + conf['nick'] = e.args[0] + import conf as _conf + _conf.save() + for network in set(w.network for w in core.manager): + if network.me == default_nick: + change_nick(network, e.args[0]) + else: + change_nick(e.network, e.args[0]) + +def setdownNick(e): + if e.source != e.network.me: + window = windows.get(windows.QueryWindow, e.network, e.source) + if window: + window.id = e.target + +# make /quit always disconnect us +def onCommandQuit(e): + if e.network.status: + e.network.quit(' '.join(e.args)) + else: + raise core.events.CommandError("We're not connected to a network.") + +def onCommandRaw(e): + if e.network.status >= irc.INITIALIZING: + e.network.raw(' '.join(e.args)) + else: + raise core.events.CommandError("We're not connected to a network.") + +onCommandQuote = onCommandRaw + +def onCommandJoin(e): + if e.args: + if e.network.status >= irc.INITIALIZING: + e.network.join(' '.join(e.args), requested = 'n' not in e.switches) + else: + raise core.events.CommandError("We're not connected.") + elif isinstance(e.window, windows.ChannelWindow): + e.window.network.join(e.window.id, requested = 'n' not in e.switches) + else: + raise core.events.CommandError("You must supply a channel.") + +def onCommandPart(e): + if e.args: + if e.network.status >= irc.INITIALIZING: + e.network.part(' '.join(e.args), requested = 'n' not in e.switches) + else: + raise core.events.CommandError("We're not connected.") + elif isinstance(e.window, windows.ChannelWindow): + e.window.network.part(e.window.id, requested = 'n' not in e.switches) + else: + raise core.events.CommandError("You must supply a channel.") + +def onCommandHop(e): + if e.args: + if e.network.status >= irc.INITIALIZING: + e.network.part(e.args[0], requested = False) + e.network.join(' '.join(e.args), requested = False) + else: + raise core.events.CommandError("We're not connected.") + elif isinstance(e.window, windows.ChannelWindow): + e.window.network.part(e.window.id, requested = False) + e.window.network.join(e.window.id, requested = False) + else: + raise core.events.CommandError("You must supply a channel.") + +#this should be used whereever a new irc.Network may need to be created +def server(server=None,port=6667,network=None,connect=True): + network_info = {} + + if server: + network_info["name"] = server + network_info["server"] = server + if port: + network_info["port"] = port + get_network_info(server, network_info) + + if not network: + network = irc.Network(**network_info) + windows.new(windows.StatusWindow, network, "status").activate() + else: + if "server" in network_info: + network.name = network_info['name'] + network.server = network_info['server'] + if not network.status: + #window = windows.get_default(network) + window = core.window + if window: + window.update() + if "port" in network_info: + network.port = network_info["port"] + + if network.status: + network.quit() + if connect: + network.connect() + core.window.write("* Connecting to %s on port %s" % (network.server, network.port)) + #windows.get_default(network).write( + # "* Connecting to %s on port %s" % (network.server, network.port) + # ) + + return network + +def onCommandServer(e): + host = port = None + + if e.args: + host = e.args[0] + + if ':' in host: + host, port = host.rsplit(':', 1) + port = int(port) + + elif len(e.args) > 1: + port = int(e.args[1]) + + else: + port = 6667 + + if 'm' in e.switches: + network = None + else: + network = e.network + + server(server=host, port=port, network=network, connect='o' not in e.switches) + +#see http://www.w3.org/Addressing/draft-mirashi-url-irc-01.txt +def onCommandIrcurl(e): + url = e.args[0] + + if url.startswith('irc://'): + url = url[6:] + + if not url.startswith('/'): + host, target = url.rsplit('/',1) + if ':' in host: + host, port = host.rsplit(':',1) + else: + port = 6667 + else: + host = None + port = 6667 + target = url + + if host: + if e.network and e.network.server == host: + network = e.network + else: + for w in list(windows.manager): + if w.network and w.network.server == host: + network = w.network + break + else: + for w in list(windows.manager): + if w.network and w.network.server == 'irc.default.org': + network = server(host,port,w.network) + break + else: + network = server(host,port) + + if ',' in target: + target, modifiers = target.split(',',1) + action = '' + else: + target = unquote(target) + if target[0] not in '#&+': + target = '#'+target + action = 'join %s' % target + + if network.status == irc.CONNECTED: + core.events.run(action, windows.get_default(network), network) + else: + if not hasattr(network,'temp_perform'): + network.temp_perform = [action] + else: + network.temp_perform.append(action) + +#commands that we need to add a : to but otherwise can send unchanged +#the dictionary contains the number of arguments we take without adding the : +trailing = { + 'away':0, + 'cnotice':2, + 'cprivmsg':2, + 'kick':2, + 'kill':1, + 'part':1, + 'squery':1, + 'squit':1, + 'topic':1, + 'wallops':0, + } + +needschan = { + 'topic':0, + 'invite':1, + 'kick':0, +# 'mode':0, #this is commonly used for channels, but can apply to users +# 'names':0, #with no parameters, this is supposed to give a list of all users; we may be able to safely ignore that. + } + +def setupCommand(e): + if not e.done: + if e.name in needschan and isinstance(e.window, windows.ChannelWindow): + valid_chan_prefixes = e.network.isupport.get('CHANTYPES', '#&+') + chan_pos = needschan[e.name] + + if len(e.args) > chan_pos: + if not e.args[chan_pos] or e.args[chan_pos][0] not in valid_chan_prefixes: + e.args.insert(chan_pos, e.window.id) + else: + e.args.append(e.window.id) + + if e.name in trailing: + trailing_pos = trailing[e.name] + + if len(e.args) > trailing_pos: + e.args[trailing_pos] = ':%s' % e.args[trailing_pos] + + e.text = '%s %s' % (e.name, ' '.join(e.args)) + +def setdownCommand(e): + if not e.done and e.network.status >= irc.INITIALIZING: + e.network.raw(e.text) + e.done = True + +def get_network_info(name, network_info): + conf_info = conf.get('networks', {}).get(name) + + if conf_info: + network_info['server'] = name + network_info.update(conf_info) + +def onStart(e): + for network in conf.get('start_networks', []): + server(server=network) + +def onConnect(e): + network_info = conf.get('networks', {}).get(e.network.name, {}) + + for command in network_info.get('perform', []): + while command.startswith(COMMAND_PREFIX): + command = command[len(COMMAND_PREFIX):] + core.events.run(command, e.window, e.network) + + tojoin = ','.join(network_info.get('join', [])) + if tojoin: + core.events.run('join -n %s' % tojoin, e.window, e.network) + + if hasattr(e.network,'temp_perform'): + for command in e.network.temp_perform: + core.events.run(command, e.window, e.network) + del e.network.temp_perform + +def isautojoin(network, channel): + try: + joinlist = conf['networks'][network.name]['join'] + except KeyError: + return False + normchannel = network.norm_case(channel) + for chan in joinlist: + if normchannel == network.norm_case(chan): + return True + return False + +def setautojoin(network, channel): + if 'networks' not in conf: + conf['networks'] = networks = {} + else: + networks = conf['networks'] + if network.name not in networks: + networks[network.name] = network_settings = {} + if 'start_networks' not in conf: + conf['start_networks'] = [] + conf['start_networks'].append(network.name) + else: + network_settings = networks[network.name] + + if 'join' not in network_settings: + network_settings['join'] = [channel] + else: + network_settings['join'].append(channel) + +def unsetautojoin(network, channel): + try: + joinlist = conf['networks'][network.name]['join'] + except KeyError: + return False + normchannel = network.norm_case(channel) + for i, chan in enumerate(joinlist[:]): + if normchannel == network.norm_case(chan): + joinlist.pop(i) + +def onChannelMenu(e): + def toggle_join(): + if isautojoin(e.network, e.channel): + unsetautojoin(e.network, e.channel) + else: + setautojoin(e.network, e.channel) + + e.menu.append(('Autojoin', isautojoin(e.network, e.channel), toggle_join)) diff --git a/services/console/lib/purk/scripts/keys.py b/services/console/lib/purk/scripts/keys.py new file mode 100644 index 0000000..e26572c --- /dev/null +++ b/services/console/lib/purk/scripts/keys.py @@ -0,0 +1,70 @@ +import windows +import widgets +import irc + +shortcuts = { + '^b': '\x02', + '^u': '\x1F', + '^r': '\x16', + '^k': '\x03', + '^l': '\x04', + '^o': '\x0F', + } + +def onKeyPress(e): + if e.key in shortcuts: + e.window.input.insert(shortcuts[e.key]) + + elif e.key == '!c': + e.window.output.copy() + + elif e.key == 'Page_Up': + e.window.output.y = e.window.output.y - e.window.output.height / 2 + + elif e.key == 'Page_Down': + e.window.output.y = e.window.output.y + e.window.output.height / 2 + + elif e.key == '^Home': + e.window.output.y = 0 + + elif e.key == '^End': + e.window.output.y = e.window.output.ymax + + elif e.key in ('^Page_Up', '^Page_Down'): + winlist = list(windows.manager) + index = winlist.index(e.window) + ((e.key == '^Page_Down') and 1 or -1) + if 0 <= index < len(winlist): + winlist[index].activate() + + elif e.key == '!a': + winlist = list(windows.manager) + winlist = winlist[winlist.index(e.window):]+winlist + w = [w for w in winlist if widgets.HILIT in w.activity] + + if not w: + w = [w for w in winlist if widgets.TEXT in w.activity] + + if w: + windows.manager.set_active(w[0]) + + # tabbed browsing + elif e.key == '^t': + windows.new(windows.StatusWindow, irc.Network(), 'status').activate() + + elif e.key == '^w': + windows.manager.get_active().close() + + elif e.key == '^f': + window = windows.manager.get_active() + + find = widgets.FindBox(window) + + window.pack_start(find, expand=False) + + find.textbox.grab_focus() + + elif len(e.key) == 2 and e.key.startswith('!') and e.key[1].isdigit(): + n = int(e.key[1]) + if n and n <= len(core.manager): + list(core.manager)[n-1].activate() + #else e.key == "!0" diff --git a/services/console/lib/purk/scripts/theme.py b/services/console/lib/purk/scripts/theme.py new file mode 100644 index 0000000..7fda4d2 --- /dev/null +++ b/services/console/lib/purk/scripts/theme.py @@ -0,0 +1,366 @@ +import time + +import windows +import widgets +import chaninfo + +from conf import conf + +textareas = {} +if 'font' in conf: + textareas['font'] = conf['font'] +if 'bg_color' in conf: + textareas['bg'] = conf['bg_color'] +if 'fg_color' in conf: + textareas['fg'] = conf['fg_color'] + +widgets.set_style("view", textareas) +widgets.set_style("nicklist", textareas) + +#copied pretty directly from something that was probably copied from wine sources +def RGBtoHSL(r, g, b): + maxval = max(r, g, b) + minval = min(r, g, b) + + luminosity = ((maxval + minval) * 240 + 255) // 510 + + if maxval == minval: + saturation = 0 + hue = 160 + else: + delta = maxval - minval + + if luminosity <= 120: + saturation = ((maxval+minval)//2 + delta*240) // (maxval + minval) + else: + saturation = ((150-maxval-minval)//2 + delta*240) // (150-maxval-minval) + + #sigh.. + rnorm = (delta//2 + maxval*40 - r*40)//delta + gnorm = (delta//2 + maxval*40 - g*40)//delta + bnorm = (delta//2 + maxval*40 - b*40)//delta + + if r == maxval: + hue = bnorm-gnorm + elif g == maxval: + hue = 80+rnorm-bnorm + else: + hue = 160+gnorm-rnorm + hue = hue % 240 + return hue, saturation, luminosity + +#copied from the same place +def huetoRGB(hue, mid1, mid2): + hue = hue % 240 + + if hue > 160: + return mid1 + elif hue > 120: + hue = 160 - hue + elif hue > 40: + return mid2 + return ((hue * (mid2 - mid1) + 20) // 40) + mid1 + +#this too +def HSLtoRGB(hue, saturation, luminosity): + if saturation != 0: + if luminosity > 120: + mid2 = saturation + luminosity - (saturation * luminosity + 120)//240 + else: + mid2 = ((saturation + 240) * luminosity + 120)//240 + + mid1 = luminosity * 2 - mid2 + + return tuple((huetoRGB(hue+x, mid1, mid2) * 255 + 120) // 240 for x in (80,0,-80)) + else: + value = luminosity * 255 // 240 + return value, value, value + +def gethashcolor(string): + h = hash(string) + rgb = HSLtoRGB(h%241, 100-h//241%61, 90) + return "%02x%02x%02x" % rgb + +#take an event e and trigger the highlight event if necessary +def hilight_text(e): + if not hasattr(e, 'Highlight'): + e.Highlight = [] + core.events.trigger('Highlight', e) + +#hilight own nick +def onHighlight(e): + lowertext = e.text.lower() + for word in conf.get('highlight_words', []) + [e.network.me] + e.network.nicks: + lowerword = word.lower() + pos = lowertext.find(lowerword, 0) + while pos != -1: + e.Highlight.append((pos, pos+len(word))) + pos = lowertext.find(lowerword, pos+1) + +def prefix(e): + return time.strftime(conf.get('timestamp', '')) + +def getsourcecolor(e): + address = getattr(e, "address", "") + if address: + if e.network.me == e.source: + e.network._my_address = address + elif e.network.me == e.source: + address = getattr(e.network, "_my_address", "") + if '@' in address: + address = address.split('@')[1] + if not address: + address = e.source + return "\x04%s" % gethashcolor(address) + +def format_source(e): + highlight = getattr(e, "Highlight", "") and '\x02' or '' + return "%s\x04%s%s" % (highlight, getsourcecolor(e), e.source) + +def format_info_source(e): + if e.source == e.network.me: + return "\x04%sYou" % (getsourcecolor(e)) + else: + return "\x04%s%s" % (getsourcecolor(e), e.source) + +def address(e): + #if e.source != e.network.me: + # return "%s " % info_in_brackets(e.address) + #else: + # return "" + return "" + +def text(e): + if e.text: + #return " %s" % info_in_brackets(e.text) + return ": \x0F%s" % e.text + else: + return "" + +def info_in_brackets(text): + return "(\x044881b6%s\x0F)" % text + +def pretty_time(secs): + times = ( + #("years", "year", 31556952), + ("weeks", "week", 604800), + ("days", "day", 86400), + ("hours", "hour", 3600), + ("minutes", "minute", 60), + ("seconds", "second", 1), + ) + if secs == 0: + return "0 seconds" + result = "" + for plural, singular, amount in times: + n, secs = divmod(secs, amount) + if n == 1: + result = result + " %s %s" % (n, singular) + elif n: + result = result + " %s %s" % (n, plural) + return result[1:] + +def onText(e): + hilight_text(e) + color = getsourcecolor(e) + to_write = prefix(e) + if e.network.me == e.target: # this is a pm + if e.window.id == e.network.norm_case(e.source): + to_write += "\x02<\x0F%s\x0F\x02>\x0F " % (format_source(e)) + else: + to_write += "\x02*\x0F%s\x0F\x02*\x0F " % (format_source(e)) + else: + if e.window.id == e.network.norm_case(e.target): + to_write += "\x02<\x0F%s\x0F\x02>\x0F " % (format_source(e)) + else: + to_write += "\x02<\x0F%s:%s\x0F\x02>\x0F " % (format_source(e), e.target) + to_write += e.text + + if e.Highlight: + e.window.write(to_write, widgets.HILIT) + else: + e.window.write(to_write, widgets.TEXT) + +def onOwnText(e): + color = getsourcecolor(e) + to_write = prefix(e) + if e.window.id == e.network.norm_case(e.target): + to_write += "\x02<\x0F%s\x0F\x02>\x0F %s" % (format_source(e), e.text) + else: + to_write += "%s->\x0F \x02*\x0F%s\x0F\x02*\x0F %s" % (color, e.target, e.text) + + e.window.write(to_write) + +def onAction(e): + hilight_text(e) + color = color = getsourcecolor(e) + to_write = "%s\x02*\x0F %s\x0F %s" % (prefix(e), format_source(e), e.text) + + if e.Highlight: + e.window.write(to_write, widgets.HILIT) + else: + e.window.write(to_write, widgets.TEXT) + +def onOwnAction(e): + color = getsourcecolor(e) + to_write = "%s\x02*\x0F %s\x0F %s" % (prefix(e), format_source(e), e.text) + + e.window.write(to_write) + +def onNotice(e): + hilight_text(e) + color = getsourcecolor(e) + to_write = prefix(e) + if e.network.me == e.target: # this is a pm + to_write += "\x02-\x0F%s\x0F\x02-\x0F " % (format_source(e)) + else: + to_write += "\x02-\x0F%s:%s\x0F\x02-\x0F " % (format_source(e), e.target) + to_write += e.text + + e.window.write(to_write, (e.Highlight and widgets.HILIT) or widgets.TEXT) + +def onOwnNotice(e): + color = getsourcecolor(e) + to_write = "%s-> \x02-\x02%s\x0F\x02-\x0F %s" % (prefix(e), e.target, e.text) + + e.window.write(to_write) + +def onCtcp(e): + color = getsourcecolor(e) + to_write = "%s\x02[\x02%s\x0F\x02]\x0F %s" % (prefix(e), format_source(e), e.text) + + if not e.quiet: + e.window.write(to_write) + +def onCtcpReply(e): + color = getsourcecolor(e) + to_write = "%s%s--- %s reply from %s:\x0F %s" % (prefix(e), color, e.name.capitalize(), format_source(e), ' '.join(e.args)) + + window = windows.manager.get_active() + if window.network != e.network: + window = windows.get_default(e.network) + window.write(to_write, widgets.TEXT) + +def onJoin(e): + if e.source == e.network.me: + to_write = "%s%s %sjoin %s" % (prefix(e), format_info_source(e), address(e), e.target) + else: + to_write = "%s%s %sjoins %s" % (prefix(e), format_info_source(e), address(e), e.target) + + e.window.write(to_write) + +def onPart(e): + if e.source == e.network.me: + to_write = "%s%s leave %s%s" % (prefix(e), format_info_source(e), e.target, text(e)) + else: + to_write = "%s%s leaves %s%s" % (prefix(e), format_info_source(e), e.target, text(e)) + + e.window.write(to_write) + +def onKick(e): + if e.source == e.network.me: + to_write = "%s%s kick %s%s" % (prefix(e), format_info_source(e), e.target, text(e)) + else: + to_write = "%s%s kicks %s%s" % (prefix(e), format_info_source(e), e.target, text(e)) + + e.window.write(to_write, (e.target == e.network.me and widgets.HILIT) or widgets.EVENT) + +def onMode(e): + if e.source == e.network.me: + to_write = "%s%s set mode:\x0F %s" % (prefix(e), format_info_source(e), e.text) + else: + to_write = "%s%s sets mode:\x0F %s" % (prefix(e), format_info_source(e), e.text) + + e.window.write(to_write) + +def onQuit(e): + to_write = "%s%s leaves%s" % (prefix(e), format_info_source(e), text(e)) + + for channame in chaninfo.channels(e.network): + if chaninfo.ison(e.network, channame, e.source): + window = windows.get(windows.ChannelWindow, e.network, channame, core) + if window: + window.write(to_write) + +def onNick(e): + color = getsourcecolor(e) + if e.source == e.network.me: + to_write = "%s%sYou are now known as %s" % (prefix(e), color, e.target) + else: + to_write = "%s%s%s is now known as %s" % (prefix(e), color, e.source, e.target) + + if e.source == e.network.me: + for window in windows.get_with(core.manager, network=e.network): + window.write(to_write) + else: + for channame in chaninfo.channels(e.network): + if chaninfo.ison(e.network,channame,e.source): + window = windows.get(windows.ChannelWindow, e.network, channame) + if window: + window.write(to_write) + +def onTopic(e): + if e.source == e.network.me: + to_write = "%s%s set topic:\x0F %s" % (prefix(e), format_info_source(e), e.text) + else: + to_write = "%s%s sets topic:\x0F %s" % (prefix(e), format_info_source(e), e.text) + + e.window.write(to_write) + +def onRaw(e): + if not e.quiet: + if e.msg[1].isdigit(): + if e.msg[1] == '332': + window = windows.get(windows.ChannelWindow, e.network, e.msg[3], core) or e.window + window.write( + "%sTopic on %s is: %s" % + (prefix(e), e.msg[3], e.text) + ) + + elif e.msg[1] == '333': + window = windows.get(windows.ChannelWindow, e.network, e.msg[3], core) or e.window + window.write( + "%sTopic on %s set by %s at time %s" % + (prefix(e), e.msg[3], e.msg[4], time.ctime(int(e.msg[5]))) + ) + + elif e.msg[1] == '329': #RPL_CREATIONTIME + pass + + elif e.msg[1] == '311': #RPL_WHOISUSER + e.window.write("* %s is %s@%s * %s" % (e.msg[3], e.msg[4], e.msg[5], e.msg[7])) + + elif e.msg[1] == '312': #RPL_WHOISSERVER + e.window.write("* %s on %s (%s)" % (e.msg[3], e.msg[4], e.msg[5])) + + elif e.msg[1] == '317': #RPL_WHOISIDLE + e.window.write("* %s has been idle for %s" % (e.msg[3], pretty_time(int(e.msg[4])))) + if e.msg[5].isdigit(): + e.window.write("* %s signed on %s" % (e.msg[3], time.ctime(int(e.msg[5])))) + + elif e.msg[1] == '319': #RPL_WHOISCHANNELS + e.window.write("* %s on channels: %s" % (e.msg[3], e.msg[4])) + + elif e.msg[1] == '330': #RPL_WHOISACCOUNT + #this appears to conflict with another raw, so if there's anything weird about it, + # we fall back on the default + if len(e.msg) == 6 and not e.msg[4].isdigit() and not e.msg[5].isdigit(): + e.window.write("* %s %s %s" % (e.msg[3], e.msg[5], e.msg[4])) + else: + e.window.write("* %s" % ' '.join(e.msg[3:])) + + else: + e.window.write("* %s" % ' '.join(e.msg[3:])) + elif e.msg[1] == 'ERROR': + e.window.write("Error: %s" % e.text) + +def onDisconnect(e): + to_write = '%s* Disconnected' % prefix(e) + if e.error: + to_write += ' (%s)' % e.error + + for window in windows.get_with(network=e.network): + if isinstance(window, windows.StatusWindow): + window.write(to_write, widgets.TEXT) + else: + window.write(to_write, widgets.EVENT) diff --git a/services/console/lib/purk/scripts/timeout.py b/services/console/lib/purk/scripts/timeout.py new file mode 100755 index 0000000..2f0f585 --- /dev/null +++ b/services/console/lib/purk/scripts/timeout.py @@ -0,0 +1,45 @@ +import time + +import ui +from conf import conf + +def setupRaw(e): + e.network._message_timeout = False + +def onSocketConnect(e): + timeout = conf.get("server_traffic_timeout", 120)*1000 + e.network._message_timeout = False + if timeout: + e.network._message_timeout_source = ui.register_timer(timeout, check_timeout, e.network) + else: + e.network._message_timeout_source = None + +def check_timeout(network): + if network._message_timeout: + network.raw("PING %s" % network.me) + timeout = conf.get("server_death_timeout", 240)*1000 + network._message_timeout_source = ui.register_timer(timeout, check_death_timeout, network) + return False + else: + network._message_timeout = True + return True # call this function again + +def check_death_timeout(network): + if network._message_timeout: + network.raw("QUIT :Server missing, presumed dead") + network.disconnect(error="The server seems to have stopped talking to us") + else: + network._message_timeout = False + timeout = conf.get("server_traffic_timeout", 120)*1000 + if timeout: + network._message_timeout_source = ui.register_timer(timeout, check_timeout, network) + else: + network._message_timeout_source = None + +def onDisconnect(e): + try: + if e.network._message_timeout_source: + e.network._message_timeout_source.unregister() + e.network._message_timeout_source = None + except AttributeError: + pass diff --git a/services/console/lib/purk/scripts/ui_script.py b/services/console/lib/purk/scripts/ui_script.py new file mode 100644 index 0000000..459de96 --- /dev/null +++ b/services/console/lib/purk/scripts/ui_script.py @@ -0,0 +1,132 @@ +import irc +import ui +import windows +import irc_script +from conf import conf + +# FIXME: meh still might want rid of these, I'm not sure yet + +def onActive(e): + e.window.activity = None + + ui.register_idle(windows.manager.set_title) + +def setupNick(e): + if e.source == e.network.me: + for w in windows.get_with(core.manager, network=e.network): + try: + w.nick_label.update(e.target) + except AttributeError: + pass + +def onExit(e): + for n in set(w.network for w in windows.manager): + if n: + n.quit() + +def setupJoin(e): + if e.source == e.network.me: + window = windows.get(windows.StatusWindow, e.network, 'status', core) + + if window and not conf.get('status'): + window.mutate(windows.ChannelWindow, e.network, e.target) + else: + window = windows.new(windows.ChannelWindow, e.network, e.target, core) + + if e.requested: + window.activate() + + e.window = windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window + +def setupText(e): + if e.target == e.network.me: + e.window = windows.new(windows.QueryWindow, e.network, e.source, core) + else: + e.window = \ + windows.get(windows.ChannelWindow, e.network, e.target, core) or \ + windows.get(windows.QueryWindow, e.network, e.source, core) or \ + e.window + +setupAction = setupText + +def setupNotice(e): + if e.target != e.network.me: + e.window = \ + windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window + +def setupOwnText(e): + e.window = \ + windows.get(windows.ChannelWindow, e.network, e.target, core) or \ + windows.get(windows.QueryWindow, e.network, e.target, core) or \ + e.window + +setupOwnAction = setupOwnText + +def setdownPart(e): + if e.source == e.network.me: + window = windows.get(windows.ChannelWindow, e.network, e.target, core) + + if window: + cwindows = list(windows.get_with( + network=window.network, + wclass=windows.ChannelWindow + )) + + if len(cwindows) == 1 and not list(windows.get_with(network=window.network, wclass=windows.StatusWindow)): + window.mutate(windows.StatusWindow, e.network, 'status') + if e.requested: + window.activate() + elif e.requested: + window.close() + +def onClose(e): + nwindows = list(windows.get_with(core.manager, network=e.window.network)) + + if isinstance(e.window, windows.ChannelWindow): + cwindows = list(windows.get_with(core.manager, + network=e.window.network, + wclass=windows.ChannelWindow + )) + + #if we only have one window for the network, don't bother to part as + # we'll soon be quitting anyway + if len(nwindows) != 1 and irc_script.ischan(e.window.network, e.window.id): + e.window.network.part(e.window.id) + + if len(nwindows) == 1: + core.events.trigger("CloseNetwork", window=e.window, network=e.window.network) + + elif isinstance(e.window, windows.StatusWindow) and conf.get('status'): + core.events.trigger("CloseNetwork", window=e.window, network=e.window.network) + for window in nwindows: + if window != e.window: + window.close() + + if len(core.manager) == 1: + windows.new(windows.StatusWindow, irc.Network(), "status", core) + +def onConnecting(e): + return + window = windows.get_default(e.network) + if window: + window.update() + +onDisconnect = onConnecting + +def setupPart(e): + e.window = windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window + +setupTopic = setupPart + +def setupKick(e): + e.window = windows.get(windows.ChannelWindow, e.network, e.channel, core) or e.window + +def setupMode(e): + if e.target != e.network.me: + e.window = windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window + +def onWindowMenu(e): + if isinstance(e.window, windows.ChannelWindow): + e.channel = e.window.id + e.network = e.window.network + core.events.trigger('ChannelMenu', e) |