Refactor MUC module

- nbxmpp provides now most of the MUC code
This commit is contained in:
Philipp Hörist 2018-12-25 20:30:36 +01:00
parent c63e32634a
commit 8094cadbea
14 changed files with 278 additions and 527 deletions

View file

@ -1482,12 +1482,12 @@ class ChatControl(ChatControlBase):
self._add_info_bar_message(markup, [b], file_props, Gtk.MessageType.ERROR)
def _on_accept_gc_invitation(self, widget, event):
if event.is_continued:
app.interface.join_gc_room(self.account, event.room_jid,
if event.continued:
app.interface.join_gc_room(self.account, str(event.muc),
app.nicks[self.account], event.password,
is_continued=True)
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)
@ -1495,14 +1495,14 @@ class ChatControl(ChatControlBase):
app.events.remove_events(self.account, self.contact.jid, event=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:
markup += ' (%s)' % event.reason
b1 = Gtk.Button.new_with_mnemonic(_('_Join'))
b1.connect('clicked', self._on_accept_gc_invitation, event)
b2 = Gtk.Button(stock=Gtk.STOCK_CANCEL)
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)
def on_event_added(self, event):
@ -1546,7 +1546,7 @@ class ChatControl(ChatControlBase):
removed = False
for ib_msg in self.info_bar_queue:
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)
removed = True
else: # file-*

View file

@ -480,6 +480,7 @@ def zeroconf_is_connected():
config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
def in_groupchat(account, room_jid):
room_jid = str(room_jid)
if room_jid not in gc_connected[account]:
return False
return gc_connected[account][room_jid]

View file

@ -307,12 +307,12 @@ class LegacyContactsAPI:
return self_contact
def create_not_in_roster_contact(self, jid, account, resource='', name='',
keyID=''):
keyID='', groupchat=False):
# Use Account object if available
account = self._accounts.get(account, account)
return self.create_contact(jid=jid, account=account, resource=resource,
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):
return self.create_contact(contact.jid, contact.account,

View file

@ -130,15 +130,10 @@ class UnsubscribedEvent(Event):
class GcInvitationtEvent(Event):
type_ = 'gc-invitation'
def __init__(self, room_jid, reason, password, is_continued, jid_from,
time_=None, show_in_roster=False, show_in_systray=True):
Event.__init__(self, time_, show_in_roster=show_in_roster,
show_in_systray=show_in_systray)
self.room_jid = room_jid
self.reason = reason
self.password = password
self.is_continued = is_continued
self.jid_from = jid_from
def __init__(self, event):
Event.__init__(self, None, show_in_roster=False, show_in_systray=True)
for key, value in vars(event).items():
setattr(self, key, value)
class FileRequestEvent(Event):
type_ = 'file-request'

View file

@ -1462,6 +1462,15 @@ def load_json(path, key=None, default=None):
return json_dict
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):
def __init__(self, initialdata=None):
collections.UserDict.__init__(self, initialdata)

View file

@ -170,5 +170,29 @@ def parse_bob_data(stanza):
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):
return BitsOfBinary(*args, **kwargs), 'BitsOfBinary'

View file

@ -212,31 +212,14 @@ class Message:
subject = event.stanza.getSubject()
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
if groupchat:
delay_entity_jid = event.jid
else:
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
delay_jid = self._con.get_own_jid().getDomain()
timestamp = parse_delay(event.stanza, from_=delay_jid)
if timestamp is None:
timestamp = time.time()
user_timestamp = parse_delay(event.stanza, from_=delay_entity_jid)
else:
timestamp = parse_delay(event.stanza, from_=delay_entity_jid)
if timestamp is None:
timestamp = time.time()
user_timestamp = parse_delay(event.stanza,
not_from=[delay_entity_jid])
user_timestamp = parse_delay(event.stanza,
not_from=[delay_jid])
if user_timestamp is not None:
event.additional_data.set_value(
@ -271,11 +254,6 @@ class Message:
event.session, event.fjid, timestamp)
return
if muc_subject:
app.nec.push_incoming_event(NetworkEvent('gc-subject-received',
**vars(event)))
return
if groupchat:
if not event.msgtxt:
return

View file

