Refactor MUC module
- nbxmpp provides now most of the MUC code
This commit is contained in:
parent
c63e32634a
commit
8094cadbea
14 changed files with 278 additions and 527 deletions
|
@ -1482,12 +1482,12 @@ class ChatControl(ChatControlBase):
|
||||||
self._add_info_bar_message(markup, [b], file_props, Gtk.MessageType.ERROR)
|
self._add_info_bar_message(markup, [b], file_props, Gtk.MessageType.ERROR)
|
||||||
|
|
||||||
def _on_accept_gc_invitation(self, widget, event):
|
def _on_accept_gc_invitation(self, widget, event):
|
||||||
if event.is_continued:
|
if event.continued:
|
||||||
app.interface.join_gc_room(self.account, event.room_jid,
|
app.interface.join_gc_room(self.account, str(event.muc),
|
||||||
app.nicks[self.account], event.password,
|
app.nicks[self.account], event.password,
|
||||||
is_continued=True)
|
is_continued=True)
|
||||||
else:
|
else:
|
||||||
app.interface.join_gc_minimal(self.account, event.room_jid)
|
app.interface.join_gc_minimal(self.account, str(event.muc))
|
||||||
|
|
||||||
app.events.remove_events(self.account, self.contact.jid, event=event)
|
app.events.remove_events(self.account, self.contact.jid, event=event)
|
||||||
|
|
||||||
|
@ -1495,14 +1495,14 @@ class ChatControl(ChatControlBase):
|
||||||
app.events.remove_events(self.account, self.contact.jid, event=event)
|
app.events.remove_events(self.account, self.contact.jid, event=event)
|
||||||
|
|
||||||
def _get_gc_invitation(self, event):
|
def _get_gc_invitation(self, event):
|
||||||
markup = '<b>%s:</b> %s' % (_('Groupchat Invitation'), event.room_jid)
|
markup = '<b>%s:</b> %s' % (_('Groupchat Invitation'), event.muc)
|
||||||
if event.reason:
|
if event.reason:
|
||||||
markup += ' (%s)' % event.reason
|
markup += ' (%s)' % event.reason
|
||||||
b1 = Gtk.Button.new_with_mnemonic(_('_Join'))
|
b1 = Gtk.Button.new_with_mnemonic(_('_Join'))
|
||||||
b1.connect('clicked', self._on_accept_gc_invitation, event)
|
b1.connect('clicked', self._on_accept_gc_invitation, event)
|
||||||
b2 = Gtk.Button(stock=Gtk.STOCK_CANCEL)
|
b2 = Gtk.Button(stock=Gtk.STOCK_CANCEL)
|
||||||
b2.connect('clicked', self._on_cancel_gc_invitation, event)
|
b2.connect('clicked', self._on_cancel_gc_invitation, event)
|
||||||
self._add_info_bar_message(markup, [b1, b2], (event.room_jid,
|
self._add_info_bar_message(markup, [b1, b2], (event.muc,
|
||||||
event.reason), Gtk.MessageType.QUESTION)
|
event.reason), Gtk.MessageType.QUESTION)
|
||||||
|
|
||||||
def on_event_added(self, event):
|
def on_event_added(self, event):
|
||||||
|
@ -1546,7 +1546,7 @@ class ChatControl(ChatControlBase):
|
||||||
removed = False
|
removed = False
|
||||||
for ib_msg in self.info_bar_queue:
|
for ib_msg in self.info_bar_queue:
|
||||||
if ev.type_ == 'gc-invitation':
|
if ev.type_ == 'gc-invitation':
|
||||||
if ev.room_jid == ib_msg[2][0]:
|
if ev.muc == ib_msg[2][0]:
|
||||||
self.info_bar_queue.remove(ib_msg)
|
self.info_bar_queue.remove(ib_msg)
|
||||||
removed = True
|
removed = True
|
||||||
else: # file-*
|
else: # file-*
|
||||||
|
|
|
@ -480,6 +480,7 @@ def zeroconf_is_connected():
|
||||||
config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
|
config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
|
||||||
|
|
||||||
def in_groupchat(account, room_jid):
|
def in_groupchat(account, room_jid):
|
||||||
|
room_jid = str(room_jid)
|
||||||
if room_jid not in gc_connected[account]:
|
if room_jid not in gc_connected[account]:
|
||||||
return False
|
return False
|
||||||
return gc_connected[account][room_jid]
|
return gc_connected[account][room_jid]
|
||||||
|
|
|
@ -307,12 +307,12 @@ class LegacyContactsAPI:
|
||||||
return self_contact
|
return self_contact
|
||||||
|
|
||||||
def create_not_in_roster_contact(self, jid, account, resource='', name='',
|
def create_not_in_roster_contact(self, jid, account, resource='', name='',
|
||||||
keyID=''):
|
keyID='', groupchat=False):
|
||||||
# Use Account object if available
|
# Use Account object if available
|
||||||
account = self._accounts.get(account, account)
|
account = self._accounts.get(account, account)
|
||||||
return self.create_contact(jid=jid, account=account, resource=resource,
|
return self.create_contact(jid=jid, account=account, resource=resource,
|
||||||
name=name, groups=[_('Not in Roster')], show='not in roster',
|
name=name, groups=[_('Not in Roster')], show='not in roster',
|
||||||
status='', sub='none', keyID=keyID)
|
status='', sub='none', keyID=keyID, groupchat=groupchat)
|
||||||
|
|
||||||
def copy_contact(self, contact):
|
def copy_contact(self, contact):
|
||||||
return self.create_contact(contact.jid, contact.account,
|
return self.create_contact(contact.jid, contact.account,
|
||||||
|
|
|
@ -130,15 +130,10 @@ class UnsubscribedEvent(Event):
|
||||||
|
|
||||||
class GcInvitationtEvent(Event):
|
class GcInvitationtEvent(Event):
|
||||||
type_ = 'gc-invitation'
|
type_ = 'gc-invitation'
|
||||||
def __init__(self, room_jid, reason, password, is_continued, jid_from,
|
def __init__(self, event):
|
||||||
time_=None, show_in_roster=False, show_in_systray=True):
|
Event.__init__(self, None, show_in_roster=False, show_in_systray=True)
|
||||||
Event.__init__(self, time_, show_in_roster=show_in_roster,
|
for key, value in vars(event).items():
|
||||||
show_in_systray=show_in_systray)
|
setattr(self, key, value)
|
||||||
self.room_jid = room_jid
|
|
||||||
self.reason = reason
|
|
||||||
self.password = password
|
|
||||||
self.is_continued = is_continued
|
|
||||||
self.jid_from = jid_from
|
|
||||||
|
|
||||||
class FileRequestEvent(Event):
|
class FileRequestEvent(Event):
|
||||||
type_ = 'file-request'
|
type_ = 'file-request'
|
||||||
|
|
|
@ -1462,6 +1462,15 @@ def load_json(path, key=None, default=None):
|
||||||
return json_dict
|
return json_dict
|
||||||
return json_dict.get(key, default)
|
return json_dict.get(key, default)
|
||||||
|
|
||||||
|
def ignore_contact(account, jid):
|
||||||
|
jid = str(jid)
|
||||||
|
known_contact = app.contacts.get_contacts(account, jid)
|
||||||
|
ignore = app.config.get_per('accounts', account, 'ignore_unknown_contacts')
|
||||||
|
if ignore and not known_contact:
|
||||||
|
log.info('Ignore unknown contact %s', jid)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
class AdditionalDataDict(collections.UserDict):
|
class AdditionalDataDict(collections.UserDict):
|
||||||
def __init__(self, initialdata=None):
|
def __init__(self, initialdata=None):
|
||||||
collections.UserDict.__init__(self, initialdata)
|
collections.UserDict.__init__(self, initialdata)
|
||||||
|
|
|
@ -170,5 +170,29 @@ def parse_bob_data(stanza):
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
def store_bob_data(bob_data):
|
||||||
|
if bob_data is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
algo_hash = '%s+%s' % (bob_data.algo, bob_data.hash_)
|
||||||
|
|
||||||
|
filepath = Path(configpaths.get('BOB')) / algo_hash
|
||||||
|
if algo_hash in app.bob_cache or filepath.exists():
|
||||||
|
log.info('BoB data already cached')
|
||||||
|
return
|
||||||
|
|
||||||
|
if bob_data.max_age == 0:
|
||||||
|
app.bob_cache[algo_hash] = bob_data.data
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open(str(filepath), 'w+b') as file:
|
||||||
|
file.write(bob_data.data)
|
||||||
|
except Exception:
|
||||||
|
log.exception('Unable to save data')
|
||||||
|
return
|
||||||
|
log.info('BoB data stored: %s', algo_hash)
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
def get_instance(*args, **kwargs):
|
def get_instance(*args, **kwargs):
|
||||||
return BitsOfBinary(*args, **kwargs), 'BitsOfBinary'
|
return BitsOfBinary(*args, **kwargs), 'BitsOfBinary'
|
||||||
|
|
|
@ -212,31 +212,14 @@ class Message:
|
||||||
subject = event.stanza.getSubject()
|
subject = event.stanza.getSubject()
|
||||||
groupchat = event.mtype == 'groupchat'
|
groupchat = event.mtype == 'groupchat'
|
||||||
|
|
||||||
# XEP-0045: only a message that contains a <subject/> but no <body/>
|
|
||||||
# element shall be considered a subject change for MUC purposes.
|
|
||||||
muc_subject = subject and groupchat and not event.msgtxt
|
|
||||||
|
|
||||||
# Determine timestamps
|
# Determine timestamps
|
||||||
if groupchat:
|
delay_jid = self._con.get_own_jid().getDomain()
|
||||||
delay_entity_jid = event.jid
|
timestamp = parse_delay(event.stanza, from_=delay_jid)
|
||||||
else:
|
if timestamp is None:
|
||||||
delay_entity_jid = self._con.get_own_jid().getDomain()
|
|
||||||
|
|
||||||
if muc_subject:
|
|
||||||
# MUC Subjects can have a delay timestamp
|
|
||||||
# to indicate when the user has set the subject,
|
|
||||||
# the 'from' attr on these delays is the MUC server
|
|
||||||
# but we treat it as user timestamp
|
|
||||||
timestamp = time.time()
|
timestamp = time.time()
|
||||||
user_timestamp = parse_delay(event.stanza, from_=delay_entity_jid)
|
|
||||||
|
|
||||||
else:
|
user_timestamp = parse_delay(event.stanza,
|
||||||
timestamp = parse_delay(event.stanza, from_=delay_entity_jid)
|
not_from=[delay_jid])
|
||||||
if timestamp is None:
|
|
||||||
timestamp = time.time()
|
|
||||||
|
|
||||||
user_timestamp = parse_delay(event.stanza,
|
|
||||||
not_from=[delay_entity_jid])
|
|
||||||
|
|
||||||
if user_timestamp is not None:
|
if user_timestamp is not None:
|
||||||
event.additional_data.set_value(
|
event.additional_data.set_value(
|
||||||
|
@ -271,11 +254,6 @@ class Message:
|
||||||
event.session, event.fjid, timestamp)
|
event.session, event.fjid, timestamp)
|
||||||
return
|
return
|
||||||
|
|
||||||
if muc_subject:
|
|
||||||
app.nec.push_incoming_event(NetworkEvent('gc-subject-received',
|
|
||||||
**vars(event)))
|
|
||||||
return
|
|
||||||
|
|
||||||
if groupchat:
|
if groupchat:
|
||||||
if not event.msgtxt:
|
if not event.msgtxt:
|
||||||
return
|
return
|
||||||
|
|
|
@ -17,18 +17,17 @@
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import weakref
|
|
||||||
|
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
|
from nbxmpp.const import InviteType
|
||||||
|
from nbxmpp.structs import StanzaHandler
|
||||||
|
|
||||||
from gajim.common import i18n
|
from gajim.common import i18n
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import helpers
|
from gajim.common import helpers
|
||||||
from gajim.common.caps_cache import muc_caps_cache
|
from gajim.common.caps_cache import muc_caps_cache
|
||||||
from gajim.common.nec import NetworkEvent
|
from gajim.common.nec import NetworkEvent
|
||||||
from gajim.common.nec import NetworkIncomingEvent
|
from gajim.common.modules.bits_of_binary import store_bob_data
|
||||||
from gajim.common.modules import dataforms
|
|
||||||
from gajim.common.modules.bits_of_binary import parse_bob_data
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.c.m.muc')
|
log = logging.getLogger('gajim.c.m.muc')
|
||||||
|
|
||||||
|
@ -39,13 +38,55 @@ class MUC:
|
||||||
self._account = con.name
|
self._account = con.name
|
||||||
|
|
||||||
self.handlers = [
|
self.handlers = [
|
||||||
('message', self._on_config_change, '', nbxmpp.NS_MUC_USER),
|
StanzaHandler(name='message',
|
||||||
('message', self._mediated_invite, 'normal', nbxmpp.NS_MUC_USER),
|
callback=self._on_subject_change,
|
||||||
('message', self._direct_invite, '', nbxmpp.NS_CONFERENCE),
|
typ='groupchat',
|
||||||
('message', self._on_captcha_challenge, '', nbxmpp.NS_CAPTCHA),
|
priority=49),
|
||||||
('message', self._on_voice_request, '', nbxmpp.NS_DATA, 45),
|
StanzaHandler(name='message',
|
||||||
|
callback=self._on_config_change,
|
||||||
|
ns=nbxmpp.NS_MUC_USER,
|
||||||
|
priority=49),
|
||||||
|
StanzaHandler(name='message',
|
||||||
|
callback=self._on_invite_or_decline,
|
||||||
|
typ='normal',
|
||||||
|
ns=nbxmpp.NS_MUC_USER,
|
||||||
|
priority=49),
|
||||||
|
StanzaHandler(name='message',
|
||||||
|
callback=self._on_invite_or_decline,
|
||||||
|
ns=nbxmpp.NS_CONFERENCE,
|
||||||
|
priority=49),
|
||||||
|
StanzaHandler(name='message',
|
||||||
|
callback=self._on_captcha_challenge,
|
||||||
|
ns=nbxmpp.NS_CAPTCHA,
|
||||||
|
priority=49),
|
||||||
|
StanzaHandler(name='message',
|
||||||
|
callback=self._on_voice_request,
|
||||||
|
ns=nbxmpp.NS_DATA,
|
||||||
|
priority=49)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
self._nbmxpp_methods = [
|
||||||
|
'get_affiliation',
|
||||||
|
'set_role',
|
||||||
|
'set_affiliation',
|
||||||
|
'set_config',
|
||||||
|
'set_subject',
|
||||||
|
'cancel_config',
|
||||||
|
'send_captcha',
|
||||||
|
'decline',
|
||||||
|
'request_voice'
|
||||||
|
'destroy',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
if key in self._nbmxpp_methods:
|
||||||
|
if not app.account_is_connected(self._account):
|
||||||
|
log.warning('Account %s not connected, cant use %s',
|
||||||
|
self._account, key)
|
||||||
|
return
|
||||||
|
module = self._con.connection.get_module('MUC')
|
||||||
|
return getattr(module, key)
|
||||||
|
|
||||||
def pass_disco(self, from_, identities, features, _data, _node):
|
def pass_disco(self, from_, identities, features, _data, _node):
|
||||||
for identity in identities:
|
for identity in identities:
|
||||||
if identity.get('category') != 'conference':
|
if identity.get('category') != 'conference':
|
||||||
|
@ -108,438 +149,136 @@ class MUC:
|
||||||
if tags:
|
if tags:
|
||||||
muc_x.setTag('history', tags)
|
muc_x.setTag('history', tags)
|
||||||
|
|
||||||
def set_subject(self, room_jid, subject):
|
def _on_subject_change(self, _con, _stanza, properties):
|
||||||
if not app.account_is_connected(self._account):
|
if not properties.is_muc_subject:
|
||||||
return
|
|
||||||
message = nbxmpp.Message(room_jid, typ='groupchat', subject=subject)
|
|
||||||
log.info('Set subject for %s', room_jid)
|
|
||||||
self._con.connection.send(message)
|
|
||||||
|
|
||||||
def _on_voice_request(self, _con, stanza):
|
|
||||||
data_form = stanza.getTag('x', namespace=nbxmpp.NS_DATA)
|
|
||||||
if data_form is None:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if stanza.getBody():
|
jid = properties.jid.getBare()
|
||||||
return
|
contact = app.contacts.get_groupchat_contact(self._account, jid)
|
||||||
|
|
||||||
room_jid = str(stanza.getFrom())
|
|
||||||
contact = app.contacts.get_groupchat_contact(self._account, room_jid)
|
|
||||||
if contact is None:
|
if contact is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
data_form = dataforms.extend_form(data_form)
|
contact.status = properties.subject
|
||||||
try:
|
|
||||||
if data_form['FORM_TYPE'].value != nbxmpp.NS_MUC_REQUEST:
|
app.nec.push_incoming_event(
|
||||||
return
|
NetworkEvent('gc-subject-received',
|
||||||
except KeyError:
|
account=self._account,
|
||||||
|
jid=jid,
|
||||||
|
subject=properties.subject,
|
||||||
|
nickname=properties.muc_nickname,
|
||||||
|
user_timestamp=properties.user_timestamp))
|
||||||
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
|
def _on_voice_request(self, _con, _stanza, properties):
|
||||||
|
if not properties.is_voice_request:
|
||||||
|
return
|
||||||
|
|
||||||
|
jid = str(properties.jid)
|
||||||
|
contact = app.contacts.get_groupchat_contact(self._account, jid)
|
||||||
|
if contact is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
app.nec.push_incoming_event(
|
app.nec.push_incoming_event(
|
||||||
NetworkEvent('voice-approval',
|
NetworkEvent('voice-approval',
|
||||||
account=self._account,
|
account=self._account,
|
||||||
room_jid=room_jid,
|
jid=jid,
|
||||||
form=data_form))
|
form=properties.voice_request.form))
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
def _on_captcha_challenge(self, _con, stanza):
|
def _on_captcha_challenge(self, _con, _stanza, properties):
|
||||||
captcha = stanza.getTag('captcha', namespace=nbxmpp.NS_CAPTCHA)
|
if not properties.is_captcha_challenge:
|
||||||
if captcha is None:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
parse_bob_data(stanza)
|
contact = app.contacts.get_groupchat_contact(self._account,
|
||||||
room_jid = str(stanza.getFrom())
|
str(properties.jid))
|
||||||
contact = app.contacts.get_groupchat_contact(self._account, room_jid)
|
|
||||||
if contact is None:
|
if contact is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info('Captcha challenge received from %s', room_jid)
|
log.info('Captcha challenge received from %s', properties.jid)
|
||||||
data_form = captcha.getTag('x', namespace=nbxmpp.NS_DATA)
|
store_bob_data(properties.captcha.bob_data)
|
||||||
data_form = dataforms.extend_form(node=data_form)
|
|
||||||
app.nec.push_incoming_event(
|
app.nec.push_incoming_event(
|
||||||
NetworkEvent('captcha-challenge',
|
NetworkEvent('captcha-challenge',
|
||||||
account=self._account,
|
account=self._account,
|
||||||
room_jid=room_jid,
|
jid=properties.jid,
|
||||||
form=data_form))
|
form=properties.captcha.form))
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
def send_captcha(self, room_jid, form_node):
|
def _on_config_change(self, _con, _stanza, properties):
|
||||||
if not app.account_is_connected(self._account):
|
if not properties.is_muc_config_change:
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq(typ='set', to=room_jid)
|
|
||||||
captcha = iq.addChild(name='captcha', namespace=nbxmpp.NS_CAPTCHA)
|
|
||||||
captcha.addChild(node=form_node)
|
|
||||||
self._con.connection.send(iq)
|
|
||||||
|
|
||||||
def request_config(self, room_jid):
|
|
||||||
if not app.account_is_connected(self._account):
|
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq(typ='get',
|
|
||||||
queryNS=nbxmpp.NS_MUC_OWNER,
|
|
||||||
to=room_jid)
|
|
||||||
iq.setAttr('xml:lang', i18n.LANG)
|
|
||||||
log.info('Request config for %s', room_jid)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._config_received)
|
|
||||||
|
|
||||||
def _config_received(self, stanza):
|
|
||||||
if not nbxmpp.isResultNode(stanza):
|
|
||||||
log.info('Error: %s', stanza.getError())
|
|
||||||
return
|
return
|
||||||
|
|
||||||
room_jid = stanza.getFrom().getStripped()
|
log.info('Received config change: %s %s',
|
||||||
payload = stanza.getQueryPayload()
|
properties.jid, properties.muc_status_codes)
|
||||||
|
|
||||||
for form in payload:
|
|
||||||
if form.getNamespace() == nbxmpp.NS_DATA:
|
|
||||||
dataform = dataforms.extend_form(node=form)
|
|
||||||
log.info('Config form received for %s', room_jid)
|
|
||||||
app.nec.push_incoming_event(MucOwnerReceivedEvent(
|
|
||||||
None,
|
|
||||||
conn=self._con,
|
|
||||||
form_node=form,
|
|
||||||
dataform=dataform,
|
|
||||||
jid=room_jid))
|
|
||||||
break
|
|
||||||
|
|
||||||
def cancel_config(self, room_jid):
|
|
||||||
if not app.account_is_connected(self._account):
|
|
||||||
return
|
|
||||||
cancel = nbxmpp.Node(tag='x', attrs={'xmlns': nbxmpp.NS_DATA,
|
|
||||||
'type': 'cancel'})
|
|
||||||
iq = nbxmpp.Iq(typ='set',
|
|
||||||
queryNS=nbxmpp.NS_MUC_OWNER,
|
|
||||||
payload=cancel,
|
|
||||||
to=room_jid)
|
|
||||||
log.info('Cancel config for %s', room_jid)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._default_response, {})
|
|
||||||
|
|
||||||
def _on_config_change(self, _con, stanza):
|
|
||||||
muc_user = stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
|
|
||||||
if muc_user is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if stanza.getBody():
|
|
||||||
return
|
|
||||||
|
|
||||||
room_list = app.contacts.get_gc_list(self._account)
|
|
||||||
room_jid = str(stanza.getFrom())
|
|
||||||
if room_jid not in room_list:
|
|
||||||
# Message not from a group chat
|
|
||||||
return
|
|
||||||
|
|
||||||
# https://xmpp.org/extensions/xep-0045.html#registrar-statuscodes
|
|
||||||
change_codes = ['100', '102', '103', '104',
|
|
||||||
'170', '171', '172', '173', '174']
|
|
||||||
|
|
||||||
codes = set()
|
|
||||||
for status in muc_user.getTags('status'):
|
|
||||||
code = status.getAttr('code')
|
|
||||||
if code in change_codes:
|
|
||||||
codes.add(code)
|
|
||||||
|
|
||||||
if not codes:
|
|
||||||
return
|
|
||||||
|
|
||||||
log.info('Received config change: %s', codes)
|
|
||||||
app.nec.push_incoming_event(
|
app.nec.push_incoming_event(
|
||||||
NetworkEvent('gc-config-changed-received',
|
NetworkEvent('gc-config-changed-received',
|
||||||
account=self._account,
|
account=self._account,
|
||||||
room_jid=room_jid,
|
jid=properties.jid,
|
||||||
status_codes=codes))
|
status_codes=properties.muc_status_codes))
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
def destroy(self, room_jid, reason='', jid=''):
|
def _on_invite_or_decline(self, _con, _stanza, properties):
|
||||||
if not app.account_is_connected(self._account):
|
if properties.muc_decline is not None:
|
||||||
return
|
data = properties.muc_decline
|
||||||
iq = nbxmpp.Iq(typ='set',
|
if helpers.ignore_contact(self._account, data.from_):
|
||||||
queryNS=nbxmpp.NS_MUC_OWNER,
|
raise nbxmpp.NodeProcessed
|
||||||
to=room_jid)
|
|
||||||
destroy = iq.setQuery().setTag('destroy')
|
|
||||||
if reason:
|
|
||||||
destroy.setTagData('reason', reason)
|
|
||||||
if jid:
|
|
||||||
destroy.setAttr('jid', jid)
|
|
||||||
log.info('Destroy room: %s, reason: %s, alternate: %s',
|
|
||||||
room_jid, reason, jid)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._default_response, {})
|
|
||||||
|
|
||||||
def set_config(self, room_jid, form):
|
log.info('Invite declined from: %s, reason: %s',
|
||||||
if not app.account_is_connected(self._account):
|
data.from_, data.reason)
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_OWNER)
|
|
||||||
query = iq.setQuery()
|
|
||||||
form.setAttr('type', 'submit')
|
|
||||||
query.addChild(node=form)
|
|
||||||
log.info('Set config for %s', room_jid)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._default_response, {})
|
|
||||||
|
|
||||||
def set_affiliation(self, room_jid, users_dict):
|
|
||||||
if not app.account_is_connected(self._account):
|
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
|
|
||||||
item = iq.setQuery()
|
|
||||||
for jid in users_dict:
|
|
||||||
affiliation = users_dict[jid].get('affiliation')
|
|
||||||
reason = users_dict[jid].get('reason')
|
|
||||||
nick = users_dict[jid].get('nick')
|
|
||||||
item_tag = item.addChild('item', {'jid': jid,
|
|
||||||
'affiliation': affiliation})
|
|
||||||
if reason is not None:
|
|
||||||
item_tag.setTagData('reason', reason)
|
|
||||||
|
|
||||||
if nick is not None:
|
|
||||||
item_tag.setAttr('nick', nick)
|
|
||||||
log.info('Set affiliation for %s: %s', room_jid, users_dict)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._default_response, {})
|
|
||||||
|
|
||||||
def get_affiliation(self, room_jid, affiliation, success_cb, error_cb):
|
|
||||||
if not app.account_is_connected(self._account):
|
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq(typ='get', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
|
|
||||||
item = iq.setQuery().setTag('item')
|
|
||||||
item.setAttr('affiliation', affiliation)
|
|
||||||
log.info('Get affiliation %s for %s', affiliation, room_jid)
|
|
||||||
|
|
||||||
weak_success_cb = weakref.WeakMethod(success_cb)
|
|
||||||
weak_error_cb = weakref.WeakMethod(error_cb)
|
|
||||||
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._affiliation_received, {'affiliation': affiliation,
|
|
||||||
'success_cb': weak_success_cb,
|
|
||||||
'error_cb': weak_error_cb})
|
|
||||||
|
|
||||||
def _affiliation_received(self, _con, stanza, affiliation,
|
|
||||||
success_cb, error_cb):
|
|
||||||
if not nbxmpp.isResultNode(stanza):
|
|
||||||
if error_cb() is not None:
|
|
||||||
error_cb()(affiliation, stanza.getError())
|
|
||||||
return
|
|
||||||
|
|
||||||
room_jid = stanza.getFrom().getStripped()
|
|
||||||
query = stanza.getTag('query', namespace=nbxmpp.NS_MUC_ADMIN)
|
|
||||||
items = query.getTags('item')
|
|
||||||
users_dict = {}
|
|
||||||
for item in items:
|
|
||||||
try:
|
|
||||||
jid = helpers.parse_jid(item.getAttr('jid'))
|
|
||||||
except helpers.InvalidFormat:
|
|
||||||
log.warning('Invalid JID: %s, ignoring it',
|
|
||||||
item.getAttr('jid'))
|
|
||||||
continue
|
|
||||||
|
|
||||||
users_dict[jid] = {}
|
|
||||||
if item.has_attr('nick'):
|
|
||||||
users_dict[jid]['nick'] = item.getAttr('nick')
|
|
||||||
if item.has_attr('role'):
|
|
||||||
users_dict[jid]['role'] = item.getAttr('role')
|
|
||||||
reason = item.getTagData('reason')
|
|
||||||
if reason:
|
|
||||||
users_dict[jid]['reason'] = reason
|
|
||||||
|
|
||||||
log.info('%s affiliations received from %s: %s',
|
|
||||||
affiliation, room_jid, users_dict)
|
|
||||||
|
|
||||||
if success_cb() is not None:
|
|
||||||
success_cb()(self._account, room_jid, affiliation, users_dict)
|
|
||||||
|
|
||||||
def set_role(self, room_jid, nick, role, reason=''):
|
|
||||||
if not app.account_is_connected(self._account):
|
|
||||||
return
|
|
||||||
iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
|
|
||||||
item = iq.setQuery().setTag('item')
|
|
||||||
item.setAttr('nick', nick)
|
|
||||||
item.setAttr('role', role)
|
|
||||||
if reason:
|
|
||||||
item.addChild(name='reason', payload=reason)
|
|
||||||
log.info('Set role for %s: %s %s %s', room_jid, nick, role, reason)
|
|
||||||
self._con.connection.SendAndCallForResponse(
|
|
||||||
iq, self._default_response, {})
|
|
||||||
|
|
||||||
def _mediated_invite(self, _con, stanza):
|
|
||||||
muc_user = stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
|
|
||||||
if muc_user is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if stanza.getType() == 'error':
|
|
||||||
return
|
|
||||||
|
|
||||||
if stanza.getBody():
|
|
||||||
return
|
|
||||||
|
|
||||||
decline = muc_user.getTag('decline')
|
|
||||||
if decline is not None:
|
|
||||||
|
|
||||||
room_jid = stanza.getFrom().getStripped()
|
|
||||||
from_ = self._get_from(room_jid, decline)
|
|
||||||
|
|
||||||
reason = decline.getTagData('reason')
|
|
||||||
log.info('Invite declined: %s, %s', reason, from_)
|
|
||||||
app.nec.push_incoming_event(
|
app.nec.push_incoming_event(
|
||||||
GcDeclineReceived(None,
|
NetworkEvent('gc-decline-received',
|
||||||
account=self._account,
|
account=self._account,
|
||||||
from_=from_,
|
**data._asdict()))
|
||||||
room_jid=room_jid,
|
|
||||||
reason=reason))
|
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
invite = muc_user.getTag('invite')
|
if properties.muc_invite is not None:
|
||||||
if invite is not None:
|
data = properties.muc_invite
|
||||||
|
if helpers.ignore_contact(self._account, data.from_):
|
||||||
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
room_jid = stanza.getFrom().getStripped()
|
log.info('Invite from: %s, to: %s', data.from_, data.muc)
|
||||||
from_ = self._get_from(room_jid, invite)
|
|
||||||
|
|
||||||
reason = invite.getTagData('reason')
|
if app.in_groupchat(self._account, data.muc):
|
||||||
password = muc_user.getTagData('password')
|
|
||||||
is_continued = invite.getTag('continue') is not None
|
|
||||||
log.info('Mediated invite: continued: %s, reason: %s, from: %s',
|
|
||||||
is_continued, reason, from_)
|
|
||||||
if room_jid in app.gc_connected[self._account] and \
|
|
||||||
app.gc_connected[self._account][room_jid]:
|
|
||||||
# We are already in groupchat. Ignore invitation
|
# We are already in groupchat. Ignore invitation
|
||||||
log.info('We are already in this room')
|
log.info('We are already in this room')
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
app.nec.push_incoming_event(
|
app.nec.push_incoming_event(
|
||||||
GcInvitationReceived(None,
|
NetworkEvent('gc-invitation-received',
|
||||||
account=self._account,
|
account=self._account,
|
||||||
from_=from_,
|
**data._asdict()))
|
||||||
room_jid=room_jid,
|
|
||||||
reason=reason,
|
|
||||||
password=password,
|
|
||||||
is_continued=is_continued))
|
|
||||||
raise nbxmpp.NodeProcessed
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
def _get_from(self, room_jid, stanza):
|
|
||||||
try:
|
|
||||||
from_ = nbxmpp.JID(helpers.parse_jid(stanza.getAttr('from')))
|
|
||||||
except helpers.InvalidFormat:
|
|
||||||
log.warning('Invalid JID on invite: %s, ignoring it',
|
|
||||||
stanza.getAttr('from'))
|
|
||||||
raise nbxmpp.NodeProcessed
|
|
||||||
|
|
||||||
known_contact = app.contacts.get_contacts(self._account, room_jid)
|
|
||||||
ignore = app.config.get_per(
|
|
||||||
'accounts', self._account, 'ignore_unknown_contacts')
|
|
||||||
if ignore and not known_contact:
|
|
||||||
log.info('Ignore invite from unknown contact %s', from_)
|
|
||||||
raise nbxmpp.NodeProcessed
|
|
||||||
|
|
||||||
return from_
|
|
||||||
|
|
||||||
def _direct_invite(self, _con, stanza):
|
|
||||||
direct = stanza.getTag('x', namespace=nbxmpp.NS_CONFERENCE)
|
|
||||||
if direct is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
from_ = stanza.getFrom()
|
|
||||||
|
|
||||||
try:
|
|
||||||
room_jid = helpers.parse_jid(direct.getAttr('jid'))
|
|
||||||
except helpers.InvalidFormat:
|
|
||||||
log.warning('Invalid JID on invite: %s, ignoring it',
|
|
||||||
direct.getAttr('jid'))
|
|
||||||
raise nbxmpp.NodeProcessed
|
|
||||||
|
|
||||||
reason = direct.getAttr('reason')
|
|
||||||
password = direct.getAttr('password')
|
|
||||||
is_continued = direct.getAttr('continue') == 'true'
|
|
||||||
|
|
||||||
log.info('Direct invite: continued: %s, reason: %s, from: %s',
|
|
||||||
is_continued, reason, from_)
|
|
||||||
|
|
||||||
app.nec.push_incoming_event(
|
|
||||||
GcInvitationReceived(None,
|
|
||||||
account=self._account,
|
|
||||||
from_=from_,
|
|
||||||
room_jid=room_jid,
|
|
||||||
reason=reason,
|
|
||||||
password=password,
|
|
||||||
is_continued=is_continued))
|
|
||||||
raise nbxmpp.NodeProcessed
|
|
||||||
|
|
||||||
def invite(self, room, to, reason=None, continue_=False):
|
def invite(self, room, to, reason=None, continue_=False):
|
||||||
if not app.account_is_connected(self._account):
|
if not app.account_is_connected(self._account):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
type_ = InviteType.MEDIATED
|
||||||
contact = app.contacts.get_contact_from_full_jid(self._account, to)
|
contact = app.contacts.get_contact_from_full_jid(self._account, to)
|
||||||
if contact and contact.supports(nbxmpp.NS_CONFERENCE):
|
if contact and contact.supports(nbxmpp.NS_CONFERENCE):
|
||||||
invite = self._build_direct_invite(room, to, reason, continue_)
|
type_ = InviteType.DIRECT
|
||||||
else:
|
|
||||||
invite = self._build_mediated_invite(room, to, reason, continue_)
|
|
||||||
self._con.connection.send(invite)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _build_direct_invite(room, to, reason, continue_):
|
|
||||||
message = nbxmpp.Message(to=to)
|
|
||||||
attrs = {'jid': room}
|
|
||||||
if reason:
|
|
||||||
attrs['reason'] = reason
|
|
||||||
if continue_:
|
|
||||||
attrs['continue'] = 'true'
|
|
||||||
password = app.gc_passwords.get(room, None)
|
password = app.gc_passwords.get(room, None)
|
||||||
if password:
|
self._con.connection.get_module('MUC').invite(
|
||||||
attrs['password'] = password
|
room, to, reason, password, continue_, type_)
|
||||||
message.addChild(name='x', attrs=attrs,
|
|
||||||
namespace=nbxmpp.NS_CONFERENCE)
|
|
||||||
return message
|
|
||||||
|
|
||||||
@staticmethod
|
def request_config(self, room_jid):
|
||||||
def _build_mediated_invite(room, to, reason, continue_):
|
|
||||||
message = nbxmpp.Message(to=room)
|
|
||||||
muc_user = message.addChild('x', namespace=nbxmpp.NS_MUC_USER)
|
|
||||||
invite = muc_user.addChild('invite', attrs={'to': to})
|
|
||||||
if continue_:
|
|
||||||
invite.addChild(name='continue')
|
|
||||||
if reason:
|
|
||||||
invite.setTagData('reason', reason)
|
|
||||||
password = app.gc_passwords.get(room, None)
|
|
||||||
if password:
|
|
||||||
muc_user.setTagData('password', password)
|
|
||||||
return message
|
|
||||||
|
|
||||||
def decline(self, room, to, reason=None):
|
|
||||||
if not app.account_is_connected(self._account):
|
if not app.account_is_connected(self._account):
|
||||||
return
|
return
|
||||||
message = nbxmpp.Message(to=room)
|
|
||||||
muc_user = message.addChild('x', namespace=nbxmpp.NS_MUC_USER)
|
|
||||||
decline = muc_user.addChild('decline', attrs={'to': to})
|
|
||||||
if reason:
|
|
||||||
decline.setTagData('reason', reason)
|
|
||||||
self._con.connection.send(message)
|
|
||||||
|
|
||||||
def request_voice(self, room):
|
self._con.connection.get_module('MUC').request_config(
|
||||||
if not app.account_is_connected(self._account):
|
room_jid, i18n.LANG, callback=self._config_received)
|
||||||
|
|
||||||
|
def _config_received(self, result):
|
||||||
|
if result.is_error:
|
||||||
return
|
return
|
||||||
message = nbxmpp.Message(to=room)
|
|
||||||
xdata = nbxmpp.DataForm(typ='submit')
|
|
||||||
xdata.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
|
|
||||||
value=nbxmpp.NS_MUC + '#request'))
|
|
||||||
xdata.addChild(node=nbxmpp.DataField(name='muc#role',
|
|
||||||
value='participant',
|
|
||||||
typ='text-single'))
|
|
||||||
message.addChild(node=xdata)
|
|
||||||
self._con.connection.send(message)
|
|
||||||
|
|
||||||
@staticmethod
|
app.nec.push_incoming_event(NetworkEvent(
|
||||||
def _default_response(_con, stanza, **kwargs):
|
'muc-owner-received',
|
||||||
if not nbxmpp.isResultNode(stanza):
|
conn=self._con,
|
||||||
log.info('Error: %s', stanza.getError())
|
dataform=result.form,
|
||||||
|
jid=result.jid))
|
||||||
|
|
||||||
class GcInvitationReceived(NetworkIncomingEvent):
|
|
||||||
name = 'gc-invitation-received'
|
|
||||||
|
|
||||||
|
|
||||||
class GcDeclineReceived(NetworkIncomingEvent):
|
|
||||||
name = 'gc-decline-received'
|
|
||||||
|
|
||||||
|
|
||||||
class MucOwnerReceivedEvent(NetworkIncomingEvent):
|
|
||||||
name = 'muc-owner-received'
|
|
||||||
|
|
||||||
|
|
||||||
def get_instance(*args, **kwargs):
|
def get_instance(*args, **kwargs):
|
||||||
|
|
|
@ -1291,51 +1291,60 @@ class RosterItemExchangeWindow:
|
||||||
|
|
||||||
|
|
||||||
class InvitationReceivedDialog:
|
class InvitationReceivedDialog:
|
||||||
def __init__(self, account, room_jid, contact_fjid, password=None,
|
def __init__(self, account, event):
|
||||||
comment=None, is_continued=False):
|
|
||||||
|
|
||||||
self.room_jid = room_jid
|
|
||||||
self.account = account
|
self.account = account
|
||||||
self.password = password
|
self.room_jid = str(event.muc)
|
||||||
self.is_continued = is_continued
|
self.from_ = str(event.from_)
|
||||||
self.contact_fjid = contact_fjid
|
self.password = event.password
|
||||||
|
self.is_continued = event.continued
|
||||||
|
|
||||||
jid = app.get_jid_without_resource(contact_fjid)
|
if event.from_.bareMatch(event.muc):
|
||||||
|
contact_text = event.from_.getResource()
|
||||||
|
else:
|
||||||
|
contact = app.contacts.get_first_contact_from_jid(
|
||||||
|
account, event.from_.getBare())
|
||||||
|
if contact is None:
|
||||||
|
contact_text = str(event.from_)
|
||||||
|
else:
|
||||||
|
contact_text = contact.get_shown_name()
|
||||||
|
|
||||||
pritext = _('''You are invited to a groupchat''')
|
pritext = _('''You are invited to a groupchat''')
|
||||||
#Don't translate $Contact
|
#Don't translate $Contact
|
||||||
if is_continued:
|
if self.is_continued:
|
||||||
sectext = _('$Contact has invited you to join a discussion')
|
sectext = _('$Contact has invited you to join a discussion')
|
||||||
else:
|
else:
|
||||||
sectext = _('$Contact has invited you to group chat %(room_jid)s')\
|
sectext = _('$Contact has invited you to group chat %(room_jid)s')\
|
||||||
% {'room_jid': room_jid}
|
% {'room_jid': self.room_jid}
|
||||||
contact = app.contacts.get_first_contact_from_jid(account, jid)
|
|
||||||
contact_text = contact and contact.name or jid
|
|
||||||
sectext = i18n.direction_mark + sectext.replace('$Contact',
|
|
||||||
contact_text)
|
|
||||||
|
|
||||||
if comment: # only if not None and not ''
|
sectext = sectext.replace('$Contact', contact_text)
|
||||||
comment = GLib.markup_escape_text(comment)
|
|
||||||
|
if event.reason:
|
||||||
|
comment = GLib.markup_escape_text(event.reason)
|
||||||
comment = _('Comment: %s') % comment
|
comment = _('Comment: %s') % comment
|
||||||
sectext += '\n\n%s' % comment
|
sectext += '\n\n%s' % comment
|
||||||
sectext += '\n\n' + _('Do you want to accept the invitation?')
|
sectext += '\n\n' + _('Do you want to accept the invitation?')
|
||||||
|
|
||||||
def on_yes(checked, text):
|
def on_yes(_checked, _text):
|
||||||
if self.is_continued:
|
if self.is_continued:
|
||||||
app.interface.join_gc_room(self.account, self.room_jid,
|
app.interface.join_gc_room(self.account,
|
||||||
app.nicks[self.account], self.password,
|
self.room_jid,
|
||||||
is_continued=True)
|
app.nicks[self.account],
|
||||||
|
self.password,
|
||||||
|
is_continued=True)
|
||||||
else:
|
else:
|
||||||
app.interface.join_gc_minimal(
|
app.interface.join_gc_minimal(self.account,
|
||||||
self.account, self.room_jid, password=self.password)
|
self.room_jid,
|
||||||
|
password=self.password)
|
||||||
|
|
||||||
def on_no(text):
|
def on_no(text):
|
||||||
app.connections[account].get_module('MUC').decline(
|
app.connections[account].get_module('MUC').decline(
|
||||||
self.room_jid, self.contact_fjid, text)
|
self.room_jid, self.from_, text)
|
||||||
|
|
||||||
dlg = YesNoDialog(pritext, sectext,
|
dlg = YesNoDialog(pritext,
|
||||||
text_label=_('Reason (if you decline):'), on_response_yes=on_yes,
|
sectext,
|
||||||
on_response_no=on_no)
|
text_label=_('Reason (if you decline):'),
|
||||||
|
on_response_yes=on_yes,
|
||||||
|
on_response_no=on_no)
|
||||||
dlg.set_title(_('Groupchat Invitation'))
|
dlg.set_title(_('Groupchat Invitation'))
|
||||||
|
|
||||||
class ProgressDialog:
|
class ProgressDialog:
|
||||||
|
|
|
@ -33,6 +33,7 @@ import logging
|
||||||
from enum import IntEnum, unique
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
|
from nbxmpp.const import StatusCode
|
||||||
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
|
@ -1080,7 +1081,7 @@ class GroupchatControl(ChatControlBase):
|
||||||
def _on_voice_approval(self, event):
|
def _on_voice_approval(self, event):
|
||||||
if event.account != self.account:
|
if event.account != self.account:
|
||||||
return
|
return
|
||||||
if event.room_jid != self.room_jid:
|
if event.jid != self.room_jid:
|
||||||
return
|
return
|
||||||
SingleMessageWindow(self.account,
|
SingleMessageWindow(self.account,
|
||||||
self.room_jid,
|
self.room_jid,
|
||||||
|
@ -1091,7 +1092,7 @@ class GroupchatControl(ChatControlBase):
|
||||||
def _on_captcha_challenge(self, event):
|
def _on_captcha_challenge(self, event):
|
||||||
if event.account != self.account:
|
if event.account != self.account:
|
||||||
return
|
return
|
||||||
if event.room_jid != self.room_jid:
|
if event.jid != self.room_jid:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.form_widget:
|
if self.form_widget:
|
||||||
|
@ -1419,11 +1420,11 @@ class GroupchatControl(ChatControlBase):
|
||||||
return
|
return
|
||||||
self.set_subject(event.subject)
|
self.set_subject(event.subject)
|
||||||
text = _('%(nick)s has set the subject to %(subject)s') % {
|
text = _('%(nick)s has set the subject to %(subject)s') % {
|
||||||
'nick': event.resource, 'subject': event.subject}
|
'nick': event.nickname, 'subject': event.subject}
|
||||||
|
|
||||||
if event.delayed:
|
if event.user_timestamp:
|
||||||
date = time.strftime('%d-%m-%Y %H:%M:%S',
|
date = time.strftime('%d-%m-%Y %H:%M:%S',
|
||||||
time.localtime(event.timestamp))
|
time.localtime(event.user_timestamp))
|
||||||
text = '%s - %s' % (text, date)
|
text = '%s - %s' % (text, date)
|
||||||
|
|
||||||
just_joined = self.join_time > time.time() - 10
|
just_joined = self.join_time > time.time() - 10
|
||||||
|
@ -1440,35 +1441,31 @@ class GroupchatControl(ChatControlBase):
|
||||||
if event.account != self.account:
|
if event.account != self.account:
|
||||||
return
|
return
|
||||||
|
|
||||||
if event.room_jid != self.room_jid:
|
if event.jid != self.room_jid:
|
||||||
return
|
return
|
||||||
|
|
||||||
changes = []
|
changes = []
|
||||||
if '100' in event.status_codes:
|
if StatusCode.SHOWING_UNAVAILABLE in event.status_codes:
|
||||||
# Can be a presence (see chg_contact_status in groupchat_control.py)
|
|
||||||
changes.append(_('Any occupant is allowed to see your full JID'))
|
|
||||||
self.is_anonymous = False
|
|
||||||
if '102' in event.status_codes:
|
|
||||||
changes.append(_('Room now shows unavailable members'))
|
changes.append(_('Room now shows unavailable members'))
|
||||||
if '103' in event.status_codes:
|
if StatusCode.NOT_SHOWING_UNAVAILABLE in event.status_codes:
|
||||||
changes.append(_('Room now does not show unavailable members'))
|
changes.append(_('Room now does not show unavailable members'))
|
||||||
if '104' in event.status_codes:
|
if StatusCode.CONFIG_NON_PRIVACY_RELATED in event.status_codes:
|
||||||
changes.append(_('A setting not related to privacy has been '
|
changes.append(_('A setting not related to privacy has been '
|
||||||
'changed'))
|
'changed'))
|
||||||
app.connections[self.account].get_module('Discovery').disco_muc(
|
app.connections[self.account].get_module('Discovery').disco_muc(
|
||||||
self.room_jid, self.update_actions, update=True)
|
self.room_jid, self.update_actions, update=True)
|
||||||
if '170' in event.status_codes:
|
if StatusCode.CONFIG_ROOM_LOGGING in event.status_codes:
|
||||||
# Can be a presence (see chg_contact_status in groupchat_control.py)
|
# Can be a presence (see chg_contact_status in groupchat_control.py)
|
||||||
changes.append(_('Room logging is now enabled'))
|
changes.append(_('Room logging is now enabled'))
|
||||||
if '171' in event.status_codes:
|
if StatusCode.CONFIG_NO_ROOM_LOGGING in event.status_codes:
|
||||||
changes.append(_('Room logging is now disabled'))
|
changes.append(_('Room logging is now disabled'))
|
||||||
if '172' in event.status_codes:
|
if StatusCode.CONFIG_NON_ANONYMOUS in event.status_codes:
|
||||||
changes.append(_('Room is now non-anonymous'))
|
changes.append(_('Room is now non-anonymous'))
|
||||||
self.is_anonymous = False
|
self.is_anonymous = False
|
||||||
if '173' in event.status_codes:
|
if StatusCode.CONFIG_SEMI_ANONYMOUS in event.status_codes:
|
||||||
changes.append(_('Room is now semi-anonymous'))
|
changes.append(_('Room is now semi-anonymous'))
|
||||||
self.is_anonymous = True
|
self.is_anonymous = True
|
||||||
if '174' in event.status_codes:
|
if StatusCode.CONFIG_FULL_ANONYMOUS in event.status_codes:
|
||||||
changes.append(_('Room is now fully anonymous'))
|
changes.append(_('Room is now fully anonymous'))
|
||||||
self.is_anonymous = True
|
self.is_anonymous = True
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,7 @@ class GroupchatConfig(Gtk.ApplicationWindow):
|
||||||
con.get_module('MUC').get_affiliation(
|
con.get_module('MUC').get_affiliation(
|
||||||
self.jid,
|
self.jid,
|
||||||
affiliation,
|
affiliation,
|
||||||
self._on_affiliations_received,
|
callback=self._on_affiliations_received)
|
||||||
self._on_affiliations_error)
|
|
||||||
|
|
||||||
if form is not None:
|
if form is not None:
|
||||||
self._ui.stack.set_visible_child_name('config')
|
self._ui.stack.set_visible_child_name('config')
|
||||||
|
@ -340,29 +339,28 @@ class GroupchatConfig(Gtk.ApplicationWindow):
|
||||||
con = app.connections[self.account]
|
con = app.connections[self.account]
|
||||||
con.get_module('MUC').set_affiliation(self.jid, diff_dict)
|
con.get_module('MUC').set_affiliation(self.jid, diff_dict)
|
||||||
|
|
||||||
def _on_affiliations_error(self, affiliation, error):
|
def _on_affiliations_received(self, result):
|
||||||
log.info('Error while requesting %s affiliations: %s',
|
if result.is_error:
|
||||||
affiliation, error)
|
log.info('Error while requesting %s affiliations: %s',
|
||||||
|
result.affiliation, result.error)
|
||||||
|
return
|
||||||
|
|
||||||
def _on_affiliations_received(self, _account, _room_jid,
|
if result.affiliation == 'outcast':
|
||||||
affiliation, users):
|
|
||||||
|
|
||||||
if affiliation == 'outcast':
|
|
||||||
self._ui.stack.get_child_by_name('outcast').show()
|
self._ui.stack.get_child_by_name('outcast').show()
|
||||||
|
|
||||||
for jid, attrs in users.items():
|
for jid, attrs in result.users.items():
|
||||||
affiliation_edit, jid_edit = self._allowed_to_edit(affiliation)
|
affiliation_edit, jid_edit = self._allowed_to_edit(result.affiliation)
|
||||||
if affiliation == 'outcast':
|
if result.affiliation == 'outcast':
|
||||||
reason = attrs.get('reason')
|
reason = attrs.get('reason')
|
||||||
self._ui.outcast_store.append(
|
self._ui.outcast_store.append(
|
||||||
[jid,
|
[jid,
|
||||||
reason,
|
reason,
|
||||||
None,
|
None,
|
||||||
affiliation,
|
result.affiliation,
|
||||||
None,
|
None,
|
||||||
affiliation_edit,
|
affiliation_edit,
|
||||||
jid_edit])
|
jid_edit])
|
||||||
self._affiliations[jid] = {'affiliation': affiliation,
|
self._affiliations[jid] = {'affiliation': result.affiliation,
|
||||||
'reason': reason}
|
'reason': reason}
|
||||||
else:
|
else:
|
||||||
nick = attrs.get('nick')
|
nick = attrs.get('nick')
|
||||||
|
@ -371,11 +369,11 @@ class GroupchatConfig(Gtk.ApplicationWindow):
|
||||||
[jid,
|
[jid,
|
||||||
nick,
|
nick,
|
||||||
role,
|
role,
|
||||||
affiliation,
|
result.affiliation,
|
||||||
_(affiliation.capitalize()),
|
_(result.affiliation.capitalize()),
|
||||||
affiliation_edit,
|
affiliation_edit,
|
||||||
jid_edit])
|
jid_edit])
|
||||||
self._affiliations[jid] = {'affiliation': affiliation,
|
self._affiliations[jid] = {'affiliation': result.affiliation,
|
||||||
'nick': nick}
|
'nick': nick}
|
||||||
if role is not None:
|
if role is not None:
|
||||||
self._ui.role_column.set_visible(True)
|
self._ui.role_column.set_visible(True)
|
||||||
|
|
|
@ -109,7 +109,7 @@ class Notification:
|
||||||
def _on_event_removed(self, event_list):
|
def _on_event_removed(self, event_list):
|
||||||
for event in event_list:
|
for event in event_list:
|
||||||
if event.type_ == 'gc-invitation':
|
if event.type_ == 'gc-invitation':
|
||||||
self._withdraw('gc-invitation', event.account, event.room_jid)
|
self._withdraw('gc-invitation', event.account, event.muc)
|
||||||
if event.type_ in ('normal', 'printed_chat', 'chat',
|
if event.type_ in ('normal', 'printed_chat', 'chat',
|
||||||
'printed_pm', 'pm', 'printed_marked_gc_msg',
|
'printed_pm', 'pm', 'printed_marked_gc_msg',
|
||||||
'printed_gc_msg'):
|
'printed_gc_msg'):
|
||||||
|
|
|
@ -609,39 +609,43 @@ class Interface:
|
||||||
else:
|
else:
|
||||||
GroupchatConfig(account, obj.jid, 'owner', obj.dataform)
|
GroupchatConfig(account, obj.jid, 'owner', obj.dataform)
|
||||||
|
|
||||||
def handle_event_gc_decline(self, obj):
|
def handle_event_gc_decline(self, event):
|
||||||
gc_control = self.msg_win_mgr.get_gc_control(obj.room_jid, obj.account)
|
gc_control = self.msg_win_mgr.get_gc_control(str(event.muc),
|
||||||
|
event.account)
|
||||||
if gc_control:
|
if gc_control:
|
||||||
if obj.reason:
|
if event.reason:
|
||||||
gc_control.print_conversation(
|
gc_control.print_conversation(
|
||||||
_('%(jid)s declined the invitation: %(reason)s') % {
|
_('%(jid)s declined the invitation: %(reason)s') % {
|
||||||
'jid': obj.from_, 'reason': obj.reason},
|
'jid': event.from_, 'reason': event.reason},
|
||||||
graphics=False)
|
graphics=False)
|
||||||
else:
|
else:
|
||||||
gc_control.print_conversation(
|
gc_control.print_conversation(
|
||||||
_('%(jid)s declined the invitation') % {
|
_('%(jid)s declined the invitation') % {
|
||||||
'jid': obj.from_}, graphics=False)
|
'jid': event.from_}, graphics=False)
|
||||||
|
|
||||||
def handle_event_gc_invitation(self, obj):
|
def handle_event_gc_invitation(self, event):
|
||||||
if helpers.allow_popup_window(obj.account) or not self.systray_enabled:
|
if helpers.allow_popup_window(event.account) or not self.systray_enabled:
|
||||||
dialogs.InvitationReceivedDialog(
|
dialogs.InvitationReceivedDialog(event.account, event)
|
||||||
obj.account, obj.room_jid,
|
|
||||||
str(obj.from_), obj.password, obj.reason,
|
|
||||||
is_continued=obj.is_continued)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
event = events.GcInvitationtEvent(
|
from_ = str(event.from_)
|
||||||
obj.room_jid, obj.reason,
|
muc = str(event.muc)
|
||||||
obj.password, obj.is_continued, str(obj.from_))
|
|
||||||
self.add_event(obj.account, str(obj.from_), event)
|
|
||||||
|
|
||||||
if helpers.allow_showing_notification(obj.account):
|
event_ = events.GcInvitationtEvent(event)
|
||||||
|
self.add_event(event.account, from_, event_)
|
||||||
|
|
||||||
|
if helpers.allow_showing_notification(event.account):
|
||||||
event_type = _('Groupchat Invitation')
|
event_type = _('Groupchat Invitation')
|
||||||
text = _('You are invited to {room} by {user}').format(
|
text = _('You are invited to {room} by {user}').format(room=muc,
|
||||||
room=obj.room_jid, user=str(obj.from_))
|
user=from_)
|
||||||
app.notification.popup(
|
app.notification.popup(event_type,
|
||||||
event_type, str(obj.from_), obj.account, 'gc-invitation',
|
from_,
|
||||||
'gajim-gc_invitation', event_type, text, room_jid=obj.room_jid)
|
event.account,
|
||||||
|
'gc-invitation',
|
||||||
|
'gajim-gc_invitation',
|
||||||
|
event_type,
|
||||||
|
text,
|
||||||
|
room_jid=muc)
|
||||||
|
|
||||||
def forget_gpg_passphrase(self, keyid):
|
def forget_gpg_passphrase(self, keyid):
|
||||||
if keyid in self.gpg_passphrase:
|
if keyid in self.gpg_passphrase:
|
||||||
|
@ -1523,7 +1527,9 @@ class Interface:
|
||||||
if app.contacts.get_contact_with_highest_priority(account, jid):
|
if app.contacts.get_contact_with_highest_priority(account, jid):
|
||||||
self.roster.draw_contact(jid, account)
|
self.roster.draw_contact(jid, account)
|
||||||
else:
|
else:
|
||||||
self.roster.add_to_not_in_the_roster(account, jid)
|
groupchat = event.type_ == 'gc-invitation'
|
||||||
|
self.roster.add_to_not_in_the_roster(
|
||||||
|
account, jid, groupchat=groupchat)
|
||||||
|
|
||||||
# Select the big brother contact in roster, it's visible because it has
|
# Select the big brother contact in roster, it's visible because it has
|
||||||
# events.
|
# events.
|
||||||
|
@ -1621,8 +1627,7 @@ class Interface:
|
||||||
event = app.events.get_first_event(account, jid, type_)
|
event = app.events.get_first_event(account, jid, type_)
|
||||||
if event is None:
|
if event is None:
|
||||||
return
|
return
|
||||||
dialogs.InvitationReceivedDialog(account, event.room_jid, jid,
|
dialogs.InvitationReceivedDialog(account, event)
|
||||||
event.password, event.reason, event.is_continued)
|
|
||||||
app.events.remove_events(account, jid, event)
|
app.events.remove_events(account, jid, event)
|
||||||
self.roster.draw_contact(jid, account)
|
self.roster.draw_contact(jid, account)
|
||||||
elif type_ == 'subscription_request':
|
elif type_ == 'subscription_request':
|
||||||
|
|
|
@ -1032,14 +1032,16 @@ class RosterWindow:
|
||||||
self.draw_group(group, account)
|
self.draw_group(group, account)
|
||||||
|
|
||||||
# FIXME: integrate into add_contact()
|
# FIXME: integrate into add_contact()
|
||||||
def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
|
def add_to_not_in_the_roster(self, account, jid, nick='', resource='',
|
||||||
|
groupchat=False):
|
||||||
keyID = ''
|
keyID = ''
|
||||||
attached_keys = app.config.get_per('accounts', account,
|
attached_keys = app.config.get_per('accounts', account,
|
||||||
'attached_gpg_keys').split()
|
'attached_gpg_keys').split()
|
||||||
if jid in attached_keys:
|
if jid in attached_keys:
|
||||||
keyID = attached_keys[attached_keys.index(jid) + 1]
|
keyID = attached_keys[attached_keys.index(jid) + 1]
|
||||||
contact = app.contacts.create_not_in_roster_contact(jid=jid,
|
contact = app.contacts.create_not_in_roster_contact(
|
||||||
account=account, resource=resource, name=nick, keyID=keyID)
|
jid=jid, account=account, resource=resource, name=nick,
|
||||||
|
keyID=keyID, groupchat=groupchat)
|
||||||
app.contacts.add_contact(account, contact)
|
app.contacts.add_contact(account, contact)
|
||||||
self.add_contact(contact.jid, account)
|
self.add_contact(contact.jid, account)
|
||||||
return contact
|
return contact
|
||||||
|
@ -2002,9 +2004,7 @@ class RosterWindow:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if event.type_ == 'gc-invitation':
|
if event.type_ == 'gc-invitation':
|
||||||
dialogs.InvitationReceivedDialog(account, event.room_jid,
|
dialogs.InvitationReceivedDialog(account, event)
|
||||||
event.jid_from, event.password, event.reason,
|
|
||||||
is_continued=event.is_continued)
|
|
||||||
app.events.remove_events(account, jid, event)
|
app.events.remove_events(account, jid, event)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -2692,12 +2692,8 @@ class RosterWindow:
|
||||||
app.log('avatar').debug('Draw roster avatar: %s', obj.jid)
|
app.log('avatar').debug('Draw roster avatar: %s', obj.jid)
|
||||||
self.draw_avatar(obj.jid, obj.account)
|
self.draw_avatar(obj.jid, obj.account)
|
||||||
|
|
||||||
def _nec_gc_subject_received(self, obj):
|
def _nec_gc_subject_received(self, event):
|
||||||
contact = app.contacts.get_contact_with_highest_priority(
|
self.draw_contact(event.jid, event.account)
|
||||||
obj.account, obj.jid)
|
|
||||||
if contact:
|
|
||||||
contact.status = obj.subject
|
|
||||||
self.draw_contact(obj.jid, obj.account)
|
|
||||||
|
|
||||||
def _nec_metacontacts_received(self, obj):
|
def _nec_metacontacts_received(self, obj):
|
||||||
self.redraw_metacontacts(obj.conn.name)
|
self.redraw_metacontacts(obj.conn.name)
|
||||||
|
|
Loading…
Add table
Reference in a new issue