diff options
Diffstat (limited to 'poezio/core')
-rw-r--r-- | poezio/core/commands.py | 19 | ||||
-rw-r--r-- | poezio/core/completions.py | 13 | ||||
-rw-r--r-- | poezio/core/core.py | 94 | ||||
-rw-r--r-- | poezio/core/handlers.py | 81 |
4 files changed, 156 insertions, 51 deletions
diff --git a/poezio/core/commands.py b/poezio/core/commands.py index 5c8199c0..2cb2b291 100644 --- a/poezio/core/commands.py +++ b/poezio/core/commands.py @@ -6,6 +6,7 @@ import logging log = logging.getLogger(__name__) +import asyncio from xml.etree import cElementTree as ET from slixmpp.exceptions import XMPPError @@ -763,6 +764,24 @@ class CommandCore: self.core.invite(to.full, room, reason=reason) self.core.information('Invited %s to %s' % (to.bare, room), 'Info') + @command_args_parser.quoted(1, 0) + def impromptu(self, args: str) -> None: + """/impromptu <jid> [<jid> ...]""" + + if args is None: + return self.help('impromptu') + + jids = set() + current_tab = self.core.tabs.current_tab + if isinstance(current_tab, tabs.ConversationTab): + jids.add(current_tab.general_jid) + + for jid in common.shell_split(' '.join(args)): + jids.add(safeJID(jid).bare) + + asyncio.ensure_future(self.core.impromptu(jids)) + self.core.information('Invited %s to a random room' % (', '.join(jids)), 'Info') + @command_args_parser.quoted(1, 1, ['']) def decline(self, args): """/decline <room@server.tld> [reason]""" diff --git a/poezio/core/completions.py b/poezio/core/completions.py index b283950e..87bb2d47 100644 --- a/poezio/core/completions.py +++ b/poezio/core/completions.py @@ -289,6 +289,19 @@ class CompletionCore: return Completion( the_input.new_completion, rooms, n, '', quotify=True) + def impromptu(self, the_input): + """Completion for /impromptu""" + n = the_input.get_argument_position(quoted=True) + onlines = [] + offlines = [] + for barejid in roster.jids(): + if len(roster[barejid]): + onlines.append(barejid) + else: + offlines.append(barejid) + comp = sorted(onlines) + sorted(offlines) + return Completion(the_input.new_completion, comp, n, quotify=True) + def activity(self, the_input): """Completion for /activity""" n = the_input.get_argument_position(quoted=True) diff --git a/poezio/core/core.py b/poezio/core/core.py index eec0d49b..9651a73b 100644 --- a/poezio/core/core.py +++ b/poezio/core/core.py @@ -13,12 +13,16 @@ import pipes import sys import shutil import time +import uuid from collections import defaultdict -from typing import Callable, Dict, List, Optional, Tuple, Type +from typing import Callable, Dict, List, Optional, Set, Tuple, Type +from xml.etree import cElementTree as ET +from functools import partial from slixmpp import JID from slixmpp.util import FileSystemPerJidCache from slixmpp.xmlstream.handler import Callback +from slixmpp.exceptions import IqError, IqTimeout from poezio import connection from poezio import decorators @@ -155,10 +159,12 @@ class Core: "KEY_F(5)": self.rotate_rooms_left, "^P": self.rotate_rooms_left, "M-[-D": self.rotate_rooms_left, + "M-[1;3D": self.rotate_rooms_left, 'kLFT3': self.rotate_rooms_left, "KEY_F(6)": self.rotate_rooms_right, "^N": self.rotate_rooms_right, "M-[-C": self.rotate_rooms_right, + "M-[1;3C": self.rotate_rooms_right, 'kRIT3': self.rotate_rooms_right, "KEY_F(4)": self.toggle_left_pane, "KEY_F(7)": self.shrink_information_win, @@ -868,6 +874,85 @@ class Core: self.xmpp.plugin['xep_0030'].get_info( jid=jid, timeout=5, callback=callback) + def _impromptu_room_form(self, room): + fields = [ + ('hidden', 'FORM_TYPE', 'http://jabber.org/protocol/muc#roomconfig'), + ('boolean', 'muc#roomconfig_changesubject', True), + ('boolean', 'muc#roomconfig_allowinvites', True), + ('boolean', 'muc#roomconfig_persistent', True), + ('boolean', 'muc#roomconfig_membersonly', True), + ('boolean', 'muc#roomconfig_publicroom', False), + ('list-single', 'muc#roomconfig_whois', 'anyone'), + # MAM + ('boolean', 'muc#roomconfig_enablearchiving', True), # Prosody + ('boolean', 'mam', True), # Ejabberd community + ('boolean', 'muc#roomconfig_mam', True), # Ejabberd saas + ] + + form = self.xmpp['xep_0004'].make_form() + form['type'] = 'submit' + for field in fields: + form.add_field( + ftype=field[0], + var=field[1], + value=field[2], + ) + + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq['to'] = room + query = ET.Element('{http://jabber.org/protocol/muc#owner}query') + query.append(form.xml) + iq.append(query) + return iq + + async def impromptu(self, jids: Set[JID]) -> None: + """ + Generates a new "Impromptu" room with a random localpart on the muc + component of the user who initiated the request. One the room is + created and the first user has joined, send invites for specified + contacts to join in. + """ + + results = await self.xmpp['xep_0030'].get_info_from_domain() + + muc_from_identity = '' + for info in results: + for identity in info['disco_info']['identities']: + if identity[0] == 'conference' and identity[1] == 'text': + muc_from_identity = info['from'].bare + + # Use config.default_muc_service as muc component if available, + # otherwise find muc component by disco#items-ing the user domain. + # If not, give up + default_muc = config.get('default_muc_service', muc_from_identity) + if not default_muc: + self.information( + "Error finding a MUC service to join. If your server does not " + "provide one, set 'default_muc_service' manually to a MUC " + "service that allows room creation.", + 'Error' + ) + return + + nick = self.own_nick + localpart = uuid.uuid4().hex + room = '{!s}@{!s}'.format(localpart, default_muc) + + self.open_new_room(room, nick).join() + iq = self._impromptu_room_form(room) + try: + await iq.send() + except (IqError, IqTimeout): + self.information('Failed to configure impromptu room.', 'Info') + # TODO: destroy? leave room. + return None + + self.information('Room %s created' % room, 'Info') + + for jid in jids: + self.invite(jid, room) + def get_error_message(self, stanza, deprecated: bool = False): """ Takes a stanza of the form <message type='error'><error/></message> @@ -1789,6 +1874,13 @@ class Core: shortdesc='Invite someone in a room.', completion=self.completion.invite) self.register_command( + 'impromptu', + self.command.impromptu, + usage='<jid> [jid ...]', + desc='Invite specified JIDs into a newly created room.', + shortdesc='Invite specified JIDs into newly created room.', + completion=self.completion.impromptu) + self.register_command( 'invitations', self.command.invitations, shortdesc='Show the pending invitations.') diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py index 0e655d68..94d05ee2 100644 --- a/poezio/core/handlers.py +++ b/poezio/core/handlers.py @@ -97,6 +97,11 @@ class HandlerCore: self.core.xmpp.plugin['xep_0030'].get_info( jid=self.core.xmpp.boundjid.domain, callback=callback) + def find_identities(self, _): + asyncio.ensure_future( + self.core.xmpp['xep_0030'].get_info_from_domain(), + ) + def on_carbon_received(self, message): """ Carbon <received/> received @@ -1063,7 +1068,8 @@ class HandlerCore: '{http://jabber.org/protocol/muc#user}x') is not None: return jid = presence['from'] - if not logger.log_roster_change(jid.bare, 'got offline'): + status = presence['status'] + if not logger.log_roster_change(jid.bare, 'got offline{}'.format(' ({})'.format(status) if status else '')): self.core.information('Unable to write in the log file', 'Error') # If a resource got offline, display the message in the conversation with this # precise resource. @@ -1073,12 +1079,15 @@ class HandlerCore: roster.connected -= 1 if contact.name: name = contact.name + offline_msg = '%s is \x191}offline' % name + if status: + offline_msg += ' (\x19o%s\x191})' % status if jid.resource: self.core.add_information_message_to_conversation_tab( - jid.full, '\x195}%s is \x191}offline' % name) + jid.full, '\x195}' + offline_msg) self.core.add_information_message_to_conversation_tab( - jid.bare, '\x195}%s is \x191}offline' % name) - self.core.information('\x193}%s \x195}is \x191}offline' % name, + jid.bare, '\x195}' + offline_msg) + self.core.information('\x193}' + offline_msg, 'Roster') roster.modified() if isinstance(self.core.tabs.current_tab, tabs.RosterInfoTab): @@ -1261,71 +1270,40 @@ class HandlerCore: semi_anon = '173' in status_codes full_anon = '174' in status_codes modif = False + info_col = {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)} if show_unavailable or hide_unavailable or non_priv or logging_off\ or non_anon or semi_anon or full_anon: tab.add_message( - '\x19%(info_col)s}Info: A configuration change not privacy-related occurred.' - % { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x19%(info_col)s}Info: A configuration change not privacy-related occurred.' % info_col, typ=2) modif = True if show_unavailable: tab.add_message( - '\x19%(info_col)s}Info: The unavailable members are now shown.' - % { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x19%(info_col)s}Info: The unavailable members are now shown.' % info_col, typ=2) elif hide_unavailable: tab.add_message( - '\x19%(info_col)s}Info: The unavailable members are now hidden.' - % { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x19%(info_col)s}Info: The unavailable members are now hidden.' % info_col, typ=2) if non_anon: tab.add_message( - '\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' - % { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % info_col, typ=2) elif semi_anon: tab.add_message( - '\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' - % { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' % info_col, typ=2) elif full_anon: tab.add_message( - '\x19%(info_col)s}Info: The room is now fully anonymous.' % - { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x19%(info_col)s}Info: The room is now fully anonymous.' % info_col, typ=2) if logging_on: tab.add_message( - '\x191}Warning: \x19%(info_col)s}This room is publicly logged' - % { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x191}Warning: \x19%(info_col)s}This room is publicly logged' % info_col, typ=2) elif logging_off: tab.add_message( - '\x19%(info_col)s}Info: This room is not logged anymore.' % - { - 'info_col': dump_tuple( - get_theme().COLOR_INFORMATION_TEXT) - }, + '\x19%(info_col)s}Info: This room is not logged anymore.' % info_col, typ=2) if modif: self.core.refresh_window() @@ -1343,9 +1321,10 @@ class HandlerCore: if subject != tab.topic: # Do not display the message if the subject did not change or if we # receive an empty topic when joining the room. + theme = get_theme() fmt = { - 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), - 'text_col': dump_tuple(get_theme().COLOR_NORMAL_TEXT), + 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT), + 'text_col': dump_tuple(theme.COLOR_NORMAL_TEXT), 'subject': subject, 'user': '', } @@ -1439,17 +1418,18 @@ class HandlerCore: xhtml_text, force=True).rstrip('\x19o').strip() else: poezio_colored = str(stanza) + char = get_theme().CHAR_XML_OUT self.core.add_message_to_text_buffer( self.core.xml_buffer, poezio_colored, - nickname=get_theme().CHAR_XML_OUT) + nickname=char) try: if self.core.xml_tab.match_stanza( ElementBase(ET.fromstring(stanza))): self.core.add_message_to_text_buffer( self.core.xml_tab.filtered_buffer, poezio_colored, - nickname=get_theme().CHAR_XML_OUT) + nickname=char) except: log.debug('', exc_info=True) @@ -1468,16 +1448,17 @@ class HandlerCore: xhtml_text, force=True).rstrip('\x19o').strip() else: poezio_colored = str(stanza) + char = get_theme().CHAR_XML_IN self.core.add_message_to_text_buffer( self.core.xml_buffer, poezio_colored, - nickname=get_theme().CHAR_XML_IN) + nickname=char) try: if self.core.xml_tab.match_stanza(stanza): self.core.add_message_to_text_buffer( self.core.xml_tab.filtered_buffer, poezio_colored, - nickname=get_theme().CHAR_XML_IN) + nickname=char) except: log.debug('', exc_info=True) if isinstance(self.core.tabs.current_tab, tabs.XMLTab): |