commands.py 33.7 KB
Newer Older
mathieui's avatar
mathieui committed
1 2 3 4 5 6 7 8
"""
Global commands which are to be linked to the Core class
"""

import logging

log = logging.getLogger(__name__)

9
import functools
10
import os
mathieui's avatar
mathieui committed
11 12 13 14 15
import sys
from datetime import datetime
from gettext import gettext as _
from xml.etree import cElementTree as ET

louiz’'s avatar
louiz’ committed
16 17 18
from slixmpp.xmlstream.stanzabase import StanzaBase
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
mathieui's avatar
mathieui committed
19 20 21 22 23 24 25

import bookmark
import common
import fixes
import pep
import tabs
from common import safeJID
Eijebong's avatar
Eijebong committed
26
from config import config, DEFAULT_CONFIG, options as config_opts
mathieui's avatar
mathieui committed
27
import multiuserchat as muc
28
from plugin import PluginConfig
mathieui's avatar
mathieui committed
29 30
from roster import roster
from theming import dump_tuple, get_theme
31
from decorators import command_args_parser
mathieui's avatar
mathieui committed
32 33 34 35

from . structs import Command, possible_show


36 37
@command_args_parser.quoted(0, 1)
def command_help(self, args):
mathieui's avatar
mathieui committed
38
    """
39
    /help [command_name]
mathieui's avatar
mathieui committed
40 41 42 43 44 45 46
    """
    if not args:
        color = dump_tuple(get_theme().COLOR_HELP_COMMANDS)
        acc = []
        buff = ['Global commands:']
        for command in self.commands:
            if isinstance(self.commands[command], Command):
47 48 49 50
                acc.append('  \x19%s}%s\x19o - %s' % (
                               color,
                               command,
                               self.commands[command].short))
mathieui's avatar
mathieui committed
51 52 53 54 55 56 57 58 59
            else:
                acc.append('  \x19%s}%s\x19o' % (color, command))
        acc = sorted(acc)
        buff.extend(acc)
        acc = []
        buff.append('Tab-specific commands:')
        commands = self.current_tab().commands
        for command in commands:
            if isinstance(commands[command], Command):
60 61 62 63
                acc.append('  \x19%s}%s\x19o - %s' % (
                                color,
                                command,
                                commands[command].short))
mathieui's avatar
mathieui committed
64 65 66 67 68 69 70
            else:
                acc.append('  \x19%s}%s\x19o' % (color, command))
        acc = sorted(acc)
        buff.extend(acc)

        msg = '\n'.join(buff)
        msg += _("\nType /help <command_name> to know what each command does")
71
    else:
mathieui's avatar
mathieui committed
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
        command = args[0].lstrip('/').strip()

        if command in self.current_tab().commands:
            tup = self.current_tab().commands[command]
        elif command in self.commands:
            tup = self.commands[command]
        else:
            self.information(_('Unknown command: %s') % command, 'Error')
            return
        if isinstance(tup, Command):
            msg = _('Usage: /%s %s\n' % (command, tup.usage))
            msg += tup.desc
        else:
            msg = tup[1]
    self.information(msg, 'Help')

88 89
@command_args_parser.quoted(1)
def command_runkey(self, args):
mathieui's avatar
mathieui committed
90 91 92 93
    """
    /runkey <key>
    """
    def replace_line_breaks(key):
94
        "replace ^J with \n"
mathieui's avatar
mathieui committed
95 96 97
        if key == '^J':
            return '\n'
        return key
98 99 100
    if args is None:
        return self.command_help('runkey')
    char = args[0]
mathieui's avatar
mathieui committed
101 102 103 104 105 106 107 108
    func = self.key_func.get(char, None)
    if func:
        func()
    else:
        res = self.do_command(replace_line_breaks(char), False)
        if res:
            self.refresh_window()

109 110
@command_args_parser.quoted(1, 1, [None])
def command_status(self, args):
mathieui's avatar
mathieui committed
111 112 113
    """
    /status <status> [msg]
    """
114 115 116
    if args is None:
        return self.command_help('status')

mathieui's avatar
mathieui committed
117
    if not args[0] in possible_show.keys():
118 119
        return self.command_help('status')

mathieui's avatar
mathieui committed
120
    show = possible_show[args[0]]
121 122
    msg = args[1]

mathieui's avatar
mathieui committed
123 124 125 126 127 128 129
    pres = self.xmpp.make_presence()
    if msg:
        pres['status'] = msg
    pres['type'] = show
    self.events.trigger('send_normal_presence', pres)
    pres.send()
    current = self.current_tab()
130 131
    is_muctab = isinstance(current, tabs.MucTab)
    if is_muctab and current.joined and show in ('away', 'xa'):
mathieui's avatar
mathieui committed
132 133 134 135 136 137 138
        current.send_chat_state('inactive')
    for tab in self.tabs:
        if isinstance(tab, tabs.MucTab) and tab.joined:
            muc.change_show(self.xmpp, tab.name, tab.own_nick, show, msg)
        if hasattr(tab, 'directed_presence'):
            del tab.directed_presence
    self.set_status(show, msg)
139
    if is_muctab and current.joined and show not in ('away', 'xa'):
mathieui's avatar
mathieui committed
140 141
        current.send_chat_state('active')

