Script: histman.py

Save and restore global and/or buffer command history.
Author: nils_2 — Version: 0.9.0 — License: GPL-3.0-or-later
For WeeChat ≥ 0.4.0.
Tags: history, py2, py3
Added: 2012-12-18 — Updated: 2026-10-01

Download GitHub Repository

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# SPDX-FileCopyrightText: 2012-2026 nils_2@libera.#weechat
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# 2026-09-29
#       0.9.0 : revised version (security and robustness fixes)
#           : fix: negative value for option 'number' truncated the history file
#             and raised an exception
#           : fix: IndexError for one-character lines in mode "text"
#           : fix: options 'min_length' and 'number' are validated
#           : fix: restore honours localvar 'save_history' / 'save_buffer'
#             (condition in read_history() was never true)
#           : fix: option 'skip_double' is really case sensitive (as documented)
#           : security: buffer names are sanitized before they are used as
#             file names (no path traversal); legacy file names are still read
#           : security: files are written with mode 0600, atomically
#             (temporary file + rename), symlinks are not followed
#           : security: invalid regex in option 'pattern' -> nothing is saved
#             (fail closed); pattern is compiled only once
#           : security: broader default value for option 'pattern' (new
#             installations only, existing settings are not changed)
#           : files are read and written as UTF-8; empty and multi-line
#             entries are skipped
#           : one failing buffer does not abort saving of all other buffers
#           : infolists are always freed
#           : add option 'save_free': buffers with free content (fset, color,
#             secure, script, ...) are only saved/restored if it is "on"
#           : entries typed in free-content buffers are also removed from the
#             global history if 'save_free' is "off"
#           : requires WeeChat >= 3.2 (string_eval_path_home), Python 3 only
#           : add SPDX copyright and license tags
#
# 2021-05-02, Sébastien Helleu <flashcode@flashtux.org>
#       0.8.2 : add compatibility with WeeChat >= 3.2 (XDG directories)
#
# 2018-08-04: nils_2 (freenode.#weechat)
#       0.8 : add option 'save_buffer'
#           : thanks catbeard for revise the help text
#
# 2018-08-01: nils_2 (freenode.#weechat)
#       0.7 : rmodifier routine removed
#           : bug fixes
#
# 2017-12-14: Sébastien Helleu <flashcode@flashtux.org>
#       0.6 : rename command "/autosetbuffer" by "/buffer_autoset" in example
#
# 2015-04-05: nils_2 (freenode.#weechat)
#       0.5 : change priority of hook_signal('buffer_opened') to 100
#
# 2013-01-25: nils_2 (freenode.#weechat)
#       0.4 : make script compatible with Python 3.x
#
# 2013-01-20: nils_2, (freenode.#weechat)
#       0.3: fix wrong command argument in help-text
#
# 2012-12-21: nils_2, (freenode.#weechat)
#       0.2 : fix UnicodeEncodeError
#
# 2012-12-09: nils_2, (freenode.#weechat)
#       0.1 : initial release
#
# thanks to nestib for help with regex and for the rmodifier idea
#
# requires: WeeChat version 3.2
#
# Development is currently hosted at
# https://github.com/weechatter/weechat-scripts

import hashlib
import io
import os
import re
import sys
from collections import Counter
from urllib.parse import quote

try:
    import weechat
except ImportError:
    print("This script must be run under WeeChat.")
    print("Get WeeChat now at: https://weechat.org/")
    sys.exit(1)

SCRIPT_NAME     = 'histman'
SCRIPT_AUTHOR   = 'nils_2 <weechatter@arcor.de>'
SCRIPT_VERSION  = '0.9.0'
SCRIPT_LICENSE  = 'GPL'
SCRIPT_DESC     = 'save and restore global and/or buffer command history'

