gajim3/gajim/command_system/implementation/standard.py

434 lines
15 KiB
Python

# 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)