142 143
@command_args_parser.quoted(1, 2, [None, None])
def command_presence(self, args):
mathieui's avatar
mathieui committed
144 145 146
    """
    /presence <JID> [type] [status]
    """
147 148 149 150
    if args is None:
        return self.command_help('presence')

    jid, type, status = args[0], args[1], args[2]
mathieui's avatar
mathieui committed
151
    if jid == '.' and isinstance(self.current_tab(), tabs.ChatTab):
152
        jid = self.current_tab().name
mathieui's avatar
mathieui committed
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
    if type == 'available':
        type = None
    try:
        pres = self.xmpp.make_presence(pto=jid, ptype=type, pstatus=status)
        self.events.trigger('send_normal_presence', pres)
        pres.send()
    except:
        self.information(_('Could not send directed presence'), 'Error')
        log.debug('Could not send directed presence to %s', jid, exc_info=True)
        return
    tab = self.get_tab_by_name(jid)
    if tab:
        if type in ('xa', 'away'):
            tab.directed_presence = False
            chatstate = 'inactive'
        else:
            tab.directed_presence = True
            chatstate = 'active'
        if tab == self.current_tab():
            tab.send_chat_state(chatstate, True)
        if isinstance(tab, tabs.MucTab):
            for private in tab.privates:
                private.directed_presence = tab.directed_presence
            if self.current_tab() in tab.privates:
                self.current_tab().send_chat_state(chatstate, True)

179 180
@command_args_parser.quoted(1)
def command_theme(self, args=None):
mathieui's avatar
mathieui committed
181
    """/theme <theme name>"""
182 183 184
    if args is None:
        return self.command_help('theme')
    self.command_set('theme %s' % (args[0],))
mathieui's avatar
mathieui committed
185

186 187
@command_args_parser.quoted(1)
def command_win(self, args):
mathieui's avatar
mathieui committed
188 189 190
    """
    /win <number>
    """
191 192 193 194
    if args is None:
        return self.command_help('win')

    nb = args[0]
mathieui's avatar
mathieui committed
195
    try:
196
        nb = int(nb)
mathieui's avatar
mathieui committed
197
    except ValueError:
198
        pass
mathieui's avatar
mathieui committed
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
    if self.current_tab_nb == nb:
        return
    self.previous_tab_nb = self.current_tab_nb
    old_tab = self.current_tab()
    if isinstance(nb, int):
        if 0 <= nb < len(self.tabs):
            if not self.tabs[nb]:
                return
            self.current_tab_nb = nb
    else:
        matchs = []
        for tab in self.tabs:
            for name in tab.matching_names():
                if nb.lower() in name[1].lower():
                    matchs.append((name[0], tab))
                    self.current_tab_nb = tab.nb
        if not matchs:
            return
        tab = min(matchs, key=lambda m: m[0])[1]
        self.current_tab_nb = tab.nb
    old_tab.on_lose_focus()
    self.current_tab().on_gain_focus()
    self.refresh_window()

223 224
@command_args_parser.quoted(2)
def command_move_tab(self, args):
mathieui's avatar
mathieui committed
225 226 227
    """
    /move_tab old_pos new_pos
    """
228
    if args is None:
mathieui's avatar
mathieui committed
229
        return self.command_help('move_tab')
230

231
    current_tab = self.current_tab()
232 233 234 235 236
    if args[0] == '.':
        args[0] = current_tab.nb
    if args[1] == '.':
        args[1] = current_tab.nb

mathieui's avatar
mathieui committed
237
    def get_nb_from_value(value):
238
        "parse the cmdline to guess the tab the users wants"
mathieui's avatar
mathieui committed
239 240 241 242 243 244
        ref = None
        try:
            ref = int(value)
        except ValueError:
            old_tab = None
            for tab in self.tabs:
245
                if not old_tab and value == tab.name:
mathieui's avatar
mathieui committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
                    old_tab = tab
            if not old_tab:
                self.information("Tab %s does not exist" % args[0], "Error")
                return None
            ref = old_tab.nb
        return ref
    old = get_nb_from_value(args[0])
    new = get_nb_from_value(args[1])
    if new is None or old is None:
        return self.information('Unable to move the tab.', 'Info')
    result = self.insert_tab(old, new)
    if not result:
        self.information('Unable to move the tab.', 'Info')
    else:
        self.current_tab_nb = self.tabs.index(current_tab)
    self.refresh_window()

263 264
@command_args_parser.quoted(0, 1)
def command_list(self, args):
mathieui's avatar
mathieui committed
265
    """
266
    /list [server]
mathieui's avatar
mathieui committed
267 268
    Opens a MucListTab containing the list of the room in the specified server
    """
269
    if args is None:
mathieui's avatar
mathieui committed
270
        return self.command_help('list')
271 272
    elif args:
        server = safeJID(args[0]).server
mathieui's avatar
mathieui committed
273 274 275
    else:
        if not isinstance(self.current_tab(), tabs.MucTab):
            return self.information('Please provide a server', 'Error')
276
        server = safeJID(self.current_tab().name).server
mathieui's avatar
mathieui committed
277 278
    list_tab = tabs.MucListTab(server)
    self.add_tab(list_tab, True)
279 280 281
    cb = list_tab.on_muc_list_item_received
    self.xmpp.plugin['xep_0030'].get_items(jid=server,
                                           callback=cb)