OPTION_DEFAULTS = {
    'number'       : ('0', 'number of history commands/text to save. A positive number will save from oldest to latest, a negative number will save from latest to oldest. 0 = save whole history (e.g. -10 will save the last 10 history entries)'),
    'pattern'      : ('(.*password|.*passphrase|.*nickserv|.*chanserv|.*authserv|/(oper|secure|identify)( |$)|/quote pass|/quit)', 'a simple regex (case insensitive, matched at the beginning of the line) to ignore commands/text. Empty value disables pattern matching. If the regex is invalid, nothing will be saved'),
    'skip_double'  : ('on', 'skip lines that already exist (case sensitive)'),
    'save'         : ('all', 'define what should be saved from history. Possible values are "command", "text", "all". This is a fallback option (see /help ' + SCRIPT_NAME + ')'),
    'history_dir'  : ('%h/history', 'local cache directory for history files ("%h" will be replaced by WeeChat data directory)'),
    'save_global'  : ('off', 'save global history, possible values are "command", "text", "all" or "off" (default: off)'),
    'save_buffer'  : ('off', 'save buffer history from all buffers, possible values are "on", "off". Using this option, localvar from buffer will be ignored (default: off)'),
    'save_free'    : ('off', 'save and restore history of buffers with free content (e.g. fset, color, secure, script), possible values are "on", "off". If "off", these buffers are never handled, even if localvar "save_history" is set or "save_buffer" is "on" (default: off)'),
    'min_length'   : ('2', 'minimum length of command/text (default: 2)'),
    'buffer_close' : ('off', 'save command history, when buffer will be closed (default: off)'),
}

OPTIONS = {}

HELP_TEXT = '\n'.join([
    '  save: save the command history now (do not wait for /quit)',
    '  list: list the "save_history" settings made with buffer_autoset',
    '',
    'What is saved, and when:',
    '  - On /quit the history of all selected buffers (and the global history, if enabled) is written',
    '    to files in the directory given by option "history_dir".',
    '  - With option "buffer_close" set to "on", the history of a buffer is also saved when the buffer is closed.',
    '  - When a buffer is opened again (or WeeChat starts), its history is restored from the file.',
    '',
    'Which buffers are handled:',
    '  - buffers with the local variable "save_history" set (values: see below)',
    '  - all buffers, if option "save_buffer" is "on" (the local variable is then not required;',
    '    buffers without it use the value of option "save")',
    '  - the global history, if option "save_global" is not "off"',
    '  - buffers with free content (fset, color, secure, script, ...) only if option "save_free" is "on",',
    '    regardless of the settings above',
    '',
    'Values for the local variable "save_history" (and for the options "save" and "save_global"):',
    '  command: save commands only (lines starting with a command char; "//text" counts as text)',
    '     text: save text only (e.g. messages sent to a channel)',
    '      all: save commands and text',
    '',
    'A local variable is lost when WeeChat restarts. Use the command "/buffer setauto" to make it persistent',
    '(see examples below).',
    '',
    'Lines that are not saved:',
    '  - lines shorter than option "min_length"',
    '  - lines matching the regex in option "pattern" (case insensitive, matched from the beginning of the line)',
    '  - duplicates, if option "skip_double" is "on" (case sensitive)',
    '  - empty and multi-line entries',
    'Option "number" limits the amount: a positive value N saves the first (oldest) N entries, a negative',
    'value saves the last N entries, 0 saves everything.',
    '',
    'Security:',
    '  History files are written with mode 0600. The option "pattern" is a blacklist and can not catch every',
    '  secret; if the regex is invalid, nothing is saved. Be careful with "save_buffer" and "save_free".',
    '',
    'Examples:',
    '  save the command history manually (e.g. from a /trigger or a cron job):',
    '    /' + SCRIPT_NAME + ' save',
    '  save and restore only the text typed in buffer #weechat on libera:',
    '    /command -buffer irc.libera.#weechat core /buffer setauto localvar_set_save_history text',
    '  save and restore only the commands typed in current buffer:',
    '    /buffer setauto localvar_set_save_history command',
])

FILENAME_GLOBAL_HISTORY = 'global_history'
POSSIBLE_SAVE_OPTIONS = ('command', 'text', 'all')
MAX_FILENAME_LEN = 200

# cache for the compiled 'pattern' option
_filter = {'source': None, 'regex': None, 'valid': True}

# history entries of free-content buffers that were closed in this session
# (they stay in the global history, so they must be excluded from it)
_closed_free_lines = Counter()


# ==================================[ helpers ]===================================
def report(message):
    weechat.prnt('', '%s%s: %s' % (weechat.prefix('error'), SCRIPT_NAME, message))


def info(message):
    weechat.prnt('', '%s: %s' % (SCRIPT_NAME, message))


def opt_int(name, default):
    try:
        return int(str(OPTIONS.get(name, default)).strip())
    except ValueError:
        return default


def opt_on(name):
    return str(OPTIONS.get(name, '')).strip().lower() == 'on'


def compile_filter():
    source = OPTIONS.get('pattern', '')
    _filter['source'] = source
    _filter['regex'] = None
    _filter['valid'] = True
    if source != '':
        try:
            _filter['regex'] = re.compile(source, re.I)
        except re.error as e:
            _filter['valid'] = False
            report('invalid regular expression in option "pattern" (%s). '
                   'Nothing will be saved until this is fixed.' % e)


def get_filter():
    if _filter['source'] != OPTIONS.get('pattern', ''):
        compile_filter()
    return _filter['regex'], _filter['valid']


# ===================================[ paths ]====================================
def get_history_dir():
    options = {'directory': 'data'}
    return weechat.string_eval_path_home(OPTIONS['history_dir'], {}, {}, options)


def config_create_dir():
    path = get_history_dir()
    if not os.path.isdir(path):
        os.makedirs(path, mode=0o700, exist_ok=True)
        try:
            os.chmod(path, 0o700)       # do not depend on umask
        except OSError:
            pass


def inside_dir(path, base):
    """True if the (resolved) path is a direct child of base."""
    return os.path.dirname(os.path.realpath(path)) == base


def history_path(name):
    """Return a safe path for the history file 'name' (raises ValueError)."""
    base = os.path.realpath(get_history_dir())
    safe = quote(name, safe='#@+', errors='replace')
    if len(safe) > MAX_FILENAME_LEN:
        digest = hashlib.sha1(name.encode('utf-8', 'replace')).hexdigest()[:12]
        safe = safe[:MAX_FILENAME_LEN - 20] + '~' + digest
    path = os.path.join(base, safe)
    if not inside_dir(path, base):
        raise ValueError('invalid history file name: %r' % name)
    return path


def resolve_read_path(name):
    """Return path of an existing history file or None."""
    try:
        path = history_path(name)
    except ValueError:
        return None
    if os.path.isfile(path):
        return path
    # fallback: file name as used by histman < 0.9.0
    base = os.path.realpath(get_history_dir())
    legacy = os.path.join(base, name)
    if os.path.isfile(legacy) and inside_dir(legacy, base):
        return legacy
    return None


def buffer_filename(ptr_buffer):
    plugin = weechat.buffer_get_string(ptr_buffer, 'localvar_plugin')
    name = weechat.buffer_get_string(ptr_buffer, 'localvar_name')
    return '%s.%s' % (plugin, name)


# ===============================[ collect history ]==============================
def is_free_buffer(ptr_buffer):
    """True for buffers with free content (fset, color, secure, script, ...)."""
    return weechat.buffer_get_integer(ptr_buffer, 'type') == 1


def buffer_wanted(ptr_buffer):
    """Should the history of this buffer be saved?"""
    if is_free_buffer(ptr_buffer) and not opt_on('save_free'):
        return False
    if weechat.buffer_get_string(ptr_buffer, 'localvar_save_history'):
        return True
    return opt_on('save_buffer')


def get_save_mode(ptr_buffer):
    """Return 'command', 'text', 'all' or '' (= save nothing)."""
    if ptr_buffer:
        mode = weechat.buffer_get_string(ptr_buffer, 'localvar_save_history').strip().lower()
    else:
        mode = str(OPTIONS.get('save_global', '')).strip().lower()
    if mode in POSSIBLE_SAVE_OPTIONS:
        return mode
    fallback = str(OPTIONS.get('save', '')).strip().lower()
    return fallback if fallback in POSSIBLE_SAVE_OPTIONS else ''


def get_command_chars():
    extra = weechat.config_string(weechat.config_get('weechat.look.command_chars')) or ''
    return '/' + extra


def is_command(line, command_chars):
    # a valid command has at least two chars, first and second char differ ("//" = text)
    return len(line) > 1 and line[0] in command_chars and line[0] != line[1]


def line_matches_mode(line, mode, command_chars):
    if mode == 'command':
        return is_command(line, command_chars)
    if mode == 'text':
        return not is_command(line, command_chars)
    return mode == 'all'


def count_history_lines(ptr_buffer, counts):
    """Add the history entries of a buffer to the Counter 'counts'."""
    infolist = weechat.infolist_get('history', ptr_buffer, '')
    if not infolist:
        return
    try:
        while weechat.infolist_next(infolist):
            counts[weechat.infolist_string(infolist, 'text')] += 1
    finally:
        weechat.infolist_free(infolist)


def get_free_buffer_lines():
    """Entries typed in free-content buffers. WeeChat also stores them in the global history."""
    counts = Counter(_closed_free_lines)
    infolist = weechat.infolist_get('buffer', '', '')
    if infolist:
        try:
            while weechat.infolist_next(infolist):
                ptr_buffer = weechat.infolist_pointer(infolist, 'pointer')
                if is_free_buffer(ptr_buffer):
                    count_history_lines(ptr_buffer, counts)
        finally:
            weechat.infolist_free(infolist)
    return counts


def get_buffer_history(ptr_buffer):
    """Return the lines to save (oldest first). Empty ptr_buffer = global history."""
    lines = []

    mode = get_save_mode(ptr_buffer)
    if not mode:
        return lines

    regex, valid = get_filter()
    if not valid:                       # fail closed
        return lines

    min_length = max(opt_int('min_length', 2), 1)
    skip_double = opt_on('skip_double')
    command_chars = get_command_chars()

    # global history: drop the entries that were typed in free-content buffers
    exclude = Counter()
    if not ptr_buffer and not opt_on('save_free'):
        exclude = get_free_buffer_lines()

    infolist = weechat.infolist_get('history', ptr_buffer, '')
    if not infolist:
        return lines

    seen = set()
    try:
        # WeeChat returns the newest entry first
        while weechat.infolist_next(infolist):
            line = weechat.infolist_string(infolist, 'text')
            if exclude[line] > 0:
                exclude[line] -= 1
                continue
            if len(line) < min_length or not line.strip():
                continue
            if '\n' in line or '\r' in line:    # would corrupt the file format
                continue
            if not line_matches_mode(line, mode, command_chars):
                continue
            if regex is not None and regex.match(line):
                continue
            if skip_double:
                if line in seen:
                    continue
                seen.add(line)
            lines.append(line)
    finally:
        weechat.infolist_free(infolist)

    lines.reverse()
    return lines


def select_lines(lines):
    """Apply option 'number' to the list (oldest first)."""
    n = opt_int('number', 0)
    if n > 0:
        return lines[:n]
    if n < 0:
        return lines[n:]                # the last abs(n) entries
    return lines


# ===============================[ read/write files ]=============================
def write_history(name, lines):
    lines = select_lines(lines)
    if not lines:
        return

    tmp = None
    try:
        config_create_dir()
        path = history_path(name)
        tmp = path + '.tmp'
        flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, 'O_NOFOLLOW', 0)
        fd = os.open(tmp, flags, 0o600)
        with io.open(fd, 'w', encoding='utf-8', errors='replace', newline='\n') as f:
            for line in lines:
                f.write(line + '\n')
            f.flush()
            os.fsync(f.fileno())
        os.replace(tmp, path)
        tmp = None
    except Exception as e:
        report('error writing history "%s": %s' % (name, e))
    finally:
        if tmp is not None:
            try:
                os.remove(tmp)
            except OSError:
                pass


def read_history(name, ptr_buffer):
    """Restore history from file. Empty ptr_buffer = global history."""
    try:
        if ptr_buffer and is_free_buffer(ptr_buffer) and not opt_on('save_free'):
            return
        if ptr_buffer and not opt_on('save_buffer'):
            # is history saving enabled for this buffer?
            plugin = weechat.buffer_get_string(ptr_buffer, 'localvar_plugin')
            bname = weechat.buffer_get_string(ptr_buffer, 'localvar_name')
            # fallback: localvar may not be set yet (buffer_autoset not applied)
            autoset = weechat.config_get('buffer_autoset.buffer.%s.%s.localvar_set_save_history' % (plugin, bname))
            if not autoset and not weechat.buffer_get_string(ptr_buffer, 'localvar_save_history'):
                return

        path = resolve_read_path(name)
        if not path:
            return

        hdata = weechat.hdata_get('history')
        if not hdata:
            return

        with io.open(path, 'r', encoding='utf-8', errors='replace') as f:
            for raw in f:
                line = raw.rstrip('\r\n')
                if not line.strip():
                    continue
                values = {'text': line}
                if ptr_buffer:
                    values['buffer'] = ptr_buffer
                weechat.hdata_update(hdata, '', values)
    except Exception as e:
        report('error loading history "%s": %s' % (name, e))


def save_buffer_history(ptr_buffer):
    if not buffer_wanted(ptr_buffer):
        return
    lines = get_buffer_history(ptr_buffer)
    if lines:
        write_history(buffer_filename(ptr_buffer), lines)


def save_history():
    infolist = weechat.infolist_get('buffer', '', '')
    if infolist:
        try:
            while weechat.infolist_next(infolist):
                ptr_buffer = weechat.infolist_pointer(infolist, 'pointer')
                try:
                    save_buffer_history(ptr_buffer)
                except Exception as e:      # continue with the other buffers
                    report('error saving buffer history: %s' % e)
        finally:
            weechat.infolist_free(infolist)

    if str(OPTIONS.get('save_global', 'off')).strip().lower() != 'off':
        try:
            lines = get_buffer_history('')  # no buffer pointer = global history
            if lines:
                write_history(FILENAME_GLOBAL_HISTORY, lines)
        except Exception as e:
            report('error saving global history: %s' % e)


# ===========================================[ Hooks() ]==========================================
def create_hooks():
    weechat.hook_signal('quit', 'quit_signal_cb', '')
    weechat.hook_signal('upgrade_ended', 'upgrade_ended_cb', '')
    # low priority for hook_signal('buffer_opened') to ensure that buffer_autoset hook_signal() runs first
    weechat.hook_signal('1000|buffer_opened', 'buffer_opened_cb', '')
    weechat.hook_config('plugins.var.python.' + SCRIPT_NAME + '.*', 'toggle_refresh', '')
    weechat.hook_signal('buffer_closing', 'buffer_closing_cb', '')


def quit_signal_cb(data, signal, signal_data):
    save_history()
    return weechat.WEECHAT_RC_OK


def buffer_opened_cb(data, signal, signal_data):
    read_history(buffer_filename(signal_data), signal_data)
    return weechat.WEECHAT_RC_OK


def buffer_closing_cb(data, signal, signal_data):
    if signal_data:
        try:
            if is_free_buffer(signal_data) and not opt_on('save_free'):
                # remember the entries: they stay in the global history after the buffer is gone
                count_history_lines(signal_data, _closed_free_lines)
            elif opt_on('buffer_close'):
                save_buffer_history(signal_data)
        except Exception as e:
            report('error saving buffer history: %s' % e)
    return weechat.WEECHAT_RC_OK


def upgrade_ended_cb(data, signal, signal_data):
    weechat.buffer_set(weechat.buffer_search_main(), 'localvar_set_histman', 'on')
    return weechat.WEECHAT_RC_OK


def histman_cmd_cb(data, buffer, args):
    argv = args.strip().split(' ', 1)
    command = argv[0].lower()

    if command == 'save':
        save_history()
    elif command == 'list':
        weechat.command('', '/set *.localvar_set_save_history')
    else:
        weechat.command('', '/help %s' % SCRIPT_NAME)

    return weechat.WEECHAT_RC_OK


# ================================[ weechat options & description ]===============================
def init_options():
    for option, (default, description) in OPTION_DEFAULTS.items():
        if not weechat.config_is_set_plugin(option):
            weechat.config_set_plugin(option, default)
        weechat.config_set_desc_plugin(option, '%s (default: "%s")' % (description, default))
        OPTIONS[option] = weechat.config_get_plugin(option)
    compile_filter()


def toggle_refresh(pointer, name, value):
    option = name[len('plugins.var.python.' + SCRIPT_NAME + '.'):]
    if option in OPTION_DEFAULTS:
        OPTIONS[option] = value if value is not None else OPTION_DEFAULTS[option][0]
        if option == 'pattern':
            compile_filter()
    return weechat.WEECHAT_RC_OK


# ================================[ main ]===============================
if __name__ == '__main__':
    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
        try:
            version = int(weechat.info_get('version_number', '') or 0)
        except ValueError:
            version = 0

        weechat.hook_command(SCRIPT_NAME, SCRIPT_DESC, '[save] || [list]',
                            HELP_TEXT,
                            'save %-'
                            '|| list %-',
                            'histman_cmd_cb', '')

        if version >= 0x03020000:
            main_buffer = weechat.buffer_search_main()
            init_options()
            if weechat.buffer_get_string(main_buffer, 'localvar_histman') == 'on':
                # script was reloaded: history is already in memory, do not restore it twice
                create_hooks()
                info('script was already started in this session, history will not be restored again.')
            else:
                try:
                    config_create_dir()
                except Exception as e:
                    report('can not create history directory: %s' % e)

                # look for global_history
                if str(OPTIONS.get('save_global', 'off')).strip().lower() != 'off':
                    read_history(FILENAME_GLOBAL_HISTORY, '')

                # core buffer is already open on script startup. Check manually!
                read_history(buffer_filename(main_buffer), main_buffer)

                create_hooks()

                # set localvar, to start script only once!
                weechat.buffer_set(main_buffer, 'localvar_set_histman', 'on')
        else:
            report('needs WeeChat version 3.2 or higher')
            weechat.command('', '/wait 1ms /python unload %s' % SCRIPT_NAME)