diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/display_corrections.py | 2 | ||||
-rw-r--r-- | plugins/embed.py | 12 | ||||
-rwxr-xr-x | plugins/qr.py | 178 | ||||
-rw-r--r-- | plugins/reorder.py | 29 | ||||
-rw-r--r-- | plugins/upload.py | 22 |
5 files changed, 223 insertions, 20 deletions
diff --git a/plugins/display_corrections.py b/plugins/display_corrections.py index e9e8a2e4..99982ec9 100644 --- a/plugins/display_corrections.py +++ b/plugins/display_corrections.py @@ -43,7 +43,7 @@ class Plugin(BasePlugin): messages = self.api.get_conversation_messages() if not messages: return None - for message in messages[::-1]: + for message in reversed(messages): if message.old_message: if nb == 1: return message diff --git a/plugins/embed.py b/plugins/embed.py index 9895a927..0c4a4a2a 100644 --- a/plugins/embed.py +++ b/plugins/embed.py @@ -28,14 +28,13 @@ class Plugin(BasePlugin): help='Embed an image url into the contact\'s client', usage='<image_url>') - def embed_image_url(self, args): + def embed_image_url(self, url): tab = self.api.current_tab() message = self.core.xmpp.make_message(tab.jid) - message['body'] = args - message['oob']['url'] = args - if isinstance(tab, tabs.MucTab): - message['type'] = 'groupchat' - else: + message['body'] = url + message['oob']['url'] = url + message['type'] = 'groupchat' + if not isinstance(tab, tabs.MucTab): message['type'] = 'chat' tab.add_message( message['body'], @@ -46,3 +45,4 @@ class Plugin(BasePlugin): typ=1, ) message.send() + self.core.refresh_window() diff --git a/plugins/qr.py b/plugins/qr.py new file mode 100755 index 00000000..25530248 --- /dev/null +++ b/plugins/qr.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 + +import io +import logging +import qrcode +import sys + +from poezio import windows +from poezio.tabs import Tab +from poezio.common import safeJID +from poezio.core.structs import Command +from poezio.decorators import command_args_parser +from poezio.plugin import BasePlugin +from poezio.theming import get_theme, to_curses_attr +from poezio.windows.base_wins import Win + +log = logging.getLogger(__name__) + +class QrWindow(Win): + __slots__ = ('qr', 'invert', 'inverted') + + str_invert = " Invert " + str_close = " Close " + + def __init__(self, qr: str) -> None: + self.qr = qr + self.invert = True + self.inverted = True + + def refresh(self) -> None: + self._win.erase() + # draw QR code + code = qrcode.QRCode() + code.add_data(self.qr) + out = io.StringIO() + code.print_ascii(out, invert=self.inverted) + self.addstr(" " + self.qr + "\n") + self.addstr(out.getvalue(), to_curses_attr((15, 0))) + self.addstr(" ") + + col = to_curses_attr(get_theme().COLOR_TAB_NORMAL) + + if self.invert: + self.addstr(self.str_invert, col) + else: + self.addstr(self.str_invert) + + self.addstr(" ") + + if self.invert: + self.addstr(self.str_close) + else: + self.addstr(self.str_close, col) + + self._refresh() + + def toggle_choice(self) -> None: + self.invert = not self.invert + + def engage(self) -> bool: + if self.invert: + self.inverted = not self.inverted + return False + else: + return True + +class QrTab(Tab): + plugin_commands = {} # type: Dict[str, Command] + plugin_keys = {} # type: Dict[str, Callable] + + def __init__(self, core, qr): + Tab.__init__(self, core) + self.state = 'highlight' + self.text = qr + self.name = qr + self.topic_win = windows.Topic() + self.topic_win.set_message(qr) + self.qr_win = QrWindow(qr) + self.help_win = windows.HelpText( + "Choose with arrow keys and press enter") + self.key_func['^I'] = self.toggle_choice + self.key_func[' '] = self.toggle_choice + self.key_func['KEY_LEFT'] = self.toggle_choice + self.key_func['KEY_RIGHT'] = self.toggle_choice + self.key_func['^M'] = self.engage + self.resize() + self.update_commands() + self.update_keys() + + def resize(self): + self.need_resize = False + self.topic_win.resize(1, self.width, 0, 0) + self.qr_win.resize(self.height-3, self.width, 1, 0) + self.help_win.resize(1, self.width, self.height-1, 0) + + def refresh(self): + if self.need_resize: + self.resize() + log.debug(' TAB Refresh: %s', self.__class__.__name__) + self.refresh_tab_win() + self.info_win.refresh() + self.topic_win.refresh() + self.qr_win.refresh() + self.help_win.refresh() + + def on_input(self, key, raw): + if not raw and key in self.key_func: + return self.key_func[key]() + + def toggle_choice(self): + log.debug(' TAB toggle_choice: %s', self.__class__.__name__) + self.qr_win.toggle_choice() + self.refresh() + self.core.doupdate() + + def engage(self): + log.debug(' TAB engage: %s', self.__class__.__name__) + if self.qr_win.engage(): + self.core.close_tab(self) + else: + self.refresh() + self.core.doupdate() + +class Plugin(BasePlugin): + def init(self): + self.api.add_command( + 'qr', + self.command_qr, + usage='<message>', + short='Display a QR code', + help='Display a QR code of <message> in a new tab') + self.api.add_command( + 'invitation', + self.command_invite, + usage='[<server>]', + short='Invite a user', + help='Generate a XEP-0401 invitation on your server or on <server> and display a QR code') + + def command_qr(self, msg): + t = QrTab(self.core, msg) + self.core.add_tab(t, True) + self.core.doupdate() + + def on_next(self, iq, adhoc_session): + status = iq['command']['status'] + xform = iq.xml.find( + '{http://jabber.org/protocol/commands}command/{jabber:x:data}x') + if xform is not None: + form = self.core.xmpp.plugin['xep_0004'].build_form(xform) + else: + form = None + uri = None + if status == 'completed' and form: + for field in form: + log.debug(' field: %s -> %s', field['var'], field['value']) + if field['var'] == 'landing-url' and field['value']: + uri = field.get_value(convert=False) + if field['var'] == 'uri' and field['value'] and uri is None: + uri = field.get_value(convert=False) + if uri: + t = QrTab(self.core, uri) + self.core.add_tab(t, True) + self.core.doupdate() + else: + self.core.handler.next_adhoc_step(iq, adhoc_session) + + + @command_args_parser.quoted(0, 1, defaults=[]) + def command_invite(self, args): + server = self.core.xmpp.boundjid.domain + if len(args) > 0: + server = safeJID(args[0]) + session = { + 'next' : self.on_next, + 'error': self.core.handler.adhoc_error + } + self.core.xmpp.plugin['xep_0050'].start_command(server, 'urn:xmpp:invite#invite', session) + diff --git a/plugins/reorder.py b/plugins/reorder.py index 8d9516f8..7be0b350 100644 --- a/plugins/reorder.py +++ b/plugins/reorder.py @@ -59,6 +59,8 @@ And finally, the ``[tab name]`` must be: - For a type ``static``, the full JID of the contact """ +from slixmpp import InvalidJID, JID + from poezio import tabs from poezio.decorators import command_args_parser from poezio.plugin import BasePlugin @@ -162,21 +164,32 @@ class Plugin(BasePlugin): new_tabs += [ tabs.GapTab(self.core) for i in range(pos - last - 1) ] - cls, name = tabs_spec[pos] - tab = self.core.tabs.by_name_and_class(name, cls=cls) - if tab and tab in old_tabs: - new_tabs.append(tab) - old_tabs.remove(tab) - else: - self.api.information('Tab %s not found' % name, 'Warning') + cls, jid = tabs_spec[pos] + try: + jid = JID(jid) + tab = self.core.tabs.by_name_and_class(str(jid), cls=cls) + if tab and tab in old_tabs: + new_tabs.append(tab) + old_tabs.remove(tab) + else: + self.api.information('Tab %s not found. Creating it' % jid, 'Warning') + # TODO: Add support for MucTab. Requires nickname. + if cls in (tabs.DynamicConversationTab, tabs.StaticConversationTab): + new_tab = cls(self.core, jid) + new_tabs.append(new_tab) + except: + self.api.information('Failed to create tab \'%s\'.' % jid, 'Error') if create_gaps: new_tabs.append(tabs.GapTab(self.core)) - last = pos + finally: + last = pos for tab in old_tabs: if tab: new_tabs.append(tab) + # TODO: Ensure we don't break poezio and call this with whatever + # tablist we have. The roster tab at least needs to be in there. self.core.tabs.replace_tabs(new_tabs) self.core.refresh_window() diff --git a/plugins/upload.py b/plugins/upload.py index 7e25070e..5e6dfb04 100644 --- a/plugins/upload.py +++ b/plugins/upload.py @@ -16,6 +16,9 @@ This plugin adds a command to the chat tabs. """ + +from typing import Optional + import asyncio import traceback from os.path import expanduser @@ -30,7 +33,11 @@ from poezio import tabs class Plugin(BasePlugin): + dependencies = {'embed'} + def init(self): + self.embed = self.refs['embed'] + if not self.core.xmpp['xep_0363']: raise Exception('slixmpp XEP-0363 plugin failed to load') for _class in (tabs.PrivateTab, tabs.StaticConversationTab, tabs.DynamicConversationTab, tabs.MucTab): @@ -43,18 +50,23 @@ class Plugin(BasePlugin): short='Upload a file', completion=self.completion_filename) - async def async_upload(self, filename): + async def upload(self, filename) -> Optional[str]: try: url = await self.core.xmpp['xep_0363'].upload_file(filename) except UploadServiceNotFound: self.api.information('HTTP Upload service not found.', 'Error') - return + return None except Exception: exception = traceback.format_exc() self.api.information('Failed to upload file: %s' % exception, 'Error') - return - self.core.insert_input_text(url) + return None + return url + + async def send_upload(self, filename): + url = await self.upload(filename) + if url is not None: + self.embed.embed_image_url(url) @command_args_parser.quoted(1) def command_upload(self, args): @@ -63,7 +75,7 @@ class Plugin(BasePlugin): return filename, = args filename = expanduser(filename) - asyncio.ensure_future(self.async_upload(filename)) + asyncio.ensure_future(self.send_upload(filename)) @staticmethod def completion_filename(the_input): |