mathieui's avatar
mathieui committed
282

283 284
@command_args_parser.quoted(1)
def command_version(self, args):
mathieui's avatar
mathieui committed
285 286 287 288
    """
    /version <jid>
    """
    def callback(res):
289
        "Callback for /version"
mathieui's avatar
mathieui committed
290
        if not res:
291 292 293 294 295 296 297 298
            return self.information(_('Could not get the software'
                                      ' version from %s') % jid,
                                    _('Warning'))
        version = _('%s is running %s version %s on %s') % (
                        jid,
                        res.get('name') or _('an unknown software'),
                        res.get('version') or _('unknown'),
                        res.get('os') or _('an unknown platform'))
mathieui's avatar
mathieui committed
299 300
        self.information(version, 'Info')

301
    if args is None:
mathieui's avatar
mathieui committed
302
        return self.command_help('version')
303

mathieui's avatar
mathieui committed
304 305 306 307 308 309 310 311 312
    jid = safeJID(args[0])
    if jid.resource or jid not in roster:
        fixes.get_version(self.xmpp, jid, callback=callback)
    elif jid in roster:
        for resource in roster[jid].resources:
            fixes.get_version(self.xmpp, resource.jid, callback=callback)
        else:
            fixes.get_version(self.xmpp, jid, callback=callback)

313 314
@command_args_parser.quoted(0, 2)
def command_join(self, args, histo_length=None):
mathieui's avatar
mathieui committed
315 316 317 318 319 320
    """
    /join [room][/nick] [password]
    """
    password = None
    if len(args) == 0:
        tab = self.current_tab()
321
        if not isinstance(tab, (tabs.MucTab, tabs.PrivateTab)):
mathieui's avatar
mathieui committed
322
            return
323
        room = safeJID(tab.name).bare
mathieui's avatar
mathieui committed
324 325 326 327 328 329 330 331 332 333 334
        nick = tab.own_nick
    else:
        if args[0].startswith('@'): # we try to join a server directly
            server_root = True
            info = safeJID(args[0][1:])
        else:
            info = safeJID(args[0])
            server_root = False
        if info == '' and len(args[0]) > 1 and args[0][0] == '/':
            nick = args[0][1:]
        elif info.resource == '':
335
            nick = self.own_nick
mathieui's avatar
mathieui committed
336 337 338 339 340 341
        else:
            nick = info.resource
        if info.bare == '':   # happens with /join /nickname, which is OK
            tab = self.current_tab()
            if not isinstance(tab, tabs.MucTab):
                return
342
            room = tab.name
mathieui's avatar
mathieui committed
343 344 345 346
            if nick == '':
                nick = tab.own_nick
        else:
            room = info.bare
347 348 349 350
            # no server is provided, like "/join hello":
            # use the server of the current room if available
            # check if the current room's name has a server
            if room.find('@') == -1 and not server_root:
mathieui's avatar
mathieui committed
351
                if isinstance(self.current_tab(), tabs.MucTab) and\
352 353
                        self.current_tab().name.find('@') != -1:
                    domain = safeJID(self.current_tab().name).domain
354
                    room += '@%s' % domain
mathieui's avatar
mathieui committed
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
                else:
                    room = args[0]
    room = room.lower()
    if room in self.pending_invites:
        del self.pending_invites[room]
    tab = self.get_tab_by_name(room, tabs.MucTab)
    if len(args) == 2:       # a password is provided
        password = args[1]
    if tab and tab.joined:       # if we are already in the room
        self.focus_tab_named(tab.name)
        if tab.own_nick == nick:
            self.information('/join: Nothing to do.', 'Info')
        else:
            tab.own_nick = nick
            tab.command_cycle('')
        return

    if room.startswith('@'):
        room = room[1:]
    current_status = self.get_status()
    if not histo_length:
376
        histo_length = config.get('muc_history_length')
mathieui's avatar
mathieui committed
377 378 379 380 381
        if histo_length == -1:
            histo_length = None
    if histo_length is not None:
        histo_length = str(histo_length)
    if password is None: # try to use a saved password
382
        password = config.get_by_tabname('password', room, fallback=False)
mathieui's avatar
mathieui committed
383 384
    if tab and not tab.joined:
        if tab.last_connection:
385 386 387 388 389
            if tab.last_connection is not None:
                delta = datetime.now() - tab.last_connection
                seconds = delta.seconds + delta.days * 24 * 3600
            else:
                seconds = 0
mathieui's avatar
mathieui committed
390 391 392 393
            seconds = int(seconds)
        else:
            seconds = 0
        muc.join_groupchat(self, room, nick, password,
394 395 396 397
                           histo_length,
                           current_status.message,
                           current_status.show,
                           seconds=seconds)
mathieui's avatar
mathieui committed
398 399 400
    if not tab:
        self.open_new_room(room, nick)
        muc.join_groupchat(self, room, nick, password,
401 402 403
                           histo_length,
                           current_status.message,
                           current_status.show)
mathieui's avatar
mathieui committed
404 405 406 407 408 409 410 411 412 413
    else:
        tab.own_nick = nick
        tab.users = []
    if tab and tab.joined:
        self.enable_private_tabs(room)
        tab.state = "normal"
        if tab == self.current_tab():
            tab.refresh()
            self.doupdate()

