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

import logging

log = logging.getLogger(__name__)

from xml.etree import cElementTree as ET

11 12
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream.xmlstream import NotConnectedError
louiz’'s avatar
louiz’ committed
13 14 15
from slixmpp.xmlstream.stanzabase import StanzaBase
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
mathieui's avatar
mathieui committed
16

17 18 19 20 21 22 23 24 25 26 27
from poezio import common
from poezio import pep
from poezio import tabs
from poezio.bookmarks import Bookmark
from poezio.common import safeJID
from poezio.config import config, DEFAULT_CONFIG, options as config_opts
from poezio import multiuserchat as muc
from poezio.plugin import PluginConfig
from poezio.roster import roster
from poezio.theming import dump_tuple, get_theme
from poezio.decorators import command_args_parser
mathieui's avatar
mathieui committed
28

mathieui's avatar
mathieui committed
29
from poezio.core.structs import Command, POSSIBLE_SHOW
mathieui's avatar
mathieui committed
30 31


32 33 34 35 36
class CommandCore:
    def __init__(self, core):
        self.core = core

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

            msg = '\n'.join(buff)
            msg += "\nType /help <command_name> to know what each command does"
        else:
            command = args[0].lstrip('/').strip()

70
            tab_commands = self.core.tabs.current_tab.commands
71 72 73 74
            if command in tab_commands:
                tup = tab_commands[command]
            elif command in self.core.commands:
                tup = self.core.commands[command]
mathieui's avatar
mathieui committed
75
            else:
76
                self.core.information('Unknown command: %s' % command, 'Error')
77 78 79 80
                return
            if isinstance(tup, Command):
                msg = 'Usage: /%s %s\n' % (command, tup.usage)
                msg += tup.desc
mathieui's avatar
mathieui committed
81
            else:
82
                msg = tup[1]
83
        self.core.information(msg, 'Help')
84 85

    @command_args_parser.quoted(1)
86
    def runkey(self, args):
87 88 89
        """
        /runkey <key>
        """
mathieui's avatar
mathieui committed
90

91 92 93 94 95
        def replace_line_breaks(key):
            "replace ^J with \n"
            if key == '^J':
                return '\n'
            return key
mathieui's avatar
mathieui committed
96

97
        if args is None:
98
            return self.help('runkey')
99
        char = args[0]
100
        func = self.core.key_func.get(char, None)
101 102
        if func:
            func()
mathieui's avatar
mathieui committed
103
        else:
104
            res = self.core.do_command(replace_line_breaks(char), False)
105
            if res:
106
                self.core.refresh_window()
107 108

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

116
        if args[0] not in POSSIBLE_SHOW.keys():
117
            return self.help('status')
118 119 120 121

        show = POSSIBLE_SHOW[args[0]]
        msg = args[1]

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

    @command_args_parser.quoted(1, 2, [None, None])
143
    def presence(self, args):
144 145 146 147
        """
        /presence <JID> [type] [status]
        """
        if args is None:
148
            return self.help('presence')
149

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

    @command_args_parser.quoted(1)
182
    def theme(self, args=None):
183 184
        """/theme <theme name>"""
        if args is None:
185
            return self.help('theme')
mathieui's avatar
mathieui committed
186
        self.set('theme %s' % (args[0], ))
187 188

    @command_args_parser.quoted(1)
189
    def win(self, args):
190
        """
mathieui's avatar
mathieui committed
191
        /win <number or name>
192 193
        """
        if args is None:
194
            return self.help('win')
195

mathieui's avatar
mathieui committed
196
        name = args[0]
mathieui's avatar
mathieui committed
197
        try:
mathieui's avatar
mathieui committed
198
            number = int(name)
mathieui's avatar
mathieui committed
199
        except ValueError:
mathieui's avatar
mathieui committed
200
            number = -1
201
            name = name.lower()
202
        if number != -1 and self.core.tabs.current_tab == number:
mathieui's avatar
mathieui committed
203
            return
mathieui's avatar
mathieui committed
204
        prev_nb = self.core.previous_tab_nb
