# HG changeset patch # User souliane # Date 1403530976 -7200 # Node ID 246712d2e7bc4e3dda81eee71e5b739e8dc02b0a # Parent 5d89fecdf6674cb97b2ca421ec280f3743f58b50 plugin XEP-0045, text_commands: add some commands: - handle generic failure in text_commands - fix command /leave and a logging message - add commands /kick /ban /affiliate diff -r 5d89fecdf667 -r 246712d2e7bc src/plugins/plugin_misc_text_commands.py --- a/src/plugins/plugin_misc_text_commands.py Thu Jun 19 20:33:42 2014 +0200 +++ b/src/plugins/plugin_misc_text_commands.py Mon Jun 23 15:42:56 2014 +0200 @@ -131,13 +131,19 @@ log.debug("text commands took over") raise failure.Failure(exceptions.CancelError()) + def genericErrback(failure): + self.feedBack("Command failed with condition '%s'" % failure.value.condition, mess_data, profile) + return False + try: mess_data["unparsed"] = msg[1 + len(command):] # part not yet parsed of the message d = defer.maybeDeferred(self._commands[command], mess_data, profile) + d.addCallbacks(lambda ret: ret, genericErrback) # XXX: dummy callback is needed d.addCallback(retHandling) + except KeyError: self.feedBack(_("Unknown command /%s. ") % command + self.HELP_SUGGESTION, mess_data, profile) - self.debug("text commands took over") + log.debug("text commands took over") raise failure.Failure(exceptions.CancelError()) return d or mess_data # if a command is detected, we should have a deferred, else we send the message normally diff -r 5d89fecdf667 -r 246712d2e7bc src/plugins/plugin_xep_0045.py --- a/src/plugins/plugin_xep_0045.py Thu Jun 19 20:33:42 2014 +0200 +++ b/src/plugins/plugin_xep_0045.py Mon Jun 23 15:42:56 2014 +0200 @@ -45,13 +45,17 @@ "description": _("""Implementation of Multi-User Chat""") } +AFFILIATIONS = ('owner', 'admin', 'member', 'none', 'outcast') + class UnknownRoom(Exception): pass + class NotReadyYet(Exception): pass + class XEP_0045(object): # TODO: this plugin is messy, need a big cleanup/refactoring @@ -125,6 +129,10 @@ nick += '_' return self.clients[profile].join(room_jid, nick, history_options, password).addCallbacks(self.__room_joined, self.__err_joining_room, callbackKeywords={'profile': profile}, errbackArgs=[room_jid, nick, history_options, password, profile]) mess = D_("Error while joining the room %s" % room_jid.userhost()) + try: + mess += " with condition '%s'" % failure.value.condition + except AttributeError: + pass log.error(mess) self.host.bridge.newAlert(mess, D_("Group chat error"), "ERROR", profile) raise failure @@ -375,6 +383,57 @@ self.clients[profile] = SatMUCClient(self) return self.clients[profile] + def kick(self, nick, room_jid, options={}, profile_key=C.PROF_KEY_NONE): + """ + Kick a participant from the room + @param nick (str): nick of the user to kick + @param room_jid_s (JID): jid of the room + @param options (dict): attribute with extra info (reason, password) as in #XEP-0045 + @param profile_key (str): %(doc_profile_key)s + """ + profile = self.host.memory.getProfileName(profile_key) + if not self.__check_profile(profile): + raise exceptions.ProfileUnknownError("Unknown or disconnected profile") + if room_jid.userhost() not in self.clients[profile].joined_rooms: + raise UnknownRoom("This room has not been joined") + return self.clients[profile].kick(room_jid, nick, reason=options.get('reason', None)) + + def ban(self, entity_jid, room_jid, options={}, profile_key=C.PROF_KEY_NONE): + """ + Ban an entity from the room + @param entity_jid (JID): bare jid of the entity to be banned + @param room_jid_s (JID): jid of the room + @param options: attribute with extra info (reason, password) as in #XEP-0045 + @param profile_key (str): %(doc_profile_key)s + """ + assert(not entity_jid.resource) + assert(not room_jid.resource) + profile = self.host.memory.getProfileName(profile_key) + if not self.__check_profile(profile): + raise exceptions.ProfileUnknownError("Unknown or disconnected profile") + if room_jid.userhost() not in self.clients[profile].joined_rooms: + raise UnknownRoom("This room has not been joined") + return self.clients[profile].ban(room_jid, entity_jid, reason=options.get('reason', None)) + + def affiliate(self, entity_jid, room_jid, options=None, profile_key=C.PROF_KEY_NONE): + """ + Change the affiliation of an entity + @param entity_jid (JID): bare jid of the entity + @param room_jid_s (JID): jid of the room + @param options: attribute with extra info (reason, nick) as in #XEP-0045 + @param profile_key (str): %(doc_profile_key)s + """ + assert(not entity_jid.resource) + assert(not room_jid.resource) + assert('affiliation' in options) + profile = self.host.memory.getProfileName(profile_key) + if not self.__check_profile(profile): + raise exceptions.ProfileUnknownError("Unknown or disconnected profile") + if room_jid.userhost() not in self.clients[profile].joined_rooms: + raise UnknownRoom("This room has not been joined") + # TODO: handles reason and nick + return self.clients[profile].modifyAffiliationList(room_jid, [entity_jid], options['affiliation']) + # Text commands # def cmd_nick(self, mess_data, profile): @@ -429,6 +488,113 @@ """just a synonym of /leave""" return self.cmd_leave(mess_data, profile) + def cmd_kick(self, mess_data, profile): + """kick a room member + + @command (group): (nick) + - nick: the nick of the person to kick + """ + log.debug("Catched kick command") + + if mess_data['type'] != "groupchat": + self.host.plugins[C.TEXT_CMDS].feedBackWrongContext('kick', 'groupchat', mess_data, profile) + return False + + options = mess_data["unparsed"].strip().split() + try: + nick = options[0] + assert(self.isNickInRoom(mess_data["to"], nick, profile)) + except (IndexError, AssertionError): + feedback = _(u"You must provide a member's nick to kick.") + self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) + return False + + d = self.kick(nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, profile) + + def cb(dummy): + mess_data['message'] = _('%s has been kicked') % nick + if len(options) > 1: + mess_data['message'] += _(' for the following reason: %s') % options[1] + return True + d.addCallback(cb) + return d + + def cmd_ban(self, mess_data, profile): + """ban an entity from the room + + @command (group): (JID) [reason] + - JID: the JID of the entity to ban + - reason: the reason why this entity is being banned + """ + log.debug("Catched ban command") + + if mess_data['type'] != "groupchat": + self.host.plugins[C.TEXT_CMDS].feedBackWrongContext('ban', 'groupchat', mess_data, profile) + return False + + options = mess_data["unparsed"].strip().split() + try: + jid_s = options[0] + entity_jid = jid.JID(jid_s).userhostJID() + assert(entity_jid.user) + assert(entity_jid.host) + except (IndexError, jid.InvalidFormat, AssertionError): + feedback = _(u"You must provide a valid JID to ban, like in '/ban contact@example.net'") + self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) + return False + + d = self.ban(entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, profile) + + def cb(dummy): + mess_data['message'] = _('%s has been banned') % entity_jid + if len(options) > 1: + mess_data['message'] += _(' for the following reason: %s') % options[1] + return True + d.addCallback(cb) + return d + + def cmd_affiliate(self, mess_data, profile): + """affiliate an entity to the room + + @command (group): (JID) [owner|admin|member|none|outcast] + - JID: the JID of the entity to affiliate + - owner: grant owner privileges + - admin: grant admin privileges + - member: grant member privileges + - none: reset entity privileges + - outcast: ban entity + """ + log.debug("Catched affiliate command") + + if mess_data['type'] != "groupchat": + self.host.plugins[C.TEXT_CMDS].feedBackWrongContext('affiliate', 'groupchat', mess_data, profile) + return False + + options = mess_data["unparsed"].strip().split() + try: + jid_s = options[0] + entity_jid = jid.JID(jid_s).userhostJID() + assert(entity_jid.user) + assert(entity_jid.host) + except (IndexError, jid.InvalidFormat, AssertionError): + feedback = _(u"You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'") + self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) + return False + + affiliation = options[1] if len(options) > 1 else 'none' + if affiliation not in AFFILIATIONS: + feedback = _(u"You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS) + self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) + return False + + d = self.affiliate(entity_jid, mess_data["to"], {'affiliation': affiliation}, profile) + + def cb(dummy): + mess_data['message'] = _('New affiliation for %(entity)s: %(affiliation)s') % {'entity': entity_jid, 'affiliation': affiliation} + return True + d.addCallback(cb) + return d + def cmd_title(self, mess_data, profile): """change room's subject""" log.debug("Catched title command") @@ -530,7 +696,7 @@ room_jid_s = room.roomJID.userhost() log.info(_("Room [%(room)s] left (%(profile)s))") % {"room": room_jid_s, "profile": self.parent.profile}) - self.host.memory.delEntityCache(room.roomJID, self.parent.profile) + self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile) del self.plugin_parent.clients[self.parent.profile].joined_rooms[room_jid_s] self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile) else: