9578053 Jan 22 2022 distfiles.gentoo.org/distfiles/gajim-1.3.3-2.tar.gz

This commit is contained in:
emdee 2022-10-19 18:09:31 +00:00
parent a5b3822651
commit 4c1b226bff
1045 changed files with 753037 additions and 18 deletions

View file

@ -0,0 +1,20 @@
# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The implementation and auxiliary systems which implement the standard
Gajim commands and also provide an infrastructure for adding custom
commands.
"""

View file

@ -0,0 +1,131 @@
# Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
This module contains examples of how to create your own commands, by
creating a new command container, bounded to a specific command host,
and defining a set of commands inside of it.
Keep in mind that this module is not being loaded from anywhere, so the
code in here will not be executed and commands defined here will not be
detected.
"""
from gajim.common.i18n import _
from gajim.command_system.framework import CommandContainer
from gajim.command_system.framework import command
from gajim.command_system.framework import doc
from gajim.command_system.implementation.hosts import ChatCommands
from gajim.command_system.implementation.hosts import PrivateChatCommands
from gajim.command_system.implementation.hosts import GroupChatCommands
class CustomCommonCommands(CommandContainer):
"""
The AUTOMATIC class variable, set to a positive value, instructs the
command system to automatically discover the command container and
enable it.
This command container bounds to all three available in the default
implementation command hosts. This means that commands defined in
this container will be available to all: chat, private chat and a
group chat.
"""
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
@command
def dance(self):
"""
First line of the doc string is called a description and will be
programmatically extracted and formatted.
After that you can give more help, like explanation of the
options. This one will be programmatically extracted and
formatted too.
After all the documentation - there will be autogenerated (based
on the method signature) usage information appended. You can
turn it off, if you want.
"""
return "I don't dance."
class CustomChatCommands(CommandContainer):
"""
This command container bounds only to the ChatCommands command host.
Therefore commands defined inside of the container will be available
only to a chat.
"""
AUTOMATIC = True
HOSTS = (ChatCommands,)
@command("squal", "bawl")
def sing(self):
"""
This command has an additional aliases. It means the command will
be available under three names: sing (the native name), squal
(the first alias), bawl (the second alias).
You can turn off the usage of the native name, if you want, and
specify a name or a set of names, as aliases, under which a
command will be available.
"""
return "Buy yourself a stereo."
class CustomPrivateChatCommands(CommandContainer):
"""
This command container bounds only to the PrivateChatCommands
command host. Therefore commands defined inside of the container
will be available only to a private chat.
"""
AUTOMATIC = True
HOSTS = (PrivateChatCommands,)
@command
#Example string. Do not translate
@doc(_("The same as using a doc-string, except it supports translation"))
def make_coffee(self):
return "I'm not a coffee machine!"
class CustomGroupChatCommands(CommandContainer):
"""
This command container bounds only to the GroupChatCommands command
host. Therefore commands defined inside of the container will be
available only to a group chat.
"""
AUTOMATIC = True
HOSTS = (GroupChatCommands,)
@command
def fetch(self):
return "Buy yourself a dog."

View file

