commands.py 36.1 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 os
mathieui's avatar
mathieui committed
10
11
12
from datetime import datetime
from xml.etree import cElementTree as ET

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
28
from poezio import common
from poezio import fixes
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
29

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


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

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

75
76
77
78
79
            tab_commands = self.core.current_tab().commands
            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
80
            else:
81
                self.core.information('Unknown command: %s' % command, 'Error')
82
83
84
85
                return
            if isinstance(tup, Command):
                msg = 'Usage: /%s %s\n' % (command, tup.usage)
                msg += tup.desc
mathieui's avatar
mathieui committed
86
            else:
87
                msg = tup[1]
88
        self.core.information(msg, 'Help')
89
90

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

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

        if not args[0] in POSSIBLE_SHOW.keys():
120
            return self.help('status')
121
122
123
124

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

125
        pres = self.core.xmpp.make_presence()
126
127
128
        if msg:
            pres['status'] = msg
        pres['type'] = show
129
        self.core.events.trigger('send_normal_presence', pres)
mathieui's avatar
mathieui committed
130
        pres.send()
131
        current = self.core.current_tab()
132
133
134
        is_muctab = isinstance(current, tabs.MucTab)
        if is_muctab and current.joined and show in ('away', 'xa'):
            current.send_chat_state('inactive')
135
        for tab in self.core.tabs:
136
            if isinstance(tab, tabs.MucTab) and tab.joined:
137
                muc.change_show(self.core.xmpp, tab.name, tab.own_nick, show, msg)
138
139
            if hasattr(tab, 'directed_presence'):
                del tab.directed_presence
140
        self.core.set_status(show, msg)
141
142
143
144
        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])
145
    def presence(self, args):
146
147
148
149
        """
        /presence <JID> [type] [status]
        """
        if args is None:
150
            return self.help('presence')
151
152

        jid, type, status = args[0], args[1], args[2]
153
154
        if jid == '.' and isinstance(self.core.current_tab(), tabs.ChatTab):
            jid = self.core.current_tab().name
155
156
157
        if type == 'available':
            type = None
        try:
158
159
            pres = self.core.xmpp.make_presence(pto=jid, ptype=type, pstatus=status)
            self.core.events.trigger('send_normal_presence', pres)
160
161
            pres.send()
        except:
162
            self.core.information('Could not send directed presence', 'Error')
163
            log.debug('Could not send directed presence to %s', jid, exc_info=True)
mathieui's avatar
mathieui committed
164
            return
165
        tab = self.core.get_tab_by_name(jid)
166
167
168
169
170
171
172
        if tab:
            if type in ('xa', 'away'):
                tab.directed_presence = False
                chatstate = 'inactive'
            else:
                tab.directed_presence = True
                chatstate = 'active'
173
            if tab == self.core.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.current_tab() in tab.privates:
                    self.core.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
186
            return self.help('theme')
        self.set('theme %s' % (args[0],))
187
188

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

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

    @command_args_parser.quoted(2)
226
    def move_tab(self, args):
227
228
229
230
        """
        /move_tab old_pos new_pos
        """
        if args is None:
231
            return self.help('move_tab')
232

233
        current_tab = self.core.current_tab()
234
235
236
237
238
239
240
241
242
243
244
245
        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
246
                for tab in self.core.tabs:
247
248
249
                    if not old_tab and value == tab.name:
                        old_tab = tab
                if not old_tab:
250
                    self.core.information("Tab %s does not exist" % args[0], "Error")
251
252
253
254
255
256
                    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:
257
258
            return self.core.information('Unable to move the tab.', 'Info')
        result = self.core.insert_tab(old, new)
259
        if not result:
260
            self.core.information('Unable to move the tab.', 'Info')
mathieui's avatar
mathieui committed
261
        else:
262
263
            self.core.current_tab_nb = self.core.tabs.index(current_tab)
        self.core.refresh_window()
264
265

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

    @command_args_parser.quoted(1)
286
    def version(self, args):
287
288
289
290
291
292
        """
        /version <jid>
        """
        def callback(res):
            "Callback for /version"
            if not res:
293
294
295
                return self.core.information('Could not get the software'
                                             ' version from %s' % jid,
                                             'Warning')
