Refactor AdHocCommands into own module
This commit is contained in:
parent
78d16c44f3
commit
a2d7283e6e
|
@ -25,12 +25,12 @@
|
|||
# FIXME: think if we need caching command list. it may be wrong if there will
|
||||
# be entities that often change the list, it may be slow to fetch it every time
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
import nbxmpp
|
||||
from gajim.common import app
|
||||
from gajim.common import dataforms
|
||||
from gajim.common import ged
|
||||
|
||||
from gajim import gtkgui_helpers
|
||||
from gajim import dialogs
|
||||
|
@ -55,7 +55,7 @@ class CommandWindow:
|
|||
"""
|
||||
|
||||
# an account object
|
||||
self.account = app.connections[account]
|
||||
self._con = app.connections[account]
|
||||
self.jid = jid
|
||||
self.commandnode = commandnode
|
||||
self.data_form_widget = None
|
||||
|
@ -87,6 +87,14 @@ class CommandWindow:
|
|||
column = Gtk.TreeViewColumn("Command", renderer, text=0)
|
||||
self.command_treeview.append_column(column)
|
||||
|
||||
app.ged.register_event_handler(
|
||||
'adhoc-command-error', ged.CORE, self._on_command_error)
|
||||
app.ged.register_event_handler(
|
||||
'adhoc-command-list', ged.CORE, self._on_command_list)
|
||||
app.ged.register_event_handler('adhoc-command-action-response',
|
||||
ged.CORE,
|
||||
self._on_action_response)
|
||||
|
||||
self.initiate()
|
||||
|
||||
def initiate(self):
|
||||
|
@ -157,8 +165,13 @@ class CommandWindow:
|
|||
return False
|
||||
|
||||
def on_adhoc_commands_window_destroy(self, *anything):
|
||||
# TODO: do all actions that are needed to remove this object from memory
|
||||
pass
|
||||
app.ged.remove_event_handler(
|
||||
'adhoc-command-error', ged.CORE, self._on_command_error)
|
||||
app.ged.remove_event_handler(
|
||||
'adhoc-command-list', ged.CORE, self._on_command_list)
|
||||
app.ged.remove_event_handler('adhoc-command-action-response',
|
||||
ged.CORE,
|
||||
self._on_action_response)
|
||||
|
||||
def on_adhoc_commands_window_delete_event(self, *anything):
|
||||
if self.stage_window_delete_cb:
|
||||
|
@ -190,7 +203,7 @@ class CommandWindow:
|
|||
self.finish_button.set_sensitive(False)
|
||||
|
||||
# request command list
|
||||
self.request_command_list()
|
||||
self._con.get_module('AdHocCommands').request_command_list(self.jid)
|
||||
self.retrieving_commands_spinner.start()
|
||||
|
||||
# setup the callbacks
|
||||
|
@ -304,7 +317,8 @@ class CommandWindow:
|
|||
return
|
||||
|
||||
def on_yes(button):
|
||||
self.send_cancel()
|
||||
self._con.get_module('AdHocCommands').send_cancel(
|
||||
self.jid, self.commandnode, self.sessionid)
|
||||
dialog.destroy()
|
||||
cb()
|
||||
|
||||
|
@ -371,7 +385,9 @@ class CommandWindow:
|
|||
self.finish_button.set_sensitive(False)
|
||||
|
||||
self.sending_form_spinner.start()
|
||||
self.send_command(action)
|
||||
self._con.get_module('AdHocCommands').send_command(
|
||||
self.jid, self.commandnode, self.sessionid,
|
||||
self.data_form_widget.data_form, action)
|
||||
|
||||
def stage3_next_form(self, command):
|
||||
if not isinstance(command, nbxmpp.Node):
|
||||
|
@ -527,85 +543,15 @@ class CommandWindow:
|
|||
def stage5_restart_button_clicked(self, widget):
|
||||
self.restart()
|
||||
|
||||
# handling xml stanzas
|
||||
def request_command_list(self):
|
||||
"""
|
||||
Request the command list. Change stage on delivery
|
||||
"""
|
||||
query = nbxmpp.Iq(typ='get', to=nbxmpp.JID(self.jid),
|
||||
queryNS=nbxmpp.NS_DISCO_ITEMS)
|
||||
query.setQuerynode(nbxmpp.NS_COMMANDS)
|
||||
def _on_command_error(self, obj):
|
||||
self.stage5(errorid=obj.error)
|
||||
|
||||
def callback(response):
|
||||
'''Called on response to query.'''
|
||||
# FIXME: move to connection_handlers.py
|
||||
# is error => error stage
|
||||
error = response.getError()
|
||||
if error:
|
||||
# extracting error description
|
||||
self.stage5(errorid=error)
|
||||
return
|
||||
|
||||
# no commands => no commands stage
|
||||
# commands => command selection stage
|
||||
query = response.getTag('query')
|
||||
if query and query.getAttr('node') == nbxmpp.NS_COMMANDS:
|
||||
items = query.getTags('item')
|
||||
else:
|
||||
items = []
|
||||
if len(items)==0:
|
||||
self.commandlist = []
|
||||
self.stage4()
|
||||
else:
|
||||
self.commandlist = [(t.getAttr('node'), t.getAttr('name')) \
|
||||
for t in items]
|
||||
self.stage2()
|
||||
|
||||
self.account.connection.SendAndCallForResponse(query, callback)
|
||||
|
||||
def send_command(self, action='execute'):
|
||||
"""
|
||||
Send the command with data form. Wait for reply
|
||||
"""
|
||||
# create the stanza
|
||||
assert action in ('execute', 'prev', 'next', 'complete')
|
||||
|
||||
stanza = nbxmpp.Iq(typ='set', to=self.jid)
|
||||
cmdnode = stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS,
|
||||
attrs={'node':self.commandnode, 'action':action})
|
||||
|
||||
if self.sessionid:
|
||||
cmdnode.setAttr('sessionid', self.sessionid)
|
||||
|
||||
if self.data_form_widget.data_form:
|
||||
cmdnode.addChild(node=self.data_form_widget.data_form.get_purged())
|
||||
|
||||
def callback(response):
|
||||
# FIXME: move to connection_handlers.py
|
||||
err = response.getError()
|
||||
if err:
|
||||
self.stage5(errorid = err)
|
||||
else:
|
||||
self.stage3_next_form(response.getTag('command'))
|
||||
|
||||
self.account.connection.SendAndCallForResponse(stanza, callback)
|
||||
|
||||
def send_cancel(self):
|
||||
"""
|
||||
Send the command with action='cancel'
|
||||
"""
|
||||
assert self.commandnode
|
||||
if self.sessionid and self.account.connection:
|
||||
# we already have sessionid, so the service sent at least one reply.
|
||||
stanza = nbxmpp.Iq(typ='set', to=self.jid)
|
||||
stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS, attrs={
|
||||
'node':self.commandnode,
|
||||
'sessionid':self.sessionid,
|
||||
'action':'cancel'
|
||||
})
|
||||
|
||||
self.account.connection.send(stanza)
|
||||
def _on_command_list(self, obj):
|
||||
self.commandlist = obj.commandlist
|
||||
if not self.commandlist:
|
||||
self.stage4()
|
||||
else:
|
||||
# we did not received any reply from service;
|
||||
# FIXME: we should wait and then send cancel; for now we do nothing
|
||||
pass
|
||||
self.stage2()
|
||||
|
||||
def _on_action_response(self, obj):
|
||||
self.stage3_next_form(obj.command)
|
||||
|
|
|
@ -1,433 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## src/common/commands.py
|
||||
##
|
||||
## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
|
||||
## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
|
||||
## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
|
||||
## Stephan Erb <steve-e AT h3c.de>
|
||||
##
|
||||
## 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/>.
|
||||
##
|
||||
|
||||
import nbxmpp
|
||||
from gajim.common import helpers
|
||||
from gajim.common import dataforms
|
||||
from gajim.common import app
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.commands')
|
||||
|
||||
class AdHocCommand:
|
||||
commandnode = 'command'
|
||||
commandname = 'The Command'
|
||||
commandfeatures = (nbxmpp.NS_DATA,)
|
||||
|
||||
@staticmethod
|
||||
def isVisibleFor(samejid):
|
||||
"""
|
||||
This returns True if that command should be visible and invokable for
|
||||
others
|
||||
|
||||
samejid - True when command is invoked by an entity with the same bare
|
||||
jid.
|
||||
"""
|
||||
return True
|
||||
|
||||
def __init__(self, conn, jid, sessionid):
|
||||
self.connection = conn
|
||||
self.jid = jid
|
||||
self.sessionid = sessionid
|
||||
|
||||
def buildResponse(self, request, status = 'executing', defaultaction = None,
|
||||
actions = None):
|
||||
assert status in ('executing', 'completed', 'canceled')
|
||||
|
||||
response = request.buildReply('result')
|
||||
cmd = response.getTag('command', namespace=nbxmpp.NS_COMMANDS)
|
||||
cmd.setAttr('sessionid', self.sessionid)
|
||||
cmd.setAttr('node', self.commandnode)
|
||||
cmd.setAttr('status', status)
|
||||
if defaultaction is not None or actions is not None:
|
||||
if defaultaction is not None:
|
||||
assert defaultaction in ('cancel', 'execute', 'prev', 'next',
|
||||
'complete')
|
||||
attrs = {'action': defaultaction}
|
||||
else:
|
||||
attrs = {}
|
||||
|
||||
cmd.addChild('actions', attrs, actions)
|
||||
return response, cmd
|
||||
|
||||
def badRequest(self, stanza):
|
||||
self.connection.connection.send(nbxmpp.Error(stanza,
|
||||
nbxmpp.NS_STANZAS + ' bad-request'))
|
||||
|
||||
def cancel(self, request):
|
||||
response = self.buildResponse(request, status = 'canceled')[0]
|
||||
self.connection.connection.send(response)
|
||||
return False # finish the session
|
||||
|
||||
class ChangeStatusCommand(AdHocCommand):
|
||||
commandnode = 'change-status'
|
||||
commandname = _('Change status information')
|
||||
|
||||
def __init__(self, conn, jid, sessionid):
|
||||
AdHocCommand.__init__(self, conn, jid, sessionid)
|
||||
self.cb = self.first_step
|
||||
|
||||
@staticmethod
|
||||
def isVisibleFor(samejid):
|
||||
"""
|
||||
Change status is visible only if the entity has the same bare jid
|
||||
"""
|
||||
return samejid
|
||||
|
||||
def execute(self, request):
|
||||
return self.cb(request)
|
||||
|
||||
def first_step(self, request):
|
||||
# first query...
|
||||
response, cmd = self.buildResponse(request, defaultaction = 'execute',
|
||||
actions = ['execute'])
|
||||
|
||||
cmd.addChild(node = dataforms.SimpleDataForm(
|
||||
title = _('Change status'),
|
||||
instructions = _('Set the presence type and description'),
|
||||
fields = [
|
||||
dataforms.Field('list-single',
|
||||
var = 'presence-type',
|
||||
label = 'Type of presence:',
|
||||
options = [
|
||||
('chat', _('Free for chat')),
|
||||
('online', _('Online')),
|
||||
('away', _('Away')),
|
||||
('xa', _('Extended away')),
|
||||
('dnd', _('Do not disturb')),
|
||||
('offline', _('Offline - disconnect'))],
|
||||
value = 'online',
|
||||
required = True),
|
||||
dataforms.Field('text-multi',
|
||||
var = 'presence-desc',
|
||||
label = _('Presence description:'))]))
|
||||
|
||||
self.connection.connection.send(response)
|
||||
|
||||
# for next invocation
|
||||
self.cb = self.second_step
|
||||
|
||||
return True # keep the session
|
||||
|
||||
def second_step(self, request):
|
||||
# check if the data is correct
|
||||
try:
|
||||
form = dataforms.SimpleDataForm(extend = request.getTag('command').\
|
||||
getTag('x'))
|
||||
except Exception:
|
||||
self.badRequest(request)
|
||||
return False
|
||||
|
||||
try:
|
||||
presencetype = form['presence-type'].value
|
||||
if not presencetype in \
|
||||
('chat', 'online', 'away', 'xa', 'dnd', 'offline'):
|
||||
self.badRequest(request)
|
||||
return False
|
||||
except Exception: # KeyError if there's no presence-type field in form or
|
||||
# AttributeError if that field is of wrong type
|
||||
self.badRequest(request)
|
||||
return False
|
||||
|
||||
try:
|
||||
presencedesc = form['presence-desc'].value
|
||||
except Exception: # same exceptions as in last comment
|
||||
presencedesc = ''
|
||||
|
||||
response, cmd = self.buildResponse(request, status = 'completed')
|
||||
cmd.addChild('note', {}, _('The status has been changed.'))
|
||||
|
||||
# if going offline, we need to push response so it won't go into
|
||||
# queue and disappear
|
||||
self.connection.connection.send(response, now = presencetype == 'offline')
|
||||
|
||||
# send new status
|
||||
app.interface.roster.send_status(self.connection.name, presencetype,
|
||||
presencedesc)
|
||||
|
||||
return False # finish the session
|
||||
|
||||
def find_current_groupchats(account):
|
||||
from gajim import message_control
|
||||
rooms = []
|
||||
for gc_control in app.interface.msg_win_mgr.get_controls(
|
||||
message_control.TYPE_GC) + \
|
||||
app.interface.minimized_controls[account].values():
|
||||
acct = gc_control.account
|
||||
# check if account is the good one
|
||||
if acct != account:
|
||||
continue
|
||||
room_jid = gc_control.room_jid
|
||||
nick = gc_control.nick
|
||||
if room_jid in app.gc_connected[acct] and \
|
||||
app.gc_connected[acct][room_jid]:
|
||||
rooms.append((room_jid, nick,))
|
||||
return rooms
|
||||
|
||||
|
||||
class LeaveGroupchatsCommand(AdHocCommand):
|
||||
commandnode = 'leave-groupchats'
|
||||
commandname = _('Leave Groupchats')
|
||||
|
||||
def __init__(self, conn, jid, sessionid):
|
||||
AdHocCommand.__init__(self, conn, jid, sessionid)
|
||||
self.cb = self.first_step
|
||||
|
||||
@staticmethod
|
||||
def isVisibleFor(samejid):
|
||||
"""
|
||||
Leave groupchats is visible only if the entity has the same bare jid
|
||||
"""
|
||||
return samejid
|
||||
|
||||
def execute(self, request):
|
||||
return self.cb(request)
|
||||
|
||||
def first_step(self, request):
|
||||
# first query...
|
||||
response, cmd = self.buildResponse(request, defaultaction = 'execute',
|
||||
actions=['execute'])
|
||||
options = []
|
||||
account = self.connection.name
|
||||
for gc in find_current_groupchats(account):
|
||||
options.append(('%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \
|
||||
{'nickname': gc[1], 'room_jid': gc[0]}))
|
||||
if not len(options):
|
||||
response, cmd = self.buildResponse(request, status = 'completed')
|
||||
cmd.addChild('note', {}, _('You have not joined a groupchat.'))
|
||||
|
||||
self.connection.connection.send(response)
|
||||
return False
|
||||
|
||||
cmd.addChild(node=dataforms.SimpleDataForm(
|
||||
title = _('Leave Groupchats'),
|
||||
instructions = _('Choose the groupchats you want to leave'),
|
||||
fields=[
|
||||
dataforms.Field('list-multi',
|
||||
var = 'groupchats',
|
||||
label = _('Groupchats'),
|
||||
options = options,
|
||||
required = True)]))
|
||||
|
||||
self.connection.connection.send(response)
|
||||
|
||||
# for next invocation
|
||||
self.cb = self.second_step
|
||||
|
||||
return True # keep the session
|
||||
|
||||
def second_step(self, request):
|
||||
# check if the data is correct
|
||||
try:
|
||||
form = dataforms.SimpleDataForm(extend = request.getTag('command').\
|
||||
getTag('x'))
|
||||
except Exception:
|
||||
self.badRequest(request)
|
||||
return False
|
||||
|
||||
try:
|
||||
gc = form['groupchats'].values
|
||||
except Exception: # KeyError if there's no groupchats in form
|
||||
self.badRequest(request)
|
||||
return False
|
||||
account = self.connection.name
|
||||
try:
|
||||
for room_jid in gc:
|
||||
gc_control = app.interface.msg_win_mgr.get_gc_control(room_jid,
|
||||
account)
|
||||
if not gc_control:
|
||||
gc_control = app.interface.minimized_controls[account]\
|
||||
[room_jid]
|
||||
gc_control.shutdown()
|
||||
app.interface.roster.remove_groupchat(room_jid, account)
|
||||
continue
|
||||
gc_control.parent_win.remove_tab(gc_control, None, force = True)
|
||||
except Exception: # KeyError if there's no such room opened
|
||||
self.badRequest(request)
|
||||
return False
|
||||
response, cmd = self.buildResponse(request, status = 'completed')
|
||||
note = _('You left the following groupchats:')
|
||||
for room_jid in gc:
|
||||
note += '\n\t' + room_jid
|
||||
cmd.addChild('note', {}, note)
|
||||
|
||||
self.connection.connection.send(response)
|
||||
return False
|
||||
|
||||
|
||||
class ConnectionCommands:
|
||||
"""
|
||||
This class depends on that it is a part of Connection() class
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# a list of all commands exposed: node -> command class
|
||||
self.__commands = {}
|
||||
if app.config.get('remote_commands'):
|
||||
for cmdobj in (ChangeStatusCommand, LeaveGroupchatsCommand):
|
||||
self.__commands[cmdobj.commandnode] = cmdobj
|
||||
|
||||
# a list of sessions; keys are tuples (jid, sessionid, node)
|
||||
self.__sessions = {}
|
||||
|
||||
def getOurBareJID(self):
|
||||
return app.get_jid_from_account(self.name)
|
||||
|
||||
def isSameJID(self, jid):
|
||||
"""
|
||||
Test if the bare jid given is the same as our bare jid
|
||||
"""
|
||||
return nbxmpp.JID(jid).getStripped() == self.getOurBareJID()
|
||||
|
||||
def commandListQuery(self, con, iq_obj):
|
||||
iq = iq_obj.buildReply('result')
|
||||
jid = helpers.get_full_jid_from_iq(iq_obj)
|
||||
q = iq.getTag('query')
|
||||
# buildReply don't copy the node attribute. Re-add it
|
||||
q.setAttr('node', nbxmpp.NS_COMMANDS)
|
||||
|
||||
for node, cmd in self.__commands.items():
|
||||
if cmd.isVisibleFor(self.isSameJID(jid)):
|
||||
q.addChild('item', {
|
||||
# TODO: find the jid
|
||||
'jid': self.getOurBareJID() + '/' + self.server_resource,
|
||||
'node': node,
|
||||
'name': cmd.commandname})
|
||||
|
||||
self.connection.send(iq)
|
||||
|
||||
def commandInfoQuery(self, con, iq_obj):
|
||||
"""
|
||||
Send disco#info result for query for command (JEP-0050, example 6.).
|
||||
Return True if the result was sent, False if not
|
||||
"""
|
||||
try:
|
||||
jid = helpers.get_full_jid_from_iq(iq_obj)
|
||||
except helpers.InvalidFormat:
|
||||
log.warning('Invalid JID: %s, ignoring it' % iq_obj.getFrom())
|
||||
return
|
||||
node = iq_obj.getTagAttr('query', 'node')
|
||||
|
||||
if node not in self.__commands: return False
|
||||
|
||||
cmd = self.__commands[node]
|
||||
if cmd.isVisibleFor(self.isSameJID(jid)):
|
||||
iq = iq_obj.buildReply('result')
|
||||
q = iq.getTag('query')
|
||||
q.addChild('identity', attrs = {'type': 'command-node',
|
||||
'category': 'automation',
|
||||
'name': cmd.commandname})
|
||||
q.addChild('feature', attrs = {'var': nbxmpp.NS_COMMANDS})
|
||||
for feature in cmd.commandfeatures:
|
||||
q.addChild('feature', attrs = {'var': feature})
|
||||
|
||||
self.connection.send(iq)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def commandItemsQuery(self, con, iq_obj):
|
||||
"""
|
||||
Send disco#items result for query for command. Return True if the result
|
||||
was sent, False if not.
|
||||
"""
|
||||
jid = helpers.get_full_jid_from_iq(iq_obj)
|
||||
node = iq_obj.getTagAttr('query', 'node')
|
||||
|
||||
if node not in self.__commands: return False
|
||||
|
||||
cmd = self.__commands[node]
|
||||
if cmd.isVisibleFor(self.isSameJID(jid)):
|
||||
iq = iq_obj.buildReply('result')
|
||||
self.connection.send(iq)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _CommandExecuteCB(self, con, iq_obj):
|
||||
jid = helpers.get_full_jid_from_iq(iq_obj)
|
||||
|
||||
cmd = iq_obj.getTag('command')
|
||||
if cmd is None: return
|
||||
|
||||
node = cmd.getAttr('node')
|
||||
if node is None: return
|
||||
|
||||
sessionid = cmd.getAttr('sessionid')
|
||||
if sessionid is None:
|
||||
# we start a new command session... only if we are visible for the jid
|
||||
# and command exist
|
||||
if node not in self.__commands.keys():
|
||||
self.connection.send(
|
||||
nbxmpp.Error(iq_obj, nbxmpp.NS_STANZAS + ' item-not-found'))
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
newcmd = self.__commands[node]
|
||||
if not newcmd.isVisibleFor(self.isSameJID(jid)):
|
||||
return
|
||||
|
||||
# generate new sessionid
|
||||
sessionid = self.connection.getAnID()
|
||||
|
||||
# create new instance and run it
|
||||
obj = newcmd(conn = self, jid = jid, sessionid = sessionid)
|
||||
rc = obj.execute(iq_obj)
|
||||
if rc:
|
||||
self.__sessions[(jid, sessionid, node)] = obj
|
||||
raise nbxmpp.NodeProcessed
|
||||
else:
|
||||
# the command is already running, check for it
|
||||
magictuple = (jid, sessionid, node)
|
||||
if magictuple not in self.__sessions:
|
||||
# we don't have this session... ha!
|
||||
return
|
||||
|
||||
action = cmd.getAttr('action')
|
||||
obj = self.__sessions[magictuple]
|
||||
|
||||
try:
|
||||
if action == 'cancel':
|
||||
rc = obj.cancel(iq_obj)
|
||||
elif action == 'prev':
|
||||
rc = obj.prev(iq_obj)
|
||||
elif action == 'next':
|
||||
rc = obj.next(iq_obj)
|
||||
elif action == 'execute' or action is None:
|
||||
rc = obj.execute(iq_obj)
|
||||
elif action == 'complete':
|
||||
rc = obj.complete(iq_obj)
|
||||
else:
|
||||
# action is wrong. stop the session, send error
|
||||
raise AttributeError
|
||||
except AttributeError:
|
||||
# the command probably doesn't handle invoked action...
|
||||
# stop the session, return error
|
||||
del self.__sessions[magictuple]
|
||||
return
|
||||
|
||||
# delete the session if rc is False
|
||||
if not rc:
|
||||
del self.__sessions[magictuple]
|
||||
|
||||
raise nbxmpp.NodeProcessed
|
|
@ -42,7 +42,6 @@ from gajim.common import helpers
|
|||
from gajim.common import app
|
||||
from gajim.common import jingle_xtls
|
||||
from gajim.common.caps_cache import muc_caps_cache
|
||||
from gajim.common.commands import ConnectionCommands
|
||||
from gajim.common.protocol.caps import ConnectionCaps
|
||||
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
|
||||
from gajim.common.protocol.bytestream import ConnectionIBBytestream
|
||||
|
@ -218,7 +217,7 @@ class ConnectionDisco:
|
|||
if not self.connection or self.connected < 2:
|
||||
return
|
||||
|
||||
if self.commandItemsQuery(con, iq_obj):
|
||||
if self.get_module('AdHocCommands').command_items_query(iq_obj):
|
||||
raise nbxmpp.NodeProcessed
|
||||
node = iq_obj.getTagAttr('query', 'node')
|
||||
if node is None:
|
||||
|
@ -226,7 +225,7 @@ class ConnectionDisco:
|
|||
self.connection.send(result)
|
||||
raise nbxmpp.NodeProcessed
|
||||
if node == nbxmpp.NS_COMMANDS:
|
||||
self.commandListQuery(con, iq_obj)
|
||||
self.get_module('AdHocCommands').command_list_query(iq_obj)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
def _DiscoverInfoGetCB(self, con, iq_obj):
|
||||
|
@ -235,7 +234,7 @@ class ConnectionDisco:
|
|||
return
|
||||
node = iq_obj.getQuerynode()
|
||||
|
||||
if self.commandInfoQuery(con, iq_obj):
|
||||
if self.get_module('AdHocCommands').command_info_query(iq_obj):
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
id_ = iq_obj.getAttr('id')
|
||||
|
@ -743,14 +742,12 @@ class ConnectionHandlersBase:
|
|||
return sess
|
||||
|
||||
class ConnectionHandlers(ConnectionArchive313,
|
||||
ConnectionSocks5Bytestream, ConnectionDisco,
|
||||
ConnectionCommands, ConnectionCaps,
|
||||
ConnectionSocks5Bytestream, ConnectionDisco, ConnectionCaps,
|
||||
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
|
||||
def __init__(self):
|
||||
ConnectionArchive313.__init__(self)
|
||||
ConnectionSocks5Bytestream.__init__(self)
|
||||
ConnectionIBBytestream.__init__(self)
|
||||
ConnectionCommands.__init__(self)
|
||||
|
||||
# Handle presences BEFORE caps
|
||||
app.nec.register_incoming_event(PresenceReceivedEvent)
|
||||
|
@ -1339,8 +1336,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
|
|||
nbxmpp.NS_MUC_ADMIN)
|
||||
con.RegisterHandler('iq', self._SecLabelCB, 'result',
|
||||
nbxmpp.NS_SECLABEL_CATALOG)
|
||||
con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
|
||||
nbxmpp.NS_COMMANDS)
|
||||
con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
|
||||
nbxmpp.NS_DISCO_INFO)
|
||||
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
|
||||
|
|
|
@ -18,6 +18,8 @@ from pathlib import Path
|
|||
|
||||
log = logging.getLogger('gajim.c.m')
|
||||
|
||||
ZEROCONF_MODULES = ['adhoc_commands']
|
||||
|
||||
imported_modules = []
|
||||
_modules = {}
|
||||
|
||||
|
@ -31,12 +33,26 @@ for file in Path(__file__).parent.iterdir():
|
|||
if file.stem == 'pep':
|
||||
# Register the PEP module first, because other modules
|
||||
# depend on it
|
||||
imported_modules.insert(0, module)
|
||||
imported_modules.insert(0, (module, file.stem))
|
||||
else:
|
||||
imported_modules.append(module)
|
||||
imported_modules.append((module, file.stem))
|
||||
|
||||
|
||||
class ModuleMock:
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
# HTTPUpload
|
||||
self.available = False
|
||||
|
||||
# Blocking
|
||||
self.blocked = []
|
||||
|
||||
# Privacy Lists
|
||||
self.blocked_contacts = []
|
||||
self.blocked_groups = []
|
||||
self.blocked_all = False
|
||||
|
||||
def __getattr__(self, key):
|
||||
def _mock(self, *args, **kwargs):
|
||||
return
|
||||
|
@ -48,7 +64,11 @@ def register(con, *args, **kwargs):
|
|||
return
|
||||
_modules[con.name] = {}
|
||||
for module in imported_modules:
|
||||
instance, name = module.get_instance(con, *args, **kwargs)
|
||||
mod, name = module
|
||||
if con.name == 'Local':
|
||||
if name not in ZEROCONF_MODULES:
|
||||
continue
|
||||
instance, name = mod.get_instance(con, *args, **kwargs)
|
||||
_modules[con.name][name] = instance
|
||||
|
||||
|
||||
|
@ -60,7 +80,7 @@ def get(account, name):
|
|||
try:
|
||||
return _modules[account][name]
|
||||
except KeyError:
|
||||
return ModuleMock()
|
||||
return ModuleMock(name)
|
||||
|
||||
|
||||
def get_handlers(con):
|
||||
|
|
|
@ -0,0 +1,573 @@
|
|||
# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||||
# Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
|
||||
# Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
|
||||
# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
|
||||
# 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/>.
|
||||
|
||||
import logging
|
||||
import nbxmpp
|
||||
|
||||
from gajim.common import helpers
|
||||
from gajim.common import dataforms
|
||||
from gajim.common import app
|
||||
from gajim.common.nec import NetworkIncomingEvent
|
||||
|
||||
log = logging.getLogger('gajim.c.m.commands')
|
||||
|
||||
|
||||
class AdHocCommand:
|
||||
commandnode = 'command'
|
||||
commandname = 'The Command'
|
||||
commandfeatures = (nbxmpp.NS_DATA,)
|
||||
|
||||
@staticmethod
|
||||
def isVisibleFor(samejid):
|
||||
"""
|
||||
This returns True if that command should be visible and invokable for
|
||||
others
|
||||
|
||||
samejid - True when command is invoked by an entity with the same bare
|
||||
jid.
|
||||
"""
|
||||
return True
|
||||
|
||||
def __init__(self, conn, jid, sessionid):
|
||||
self.connection = conn
|
||||
self.jid = jid
|
||||
self.sessionid = sessionid
|
||||
|
||||
def buildResponse(self, request, status='executing', defaultaction=None,
|
||||
actions=None):
|
||||
assert status in ('executing', 'completed', 'canceled')
|
||||
|
||||
response = request.buildReply('result')
|
||||
cmd = response.getTag('command', namespace=nbxmpp.NS_COMMANDS)
|
||||
cmd.setAttr('sessionid', self.sessionid)
|
||||
cmd.setAttr('node', self.commandnode)
|
||||
cmd.setAttr('status', status)
|
||||
if defaultaction is not None or actions is not None:
|
||||
if defaultaction is not None:
|
||||
assert defaultaction in ('cancel', 'execute', 'prev', 'next',
|
||||
'complete')
|
||||
attrs = {'action': defaultaction}
|
||||
else:
|
||||
attrs = {}
|
||||
|
||||
cmd.addChild('actions', attrs, actions)
|
||||
return response, cmd
|
||||
|
||||
def badRequest(self, stanza):
|
||||
self.connection.connection.send(
|
||||
nbxmpp.Error(stanza, nbxmpp.NS_STANZAS + ' bad-request'))
|
||||
|
||||
def cancel(self, request):
|
||||
response = self.buildResponse(request, status='canceled')[0]
|
||||
self.connection.connection.send(response)
|
||||
return False # finish the session
|
||||
|
||||
|
||||
class ChangeStatusCommand(AdHocCommand):
|
||||
commandnode = 'change-status'
|
||||
commandname = _('Change status information')
|
||||
|
||||
def __init__(self, conn, jid, sessionid):
|
||||
AdHocCommand.__init__(self, conn, jid, sessionid)
|
||||
self.cb = self.first_step
|
||||
|
||||
@staticmethod
|
||||
def isVisibleFor(samejid):
|
||||
"""
|
||||
Change status is visible only if the entity has the same bare jid
|
||||
"""
|
||||
return samejid
|
||||
|
||||
def execute(self, request):
|
||||
return self.cb(request)
|
||||
|
||||
def first_step(self, request):
|
||||
# first query...
|
||||
response, cmd = self.buildResponse(request,
|
||||
defaultaction='execute',
|
||||
actions=['execute'])
|
||||
|
||||
cmd.addChild(
|
||||
node=dataforms.SimpleDataForm(
|
||||
title=_('Change status'),
|
||||
instructions=_('Set the presence type and description'),
|
||||
fields=[
|
||||
dataforms.Field(
|
||||
'list-single',
|
||||
var='presence-type',
|
||||
label='Type of presence:',
|
||||
options=[
|
||||
('chat', _('Free for chat')),
|
||||
('online', _('Online')),
|
||||
('away', _('Away')),
|
||||
('xa', _('Extended away')),
|
||||
('dnd', _('Do not disturb')),
|
||||
('offline', _('Offline - disconnect'))],
|
||||
value='online',
|
||||
required=True),
|
||||
dataforms.Field(
|
||||
'text-multi',
|
||||
var='presence-desc',
|
||||
label=_('Presence description:'))
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
self.connection.connection.send(response)
|
||||
|
||||
# for next invocation
|
||||
self.cb = self.second_step
|
||||
|
||||
return True # keep the session
|
||||
|
||||
def second_step(self, request):
|
||||
# check if the data is correct
|
||||
try:
|
||||
form = dataforms.SimpleDataForm(
|
||||
extend=request.getTag('command').getTag('x'))
|
||||
except Exception:
|
||||
self.badRequest(request)
|
||||
return False
|
||||
|
||||
try:
|
||||
presencetype = form['presence-type'].value
|
||||
if presencetype not in ('chat', 'online', 'away',
|
||||
'xa', 'dnd', 'offline'):
|
||||
self.badRequest(request)
|
||||
return False
|
||||
except Exception:
|
||||
# KeyError if there's no presence-type field in form or
|
||||
# AttributeError if that field is of wrong type
|
||||
self.badRequest(request)
|
||||
return False
|
||||
|
||||
try:
|
||||
presencedesc = form['presence-desc'].value
|
||||
except Exception: # same exceptions as in last comment
|
||||
presencedesc = ''
|
||||
|
||||
response, cmd = self.buildResponse(request, status='completed')
|
||||
cmd.addChild('note', {}, _('The status has been changed.'))
|
||||
|
||||
# if going offline, we need to push response so it won't go into
|
||||
# queue and disappear
|
||||
self.connection.connection.send(response,
|
||||
now=presencetype == 'offline')
|
||||
|
||||
# send new status
|
||||
app.interface.roster.send_status(
|
||||
self.connection.name, presencetype, presencedesc)
|
||||
|
||||
return False # finish the session
|
||||
|
||||
|
||||
def find_current_groupchats(account):
|
||||
from gajim import message_control
|
||||
rooms = []
|
||||
for gc_control in app.interface.msg_win_mgr.get_controls(
|
||||
message_control.TYPE_GC) + \
|
||||
app.interface.minimized_controls[account].values():
|
||||
acct = gc_control.account
|
||||
# check if account is the good one
|
||||
if acct != account:
|
||||
continue
|
||||
room_jid = gc_control.room_jid
|
||||
nick = gc_control.nick
|
||||
if (room_jid in app.gc_connected[acct] and
|
||||
app.gc_connected[acct][room_jid]):
|
||||
rooms.append((room_jid, nick,))
|
||||
return rooms
|
||||
|
||||
|
||||
class LeaveGroupchatsCommand(AdHocCommand):
|
||||
commandnode = 'leave-groupchats'
|
||||
commandname = _('Leave Groupchats')
|
||||
|
||||
def __init__(self, conn, jid, sessionid):
|
||||
AdHocCommand.__init__(self, conn, jid, sessionid)
|
||||
self.cb = self.first_step
|
||||
|
||||
@staticmethod
|
||||
def isVisibleFor(samejid):
|
||||
"""
|
||||
Leave groupchats is visible only if the entity has the same bare jid
|
||||
"""
|
||||
return samejid
|
||||
|
||||
def execute(self, request):
|
||||
return self.cb(request)
|
||||
|
||||
def first_step(self, request):
|
||||
# first query...
|
||||
response, cmd = self.buildResponse(request,
|
||||
defaultaction='execute',
|
||||
actions=['execute'])
|
||||
options = []
|
||||
account = self.connection.name
|
||||
for gc in find_current_groupchats(account):
|
||||
options.append(
|
||||
('%s' % gc[0],
|
||||
_('%(nickname)s on %(room_jid)s') % {'nickname': gc[1],
|
||||
'room_jid': gc[0]}))
|
||||
if not len(options):
|
||||
response, cmd = self.buildResponse(request, status='completed')
|
||||
cmd.addChild('note', {}, _('You have not joined a groupchat.'))
|
||||
|
||||
self.connection.connection.send(response)
|
||||
return False
|
||||
|
||||
cmd.addChild(
|
||||
node=dataforms.SimpleDataForm(
|
||||
title=_('Leave Groupchats'),
|
||||
instructions=_('Choose the groupchats you want to leave'),
|
||||
fields=[
|
||||
dataforms.Field(
|
||||
'list-multi',
|
||||
var='groupchats',
|
||||
label=_('Groupchats'),
|
||||
options=options,
|
||||
required=True)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
self.connection.connection.send(response)
|
||||
|
||||
# for next invocation
|
||||
self.cb = self.second_step
|
||||
|
||||
return True # keep the session
|
||||
|
||||
def second_step(self, request):
|
||||
# check if the data is correct
|
||||
try:
|
||||
form = dataforms.SimpleDataForm(
|
||||
extend=request.getTag('command').getTag('x'))
|
||||
except Exception:
|
||||
self.badRequest(request)
|
||||
return False
|
||||
|
||||
try:
|
||||
gc = form['groupchats'].values
|
||||
except Exception: # KeyError if there's no groupchats in form
|
||||
self.badRequest(request)
|
||||
return False
|
||||
account = self.connection.name
|
||||
try:
|
||||
for room_jid in gc:
|
||||
gc_control = app.interface.msg_win_mgr.get_gc_control(
|
||||
room_jid, account)
|
||||
if not gc_control:
|
||||
gc_control = app.interface.minimized_controls[account][room_jid]
|
||||
gc_control.shutdown()
|
||||
app.interface.roster.remove_groupchat(room_jid, account)
|
||||
continue
|
||||
gc_control.parent_win.remove_tab(gc_control, None, force=True)
|
||||
except Exception: # KeyError if there's no such room opened
|
||||
self.badRequest(request)
|
||||
return False
|
||||
response, cmd = self.buildResponse(request, status='completed')
|
||||
note = _('You left the following groupchats:')
|
||||
for room_jid in gc:
|
||||
note += '\n\t' + room_jid
|
||||
cmd.addChild('note', {}, note)
|
||||
|
||||
self.connection.connection.send(response)
|
||||
return False
|
||||
|
||||
|
||||
class AdHocCommands:
|
||||
def __init__(self, con):
|
||||
self._con = con
|
||||
self._account = con.name
|
||||
|
||||
self.handlers = [
|
||||
('iq', self._execute_command_received, 'set', nbxmpp.NS_COMMANDS)
|
||||
]
|
||||
|
||||
# a list of all commands exposed: node -> command class
|
||||
self._commands = {}
|
||||
if app.config.get('remote_commands'):
|
||||
for cmdobj in (ChangeStatusCommand, LeaveGroupchatsCommand):
|
||||
self._commands[cmdobj.commandnode] = cmdobj
|
||||
|
||||
# a list of sessions; keys are tuples (jid, sessionid, node)
|
||||
self._sessions = {}
|
||||
|
||||
def get_own_bare_jid(self):
|
||||
return self._con.get_own_jid().getStripped()
|
||||
|
||||
def is_same_jid(self, jid):
|
||||
"""
|
||||
Test if the bare jid given is the same as our bare jid
|
||||
"""
|
||||
return nbxmpp.JID(jid).getStripped() == self.get_own_bare_jid()
|
||||
|
||||
def command_list_query(self, stanza):
|
||||
iq = stanza.buildReply('result')
|
||||
jid = helpers.get_full_jid_from_iq(stanza)
|
||||
q = iq.getTag('query')
|
||||
# buildReply don't copy the node attribute. Re-add it
|
||||
q.setAttr('node', nbxmpp.NS_COMMANDS)
|
||||
|
||||
for node, cmd in self._commands.items():
|
||||
if cmd.isVisibleFor(self.is_same_jid(jid)):
|
||||
q.addChild('item', {
|
||||
# TODO: find the jid
|
||||
'jid': str(self._con.get_own_jid()),
|
||||
'node': node,
|
||||
'name': cmd.commandname})
|
||||
|
||||
self._con.connection.send(iq)
|
||||
|
||||
def command_info_query(self, stanza):
|
||||
"""
|
||||
Send disco#info result for query for command (XEP-0050, example 6.).
|
||||
Return True if the result was sent, False if not
|
||||
"""
|
||||
try:
|
||||
jid = helpers.get_full_jid_from_iq(stanza)
|
||||
except helpers.InvalidFormat:
|
||||
log.warning('Invalid JID: %s, ignoring it' % stanza.getFrom())
|
||||
return
|
||||
node = stanza.getTagAttr('query', 'node')
|
||||
|
||||
if node not in self._commands:
|
||||
return False
|
||||
|
||||
cmd = self._commands[node]
|
||||
if cmd.isVisibleFor(self.is_same_jid(jid)):
|
||||
iq = stanza.buildReply('result')
|
||||
q = iq.getTag('query')
|
||||
q.addChild('identity',
|
||||
attrs={'type': 'command-node',
|
||||
'category': 'automation',
|
||||
'name': cmd.commandname})
|
||||
q.addChild('feature', attrs={'var': nbxmpp.NS_COMMANDS})
|
||||
for feature in cmd.commandfeatures:
|
||||
q.addChild('feature', attrs={'var': feature})
|
||||
|
||||
self._con.connection.send(iq)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def command_items_query(self, stanza):
|
||||
"""
|
||||
Send disco#items result for query for command.
|
||||
Return True if the result was sent, False if not.
|
||||
"""
|
||||
jid = helpers.get_full_jid_from_iq(stanza)
|
||||
node = stanza.getTagAttr('query', 'node')
|
||||
|
||||
if node not in self._commands:
|
||||
return False
|
||||
|
||||
cmd = self._commands[node]
|
||||
if cmd.isVisibleFor(self.is_same_jid(jid)):
|
||||
iq = stanza.buildReply('result')
|
||||
self._con.connection.send(iq)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _execute_command_received(self, con, stanza):
|
||||
jid = helpers.get_full_jid_from_iq(stanza)
|
||||
|
||||
cmd = stanza.getTag('command')
|
||||
if cmd is None:
|
||||
log.error('Malformed stanza (no command node) %s', stanza)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
node = cmd.getAttr('node')
|
||||
if node is None:
|
||||
log.error('Malformed stanza (no node attr) %s', stanza)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
sessionid = cmd.getAttr('sessionid')
|
||||
if sessionid is None:
|
||||
# we start a new command session
|
||||
# only if we are visible for the jid and command exist
|
||||
if node not in self._commands.keys():
|
||||
self._con.connection.send(
|
||||
nbxmpp.Error(
|
||||
stanza, nbxmpp.NS_STANZAS + ' item-not-found'))
|
||||
log.warning('Comand %s does not exist: %s', node, jid)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
newcmd = self._commands[node]
|
||||
if not newcmd.isVisibleFor(self.is_same_jid(jid)):
|
||||
log.warning('Command not visible for jid: %s', jid)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
# generate new sessionid
|
||||
sessionid = self._con.connection.getAnID()
|
||||
|
||||
# create new instance and run it
|
||||
obj = newcmd(conn=self, jid=jid, sessionid=sessionid)
|
||||
rc = obj.execute(stanza)
|
||||
if rc:
|
||||
self._sessions[(jid, sessionid, node)] = obj
|
||||
log.info('Comand %s executed: %s', node, jid)
|
||||
raise nbxmpp.NodeProcessed
|
||||
else:
|
||||
# the command is already running, check for it
|
||||
magictuple = (jid, sessionid, node)
|
||||
if magictuple not in self._sessions:
|
||||
# we don't have this session... ha!
|
||||
log.warning('Invalid session %s', magictuple)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
action = cmd.getAttr('action')
|
||||
obj = self._sessions[magictuple]
|
||||
|
||||
try:
|
||||
if action == 'cancel':
|
||||
rc = obj.cancel(stanza)
|
||||
elif action == 'prev':
|
||||
rc = obj.prev(stanza)
|
||||
elif action == 'next':
|
||||
rc = obj.next(stanza)
|
||||
elif action == 'execute' or action is None:
|
||||
rc = obj.execute(stanza)
|
||||
elif action == 'complete':
|
||||
rc = obj.complete(stanza)
|
||||
else:
|
||||
# action is wrong. stop the session, send error
|
||||
raise AttributeError
|
||||
except AttributeError:
|
||||
# the command probably doesn't handle invoked action...
|
||||
# stop the session, return error
|
||||
del self._sessions[magictuple]
|
||||
log.warning('Wrong action %s %s', node, jid)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
# delete the session if rc is False
|
||||
if not rc:
|
||||
del self._sessions[magictuple]
|
||||
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
def request_command_list(self, jid):
|
||||
"""
|
||||
Request the command list.
|
||||
"""
|
||||
log.info('Request Command List: %s', jid)
|
||||
query = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_ITEMS)
|
||||
query.setQuerynode(nbxmpp.NS_COMMANDS)
|
||||
|
||||
self._con.connection.SendAndCallForResponse(
|
||||
query, self._command_list_received)
|
||||
|
||||
def _command_list_received(self, stanza):
|
||||
if not nbxmpp.isResultNode(stanza):
|
||||
log.info('Error: %s', stanza.getError())
|
||||
|
||||
app.nec.push_incoming_event(
|
||||
AdHocCommandError(None, conn=self._con,
|
||||
error=stanza.getError()))
|
||||
return
|
||||
|
||||
items = stanza.getQueryPayload()
|
||||
commandlist = []
|
||||
if items:
|
||||
commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items]
|
||||
|
||||
log.info('Received: %s', commandlist)
|
||||
app.nec.push_incoming_event(
|
||||
AdHocCommandListReceived(
|
||||
None, conn=self._con, commandlist=commandlist))
|
||||
|
||||
def send_command(self, jid, node, session_id,
|
||||
form, action='execute'):
|
||||
"""
|
||||
Send the command with data form. Wait for reply
|
||||
"""
|
||||
log.info('Send Command: %s %s %s %s', jid, node, session_id, action)
|
||||
stanza = nbxmpp.Iq(typ='set', to=jid)
|
||||
cmdnode = stanza.addChild('command',
|
||||
namespace=nbxmpp.NS_COMMANDS,
|
||||
attrs={'node': node,
|
||||
'action': action})
|
||||
|
||||
if session_id:
|
||||
cmdnode.setAttr('sessionid', session_id)
|
||||
|
||||
if form:
|
||||
cmdnode.addChild(node=form.get_purged())
|
||||
|
||||
self._con.connection.SendAndCallForResponse(
|
||||
stanza, self._action_response_received)
|
||||
|
||||
def _action_response_received(self, stanza):
|
||||
if not nbxmpp.isResultNode(stanza):
|
||||
log.info('Error: %s', stanza.getError())
|
||||
|
||||
app.nec.push_incoming_event(
|
||||
AdHocCommandError(None, conn=self._con,
|
||||
error=stanza.getError()))
|
||||
return
|
||||
log.info('Received action response')
|
||||
command = stanza.getTag('command')
|
||||
app.nec.push_incoming_event(
|
||||
AdHocCommandActionResponse(
|
||||
None, conn=self._con, command=command))
|
||||
|
||||
def send_cancel(self, jid, node, session_id):
|
||||
"""
|
||||
Send the command with action='cancel'
|
||||
"""
|
||||
log.info('Cancel: %s %s %s', jid, node, session_id)
|
||||
stanza = nbxmpp.Iq(typ='set', to=jid)
|
||||
stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS,
|
||||
attrs={
|
||||
'node': node,
|
||||
'sessionid': session_id,
|
||||
'action': 'cancel'
|
||||
})
|
||||
|
||||
self._con.connection.SendAndCallForResponse(
|
||||
stanza, self._cancel_result_received)
|
||||
|
||||
def _cancel_result_received(self, stanza):
|
||||
if not nbxmpp.isResultNode(stanza):
|
||||
log.warning('Error: %s', stanza.getError())
|
||||
else:
|
||||
log.info('Cancel successful')
|
||||
|
||||
|
||||
class AdHocCommandError(NetworkIncomingEvent):
|
||||
name = 'adhoc-command-error'
|
||||
base_network_events = []
|
||||
|
||||
|
||||
class AdHocCommandListReceived(NetworkIncomingEvent):
|
||||
name = 'adhoc-command-list'
|
||||
base_network_events = []
|
||||
|
||||
|
||||
class AdHocCommandActionResponse(NetworkIncomingEvent):
|
||||
name = 'adhoc-command-action-response'
|
||||
base_network_events = []
|
||||
|
||||
|
||||
def get_instance(*args, **kwargs):
|
||||
return AdHocCommands(*args, **kwargs), 'AdHocCommands'
|
|
@ -26,7 +26,6 @@
|
|||
import nbxmpp
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common.commands import ConnectionCommands
|
||||
from gajim.common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
|
||||
from gajim.common.connection_handlers_events import ZeroconfMessageReceivedEvent
|
||||
|
||||
|
@ -49,13 +48,12 @@ class ConnectionVcard:
|
|||
|
||||
|
||||
class ConnectionHandlersZeroconf(ConnectionVcard,
|
||||
ConnectionSocks5BytestreamZeroconf, ConnectionCommands,
|
||||
ConnectionSocks5BytestreamZeroconf,
|
||||
connection_handlers.ConnectionHandlersBase,
|
||||
connection_handlers.ConnectionJingle):
|
||||
def __init__(self):
|
||||
ConnectionVcard.__init__(self)
|
||||
ConnectionSocks5BytestreamZeroconf.__init__(self)
|
||||
ConnectionCommands.__init__(self)
|
||||
connection_handlers.ConnectionJingle.__init__(self)
|
||||
connection_handlers.ConnectionHandlersBase.__init__(self)
|
||||
|
||||
|
@ -82,13 +80,13 @@ connection_handlers.ConnectionJingle):
|
|||
if not self.connection or self.connected < 2:
|
||||
return
|
||||
|
||||
if self.commandItemsQuery(con, iq_obj):
|
||||
if self.get_module('AdHocCommands').command_items_query(iq_obj):
|
||||
raise nbxmpp.NodeProcessed
|
||||
node = iq_obj.getTagAttr('query', 'node')
|
||||
if node is None:
|
||||
result = iq_obj.buildReply('result')
|
||||
self.connection.send(result)
|
||||
raise nbxmpp.NodeProcessed
|
||||
if node==nbxmpp.NS_COMMANDS:
|
||||
self.commandListQuery(con, iq_obj)
|
||||
if node == nbxmpp.NS_COMMANDS:
|
||||
self.get_module('AdHocCommands').command_list_query(iq_obj)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
|
|
@ -47,6 +47,7 @@ from gi.repository import GLib
|
|||
from gajim.common.connection import CommonConnection
|
||||
from gajim.common import app
|
||||
from gajim.common import ged
|
||||
from gajim.common import modules
|
||||
from gajim.common.zeroconf import client_zeroconf
|
||||
from gajim.common.zeroconf import zeroconf
|
||||
from gajim.common.zeroconf.connection_handlers_zeroconf import *
|
||||
|
@ -69,6 +70,9 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
|||
CommonConnection.__init__(self, name)
|
||||
self.is_zeroconf = True
|
||||
|
||||
# Register all modules
|
||||
modules.register(self)
|
||||
|
||||
app.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
|
||||
self._nec_message_outgoing)
|
||||
app.ged.register_event_handler('stanza-message-outgoing', ged.OUT_CORE,
|
||||
|
|
Loading…
Reference in New Issue