414 415
@command_args_parser.quoted(0, 2)
def command_bookmark_local(self, args):
mathieui's avatar
mathieui committed
416 417 418 419 420 421 422 423 424
    """
    /bookmark_local [room][/nick] [password]
    """
    nick = None
    password = None
    if not args and not isinstance(self.current_tab(), tabs.MucTab):
        return
    if not args:
        tab = self.current_tab()
425
        roomname = tab.name
mathieui's avatar
mathieui committed
426 427 428 429 430
        if tab.joined and tab.own_nick != self.own_nick:
            nick = tab.own_nick
    elif args[0] == '*':
        new_bookmarks = []
        for tab in self.get_tabs(tabs.MucTab):
431
            b = bookmark.get_by_jid(tab.name)
mathieui's avatar
mathieui committed
432
            if not b:
433
                b = bookmark.Bookmark(tab.name,
434 435
                                      autojoin=True,
                                      method="local")
mathieui's avatar
mathieui committed
436 437 438 439 440 441 442 443
                new_bookmarks.append(b)
            else:
                b.method = "local"
                new_bookmarks.append(b)
                bookmark.bookmarks.remove(b)
        new_bookmarks.extend(bookmark.bookmarks)
        bookmark.bookmarks = new_bookmarks
        bookmark.save_local()
444
        bookmark.save_remote(self.xmpp, None)
mathieui's avatar
mathieui committed
445 446 447 448 449 450 451 452 453 454
        self.information('Bookmarks added and saved.', 'Info')
        return
    else:
        info = safeJID(args[0])
        if info.resource != '':
            nick = info.resource
        roomname = info.bare
        if not roomname:
            if not isinstance(self.current_tab(), tabs.MucTab):
                return
455
            roomname = self.current_tab().name
mathieui's avatar
mathieui committed
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
        if len(args) > 1:
            password = args[1]

    bm = bookmark.get_by_jid(roomname)
    if not bm:
        bm = bookmark.Bookmark(jid=roomname)
        bookmark.bookmarks.append(bm)
        self.information('Bookmark added.', 'Info')
    else:
        self.information('Bookmark updated.', 'Info')
    if nick:
        bm.nick = nick
    bm.autojoin = True
    bm.password = password
    bm.method = "local"
    bookmark.save_local()
    self.information(_('Your local bookmarks are now: %s') %
            [b for b in bookmark.bookmarks if b.method == 'local'], 'Info')

475 476
@command_args_parser.quoted(0, 3)
def command_bookmark(self, args):
mathieui's avatar
mathieui committed
477 478 479
    """
    /bookmark [room][/nick] [autojoin] [password]
    """
480
    if not config.get('use_remote_bookmarks'):
481 482
        return self.command_bookmark_local(" ".join(args))

mathieui's avatar
mathieui committed
483 484 485 486 487
    nick = None
    if not args and not isinstance(self.current_tab(), tabs.MucTab):
        return
    if not args:
        tab = self.current_tab()
488
        roomname = tab.name
mathieui's avatar
mathieui committed
489 490 491 492 493 494 495 496 497 498 499
        if tab.joined:
            nick = tab.own_nick
        autojoin = True
        password = None
    elif args[0] == '*':
        if len(args) > 1:
            autojoin = False if args[1].lower() != 'true' else True
        else:
            autojoin = True
        new_bookmarks = []
        for tab in self.get_tabs(tabs.MucTab):
500
            b = bookmark.get_by_jid(tab.name)
mathieui's avatar
mathieui committed
501
            if not b:
502
                b = bookmark.Bookmark(tab.name, autojoin=autojoin,
mathieui's avatar
mathieui committed
503 504 505 506 507 508 509 510
                        method=bookmark.preferred)
                new_bookmarks.append(b)
            else:
                b.method = bookmark.preferred
                bookmark.bookmarks.remove(b)
                new_bookmarks.append(b)
        new_bookmarks.extend(bookmark.bookmarks)
        bookmark.bookmarks = new_bookmarks
511 512 513 514 515 516 517
        def _cb(self, iq):
            if iq["type"] != "error":
                bookmark.save_local()
                self.information("Bookmarks added.", "Info")
            else:
                self.information("Could not add the bookmarks.", "Info")
        bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
mathieui's avatar
mathieui committed
518 519 520 521 522 523 524 525 526
        return
    else:
        info = safeJID(args[0])
        if info.resource != '':
            nick = info.resource
        roomname = info.bare
        if roomname == '':
            if not isinstance(self.current_tab(), tabs.MucTab):
                return
527
            roomname = self.current_tab().name
mathieui's avatar
mathieui committed
528 529 530 531 532 533 534 535 536 537 538 539
        if len(args) > 1:
            autojoin = False if args[1].lower() != 'true' else True
        else:
            autojoin = True
        if len(args) > 2:
            password = args[2]
        else:
            password = None
    bm = bookmark.get_by_jid(roomname)
    if not bm:
        bm = bookmark.Bookmark(roomname)
        bookmark.bookmarks.append(bm)
540
    bm.method = config.get('use_bookmarks_method')
mathieui's avatar
mathieui committed
541 542 543 544 545
    if nick:
        bm.nick = nick
    if password:
        bm.password = password
    bm.autojoin = autojoin