@ -0,0 +1,136 @@
# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Provides facilities to safely execute expressions inside a shell process
and capture the resulting output, in an asynchronous fashion, avoiding
deadlocks. If the process execution time reaches the threshold - it is
forced to terminate. Consists of a tiny framework and a couple of
commands as a frontend.
"""
from subprocess import Popen, PIPE
from os.path import expanduser
from gi.repository import GLib
from gajim.common import app
from gajim.common.i18n import _
from gajim.command_system.framework import CommandContainer
from gajim.command_system.framework import command
from gajim.command_system.framework import doc
from gajim.command_system.implementation.hosts import ChatCommands
from gajim.command_system.implementation.hosts import PrivateChatCommands
from gajim.command_system.implementation.hosts import GroupChatCommands
class Execute(CommandContainer):
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
DIRECTORY = "~"
POLL_INTERVAL = 100
POLL_COUNT = 5
@command("exec", raw=True)
@doc(_("Execute expression inside a shell, show output"))
def execute(self, expression):
Execute.spawn(self, expression)
@classmethod
def spawn(cls, processor, expression):
command_system_execute = app.settings.get('command_system_execute')
if command_system_execute:
pipes = dict(stdout=PIPE, stderr=PIPE)
directory = expanduser(cls.DIRECTORY)
popen = Popen(expression, shell=True, cwd=directory, **pipes)
cls.monitor(processor, popen)
else:
processor.echo_error(
_('Command disabled. This command can be enabled by '
'setting \'command_system_execute\' to True in ACE '
'(Advanced Configuration Editor).'))
return
@classmethod
def monitor(cls, processor, popen):
poller = cls.poller(processor, popen)
GLib.timeout_add(cls.POLL_INTERVAL, next, poller)
@classmethod
def poller(cls, processor, popen):
for _ in range(cls.POLL_COUNT):
yield cls.brush(processor, popen)
cls.overdue(processor, popen)
yield False
@classmethod
def brush(cls, processor, popen):
if popen.poll() is not None:
cls.terminated(processor, popen)
return False
return True
@classmethod
def terminated(cls, processor, popen):
stdout, stderr = cls.fetch(popen)
success = popen.returncode == 0
if success and stdout:
processor.echo(stdout)
elif not success and stderr:
processor.echo_error(stderr)
@classmethod
def overdue(cls, processor, popen):
popen.terminate()
@classmethod
def fetch(cls, popen):
data = popen.communicate()
return map(cls.clean, data)
@staticmethod
def clean(text):
strip = chr(10) + chr(32)
return text.decode().strip(strip)
class Show(Execute):
@command("sh", raw=True)
@doc(_("Execute expression inside a shell, send output"))
def show(self, expression):
Show.spawn(self, expression)
@classmethod
def terminated(cls, processor, popen):
stdout, stderr = cls.fetch(popen)
success = popen.returncode == 0
if success and stdout:
processor.send(stdout)
elif not success and stderr:
processor.echo_error(stderr)

View file

@ -0,0 +1,45 @@
# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The module defines a set of command hosts, which are bound to a
different command processors, which are the source of commands.
"""
from gajim.command_system.framework import CommandHost
class ChatCommands(CommandHost):
"""
This command host is bound to the command processor which processes
commands from a chat.
"""
AUTOMATIC = True
class PrivateChatCommands(CommandHost):
"""
This command host is bound to the command processor which processes
commands from a private chat.
"""
AUTOMATIC = True
class GroupChatCommands(CommandHost):
"""
This command host is bound to the command processor which processes
commands from a group chat.
"""
AUTOMATIC = True

View file

