commands.py 35.9 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
from xml.etree import cElementTree as ET

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

16
17
18
19
20
21
22
23
24
25
26
27
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
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):
47
48
                    acc.append('  \x19%s}%s\x19o - %s' % (
                                   color,
49
50
                                   name,
                                   command.short_desc))
51
                else:
52
                    acc.append('  \x19%s}%s\x19o' % (color, name))
53
54
55
56
            acc = sorted(acc)
            buff.extend(acc)
            acc = []
            buff.append('Tab-specific commands:')
57
            tab_commands = self.core.current_tab().commands
mathieui's avatar
mathieui committed
58
            for name, command in tab_commands.items():
59
                if isinstance(command, Command):
60
61
                    acc.append('  \x19%s}%s\x19o - %s' % (
                                    color,
62
63
                                    name,
                                    command.short_desc))
64
                else:
65
                    acc.append('  \x19%s}%s\x19o' % (color, name))
66
67
68
69
70
71
72
73
            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()

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

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

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

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

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

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

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

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

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

mathieui's avatar
mathieui committed
195
        name = args[0]
mathieui's avatar
mathieui committed
196
        try:
mathieui's avatar
mathieui committed
197
            number = int(name)
mathieui's avatar
mathieui committed
198
        except ValueError:
mathieui's avatar
mathieui committed
199
            number = -1
200
            name = name.lower()
mathieui's avatar
mathieui committed
201
        if number != -1 and self.core.current_tab_nb == number:
mathieui's avatar
mathieui committed
202
            return
mathieui's avatar
mathieui committed
203
        prev_nb = self.core.previous_tab_nb
204
205
        self.core.previous_tab_nb = self.core.current_tab_nb
        old_tab = self.core.current_tab()
mathieui's avatar
mathieui committed
206
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
            self.core.current_tab_nb = number
mathieui's avatar
mathieui committed
211
        else:
212
            matchs = []
213
            for tab in self.core.tabs:
mathieui's avatar
mathieui committed
214
                for tab_name in tab.matching_names():
215
                    if tab_name[1] and name in tab_name[1].lower():
mathieui's avatar
mathieui committed
216
                        matchs.append((tab_name[0], tab))
217
            if not matchs:
218
                self.core.previous_tab_nb = prev_nb
219
220
                return
            tab = min(matchs, key=lambda m: m[0])[1]
221
            self.core.current_tab_nb = tab.nb
222
        old_tab.on_lose_focus()
223
224
        self.core.current_tab().on_gain_focus()
        self.core.refresh_window()
225
226

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

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

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

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

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

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

mathieui's avatar
mathieui committed
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
358
    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)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        if args is None:
701
            return self.help('last_activity')
702
        jid = safeJID(args[0])
703
        self.core.xmpp.plugin['xep_0012'].get_last_activity(jid,
704
705
706
                                                       callback=callback)

    @command_args_parser.quoted(0, 2)
707
    def mood(self, args):
708
709
710
711
        """
        /mood [<mood> [text]]
        """
        if not args:
712
            return self.core.xmpp.plugin['xep_0107'].stop()
713
714
715

        mood = args[0]
        if mood not in pep.MOODS:
716
717
718
            return self.core.information('%s is not a correct value for a mood.'
                                             % mood,
                                          'Error')
719
720
        if len(args) == 2:
            text = args[1]
mathieui's avatar
mathieui committed
721
        else:
722
            text = None
723
724
        self.core.xmpp.plugin['xep_0107'].publish_mood(mood, text,
                                                       callback=dumb_callback)
725
726

    @command_args_parser.quoted(0, 3)
727
    def activity(self, args):
728
729
730
731
732
        """
        /activity [<general> [specific] [text]]
        """
        length = len(args)
        if not length:
733
            return self.core.xmpp.plugin['xep_0108'].stop()
734
735
736

        general = args[0]
        if general not in pep.ACTIVITIES:
737
738
739
            return self.core.information('%s is not a correct value for an activity'
                                             % general,
                                         'Error')
740
        specific = None
mathieui's avatar
mathieui committed
741
        text = None
742
743
744
745
746
747
        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
748
            specific = args[1]
749
750
            text = args[2]
        if specific and specific not in pep.ACTIVITIES[general]:
751
752
753
754
755
            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)
756
757

    @command_args_parser.quoted(0, 2)
758
    def gaming(self, args):
759
760
761
762
        """
        /gaming [<game name> [server address]]
        """
        if not args:
763
            return self.core.xmpp.plugin['xep_0196'].stop()
764
765
766
767

        name = args[0]
        if len(args) > 1:
            address = args[1]
mathieui's avatar
mathieui committed
768
        else:
769
            address = None