546 547 548 549 550 551
    def _cb(self, iq):
        if iq["type"] != "error":
            self.information('Bookmark added.', 'Info')
        else:
            self.information("Could not add the bookmarks.", "Info")
    bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
552 553 554 555
    remote = []
    for each in bookmark.bookmarks:
        if each.method in ('pep', 'privatexml'):
            remote.append(each)
mathieui's avatar
mathieui committed
556

557 558
@command_args_parser.ignored
def command_bookmarks(self):
mathieui's avatar
mathieui committed
559
    """/bookmarks"""
560 561 562 563 564 565 566 567 568 569 570 571
    local = []
    remote = []
    for each in bookmark.bookmarks:
        if each.method in ('pep', 'privatexml'):
            remote.append(each)
        elif each.method == 'local':
            local.append(each)

    self.information(_('Your remote bookmarks are: %s') % remote,
                     _('Info'))
    self.information(_('Your local bookmarks are: %s') % local,
                     _('Info'))
mathieui's avatar
mathieui committed
572

573 574
@command_args_parser.quoted(0, 1)
def command_remove_bookmark(self, args):
mathieui's avatar
mathieui committed
575
    """/remove_bookmark [jid]"""
576

mathieui's avatar
mathieui committed
577 578
    if not args:
        tab = self.current_tab()
579 580
        if isinstance(tab, tabs.MucTab) and bookmark.get_by_jid(tab.name):
            bookmark.remove(tab.name)
mathieui's avatar
mathieui committed
581 582 583 584 585 586 587 588 589 590 591 592 593 594
            bookmark.save(self.xmpp)
            if bookmark.save(self.xmpp):
                self.information('Bookmark deleted', 'Info')
        else:
            self.information('No bookmark to remove', 'Info')
    else:
        if bookmark.get_by_jid(args[0]):
            bookmark.remove(args[0])
            if bookmark.save(self.xmpp):
                self.information('Bookmark deleted', 'Info')

        else:
            self.information('No bookmark to remove', 'Info')

595 596
@command_args_parser.quoted(1, 2)
def command_set(self, args):
mathieui's avatar
mathieui committed
597
    """
598
    /set [module|][section] <option> [value]
mathieui's avatar
mathieui committed
599
    """
600
    if len(args) == 1:
mathieui's avatar
mathieui committed
601
        option = args[0]
602 603 604 605 606 607 608 609 610
        value = config.get(option)
        info = ('%s=%s' % (option, value), 'Info')
    elif len(args) == 2:
        if '|' in args[0]:
            plugin_name, section = args[0].split('|')[:2]
            if not section:
                section = plugin_name
            option = args[1]
            if not plugin_name in self.plugin_manager.plugins:
611 612 613 614 615 616
                file_name = self.plugin_manager.plugins_conf_dir
                file_name = os.path.join(file_name, plugin_name + '.cfg')
                plugin_config = PluginConfig(file_name, plugin_name)
            else:
                plugin_config = self.plugin_manager.plugins[plugin_name].config
            value = plugin_config.get(option, default='', section=section)
617 618 619 620 621 622 623 624 625 626 627 628 629
            info = ('%s=%s' % (option, value), 'Info')
        else:
            possible_section = args[0]
            if config.has_section(possible_section):
                section = possible_section
                option = args[1]
                value = config.get(option, section=section)
                info = ('%s=%s' % (option, value), 'Info')
            else:
                option = args[0]
                value = args[1]
                info = config.set_and_save(option, value)
                self.trigger_configuration_change(option, value)
mathieui's avatar
mathieui committed
630 631 632 633 634 635 636 637
    elif len(args) == 3:
        if '|' in args[0]:
            plugin_name, section = args[0].split('|')[:2]
            if not section:
                section = plugin_name
            option = args[1]
            value = args[2]
            if not plugin_name in self.plugin_manager.plugins:
638 639 640 641 642 643
                file_name = self.plugin_manager.plugins_conf_dir
                file_name = os.path.join(file_name, plugin_name + '.cfg')
                plugin_config = PluginConfig(file_name, plugin_name)
            else:
                plugin_config = self.plugin_manager.plugins[plugin_name].config
            info = plugin_config.set_and_save(option, value, section)
mathieui's avatar
mathieui committed
644 645 646 647 648 649
        else:
            section = args[0]
            option = args[1]
            value = args[2]
            info = config.set_and_save(option, value, section)
            self.trigger_configuration_change(option, value)
650
    else:
Eijebong's avatar
Eijebong committed
651
        return self.command_help('set')
mathieui's avatar
mathieui committed
652 653 654
    self.call_for_resize()
    self.information(*info)

Eijebong's avatar
Eijebong committed
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
@command_args_parser.quoted(1, 2)
def command_set_default(self, args):
    """
    /set_default [section] <option>
    """
    if len(args) == 1:
        option = args[0]
        section = 'Poezio'
    elif len(args) == 2:
        section = args[0]
        option = args[1]
    else:
        return self.command_help('set_default')

    default_config = DEFAULT_CONFIG.get(section, tuple())
    if option not in default_config:
        info = ("Option %s has no default value" % (option), "Error")
        return self.information(*info)
    self.command_set('%s %s %s' % (section, option, default_config[option]))

675 676
@command_args_parser.quoted(1)
def command_toggle(self, args):
mathieui's avatar
mathieui committed
677 678 679 680
    """
    /toggle <option>
    shortcut for /set <option> toggle
    """