205 206
        self.core.previous_tab_nb = self.core.tabs.current_tab
        old_tab = self.core.tabs.current_tab
mathieui's avatar
mathieui committed
207 208 209 210
        if 0 <= number < len(self.core.tabs):
            if not self.core.tabs[number]:
                self.core.previous_tab_nb = prev_nb
                return
211
            self.core.tabs.set_current_index(number)
mathieui's avatar
mathieui committed
212
        else:
213
            match = self.core.tabs.find_match(name)
214
            if match is None:
215
                return
216
            self.core.tabs.set_current_tab(match)
217 218

    @command_args_parser.quoted(2)
219
    def move_tab(self, args):
220 221 222 223
        """
        /move_tab old_pos new_pos
        """
        if args is None:
224
            return self.help('move_tab')
225

226
        current_tab = self.core.tabs.current_tab
227 228 229 230 231 232 233 234 235 236 237 238
        if args[0] == '.':
            args[0] = current_tab.nb
        if args[1] == '.':
            args[1] = current_tab.nb

        def get_nb_from_value(value):
            "parse the cmdline to guess the tab the users wants"
            ref = None
            try:
                ref = int(value)
            except ValueError:
                old_tab = None
239
                for tab in self.core.tabs:
240 241 242
                    if not old_tab and value == tab.name:
                        old_tab = tab
                if not old_tab:
mathieui's avatar
mathieui committed
243 244
                    self.core.information("Tab %s does not exist" % args[0],
                                          "Error")
245 246 247
                    return None
                ref = old_tab.nb
            return ref
mathieui's avatar
mathieui committed
248

249 250 251
        old = get_nb_from_value(args[0])
        new = get_nb_from_value(args[1])
        if new is None or old is None:
252 253
            return self.core.information('Unable to move the tab.', 'Info')
        result = self.core.insert_tab(old, new)
254
        if not result:
255 256
            self.core.information('Unable to move the tab.', 'Info')
        self.core.refresh_window()
257 258

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

    @command_args_parser.quoted(1)
279
    def version(self, args):
280 281 282 283
        """
        /version <jid>
        """
        if args is None:
284
            return self.help('version')
285
        jid = safeJID(args[0])
286
        if jid.resource or jid not in roster or not roster[jid].resources:
287 288
            self.core.xmpp.plugin['xep_0092'].get_version(
                jid, callback=self.core.handler.on_version_result)
289 290
        elif jid in roster:
            for resource in roster[jid].resources:
291 292
                self.core.xmpp.plugin['xep_0092'].get_version(
                    resource.jid, callback=self.core.handler.on_version_result)
293

mathieui's avatar
mathieui committed
294
    def _empty_join(self):
295
        tab = self.core.tabs.current_tab
mathieui's avatar
mathieui committed
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
        if not isinstance(tab, (tabs.MucTab, tabs.PrivateTab)):
            return (None, None)
        room = safeJID(tab.name).bare
        nick = tab.own_nick
        return (room, nick)

    def _parse_join_jid(self, jid_string):
        # we try to join a server directly
        if jid_string.startswith('@'):
            server_root = True
            info = safeJID(jid_string[1:])
        else:
            info = safeJID(jid_string)
            server_root = False

        set_nick = ''
        if len(jid_string) > 1 and jid_string.startswith('/'):
            set_nick = jid_string[1:]
        elif info.resource:
            set_nick = info.resource

        # happens with /join /nickname, which is OK
        if info.bare == '':
319
            tab = self.core.tabs.current_tab
mathieui's avatar
mathieui committed
320 321 322 323 324 325 326 327 328 329 330 331
            if not isinstance(tab, tabs.MucTab):
                room, set_nick = (None, None)
            else:
                room = tab.name
                if not set_nick:
                    set_nick = tab.own_nick
        else:
            room = info.bare
            # 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:
332
                tab = self.core.tabs.current_tab
mathieui's avatar
mathieui committed
333 334 335 336 337 338
                if isinstance(tab, tabs.MucTab):
                    if tab.name.find('@') != -1:
                        domain = safeJID(tab.name).domain
                        room += '@%s' % domain
        return (room, set_nick)