770
771
772
        return self.core.xmpp.plugin['xep_0196'].publish_gaming(name=name,
                                                                server_address=address,
                                                                callback=dumb_callback)
773
774

    @command_args_parser.quoted(2, 1, [None])
775
    def invite(self, args):
776
777
778
        """/invite <to> <room> [reason]"""

        if args is None:
779
            return self.help('invite')
780
781
782
783

        reason = args[2]
        to = safeJID(args[0])
        room = safeJID(args[1]).bare
784
785
        self.core.invite(to.full, room, reason=reason)
        self.core.information('Invited %s to %s' % (to.bare, room), 'Info')
786
787

    @command_args_parser.quoted(1, 1, [''])
788
    def decline(self, args):
789
790
        """/decline <room@server.tld> [reason]"""
        if args is None:
791
            return self.help('decline')
792
        jid = safeJID(args[0])
793
        if jid.bare not in self.core.pending_invites:
794
795
            return
        reason = args[1]
796
797
798
799
        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
800
801
802

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

803
    @command_args_parser.ignored
804
    def invitations(self):
805
806
        """/invitations"""
        build = ""
807
        for invite in self.core.pending_invites:
808
            build += "%s by %s" % (invite,
809
810
                                   safeJID(self.core.pending_invites[invite]).bare)
        if self.core.pending_invites:
811
812
813
            build = "You are invited to the following rooms:\n" + build
        else:
            build = "You do not have any pending invitations."
814
        self.core.information(build, 'Info')
815
816

    @command_args_parser.quoted(0, 1, [None])
817
    def quit(self, args):
818
819
820
        """
        /quit [message]
        """
821
822
        if not self.core.xmpp.is_connected():
            self.core.exit()
823
824
825
826
            return

        msg = args[0]
        if config.get('enable_user_mood'):
827
            self.core.xmpp.plugin['xep_0107'].stop()
828
        if config.get('enable_user_activity'):
829
            self.core.xmpp.plugin['xep_0108'].stop()
830
        if config.get('enable_user_gaming'):
831
832
833
834
835
            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)
836
837

    @command_args_parser.quoted(0, 1, [''])
838
    def destroy_room(self, args):
839
840
841
842
843
        """
        /destroy_room [JID]
        """
        room = safeJID(args[0]).bare
        if room:
844
845
846
            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)
847
        else:
848
            self.core.information('Invalid JID: "%s"' % args[0], 'Error')
849
850

    @command_args_parser.quoted(1, 1, [''])
851
    def bind(self, args):
852
853
854
855
        """
        Bind a key.
        """
        if args is None:
856
            return self.help('bind')
857
858

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

        if args[1]:
862
            self.core.information('%s is now bound to %s' % (args[0], args[1]), 'Info')
863
        else:
864
            self.core.information('%s is now unbound' % args[0], 'Info')
865
866

    @command_args_parser.raw
867
    def rawxml(self, args):
868
869
870
871
872
873
874
875
876
        """
        /rawxml <xml stanza>
        """

        if not args:
            return

        stanza = args
        try:
877
            stanza = StanzaBase(self.core.xmpp, xml=ET.fromstring(stanza))
878
879
880
            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:
881
                    iq_id = self.core.xmpp.new_id()
882
883
884
885
                    stanza['id'] = iq_id

                def iqfunc(iq):
                    "handler for an iq reply"
886
887
                    self.core.information('%s' % iq, 'Iq')
                    self.core.xmpp.remove_handler('Iq %s' % iq_id)
888

889
                self.core.xmpp.register_handler(
890
891
892
893
                        Callback('Iq %s' % iq_id,
                            StanzaPath('iq@id=%s' % iq_id),
                            iqfunc
                            )
mathieui's avatar
mathieui committed
894
                        )
895
896
            stanza.send()
        except:
897
            self.core.information('Could not send custom stanza', 'Error')
898
            log.debug('/rawxml: Could not send custom stanza (%s)',
899
900
                      repr(stanza),
                      exc_info=True)
901
902
903


    @command_args_parser.quoted(1, 256)
904
    def load(self, args):
905
906
907
908
909
        """
        /load <plugin> [<otherplugin> …]
        # TODO: being able to load more than 256 plugins at once, hihi.
        """
        for plugin in args:
910
            self.core.plugin_manager.load(plugin)
911
912

    @command_args_parser.quoted(1, 256)
913
    def unload(self, args):
914
915
916
917
        """
        /unload <plugin> [<otherplugin> …]
        """
        for plugin in args:
918
            self.core.plugin_manager.unload(plugin)
919
920

    @command_args_parser.ignored
921
    def plugins(self):
922
923
924
        """
        /plugins
        """
925
926
927
        self.core.information("Plugins currently in use: %s" %
                                  repr(list(self.core.plugin_manager.plugins.keys())),
                              'Info')