681 682 683 684
    if args is None:
        return self.command_help('toggle')

    if args[0]:
mathieui's avatar
mathieui committed
685
        self.command_set('%s toggle' % args[0])
mathieui's avatar
mathieui committed
686

687 688
@command_args_parser.quoted(1, 1)
def command_server_cycle(self, args):
mathieui's avatar
mathieui committed
689
    """
690 691
    Do a /cycle on each room of the given server.
    If none, do it on the current tab
mathieui's avatar
mathieui committed
692 693 694
    """
    tab = self.current_tab()
    message = ""
695
    if args:
mathieui's avatar
mathieui committed
696
        domain = args[0]
697
        if len(args) == 2:
mathieui's avatar
mathieui committed
698 699 700
            message = args[1]
    else:
        if isinstance(tab, tabs.MucTab):
701
            domain = safeJID(tab.name).domain
mathieui's avatar
mathieui committed
702
        else:
703
            return self.information(_("No server specified"), "Error")
mathieui's avatar
mathieui committed
704
    for tab in self.get_tabs(tabs.MucTab):
705
        if tab.name.endswith(domain):
mathieui's avatar
mathieui committed
706
            if tab.joined:
707
                muc.leave_groupchat(tab.core.xmpp,
708
                                    tab.name,
709 710
                                    tab.own_nick,
                                    message)
mathieui's avatar
mathieui committed
711
            tab.joined = False
712 713
            if tab.name == domain:
                self.command_join('"@%s/%s"' %(tab.name, tab.own_nick))
mathieui's avatar
mathieui committed
714
            else:
715
                self.command_join('"%s/%s"' %(tab.name, tab.own_nick))
mathieui's avatar
mathieui committed
716

717 718
@command_args_parser.quoted(1)
def command_last_activity(self, args):
mathieui's avatar
mathieui committed
719 720 721 722
    """
    /last_activity <jid>
    """
    def callback(iq):
723
        "Callback for the last activity"
mathieui's avatar
mathieui committed
724 725
        if iq['type'] != 'result':
            if iq['error']['type'] == 'auth':
726 727 728
                self.information(_('You are not allowed to see the '
                                   'activity of this contact.'),
                                 _('Error'))
mathieui's avatar
mathieui committed
729
            else:
730
                self.information(_('Error retrieving the activity'), 'Error')
mathieui's avatar
mathieui committed
731 732 733 734 735 736 737 738 739 740 741 742
            return
        seconds = iq['last_activity']['seconds']
        status = iq['last_activity']['status']
        from_ = iq['from']
        if not safeJID(from_).user:
            msg = 'The uptime of %s is %s.' % (
                    from_,
                    common.parse_secs_to_str(seconds))
        else:
            msg = 'The last activity of %s was %s ago%s' % (
                from_,
                common.parse_secs_to_str(seconds),
743
                (' and his/her last status was %s' % status) if status else '')
mathieui's avatar
mathieui committed
744
        self.information(msg, 'Info')
745 746

    if args is None:
mathieui's avatar
mathieui committed
747
        return self.command_help('last_activity')
748
    jid = safeJID(args[0])
749 750
    self.xmpp.plugin['xep_0012'].get_last_activity(jid,
                                                   callback=callback)
mathieui's avatar
mathieui committed
751

752 753
@command_args_parser.quoted(0, 2)
def command_mood(self, args):
mathieui's avatar
mathieui committed
754 755 756 757
    """
    /mood [<mood> [text]]
    """
    if not args:
758 759
        return self.xmpp.plugin['xep_0107'].stop()

mathieui's avatar
mathieui committed
760 761
    mood = args[0]
    if mood not in pep.MOODS:
762
        return self.information(_('%s is not a correct value for a mood.')
763
                                % mood,
764
                                _('Error'))
765
    if len(args) == 2:
mathieui's avatar
mathieui committed
766 767 768
        text = args[1]
    else:
        text = None
769 770
    self.xmpp.plugin['xep_0107'].publish_mood(mood, text,
                                              callback=dumb_callback)
mathieui's avatar
mathieui committed
771

772 773
@command_args_parser.quoted(0, 3)
def command_activity(self, args):
mathieui's avatar
mathieui committed
774 775 776 777 778
    """
    /activity [<general> [specific] [text]]
    """
    length = len(args)
    if not length:
779 780
        return self.xmpp.plugin['xep_0108'].stop()

mathieui's avatar
mathieui committed
781 782
    general = args[0]
    if general not in pep.ACTIVITIES:
783 784 785
        return self.information(_('%s is not a correct value for an activity')
                                    % general,
                                _('Error'))
mathieui's avatar
mathieui committed
786 787 788 789 790 791 792 793 794 795 796
    specific = None
    text = None
    if length == 2:
        if args[1] in pep.ACTIVITIES[general]:
            specific = args[1]
        else:
            text = args[1]
    elif length == 3:
        specific = args[1]
        text = args[2]
    if specific and specific not in pep.ACTIVITIES[general]:
797 798 799
        return self.information(_('%s is not a correct value '
                                  'for an activity') % specific,
                                _('Error'))
800 801
    self.xmpp.plugin['xep_0108'].publish_activity(general, specific, text,
                                                  callback=dumb_callback)
mathieui's avatar
mathieui committed
802