@ -17,18 +17,17 @@
import time
import logging
import weakref
import nbxmpp
from nbxmpp.const import InviteType
from nbxmpp.structs import StanzaHandler
from gajim.common import i18n
from gajim.common import app
from gajim.common import helpers
from gajim.common.caps_cache import muc_caps_cache
from gajim.common.nec import NetworkEvent
from gajim.common.nec import NetworkIncomingEvent
from gajim.common.modules import dataforms
from gajim.common.modules.bits_of_binary import parse_bob_data
from gajim.common.modules.bits_of_binary import store_bob_data
log = logging.getLogger('gajim.c.m.muc')
@ -39,13 +38,55 @@ class MUC:
self._account = con.name
self.handlers = [
('message', self._on_config_change, '', nbxmpp.NS_MUC_USER),
('message', self._mediated_invite, 'normal', nbxmpp.NS_MUC_USER),
('message', self._direct_invite, '', nbxmpp.NS_CONFERENCE),
('message', self._on_captcha_challenge, '', nbxmpp.NS_CAPTCHA),
('message', self._on_voice_request, '', nbxmpp.NS_DATA, 45),
StanzaHandler(name='message',
callback=self._on_subject_change,
typ='groupchat',
priority=49),
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):
for identity in identities:
if identity.get('category') != 'conference':
@ -108,438 +149,136 @@ class MUC:
if tags:
muc_x.setTag('history', tags)
def set_subject(self, room_jid, subject):
if not app.account_is_connected(self._account):
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:
def _on_subject_change(self, _con, _stanza, properties):
if not properties.is_muc_subject:
return
if stanza.getBody():
return
room_jid = str(stanza.getFrom())
contact = app.contacts.get_groupchat_contact(self._account, room_jid)
jid = properties.jid.getBare()
contact = app.contacts.get_groupchat_contact(self._account, jid)
if contact is None:
return
data_form = dataforms.extend_form(data_form)
try:
if data_form['FORM_TYPE'].value != nbxmpp.NS_MUC_REQUEST:
return
except KeyError:
contact.status = properties.subject
app.nec.push_incoming_event(
NetworkEvent('gc-subject-received',
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
app.nec.push_incoming_event(
NetworkEvent('voice-approval',
account=self._account,
room_jid=room_jid,
form=data_form))
jid=jid,
form=properties.voice_request.form))
raise nbxmpp.NodeProcessed
def _on_captcha_challenge(self, _con, stanza):
captcha = stanza.getTag('captcha', namespace=nbxmpp.NS_CAPTCHA)
if captcha is None:
def _on_captcha_challenge(self, _con, _stanza, properties):
if not properties.is_captcha_challenge:
return
parse_bob_data(stanza)
room_jid = str(stanza.getFrom())
contact = app.contacts.get_groupchat_contact(self._account, room_jid)
contact = app.contacts.get_groupchat_contact(self._account,
str(properties.jid))
if contact is None:
return
log.info('Captcha challenge received from %s', room_jid)
data_form = captcha.getTag('x', namespace=nbxmpp.NS_DATA)
data_form = dataforms.extend_form(node=data_form)
log.info('Captcha challenge received from %s', properties.jid)
store_bob_data(properties.captcha.bob_data)
app.nec.push_incoming_event(
NetworkEvent('captcha-challenge',
account=self._account,
room_jid=room_jid,
form=data_form))
jid=properties.jid,
form=properties.captcha.form))
raise nbxmpp.NodeProcessed
def send_captcha(self, room_jid, form_node):
if not app.account_is_connected(self._account):
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())
def _on_config_change(self, _con, _stanza, properties):
if not properties.is_muc_config_change:
return
room_jid = stanza.getFrom().getStripped()
payload = stanza.getQueryPayload()
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)
log.info('Received config change: %s %s',
properties.jid, properties.muc_status_codes)
app.nec.push_incoming_event(
NetworkEvent('gc-config-changed-received',
account=self._account,
room_jid=room_jid,
status_codes=codes))
jid=properties.jid,
status_codes=properties.muc_status_codes))
raise nbxmpp.NodeProcessed
def destroy(self, room_jid, reason='', jid=''):
if not app.account_is_connected(self._account):
return
iq = nbxmpp.Iq(typ='set',
queryNS=nbxmpp.NS_MUC_OWNER,
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 _on_invite_or_decline(self, _con, _stanza, properties):
if properties.muc_decline is not None:
data = properties.muc_decline
if helpers.ignore_contact(self._account, data.from_):
raise nbxmpp.NodeProcessed
def set_config(self, room_jid, form):
if not app.account_is_connected(self._account):
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, {})
log.info('Invite declined from: %s, reason: %s',
data.from_, data.reason)
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(
GcDeclineReceived(None,
account=self._account,
from_=from_,
room_jid=room_jid,
reason=reason))
NetworkEvent('gc-decline-received',
account=self._account,
**data._asdict()))
raise nbxmpp.NodeProcessed
invite = muc_user.getTag('invite')
if invite is not None:
if properties.muc_invite is not None:
data = properties.muc_invite
if helpers.ignore_contact(self._account, data.from_):
raise nbxmpp.NodeProcessed
room_jid = stanza.getFrom().getStripped()
from_ = self._get_from(room_jid, invite)
log.info('Invite from: %s, to: %s', data.from_, data.muc)
reason = invite.getTagData('reason')
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]:
if app.in_groupchat(self._account, data.muc):
# We are already in groupchat. Ignore invitation
log.info('We are already in this room')
raise nbxmpp.NodeProcessed
app.nec.push_incoming_event(
GcInvitationReceived(None,
account=self._account,
from_=from_,
room_jid=room_jid,
reason=reason,
password=password,
is_continued=is_continued))
NetworkEvent('gc-invitation-received',
account=self._account,
**data._asdict()))
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):
if not app.account_is_connected(self._account):
return
type_ = InviteType.MEDIATED
contact = app.contacts.get_contact_from_full_jid(self._account, to)
if contact and contact.supports(nbxmpp.NS_CONFERENCE):
invite = self._build_direct_invite(room, to, reason, continue_)
else:
invite = self._build_mediated_invite(room, to, reason, continue_)
self._con.connection.send(invite)
type_ = InviteType.DIRECT
@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)
if password:
attrs['password'] = password
message.addChild(name='x', attrs=attrs,
namespace=nbxmpp.NS_CONFERENCE)
return message
self._con.connection.get_module('MUC').invite(
room, to, reason, password, continue_, type_)
@staticmethod
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):
def request_config(self, room_jid):
if not app.account_is_connected(self._account):
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):
if not app.account_is_connected(self._account):
self._con.connection.get_module('MUC').request_config(
room_jid, i18n.LANG, callback=self._config_received)
def _config_received(self, result):
if result.is_error:
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
def _default_response(_con, stanza, **kwargs):
if not nbxmpp.isResultNode(stanza):
log.info('Error: %s', stanza.getError())
class GcInvitationReceived(NetworkIncomingEvent):
name = 'gc-invitation-received'
class GcDeclineReceived(NetworkIncomingEvent):
name = 'gc-decline-received'
class MucOwnerReceivedEvent(NetworkIncomingEvent):
name = 'muc-owner-received'
app.nec.push_incoming_event(NetworkEvent(
'muc-owner-received',
conn=self._con,
dataform=result.form,
jid=result.jid))
def get_instance(*args, **kwargs):

