from twisted.words.protocols import irc from twisted.words import iwords, ewords from twisted.python import log, failure, reflect from twisted.internet import protocol, reactor, defer from twisted.words.service import WordsRealm from twisted.cred import portal, credentials, error as ecred from twisted.python import failure from zope.interface import implements, Interface, Attribute from time import ctime, time import re USESERV = "AresServ!@ares.shadowfire.org" # A ficticious "services" WATCH=128 SILENCE=5 MODES=12 MAXCHANNELS=30 MAXBANS=60 NICKLEN=30 TOPICLEN=307 KICKLEN=307 CHANTYPES='#' PREFIX= { 'o' : '@', 'a' : '&', 'h' : '%', 'q' : '~', 'v' : '+', } PREFIXPRIO= { 'q': 0, 'a': 1, 'o': 2, 'h': 3, 'v': 4 } CHANMODES='ohvbeqa,kfL,l,psmntirRcOAQKVHGCuzN' UMODES = 'oOiwghskSaHANcCfrxeWqBFzdvtGj' PMODES = 'lvhopsmntikrRcaqOALQbSeKVfGCuzNoOiwghskSaHANcCfrxeWqBFzdvtGj' class Ares: helpText = """ARES is an extended services system which provides added user services - or maybe not, who knows.""" adminText = """At your service master! GROUP ADD : Add a group which will be given on identify. Founder represents the user who requested the group and is the designated contact. GROUP SHOW : Show details for a group GROUP MOD : Modify a group as per "ADD" GROUP REMOVE : Disband a group permenantly: GROUP LIST: List groups GROUP MEMBER ADD : Add to . The group must exist first. GROUP MEMBER FIND : Show which group the nick belongs to. GROUP MEMBER REMOVE : Removes a member from their group GROUP MEMBER FORCE : Forces a member into a group (Sets their approved status) ADMIN ADD Add an admin ADMIN DELETE Delete an admin ADMIN LIST List admins TESTMATCH Tests a kill regex against the user list""" founderText = """You may modify your group. The following commands are available to you. MEMBER INVITE MEMBER REMOVE GROUP DISBAND GROUP SHOW""" validAdmin = [] # Just a crummy class to put Ares in def aresRespond(self, user, message, connector): connector.notice(USESERV.split('!')[0], user, message) def ares_HELP(self, user, connector): if connector.realm.db.isAdmin(user): for line in self.adminText.split('\n'): connector.notice(USESERV.split('!')[0], user, line) elif connector.realm.db.isFounder(user): for line in self.founderText.split('\n'): connector.notice(USESERV.split('!')[0], user, line) else: for line in self.helpText.split('\n'): connector.notice(USESERV.split('!')[0], user, line) def ares_UNKNOWN(self, user, command, connector): connector.notice(USESERV.split('!')[0], user, "Unknown command %s. Type /msg Ares HELP for help." % command) def ares_LOGIN(self, user, command, connector): sp = command.split() if len(sp) < 2: self.aresRespond(user, "I think you're missing something...", connector) return if sp[1]=="testservicepass": self.validAdmin.append(user) self.aresRespond(user, "Lets go fast!", connector) def ares_SHOWMEMBER(self, user, command, connector): sp = command.split() if len(sp) > 1: grp = connector.realm.db.getGroup(sp[1]) if grp: self.aresRespond(user, "%s belongs to the group %s" % (sp[1], grp), connector) else: self.aresRespond(user, "%s is not in a group" % sp[1], connector) else: self.aresRespond(user, "You seem to be missing some parameters.", connector) def ares_SHOWGROUP(self, user, command, connector): sp = command.split() if len(sp) > 1: grp = connector.realm.db.showGroup(sp[1]) if grp: for line in grp: self.aresRespond(user, line, connector) else: self.aresRespond(user, "%s is not a group" % sp[1], connector) else: self.aresRespond(user, "You seem to be missing some parameters.", connector) def ares_LISTGROUPS(self, user, command, connector): sp = command.split() for ln in connector.realm.db.iterGroups(): self.aresRespond(user, " * %s" % ln, connector) def ares_ADDGROUP(self, user, command, connector): sp = command.split() if len(sp) > 3: connector.realm.db.addGroup(sp[1], sp[2], sp[3]) self.aresRespond(user, "Group added", connector) else: self.aresRespond(user, "You seem to be missing some parameters.", connector) def ares_ADDMEMBER(self, user, command, connector): sp = command.split() if len(sp) > 2: if connector.realm.db.addMember(sp[1], sp[2]): self.bitchAtUser(sp[2], connector) self.aresRespond(user, "User %s added to group %s" % (sp[1], sp[2]), connector) else: self.aresRespond(user, "You seem to be missing some parameters.", connector) def ares_REMOVEMEMBER(self, user, command, connector): sp = command.split() if len(sp) > 1: connector.realm.db.removeMember(sp[1]) self.aresRespond(user, "User %s removed from group" % (sp[1],), connector) else: self.aresRespond(user, "You seem to be missing some parameters.", connector) def ares_ADDADMIN(self, user, command, connector): sp = command.split() if len(sp) > 1: connector.realm.db.addAdmin(sp[1]) def ares_DELETEADMIN(self, user, command, connector): sp = command.split() if len(sp) > 1: connector.realm.db.deleteAdmin(sp[1]) def ares_LISTADMIN(self, user, command, connector): sp = command.split() ad = connector.realm.db.getAdmin() if ad: for a in ad: self.aresRespond(user, " * %s" % a, connector) def ares_RAW(self, user, command, connector): sp = ' '.join(command.split()[1:]) print "<", sp connector.sendLine(sp) def ares_TESTMATCH(self, user, command, connector): sp = command.split()[1:] regex = re.compile(sp[0], re.IGNORECASE) print "Searching ", sp[0] self.aresRespond(user, "Searching for matches to %s" % sp[0], connector) if len(sp)>1: lim = int(sp[1]) else: lim = 20 matches = [] for cuser in connector.users.keys(): mask = "%s:%s" % (connector.users[cuser][0], connector.users[cuser][2]) if regex.search(mask): matches.append(mask) if matches: self.aresRespond(user, "Regex matches %s users" % len(matches), connector) for match in matches[:lim]: self.aresRespond(user, "Matches: %s" % match, connector) else: self.aresRespond(user, "No matches", connector) def ares_FORCEAUTH(self, user, command, connector): sp = command.split()[1:] connector.realm.db.authoriseUser(sp[0].lower()) group, mask = connector.realm.db.getGroupAndMask(sp[0].lower()) if mask: connector.forceMask(sp[0].lower(), mask) self.aresRespond(user, "User mask forced", connector) def ares_YES(self, user, command, connector): connector.realm.db.authoriseUser(user) connector.forceMask(user, connector.realm.db.getMask(user)) self.aresRespond(user, "Your mask has been confirmed. It will be applied the next time you identify for your nickname", connector) def ares_NO(self, user, command, connector): connector.realm.db.removeMember(user) self.aresRespond(user, "You have revoked your group membership invite", connector) def ares_DELGROUP(self, user, command, connector): sp = command.split() if len(sp) > 1: connector.realm.db.deleteGroup(sp[1]) self.aresRespond(user, "Group deleted", connector) else: self.aresRespond(user, "You seem to be missing some parameters.", connector) def ares_MODGROUP(self, user, command, connector): sp = command.split() if len(sp) > 3: connector.realm.db.modGroup(sp[1], sp[2], sp[3]) self.aresRespond(user, "Group modified", connector) else: self.aresRespond(user, "You seem to be missing some parameters.", connector) def subMenu(self, user, command, md, connector): md[command.lower().split()[0]](user, command, connector) def ares_GROUP_MEMBER(self, user, command, connector): memberCmds = { 'add':self.ares_ADDMEMBER, 'find':self.ares_SHOWMEMBER, 'remove':self.ares_REMOVEMEMBER, 'force':self.ares_FORCEAUTH } command = ' '.join(command.split()[1:]) self.subMenu(user, command, memberCmds, connector) def ares_GROUP(self, user, command, connector): groupCmds = { 'add':self.ares_ADDGROUP, 'remove':self.ares_DELGROUP, 'show':self.ares_SHOWGROUP, 'mod':self.ares_MODGROUP, 'list':self.ares_LISTGROUPS, 'member':self.ares_GROUP_MEMBER } command = ' '.join(command.split()[1:]) self.subMenu(user, command, groupCmds, connector) def ares_ADMIN(self, user, command, connector): adminCmds = { 'add':self.ares_ADDADMIN, 'delete':self.ares_DELETEADMIN, 'list':self.ares_LISTADMIN } command = ' '.join(command.split()[1:]) self.subMenu(user, command, adminCmds, connector) def bitchAtUser(self, user, connector): group, mask = connector.realm.db.getGroupAndMask(user) if not group: print "Thats odd..." return self.aresRespond(user, "You have been invited to join the group '%s'. This will give you the host mask of '%s'." % ( group, mask ), connector) self.aresRespond(user, "Please confirm or deny your invite by messaging 'yes' or 'no' to me.", connector) def aresUserCommand(self, user, command, connector): sp = command.lower().split() if connector.realm.db.unauthUser(user): print "This is a user" if sp[0]=="yes": self.ares_YES(user, command, connector) elif sp[0]=="no": self.ares_NO(user, command, connector) else: self.aresRespond(user, "I never asked you anything.", connector) def aresFounderCommand(self, user, command, connector): sp = command.lower().split() print sp, "Founder" group = connector.realm.db.isFounder(user) if sp[0] == "member": if sp[1] == "invite": if connector.realm.db.addMember(sp[2], group): self.bitchAtUser(sp[2], connector) self.aresRespond(user, "%s has been invited to %s" % (sp[2], group), connector) if sp[1] == "remove" and connector.realm.db.getGroupAndMask(sp[2])[0]==group: connector.realm.db.removeMember(sp[2]) self.aresRespond(user, "%s has been removed from %s" % (sp[2], group), connector) elif sp[0] == "group": if sp[1] == "disband": self.aresRespond(user, "Your group %s has been removed" % group, connector) if sp[1] == "show": grp = connector.realm.db.showGroup(group) if grp: for line in grp: self.aresRespond(user, line, connector) else: self.aresRespond(user, "%s is not a group" % group, connector) else: self.ares_UNKNOWN(user, command, connector) def aresCommand(self, user, command, connector): print "-Ares Command-" if not 'r' in connector.users[user.lower()][3]: self.aresRespond(user, "Dude, you're not identified for your nick. How do i know who you are?!", connector) return adminCmds = {'group':self.ares_GROUP, 'admin':self.ares_ADMIN, 'testmatch':self.ares_TESTMATCH, 'rawc':self.ares_RAW } splitCmd = command.lower().split() print connector.realm.db.isFounder(user) if splitCmd[0]=="help": print "Help" self.ares_HELP(user, connector) # derive permissions to commands from here on elif connector.realm.db.isAdmin(user): if splitCmd[0] in adminCmds.keys(): print "-admin commands-" adminCmds[splitCmd[0]](user, command, connector) elif connector.realm.db.unauthUser(user): print "-unauth users..-" self.aresUserCommand(user, command, connector) elif connector.realm.db.isFounder(user): print "-is founder-" self.aresFounderCommand(user, command, connector) # Default to unknown command. else: self.ares_UNKNOWN(user, command, connector) class User(object): implements(iwords.IUser) realm = None mind = None authorised = False shunned = True # All users start shunned def __init__(self, name, mask=None): self.name = name self.mask = mask self.groups = [] self.lastMessage = time() def authoriseUser(self): self.authorised = True self.shunned = False def shunUser(self): shunned = True def loggedIn(self, realm, mind): self.realm = realm self.mind = mind self.signOn = time() def join(self, group): def cbJoin(result): self.groups.append(group) return result return group.add(self.mind).addCallback(cbJoin) def leave(self, group, reason=None): def cbLeave(result): self.groups.remove(group) return result return group.remove(self.mind, reason).addCallback(cbLeave) def send(self, recipient, message): self.lastMessage = time() return recipient.receive(self.mind, recipient, message) def itergroups(self): return iter(self.groups) def logout(self): for g in self.groups[:]: self.leave(g) class Group(object): implements(iwords.IGroup) def __init__(self, name): self.name = name self.users = {} self.meta = { "topic": "", "topic_author": "", "modes": ['n', 't'], "bans": [('foo!fo@foo.net', 'foo', 1)], "prefixes": {} # Mode flags, like @ and + etc... } def getModes(self): return '+' + ''.join(self.meta['modes']) def isBanned(self, nick): return nick in self.meta['bans'] def banUser(self, user, mask): self.meta['bans'].append((mask, user, int(time()))) def _ebUserCall(self, err, p): return failure.Failure(Exception(p, err)) def _cbUserCall(self, results): for (success, result) in results: if not success: user, err = result.value # XXX self.remove(user, err.getErrorMessage()) def modeUser(self, nickname, mode): if nickname in self.users: if self.meta['prefixes'].get(nickname, False): # Only bother changing if the prefix is a higher "priority" (inverse for whatever reason i decided) if PREFIXPRIO[self.meta['prefixes'][nickname]] > PREFIXPRIO[mode]: self.meta['prefixes'][nickname] = mode else: # obviously set it anyway if the user has no prefix.. self.meta['prefixes'][nickname] = mode def getUserMode(self, nickname): return self.meta['prefixes'].get(nickname, None) def getPrefixedNames(self): print "GET NAMES" names = [] print self.users.keys() for n in self.users.keys(): if n in self.meta['prefixes'].keys(): names.append(PREFIX[self.meta['prefixes'][n]]+n) else: names.append(n) print names, self.meta['prefixes'] return names def add(self, user): assert iwords.IChatClient.providedBy(user), "%r is not a chat client" % (user,) if user.name not in self.users: additions = [] self.users[user.name] = user for p in self.users.itervalues(): if p is not user: d = defer.maybeDeferred(p.userJoined, self, user) d.addErrback(self._ebUserCall, p=p) additions.append(d) defer.DeferredList(additions).addCallback(self._cbUserCall) return defer.succeed(None) def remove(self, user, reason=None): assert reason is None or isinstance(reason, unicode) try: del self.users[user.name] del self.meta['prefixes'][user.name] except KeyError: pass else: removals = [] for p in self.users.itervalues(): if p is not user: d = defer.maybeDeferred(p.userLeft, self, user, reason) d.addErrback(self._ebUserCall, p=p) removals.append(d) defer.DeferredList(removals).addCallback(self._cbUserCall) return defer.succeed(None) def size(self): return defer.succeed(len(self.users)) def receive(self, sender, recipient, message): assert recipient is self receives = [] for p in self.users.itervalues(): if p is not sender: d = defer.maybeDeferred(p.receive, sender, self, message) d.addErrback(self._ebUserCall, p=p) receives.append(d) defer.DeferredList(receives).addCallback(self._cbUserCall) return defer.succeed(None) def setMetadata(self, meta): self.meta = meta sets = [] for p in self.users.itervalues(): d = defer.maybeDeferred(p.groupMetaUpdate, self, meta) d.addErrback(self._ebUserCall, p=p) sets.append(d) defer.DeferredList(sets).addCallback(self._cbUserCall) return defer.succeed(None) def iterusers(self): # XXX Deferred? return iter(self.users.values()) class IRCRealm(WordsRealm): # Store server state crap here... def __init__(self, db, *a, **kw): super(IRCRealm, self).__init__(*a, **kw) self.users = {} self.groups = {} self.db = db def itergroups(self): return defer.succeed(self.groups.itervalues()) def requestAvatar(self, avatarId, mind, mask, *interfaces): if isinstance(avatarId, str): avatarId = avatarId.decode(self._encoding) def gotAvatar(avatar): if avatar.realm is not None: raise ewords.AlreadyLoggedIn() for iface in interfaces: facet = iface(avatar, None) if facet is not None: avatar.loggedIn(self, mind) mind.name = avatarId mind.realm = self mind.avatar = avatar return iface, facet, self.logoutFactory(avatar, facet) raise NotImplementedError(self, interfaces) try: return self.getUser(avatarId, mask=mask).addCallback(gotAvatar) except: pass def createUser(self, name, mask=None): assert isinstance(name, unicode) def cbLookup(user): return failure.Failure(ewords.DuplicateUser(name)) def ebLookup(err): err.trap(ewords.NoSuchUser) return self.userFactory(name, mask=mask) name = name.lower() d = self.lookupUser(name) d.addCallbacks(cbLookup, ebLookup) d.addCallback(self.addUser) return d def userFactory(self, name, mask=None): return User(name, mask) def getUser(self, name, mask=None): assert isinstance(name, unicode) if self.createUserOnRequest: def ebUser(err): err.trap(ewords.DuplicateUser) return self.lookupUser(name) return self.createUser(name, mask=mask).addErrback(ebUser) return self.lookupUser(name) def addUser(self, user): if user.name in self.users: return defer.fail(failure.Failure(ewords.DuplicateUser())) self.users[user.name] = user return defer.succeed(user) def addGroup(self, group): if group.name in self.groups: return defer.fail(failure.Failure(ewords.DuplicateGroup())) self.groups[group.name] = group return defer.succeed(group) def lookupUser(self, name): assert isinstance(name, unicode) name = name.lower() try: user = self.users[name] except KeyError: return defer.fail(failure.Failure(ewords.NoSuchUser(name))) else: return defer.succeed(user) def lookupGroup(self, name): assert isinstance(name, unicode) name = name.lower() try: group = self.groups[name] except KeyError: return defer.fail(failure.Failure(ewords.NoSuchGroup(name))) else: return defer.succeed(group) class IRCService(irc.IRC): implements(iwords.IChatClient) # A list of IGroups in which I am participating groups = None AresService = Ares() # A no-argument callable I should invoke when I go away logout = None # An IUser we use to interact with the chat service avatar = None name = None # need this to exist, even if it doesn't really # To whence I belong realm = None # How to handle unicode (TODO: Make this customizable on a per-user basis) encoding = 'utf-8' requirePassword = True rPass = None users = {} # Twisted callbacks #def connectionMade(self): # self.irc_PRIVMSG = self.irc_USESERV_PRIVMSG def connectionLost(self, reason): if self.logout is not None: self.logout() self.avatar = None # Make sendMessage a bit more useful to us def sendMessage(self, command, *parameter_list, **kw): if not kw.has_key('prefix'): kw['prefix'] = self.hostname if not kw.has_key('to'): if self.name: kw['to'] = self.name.encode(self.encoding) else: kw['to'] = self.realm.name arglist = [self, command, kw['to']] + list(parameter_list) irc.IRC.sendMessage(*arglist, **kw) def userJoined(self, group, user): self.join("%s!%s@%s" % (user.name, user.name, self.hostname),'#' + group.name) def userLeft(self, group, user, reason=None): assert reason is None or isinstance(reason, unicode) self.part("%s!%s@%s" % (user.name, user.name, self.hostname), '#' + group.name, (reason or u"leaving").encode(self.encoding, 'replace')) def receive(self, sender, recipient, message): print "Recieve line <%s> %s: %s" % (sender, recipient, message) #>> :glyph!glyph@adsl-64-123-27-108.dsl.austtx.swbell.net PRIVMSG glyph_ :hello if iwords.IGroup.providedBy(recipient): recipientName = '#' + recipient.name else: recipientName = recipient.name text = message.get('text', '') for L in text.splitlines(): self.privmsg( '%s!%s@%s' % (sender.name, sender.name, self.hostname), recipientName, L) def groupMetaUpdate(self, group, meta): if 'topic' in meta: topic = meta['topic'] author = meta.get('topic_author', '') self.topic( self.name, '#' + group.name, topic, '%s!%s@%s' % (author, author, self.hostname) ) # irc.IRC callbacks - starting with login related stuff. nickname = None password = None def irc_PASS(self, prefix, params): """Password message -- Register a password. Parameters: [REQUIRED] Note that IRC requires the client send this *before* NICK and USER. """ self.password = params[-1] def irc_NICK(self, prefix, params): """Nick message -- Set your nickname. Parameters: [REQUIRED] """ try: nickname = params[0].decode(self.encoding) except UnicodeDecodeError: self.privmsg( USESERV, nickname, 'Your nickname is cannot be decoded. Please use ASCII or UTF-8.') self.transport.loseConnection() return password = self.password self.password = None if len(params) > 4: # NICK ['colin', '1', '1162966984', 'root', 'jupiter.netlink.za.net', 'devsrv.shadowfire.org', '986481221', 'root'] self.logInAs(nickname, password, hostname=params[4], ident=params[3], contrc=params[5]) else: self.logInAs(nickname, password) def irc_USER(self, prefix, params): """User message -- Set your realname. Parameters: """ # Note: who gives a crap about this? The IUser has the real # information we care about. Save it anyway, I guess, just # for fun. self.realname = params[-1] def irc_USESERV_PRIVMSG(self, prefix, params): """Send a (private) message. Parameters: """ target = params[0] password = params[-1] if self.nickname is None: # XXX Send an error response here self.transport.loseConnection() elif target.lower() != "nickserv": self.privmsg( USESERV, self.nickname, "Denied. ") else: nickname = self.nickname self.nickname = None self.logInAs(nickname, password) def getPrefixMapping(self): k = PREFIX.keys() return '(%s)%s' % (''.join(k), ''.join([ PREFIX[i] for i in k ])) def logInAs(self, nickname, password, hostname=None, ident=None, contrc=None): if not contrc: print "Standard user? On a services server??" if self.requirePassword: if self.rPass == self.password: d = self.factory.portal.login("!@userCloud", credentials.UsernamePassword(nickname, password), self, iwords.IUser) d.addCallbacks(self._cbLogin, self._ebLogin, errbackArgs=(nickname,)) else: #del self.irc_NICK del self.irc_PRIVMSG del self.irc_PING del self.irc_PONG print "Castrating the user untill it goes away" else: mask = "%s@%s" % (ident, hostname) d = self.factory.portal.login(mask, credentials.UsernamePassword(nickname, password), self, iwords.IUser) d.addCallbacks(lambda _: None) _welcomeMessages = [ (irc.RPL_WELCOME, ":connected to Apollo"), (irc.RPL_YOURHOST, ":Your host is %(serviceName)s, running version %(serviceVersion)s"), (irc.RPL_CREATED, ":This server was created on %(creationDate)s"), (irc.RPL_MYINFO, "%(serviceName)s %(serviceVersion)s %(UMODES)s %(PMODES)s"), (irc.RPL_MYINFO, "MAP KNOCK SAFELIST HCN WATCH=%(WATCH)s SILENCE=%(SILENCE)s MODES=%(MODES)s " + "MAXCHANNELS=%(MAXCHANNELS)s MAXBANS=%(MAXBANS)s NICKLEN=%(NICKLEN)s " + "TOPICLEN=%(TOPICLEN)s KICKLEN=%(KICKLEN)s CHANTYPES=%(CHANTYPES)s" + "PREFIX=%(PREFIX)s CHANMODES=%(CHANMODES)s are supported by this server") ] def _cbLogin(self, (iface, avatar, logout)): assert iface is iwords.IUser, "Realm is buggy, got %r" % (iface,) # Let them send messages to the world #del self.irc_PRIVMSG # By deleting the method? Hello? self.avatar = avatar self.logout = logout self.realm = avatar.realm self.hostname = self.realm.name info = { "serviceName": self.hostname, "serviceVersion": "Apollo-0.1", "creationDate": ctime(), "UMODES": UMODES, "PMODES": PMODES, "WATCH": WATCH, "SILENCE": SILENCE, "MODES": MODES, "MAXCHANNELS" : MAXCHANNELS, "MAXBANS" : MAXBANS, "NICKLEN" : NICKLEN, "TOPICLEN" : TOPICLEN, "KICKLEN" : KICKLEN, "CHANTYPES" : CHANTYPES, "PREFIX" : self.getPrefixMapping(), "CHANMODES": CHANMODES } for code, text in self._welcomeMessages: self.sendMessage(code, text % info) def _ebLogin(self, err, nickname): if err.check(ewords.AlreadyLoggedIn): self.privmsg( USESERV, nickname, "Already logged in. No pod people allowed!") elif err.check(ecred.UnauthorizedLogin): self.privmsg( USESERV, nickname, "Login failed. Goodbye.") else: log.msg("Unhandled error during login:") log.err(err) self.privmsg( USESERV, nickname, "Server error during login. Sorry.") self.transport.loseConnection() # Great, now that's out of the way, here's some of the interesting # bits def irc_PING(self, prefix, params): """Ping message Parameters: [ ] """ if self.realm is not None: if not self.hostname: self.hostname = self.realm.name self.sendMessage('PONG', self.hostname) def irc_QUIT(self, prefix, params): """Quit Parameters: [ ] """ #self.transport.loseConnection() def kick(self, kicker, channel, user, reason=None): if channel[0] not in '&#!+': channel = '#' + channel if reason: self.sendLine(":%s!@userCloud KICK %s %s :%s" % (kicker, channel, user, reason)) else: self.sendLine(":%s!@userCloud KICK %s %s" % (kicker, channel, user)) def irc_KILL(self, prefix, params): #print "Someone got killed, oh dears!", params pass def irc_KICK(self, prefix, params): try: channelName = params[0].decode(self.encoding) userName = params[1].decode(self.encoding) reason = params[2].decode(self.encoding) or None except UnicodeDecodeError: self.sendMessage( irc.ERR_NOSUCHNICK, params[0], ":No such nickname/channel (could not decode your unicode!)") return if channelName[0]=='#': channelName = channelName[1:] print "Kicking ", userName, " from #"+channelName, "(%s)"% reason def cbRemoveUser(user, group): print "Ok.." print user, group #self.userLeft(user, "#"+group.name) for users in group.iterusers(): users.kick(self.avatar.name, "#"+group.name, user.name, reason) group.remove(user) def ebUser(err, gp): print "Error :/" err.trap(ewords.NoSuchUser) self.sendMessage(irc.ERR_NOSUCHUSER, params[0], ":That user doesn't exist.") def cbUserKicked(group): print "Got group", group.name if userName in [user.name.decode() for user in group.iterusers()]: d = self.realm.lookupUser(userName) d.addCallbacks(cbRemoveUser, ebUser, callbackArgs=(group,)) else: self.sendMessage(irc.ERR_NOSUCHUSER, params[0], ":That user doesn't exist.") def ebGroup(err): err.trap(ewords.NoSuchGroup) self.sendMessage(irc.ERR_NOSUCHCHANNEL, params[0], ":That channel doesn't exist.") d = self.realm.lookupGroup(channelName) d.addCallbacks(cbUserKicked, ebGroup) def channelMode(self, user, channel, mode, *args): print "MODES ", mode, args self.sendLine(":%s %s %s %s %s %s" % ( self.hostname, irc.RPL_CHANNELMODEIS, user, channel, mode, ' '.join(args))) def setChannelMode(self, user, channel, mode, *args): self.sendLine(":%s MODE %s %s %s" % (user, channel, mode, ' '.join(args))) def channelBans(self, user, group): """ Send channel bans to user param user: nickname of user to send command to param group: C{Group} of channel ban list to send """ #>> :Caladan.ShadowFire.ORG 367 colin_ #42 lulu!*@* Scrye 1161169996 #>> :Caladan.ShadowFire.ORG 368 colin_ #42 :End of Channel Ban List print "Getting bans.." for banMask, banAuthor, banAge in group.meta['bans']: self.sendLine(":%s %s %s #%s %s %s %s" % ( self.hostname, irc.RPL_BANLIST, user, group.name, banMask, banAuthor, int(time())-banAge) ) self.sendLine(":%s %s %s #%s :End of Channel Ban List" % ( self.hostname, irc.RPL_ENDOFBANLIST, user, group.name) ) def _channelMode(self, group, modes=None, *targets): #print "%s/%s by %s " % ('#'+group.name, modes, self.avatar.name) # some debug # TODO - Implement ban lists # invite lists (UnrealIRCd feature) def applyMode(m, *rest): print m, rest if m in CHANMODES: if not rest: # no user target, must be a channel mode group.meta['modes'].append(mode) if m=='b': self.channelBans(self.avatar.name, group) elif m in PREFIX.keys(): # Is a user prefix mode group.modeUser(self.avatar.name, m) elif m == 'b': group.banUser(self.avatar.name, rest) self.setChannelMode(self.avatar.name+"!@userCloud", "#" + group.name, "+%s" % m, *rest) # Set a mode if group and modes and self.avatar: if modes[0]=='+' or modes[0]=='-': modeList = modes[1:] if targets: for mode, target in zip(modeList, targets): applyMode(mode, target) else: for mode in modeList: applyMode(mode) elif modes[0]=='b': self.channelBans(self.avatar.name, group) # List modes elif not group: print "I don't have this group, and I don't care" elif not self.avatar: self.channelMode(self.realm.name, '#' + group.name, group.getModes()) else: self.channelMode(self.name, '#' + group.name, group.getModes()) def _userMode(self, user, modes=None): #self.notice(USESERV, user.name, "This server doesn't care about umodes, don't bother setting them") print user.name, modes #if modes: # self.sendMessage( # irc.ERR_UNKNOWNMODE, # ":Unknown MODE flag.") #elif user is self.avatar: #self.sendMessage( # irc.RPL_UMODEIS, user.name, # modes) #else: # self.sendMessage( # irc.ERR_USERSDONTMATCH, # ":You can't look at someone else's modes.") def irc_MODE(self, prefix, params): print params try: channelOrUser = params[0].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.ERR_NOSUCHNICK, params[0], ":No such nickname (could not decode your unicode!)") return if channelOrUser == "Ares": print "Ares mode", params return if channelOrUser.startswith('#'): # Try channel mode def ebGroup(err): err.trap(ewords.NoSuchGroup) self.sendMessage(irc.ERR_NOSUCHCHANNEL, params[0], ":That channel doesn't exist.") d = self.realm.lookupGroup(channelOrUser[1:]) d.addCallbacks(self._channelMode, ebGroup, callbackArgs=tuple(params[1:])) else: def ebUser(err): self.sendMessage(irc.ERR_NOSUCHNICK,":No such nickname.") d = self.realm.lookupUser(channelOrUser) d.addCallbacks(self._userMode, ebUser, callbackArgs=tuple(params[1:])) def irc_USERHOST(self, prefix, params): """Userhost message Parameters: *( SPACE ) [Optional] """ pass def irc_PRIVMSG(self, prefix, params): """Send a (private) message. Parameters: """ print prefix, params print "Privmsg ", params, "from", prefix try: targetName = params[0].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.ERR_NOSUCHNICK, targetName, ":No such nick/channel (could not decode your unicode!)") return messageText = params[-1] if targetName.startswith('#'): target = self.realm.lookupGroup(targetName[1:]) elif targetName.lower() == "aresserv": self.AresService.aresCommand(prefix, messageText, self) else: target = self.realm.lookupUser(targetName).addCallback(lambda user: user.mind) def cbTarget(targ): if targ is not None: return self.avatar.send(targ, {"text": messageText}) def ebTarget(err): self.sendMessage( irc.ERR_NOSUCHNICK, targetName, ":No such nick/channel.") if targetName.lower()=="aresserv": pass # Part of "shun user untill identified" concept, to deter spammers. #elif not self.avatar.shunned: # target.addCallbacks(cbTarget, ebTarget) #else: # self.notice(USESERV, self.avatar.name, "You are shunned and cannot send messages. ") else: target.addCallbacks(cbTarget, ebTarget) def irc_JOIN(self, prefix, params): """Join message Parameters: ( *( "," ) [ *( "," ) ] ) """ try: groupName = params[0].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.IRC_NOSUCHCHANNEL, params[0], ":No such channel (could not decode your unicode!)") return if groupName.startswith('#'): groupName = groupName[1:] def cbGroup(group): def cbJoin(ign): self.userJoined(group, self) self.names( self.name, '#' + group.name, group.getPrefixedNames()) self._sendTopic(group) if self.avatar and group.isBanned(self.avatar.name): # user is banned from channel self.notice(USESERV, self.avatar.name, "Sorry, you have been banned from #" + group.name) return None if self.avatar: return self.avatar.join(group).addCallback(cbJoin) def ebGroup(err): # Try to add channel on empty join. def reallyFail(groupName): self.sendMessage(irc.ERR_NOSUCHCHANNEL, '#' + groupName, ": Cannot join that channel") def joinChannel(_): self.realm.getGroup(groupName).addCallbacks(cbGroup, reallyFail) # Make the user +o self.realm.addGroup(Group(groupName)).addCallbacks(joinChannel, reallyFail) self.realm.getGroup(groupName).addCallbacks(cbGroup, ebGroup) def irc_PART(self, prefix, params): """Part message Parameters: *( "," ) [ ] """ try: groupName = params[0].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.ERR_NOTONCHANNEL, params[0], ":Could not decode your unicode!") return if groupName.startswith('#'): groupName = groupName[1:] if len(params) > 1: reason = params[1].decode('utf-8') else: reason = None def cbGroup(group): def cbLeave(result): self.userLeft(group, self, reason) return self.avatar.leave(group, reason).addCallback(cbLeave) def ebGroup(err): err.trap(ewords.NoSuchGroup) self.sendMessage( irc.ERR_NOTONCHANNEL, '#' + groupName, ":" + err.getErrorMessage()) self.realm.lookupGroup(groupName).addCallbacks(cbGroup, ebGroup) def irc_NAMES(self, prefix, params): """Names message Parameters: [ *( "," ) [ ] ] """ #<< NAMES #python #>> :benford.openprojects.net 353 glyph = #python :Orban ... @glyph ... Zymurgy skreech #>> :benford.openprojects.net 366 glyph #python :End of /NAMES list. try: channel = params[-1].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.ERR_NOSUCHCHANNEL, params[-1], ":No such channel (could not decode your unicode!)") return if channel.startswith('#'): channel = channel[1:] def cbGroup(group): self.names( self.name, '#' + group.name, group.getPrefixedNames()) def ebGroup(err): err.trap(ewords.NoSuchGroup) # No group? Fine, no names! self.names( self.name, '#' + group.name, []) self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup) def irc_TOPIC(self, prefix, params): """Topic message Parameters: [ ] """ try: channel = params[0].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.ERR_NOSUCHCHANNEL, ":That channel doesn't exist (could not decode your unicode!)") return if channel.startswith('#'): channel = channel[1:] if len(params) > 1: self._setTopic(channel, params[1]) else: self._getTopic(channel) def _sendTopic(self, group): topic = group.meta.get("topic") author = group.meta.get("topic_author") or "" date = group.meta.get("topic_date", 0) if topic: self.topic(self.name, '#' + group.name, topic) self.topicAuthor(self.name, '#' + group.name, author, date) def _getTopic(self, channel): #<< TOPIC #python #>> :benford.openprojects.net 332 glyph #python : I really did. I sprained all my toes. #>> :benford.openprojects.net 333 glyph #python itamar|nyc 994713482 def ebGroup(err): err.trap(ewords.NoSuchGroup) self.sendMessage( irc.ERR_NOSUCHCHANNEL, '=', channel, ":That channel doesn't exist.") self.realm.lookupGroup(channel).addCallbacks(self._sendTopic, ebGroup) def _setTopic(self, channel, topic): def cbGroup(group): newMeta = group.meta.copy() newMeta['topic'] = topic newMeta['topic_author'] = self.name newMeta['topic_date'] = int(time()) def ebSet(err): self.sendMessage( irc.ERR_CHANOPRIVSNEEDED, "#" + group.name, ":You need to be a channel operator to do that.") if 't' in newMeta['modes']: if newMeta['prefixes'].get(self.name, None): if PREFIXPRIO[newMeta['prefixes'][self.name]] < 3: return group.setMetadata(newMeta).addErrback(ebSet) return ebSet(None) else: return group.setMetadata(newMeta).addErrback(ebSet) def ebGroup(err): err.trap(ewords.NoSuchGroup) self.sendMessage( irc.ERR_NOSUCHCHANNEL, '=', channel, ":That channel doesn't exist.") self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup) def list(self, channels): """Send a group of LIST response lines @type channel: C{list} of C{(str, int, str)} @param channel: Information about the channels being sent: their name, the number of participants, and their topic. """ for (name, size, topic) in channels: self.sendMessage(irc.RPL_LIST, name, str(size), ":" + topic) self.sendMessage(irc.RPL_LISTEND, ":End of /LIST") def irc_LIST(self, prefix, params): """List query Return information about the indicated channels, or about all channels if none are specified. Parameters: [ *( "," ) [ ] ] """ #<< list #python #>> :orwell.freenode.net 321 exarkun Channel :Users Name #>> :orwell.freenode.net 322 exarkun #python 358 :The Python programming language #>> :orwell.freenode.net 323 exarkun :End of /LIST if params: # Return information about indicated channels try: channels = params[0].decode(self.encoding).split(',') except UnicodeDecodeError: self.sendMessage( irc.ERR_NOSUCHCHANNEL, params[0], ":No such channel (could not decode your unicode!)") return groups = [] for ch in channels: if ch.startswith('#'): ch = ch[1:] groups.append(self.realm.lookupGroup(ch)) groups = defer.DeferredList(groups, consumeErrors=True) groups.addCallback(lambda gs: [r for (s, r) in gs if s]) else: # Return information about all channels groups = self.realm.itergroups() def cbGroups(groups): def gotSize(size, group): return group.name, size, group.meta.get('topic') d = defer.DeferredList([ group.size().addCallback(gotSize, group) for group in groups]) d.addCallback(lambda results: self.list([r for (s, r) in results if s])) return d groups.addCallback(cbGroups) def _channelWho(self, group): self.who(self.name, '#' + group.name, [(m.name, self.hostname, self.realm.name, m.name, "H", 0, m.name) for m in group.iterusers()]) def _userWho(self, user): self.sendMessage(irc.RPL_ENDOFWHO, ":User /WHO not implemented") def irc_WHO(self, prefix, params): """Who query Parameters: [ [ "o" ] ] """ #<< who #python #>> :x.opn 352 glyph #python aquarius pc-62-31-193-114-du.blueyonder.co.uk y.opn Aquarius H :3 Aquarius # ... #>> :x.opn 352 glyph #python foobar europa.tranquility.net z.opn skreech H :0 skreech #>> :x.opn 315 glyph #python :End of /WHO list. ### also #<< who glyph #>> :x.opn 352 glyph #python glyph adsl-64-123-27-108.dsl.austtx.swbell.net x.opn glyph H :0 glyph #>> :x.opn 315 glyph glyph :End of /WHO list. if not params: self.sendMessage(irc.RPL_ENDOFWHO, ":/WHO not supported.") return try: channelOrUser = params[0].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.RPL_ENDOFWHO, params[0], ":End of /WHO list (could not decode your unicode!)") return if channelOrUser.startswith('#'): def ebGroup(err): err.trap(ewords.NoSuchGroup) self.sendMessage( irc.RPL_ENDOFWHO, channelOrUser, ":End of /WHO list.") d = self.realm.lookupGroup(channelOrUser[1:]) d.addCallbacks(self._channelWho, ebGroup) else: def ebUser(err): err.trap(ewords.NoSuchUser) self.sendMessage( irc.RPL_ENDOFWHO, channelOrUser, ":End of /WHO list.") d = self.realm.lookupUser(channelOrUser) d.addCallbacks(self._userWho, ebUser) def irc_WHOIS(self, prefix, params): """Whois query Parameters: [ ] *( "," ) """ def cbUser(user): self.whois( self.name, user.name, user.name, "userCloud", user.name, self.realm.name, 'For Honour!', False, int(time() - user.lastMessage), user.signOn, ['#' + group.name for group in user.itergroups()]) def ebUser(err): err.trap(ewords.NoSuchUser) self.sendMessage( irc.ERR_NOSUCHNICK, params[0], ":No such nick/channel") try: user = params[0].decode(self.encoding) except UnicodeDecodeError: self.sendMessage( irc.ERR_NOSUCHNICK, params[0], ":No such nick/channel") return self.realm.lookupUser(user).addCallbacks(cbUser, ebUser) def irc_OPER(self, prefix, params): """Oper message Parameters: """ self.sendMessage(irc.ERR_NOOPERHOST, ":O-lines not applicable")