803 804
@command_args_parser.quoted(0, 2)
def command_gaming(self, args):
mathieui's avatar
mathieui committed
805 806 807 808
    """
    /gaming [<game name> [server address]]
    """
    if not args:
809 810
        return self.xmpp.plugin['xep_0196'].stop()

mathieui's avatar
mathieui committed
811 812 813 814 815
    name = args[0]
    if len(args) > 1:
        address = args[1]
    else:
        address = None
816 817
    return self.xmpp.plugin['xep_0196'].publish_gaming(name=name,
                                                       server_address=address,
818
                                                       callback=dumb_callback)
mathieui's avatar
mathieui committed
819

820 821
@command_args_parser.quoted(2, 1, [None])
def command_invite(self, args):
mathieui's avatar
mathieui committed
822
    """/invite <to> <room> [reason]"""
823 824 825 826 827

    if args is None:
        return self.command_help('invite')

    reason = args[2]
mathieui's avatar
mathieui committed
828
    to = safeJID(args[0])
829 830
    room = safeJID(args[1]).bare
    self.invite(to.full, room, reason=reason)
mathieui's avatar
mathieui committed
831

832 833
@command_args_parser.quoted(1, 1, [''])
def command_decline(self, args):
mathieui's avatar
mathieui committed
834
    """/decline <room@server.tld> [reason]"""
835 836
    if args is None:
        return self.command_help('decline')
mathieui's avatar
mathieui committed
837 838 839
    jid = safeJID(args[0])
    if jid.bare not in self.pending_invites:
        return
840
    reason = args[1]
mathieui's avatar
mathieui committed
841
    del self.pending_invites[jid.bare]
842 843 844
    self.xmpp.plugin['xep_0045'].decline_invite(jid.bare,
                                                self.pending_invites[jid.bare],
                                                reason)
mathieui's avatar
mathieui committed
845 846 847

### Commands without a completion in this class ###

848 849
@command_args_parser.ignored
def command_invitations(self):
mathieui's avatar
mathieui committed
850 851 852
    """/invitations"""
    build = ""
    for invite in self.pending_invites:
853 854
        build += "%s by %s" % (invite,
                               safeJID(self.pending_invites[invite]).bare)
mathieui's avatar
mathieui committed
855 856 857 858 859 860
    if self.pending_invites:
        build = "You are invited to the following rooms:\n" + build
    else:
        build = "You do not have any pending invitations."
    self.information(build, 'Info')

861 862
@command_args_parser.quoted(0, 1, [None])
def command_quit(self, args):
mathieui's avatar
mathieui committed
863
    """
864
    /quit [message]
mathieui's avatar
mathieui committed
865
    """
866 867 868
    if not self.xmpp.is_connected():
        self.exit()
        return
869 870

    msg = args[0]
871
    if config.get('enable_user_mood'):
872
        self.xmpp.plugin['xep_0107'].stop()
873
    if config.get('enable_user_activity'):
874
        self.xmpp.plugin['xep_0108'].stop()
875
    if config.get('enable_user_gaming'):
876
        self.xmpp.plugin['xep_0196'].stop()
mathieui's avatar
mathieui committed
877 878 879
    self.save_config()
    self.plugin_manager.disable_plugins()
    self.disconnect(msg)
880
    self.xmpp.add_event_handler("disconnected", self.exit, disposable=True)
mathieui's avatar
mathieui committed
881

882 883
@command_args_parser.quoted(0, 1, [''])
def command_destroy_room(self, args):
mathieui's avatar
mathieui committed
884 885 886
    """
    /destroy_room [JID]
    """
887
    room = safeJID(args[0]).bare
mathieui's avatar
mathieui committed
888 889
    if room:
        muc.destroy_room(self.xmpp, room)
mathieui's avatar
mathieui committed
890
    elif isinstance(self.current_tab(), tabs.MucTab) and not args[0]:
mathieui's avatar
mathieui committed
891 892
        muc.destroy_room(self.xmpp, self.current_tab().general_jid)
    else:
893
        self.information(_('Invalid JID: "%s"') % args[0], _('Error'))
mathieui's avatar
mathieui committed
894

895 896
@command_args_parser.quoted(1, 1, [''])
def command_bind(self, args):
mathieui's avatar
mathieui committed
897 898 899
    """
    Bind a key.
    """
900
    if args is None:
mathieui's avatar
mathieui committed
901
        return self.command_help('bind')
902

mathieui's avatar
mathieui committed
903 904
    if not config.silent_set(args[0], args[1], section='bindings'):
        self.information(_('Unable to write in the config file'), 'Error')
905

mathieui's avatar
mathieui committed
906 907 908 909 910
    if args[1]:
        self.information('%s is now bound to %s' % (args[0], args[1]), 'Info')
    else:
        self.information('%s is now unbound' % args[0], 'Info')

911 912
@command_args_parser.raw
def command_rawxml(self, args):
mathieui's avatar
mathieui committed
913 914 915 916
    """
    /rawxml <xml stanza>
    """

917 918
    if not args:
        return
mathieui's avatar
mathieui committed
919

mathieui's avatar
mathieui committed
920
    stanza = args
mathieui's avatar
mathieui committed
921
    try:
922
        stanza = StanzaBase(self.xmpp, xml=ET.fromstring(stanza))