339
    @command_args_parser.quoted(0, 2)
340
    def join(self, args):
341 342 343 344
        """
        /join [room][/nick] [password]
        """
        if len(args) == 0:
mathieui's avatar
mathieui committed
345
            room, nick = self._empty_join()
mathieui's avatar
mathieui committed
346
        else:
mathieui's avatar
mathieui committed
347 348
            room, nick = self._parse_join_jid(args[0])
        if not room and not nick:
mathieui's avatar
mathieui committed
349
            return  # nothing was parsed
mathieui's avatar
mathieui committed
350

351
        room = room.lower()
mathieui's avatar
mathieui committed
352 353 354 355 356 357 358 359 360
        if nick == '':
            nick = self.core.own_nick

        # a password is provided
        if len(args) == 2:
            password = args[1]
        else:
            password = config.get_by_tabname('password', room, fallback=False)

361 362
        if room in self.core.pending_invites:
            del self.core.pending_invites[room]
mathieui's avatar
mathieui committed
363

364
        tab = self.core.tabs.by_name_and_class(room, tabs.MucTab)
mathieui's avatar
mathieui committed
365 366 367 368 369
        # New tab
        if tab is None:
            tab = self.core.open_new_room(room, nick, password=password)
            tab.join()
        else:
370
            self.core.focus_tab(tab)
371
            if tab.own_nick == nick and tab.joined:
372
                self.core.information('/join: Nothing to do.', 'Info')
373 374 375 376
            else:
                tab.command_part('')
                tab.own_nick = nick
                tab.password = password
mathieui's avatar
mathieui committed
377
                tab.join()
mathieui's avatar
mathieui committed
378

379
        if config.get('bookmark_on_join'):
mathieui's avatar
mathieui committed
380 381
            method = 'remote' if config.get(
                'use_remote_bookmarks') else 'local'
382 383
            self._add_bookmark('%s/%s' % (room, nick), True, password, method)

384
        if tab == self.core.tabs.current_tab:
mathieui's avatar
mathieui committed
385 386
            tab.refresh()
            self.core.doupdate()
387 388

    @command_args_parser.quoted(0, 2)
389
    def bookmark_local(self, args):
390 391 392
        """
        /bookmark_local [room][/nick] [password]
        """
393 394
        if not args and not isinstance(self.core.tabs.current_tab,
                                       tabs.MucTab):
395 396 397
            return
        password = args[1] if len(args) > 1 else None
        jid = args[0] if args else None
mathieui's avatar
mathieui committed
398

399
        self._add_bookmark(jid, True, password, 'local')
400 401

    @command_args_parser.quoted(0, 3)
402
    def bookmark(self, args):
403 404 405
        """
        /bookmark [room][/nick] [autojoin] [password]
        """
406 407
        if not args and not isinstance(self.core.tabs.current_tab,
                                       tabs.MucTab):
408 409 410 411 412
            return
        jid = args[0] if args else ''
        password = args[2] if len(args) > 2 else None

        if not config.get('use_remote_bookmarks'):
413
            return self._add_bookmark(jid, True, password, 'local')
414 415 416

        if len(args) > 1:
            autojoin = False if args[1].lower() != 'true' else True
417
        else:
418 419
            autojoin = True

420
        self._add_bookmark(jid, autojoin, password, 'remote')
421 422 423 424

    def _add_bookmark(self, jid, autojoin, password, method):
        nick = None
        if not jid:
425
            tab = self.core.tabs.current_tab
426
            roomname = tab.name
427
            if tab.joined and tab.own_nick != self.core.own_nick:
428 429 430 431
                nick = tab.own_nick
            if password is None and tab.password is not None:
                password = tab.password
        elif jid == '*':
432
            return self._add_wildcard_bookmarks(method)
433
        else:
434 435 436
            info = safeJID(jid)
            roomname, nick = info.bare, info.resource
            if roomname == '':
437
                tab = self.core.tabs.current_tab
438
                if not isinstance(tab, tabs.MucTab):
439
                    return
