2018-07-01 02:16:33 +02:00
|
|
|
# Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
|
|
|
|
# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
|
|
|
# Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
|
|
|
|
# Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de>
|
|
|
|
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
|
|
|
|
#
|
|
|
|
# This file is part of Gajim.
|
|
|
|
#
|
|
|
|
# Gajim is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published
|
|
|
|
# by the Free Software Foundation; version 3 only.
|
|
|
|
#
|
|
|
|
# Gajim 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 General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
# XEP-0060: Publish-Subscribe
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
import nbxmpp
|
|
|
|
|
|
|
|
from gajim.common import app
|
2018-07-21 14:35:28 +02:00
|
|
|
from gajim.common.modules import dataforms
|
2018-07-07 01:49:25 +02:00
|
|
|
from gajim.common.nec import NetworkIncomingEvent
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
log = logging.getLogger('gajim.c.m.pubsub')
|
|
|
|
|
|
|
|
|
|
|
|
class PubSub:
|
|
|
|
def __init__(self, con):
|
|
|
|
self._con = con
|
|
|
|
self._account = con.name
|
|
|
|
|
|
|
|
self.handlers = []
|
|
|
|
|
2018-07-22 12:18:24 +02:00
|
|
|
self.publish_options = False
|
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
def pass_disco(self, from_, _identities, features, _data, _node):
|
2018-07-22 12:18:24 +02:00
|
|
|
if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS not in features:
|
|
|
|
# Remove stored bookmarks accessible to everyone.
|
|
|
|
self._con.get_module('Bookmarks').purge_pubsub_bookmarks()
|
|
|
|
return
|
|
|
|
log.info('Discovered Pubsub publish options: %s', from_)
|
|
|
|
self.publish_options = True
|
|
|
|
|
2018-07-01 02:16:33 +02:00
|
|
|
def send_pb_subscription_query(self, jid, cb, **kwargs):
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
query = nbxmpp.Iq('get', to=jid)
|
|
|
|
pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
|
|
|
pb.addChild('subscriptions')
|
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
|
|
|
|
|
|
|
def send_pb_subscribe(self, jid, node, cb, **kwargs):
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
our_jid = app.get_jid_from_account(self._account)
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
|
|
|
pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
|
|
|
pb.addChild('subscribe', {'node': node, 'jid': our_jid})
|
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
|
|
|
|
|
|
|
def send_pb_unsubscribe(self, jid, node, cb, **kwargs):
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
our_jid = app.get_jid_from_account(self._account)
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
|
|
|
pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
|
|
|
pb.addChild('unsubscribe', {'node': node, 'jid': our_jid})
|
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
|
|
|
|
|
|
|
def send_pb_publish(self, jid, node, item,
|
|
|
|
id_=None, options=None, cb=None, **kwargs):
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
if cb is None:
|
|
|
|
cb = self._default_callback
|
|
|
|
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
|
|
|
publish = pubsub.addChild('publish', {'node': node})
|
2018-07-01 02:16:33 +02:00
|
|
|
attrs = {}
|
|
|
|
if id_:
|
|
|
|
attrs = {'id': id_}
|
2018-09-11 22:25:55 +02:00
|
|
|
publish.addChild('item', attrs, [item])
|
2018-07-01 02:16:33 +02:00
|
|
|
if options:
|
2018-09-11 22:25:55 +02:00
|
|
|
publish = pubsub.addChild('publish-options')
|
|
|
|
publish.addChild(node=options)
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_pb_retrieve_iq(jid, node, item_id=None):
|
|
|
|
"""
|
|
|
|
Get IQ to query items from a node
|
|
|
|
"""
|
|
|
|
query = nbxmpp.Iq('get', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
|
|
|
items = pubsub.addChild('items', {'node': node})
|
2018-07-01 02:16:33 +02:00
|
|
|
if item_id is not None:
|
2018-09-11 22:25:55 +02:00
|
|
|
items.addChild('item', {'id': item_id})
|
2018-07-01 02:16:33 +02:00
|
|
|
return query
|
|
|
|
|
|
|
|
def send_pb_retrieve(self, jid, node, item_id=None, cb=None, **kwargs):
|
|
|
|
"""
|
|
|
|
Get items from a node
|
|
|
|
"""
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
if cb is None:
|
|
|
|
cb = self._default_callback
|
|
|
|
|
|
|
|
query = self.get_pb_retrieve_iq(jid, node, item_id)
|
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
|
|
|
|
|
|
|
def send_pb_retract(self, jid, node, id_, cb=None, **kwargs):
|
|
|
|
"""
|
|
|
|
Delete item from a node
|
|
|
|
"""
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
if cb is None:
|
|
|
|
cb = self._default_callback
|
|
|
|
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
|
|
|
retract = pubsub.addChild('retract', {'node': node, 'notify': '1'})
|
|
|
|
retract.addChild('item', {'id': id_})
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
|
|
|
|
|
|
|
def send_pb_purge(self, jid, node, cb=None, **kwargs):
|
|
|
|
"""
|
|
|
|
Purge node: Remove all items
|
|
|
|
"""
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
if cb is None:
|
|
|
|
cb = self._default_callback
|
|
|
|
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
|
|
|
|
pubsub.addChild('purge', {'node': node})
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
|
|
|
|
|
|
|
def send_pb_delete(self, jid, node, on_ok=None, on_fail=None):
|
|
|
|
"""
|
|
|
|
Delete node
|
|
|
|
"""
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
|
|
|
|
pubsub.addChild('delete', {'node': node})
|
2018-07-01 02:16:33 +02:00
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
def response(_con, resp, jid, node):
|
2018-07-01 02:16:33 +02:00
|
|
|
if resp.getType() == 'result' and on_ok:
|
|
|
|
on_ok(jid, node)
|
|
|
|
elif on_fail:
|
|
|
|
msg = resp.getErrorMsg()
|
|
|
|
on_fail(jid, node, msg)
|
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(
|
|
|
|
query, response, {'jid': jid, 'node': node})
|
|
|
|
|
|
|
|
def send_pb_create(self, jid, node, cb,
|
|
|
|
configure=False, configure_form=None):
|
|
|
|
"""
|
|
|
|
Create a new node
|
|
|
|
"""
|
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
|
|
|
create = pubsub.addChild('create', {'node': node})
|
2018-07-01 02:16:33 +02:00
|
|
|
if configure:
|
2018-09-11 22:25:55 +02:00
|
|
|
conf = create.addChild('configure')
|
2018-07-01 02:16:33 +02:00
|
|
|
if configure_form is not None:
|
|
|
|
conf.addChild(node=configure_form)
|
|
|
|
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb)
|
|
|
|
|
2018-07-07 01:49:25 +02:00
|
|
|
def send_pb_configure(self, jid, node, form, cb=None, **kwargs):
|
2018-07-01 02:16:33 +02:00
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
if cb is None:
|
|
|
|
cb = self._default_callback
|
|
|
|
|
|
|
|
query = nbxmpp.Iq('set', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
|
|
|
|
configure = pubsub.addChild('configure', {'node': node})
|
|
|
|
configure.addChild(node=form)
|
2018-07-01 02:16:33 +02:00
|
|
|
|
2018-07-07 01:49:25 +02:00
|
|
|
log.info('Send node config for %s', node)
|
|
|
|
self._con.connection.SendAndCallForResponse(query, cb, kwargs)
|
2018-07-01 02:16:33 +02:00
|
|
|
|
2018-07-07 01:49:25 +02:00
|
|
|
def request_pb_configuration(self, jid, node):
|
2018-07-01 02:16:33 +02:00
|
|
|
if not app.account_is_connected(self._account):
|
|
|
|
return
|
|
|
|
|
|
|
|
query = nbxmpp.Iq('get', to=jid)
|
2018-09-11 22:25:55 +02:00
|
|
|
pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
|
|
|
|
pubsub.addChild('configure', {'node': node})
|
2018-07-01 02:16:33 +02:00
|
|
|
|
2018-07-07 01:49:25 +02:00
|
|
|
log.info('Request node config for %s', node)
|
|
|
|
self._con.connection.SendAndCallForResponse(
|
|
|
|
query, self._received_pb_configuration, {'node': node})
|
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
def _received_pb_configuration(self, _con, stanza, node):
|
2018-07-07 01:49:25 +02:00
|
|
|
if not nbxmpp.isResultNode(stanza):
|
|
|
|
log.warning('Error: %s', stanza.getError())
|
|
|
|
return
|
|
|
|
|
|
|
|
pubsub = stanza.getTag('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
|
|
|
|
if pubsub is None:
|
|
|
|
log.warning('Malformed PubSub configure '
|
|
|
|
'stanza (no pubsub node): %s', stanza)
|
|
|
|
return
|
|
|
|
|
|
|
|
configure = pubsub.getTag('configure')
|
|
|
|
if configure is None:
|
|
|
|
log.warning('Malformed PubSub configure '
|
|
|
|
'stanza (no configure node): %s', stanza)
|
|
|
|
return
|
|
|
|
|
|
|
|
if configure.getAttr('node') != node:
|
|
|
|
log.warning('Malformed PubSub configure '
|
|
|
|
'stanza (wrong node): %s', stanza)
|
|
|
|
return
|
|
|
|
|
|
|
|
form = configure.getTag('x', namespace=nbxmpp.NS_DATA)
|
|
|
|
if form is None:
|
|
|
|
log.warning('Malformed PubSub configure '
|
|
|
|
'stanza (no form): %s', stanza)
|
|
|
|
return
|
|
|
|
|
|
|
|
app.nec.push_incoming_event(PubSubConfigReceivedEvent(
|
|
|
|
None, conn=self._con, node=node,
|
2018-09-12 00:01:54 +02:00
|
|
|
form=dataforms.extend_form(node=form)))
|
2018-07-01 02:16:33 +02:00
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
@staticmethod
|
|
|
|
def _default_callback(_con, stanza, *args, **kwargs):
|
2018-07-01 02:16:33 +02:00
|
|
|
if not nbxmpp.isResultNode(stanza):
|
|
|
|
log.warning('Error: %s', stanza.getError())
|
2018-07-07 01:49:25 +02:00
|
|
|
|
|
|
|
|
|
|
|
class PubSubConfigReceivedEvent(NetworkIncomingEvent):
|
|
|
|
name = 'pubsub-config-received'
|
2018-07-07 13:52:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
def get_instance(*args, **kwargs):
|
|
|
|
return PubSub(*args, **kwargs), 'PubSub'
|