view src/plugins/plugin_xep_0077.py @ 1963:a2bc5089c2eb

backend, frontends: message refactoring (huge commit): /!\ several features are temporarily disabled, like notifications in frontends next step in refactoring, with the following changes: - jp: updated jp message to follow changes in backend/bridge - jp: added --lang, --subject, --subject_lang, and --type options to jp message + fixed unicode handling for jid - quick_frontend (QuickApp, QuickChat): - follow backend changes - refactored chat, message are now handled in OrderedDict and uid are kept so they can be updated - Message and Occupant classes handle metadata, so frontend just have to display them - Primitivus (Chat): - follow backend/QuickFrontend changes - info & standard messages are handled in the same MessageWidget class - improved/simplified handling of messages, removed update() method - user joined/left messages are merged when next to each other - a separator is shown when message is received while widget is out of focus, so user can quickly see the new messages - affiliation/role are shown (in a basic way for now) in occupants panel - removed "/me" messages handling, as it will be done by a backend plugin - message language is displayed when available (only one language per message for now) - fixed :history and :search commands - core (constants): new constants for messages type, XML namespace, entity type - core: *Message methods renamed to follow new code sytle (e.g. sendMessageToBridge => messageSendToBridge) - core (messages handling): fixed handling of language - core (messages handling): mes_data['from'] and ['to'] are now jid.JID - core (core.xmpp): reorganised message methods, added getNick() method to client.roster - plugin text commands: fixed plugin and adapted to new messages behaviour. client is now used in arguments instead of profile - plugins: added information for cancellation reason in CancelError calls - plugin XEP-0045: various improvments, but this plugin still need work: - trigger is used to avoid message already handled by the plugin to be handled a second time - changed the way to handle history, the last message from DB is checked and we request only messages since this one, in seconds (thanks Poezio folks :)) - subject reception is waited before sending the roomJoined signal, this way we are sure that everything including history is ready - cmd_* method now follow the new convention with client instead of profile - roomUserJoined and roomUserLeft messages are removed, the events are now handled with info message with a "ROOM_USER_JOINED" info subtype - probably other forgotten stuffs :p
author Goffi <goffi@goffi.org>
date Mon, 20 Jun 2016 18:41:53 +0200
parents 2daf7b4c6756
children 33c8c4973743
line wrap: on
line source

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# SAT plugin for managing xep-0077
# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from sat.core.i18n import _
from sat.core.constants import Const as C
from sat.core import exceptions
from sat.core.log import getLogger
log = getLogger(__name__)
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber.xmlstream import IQ
from sat.tools import xml_tools

from wokkel import data_form, compat

NS_REG = 'jabber:iq:register'

PLUGIN_INFO = {
    "name": "XEP 0077 Plugin",
    "import_name": "XEP-0077",
    "type": "XEP",
    "protocols": ["XEP-0077"],
    "dependencies": [],
    "main": "XEP_0077",
    "description": _("""Implementation of in-band registration""")
}


class XEP_0077(object):

    def __init__(self, host):
        log.info(_("Plugin XEP_0077 initialization"))
        self.host = host
        self.triggers = {}  # used by other protocol (e.g. XEP-0100) to finish registration. key = target_jid
        host.bridge.addMethod("inBandRegister", ".plugin", in_sign='ss', out_sign='s',
                              method=self._inBandRegister,
                              async=True)

    def _regOk(self, answer, client, post_treat_cb):
        """Called after the first get IQ"""
        try:
            query_elt = answer.elements(NS_REG, 'query').next()
        except StopIteration:
            raise exceptions.DataError("Can't find expected query element")

        try:
            x_elem = query_elt.elements(data_form.NS_X_DATA, 'x').next()
        except StopIteration:
            # XXX: it seems we have an old service which doesn't manage data forms
            log.warning(_("Can't find data form"))
            raise exceptions.DataError(_("This gateway can't be managed by SàT, sorry :("))

        def submitForm(data, profile):
            form_elt = xml_tools.XMLUIResultToElt(data)

            iq_elt = compat.IQ(client.xmlstream, 'set')
            iq_elt['id'] = answer['id']
            iq_elt['to'] = answer['from']
            query_elt = iq_elt.addElement("query", NS_REG)
            query_elt.addChild(form_elt)
            d = iq_elt.send()
            d.addCallback(self._regSuccess, client, post_treat_cb)
            d.addErrback(self._regFailure, client)
            return d

        form = data_form.Form.fromElement(x_elem)
        submit_reg_id = self.host.registerCallback(submitForm, with_data=True, one_shot=True)
        return xml_tools.dataForm2XMLUI(form, submit_reg_id)

    def _regErr(self, failure, client):
        """Called when something is wrong with registration"""
        log.info(_("Registration failure: %s") % unicode(failure.value))
        raise failure

    def _regSuccess(self, answer, client, post_treat_cb):
        log.debug(_(u"registration answer: %s") % answer.toXml())
        if post_treat_cb is not None:
            post_treat_cb(jid.JID(answer['from']), client.profile)
        return {}

    def _regFailure(self, failure, client):
        log.info(_(u"Registration failure: %s") % unicode(failure.value))
        if failure.value.condition == 'conflict':
            raise exceptions.ConflictError( _("Username already exists, please choose an other one"))
        raise failure

    def _inBandRegister(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
        return self.inBandRegister, jid.JID(to_jid_s, profile_key)

    def inBandRegister(self, to_jid, post_treat_cb=None, profile_key=C.PROF_KEY_NONE):
        """register to a target JID"""
        client = self.host.getClient(profile_key)
        log.debug(_(u"Asking registration for [%s]") % to_jid.full())
        reg_request = IQ(client.xmlstream, 'get')
        reg_request["from"] = client.jid.full()
        reg_request["to"] = to_jid.full()
        reg_request.addElement('query', NS_REG)
        d = reg_request.send(to_jid.full()).addCallbacks(self._regOk, self._regErr, callbackArgs=[client, post_treat_cb], errbackArgs=[client])
        return d