Poezio theme
This commit is contained in:
parent
fa3f668f6b
commit
1e334f20f1
74 changed files with 301 additions and 6983 deletions
|
@ -36,6 +36,7 @@ You need to install:
|
|||
- offlineimap3
|
||||
- pandoc
|
||||
- poezio
|
||||
- pyinotify
|
||||
- tmux
|
||||
- toot
|
||||
- unoconv
|
||||
|
|
|
@ -63,3 +63,5 @@ vim.keymap.set('v', 'gL', ":'<,'>!lou_translate --backward en-us-brf.dis,fr-bfu-
|
|||
|
||||
vim.cmd 'colorscheme nightfox'
|
||||
vim.transparent_window = true
|
||||
|
||||
vim.cmd 'highlight Normal ctermbg=none guibg=none'
|
||||
|
|
|
@ -7,7 +7,7 @@ bind-key -T copy-mode-vi C-v send -X rectangle-toggle
|
|||
bind-key -T copy-mode-vi y send -X copy-pipe-and-cancel
|
||||
|
||||
set -g lock-command vlock
|
||||
set -g lock-after-time 0
|
||||
set -g lock-after-time 900
|
||||
bind l lock-client
|
||||
bind L lock-session
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
# like room administration, nickname registration.
|
||||
# The 'server' option will be ignored if you specify a JID (Jabber identifier)
|
||||
# It should be in the form nickname@server.tld or nickname@server.tld/resource
|
||||
jid = template@example.com
|
||||
jid = REPLACE_JID
|
||||
|
||||
# A password is needed only if you specified a jid. It will be ignored otherwise
|
||||
# If you leave this empty, the password will be asked at each startup
|
||||
|
@ -20,11 +20,11 @@ jid = template@example.com
|
|||
|
||||
# A command that will be executed if "password" is not set, e.g. a session password
|
||||
# manager like secret-tool on gnome, or anything you want
|
||||
eval_password = keyring get "xmpp:template@example.com" "template@example.com"
|
||||
eval_password = keyring get "KEYRING_ID" "KEYRING_USER"
|
||||
|
||||
# This identifies this client over time with the server, to let it optimise
|
||||
# offline storage and various other features.
|
||||
device_id = REPLACEDEVICEHOSTNAME
|
||||
device_id = REPLACE_DEVICE_HOSTNAME
|
||||
|
||||
# Path to a PEM certificate file to use for certificate authentication
|
||||
# through SASL External. If set, keyfile MUST be provided as well in
|
||||
|
@ -50,7 +50,7 @@ rooms =
|
|||
highlight_on =
|
||||
|
||||
# Colon-separated list of plugins to load on startup
|
||||
plugins_autoload = omemo:autocorrect:user_extras:status:dice:link:quote:reorder:vcard:upload:screen_detach:qr:close_all:contact:disco:display_corrections:irc:link:mpd_client
|
||||
plugins_autoload = omemo:autocorrect:user_extras:status:dice:link:quote:reorder:vcard:upload:screen_detach:qr:close_all:contact:disco:display_corrections:irc:link:mpd_client:change_title
|
||||
|
||||
# The server used for anonymous connection.
|
||||
# Make sure the server you're using accepts anonymous authentication
|
||||
|
@ -58,7 +58,7 @@ plugins_autoload = omemo:autocorrect:user_extras:status:dice:link:quote:reorder:
|
|||
|
||||
# TLS Certificate fingerprint
|
||||
# Do not touch this if you don’t know what you are doing
|
||||
certificate = F5:9A:6D:C9:C5:1A:B5:05:22:F4:EB:4E:23:32:7C:97:6A:24:FF:F8:BA:8D:7F:C1:D1:E8:6F:37:EE:A3:FE:43
|
||||
certificate = REPLACE_FINGERPRINT
|
||||
|
||||
# List of ciphers allowed when connecting to the server,
|
||||
# this list prioritizes forward secrecy and forbids anything
|
||||
|
@ -194,9 +194,9 @@ enable_xhtml_im = true
|
|||
# - status changes won't be displayed unless
|
||||
# the user talked in the last 2 minutes
|
||||
|
||||
#hide_exit_join = -1
|
||||
hide_exit_join = 600
|
||||
|
||||
#hide_status_change = 120
|
||||
hide_status_change = 120
|
||||
|
||||
|
||||
# Some informational messages (error, a contact getting connected, etc)
|
||||
|
@ -298,6 +298,11 @@ hide_user_list = true
|
|||
# - false or anything else: no highlighting
|
||||
#show_composing_tabs = direct
|
||||
|
||||
# Use deterministic coloration for tab names or tab numbers in the activity bar.
|
||||
# Consistent Color Generation (XEP-0392).
|
||||
|
||||
autocolor_tab_names = false
|
||||
|
||||
# Ignore private messages received in chatrooms
|
||||
#ignore_private = false
|
||||
|
||||
|
@ -381,7 +386,7 @@ roster_show_offline = true
|
|||
# in the theme_dir directory.
|
||||
# If the file is not found (or no filename is specified) the default
|
||||
# theme will be used instead
|
||||
#theme = default
|
||||
theme = Nightfox
|
||||
|
||||
# Whether to create gaps when moving or closing a tab
|
||||
# (a gap means that the number of your tabs does not depend of the previous tabs
|
||||
|
@ -533,9 +538,8 @@ M-t = _exc_toggle show_timestamps
|
|||
# You should not edit this section, it is just used by poezio
|
||||
# to save various data across restarts
|
||||
folded_roster_groups =
|
||||
info_win_height = 2
|
||||
info_win_height = 0
|
||||
|
||||
[muc_colors]
|
||||
# Set color for a nick, under the form
|
||||
# nick = color
|
||||
[reorder]
|
|
@ -1 +0,0 @@
|
|||
Plugins from poezio directory
|
|
@ -1,139 +0,0 @@
|
|||
"""
|
||||
This plugin adds several convenient aliases, to shorten
|
||||
roles/affiliation management.
|
||||
|
||||
Aliases defined
|
||||
---------------
|
||||
|
||||
All those commands take a nick or a JID as a parameter.
|
||||
|
||||
For roles
|
||||
~~~~~~~~~
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
/visitor
|
||||
/mute
|
||||
Set the role to ``visitor``
|
||||
|
||||
/participant
|
||||
Set the role to ``participant``
|
||||
|
||||
/moderator
|
||||
/op
|
||||
Set the role to ``moderator``
|
||||
|
||||
|
||||
For affiliations
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
/admin
|
||||
Set the affiliation to ``admin``
|
||||
|
||||
/member
|
||||
/voice
|
||||
Set the affiliation to ``member``
|
||||
|
||||
/noaffiliation
|
||||
Set the affiliation to ``none``
|
||||
|
||||
/owner
|
||||
Set the affiliation to ``owner``
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.tabs import MucTab
|
||||
from poezio.core.structs import Completion
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
"""
|
||||
Adds several convenient aliases to /affiliation and /role:
|
||||
/visitor
|
||||
/participant
|
||||
/moderator == /op
|
||||
/member == /voice
|
||||
/owner
|
||||
/admin
|
||||
/noaffiliation
|
||||
"""
|
||||
|
||||
def init(self):
|
||||
for role in ('visitor', 'participant', 'moderator'):
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
role,
|
||||
self.role(role),
|
||||
help='Set the role of a nick to %s' % role,
|
||||
usage='<nick>',
|
||||
short='Set the role to %s' % role,
|
||||
completion=self.complete_nick)
|
||||
|
||||
for aff in ('member', 'owner', 'admin'):
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
aff,
|
||||
self.affiliation(aff),
|
||||
usage='<nick>',
|
||||
help='Set the affiliation of a nick to %s' % aff,
|
||||
short='Set the affiliation to %s' % aff,
|
||||
completion=self.complete_nick)
|
||||
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
'noaffiliation',
|
||||
self.affiliation('none'),
|
||||
usage='<nick>',
|
||||
help='Set the affiliation of a nick to none.',
|
||||
short='Set the affiliation to none.',
|
||||
completion=self.complete_nick)
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
'voice',
|
||||
self.affiliation('member'),
|
||||
usage='<nick>',
|
||||
help='Set the affiliation of a nick to member.',
|
||||
short='Set the affiliation to member.',
|
||||
completion=self.complete_nick)
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
'op',
|
||||
self.role('moderator'),
|
||||
usage='<nick>',
|
||||
help='Set the role of a nick to moderator.',
|
||||
short='Set the role to moderator.',
|
||||
completion=self.complete_nick)
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
'mute',
|
||||
self.role('visitor'),
|
||||
usage='<nick>',
|
||||
help='Set the role of a nick to visitor.',
|
||||
short='Set the role to visitor.',
|
||||
completion=self.complete_nick)
|
||||
|
||||
def role(self, role):
|
||||
async def inner(args):
|
||||
await self.api.current_tab().command_role(args + ' ' + role)
|
||||
return inner
|
||||
|
||||
def affiliation(self, affiliation):
|
||||
async def inner(args):
|
||||
await self.api.current_tab().command_affiliation(args + ' ' + affiliation)
|
||||
return inner
|
||||
|
||||
def complete_nick(self, the_input):
|
||||
tab = self.api.current_tab()
|
||||
compare_users = lambda x: x.last_talked
|
||||
word_list = [user.nick for user in sorted(tab.users, key=compare_users, reverse=True)\
|
||||
if user.nick != tab.own_nick]
|
||||
return Completion(the_input.auto_completion, word_list, '')
|
|
@ -1,207 +0,0 @@
|
|||
"""
|
||||
Usage
|
||||
-----
|
||||
|
||||
This plugin defines two new global commands: :term:`/alias` and :term:`/unalias`.
|
||||
|
||||
.. glossary::
|
||||
|
||||
/alias
|
||||
**Usage:** ``/alias <name> <command> [args]``
|
||||
|
||||
This command will create a new command, named ``<name>`` (and callable
|
||||
with ``/name``), that runs ``/command``, with ``[args]`` as fixed
|
||||
args for the command.
|
||||
When you run the alias, you can also pass parameters to it, that will be
|
||||
given to the original command.
|
||||
|
||||
Example: ::
|
||||
|
||||
/alias toto say koin
|
||||
|
||||
Will bind ``/say koin`` to ``/toto``, so this alias will work in any
|
||||
Chat tab. If someone calls it with ::
|
||||
|
||||
/toto koin
|
||||
|
||||
Poezio will then execute ``/say koin koin``.
|
||||
|
||||
Also, you can rebind arguments arbitrarily, with the ``{}`` placeholder.
|
||||
For example, ::
|
||||
|
||||
/alias toto say {} le {}
|
||||
/toto loulou coucou
|
||||
|
||||
Will execute ``/say loulou le coucou``, because the ``{}`` are
|
||||
replaced with the command args, in the order they are given.
|
||||
|
||||
Extra args are still added at the end of the command if provided
|
||||
(args used for the formatting are only used for the formatting).
|
||||
|
||||
/unalias
|
||||
**Usage:** ``/unalias <name>``
|
||||
|
||||
This command removes a defined alias.
|
||||
|
||||
|
||||
Config
|
||||
------
|
||||
|
||||
The aliases are stored inside the configuration file for the plugin.
|
||||
You can either use the above commands or write it manually, and it
|
||||
will be read when the plugin is loaded.
|
||||
|
||||
|
||||
Example of the syntax:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[alias]
|
||||
toto = say {} le {}
|
||||
j = join {}@conference.jabber.org/nick
|
||||
jp = say je proteste
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.common import shell_split
|
||||
from poezio.core.structs import Completion
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'alias',
|
||||
self.command_alias,
|
||||
usage='<alias> <command> [args]',
|
||||
short='Create an alias command',
|
||||
help='Create an alias for <command> with [args].')
|
||||
self.api.add_command(
|
||||
'unalias',
|
||||
self.command_unalias,
|
||||
usage='<alias>',
|
||||
help='Remove a previously created alias',
|
||||
short='Remove an alias',
|
||||
completion=self.completion_unalias)
|
||||
self.commands = {}
|
||||
self.load_conf()
|
||||
|
||||
def load_conf(self):
|
||||
"""
|
||||
load stored aliases on startup
|
||||
"""
|
||||
for alias in self.config.options():
|
||||
full = self.config.get(alias, '')
|
||||
if full:
|
||||
self.command_alias(alias + ' ' + full, silent=True)
|
||||
|
||||
def command_alias(self, line, silent=False):
|
||||
"""
|
||||
/alias <alias> <command> [args]
|
||||
"""
|
||||
arg = split_args(line)
|
||||
if not arg:
|
||||
if not silent:
|
||||
self.api.information('Alias: Not enough parameters', 'Error')
|
||||
return
|
||||
alias, command, args = arg
|
||||
|
||||
if alias in self.commands:
|
||||
update = True
|
||||
elif alias in self.core.commands:
|
||||
if not silent:
|
||||
self.api.information('Alias: command already exists', 'Error')
|
||||
return
|
||||
else:
|
||||
update = False
|
||||
|
||||
self.config.set(alias, command + ' ' + args)
|
||||
self.commands[alias] = command_wrapper(
|
||||
generic_command, lambda: self.get_command(command), args)
|
||||
self.api.del_command(alias)
|
||||
self.api.add_command(
|
||||
alias, self.commands[alias],
|
||||
'This command is an alias for /%s %s' % (alias, command))
|
||||
|
||||
if not silent:
|
||||
if update:
|
||||
self.api.information('Alias /%s updated' % alias, 'Info')
|
||||
else:
|
||||
self.api.information('Alias /%s successfully created' % alias,
|
||||
'Info')
|
||||
|
||||
def command_unalias(self, alias):
|
||||
"""
|
||||
/unalias <existing alias>
|
||||
"""
|
||||
if alias in self.commands:
|
||||
del self.commands[alias]
|
||||
self.api.del_command(alias)
|
||||
self.config.remove(alias)
|
||||
self.api.information('Alias /%s successfully deleted' % alias,
|
||||
'Info')
|
||||
|
||||
def completion_unalias(self, the_input):
|
||||
"Completion for /unalias"
|
||||
aliases = [alias for alias in self.commands]
|
||||
aliases.sort()
|
||||
return Completion(
|
||||
the_input.auto_completion, aliases, '', quotify=False)
|
||||
|
||||
def get_command(self, name):
|
||||
"""Returns the function associated with a command"""
|
||||
|
||||
def dummy(args):
|
||||
"""Dummy function called if the command doesn’t exist"""
|
||||
pass
|
||||
|
||||
if name in self.commands:
|
||||
return dummy
|
||||
elif name in self.core.commands:
|
||||
return self.core.commands[name].func
|
||||
elif name in self.api.current_tab().commands:
|
||||
return self.api.current_tab().commands[name].func
|
||||
return dummy
|
||||
|
||||
|
||||
def split_args(line):
|
||||
"""
|
||||
Extract the relevant vars from the command line
|
||||
"""
|
||||
arg = line.split()
|
||||
if len(arg) < 2:
|
||||
return None
|
||||
alias_pos = line.find(' ')
|
||||
alias = line[:alias_pos]
|
||||
end = line[alias_pos + 1:]
|
||||
args_pos = end.find(' ')
|
||||
if args_pos == -1:
|
||||
command = end
|
||||
args = ''
|
||||
else:
|
||||
command = end[:args_pos]
|
||||
args = end[args_pos + 1:]
|
||||
return (alias, command, args)
|
||||
|
||||
|
||||
def generic_command(command, extra_args, args):
|
||||
"""
|
||||
Function that will execute the command and set the relevant
|
||||
parameters (format string, etc).
|
||||
"""
|
||||
args = shell_split(args)
|
||||
new_extra_args = extra_args.format(*args)
|
||||
count = extra_args.count('{}')
|
||||
args = args[count:]
|
||||
new_extra_args += ' '.join(args)
|
||||
return command()(new_extra_args)
|
||||
|
||||
|
||||
def command_wrapper(func, command, extra_args):
|
||||
"set the predefined arguments"
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(command, extra_args, *args, **kwargs)
|
||||
|
||||
return wrapper
|
|
@ -1,35 +0,0 @@
|
|||
"""
|
||||
This plugin broadcasts a message to all your joined rooms.
|
||||
|
||||
.. note:: With great power comes great responsibility.
|
||||
Use with moderation.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/amsg
|
||||
**Usage:** ``/amsg <message>``
|
||||
|
||||
Broadcast a message.
|
||||
|
||||
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.tabs import MucTab
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'amsg',
|
||||
self.command_amsg,
|
||||
usage='<message>',
|
||||
short='Broadcast a message',
|
||||
help='Broadcast the message to all the joined rooms.')
|
||||
|
||||
async def command_amsg(self, args):
|
||||
for room in self.core.tabs:
|
||||
if isinstance(room, MucTab) and room.joined:
|
||||
await room.command_say(args)
|
|
@ -1,97 +0,0 @@
|
|||
"""
|
||||
This plugin lets you perform simple replacements on the last message.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. note:: the ``/``, ``#``, ``!``, ``:`` and ``;`` chars can be used as separators,
|
||||
even if the examples only use ``/``
|
||||
|
||||
|
||||
Regex replacement
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once the plugin is loaded, any message matching the following regex::
|
||||
|
||||
^s/(.+?)/(.*?)(/|/g)?$
|
||||
|
||||
will be interpreted as a regex replacement, and the substitution will be
|
||||
applied to the last sent message.
|
||||
|
||||
For example, if you sent the message::
|
||||
|
||||
This tab lists all public rooms on a MUC service. It is currently very limited but will be improved in the future. There currently is no way to search a room.
|
||||
|
||||
And you now want to replace “MUC” with “multi-user chat”, you input::
|
||||
|
||||
s/MUC/multi-user chat
|
||||
|
||||
And poezio will correct the message for you.
|
||||
|
||||
|
||||
Raw string replacement
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once the plugin is loaded, any message matching the following regex::
|
||||
|
||||
^r/(.+?)/(.*?)(/|/g)?$
|
||||
|
||||
will be interpreted as a replacement, and the substitution will be applied
|
||||
to the last send message.
|
||||
|
||||
This variant is useful if you don’t want to care about regular expressions
|
||||
(and you do not want to have to escape stuff like space or backslashes).
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
import re
|
||||
|
||||
allowed_separators = '/#!:;'
|
||||
sed_re = re.compile(
|
||||
'^([sr])(?P<sep>[%s])(.+?)(?P=sep)(.*?)((?P=sep)|(?P=sep)g)?$' %
|
||||
allowed_separators)
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.sed_fix)
|
||||
self.api.add_event_handler('conversation_say', self.sed_fix)
|
||||
self.api.add_event_handler('private_say', self.sed_fix)
|
||||
|
||||
def sed_fix(self, msg, tab):
|
||||
if not tab.last_sent_message:
|
||||
return
|
||||
if 'correct' not in tab.commands:
|
||||
return
|
||||
body = tab.last_sent_message['body']
|
||||
match = sed_re.match(msg['body'])
|
||||
if not match:
|
||||
return
|
||||
typ, sep, remove, put, matchall = match.groups()
|
||||
|
||||
replace_all = False
|
||||
if matchall == sep + 'g':
|
||||
replace_all = True
|
||||
|
||||
if typ == 's':
|
||||
try:
|
||||
if replace_all:
|
||||
new_body = re.sub(remove, put, body)
|
||||
else:
|
||||
new_body = re.sub(remove, put, body, count=1)
|
||||
except Exception as e:
|
||||
self.api.information(
|
||||
'Invalid regex for the autocorrect '
|
||||
'plugin: %s' % e, 'Error')
|
||||
return
|
||||
elif typ == 'r':
|
||||
if replace_all:
|
||||
new_body = body.replace(remove, put)
|
||||
else:
|
||||
new_body = body.replace(remove, put, 1)
|
||||
|
||||
if body != new_body:
|
||||
msg['body'] = new_body
|
||||
msg['replace']['id'] = tab.last_sent_message['id']
|
|
@ -1,70 +0,0 @@
|
|||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fenc=utf-8
|
||||
#
|
||||
# Copyright © 2019 Maxime “pep” Buquet <pep@bouah.net>
|
||||
#
|
||||
# Distributed under terms of the GPL-3.0+ license.
|
||||
|
||||
"""
|
||||
Usage
|
||||
-----
|
||||
|
||||
Base64 encryption plugin.
|
||||
|
||||
This plugin also respects security guidelines listed in XEP-0419.
|
||||
|
||||
.. glossary::
|
||||
/b64
|
||||
**Usage:** ``/b64``
|
||||
|
||||
This command enables encryption of outgoing messages for the current
|
||||
tab.
|
||||
"""
|
||||
|
||||
from base64 import b64decode, b64encode
|
||||
from typing import List, Optional
|
||||
from slixmpp import Message, JID
|
||||
|
||||
from poezio.plugin_e2ee import E2EEPlugin
|
||||
from poezio.tabs import (
|
||||
ChatTab,
|
||||
MucTab,
|
||||
PrivateTab,
|
||||
DynamicConversationTab,
|
||||
StaticConversationTab,
|
||||
)
|
||||
|
||||
|
||||
class Plugin(E2EEPlugin):
|
||||
"""Base64 Plugin"""
|
||||
|
||||
encryption_name = 'base64'
|
||||
encryption_short_name = 'b64'
|
||||
eme_ns = 'urn:xmpps:base64:0'
|
||||
|
||||
# This encryption mechanism is using <body/> as a container
|
||||
replace_body_with_eme = False
|
||||
|
||||
# In what tab is it ok to use this plugin. Here we want all of them
|
||||
supported_tab_types = (
|
||||
MucTab,
|
||||
PrivateTab,
|
||||
DynamicConversationTab,
|
||||
StaticConversationTab,
|
||||
)
|
||||
|
||||
async def decrypt(self, message: Message, jid: Optional[JID], _tab: Optional[ChatTab]) -> None:
|
||||
"""
|
||||
Decrypt base64
|
||||
"""
|
||||
body = message['body']
|
||||
message['body'] = b64decode(body.encode()).decode()
|
||||
|
||||
async def encrypt(self, message: Message, _jid: Optional[List[JID]], _tab: ChatTab) -> None:
|
||||
"""
|
||||
Encrypt to base64
|
||||
"""
|
||||
# TODO: Stop using <body/> for this. Put the encoded payload in another element.
|
||||
body = message['body']
|
||||
message['body'] = b64encode(body.encode()).decode()
|
|
@ -1,83 +0,0 @@
|
|||
"""
|
||||
This plugin sends a small image to the recipient of your choice, using XHTML-IM and Bits of Binary.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
/bob some/image.png
|
||||
|
||||
Configuration options
|
||||
---------------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
max_size
|
||||
**Default:** ``2048``
|
||||
|
||||
The maximum acceptable size of a file, over which you will get an error instead.
|
||||
|
||||
max_age
|
||||
**Default:** ``86400``
|
||||
|
||||
The time during which the file should stay in cache on the receiving side.
|
||||
"""
|
||||
|
||||
from poezio.core.structs import Completion
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
|
||||
from pathlib import Path
|
||||
from glob import glob
|
||||
from os.path import expanduser
|
||||
from mimetypes import guess_type
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
default_config = {'bob': {'max_size': 2048, 'max_age': 86400}}
|
||||
|
||||
def init(self):
|
||||
for tab in tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab, tabs.MucTab:
|
||||
self.api.add_tab_command(
|
||||
tab,
|
||||
'bob',
|
||||
self.command_bob,
|
||||
usage='<image>',
|
||||
help='Send image <image> to the current discussion',
|
||||
short='Send a short image',
|
||||
completion=self.completion_bob)
|
||||
|
||||
async def command_bob(self, filename):
|
||||
path = Path(expanduser(filename))
|
||||
try:
|
||||
size = path.stat().st_size
|
||||
except OSError as exc:
|
||||
self.api.information('Error sending “%s”: %s' % (path.name, exc),
|
||||
'Error')
|
||||
return
|
||||
mime_type = guess_type(path.as_posix())[0]
|
||||
if mime_type is None or not mime_type.startswith('image/'):
|
||||
self.api.information(
|
||||
'Error sending “%s”, not an image file.' % path.name, 'Error')
|
||||
return
|
||||
if size > self.config.get('max_size'):
|
||||
self.api.information(
|
||||
'Error sending “%s”, file too big.' % path.name, 'Error')
|
||||
return
|
||||
with open(path.as_posix(), 'rb') as file:
|
||||
data = file.read()
|
||||
max_age = self.config.get('max_age')
|
||||
cid = await self.core.xmpp.plugin['xep_0231'].set_bob(
|
||||
data, mime_type, max_age=max_age)
|
||||
self.api.run_command(
|
||||
'/xhtml <img src="cid:%s" alt="%s"/>' % (cid, path.name))
|
||||
|
||||
@staticmethod
|
||||
def completion_bob(the_input):
|
||||
txt = expanduser(the_input.get_text()[5:])
|
||||
images = []
|
||||
for filename in glob(txt + '*'):
|
||||
mime_type = guess_type(filename)[0]
|
||||
if mime_type is not None and mime_type.startswith('image/'):
|
||||
images.append(filename)
|
||||
return Completion(the_input.auto_completion, images, quotify=False)
|
|
@ -1,15 +0,0 @@
|
|||
"""
|
||||
Once loaded, everything you will send will be IN CAPITAL LETTERS.
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import xhtml
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.caps)
|
||||
self.api.add_event_handler('conversation_say', self.caps)
|
||||
self.api.add_event_handler('private_say', self.caps)
|
||||
|
||||
def caps(self, msg, tab):
|
||||
msg['body'] = xhtml.clean_text(msg['body']).upper()
|
|
@ -1,21 +0,0 @@
|
|||
"""
|
||||
This plugin will set the title of your terminal to the name of the current tab.
|
||||
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
import sys
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.on_tab_change(None, new_tab=self.core.tabs.current_tab)
|
||||
self.api.add_event_handler('tab_change', self.on_tab_change)
|
||||
|
||||
def cleanup(self):
|
||||
"Re-set the terminal title to 'poezio'"
|
||||
sys.stdout.write("\x1b]0;poezio\x07")
|
||||
sys.stdout.flush()
|
||||
|
||||
def on_tab_change(self, old_tab, new_tab):
|
||||
sys.stdout.write("\x1b]0;{}\x07".format(new_tab.name))
|
||||
sys.stdout.flush()
|
|
@ -1,44 +0,0 @@
|
|||
"""
|
||||
``close_all`` plugin: close all tabs except chatrooms and the contact list.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/closeall
|
||||
**Usage:** ``/closeall``
|
||||
|
||||
Close all tabs except the roster and chatroom tabs.
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
from poezio.decorators import command_args_parser
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'closeall',
|
||||
self.command_closeall,
|
||||
help='Close all non-chatroom tabs.')
|
||||
|
||||
@command_args_parser.ignored
|
||||
def command_closeall(self):
|
||||
"""
|
||||
/closeall
|
||||
"""
|
||||
current = self.api.current_tab()
|
||||
if not isinstance(current, (tabs.RosterInfoTab, tabs.MucTab)):
|
||||
self.core.go_to_roster()
|
||||
current = self.api.current_tab()
|
||||
|
||||
def filter_func(x):
|
||||
return not isinstance(x, (tabs.RosterInfoTab, tabs.MucTab))
|
||||
|
||||
matching_tabs = list(filter(filter_func, self.core.tabs.get_tabs()))
|
||||
length = len(matching_tabs)
|
||||
for tab in matching_tabs:
|
||||
self.core.close_tab(tab)
|
||||
self.api.information('%s tabs closed.' % length, 'Info')
|
||||
self.core.refresh_window()
|
|
@ -1,52 +0,0 @@
|
|||
"""
|
||||
This plugin adds a :term:`/code` command, to send syntax highlighted snippets
|
||||
of code using pygments and XHTML-IM (XEP-0071).
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
Either use your distribution tools to install python3-pygments or equivalent,
|
||||
or run:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip install --user pygments
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/code <language> <snippet>
|
||||
|
||||
Run this command to send the <snippet> of code, syntax highlighted
|
||||
using pygments’s <language> lexer.
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.formatters import HtmlFormatter #pylint: disable=no-name-in-module
|
||||
FORMATTER = HtmlFormatter(nowrap=True, noclasses=True)
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'code',
|
||||
self.command_code,
|
||||
usage='<language> <code>',
|
||||
short='Sends syntax-highlighted code',
|
||||
help='Sends syntax-highlighted code in the current tab')
|
||||
|
||||
def command_code(self, args):
|
||||
split = args.split(None, 1)
|
||||
if len(split) != 2:
|
||||
self.api.information('Usage: /code <language> <code>', 'Error')
|
||||
return None
|
||||
language, code = split
|
||||
lexer = get_lexer_by_name(language)
|
||||
tab = self.api.current_tab()
|
||||
code = highlight(code, lexer, FORMATTER)
|
||||
tab.command_xhtml('<pre><code class="language-%s">%s</code></pre>' % (language, code.rstrip('\n')))
|
|
@ -1,60 +0,0 @@
|
|||
"""
|
||||
Do a disco#info query on a JID, display the XEP-0157 Contact Addresses
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/contact
|
||||
**Usage:** ``/contact <JID>``
|
||||
|
||||
This command queries a JID for its Contact Addresses.
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp.jid import InvalidJID
|
||||
|
||||
CONTACT_TYPES = ['abuse', 'admin', 'feedback', 'sales', 'security', 'support']
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command('contact', self.command_disco,
|
||||
usage='<JID>',
|
||||
short='Get the Contact Addresses of a JID',
|
||||
help='Get the Contact Addresses of a JID')
|
||||
|
||||
def on_disco(self, iq):
|
||||
info = iq['disco_info']
|
||||
contacts = []
|
||||
# iterate all data forms, in case there are multiple
|
||||
for form in iq['disco_info']:
|
||||
values = form.get_values()
|
||||
if values['FORM_TYPE'][0] == 'http://jabber.org/network/serverinfo':
|
||||
for var in values:
|
||||
if not var.endswith('-addresses'):
|
||||
continue
|
||||
title = var[:-10] # strip '-addresses'
|
||||
sep = '\n ' + len(title) * ' '
|
||||
field_value = values[var]
|
||||
if field_value:
|
||||
value = sep.join(field_value) if isinstance(field_value, list) else field_value
|
||||
contacts.append(f'{title}: {value}')
|
||||
if contacts:
|
||||
self.api.information('\n'.join(contacts), 'Contact Info')
|
||||
else:
|
||||
self.api.information(f'No Contact Addresses for {iq["from"]}', 'Error')
|
||||
|
||||
async def command_disco(self, jid):
|
||||
try:
|
||||
iq = await self.core.xmpp.plugin['xep_0030'].get_info(jid=jid, cached=False)
|
||||
self.on_disco(iq)
|
||||
except InvalidJID as exn:
|
||||
self.api.information(f'Invalid JID “{jid}”: {exn}', 'Error')
|
||||
except (IqError, IqTimeout,) as exn:
|
||||
ifrom = exn.iq['from']
|
||||
condition = exn.iq['error']['condition']
|
||||
text = exn.iq['error']['text']
|
||||
message = f'Error getting Contact Addresses from {ifrom}: {condition}: {text}'
|
||||
self.api.information(message, 'Error')
|
|
@ -1,51 +0,0 @@
|
|||
"""
|
||||
This plugin lets you set the CSI_ state manually, when the autoaway plugin
|
||||
is not sufficient for your usage.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/csi_active
|
||||
**Usage:** ``/csi_active``
|
||||
|
||||
Set CSI state to ``active``.
|
||||
|
||||
/csi_inactive
|
||||
**Usage:** ``/csi_inactive``
|
||||
|
||||
Set CSI state to ``inactive``.
|
||||
|
||||
.. _CSI: https://xmpp.org/extensions/xep-0352.html
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'csi_active',
|
||||
self.command_active,
|
||||
help='Set the client state indication to “active”',
|
||||
short='Manual set active')
|
||||
self.api.add_command(
|
||||
'csi_inactive',
|
||||
self.command_inactive,
|
||||
help='Set the client state indication to “inactive”',
|
||||
short='Manual set inactive')
|
||||
|
||||
def command_active(self, args):
|
||||
if not self.core.xmpp.plugin['xep_0352'].enabled:
|
||||
self.api.information('CSI is not enabled in this server',
|
||||
'Warning')
|
||||
else:
|
||||
self.core.xmpp.plugin['xep_0352'].send_active()
|
||||
|
||||
def command_inactive(self, args):
|
||||
if not self.core.xmpp.plugin['xep_0352'].enabled:
|
||||
self.api.information('CSI is not enabled in this server',
|
||||
'Warning')
|
||||
else:
|
||||
self.core.xmpp.plugin['xep_0352'].send_inactive()
|
|
@ -1,42 +0,0 @@
|
|||
"""
|
||||
This plugin adds a "cyber" prefix to a random word in your chatroom messages.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Say something in a MUC tab.
|
||||
|
||||
Configuration options
|
||||
---------------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
frequency
|
||||
**Default:** ``10``
|
||||
|
||||
The percentage of the time the plugin will activate (randomly). 100 for every message, <= 0 for never.
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from random import choice, randint
|
||||
import re
|
||||
|
||||
DEFAULT_CONFIG = {'cyber': {'frequency': 10}}
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
default_config = DEFAULT_CONFIG
|
||||
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.cyberize)
|
||||
|
||||
def cyberize(self, msg, tab):
|
||||
if randint(1, 100) > self.config.get('frequency'):
|
||||
return
|
||||
words = [
|
||||
word for word in re.split('\W+', msg['body']) if len(word) > 3
|
||||
]
|
||||
if words:
|
||||
word = choice(words)
|
||||
msg['body'] = msg['body'].replace(word, 'cyber' + word)
|
|
@ -1,37 +0,0 @@
|
|||
"""
|
||||
This plugin adds a message at 00:00 in each of your chat tabs saying that the
|
||||
date has changed.
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from gettext import gettext as _
|
||||
|
||||
from poezio import timed_events, tabs
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.ui.types import InfoMessage
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.schedule_event()
|
||||
|
||||
def cleanup(self):
|
||||
self.api.remove_timed_event(self.next_event)
|
||||
|
||||
def schedule_event(self):
|
||||
day_change = datetime.datetime.combine(datetime.date.today(),
|
||||
datetime.time())
|
||||
day_change += datetime.timedelta(1)
|
||||
self.next_event = timed_events.TimedEvent(day_change, self.day_change)
|
||||
self.api.add_timed_event(self.next_event)
|
||||
|
||||
def day_change(self):
|
||||
msg = _("Day changed to %s") % (datetime.date.today().isoformat())
|
||||
|
||||
for tab in self.core.tabs:
|
||||
if isinstance(tab, tabs.ChatTab):
|
||||
tab.add_message(InfoMessage(msg))
|
||||
|
||||
self.core.refresh_window()
|
||||
self.schedule_event()
|
|
@ -1,122 +0,0 @@
|
|||
"""
|
||||
Dice plugin: roll some dice
|
||||
|
||||
Usage of this plugin is not recommended.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/roll [number of dice] [duration of the roll]
|
||||
Roll one or several unicode dice
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
refresh
|
||||
**Default:** ``0.5``
|
||||
|
||||
Interval in seconds between each correction (the closest to 0 is the fastest)
|
||||
|
||||
default_duration
|
||||
**Default:** ``5``
|
||||
|
||||
Total duration of the animation.
|
||||
"""
|
||||
|
||||
import random
|
||||
from typing import Optional
|
||||
|
||||
from poezio import tabs
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
DICE = '\u2680\u2681\u2682\u2683\u2684\u2685'
|
||||
|
||||
|
||||
class DiceRoll:
|
||||
__slots__ = [
|
||||
'duration', 'total_duration', 'dice_number', 'msgtype', 'jid',
|
||||
'msgid', 'increments'
|
||||
]
|
||||
|
||||
def __init__(self, total_duration, dice_number, msgtype, jid, msgid, increments):
|
||||
self.duration = 0
|
||||
self.total_duration = total_duration
|
||||
self.dice_number = dice_number
|
||||
self.msgtype = msgtype
|
||||
self.jid = jid
|
||||
self.msgid = msgid
|
||||
self.increments = increments
|
||||
|
||||
def reroll(self):
|
||||
self.duration += self.increments
|
||||
|
||||
def is_finished(self):
|
||||
return self.duration >= self.total_duration
|
||||
|
||||
|
||||
def roll_dice(num_dice: int) -> str:
|
||||
return ''.join(random.choice(DICE) for _ in range(num_dice))
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
default_config = {"dice": {"refresh": 0.75, "default_duration": 7.5}}
|
||||
|
||||
def init(self):
|
||||
for tab_t in [tabs.MucTab, tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab]:
|
||||
self.api.add_tab_command(
|
||||
tab_t,
|
||||
'roll',
|
||||
self.command_dice,
|
||||
help='Roll a die',
|
||||
usage='[number] [duration]')
|
||||
|
||||
@command_args_parser.quoted(0, 2, ['', ''], True)
|
||||
def command_dice(self, args):
|
||||
tab = self.api.current_tab()
|
||||
duration = self.config.get('default_duration')
|
||||
num_dice = 1
|
||||
try:
|
||||
if args[0]:
|
||||
num_dice = int(args[0])
|
||||
if args[1]:
|
||||
duration = float(args[1])
|
||||
except ValueError:
|
||||
self.core.command.help("roll")
|
||||
return
|
||||
else:
|
||||
if num_dice <= 0 or duration < 0:
|
||||
self.core.command.help("roll")
|
||||
return
|
||||
|
||||
msgtype = 'groupchat' if isinstance(tab, tabs.MucTab) else 'chat'
|
||||
|
||||
message = self.core.xmpp.make_message(tab.jid)
|
||||
message['type'] = msgtype
|
||||
message['body'] = roll_dice(num_dice)
|
||||
message.send()
|
||||
|
||||
increment = self.config.get('refresh')
|
||||
msgid = message['id']
|
||||
|
||||
roll = DiceRoll(duration, num_dice, msgtype, tab.jid, msgid, increment)
|
||||
event = self.api.create_delayed_event(increment, self.delayed_event,
|
||||
roll)
|
||||
self.api.add_timed_event(event)
|
||||
|
||||
def delayed_event(self, roll):
|
||||
if roll.is_finished():
|
||||
return
|
||||
roll.reroll()
|
||||
message = self.core.xmpp.make_message(roll.jid)
|
||||
message["type"] = roll.msgtype
|
||||
message["body"] = roll_dice(roll.dice_number)
|
||||
message["replace"]["id"] = roll.msgid
|
||||
message.send()
|
||||
event = self.api.create_delayed_event(roll.increments,
|
||||
self.delayed_event, roll)
|
||||
self.api.add_timed_event(event)
|
|
@ -1,106 +0,0 @@
|
|||
"""
|
||||
Do a disco#info query on a JID
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/disco
|
||||
**Usage:** ``/disco <JID>``
|
||||
|
||||
This command queries a JID for its disco#info.
|
||||
|
||||
There is no cache, as this is generally used for debug more than
|
||||
anything user-related.
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.decorators import command_args_parser
|
||||
from slixmpp.jid import InvalidJID
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'disco',
|
||||
self.command_disco,
|
||||
usage='<JID> [node] [info|items]',
|
||||
short='Get the disco#info of a JID',
|
||||
help='Get the disco#info of a JID')
|
||||
|
||||
def on_info(self, iq):
|
||||
if iq['type'] == 'error':
|
||||
self.api.information(iq['error']['text'] or iq['error']['condition'], 'Error')
|
||||
return
|
||||
|
||||
info = iq['disco_info']
|
||||
identities = (str(identity) for identity in info['identities'])
|
||||
self.api.information('\n'.join(identities), 'Identities')
|
||||
features = sorted(str(feature) for feature in info['features'])
|
||||
self.api.information('\n'.join(features), 'Features')
|
||||
title = 'Server Info'
|
||||
server_info = []
|
||||
for field in info['form']:
|
||||
var = field['var']
|
||||
if field['type'] == 'hidden' and var == 'FORM_TYPE':
|
||||
title = field['value'][0]
|
||||
continue
|
||||
sep = '\n ' + len(var) * ' '
|
||||
field_value = field.get_value(convert=False)
|
||||
value = sep.join(field_value) if isinstance(field_value,
|
||||
list) else field_value
|
||||
server_info.append('%s: %s' % (var, value))
|
||||
if server_info:
|
||||
self.api.information('\n'.join(server_info), title)
|
||||
|
||||
def on_items(self, iq):
|
||||
if iq['type'] == 'error':
|
||||
self.api.information(iq['error']['text'] or iq['error']['condition'], 'Error')
|
||||
return
|
||||
|
||||
def describe(item):
|
||||
text = item[0]
|
||||
node = item[1]
|
||||
name = item[2]
|
||||
if node is not None:
|
||||
text += ', node=' + node
|
||||
if name is not None:
|
||||
text += ', name=' + name
|
||||
return text
|
||||
|
||||
items = iq['disco_items']
|
||||
self.api.information('\n'.join(describe(item) for item in items['items']), 'Items')
|
||||
|
||||
@command_args_parser.quoted(1, 3)
|
||||
async def command_disco(self, args):
|
||||
if args is None:
|
||||
self.core.command.help('disco')
|
||||
return
|
||||
if len(args) == 1:
|
||||
jid, = args
|
||||
node = None
|
||||
type_ = 'info'
|
||||
elif len(args) == 2:
|
||||
jid, node = args
|
||||
type_ = 'info'
|
||||
else:
|
||||
jid, node, type_ = args
|
||||
try:
|
||||
if type_ == 'info':
|
||||
iq = await self.core.xmpp.plugin['xep_0030'].get_info(
|
||||
jid=jid, node=node, cached=False
|
||||
)
|
||||
self.on_info(iq)
|
||||
elif type_ == 'items':
|
||||
iq = await self.core.xmpp.plugin['xep_0030'].get_items(
|
||||
jid=jid, node=node
|
||||
)
|
||||
self.on_items(iq)
|
||||
except InvalidJID as e:
|
||||
self.api.information('Invalid JID “%s”: %s' % (jid, e), 'Error')
|
||||
except IqError as e:
|
||||
self.api.information('Received iq error while querying “%s”: %s' % (jid, e), 'Error')
|
||||
except IqTimeout:
|
||||
self.api.information('Received no reply querying “%s”…' % jid, 'Error')
|
|
@ -1,84 +0,0 @@
|
|||
"""
|
||||
Lists old versions of a corrected message.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/display_corrections
|
||||
**Usage:** ``/display_corrections [number]``
|
||||
|
||||
This command lists the old versions of a message.
|
||||
|
||||
Without argument, it will list the last corrected message if there
|
||||
is any. If you give an integer as an argument, ``/display_corrections``
|
||||
will go back gradually in the buffer to find the message matching
|
||||
that number (starting from 1, for the last corrected message).
|
||||
|
||||
If you are scrolling in the buffer, Poezio will list the corrected messages
|
||||
starting from the first you can see. (although there are some problems with
|
||||
multiline messages).
|
||||
|
||||
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.common import shell_split
|
||||
from poezio import tabs
|
||||
from poezio.ui.types import Message
|
||||
from poezio.theming import get_theme
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for tab_type in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab):
|
||||
self.api.add_tab_command(
|
||||
tab_type,
|
||||
'display_corrections',
|
||||
handler=self.command_display_corrections,
|
||||
usage='<number>',
|
||||
help=
|
||||
'Display all the corrections of the number-th last corrected message.',
|
||||
short='Display the corrections of a message')
|
||||
|
||||
def find_corrected(self, nb):
|
||||
messages = self.api.get_conversation_messages()
|
||||
if not messages:
|
||||
return None
|
||||
for message in reversed(messages):
|
||||
if not isinstance(message, Message):
|
||||
continue
|
||||
if message.old_message:
|
||||
if nb == 1:
|
||||
return message
|
||||
else:
|
||||
nb -= 1
|
||||
return None
|
||||
|
||||
def command_display_corrections(self, args):
|
||||
theme = get_theme()
|
||||
args = shell_split(args)
|
||||
if len(args) == 1:
|
||||
try:
|
||||
nb = int(args[0])
|
||||
except:
|
||||
return self.api.run_command('/help display_corrections')
|
||||
else:
|
||||
nb = 1
|
||||
message = self.find_corrected(nb)
|
||||
if message:
|
||||
display = []
|
||||
while message:
|
||||
str_time = message.time.strftime(theme.SHORT_TIME_FORMAT)
|
||||
display.append('%s %s%s%s %s' %
|
||||
(str_time, '* '
|
||||
if message.me else '', message.nickname, ''
|
||||
if message.me else '>', message.txt))
|
||||
message = message.old_message
|
||||
self.api.information(
|
||||
'Older versions:\n' + '\n'.join(display[::-1]), 'Info')
|
||||
else:
|
||||
self.api.information('No corrected message found.', 'Warning')
|
||||
|
||||
def cleanup(self):
|
||||
del self.config
|
|
@ -1,14 +0,0 @@
|
|||
"""
|
||||
Double the first word of any message you send in a :ref:`muctab`, making you appear retarded.
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.double)
|
||||
|
||||
def double(self, msg, tab):
|
||||
split = msg['body'].split()
|
||||
if split:
|
||||
msg['body'] = split[0] + ' ' + msg['body']
|
|
@ -1,50 +0,0 @@
|
|||
"""
|
||||
Display an image URL as an embedded image in some clients like Conversations.
|
||||
Uses: https://xmpp.org/extensions/xep-0066.html#x-oob
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/embed <image_url>
|
||||
|
||||
Run this command to send the <image_url> as an
|
||||
embedded image in your contact's client.
|
||||
"""
|
||||
|
||||
from poezio import tabs
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.theming import get_theme
|
||||
from poezio.ui.types import Message
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for tab_t in [tabs.MucTab, tabs.StaticConversationTab, tabs.DynamicConversationTab, tabs.PrivateTab]:
|
||||
self.api.add_tab_command(
|
||||
tab_t,
|
||||
'embed',
|
||||
self.embed_image_url,
|
||||
help='Embed an image url into the contact\'s client',
|
||||
usage='<image_url>')
|
||||
|
||||
def embed_image_url(self, url, tab=None):
|
||||
tab = tab or self.api.current_tab()
|
||||
message = self.core.xmpp.make_message(tab.jid)
|
||||
message['body'] = url
|
||||
message['oob']['url'] = url
|
||||
message['type'] = 'groupchat'
|
||||
if not isinstance(tab, tabs.MucTab):
|
||||
message['type'] = 'chat'
|
||||
tab.add_message(
|
||||
Message(
|
||||
message['body'],
|
||||
nickname=tab.core.own_nick,
|
||||
nick_color=get_theme().COLOR_OWN_NICK,
|
||||
identifier=message['id'],
|
||||
jid=tab.core.xmpp.boundjid,
|
||||
),
|
||||
)
|
||||
message.send()
|
||||
self.core.refresh_window()
|
|
@ -1,60 +0,0 @@
|
|||
# poezio emoji_ascii plugin
|
||||
#
|
||||
# Will translate received Emoji to :emoji: for better display on text terminals,
|
||||
# and outgoing :emoji: into Emoji on the wire.
|
||||
#
|
||||
# Requires emojis.json.gz (MIT licensed) from:
|
||||
#
|
||||
# git clone https://github.com/vdurmont/emoji-java
|
||||
# gzip -9 < ./src/main/resources/emojis.json > poezio/plugins/emojis.json.gz
|
||||
|
||||
# TODOs:
|
||||
# 1. it messes up your log files (doesn't log original message, logs mutilated :emoji: instead)
|
||||
# 2. Doesn't work on outgoing direct messages
|
||||
# 3. Doesn't detect pastes, corrupts jabber:x:foobar
|
||||
# 4. no auto-completion of emoji aliases
|
||||
# 5. coloring of converted Emojis to be able to differentiate them from incoming ASCII
|
||||
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from typing import Dict
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
emoji_to_ascii: Dict[str, str] = {}
|
||||
ascii_to_emoji: Dict[str, str] = {}
|
||||
emoji_pattern = None
|
||||
alias_pattern = None
|
||||
|
||||
def init(self):
|
||||
emoji_map_file_name = os.path.abspath(os.path.dirname(__file__) + '/emojis.json.gz')
|
||||
emoji_map_data = gzip.open(emoji_map_file_name, 'r').read().decode('utf-8')
|
||||
emoji_map = json.loads(emoji_map_data)
|
||||
for e in emoji_map:
|
||||
self.emoji_to_ascii[e['emoji']] = ':%s:' % e['aliases'][0]
|
||||
for alias in e['aliases']:
|
||||
# work around :iq: and similar country code misdetection
|
||||
flag = re.match('^[a-z][a-z]$', alias) and "flag" in e["tags"]
|
||||
if not flag:
|
||||
self.ascii_to_emoji[':%s:' % alias] = e['emoji']
|
||||
self.emoji_pattern = re.compile('|'.join(self.emoji_to_ascii.keys()).replace('*', '\*'))
|
||||
self.alias_pattern = re.compile('|'.join(self.ascii_to_emoji.keys()).replace('+', '\+'))
|
||||
|
||||
self.api.add_event_handler('muc_msg', self.emoji2alias)
|
||||
self.api.add_event_handler('conversation_msg', self.emoji2alias)
|
||||
self.api.add_event_handler('private_msg', self.emoji2alias)
|
||||
|
||||
self.api.add_event_handler('muc_say', self.alias2emoji)
|
||||
self.api.add_event_handler('private_say', self.alias2emoji)
|
||||
self.api.add_event_handler('conversation_say', self.alias2emoji)
|
||||
|
||||
|
||||
def emoji2alias(self, msg, tab):
|
||||
msg['body'] = self.emoji_pattern.sub(lambda m: self.emoji_to_ascii[m.group()], msg['body'])
|
||||
|
||||
def alias2emoji(self, msg, tab):
|
||||
msg['body'] = self.alias_pattern.sub(lambda m: self.ascii_to_emoji[m.group()], msg['body'])
|
|
@ -1,98 +0,0 @@
|
|||
"""
|
||||
This plugin lets you execute a system command through poezio.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. warning:: Running commands that start a daemon or an interface is not a good
|
||||
idea.
|
||||
|
||||
.. glossary::
|
||||
|
||||
/exec
|
||||
**Usage:** ``/exec [-o|-O] <command>``
|
||||
|
||||
Execute a system command.
|
||||
|
||||
::
|
||||
|
||||
/exec command
|
||||
|
||||
Will give you the result in the information buffer.
|
||||
|
||||
::
|
||||
|
||||
/exec -o command
|
||||
|
||||
Will send the result of the command into the current tab, if possible.
|
||||
|
||||
::
|
||||
|
||||
/exec -O command
|
||||
|
||||
Will send the result of the command and the command summary into the current
|
||||
tab, if possible.
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import common
|
||||
import asyncio
|
||||
import subprocess
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'exec',
|
||||
self.command_exec,
|
||||
usage='[-o|-O] <command>',
|
||||
help=
|
||||
'Execute a shell command and prints the result in the information buffer. The command should be ONE argument, that means it should be between \"\". The first argument (before the command) can be -o or -O. If -o is specified, it sends the result in the current conversation. If -O is specified, it sends the command and its result in the current conversation.\nExample: /exec -O \"uptime\" will send “uptime\n20:36:19 up 3:47, 4 users, load average: 0.09, 0.13, 0.09” in the current conversation.',
|
||||
short='Execute a command')
|
||||
|
||||
async def async_exec(self, command, arg):
|
||||
create = asyncio.create_subprocess_exec(
|
||||
'sh',
|
||||
'-c',
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
try:
|
||||
process = await create
|
||||
except OSError as e:
|
||||
self.api.information('Failed to execute command: %s' % (e, ),
|
||||
'Error')
|
||||
return
|
||||
stdout, stderr = await process.communicate()
|
||||
result = stdout.decode('utf-8')
|
||||
stderr = stderr.decode('utf-8')
|
||||
if arg == '-o':
|
||||
if not self.api.send_message('%s' % (result, )):
|
||||
self.api.information(
|
||||
'Cannot send result (%s), this is not a conversation tab' %
|
||||
result, 'Error')
|
||||
elif arg == '-O':
|
||||
if not self.api.send_message('%s:\n%s' % (command, result)):
|
||||
self.api.information(
|
||||
'Cannot send result (%s), this is not a conversation tab' %
|
||||
result, 'Error')
|
||||
else:
|
||||
self.api.information('%s:\n%s' % (command, result), 'Info')
|
||||
if stderr:
|
||||
self.api.information('stderr for %s:\n%s' % (command, stderr),
|
||||
'Info')
|
||||
await process.wait()
|
||||
|
||||
def command_exec(self, args):
|
||||
args = common.shell_split(args)
|
||||
if len(args) == 1:
|
||||
command = args[0]
|
||||
arg = None
|
||||
elif len(args) == 2:
|
||||
command = args[1]
|
||||
arg = args[0]
|
||||
else:
|
||||
self.api.run_command('/help exec')
|
||||
return
|
||||
asyncio.create_task(self.async_exec(command, arg))
|
|
@ -1,48 +0,0 @@
|
|||
"""
|
||||
This plugin uses figlet to transform every message into a big ascii-art
|
||||
message.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Say something in a Chat tab.
|
||||
|
||||
.. note:: Can create fun things when used with :ref:`The rainbow plugin <rainbow-plugin>`.
|
||||
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
|
||||
def is_figlet() -> bool:
|
||||
"""Ensure figlet exists"""
|
||||
process = subprocess.Popen(
|
||||
['which', 'figlet'],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return process.wait() == 0
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
if not is_figlet():
|
||||
self.api.information(
|
||||
'Couldn\'t find the figlet program. '
|
||||
'Please install it and reload the plugin.',
|
||||
'Error',
|
||||
)
|
||||
return None
|
||||
|
||||
self.api.add_event_handler('muc_say', self.figletize)
|
||||
self.api.add_event_handler('conversation_say', self.figletize)
|
||||
self.api.add_event_handler('private_say', self.figletize)
|
||||
return None
|
||||
|
||||
def figletize(self, msg, tab):
|
||||
process = subprocess.Popen(
|
||||
['figlet', '--', msg['body']], stdout=subprocess.PIPE)
|
||||
result = process.communicate()[0].decode('utf-8')
|
||||
msg['body'] = result
|
|
@ -1,19 +0,0 @@
|
|||
"""
|
||||
Show the exchanged IQs (useful for debugging).
|
||||
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.core.xmpp.register_handler(
|
||||
Callback('Iq_show', StanzaPath('iq'), self.handle_iq))
|
||||
|
||||
def handle_iq(self, iq):
|
||||
self.api.information('%s' % iq, 'Iq')
|
||||
|
||||
def cleanup(self):
|
||||
self.core.xmpp.remove_handler('Iq_show')
|
|
@ -1,323 +0,0 @@
|
|||
"""
|
||||
Plugin destined to be used together with the Biboumi IRC gateway.
|
||||
|
||||
For more information about Biboumi, please see the `official website`_.
|
||||
|
||||
This plugin is here as a non-default extension of the poezio configuration
|
||||
made to work with IRC rooms and logins. It also defines commands aimed at
|
||||
reducing the amount of effort needed to navigate smoothly between IRC and
|
||||
XMPP rooms.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Every feature of this plugin is centered around its :ref:`configuration file <plugin-configuration>`,
|
||||
so you have to make sure it is filled properly.
|
||||
|
||||
Global configuration
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
gateway
|
||||
**Default:** ``irc.jabberfr.org``
|
||||
|
||||
The JID of the IRC gateway to use. If empty, irc.jabberfr.org will be
|
||||
used. Please try to run your own, though, it’s painless to setup.
|
||||
|
||||
initial_connect
|
||||
**Default:** ``true``
|
||||
|
||||
Set to ``true`` if you want to join all the rooms and try to
|
||||
authenticate with nickserv when the plugin gets loaded. If it set to
|
||||
``false``, you will have to use the :term:`/irc_login` command to
|
||||
authenticate, and the :term:`/irc_join` command to join
|
||||
preconfigured rooms.
|
||||
|
||||
.. note:: There is no nickname option because the default from poezio will be used.
|
||||
|
||||
Server-specific configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Write a configuration section for each server, with the server address as the
|
||||
section name, and the following options:
|
||||
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
nickname
|
||||
**Default:** ``[empty]``
|
||||
|
||||
Your nickname on this server. If empty, the default configuration will be used.
|
||||
|
||||
rooms [IRC plugin]
|
||||
**Default:** ``[empty]``
|
||||
|
||||
The list of rooms to join on this server (e.g. ``#room1:#room2``).
|
||||
|
||||
.. note:: If no login_command or login_nick is set, the authentication phase
|
||||
won’t take place and you will join the rooms without authentication
|
||||
with nickserv or whatever.
|
||||
|
||||
Commands
|
||||
~~~~~~~~
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
/irc_join
|
||||
**Usage:** ``/irc_join <room or server>``
|
||||
|
||||
Join the specified room on the same server as the current tab (can
|
||||
be a private conversation or a chatroom). If a server that appears
|
||||
in the conversation is specified instead of a room, the plugin
|
||||
will try to join all the rooms configured with autojoin on that
|
||||
server.
|
||||
|
||||
/irc_query
|
||||
**Usage:** ``/irc_query <nickname> [message]``
|
||||
|
||||
Open a private conversation with the given nickname, on the same IRC
|
||||
server as the current tab (can be a private conversation or a
|
||||
chatroom). Doing `/irc_query foo "hello there"` when the current
|
||||
tab is #foo%irc.example.com@biboumi.example.com is equivalent to
|
||||
``/message foo%irc.example.com@biboumi.example.com "hello there"``
|
||||
|
||||
Example configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[irc]
|
||||
gateway = irc.jabberfr.org
|
||||
|
||||
[irc.libera.chat]
|
||||
nickname = mynick
|
||||
login_nick = nickserv
|
||||
login_command = identify mypassword
|
||||
rooms = #testroom1:#testroom2
|
||||
|
||||
[irc.geeknode.org]
|
||||
nickname = anothernick
|
||||
login_nick = C
|
||||
login_command = nick identify mypassword
|
||||
rooms = #testvroum
|
||||
|
||||
|
||||
|
||||
.. _official website: http://biboumi.louiz.org/
|
||||
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from typing import Optional, Tuple, List, Any
|
||||
from slixmpp.jid import JID, InvalidJID
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio.core.structs import Completion
|
||||
from poezio import tabs
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
default_config = {
|
||||
'irc': {
|
||||
"initial_connect": True,
|
||||
"gateway": "irc.jabberfr.org",
|
||||
}
|
||||
}
|
||||
|
||||
def init(self) -> None:
|
||||
if self.config.getbool('initial_connect'):
|
||||
asyncio.create_task(
|
||||
self.initial_connect()
|
||||
)
|
||||
self.api.add_command(
|
||||
'irc_join',
|
||||
self.command_irc_join,
|
||||
usage='<room or server>',
|
||||
help=('Join <room> in the same server as the '
|
||||
'current tab (if it is an IRC tab). Or '
|
||||
'join all the preconfigured rooms in '
|
||||
'<server> '),
|
||||
short='Join irc rooms more easily',
|
||||
completion=self.completion_irc_join)
|
||||
|
||||
self.api.add_command(
|
||||
'irc_query',
|
||||
self.command_irc_query,
|
||||
usage='<nickname> [message]',
|
||||
help=('Open a private conversation with the '
|
||||
'given <nickname>, on the current IRC '
|
||||
'server. Optionally immediately send '
|
||||
'the given message. For example, if the '
|
||||
'current tab is #foo%irc.example.com@'
|
||||
'biboumi.example.com, doing `/irc_query '
|
||||
'nick "hi there"` is equivalent to '
|
||||
'`/message nick%irc.example.com@biboumi.'
|
||||
'example.com "hi there"`'),
|
||||
short='Open a private conversation with an IRC user')
|
||||
|
||||
async def join(self, gateway: str, server: JID) -> None:
|
||||
"Join irc rooms on a server"
|
||||
nick: str = self.config.get_by_tabname(
|
||||
'nickname', server, default='') or self.core.own_nick
|
||||
rooms: List[str] = self.config.get_by_tabname(
|
||||
'rooms', server, default='').split(':')
|
||||
joins = []
|
||||
for room in rooms:
|
||||
room = '{}%{}@{}/{}'.format(room, server, gateway, nick)
|
||||
joins.append(self.core.command.join(room))
|
||||
|
||||
await asyncio.gather(*joins)
|
||||
|
||||
async def initial_connect(self) -> None:
|
||||
gateway: str = self.config.getstr('gateway')
|
||||
sections: List[str] = self.config.sections()
|
||||
|
||||
sections_jid = []
|
||||
for sect in sections:
|
||||
if sect == 'irc':
|
||||
continue
|
||||
try:
|
||||
sect_jid = JID(sect)
|
||||
if sect_jid != sect_jid.server:
|
||||
self.api.information(f'Invalid server: {sect}', 'Warning')
|
||||
continue
|
||||
except InvalidJID:
|
||||
self.api.information(f'Invalid server: {sect}', 'Warning')
|
||||
continue
|
||||
sections_jid.append(sect_jid)
|
||||
|
||||
for section in sections_jid:
|
||||
room_suffix = '%{}@{}'.format(section, gateway)
|
||||
|
||||
already_opened = False
|
||||
for tab in self.core.tabs:
|
||||
if tab.name.endswith(room_suffix) and tab.joined:
|
||||
already_opened = True
|
||||
break
|
||||
|
||||
if not already_opened:
|
||||
await self.join(gateway, section)
|
||||
|
||||
@command_args_parser.quoted(1, 1)
|
||||
async def command_irc_join(self, args: Optional[List[str]]) -> None:
|
||||
"""
|
||||
/irc_join <room or server>
|
||||
"""
|
||||
if not args:
|
||||
self.core.command.help('irc_join')
|
||||
return
|
||||
sections: List[str] = self.config.sections()
|
||||
if 'irc' in sections:
|
||||
sections.remove('irc')
|
||||
if args[0] in sections:
|
||||
try:
|
||||
section_jid = JID(args[0])
|
||||
except InvalidJID:
|
||||
self.api.information(f'Invalid address: {args[0]}', 'Error')
|
||||
return
|
||||
#self.config.get_by_tabname('rooms', section_jid)
|
||||
await self.join_server_rooms(section_jid)
|
||||
else:
|
||||
await self.join_room(args[0])
|
||||
|
||||
@command_args_parser.quoted(1, 1)
|
||||
def command_irc_query(self, args: Optional[List[str]]) -> None:
|
||||
"""
|
||||
Open a private conversation with the given nickname, on the current IRC
|
||||
server.
|
||||
"""
|
||||
if args is None:
|
||||
self.core.command.help('irc_query')
|
||||
return
|
||||
current_tab_info = self.get_current_tab_irc_info()
|
||||
if not current_tab_info:
|
||||
return
|
||||
server, gateway = current_tab_info
|
||||
nickname = args[0]
|
||||
message = None
|
||||
if len(args) == 2:
|
||||
message = args[1]
|
||||
jid = '{}%{}@{}'.format(nickname, server, gateway)
|
||||
if message:
|
||||
self.core.command.message('{} "{}"'.format(jid, message))
|
||||
else:
|
||||
self.core.command.message('{}'.format(jid))
|
||||
|
||||
async def join_server_rooms(self, section: JID) -> None:
|
||||
"""
|
||||
Join all the rooms configured for a section
|
||||
(section = irc server)
|
||||
"""
|
||||
gateway: str = self.config.getstr('gateway')
|
||||
rooms: List[str] = self.config.get_by_tabname('rooms', section).split(':')
|
||||
nick: str = self.config.get_by_tabname('nickname', section)
|
||||
if nick:
|
||||
nick = '/' + nick
|
||||
else:
|
||||
nick = ''
|
||||
suffix = '%{}@{}{}'.format(section, gateway, nick)
|
||||
|
||||
for room in rooms:
|
||||
await self.core.command.join(room + suffix)
|
||||
|
||||
async def join_room(self, name: str) -> None:
|
||||
"""
|
||||
Join a room with only its name and the current tab
|
||||
"""
|
||||
current_tab_info = self.get_current_tab_irc_info()
|
||||
if not current_tab_info:
|
||||
return
|
||||
server, gateway = current_tab_info
|
||||
try:
|
||||
server_jid = JID(server)
|
||||
except InvalidJID:
|
||||
return
|
||||
|
||||
room = '{}%{}@{}'.format(name, server, gateway)
|
||||
if self.config.get_by_tabname('nickname', server_jid.bare):
|
||||
room += '/' + self.config.get_by_tabname('nickname', server_jid.bare)
|
||||
|
||||
await self.core.command.join(room)
|
||||
|
||||
def get_current_tab_irc_info(self) -> Optional[Tuple[str, str]]:
|
||||
"""
|
||||
Return a tuple with the irc server and the gateway hostnames of the
|
||||
current tab. If the current tab is not an IRC channel or private
|
||||
conversation, a warning is displayed and None is returned
|
||||
"""
|
||||
gateway: str = self.config.getstr('gateway')
|
||||
current = self.api.current_tab()
|
||||
current_jid = current.jid
|
||||
if not current_jid.server == gateway:
|
||||
self.api.information(
|
||||
'The current tab does not appear to be an IRC one', 'Warning')
|
||||
return None
|
||||
if isinstance(current, tabs.OneToOneTab):
|
||||
if '%' not in current_jid.node:
|
||||
server = current_jid.node
|
||||
else:
|
||||
ignored, server = current_jid.node.rsplit('%', 1)
|
||||
elif isinstance(current, tabs.MucTab):
|
||||
if '%' not in current_jid.node:
|
||||
server = current_jid.node
|
||||
else:
|
||||
ignored, server = current_jid.node.rsplit('%', 1)
|
||||
else:
|
||||
self.api.information(
|
||||
'The current tab does not appear to be an IRC one', 'Warning')
|
||||
return None
|
||||
return server, gateway
|
||||
|
||||
def completion_irc_join(self, the_input: Any) -> Completion:
|
||||
"""
|
||||
completion for /irc_join
|
||||
"""
|
||||
sections: List[str] = self.config.sections()
|
||||
if 'irc' in sections:
|
||||
sections.remove('irc')
|
||||
return Completion(the_input.new_completion, sections, 1)
|
|
@ -1,61 +0,0 @@
|
|||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fenc=utf-8
|
||||
#
|
||||
# Copyright © 2018 Maxime “pep” Buquet
|
||||
# Copyright © 2019 Madhur Garg
|
||||
#
|
||||
# Distributed under terms of the GPL-3.0+ license. See the COPYING file.
|
||||
|
||||
"""
|
||||
Search provided string in the buffer and return all results on the screen
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
from poezio.text_buffer import TextBuffer
|
||||
from poezio.ui.types import Message as PMessage, InfoMessage
|
||||
|
||||
|
||||
def add_line(
|
||||
text_buffer: TextBuffer,
|
||||
text: str,
|
||||
datetime: Optional[datetime] = None,
|
||||
) -> None:
|
||||
"""Adds a textual entry in the TextBuffer"""
|
||||
text_buffer.add_message(InfoMessage(text, time=datetime))
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
"""Lastlog Plugin"""
|
||||
|
||||
def init(self):
|
||||
for tab in tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab, tabs.MucTab:
|
||||
self.api.add_tab_command(
|
||||
tab,
|
||||
'lastlog',
|
||||
self.command_lastlog,
|
||||
usage='<keyword>',
|
||||
help='Search <keyword> in the buffer and returns results'
|
||||
'on the screen')
|
||||
|
||||
def command_lastlog(self, input_):
|
||||
"""Define lastlog command"""
|
||||
|
||||
text_buffer = self.api.current_tab()._text_buffer
|
||||
search_re = re.compile(input_, re.I)
|
||||
|
||||
res = []
|
||||
add_line(text_buffer, "Lastlog:")
|
||||
for message in text_buffer.messages:
|
||||
if isinstance(message, PMessage) and \
|
||||
search_re.search(message.txt) is not None:
|
||||
res.append(message)
|
||||
add_line(text_buffer, "%s> %s" % (message.nickname, message.txt), message.time)
|
||||
add_line(text_buffer, "End of Lastlog")
|
||||
self.api.current_tab().text_win.pos = 0
|
||||
self.api.current_tab().core.refresh_window()
|
|
@ -1,178 +0,0 @@
|
|||
"""
|
||||
Opens links in a browser.
|
||||
|
||||
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
|
||||
**Usage:** ``/link [range] [command]``
|
||||
|
||||
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.
|
||||
|
||||
|
||||
: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
|
||||
|
||||
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
|
||||
.. _daemon.py: https://lab.louiz.org/poezio/poezio/raw/main/poezio/daemon.py
|
||||
|
||||
"""
|
||||
import platform
|
||||
import re
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.xhtml import clean_text
|
||||
from poezio import common
|
||||
from poezio import tabs
|
||||
|
||||
url_pattern = re.compile(
|
||||
r'\b'
|
||||
'(?:http[s]?://(?:\S+))|'
|
||||
'(?:magnet:\?(?:\S+))|'
|
||||
'(?:aesgcm://(?:\S+))|'
|
||||
'(?:gopher://(?:\S+))|'
|
||||
'(?:gemini://(?:\S+))'
|
||||
'\b',
|
||||
re.I | re.U
|
||||
)
|
||||
|
||||
app_mapping = {
|
||||
'Linux': 'xdg-open',
|
||||
'Darwin': 'open',
|
||||
}
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for _class in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab):
|
||||
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\
|
||||
If [num] is given, then it will\open the num-th link displayed. \
|
||||
Use a [command] argument to override the configured browser value.',
|
||||
short='Open links into a browser')
|
||||
|
||||
def find_link(self, nb):
|
||||
messages = self.api.get_conversation_messages()
|
||||
if not messages:
|
||||
return None
|
||||
for message in messages[::-1]:
|
||||
matches = url_pattern.findall(clean_text(message.txt))
|
||||
if matches:
|
||||
for url in matches[::-1]:
|
||||
if nb == 1:
|
||||
return url
|
||||
else:
|
||||
nb -= 1
|
||||
return None
|
||||
|
||||
def command_link(self, args):
|
||||
args = common.shell_split(args)
|
||||
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] == ":"):
|
||||
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:
|
||||
return self.api.information(
|
||||
'Invalid range: %s' % (args[0]), 'Error')
|
||||
command = None
|
||||
if len(args) == 2:
|
||||
command = args[1]
|
||||
if len(args) == 1 and (not args[0][0].isnumeric()
|
||||
and args[0][0] != ":"):
|
||||
command = args[0]
|
||||
for nb in range(start, end + 1):
|
||||
link = self.find_link(nb)
|
||||
if not link:
|
||||
return self.api.information('No URL found.', 'Warning')
|
||||
default = app_mapping.get(platform.system(), 'firefox')
|
||||
if command is None:
|
||||
self.core.exec_command(
|
||||
[self.config.get('browser', default), link])
|
||||
else:
|
||||
self.core.exec_command([command, link])
|
||||
|
||||
def cleanup(self):
|
||||
del self.config
|
|
@ -1,93 +0,0 @@
|
|||
"""
|
||||
Marquee plugin: replicate the html <marquee/> tag with message corrections.
|
||||
|
||||
Usage of this plugin is not recommended.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/marquee <text>
|
||||
Send the following text with <marquee/> behavior
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
refresh
|
||||
**Default:** ``1``
|
||||
|
||||
Interval between each correction (the closest to 0 is the fastest)
|
||||
|
||||
total_duration
|
||||
**Default:** ``30``
|
||||
|
||||
Total duration of the animation.
|
||||
|
||||
padding
|
||||
**Default:** ``20``
|
||||
|
||||
Padding to use to move the text.
|
||||
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
from poezio import xhtml
|
||||
from poezio.decorators import command_args_parser
|
||||
|
||||
|
||||
def move(text, step, spacing):
|
||||
new_text = text + ("\u00A0" * spacing)
|
||||
return new_text[-(step % len(new_text)):] + new_text[:-(
|
||||
step % len(new_text))]
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
default_config = {
|
||||
"marquee": {
|
||||
"refresh": 1.0,
|
||||
"total_duration": 30,
|
||||
"padding": 20
|
||||
}
|
||||
}
|
||||
|
||||
def init(self):
|
||||
for tab_t in [tabs.MucTab, tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab]:
|
||||
self.add_tab_command(
|
||||
tab_t, 'marquee', self.command_marquee,
|
||||
'Replicate the <marquee/> behavior in a message')
|
||||
|
||||
@command_args_parser.raw
|
||||
async def command_marquee(self, args):
|
||||
if not args:
|
||||
return None
|
||||
tab = self.api.current_tab()
|
||||
args = xhtml.clean_text(xhtml.convert_simple_to_full_colors(args))
|
||||
await tab.command_say(args)
|
||||
is_muctab = isinstance(tab, tabs.MucTab)
|
||||
msg_id = tab.last_sent_message["id"]
|
||||
jid = tab.jid
|
||||
|
||||
event = self.api.create_delayed_event(
|
||||
self.config.get("refresh"), self.delayed_event, jid, args, msg_id,
|
||||
1, 0, is_muctab)
|
||||
self.api.add_timed_event(event)
|
||||
|
||||
def delayed_event(self, jid, body, msg_id, step, duration, is_muctab):
|
||||
if duration >= self.config.get("total_duration"):
|
||||
return
|
||||
message = self.core.xmpp.make_message(jid)
|
||||
message["type"] = "groupchat" if is_muctab else "chat"
|
||||
message["body"] = move(body, step, self.config.get("padding"))
|
||||
message["replace"]["id"] = msg_id
|
||||
message.send()
|
||||
event = self.api.create_delayed_event(
|
||||
self.config.get("refresh"), self.delayed_event, jid, body,
|
||||
msg_id, step + 1, duration + self.config.get("refresh"),
|
||||
is_muctab)
|
||||
self.api.add_timed_event(event)
|
|
@ -1,33 +0,0 @@
|
|||
"""
|
||||
Repeats the last message in the conversation.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/mirror
|
||||
**Usage:** ``/mirror``
|
||||
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for tab_type in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab):
|
||||
self.api.add_tab_command(
|
||||
tab_type,
|
||||
'mirror',
|
||||
handler=self.mirror,
|
||||
help='Repeat the last message from the conversation.',
|
||||
short='Repeat the last message from the conversation.')
|
||||
|
||||
def mirror(self, args):
|
||||
messages = self.api.get_conversation_messages()
|
||||
if not messages:
|
||||
# Do nothing if the conversation doesn’t contain any message
|
||||
return
|
||||
last_message = messages[-1]
|
||||
self.api.send_message(last_message.txt)
|
|
@ -1,98 +0,0 @@
|
|||
"""
|
||||
This plugin is here to send what you are listening to in a chat tab.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
You need `python-mpd`_, in its python3 version.
|
||||
|
||||
Then you can load the plugin.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/load mpd_client
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
You have to put the following into :file:`mpd_client.cfg`, as explained in
|
||||
the :ref:`plugin-configuration` section.
|
||||
|
||||
.. note:: If you do not put anything, the plugin will try to connect to
|
||||
:file:`localhost:6600` with no password.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[mpd_client]
|
||||
host = the_mpd_host
|
||||
port = 6600
|
||||
password = password if necessary
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/mpd
|
||||
**Usage:** ``/mpd [full]``
|
||||
|
||||
The bare command will show the current song, artist, and album
|
||||
|
||||
``/mpd full`` will show the current song, artist, and album,
|
||||
plus a nice progress bar in color.
|
||||
|
||||
.. _python-mpd: https://github.com/Mic92/python-mpd2
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.common import shell_split
|
||||
from poezio.core.structs import Completion
|
||||
from os.path import basename as base
|
||||
from poezio import tabs
|
||||
import mpd
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for _class in (tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.MucTab, tabs.PrivateTab):
|
||||
self.api.add_tab_command(
|
||||
_class,
|
||||
'mpd',
|
||||
self.command_mpd,
|
||||
usage='[full]',
|
||||
help=
|
||||
'Sends a message showing the current song of an MPD instance. If full is provided, the message is more verbose.',
|
||||
short='Send the MPD status',
|
||||
completion=self.completion_mpd)
|
||||
|
||||
def command_mpd(self, args):
|
||||
args = shell_split(args)
|
||||
c = mpd.MPDClient()
|
||||
c.connect(
|
||||
host=self.config.get('host', 'localhost'),
|
||||
port=self.config.get('port', '6600'))
|
||||
password = self.config.get('password', '')
|
||||
if password:
|
||||
c.password(password) #pylint: disable=no-member
|
||||
current = c.currentsong() #pylint: disable=no-member
|
||||
artist = current.get('artist', 'Unknown artist')
|
||||
album = current.get('album', 'Unknown album')
|
||||
title = current.get('title', base(
|
||||
current.get('file', 'Unknown title')))
|
||||
|
||||
s = '%s - %s (%s)' % (artist, title, album)
|
||||
if 'full' in args:
|
||||
if 'elapsed' in current and 'time' in current:
|
||||
current_time = float(c.status()['elapsed']) #pylint: disable=no-member
|
||||
percents = int(current_time / float(current['time']) * 10)
|
||||
s += ' \x192}[\x191}' + '-' * (
|
||||
percents - 1) + '\x193}+' + '\x191}' + '-' * (
|
||||
10 - percents - 1) + '\x192}]\x19o'
|
||||
if not self.api.send_message('%s' % (s, )):
|
||||
self.api.information('Cannot send result (%s)' % s, 'Error')
|
||||
|
||||
def completion_mpd(self, the_input):
|
||||
return Completion(the_input.auto_completion, ['full'], quotify=False)
|
File diff suppressed because it is too large
Load diff
|
@ -1,42 +0,0 @@
|
|||
"""
|
||||
This plugin adds a :term:`/pacokick` command, which is a random kick.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/pacokick
|
||||
|
||||
Run the command in a room where you are a moderator to
|
||||
kick someone randomly.
|
||||
"""
|
||||
|
||||
from random import choice
|
||||
from poezio.tabs import MucTab
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'pacokick',
|
||||
self.command_kick,
|
||||
usage='',
|
||||
help='Kick a random user.',
|
||||
short='Kick a random user')
|
||||
|
||||
def command_kick(self, arg):
|
||||
tab = self.api.current_tab()
|
||||
if isinstance(tab, MucTab):
|
||||
kickable = list(
|
||||
filter(lambda x: x.affiliation in ('none', 'member'),
|
||||
tab.users))
|
||||
if kickable:
|
||||
to_kick = choice(kickable)
|
||||
if to_kick:
|
||||
to_kick = to_kick.nick
|
||||
tab.command_kick(to_kick + ' ' + arg)
|
||||
else:
|
||||
self.api.information('No one to kick :(', 'Info')
|
|
@ -1,172 +0,0 @@
|
|||
"""
|
||||
This plugin allows you to ping an entity.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/ping
|
||||
**Usage (globally):** ``/ping <jid>``
|
||||
|
||||
**Usage (in a MUC tab):** ``/ping <jid or nick>``
|
||||
|
||||
**Usage (in a conversation tab):** ``/ping [jid]``
|
||||
|
||||
Globally, you can do ``/ping jid@example.com`` to get a ping.
|
||||
|
||||
In a MUC, you can either do it to a JID or a nick (``/ping nick`` or ``/ping
|
||||
jid@example.com``).
|
||||
|
||||
In a private or a direct conversation, you can do ``/ping`` to ping
|
||||
the current interlocutor.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from slixmpp import InvalidJID, JID
|
||||
from slixmpp.exceptions import IqTimeout
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.roster import roster
|
||||
from poezio.contact import Contact, Resource
|
||||
from poezio.core.structs import Completion
|
||||
from poezio import tabs
|
||||
import time
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'ping',
|
||||
self.command_ping,
|
||||
usage='<jid>',
|
||||
help='Send an XMPP ping to jid (see XEP-0199).',
|
||||
short='Send a ping',
|
||||
completion=self.completion_ping)
|
||||
self.api.add_tab_command(
|
||||
tabs.MucTab,
|
||||
'ping',
|
||||
self.command_muc_ping,
|
||||
usage='<jid|nick>',
|
||||
help='Send an XMPP ping to jid or nick (see XEP-0199).',
|
||||
short='Send a ping.',
|
||||
completion=self.completion_muc_ping)
|
||||
self.api.add_tab_command(
|
||||
tabs.RosterInfoTab,
|
||||
'ping',
|
||||
self.command_roster_ping,
|
||||
usage='<jid>',
|
||||
help='Send an XMPP ping to jid (see XEP-0199).',
|
||||
short='Send a ping.',
|
||||
completion=self.completion_ping)
|
||||
for _class in (tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab):
|
||||
self.api.add_tab_command(
|
||||
_class,
|
||||
'ping',
|
||||
self.command_private_ping,
|
||||
usage='[jid]',
|
||||
help=
|
||||
'Send an XMPP ping to the current interlocutor or the given JID.',
|
||||
short='Send a ping',
|
||||
completion=self.completion_ping)
|
||||
|
||||
@command_args_parser.raw
|
||||
async def command_ping(self, arg):
|
||||
if not arg:
|
||||
return self.core.command.help('ping')
|
||||
try:
|
||||
jid = JID(arg)
|
||||
except InvalidJID:
|
||||
return self.api.information('Invalid JID: %s' % arg, 'Error')
|
||||
start = time.time()
|
||||
|
||||
try:
|
||||
iq = await self.core.xmpp.plugin['xep_0199'].send_ping(
|
||||
jid=jid, timeout=10
|
||||
)
|
||||
delay = time.time() - start
|
||||
error = False
|
||||
reply = ''
|
||||
if iq['type'] == 'error':
|
||||
error_condition = iq['error']['condition']
|
||||
reply = error_condition
|
||||
# These IQ errors are not ping errors:
|
||||
# 'service-unavailable': official "not supported" response as of RFC6120 (§8.4) and XEP-0199 (§4.1)
|
||||
# 'feature-not-implemented': inoffcial not-supported response from many clients
|
||||
if error_condition not in ('service-unavailable',
|
||||
'feature-not-implemented'):
|
||||
error = True
|
||||
error_text = iq['error']['text']
|
||||
if error_text:
|
||||
reply = '%s: %s' % (error_condition, error_text)
|
||||
if error:
|
||||
message = '%s did not respond to ping: %s' % (jid, reply)
|
||||
else:
|
||||
reply = ' (%s)' % reply if reply else ''
|
||||
message = '%s responded to ping after %ss%s' % (
|
||||
jid, round(delay, 4), reply)
|
||||
self.api.information(message, 'Info')
|
||||
except IqTimeout:
|
||||
self.api.information(
|
||||
'%s did not respond to ping after 10s: timeout' % jid,
|
||||
'Info'
|
||||
)
|
||||
|
||||
def completion_muc_ping(self, the_input):
|
||||
users = [user.nick for user in self.api.current_tab().users]
|
||||
l = self.resources()
|
||||
users.extend(l)
|
||||
return Completion(the_input.auto_completion, users, '', quotify=False)
|
||||
|
||||
@command_args_parser.raw
|
||||
def command_private_ping(self, arg):
|
||||
jid = arg
|
||||
if not arg:
|
||||
jid = self.api.current_tab().jid
|
||||
asyncio.create_task(
|
||||
self.command_ping(jid)
|
||||
)
|
||||
|
||||
@command_args_parser.raw
|
||||
def command_muc_ping(self, arg):
|
||||
if not arg:
|
||||
return
|
||||
user = self.api.current_tab().get_user_by_name(arg)
|
||||
if user:
|
||||
jid = self.api.current_tab().jid
|
||||
jid.resource = user.nick
|
||||
else:
|
||||
try:
|
||||
jid = JID(arg)
|
||||
except InvalidJID:
|
||||
return self.api.information('Invalid JID: %s' % arg, 'Error')
|
||||
asyncio.create_task(
|
||||
self.command_ping(jid.full)
|
||||
)
|
||||
|
||||
@command_args_parser.raw
|
||||
def command_roster_ping(self, arg):
|
||||
if arg:
|
||||
jid = arg
|
||||
else:
|
||||
current = self.api.current_tab().selected_row
|
||||
if isinstance(current, Resource):
|
||||
jid = current.jid
|
||||
elif isinstance(current, Contact):
|
||||
res = current.get_highest_priority_resource()
|
||||
if res is not None:
|
||||
jid =res.jid
|
||||
asyncio.create_task(
|
||||
self.command_ping(jid)
|
||||
)
|
||||
|
||||
def resources(self):
|
||||
l = []
|
||||
for contact in roster.get_contacts():
|
||||
for resource in contact.resources:
|
||||
l.append(resource.jid)
|
||||
return l
|
||||
|
||||
def completion_ping(self, the_input):
|
||||
return Completion(
|
||||
the_input.auto_completion, self.resources(), '', quotify=False)
|
|
@ -1,68 +0,0 @@
|
|||
"""
|
||||
|
||||
This plugins allows commands to be sent to poezio via a named pipe.
|
||||
|
||||
You can run the same commands that you would in the poezio input
|
||||
(e.g. ``echo '/message toto@example.tld Hi' >> /tmp/poezio.fifo``).
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
pipename
|
||||
**Default:** :file:`/tmp/poezio.fifo`
|
||||
|
||||
The path to the fifo which will receive commands.
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
import os
|
||||
import stat
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PIPENAME = "/tmp/poezio.fifo"
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.stop = False
|
||||
|
||||
self.pipename = self.config.get("pipename", PIPENAME)
|
||||
|
||||
if not os.path.exists(self.pipename):
|
||||
os.mkfifo(self.pipename)
|
||||
|
||||
if not stat.S_ISFIFO(os.stat(self.pipename).st_mode):
|
||||
raise TypeError("File %s is not a fifo file" % self.pipename)
|
||||
|
||||
self.fd = os.open(self.pipename, os.O_RDONLY | os.O_NONBLOCK)
|
||||
|
||||
self.data = b""
|
||||
asyncio.get_event_loop().add_reader(self.fd, self.read_from_fifo)
|
||||
|
||||
def read_from_fifo(self):
|
||||
data = os.read(self.fd, 512)
|
||||
if not data:
|
||||
# EOF, close the fifo. And reopen it
|
||||
asyncio.get_event_loop().remove_reader(self.fd)
|
||||
os.close(self.fd)
|
||||
self.fd = os.open(self.pipename, os.O_RDONLY | os.O_NONBLOCK)
|
||||
asyncio.get_event_loop().add_reader(self.fd, self.read_from_fifo)
|
||||
self.data = b''
|
||||
else:
|
||||
self.data += data
|
||||
l = self.data.split(b'\n', 1)
|
||||
if len(l) == 2:
|
||||
line, self.data = l
|
||||
log.debug("run: %s" % (line.decode().strip()))
|
||||
self.api.run_command(line.decode().strip())
|
||||
|
||||
def cleanup(self):
|
||||
asyncio.get_event_loop().remove_reader(self.fd)
|
||||
os.close(self.fd)
|
|
@ -1,49 +0,0 @@
|
|||
"""
|
||||
This plugin adds a command (that can be bound to a key) that adds a random
|
||||
number of dots in the input, making you look depressed, or overly thinking...
|
||||
|
||||
Installation
|
||||
------------
|
||||
Load the plugin.::
|
||||
|
||||
/load pointpoint
|
||||
|
||||
Then use the command: ::
|
||||
|
||||
/pointpoint
|
||||
|
||||
But since the goal is to be able to add the dots while typing a message,
|
||||
entering a command is not really useful. To be useful, this plugin needs to
|
||||
be used through a bound key, for example like this: ::
|
||||
|
||||
/bind M-. _exc_pointpoint
|
||||
|
||||
You just need to press Alt+. and this will insert dots in your message.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/pointpoint
|
||||
**Usage:** ``/pointpoint``
|
||||
|
||||
…
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from random import randrange
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'pointpoint',
|
||||
self.command_pointpoint,
|
||||
help='Insert a random number of dots in the input')
|
||||
|
||||
def command_pointpoint(self, args):
|
||||
for i in range(randrange(8, 25)):
|
||||
self.core.current_tab().input.do_command(".")
|
|
@ -1,184 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import io
|
||||
import logging
|
||||
import qrcode
|
||||
|
||||
from typing import Dict, Callable
|
||||
|
||||
from slixmpp import JID, InvalidJID
|
||||
|
||||
from poezio import windows
|
||||
from poezio.tabs import Tab
|
||||
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:
|
||||
try:
|
||||
server = JID(args[0])
|
||||
except InvalidJID:
|
||||
self.api.information(f'Invalid JID: {args[0]}', 'Error')
|
||||
return
|
||||
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)
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
"""
|
||||
This plugin allows you to quote messages easily.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/quote
|
||||
**Usage:** ``/quote <message>``
|
||||
|
||||
The message must exist. You can use autocompletion to get the message
|
||||
you want to quote easily.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/quote "Pouet"
|
||||
|
||||
If the message "Pouet" exists, it will be put in the input. If not you
|
||||
will get a warning.
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
before_quote
|
||||
|
||||
**Default value:** ``[empty]``
|
||||
|
||||
Text to insert before the quote. ``%(nick)s`` and ``%(time)s`` can
|
||||
be used to insert the nick of the user who sent the message or the
|
||||
time of the message.
|
||||
|
||||
after_quote
|
||||
|
||||
**Default value:** ``[empty]``
|
||||
|
||||
Text to insert after the quote. ``%(nick)s`` and ``%(time)s`` can
|
||||
be used to insert the nick of the user who sent the message or the
|
||||
time of the message.
|
||||
"""
|
||||
|
||||
from poezio.core.structs import Completion
|
||||
from poezio.ui.types import Message
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.xhtml import clean_text
|
||||
from poezio.theming import get_theme
|
||||
from poezio import common
|
||||
from poezio import tabs
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for _class in (tabs.MucTab, tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.PrivateTab):
|
||||
self.api.add_tab_command(
|
||||
_class,
|
||||
'quote',
|
||||
self.command_quote,
|
||||
usage='<message>',
|
||||
help='Quote the message you typed if it exists.',
|
||||
short='Quote a message.',
|
||||
completion=self.completion_quote)
|
||||
|
||||
def command_quote(self, args):
|
||||
args = common.shell_split(args)
|
||||
if len(args) == 1:
|
||||
message = args[-1]
|
||||
else:
|
||||
return self.api.run_command('/help quote')
|
||||
message = self.find_message(message)
|
||||
if message:
|
||||
str_time = message.time.strftime(get_theme().SHORT_TIME_FORMAT)
|
||||
before = self.config.get('before_quote', '') % {
|
||||
'nick': message.nickname or '',
|
||||
'time': str_time,
|
||||
}
|
||||
after = self.config.get('after_quote', '') % {
|
||||
'nick': message.nickname or '',
|
||||
'time': str_time,
|
||||
}
|
||||
self.core.insert_input_text(
|
||||
'%(before)s%(quote)s%(after)s' % {
|
||||
'before': before.replace('\\n', '\n').replace('[SP]', ' '),
|
||||
'quote': clean_text(message.txt),
|
||||
'after': after.replace('\\n', '\n').replace('[SP]', ' ')
|
||||
})
|
||||
else:
|
||||
self.api.information('No message found', 'Warning')
|
||||
|
||||
def find_message(self, txt):
|
||||
messages = self.api.get_conversation_messages()
|
||||
if not messages:
|
||||
return None
|
||||
for message in messages[::-1]:
|
||||
if isinstance(message, Message) and clean_text(message.txt) == txt:
|
||||
return message
|
||||
return None
|
||||
|
||||
def completion_quote(self, the_input):
|
||||
def message_match(msg):
|
||||
return input_message.lower() in clean_text(msg.txt).lower()
|
||||
|
||||
messages = self.api.get_conversation_messages()
|
||||
if not messages:
|
||||
return
|
||||
text = the_input.get_text()
|
||||
args = common.shell_split(text)
|
||||
if not text.endswith(' '):
|
||||
input_message = args[-1]
|
||||
messages = list(filter(message_match, messages))
|
||||
elif len(args) > 1:
|
||||
return False
|
||||
return Completion(
|
||||
the_input.auto_completion,
|
||||
[clean_text(msg.txt) for msg in messages[::-1] if isinstance(msg, Message)],
|
||||
''
|
||||
)
|
|
@ -1,47 +0,0 @@
|
|||
"""
|
||||
This plugin colors each character of a message with a random color.
|
||||
|
||||
.. note:: As ticket `#3273`_ puts it, the final output is closer to vomit than a rainbow.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/rainbow
|
||||
|
||||
Say something in a Chat tab.
|
||||
|
||||
.. note:: Can create fun things when used with :ref:`The figlet plugin <figlet-plugin>`.
|
||||
|
||||
.. _#3273: https://lab.louiz.org/poezio/poezio/-/issues/3273
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import xhtml
|
||||
import random
|
||||
|
||||
possible_colors = list(range(256))
|
||||
# remove the colors that are almost white or almost black
|
||||
for col in [
|
||||
16, 232, 233, 234, 235, 236, 237, 15, 231, 255, 254, 253, 252, 251
|
||||
]:
|
||||
possible_colors.remove(col)
|
||||
|
||||
|
||||
def rand_color():
|
||||
return '\x19%s}' % (random.choice(possible_colors), )
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.rainbowize)
|
||||
self.api.add_event_handler('private_say', self.rainbowize)
|
||||
self.api.add_event_handler('conversation_say', self.rainbowize)
|
||||
|
||||
def rainbowize(self, msg, tab):
|
||||
msg['body'] = ''.join([
|
||||
'%s%s' % (
|
||||
rand_color(),
|
||||
char,
|
||||
) for char in xhtml.clean_text(msg['body'])
|
||||
])
|
|
@ -1,41 +0,0 @@
|
|||
"""
|
||||
This plugin makes you have a random nick when joining a chatroom.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To have a random nick, just join a room with “RANDOM” as your nick. It will
|
||||
automatically be changed to something random, for example: ::
|
||||
|
||||
/join coucou@conference.example.com/RANDOM
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from random import choice
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('joining_muc', self.change_nick_to_random)
|
||||
self.api.add_event_handler('changing_nick', self.change_nick_to_random)
|
||||
|
||||
def change_nick_to_random(self, presence):
|
||||
to = presence["to"]
|
||||
if to.resource == 'RANDOM':
|
||||
to.resource = gen_nick(3)
|
||||
presence["to"] = to
|
||||
|
||||
|
||||
s = ["i", "ou", "ou", "on", "a", "o", "u", "i"]
|
||||
c = [
|
||||
"b", "c", "d", "f", "g", "h", "j", "k", "m", "l", "n", "p", "r", "s", "t",
|
||||
"v", "z"
|
||||
]
|
||||
|
||||
|
||||
def gen_nick(size):
|
||||
res = ''
|
||||
for _ in range(size):
|
||||
res += '%s%s' % (choice(c), choice(s))
|
||||
return res
|
|
@ -1,75 +0,0 @@
|
|||
"""
|
||||
This plugins adds a :term:`/rkick` and a :term:`/rban` command,
|
||||
in order to kick/ban according to a regex on a nick.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
Those commands take a regular expression (as defined in the
|
||||
`re module documentation`_) as a parameter.
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
/rkick
|
||||
**Usage:** ``/rkick <regex>``
|
||||
|
||||
Kick a participant using a regex.
|
||||
|
||||
|
||||
/rban
|
||||
**Usage:** ``/rban <regex>``
|
||||
|
||||
Ban a participant using a regex.
|
||||
|
||||
.. _re module documentation: http://docs.python.org/3/library/re.html
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.tabs import MucTab
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
'rkick',
|
||||
self.command_rkick,
|
||||
usage='<regex>',
|
||||
help='Kick occupants of a room according to a regex',
|
||||
short='Regex Kick')
|
||||
|
||||
self.api.add_tab_command(
|
||||
MucTab,
|
||||
'rban',
|
||||
self.command_rban,
|
||||
usage='<regex>',
|
||||
help='Ban occupants of a room according to a regex',
|
||||
short='Regex Ban')
|
||||
|
||||
def return_users(self, users, regex):
|
||||
try:
|
||||
reg = re.compile(regex)
|
||||
except:
|
||||
return []
|
||||
|
||||
ret = []
|
||||
for user in users:
|
||||
if reg.match(user.nick):
|
||||
ret.append(user)
|
||||
|
||||
return ret
|
||||
|
||||
def command_rban(self, regex):
|
||||
tab = self.api.current_tab()
|
||||
users = self.return_users(tab.users, regex)
|
||||
for user in users:
|
||||
tab.command_ban(user.nick)
|
||||
|
||||
def command_rkick(self, regex):
|
||||
tab = self.api.current_tab()
|
||||
users = self.return_users(tab.users, regex)
|
||||
for user in users:
|
||||
tab.command_kick(user.nick)
|
|
@ -1,168 +0,0 @@
|
|||
"""
|
||||
Usage
|
||||
-----
|
||||
|
||||
This plugin defines three new global commands: :term:`/remind`,
|
||||
:term:`/done`, and :term:`/tasks`.
|
||||
|
||||
.. glossary::
|
||||
|
||||
/remind
|
||||
**Usage:** ``/remind <time> <todo>``
|
||||
|
||||
This command will remind you to do ``todo`` every ``time``.
|
||||
|
||||
/done
|
||||
**Usage:** ``/done <id>``
|
||||
Remove a reminder.
|
||||
|
||||
The ``id`` is found using :term:`/tasks`.
|
||||
|
||||
|
||||
/tasks
|
||||
|
||||
Print a list of the tasks, their ids, and their frequency, into the
|
||||
information buffer.
|
||||
|
||||
Time format
|
||||
-----------
|
||||
|
||||
In seconds:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/remind 600 Work!
|
||||
|
||||
Will remind you to work every 10 minutes.
|
||||
|
||||
Defining the time in seconds is not really practical, so you can describe it
|
||||
with days, hours, and minutes, in a time-string, e.g:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
/remind 1h23m "Get up"
|
||||
|
||||
Will remind you to get up every 1 hour 23 minutes.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from poezio.core.structs import Completion
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import timed_events
|
||||
from poezio import common
|
||||
import curses
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'remind',
|
||||
self.command_remind,
|
||||
usage='<seconds> <todo>',
|
||||
help='Remind you of <todo> every <time> seconds.',
|
||||
short='Remind you of a task',
|
||||
completion=self.completion_remind)
|
||||
self.api.add_command(
|
||||
'done',
|
||||
self.command_done,
|
||||
usage='<id>',
|
||||
help='Stop reminding you do the task identified by <id>.',
|
||||
short='Remove a task',
|
||||
completion=self.completion_done)
|
||||
self.api.add_command(
|
||||
'tasks',
|
||||
self.command_tasks,
|
||||
usage='',
|
||||
help='List all the current tasks and their ids.',
|
||||
short='List current tasks')
|
||||
self.tasks = {}
|
||||
self.count = 0
|
||||
|
||||
for option in self.config.options(self.__module__):
|
||||
id, secs = option.split(',')
|
||||
id = int(id)
|
||||
if id > self.count:
|
||||
self.count = id
|
||||
value = self.config.get(option, '')
|
||||
self.tasks[id] = (int(secs), value)
|
||||
self.config.remove_section(self.__module__)
|
||||
self.config.add_section(self.__module__)
|
||||
if self.tasks:
|
||||
self.count += 1
|
||||
self.command_tasks('', nocommand=True)
|
||||
|
||||
def command_remind(self, arg):
|
||||
args = common.shell_split(arg)
|
||||
if len(args) < 2:
|
||||
return
|
||||
time = common.parse_str_to_secs(args[0])
|
||||
if not time:
|
||||
return
|
||||
|
||||
self.tasks[self.count] = (time, args[1])
|
||||
timed_event = timed_events.DelayedEvent(time, self.remind, self.count)
|
||||
self.api.add_timed_event(timed_event)
|
||||
self.api.information(
|
||||
'Task %s added: %s every %s.' % (self.count, args[1],
|
||||
common.parse_secs_to_str(time)),
|
||||
'Info')
|
||||
self.count += 1
|
||||
|
||||
def completion_remind(self, the_input):
|
||||
txt = the_input.get_text()
|
||||
args = common.shell_split(txt)
|
||||
n = len(args)
|
||||
if txt.endswith(' '):
|
||||
n += 1
|
||||
if n == 2:
|
||||
return Completion(the_input.auto_completion,
|
||||
["60", "5m", "15m", "30m", "1h", "10h", "1d"],
|
||||
'')
|
||||
|
||||
def completion_done(self, the_input):
|
||||
return Completion(the_input.auto_completion,
|
||||
["%s" % key for key in self.tasks], '')
|
||||
|
||||
def command_done(self, arg="0"):
|
||||
try:
|
||||
id_ = int(arg)
|
||||
except ValueError:
|
||||
return
|
||||
if id_ not in self.tasks:
|
||||
return
|
||||
|
||||
self.api.information('Task %s: %s [DONE]' % (id_, self.tasks[id_][1]),
|
||||
'Info')
|
||||
del self.tasks[id_]
|
||||
|
||||
def command_tasks(self, arg, nocommand=None):
|
||||
if nocommand:
|
||||
s = 'The following tasks were loaded:\n'
|
||||
else:
|
||||
s = 'The following tasks are active:\n'
|
||||
for key in self.tasks:
|
||||
s += 'Task %s: %s every %s.\n' % (key, repr(self.tasks[key][1]),
|
||||
common.parse_secs_to_str(
|
||||
self.tasks[key][0]))
|
||||
if s:
|
||||
self.api.information(s, 'Info')
|
||||
|
||||
def remind(self, id_=0):
|
||||
if id_ not in self.tasks:
|
||||
return
|
||||
self.api.information('Task %s: %s' % (id_, self.tasks[id_][1]), 'Info')
|
||||
if self.config.get('beep', '') == 'true':
|
||||
curses.beep()
|
||||
timed_event = timed_events.DelayedEvent(self.tasks[id_][0],
|
||||
self.remind, id_)
|
||||
self.api.add_timed_event(timed_event)
|
||||
|
||||
def cleanup(self):
|
||||
if self.tasks:
|
||||
self.config.remove_section(self.__module__)
|
||||
self.config.add_section(self.__module__)
|
||||
for task in self.tasks:
|
||||
self.config.set('%s,%s' % (task, self.tasks[task][0]),
|
||||
self.tasks[task][1])
|
||||
self.config.write()
|
|
@ -1,24 +0,0 @@
|
|||
"""
|
||||
Remove GET trackers from URLs in sent messages.
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
import re
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.information('This plugin is deprecated and will be replaced by \'untrackme\'.', 'Warning')
|
||||
|
||||
self.api.add_event_handler('muc_say', self.remove_get_trackers)
|
||||
self.api.add_event_handler('conversation_say', self.remove_get_trackers)
|
||||
self.api.add_event_handler('private_say', self.remove_get_trackers)
|
||||
|
||||
def remove_get_trackers(self, msg, tab):
|
||||
# fbclid: used globally (Facebook)
|
||||
# utm_*: used globally https://en.wikipedia.org/wiki/UTM_parameters
|
||||
# ncid: DoubleClick (Google)
|
||||
# ref_src, ref_url: twitter
|
||||
# Others exist but are excluded because they are not common.
|
||||
# See https://en.wikipedia.org/wiki/UTM_parameters
|
||||
msg['body'] = re.sub('(https?://[^ ]+)&?(fbclid|dclid|ncid|utm_source|utm_medium|utm_campaign|utm_term|utm_content|ref_src|ref_url)=[^ &#]*',
|
||||
r'\1',
|
||||
msg['body'])
|
|
@ -1,203 +0,0 @@
|
|||
"""
|
||||
``reorder`` plugin: Reorder the tabs according to a layout
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/reorder
|
||||
**Usage:** ``/reorder``
|
||||
|
||||
Reorder the tabs according to the configuration.
|
||||
|
||||
/save_order
|
||||
**Usage:** ``/save_order``
|
||||
|
||||
Save the current tab order to the configuration.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The configuration file must contain a section ``[reorder]`` and each option
|
||||
must be formatted like ``[tab number] = [tab type]:[tab name]``.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[reorder]
|
||||
1 = muc:toto@conference.example.com
|
||||
2 = muc:example@muc.example.im
|
||||
3 = dynamic:robert@example.org
|
||||
|
||||
The ``[tab number]`` must be at least ``1``; if the range is not entirely
|
||||
covered, e.g.:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[reorder]
|
||||
1 = muc:toto@conference.example.com
|
||||
3 = dynamic:robert@example.org
|
||||
|
||||
Poezio will insert gaps between the tabs in order to keep the specified
|
||||
numbering (so in this case, there will be a tab 1, a tab 3, but no tab 2).
|
||||
|
||||
|
||||
The ``[tab type]`` must be one of:
|
||||
|
||||
- ``muc`` (for multi-user chats)
|
||||
- ``private`` (for chats with a specific user inside a multi-user chat)
|
||||
- ``dynamic`` (for normal, dynamic conversations tabs)
|
||||
- ``static`` (for conversations with a specific resource)
|
||||
|
||||
And finally, the ``[tab name]`` must be:
|
||||
|
||||
- For a type ``muc``, the bare JID of the room
|
||||
- For a type ``private``, the full JID of the user (room JID with the username as a resource)
|
||||
- For a type ``dynamic``, the bare JID of the contact
|
||||
- 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
|
||||
from poezio.config import config
|
||||
|
||||
TEXT_TO_TAB = {
|
||||
'muc': tabs.MucTab,
|
||||
'private': tabs.PrivateTab,
|
||||
'dynamic': tabs.DynamicConversationTab,
|
||||
'static': tabs.StaticConversationTab,
|
||||
'empty': tabs.GapTab
|
||||
}
|
||||
|
||||
TAB_TO_TEXT = {
|
||||
tabs.MucTab: 'muc',
|
||||
tabs.DynamicConversationTab: 'dynamic',
|
||||
tabs.PrivateTab: 'private',
|
||||
tabs.StaticConversationTab: 'static',
|
||||
tabs.GapTab: 'empty'
|
||||
}
|
||||
|
||||
|
||||
def parse_config(tab_config):
|
||||
result = {}
|
||||
for option in tab_config.options('reorder'):
|
||||
if not option.isdecimal():
|
||||
continue
|
||||
pos = int(option)
|
||||
if pos in result or pos <= 0:
|
||||
return None
|
||||
|
||||
spec = tab_config.get(option, default=':').split(':', maxsplit=1)
|
||||
# Gap tabs are recreated automatically if there's a gap in indices.
|
||||
if spec == 'empty':
|
||||
return None
|
||||
typ, name = spec
|
||||
if typ not in TEXT_TO_TAB:
|
||||
return None
|
||||
result[pos] = (TEXT_TO_TAB[typ], name)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def check_tab(tab):
|
||||
for cls, rep in TAB_TO_TEXT.items():
|
||||
if isinstance(tab, cls):
|
||||
return rep
|
||||
return ''
|
||||
|
||||
|
||||
def parse_runtime_tablist(tablist):
|
||||
props = []
|
||||
i = 0
|
||||
for tab in tablist[1:]:
|
||||
i += 1
|
||||
result = check_tab(tab)
|
||||
# Don't serialize gap tabs as they're recreated automatically
|
||||
if result != 'empty' and isinstance(tab, TEXT_TO_TAB.values()):
|
||||
props.append((i, '%s:%s' % (result, tab.jid.full)))
|
||||
return props
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
"""reorder plugin"""
|
||||
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'reorder',
|
||||
self.command_reorder,
|
||||
help='Reorder all tabs using the pre-defined'
|
||||
' layout from the configuration file.')
|
||||
self.api.add_command(
|
||||
'save_order',
|
||||
self.command_save_order,
|
||||
help='Save the current tab layout')
|
||||
|
||||
@command_args_parser.ignored
|
||||
def command_save_order(self) -> None:
|
||||
"""
|
||||
/save_order
|
||||
"""
|
||||
conf = parse_runtime_tablist(self.core.tabs)
|
||||
for key, value in conf:
|
||||
self.config.set(key, value)
|
||||
self.api.information('Tab order saved', 'Info')
|
||||
|
||||
@command_args_parser.ignored
|
||||
def command_reorder(self) -> None:
|
||||
"""
|
||||
/reorder
|
||||
"""
|
||||
tabs_spec = parse_config(self.config)
|
||||
if not tabs_spec:
|
||||
self.api.information('Invalid reorder config', 'Error')
|
||||
return None
|
||||
|
||||
old_tabs = self.core.tabs.get_tabs()
|
||||
roster = old_tabs.pop(0)
|
||||
|
||||
create_gaps = config.get('create_gaps')
|
||||
|
||||
new_tabs = [roster]
|
||||
last = 0
|
||||
for pos in sorted(tabs_spec):
|
||||
if create_gaps and pos > last + 1:
|
||||
new_tabs += [
|
||||
tabs.GapTab() for i in range(pos - last - 1)
|
||||
]
|
||||
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:
|
||||
# TODO: Add support for MucTab. Requires nickname.
|
||||
if cls in (tabs.DynamicConversationTab, tabs.StaticConversationTab):
|
||||
self.api.information('Tab %s not found. Creating it' % jid, 'Warning')
|
||||
new_tab = cls(self.core, jid)
|
||||
new_tabs.append(new_tab)
|
||||
else:
|
||||
new_tabs.append(tabs.GapTab())
|
||||
except:
|
||||
self.api.information('Failed to create tab \'%s\'.' % jid, 'Error')
|
||||
if create_gaps:
|
||||
new_tabs.append(tabs.GapTab())
|
||||
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()
|
||||
|
||||
return None
|
|
@ -1,113 +0,0 @@
|
|||
"""
|
||||
Replace some patterns in a message before sending it.
|
||||
|
||||
Usage
|
||||
-----
|
||||
Insert a pattern in the form
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
%pattern%
|
||||
|
||||
in your message, and it will be replaced by the corresponding text.
|
||||
|
||||
The list of provided patterns is:
|
||||
|
||||
- **time**: Insert the current time
|
||||
- **date**: Insert the current date
|
||||
- **datetime**: Insert the current date and time
|
||||
- **random_nick**: Insert a random nick from the current MUC
|
||||
- **dice**: Insert a random number between 1 and 6
|
||||
|
||||
Add your own pattern
|
||||
--------------------
|
||||
|
||||
You can easily edit this plugin to add your own patterns. For example if
|
||||
don’t want to search for an insult every time you’re angry, you can create a
|
||||
curse pattern this way:
|
||||
|
||||
- In the init(self) method of the Plugin class, add something like
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.patterns['curse'] = replace_curse
|
||||
|
||||
- then define a function (not a method of the Plugin class) at the bottom
|
||||
of the file. For example:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def replace_curse(message, tab):
|
||||
return random.choice(['dumb shit', 'idiot', 'moron'])
|
||||
|
||||
and you can now use something like
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Shut up, %curse%!
|
||||
|
||||
in your everyday-conversations.
|
||||
|
||||
For more convenience, you can read your nice words from a file, do whatever
|
||||
you want in that function, as long as it returns a string.
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
from slixmpp.xmlstream.stanzabase import JID
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.patterns = {}
|
||||
self.api.add_event_handler('conversation_say', self.replace_pattern)
|
||||
self.api.add_event_handler('muc_say', self.replace_pattern)
|
||||
self.api.add_event_handler('private_say', self.replace_pattern)
|
||||
self.patterns['time'] = replace_time
|
||||
self.patterns['date'] = replace_date
|
||||
self.patterns['datetime'] = replace_datetime
|
||||
self.patterns['random_nick'] = replace_random_user
|
||||
self.patterns['dice'] = replace_dice
|
||||
|
||||
def replace_pattern(self, message, tab):
|
||||
"""
|
||||
Look for a %*% pattern in the message and replace it by the result
|
||||
of the corresponding function.
|
||||
"""
|
||||
body = message['body']
|
||||
for pattern in self.patterns:
|
||||
new = body
|
||||
body = re.sub('%%%s%%' % pattern,
|
||||
lambda x: self.patterns[pattern](message, tab), body)
|
||||
message['body'] = body
|
||||
|
||||
|
||||
def replace_time(message, tab):
|
||||
return datetime.datetime.now().strftime("%X")
|
||||
|
||||
|
||||
def replace_date(message, tab):
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def replace_datetime(message, tab):
|
||||
return datetime.datetime.now().strftime("%c")
|
||||
|
||||
|
||||
def replace_random_user(message, tab):
|
||||
if isinstance(tab, tabs.MucTab):
|
||||
return random.choice(tab.users).nick
|
||||
elif isinstance(tab, tabs.PrivateTab):
|
||||
return random.choice([tab.jid.resource, tab.own_nick])
|
||||
else:
|
||||
# that doesn’t make any sense. By why use this pattern in a
|
||||
# ConversationTab anyway?
|
||||
return tab.jid.full
|
||||
|
||||
|
||||
def replace_dice(message, tab):
|
||||
return str(random.randrange(1, 7))
|
|
@ -1,37 +0,0 @@
|
|||
"""
|
||||
Replace some word with some other word in a message before sending it.
|
||||
|
||||
Configuration example
|
||||
---------------------
|
||||
.. code-block:: ini
|
||||
|
||||
[replace_word]
|
||||
# How to appear casual in your daily conversations.
|
||||
yes = yep
|
||||
no = nope
|
||||
|
||||
Usage
|
||||
-----
|
||||
Just use the word in a message. It will be replaced automatically.
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
import re
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('conversation_say', self.replace_pattern)
|
||||
self.api.add_event_handler('muc_say', self.replace_pattern)
|
||||
self.api.add_event_handler('private_say', self.replace_pattern)
|
||||
|
||||
def replace_pattern(self, message, tab):
|
||||
"""
|
||||
Look for a given word in the message and replace it by the corresponding word.
|
||||
"""
|
||||
body = message['body']
|
||||
for before in self.config.options("replace_word"):
|
||||
after = self.config.get(before, before)
|
||||
body = re.sub(r"\b%s\b" % before, after, body)
|
||||
message['body'] = body
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
Reverse everything you say (``Je proteste énergiquement`` will become
|
||||
``tnemeuqigrené etsetorp eJ``)
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import xhtml
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.revstr)
|
||||
self.api.add_event_handler('conversation_say', self.revstr)
|
||||
self.api.add_event_handler('private_say', self.revstr)
|
||||
|
||||
def revstr(self, msg, tab):
|
||||
msg['body'] = xhtml.clean_text(msg['body'])[::-1]
|
|
@ -1,15 +0,0 @@
|
|||
"""
|
||||
Once loaded, every line of your messages will be stripped of their trailing spaces.
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.rstrip)
|
||||
self.api.add_event_handler('conversation_say', self.rstrip)
|
||||
self.api.add_event_handler('private_say', self.rstrip)
|
||||
|
||||
def rstrip(self, msg, tab):
|
||||
msg['body'] = '\n'.join(
|
||||
line.rstrip() for line in msg['body'].split('\n'))
|
|
@ -1,119 +0,0 @@
|
|||
"""
|
||||
This plugin will set your status to **away** if you detach your screen.
|
||||
|
||||
The default behaviour is to check for both tmux and screen (in that order).
|
||||
|
||||
Configuration options
|
||||
---------------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
use_screen
|
||||
**Default:** ``true``
|
||||
|
||||
Try to find an attached screen.
|
||||
|
||||
use_tmux
|
||||
**Default:** ``true``
|
||||
|
||||
Try to find and attached tmux.
|
||||
|
||||
use_csi
|
||||
**Default:** ``false``
|
||||
|
||||
Use `client state indication`_ to limit bandwidth (thus CPU) usage when detached. WARNING: using CSI together with chatrooms will result in inaccurate logs due to presence filtering or other inaccuracies.
|
||||
|
||||
.. _client state indication: https://xmpp.org/extensions/xep-0352.html
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
import os
|
||||
import stat
|
||||
import pyinotify
|
||||
import asyncio
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
'screen_detach': {
|
||||
'use_tmux': True,
|
||||
'use_screen': True,
|
||||
'use_csi': False
|
||||
}
|
||||
}
|
||||
|
||||
# overload if this is not how your stuff
|
||||
# is configured
|
||||
try:
|
||||
LOGIN = os.getlogin() or ''
|
||||
LOGIN_TMUX = os.getuid()
|
||||
except Exception:
|
||||
LOGIN = os.getenv('USER') or ''
|
||||
LOGIN_TMUX = os.getuid()
|
||||
|
||||
SCREEN_DIR = '/var/run/screens/S-%s' % LOGIN
|
||||
TMUX_DIR = '/tmp/tmux-%s' % LOGIN_TMUX
|
||||
|
||||
|
||||
def find_screen(path):
|
||||
if not os.path.isdir(path):
|
||||
return
|
||||
for f in os.listdir(path):
|
||||
path = os.path.join(path, f)
|
||||
if screen_attached(path):
|
||||
return path
|
||||
|
||||
|
||||
def screen_attached(socket):
|
||||
return (os.stat(socket).st_mode & stat.S_IXUSR) != 0
|
||||
|
||||
|
||||
class Plugin(BasePlugin, pyinotify.Notifier):
|
||||
|
||||
default_config = DEFAULT_CONFIG
|
||||
|
||||
def init(self):
|
||||
sock_path = None
|
||||
if self.config.get('use_tmux'):
|
||||
sock_path = find_screen(TMUX_DIR)
|
||||
if sock_path is None and self.config.get('use_screen'):
|
||||
sock_path = find_screen(SCREEN_DIR)
|
||||
|
||||
# Only actually do something if we found an attached screen (assuming only one)
|
||||
if sock_path:
|
||||
self.attached = True
|
||||
wm = pyinotify.WatchManager()
|
||||
wm.add_watch(sock_path,
|
||||
pyinotify.EventsCodes.ALL_FLAGS['IN_ATTRIB'])
|
||||
pyinotify.Notifier.__init__(
|
||||
self, wm, default_proc_fun=HandleScreen(plugin=self))
|
||||
asyncio.get_event_loop().add_reader(self._fd, self.process)
|
||||
else:
|
||||
self.api.information(
|
||||
'screen_detach plugin: No tmux or screen found', 'Warning')
|
||||
self.attached = False
|
||||
|
||||
def process(self):
|
||||
self.read_events()
|
||||
self.process_events()
|
||||
|
||||
def cleanup(self):
|
||||
asyncio.get_event_loop().remove_reader(self._fd)
|
||||
|
||||
def update_screen_state(self, socket):
|
||||
attached = screen_attached(socket)
|
||||
if attached != self.attached:
|
||||
self.attached = attached
|
||||
status = 'available' if self.attached else 'away'
|
||||
self.core.command.status(status)
|
||||
if self.config.get('use_csi'):
|
||||
if self.attached:
|
||||
self.core.xmpp.plugin['xep_0352'].send_active()
|
||||
else:
|
||||
self.core.xmpp.plugin['xep_0352'].send_inactive()
|
||||
|
||||
|
||||
class HandleScreen(pyinotify.ProcessEvent):
|
||||
def my_init(self, **kwargs):
|
||||
self.plugin = kwargs['plugin']
|
||||
|
||||
def process_IN_ATTRIB(self, event):
|
||||
self.plugin.update_screen_state(event.path)
|
|
@ -1,80 +0,0 @@
|
|||
"""
|
||||
Send a message after a certain delay.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
This plugin adds a command to the chat tabs.
|
||||
|
||||
.. glossary::
|
||||
|
||||
/send_delayed
|
||||
**Usage:** ``/send_delayed <delay> <message>``
|
||||
|
||||
Send a message after a given delay to the current tab.
|
||||
The delay can be either in seconds or in a classic XdXhXm format
|
||||
(e.g. ``7h3m`` or ``1d``), some examples are given with the
|
||||
autocompletion.
|
||||
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.core.structs import Completion
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio import tabs
|
||||
from poezio import common
|
||||
from poezio import timed_events
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for _class in (tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab, tabs.MucTab):
|
||||
self.api.add_tab_command(
|
||||
_class,
|
||||
'send_delayed',
|
||||
self.command_delayed,
|
||||
usage='<delay> <message>',
|
||||
help='Send <message> with a delay of <delay> seconds.',
|
||||
short='Send a message later',
|
||||
completion=self.completion_delay)
|
||||
|
||||
@command_args_parser.quoted(2)
|
||||
def command_delayed(self, args):
|
||||
if args is None:
|
||||
self.core.command.help('send_delayed')
|
||||
return
|
||||
delay_str, txt = args
|
||||
delay = common.parse_str_to_secs(delay_str)
|
||||
if not delay:
|
||||
self.api.information('Failed to parse %s.' % delay_str, 'Error')
|
||||
return
|
||||
|
||||
tab = self.api.current_tab()
|
||||
timed_event = timed_events.DelayedEvent(delay, self.say, (tab, txt))
|
||||
self.api.add_timed_event(timed_event)
|
||||
self.api.information(
|
||||
'Delayed message will be sent in %ds (%s).' % (delay, delay_str),
|
||||
'Info')
|
||||
|
||||
def completion_delay(self, the_input):
|
||||
txt = the_input.get_text()
|
||||
args = common.shell_split(txt)
|
||||
n = len(args)
|
||||
if txt.endswith(' '):
|
||||
n += 1
|
||||
if n == 2:
|
||||
return Completion(the_input.auto_completion,
|
||||
["60", "5m", "15m", "30m", "1h", "10h", "1d"],
|
||||
'')
|
||||
|
||||
def say(self, args=None):
|
||||
if not args:
|
||||
return
|
||||
|
||||
tab = args[0]
|
||||
# anything could happen to the tab during the interval
|
||||
try:
|
||||
asyncio.ensure_future(tab.command_say(args[1]))
|
||||
except:
|
||||
pass
|
|
@ -1,67 +0,0 @@
|
|||
"""
|
||||
This plugin adds a ``/server_part`` command to leave all rooms
|
||||
on a server.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/server_part
|
||||
**Usage:** ``/server_part [<server> [message]]``
|
||||
|
||||
Leave all rooms on ``<server>``, if not provided and the current
|
||||
tab is a chatroom tab, it will leave all rooms on the current server.
|
||||
``[message]`` can indicate a quit message.
|
||||
|
||||
|
||||
"""
|
||||
from slixmpp import JID, InvalidJID
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.tabs import MucTab
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio.core.structs import Completion
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'server_part',
|
||||
self.command_server_part,
|
||||
usage='[<server> [message]]',
|
||||
short='Leave all the rooms on a server',
|
||||
help='Leave all the rooms on a sever.',
|
||||
completion=self.completion_server_part)
|
||||
|
||||
@command_args_parser.quoted(0, 2, defaults=[])
|
||||
def command_server_part(self, args):
|
||||
current_tab = self.api.current_tab()
|
||||
if not args and not isinstance(current_tab, MucTab):
|
||||
return self.core.command_help('server_part')
|
||||
elif not args:
|
||||
jid = current_tab.jid.bare
|
||||
message = None
|
||||
elif len(args) == 1:
|
||||
try:
|
||||
jid = JID(args[0]).domain
|
||||
except InvalidJID:
|
||||
return self.core.command_help('server_part')
|
||||
message = None
|
||||
else:
|
||||
try:
|
||||
jid = JID(args[0]).domain
|
||||
except InvalidJID:
|
||||
return self.core.command_help('server_part')
|
||||
message = args[1]
|
||||
|
||||
for tab in self.core.get_tabs(MucTab):
|
||||
if tab.name.endswith(jid):
|
||||
tab.command_part(message)
|
||||
|
||||
def completion_server_part(self, the_input):
|
||||
serv_list = set()
|
||||
for tab in self.core.get_tabs(MucTab):
|
||||
if tab.joined:
|
||||
serv = tab.jid.server
|
||||
serv_list.add(serv)
|
||||
return Completion(the_input.new_completion, sorted(serv_list), 1, ' ')
|
|
@ -1,17 +0,0 @@
|
|||
"""
|
||||
Shuffle the words in every message you send in a :ref:`muctab`
|
||||
(may/should confuse the reader).
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from random import shuffle
|
||||
from poezio import xhtml
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.shuffle)
|
||||
|
||||
def shuffle(self, msg, tab):
|
||||
split = xhtml.clean_text(msg['body']).split()
|
||||
shuffle(split)
|
||||
msg['body'] = ' '.join(split)
|
|
@ -1,165 +0,0 @@
|
|||
"""
|
||||
This plugin lets you execute a command, to notify you from new important
|
||||
messages.
|
||||
|
||||
Installation and configuration
|
||||
------------------------------
|
||||
|
||||
You need to create a plugin configuration file. Create a file named :file:`simple_notify.cfg`
|
||||
into your plugins configuration directory (:file:`~/.config/poezio/plugins` by
|
||||
default), and fill it like this:
|
||||
|
||||
First example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[simple_notify]
|
||||
command = notify-send -i /path/to/poezio/data/poezio_80.png "New message from %(from)s" "%(body)s"
|
||||
|
||||
Second example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[simple_notify]
|
||||
command = echo \\<%(from)s\\> %(body)s >> some.fifo
|
||||
delay = 3
|
||||
after_command = echo >> some.fifo
|
||||
|
||||
You can put any command, instead of these ones. You can also use the
|
||||
special keywords ``%(from)s`` and ``%(body)s`` that will be replaced
|
||||
directly in the command line by the author of the message, and the body.
|
||||
|
||||
The first example shown above will display something like this:
|
||||
|
||||
.. figure:: ../images/simple_notify_example.png
|
||||
:alt: Simple notify example
|
||||
|
||||
The second example will first write the author and the message in a
|
||||
fifo, that fifo can locally be read by some other program (was tested
|
||||
with the xmobar PipeReader command, which displays what is read from a
|
||||
fifo into a status bar. Be careful, you have two different fifos in
|
||||
that case, don’t get confused). The :term:`delay` and :term:`after_command` options
|
||||
are used to erase/delete/kill the notification after a certain
|
||||
delay. In our example it is used to display an empty message in our
|
||||
xmobar, erasing the notification after 3 seconds.
|
||||
|
||||
Third example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[simple_notify]
|
||||
command = notify-send -i /path/to/poezio/data/poezio_80.png "New message from %(from)s" "%(body)s"
|
||||
muc_too = true
|
||||
muc_list = someroom@conference.jabber.org:someotherroom@conference.jabber.org
|
||||
|
||||
If present and set to ``True``, the ``muc_too`` option will also trigger a
|
||||
notification when a new message arrives on a Multi User Chat you've joined.
|
||||
|
||||
If present and set to a colon separated list of muc JIDs, muc_list together
|
||||
with muc_too = true will only notify when a new message arrives on a Multi
|
||||
User Chat, you've joined if it is present on the list.
|
||||
|
||||
.. note:: If you set the :term:`exec_remote` option to ``true`` into the
|
||||
main configuration file, the command will be executed remotely
|
||||
(as explained in the :ref:`link-plugin` plugin help).
|
||||
|
||||
Options defined
|
||||
---------------
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
command
|
||||
The command to execute (with special keywords ``%{from}s`` and ``${body}s``)
|
||||
|
||||
delay
|
||||
Delay after which :term:`after_command` must be executed.
|
||||
|
||||
after_command
|
||||
Command to run after :term:`delay`. You probably want to clean up things.
|
||||
|
||||
muc_too
|
||||
Boolean indicating whether new messages in Multi User Chat rooms should
|
||||
trigger a notification or not.
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.xhtml import get_body_from_message_stanza
|
||||
from poezio.timed_events import DelayedEvent
|
||||
import shlex
|
||||
from poezio import common
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('private_msg', self.on_private_msg)
|
||||
self.api.add_event_handler('conversation_msg',
|
||||
self.on_conversation_msg)
|
||||
if self.config.get('muc_too', False):
|
||||
self.api.add_event_handler('muc_msg', self.on_muc_msg)
|
||||
self.api.add_event_handler('highlight', self.on_highlight)
|
||||
|
||||
def on_private_msg(self, message, tab):
|
||||
fro = message['from']
|
||||
self.do_notify(message, fro)
|
||||
|
||||
def on_highlight(self, message, tab):
|
||||
whitelist = self.config.get('muc_list', '').split(':')
|
||||
# prevents double notifications
|
||||
if message['from'].bare in whitelist:
|
||||
return
|
||||
fro = message['from'].resource
|
||||
self.do_notify(message, fro)
|
||||
|
||||
def on_conversation_msg(self, message, tab):
|
||||
fro = message['from'].bare
|
||||
if fro.bare != self.core.xmpp.boundjid.bare:
|
||||
self.do_notify(message, fro)
|
||||
|
||||
def on_muc_msg(self, message, tab):
|
||||
# Don't notify if message is from yourself
|
||||
if message['from'].resource == tab.own_nick:
|
||||
return
|
||||
|
||||
fro = message['from'].full
|
||||
muc = message['from'].bare
|
||||
whitelist = self.config.get('muc_list', '').split(':')
|
||||
|
||||
# Prevent old messages to be notified
|
||||
# find_delayed_tag(message) returns (True, the datetime) or
|
||||
# (False, None)
|
||||
if not common.find_delayed_tag(message)[0]:
|
||||
# Only notify if whitelist is empty or muc in whitelist
|
||||
if whitelist == [''] or muc in whitelist:
|
||||
self.do_notify(message, fro)
|
||||
|
||||
def do_notify(self, message, fro):
|
||||
body = get_body_from_message_stanza(message, use_xhtml=False)
|
||||
if not body:
|
||||
return
|
||||
command_str = self.config.get('command', '').strip()
|
||||
if not command_str:
|
||||
self.api.information(
|
||||
'No notification command was provided in the configuration file',
|
||||
'Warning')
|
||||
return
|
||||
command = [
|
||||
arg % {
|
||||
'body': body.replace('\n', ' '),
|
||||
'from': fro
|
||||
} for arg in shlex.split(command_str)
|
||||
]
|
||||
self.core.exec_command(command)
|
||||
after_command_str = self.config.get('after_command', '').strip()
|
||||
if not after_command_str:
|
||||
return
|
||||
after_command = [
|
||||
arg % {
|
||||
'body': body.replace('\n', ' '),
|
||||
'from': fro
|
||||
} for arg in shlex.split(after_command_str)
|
||||
]
|
||||
delayed_event = DelayedEvent(
|
||||
self.config.get('delay', 1), self.core.exec_command, after_command)
|
||||
self.api.add_timed_event(delayed_event)
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
Insert a space between each character, in messages that you send, making
|
||||
them horrible to read.
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import xhtml
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.add_spaces)
|
||||
self.api.add_event_handler('conversation_say', self.add_spaces)
|
||||
self.api.add_event_handler('private_say', self.add_spaces)
|
||||
|
||||
def add_spaces(self, msg, tab):
|
||||
msg['body'] = " ".join(x for x in xhtml.clean_text(msg['body']))
|
|
@ -1,25 +0,0 @@
|
|||
"""
|
||||
Add a subtle little advertising in your messages.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[spam]
|
||||
ad = I’m a happy poezio user. Get it at http://poezio.eu
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.advert)
|
||||
self.api.add_event_handler('conversation_say', self.advert)
|
||||
self.api.add_event_handler('private_say', self.advert)
|
||||
|
||||
def advert(self, msg, tab):
|
||||
msg['body'] = "%s\n\n%s" % (msg['body'],
|
||||
self.config.get("ad", "Sent from poezio"))
|
|
@ -1,43 +0,0 @@
|
|||
"""
|
||||
This plugin adds several aliases, to shorten status changes.
|
||||
|
||||
Aliases
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
/afk
|
||||
/away
|
||||
Set your status to ``away``
|
||||
|
||||
/dnd
|
||||
/busy
|
||||
Set your status to ``dnd``
|
||||
|
||||
/available
|
||||
Set your status to ``available``
|
||||
|
||||
/chat
|
||||
Set your status to ``chat``
|
||||
|
||||
/xa
|
||||
Set your status to ``xa``
|
||||
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
"""
|
||||
Adds several convenient aliases to /status command
|
||||
"""
|
||||
|
||||
def init(self):
|
||||
for st in ('dnd', 'busy', 'afk', 'chat', 'xa', 'away', 'available'):
|
||||
self.api.add_command(
|
||||
st,
|
||||
lambda line, st=st: self.api.run_command('/status ' + st + ' "' + line + '"'),
|
||||
usage='[status message]',
|
||||
short='Set your status as %s' % st,
|
||||
help='Set your status as %s' % st)
|
|
@ -1,97 +0,0 @@
|
|||
'''
|
||||
This plugin lets the user select and send a sticker from a pack of stickers.
|
||||
|
||||
The protocol used here is based on XEP-0363 and XEP-0066, while a future
|
||||
version may use XEP-0449 instead.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
/sticker
|
||||
**Usage:** ``/sticker <pack>``
|
||||
|
||||
Opens a picker tool, and send the sticker which has been selected.
|
||||
|
||||
Configuration options
|
||||
---------------------
|
||||
|
||||
.. glossary::
|
||||
sticker_picker
|
||||
**Default:** ``poezio-sticker-picker``
|
||||
|
||||
The command to invoke as a sticker picker. A sample one is provided in
|
||||
tools/sticker-picker.
|
||||
|
||||
stickers_dir
|
||||
**Default:** ``XDG_DATA_HOME/poezio/stickers``
|
||||
|
||||
The directory under which the sticker packs can be found.
|
||||
'''
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
from poezio import xdg
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.config import config
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio.core.structs import Completion
|
||||
from pathlib import Path
|
||||
from asyncio.subprocess import PIPE, DEVNULL
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
dependencies = {'upload'}
|
||||
|
||||
def init(self):
|
||||
# The command to use as a picker helper.
|
||||
self.picker_command = config.getstr('sticker_picker') or 'poezio-sticker-picker'
|
||||
|
||||
# Select and create the stickers directory.
|
||||
directory = config.getstr('stickers_dir')
|
||||
if directory:
|
||||
self.directory = Path(directory).expanduser()
|
||||
else:
|
||||
self.directory = xdg.DATA_HOME / 'stickers'
|
||||
self.directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.upload = self.refs['upload']
|
||||
self.api.add_command('sticker', self.command_sticker,
|
||||
usage='<sticker pack>',
|
||||
short='Send a sticker',
|
||||
help='Send a sticker, with a helper GUI sticker picker',
|
||||
completion=self.completion_sticker)
|
||||
|
||||
def command_sticker(self, pack):
|
||||
'''
|
||||
Sends a sticker
|
||||
'''
|
||||
if not pack:
|
||||
self.api.information('Missing sticker pack argument.', 'Error')
|
||||
return
|
||||
async def run_command(tab, path: Path):
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
self.picker_command, path, stdout=PIPE, stderr=PIPE)
|
||||
sticker, stderr = await process.communicate()
|
||||
except FileNotFoundError as err:
|
||||
self.api.information('Failed to launch the sticker picker: %s' % err, 'Error')
|
||||
return
|
||||
else:
|
||||
if process.returncode != 0:
|
||||
self.api.information('Sticker picker failed: %s' % stderr.decode(), 'Error')
|
||||
return
|
||||
if sticker:
|
||||
filename = sticker.decode().rstrip()
|
||||
self.api.information('Sending sticker %s' % filename, 'Info')
|
||||
await self.upload.send_upload(path / filename, tab)
|
||||
tab = self.api.current_tab()
|
||||
path = self.directory / pack
|
||||
asyncio.create_task(run_command(tab, path))
|
||||
|
||||
def completion_sticker(self, the_input):
|
||||
'''
|
||||
Completion for /sticker
|
||||
'''
|
||||
txt = the_input.get_text()[9:]
|
||||
directories = [directory.name for directory in self.directory.glob(txt + '*')]
|
||||
return Completion(the_input.auto_completion, directories, quotify=False)
|
|
@ -1,60 +0,0 @@
|
|||
"""
|
||||
Repeats the last word of the last message in the conversation, and use it in
|
||||
an annoying “C’est toi le” sentence.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
You only have to load the plugin:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/load stoi
|
||||
|
||||
.. glossary::
|
||||
|
||||
/stoi
|
||||
**Usage:** ``/stoi``
|
||||
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
import string
|
||||
from poezio import xhtml
|
||||
import random
|
||||
|
||||
char_we_dont_want = string.punctuation + ' ’„“”…«»'
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
for tab_type in (tabs.MucTab, tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab):
|
||||
self.api.add_tab_command(
|
||||
tab_type,
|
||||
'stoi',
|
||||
handler=self.stoi,
|
||||
help="Repeats the last word of the last "
|
||||
"message in the conversation, and "
|
||||
"use it in an annoying “C’est toi "
|
||||
"le” sentence.",
|
||||
short='C’est toi le stoi.')
|
||||
|
||||
def stoi(self, args):
|
||||
messages = self.api.get_conversation_messages()
|
||||
if not messages:
|
||||
# Do nothing if the conversation doesn’t contain any message
|
||||
return
|
||||
last_message = messages[-1]
|
||||
txt = xhtml.clean_text(last_message.txt)
|
||||
for char in char_we_dont_want:
|
||||
txt = txt.replace(char, ' ')
|
||||
if txt.strip():
|
||||
last_word = txt.split()[-1]
|
||||
else:
|
||||
last_word = "vide"
|
||||
intro = "C'est toi " if random.getrandbits(1) else "Stoi "
|
||||
if last_word[0] in 'aeiouAEIOUÀàÉéÈè':
|
||||
msg = intro + ('l’%s' % last_word)
|
||||
else:
|
||||
msg = intro + ('le %s' % last_word)
|
||||
self.api.send_message(msg)
|
|
@ -1,117 +0,0 @@
|
|||
"""
|
||||
The command added by this plugin sends a message to someone when he next joins.
|
||||
|
||||
Usage
|
||||
-----
|
||||
This plugin defines two new commands for chatroom tabs:
|
||||
:term:`/tell`, :term:`/untell`, and :term:`/list_tell`.
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
/tell
|
||||
**Usage:** ``/tell <nick> <message>``
|
||||
|
||||
Send *message* to *nick* at his next join.
|
||||
|
||||
/untell
|
||||
**Usage:** ``/untell <nick>``
|
||||
|
||||
Cancel all scheduled messages to *nick*.
|
||||
|
||||
/list_tell
|
||||
**Usage:** ``/list_tell``
|
||||
|
||||
List all queued messages for the current chatroom.
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.core.structs import Completion
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio import tabs
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_tab_command(
|
||||
tabs.MucTab,
|
||||
'tell',
|
||||
self.command_tell,
|
||||
usage='<nick> <message>',
|
||||
help='Will tell <nick> of <message> when he next joins.',
|
||||
short='Send a message when someone joins')
|
||||
self.api.add_tab_command(
|
||||
tabs.MucTab,
|
||||
'untell',
|
||||
self.command_untell,
|
||||
usage='<nick>',
|
||||
help='Remove the planned messages from /tell.',
|
||||
short='Cancel a /tell message',
|
||||
completion=self.completion_untell)
|
||||
self.api.add_tab_command(
|
||||
tabs.MucTab,
|
||||
'list_tell',
|
||||
self.command_list_tell,
|
||||
usage='',
|
||||
help='List currently queued messages')
|
||||
self.api.add_event_handler('muc_join', self.on_join)
|
||||
self.api.add_event_handler('muc_nickchange', self.on_join)
|
||||
# {tab -> {nick -> [messages]}
|
||||
self.tabs = {}
|
||||
|
||||
def on_join(self, presence, tab):
|
||||
if tab not in self.tabs:
|
||||
return
|
||||
nick = presence['from'].resource
|
||||
if nick not in self.tabs[tab]:
|
||||
return
|
||||
for i in self.tabs[tab][nick]:
|
||||
asyncio.ensure_future(tab.command_say("%s: %s" % (nick, i)))
|
||||
del self.tabs[tab][nick]
|
||||
|
||||
@command_args_parser.ignored
|
||||
def command_list_tell(self):
|
||||
tab = self.api.current_tab()
|
||||
if not self.tabs.get(tab):
|
||||
self.api.information('No message queued.', 'Info')
|
||||
return
|
||||
build = ['Messages queued for %s:' % tab.jid.bare]
|
||||
for nick, messages in self.tabs[tab].items():
|
||||
build.append(' for %s:' % nick)
|
||||
for message in messages:
|
||||
build.append(' - %s' % message)
|
||||
self.api.information('\n'.join(build), 'Info')
|
||||
|
||||
@command_args_parser.quoted(2)
|
||||
def command_tell(self, args):
|
||||
"""/tell <nick> <message>"""
|
||||
if args is None:
|
||||
self.core.command.help('tell')
|
||||
return
|
||||
nick, msg = args
|
||||
tab = self.api.current_tab()
|
||||
if tab not in self.tabs:
|
||||
self.tabs[tab] = {}
|
||||
if nick not in self.tabs[tab]:
|
||||
self.tabs[tab][nick] = []
|
||||
self.tabs[tab][nick].append(msg)
|
||||
self.api.information('Message for %s queued' % nick, 'Info')
|
||||
|
||||
def command_untell(self, args):
|
||||
"""/untell <nick>"""
|
||||
tab = self.api.current_tab()
|
||||
if tab not in self.tabs:
|
||||
return
|
||||
nick = args
|
||||
if nick not in self.tabs[tab]:
|
||||
return
|
||||
del self.tabs[tab][nick]
|
||||
self.api.information('Messages for %s unqueued' % nick, 'Info')
|
||||
|
||||
def completion_untell(self, the_input):
|
||||
tab = self.api.current_tab()
|
||||
if tab not in self.tabs:
|
||||
return Completion(the_input.auto_completion, [], '')
|
||||
return Completion(
|
||||
the_input.auto_completion, list(self.tabs[tab]), '', quotify=False)
|
|
@ -1,26 +0,0 @@
|
|||
from poezio.plugin import BasePlugin
|
||||
from poezio import tabs
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command('plugintest', self.command_plugintest,
|
||||
'Test command')
|
||||
self.api.add_tab_command(tabs.MucTab, 'plugintest',
|
||||
self.command_tab_plugintest, 'Test command')
|
||||
self.api.add_slix_event_handler('message', self.on_message)
|
||||
self.api.information("Plugin loaded")
|
||||
|
||||
def cleanup(self):
|
||||
self.api.information("Plugin unloaded")
|
||||
|
||||
def on_message(self, message):
|
||||
self.api.information(
|
||||
"Test plugin received message: {}".format(message))
|
||||
|
||||
def command_tab_plugintest(self, args):
|
||||
self.api.information("Command for MucTabs! With args {}".format(args))
|
||||
self.api.del_tab_command(tabs.MucTab, 'plugintest')
|
||||
|
||||
def command_plugintest(self, args):
|
||||
self.api.information("Command! With args {}".format(args))
|
|
@ -1,77 +0,0 @@
|
|||
"""
|
||||
Display the time between two messages.
|
||||
|
||||
Helps you identify the times of a conversation. For example
|
||||
if you disable the timestamps, and remove the join/quit notifications in a
|
||||
chatroom, you can’t really distinguish when a conversation stopped and when
|
||||
a new one started, because you don’t have a visual separation between the two.
|
||||
|
||||
This plugin displays a message in the conversation indicating the time that
|
||||
passed between two messages, if the time is bigger than X minutes
|
||||
(configurable, of course. Default is 15 minutes). This way you know how many time elapsed between
|
||||
them, letting you understand more easily what is going on without any visual
|
||||
clutter.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
You can configure the minimum delay between two messages, to display the time marker, in seconds. The default is 10 minutes (aka 600 seconds).
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[time_marker]
|
||||
delay = 600
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Messages like “2 hours, 25 minutes passed…” are automatically displayed into the converstation. You don’t need to (and can’t) do anything.
|
||||
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from datetime import datetime, timedelta
|
||||
from poezio.ui.types import InfoMessage
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler("muc_msg", self.on_muc_msg)
|
||||
# Dict of MucTab.jid.bare: last_message date, so we don’t have to
|
||||
# retrieve the messages of the given muc to look for the last
|
||||
# message’s date each time. Also, now that I think about it, the
|
||||
# date of the message is not event kept in the Message object, so…
|
||||
self.last_messages = {}
|
||||
|
||||
def on_muc_msg(self, message, tab):
|
||||
def format_timedelta(delta):
|
||||
"""
|
||||
Return a string of the form D days, H hours, M minutes, S
|
||||
seconds. If the number of total minutes is bigger than 10, we
|
||||
usually don’t care anymore about the number of seconds, so we
|
||||
don’t display it. Same thing if the number of days is bigger
|
||||
than one, we don’t display the minutes either.
|
||||
"""
|
||||
days = delta.days
|
||||
hours = delta.seconds // 3600
|
||||
minutes = delta.seconds // 60 % 60
|
||||
seconds = delta.seconds % 60
|
||||
res = ''
|
||||
if days > 0:
|
||||
res = "%s days, " % days
|
||||
if hours > 0:
|
||||
res += "%s hours, " % hours
|
||||
if days == 0 and minutes != 0:
|
||||
res += "%s minutes, " % minutes
|
||||
if delta.total_seconds() < 600:
|
||||
res += "%s seconds, " % seconds
|
||||
return res[:-2]
|
||||
|
||||
last_message_date = self.last_messages.get(tab.jid.bare)
|
||||
self.last_messages[tab.jid.bare] = datetime.now()
|
||||
if last_message_date:
|
||||
delta = datetime.now() - last_message_date
|
||||
if delta >= timedelta(0, self.config.get('delay', 900)):
|
||||
tab.add_message(
|
||||
InfoMessage("%s passed…" % (format_timedelta(delta), ))
|
||||
)
|
|
@ -1,140 +0,0 @@
|
|||
"""
|
||||
UntrackMe wannabe plugin
|
||||
"""
|
||||
|
||||
from typing import Callable, Dict, List, Tuple, Union
|
||||
|
||||
import re
|
||||
import logging
|
||||
from slixmpp import Message
|
||||
from poezio import tabs
|
||||
from poezio.plugin import BasePlugin
|
||||
from urllib.parse import quote as urlquote
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
ChatTabs = Union[
|
||||
tabs.MucTab,
|
||||
tabs.DynamicConversationTab,
|
||||
tabs.StaticConversationTab,
|
||||
tabs.PrivateTab,
|
||||
]
|
||||
|
||||
RE_URL: re.Pattern = re.compile('https?://(?P<host>[^/]+)(?P<rest>[^ ]*)')
|
||||
|
||||
SERVICES: Dict[str, Tuple[str, bool]] = { # host: (service, proxy)
|
||||
'm.youtube.com': ('invidious', False),
|
||||
'www.youtube.com': ('invidious', False),
|
||||
'youtube.com': ('invidious', False),
|
||||
'youtu.be': ('invidious', False),
|
||||
'youtube-nocookie.com': ('invidious', False),
|
||||
'mobile.twitter.com': ('nitter', False),
|
||||
'www.twitter.com': ('nitter', False),
|
||||
'twitter.com': ('nitter', False),
|
||||
'pic.twitter.com': ('nitter_img', True),
|
||||
'pbs.twimg.com': ('nitter_img', True),
|
||||
'instagram.com': ('bibliogram', False),
|
||||
'www.instagram.com': ('bibliogram', False),
|
||||
'm.instagram.com': ('bibliogram', False),
|
||||
}
|
||||
|
||||
def proxy(service: str) -> Callable[[str], str]:
|
||||
"""Some services require the original url"""
|
||||
def inner(origin: str) -> str:
|
||||
return service + urlquote(origin)
|
||||
return inner
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
"""UntrackMe"""
|
||||
|
||||
default_config: Dict[str, Dict[str, Union[str, bool]]] = {
|
||||
'default': {
|
||||
'cleanup': True,
|
||||
'redirect': True,
|
||||
'display_corrections': False,
|
||||
},
|
||||
'services': {
|
||||
'invidious': 'https://invidious.snopyta.org',
|
||||
'nitter': 'https://nitter.net',
|
||||
'bibliogram': 'https://bibliogram.art',
|
||||
},
|
||||
}
|
||||
|
||||
def init(self):
|
||||
nitter_img = self.config.get('nitter', section='services') + '/pic/'
|
||||
self.config.set('nitter_img', nitter_img, section='services')
|
||||
|
||||
self.api.add_event_handler('muc_say', self.handle_msg)
|
||||
self.api.add_event_handler('conversation_say', self.handle_msg)
|
||||
self.api.add_event_handler('private_say', self.handle_msg)
|
||||
|
||||
self.api.add_event_handler('muc_msg', self.handle_msg)
|
||||
self.api.add_event_handler('conversation_msg', self.handle_msg)
|
||||
self.api.add_event_handler('private_msg', self.handle_msg)
|
||||
|
||||
def map_services(self, match: re.Match) -> str:
|
||||
"""
|
||||
If it matches a host that we know about, change the domain for the
|
||||
alternative service. Some hosts needs to be proxied instead (such
|
||||
as twitter pictures), so they're url encoded and appended to the
|
||||
proxy service.
|
||||
"""
|
||||
|
||||
host = match.group('host')
|
||||
|
||||
dest = SERVICES.get(host)
|
||||
if dest is None:
|
||||
return match.group(0)
|
||||
|
||||
destname, proxy = dest
|
||||
replaced = self.config.get(destname, section='services')
|
||||
result = replaced + match.group('rest')
|
||||
|
||||
if proxy:
|
||||
url = urlquote(match.group(0))
|
||||
result = replaced + url
|
||||
|
||||
# TODO: count parenthesis?
|
||||
# Removes comma at the end of a link.
|
||||
if result[-3] == '%2C':
|
||||
result = result[:-3] + ','
|
||||
|
||||
return result
|
||||
|
||||
def handle_msg(self, msg: Message, tab: ChatTabs) -> None:
|
||||
orig = msg['body']
|
||||
|
||||
if self.config.get('cleanup', section='default'):
|
||||
msg['body'] = self.cleanup_url(msg['body'])
|
||||
if self.config.get('redirect', section='default'):
|
||||
msg['body'] = self.redirect_url(msg['body'])
|
||||
|
||||
if self.config.get('display_corrections', section='default') and \
|
||||
msg['body'] != orig:
|
||||
log.debug(
|
||||
'UntrackMe in tab \'%s\':\nOriginal: %s\nModified: %s',
|
||||
tab.name, orig, msg['body'],
|
||||
)
|
||||
|
||||
self.api.information(
|
||||
'UntrackMe in tab \'{}\':\nOriginal: {}\nModified: {}'.format(
|
||||
tab.name, orig, msg['body']
|
||||
),
|
||||
'Info',
|
||||
)
|
||||
|
||||
def cleanup_url(self, txt: str) -> str:
|
||||
# fbclid: used globally (Facebook)
|
||||
# utm_*: used globally https://en.wikipedia.org/wiki/UTM_parameters
|
||||
# ncid: DoubleClick (Google)
|
||||
# ref_src, ref_url: twitter
|
||||
# Others exist but are excluded because they are not common.
|
||||
# See https://en.wikipedia.org/wiki/UTM_parameters
|
||||
return re.sub('(https?://[^ ]+)&?(fbclid|dclid|ncid|utm_source|utm_medium|utm_campaign|utm_term|utm_content|ref_src|ref_url)=[^ &#]*',
|
||||
r'\1',
|
||||
txt)
|
||||
|
||||
def redirect_url(self, txt: str) -> str:
|
||||
return RE_URL.sub(self.map_services, txt)
|
|
@ -1,98 +0,0 @@
|
|||
"""
|
||||
Upload a file and auto-complete the input with its URL.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
This plugin adds a command to the chat tabs.
|
||||
|
||||
.. glossary::
|
||||
|
||||
/upload
|
||||
**Usage:** ``/upload <filename>``
|
||||
|
||||
Uploads the <filename> file to the preferred HTTP File Upload
|
||||
service (see XEP-0363) and fill the input with its URL.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import asyncio
|
||||
import traceback
|
||||
from os.path import expanduser
|
||||
from glob import glob
|
||||
|
||||
from slixmpp.plugins.xep_0363.http_upload import FileTooBig, HTTPError, UploadServiceNotFound
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.core.structs import Completion
|
||||
from poezio.decorators import command_args_parser
|
||||
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')
|
||||
if not self.core.xmpp['xep_0454']:
|
||||
self.api.information(
|
||||
'slixmpp XEP-0454 plugin failed to load. '
|
||||
'Will not be able to encrypt uploaded files.',
|
||||
'Warning',
|
||||
)
|
||||
for _class in (tabs.PrivateTab, tabs.StaticConversationTab, tabs.DynamicConversationTab, tabs.MucTab):
|
||||
self.api.add_tab_command(
|
||||
_class,
|
||||
'upload',
|
||||
self.command_upload,
|
||||
usage='<filename>',
|
||||
help='Upload a file and auto-complete the input with its URL.',
|
||||
short='Upload a file',
|
||||
completion=self.completion_filename)
|
||||
|
||||
async def upload(self, filename, encrypted=False) -> Optional[str]:
|
||||
try:
|
||||
upload_file = self.core.xmpp['xep_0363'].upload_file
|
||||
if encrypted:
|
||||
upload_file = self.core.xmpp['xep_0454'].upload_file
|
||||
url = await upload_file(filename)
|
||||
except UploadServiceNotFound:
|
||||
self.api.information('HTTP Upload service not found.', 'Error')
|
||||
return None
|
||||
except (FileTooBig, HTTPError) as exn:
|
||||
self.api.information(str(exn), 'Error')
|
||||
return None
|
||||
except Exception:
|
||||
exception = traceback.format_exc()
|
||||
self.api.information('Failed to upload file: %s' % exception,
|
||||
'Error')
|
||||
return None
|
||||
return url
|
||||
|
||||
async def send_upload(self, filename, tab, encrypted=False):
|
||||
url = await self.upload(filename, encrypted)
|
||||
if url is not None:
|
||||
self.embed.embed_image_url(url, tab)
|
||||
|
||||
@command_args_parser.quoted(1)
|
||||
def command_upload(self, args):
|
||||
if args is None:
|
||||
self.core.command.help('upload')
|
||||
return
|
||||
filename, = args
|
||||
filename = expanduser(filename)
|
||||
tab = self.api.current_tab()
|
||||
encrypted = self.core.xmpp['xep_0454'] and tab.e2e_encryption is not None
|
||||
asyncio.create_task(self.send_upload(filename, tab, encrypted))
|
||||
|
||||
@staticmethod
|
||||
def completion_filename(the_input):
|
||||
txt = expanduser(the_input.get_text()[8:])
|
||||
files = glob(txt + '*')
|
||||
return Completion(the_input.auto_completion, files, quotify=False)
|
|
@ -1,49 +0,0 @@
|
|||
"""
|
||||
This plugin retrieves the uptime of a server.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/uptime
|
||||
**Usage:** ``/uptime <jid>``
|
||||
|
||||
Retrieve the uptime of the server of ``jid``.
|
||||
"""
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.common import parse_secs_to_str
|
||||
from slixmpp.xmlstream import ET
|
||||
from slixmpp import JID, InvalidJID
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'uptime',
|
||||
self.command_uptime,
|
||||
usage='<jid>',
|
||||
help='Ask for the uptime of a server or component (see XEP-0012).',
|
||||
short='Get the uptime')
|
||||
|
||||
async def command_uptime(self, arg):
|
||||
try:
|
||||
jid = JID(arg)
|
||||
except InvalidJID:
|
||||
return
|
||||
iq = self.core.xmpp.make_iq_get(ito=jid.server)
|
||||
iq.append(ET.Element('{jabber:iq:last}query'))
|
||||
try:
|
||||
iq = await iq.send()
|
||||
result = iq.xml.find('{jabber:iq:last}query')
|
||||
if result is not None:
|
||||
self.api.information(
|
||||
'Server %s online since %s' %
|
||||
(iq['from'], parse_secs_to_str(
|
||||
int(result.attrib['seconds']))), 'Info')
|
||||
return
|
||||
except (IqError, IqTimeout):
|
||||
pass
|
||||
self.api.information('Could not retrieve uptime', 'Error')
|
||||
|
|
@ -1,634 +0,0 @@
|
|||
"""
|
||||
This plugin enables rich presence events, such as mood, activity, gaming or tune.
|
||||
|
||||
.. versionadded:: 0.14
|
||||
This plugin was previously provided in the poezio core features.
|
||||
|
||||
Command
|
||||
-------
|
||||
.. glossary::
|
||||
|
||||
/activity
|
||||
**Usage:** ``/activity [<general> [specific] [comment]]``
|
||||
|
||||
Send your current activity to your contacts (use the completion to cycle
|
||||
through all the general and specific possible activities).
|
||||
|
||||
Nothing means "stop broadcasting an activity".
|
||||
|
||||
/mood
|
||||
**Usage:** ``/mood [<mood> [comment]]``
|
||||
Send your current mood to your contacts (use the completion to cycle
|
||||
through all the possible moods).
|
||||
|
||||
Nothing means "stop broadcasting a mood".
|
||||
|
||||
/gaming
|
||||
**Usage:** ``/gaming [<game name> [server address]]``
|
||||
|
||||
Send your current gaming activity to your contacts.
|
||||
|
||||
Nothing means "stop broadcasting a gaming activity".
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
display_gaming_notifications
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
If set to true, notifications about the games your contacts are playing
|
||||
will be displayed in the info buffer as 'Gaming' messages.
|
||||
|
||||
display_tune_notifications
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
If set to true, notifications about the music your contacts listen to
|
||||
will be displayed in the info buffer as 'Tune' messages.
|
||||
|
||||
display_mood_notifications
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
If set to true, notifications about the mood of your contacts
|
||||
will be displayed in the info buffer as 'Mood' messages.
|
||||
|
||||
display_activity_notifications
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
If set to true, notifications about the current activity of your contacts
|
||||
will be displayed in the info buffer as 'Activity' messages.
|
||||
|
||||
enable_user_activity
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
Set this to ``false`` if you don’t want to receive the activity of your contacts.
|
||||
|
||||
enable_user_gaming
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
Set this to ``false`` if you don’t want to receive the gaming activity of your contacts.
|
||||
|
||||
enable_user_mood
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
Set this to ``false`` if you don’t want to receive the mood of your contacts.
|
||||
|
||||
enable_user_tune
|
||||
|
||||
**Default value:** ``true``
|
||||
|
||||
If this is set to ``false``, you will no longer be subscribed to tune events,
|
||||
and the :term:`display_tune_notifications` option will be ignored.
|
||||
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
from functools import reduce
|
||||
from typing import Dict
|
||||
|
||||
from slixmpp import InvalidJID, JID, Message
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.roster import roster
|
||||
from poezio.contact import Contact, Resource
|
||||
from poezio.core.structs import Completion
|
||||
from poezio import common
|
||||
from poezio import tabs
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
default_config = {
|
||||
'user_extras': {
|
||||
'display_gaming_notifications': True,
|
||||
'display_mood_notifications': True,
|
||||
'display_activity_notifications': True,
|
||||
'display_tune_notifications': True,
|
||||
'enable_user_activity': True,
|
||||
'enable_user_gaming': True,
|
||||
'enable_user_mood': True,
|
||||
'enable_user_tune': True,
|
||||
}
|
||||
}
|
||||
|
||||
def init(self):
|
||||
for plugin in {'xep_0196', 'xep_0108', 'xep_0107', 'xep_0118'}:
|
||||
self.core.xmpp.register_plugin(plugin)
|
||||
self.api.add_command(
|
||||
'activity',
|
||||
self.command_activity,
|
||||
usage='[<general> [specific] [text]]',
|
||||
help='Send your current activity to your contacts '
|
||||
'(use the completion). Nothing means '
|
||||
'"stop broadcasting an activity".',
|
||||
short='Send your activity.',
|
||||
completion=self.comp_activity
|
||||
)
|
||||
self.api.add_command(
|
||||
'mood',
|
||||
self.command_mood,
|
||||
usage='[<mood> [text]]',
|
||||
help='Send your current mood to your contacts '
|
||||
'(use the completion). Nothing means '
|
||||
'"stop broadcasting a mood".',
|
||||
short='Send your mood.',
|
||||
completion=self.comp_mood,
|
||||
)
|
||||
self.api.add_command(
|
||||
'gaming',
|
||||
self.command_gaming,
|
||||
usage='[<game name> [server address]]',
|
||||
help='Send your current gaming activity to '
|
||||
'your contacts. Nothing means "stop '
|
||||
'broadcasting a gaming activity".',
|
||||
short='Send your gaming activity.',
|
||||
completion=None
|
||||
)
|
||||
handlers = [
|
||||
('user_mood_publish', self.on_mood_event),
|
||||
('user_tune_publish', self.on_tune_event),
|
||||
('user_gaming_publish', self.on_gaming_event),
|
||||
('user_activity_publish', self.on_activity_event),
|
||||
]
|
||||
for name, handler in handlers:
|
||||
self.core.xmpp.add_event_handler(name, handler)
|
||||
|
||||
def cleanup(self):
|
||||
handlers = [
|
||||
('user_mood_publish', self.on_mood_event),
|
||||
('user_tune_publish', self.on_tune_event),
|
||||
('user_gaming_publish', self.on_gaming_event),
|
||||
('user_activity_publish', self.on_activity_event),
|
||||
]
|
||||
for name, handler in handlers:
|
||||
self.core.xmpp.del_event_handler(name, handler)
|
||||
asyncio.create_task(self._stop())
|
||||
|
||||
async def _stop(self):
|
||||
await asyncio.gather(
|
||||
self.core.xmpp.plugin['xep_0108'].stop(),
|
||||
self.core.xmpp.plugin['xep_0107'].stop(),
|
||||
self.core.xmpp.plugin['xep_0196'].stop(),
|
||||
)
|
||||
|
||||
|
||||
@command_args_parser.quoted(0, 2)
|
||||
async def command_mood(self, args):
|
||||
"""
|
||||
/mood [<mood> [text]]
|
||||
"""
|
||||
if not args:
|
||||
return await self.core.xmpp.plugin['xep_0107'].stop()
|
||||
mood = args[0]
|
||||
if mood not in MOODS:
|
||||
return self.core.information(
|
||||
'%s is not a correct value for a mood.' % mood, 'Error')
|
||||
if len(args) == 2:
|
||||
text = args[1]
|
||||
else:
|
||||
text = None
|
||||
await self.core.xmpp.plugin['xep_0107'].publish_mood(
|
||||
mood, text
|
||||
)
|
||||
|
||||
@command_args_parser.quoted(0, 3)
|
||||
async def command_activity(self, args):
|
||||
"""
|
||||
/activity [<general> [specific] [text]]
|
||||
"""
|
||||
length = len(args)
|
||||
if not length:
|
||||
return await self.core.xmpp.plugin['xep_0108'].stop()
|
||||
|
||||
general = args[0]
|
||||
if general not in ACTIVITIES:
|
||||
return self.api.information(
|
||||
'%s is not a correct value for an activity' % general, 'Error')
|
||||
specific = None
|
||||
text = None
|
||||
if length == 2:
|
||||
if args[1] in ACTIVITIES[general]:
|
||||
specific = args[1]
|
||||
else:
|
||||
text = args[1]
|
||||
elif length == 3:
|
||||
specific = args[1]
|
||||
text = args[2]
|
||||
if specific and specific not in ACTIVITIES[general]:
|
||||
return self.core.information(
|
||||
'%s is not a correct value '
|
||||
'for an activity' % specific, 'Error')
|
||||
await self.core.xmpp.plugin['xep_0108'].publish_activity(
|
||||
general, specific, text
|
||||
)
|
||||
|
||||
@command_args_parser.quoted(0, 2)
|
||||
async def command_gaming(self, args):
|
||||
"""
|
||||
/gaming [<game name> [server address]]
|
||||
"""
|
||||
if not args:
|
||||
return await self.core.xmpp.plugin['xep_0196'].stop()
|
||||
|
||||
name = args[0]
|
||||
if len(args) > 1:
|
||||
address = args[1]
|
||||
else:
|
||||
address = None
|
||||
return await self.core.xmpp.plugin['xep_0196'].publish_gaming(
|
||||
name=name, server_address=address
|
||||
)
|
||||
|
||||
def comp_activity(self, the_input):
|
||||
"""Completion for /activity"""
|
||||
n = the_input.get_argument_position(quoted=True)
|
||||
args = common.shell_split(the_input.text)
|
||||
if n == 1:
|
||||
return Completion(
|
||||
the_input.new_completion,
|
||||
sorted(ACTIVITIES.keys()),
|
||||
n,
|
||||
quotify=True)
|
||||
elif n == 2:
|
||||
if args[1] in ACTIVITIES:
|
||||
l = list(ACTIVITIES[args[1]])
|
||||
l.remove('category')
|
||||
l.sort()
|
||||
return Completion(the_input.new_completion, l, n, quotify=True)
|
||||
|
||||
def comp_mood(self, the_input):
|
||||
"""Completion for /mood"""
|
||||
n = the_input.get_argument_position(quoted=True)
|
||||
if n == 1:
|
||||
return Completion(
|
||||
the_input.new_completion,
|
||||
sorted(MOODS.keys()),
|
||||
1,
|
||||
quotify=True)
|
||||
|
||||
def on_gaming_event(self, message: Message):
|
||||
"""
|
||||
Called when a pep notification for user gaming
|
||||
is received
|
||||
"""
|
||||
contact = roster[message['from'].bare]
|
||||
if not contact:
|
||||
return
|
||||
item = message['pubsub_event']['items']['item']
|
||||
old_gaming = contact.rich_presence['gaming']
|
||||
xml_node = item.xml.find('{urn:xmpp:gaming:0}game')
|
||||
# list(xml_node) checks whether there are children or not.
|
||||
if xml_node is not None and list(xml_node):
|
||||
item = item['gaming']
|
||||
# only name and server_address are used for now
|
||||
contact.rich_presence['gaming'] = {
|
||||
'character_name': item['character_name'],
|
||||
'character_profile': item['character_profile'],
|
||||
'name': item['name'],
|
||||
'level': item['level'],
|
||||
'uri': item['uri'],
|
||||
'server_name': item['server_name'],
|
||||
'server_address': item['server_address'],
|
||||
}
|
||||
else:
|
||||
contact.rich_presence['gaming'] = {}
|
||||
|
||||
if old_gaming != contact.rich_presence['gaming'] and self.config.get(
|
||||
'display_gaming_notifications'):
|
||||
if contact.rich_presence['gaming']:
|
||||
self.core.information(
|
||||
'%s is playing %s' % (contact.bare_jid,
|
||||
common.format_gaming_string(
|
||||
contact.rich_presence['gaming'])), 'Gaming')
|
||||
else:
|
||||
self.core.information(contact.bare_jid + ' stopped playing.',
|
||||
'Gaming')
|
||||
|
||||
def on_mood_event(self, message: Message):
|
||||
"""
|
||||
Called when a pep notification for a user mood
|
||||
is received.
|
||||
"""
|
||||
contact = roster[message['from'].bare]
|
||||
if not contact:
|
||||
return
|
||||
item = message['pubsub_event']['items']['item']
|
||||
old_mood = contact.rich_presence.get('mood')
|
||||
plugin = item.get_plugin('mood', check=True)
|
||||
if plugin:
|
||||
mood = item['mood']['value']
|
||||
else:
|
||||
mood = ''
|
||||
if mood:
|
||||
mood = MOODS.get(mood, mood)
|
||||
text = item['mood']['text']
|
||||
if text:
|
||||
mood = '%s (%s)' % (mood, text)
|
||||
contact.rich_presence['mood'] = mood
|
||||
else:
|
||||
contact.rich_presence['mood'] = ''
|
||||
|
||||
if old_mood != contact.rich_presence['mood'] and self.config.get(
|
||||
'display_mood_notifications'):
|
||||
if contact.rich_presence['mood']:
|
||||
self.core.information(
|
||||
'Mood from ' + contact.bare_jid + ': ' + contact.rich_presence['mood'],
|
||||
'Mood')
|
||||
else:
|
||||
self.core.information(
|
||||
contact.bare_jid + ' stopped having their mood.', 'Mood')
|
||||
|
||||
def on_activity_event(self, message: Message):
|
||||
"""
|
||||
Called when a pep notification for a user activity
|
||||
is received.
|
||||
"""
|
||||
contact = roster[message['from'].bare]
|
||||
if not contact:
|
||||
return
|
||||
item = message['pubsub_event']['items']['item']
|
||||
old_activity = contact.rich_presence['activity']
|
||||
xml_node = item.xml.find('{http://jabber.org/protocol/activity}activity')
|
||||
# list(xml_node) checks whether there are children or not.
|
||||
if xml_node is not None and list(xml_node):
|
||||
try:
|
||||
activity = item['activity']['value']
|
||||
except ValueError:
|
||||
return
|
||||
if activity[0]:
|
||||
general = ACTIVITIES.get(activity[0])
|
||||
if general is None:
|
||||
return
|
||||
s = general['category']
|
||||
if activity[1]:
|
||||
s = s + '/' + general.get(activity[1], 'other')
|
||||
text = item['activity']['text']
|
||||
if text:
|
||||
s = '%s (%s)' % (s, text)
|
||||
contact.rich_presence['activity'] = s
|
||||
else:
|
||||
contact.rich_presence['activity'] = ''
|
||||
else:
|
||||
contact.rich_presence['activity'] = ''
|
||||
|
||||
if old_activity != contact.rich_presence['activity'] and self.config.get(
|
||||
'display_activity_notifications'):
|
||||
if contact.rich_presence['activity']:
|
||||
self.core.information(
|
||||
'Activity from ' + contact.bare_jid + ': ' +
|
||||
contact.rich_presence['activity'], 'Activity')
|
||||
else:
|
||||
self.core.information(
|
||||
contact.bare_jid + ' stopped doing their activity.',
|
||||
'Activity')
|
||||
|
||||
def on_tune_event(self, message: Message):
|
||||
"""
|
||||
Called when a pep notification for a user tune
|
||||
is received
|
||||
"""
|
||||
contact = roster[message['from'].bare]
|
||||
if not contact:
|
||||
return
|
||||
roster.modified()
|
||||
item = message['pubsub_event']['items']['item']
|
||||
old_tune = contact.rich_presence['tune']
|
||||
xml_node = item.xml.find('{http://jabber.org/protocol/tune}tune')
|
||||
# list(xml_node) checks whether there are children or not.
|
||||
if xml_node is not None and list(xml_node):
|
||||
item = item['tune']
|
||||
contact.rich_presence['tune'] = {
|
||||
'artist': item['artist'],
|
||||
'length': item['length'],
|
||||
'rating': item['rating'],
|
||||
'source': item['source'],
|
||||
'title': item['title'],
|
||||
'track': item['track'],
|
||||
'uri': item['uri']
|
||||
}
|
||||
else:
|
||||
contact.rich_presence['tune'] = {}
|
||||
|
||||
if old_tune != contact.rich_presence['tune'] and self.config.get(
|
||||
'display_tune_notifications'):
|
||||
if contact.rich_presence['tune']:
|
||||
self.core.information(
|
||||
'Tune from ' + message['from'].bare + ': ' +
|
||||
common.format_tune_string(contact.rich_presence['tune']), 'Tune')
|
||||
else:
|
||||
self.core.information(
|
||||
contact.bare_jid + ' stopped listening to music.', 'Tune')
|
||||
|
||||
|
||||
# Collection of mappings for PEP moods/activities
|
||||
# extracted directly from the XEP
|
||||
|
||||
MOODS: Dict[str, str] = {
|
||||
'afraid': 'Afraid',
|
||||
'amazed': 'Amazed',
|
||||
'angry': 'Angry',
|
||||
'amorous': 'Amorous',
|
||||
'annoyed': 'Annoyed',
|
||||
'anxious': 'Anxious',
|
||||
'aroused': 'Aroused',
|
||||
'ashamed': 'Ashamed',
|
||||
'bored': 'Bored',
|
||||
'brave': 'Brave',
|
||||
'calm': 'Calm',
|
||||
'cautious': 'Cautious',
|
||||
'cold': 'Cold',
|
||||
'confident': 'Confident',
|
||||
'confused': 'Confused',
|
||||
'contemplative': 'Contemplative',
|
||||
'contented': 'Contented',
|
||||
'cranky': 'Cranky',
|
||||
'crazy': 'Crazy',
|
||||
'creative': 'Creative',
|
||||
'curious': 'Curious',
|
||||
'dejected': 'Dejected',
|
||||
'depressed': 'Depressed',
|
||||
'disappointed': 'Disappointed',
|
||||
'disgusted': 'Disgusted',
|
||||
'dismayed': 'Dismayed',
|
||||
'distracted': 'Distracted',
|
||||
'embarrassed': 'Embarrassed',
|
||||
'envious': 'Envious',
|
||||
'excited': 'Excited',
|
||||
'flirtatious': 'Flirtatious',
|
||||
'frustrated': 'Frustrated',
|
||||
'grumpy': 'Grumpy',
|
||||
'guilty': 'Guilty',
|
||||
'happy': 'Happy',
|
||||
'hopeful': 'Hopeful',
|
||||
'hot': 'Hot',
|
||||
'humbled': 'Humbled',
|
||||
'humiliated': 'Humiliated',
|
||||
'hungry': 'Hungry',
|
||||
'hurt': 'Hurt',
|
||||
'impressed': 'Impressed',
|
||||
'in_awe': 'In awe',
|
||||
'in_love': 'In love',
|
||||
'indignant': 'Indignant',
|
||||
'interested': 'Interested',
|
||||
'intoxicated': 'Intoxicated',
|
||||
'invincible': 'Invincible',
|
||||
'jealous': 'Jealous',
|
||||
'lonely': 'Lonely',
|
||||
'lucky': 'Lucky',
|
||||
'mean': 'Mean',
|
||||
'moody': 'Moody',
|
||||
'nervous': 'Nervous',
|
||||
'neutral': 'Neutral',
|
||||
'offended': 'Offended',
|
||||
'outraged': 'Outraged',
|
||||
'playful': 'Playful',
|
||||
'proud': 'Proud',
|
||||
'relaxed': 'Relaxed',
|
||||
'relieved': 'Relieved',
|
||||
'remorseful': 'Remorseful',
|
||||
'restless': 'Restless',
|
||||
'sad': 'Sad',
|
||||
'sarcastic': 'Sarcastic',
|
||||
'serious': 'Serious',
|
||||
'shocked': 'Shocked',
|
||||
'shy': 'Shy',
|
||||
'sick': 'Sick',
|
||||
'sleepy': 'Sleepy',
|
||||
'spontaneous': 'Spontaneous',
|
||||
'stressed': 'Stressed',
|
||||
'strong': 'Strong',
|
||||
'surprised': 'Surprised',
|
||||
'thankful': 'Thankful',
|
||||
'thirsty': 'Thirsty',
|
||||
'tired': 'Tired',
|
||||
'undefined': 'Undefined',
|
||||
'weak': 'Weak',
|
||||
'worried': 'Worried'
|
||||
}
|
||||
|
||||
ACTIVITIES: Dict[str, Dict[str, str]] = {
|
||||
'doing_chores': {
|
||||
'category': 'Doing_chores',
|
||||
'buying_groceries': 'Buying groceries',
|
||||
'cleaning': 'Cleaning',
|
||||
'cooking': 'Cooking',
|
||||
'doing_maintenance': 'Doing maintenance',
|
||||
'doing_the_dishes': 'Doing the dishes',
|
||||
'doing_the_laundry': 'Doing the laundry',
|
||||
'gardening': 'Gardening',
|
||||
'running_an_errand': 'Running an errand',
|
||||
'walking_the_dog': 'Walking the dog',
|
||||
'other': 'Other',
|
||||
},
|
||||
'drinking': {
|
||||
'category': 'Drinking',
|
||||
'having_a_beer': 'Having a beer',
|
||||
'having_coffee': 'Having coffee',
|
||||
'having_tea': 'Having tea',
|
||||
'other': 'Other',
|
||||
},
|
||||
'eating': {
|
||||
'category': 'Eating',
|
||||
'having_breakfast': 'Having breakfast',
|
||||
'having_a_snack': 'Having a snack',
|
||||
'having_dinner': 'Having dinner',
|
||||
'having_lunch': 'Having lunch',
|
||||
'other': 'Other',
|
||||
},
|
||||
'exercising': {
|
||||
'category': 'Exercising',
|
||||
'cycling': 'Cycling',
|
||||
'dancing': 'Dancing',
|
||||
'hiking': 'Hiking',
|
||||
'jogging': 'Jogging',
|
||||
'playing_sports': 'Playing sports',
|
||||
'running': 'Running',
|
||||
'skiing': 'Skiing',
|
||||
'swimming': 'Swimming',
|
||||
'working_out': 'Working out',
|
||||
'other': 'Other',
|
||||
},
|
||||
'grooming': {
|
||||
'category': 'Grooming',
|
||||
'at_the_spa': 'At the spa',
|
||||
'brushing_teeth': 'Brushing teeth',
|
||||
'getting_a_haircut': 'Getting a haircut',
|
||||
'shaving': 'Shaving',
|
||||
'taking_a_bath': 'Taking a bath',
|
||||
'taking_a_shower': 'Taking a shower',
|
||||
'other': 'Other',
|
||||
},
|
||||
'having_appointment': {
|
||||
'category': 'Having appointment',
|
||||
'other': 'Other',
|
||||
},
|
||||
'inactive': {
|
||||
'category': 'Inactive',
|
||||
'day_off': 'Day_off',
|
||||
'hanging_out': 'Hanging out',
|
||||
'hiding': 'Hiding',
|
||||
'on_vacation': 'On vacation',
|
||||
'praying': 'Praying',
|
||||
'scheduled_holiday': 'Scheduled holiday',
|
||||
'sleeping': 'Sleeping',
|
||||
'thinking': 'Thinking',
|
||||
'other': 'Other',
|
||||
},
|
||||
'relaxing': {
|
||||
'category': 'Relaxing',
|
||||
'fishing': 'Fishing',
|
||||
'gaming': 'Gaming',
|
||||
'going_out': 'Going out',
|
||||
'partying': 'Partying',
|
||||
'reading': 'Reading',
|
||||
'rehearsing': 'Rehearsing',
|
||||
'shopping': 'Shopping',
|
||||
'smoking': 'Smoking',
|
||||
'socializing': 'Socializing',
|
||||
'sunbathing': 'Sunbathing',
|
||||
'watching_a_movie': 'Watching a movie',
|
||||
'watching_tv': 'Watching tv',
|
||||
'other': 'Other',
|
||||
},
|
||||
'talking': {
|
||||
'category': 'Talking',
|
||||
'in_real_life': 'In real life',
|
||||
'on_the_phone': 'On the phone',
|
||||
'on_video_phone': 'On video phone',
|
||||
'other': 'Other',
|
||||
},
|
||||
'traveling': {
|
||||
'category': 'Traveling',
|
||||
'commuting': 'Commuting',
|
||||
'driving': 'Driving',
|
||||
'in_a_car': 'In a car',
|
||||
'on_a_bus': 'On a bus',
|
||||
'on_a_plane': 'On a plane',
|
||||
'on_a_train': 'On a train',
|
||||
'on_a_trip': 'On a trip',
|
||||
'walking': 'Walking',
|
||||
'cycling': 'Cycling',
|
||||
'other': 'Other',
|
||||
},
|
||||
'undefined': {
|
||||
'category': 'Undefined',
|
||||
'other': 'Other',
|
||||
},
|
||||
'working': {
|
||||
'category': 'Working',
|
||||
'coding': 'Coding',
|
||||
'in_a_meeting': 'In a meeting',
|
||||
'writing': 'Writing',
|
||||
'studying': 'Studying',
|
||||
'other': 'Other',
|
||||
}
|
||||
}
|
|
@ -1,323 +0,0 @@
|
|||
"""
|
||||
This plugin adds a :term:`/vcard` command to all tabs, allowing you to request
|
||||
and display the vcard-temp of any given entity.
|
||||
|
||||
Command
|
||||
-------
|
||||
|
||||
.. glossary::
|
||||
|
||||
/vcard
|
||||
|
||||
**Usage (globally):** ``/vcard <jid>``
|
||||
|
||||
**Usage (in a chatroom tab):** ``/vcard <jid or nick>``
|
||||
|
||||
**Usage (in a conversation or contact list tab):** ``/vcard [jid]``
|
||||
|
||||
Globally, you can do ``/vcard user@server.example`` to get a vcard.
|
||||
|
||||
In a chatroom , you can either do it on a JID or a nick (``/vcard nick``,
|
||||
``/vcard room@muc.server.example/nick`` or ``/vcard
|
||||
user@server.example``).
|
||||
|
||||
In a private or a direct conversation, you can do ``/vcard`` to request
|
||||
vcard from the current interlocutor, and in the contact list to do it
|
||||
on the currently selected contact.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from poezio.decorators import command_args_parser
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio.roster import roster
|
||||
from poezio.contact import Contact, Resource
|
||||
from poezio.core.structs import Completion
|
||||
from poezio import tabs
|
||||
from slixmpp.jid import JID, InvalidJID
|
||||
from slixmpp.exceptions import IqTimeout
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_command(
|
||||
'vcard',
|
||||
self.command_vcard,
|
||||
usage='<jid>',
|
||||
help='Send an XMPP vcard request to jid (see XEP-0054).',
|
||||
short='Send a vcard request',
|
||||
completion=self.completion_vcard)
|
||||
self.api.add_tab_command(
|
||||
tabs.MucTab,
|
||||
'vcard',
|
||||
self.command_muc_vcard,
|
||||
usage='<jid|nick>',
|
||||
help='Send an XMPP vcard request to jid or nick (see XEP-0054).',
|
||||
short='Send a vcard request.',
|
||||
completion=self.completion_muc_vcard)
|
||||
self.api.add_tab_command(
|
||||
tabs.RosterInfoTab,
|
||||
'vcard',
|
||||
self.command_roster_vcard,
|
||||
usage='<jid>',
|
||||
help='Send an XMPP vcard request to jid (see XEP-0054).',
|
||||
short='Send a vcard request.',
|
||||
completion=self.completion_vcard)
|
||||
for _class in (tabs.PrivateTab, tabs.DynamicConversationTab, tabs.StaticConversationTab):
|
||||
self.api.add_tab_command(
|
||||
_class,
|
||||
'vcard',
|
||||
self.command_private_vcard,
|
||||
usage='[jid]',
|
||||
help=
|
||||
'Send an XMPP vcard request to the current interlocutor or the given JID.',
|
||||
short='Send a vcard request',
|
||||
completion=self.completion_vcard)
|
||||
|
||||
def _handle_vcard(self, iq):
|
||||
'''Retrieves a vCard from vcard-temp and present it as a DataFormsTab.
|
||||
'''
|
||||
jid = iq['from']
|
||||
|
||||
if iq['type'] == 'error':
|
||||
self.api.information(
|
||||
'Error retrieving vCard for %s: %s: %s' %
|
||||
(jid, iq['error']['type'], iq['error']['condition']), 'Error')
|
||||
return
|
||||
|
||||
vcard = iq['vcard_temp']
|
||||
|
||||
form = self.core.xmpp['xep_0004'].make_form(
|
||||
ftype='result', title='vCard of %s' % jid)
|
||||
|
||||
# TODO: implement the other fields.
|
||||
|
||||
form.add_field(
|
||||
var='FN', ftype='text-single', label='Name', value=vcard['FN'])
|
||||
form.add_field(
|
||||
var='NICKNAME',
|
||||
ftype='text-multi',
|
||||
label='Nicknames',
|
||||
value=vcard['NICKNAME'])
|
||||
|
||||
# TODO: find a way to detect whether this is present or not.
|
||||
form.add_field(ftype='fixed', value='Full Name')
|
||||
form.add_field(
|
||||
var='N/GIVEN',
|
||||
ftype='text-single',
|
||||
label='Given',
|
||||
value=vcard['N']['GIVEN'])
|
||||
form.add_field(
|
||||
var='N/MIDDLE',
|
||||
ftype='text-single',
|
||||
label='Middle',
|
||||
value=vcard['N']['MIDDLE'])
|
||||
form.add_field(
|
||||
var='N/FAMILY',
|
||||
ftype='text-single',
|
||||
label='Family',
|
||||
value=vcard['N']['FAMILY'])
|
||||
form.add_field(
|
||||
var='N/PREFIX',
|
||||
ftype='text-single',
|
||||
label='Prefix',
|
||||
value=vcard['N']['PREFIX'])
|
||||
form.add_field(
|
||||
var='N/SUFFIX',
|
||||
ftype='text-single',
|
||||
label='Suffix',
|
||||
value=vcard['N']['SUFFIX'])
|
||||
|
||||
for i, addr in enumerate(vcard['addresses']):
|
||||
form.add_field(ftype='fixed', value='Address')
|
||||
values = [type_ for type_ in addr.bool_interfaces if addr[type_]]
|
||||
addr_type = form.add_field(
|
||||
var='ADR %d/TYPE' % i,
|
||||
ftype='list-multi',
|
||||
label='Type',
|
||||
value=values)
|
||||
addr_type.add_option(label='Home', value='HOME')
|
||||
for type_ in addr.bool_interfaces:
|
||||
addr_type.add_option(label=type_, value=type_)
|
||||
form.add_field(
|
||||
var='ADR %d/POBOX' % i,
|
||||
ftype='text-single',
|
||||
label='Pobox',
|
||||
value=addr['POBOX'])
|
||||
form.add_field(
|
||||
var='ADR %d/EXTADD' % i,
|
||||
ftype='text-single',
|
||||
label='Extended Address',
|
||||
value=addr['EXTADD'])
|
||||
form.add_field(
|
||||
var='ADR %d/STREET' % i,
|
||||
ftype='text-single',
|
||||
label='Street',
|
||||
value=addr['STREET'])
|
||||
form.add_field(
|
||||
var='ADR %d/LOCALITY' % i,
|
||||
ftype='text-single',
|
||||
label='Locality',
|
||||
value=addr['LOCALITY'])
|
||||
form.add_field(
|
||||
var='ADR %d/REGION' % i,
|
||||
ftype='text-single',
|
||||
label='Region',
|
||||
value=addr['REGION'])
|
||||
form.add_field(
|
||||
var='ADR %d/PCODE' % i,
|
||||
ftype='text-single',
|
||||
label='Post Code',
|
||||
value=addr['PCODE'])
|
||||
form.add_field(
|
||||
var='ADR %d/CTRY' % i,
|
||||
ftype='text-single',
|
||||
label='Country',
|
||||
value=addr['CTRY'])
|
||||
|
||||
for i, tel in enumerate(vcard['telephone_numbers']):
|
||||
form.add_field(ftype='fixed', value='Telephone')
|
||||
values = [type_ for type_ in tel.bool_interfaces if tel[type_]]
|
||||
tel_type = form.add_field(
|
||||
var='TEL %d/TYPE' % i,
|
||||
ftype='list-multi',
|
||||
label='Type',
|
||||
value=values)
|
||||
for type_ in tel.bool_interfaces:
|
||||
tel_type.add_option(label=type_, value=type_)
|
||||
form.add_field(
|
||||
var='TEL %d/NUMBER' % i,
|
||||
ftype='text-single',
|
||||
label='Number',
|
||||
value=tel['NUMBER'])
|
||||
|
||||
for i, email in enumerate(vcard['emails']):
|
||||
form.add_field(ftype='fixed', value='Email address')
|
||||
values = [type_ for type_ in email.bool_interfaces if email[type_]]
|
||||
email_type = form.add_field(
|
||||
var='EMAIL %d/TYPE' % i,
|
||||
ftype='list-multi',
|
||||
label='Type',
|
||||
value=values)
|
||||
for type_ in email.bool_interfaces:
|
||||
email_type.add_option(label=type_, value=type_)
|
||||
form.add_field(
|
||||
var='EMAIL %d/USERID' % i,
|
||||
ftype='text-single',
|
||||
label='Email Address',
|
||||
value=email['USERID'])
|
||||
|
||||
form.add_field(ftype='fixed', value='Misc')
|
||||
form.add_field(
|
||||
var='BDAY',
|
||||
ftype='text-single',
|
||||
label='Birthday',
|
||||
value=str(vcard['BDAY']))
|
||||
|
||||
for i, jabberid in enumerate(vcard['jids']):
|
||||
form.add_field(ftype='fixed', value='URL')
|
||||
form.add_field(
|
||||
var='JABBERID %d' % i,
|
||||
ftype='jid-single',
|
||||
label='URL',
|
||||
value=jabberid['JABBERID'])
|
||||
|
||||
for i, url in enumerate(vcard['urls']):
|
||||
form.add_field(ftype='fixed', value='URL')
|
||||
form.add_field(
|
||||
var='URL %d' % i,
|
||||
ftype='text-single',
|
||||
label='URL',
|
||||
value=url['URL'])
|
||||
|
||||
for i, desc in enumerate(vcard['descriptions']):
|
||||
form.add_field(ftype='fixed', value='Description')
|
||||
form.add_field(
|
||||
var='DESC %d' % i,
|
||||
ftype='text-multi',
|
||||
label='Description',
|
||||
value=desc['DESC'])
|
||||
|
||||
on_validate = lambda form: self.core.close_tab()
|
||||
on_cancel = lambda form: self.core.close_tab()
|
||||
self.core.open_new_form(form, on_cancel, on_validate)
|
||||
|
||||
async def _get_vcard(self, jid):
|
||||
'''Send an iq to ask the vCard.'''
|
||||
try:
|
||||
vcard = await self.core.xmpp.plugin['xep_0054'].get_vcard(
|
||||
jid=jid,
|
||||
timeout=30,
|
||||
)
|
||||
self._handle_vcard(vcard)
|
||||
except IqTimeout:
|
||||
self.api.information('Timeout while retrieving vCard for %s' % jid,
|
||||
'Error')
|
||||
|
||||
|
||||
@command_args_parser.raw
|
||||
def command_vcard(self, arg):
|
||||
if not arg:
|
||||
self.core.command.help('vcard')
|
||||
return
|
||||
|
||||
try:
|
||||
jid = JID(arg)
|
||||
except InvalidJID:
|
||||
self.api.information('Invalid JID: %s' % arg, 'Error')
|
||||
return
|
||||
|
||||
asyncio.create_task(
|
||||
self._get_vcard(jid)
|
||||
)
|
||||
|
||||
@command_args_parser.raw
|
||||
def command_private_vcard(self, arg):
|
||||
if arg:
|
||||
self.command_vcard(arg)
|
||||
return
|
||||
self.command_vcard(self.api.current_tab().jid.full)
|
||||
|
||||
@command_args_parser.raw
|
||||
def command_muc_vcard(self, arg):
|
||||
if not arg:
|
||||
self.core.command.help('vcard')
|
||||
return
|
||||
user = self.api.current_tab().get_user_by_name(arg)
|
||||
if user:
|
||||
jid = self.api.current_tab().jid.bare + '/' + user.nick
|
||||
else:
|
||||
try:
|
||||
jid = JID(arg)
|
||||
except InvalidJID:
|
||||
return self.api.information('Invalid JID: %s' % arg, 'Error')
|
||||
asyncio.create_task(
|
||||
self._get_vcard(jid)
|
||||
)
|
||||
|
||||
@command_args_parser.raw
|
||||
def command_roster_vcard(self, arg):
|
||||
if arg:
|
||||
self.command_vcard(arg)
|
||||
return
|
||||
current = self.api.current_tab().selected_row
|
||||
if isinstance(current, Resource):
|
||||
asyncio.create_task(
|
||||
self._get_vcard(JID(current.jid).bare)
|
||||
)
|
||||
elif isinstance(current, Contact):
|
||||
asyncio.create_task(
|
||||
self._get_vcard(current.bare_jid)
|
||||
)
|
||||
|
||||
def completion_vcard(self, the_input):
|
||||
contacts = [contact.bare_jid for contact in roster.get_contacts()]
|
||||
return Completion(
|
||||
the_input.auto_completion, contacts, '', quotify=False)
|
||||
|
||||
def completion_muc_vcard(self, the_input):
|
||||
users = [user.nick for user in self.api.current_tab().users]
|
||||
users.extend([
|
||||
resource.jid for contact in roster.get_contacts()
|
||||
for resource in contact.resources
|
||||
])
|
||||
return Completion(the_input.auto_completion, users, '', quotify=False)
|
|
@ -1,29 +0,0 @@
|
|||
"""
|
||||
This plugin colors each character of a message in white.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. glossary::
|
||||
|
||||
/load white
|
||||
|
||||
Say something in a Chat tab.
|
||||
|
||||
.. note:: This plugin is best used when someone else is writing in black,
|
||||
assuming everyone is using a white background. Black backgrounds matter too!
|
||||
"""
|
||||
|
||||
from poezio.plugin import BasePlugin
|
||||
from poezio import xhtml
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.api.add_event_handler('muc_say', self.whiteify)
|
||||
self.api.add_event_handler('private_say', self.whiteify)
|
||||
self.api.add_event_handler('conversation_say', self.whiteify)
|
||||
|
||||
@staticmethod
|
||||
def whiteify(msg, _):
|
||||
msg['body'] = '\x197}' + xhtml.clean_text(msg['body'])
|
263
desktop/.local/share/poezio/themes/Nightfox.py
Normal file
263
desktop/.local/share/poezio/themes/Nightfox.py
Normal file
|
@ -0,0 +1,263 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# From Nightfox theme
|
||||
# Upstream: https://github.com/edeneast/nightfox.nvim
|
||||
|
||||
import poezio.theming
|
||||
from datetime import datetime
|
||||
|
||||
NFOX_BLACK = "#393b44"
|
||||
NFOX_RED = "#c94f6d"
|
||||
NFOX_GREEN = "#81b29a"
|
||||
NFOX_YELLOW = "#dbc074"
|
||||
NFOX_BLUE = "#719cd6"
|
||||
NFOX_MAGENTA = "#9d79d6"
|
||||
NFOX_CYAN = "#63cdcf"
|
||||
NFOX_WHITE = "#dfdfe0"
|
||||
NFOX_ORANGE = "#f4a261"
|
||||
NFOX_PINK = "#d67ad2"
|
||||
|
||||
NFOX_COMMENT = "#738091"
|
||||
|
||||
NFOX_BG0 = "#131a24"
|
||||
NFOX_BG1 = "#192330"
|
||||
NFOX_BG2 = "#212e3f"
|
||||
NFOX_BG3 = "#29394f"
|
||||
NFOX_BG4 = "#39506d"
|
||||
|
||||
NFOX_FG0 = "#d6d6d7"
|
||||
NFOX_FG1 = "#cdcecf"
|
||||
NFOX_FG2 = "#aeafb0"
|
||||
NFOX_FG3 = "#71839b"
|
||||
|
||||
NFOX_SEL0 = "#2b3b51"
|
||||
NFOX_SEL1 = "#3c5372"
|
||||
|
||||
# Redefine with 256 colors until poezio manages 24 bits
|
||||
|
||||
NFOX_BLACK = 59
|
||||
NFOX_RED = 167
|
||||
NFOX_GREEN = 108
|
||||
NFOX_YELLOW = 180
|
||||
NFOX_BLUE = 39
|
||||
NFOX_MAGENTA = 140
|
||||
NFOX_CYAN = 80
|
||||
NFOX_WHITE = 188
|
||||
NFOX_ORANGE = 215
|
||||
NFOX_PINK = 176
|
||||
|
||||
NFOX_COMMENT = 102
|
||||
|
||||
NFOX_BG0 = 232
|
||||
NFOX_BG1 = 234
|
||||
NFOX_BG2 = 236
|
||||
NFOX_BG3 = 237
|
||||
NFOX_BG4 = 59
|
||||
|
||||
NFOX_FG0 = 253
|
||||
NFOX_FG1 = 188
|
||||
NFOX_FG2 = 145
|
||||
NFOX_FG3 = 67
|
||||
|
||||
NFOX_SEL0 = 24
|
||||
NFOX_SEL1 = 67
|
||||
|
||||
class NightfoxTheme(poezio.theming.Theme):
|
||||
SHORT_TIME_FORMAT = '%H:%M'
|
||||
SHORT_TIME_FORMAT_LENGTH = len(datetime.now().strftime(SHORT_TIME_FORMAT))
|
||||
|
||||
LONG_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
LONG_TIME_FORMAT_LENGTH = len(datetime.now().strftime(LONG_TIME_FORMAT))
|
||||
|
||||
CHAR_TIME_LEFT = ''
|
||||
CHAR_TIME_RIGHT = ''
|
||||
CHAR_STATUS = '█'
|
||||
CHAR_CHATSTATE_ACTIVE = 'A'
|
||||
CHAR_CHATSTATE_COMPOSING = '…'
|
||||
CHAR_CHATSTATE_PAUSED = 'p'
|
||||
|
||||
CHAR_AFFILIATION_OWNER = '♚'
|
||||
CHAR_AFFILIATION_ADMIN = '♞'
|
||||
CHAR_AFFILIATION_MEMBER = '♜'
|
||||
CHAR_AFFILIATION_NONE = '♟'
|
||||
CHAR_AFFILIATION_OUTCAST = '!'
|
||||
|
||||
CHAR_XML_IN = '←'
|
||||
CHAR_XML_OUT = '→'
|
||||
|
||||
COLOR_OWN_NICK = (NFOX_CYAN, -1)
|
||||
|
||||
CHAR_BEFORE_NICK_ME = '@'
|
||||
CHAR_AFTER_NICK_ME = ' '
|
||||
CHAR_AFTER_NICK = ': '
|
||||
CHAR_JOIN = '→'
|
||||
CHAR_QUIT = '←'
|
||||
CHAR_KICK = '-!-'
|
||||
CHAR_NEW_TEXT_SEPARATOR = '┄'
|
||||
CHAR_OK = '✔'
|
||||
CHAR_ERROR = '!'
|
||||
CHAR_EMPTY = ' '
|
||||
CHAR_ACK_RECEIVED = CHAR_OK
|
||||
CHAR_NACK = CHAR_ERROR
|
||||
CHAR_COLUMN_ASC = ' ↗'
|
||||
CHAR_COLUMN_DESC = ' ↘'
|
||||
CHAR_ROSTER_ERROR = CHAR_ERROR
|
||||
CHAR_ROSTER_TUNE = '♪'
|
||||
CHAR_ROSTER_ASKED = '?'
|
||||
CHAR_ROSTER_ACTIVITY = 'A'
|
||||
CHAR_ROSTER_MOOD = 'M'
|
||||
CHAR_ROSTER_GAMING = 'G'
|
||||
CHAR_ROSTER_FROM = '←'
|
||||
CHAR_ROSTER_BOTH = ' '
|
||||
CHAR_ROSTER_TO = '→'
|
||||
CHAR_ROSTER_NONE = '¬'
|
||||
|
||||
COLOR_INFORMATION_BAR = (NFOX_FG3, NFOX_BG1)
|
||||
COLOR_WARNING_PROMPT = (NFOX_ORANGE, NFOX_BG1, 'b')
|
||||
|
||||
COLOR_STATUS_XA = (NFOX_FG0, NFOX_BG1)
|
||||
COLOR_STATUS_AWAY = (NFOX_PINK, NFOX_BG1)
|
||||
COLOR_STATUS_DND = (NFOX_RED, NFOX_BG1)
|
||||
COLOR_STATUS_CHAT = (NFOX_BLUE, NFOX_BG1)
|
||||
COLOR_STATUS_UNAVAILABLE = (NFOX_COMMENT, NFOX_BG1)
|
||||
COLOR_STATUS_ONLINE = (NFOX_GREEN, NFOX_BG1)
|
||||
COLOR_STATUS_NONE = (NFOX_FG1, NFOX_BG1)
|
||||
|
||||
COLOR_VERTICAL_SEPARATOR = (NFOX_COMMENT, -1)
|
||||
COLOR_NEW_TEXT_SEPARATOR = (NFOX_COMMENT, -1)
|
||||
COLOR_MORE_INDICATOR = (NFOX_MAGENTA, -1)
|
||||
|
||||
COLOR_TAB_NORMAL = (-1, NFOX_BG1)
|
||||
COLOR_TAB_NONEMPTY = (-1, NFOX_BG3)
|
||||
COLOR_TAB_JOINED = (NFOX_YELLOW, NFOX_BG1)
|
||||
COLOR_TAB_CURRENT = (-1, NFOX_SEL0)
|
||||
COLOR_TAB_COMPOSING = (NFOX_GREEN, NFOX_BG1)
|
||||
COLOR_TAB_NEW_MESSAGE = (NFOX_ORANGE, NFOX_BG1)
|
||||
COLOR_TAB_HIGHLIGHT = (NFOX_CYAN, NFOX_BG1)
|
||||
COLOR_TAB_ATTENTION = (NFOX_RED, NFOX_BG1)
|
||||
COLOR_TAB_PRIVATE = (NFOX_BLUE, NFOX_BG1)
|
||||
COLOR_TAB_DISCONNECTED = (NFOX_COMMENT, NFOX_BG1)
|
||||
COLOR_TAB_SCROLLED = (NFOX_PINK, NFOX_BG1)
|
||||
|
||||
COLOR_TOPIC_BAR = (-1, NFOX_BG1)
|
||||
COLOR_SCROLLABLE_NUMBER = (NFOX_BLUE, -1, 'b')
|
||||
COLOR_SELECTED_ROW = (-1, NFOX_SEL0)
|
||||
COLOR_PRIVATE_NAME = (NFOX_BLUE, NFOX_BG1)
|
||||
COLOR_CONVERSATION_NAME = (NFOX_GREEN, NFOX_BG1)
|
||||
COLOR_CONVERSATION_RESOURCE = (NFOX_YELLOW, NFOX_BG1)
|
||||
COLOR_GROUPCHAT_NAME = (NFOX_FG1, NFOX_BG1)
|
||||
COLOR_COLUMN_HEADER = (NFOX_CYAN, NFOX_BG3)
|
||||
COLOR_COLUMN_HEADER_SEL = (NFOX_CYAN, NFOX_SEL0)
|
||||
|
||||
COLOR_VERTICAL_TAB_NORMAL = (NFOX_FG2, -1)
|
||||
COLOR_VERTICAL_TAB_NONEMPTY = (-1, NFOX_BG3)
|
||||
COLOR_VERTICAL_TAB_JOINED = (NFOX_YELLOW, -1)
|
||||
COLOR_VERTICAL_TAB_SCROLLED = (NFOX_PINK, -1)
|
||||
COLOR_VERTICAL_TAB_CURRENT = (-1, NFOX_SEL0)
|
||||
COLOR_VERTICAL_TAB_NEW_MESSAGE = (NFOX_ORANGE, -1)
|
||||
COLOR_VERTICAL_TAB_COMPOSING = (NFOX_GREEN, -1)
|
||||
COLOR_VERTICAL_TAB_HIGHLIGHT = (NFOX_CYAN, -1)
|
||||
COLOR_VERTICAL_TAB_PRIVATE = (NFOX_BLUE, -1)
|
||||
COLOR_VERTICAL_TAB_ATTENTION = (NFOX_RED, -1)
|
||||
COLOR_VERTICAL_TAB_DISCONNECTED = (NFOX_COMMENT, -1)
|
||||
COLOR_VERTICAL_TAB_NUMBER = (NFOX_BLUE, -1)
|
||||
|
||||
COLOR_INFORMATION_TEXT = (NFOX_BLUE, -1)
|
||||
COLOR_NORMAL_TEXT = (NFOX_FG1, -1)
|
||||
COLOR_WARNING_TEXT = (NFOX_ORANGE, -1)
|
||||
|
||||
COLOR_LOG_MSG = (NFOX_COMMENT, -1)
|
||||
COLOR_ERROR_MSG = (NFOX_RED, NFOX_BG2, 'b')
|
||||
|
||||
COLOR_HELP_COMMANDS = (NFOX_GREEN, -1)
|
||||
COLOR_HIGHLIGHT_NICK = "reverse"
|
||||
COLOR_MUC_JID = (NFOX_BLUE, -1)
|
||||
|
||||
COLOR_USER_VISITOR = (NFOX_FG1, -1)
|
||||
COLOR_USER_PARTICIPANT = (NFOX_FG2, -1)
|
||||
COLOR_USER_NONE = (NFOX_COMMENT, -1)
|
||||
COLOR_USER_MODERATOR = (NFOX_MAGENTA, -1)
|
||||
COLOR_REMOTE_USER = (NFOX_ORANGE, -1)
|
||||
|
||||
COLOR_XML_IN = (NFOX_BLUE, -1)
|
||||
COLOR_XML_OUT = (NFOX_GREEN, -1)
|
||||
|
||||
COLOR_ME_MESSAGE = (NFOX_CYAN, -1)
|
||||
|
||||
COLOR_REVISIONS_MESSAGE = (NFOX_MAGENTA, -1, 'b')
|
||||
|
||||
COLOR_IMPORTANT_TEXT = (NFOX_RED, -1, 'b')
|
||||
|
||||
COLOR_TIME_STRING = (NFOX_COMMENT, -1)
|
||||
|
||||
COLOR_CHAR_ACK = (NFOX_GREEN, -1)
|
||||
COLOR_CHAR_NACK = (NFOX_ORANGE, -1)
|
||||
|
||||
COLOR_ROSTER_GAMING = (NFOX_CYAN, -1)
|
||||
COLOR_ROSTER_MOOD = (NFOX_PINK, -1)
|
||||
COLOR_ROSTER_ACTIVITY = (NFOX_GREEN, -1)
|
||||
COLOR_ROSTER_TUNE = (NFOX_BLUE, -1)
|
||||
COLOR_ROSTER_ERROR = (NFOX_RED, -1)
|
||||
COLOR_ROSTER_SUBSCRIPTION = (NFOX_FG1, -1)
|
||||
|
||||
COLOR_JOIN_CHAR = (NFOX_FG2, -1)
|
||||
COLOR_QUIT_CHAR = (NFOX_FG2, -1)
|
||||
COLOR_KICK_CHAR = (NFOX_RED, -1)
|
||||
|
||||
MODE_TAB_NORMAL = ''
|
||||
MODE_TAB_IMPORTANT = ''
|
||||
MODE_TAB_NAME = ''
|
||||
|
||||
INFO_COLORS = {
|
||||
'info': (NFOX_BLUE, -1),
|
||||
'error': (NFOX_RED, 7, 'b'),
|
||||
'warning': (NFOX_ORANGE, -1),
|
||||
'roster': (NFOX_FG1, -1),
|
||||
'help': (NFOX_GREEN, -1),
|
||||
'headline': (NFOX_FG2, -1, 'b'),
|
||||
'tune': (NFOX_BLUE, -1),
|
||||
'gaming': (NFOX_CYAN, -1),
|
||||
'mood': (NFOX_PINK, -1),
|
||||
'activity': (NFOX_GREEN, -1),
|
||||
'default': (NFOX_FG1, -1),
|
||||
}
|
||||
|
||||
LIST_COLOR_NICKNAMES = [
|
||||
(1, -1), (2, -1), (3, -1), (4, -1), (5, -1), (6, -1), (9, -1),
|
||||
(10, -1), (11, -1), (12, -1), (13, -1), (14, -1), (19, -1),
|
||||
(20, -1), (21, -1), (22, -1), (23, -1), (24, -1), (25, -1),
|
||||
(26, -1), (27, -1), (28, -1), (29, -1), (30, -1), (31, -1),
|
||||
(32, -1), (33, -1), (34, -1), (35, -1), (36, -1), (37, -1),
|
||||
(38, -1), (39, -1), (40, -1), (41, -1), (42, -1), (43, -1),
|
||||
(44, -1), (45, -1), (46, -1), (47, -1), (48, -1), (49, -1),
|
||||
(50, -1), (51, -1), (54, -1), (55, -1), (56, -1), (57, -1),
|
||||
(58, -1), (60, -1), (61, -1), (62, -1), (63, -1), (64, -1),
|
||||
(65, -1), (66, -1), (67, -1), (68, -1), (69, -1), (70, -1),
|
||||
(71, -1), (72, -1), (73, -1), (74, -1), (75, -1), (76, -1),
|
||||
(77, -1), (78, -1), (79, -1), (81, -1), (82, -1),
|
||||
(83, -1), (84, -1), (85, -1), (86, -1), (87, -1), (88, -1),
|
||||
(89, -1), (90, -1), (91, -1), (92, -1), (93, -1), (94, -1),
|
||||
(95, -1), (96, -1), (97, -1), (98, -1), (99, -1), (100, -1),
|
||||
(101, -1), (103, -1), (104, -1), (105, -1), (106, -1), (107, -1),
|
||||
(108, -1), (109, -1), (110, -1), (111, -1), (112, -1), (113, -1),
|
||||
(114, -1), (115, -1), (116, -1), (117, -1), (118, -1), (119, -1),
|
||||
(120, -1), (121, -1), (122, -1), (123, -1), (124, -1), (125, -1),
|
||||
(126, -1), (127, -1), (128, -1), (129, -1), (130, -1), (131, -1),
|
||||
(132, -1), (133, -1), (134, -1), (135, -1), (136, -1), (137, -1),
|
||||
(138, -1), (139, -1), (140, -1), (141, -1), (142, -1), (143, -1),
|
||||
(144, -1), (145, -1), (146, -1), (147, -1), (148, -1), (149, -1),
|
||||
(150, -1), (151, -1), (152, -1), (153, -1), (154, -1), (155, -1),
|
||||
(156, -1), (157, -1), (158, -1), (159, -1), (160, -1), (161, -1),
|
||||
(162, -1), (163, -1), (164, -1), (165, -1), (166, -1), (167, -1),
|
||||
(168, -1), (169, -1), (170, -1), (171, -1), (172, -1), (173, -1),
|
||||
(174, -1), (175, -1), (176, -1), (177, -1), (178, -1), (179, -1),
|
||||
(180, -1), (181, -1), (182, -1), (183, -1), (184, -1), (185, -1),
|
||||
(186, -1), (187, -1), (188, -1), (189, -1), (190, -1), (191, -1),
|
||||
(192, -1), (193, -1), (196, -1), (197, -1), (198, -1), (199, -1),
|
||||
(200, -1), (201, -1), (202, -1), (203, -1), (204, -1), (205, -1),
|
||||
(206, -1), (207, -1), (208, -1), (209, -1), (210, -1), (211, -1),
|
||||
(212, -1), (213, -1), (214, -1), (215, -1), (216, -1), (217, -1),
|
||||
(218, -1), (219, -1), (220, -1), (221, -1), (222, -1), (223, -1),
|
||||
(224, -1), (225, -1), (226, -1), (227, -1)]
|
||||
|
||||
theme = NightfoxTheme()
|
|
@ -16,11 +16,25 @@ if set --query _flag_help
|
|||
exit
|
||||
end
|
||||
|
||||
mkdir -p ~/.cache
|
||||
|
||||
if set --query _flag_desktop
|
||||
# Copy the desktop part
|
||||
echo "**** Copying desktop part"
|
||||
rsync --exclude=".*.swp" -av ./desktop/ ~/
|
||||
pip install --user --break-system-packages -U poezio-omemo epr-reader
|
||||
|
||||
rm -rf ~/.local/share/poezio/plugins
|
||||
mkdir -p ~/.local/share/poezio
|
||||
|
||||
set -l poezio_version $(poezio --version | cut -d " " -f 2)
|
||||
rm -rf ~/.cache/poezio-git
|
||||
git clone --depth 1 https://codeberg.org/poezio/poezio ~/.cache/poezio-git
|
||||
pushd ~/.cache/poezio-git
|
||||
git checkout $poezio_version
|
||||
popd
|
||||
cp -r ~/.cache/poezio-git/plugins ~/.local/share/poezio
|
||||
rm -rf ~/.cache/poezio-git
|
||||
end
|
||||
|
||||
if set --query _flag_remove
|
||||
|
@ -45,8 +59,7 @@ go install github.com/jesseduffield/lazydocker@latest
|
|||
go install github.com/jesseduffield/lazygit@latest
|
||||
|
||||
rm -rf ~/.cache/nvimpager-git
|
||||
mkdir -p ~/.cache
|
||||
git clone https://github.com/lucc/nvimpager ~/.cache/nvimpager-git
|
||||
git clone --depth 1 https://github.com/lucc/nvimpager ~/.cache/nvimpager-git
|
||||
pushd ~/.cache/nvimpager-git
|
||||
make PREFIX=$HOME/.local install
|
||||
popd
|
||||
|
|
|
@ -28,7 +28,11 @@ if set --query _flag_private
|
|||
echo "**** Copying private part"
|
||||
rsync --exclude=".*.swp" -av ./private/ ~/
|
||||
|
||||
sed -i "s/REPLACEDEVICEHOSTNAME/`hostname`/g" ~/.config/poezio/poezio.cfg
|
||||
sed -i "s/REPLACE_DEVICE_HOSTNAME/`hostname`/g" ~/.config/poezio/poezio.cfg
|
||||
sed -i "s/REPLACE_FINGERPRINT/cert_fingerprint/g" ~/.config/poezio/poezio.cfg
|
||||
sed -i "s/REPLACE_JID/template@example.com/g" ~/.config/poezio/poezio.cfg
|
||||
sed -i "s/KEYRING_ID/xmpp:template@example.com/g" ~/.config/poezio/poezio.cfg
|
||||
sed -i "s/KEYRING_USER/template@example.com/g" ~/.config/poezio/poezio.cfg
|
||||
|
||||
sed -i "s/FRESHRSSLOGIN/example_user/g" ~/.config/newsboat/config
|
||||
sed -i "s/FRESHRSSKEEPASSXC/rss:freshrss.example.com/g" ~/.config/newsboat/config
|
||||
|
|
Loading…
Reference in a new issue