diff options
Diffstat (limited to 'services/console/lib/purk/scripts/irc_script.py')
-rw-r--r-- | services/console/lib/purk/scripts/irc_script.py | 588 |
1 files changed, 588 insertions, 0 deletions
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)) |