296
297
298
299
300
            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')
301
            self.core.information(version, 'Info')
302
303

        if args is None:
304
            return self.help('version')
305
306

        jid = safeJID(args[0])
307
        if jid.resource or jid not in roster or not roster[jid].resources:
308
            fixes.get_version(self.core.xmpp, jid, callback=callback)
309
310
        elif jid in roster:
            for resource in roster[jid].resources:
311
                fixes.get_version(self.core.xmpp, resource.jid, callback=callback)
312

mathieui's avatar
mathieui committed
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
    def _empty_join(self):
        tab = self.core.current_tab()
        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 == '':
            tab = self.core.current_tab()
            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:
                tab = self.core.current_tab()
                if isinstance(tab, tabs.MucTab):
                    if tab.name.find('@') != -1:
                        domain = safeJID(tab.name).domain
                        room += '@%s' % domain
        return (room, set_nick)

358
    @command_args_parser.quoted(0, 2)
359
    def join(self, args):
360
361
362
363
        """
        /join [room][/nick] [password]
        """
        if len(args) == 0:
mathieui's avatar
mathieui committed
364
            room, nick = self._empty_join()
mathieui's avatar
mathieui committed
365
        else:
mathieui's avatar
mathieui committed
366
367
368
369
            room, nick = self._parse_join_jid(args[0])
        if not room and not nick:
            return # nothing was parsed

370
        room = room.lower()
mathieui's avatar
mathieui committed
371
372
373
374
375
376
377
378
379
        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)

380
381
        if room in self.core.pending_invites:
            del self.core.pending_invites[room]
mathieui's avatar
mathieui committed
382

383
        tab = self.core.get_tab_by_name(room, tabs.MucTab)
mathieui's avatar
mathieui committed
384
385
386
387
388
        # New tab
        if tab is None:
            tab = self.core.open_new_room(room, nick, password=password)
            tab.join()
        else:
389
            self.core.focus_tab_named(tab.name)
390
            if tab.own_nick == nick and tab.joined:
391
                self.core.information('/join: Nothing to do.', 'Info')
392
393
394
395
            else:
                tab.command_part('')
                tab.own_nick = nick
                tab.password = password
mathieui's avatar
mathieui committed
396
                tab.join()
mathieui's avatar
mathieui committed
397

mathieui's avatar
mathieui committed
398
399
400
        if tab == self.core.current_tab():
            tab.refresh()
            self.core.doupdate()
401
402

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

412
        self._add_bookmark(jid, True, password, 'local')
413
414

    @command_args_parser.quoted(0, 3)
415
    def bookmark(self, args):
416
417
418
        """
        /bookmark [room][/nick] [autojoin] [password]
        """
419
        if not args and not isinstance(self.core.current_tab(), tabs.MucTab):
420
421
422
423
424
            return
        jid = args[0] if args else ''
        password = args[2] if len(args) > 2 else None

        if not config.get('use_remote_bookmarks'):
425
            return self._add_bookmark(jid, True, password, 'local')
426
427
428

        if len(args) > 1:
            autojoin = False if args[1].lower() != 'true' else True
429
        else:
430
431
            autojoin = True

432
        self._add_bookmark(jid, autojoin, password, 'remote')
433
434
435
436

    def _add_bookmark(self, jid, autojoin, password, method):
        nick = None
        if not jid:
437
            tab = self.core.current_tab()
438
            roomname = tab.name
439
            if tab.joined and tab.own_nick != self.core.own_nick:
440
441
442
443
                nick = tab.own_nick
            if password is None and tab.password is not None:
                password = tab.password
        elif jid == '*':
444
            return self._add_wildcard_bookmarks(method)
445
        else:
446
447
448
            info = safeJID(jid)
            roomname, nick = info.bare, info.resource
            if roomname == '':
449
450
                tab = self.core.current_tab()
                if not isinstance(tab, tabs.MucTab):
451
                    return
452
453
                roomname = tab.name
        bookmark = self.core.bookmarks[roomname]
454
455
        if bookmark is None:
            bookmark = Bookmark(roomname)
456
            self.core.bookmarks.append(bookmark)
