# HG changeset patch # User Steve Losh # Date 1431616003 14400 # Node ID 954e9528873269d653f4b41382d88c47ce461e72 # Parent d077c69110440fd382a0f1cef477bb69a5597fb1# Parent 603bb1ae9da27c6e08ab115df1cb5d8f6a1442c3 Merge. diff -r 603bb1ae9da2 -r 954e95288732 agignore --- a/agignore Thu Dec 18 21:59:14 2014 -0500 +++ b/agignore Thu May 14 11:06:43 2015 -0400 @@ -4,3 +4,4 @@ target *.public.key *.private.key +*.pgp diff -r 603bb1ae9da2 -r 954e95288732 hgrc --- a/hgrc Thu Dec 18 21:59:14 2014 -0500 +++ b/hgrc Thu May 14 11:06:43 2015 -0400 @@ -28,7 +28,8 @@ delay = 1.0 [web] -cacerts = /etc/hg-dummy-cert.pem +# cacerts = /etc/hg-dummy-cert.pem +cacerts = [schemes] webf = ssh://sjl@sjl.webfactional.com/repos/ diff -r 603bb1ae9da2 -r 954e95288732 mutt/muttrc --- a/mutt/muttrc Thu Dec 18 21:59:14 2014 -0500 +++ b/mutt/muttrc Thu May 14 11:06:43 2015 -0400 @@ -43,6 +43,29 @@ set thorough_search # strip headers and eval mimes before searching # }}} +# PGP {{{ + +set pgp_decode_command="gpg %?p?--passphrase-fd 0? --no-verbose --batch --output - %f" +set pgp_verify_command="gpg --no-verbose --batch --output - --verify %s %f" +set pgp_decrypt_command="gpg --passphrase-fd 0 --no-verbose --batch --output - %f" +set pgp_sign_command="gpg --no-verbose --batch --output - --passphrase-fd 0 --armor --detach-sign --textmode %?a?-u %a? %f" +set pgp_clearsign_command="gpg --no-verbose --batch --output - --passphrase-fd 0 --armor --textmode --clearsign %?a?-u %a? %f" +set pgp_verify_key_command="gpg --no-verbose --batch --fingerprint --check-sigs %r" + +set pgp_import_command="gpg --no-verbose --import -v %f" +set pgp_export_command="gpg --no-verbose --export --armor %r" +set pgp_list_pubring_command="gpg --no-verbose --batch --with-colons --list-keys %r" +set pgp_list_secring_command="gpg --no-verbose --batch --with-colons --list-secret-keys %r" + +set crypt_verify_sig=yes + +message-hook '!(~g|~G) ~b"^-----BEGIN\ PGP\ (SIGNED\ )?MESSAGE"' "exec check-traditional-pgp" + +# set these in .mutt-local with the appropriate encrypt-to key id +# set pgp_encrypt_only_command="pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust --encrypt-to 0x7438228A -- -r %r -- %f" +# set pgp_encrypt_sign_command="pgpewrap gpg --passphrase-fd 0 --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust --encrypt-to 0x7438228A -- -r %r -- %f" + +# }}} # Sidebar Patch {{{ # set sidebar_delim = ' │' @@ -210,6 +233,7 @@ # Compose {{{ bind compose p postpone-message +bind compose P pgp-menu # }}} # Attachment {{{ diff -r 603bb1ae9da2 -r 954e95288732 vim/vimrc --- a/vim/vimrc Thu Dec 18 21:59:14 2014 -0500 +++ b/vim/vimrc Thu May 14 11:06:43 2015 -0400 @@ -2446,7 +2446,13 @@ return MS2UTC(expand("")) endfunction -nnoremap U :echo MS2UTCWord() +function! ReplaceMS2UTCWord() + let res = MS2UTC(expand("")) + execute 'normal! ciw"' . res . '"' +endfunction + +" nnoremap U :echo MS2UTCWord() +nnoremap U :call ReplaceMS2UTCWord() " }}} diff -r 603bb1ae9da2 -r 954e95288732 weechat/.agignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weechat/.agignore Thu May 14 11:06:43 2015 -0400 @@ -0,0 +1,2 @@ +logs +urls.log diff -r 603bb1ae9da2 -r 954e95288732 weechat/autosort.conf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weechat/autosort.conf Thu May 14 11:06:43 2015 -0400 @@ -0,0 +1,11 @@ +# +# autosort.conf -- weechat v0.4.3 +# + +[sorting] +case_sensitive = off +group_irc = on +replacements = "[]" +rules = "[["core", 0], ["irc", 2], ["*", 1], ["irc.irc_raw", 0], ["irc.server", 1]]" +signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed" +sort_on_config_change = on diff -r 603bb1ae9da2 -r 954e95288732 weechat/python/autoload/autosort.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weechat/python/autoload/autosort.py Thu May 14 11:06:43 2015 -0400 @@ -0,0 +1,860 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2014 Maarten de Vries +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# +# Autosort automatically keeps your buffers sorted and grouped by server. +# You can define your own sorting rules. See /help autosort for more details. +# +# http://github.com/de-vri.es/weechat-autosort +# + +# +# Changelog: +# 2.5: +# * Fix handling unicode buffer names. +# * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on. +# 2.4: +# * Make script python3 compatible. +# 2.3: +# * Fix sorting items without score last (regressed in 2.2). +# 2.2: +# * Add configuration option for signals that trigger a sort. +# * Add command to manually trigger a sort (/autosort sort). +# * Add replacement patterns to apply before sorting. +# 2.1: +# * Fix some minor style issues. +# 2.0: +# * Allow for custom sort rules. +# + + +import weechat +import re +import json + +SCRIPT_NAME = 'autosort' +SCRIPT_AUTHOR = 'Maarten de Vries ' +SCRIPT_VERSION = '2.5' +SCRIPT_LICENSE = 'GPL3' +SCRIPT_DESC = 'Automatically or manually keep your buffers sorted and grouped by server.' + + +config = None +hooks = [] + +class HumanReadableError(Exception): + pass + + +def parse_int(arg, arg_name = 'argument'): + ''' Parse an integer and provide a more human readable error. ''' + arg = arg.strip() + try: + return int(arg) + except ValueError: + raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg)) + + +class Pattern: + ''' A simple glob-like pattern for matching buffer names. ''' + + def __init__(self, pattern): + ''' Construct a pattern from a string. ''' + escaped = False + char_class = 0 + chars = '' + regex = '' + for c in pattern: + if escaped and char_class: + escaped = False + chars += re.escape(c) + elif escaped: + escaped = False + regex += re.escape(c) + elif c == '\\': + escaped = True + elif c == '*' and not char_class: + regex += '[^.]*' + elif c == '?' and not char_class: + regex += '[^.]' + elif c == '[' and not char_class: + char_class = 1 + chars = '' + elif c == '^' and char_class and not chars: + chars += '^' + elif c == ']' and char_class and chars not in ('', '^'): + char_class = False + regex += '[' + chars + ']' + elif c == '-' and char_class: + chars += '-' + elif char_class: + chars += re.escape(c) + else: + regex += re.escape(c) + + if char_class: + raise ValueError("unmatched opening '['") + if escaped: + raise ValueError("unexpected trailing '\\'") + + self.regex = re.compile('^' + regex + '$') + self.pattern = pattern + + def match(self, input): + ''' Match the pattern against a string. ''' + return self.regex.match(input) + + +class FriendlyList(object): + ''' A list with human readable errors. ''' + + def __init__(self): + self.__data = [] + + def raw(self): + return self.__data + + def append(self, value): + ''' Add a rule to the list. ''' + self.__data.append(value) + + def insert(self, index, value): + ''' Add a rule to the list. ''' + if not 0 <= index <= len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}], got {1}.'.format(len(self), index)) + self.__data.insert(index, value) + + def pop(self, index): + ''' Remove a rule from the list and return it. ''' + if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index)) + return self.__data.pop(index) + + def move(self, index_a, index_b): + ''' Move a rule to a new position in the list. ''' + self.insert(index_b, self.pop(index_a)) + + def swap(self, index_a, index_b): + ''' Swap two elements in the list. ''' + self[index_a], self[index_b] = self[index_b], self[index_a] + + def __len__(self): + return len(self.__data) + + def __getitem__(self, index): + if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index)) + return self.__data[index] + + def __setitem__(self, index, value): + if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index)) + self.__data[index] = value + + def __iter__(self): + return iter(self.__data) + + +class RuleList(FriendlyList): + ''' A list of rules to test buffer names against. ''' + rule_regex = re.compile(r'^(.*)=\s*([+-]?[^=]*)$') + + def __init__(self, rules): + ''' Construct a RuleList from a list of rules. ''' + super(RuleList, self).__init__() + for rule in rules: self.append(rule) + + def get_score(self, name, rules): + ''' Get the sort score of a partial name according to a rule list. ''' + for rule in self: + if rule[0].match(name): return rule[1] + return 999999999 + + def encode(self): + ''' Encode the rules for storage. ''' + return json.dumps(list(map(lambda x: (x[0].pattern, x[1]), self))) + + @staticmethod + def decode(blob): + ''' Parse rules from a string blob. ''' + result = [] + + try: + decoded = json.loads(blob) + except ValueError: + log('Invalid rules: expected JSON encoded list of pairs, got "{0}".'.format(blob)) + return [], 0 + + for rule in decoded: + # Rules must be a pattern,score pair. + if len(rule) != 2: + log('Invalid rule: expected (pattern, score), got "{0}". Rule ignored.'.format(rule)) + continue + + # Rules must have a valid pattern. + try: + pattern = Pattern(rule[0]) + except ValueError as e: + log('Invalid pattern: {0} in "{1}". Rule ignored.'.format(e, rule[0])) + continue + + # Rules must have a valid score. + try: + score = int(rule[1]) + except ValueError as e: + log('Invalid score: expected an integer, got "{0}". Rule ignored.'.format(score)) + continue + + result.append((pattern, score)) + + return RuleList(result) + + @staticmethod + def parse_rule(arg): + ''' Parse a rule argument. ''' + arg = arg.strip() + match = RuleList.rule_regex.match(arg) + if not match: + raise HumanReadableError('Invalid rule: expected " = ", got "{0}".'.format(arg)) + + pattern = match.group(1).strip() + try: + pattern = Pattern(pattern) + except ValueError as e: + raise HumanReadableError('Invalid pattern: {0} in "{1}".'.format(e, pattern)) + + score = parse_int(match.group(2), 'score') + return (pattern, score) + + +def decode_replacements(blob): + ''' Decode a replacement list encoded as JSON. ''' + result = FriendlyList() + try: + decoded = json.loads(blob) + except ValueError: + log('Invalid replacement list: expected JSON encoded list of pairs, got "{0}".'.format(blob)) + return [], 0 + + for replacement in decoded: + # Replacements must be a (string, string) pair. + if len(replacement) != 2: + log('Invalid replacement pattern: expected (pattern, replacement), got "{0}". Replacement ignored.'.format(rule)) + continue + result.append(replacement) + + return result + + +def encode_replacements(replacements): + ''' Encode a list of replacement patterns as JSON. ''' + return json.dumps(replacements.raw()) + + +class Config: + ''' The autosort configuration. ''' + + default_rules = json.dumps([ + ('core', 0), + ('irc', 2), + ('*', 1), + + ('irc.irc_raw', 0), + ('irc.server', 1), + ]) + + default_replacements = '[]' + default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed' + + def __init__(self, filename): + ''' Initialize the configuration. ''' + + self.filename = filename + self.config_file = weechat.config_new(self.filename, '', '') + self.sorting_section = None + + self.case_sensitive = False + self.group_irc = True + self.rules = [] + self.replacements = [] + self.signals = [] + self.sort_on_config = True + + self.__case_sensitive = None + self.__group_irc = None + self.__rules = None + self.__replacements = None + self.__signals = None + self.__sort_on_config = None + + if not self.config_file: + log('Failed to initialize configuration file "{0}".'.format(self.filename)) + return + + self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '') + + if not self.sorting_section: + log('Failed to initialize section "sorting" of configuration file.') + weechat.config_free(self.config_file) + return + + self.__case_sensitive = weechat.config_new_option( + self.config_file, self.sorting_section, + 'case_sensitive', 'boolean', + 'If this option is on, sorting is case sensitive.', + '', 0, 0, 'off', 'off', 0, + '', '', '', '', '', '' + ) + + self.__group_irc = weechat.config_new_option( + self.config_file, self.sorting_section, + 'group_irc', 'boolean', + 'If this option is on, the script pretends that IRC channel/private buffers are renamed to "irc.server.{network}.{channel}" rather than "irc.{network}.{channel}".' + + 'This ensures that these buffers are grouped with their respective server buffer.', + '', 0, 0, 'on', 'on', 0, + '', '', '', '', '', '' + ) + + self.__rules = weechat.config_new_option( + self.config_file, self.sorting_section, + 'rules', 'string', + 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.', + '', 0, 0, Config.default_rules, Config.default_rules, 0, + '', '', '', '', '', '' + ) + + self.__replacements = weechat.config_new_option( + self.config_file, self.sorting_section, + 'replacements', 'string', + 'An ordered list of replacement patterns to use on buffer name components, encoded as JSON. See /help autosort for commands to manipulate these replacements.', + '', 0, 0, Config.default_replacements, Config.default_replacements, 0, + '', '', '', '', '', '' + ) + + self.__signals = weechat.config_new_option( + self.config_file, self.sorting_section, + 'signals', 'string', + 'The signals that will cause autosort to resort your buffer list. Seperate signals with spaces.', + '', 0, 0, Config.default_signals, Config.default_signals, 0, + '', '', '', '', '', '' + ) + + self.__sort_on_config = weechat.config_new_option( + self.config_file, self.sorting_section, + 'sort_on_config_change', 'boolean', + 'Decides if the buffer list should be sorted when autosort configuration changes.', + '', 0, 0, 'on', 'on', 0, + '', '', '', '', '', '' + ) + + if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK: + log('Failed to load configuration file.') + + if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK: + log('Failed to write configuration file.') + + self.reload() + + def reload(self): + ''' Load configuration variables. ''' + + self.case_sensitive = weechat.config_boolean(self.__case_sensitive) + self.group_irc = weechat.config_boolean(self.__group_irc) + + rules_blob = weechat.config_string(self.__rules) + replacements_blob = weechat.config_string(self.__replacements) + signals_blob = weechat.config_string(self.__signals) + + self.rules = RuleList.decode(rules_blob) + self.replacements = decode_replacements(replacements_blob) + self.signals = signals_blob.split() + self.sort_on_config = weechat.config_boolean(self.__sort_on_config) + + def save_rules(self, run_callback = True): + ''' Save the current rules to the configuration. ''' + weechat.config_option_set(self.__rules, RuleList.encode(self.rules), run_callback) + + def save_replacements(self, run_callback = True): + ''' Save the current replacement patterns to the configuration. ''' + weechat.config_option_set(self.__replacements, encode_replacements(self.replacements), run_callback) + + +def pad(sequence, length, padding = None): + ''' Pad a list until is has a certain length. ''' + return sequence + [padding] * max(0, (length - len(sequence))) + + +def log(message, buffer = 'NULL'): + weechat.prnt(buffer, 'autosort: {0}'.format(message)) + + +def get_buffers(): + ''' Get a list of all the buffers in weechat. ''' + buffers = [] + + buffer_list = weechat.infolist_get('buffer', '', '') + + while weechat.infolist_next(buffer_list): + name = weechat.infolist_string (buffer_list, 'full_name') + number = weechat.infolist_integer(buffer_list, 'number') + + # Buffer is merged with one we already have in the list, skip it. + if number <= len(buffers): + continue + buffers.append(name) + + weechat.infolist_free(buffer_list) + return buffers + + +def preprocess(buffer, config): + ''' + Preprocess a buffers names. + ''' + if not config.case_sensitive: + buffer = buffer.lower() + + for replacement in config.replacements: + buffer = buffer.replace(replacement[0], replacement[1]) + + buffer = buffer.split('.') + if config.group_irc and len(buffer) >= 2 and buffer[0] == 'irc' and buffer[1] not in ('server', 'irc_raw'): + buffer.insert(1, 'server') + + return buffer + + +def buffer_sort_key(rules): + ''' Create a sort key function for a buffer list from a rule list. ''' + def key(buffer): + result = [] + name = '' + for word in preprocess(buffer.decode('utf-8'), config): + name += ('.' if name else '') + word + result.append((rules.get_score(name, rules), word)) + return result + + return key + + +def apply_buffer_order(buffers): + ''' Sort the buffers in weechat according to the order in the input list. ''' + for i, buffer in enumerate(buffers): + weechat.command('', '/buffer swap {0} {1}'.format(buffer, i + 1)) + + +def split_args(args, expected, optional = 0): + ''' Split an argument string in the desired number of arguments. ''' + split = args.split(' ', expected - 1) + if (len(split) < expected): + raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split))) + return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '') + + +def command_sort(buffer, command, args): + ''' Sort the buffers and print a confirmation. ''' + on_buffers_changed() + log("Finished sorting buffers.", buffer) + return weechat.WEECHAT_RC_OK + + +def command_rule_list(buffer, command, args): + ''' Show the list of sorting rules. ''' + output = 'Sorting rules:\n' + for i, rule in enumerate(config.rules): + output += ' {0}: {1} = {2}\n'.format(i, rule[0].pattern, rule[1]) + if not len(config.rules): + output += ' No sorting rules configured.\n' + log(output, buffer) + + return weechat.WEECHAT_RC_OK + + +def command_rule_add(buffer, command, args): + ''' Add a rule to the rule list. ''' + rule = RuleList.parse_rule(args) + + config.rules.append(rule) + config.save_rules() + command_rule_list(buffer, command, '') + + return weechat.WEECHAT_RC_OK + + +def command_rule_insert(buffer, command, args): + ''' Insert a rule at the desired position in the rule list. ''' + index, rule = split_args(args, 2) + index = parse_int(index, 'index') + rule = RuleList.parse_rule(rule) + + config.rules.insert(index, rule) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_update(buffer, command, args): + ''' Update a rule in the rule list. ''' + index, rule = split_args(args, 2) + index = parse_int(index, 'index') + rule = RuleList.parse_rule(rule) + + config.rules[index] = rule + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_delete(buffer, command, args): + ''' Delete a rule from the rule list. ''' + index = args.strip() + index = parse_int(index, 'index') + + config.rules.pop(index) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_move(buffer, command, args): + ''' Move a rule to a new position. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.rules.move(index_a, index_b) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_swap(buffer, command, args): + ''' Swap two rules. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.rules.swap(index_a, index_b) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_list(buffer, command, args): + ''' Show the list of sorting rules. ''' + output = 'Replacement patterns:\n' + for i, pattern in enumerate(config.replacements): + output += ' {0}: {1} -> {2}\n'.format(i, pattern[0], pattern[1]) + if not len(config.replacements): + output += ' No replacement patterns configured.' + log(output, buffer) + + return weechat.WEECHAT_RC_OK + + +def command_replacement_add(buffer, command, args): + ''' Add a rule to the rule list. ''' + pattern, replacement = split_args(args, 1, 1) + + config.replacements.append((pattern, replacement)) + config.save_replacements() + command_replacement_list(buffer, command, '') + + return weechat.WEECHAT_RC_OK + + +def command_replacement_insert(buffer, command, args): + ''' Insert a rule at the desired position in the rule list. ''' + index, pattern, replacement = split_args(args, 2, 1) + index = parse_int(index, 'index') + + config.replacements.insert(index, (pattern, replacement)) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_update(buffer, command, args): + ''' Update a rule in the rule list. ''' + index, pattern, replacement = split_args(args, 2, 1) + index = parse_int(index, 'index') + + config.replacements[index] = (pattern, replacement) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_delete(buffer, command, args): + ''' Delete a rule from the rule list. ''' + index = args.strip() + index = parse_int(index, 'index') + + config.replacements.pop(index) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_move(buffer, command, args): + ''' Move a rule to a new position. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.replacements.move(index_a, index_b) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_swap(buffer, command, args): + ''' Swap two rules. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.replacements.swap(index_a, index_b) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + + + +def call_command(buffer, command, args, subcommands): + ''' Call a subccommand from a dictionary. ''' + subcommand, tail = pad(args.split(' ', 1), 2, '') + subcommand = subcommand.strip() + if (subcommand == ''): + child = subcommands.get(' ') + else: + command = command + [subcommand] + child = subcommands.get(subcommand) + + if isinstance(child, dict): + return call_command(buffer, command, tail, child) + elif callable(child): + return child(buffer, command, tail) + + log('{0}: command not found'.format(' '.join(command))) + return weechat.WEECHAT_RC_ERROR + + +def on_buffers_changed(*args, **kwargs): + ''' Called whenever the buffer list changes. ''' + buffers = get_buffers() + buffers.sort(key=buffer_sort_key(config.rules)) + apply_buffer_order(buffers) + return weechat.WEECHAT_RC_OK + + +def on_config_changed(*args, **kwargs): + ''' Called whenever the configuration changes. ''' + config.reload() + + # Unhook all signals and hook the new ones. + for hook in hooks: + weechat.unhook(hook) + for signal in config.signals: + hooks.append(weechat.hook_signal(signal, 'on_buffers_changed', '')) + + if config.sort_on_config: + on_buffers_changed() + + return weechat.WEECHAT_RC_OK + + +def on_autosort_command(data, buffer, args): + ''' Called when the autosort command is invoked. ''' + try: + return call_command(buffer, ['/autosort'], args, { + ' ': command_sort, + 'sort': command_sort, + + 'rules': { + ' ': command_rule_list, + 'list': command_rule_list, + 'add': command_rule_add, + 'insert': command_rule_insert, + 'update': command_rule_update, + 'delete': command_rule_delete, + 'move': command_rule_move, + 'swap': command_rule_swap, + }, + 'replacements': { + ' ': command_replacement_list, + 'list': command_replacement_list, + 'add': command_replacement_add, + 'insert': command_replacement_insert, + 'update': command_replacement_update, + 'delete': command_replacement_delete, + 'move': command_replacement_move, + 'swap': command_replacement_swap, + }, + 'sort': on_buffers_changed, + }) + except HumanReadableError as e: + log(e, buffer) + return weechat.WEECHAT_RC_ERROR + + +command_description = r''' +NOTE: For the best effect, you may want to consider setting the option irc.look.server_buffer to independent and buffers.look.indenting to on. + +# Commands + +## Miscellaneous +/autosort sort +Manually trigger the buffer sorting. + + +## Sorting rules + +/autosort rules list +Print the list of sort rules. + +/autosort rules add = +Add a new rule at the end of the list. + +/autosort rules insert = +Insert a new rule at the given index in the list. + +/autosort rules update = +Update a rule in the list with a new pattern and score. + +/autosort rules delete +Delete a rule from the list. + +/autosort rules move +Move a rule from one position in the list to another. + +/autosort rules swap +Swap two rules in the list + + +## Replacement patterns + +/autosort replacements list +Print the list of replacement patterns. + +/autosort replacements add +Add a new replacement pattern at the end of the list. + +/autosort replacements insert +Insert a new replacement pattern at the given index in the list. + +/autosort replacements update +Update a replacement pattern in the list. + +/autosort replacements delete +Delete a replacement pattern from the list. + +/autosort replacements move +Move a replacement pattern from one position in the list to another. + +/autosort replacements swap +Swap two replacement pattern in the list + + +# Introduction +Autosort is a weechat script to automatically keep your buffers sorted. +The sort order can be customized by defining your own sort rules, +but the default should be sane enough for most people. +It can also group IRC channel/private buffers under their server buffer if you like. + +Autosort first turns buffer names into a list of their components by splitting on them on the period character. +For example, the buffer name "irc.server.freenode" is turned into ['irc', 'server', 'freenode']. +The list of buffers is then lexicographically sorted. + +To facilitate custom sort orders, it is possible to assign a score to each component individually before the sorting is done. +Any name component that did not get a score assigned will be sorted after those that did receive a score. +Components are always sorted on their score first and on their name second. +Lower scores are sorted first. + +## Automatic or manual sorting +By default, autosort will automatically sort your buffer list whenever a buffer is opened, merged, unmerged or renamed. +This should keep your buffers sorted in almost all situations. +However, you may wish to change the list of signals that cause your buffer list to be sorted. +Simply edit the "autosort.sorting.signals" option to add or remove any signal you like. +If you remove all signals you can still sort your buffers manually with the "/autosort sort" command. +To prevent all automatic sorting, "autosort.sorting.sort_on_config_change" should also be set to off. + +## Grouping IRC buffers +In weechat, IRC channel/private buffers are named "irc..<#channel>", +and IRC server buffers are named "irc.server.". +This does not work very well with lexicographical sorting if you want all buffers for one network grouped together. +That is why autosort comes with the "autosort.sorting.group_irc" option, +which secretly pretends IRC channel/private buffers are called "irc.server..<#channel>". +The buffers are not actually renamed, autosort simply pretends they are for sorting purposes. + +## Replacement patterns +Sometimes you may want to ignore some characters for sorting purposes. +On Freenode for example, you may wish to ignore the difference between channels starting with a double or a single hash sign. +To do so, simply add a replacement pattern that replaces ## with # with the following command: +/autosort replacements add ## # + +Replacement patterns do not support wildcards or special characters at the moment. + +## Sort rules +You can assign scores to name components by defining sort rules. +The first rule that matches a component decides the score. +Further rules are not examined. +Sort rules use the following syntax: + = + +You can use the "/autosort rules" command to show and manipulate the list of sort rules. + + +Allowed special characters in the glob patterns are: + +Pattern | Meaning +--------|-------- +* | Matches a sequence of any characters except for periods. +? | Matches a single character, but not a period. +[a-z] | Matches a single character in the given regex-like character class. +[^ab] | A negated regex-like character class. +\* | A backslash escapes the next characters and removes its special meaning. +\\ | A literal backslash. + + +## Example +As an example, consider the following rule list: +0: core = 0 +1: irc = 2 +2: * = 1 + +3: irc.server.*.#* = 1 +4: irc.server.*.* = 0 + +Rule 0 ensures the core buffer is always sorted first. +Rule 1 sorts IRC buffers last and rule 2 puts all remaining buffers in between the two. + +Rule 3 and 4 would make no sense with the group_irc option off. +With the option on though, these rules will sort private buffers before regular channel buffers. +Rule 3 matches channel buffers and assigns them a higher score, +while rule 4 matches the buffers that remain and assigns them a lower score. +The same effect could also be achieved with a single rule: +irc.server.*.[^#]* = 0 +''' + +command_completion = 'sort||rules list|add|insert|update|delete|move|swap||replacements list|add|insert|update|delete|move|swap' + + +if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + config = Config('autosort') + + weechat.hook_config('autosort.*', 'on_config_changed', '') + weechat.hook_command('autosort', command_description, '', '', command_completion, 'on_autosort_command', 'NULL') + on_config_changed() diff -r 603bb1ae9da2 -r 954e95288732 weechat/python/listbuffer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weechat/python/listbuffer.py Thu May 14 11:06:43 2015 -0400 @@ -0,0 +1,467 @@ +# -*- coding: utf-8 -*- +# +# ListBuffer, version 0.8.1 for WeeChat version 0.3 +# Latest development version: https://github.com/FiXato/listbuffer +# +# Show /list results in a common buffer and interact with them. +# +# This script allows you to easily join channels from the /list output. +# It will open a common buffer for the /list result, through which you +# browse with your cursor keys, and join with the meta-enter keys. +# Adjust sorting with meta->, meta-< and meta-/ keybindings. +# +## History: +### 2011-09-08: FiXato: +# +# * version 0.1: initial release. +# * added a common buffer for /list results +# * added highlighting for currently selected line +# * added /join support via enter key +# * added scroll_top and scroll_bottom support +# +# * version 0.2: /list format bugfix +# * added support for /list results without modes +# * some servers don't send 321 (/list start). Taken into account. +# +# * version 0.3: Sorting support +# * Added some basic sorting support. Scroll through sort options +# with meta-> and meta-< (users, channel, topic, modes) +# +### 2011-09-19: FiXato +# +# * version 0.4: +# * Case-insensitive buffer lookup fix. +# * Removed default enter keybind +# +### 2011-12-28: troydm: +# +# * version 0.5: It's an upside-down-world +# * Added inverted sorting support provided by Dmitry "troydm" Geurkov +# Use meta-/ to switch between inverted and regular sorting. +# +### 2012-02-10: FiXato: +# +# * version 0.6: Stop shoving that buffer in my face! +# * The listbuffer should no longer pop up by itself when you load the script. +# It should only pop up now when you actually do a /list query. +# +# * version 0.7: .. but please pop it up in my current window when I ask for it +# * Added setting plugins.var.python.listbuffer.autofocus +# This will autofocus the listbuffer in the current window if another window isn't +# already showing it, and of course only when the user issues /list +# +### 2012-07-10: FiXato: +# +# * version 0.7.1: Forgetful bugfix +# * Made sure lb_curline global variable is defined +# +### 2013-03-19: FiXato: +# +# * version 0.8: Sorted out the sorting +# * Added automatically updating options for sorting: +# * plugins.var.python.listbuffer.sort_inverted +# * plugins.var.python.listbuffer.sort_order +# * version 0.8.1: Pad it baby! +# * Channel modes are equally padded even when there are no channel modes. +# * Added padding options: +# * plugins.var.python.listbuffer.modes_min_width +# * plugins.var.python.listbuffer.channel_min_width +# * plugins.var.python.listbuffer.users_min_width +# +## Acknowledgements: +# * Dmitry "troydm" Geurkov, for providing the inverse-sorting patch to the project. +# * Sebastien "Flashcode" Helleu, for developing the kick-ass IRC client WeeChat +# and the iset.pl script which inspired me to this script. +# * Nils "nils_2" Görs, for his contributions to iset.pl which served as +# example code. +# * David "drubin" Rubin, for his urlgrab.py script, which also served +# as example code. +# * ArZa, whose listsort.pl script helped me getting started with +# grabbing the /list results. Parts of his code have been shamelessly +# copied and ported to Python. +# * Khaled Mardam-Bey, for making me yearn for similar /list support in +# WeeChat as mIRC already offered. :P +# * mave_, for pointing out that sort orders weren't remembered. +# +## TODO: +# - Auto-scroll selected line upon window scroll. +# - Add option to hide already joined channels. +# - Improve sorting methods +# - Add auto-join support +# - Detect if channel is already in auto-join +# - Allow automatically switching to the listbuffer +# - Add support for ALIS (/squery alis LIST * -mix 100 (IRCNet) +# - Make colours configurable +# - Limit number of channels to parse +# - Add filter support a la iset +# - Allow selecting multiple channels +# - Add optional command redirection. +# +## Copyright (c) 2011,2012,2013 Filip H.F. "FiXato" Slagter, +# +# http://profile.fixato.org +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +SCRIPT_NAME = "listbuffer" +SCRIPT_AUTHOR = "Filip H.F. 'FiXato' Slagter " +SCRIPT_VERSION = "0.8.1" +SCRIPT_LICENSE = "MIT" +SCRIPT_DESC = "A common buffer for /list output." +SCRIPT_COMMAND = "listbuffer" + +import_ok = True + +try: + import weechat +except ImportError: + print "This script must be run under WeeChat." + import_ok = False + +import re + +lb_settings = ( + ("autofocus", "on", "Focus the listbuffer in the current window if it isn't already displayed by a window."), + ("sort_order", "users", "Last used sort order for the channel list."), + ("sort_inverted", "on", "Invert the sort order for the channel list."), + ("modes_min_width", "8", "The minimum width used for modes in the channel list. If a channel has less modes than this amount, the column will be padded with spaces."), + ("channel_min_width", "25", "The minimum width used for the channel name in the channel list. If a channelname is shorter than this amount, the column will be padded with spaces."), + ("users_min_width", "8", "The minimum width used for the usercount in the channel list. If the usercount has less digits than this amount, the column will be padded with spaces."), +) +lb_buffer = None +lb_curline = 0 +lb_channels = [] +lb_network = None +lb_list_started = False +lb_current_sort = None +lb_sort_inverted = False +lb_sort_options = ( + 'channel', + 'users', + 'modes', + 'topic', +) + +# server numeric Nick Chan Users Modes Topic +lb_channel_list_expression = '(:\S+) (\d{3}) (\S+) (\S+) (\d+) :(\[(.*?)\] )?(.*)' + +# Create listbuffer. +def lb_create_buffer(): + global lb_buffer, lb_curline + + if not lb_buffer: + lb_buffer = weechat.buffer_new("listbuffer", "lb_input_cb", \ + "", "lb_close_cb", "") + lb_set_buffer_title() + # Sets notify to 0 as this buffer does not need to be in hotlist. + weechat.buffer_set(lb_buffer, "notify", "0") + weechat.buffer_set(lb_buffer, "nicklist", "0") + weechat.buffer_set(lb_buffer, "type", "free") + weechat.buffer_set(lb_buffer, "key_bind_ctrl-L", "/listbuffer **refresh") + weechat.buffer_set(lb_buffer, "key_bind_meta2-A", "/listbuffer **up") + weechat.buffer_set(lb_buffer, "key_bind_meta2-B", "/listbuffer **down") + weechat.buffer_set(lb_buffer, "key_bind_meta2-1~", "/listbuffer **scroll_top") + weechat.buffer_set(lb_buffer, "key_bind_meta2-4~", "/listbuffer **scroll_bottom") + weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-J", "/listbuffer **enter") + weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-M", "/listbuffer **enter") + weechat.buffer_set(lb_buffer, "key_bind_meta->", "/listbuffer **sort_next") + weechat.buffer_set(lb_buffer, "key_bind_meta-<", "/listbuffer **sort_previous") + weechat.buffer_set(lb_buffer, "key_bind_meta-/", "/listbuffer **sort_invert") + lb_curline = 0 + if weechat.config_get_plugin("autofocus") == "on": + if not weechat.window_search_with_buffer(lb_buffer): + weechat.command("", "/buffer " + weechat.buffer_get_string(lb_buffer,"name")) + +def lb_set_buffer_title(): + global lb_buffer, lb_current_sort + ascdesc = '(v)' if lb_sort_inverted else '(^)' + weechat.buffer_set(lb_buffer, "title", lb_line_format({ + 'channel': 'Channel name%s' % (ascdesc if lb_current_sort == 'channel' else ''), + 'users': 'Users%s' % (ascdesc if lb_current_sort == 'users' else ''), + 'modes': 'Modes%s' % (ascdesc if lb_current_sort == 'modes' else ''), + 'topic': 'Topic%s' % (ascdesc if lb_current_sort == 'topic' else ''), + 'nomodes': None, + })) + +def lb_list_start(data, signal, message): + lb_initialise_list + + return weechat.WEECHAT_RC_OK + +def lb_initialise_list(signal): + global lb_channels, lb_network, lb_list_started + + lb_create_buffer() + lb_channels = [] + lb_network = signal.split(',')[0] + lb_list_started = True + return + + +def lb_list_chan(data, signal, message): + global lb_channels, lb_buffer, lb_list_started + + # Work-around for IRCds which don't send 321 Numeric (/List start) + if not lb_list_started: + lb_initialise_list(signal) + + for chan_data in re.findall(lb_channel_list_expression,message): + lb_channels.append({ + 'server': chan_data[0][1:-1], + 'numeric': chan_data[1], + 'nick': chan_data[2], + 'channel': chan_data[3], + 'users': chan_data[4], + 'nomodes': chan_data[5] == '', + 'modes': chan_data[6], + 'topic': weechat.hook_modifier_exec("irc_color_decode", "1", chan_data[7]) + }) + return weechat.WEECHAT_RC_OK + +def lb_list_end(data, signal, message): + global lb_list_started + + # Work-around for IRCds which don't send 321 Numeric (/List start) + if not lb_list_started: + lb_initialise_list(signal) + + lb_list_started = False + if lb_current_sort: + lb_sort() + lb_refresh() + return weechat.WEECHAT_RC_OK + +def keyEvent (data, buffer, args): + global lb_options + lb_options[args]() + +def lb_input_cb(data, buffer, input_data): + global lb_options, lb_curline + lb_options[input_data]() + return weechat.WEECHAT_RC_OK + +def lb_refresh(): + global lb_channels, lb_buffer + weechat.buffer_clear(lb_buffer) + + y = 0 + for list_data in lb_channels: + lb_refresh_line(y) + y += 1 + return + +def lb_refresh_line(y): + global lb_buffer, lb_curline, lb_channels + if y >= 0 and y < len(lb_channels): + formatted_line = lb_line_format(lb_channels[y], y == lb_curline) + weechat.prnt_y(lb_buffer, y, formatted_line) + +def lb_refresh_curline(): + global lb_curline + lb_refresh_line(lb_curline-1) + lb_refresh_line(lb_curline) + lb_refresh_line(lb_curline+1) + return + +def lb_line_format(list_data,curr=False): + str = "" + if (curr): + str += weechat.color("yellow,red") + channel_text = list_data['channel'].ljust(int(weechat.config_get_plugin('channel_min_width'))) + users_text = "(%s)" % list_data['users'] + padded_users_text = users_text.rjust(int(weechat.config_get_plugin('users_min_width')) + 2) + str += "%s%s %s " % (weechat.color("bold"), channel_text, padded_users_text) + if not list_data['nomodes']: + modes = "[%s]" % list_data['modes'] + else: + modes = "[]" + str += "%s: " % modes.rjust(int(weechat.config_get_plugin('modes_min_width')) + 2) + str += "%s" % list_data['topic'] + return str + +def lb_line_up(): + global lb_curline + if lb_curline <= 0: + return + lb_curline -= 1 + lb_refresh_curline() + lb_check_outside_window() + return + +def lb_line_down(): + global lb_curline, lb_channels + if lb_curline+1 >= len(lb_channels): + return + lb_curline += 1 + lb_refresh_curline() + lb_check_outside_window() + return + +def lb_line_run(): + global lb_channels, lb_curline, lb_network + buff = weechat.info_get("irc_buffer", lb_network) + channel = lb_channels[lb_curline]['channel'] + command = "/join %s" % channel + weechat.command(buff, command) + return + +def lb_line_select(): + return + +def lb_scroll_top(): + global lb_curline + old_y = lb_curline + lb_curline = 0 + lb_refresh_curline() + lb_refresh_line(old_y) + weechat.command(lb_buffer, "/window scroll_top") + return + +def lb_scroll_bottom(): + global lb_curline, lb_channels + old_y = lb_curline + lb_curline = len(lb_channels)-1 + lb_refresh_curline() + lb_refresh_line(old_y) + weechat.command(lb_buffer, "/window scroll_bottom") + return + +def lb_check_outside_window(): + global lb_buffer, lb_curline + if (lb_buffer): + infolist = weechat.infolist_get("window", "", "current") + if (weechat.infolist_next(infolist)): + start_line_y = weechat.infolist_integer(infolist, "start_line_y") + chat_height = weechat.infolist_integer(infolist, "chat_height") + if(start_line_y > lb_curline): + weechat.command(lb_buffer, "/window scroll -%i" %(start_line_y - lb_curline)) + elif(start_line_y <= lb_curline - chat_height): + weechat.command(lb_buffer, "/window scroll +%i"%(lb_curline - start_line_y - chat_height + 1)) + weechat.infolist_free(infolist) + +def lb_sort_next(): + global lb_current_sort, lb_sort_options + if lb_current_sort: + new_index = lb_sort_options.index(lb_current_sort) + 1 + else: + new_index = 0 + + if len(lb_sort_options) <= new_index: + new_index = 0 + + lb_set_current_sort_order(lb_sort_options[new_index]) + lb_sort() + +def lb_set_current_sort_order(value): + global lb_current_sort + lb_current_sort = value + weechat.config_set_plugin('sort_order', lb_current_sort) + +def lb_set_invert_sort_order(value): + global lb_sort_inverted + lb_sort_inverted = value + weechat.config_set_plugin('sort_inverted', ('on' if lb_sort_inverted else 'off')) + +def lb_sort_previous(): + global lb_current_sort, lb_sort_options + if lb_current_sort: + new_index = lb_sort_options.index(lb_current_sort) - 1 + else: + new_index = 0 + + if new_index < 0: + new_index = len(lb_sort_options) - 1 + + lb_set_current_sort_order(lb_sort_options[new_index]) + lb_sort() + +def lb_sort(sort_key=None): + global lb_channels, lb_current_sort, lb_sort_inverted + if sort_key: + lb_set_current_sort_order(sort_key) + if lb_current_sort == 'users': + lb_channels = sorted(lb_channels, key=lambda chan_data: int(chan_data[lb_current_sort])) + else: + lb_channels = sorted(lb_channels, key=lambda chan_data: chan_data[lb_current_sort]) + if lb_sort_inverted: + lb_channels.reverse() + lb_set_buffer_title() + lb_refresh() + +def lb_sort_invert(): + global lb_current_sort, lb_sort_inverted + if lb_current_sort: + lb_set_invert_sort_order(not lb_sort_inverted) + lb_sort() + +def lb_close_cb(*kwargs): + """ A callback for buffer closing. """ + global lb_buffer + + lb_buffer = None + return weechat.WEECHAT_RC_OK + +lb_options = { + 'refresh' : lb_refresh, + 'up' : lb_line_up, + 'down' : lb_line_down, + 'enter' : lb_line_run, + 'space' : lb_line_select, + 'scroll_top' : lb_scroll_top, + 'scroll_bottom': lb_scroll_bottom, + 'sort_next' : lb_sort_next, + 'sort_previous': lb_sort_previous, + 'sort_invert': lb_sort_invert +} + +def lb_command_main(data, buffer, args): + if args[0:2] == "**": + keyEvent(data, buffer, args[2:]) + return weechat.WEECHAT_RC_OK + +def lb_set_default_settings(): + global lb_settings + # Set default settings + for option, default_value, description in lb_settings: + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, default_value) + version = weechat.info_get("version_number", "") or 0 + if int(version) >= 0x00030500: + weechat.config_set_desc_plugin(option, description) + +def lb_reset_stored_sort_order(): + global lb_current_sort, lb_sort_inverted + lb_current_sort = weechat.config_get_plugin('sort_order') + lb_sort_inverted = (True if weechat.config_get_plugin('sort_inverted') == 'on' else False) + +if __name__ == "__main__" and import_ok: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, "lb_close_cb", ""): + lb_set_default_settings() + lb_reset_stored_sort_order() + lb_buffer = weechat.buffer_search("python", "listbuffer") + + weechat.hook_signal("*,irc_in_321", "lb_list_start", "") + weechat.hook_signal("*,irc_in_322", "lb_list_chan", "") + weechat.hook_signal("*,irc_in_323", "lb_list_end", "") + weechat.hook_command(SCRIPT_COMMAND, + "List Buffer", + "", "", "", + "lb_command_main", "") diff -r 603bb1ae9da2 -r 954e95288732 weechat/ruby/autoload/challengeauth.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weechat/ruby/autoload/challengeauth.rb Thu May 14 11:06:43 2015 -0400 @@ -0,0 +1,111 @@ +# Copyright (c) 2013 Dominik Honnef + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# History: +# 2013-04-20, Dominik Honnef +# version 0.0.1: initial version + +require "openssl" + +QBot = "Q@CServe.quakenet.org" +QBotHost = "Q!TheQBot@CServe.quakenet.org" +Request = Struct.new(:username, :hash) + +def weechat_init + @requests = {} + + Weechat.register("challengeauth", + "Dominik Honnef", + "0.0.1", + "MIT", + "Securely authenticate with QuakeNet by using CHALLENGEAUTH", + "", + "") + + Weechat.hook_command("challengeauth", + "Authenticate with Q using CHALLENGEAUTH", + "[username] [password]", + "", + "", + "challengeauth", + "") + + Weechat.hook_modifier("irc_in_notice", "challenge_notice", "") + + return Weechat::WEECHAT_RC_OK +end + +def calculate_q_hash(username, hash, challenge) + username = username.tr("A-Z[]\\\\^", "a-z{}|~") + + key = OpenSSL::Digest::SHA256.hexdigest("#{username}:#{hash}") + return OpenSSL::HMAC.hexdigest("SHA256", key, challenge) +end + +def get_server_buffer(server) + Weechat.buffer_search("irc", "server." + server) +end + +def challengeauth(data, buffer, args) + plugin = Weechat.buffer_get_string(buffer, "localvar_plugin") + if plugin != "irc" + Weechat.print(buffer, "/challengeauth only works for IRC buffers.") + return Weechat.WEECHAT_RC_ERROR + end + + server = Weechat.buffer_get_string(buffer, "localvar_server") + args = args.split(" ", 2) + username = args[0] + password = args[1] + hash = OpenSSL::Digest::SHA256.hexdigest(password[0, 10]) + + @requests[server] = Request.new(username, hash) + server_buffer = get_server_buffer(server) + Weechat.print(server_buffer, "Authenticating as #{username}...") + Weechat.command(server_buffer, "/quote PRIVMSG #{QBot} :CHALLENGE") + + return Weechat::WEECHAT_RC_OK +end + +def challenge_notice(modifier, data, server, line) + return line unless @requests.has_key?(server) + + parts = line.split(" ") + return line unless parts.size > 5 + + host = parts[0][1..-1] + command = parts[3][1..-1] + challenge = parts[4] + + return line if host != QBotHost || command != "CHALLENGE" + + request = @requests[server] + response = calculate_q_hash(request.username, request.hash, challenge) + server_buffer = get_server_buffer(server) + + Weechat.print(server_buffer, "Sending challengeauth for #{request.username}...") + Weechat.command(server_buffer, + "/quote PRIVMSG %s :CHALLENGEAUTH %s %s HMAC-SHA-256" % [QBot, request.username, response]) + + @requests.delete(server) + + return line +end diff -r 603bb1ae9da2 -r 954e95288732 weechat/weechat.conf --- a/weechat/weechat.conf Thu Dec 18 21:59:14 2014 -0500 +++ b/weechat/weechat.conf Thu May 14 11:06:43 2015 -0400 @@ -35,8 +35,8 @@ day_change_message_1date = "-- %a, %d %b %Y --" day_change_message_2dates = "-- %%a, %%d %%b %%Y (%a, %d %b %Y) --" eat_newline_glitch = off -highlight = "sjl,slosh,slj,clojerks,horrifying" -highlight_regex = "(^all[,:]|backend(ia)?[^s/]|red[- ]tape|steve ?losh|rob ford|(jesus )?fucking christ|bring me my [A-Za-z0-9_]+ (pant|breeche|kilt|skirt|short|jort|plort)s?|(horse|mouse|clown)fuckers?|([[:<:]]((mother)?fuck([ie]ng?|er)?|(god?)?damn(ed)?|dammit|(bull|horse)?shite?)[[:>:]].*){3,}|actual footage)" +highlight = "sjl,slosh,slj,clojerks,horrifying,remotes" +highlight_regex = "(^all[,:]|backend(ia)?[^s/]|red[- ]tape|steve ?losh|rob ford|(jesus )?fucking christ|bring me my [A-Za-z0-9_]+ (pant|breeche|kilt|skirt|short|jort|plort)s?|(horse|mouse|clown)fuckers?|([[:<:]]((mother)?fuck([ie]ng?|er)?|(god?)?damn(ed)?|dammit|(bull|horse)?shite?)[[:>:]].*){3,}|actual footage|pork ?belly)" highlight_tags = "" hotlist_add_buffer_if_away = on hotlist_buffer_separator = ", " @@ -250,7 +250,7 @@ status.filling_left_right = vertical status.filling_top_bottom = horizontal status.hidden = off -status.items = "[time],buffer_number+:+buffer_name" +status.items = "[time],buffer_number+:+buffer_name,buffer_title" status.position = bottom status.priority = 500 status.separator = off