440 441
                roomname = tab.name
        bookmark = self.core.bookmarks[roomname]
442 443
        if bookmark is None:
            bookmark = Bookmark(roomname)
444
            self.core.bookmarks.append(bookmark)
445 446 447 448 449 450
        bookmark.method = method
        bookmark.autojoin = autojoin
        if nick:
            bookmark.nick = nick
        if password:
            bookmark.password = password
mathieui's avatar
mathieui committed
451

452
        self.core.bookmarks.save_local()
453 454
        self.core.bookmarks.save_remote(self.core.xmpp,
                                        self.core.handler.on_bookmark_result)
455 456 457

    def _add_wildcard_bookmarks(self, method):
        new_bookmarks = []
458 459
        for tab in self.core.get_tabs(tabs.MucTab):
            bookmark = self.core.bookmarks[tab.name]
460
            if not bookmark:
mathieui's avatar
mathieui committed
461
                bookmark = Bookmark(tab.name, autojoin=True, method=method)
462 463 464 465
                new_bookmarks.append(bookmark)
            else:
                bookmark.method = method
                new_bookmarks.append(bookmark)
466 467 468 469
                self.core.bookmarks.remove(bookmark)
        new_bookmarks.extend(self.core.bookmarks.bookmarks)
        self.core.bookmarks.set(new_bookmarks)
        self.core.bookmarks.save_local()
470 471
        self.core.bookmarks.save_remote(self.core.xmpp,
                                        self.core.handler.on_bookmark_result)
472 473

    @command_args_parser.ignored
474
    def bookmarks(self):
475
        """/bookmarks"""
476 477
        tab = self.core.tabs.by_name_and_class('Bookmarks', tabs.BookmarksTab)
        old_tab = self.core.tabs.current_tab
478
        if tab:
479
            self.core.tabs.set_current_tab(tab)
mathieui's avatar
mathieui committed
480
        else:
481
            tab = tabs.BookmarksTab(self.core, self.core.bookmarks)
482
            self.core.tabs.append(tab)
483
            self.core.tabs.set_current_tab(tab)
484 485

    @command_args_parser.quoted(0, 1)
486
    def remove_bookmark(self, args):
487 488 489 490
        """/remove_bookmark [jid]"""

        def cb(success):
            if success:
491
                self.core.information('Bookmark deleted', 'Info')
492
            else:
mathieui's avatar
mathieui committed
493 494
                self.core.information('Error while deleting the bookmark',
                                      'Error')
mathieui's avatar
mathieui committed
495

496
        if not args:
497
            tab = self.core.tabs.current_tab
498 499 500
            if isinstance(tab, tabs.MucTab) and self.core.bookmarks[tab.name]:
                self.core.bookmarks.remove(tab.name)
                self.core.bookmarks.save(self.core.xmpp, callback=cb)
501
            else:
502
                self.core.information('No bookmark to remove', 'Info')
mathieui's avatar
mathieui committed
503
        else:
504 505 506
            if self.core.bookmarks[args[0]]:
                self.core.bookmarks.remove(args[0])
                self.core.bookmarks.save(self.core.xmpp, callback=cb)
507
            else:
508
                self.core.information('No bookmark to remove', 'Info')
509 510

    @command_args_parser.quoted(0, 3)
511
    def set(self, args):
512 513 514 515 516 517 518 519
        """
        /set [module|][section] <option> [value]
        """
        if args is None or len(args) == 0:
            config_dict = config.to_dict()
            lines = []
            theme = get_theme()
            for section_name, section in config_dict.items():
mathieui's avatar
mathieui committed
520 521 522 523 524 525
                lines.append(
                    '\x19%(section_col)s}[%(section)s]\x19o' % {
                        'section': section_name,
                        'section_col': dump_tuple(
                            theme.COLOR_INFORMATION_TEXT),
                    })
526
                for option_name, option_value in section.items():
mathieui's avatar
mathieui committed
527 528 529 530
                    lines.append(
                        '%s\x19%s}=\x19o%s' %
                        (option_name, dump_tuple(
                            theme.COLOR_REVISIONS_MESSAGE), option_value))
531 532 533 534 535 536
            info = ('Current  options:\n%s' % '\n'.join(lines), 'Info')
        elif len(args) == 1:
            option = args[0]
            value = config.get(option)
            if value is None and '=' in option:
                args = option.split('=', 1)
537
            info = ('%s=%s' % (option, value), 'Info')
538 539 540 541 542
        if len(args) == 2:
            if '|' in args[0]:
                plugin_name, section = args[0].split('|')[:2]
                if not section:
                    section = plugin_name
543
                option = args[1]
544
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
545 546
                    file_name = self.core.plugin_manager.plugins_conf_dir / (
                        plugin_name + '.cfg')
547 548
                    plugin_config = PluginConfig(file_name, plugin_name)
                else:
mathieui's avatar
mathieui committed
549 550
                    plugin_config = self.core.plugin_manager.plugins[
                        plugin_name].config
551
                value = plugin_config.get(option, default='', section=section)
552 553
                info = ('%s=%s' % (option, value), 'Info')
            else:
554 555 556 557 558 559 560 561 562 563
                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)
564
                    self.core.trigger_configuration_change(option, value)
565 566 567 568 569 570 571
        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]
572
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
573 574
                    file_name = self.core.plugin_manager.plugins_conf_dir / (
                        plugin_name + '.cfg')
575 576
                    plugin_config = PluginConfig(file_name, plugin_name)
                else:
mathieui's avatar
mathieui committed
577 578
                    plugin_config = self.core.plugin_manager.plugins[
                        plugin_name].config
579 580 581
                info = plugin_config.set_and_save(option, value, section)
            else:
                if args[0] == '.':
582
                    name = safeJID(self.core.tabs.current_tab.name).bare
583
                    if not name:
mathieui's avatar
mathieui committed
584 585
                        self.core.information(
                            'Invalid tab to use the "." argument.', 'Error')
586 587 588 589 590 591 592
                        return
                    section = name
                else:
                    section = args[0]
                option = args[1]
                value = args[2]
                info = config.set_and_save(option, value, section)
593
                self.core.trigger_configuration_change(option, value)
594
        elif len(args) > 3:
595 596
            return self.help('set')
        self.core.information(*info)
597 598

    @command_args_parser.quoted(1, 2)
599
    def set_default(self, args):
600 601 602 603 604 605 606 607
        """
        /set_default [section] <option>
        """
        if len(args) == 1:
            option = args[0]
            section = 'Poezio'
        elif len(args) == 2:
            section = args[0]
mathieui's avatar
mathieui committed
608 609
            option = args[1]
        else:
610
            return self.help('set_default')
611 612 613 614

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

    @command_args_parser.quoted(1)
619
    def toggle(self, args):
620 621 622 623 624
        """
        /toggle <option>
        shortcut for /set <option> toggle
        """
        if args is None:
625
            return self.help('toggle')
626 627

        if args[0]:
628
            self.set('%s toggle' % args[0])
629 630

    @command_args_parser.quoted(1, 1)
631
    def server_cycle(self, args):
632 633 634 635
        """
        Do a /cycle on each room of the given server.
        If none, do it on the current tab
        """
636
        tab = self.core.tabs.current_tab
637 638 639 640 641
        message = ""
        if args:
            domain = args[0]
            if len(args) == 2:
                message = args[1]
mathieui's avatar
mathieui committed
642
        else:
643 644
            if isinstance(tab, tabs.MucTab):
                domain = safeJID(tab.name).domain
mathieui's avatar
mathieui committed
645
            else:
646 647
                return self.core.information("No server specified", "Error")
        for tab in self.core.get_tabs(tabs.MucTab):
648
            if tab.name.endswith(domain):
mathieui's avatar
mathieui committed
649 650
                tab.leave_room(message)
                tab.join()
651 652

    @command_args_parser.quoted(1)
653
    def last_activity(self, args):
654 655 656
        """
        /last_activity <jid>
        """
mathieui's avatar
mathieui committed
657

658 659 660 661
        def callback(iq):
            "Callback for the last activity"
            if iq['type'] != 'result':
                if iq['error']['type'] == 'auth':
mathieui's avatar
mathieui committed
662 663 664
                    self.core.information(
                        'You are not allowed to see the '
                        'activity of this contact.', 'Error')
665
                else:
mathieui's avatar
mathieui committed
666 667
                    self.core.information('Error retrieving the activity',
                                          'Error')
668 669 670 671 672 673
                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.' % (
mathieui's avatar
mathieui committed
674
                    from_, common.parse_secs_to_str(seconds))
mathieui's avatar
mathieui committed
675
            else:
676
                msg = 'The last activity of %s was %s ago%s' % (
mathieui's avatar
mathieui committed
677 678 679
                    from_, common.parse_secs_to_str(seconds),
                    (' and his/her last status was %s' % status)
                    if status else '')
680
            self.core.information(msg, 'Info')
681 682

        if args is None:
683
            return self.help('last_activity')
684
        jid = safeJID(args[0])
mathieui's avatar
mathieui committed
685 686
        self.core.xmpp.plugin['xep_0012'].get_last_activity(
            jid, callback=callback)
687 688

    @command_args_parser.quoted(0, 2)
689
    def mood(self, args):
690 691 692 693
        """
        /mood [<mood> [text]]
        """
        if not args:
694
            return self.core.xmpp.plugin['xep_0107'].stop()
695 696 697

        mood = args[0]
        if mood not in pep.MOODS:
mathieui's avatar
mathieui committed
698 699
            return self.core.information(
                '%s is not a correct value for a mood.' % mood, 'Error')
700 701
        if len(args) == 2:
            text = args[1]
mathieui's avatar
mathieui committed
702
        else:
703
            text = None
mathieui's avatar
mathieui committed
704 705
        self.core.xmpp.plugin['xep_0107'].publish_mood(
            mood, text, callback=dumb_callback)
706 707

    @command_args_parser.quoted(0, 3)
708
    def activity(self, args):
709 710 711 712 713
        """
        /activity [<general> [specific] [text]]
        """
        length = len(args)
        if not length:
714
            return self.core.xmpp.plugin['xep_0108'].stop()
715 716 717

        general = args[0]
        if general not in pep.ACTIVITIES:
mathieui's avatar
mathieui committed
718 719
            return self.core.information(
                '%s is not a correct value for an activity' % general, 'Error')
720
        specific = None
mathieui's avatar
mathieui committed
721
        text = None
722 723 724 725 726 727
        if length == 2:
            if args[1] in pep.ACTIVITIES[general]:
                specific = args[1]
            else:
                text = args[1]
        elif length == 3:
mathieui's avatar
mathieui committed
728
            specific = args[1]
729 730
            text = args[2]
        if specific and specific not in pep.ACTIVITIES[general]:
mathieui's avatar
mathieui committed
731 732 733
            return self.core.information(
                '%s is not a correct value '
                'for an activity' % specific, 'Error')
mathieui's avatar
mathieui committed
734 735
        self.core.xmpp.plugin['xep_0108'].publish_activity(
            general, specific, text, callback=dumb_callback)
736 737

    @command_args_parser.quoted(0, 2)
738
    def gaming(self, args):
739 740 741 742
        """
        /gaming [<game name> [server address]]
        """
        if not args:
743
            return self.core.xmpp.plugin['xep_0196'].stop()
744 745 746 747

        name = args[0]
        if len(args) > 1:
            address = args[1]
mathieui's avatar
mathieui committed
748
        else:
749
            address = None
mathieui's avatar
mathieui committed
750 751
        return self.core.xmpp.plugin['xep_0196'].publish_gaming(
            name=name, server_address=address, callback=dumb_callback)
752 753

    @command_args_parser.quoted(2, 1, [None])
754
    def invite(self, args):
755 756 757
        """/invite <to> <room> [reason]"""

        if args is None:
758
            return self.help('invite')
759 760 761 762

        reason = args[2]
        to = safeJID(args[0])
        room = safeJID(args[1]).bare
