changeset 1082:246712d2e7bc

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
author souliane <souliane@mailoo.org>
date Mon, 23 Jun 2014 15:42:56 +0200
parents 5d89fecdf667
children e8731b02f5ea
files src/plugins/plugin_misc_text_commands.py src/plugins/plugin_xep_0045.py
diffstat 2 files changed, 174 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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: