commands.py 33.3 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

import common
import fixes
import pep
import tabs
mathieui's avatar
mathieui committed
21
from bookmarks import Bookmark
mathieui's avatar
mathieui committed
22
from common import safeJID
Eijebong's avatar
Eijebong committed
23
from config import config, DEFAULT_CONFIG, options as config_opts
mathieui's avatar
mathieui committed
24
import multiuserchat as muc
25
from plugin import PluginConfig
mathieui's avatar
mathieui committed
26
27
from roster import roster
from theming import dump_tuple, get_theme
28
from decorators import command_args_parser
mathieui's avatar
mathieui committed
29
30
31
32

from . structs import Command, possible_show


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

        msg = '\n'.join(buff)
67
        msg += "\nType /help <command_name> to know what each command does"
68
    else:
mathieui's avatar
mathieui committed
69
70
71
72
73
74
75
        command = args[0].lstrip('/').strip()

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

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

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

mathieui's avatar
mathieui committed
114
    if not args[0] in possible_show.keys():
115
116
        return self.command_help('status')

mathieui's avatar
mathieui committed
117
    show = possible_show[args[0]]
118
119
    msg = args[1]

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

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

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

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

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

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

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

228
    current_tab = self.current_tab()
229
230
231
232
233
    if args[0] == '.':
        args[0] = current_tab.nb
    if args[1] == '.':
        args[1] = current_tab.nb

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

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

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

298
    if args is None:
mathieui's avatar
mathieui committed
299
        return self.command_help('version')
300

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

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

    if room.startswith('@'):
        room = room[1:]
    current_status = self.get_status()
    if not histo_length:
373
        histo_length = config.get('muc_history_length')
mathieui's avatar
mathieui committed
374
375
376
377
378
        if histo_length == -1:
            histo_length = None
    if histo_length is not None:
        histo_length = str(histo_length)
    if password is None: # try to use a saved password
379
        password = config.get_by_tabname('password', room, fallback=False)
mathieui's avatar
mathieui committed
380
381
    if tab and not tab.joined:
        if tab.last_connection:
382
383
384
385
386
            if tab.last_connection is not None:
                delta = datetime.now() - tab.last_connection
                seconds = delta.seconds + delta.days * 24 * 3600
            else:
                seconds = 0
mathieui's avatar
mathieui committed
387
388
389
            seconds = int(seconds)
        else:
            seconds = 0
390
391
392
393
394
        # If we didn’t have a password by now (from a bookmark or the
        # explicit argument), just use the password that is stored in the
        # tab because of our last join
        if not password:
            password = tab.password
mathieui's avatar
mathieui committed
395
        muc.join_groupchat(self, room, nick, password,
396
397
398
399
                           histo_length,
                           current_status.message,
                           current_status.show,
                           seconds=seconds)
400
401
        # Store in the tab the password we used, for later use
        tab.password = password
mathieui's avatar
mathieui committed
402
    if not tab:
403
        self.open_new_room(room, nick, password=password)
mathieui's avatar
mathieui committed
404
        muc.join_groupchat(self, room, nick, password,
405
406
407
                           histo_length,
                           current_status.message,
                           current_status.show)
mathieui's avatar
mathieui committed
408
409
410
411
412
413
414
415
416
417
    else:
        tab.own_nick = nick
        tab.users = []
    if tab and tab.joined:
        self.enable_private_tabs(room)
        tab.state = "normal"
        if tab == self.current_tab():
            tab.refresh()
            self.doupdate()

418
419
@command_args_parser.quoted(0, 2)
def command_bookmark_local(self, args):
mathieui's avatar
mathieui committed
420
421
422
423
424
    """
    /bookmark_local [room][/nick] [password]
    """
    if not args and not isinstance(self.current_tab(), tabs.MucTab):
        return
425
426
427
428
    password = args[1] if len(args) > 1 else None
    jid = args[0] if args else None

    _add_bookmark(self, jid, True, password, 'local')
mathieui's avatar
mathieui committed
429

430
431
@command_args_parser.quoted(0, 3)
def command_bookmark(self, args):
mathieui's avatar
mathieui committed
432
433
434
    """
    /bookmark [room][/nick] [autojoin] [password]
    """
435
436
437
438
439
    if not args and not isinstance(self.current_tab(), tabs.MucTab):
        return
    jid = args[0] if args else ''
    password = args[2] if len(args) > 2 else None

440
    if not config.get('use_remote_bookmarks'):
441
        return _add_bookmark(self, jid, True, password, 'local')
442

443
444
445
446
447
448
449
450
    if len(args) > 1:
        autojoin = False if args[1].lower() != 'true' else True
    else:
        autojoin = True

    _add_bookmark(self, jid, autojoin, password, 'remote')

def _add_bookmark(self, jid, autojoin, password, method):
mathieui's avatar
mathieui committed
451
    nick = None
452
    if not jid:
mathieui's avatar
mathieui committed
453
        tab = self.current_tab()
454
        roomname = tab.name
455
        if tab.joined and tab.own_nick != self.own_nick:
mathieui's avatar
mathieui committed
456
            nick = tab.own_nick
457
458
        if password is None and tab.password is not None:
            password = tab.password
459
460
    elif jid == '*':
        return _add_wildcard_bookmarks(self, method)
mathieui's avatar
mathieui committed
461
    else:
462
        info = safeJID(jid)
mathieui's avatar
mathieui committed
463
        roomname, nick = info.bare, info.resource
mathieui's avatar
mathieui committed
464
465
466
        if roomname == '':
            if not isinstance(self.current_tab(), tabs.MucTab):
                return
467
            roomname = self.current_tab().name
468
469
470
471
472
473
    bookmark = self.bookmarks[roomname]
    if bookmark is None:
        bookmark = Bookmark(roomname)
        self.bookmarks.append(bookmark)
    bookmark.method = method
    bookmark.autojoin = autojoin
mathieui's avatar
mathieui committed
474
    if nick:
475
        bookmark.nick = nick
mathieui's avatar
mathieui committed
476
    if password:
477
478
        bookmark.password = password
    def callback(iq):
479
480
481
482
        if iq["type"] != "error":
            self.information('Bookmark added.', 'Info')
        else:
            self.information("Could not add the bookmarks.", "Info")
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
    self.bookmarks.save_local()
    self.bookmarks.save_remote(self.xmpp, callback)

def _add_wildcard_bookmarks(self, method):
    new_bookmarks = []
    for tab in self.get_tabs(tabs.MucTab):
        bookmark = self.bookmarks[tab.name]
        if not bookmark:
            bookmark = Bookmark(tab.name, autojoin=True,
                                method=method)
            new_bookmarks.append(bookmark)
        else:
            bookmark.method = method
            new_bookmarks.append(bookmark)
            self.bookmarks.remove(bookmark)
    new_bookmarks.extend(self.bookmarks.bookmarks)
    self.bookmarks.set(new_bookmarks)
    def _cb(iq):
        if iq["type"] != "error":
            self.information("Bookmarks saved.", "Info")
        else:
            self.information("Could not save the remote bookmarks.", "Info")
    self.bookmarks.save_local()
    self.bookmarks.save_remote(self.xmpp, _cb)
mathieui's avatar
mathieui committed
507

508
509
@command_args_parser.ignored
def command_bookmarks(self):
mathieui's avatar
mathieui committed
510
    """/bookmarks"""
mathieui's avatar
mathieui committed
511
    tab = self.get_tab_by_name('Bookmarks', tabs.BookmarksTab)
mathieui's avatar
mathieui committed
512
    old_tab = self.current_tab()
mathieui's avatar
mathieui committed
513
514
515
516
517
518
    if tab:
        self.current_tab_nb = tab.nb
    else:
        tab = tabs.BookmarksTab(self.bookmarks)
        self.tabs.append(tab)
        self.current_tab_nb = tab.nb
mathieui's avatar
mathieui committed
519
520
    old_tab.on_lose_focus()
    tab.on_gain_focus()
mathieui's avatar
mathieui committed
521
    self.refresh_window()
mathieui's avatar
mathieui committed
522

523
524
@command_args_parser.quoted(0, 1)
def command_remove_bookmark(self, args):
mathieui's avatar
mathieui committed
525
    """/remove_bookmark [jid]"""
526

mathieui's avatar
mathieui committed
527
528
    def cb(success):
        if success:
529
            self.information('Bookmark deleted', 'Info')
mathieui's avatar
mathieui committed
530
        else:
531
            self.information('Error while deleting the bookmark', 'Error')
mathieui's avatar
mathieui committed
532

mathieui's avatar
mathieui committed
533
534
    if not args:
        tab = self.current_tab()
mathieui's avatar
mathieui committed
535
536
537
        if isinstance(tab, tabs.MucTab) and self.bookmarks[tab.name]:
            self.bookmarks.remove(tab.name)
            self.bookmarks.save(self.xmpp, callback=cb)
mathieui's avatar
mathieui committed
538
        else:
539
            self.information('No bookmark to remove', 'Info')
mathieui's avatar
mathieui committed
540
    else:
mathieui's avatar
mathieui committed
541
542
543
        if self.bookmarks[args[0]]:
            self.bookmarks.remove(args[0])
            self.bookmarks.save(self.xmpp, callback=cb)
mathieui's avatar
mathieui committed
544
        else:
545
            self.information('No bookmark to remove', 'Info')
mathieui's avatar
mathieui committed
546

mathieui's avatar
mathieui committed
547
@command_args_parser.quoted(0, 3)
548
def command_set(self, args):
mathieui's avatar
mathieui committed
549
    """
550
    /set [module|][section] <option> [value]
mathieui's avatar
mathieui committed
551
    """
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
    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:
mathieui's avatar
mathieui committed
568
        option = args[0]
569
        value = config.get(option)
mathieui's avatar
mathieui committed
570
571
        if value is None and '=' in option:
            args = option.split('=', 1)
572
        info = ('%s=%s' % (option, value), 'Info')
mathieui's avatar
mathieui committed
573
    if len(args) == 2:
574
575
576
577
578
579
        if '|' in args[0]:
            plugin_name, section = args[0].split('|')[:2]
            if not section:
                section = plugin_name
            option = args[1]
            if not plugin_name in self.plugin_manager.plugins:
580
581
582
583
584
585
                file_name = self.plugin_manager.plugins_conf_dir
                file_name = os.path.join(file_name, plugin_name + '.cfg')
                plugin_config = PluginConfig(file_name, plugin_name)
            else:
                plugin_config = self.plugin_manager.plugins[plugin_name].config
            value = plugin_config.get(option, default='', section=section)
586
587
588
589
590
591
592
593
594
595
596
597
598
            info = ('%s=%s' % (option, value), 'Info')
        else:
            possible_section = args[0]
            if config.has_section(possible_section):
                section = possible_section
                option = args[1]
                value = config.get(option, section=section)
                info = ('%s=%s' % (option, value), 'Info')
            else:
                option = args[0]
                value = args[1]
                info = config.set_and_save(option, value)
                self.trigger_configuration_change(option, value)
mathieui's avatar
mathieui committed
599
600
601
602
603
604
605
606
    elif len(args) == 3:
        if '|' in args[0]:
            plugin_name, section = args[0].split('|')[:2]
            if not section:
                section = plugin_name
            option = args[1]
            value = args[2]
            if not plugin_name in self.plugin_manager.plugins:
607
608
609
610
611
612
                file_name = self.plugin_manager.plugins_conf_dir
                file_name = os.path.join(file_name, plugin_name + '.cfg')
                plugin_config = PluginConfig(file_name, plugin_name)
            else:
                plugin_config = self.plugin_manager.plugins[plugin_name].config
            info = plugin_config.set_and_save(option, value, section)
mathieui's avatar
mathieui committed
613
        else:
614
615
616
            if args[0] == '.':
                name = safeJID(self.current_tab().name).bare
                if not name:
617
618
                    self.information('Invalid tab to use the "." argument.',
                                     'Error')
619
620
621
622
                    return
                section = name
            else:
                section = args[0]
mathieui's avatar
mathieui committed
623
624
625
626
            option = args[1]
            value = args[2]
            info = config.set_and_save(option, value, section)
            self.trigger_configuration_change(option, value)
mathieui's avatar
mathieui committed
627
    elif len(args) > 3:
Eijebong's avatar
Eijebong committed
628
        return self.command_help('set')
mathieui's avatar
mathieui committed
629
630
    self.information(*info)

Eijebong's avatar
Eijebong committed
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
@command_args_parser.quoted(1, 2)
def command_set_default(self, args):
    """
    /set_default [section] <option>
    """
    if len(args) == 1:
        option = args[0]
        section = 'Poezio'
    elif len(args) == 2:
        section = args[0]
        option = args[1]
    else:
        return self.command_help('set_default')

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

651
652
@command_args_parser.quoted(1)
def command_toggle(self, args):
mathieui's avatar
mathieui committed
653
654
655
656
    """
    /toggle <option>
    shortcut for /set <option> toggle
    """
657
658
659
660
    if args is None:
        return self.command_help('toggle')

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

663
664
@command_args_parser.quoted(1, 1)
def command_server_cycle(self, args):
mathieui's avatar
mathieui committed
665
    """
666
667
    Do a /cycle on each room of the given server.
    If none, do it on the current tab
mathieui's avatar
mathieui committed
668
669
670
    """
    tab = self.current_tab()
    message = ""
671
    if args:
mathieui's avatar
mathieui committed
672
        domain = args[0]
673
        if len(args) == 2:
mathieui's avatar
mathieui committed
674
675
676
            message = args[1]
    else:
        if isinstance(tab, tabs.MucTab):
677
            domain = safeJID(tab.name).domain
mathieui's avatar
mathieui committed
678
        else:
679
            return self.information("No server specified", "Error")
mathieui's avatar
mathieui committed
680
    for tab in self.get_tabs(tabs.MucTab):
681
        if tab.name.endswith(domain):
mathieui's avatar
mathieui committed
682
            if tab.joined:
683
                muc.leave_groupchat(tab.core.xmpp,
684
                                    tab.name,
685
686
                                    tab.own_nick,
                                    message)
mathieui's avatar
mathieui committed
687
            tab.joined = False
688
689
            if tab.name == domain:
                self.command_join('"@%s/%s"' %(tab.name, tab.own_nick))
mathieui's avatar
mathieui committed
690
            else:
691
                self.command_join('"%s/%s"' %(tab.name, tab.own_nick))
mathieui's avatar
mathieui committed
692

693
694
@command_args_parser.quoted(1)
def command_last_activity(self, args):
mathieui's avatar
mathieui committed
695
696
697
698
    """
    /last_activity <jid>
    """
    def callback(iq):
699
        "Callback for the last activity"
mathieui's avatar
mathieui committed
700
701
        if iq['type'] != 'result':
            if iq['error']['type'] == 'auth':
702
703
704
                self.information('You are not allowed to see the '
                                 'activity of this contact.',
                                 'Error')
mathieui's avatar
mathieui committed
705
            else:
706
                self.information('Error retrieving the activity', 'Error')
mathieui's avatar
mathieui committed
707
708
709
710
711
712
713
714
715
716
717
718
            return
        seconds = iq['last_activity']['seconds']
        status = iq['last_activity']['status']
        from_ = iq['from']
        if not safeJID(from_).user:
            msg = 'The uptime of %s is %s.' % (
                    from_,
                    common.parse_secs_to_str(seconds))
        else:
            msg = 'The last activity of %s was %s ago%s' % (
                from_,
                common.parse_secs_to_str(seconds),
719
                (' and his/her last status was %s' % status) if status else '')
mathieui's avatar
mathieui committed
720
        self.information(msg, 'Info')
721
722

    if args is None:
mathieui's avatar
mathieui committed
723
        return self.command_help('last_activity')
724
    jid = safeJID(args[0])
725
726
    self.xmpp.plugin['xep_0012'].get_last_activity(jid,
                                                   callback=callback)
mathieui's avatar
mathieui committed
727

728
729
@command_args_parser.quoted(0, 2)
def command_mood(self, args):
mathieui's avatar
mathieui committed
730
731
732
733
    """
    /mood [<mood> [text]]
    """
    if not args:
734
735
        return self.xmpp.plugin['xep_0107'].stop()

mathieui's avatar
mathieui committed
736
737
    mood = args[0]
    if mood not in pep.MOODS:
738
        return self.information('%s is not a correct value for a mood.'
739
                                % mood,
740
                                'Error')
741
    if len(args) == 2:
mathieui's avatar
mathieui committed
742
743
744
        text = args[1]
    else:
        text = None
745
746
    self.xmpp.plugin['xep_0107'].publish_mood(mood, text,
                                              callback=dumb_callback)
mathieui's avatar
mathieui committed
747

748
749
@command_args_parser.quoted(0, 3)
def command_activity(self, args):
mathieui's avatar
mathieui committed
750
751
752
753
754
    """
    /activity [<general> [specific] [text]]
    """
    length = len(args)
    if not length:
755
756
        return self.xmpp.plugin['xep_0108'].stop()

mathieui's avatar
mathieui committed
757
758
    general = args[0]
    if general not in pep.ACTIVITIES:
759
        return self.information('%s is not a correct value for an activity'
760
                                    % general,
761
                                'Error')
mathieui's avatar
mathieui committed
762
763
764
765
766
767
768
769
770
771
772
    specific = None
    text = None
    if length == 2:
        if args[1] in pep.ACTIVITIES[general]:
            specific = args[1]
        else:
            text = args[1]
    elif length == 3:
        specific = args[1]
        text = args[2]
    if specific and specific not in pep.ACTIVITIES[general]:
773
774
775
        return self.information('%s is not a correct value '
                                'for an activity' % specific,
                                'Error')
776
777
    self.xmpp.plugin['xep_0108'].publish_activity(general, specific, text,
                                                  callback=dumb_callback)
mathieui's avatar
mathieui committed
778

779
780
@command_args_parser.quoted(0, 2)
def command_gaming(self, args):
mathieui's avatar
mathieui committed
781
782
783
784
    """
    /gaming [<game name> [server address]]
    """
    if not args:
785
786
        return self.xmpp.plugin['xep_0196'].stop()

mathieui's avatar
mathieui committed
787
788
789
790
791
    name = args[0]
    if len(args) > 1:
        address = args[1]
    else:
        address = None
792
793
    return self.xmpp.plugin['xep_0196'].publish_gaming(name=name,
                                                       server_address=address,
794
                                                       callback=dumb_callback)
mathieui's avatar
mathieui committed
795

796
797
@command_args_parser.quoted(2, 1, [None])
def command_invite(self, args):
mathieui's avatar
mathieui committed
798
    """/invite <to> <room> [reason]"""
799
800
801
802
803

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

    reason = args[2]
mathieui's avatar
mathieui committed
804
    to = safeJID(args[0])
805
806
    room = safeJID(args[1]).bare
    self.invite(to.full, room, reason=reason)
mathieui's avatar
mathieui committed
807

808
809
@command_args_parser.quoted(1, 1, [''])
def command_decline(self, args):
mathieui's avatar
mathieui committed
810
    """/decline <room@server.tld> [reason]"""
811
812
    if args is None:
        return self.command_help('decline')
mathieui's avatar
mathieui committed
813
814
815
    jid = safeJID(args[0])
    if jid.bare not in self.pending_invites:
        return
816
    reason = args[1]
mathieui's avatar
mathieui committed
817
    del self.pending_invites[jid.bare]
818
819
820
    self.xmpp.plugin['xep_0045'].decline_invite(jid.bare,
                                                self.pending_invites[jid.bare],
                                                reason)
mathieui's avatar
mathieui committed
821
822
823

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

824
825
@command_args_parser.ignored
def command_invitations(self):
mathieui's avatar
mathieui committed
826
827
828
    """/invitations"""
    build = ""
    for invite in self.pending_invites:
829
830
        build += "%s by %s" % (invite,
                               safeJID(self.pending_invites[invite]).bare)
mathieui's avatar
mathieui committed
831
832
833
834
835
836
    if self.pending_invites:
        build = "You are invited to the following rooms:\n" + build
    else:
        build = "You do not have any pending invitations."
    self.information(build, 'Info')

837
838
@command_args_parser.quoted(0, 1, [None])
def command_quit(self, args):
mathieui's avatar
mathieui committed
839
    """
840
    /quit [message]
mathieui's avatar
mathieui committed
841
    """
842
843
844
    if not self.xmpp.is_connected():
        self.exit()
        return
845
846

    msg = args[0]
847
    if config.get('enable_user_mood'):
848
        self.xmpp.plugin['xep_0107'].stop()
849
    if config.get('enable_user_activity'):
850
        self.xmpp.plugin['xep_0108'].stop()
851
    if config.get('enable_user_gaming'):
852
        self.xmpp.plugin['xep_0196'].stop()
mathieui's avatar
mathieui committed
853
854
855
    self.save_config()
    self.plugin_manager.disable_plugins()
    self.disconnect(msg)
856
    self.xmpp.add_event_handler("disconnected", self.exit, disposable=True)
mathieui's avatar
mathieui committed
857

858
859
@command_args_parser.quoted(0, 1, [''])
def command_destroy_room(self, args):
mathieui's avatar
mathieui committed
860
861
862
    """
    /destroy_room [JID]
    """
863
    room = safeJID(args[0]).bare
mathieui's avatar
mathieui committed
864
865
    if room:
        muc.destroy_room(self.xmpp, room)
mathieui's avatar
mathieui committed
866
    elif isinstance(self.current_tab(), tabs.MucTab) and not args[0]:
mathieui's avatar
mathieui committed
867
868
        muc.destroy_room(self.xmpp, self.current_tab().general_jid)
    else:
869
        self.information('Invalid JID: "%s"' % args[0], 'Error')
mathieui's avatar
mathieui committed
870

871
872
@command_args_parser.quoted(1, 1, [''])
def command_bind(self, args):
mathieui's avatar
mathieui committed
873
874
875
    """
    Bind a key.
    """
876
    if args is None:
mathieui's avatar
mathieui committed
877
        return self.command_help('bind')
878

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

mathieui's avatar
mathieui committed
882
883
884
885
886
    if args[1]:
        self.information('%s is now bound to %s' % (args[0], args[1]), 'Info')
    else:
        self.information('%s is now unbound' % args[0], 'Info')

887
888
@command_args_parser.raw
def command_rawxml(self, args):
mathieui's avatar
mathieui committed
889
890
891
892
    """
    /rawxml <xml stanza>
    """

893
894
    if not args:
        return
mathieui's avatar
mathieui committed
895

mathieui's avatar
mathieui committed
896
    stanza = args
mathieui's avatar
mathieui committed
897
    try:
898
        stanza = StanzaBase(self.xmpp, xml=ET.fromstring(stanza))
899
        if stanza.xml.tag == 'iq' and stanza.xml.attrib.get('type') in ('get', 'set'):
mathieui's avatar
mathieui committed
900
            iq_id = stanza.xml.attrib.get('id')
901
902
903
            if not iq_id:
                iq_id = self.xmpp.new_id()
                stanza['id'] = iq_id
mathieui's avatar
mathieui committed
904
905

            def iqfunc(iq):
906
                "handler for an iq reply"
mathieui's avatar
mathieui committed
907
908
909
910
911
912
913
914
915
916
917
918
919
920
                self.information('%s' % iq, 'Iq')
                self.xmpp.remove_handler('Iq %s' % iq_id)

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

        stanza.send()
    except:
921
        self.information('Could not send custom stanza', 'Error')
mathieui's avatar
mathieui committed
922
        log.debug('/rawxml: Could not send custom stanza (%s)',
923
                repr(stanza),
mathieui's avatar
mathieui committed
924
925
926
                exc_info=True)


927
928
@command_args_parser.quoted(1, 256)
def command_load(self, args):
mathieui's avatar
mathieui committed
929
    """
930
    /load <plugin> [<otherplugin> …]
931
    # TODO: being able to load more than 256 plugins at once, hihi.
mathieui's avatar
mathieui committed
932
    """
933
934
    for plugin in args:
        self.plugin_manager.load(plugin)
mathieui's avatar
mathieui committed
935

936
937
@command_args_parser.quoted(1, 256)
def command_unload(self, args):
mathieui's avatar
mathieui committed
938
    """
939
    /unload <plugin> [<otherplugin> …]
mathieui's avatar
mathieui committed
940
    """
941
942
    for plugin in args:
        self.plugin_manager.unload(plugin)
mathieui's avatar
mathieui committed
943

944
945
@command_args_parser.ignored
def command_plugins(self):
mathieui's avatar
mathieui committed
946
947
948
    """
    /plugins
    """
949
    self.information("Plugins currently in use: %s" %
950
                        repr(list(self.plugin_manager.plugins.keys())),
951
                     'Info')
mathieui's avatar
mathieui committed
952

953
954
@command_args_parser.quoted(1, 1)
def command_message(self, args):
mathieui's avatar
mathieui committed
955
956
957
    """
    /message <jid> [message]
    """
958
959
    if args is None:
        return self.command_help('message')
mathieui's avatar
mathieui committed
960
961
962
963
    jid = safeJID(args[0])
    if not jid.user and not jid.domain and not jid.resource:
        return self.information('Invalid JID.', 'Error')
    tab = self.get_conversation_by_jid(jid.full, False, fallback_barejid=False)
964
965
    muc = self.get_tab_by_name(jid.bare, typ=tabs.MucTab)
    if not tab and not muc:
mathieui's avatar
mathieui committed
966
        tab = self.open_conversation_window(jid.full, focus=True)
967
968
969
970
971
972
    elif muc:
        tab = self.get_tab_by_name(jid.full, typ=tabs.PrivateTab)
        if tab:
            self.focus_tab_named(tab.name)
        else:
            tab = self.open_private_window(jid.bare, jid.resource)
mathieui's avatar
mathieui committed
973
    else:
974
        self.focus_tab_named(tab.name)
975
    if len(args) == 2:
mathieui's avatar
mathieui committed
976
977
        tab.command_say(args[1])

978
979
@command_args_parser.ignored
def command_xml_tab(self):
mathieui's avatar
mathieui committed
980
981
982
983
984
    """/xml_tab"""
    xml_tab = self.focus_tab_named('XMLTab', tabs.XMLTab)
    if not xml_tab:
        tab = tabs.XMLTab()
        self.add_tab(tab, True)
985
        self.xml_tab = tab
mathieui's avatar
mathieui committed
986

987
988
989
@command_args_parser.quoted(1)
def command_adhoc(self, args):
    if not args:
990
        return self.command_help('ad-hoc')
991
    jid = safeJID(args[0])
992
993
994
    list_tab = tabs.AdhocCommandsListTab(jid)
    self.add_tab(list_tab, True)
    cb = list_tab.on_list_received
995
    self.xmpp.plugin['xep_0050'].get_commands(jid=jid, local=False,
996
997
                                              callback=cb)

998
999
@command_args_parser.ignored
def command_self(self):
mathieui's avatar
mathieui committed
1000
    """
For faster browsing, not all history is shown. View entire blame