Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/services/console/lib/purk/scripts/irc_script.py
diff options
context:
space:
mode:
Diffstat (limited to 'services/console/lib/purk/scripts/irc_script.py')
-rw-r--r--services/console/lib/purk/scripts/irc_script.py588
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))