457
458
459
460
461
462
463
464
        bookmark.method = method
        bookmark.autojoin = autojoin
        if nick:
            bookmark.nick = nick
        if password:
            bookmark.password = password
        def callback(iq):
            if iq["type"] != "error":
465
                self.core.information('Bookmark added.', 'Info')
466
            else:
467
468
469
                self.core.information("Could not add the bookmarks.", "Info")
        self.core.bookmarks.save_local()
        self.core.bookmarks.save_remote(self.core.xmpp, callback)
470
471
472

    def _add_wildcard_bookmarks(self, method):
        new_bookmarks = []
473
474
        for tab in self.core.get_tabs(tabs.MucTab):
            bookmark = self.core.bookmarks[tab.name]
475
476
477
478
479
480
481
            if not bookmark:
                bookmark = Bookmark(tab.name, autojoin=True,
                                    method=method)
                new_bookmarks.append(bookmark)
            else:
                bookmark.method = method
                new_bookmarks.append(bookmark)
482
483
484
                self.core.bookmarks.remove(bookmark)
        new_bookmarks.extend(self.core.bookmarks.bookmarks)
        self.core.bookmarks.set(new_bookmarks)
485
486
        def _cb(iq):
            if iq["type"] != "error":
487
                self.core.information("Bookmarks saved.", "Info")
488
            else:
489
490
491
                self.core.information("Could not save the remote bookmarks.", "Info")
        self.core.bookmarks.save_local()
        self.core.bookmarks.save_remote(self.core.xmpp, _cb)
492
493

    @command_args_parser.ignored
494
    def bookmarks(self):
495
        """/bookmarks"""
496
497
        tab = self.core.get_tab_by_name('Bookmarks', tabs.BookmarksTab)
        old_tab = self.core.current_tab()
498
        if tab:
499
            self.core.current_tab_nb = tab.nb
mathieui's avatar
mathieui committed
500
        else:
501
            tab = tabs.BookmarksTab(self.core, self.core.bookmarks)
502
503
            self.core.tabs.append(tab)
            self.core.current_tab_nb = tab.nb
504
505
        old_tab.on_lose_focus()
        tab.on_gain_focus()
506
        self.core.refresh_window()
507
508

    @command_args_parser.quoted(0, 1)
509
    def remove_bookmark(self, args):
510
511
512
513
        """/remove_bookmark [jid]"""

        def cb(success):
            if success:
514
                self.core.information('Bookmark deleted', 'Info')
515
            else:
516
                self.core.information('Error while deleting the bookmark', 'Error')
mathieui's avatar
mathieui committed
517

518
        if not args:
519
520
521
522
            tab = self.core.current_tab()
            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)
523
            else:
524
                self.core.information('No bookmark to remove', 'Info')
mathieui's avatar
mathieui committed
525
        else:
526
527
528
            if self.core.bookmarks[args[0]]:
                self.core.bookmarks.remove(args[0])
                self.core.bookmarks.save(self.core.xmpp, callback=cb)
529
            else:
530
                self.core.information('No bookmark to remove', 'Info')
531
532

    @command_args_parser.quoted(0, 3)
533
    def set(self, args):
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
        """
        /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():
                lines.append('\x19%(section_col)s}[%(section)s]\x19o' %
                        {
                            'section': section_name,
                            'section_col': dump_tuple(theme.COLOR_INFORMATION_TEXT),
                        })
                for option_name, option_value in section.items():
                    lines.append('%s\x19%s}=\x19o%s' % (option_name,
                                                        dump_tuple(theme.COLOR_REVISIONS_MESSAGE),
                                                        option_value))
            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)
557
            info = ('%s=%s' % (option, value), 'Info')
558
559
560
561
562
        if len(args) == 2:
            if '|' in args[0]:
                plugin_name, section = args[0].split('|')[:2]
                if not section:
                    section = plugin_name
563
                option = args[1]
564
565
                if not plugin_name in self.core.plugin_manager.plugins:
                    file_name = self.core.plugin_manager.plugins_conf_dir
566
567
568
                    file_name = os.path.join(file_name, plugin_name + '.cfg')
                    plugin_config = PluginConfig(file_name, plugin_name)
                else:
569
                    plugin_config = self.core.plugin_manager.plugins[plugin_name].config
570
                value = plugin_config.get(option, default='', section=section)
571
572
                info = ('%s=%s' % (option, value), 'Info')
            else:
573
574
575
576
577
578
579
580
581
582
                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)
583
                    self.core.trigger_configuration_change(option, value)
584
585
586
587
588
589
590
        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]
591
592
                if not plugin_name in self.core.plugin_manager.plugins:
                    file_name = self.core.plugin_manager.plugins_conf_dir
593
594
595
                    file_name = os.path.join(file_name, plugin_name + '.cfg')
                    plugin_config = PluginConfig(file_name, plugin_name)
                else:
596
                    plugin_config = self.core.plugin_manager.plugins[plugin_name].config
597
598
599
                info = plugin_config.set_and_save(option, value, section)
            else:
                if args[0] == '.':
600
                    name = safeJID(self.core.current_tab().name).bare
601
                    if not name:
602
603
                        self.core.information('Invalid tab to use the "." argument.',
                                              'Error')
604
605
606
607
608
609
610
                        return
                    section = name
                else:
                    section = args[0]
                option = args[1]
                value = args[2]
                info = config.set_and_save(option, value, section)
611
                self.core.trigger_configuration_change(option, value)
612
        elif len(args) > 3:
613
614
            return self.help('set')
        self.core.information(*info)
615
616

    @command_args_parser.quoted(1, 2)
617
    def set_default(self, args):
618
619
620
621
622
623
624
625
        """
        /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
626
627
            option = args[1]
        else:
628
            return self.help('set_default')
629
630
631
632

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

    @command_args_parser.quoted(1)
637
    def toggle(self, args):
638
639
640
641
642
        """
        /toggle <option>
        shortcut for /set <option> toggle
        """
        if args is None:
643
            return self.help('toggle')
644
645

        if args[0]:
646
            self.set('%s toggle' % args[0])
647
648

    @command_args_parser.quoted(1, 1)
649
    def server_cycle(self, args):
650
651
652
653
        """
        Do a /cycle on each room of the given server.
        If none, do it on the current tab
        """
654
        tab = self.core.current_tab()
655
656
657
658
659
        message = ""
        if args:
            domain = args[0]
            if len(args) == 2:
                message = args[1]
mathieui's avatar
mathieui committed
660
        else:
661
662
            if isinstance(tab, tabs.MucTab):
                domain = safeJID(tab.name).domain
mathieui's avatar
mathieui committed
663
            else:
664
665
                return self.core.information("No server specified", "Error")
        for tab in self.core.get_tabs(tabs.MucTab):
666
667
668
669
670
671
672
673
            if tab.name.endswith(domain):
                if tab.joined:
                    muc.leave_groupchat(tab.core.xmpp,
                                        tab.name,
                                        tab.own_nick,
                                        message)
                tab.joined = False
                if tab.name == domain:
674
                    self.join('"@%s/%s"' %(tab.name, tab.own_nick))
675
                else:
676
                    self.join('"%s/%s"' %(tab.name, tab.own_nick))
677
678

    @command_args_parser.quoted(1)
679
    def last_activity(self, args):
680
681
682
683
684
685
686
        """
        /last_activity <jid>
        """
        def callback(iq):
            "Callback for the last activity"
            if iq['type'] != 'result':
                if iq['error']['type'] == 'auth':
687
688
689
                    self.core.information('You are not allowed to see the '
                                          'activity of this contact.',
                                          'Error')
690
                else:
691
                    self.core.information('Error retrieving the activity', 'Error')
692
693
694
695
696
697
698
699
                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))
mathieui's avatar
mathieui committed
700
            else:
701
                msg = 'The last activity of %s was %s ago%s' % (
mathieui's avatar
mathieui committed
702
                    from_,
703
704
                    common.parse_secs_to_str(seconds),
                    (' and his/her last status was %s' % status) if status else '')
705
            self.core.information(msg, 'Info')
706
707

        if args is None:
708
            return self.help('last_activity')
709
        jid = safeJID(args[0])
710
        self.core.xmpp.plugin['xep_0012'].get_last_activity(jid,
711
712
713
                                                       callback=callback)

    @command_args_parser.quoted(0, 2)
714
    def mood(self, args):
715
716
717
718
        """
        /mood [<mood> [text]]
        """
        if not args:
719
            return self.core.xmpp.plugin['xep_0107'].stop()
720
721
722

        mood = args[0]
        if mood not in pep.MOODS:
723
724
725
            return self.core.information('%s is not a correct value for a mood.'
                                             % mood,
                                          'Error')
726
727
        if len(args) == 2:
            text = args[1]
mathieui's avatar
mathieui committed
728
        else:
729
            text = None
730
731
        self.core.xmpp.plugin['xep_0107'].publish_mood(mood, text,
                                                       callback=dumb_callback)
732
733

    @command_args_parser.quoted(0, 3)
734
    def activity(self, args):
735
736
737
738
739
        """
        /activity [<general> [specific] [text]]
        """
        length = len(args)
        if not length:
740
            return self.core.xmpp.plugin['xep_0108'].stop()
741
742
743

        general = args[0]
        if general not in pep.ACTIVITIES:
744
745
746
            return self.core.information('%s is not a correct value for an activity'
                                             % general,
                                         'Error')
747
        specific = None
mathieui's avatar
mathieui committed
748
        text = None
749
750
751
752
753
754
        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
755
            specific = args[1]
756
757
            text = args[2]
        if specific and specific not in pep.ACTIVITIES[general]:
758
759
760
761
762
            return self.core.information('%s is not a correct value '
                                         'for an activity' % specific,
                                         'Error')
        self.core.xmpp.plugin['xep_0108'].publish_activity(general, specific, text,
                                                           callback=dumb_callback)
763
764

    @command_args_parser.quoted(0, 2)
765
    def gaming(self, args):
766
767
768
769
        """
        /gaming [<game name> [server address]]
        """
        if not args:
770
            return self.core.xmpp.plugin['xep_0196'].stop()
771
772
773
774

        name = args[0]
        if len(args) > 1:
            address = args[1]
mathieui's avatar
mathieui committed
775
        else:
776
            address = None
777
778
779
        return self.core.xmpp.plugin['xep_0196'].publish_gaming(name=name,
                                                                server_address=address,
                                                                callback=dumb_callback)
780
781

    @command_args_parser.quoted(2, 1, [None])
782
    def invite(self, args):
783
784
785
        """/invite <to> <room> [reason]"""

        if args is None:
786
            return self.help('invite')
787
788
789
790

        reason = args[2]
        to = safeJID(args[0])
        room = safeJID(args[1]).bare
791
792
        self.core.invite(to.full, room, reason=reason)
        self.core.information('Invited %s to %s' % (to.bare, room), 'Info')
793
794

    @command_args_parser.quoted(1, 1, [''])
795
    def decline(self, args):
796
797
        """/decline <room@server.tld> [reason]"""
        if args is None:
798
            return self.help('decline')
799
        jid = safeJID(args[0])
800
        if jid.bare not in self.core.pending_invites:
801
802
            return
        reason = args[1]
803
804
805
806
        del self.core.pending_invites[jid.bare]
        self.core.xmpp.plugin['xep_0045'].decline_invite(jid.bare,
                                                         self.core.pending_invites[jid.bare],
                                                         reason)
mathieui's avatar
mathieui committed
807
808
809

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

810
    @command_args_parser.ignored
811
    def invitations(self):
812
813
        """/invitations"""
        build = ""
814
        for invite in self.core.pending_invites:
815
            build += "%s by %s" % (invite,
816
817
                                   safeJID(self.core.pending_invites[invite]).bare)
        if self.core.pending_invites:
818
819
820
            build = "You are invited to the following rooms:\n" + build
        else:
            build = "You do not have any pending invitations."
821
        self.core.information(build, 'Info')
822
823

    @command_args_parser.quoted(0, 1, [None])
824
    def quit(self, args):
825
826
827
        """
        /quit [message]
        """
828
829
        if not self.core.xmpp.is_connected():
            self.core.exit()
830
831
832
833
            return

        msg = args[0]
        if config.get('enable_user_mood'):
834
            self.core.xmpp.plugin['xep_0107'].stop()
835
        if config.get('enable_user_activity'):
836
            self.core.xmpp.plugin['xep_0108'].stop()
837
        if config.get('enable_user_gaming'):
838
839
840
841
842
            self.core.xmpp.plugin['xep_0196'].stop()
        self.core.save_config()
        self.core.plugin_manager.disable_plugins()
        self.core.disconnect(msg)
        self.core.xmpp.add_event_handler("disconnected", self.core.exit, disposable=True)
843
844

    @command_args_parser.quoted(0, 1, [''])
845
    def destroy_room(self, args):
846
847
848
849
850
        """
        /destroy_room [JID]
        """
        room = safeJID(args[0]).bare
        if room:
851
852
853
            muc.destroy_room(self.core.xmpp, room)
        elif isinstance(self.core.current_tab(), tabs.MucTab) and not args[0]:
            muc.destroy_room(self.core.xmpp, self.core.current_tab().general_jid)
854
        else:
855
            self.core.information('Invalid JID: "%s"' % args[0], 'Error')
856
857

    @command_args_parser.quoted(1, 1, [''])
858
    def bind(self, args):
859
860
861
862
        """
        Bind a key.
        """
        if args is None:
863
            return self.help('bind')
864
865

        if not config.silent_set(args[0], args[1], section='bindings'):
866
            self.core.information('Unable to write in the config file', 'Error')
867
868

        if args[1]:
869
            self.core.information('%s is now bound to %s' % (args[0], args[1]), 'Info')
870
        else:
871
            self.core.information('%s is now unbound' % args[0], 'Info')
872
873

    @command_args_parser.raw
874
    def rawxml(self, args):
875
876
877
878
879
880
881
882
883
        """
        /rawxml <xml stanza>
        """

        if not args:
            return

        stanza = args
        try:
884
            stanza = StanzaBase(self.core.xmpp, xml=ET.fromstring(stanza))
885
886
887
            if stanza.xml.tag == 'iq' and stanza.xml.attrib.get('type') in ('get', 'set'):
                iq_id = stanza.xml.attrib.get('id')
                if not iq_id:
888
                    iq_id = self.core.xmpp.new_id()
889
890
891
892
                    stanza['id'] = iq_id

                def iqfunc(iq):
                    "handler for an iq reply"
893
894
                    self.core.information('%s' % iq, 'Iq')
                    self.core.xmpp.remove_handler('Iq %s' % iq_id)
895

896
                self.core.xmpp.register_handler(
897
898
899
900
                        Callback('Iq %s' % iq_id,
                            StanzaPath('iq@id=%s' % iq_id),
                            iqfunc
                            )
mathieui's avatar
mathieui committed
901
                        )
902
903
            stanza.send()
        except:
904
            self.core.information('Could not send custom stanza', 'Error')
905
            log.debug('/rawxml: Could not send custom stanza (%s)',
906
907
                      repr(stanza),
                      exc_info=True)
908
909
910


    @command_args_parser.quoted(1, 256)
911
    def load(self, args):
912
913
914
915
916
        """
        /load <plugin> [<otherplugin> …]
        # TODO: being able to load more than 256 plugins at once, hihi.
        """
        for plugin in args:
917
            self.core.plugin_manager.load(plugin)
918
919

    @command_args_parser.quoted(1, 256)
920
    def unload(self, args):
921
922
923
924
        """
        /unload <plugin> [<otherplugin> …]
        """
        for plugin in args:
925
            self.core.plugin_manager.unload(plugin)
926
927

    @command_args_parser.ignored
928
    def plugins(self):
929
930
931
        """
        /plugins
        """
932
933
934
        self.core.information("Plugins currently in use: %s" %
                                  repr(list(self.core.plugin_manager.plugins.keys())),
                              'Info')
935
936

    @command_args_parser.quoted(1, 1)
937
    def message(self, args):
938
939
940
941
        """
        /message <jid> [message]
        """
        if args is None:
942
            return self.help('message')
943
944
        jid = safeJID(args[0])
        if not jid.user and not jid.domain and not jid.resource:
945
946
947
            return self.core.information('Invalid JID.', 'Error')
        tab = self.core.get_conversation_by_jid(jid.full,