link.py 5.57 KB
Newer Older
mathieui's avatar
mathieui committed
1 2
"""
Opens links in a browser.
louiz’'s avatar
louiz’ committed
3

mathieui's avatar
mathieui committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
Installation
------------

First use case: local use
~~~~~~~~~~~~~~~~~~~~~~~~~
If you use poezio on your workstation, this is for you.
You only have to load the plugin: ::

    /load link

Second use case: remote use
~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you use poezio through SSH, this is for you.

.. note:: Small explanation: Poezio will create a `Unix FIFO`_ and send the commands in,
            and you will have to run a dæmon locally with ssh, to get those commands.

First, set the :term:`exec_remote` option in the config file to ``true``. Then select
the directory you want to put the fifo in (default is the current
directory, :file:`./`), the :file:`poezio.fifo` file will be created there.

After that, load the plugin: ::

    /load link

And open a link with :term:`/link` (as described below), this will create the FIFO.

You need to grab poezio’s sources on your client computer, or at least the `daemon.py`_
file.

Finally, on your client computer, run the ssh command:

.. code-block:: bash

    ssh toto@example.org "cat ~/poezio/poezio.fifo" | python3 daemon.py

Usage
-----

.. glossary::

    /link
46
        **Usage:** ``/link [range] [command]``
mathieui's avatar
mathieui committed
47

48 49 50 51 52 53
        This plugin adds a :term:`/link` command that will open the links in
        ``firefox``. If you want to use another browser, or any other
        command, you can use the :term:`/set` command to change the
        :term:`browser` option. You can also specify the command to execute
        directly in the arguments.  For example `/link "mpv %s"` will open
        the first link found using mpv, instead of the configured browser.
mathieui's avatar
mathieui committed
54 55


56 57 58 59 60 61 62 63 64
        :term:`/link` without argument will open the last link found
        in the current tab, if any is found. An optional range
        argument can be given, to select one or more links to
        open. Examples:
        ``/link 1`` is equivalent to ``/link``
        ``/link 3`` will open the third link found in the current tab,
        starting from the bottom.
        ``/link 1:5`` will open the last five links in the current tab
        ``/link :2`` will open the last two links
mathieui's avatar
mathieui committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78

Options
-------

:term:`exec_remote`

    To execute the command on your client

.. glossary::

    browser
        Set the default browser started by the plugin

.. _Unix FIFO: https://en.wikipedia.org/wiki/Named_pipe
Maxime Buquet's avatar
Maxime Buquet committed
79
.. _daemon.py: https://lab.louiz.org/poezio/poezio/raw/main/poezio/daemon.py
mathieui's avatar
mathieui committed
80 81

"""
82
import platform
louiz’'s avatar
louiz’ committed
83 84
import re

85 86 87 88
from poezio.plugin import BasePlugin
from poezio.xhtml import clean_text
from poezio import common
from poezio import tabs
louiz’'s avatar
louiz’ committed
89

90 91 92 93 94 95 96 97 98 99 100
url_pattern = re.compile(
    r'\b'
    '(?:http[s]?://(?:\S+))|'
    '(?:magnet:\?(?:\S+))|'
    '(?:aesgcm://(?:\S+))|'
    '(?:gopher://(?:\S+))|'
    '(?:gemini://(?:\S+))'
    '\b',
     re.I | re.U
)

101 102 103 104
app_mapping = {
    'Linux': 'xdg-open',
    'Darwin': 'open',
}
louiz’'s avatar
louiz’ committed
105

mathieui's avatar
mathieui committed
106

louiz’'s avatar
louiz’ committed
107 108
class Plugin(BasePlugin):
    def init(self):
109
        for _class in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab):
mathieui's avatar
mathieui committed
110 111 112 113 114 115 116
            self.api.add_tab_command(
                _class,
                'link',
                self.command_link,
                usage='[num] [command]',
                help=
                'Opens the last link from the conversation into a browser.\n\
117 118
                    If [num] is given, then it will\open the num-th link displayed. \
                    Use a [command] argument to override the configured browser value.',
mathieui's avatar
mathieui committed
119
                short='Open links into a browser')
louiz’'s avatar
louiz’ committed
120 121

    def find_link(self, nb):
122
        messages = self.api.get_conversation_messages()
louiz’'s avatar
louiz’ committed
123 124 125
        if not messages:
            return None
        for message in messages[::-1]:
126 127 128
            matches = url_pattern.findall(clean_text(message.txt))
            if matches:
                for url in matches[::-1]:
louiz’'s avatar
louiz’ committed
129 130 131 132 133 134 135 136
                    if nb == 1:
                        return url
                    else:
                        nb -= 1
        return None

    def command_link(self, args):
        args = common.shell_split(args)
137 138 139 140 141 142 143
        start = 1
        end = 1
        # With two arguments, the first is the range, the second is the command
        # With only one argument, it is a range if it starts with a number
        # or :, otherwise it is a command
        if len(args) == 2 or\
           len(args) == 1 and (args[0][0].isnumeric() or args[0][0] == ":"):
144 145 146 147 148 149 150 151 152 153 154 155 156 157
            if args[0].find(':') == -1:
                try:
                    start = int(args[0])
                    end = start
                except ValueError:
                    return self.api.run_command('/help link')
            else:
                start, end = args[0].split(':', 1)
                if start == '':
                    start = 1
                try:
                    start = int(start)
                    end = int(end)
                except ValueError:
mathieui's avatar
mathieui committed
158 159
                    return self.api.information(
                        'Invalid range: %s' % (args[0]), 'Error')
160 161 162
        command = None
        if len(args) == 2:
            command = args[1]
mathieui's avatar
mathieui committed
163 164
        if len(args) == 1 and (not args[0][0].isnumeric()
                               and args[0][0] != ":"):
165
            command = args[0]
mathieui's avatar
mathieui committed
166
        for nb in range(start, end + 1):
167 168 169
            link = self.find_link(nb)
            if not link:
                return self.api.information('No URL found.', 'Warning')
170
            default = app_mapping.get(platform.system(), 'firefox')
171
            if command is None:
mathieui's avatar
mathieui committed
172 173
                self.core.exec_command(
                    [self.config.get('browser', default), link])
174 175
            else:
                self.core.exec_command([command, link])
mathieui's avatar
mathieui committed
176

louiz’'s avatar
louiz’ committed
177 178
    def cleanup(self):
        del self.config