763 764
        self.core.invite(to.full, room, reason=reason)
        self.core.information('Invited %s to %s' % (to.bare, room), 'Info')
765 766

    @command_args_parser.quoted(1, 1, [''])
767
    def decline(self, args):
768 769
        """/decline <room@server.tld> [reason]"""
        if args is None:
770
            return self.help('decline')
771
        jid = safeJID(args[0])
772
        if jid.bare not in self.core.pending_invites:
773 774
            return
        reason = args[1]
775
        del self.core.pending_invites[jid.bare]
mathieui's avatar
mathieui committed
776 777 778
        self.core.xmpp.plugin['xep_0045'].decline_invite(
            jid.bare, self.core.pending_invites[jid.bare], reason)

mathieui's avatar
mathieui committed
779 780 781

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

782
    @command_args_parser.ignored
783
    def invitations(self):
784 785
        """/invitations"""
        build = ""
786
        for invite in self.core.pending_invites:
mathieui's avatar
mathieui committed
787 788
            build += "%s by %s" % (
                invite, safeJID(self.core.pending_invites[invite]).bare)
789
        if self.core.pending_invites:
790 791 792
            build = "You are invited to the following rooms:\n" + build
        else:
            build = "You do not have any pending invitations."
793
        self.core.information(build, 'Info')
794 795

    @command_args_parser.quoted(0, 1, [None])
796
    def quit(self, args):
797 798 799
        """
        /quit [message]
        """
800 801
        if not self.core.xmpp.is_connected():
            self.core.exit()
802 803 804 805
            return

        msg = args[0]
        if config.get('enable_user_mood'):
806
            self.core.xmpp.plugin['xep_0107'].stop()
807
        if config.get('enable_user_activity'):
808
            self.core.xmpp.plugin['xep_0108'].stop()
809
        if config.get('enable_user_gaming'):
810 811 812 813
            self.core.xmpp.plugin['xep_0196'].stop()
        self.core.save_config()
        self.core.plugin_manager.disable_plugins()
        self.core.disconnect(msg)
mathieui's avatar
mathieui committed
814 815
        self.core.xmpp.add_event_handler(
            "disconnected", self.core.exit, disposable=True)
816 817

    @command_args_parser.quoted(0, 1, [''])
818
    def destroy_room(self, args):
819 820 821 822 823
        """
        /destroy_room [JID]
        """
        room = safeJID(args[0]).bare
        if room:
824
            muc.destroy_room(self.core.xmpp, room)
825 826
        elif isinstance(self.core.tabs.current_tab,
                        tabs.MucTab) and not args[0]:
mathieui's avatar
mathieui committed
827
            muc.destroy_room(self.core.xmpp,
828
                             self.core.tabs.current_tab.general_jid)
829
        else:
830
            self.core.information('Invalid JID: "%s"' % args[0], 'Error')
831 832

    @command_args_parser.quoted(1, 1, [''])
833
    def bind(self, args):
834 835 836 837
        """
        Bind a key.
        """
        if args is None:
838
            return self.help('bind')
839 840

        if not config.silent_set(args[0], args[1], section='bindings'):
mathieui's avatar
mathieui committed
841 842
            self.core.information('Unable to write in the config file',
                                  'Error')
843 844

        if args[1]:
mathieui's avatar
mathieui committed
845 846
            self.core.information('%s is now bound to %s' % (args[0], args[1]),
                                  'Info')
847
        else:
mathieui's avatar
mathieui committed
848 849
            self.core.information(
                '%s is now reset to the default binding' % args[0], 'Info')
850 851

    @command_args_parser.raw
852
    def rawxml(self, args):
853 854 855 856 857 858 859 860 861
        """
        /rawxml <xml stanza>
        """

        if not args:
            return

        stanza = args
        try:
862
            stanza = StanzaBase(self.core.xmpp, xml=ET.fromstring(stanza))
mathieui's avatar
mathieui committed
863 864
            if stanza.xml.tag == 'iq' and stanza.xml.attrib.get('type') in (
                    'get', 'set'):
865 866
                iq_id = stanza.xml.attrib.get('id')
                if not iq_id: