summaryrefslogtreecommitdiff
path: root/poezio/core
diff options
context:
space:
mode:
Diffstat (limited to 'poezio/core')
-rw-r--r--poezio/core/commands.py19
-rw-r--r--poezio/core/completions.py13
-rw-r--r--poezio/core/core.py94
-rw-r--r--poezio/core/handlers.py81
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):