@ -0,0 +1,195 @@
# Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Provides a glue to tie command system framework and the actual code
where it would be dropped in. Defines a little bit of scaffolding to
support interaction between the two and a few utility methods so you
don't need to dig up the code itself to write basic commands.
"""
from traceback import print_exc
from gi.repository import Pango
from gajim.common import app
from gajim.common.i18n import _
from gajim.command_system.framework import CommandProcessor
from gajim.command_system.errors import CommandError
from gajim.command_system.errors import NoCommandError
class ChatCommandProcessor(CommandProcessor):
"""
A basic scaffolding to provide convenient interaction between the
command system and chat controls. It will be merged directly into
the controls, by ChatCommandProcessor being among superclasses of
the controls.
"""
def process_as_command(self, text):
self.command_succeeded = False
parents = super(ChatCommandProcessor, self)
flag = parents.process_as_command(text)
if flag and self.command_succeeded:
self.add_history(text)
self.clear_input()
return flag
def execute_command(self, name, arguments):
try:
parents = super(ChatCommandProcessor, self)
parents.execute_command(name, arguments)
except NoCommandError as error:
details = dict(name=error.name, message=error.message)
message = "%(name)s: %(message)s\n" % details
message += "Try using the //%(name)s or /say /%(name)s " % details
message += "construct if you intended to send it as a text."
self.echo_error(message)
except CommandError as error:
self.echo_error("%s: %s" % (error.name, error.message))
except Exception:
self.echo_error(_("Error during command execution!"))
print_exc()
else:
self.command_succeeded = True
def looks_like_command(self, text, body, name, arguments):
# Command escape stuff goes here. If text was prepended by the
# command prefix twice, like //not_a_command (if prefix is set
# to /) then it will be escaped, that is sent just as a regular
# message with one (only one) prefix removed, so message will be
# /not_a_command.
if body.startswith(self.COMMAND_PREFIX):
self.send(body)
return True
def command_preprocessor(self, command, name, arguments, args, kwargs):
# If command argument contain h or help option - forward it to
# the /help command. Don't forget to pass self, as all commands
# are unbound. And also don't forget to print output.
if 'h' in kwargs or 'help' in kwargs:
help_ = self.get_command('help')
self.echo(help_(self, name))
return True
def command_postprocessor(self, command, name, arguments, args, kwargs,
value):
# If command returns a string - print it to a user. A convenient
# and sufficient in most simple cases shortcut to a using echo.
if value and isinstance(value, str):
self.echo(value)
class CommandTools:
"""
Contains a set of basic tools and shortcuts you can use in your
commands to perform some simple operations. These will be merged
directly into the controls, by CommandTools being among superclasses
of the controls.
"""
def __init__(self):
self.install_tags()
def install_tags(self):
buffer_ = self.conv_textview.tv.get_buffer()
name = "Monospace"
font = Pango.FontDescription(name)
command_ok_tag = buffer_.create_tag("command_ok")
command_ok_tag.set_property("font-desc", font)
command_ok_tag.set_property("foreground", "#3465A4")
command_error_tag = buffer_.create_tag("command_error")
command_error_tag.set_property("font-desc", font)
command_error_tag.set_property("foreground", "#F57900")
def shift_line(self):
buffer_ = self.conv_textview.tv.get_buffer()
iter_ = buffer_.get_end_iter()
if iter_.ends_line() and not iter_.is_start():
buffer_.insert_with_tags_by_name(iter_, "\n", "eol")
def append_with_tags(self, text, *tags):
buffer_ = self.conv_textview.tv.get_buffer()
iter_ = buffer_.get_end_iter()
buffer_.insert_with_tags_by_name(iter_, text, *tags)
def echo(self, text, tag="command_ok"):
"""
Print given text to the user, as a regular command output.
"""
self.shift_line()
self.append_with_tags(text, tag)
def echo_error(self, text):
"""
Print given text to the user, as an error command output.
"""
self.echo(text, "command_error")
def send(self, text):
"""
Send a message to the contact.
"""
self.send_message(text, process_commands=False)
def set_input(self, text):
"""
Set given text into the input.
"""
buffer = self.msg_textview.get_buffer()
buffer.set_text(text)
def clear_input(self):
"""
Clear input.
"""
self.set_input(str())
def add_history(self, text):
"""
Add given text to the input history, so user can scroll through
it using ctrl + up/down arrow keys.
"""
self.save_message(text, 'sent')
@property
def connection(self):
"""
Get the current connection object.
"""
return app.connections[self.account]
@property
def full_jid(self):
"""
Get a full JID of the contact.
"""
return self.contact.get_full_jid()

View file

@ -0,0 +1,433 @@
# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Provides an actual implementation for the standard commands.
"""
from time import localtime
from time import strftime
from datetime import date
from gi.repository import GLib
from gajim.common import app
from gajim.common import helpers
from gajim.common.i18n import _
from gajim.common.const import KindConstant
from gajim.command_system.errors import CommandError
from gajim.command_system.framework import CommandContainer
from gajim.command_system.framework import command
from gajim.command_system.framework import doc
from gajim.command_system.mapping import generate_usage
from gajim.command_system.implementation.hosts import ChatCommands
from gajim.command_system.implementation.hosts import PrivateChatCommands
from gajim.command_system.implementation.hosts import GroupChatCommands
class StandardCommonCommands(CommandContainer):
"""
This command container contains standard commands which are common
to all - chat, private chat, group chat.
"""
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
@command(overlap=True)
@doc(_("Show help on a given command or a list of available commands if "
"-a is given"))
def help(self, cmd=None, all_=False):
if cmd:
cmd = self.get_command(cmd)
documentation = _(cmd.extract_documentation())
usage = generate_usage(cmd)
text = []
if documentation:
text.append(documentation)
if cmd.usage:
text.append(usage)
return '\n\n'.join(text)
if all_:
for cmd_ in self.list_commands():
names = ', '.join(cmd_.names)
description = cmd_.extract_description()
self.echo("%s - %s" % (names, description))
else:
help_ = self.get_command('help')
self.echo(help_(self, 'help'))
@command(raw=True)
@doc(_("Send a message to the contact"))
def say(self, message):
self.send(message)
@command(raw=True)
@doc(_("Send action (in the third person) to the current chat"))
def me(self, action):
self.send("/me %s" % action)
@command('lastlog', overlap=True)
@doc(_("Show logged messages which mention given text"))
def grep(self, text, limit=None):
results = app.storage.archive.search_log(self.account, self.contact.jid, text)
if not results:
raise CommandError(_("%s: Nothing found") % text)
if limit:
try:
results = results[len(results) - int(limit):]
except ValueError:
raise CommandError(_("Limit must be an integer"))
for row in results:
contact = row.contact_name
if not contact:
if row.kind == KindConstant.CHAT_MSG_SENT:
contact = app.nicks[self.account]
else:
contact = self.contact.name
time_obj = localtime(row.time)
date_obj = date.fromtimestamp(row.time)
date_ = strftime('%Y-%m-%d', time_obj)
time_ = strftime('%H:%M:%S', time_obj)
if date_obj == date.today():
formatted = "[%s] %s: %s" % (time_, contact, row.message)
else:
formatted = "[%s, %s] %s: %s" % (
date_, time_, contact, row.message)
self.echo(formatted)
@command(raw=True, empty=True)
# Do not translate online, away, chat, xa, dnd
@doc(_("""
Set the current status
Status can be given as one of the following values:
online, away, chat, xa, dnd.
"""))
def status(self, status, message):
if status not in ('online', 'away', 'chat', 'xa', 'dnd'):
raise CommandError("Invalid status given")
for connection in app.connections.values():
if not app.settings.get_account_setting(connection.name,
'sync_with_global_status'):
continue
if not connection.state.is_available:
continue
connection.change_status(status, message)
@command(raw=True, empty=True)
@doc(_("Set the current status to away"))
def away(self, message):
if not message:
message = _("Away")
for connection in app.connections.values():
if not app.settings.get_account_setting(connection.name,
'sync_with_global_status'):
continue
if not connection.state.is_available:
continue
connection.change_status('away', message)
@command('back', raw=True, empty=True)
@doc(_("Set the current status to online"))
def online(self, message):
if not message:
message = _("Available")
for connection in app.connections.values():
if not app.settings.get_account_setting(connection.name,
'sync_with_global_status'):
continue
if not connection.state.is_available:
continue
connection.change_status('online', message)
@command
@doc(_("Send a disco info request"))
def disco(self):
client = app.get_client(self.account)
if not client.state.is_available:
return
client.get_module('Discovery').disco_contact(self.contact)
class StandardCommonChatCommands(CommandContainer):
"""
This command container contains standard commands, which are common
to a chat and a private chat only.
"""
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands
@command
@doc(_("Clear the text window"))
def clear(self):
self.conv_textview.clear()
@command
@doc(_("Send a ping to the contact"))
def ping(self):
if self.account == app.ZEROCONF_ACC_NAME:
raise CommandError(
_('Command is not supported for zeroconf accounts'))
app.connections[self.account].get_module('Ping').send_ping(self.contact)
@command
@doc(_("Send DTMF sequence through an open voice chat"))
def dtmf(self, sequence):
if not self.audio_sid:
raise CommandError(_("No open voice chats with the contact"))
for tone in sequence:
if not (tone in ("*", "#") or tone.isdigit()):
raise CommandError(_("%s is not a valid tone") % tone)
gjs = self.connection.get_module('Jingle').get_jingle_session
session = gjs(self.full_jid, self.audio_sid)
content = session.get_content("audio")
content.batch_dtmf(sequence)
@command
@doc(_("Toggle Voice Chat"))
def audio(self):
if not self.audio_available:
raise CommandError(_("Voice chats are not available"))
# An audio session is toggled by inverting the state of the
# appropriate button.
state = self._audio_button.get_active()
self._audio_button.set_active(not state)
@command
@doc(_("Toggle Video Chat"))
def video(self):
if not self.video_available:
raise CommandError(_("Video chats are not available"))
# A video session is toggled by inverting the state of the
# appropriate button.
state = self._video_button.get_active()
self._video_button.set_active(not state)
@command(raw=True)
@doc(_("Send a message to the contact that will attract their attention"))
def attention(self, message):
self.send_message(message, process_commands=False, attention=True)
class StandardChatCommands(CommandContainer):
"""
This command container contains standard commands which are unique
to a chat.
"""
AUTOMATIC = True
HOSTS = (ChatCommands,)
class StandardPrivateChatCommands(CommandContainer):
"""
This command container contains standard commands which are unique
to a private chat.
"""
AUTOMATIC = True
HOSTS = (PrivateChatCommands,)
class StandardGroupChatCommands(CommandContainer):
"""
This command container contains standard commands which are unique
to a group chat.
"""
AUTOMATIC = True
HOSTS = (GroupChatCommands,)
@command
@doc(_("Clear the text window"))
def clear(self):
self.conv_textview.clear()
@command(raw=True)
@doc(_("Change your nickname in a group chat"))
def nick(self, new_nick):
try:
new_nick = helpers.parse_resource(new_nick)
except Exception:
raise CommandError(_("Invalid nickname"))
# FIXME: Check state of MUC
self.connection.get_module('MUC').change_nick(
self.room_jid, new_nick)
self.new_nick = new_nick
@command('query', raw=True)
@doc(_("Open a private chat window with a specified participant"))
def chat(self, nick):
nicks = app.contacts.get_nick_list(self.account, self.room_jid)
if nick in nicks:
self.send_pm(nick)
else:
raise CommandError(_("Nickname not found"))
@command('msg', raw=True)
@doc(_("Open a private chat window with a specified participant and send "
"him a message"))
def message(self, nick, message):
nicks = app.contacts.get_nick_list(self.account, self.room_jid)
if nick in nicks:
self.send_pm(nick, message)
else:
raise CommandError(_("Nickname not found"))
@command(raw=True, empty=True)
@doc(_("Display or change a group chat topic"))
def topic(self, new_topic):
if new_topic:
self.connection.get_module('MUC').set_subject(
self.room_jid, new_topic)
else:
return self.subject
@command(raw=True, empty=True)
@doc(_("Invite a user to a group chat for a reason"))
def invite(self, jid, reason):
control = app.get_groupchat_control(self.account, self.room_jid)
if control is not None:
control.invite(jid)
@command(raw=True, empty=True)
@doc(_("Join a group chat given by an XMPP Address"))
def join(self, jid):
if '@' not in jid:
jid = jid + '@' + app.get_server_from_jid(self.room_jid)
app.app.activate_action(
'groupchat-join',
GLib.Variant('as', [self.account, jid]))
@command('part', 'close', raw=True, empty=True)
@doc(_("Leave the group chat, optionally giving a reason, and close tab or "
"window"))
def leave(self, reason):
self.leave(reason=reason)
@command(raw=True, empty=True)
@doc(_("""
Ban user by a nick or a JID from a groupchat
If given nickname is not found it will be treated as a JID.
"""))
def ban(self, who, reason=''):
if who in app.contacts.get_nick_list(self.account, self.room_jid):
contact = app.contacts.get_gc_contact(
self.account, self.room_jid, who)
who = contact.jid
self.connection.get_module('MUC').set_affiliation(
self.room_jid,
{who: {'affiliation': 'outcast',
'reason': reason}})
@command(raw=True, empty=True)
@doc(_("Kick user from group chat by nickname"))
def kick(self, who, reason):
if who not in app.contacts.get_nick_list(self.account, self.room_jid):
raise CommandError(_("Nickname not found"))
self.connection.get_module('MUC').set_role(
self.room_jid, who, 'none', reason)
@command(raw=True)
# Do not translate moderator, participant, visitor, none
@doc(_("""Set participant role in group chat.
Role can be given as one of the following values:
moderator, participant, visitor, none"""))
def role(self, who, role):
if role not in ('moderator', 'participant', 'visitor', 'none'):
raise CommandError(_("Invalid role given"))
if who not in app.contacts.get_nick_list(self.account, self.room_jid):
raise CommandError(_("Nickname not found"))
self.connection.get_module('MUC').set_role(self.room_jid, who, role)
@command(raw=True)
# Do not translate owner, admin, member, outcast, none
@doc(_("""Set participant affiliation in group chat.
Affiliation can be given as one of the following values:
owner, admin, member, outcast, none"""))
def affiliate(self, who, affiliation):
if affiliation not in ('owner', 'admin', 'member', 'outcast', 'none'):
raise CommandError(_("Invalid affiliation given"))
if who not in app.contacts.get_nick_list(self.account, self.room_jid):
raise CommandError(_("Nickname not found"))
contact = app.contacts.get_gc_contact(self.account, self.room_jid, who)
self.connection.get_module('MUC').set_affiliation(
self.room_jid,
{contact.jid: {'affiliation': affiliation}})
@command
@doc(_("Display names of all group chat participants"))
def names(self, verbose=False):
ggc = app.contacts.get_gc_contact
gnl = app.contacts.get_nick_list
get_contact = lambda nick: ggc(self.account, self.room_jid, nick)
get_role = lambda nick: get_contact(nick).role
nicks = gnl(self.account, self.room_jid)
nicks = sorted(nicks)
nicks = sorted(nicks, key=get_role)
if not verbose:
return ", ".join(nicks)
for nick in nicks:
contact = get_contact(nick)
role = helpers.get_uf_role(contact.role)
affiliation = helpers.get_uf_affiliation(contact.affiliation)
self.echo("%s - %s - %s" % (nick, role, affiliation))
@command('ignore', raw=True)
@doc(_("Forbid a participant to send you public or private messages"))
def block(self, who):
self.on_block(None, who)
@command('unignore', raw=True)
@doc(_("Allow a participant to send you public or private messages"))
def unblock(self, who):
self.on_unblock(None, who)
@command
@doc(_("Send a ping to the contact"))
def ping(self, nick):
if self.account == app.ZEROCONF_ACC_NAME:
raise CommandError(
_('Command is not supported for zeroconf accounts'))
gc_c = app.contacts.get_gc_contact(self.account, self.room_jid, nick)
if gc_c is None:
raise CommandError(_("Unknown nickname"))
app.connections[self.account].get_module('Ping').send_ping(gc_c)