View file

@ -1291,51 +1291,60 @@ class RosterItemExchangeWindow:
class InvitationReceivedDialog:
def __init__(self, account, room_jid, contact_fjid, password=None,
comment=None, is_continued=False):
self.room_jid = room_jid
def __init__(self, account, event):
self.account = account
self.password = password
self.is_continued = is_continued
self.contact_fjid = contact_fjid
self.room_jid = str(event.muc)
self.from_ = str(event.from_)
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''')
#Don't translate $Contact
if is_continued:
if self.is_continued:
sectext = _('$Contact has invited you to join a discussion')
else:
sectext = _('$Contact has invited you to group chat %(room_jid)s')\
% {'room_jid': 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)
% {'room_jid': self.room_jid}
if comment: # only if not None and not ''
comment = GLib.markup_escape_text(comment)
sectext = sectext.replace('$Contact', contact_text)
if event.reason:
comment = GLib.markup_escape_text(event.reason)
comment = _('Comment: %s') % comment
sectext += '\n\n%s' % comment
sectext += '\n\n' + _('Do you want to accept the invitation?')
def on_yes(checked, text):
def on_yes(_checked, _text):
if self.is_continued:
app.interface.join_gc_room(self.account, self.room_jid,
app.nicks[self.account], self.password,
is_continued=True)
app.interface.join_gc_room(self.account,
self.room_jid,
app.nicks[self.account],
self.password,
is_continued=True)
else:
app.interface.join_gc_minimal(
self.account, self.room_jid, password=self.password)
app.interface.join_gc_minimal(self.account,
self.room_jid,
password=self.password)
def on_no(text):
app.connections[account].get_module('MUC').decline(
self.room_jid, self.contact_fjid, text)
self.room_jid, self.from_, text)
dlg = YesNoDialog(pritext, sectext,
text_label=_('Reason (if you decline):'), on_response_yes=on_yes,
on_response_no=on_no)
dlg = YesNoDialog(pritext,
sectext,
text_label=_('Reason (if you decline):'),
on_response_yes=on_yes,
on_response_no=on_no)
dlg.set_title(_('Groupchat Invitation'))
class ProgressDialog:

View file

@ -33,6 +33,7 @@ import logging
from enum import IntEnum, unique
import nbxmpp
from nbxmpp.const import StatusCode
from gi.repository import Gtk
from gi.repository import Gdk
@ -1080,7 +1081,7 @@ class GroupchatControl(ChatControlBase):
def _on_voice_approval(self, event):
if event.account != self.account:
return
if event.room_jid != self.room_jid:
if event.jid != self.room_jid:
return
SingleMessageWindow(self.account,
self.room_jid,
@ -1091,7 +1092,7 @@ class GroupchatControl(ChatControlBase):
def _on_captcha_challenge(self, event):
if event.account != self.account:
return
if event.room_jid != self.room_jid:
if event.jid != self.room_jid:
return
if self.form_widget:
@ -1419,11 +1420,11 @@ class GroupchatControl(ChatControlBase):
return
self.set_subject(event.subject)
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',
time.localtime(event.timestamp))
time.localtime(event.user_timestamp))
text = '%s - %s' % (text, date)
just_joined = self.join_time > time.time() - 10
@ -1440,35 +1441,31 @@ class GroupchatControl(ChatControlBase):
if event.account != self.account:
return
if event.room_jid != self.room_jid:
if event.jid != self.room_jid:
return
changes = []
if '100' 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:
if StatusCode.SHOWING_UNAVAILABLE in event.status_codes:
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'))
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 '
'changed'))
app.connections[self.account].get_module('Discovery').disco_muc(
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)
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'))
if '172' in event.status_codes:
if StatusCode.CONFIG_NON_ANONYMOUS in event.status_codes:
changes.append(_('Room is now non-anonymous'))
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'))
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'))
self.is_anonymous = True

View file

@ -62,8 +62,7 @@ class GroupchatConfig(Gtk.ApplicationWindow):
con.get_module('MUC').get_affiliation(
self.jid,
affiliation,
self._on_affiliations_received,
self._on_affiliations_error)
callback=self._on_affiliations_received)
if form is not None:
self._ui.stack.set_visible_child_name('config')
@ -340,29 +339,28 @@ class GroupchatConfig(Gtk.ApplicationWindow):
con = app.connections[self.account]
con.get_module('MUC').set_affiliation(self.jid, diff_dict)
def _on_affiliations_error(self, affiliation, error):
log.info('Error while requesting %s affiliations: %s',
affiliation, error)
def _on_affiliations_received(self, result):
if result.is_error:
log.info('Error while requesting %s affiliations: %s',
result.affiliation, result.error)
return
def _on_affiliations_received(self, _account, _room_jid,
affiliation, users):
if affiliation == 'outcast':
if result.affiliation == 'outcast':
self._ui.stack.get_child_by_name('outcast').show()
for jid, attrs in users.items():
affiliation_edit, jid_edit = self._allowed_to_edit(affiliation)
if affiliation == 'outcast':
for jid, attrs in result.users.items():
affiliation_edit, jid_edit = self._allowed_to_edit(result.affiliation)
if result.affiliation == 'outcast':
reason = attrs.get('reason')
self._ui.outcast_store.append(
[jid,
reason,
None,
affiliation,
result.affiliation,
None,
affiliation_edit,
jid_edit])
self._affiliations[jid] = {'affiliation': affiliation,
self._affiliations[jid] = {'affiliation': result.affiliation,
'reason': reason}
else:
nick = attrs.get('nick')
@ -371,11 +369,11 @@ class GroupchatConfig(Gtk.ApplicationWindow):
[jid,
nick,
role,
affiliation,
_(affiliation.capitalize()),
result.affiliation,
_(result.affiliation.capitalize()),
affiliation_edit,
jid_edit])
self._affiliations[jid] = {'affiliation': affiliation,
self._affiliations[jid] = {'affiliation': result.affiliation,
'nick': nick}
if role is not None:
self._ui.role_column.set_visible(True)

View file

@ -109,7 +109,7 @@ class Notification:
def _on_event_removed(self, event_list):
for event in event_list:
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',
'printed_pm', 'pm', 'printed_marked_gc_msg',
'printed_gc_msg'):

View file

@ -609,39 +609,43 @@ class Interface:
else:
GroupchatConfig(account, obj.jid, 'owner', obj.dataform)
def handle_event_gc_decline(self, obj):
gc_control = self.msg_win_mgr.get_gc_control(obj.room_jid, obj.account)
def handle_event_gc_decline(self, event):
gc_control = self.msg_win_mgr.get_gc_control(str(event.muc),
event.account)
if gc_control:
if obj.reason:
if event.reason:
gc_control.print_conversation(
_('%(jid)s declined the invitation: %(reason)s') % {
'jid': obj.from_, 'reason': obj.reason},
'jid': event.from_, 'reason': event.reason},
graphics=False)
else:
gc_control.print_conversation(
_('%(jid)s declined the invitation') % {
'jid': obj.from_}, graphics=False)
'jid': event.from_}, graphics=False)
def handle_event_gc_invitation(self, obj):
if helpers.allow_popup_window(obj.account) or not self.systray_enabled:
dialogs.InvitationReceivedDialog(
obj.account, obj.room_jid,
str(obj.from_), obj.password, obj.reason,
is_continued=obj.is_continued)
def handle_event_gc_invitation(self, event):
if helpers.allow_popup_window(event.account) or not self.systray_enabled:
dialogs.InvitationReceivedDialog(event.account, event)
return
event = events.GcInvitationtEvent(
obj.room_jid, obj.reason,
obj.password, obj.is_continued, str(obj.from_))
self.add_event(obj.account, str(obj.from_), event)
from_ = str(event.from_)
muc = str(event.muc)
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')
text = _('You are invited to {room} by {user}').format(
room=obj.room_jid, user=str(obj.from_))
app.notification.popup(
event_type, str(obj.from_), obj.account, 'gc-invitation',
'gajim-gc_invitation', event_type, text, room_jid=obj.room_jid)
text = _('You are invited to {room} by {user}').format(room=muc,
user=from_)
app.notification.popup(event_type,
from_,
event.account,
'gc-invitation',
'gajim-gc_invitation',
event_type,
text,
room_jid=muc)
def forget_gpg_passphrase(self, keyid):
if keyid in self.gpg_passphrase:
@ -1523,7 +1527,9 @@ class Interface:
if app.contacts.get_contact_with_highest_priority(account, jid):
self.roster.draw_contact(jid, account)
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
# events.
@ -1621,8 +1627,7 @@ class Interface:
event = app.events.get_first_event(account, jid, type_)
if event is None:
return
dialogs.InvitationReceivedDialog(account, event.room_jid, jid,
event.password, event.reason, event.is_continued)
dialogs.InvitationReceivedDialog(account, event)
app.events.remove_events(account, jid, event)
self.roster.draw_contact(jid, account)
elif type_ == 'subscription_request':

View file

@ -1032,14 +1032,16 @@ class RosterWindow:
self.draw_group(group, account)
# 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 = ''
attached_keys = app.config.get_per('accounts', account,
'attached_gpg_keys').split()
if jid in attached_keys:
keyID = attached_keys[attached_keys.index(jid) + 1]
contact = app.contacts.create_not_in_roster_contact(jid=jid,
account=account, resource=resource, name=nick, keyID=keyID)
contact = app.contacts.create_not_in_roster_contact(
jid=jid, account=account, resource=resource, name=nick,
keyID=keyID, groupchat=groupchat)
app.contacts.add_contact(account, contact)
self.add_contact(contact.jid, account)
return contact
@ -2002,9 +2004,7 @@ class RosterWindow:
return True
if event.type_ == 'gc-invitation':
dialogs.InvitationReceivedDialog(account, event.room_jid,
event.jid_from, event.password, event.reason,
is_continued=event.is_continued)
dialogs.InvitationReceivedDialog(account, event)
app.events.remove_events(account, jid, event)
return True
@ -2692,12 +2692,8 @@ class RosterWindow:
app.log('avatar').debug('Draw roster avatar: %s', obj.jid)
self.draw_avatar(obj.jid, obj.account)
def _nec_gc_subject_received(self, obj):
contact = app.contacts.get_contact_with_highest_priority(
obj.account, obj.jid)
if contact:
contact.status = obj.subject
self.draw_contact(obj.jid, obj.account)
def _nec_gc_subject_received(self, event):
self.draw_contact(event.jid, event.account)
def _nec_metacontacts_received(self, obj):
self.redraw_metacontacts(obj.conn.name)