Refactor AdHocCommands into own module
This commit is contained in:
parent
78d16c44f3
commit
a2d7283e6e
7 changed files with 642 additions and 539 deletions
|
@ -25,12 +25,12 @@
|
||||||
# FIXME: think if we need caching command list. it may be wrong if there will
|
# 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
|
# 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
|
from gi.repository import Gtk
|
||||||
|
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import dataforms
|
from gajim.common import dataforms
|
||||||
|
from gajim.common import ged
|
||||||
|
|
||||||
from gajim import gtkgui_helpers
|
from gajim import gtkgui_helpers
|
||||||
from gajim import dialogs
|
from gajim import dialogs
|
||||||
|
@ -55,7 +55,7 @@ class CommandWindow:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# an account object
|
# an account object
|
||||||
self.account = app.connections[account]
|
self._con = app.connections[account]
|
||||||
self.jid = jid
|
self.jid = jid
|
||||||
self.commandnode = commandnode
|
self.commandnode = commandnode
|
||||||
self.data_form_widget = None
|
self.data_form_widget = None
|
||||||
|
@ -87,6 +87,14 @@ class CommandWindow:
|
||||||
column = Gtk.TreeViewColumn("Command", renderer, text=0)
|
column = Gtk.TreeViewColumn("Command", renderer, text=0)
|
||||||
self.command_treeview.append_column(column)
|
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()
|
self.initiate()
|
||||||
|
|
||||||
def initiate(self):
|
def initiate(self):
|
||||||
|
@ -157,8 +165,13 @@ class CommandWindow:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_adhoc_commands_window_destroy(self, *anything):
|
def on_adhoc_commands_window_destroy(self, *anything):
|
||||||
# TODO: do all actions that are needed to remove this object from memory
|
app.ged.remove_event_handler(
|
||||||
pass
|
'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):
|
def on_adhoc_commands_window_delete_event(self, *anything):
|
||||||
if self.stage_window_delete_cb:
|
if self.stage_window_delete_cb:
|
||||||
|
@ -190,7 +203,7 @@ class CommandWindow:
|
||||||
self.finish_button.set_sensitive(False)
|
self.finish_button.set_sensitive(False)
|
||||||
|
|
||||||
# request command list
|
# request command list
|
||||||
self.request_command_list()
|
self._con.get_module('AdHocCommands').request_command_list(self.jid)
|
||||||
self.retrieving_commands_spinner.start()
|
self.retrieving_commands_spinner.start()
|
||||||
|
|
||||||
# setup the callbacks
|
# setup the callbacks
|
||||||
|
@ -304,7 +317,8 @@ class CommandWindow:
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_yes(button):
|
def on_yes(button):
|
||||||
self.send_cancel()
|
self._con.get_module('AdHocCommands').send_cancel(
|
||||||
|
self.jid, self.commandnode, self.sessionid)
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
cb()
|
cb()
|
||||||
|
|
||||||
|
@ -371,7 +385,9 @@ class CommandWindow:
|
||||||
self.finish_button.set_sensitive(False)
|
self.finish_button.set_sensitive(False)
|
||||||
|
|
||||||
self.sending_form_spinner.start()
|
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):
|
def stage3_next_form(self, command):
|
||||||
if not isinstance(command, nbxmpp.Node):
|
if not isinstance(command, nbxmpp.Node):
|
||||||
|
@ -527,85 +543,15 @@ class CommandWindow:
|
||||||
def stage5_restart_button_clicked(self, widget):
|
def stage5_restart_button_clicked(self, widget):
|
||||||
self.restart()
|
self.restart()
|
||||||
|
|
||||||
# handling xml stanzas
|
def _on_command_error(self, obj):
|
||||||
def request_command_list(self):
|
self.stage5(errorid=obj.error)
|
||||||
"""
|
|
||||||
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 callback(response):
|
def _on_command_list(self, obj):
|
||||||
'''Called on response to query.'''
|
self.commandlist = obj.commandlist
|
||||||
# FIXME: move to connection_handlers.py
|
if not self.commandlist:
|
||||||
# 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()
|
self.stage4()
|
||||||
else:
|
else:
|
||||||
self.commandlist = [(t.getAttr('node'), t.getAttr('name')) \
|
|
||||||
for t in items]
|
|
||||||
self.stage2()
|
self.stage2()
|
||||||
|
|
||||||
self.account.connection.SendAndCallForResponse(query, callback)
|
def _on_action_response(self, obj):
|
||||||
|
self.stage3_next_form(obj.command)
|
||||||
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)
|
|
||||||
else:
|
|
||||||
# we did not received any reply from service;
|
|
||||||
# FIXME: we should wait and then send cancel; for now we do nothing
|
|
||||||
pass
|
|
||||||
|
|
|
@ -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 app
|
||||||
from gajim.common import jingle_xtls
|
from gajim.common import jingle_xtls
|
||||||
from gajim.common.caps_cache import muc_caps_cache
|
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.caps import ConnectionCaps
|
||||||
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
|
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
|
||||||
from gajim.common.protocol.bytestream import ConnectionIBBytestream
|
from gajim.common.protocol.bytestream import ConnectionIBBytestream
|
||||||
|
@ -218,7 +217,7 @@ class ConnectionDisco:
|
||||||
if not self.connection or self.connected < 2:
|
if not self.connection or self.connected < 2:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.commandItemsQuery(con, iq_obj):
|
if self.get_module('AdHocCommands').command_items_query(iq_obj):
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
node = iq_obj.getTagAttr('query', 'node')
|
node = iq_obj.getTagAttr('query', 'node')
|
||||||
if node is None:
|
if node is None:
|
||||||
|
@ -226,7 +225,7 @@ class ConnectionDisco:
|
||||||
self.connection.send(result)
|
self.connection.send(result)
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
if node == nbxmpp.NS_COMMANDS:
|
if node == nbxmpp.NS_COMMANDS:
|
||||||
self.commandListQuery(con, iq_obj)
|
self.get_module('AdHocCommands').command_list_query(iq_obj)
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
def _DiscoverInfoGetCB(self, con, iq_obj):
|
def _DiscoverInfoGetCB(self, con, iq_obj):
|
||||||
|
@ -235,7 +234,7 @@ class ConnectionDisco:
|
||||||
return
|
return
|
||||||
node = iq_obj.getQuerynode()
|
node = iq_obj.getQuerynode()
|
||||||
|
|
||||||
if self.commandInfoQuery(con, iq_obj):
|
if self.get_module('AdHocCommands').command_info_query(iq_obj):
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
id_ = iq_obj.getAttr('id')
|
id_ = iq_obj.getAttr('id')
|
||||||
|
@ -743,14 +742,12 @@ class ConnectionHandlersBase:
|
||||||
return sess
|
return sess
|
||||||
|
|
||||||
class ConnectionHandlers(ConnectionArchive313,
|
class ConnectionHandlers(ConnectionArchive313,
|
||||||
ConnectionSocks5Bytestream, ConnectionDisco,
|
ConnectionSocks5Bytestream, ConnectionDisco, ConnectionCaps,
|
||||||
ConnectionCommands, ConnectionCaps,
|
|
||||||
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
|
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ConnectionArchive313.__init__(self)
|
ConnectionArchive313.__init__(self)
|
||||||
ConnectionSocks5Bytestream.__init__(self)
|
ConnectionSocks5Bytestream.__init__(self)
|
||||||
ConnectionIBBytestream.__init__(self)
|
ConnectionIBBytestream.__init__(self)
|
||||||
ConnectionCommands.__init__(self)
|
|
||||||
|
|
||||||
# Handle presences BEFORE caps
|
# Handle presences BEFORE caps
|
||||||
app.nec.register_incoming_event(PresenceReceivedEvent)
|
app.nec.register_incoming_event(PresenceReceivedEvent)
|
||||||
|
@ -1339,8 +1336,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
|
||||||
nbxmpp.NS_MUC_ADMIN)
|
nbxmpp.NS_MUC_ADMIN)
|
||||||
con.RegisterHandler('iq', self._SecLabelCB, 'result',
|
con.RegisterHandler('iq', self._SecLabelCB, 'result',
|
||||||
nbxmpp.NS_SECLABEL_CATALOG)
|
nbxmpp.NS_SECLABEL_CATALOG)
|
||||||
con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
|
|
||||||
nbxmpp.NS_COMMANDS)
|
|
||||||
con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
|
con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
|
||||||
nbxmpp.NS_DISCO_INFO)
|
nbxmpp.NS_DISCO_INFO)
|
||||||
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
|
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
|
||||||
|
|
|
@ -18,6 +18,8 @@ from pathlib import Path
|
||||||
|
|
||||||
log = logging.getLogger('gajim.c.m')
|
log = logging.getLogger('gajim.c.m')
|
||||||
|
|
||||||
|
ZEROCONF_MODULES = ['adhoc_commands']
|
||||||
|
|
||||||
imported_modules = []
|
imported_modules = []
|
||||||
_modules = {}
|
_modules = {}
|
||||||
|
|
||||||
|
@ -31,12 +33,26 @@ for file in Path(__file__).parent.iterdir():
|
||||||
if file.stem == 'pep':
|
if file.stem == 'pep':
|
||||||
# Register the PEP module first, because other modules
|
# Register the PEP module first, because other modules
|
||||||
# depend on it
|
# depend on it
|
||||||
imported_modules.insert(0, module)
|
imported_modules.insert(0, (module, file.stem))
|
||||||
else:
|
else:
|
||||||
imported_modules.append(module)
|
imported_modules.append((module, file.stem))
|
||||||
|
|
||||||
|
|
||||||
class ModuleMock:
|
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 __getattr__(self, key):
|
||||||
def _mock(self, *args, **kwargs):
|
def _mock(self, *args, **kwargs):
|
||||||
return
|
return
|
||||||
|
@ -48,7 +64,11 @@ def register(con, *args, **kwargs):
|
||||||
return
|
return
|
||||||
_modules[con.name] = {}
|
_modules[con.name] = {}
|
||||||
for module in imported_modules:
|
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
|
_modules[con.name][name] = instance
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,7 +80,7 @@ def get(account, name):
|
||||||
try:
|
try:
|
||||||
return _modules[account][name]
|
return _modules[account][name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return ModuleMock()
|
return ModuleMock(name)
|
||||||
|
|
||||||
|
|
||||||
def get_handlers(con):
|
def get_handlers(con):
|
||||||
|
|
573
gajim/common/modules/adhoc_commands.py
Normal file
573
gajim/common/modules/adhoc_commands.py
Normal file
|
@ -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
|
import nbxmpp
|
||||||
|
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common.commands import ConnectionCommands
|
|
||||||
from gajim.common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
|
from gajim.common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
|
||||||
from gajim.common.connection_handlers_events import ZeroconfMessageReceivedEvent
|
from gajim.common.connection_handlers_events import ZeroconfMessageReceivedEvent
|
||||||
|
|
||||||
|
@ -49,13 +48,12 @@ class ConnectionVcard:
|
||||||
|
|
||||||
|
|
||||||
class ConnectionHandlersZeroconf(ConnectionVcard,
|
class ConnectionHandlersZeroconf(ConnectionVcard,
|
||||||
ConnectionSocks5BytestreamZeroconf, ConnectionCommands,
|
ConnectionSocks5BytestreamZeroconf,
|
||||||
connection_handlers.ConnectionHandlersBase,
|
connection_handlers.ConnectionHandlersBase,
|
||||||
connection_handlers.ConnectionJingle):
|
connection_handlers.ConnectionJingle):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ConnectionVcard.__init__(self)
|
ConnectionVcard.__init__(self)
|
||||||
ConnectionSocks5BytestreamZeroconf.__init__(self)
|
ConnectionSocks5BytestreamZeroconf.__init__(self)
|
||||||
ConnectionCommands.__init__(self)
|
|
||||||
connection_handlers.ConnectionJingle.__init__(self)
|
connection_handlers.ConnectionJingle.__init__(self)
|
||||||
connection_handlers.ConnectionHandlersBase.__init__(self)
|
connection_handlers.ConnectionHandlersBase.__init__(self)
|
||||||
|
|
||||||
|
@ -82,7 +80,7 @@ connection_handlers.ConnectionJingle):
|
||||||
if not self.connection or self.connected < 2:
|
if not self.connection or self.connected < 2:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.commandItemsQuery(con, iq_obj):
|
if self.get_module('AdHocCommands').command_items_query(iq_obj):
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
node = iq_obj.getTagAttr('query', 'node')
|
node = iq_obj.getTagAttr('query', 'node')
|
||||||
if node is None:
|
if node is None:
|
||||||
|
@ -90,5 +88,5 @@ connection_handlers.ConnectionJingle):
|
||||||
self.connection.send(result)
|
self.connection.send(result)
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
if node == nbxmpp.NS_COMMANDS:
|
if node == nbxmpp.NS_COMMANDS:
|
||||||
self.commandListQuery(con, iq_obj)
|
self.get_module('AdHocCommands').command_list_query(iq_obj)
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
|
@ -47,6 +47,7 @@ from gi.repository import GLib
|
||||||
from gajim.common.connection import CommonConnection
|
from gajim.common.connection import CommonConnection
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import ged
|
from gajim.common import ged
|
||||||
|
from gajim.common import modules
|
||||||
from gajim.common.zeroconf import client_zeroconf
|
from gajim.common.zeroconf import client_zeroconf
|
||||||
from gajim.common.zeroconf import zeroconf
|
from gajim.common.zeroconf import zeroconf
|
||||||
from gajim.common.zeroconf.connection_handlers_zeroconf import *
|
from gajim.common.zeroconf.connection_handlers_zeroconf import *
|
||||||
|
@ -69,6 +70,9 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
||||||
CommonConnection.__init__(self, name)
|
CommonConnection.__init__(self, name)
|
||||||
self.is_zeroconf = True
|
self.is_zeroconf = True
|
||||||
|
|
||||||
|
# Register all modules
|
||||||
|
modules.register(self)
|
||||||
|
|
||||||
app.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
|
app.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
|
||||||
self._nec_message_outgoing)
|
self._nec_message_outgoing)
|
||||||
app.ged.register_event_handler('stanza-message-outgoing', ged.OUT_CORE,
|
app.ged.register_event_handler('stanza-message-outgoing', ged.OUT_CORE,
|
||||||
|
|
Loading…
Add table
Reference in a new issue