mathieui's avatar
mathieui committed
923 924 925 926 927 928
        if stanza.xml.tag == 'iq' and \
                stanza.xml.attrib.get('type') in ('get', 'set') and \
                stanza.xml.attrib.get('id'):
            iq_id = stanza.xml.attrib.get('id')

            def iqfunc(iq):
929
                "handler for an iq reply"
mathieui's avatar
mathieui committed
930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945
                self.information('%s' % iq, 'Iq')
                self.xmpp.remove_handler('Iq %s' % iq_id)

            self.xmpp.register_handler(
                    Callback('Iq %s' % iq_id,
                        StanzaPath('iq@id=%s' % iq_id),
                        iqfunc
                        )
                    )
            log.debug('handler')
        log.debug('%s %s', stanza.xml.tag, stanza.xml.attrib)

        stanza.send()
    except:
        self.information(_('Could not send custom stanza'), 'Error')
        log.debug('/rawxml: Could not send custom stanza (%s)',
946
                repr(stanza),
mathieui's avatar
mathieui committed
947 948 949
                exc_info=True)


950 951
@command_args_parser.quoted(1, 256)
def command_load(self, args):
mathieui's avatar
mathieui committed
952
    """
953
    /load <plugin> [<otherplugin> …]
954
    # TODO: being able to load more than 256 plugins at once, hihi.
mathieui's avatar
mathieui committed
955
    """
956 957
    for plugin in args:
        self.plugin_manager.load(plugin)
mathieui's avatar
mathieui committed
958

959 960
@command_args_parser.quoted(1, 256)
def command_unload(self, args):
mathieui's avatar
mathieui committed
961
    """
962
    /unload <plugin> [<otherplugin> …]
mathieui's avatar
mathieui committed
963
    """
964 965
    for plugin in args:
        self.plugin_manager.unload(plugin)
mathieui's avatar
mathieui committed
966

967 968
@command_args_parser.ignored
def command_plugins(self):
mathieui's avatar
mathieui committed
969 970 971
    """
    /plugins
    """
972 973 974
    self.information(_("Plugins currently in use: %s") %
                        repr(list(self.plugin_manager.plugins.keys())),
                     _('Info'))
mathieui's avatar
mathieui committed
975

976 977
@command_args_parser.quoted(1, 1)
def command_message(self, args):
mathieui's avatar
mathieui committed
978 979 980
    """
    /message <jid> [message]
    """
981 982
    if args is None:
        return self.command_help('message')
mathieui's avatar
mathieui committed
983 984 985 986
    jid = safeJID(args[0])
    if not jid.user and not jid.domain and not jid.resource:
        return self.information('Invalid JID.', 'Error')
    tab = self.get_conversation_by_jid(jid.full, False, fallback_barejid=False)
987 988
    muc = self.get_tab_by_name(jid.bare, typ=tabs.MucTab)
    if not tab and not muc:
mathieui's avatar
mathieui committed
989
        tab = self.open_conversation_window(jid.full, focus=True)
990 991 992 993 994 995
    elif muc:
        tab = self.get_tab_by_name(jid.full, typ=tabs.PrivateTab)
        if tab:
            self.focus_tab_named(tab.name)
        else:
            tab = self.open_private_window(jid.bare, jid.resource)
mathieui's avatar
mathieui committed
996
    else:
997
        self.focus_tab_named(tab.name)
998
    if len(args) == 2:
mathieui's avatar
mathieui committed
999 1000
        tab.command_say(args[1])

1001 1002
@command_args_parser.ignored
def command_xml_tab(self):
mathieui's avatar
mathieui committed
1003 1004 1005 1006 1007
    """/xml_tab"""
    xml_tab = self.focus_tab_named('XMLTab', tabs.XMLTab)
    if not xml_tab:
        tab = tabs.XMLTab()
        self.add_tab(tab, True)
1008
        self.xml_tab = tab
mathieui's avatar
mathieui committed
1009

1010 1011 1012
@command_args_parser.quoted(1)
def command_adhoc(self, args):
    if not args:
1013
        return self.command_help('ad-hoc')
1014
    jid = safeJID(args[0])
1015 1016 1017
    list_tab = tabs.AdhocCommandsListTab(jid)
    self.add_tab(list_tab, True)
    cb = list_tab.on_list_received
1018
    self.xmpp.plugin['xep_0050'].get_commands(jid=jid, local=False,
1019 1020
                                              callback=cb)

1021 1022
@command_args_parser.ignored
def command_self(self):
mathieui's avatar
mathieui committed
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
    """
    /self
    """
    status = self.get_status()
    show, message = status.show, status.message
    nick = self.own_nick
    jid = self.xmpp.boundjid.full
    info = ('Your JID is %s\nYour current status is "%s" (%s)'
            '\nYour default nickname is %s\nYou are running poezio %s' % (
            jid,
            message if message else '',
            show if show else 'available',
            nick,
            config_opts.version))
    self.information(info, 'Info')

Eijebong's avatar
Eijebong committed
1039 1040 1041 1042 1043 1044 1045 1046

@command_args_parser.ignored
def command_reload(self):
    """
    /reload
    """
    self.reload_config()

mathieui's avatar
mathieui committed
1047
def dumb_callback(*args, **kwargs):
1048
    "mock callback"
Eijebong's avatar
Eijebong committed
1049