From 941b0926f21ba1591e17099029581db96700b912 Mon Sep 17 00:00:00 2001 From: Simmo Saan Date: Sat, 4 Feb 2017 09:25:16 +0100 Subject: [PATCH 001/642] New script force_nick.py: force nick change on channels which disallow it --- python/force_nick.py | 169 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 python/force_nick.py diff --git a/python/force_nick.py b/python/force_nick.py new file mode 100644 index 00000000..26c0fa06 --- /dev/null +++ b/python/force_nick.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015 by Simmo Saan +# +# 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 . +# + +# +# History: +# +# 2015-08-07, Simmo Saan +# version 0.4: option for invite-only channels +# 2015-08-07, Simmo Saan +# version 0.3: options to control risky channel cycling +# 2015-07-03, Simmo Saan +# version 0.2: ability to rejoin passworded channels +# 2015-07-01, Simmo Saan +# version 0.1: initial script +# + +""" +Force nick change on channels which disallow it +""" + +from __future__ import print_function + +SCRIPT_NAME = "force_nick" +SCRIPT_AUTHOR = "Simmo Saan " +SCRIPT_VERSION = "0.4" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Force nick change on channels which disallow it" + +IMPORT_OK = True + +try: + import weechat +except ImportError: + print("This script must be run under WeeChat.") + print("Get WeeChat now at: http://www.weechat.org/") + IMPORT_OK = False + +SETTINGS = { + "cycle_detach": ( + "off", + "automatically cycle channels which are not open in WeeChat"), + "cycle_key": ( + "on", + "automatically cycle passworded channels (+k)"), + "cycle_invite": ( + "off", + "automatically cycle invite-only channels (+i)") +} + +import re + +servers = {} + +def parse_message(signal_data): + hashtable = weechat.info_get_hashtable("irc_message_parse", {"message": signal_data}) + + # parse arguments string into usable pieces + args = hashtable["arguments"].split(":", 1) + hashtable["args"] = args[0].split() + if len(args) > 1: + hashtable["text"] = args[1] + + return hashtable + +def channel_block(server, channel): + fail = None + config_cycle = lambda opt: weechat.config_string_to_boolean(weechat.config_get_plugin("cycle_%s" % opt)) + + channels = weechat.infolist_get("irc_channel", "", "%s,%s" % (server, channel)) + if weechat.infolist_next(channels): + modes = weechat.infolist_string(channels, "modes") + + if not config_cycle("key") and weechat.infolist_string(channels, "key") != "": + fail = "cycle_key" + elif not config_cycle("invite") and "i" in modes: + fail = "cycle_invite" + elif not config_cycle("detach"): + fail = "cycle_detach" + + weechat.infolist_free(channels) + + if fail: + weechat.prnt("", "%s: won't automatically cycle %s.%s: %s" % (SCRIPT_NAME, server, channel, fail)) + else: + servers[server]["channels"].append(channel) + buffer = weechat.buffer_search("irc", server) + weechat.command(buffer, "/part %s" % channel) + weechat.command(buffer, "/nick %s" % servers[server]["nick"]) + +def nick_out_cb(data, signal, signal_data): + server = signal.split(",")[0] + parsed = parse_message(signal_data) + nick = parsed["args"][0] + + if server not in servers: # initialize new nickchange + servers[server] = {} + servers[server]["channels"] = [] + + servers[server]["nick"] = nick + + return weechat.WEECHAT_RC_OK + +def nick_in_cb(data, signal, signal_data): + server = signal.split(",")[0] + parsed = parse_message(signal_data) + mynick = weechat.info_get("irc_nick", server) + + if parsed["nick"] == mynick: # nick change worked + channels = weechat.infolist_get("irc_channel", "", server) + keys = {} + while weechat.infolist_next(channels): + keys[weechat.infolist_string(channels, "name")] = weechat.infolist_string(channels, "key") + + buffer = weechat.buffer_search("irc", server) + for channel in servers[server]["channels"]: + weechat.command(buffer, "/join -noswitch %s %s" % (channel, keys.get(channel, ""))) + + weechat.infolist_free(channels) + + servers.pop(server) + + return weechat.WEECHAT_RC_OK + +def unreal_cb(data, signal, signal_data): + server = signal.split(",")[0] + parsed = parse_message(signal_data) + + match = re.match(r"Can not change nickname while on (#\w+) \(\+N\)", parsed["text"]) + if match: + channel = match.group(1) + channel_block(server, channel) + + return weechat.WEECHAT_RC_OK + +def freenode_cb(data, signal, signal_data): + server = signal.split(",")[0] + parsed = parse_message(signal_data) + + channel_block(server, parsed["args"][2]) + + return weechat.WEECHAT_RC_OK + +if __name__ == "__main__" and IMPORT_OK: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + weechat.hook_signal("*,irc_out1_nick", "nick_out_cb", "") + weechat.hook_signal("*,irc_in_nick", "nick_in_cb", "") + weechat.hook_signal("*,irc_in_447", "unreal_cb", "") + weechat.hook_signal("*,irc_in_435", "freenode_cb", "") + + for option, value in SETTINGS.items(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value[0]) + + weechat.config_set_desc_plugin(option, "%s (default: \"%s\")" % (value[1], value[0])) From b063094594e223f543dab8961123a613b988e3f0 Mon Sep 17 00:00:00 2001 From: Yidhra Kthanid Date: Sun, 19 Feb 2017 07:53:29 +0100 Subject: [PATCH 002/642] emote.scm 0.3: add more emotes --- guile/emote.scm | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/guile/emote.scm b/guile/emote.scm index 65f058da..f3555f56 100644 --- a/guile/emote.scm +++ b/guile/emote.scm @@ -17,29 +17,39 @@ ; (this script requires WeeChat 0.4.1 or newer) ; ; History: -; 2016-06-03, nycatelos +; 2017-02-18, nycatelos +; version 0.3: added more emotes +; 2016-06-03, nycatelos ; version 0.2: added additional emotes ; 2014-05-03, csmith ; version 0.1: initial release (use-modules (srfi srfi-69)) -(weechat:register "emote" "Caleb Smith" "0.2" "GPL" "Emote" "" "") +(weechat:register "emote" "Caleb Smith" "0.3" "GPL" "Emote" "" "") ; Mappings of words with their emoticons (define patterns (alist->hash-table '( ("tableflip" . "(╯° °)╯︵ ┻━┻)") ("rageflip" . "(ノಠ益ಠ)ノ彡┻━┻") ("doubleflip" . "┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻") - ("lookofdisapproval" . "ಠ_ಠ") + ("disapproval" . "ಠ_ಠ") ("sun" . "☼") ("kitaa" . "キタ━━━(゜∀゜)━━━!!!!!") ("joy" . "◕‿◕") ("nyancat" . "~=[,,_,,]:3") - ("lennyface" . "( ͡° ͜ʖ ͡°)") + ("lenny" . "( ͡° ͜ʖ ͡°)") ("shrug" . "¯\\_(ツ)_/¯") ("denko" . "(・ω・)") ("tableplace" . "┬─┬ ノ( ゜-゜ノ)") + ("gface" . "( ≖‿≖)") + ("facepalm" . "(-‸ლ)") + ("tehe" . "☆~(◡﹏◕✿)") + ("angry" . "(╬ ಠ益ಠ)") + ("umu" . "( ̄ー ̄)") + ("toast" . "( ^_^)o自自o(^_^ )") + ("yay" . "ヽ(´ー`)ノ") + ))) From 717e460126e87cc61c422f093708df6b6a1d0eb1 Mon Sep 17 00:00:00 2001 From: arza Date: Tue, 21 Feb 2017 19:20:04 +0100 Subject: [PATCH 003/642] buffers.pl 5.5: fix memory leak in perl 5.23.7-5.24.1, fix truncation and crop_suffix when truncating to 1-4 characters, fix prefix_empty for inactive buffers, tidy code --- perl/buffers.pl | 971 +++++++++++++++++++++++------------------------- 1 file changed, 475 insertions(+), 496 deletions(-) diff --git a/perl/buffers.pl b/perl/buffers.pl index 73eb4b55..600ab40c 100644 --- a/perl/buffers.pl +++ b/perl/buffers.pl @@ -20,15 +20,20 @@ # # History: # +# 2017-02-21, arza : +# v5.5: fix memory leak in perl 5.23.7-5.24.1 +# fix truncation and crop_suffix when truncating to 1-4 characters +# fix prefix_empty for inactive buffers +# tidy code # 2016-05-01, mumixam : -# v5.4: added option "detach_buffer_immediately_level" +# v5.4: add option "detach_buffer_immediately_level" # 2015-08-21, Matthew Cox # v5.3: add option "indenting_amount", to adjust the indenting of channel buffers # 2015-05-02, arza : # v5.2: truncate long names (name_size_max) more when mark_inactive adds parenthesis # 2015-03-29, Ed Santiago : # v5.1: merged buffers: always indent, except when filling is horizontal -# 2014-12-12 +# 2014-12-12, oakkitten # v5.0: fix cropping non-latin buffer names # 2014-08-29, Patrick Steinhardt : # v4.9: add support for specifying custom buffer names @@ -46,8 +51,8 @@ # weechat.look.buffer_auto_renumber is off # 2013-12-10, nils_2@freenode.#weechat: # v4.3: add options "prefix_bufname" and "suffix_bufname (idea by silverd) -# : fix hook_timer() for show_lag wasn't disabled -# : improved signal handling (less updating of buffers list) +# fix hook_timer() for show_lag wasn't disabled +# improve signal handling (less updating of buffers list) # 2013-11-07, Sebastien Helleu : # v4.2: use default filling "columns_vertical" when bar position is top/bottom # 2013-10-31, nils_2@freenode.#weechat: @@ -56,11 +61,11 @@ # v4.0: add options "detach_displayed_buffers", "detach_display_window_number" # 2013-09-27, nils_2@freenode.#weechat: # v3.9: add option "toggle_bar" and option "show_prefix_query" (idea by IvarB) -# : fix problem with linefeed at end of list of buffers (reported by grawity) +# fix problem with linefeed at end of list of buffers (reported by grawity) # 2012-10-18, nils_2@freenode.#weechat: # v3.8: add option "mark_inactive", to mark buffers you are not in (idea by xrdodrx) -# : add wildcard "*" for immune_detach_buffers (idea by StarWeaver) -# : add new options "detach_query" and "detach_free_content" (idea by StarWeaver) +# add wildcard "*" for immune_detach_buffers (idea by StarWeaver) +# add new options "detach_query" and "detach_free_content" (idea by StarWeaver) # 2012-10-06, Nei : # v3.7: call menu on right mouse if menu script is loaded. # 2012-10-06, nils_2 : @@ -104,7 +109,7 @@ # 2011-08-24, Sebastien Helleu : # v2.4: add mouse support # 2011-06-06, nils_2 : -# v2.3: added: missed option "color_whitelist_default" +# v2.3: add missing option "color_whitelist_default" # 2011-03-23, Sebastien Helleu : # v2.2: fix color of nick prefix with WeeChat >= 0.3.5 # 2011-02-13, nils_2 : @@ -172,15 +177,13 @@ use Encode qw( decode encode ); # -----------------------------[ internal ]------------------------------------- my $SCRIPT_NAME = "buffers"; -my $SCRIPT_VERSION = "5.4"; +my $SCRIPT_VERSION = "5.5"; my $BUFFERS_CONFIG_FILE_NAME = "buffers"; my $buffers_config_file; my $cmd_buffers_whitelist= "buffers_whitelist"; my $cmd_buffers_detach = "buffers_detach"; -my $maxlength; - my %mouse_keys = ("\@item(buffers):button1*" => "hsignal:buffers_mouse", "\@item(buffers):button2*" => "hsignal:buffers_mouse", "\@bar(buffers):ctrl-wheelup" => "hsignal:buffers_mouse", @@ -323,6 +326,7 @@ sub buffers_cmd_whitelist } return weechat::WEECHAT_RC_OK; } + sub buffers_cmd_detach { my ( $data, $buffer, $args ) = @_; @@ -367,12 +371,12 @@ sub create_whitelist { my @buffers_list = @{$_[0]}; my $buffers_list = ""; - foreach (@buffers_list) - { - $buffers_list .= $_ .","; - } - # remove last "," - chop $buffers_list; + foreach (@buffers_list) + { + $buffers_list .= $_ .","; + } + # remove last "," + chop $buffers_list; return $buffers_list; } @@ -388,7 +392,7 @@ sub hook_timer_detach else { weechat::unhook($Hooks{timer_detach}) if $Hooks{timer_detach}; - $Hooks{timer_detach} = weechat::hook_timer( weechat::config_integer( $options{"detach"}) * 1000, 60, 0, "buffers_signal_hotlist", ""); + $Hooks{timer_detach} = weechat::hook_timer( weechat::config_integer($options{"detach"}) * 1000, 60, 0, "buffers_signal_hotlist", ""); } weechat::bar_item_update($SCRIPT_NAME); return weechat::WEECHAT_RC_OK; @@ -415,15 +419,18 @@ sub buffers_config_read { return weechat::config_read($buffers_config_file) if ($buffers_config_file ne ""); } + sub buffers_config_write { return weechat::config_write($buffers_config_file) if ($buffers_config_file ne ""); } + sub buffers_config_reload_cb { my ($data, $config_file) = ($_[0], $_[1]); return weechat::config_reload($config_file) } + sub buffers_config_init { $buffers_config_file = weechat::config_new($BUFFERS_CONFIG_FILE_NAME, @@ -859,6 +866,7 @@ sub buffers_config_init "", "", "buffers_signal_config", "", "", "" ], ); + # section "color" my $section_color = weechat::config_new_section( $buffers_config_file, @@ -923,6 +931,297 @@ sub buffers_config_init } } +# get sort key of buffer +sub key_of_buffer +{ + my ($buffer, $number) = @_; + my $key = ""; + + if (weechat::config_integer($options{"sort"}) eq 1) # number = 0; name = 1 + { + my $name = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_custom_name"); + if (not defined $name or $name eq "") + { + if (weechat::config_boolean( $options{"short_names"} ) eq 1) + { + $name = $buffer->{"short_name"}; + } + else + { + $name = $buffer->{"name"}; + } + } + if ( weechat::config_boolean($options{"core_to_front"}) eq 1) + { + if ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "channel" and + weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "private" ) + { + my $type = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type"); + if ( $type eq "" and $name ne "weechat") + { + $name = " " . $name; + } + else + { + $name = " " . $name; + } + } + } + $key = sprintf("%s%08d", lc($name), $buffer->{"number"}); + } + else + { + $key = sprintf("%08d", $number); + } + + return $key; +} + +# whether to skip this buffer +sub skip_buffer +{ + my ($buffer) = @_; + return 0 if $buffer->{"active"}; + + if ( weechat::config_string($options{"hide_merged_buffers"}) eq "server" and + ($buffer->{"type"} eq "server" or $buffer->{"plugin_name"} eq "core") ) + { + return 1; + } + if ( weechat::config_string($options{"hide_merged_buffers"}) eq "channel" and + ($buffer->{"type"} eq "channel" or $buffer->{"plugin_name"} eq "core") ) + { + return 1; + } + if ( weechat::config_string($options{"hide_merged_buffers"}) eq "private" and + ($buffer->{"type"} eq "private" or $buffer->{"plugin_name"} eq "core") ) + { + return 1; + } + if ( weechat::config_string($options{"hide_merged_buffers"}) eq "keepserver" and + ($buffer->{"type"} ne "server" or $buffer->{"plugin_name"} eq "core") ) + { + return 1; + } + if ( weechat::config_string($options{"hide_merged_buffers"}) eq "all" ) + { + return 1; + } + + return 0; +} + +# truncate string from the end to $maxlength, 0 = don't truncate +sub truncate_end +{ + my ($name, $maxlength) = @_; + if ($maxlength == 0) + { + return $name; + } + my $str = decode("UTF-8", $name); + $str = substr($str, 0, $maxlength); + $str = encode("UTF-8", $str); + return $str; +} + +# format one buffer name in buffers bar: truncate and add parentheses +sub format_name +{ + my ($buffer, $fg, $bg) = @_; + my $output = ""; + my $crop_suffix = weechat::color( weechat::config_color($options{"color_number_char"}) ) . weechat::config_string($options{"name_crop_suffix"}); + my $maxlength = weechat::config_integer($options{"name_size_max"}); + + my $name = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_custom_name"); + if (not defined $name or $name eq "") + { + if (weechat::config_boolean( $options{"short_names"} ) eq 1) + { + $name = $buffer->{"short_name"}; + } + else + { + $name = $buffer->{"name"}; + } + } + + if ( $buffer->{"type"} eq "channel" && + weechat::config_boolean($options{"mark_inactive"}) eq 1 && + $buffer->{"nicks_count"} == 0 && + $maxlength > 2 ) + { + $output = weechat::color( weechat::config_color($options{"color_number_char"}) ). + "(". + weechat::color($fg). + weechat::color(",$bg"). + truncate_end($name, $maxlength-2). + (length($name) > $maxlength-2 ? $crop_suffix : ""). + weechat::color( weechat::config_color($options{"color_number_char"}) ). + ")"; + } + else + { + $output = weechat::color($fg). + weechat::color(",$bg"). + truncate_end($name, $maxlength). + (length($name) > $maxlength && $maxlength > 0 ? $crop_suffix : ""); + } + + return $output; +} + +# get fg and bg for a buffer +sub get_colors +{ + my ($buffer, %hotlist) = @_; + my $fg = weechat::config_color( $options{"color_default_fg"} ); + my $bg = weechat::config_color( $options{"color_default_bg"} ); + + if ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "private" ) + { + if ( weechat::config_color($options{"queries_default_bg"}) ne "default" || weechat::config_color($options{"queries_default_fg"}) ne "default" ) + { + $fg = weechat::config_color( $options{"queries_default_fg"} ); + $bg = weechat::config_color( $options{"queries_default_bg"} ); + } + } + # check for core and buffer with free content + elsif ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "channel" and + weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "private" ) + { + $fg = weechat::config_color( $options{"color_none_channel_fg"} ); + $bg = weechat::config_color( $options{"color_none_channel_bg"} ); + } + # default whitelist buffer? + if (grep {$_ eq $buffer->{"name"}} @whitelist_buffers) + { + $fg = weechat::config_color( $options{"color_whitelist_default_fg"} ); + $bg = weechat::config_color( $options{"color_whitelist_default_bg"} ); + } + + $fg = "default" if ($fg eq ""); + $bg = "default" if ($bg eq ""); + + # color for channel and query buffer + if (exists $hotlist{$buffer->{"pointer"}}) + { + delete $buffers_timer{$buffer->{"pointer"}}; + # check if buffer is in whitelist buffer + if (grep {$_ eq $buffer->{"name"}} @whitelist_buffers) + { + $fg = weechat::config_color( $options{"color_whitelist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} ); + $bg = weechat::config_color( $options{"color_whitelist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} ); + } + elsif ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "private" ) + { + # queries_default_fg/bg and buffers.color.queries_message_fg/bg + if ( weechat::config_color($options{"queries_highlight_fg"}) ne "default" || + weechat::config_color($options{"queries_highlight_bg"}) ne "default" || + weechat::config_color($options{"queries_message_fg"}) ne "default" || + weechat::config_color($options{"queries_message_bg"}) ne "default" ) + { + if ( ($hotlist{$buffer->{"pointer"}}) == 2 ) + { + $fg = weechat::config_color( $options{"queries_message_fg"} ); + $bg = weechat::config_color( $options{"queries_message_bg"} ); + } + + elsif ( ($hotlist{$buffer->{"pointer"}}) == 3 ) + { + $fg = weechat::config_color( $options{"queries_highlight_fg"} ); + $bg = weechat::config_color( $options{"queries_highlight_bg"} ); + } + } + else + { + $fg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} ); + $bg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} ); + } + } + else + { + $fg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} ); + $bg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} ); + } + } + + if ($buffer->{"current_buffer"}) + { + $fg = weechat::config_color( $options{"color_current_fg"} ); + $bg = weechat::config_color( $options{"color_current_bg"} ); + } + return ($fg, $bg); +} + +# get nick prefix of channel +sub nick_prefix +{ + my ($buffer) = @_; + my $output = ""; + + my $nickname = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_nick"); + if ($nickname eq "") + { + return ""; + } + + # with version >= 0.3.2, this infolist will return only nick + # with older versions, whole nicklist is returned for buffer, and this can be very slow + my $infolist_nick = weechat::infolist_get("nicklist", $buffer->{"pointer"}, "nick_".$nickname); + if ($infolist_nick eq "") + { + return weechat::config_boolean($options{"show_prefix_empty"}) eq 1 && $buffer->{"type"} eq "channel" ? " " : ""; + } + while (weechat::infolist_next($infolist_nick)) + { + if ( (weechat::infolist_string($infolist_nick, "type") eq "nick") + && (weechat::infolist_string($infolist_nick, "name") eq $nickname) ) + { + my $prefix = weechat::infolist_string($infolist_nick, "prefix"); + if ( ($prefix eq " ") and (weechat::config_boolean($options{"show_prefix_empty"}) eq 0) ) + { + last; + } + + # with version >= 0.3.5, it is now a color name (for older versions: option name with color) + if ($weechat_version >= 0x00030500) + { + $output .= weechat::color(weechat::infolist_string($infolist_nick, "prefix_color")); + } + else + { + $output .= weechat::color(weechat::config_color( + weechat::config_get( + weechat::infolist_string($infolist_nick, "prefix_color")))); + } + $output .= $prefix; + last; + } + } + weechat::infolist_free($infolist_nick); + return $output; +} + +# get all hotlist counts for a buffer +sub hotlist_counts +{ + my ($buffer, %hotlist) = @_; + my $delim_color = weechat::color( weechat::config_color($options{"color_number_char"}) ); + my $counters = ""; + + foreach my $counter ("low", "message", "private", "highlight") + { + if ($hotlist{$buffer."_count_${counter}"}) + { + $counters =~ s/([0-9])$/$1,/; + $counters .= weechat::color( weechat::config_color($options{"color_hotlist_${counter}_fg"}) ) . $hotlist{$buffer."_count_${counter}"}; + } + } + return " $delim_color($counters$delim_color)"; +} + +# buffers item sub build_buffers { my $str = ""; @@ -942,15 +1241,15 @@ sub build_buffers { $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")} = weechat::infolist_integer($infolist, "priority"); - if ( weechat::config_boolean( $options{"hotlist_counter"} ) eq 1 and $weechat_version >= 0x00030500) + if ( weechat::config_boolean( $options{"hotlist_counter"} ) eq 1 and $weechat_version >= 0x00030500 ) { - $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_00"} = + $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_low"} = weechat::infolist_integer($infolist, "count_00"); # low message - $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_01"} = + $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_message"} = weechat::infolist_integer($infolist, "count_01"); # channel message - $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_02"} = + $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_private"} = weechat::infolist_integer($infolist, "count_02"); # private message - $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_03"} = + $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_highlight"} = weechat::infolist_integer($infolist, "count_03"); # highlight message } } @@ -965,13 +1264,15 @@ sub build_buffers my $max_number = 0; my $max_number_digits = 0; my $active_seen = 0; + my $current_time = time(); + $infolist = weechat::infolist_get("buffer", "", ""); while (weechat::infolist_next($infolist)) { # ignore hidden buffers (WeeChat >= 0.4.4) - if ($weechat_version >= 0x00040400) + if ($weechat_version >= 0x00040400 and weechat::infolist_integer($infolist, "hidden")) { - next if (weechat::infolist_integer($infolist, "hidden")); + next; } my $buffer; my $number = weechat::infolist_integer($infolist, "number"); @@ -1002,35 +1303,44 @@ sub build_buffers $buffer->{"short_name"} = weechat::infolist_string($infolist, "short_name"); $buffer->{"full_name"} = $buffer->{"plugin_name"}.".".$buffer->{"name"}; $buffer->{"type"} = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type"); - #weechat::print("", $buffer->{"type"}); # check if buffer is active (or maybe a /part, /kick channel) if ($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1) { my $server = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_server"); my $channel = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_channel"); - my $infolist_channel = weechat::infolist_get("irc_channel", "", $server.",".$channel); + my $infolist_channel = weechat::infolist_get("irc_channel", "", "$server,$channel"); if ($infolist_channel) { weechat::infolist_next($infolist_channel); $buffer->{"nicks_count"} = weechat::infolist_integer($infolist_channel, "nicks_count"); - }else + } + else { $buffer->{"nicks_count"} = 0; } weechat::infolist_free($infolist_channel); } - my $result = check_immune_detached_buffers($buffer->{"name"}); # checking for wildcard my $maxlevel = weechat::config_integer($options{"detach_buffer_immediately_level"}); next if ( check_detach_buffer_immediately($buffer->{"name"}) eq 1 and $buffer->{"current_buffer"} eq 0 and ( not exists $hotlist{$buffer->{"pointer"}} or $hotlist{$buffer->{"pointer"}} < $maxlevel) ); # checking for buffer to immediately detach - unless ($result) + if (check_immune_detached_buffers($buffer->{"name"})) { - my $detach_time = weechat::config_integer( $options{"detach"}); - my $current_time = time(); + if ($active_seen) + { + push(@current2, $buffer); + } + else + { + push(@current1, $buffer); + } + } + else + { + my $detach_time = weechat::config_integer($options{"detach"}); # set timer for buffers with no hotlist action $buffers_timer{$buffer->{"pointer"}} = $current_time if ( not exists $hotlist{$buffer->{"pointer"}} @@ -1039,37 +1349,40 @@ sub build_buffers and $detach_time > 0); $buffers_timer{$buffer->{"pointer"}} = $current_time - if (weechat::config_boolean($options{"detach_query"}) eq 1 - and not exists $hotlist{$buffer->{"pointer"}} - and $buffer->{"type"} eq "private" - and not exists $buffers_timer{$buffer->{"pointer"}} - and $detach_time > 0); + if (weechat::config_boolean($options{"detach_query"}) eq 1 + and not exists $hotlist{$buffer->{"pointer"}} + and $buffer->{"type"} eq "private" + and not exists $buffers_timer{$buffer->{"pointer"}} + and $detach_time > 0); $detach_time = 0 - if (weechat::config_boolean($options{"detach_query"}) eq 0 - and $buffer->{"type"} eq "private"); + if (weechat::config_boolean($options{"detach_query"}) eq 0 + and $buffer->{"type"} eq "private"); # free content buffer $buffers_timer{$buffer->{"pointer"}} = $current_time - if (weechat::config_boolean($options{"detach_free_content"}) eq 1 - and not exists $hotlist{$buffer->{"pointer"}} - and $buffer->{"type"} eq "" - and not exists $buffers_timer{$buffer->{"pointer"}} - and $detach_time > 0); + if (weechat::config_boolean($options{"detach_free_content"}) eq 1 + and not exists $hotlist{$buffer->{"pointer"}} + and $buffer->{"type"} eq "" + and not exists $buffers_timer{$buffer->{"pointer"}} + and $detach_time > 0); + $detach_time = 0 - if (weechat::config_boolean($options{"detach_free_content"}) eq 0 - and $buffer->{"type"} eq ""); + if (weechat::config_boolean($options{"detach_free_content"}) eq 0 + and $buffer->{"type"} eq ""); - $detach_time = 0 if (weechat::config_boolean($options{"mark_inactive"}) eq 1 and defined $buffer->{"nicks_count"} and $buffer->{"nicks_count"} == 0); + $detach_time = 0 + if (weechat::config_boolean($options{"mark_inactive"}) eq 1 + and defined $buffer->{"nicks_count"} + and $buffer->{"nicks_count"} == 0); # check for detach unless ( $buffer->{"current_buffer"} eq 0 - and not exists $hotlist{$buffer->{"pointer"}} -# and $buffer->{"type"} eq "channel" - and exists $buffers_timer{$buffer->{"pointer"}} - and $detach_time > 0 - and $weechat_version >= 0x00030800 - and $current_time - $buffers_timer{$buffer->{"pointer"}} >= $detach_time) + and not exists $hotlist{$buffer->{"pointer"}} + and exists $buffers_timer{$buffer->{"pointer"}} + and $detach_time > 0 + and $weechat_version >= 0x00030800 + and $current_time - $buffers_timer{$buffer->{"pointer"}} >= $detach_time) { if ($active_seen) { @@ -1081,102 +1394,42 @@ sub build_buffers } } elsif ( $buffer->{"current_buffer"} eq 0 - and not exists $hotlist{$buffer->{"pointer"}} -# and $buffer->{"type"} eq "channel" - and exists $buffers_timer{$buffer->{"pointer"}} - and $detach_time > 0 - and $weechat_version >= 0x00030800 - and $current_time - $buffers_timer{$buffer->{"pointer"}} >= $detach_time) - { # check for option detach_displayed_buffers and if buffer is displayed in a split window - if ( $buffer->{"num_displayed"} eq 1 - and weechat::config_boolean($options{"detach_displayed_buffers"}) eq 0 ) + and not exists $hotlist{$buffer->{"pointer"}} + and exists $buffers_timer{$buffer->{"pointer"}} + and $detach_time > 0 + and $weechat_version >= 0x00030800 + and $current_time - $buffers_timer{$buffer->{"pointer"}} >= $detach_time + and $buffer->{"num_displayed"} eq 1 # check for option detach_displayed_buffers and if buffer is displayed in a split window + and weechat::config_boolean($options{"detach_displayed_buffers"}) eq 0 ) + { + my $infolist_window = weechat::infolist_get("window", "", ""); + while (weechat::infolist_next($infolist_window)) { - my $infolist_window = weechat::infolist_get("window", "", ""); - while (weechat::infolist_next($infolist_window)) + my $buffer_ptr = weechat::infolist_pointer($infolist_window, "buffer"); + if ($buffer_ptr eq $buffer->{"pointer"}) { - my $buffer_ptr = weechat::infolist_pointer($infolist_window, "buffer"); - if ($buffer_ptr eq $buffer->{"pointer"}) - { - $buffer->{"window"} = weechat::infolist_integer($infolist_window, "number"); - } + $buffer->{"window"} = weechat::infolist_integer($infolist_window, "number"); } - weechat::infolist_free($infolist_window); - - push(@current2, $buffer); } + weechat::infolist_free($infolist_window); + push(@current2, $buffer); } } - else # buffer in "immune_detach_buffers" - { - if ($active_seen) - { - push(@current2, $buffer); - } - else - { - push(@current1, $buffer); - } - } - } # while end + } # end of while - if ($max_number >= 1) - { - $max_number_digits = length(int($max_number)); - } + $max_number_digits = length($max_number); @buffers = (@buffers, @current2, @current1); weechat::infolist_free($infolist); # sort buffers by number, name or shortname my %sorted_buffers; - if (1) + + my $number = 0; + for my $buffer (@buffers) { - my $number = 0; - for my $buffer (@buffers) - { - my $key; - if (weechat::config_integer( $options{"sort"} ) eq 1) # number = 0; name = 1 - { - my $name = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_custom_name"); - if (not defined $name or $name eq "") { - if (weechat::config_boolean( $options{"short_names"} ) eq 1) { - $name = $buffer->{"short_name"}; - } else { - $name = $buffer->{"name"}; - } - } - if (weechat::config_integer($options{"name_size_max"}) >= 1) - { - $maxlength = weechat::config_integer($options{"name_size_max"}); - if($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buffer->{"nicks_count"} == 0) - { - $maxlength -= 2; - } - $name = encode("UTF-8", substr(decode("UTF-8", $name), 0, $maxlength)); - } - if ( weechat::config_boolean($options{"core_to_front"}) eq 1) - { - if ( (weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "channel" ) and ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "private") ) - { - my $type = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type"); - if ( $type eq "" and $name ne "weechat") - { - $name = " " . $name - }else - { - $name = " " . $name; - } - } - } - $key = sprintf("%s%08d", lc($name), $buffer->{"number"}); - } - else - { - $key = sprintf("%08d", $number); - } - $sorted_buffers{$key} = $buffer; - $number++; - } + $sorted_buffers{key_of_buffer($buffer, $number)} = $buffer; + $number++; } # build string with buffers @@ -1185,274 +1438,84 @@ sub build_buffers { my $buffer = $sorted_buffers{$key}; - if ( weechat::config_string($options{"hide_merged_buffers"}) eq "server" ) - { - # buffer type "server" or merged with core? - if ( ($buffer->{"type"} eq "server" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) ) - { - next; - } - } - if ( weechat::config_string($options{"hide_merged_buffers"}) eq "channel" ) - { - # buffer type "channel" or merged with core? - if ( ($buffer->{"type"} eq "channel" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) ) - { - next; - } - } - if ( weechat::config_string($options{"hide_merged_buffers"}) eq "private" ) + if (skip_buffer($buffer)) { - # buffer type "private" or merged with core? - if ( ($buffer->{"type"} eq "private" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) ) - { - next; - } - } - if ( weechat::config_string($options{"hide_merged_buffers"}) eq "keepserver" ) - { - if ( ($buffer->{"type"} ne "server" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) ) - { - next; - } - } - if ( weechat::config_string($options{"hide_merged_buffers"}) eq "all" ) - { - if ( ! $buffer->{"active"} ) - { - next; - } + next; } push(@buffers_focus, $buffer); # buffer > buffers_focus, for mouse support - my $color = ""; - my $bg = ""; - $color = weechat::config_color( $options{"color_default_fg"} ); - $bg = weechat::config_color( $options{"color_default_bg"} ); + my ($fg, $bg) = get_colors($buffer, %hotlist); + my $color_bg = weechat::color(",$bg"); - if ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "private" ) - { - if ( (weechat::config_color($options{"queries_default_bg"})) ne "default" || (weechat::config_color($options{"queries_default_fg"})) ne "default" ) - { - $bg = weechat::config_color( $options{"queries_default_bg"} ); - $color = weechat::config_color( $options{"queries_default_fg"} ); - } - } - # check for core and buffer with free content - if ( (weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "channel" ) and ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "private") ) + if ( weechat::config_string($options{"show_prefix_bufname"}) ne "" ) { - $color = weechat::config_color( $options{"color_none_channel_fg"} ); - $bg = weechat::config_color( $options{"color_none_channel_bg"} ); - } - # default whitelist buffer? - if (grep {$_ eq $buffer->{"name"}} @whitelist_buffers) - { - $color = weechat::config_color( $options{"color_whitelist_default_fg"} ); - $bg = weechat::config_color( $options{"color_whitelist_default_bg"} ); - } - - $color = "default" if ($color eq ""); - - # color for channel and query buffer - if (exists $hotlist{$buffer->{"pointer"}}) - { - delete $buffers_timer{$buffer->{"pointer"}}; - # check if buffer is in whitelist buffer - if (grep {$_ eq $buffer->{"name"}} @whitelist_buffers) - { - $bg = weechat::config_color( $options{"color_whitelist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} ); - $color = weechat::config_color( $options{"color_whitelist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} ); - } - elsif ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "private" ) - { - # queries_default_fg/bg and buffers.color.queries_message_fg/bg - if ( (weechat::config_color($options{"queries_highlight_fg"})) ne "default" || - (weechat::config_color($options{"queries_highlight_bg"})) ne "default" || - (weechat::config_color($options{"queries_message_fg"})) ne "default" || - (weechat::config_color($options{"queries_message_bg"})) ne "default" ) - { - if ( ($hotlist{$buffer->{"pointer"}}) == 2 ) - { - $bg = weechat::config_color( $options{"queries_message_bg"} ); - $color = weechat::config_color( $options{"queries_message_fg"} ); - } - - elsif ( ($hotlist{$buffer->{"pointer"}}) == 3 ) - { - $bg = weechat::config_color( $options{"queries_highlight_bg"} ); - $color = weechat::config_color( $options{"queries_highlight_fg"} ); - } - }else - { - $bg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} ); - $color = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} ); - } - }else - { - $bg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} ); - $color = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} ); - } - } - - if ($buffer->{"current_buffer"}) - { - $color = weechat::config_color( $options{"color_current_fg"} ); - $bg = weechat::config_color( $options{"color_current_bg"} ); - } - my $color_bg = ""; - $color_bg = weechat::color(",".$bg) if ($bg ne ""); - - # create channel number for output - if ( weechat::config_string( $options{"show_prefix_bufname"} ) ne "" ) - { - $str .= $color_bg . - weechat::color( weechat::config_color( $options{"color_prefix_bufname"} ) ). + $str .= $color_bg. + weechat::color( weechat::config_color($options{"color_prefix_bufname"}) ). weechat::config_string( $options{"show_prefix_bufname"} ). weechat::color("default"); } - if ( weechat::config_boolean( $options{"show_number"} ) eq 1 ) # on + if ( weechat::config_boolean($options{"show_number"}) eq 1 ) # on { if (( weechat::config_boolean( $options{"indenting_number"} ) eq 1) && (($position eq "left") || ($position eq "right"))) { - $str .= weechat::color("default").$color_bg - .(" " x ($max_number_digits - length(int($buffer->{"number"})))); + $str .= weechat::color("default") + . $color_bg + . " " x ($max_number_digits - length($buffer->{"number"})); } + if ($old_number ne $buffer->{"number"}) { - $str .= weechat::color( weechat::config_color( $options{"color_number"} ) ) - .$color_bg - .$buffer->{"number"} - .weechat::color("default") - .$color_bg - .weechat::color( weechat::config_color( $options{"color_number_char"} ) ) - .weechat::config_string( $options{"show_number_char"} ) - .$color_bg; + $str .= weechat::color( weechat::config_color($options{"color_number"}) ) + .$color_bg + .$buffer->{"number"} + .weechat::color("default") + .$color_bg + .weechat::color( weechat::config_color($options{"color_number_char"}) ) + .weechat::config_string( $options{"show_number_char"} ) + .$color_bg; } else { # Indentation aligns channels in a visually appealing way # when viewing list top-to-bottom... - my $indent = (" " x length($buffer->{"number"}))." "; + my $indent = " " x length($buffer->{"number"}) . " "; # ...except when list is top/bottom and channels left-to-right. - my $option_pos = weechat::config_string( weechat::config_get( "weechat.bar.buffers.position" ) ); - if (($option_pos eq 'top') || ($option_pos eq 'bottom')) { - my $option_filling = weechat::config_string( weechat::config_get( "weechat.bar.buffers.filling_top_bottom" ) ); - if ($option_filling =~ /horizontal/) { - $indent = ''; - } + my $option_pos = weechat::config_string( weechat::config_get("weechat.bar.buffers.position") ); + if ( ($option_pos eq 'top') || ($option_pos eq 'bottom') + and weechat::config_string( weechat::config_get("weechat.bar.buffers.filling_top_bottom") ) =~ /horizontal/ ) + { + $indent = ''; } - $str .= weechat::color("default") - .$color_bg - .$indent; + $str .= weechat::color("default") . $color_bg . $indent; } } - if (( weechat::config_integer( $options{"indenting"} ) ne 0 ) # indenting NOT off - && (($position eq "left") || ($position eq "right"))) + if ( ( weechat::config_integer( $options{"indenting"} ) ne 0 ) # indenting NOT off + && (($position eq "left") || ($position eq "right")) ) { my $type = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type"); if (($type eq "channel") || ($type eq "private")) { - if ( weechat::config_integer( $options{"indenting"} ) eq 1 ) + if ( weechat::config_integer($options{"indenting"}) eq 2 # under_name + and weechat::config_integer($options{"indenting_number"}) eq 0 + and weechat::config_boolean($options{"show_number"}) ne 0 ) { - $str .= (" " x weechat::config_integer( $options{"indenting_amount"} ) ); - } - elsif ( (weechat::config_integer($options{"indenting"}) eq 2) and (weechat::config_integer($options{"indenting_number"}) eq 0) ) #under_name - { - if ( weechat::config_boolean( $options{"show_number"} ) eq 0 ) - { - $str .= (" " x weechat::config_integer( $options{"indenting_amount"} ) ); - } - else - { - $str .= ( (" " x ( $max_number_digits - length($buffer->{"number"}) )).(" " x weechat::config_integer( $options{"indenting_amount"} ) ) ); - } + $str .= " " x ( $max_number_digits - length($buffer->{"number"}) ); } + $str .= " " x weechat::config_integer($options{"indenting_amount"}); } } - $str .= weechat::config_string( $options{"show_prefix_query"}) if (weechat::config_string( $options{"show_prefix_query"} ) ne "" and $buffer->{"type"} eq "private"); - - if (weechat::config_boolean( $options{"show_prefix"} ) eq 1) - { - my $nickname = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_nick"); - if ($nickname ne "") - { - # with version >= 0.3.2, this infolist will return only nick - # with older versions, whole nicklist is returned for buffer, and this can be very slow - my $infolist_nick = weechat::infolist_get("nicklist", $buffer->{"pointer"}, "nick_".$nickname); - if ($infolist_nick ne "") - { - while (weechat::infolist_next($infolist_nick)) - { - if ((weechat::infolist_string($infolist_nick, "type") eq "nick") - && (weechat::infolist_string($infolist_nick, "name") eq $nickname)) - { - my $prefix = weechat::infolist_string($infolist_nick, "prefix"); - if (($prefix ne " ") or (weechat::config_boolean( $options{"show_prefix_empty"} ) eq 1)) - { - # with version >= 0.3.5, it is now a color name (for older versions: option name with color) - if (int($weechat_version) >= 0x00030500) - { - $str .= weechat::color(weechat::infolist_string($infolist_nick, "prefix_color")); - } - else - { - $str .= weechat::color(weechat::config_color( - weechat::config_get( - weechat::infolist_string($infolist_nick, "prefix_color")))); - } - $str .= $prefix; - } - last; - } - } - weechat::infolist_free($infolist_nick); - } - } - } - if ($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buffer->{"nicks_count"} == 0) - { - $str .= "("; - } - - $str .= weechat::color($color) . weechat::color(",".$bg); - - my $name = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_custom_name"); - if (not defined $name or $name eq "") - { - if (weechat::config_boolean( $options{"short_names"} ) eq 1) { - $name = $buffer->{"short_name"}; - } else { - $name = $buffer->{"name"}; - } - } - - if (weechat::config_integer($options{"name_size_max"}) >= 1) # check max_size of buffer name - { - $name = decode("UTF-8", $name); - - $maxlength = weechat::config_integer($options{"name_size_max"}); - if($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buffer->{"nicks_count"} == 0) - { - $maxlength -= 2; - } - - $str .= encode("UTF-8", substr($name, 0, $maxlength)); - $str .= weechat::color(weechat::config_color( $options{"color_number_char"})).weechat::config_string($options{"name_crop_suffix"}) if (length($name) > weechat::config_integer($options{"name_size_max"})); - $str .= add_inactive_parentless($buffer->{"type"}, $buffer->{"nicks_count"}); - $str .= add_hotlist_count($buffer->{"pointer"}, %hotlist); - } - else - { - $str .= $name; - $str .= add_inactive_parentless($buffer->{"type"}, $buffer->{"nicks_count"}); - $str .= add_hotlist_count($buffer->{"pointer"}, %hotlist); - } + $str .= weechat::config_string($options{"show_prefix_query"}) if (weechat::config_string($options{"show_prefix_query"}) ne "" and $buffer->{"type"} eq "private"); + $str .= nick_prefix($buffer) if (weechat::config_boolean($options{"show_prefix"}) eq 1); + $str .= format_name($buffer, $fg, $bg); + $str .= hotlist_counts($buffer->{"pointer"}, %hotlist) + if ( weechat::config_boolean($options{"hotlist_counter"}) eq 1 and $weechat_version >= 0x00030500 and defined $hotlist{$buffer->{"pointer"}}); + # lag if ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "server" and weechat::config_boolean($options{"show_lag"}) eq 1) { my $color_lag = weechat::config_color(weechat::config_get("irc.color.item_lag_finished")); @@ -1467,26 +1530,32 @@ sub build_buffers $str .= weechat::color("default") . " (" . weechat::color($color_lag) . $lag . weechat::color("default") . ")"; } } + + # window number if (weechat::config_boolean($options{"detach_displayed_buffers"}) eq 0 - and weechat::config_boolean($options{"detach_display_window_number"}) eq 1) + and weechat::config_boolean($options{"detach_display_window_number"}) eq 1 + and $buffer->{"window"}) { - if ($buffer->{"window"}) - { - $str .= weechat::color("default") . " (" . weechat::color(weechat::config_color( $options{"color_number"})) . $buffer->{"window"} . weechat::color("default") . ")"; - } + $str .= weechat::color("default"). + " (". + weechat::color( weechat::config_color($options{"color_number"}) ). + $buffer->{"window"}. + weechat::color("default"). + ")"; } $str .= weechat::color("default"); - if ( weechat::config_string( $options{"show_suffix_bufname"} ) ne "" ) + # suffix + if ( weechat::config_string($options{"show_suffix_bufname"}) ne "" ) { - $str .= weechat::color( weechat::config_color( $options{"color_suffix_bufname"} ) ). + $str .= weechat::color( weechat::config_color($options{"color_suffix_bufname"}) ). weechat::config_string( $options{"show_suffix_bufname"} ). weechat::color("default"); } $str .= "\n"; $old_number = $buffer->{"number"}; - } + } # end of foreach # remove spaces and/or linefeed at the end $str =~ s/\s+$//; @@ -1494,96 +1563,7 @@ sub build_buffers return $str; } -sub add_inactive_parentless -{ -my ($buf_type, $buf_nicks_count) = @_; -my $str = ""; - if ($buf_type eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buf_nicks_count == 0) - { - $str .= weechat::color(weechat::config_color( $options{"color_number_char"})); - $str .= ")"; - } -return $str; -} - -sub add_hotlist_count -{ -my ($bufpointer, %hotlist) = @_; - -return "" if ( weechat::config_boolean( $options{"hotlist_counter"} ) eq 0 or ($weechat_version < 0x00030500)); # off -my $col_number_char = weechat::color(weechat::config_color( $options{"color_number_char"}) ); -my $str = " ".$col_number_char."("; - -# 0 = low level -if (defined $hotlist{$bufpointer."_count_00"}) -{ - my $bg = weechat::config_color( $options{"color_hotlist_low_bg"} ); - my $color = weechat::config_color( $options{"color_hotlist_low_fg"} ); - $str .= weechat::color($bg). - weechat::color($color). - $hotlist{$bufpointer."_count_00"} if ($hotlist{$bufpointer."_count_00"} ne "0"); -} - -# 1 = message -if (defined $hotlist{$bufpointer."_count_01"}) -{ - my $bg = weechat::config_color( $options{"color_hotlist_message_bg"} ); - my $color = weechat::config_color( $options{"color_hotlist_message_fg"} ); - if ($str =~ /[0-9]$/) - { - $str .= ",". - weechat::color($bg). - weechat::color($color). - $hotlist{$bufpointer."_count_01"} if ($hotlist{$bufpointer."_count_01"} ne "0"); - }else - { - $str .= weechat::color($bg). - weechat::color($color). - $hotlist{$bufpointer."_count_01"} if ($hotlist{$bufpointer."_count_01"} ne "0"); - } -} -# 2 = private -if (defined $hotlist{$bufpointer."_count_02"}) -{ - my $bg = weechat::config_color( $options{"color_hotlist_private_bg"} ); - my $color = weechat::config_color( $options{"color_hotlist_private_fg"} ); - if ($str =~ /[0-9]$/) - { - $str .= ",". - weechat::color($bg). - weechat::color($color). - $hotlist{$bufpointer."_count_02"} if ($hotlist{$bufpointer."_count_02"} ne "0"); - }else - { - $str .= weechat::color($bg). - weechat::color($color). - $hotlist{$bufpointer."_count_02"} if ($hotlist{$bufpointer."_count_02"} ne "0"); - } -} -# 3 = highlight -if (defined $hotlist{$bufpointer."_count_03"}) -{ - my $bg = weechat::config_color( $options{"color_hotlist_highlight_bg"} ); - my $color = weechat::config_color( $options{"color_hotlist_highlight_fg"} ); - if ($str =~ /[0-9]$/) - { - $str .= ",". - weechat::color($bg). - weechat::color($color). - $hotlist{$bufpointer."_count_03"} if ($hotlist{$bufpointer."_count_03"} ne "0"); - }else - { - $str .= weechat::color($bg). - weechat::color($color). - $hotlist{$bufpointer."_count_03"} if ($hotlist{$bufpointer."_count_03"} ne "0"); - } -} -$str .= $col_number_char. ")"; - -$str = "" if (weechat::string_remove_color($str, "") eq " ()"); # remove color and check for buffer with no messages -return $str; -} - +# react to numerous signals sub buffers_signal_buffer { my ($data, $signal, $signal_data) = @_; @@ -1604,12 +1584,12 @@ sub buffers_signal_buffer delete $buffers_timer{$pointer}; } } - if ($signal eq "buffer_opened") + elsif ($signal eq "buffer_opened") { my $current_time = time(); $buffers_timer{$signal_data} = $current_time; } - if ($signal eq "buffer_closing") + elsif ($signal eq "buffer_closing") { delete $buffers_timer{$signal_data}; } @@ -1627,24 +1607,21 @@ sub buffers_signal_hotlist sub buffers_signal_config_whitelist { - @whitelist_buffers = (); - @whitelist_buffers = split( /,/, weechat::config_string( $options{"look_whitelist_buffers"} ) ); + @whitelist_buffers = split( /,/, weechat::config_string($options{"look_whitelist_buffers"}) ); weechat::bar_item_update($SCRIPT_NAME); return weechat::WEECHAT_RC_OK; } sub buffers_signal_config_immune_detach_buffers { - @immune_detach_buffers = (); - @immune_detach_buffers = split( /,/, weechat::config_string( $options{"immune_detach_buffers"} ) ); + @immune_detach_buffers = split( /,/, weechat::config_string($options{"immune_detach_buffers"}) ); weechat::bar_item_update($SCRIPT_NAME); return weechat::WEECHAT_RC_OK; } sub buffers_signal_config_detach_buffer_immediately { - @detach_buffer_immediately = (); - @detach_buffer_immediately = split( /,/, weechat::config_string( $options{"detach_buffer_immediately"} ) ); + @detach_buffer_immediately = split( /,/, weechat::config_string($options{"detach_buffer_immediately"}) ); weechat::bar_item_update($SCRIPT_NAME); return weechat::WEECHAT_RC_OK; } @@ -1761,35 +1738,36 @@ sub buffers_hsignal_mouse sub move_buffer { - my %hash = @_; - my $number2 = $hash{"number2"}; - if ($number2 eq "?") - { - # if number 2 is not known (end of gesture outside buffers list), then set it - # according to mouse gesture - $number2 = "1"; - if (($hash{"_key"} =~ /gesture-right/) || ($hash{"_key"} =~ /gesture-down/)) - { - $number2 = "999999"; - if ($weechat_version >= 0x00030600) - { - my $hdata_buffer = weechat::hdata_get("buffer"); - my $last_gui_buffer = weechat::hdata_get_list($hdata_buffer, "last_gui_buffer"); - if ($last_gui_buffer) - { - $number2 = weechat::hdata_integer($hdata_buffer, $last_gui_buffer, "number") + 1; - } - } - } - } - my $ptrbuf = weechat::current_buffer(); - weechat::command($hash{"pointer"}, "/buffer move ".$number2); + my %hash = @_; + my $number2 = $hash{"number2"}; + if ($number2 eq "?") + { + # if number 2 is not known (end of gesture outside buffers list), then set it + # according to mouse gesture + $number2 = "1"; + if (($hash{"_key"} =~ /gesture-right/) || ($hash{"_key"} =~ /gesture-down/)) + { + $number2 = "999999"; + if ($weechat_version >= 0x00030600) + { + my $hdata_buffer = weechat::hdata_get("buffer"); + my $last_gui_buffer = weechat::hdata_get_list($hdata_buffer, "last_gui_buffer"); + if ($last_gui_buffer) + { + $number2 = weechat::hdata_integer($hdata_buffer, $last_gui_buffer, "number") + 1; + } + } + } + } + my $ptrbuf = weechat::current_buffer(); + weechat::command($hash{"pointer"}, "/buffer move ".$number2); } sub check_immune_detached_buffers { my ($buffername) = @_; - foreach ( @immune_detach_buffers ){ + foreach ( @immune_detach_buffers ) + { my $immune_buffer = weechat::string_mask_to_regex($_); if ($buffername =~ /^$immune_buffer$/i) { @@ -1802,7 +1780,8 @@ sub check_immune_detached_buffers sub check_detach_buffer_immediately { my ($buffername) = @_; - foreach ( @detach_buffer_immediately ){ + foreach ( @detach_buffer_immediately ) + { my $detach_buffer = weechat::string_mask_to_regex($_); if ($buffername =~ /^$detach_buffer$/i) { From 9efd22318c691f54cef8bb5b67e4d0b248cd0a44 Mon Sep 17 00:00:00 2001 From: Simmo Saan Date: Sat, 25 Feb 2017 10:18:51 +0200 Subject: [PATCH 004/642] go.py 2.3: fix fuzzy search breaking buffer number search display (closes #191) An extra fuzzy match check was added to displaying the search results to avoid buffers matched by number from being rendered using fuzzy search display methods which fail in this case due to the number not actually being present in the buffer name. --- python/go.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/go.py b/python/go.py index 509cfb8b..c2fdf9b2 100644 --- a/python/go.py +++ b/python/go.py @@ -21,6 +21,8 @@ # # History: # +# 2017-02-25, Simmo Saan +# version 2.3: fix fuzzy search breaking buffer number search display # 2016-01-28, ylambda # version 2.2: add option fuzzy_search # 2015-11-12, nils_2 @@ -86,7 +88,7 @@ SCRIPT_NAME = 'go' SCRIPT_AUTHOR = 'Sébastien Helleu ' -SCRIPT_VERSION = '2.2' +SCRIPT_VERSION = '2.3' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Quick jump to buffers' @@ -406,7 +408,8 @@ def go_buffers_to_string(listbuf, pos, strinput): weechat.color(weechat.config_get_plugin( 'color_name' + selected)), buffer_name[index2:]) - elif go_option_enabled("fuzzy_search"): + elif go_option_enabled("fuzzy_search") and + go_match_fuzzy(buffer_name.lower(), strinput): name = "" prev_index = -1 for char in strinput.lower(): From 64726eb3433400e1dcf9b6f197f6588827dd3260 Mon Sep 17 00:00:00 2001 From: arza Date: Thu, 2 Mar 2017 07:27:55 +0100 Subject: [PATCH 005/642] colorize_nicks.py 23: don't colorize nicklist group names --- python/colorize_nicks.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/colorize_nicks.py b/python/colorize_nicks.py index 506a3abd..1460f013 100644 --- a/python/colorize_nicks.py +++ b/python/colorize_nicks.py @@ -21,6 +21,8 @@ # # # History: +# 2017-03-01, arza +# version 23: don't colorize nicklist group names # 2016-05-01, Simmo Saan # version 22: invalidate cached colors on hash algorithm change # 2015-07-28, xt @@ -77,7 +79,7 @@ SCRIPT_NAME = "colorize_nicks" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "22" +SCRIPT_VERSION = "23" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Use the weechat nick colors in the chat area" @@ -276,6 +278,9 @@ def populate_nicks(*args): if buffer_ptr not in colored_nicks: colored_nicks[buffer_ptr] = {} + if w.infolist_string(nicklist, 'type') != 'nick': + continue + nick = w.infolist_string(nicklist, 'name') nick_color = colorize_nick_color(nick, my_nick) From 7dc22966be0c871c6bedd628901fc7309163e1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Thu, 2 Mar 2017 09:38:54 +0100 Subject: [PATCH 006/642] go.py 2.4: fix syntax and indentation error --- python/go.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/go.py b/python/go.py index c2fdf9b2..3d1828b6 100644 --- a/python/go.py +++ b/python/go.py @@ -21,6 +21,8 @@ # # History: # +# 2017-03-02, Sébastien Helleu : +# version 2.4: fix syntax and indentation error # 2017-02-25, Simmo Saan # version 2.3: fix fuzzy search breaking buffer number search display # 2016-01-28, ylambda @@ -88,7 +90,7 @@ SCRIPT_NAME = 'go' SCRIPT_AUTHOR = 'Sébastien Helleu ' -SCRIPT_VERSION = '2.3' +SCRIPT_VERSION = '2.4' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Quick jump to buffers' @@ -408,8 +410,8 @@ def go_buffers_to_string(listbuf, pos, strinput): weechat.color(weechat.config_get_plugin( 'color_name' + selected)), buffer_name[index2:]) - elif go_option_enabled("fuzzy_search") and - go_match_fuzzy(buffer_name.lower(), strinput): + elif go_option_enabled("fuzzy_search") and \ + go_match_fuzzy(buffer_name.lower(), strinput): name = "" prev_index = -1 for char in strinput.lower(): From 41c7bd915d9534279d4ecb419ea0ea1f37327d12 Mon Sep 17 00:00:00 2001 From: stfn Date: Fri, 10 Mar 2017 14:33:01 +0100 Subject: [PATCH 007/642] pushover.pl 2.0: fix pushover.net API call Also dropped support for the other notification services in favor of the new hsignal "pushover". This can be used with any /trigger (for example combined with /exec) or from other weechat scripts. That way it's very easy to use different different notification services without having to reimplement the entire "when to notify" logic and without bloating this script. See /help pushover for trigger examples. --- perl/pushover.pl | 135 +++++++++++++---------------------------------- 1 file changed, 37 insertions(+), 98 deletions(-) diff --git a/perl/pushover.pl b/perl/pushover.pl index 797d224a..aaa6904c 100644 --- a/perl/pushover.pl +++ b/perl/pushover.pl @@ -1,5 +1,5 @@ # -# Copyright (C) 2013-2015 stfn +# Copyright (C) 2013-2017 stfn # https://github.com/stfnm/weechat-scripts # # This program is free software: you can redistribute it and/or modify @@ -22,19 +22,15 @@ my %SCRIPT = ( name => 'pushover', author => 'stfn ', - version => '1.4', + version => '2.0', license => 'GPL3', - desc => 'Send push notifications to your mobile devices using Pushover, NMA, Pushbullet or Free Mobile', + desc => 'Send push notifications to your mobile devices using Pushover', opt => 'plugins.var.perl', ); my %OPTIONS_DEFAULT = ( 'enabled' => ['on', "Turn script on or off"], - 'service' => ['pushover', 'Notification service to use. Multiple services may be supplied as comma separated list. (supported services: pushover, nma, pushbullet)'], 'token' => ['ajEX9RWhxs6NgeXFJxSK2jmpY54C9S', 'pushover API token/key (You may feel free to use your own token, so you get your own monthly quota of messages without being affected by other users. See also: https://pushover.net/faq#overview-distribution )'], 'user' => ['', "pushover user key"], - 'nma_apikey' => ['', "nma API key"], - 'pb_apikey' => ['', "Pushbullet API key"], - 'pb_device_iden' => ['', "Device Iden of pushbullet device"], 'sound' => ['', "Sound (empty for default)"], 'priority' => ['', "priority (empty for default)"], 'show_highlights' => ['on', 'Notify on highlights'], @@ -46,8 +42,6 @@ 'verbose' => ['1', 'Verbosity level (0 = silently ignore any errors, 1 = display brief error, 2 = display full server response)'], 'rate_limit' => ['0', 'Rate limit in seconds (0 = unlimited), will send a maximum of 1 notification per time limit'], 'short_name' => ['off', 'Use short buffer name in notification'], - 'free_user' => ['', 'Free Mobile User ID (see your account)'], - 'free_pass' => ['', 'Automatic generated Free key'], ); my %OPTIONS = (); my $TIMEOUT = 30 * 1000; @@ -65,10 +59,20 @@ # Setup hooks weechat::hook_print("", "notify_message,notify_private,notify_highlight", "", 1, "print_cb", ""); -weechat::hook_command($SCRIPT{"name"}, "send custom push notification", +weechat::hook_command($SCRIPT{"name"}, "send notification", "", - "text: notification text to send", - "", "pushover_cb", ""); + "text: notification text to send\n" . + "\n" . + "Don't forget to configure your own Pushover user and token.\n" . + "\n" . + "You can also setup custom notifications (for any other services etc.) by using a /trigger on the hsignal \"pushover\".\n\nExamples:\n" . + "\n" . + "pushover.net using curl:\n" . + "/trigger add mynotify hsignal pushover \"\" \"\" \"/exec -bg curl -s --form-string 'token=abc123' --form-string 'user=user123' --form-string 'message=\${message_stripped}' https://api.pushover.net/1/messages.json\"\n" . + "\n" . + "free-mobile.fr using curl:\n" . + "/trigger add mynotify hsignal pushover \"\" \"\" \"/exec -bg curl -s 'https://smsapi.free-mobile.fr/sendmsg?user=USER&pass=TOKEN&msg=\${message_escaped}'\"\n", + "", "pushover_cmd_cb", ""); # # Handle config stuff @@ -200,7 +204,7 @@ sub print_cb # # /pushover # -sub pushover_cb +sub pushover_cmd_cb { my ($data, $buffer, $args) = @_; @@ -229,20 +233,19 @@ sub url_cb $msg .= "@_"; } - # Check server response and display error message if NOT successful - if ($command =~ /pushover/ && $return_code == 0 && !($out =~ /\"status\":1/)) { - weechat::print("", $msg); - } elsif ($command =~ /notifymyandroid/ && $return_code == 0 && !($out =~ /success code=\"200\"/)) { - weechat::print("", $msg); - } elsif ($command =~ /pushbullet/ && $return_code == 0 && !($out =~ /\"iden\"/)) { - weechat::print("", $msg); + # Check if server response reported success + if ($command =~ /pushover/ && $return_code == 0 && $out =~ /\"status\":1/) { + return weechat::WEECHAT_RC_OK; } + # Otherwise display error message + weechat::print("", $msg); + return weechat::WEECHAT_RC_OK; } # -# Notify wrapper (decides which service to use) +# Notify wrapper # sub notify($) { @@ -260,19 +263,13 @@ ($) weechat::hook_timer($timer, 0, 1, "rate_limit_cb", ""); } - # Notify services - if (grep_list("pushover", $OPTIONS{service})) { + # Notify service + if ($OPTIONS{token} ne "" && $OPTIONS{user} ne "") { notify_pushover(eval_expr($OPTIONS{token}), eval_expr($OPTIONS{user}), $message, "weechat", $OPTIONS{priority}, $OPTIONS{sound}); } - if (grep_list("nma", $OPTIONS{service})) { - notify_nma(eval_expr($OPTIONS{nma_apikey}), "weechat", "$SCRIPT{name}.pl", $message, $OPTIONS{priority}); - } - if (grep_list("pushbullet", $OPTIONS{service})) { - notify_pushbullet(eval_expr($OPTIONS{pb_apikey}), eval_expr($OPTIONS{pb_device_iden}), "weechat", $message); - } - if (grep_list("freemobile", $OPTIONS{service})) { - notify_freemobile(eval_expr($OPTIONS{free_pass}), eval_expr($OPTIONS{free_user}), $message); - } + + # Send hsignal (so triggers can pick up on it) + notify_hsignal($message); } # @@ -295,7 +292,7 @@ ($$$$$$) push(@post, "sound=" . url_escape($sound)) if ($sound && length($sound) > 0); # Send HTTP POST - my $hash = { "post" => 1, "postfields" => join(";", @post) }; + my $hash = { "post" => 1, "postfields" => join("&", @post) }; if ($DEBUG) { weechat::print("", "[$SCRIPT{name}] Debug: msg -> `$message' HTTP POST -> @post"); } else { @@ -306,72 +303,14 @@ ($$$$$$) } # -# https://www.notifymyandroid.com/api.jsp -# -sub notify_nma($$$$$) -{ - my ($apikey, $application, $event, $description, $priority) = @_; - - # Required API arguments - my @post = ( - "apikey=" . url_escape($apikey), - "application=" . url_escape($application), - "event=" . url_escape($event), - "description=" . url_escape($description), - ); - - # Optional API arguments - push(@post, "priority=" . url_escape($priority)) if ($priority && length($priority) > 0); - - # Send HTTP POST - my $hash = { "post" => 1, "postfields" => join("&", @post) }; - if ($DEBUG) { - weechat::print("", "[$SCRIPT{name}] Debug: msg -> `$description' HTTP POST -> @post"); - } else { - weechat::hook_process_hashtable("url:https://www.notifymyandroid.com/publicapi/notify", $hash, $TIMEOUT, "url_cb", ""); - } - - return weechat::WEECHAT_RC_OK; -} - -# -# https://docs.pushbullet.com/v2/pushes/ +# hsignal to use with /trigger # -sub notify_pushbullet($$$$) +sub notify_hsignal($) { - my ($apikey, $device_iden, $title, $body) = @_; - - # Required API arguments - my $apiurl = "https://$apikey\@api.pushbullet.com/v2/pushes"; - my @post = ( - "type=note", - ); - - # Optional API arguments - push(@post, "device_iden=" . url_escape($device_iden)) if ($device_iden && length($device_iden) > 0); - push(@post, "title=" . url_escape($title)) if ($title && length($title) > 0); - push(@post, "body=" . url_escape($body)) if ($body && length($body) > 0); - - # Send HTTP POST - my $hash = { "post" => 1, "postfields" => join("&", @post) }; - if ($DEBUG) { - weechat::print("", "$apiurl [$SCRIPT{name}] Debug: msg -> `$body' HTTP POST -> @post"); - } else { - weechat::hook_process_hashtable("url:$apiurl", $hash, $TIMEOUT, "url_cb", ""); - } - - return weechat::WEECHAT_RC_OK; -} - -# -# Free Mobile -# -sub notify_freemobile($$$) -{ - my ($token, $user, $message) = @_; - - # Not very clean but it works - system "curl \"https://smsapi.free-mobile.fr/sendmsg?user=" . url_escape($user) . "&pass=" . url_escape($token) . "&msg=" . url_escape($message) . "\""; + my $message = $_[0]; + my $message_escaped = url_escape($message); + my $message_stripped = $message; + $message_stripped =~ s/'//gi; # strip apostrophe - return weechat::WEECHAT_RC_OK; + weechat::hook_hsignal_send($SCRIPT{name}, { "message" => $message, "message_escaped" => $message_escaped, "message_stripped" => $message_stripped }); } From 31a7bd5bb82de49815fe41babb0cb2ec345b7d0a Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Mon, 13 Mar 2017 02:29:33 +0000 Subject: [PATCH 008/642] weestreamer.py 0.4.2: Switch to streamlink --- python/weestreamer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/weestreamer.py b/python/weestreamer.py index b9882e7e..aa14860d 100644 --- a/python/weestreamer.py +++ b/python/weestreamer.py @@ -16,7 +16,7 @@ import weechat -weechat.register("weestreamer", "Miblo", "0.4.1", "GPL3", "Livestreamer companion for WeeChat", "", "") +weechat.register("weestreamer", "Miblo", "0.4.2", "GPL3", "Streamlink companion for WeeChat", "", "") def stream(data, buffer, args): bufserver = weechat.buffer_get_string(weechat.current_buffer(), "localvar_server") @@ -38,7 +38,7 @@ def stream(data, buffer, args): % (weechat.prefix("error"),(len(input)))) return weechat.WEECHAT_RC_ERROR - # NOTE(matt): http://docs.livestreamer.io/plugin_matrix.html + # NOTE(matt): https://streamlink.github.io/plugin_matrix.html servers = { "afreeca":"http://play.afreeca.com/%s" % (channel), "hitbox":"http://www.hitbox.tv/%s" % (channel), "twitch":"http://www.twitch.tv/%s" % (channel), @@ -56,7 +56,7 @@ def stream(data, buffer, args): weechat.prnt(weechat.current_buffer(), " %s" % key) return weechat.WEECHAT_RC_ERROR - command = "livestreamer %s %s" % (streamurl, quality) + command = "streamlink %s %s" % (streamurl, quality) weechat.prnt(weechat.current_buffer(), "%sLAUNCHING: %s" % (weechat.prefix("action"), command)) weechat.hook_process("%s" % (command), 0, "handle_output", "") @@ -71,7 +71,7 @@ def handle_output(data, command, rc, out, err): weechat.prnt(weechat.current_buffer(), process_output) return weechat.WEECHAT_RC_OK -weechat.hook_command("weestreamer", "Livestreamer companion for WeeChat", +weechat.hook_command("weestreamer", "Streamlink companion for WeeChat", "server channel", "Run /weestreamer without any arguments while in a channel on a supported irc\n" From 4a1375d97851f7a2f63054648015a3a960aeeaa9 Mon Sep 17 00:00:00 2001 From: arza Date: Fri, 17 Mar 2017 16:42:25 +0200 Subject: [PATCH 009/642] buffers.pl 5.6: fix truncating buffer names that contain multibyte characters --- perl/buffers.pl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/perl/buffers.pl b/perl/buffers.pl index 600ab40c..46e2460c 100644 --- a/perl/buffers.pl +++ b/perl/buffers.pl @@ -20,6 +20,8 @@ # # History: # +# 2017-03-17, arza : +# v5.6: fix truncating buffer names that contain multibyte characters # 2017-02-21, arza : # v5.5: fix memory leak in perl 5.23.7-5.24.1 # fix truncation and crop_suffix when truncating to 1-4 characters @@ -177,7 +179,7 @@ use Encode qw( decode encode ); # -----------------------------[ internal ]------------------------------------- my $SCRIPT_NAME = "buffers"; -my $SCRIPT_VERSION = "5.5"; +my $SCRIPT_VERSION = "5.6"; my $BUFFERS_CONFIG_FILE_NAME = "buffers"; my $buffers_config_file; @@ -1056,7 +1058,7 @@ sub format_name weechat::color($fg). weechat::color(",$bg"). truncate_end($name, $maxlength-2). - (length($name) > $maxlength-2 ? $crop_suffix : ""). + (length(Encode::decode_utf8($name)) > $maxlength-2 ? $crop_suffix : ""). weechat::color( weechat::config_color($options{"color_number_char"}) ). ")"; } @@ -1065,7 +1067,7 @@ sub format_name $output = weechat::color($fg). weechat::color(",$bg"). truncate_end($name, $maxlength). - (length($name) > $maxlength && $maxlength > 0 ? $crop_suffix : ""); + (length(Encode::decode_utf8($name)) > $maxlength && $maxlength > 0 ? $crop_suffix : ""); } return $output; From 6158bbed956cab86df0b00c51e6d6d5af59635f4 Mon Sep 17 00:00:00 2001 From: Grant Bacon Date: Sat, 18 Mar 2017 16:36:04 +0100 Subject: [PATCH 010/642] xfer_scp.py 1.0.5: add ability to send files that don't match a pattern to a default directory on remote server --- python/xfer_scp.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/python/xfer_scp.py b/python/xfer_scp.py index f93c9716..37121e26 100644 --- a/python/xfer_scp.py +++ b/python/xfer_scp.py @@ -7,7 +7,10 @@ * plugins.var.python.xfer_scp.remote_host * plugins.var.python.xfer_scp.remote_user * plugins.var.python.xfer_scp.remote_port + * plugins.var.python.xfer_scp.remote_default_dir * plugins.var.python.xfer_scp.local_identity_key + * plugins.var.python.xfer_scp.delete_after_send + * plugins.var.python.xfer_scp.send_only_matches Commands: * /scp add @@ -17,10 +20,10 @@ * /scp del Remove an existing rule -Version: 1.0.1 +Version: 1.0.5 Author: Grant Bacon License: GPL3 -Date: 05 May 2014 +Date: 17 Mar 2017 """ import_ok = True @@ -38,7 +41,7 @@ ##### SCRIPT_NAME = "xfer_scp" SCRIPT_AUTHOR = "Grant Bacon " -SCRIPT_VERSION = "1.0.1" +SCRIPT_VERSION = "1.0.5" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Send files via scp after xfer completes, optionally delete after" @@ -53,8 +56,8 @@ "remote_user" : "", "local_identity_key" : "", "remote_port" : "", -# "remote_default_dir" : "/mnt/abyss/weechat_xfer", # not yet implemented -# "only_send_matches" : "true", # not yet implemented + "remote_default_dir" : "", + "only_send_matches" : "true", "patternlist" : "" } @@ -64,11 +67,15 @@ def xfer_scp_process_cb(data, command, rc, out, err): weechat.prnt('', "xfer_scp: File " + data + " sent via SCP successfully") if configurations['delete_after_send'].lower() == "true": del_file(data) + + weechat.hook_signal_send("xfer_scp_success", weechat.WEECHAT_HOOK_SIGNAL_STRING, data.rsplit("/", 1).pop()) return weechat.WEECHAT_RC_OK elif rc > 0: # process terminated unsuccesfully weechat.prnt('', "xfer_scp: File " + data + " did not send successfully") + + weechat.hook_signal_send("xfer_scp_failure", weechat.WEECHAT_HOOK_SIGNAL_STRING, data.rsplit("/", 1).pop()) return weechat.WEECHAT_RC_ERROR else: @@ -175,7 +182,10 @@ def xfer_ended_signal_cb(data, signal, signal_data): if re.match(pattern, filename): scp_file(local_filename, patterns[pattern]) return weechat.WEECHAT_RC_OK - # check for a defualt dir and send there since for loop completed without returning + if configurations['only_send_matches'] == "false" and 'remote_default_dir' in configurations: + scp_file(local_filename, configurations['remote_default_dir']) + return weechat.WEECHAT_RC_OK + return weechat.WEECHAT_RC_OK From cdd552574840b8f57387d3f670703c92de5916cf Mon Sep 17 00:00:00 2001 From: Zephyr Quarto-Pellerin Date: Sun, 19 Mar 2017 00:38:36 +0100 Subject: [PATCH 011/642] gateway_rename.scm 1.2: let user configure gateway renamers via options, add command /gateway_rename, improve testing --- guile/gateway_rename.scm | 214 ++++++++++++++++++++++++++++++++++----- 1 file changed, 191 insertions(+), 23 deletions(-) diff --git a/guile/gateway_rename.scm b/guile/gateway_rename.scm index 03209a4d..c58d4a51 100644 --- a/guile/gateway_rename.scm +++ b/guile/gateway_rename.scm @@ -1,3 +1,24 @@ +;; -*- geiser-scheme-implementation: 'guile -*- +;; Copyright 2017 by Zephyr Pellerin +;; ------------------------------------------------------------ +;; 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 . + +;; History: +;; 1.2 - Use weechat plugin configuration data to match IRC gateways +;; 0.9 - Lookup correct servername in /VERSION +;; 0.8 - Barebones, contained list of translations + (use-modules ((srfi srfi-1) #:select (any))) (use-modules ((srfi srfi-26) @@ -6,17 +27,30 @@ (use-modules (ice-9 hash-table)) (use-modules (ice-9 match)) +(define *weechat/script-name* "gateway_rename") +(define *weechat/script-author* "zv ") +(define *weechat/script-version* "1.2") +(define *weechat/script-license* "GPL3") +(define *weechat/script-description* "Convert usernames of gateway connections their real names") + +;; A test-harness for checking if we are inside weechat +(define-syntax if-weechat + (syntax-rules () + ((_ conseq alt) (if (defined? 'weechat:register) conseq alt)) + ((_ conseq) (if (defined? 'weechat:register) conseq)))) + (if (defined? 'weechat:register) - (weechat:register "gateway-nickconverter" - "zv " - "1.1" - "GPL3" - "Convert usernames of gateway connections their real names" - "" - "")) + (weechat:register *weechat/script-name* + *weechat/script-author* + *weechat/script-version* + *weechat/script-license* + *weechat/script-description* + "" "")) ;; `user-prefix' is a distinguishing username prefix for 'fake' users (define *user-prefix* "^") +(define *gateway-config* "gateways") +(define *default-irc-gateways* "(freenode #radare r2tg )(freenode #test-channel zv-test )") (define (print . msgs) (if (defined? 'weechat:print) @@ -25,15 +59,7 @@ ;; A regular expression must have the gateway username in the first matchgroup, ;; the "real" username in the 3rd, and the real-username along with it's enclosing ;; brackets in the 2nd -(define *gateway-regexps* - (alist->hash-table - `(("freenode" . - (;; r2tg - ,(make-regexp ":(r2tg)!\\S* PRIVMSG #radare :(<(\\S*?)>) .*") - ;; slack-irc-bot - ,(make-regexp ":(slack-irc-bot(1\\|2)?)!\\S* PRIVMSG #\\S* :(<(\\S*?)>) .*") - ;; test - ,(make-regexp ":(zv-test)!\\S* PRIVMSG #test-channel :(<(\\S*?)>) .*")))))) +(define *gateway-regexps* (make-hash-table)) (define (process-network-infolist) "Convert the internal user-defined servername to the 'true' servername @@ -62,10 +88,13 @@ returned during /version" (process (weechat:infolist_next il))) -(define *hostname-table* (alist->hash-table (process-network-infolist))) +;; This is a table that maps a weechat network 'name' to it's IRC-style hostname +(define *hostname-table* (alist->hash-table '(("freenode" . "freenode")))) +(if-weechat + (set! *hostname-table* (alist->hash-table (process-network-infolist)))) (define (replace-privmsg msg gateways) - "A function to replace the privmsg sent by by a gateway " + "A function to replace the PRIVMSG sent by by a gateway " (let* ((match? (cut regexp-exec <> msg)) (result (any match? gateways))) (if result @@ -86,17 +115,156 @@ returned during /version" msg))) (define (server->gateways server) - (hash-ref *gateway-regexps* - (hash-ref *hostname-table* server))) + (hash-ref *gateway-regexps* (hash-ref *hostname-table* server))) (define (privmsg-modifier data modifier-type server msg) - ;; fetch the appropriate gateway by server + "The hook for all PRIVMSGs in Weechat" (let ((gateways (server->gateways server))) (if gateways (replace-privmsg msg gateways) msg))) -(if (defined? 'weechat:hook_modifier) - (weechat:hook_modifier "irc_in_privmsg" "privmsg-modifier" "")) +(define* (make-gateway-regexp gateway-nick channel mask #:optional emit-string) + "Build a regular expression that will match the nick, channel and \"\"-style mask" + (let* ([mask-regexp ;; replace with <(\\S*?)> + (regexp-substitute/global #f "NICK" mask 'pre "(\\S*?)" 'post "")] + [composed-str (format #f ":(~a)!\\S* PRIVMSG ~a :(~a)" gateway-nick channel mask-regexp)]) + (if emit-string composed-str (make-regexp composed-str)))) + +(define (extract-gateway-fields str) + "This is a hack around Guile's non-greedy matchers. + + # Example + scheme@(guile-user)> (extract-gateway-fields \"(freenode #radare r2tg )\") + $1 = (\"freenode\" \"#radare\" \"r2tg\" \"\")" + (let* ((range-end (λ (range) (+ 1 (cdr range)))) + (find-space (λ (end) (string-index str #\space end))) + ;; opening (first) and closing (last) parenthesis + (opening-par (string-index str #\()) + (closing-par (string-index str #\))) + ;; extract the range of each + (server (cons (+ 1 opening-par) (find-space 0))) + (channel (cons (range-end server) (find-space (range-end server)))) + (gateway-nick (cons (range-end channel) (find-space (range-end channel)))) + (mask (cons (range-end gateway-nick) closing-par))) + + ;; and then get the strings + (map (λ (window) (substring str (car window) (cdr window))) + (list server channel gateway-nick mask)))) + +(define* (process-weechat-option opt #:optional emit-string) + "Takes in the application-define weechat-options and emits a server and +matching regular expression. + +The optional parameter `emit-string' controls if a string or a compiled regular +expression is returned. + +# Example + +scheme@(guile-user)> (process-weechat-option \"(freenode #radare r2tg )\") +$1 = '(\"freenode\" . (make-regexp \":(r2tg)!\\S* PRIVMSG #radare :(<(\\S*?)>) .*\")))" + (let* ((fields (extract-gateway-fields opt)) + (server (list-ref fields 0)) + (channel (list-ref fields 1)) + (gateway-nick (list-ref fields 2)) + (mask (list-ref fields 3))) + (cons server (make-gateway-regexp gateway-nick channel mask emit-string)))) + + +(define (split-gateways config) + "Push our elts onto the stack to extract our configs + +# Example +scheme@(guile-user)> (split-gateways \"(freenode #radare r2tg )(* * slack-irc-bot NICK:)\") +$1 = (\"(freenode #radare r2tg )\" \"(* * slack-irc-bot NICK:)\") +" + (define (process stk current rest) + (if (string-null? rest) (cons current '()) + (let* ((head (string-ref rest 0)) + (nrest (string-drop rest 1)) + (ncurrent (string-append current (string head)))) + (cond + [(and (null? stk) (not (string-null? current))) + (cons current (process stk "" rest))] + [(eq? head #\() (process (cons #\( stk) ncurrent nrest)] + [(eq? head #\)) (process (cdr stk) ncurrent nrest)] + ;; skip characters if our stk is empty + [(null? stk) (process stk current nrest)] + [else (process stk ncurrent nrest)])))) + + (process '() "" config)) + +(define (fetch-weechat-gateway-config) + "Extract the gateway configuration string" + (if-weechat (weechat:config_get_plugin *gateway-config*) + *default-irc-gateways*)) + +(define (assign-gateways-regex) + "Fetch our weechat gateway configuration and assign it to our local regexps" + (let* ((config_str (fetch-weechat-gateway-config)) + (config_lst (split-gateways config_str)) + (gateways (map process-weechat-option config_lst))) + ;; for each gateway, add it to our `*gateway-regexps*' ht + (for-each + (λ (gt) + (let* ((server (car gt)) + (new-regex (cdr gt)) + (server-regexps (hash-ref *gateway-regexps* server '()))) + (hash-set! *gateway-regexps* server + (cons new-regex server-regexps)))) + gateways))) + +;; Initialize our weechat settings & privmsg hook +(define (renamer_command_cb data buffer args) weechat::WEECHAT_RC_OK) + +(if-weechat + (begin + (if (not (= 1 (weechat:config_is_set_plugin *gateway-config*))) + (weechat:config_set_plugin + *gateway-config* + *default-irc-gateways*)) + + (weechat:hook_modifier "irc_in_privmsg" "privmsg-modifier" "") + + (weechat:hook_command *weechat/script-name* + *weechat/script-description* + "" ;; arguments + " +There are many IRC gateway programs that, rather than sending as if they were +another user, simply prepend the name of the user that is using that gateway to +the messages they are sending. + +For example: `slack-irc-bot` might send a message to #weechat: + + slack-irc-bot: How about them Yankees? + +gateway_rename intercepts that message and converts it to: + + ^zv: How about them Yankees? + +(gateway_rename prefixes the `^' (caret) symbol to each message to prevent message spoofing) + +Adding a Renamer: + + Which servers, channels, users and nickname templates are renamed can all be + modified in `plugins.var.guile.gateway_rename.gateways' + + Two gateways are matched by default, but are primarily intended to serve as a + template for you to add others. + + Each gateway renamer is placed inside of a set of parenthesis and contain four fields respectively: + 1. IRC server name (use the same name that weechat uses) + 2. Channel + 3. Gateway's nick/user name + 4. The last field is a template for how to match the nickname of the 'real user' + For example, if you wanted to convert the message 'gateway-bot: zv: Yes' into 'zv: Yes' + You would set the last field to 'NICK:' because each NICK at the beginning of the message is suffixed with a `:' + " + "" + "renamer_command_cb" + ""))) + +;; Setup our gateways->regex map +(assign-gateways-regex) ;;(print "Gateway Nickconverter by zv ") From 8027d0d64415ce6620028dc5419ad12c68a2b113 Mon Sep 17 00:00:00 2001 From: Ricardo Ferreira Date: Sun, 19 Mar 2017 01:33:05 +0100 Subject: [PATCH 012/642] fish.py 0.9.1: fix delete of key in fish_cyphers dict --- python/fish.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/fish.py b/python/fish.py index 426560c1..fc812774 100644 --- a/python/fish.py +++ b/python/fish.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +# Copyright (C) 2017 Ricardo Ferreira # Copyright (C) 2014 Charles Franklin # Copyright (C) 2012 Markus Näsman # Copyright (C) 2011 David Flatz @@ -52,7 +53,7 @@ SCRIPT_NAME = "fish" SCRIPT_AUTHOR = "David Flatz " -SCRIPT_VERSION = "0.9" +SCRIPT_VERSION = "0.9.1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "FiSH for weechat" CONFIG_FILE_NAME = SCRIPT_NAME @@ -895,7 +896,7 @@ def fish_cmd_blowkey(data, buffer, args): if argv[0] == "set": fish_keys[targetl] = argv2eol - if target in fish_cyphers: + if targetl in fish_cyphers: del fish_cyphers[targetl] weechat.prnt(buffer, "set key for %s to %s" % (target, argv2eol)) From 132c46338909b6b20806a31140887c9a3ba46663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sat, 25 Mar 2017 12:12:03 +0100 Subject: [PATCH 013/642] stick_buffer.py 0.4: fix behavior with script go.py and buffers names --- python/stick_buffer.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/python/stick_buffer.py b/python/stick_buffer.py index 36e90865..f7203e9b 100644 --- a/python/stick_buffer.py +++ b/python/stick_buffer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2013-2015 by nils_2 +# Copyright (c) 2013-2017 by nils_2 # Copyright (c) 2015 by Damien Bargiacchi # # stick buffer to a window, irssi like @@ -20,6 +20,9 @@ # # idea by shad0VV@freenode.#weechat # +# 2017-03-25: nils_2, (freenode.#weechat) +# 0.4 : script did not work with /go script and buffer names (reported: squigz) +# # 2015-05-12: Damien Bargiacchi # 0.3 : Stop script from truncating localvar lookup to first character of the buffer number # : Clean up destination buffer number logic @@ -46,7 +49,7 @@ SCRIPT_NAME = "stick_buffer" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.3" +SCRIPT_VERSION = "0.4" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Stick buffers to particular windows, like irssi" @@ -82,7 +85,7 @@ def get_default_stick_window_number(): return None # ======================================[ buffer utils ]====================================== # -def infolist_get_buffer_name_and_ptr(str_buffer_number): +def infolist_get_buffer_name_and_ptr_by_number(str_buffer_number): infolist = weechat.infolist_get('buffer', '', '') full_name = '' ptr_buffer = '' @@ -92,7 +95,19 @@ def infolist_get_buffer_name_and_ptr(str_buffer_number): full_name = weechat.infolist_string(infolist, 'full_name') ptr_buffer = weechat.infolist_pointer(infolist, 'pointer') break - weechat.infolist_free(infolist) + weechat.infolist_free(infolist) + return full_name, ptr_buffer + +def infolist_get_buffer_name_and_ptr_by_name(str_buffer_name): + infolist = weechat.infolist_get('buffer', '', '*%s*' % str_buffer_name) + full_name = '' + ptr_buffer = '' + if infolist: + while weechat.infolist_next(infolist): + full_name = weechat.infolist_string(infolist, 'full_name') + ptr_buffer = weechat.infolist_pointer(infolist, 'pointer') + break + weechat.infolist_free(infolist) return full_name, ptr_buffer def get_current_buffer_number(): @@ -129,14 +144,17 @@ def buffer_switch_cb(data, buffer, command): if len(args) != 1: return weechat.WEECHAT_RC_OK + ptr_buffer = '' + # check if argument is a buffer "number" destination_buffer = get_destination_buffer_number(args[0]) + if destination_buffer: + if destination_buffer < 1: + destination_buffer = 1 + buffer_name, ptr_buffer = infolist_get_buffer_name_and_ptr_by_number(destination_buffer) + else: + # search for buffer name + buffer_name, ptr_buffer = infolist_get_buffer_name_and_ptr_by_name(args[0]) - if not destination_buffer: - return weechat.WEECHAT_RC_OK - if destination_buffer < 1: - destination_buffer = 1 - - buffer_name, ptr_buffer = infolist_get_buffer_name_and_ptr(destination_buffer) if not ptr_buffer: return weechat.WEECHAT_RC_OK From 710a03d24488ecf89304b45b27249dc48dc68d0e Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Sat, 25 Mar 2017 12:30:06 +0100 Subject: [PATCH 014/642] weetweet.py 1.2.5: Add support for encrypted tokens with sec.conf Also change the way tokens are sent to the stream process so that they are not displayed in the process name. --- python/weetweet.py | 123 +++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 48 deletions(-) diff --git a/python/weetweet.py b/python/weetweet.py index 2e793352..668aab1c 100644 --- a/python/weetweet.py +++ b/python/weetweet.py @@ -99,10 +99,10 @@ clear_nicks="cnicks",clear_buffer="clear",create_stream="stream", restart_home_stream="re_home") desc_dict = dict( - user="[||], Request user timeline, " + + user=" [ ||], Request user timeline, " + "if is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", - replies="[||],Get any replies/mentions of you " + + replies="[ ||],Get any replies/mentions of you " + "if is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", view_tweet=", View/get tweet with ", @@ -112,18 +112,18 @@ retweet=", Retweet ", delete=", Delete tweet . You can only delete your own tweets...", tweet="Tweet the text following this command", - reply=", reply to . You need to have @ " + + reply=" , reply to . You need to have @ " + "of the user that you reply to in the tweet text. If this is not " + "the case this will be treated like a normal tweet instead.", new_tweets="Get new tweets from your home_timeline. This is only " + "useful if you have disabled the auto updater", follow_user=", Add user to people you follow", unfollow_user=", Remove user for people you follow", - following="[|||], Show 'friends' of or " + + following="[||| ], Show 'friends' of or " + "if no user were given show the people you follow. If not all " + "followers were printed supply the of the last list to get " + "the new batch of nicks", - followers="[|||], Who followes or " + + followers="[||| ], Who followes or " + "if no user were given show your follower. If not all " + "followers were printed supply the of the last list to get " + "the new batch of nicks", @@ -133,7 +133,7 @@ blocked_users="Print a list of users you have currently blocked", favorite=", Add tweet to you favorites", unfavorite=", Remove tweet from yout favorites", - favorites="[|][||], Request favs, " + + favorites="[|][ ||], Request favs, " + "if is not given get your own favs. " + "If is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", @@ -141,7 +141,7 @@ rate_limits="[|], get the current status of the twitter " + "api limits. It prints how much you have left/used. " + " if is supplied it will only get/print that sub_group.", - home_timeline="[||],Get tweets from you home " + + home_timeline="[ ||],Get tweets from you home " + "timeline" + "if is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", @@ -151,9 +151,10 @@ clear_buffer="Clear the twitter buffer of text "+ "same as '/buffer clear'", create_stream="Create a twitter stream with the following filter "+ - "options: & . Note that they must be " + - "seperated by a ' & '. To only use keywords just have ' & ' in the "+ - "begininng.\n NOTE: you can only have one stream at a time because "+ + "options: . Note that the user list and the "+ + "keyword list must be comma seperated. IE user1,user2,user3 keyword1,keyword2.\n" + + "If you only want to stream keywords, write '&' as 'users' input.\n"+ + "NOTE: you can only have one stream at a time because "+ "twitter will IP ban you if you repeatedly request more than one "+ "stream.", restart_home_stream="Restart the home timeline stream after it has " + @@ -204,6 +205,9 @@ def read_config(): for item in ["auth_complete","print_id","alt_rt_style","home_replies","tweet_nicks"]: #Convert to bool script_options[item] = weechat.config_string_to_boolean(script_options[item]) + for item in ["oauth_token","oauth_secret"]: + #Convert potentially encrypted tokens + script_options[item] = weechat.string_eval_expression(script_options[item], {}, {}, {}) def config_cb(data, option, value): """Callback called when a script option is changed.""" @@ -337,10 +341,10 @@ def stream_message(buffer,tweet): "recv stream data: " + str(tweet))) def twitter_stream_cb(buffer,fd): + #accept connection server = sock_fd_dict[sock_fd_dict[fd]] conn, addr = server.accept() - error = False tweet = "" data = True while data: @@ -362,7 +366,18 @@ def twitter_stream_cb(buffer,fd): print_tweet_data(buffer,tweet,"id") else: print_tweet_data(buffer,tweet,"") - elif True: + elif tweet == "options": + #We need to send over the stream options + + options = dict(screen_name = script_options['screen_name'], + name = sock_fd_dict[fd], + alt_rt_style = int(script_options['alt_rt_style']), + home_replies = int(script_options['home_replies']), + token = script_options["oauth_token"], + secret = script_options["oauth_secret"]) + + conn.sendall(bytes(str(options))) + else: #https://dev.twitter.com/docs/streaming-apis/messages #TODO handle stream events stream_message(buffer,tweet) @@ -371,32 +386,51 @@ def twitter_stream_cb(buffer,fd): return weechat.WEECHAT_RC_OK def twitter_stream(cmd_args): - if len(cmd_args) < 5: + if len(cmd_args) < 3: return "Invalid stream command" - if not os.path.exists(cmd_args[4]): - return "The socket file doesn't exist! " + cmd_args[4] + if not os.path.exists(cmd_args[2]): + return "The socket file doesn't exist! " + cmd_args[2] - oauth_token = cmd_args[1] - oauth_secret= cmd_args[2] + def connect(): + client = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM ) + client.connect(cmd_args[2]) + #Don't block by default, timeout if no data is present + client.setblocking(False) + return client + + client = connect() + client.settimeout(3) + client.sendall(bytes('"options"',"utf-8")) + client.shutdown(socket.SHUT_WR) + + data = True + options = "" + + while data: + try: + data = client.recv(1024).decode('utf-8') + options += data + except: + break try: - if cmd_args[-1][0] == "{": - option_dict = ast.literal_eval(cmd_args[-1]) - cmd_args.pop(-1) - home_replies = option_dict['home_replies'] - alt_rt_style = option_dict['alt_rt_style'] - screen_name = option_dict['screen_name'] - name = option_dict['name'] - stream_args = option_dict['stream_args'] + option_dict = ast.literal_eval(options) except: - return "Error starting stream, no option arguments" + return "Did not manage to get startup arguments to stream" - def connect(): - client = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM ) - client.connect(cmd_args[4]) - #Don't block, timeout if no data is present - client.setblocking(0) - return client + client.close() + + oauth_token = option_dict['token'] + oauth_secret= option_dict['secret'] + home_replies = option_dict['home_replies'] + alt_rt_style = option_dict['alt_rt_style'] + screen_name = option_dict['screen_name'] + name = option_dict['name'] + + if len(cmd_args) >= 4: + stream_args = cmd_args[3:] + else: + stream_args = "" # These arguments are optional. But the current code only handles this # configuration. So it's defined here if the defaults change. @@ -418,7 +452,6 @@ def connect(): tweet_iter = stream.user() else: h = html.parser.HTMLParser() - args = stream_args.split(" & ") stream = TwitterStream(auth=OAuth( oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET), **stream_options) @@ -426,20 +459,20 @@ def connect(): twitter = Twitter(auth=OAuth( oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET)) - if args[0] != "": - follow = ",".join(h.unescape(args[0]).split()) + if stream_args[0] != "&": + follow = stream_args[0] twitter_data = twitter.users.lookup(screen_name=follow) follow_ids = "" for user in twitter_data: follow_ids += user['id_str'] + "," follow_ids = follow_ids[:-1] - if len(args) == 2 and args[1] != "": - track = ",".join(h.unescape(args[1]).split()) + if len(stream_args) == 2: + track = stream_args[1] tweet_iter = stream.statuses.filter(track=track,follow=follow_ids) else: tweet_iter = stream.statuses.filter(follow=follow_ids) else: - track = ",".join(h.unescape(args[1]).split()) + track = stream_args[1] tweet_iter = stream.statuses.filter(track=track) except: stream_end_message = "Connection problem (could not connect to twitter)" @@ -527,21 +560,15 @@ def create_stream(name, args = ""): server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(file_name) #Don't block, timeout if no data is present - server.setblocking(0) + server.setblocking(False) server.listen(1) file_fd = server.fileno() sock_fd_dict[str(file_fd)] = name sock_fd_dict[name] = server sock_hooks[name] = weechat.hook_fd(file_fd, 1, 0, 0, "twitter_stream_cb", buffer) - options = dict(screen_name = script_options['screen_name'], name = name, - alt_rt_style = int(script_options['alt_rt_style']), - home_replies = int(script_options['home_replies']), - stream_args = args) - proc_hooks[name] = weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + - script_options["oauth_token"] + " " + script_options["oauth_secret"] + " " + - "stream " + file_name + ' "' + str(options) + '"', 0 , "my_process_cb", str([buffer,"Stream"])) + "stream " + file_name + " " + args, 0 , "my_process_cb", str([buffer,"Stream"])) return "Started stream" def my_process_cb(data, command, rc, out, err): @@ -1205,7 +1232,7 @@ def finish_init(): "f " + script_options['screen_name'] + " []", 10 * 1000, "oauth_proc_cb", "friends") if __name__ == "__main__" and weechat_call: - weechat.register( SCRIPT_NAME , "DarkDefender", "1.2.4", "GPL3", "Weechat twitter client", "", "") + weechat.register( SCRIPT_NAME , "DarkDefender", "1.2.5", "GPL3", "Weechat twitter client", "", "") if not import_ok: weechat.prnt("", "Can't load twitter python lib >= " + required_twitter_version) @@ -1244,7 +1271,7 @@ def finish_init(): Type ":auth" and follow the instructions to do that""") elif import_ok: - if sys.argv[3] == "stream": + if sys.argv[1] == "stream": print(twitter_stream(sys.argv)) else: print(get_twitter_data(sys.argv)) From 1ec7ba80f5fec074c86b5e2054537e5f94fde1a7 Mon Sep 17 00:00:00 2001 From: Kevin Pulo Date: Fri, 31 Mar 2017 15:31:28 +1100 Subject: [PATCH 015/642] title.py 0.9: Make hotlist optional --- python/title.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/python/title.py b/python/title.py index 7719d76b..e746c96f 100644 --- a/python/title.py +++ b/python/title.py @@ -22,6 +22,8 @@ # (this script requires WeeChat 0.3.0 or newer) # # History: +# 2016-03-31, devkev +# version 0.9, Make hotlist optional # 2016-05-01, Ferus # version 0.8, Add ability to prefix and suffix the title, current # buffer, and hotlist buffers. As well as specify hotlist separator @@ -44,7 +46,7 @@ SCRIPT_NAME = "title" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "0.8" +SCRIPT_VERSION = "0.9" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Set screen title to current buffer name + hotlist items with configurable priority level" @@ -61,6 +63,7 @@ "hotlist_buffer_suffix": '', "current_buffer_prefix": '', "current_buffer_suffix": '', + "show_hotlist" : 'on', } hooks = ( @@ -84,22 +87,23 @@ def update_title(data, signal, signal_data): title += w.buffer_get_string(w.current_buffer(), 'name') title += w.config_get_plugin('current_buffer_suffix') - # hotlist buffers - hotlist = w.infolist_get('hotlist', '', '') - pnumber = w.config_get_plugin('hotlist_number_prefix') - snumber = w.config_get_plugin('hotlist_number_suffix') - pname = w.config_get_plugin('hotlist_buffer_prefix') - sname = w.config_get_plugin('hotlist_buffer_suffix') - separator = w.config_get_plugin('hotlist_separator') - while w.infolist_next(hotlist): - priority = w.infolist_integer(hotlist, 'priority') - if priority >= int(w.config_get_plugin('title_priority')): - number = w.infolist_integer(hotlist, 'buffer_number') - thebuffer = w.infolist_pointer(hotlist, 'buffer_pointer') - name = w.buffer_get_string(thebuffer, 'short_name') - title += ' {0}{1}{2}{3}{4}{5}{6}'.format(pnumber, \ - number, snumber, separator, pname, name, sname) - w.infolist_free(hotlist) + if w.config_get_plugin('show_hotlist') == 'on': + # hotlist buffers + hotlist = w.infolist_get('hotlist', '', '') + pnumber = w.config_get_plugin('hotlist_number_prefix') + snumber = w.config_get_plugin('hotlist_number_suffix') + pname = w.config_get_plugin('hotlist_buffer_prefix') + sname = w.config_get_plugin('hotlist_buffer_suffix') + separator = w.config_get_plugin('hotlist_separator') + while w.infolist_next(hotlist): + priority = w.infolist_integer(hotlist, 'priority') + if priority >= int(w.config_get_plugin('title_priority')): + number = w.infolist_integer(hotlist, 'buffer_number') + thebuffer = w.infolist_pointer(hotlist, 'buffer_pointer') + name = w.buffer_get_string(thebuffer, 'short_name') + title += ' {0}{1}{2}{3}{4}{5}{6}'.format(pnumber, \ + number, snumber, separator, pname, name, sname) + w.infolist_free(hotlist) # suffix title += w.config_get_plugin('title_suffix') From dc9e69f37fcdc51f0f5a8856ef977ee3d8d8e371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Sat, 1 Apr 2017 13:54:29 +0200 Subject: [PATCH 016/642] go.py 2.5: add option "buffer_number" (closes #202) --- python/go.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/python/go.py b/python/go.py index 3d1828b6..a30f58f0 100644 --- a/python/go.py +++ b/python/go.py @@ -21,12 +21,14 @@ # # History: # +# 2017-04-01, Sébastien Helleu : +# version 2.5: add option "buffer_number" # 2017-03-02, Sébastien Helleu : # version 2.4: fix syntax and indentation error # 2017-02-25, Simmo Saan # version 2.3: fix fuzzy search breaking buffer number search display # 2016-01-28, ylambda -# version 2.2: add option fuzzy_search +# version 2.2: add option "fuzzy_search" # 2015-11-12, nils_2 # version 2.1: fix problem with buffer short_name "weechat", using option # "use_core_instead_weechat", see: @@ -42,7 +44,7 @@ # version 1.8: fix jump to non-active merged buffers (jump with buffer name # instead of number) # 2012-01-03 nils_2 -# version 1.7: add option use_core_instead_weechat +# version 1.7: add option "use_core_instead_weechat" # 2012-01-03, Sébastien Helleu : # version 1.6: make script compatible with Python 3.x # 2011-08-24, stfn : @@ -56,9 +58,9 @@ # version 1.2: use high priority for hooks to prevent conflict with other # plugins/scripts (WeeChat >= 0.3.4 only) # 2010-03-25, Elián Hanisch : -# version 1.1: use a space for match the end of a string +# version 1.1: use a space to match the end of a string # 2009-11-16, Sébastien Helleu : -# version 1.0: add new option for displaying short names +# version 1.0: add new option to display short names # 2009-06-15, Sébastien Helleu : # version 0.9: fix typo in /help go with command /key # 2009-05-16, Sébastien Helleu : @@ -90,7 +92,7 @@ SCRIPT_NAME = 'go' SCRIPT_AUTHOR = 'Sébastien Helleu ' -SCRIPT_VERSION = '2.4' +SCRIPT_VERSION = '2.5' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Quick jump to buffers' @@ -151,6 +153,9 @@ 'fuzzy_search': ( 'off', 'search buffer matches using approximation'), + 'buffer_number': ( + 'on', + 'display buffer number'), } # hooks management @@ -434,10 +439,13 @@ def go_buffers_to_string(listbuf, pos, strinput): name += buffer_name[prev_index+1:] else: name = buffer_name - string += ' %s%s%s%s%s' % ( - weechat.color(weechat.config_get_plugin( - 'color_number' + selected)), - str(listbuf[i]['number']), + string += ' ' + if go_option_enabled('buffer_number'): + string += '%s%s' % ( + weechat.color(weechat.config_get_plugin( + 'color_number' + selected)), + str(listbuf[i]['number'])) + string += '%s%s%s' % ( weechat.color(weechat.config_get_plugin( 'color_name' + selected)), name, From 8314faff6dceaf770174be2ba66d26eb67d2b2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sun, 2 Apr 2017 19:49:02 +0200 Subject: [PATCH 017/642] stick_buffer.py 0.5: add support of "/input jump_smart" and "/buffer +/-" (closes #200) --- python/stick_buffer.py | 57 ++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/python/stick_buffer.py b/python/stick_buffer.py index f7203e9b..239be8da 100644 --- a/python/stick_buffer.py +++ b/python/stick_buffer.py @@ -20,6 +20,9 @@ # # idea by shad0VV@freenode.#weechat # +# 2017-04-02: nils_2, (freenode.#weechat) +# 0.5 : support of "/input jump_smart" and "/buffer +/-" (reported: squigz) +# # 2017-03-25: nils_2, (freenode.#weechat) # 0.4 : script did not work with /go script and buffer names (reported: squigz) # @@ -49,7 +52,7 @@ SCRIPT_NAME = "stick_buffer" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.4" +SCRIPT_VERSION = "0.5" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Stick buffers to particular windows, like irssi" @@ -110,6 +113,16 @@ def infolist_get_buffer_name_and_ptr_by_name(str_buffer_name): weechat.infolist_free(infolist) return full_name, ptr_buffer +def infolist_get_first_entry_from_hotlist(): + infolist = weechat.infolist_get('hotlist', '', '') + if infolist: + weechat.infolist_next(infolist) # go to first entry in hotlist + buffer_name = weechat.infolist_string(infolist, 'buffer_name') + buffer_number = weechat.infolist_integer(infolist, 'buffer_number') + ptr_buffer = weechat.infolist_pointer(infolist, 'buffer_pointer') + weechat.infolist_free(infolist) + return buffer_name, ptr_buffer, buffer_number + def get_current_buffer_number(): ptr_buffer = weechat.window_get_pointer(weechat.current_window(), 'buffer') return weechat.buffer_get_integer(ptr_buffer, 'number') @@ -137,37 +150,48 @@ def get_destination_buffer_number(arg): # ======================================[ callbacks ]====================================== # def buffer_switch_cb(data, buffer, command): +# weechat.prnt("","data: %s buffer: %s command: %s" % (data,buffer,command)) + # command exist? if command == '': return weechat.WEECHAT_RC_OK + # get command without leading command char! + cmd = command[1:].strip().split(' ',)[0:1] + # get number from command /buffer args = command.strip().split(' ',)[1:] - if len(args) != 1: - return weechat.WEECHAT_RC_OK - ptr_buffer = '' - # check if argument is a buffer "number" - destination_buffer = get_destination_buffer_number(args[0]) - if destination_buffer: - if destination_buffer < 1: - destination_buffer = 1 - buffer_name, ptr_buffer = infolist_get_buffer_name_and_ptr_by_number(destination_buffer) - else: - # search for buffer name - buffer_name, ptr_buffer = infolist_get_buffer_name_and_ptr_by_name(args[0]) + + if "input" in cmd and "jump_smart" in args: + buffer_name, ptr_buffer, buffer_number = infolist_get_first_entry_from_hotlist() + + if "buffer" in cmd: + if len(args) != 1: + return weechat.WEECHAT_RC_OK + + # check if argument is a buffer "number" + destination_buffer = get_destination_buffer_number(args[0]) + if destination_buffer: + if destination_buffer < 1: + destination_buffer = 1 + buffer_name, ptr_buffer = infolist_get_buffer_name_and_ptr_by_number(destination_buffer) + else: + # search for buffer name + buffer_name, ptr_buffer = infolist_get_buffer_name_and_ptr_by_name(args[0]) if not ptr_buffer: return weechat.WEECHAT_RC_OK if ptr_buffer == weechat.window_get_pointer(weechat.current_window(), 'buffer'): return weechat.WEECHAT_RC_OK - window_number = weechat.buffer_get_string(ptr_buffer, 'localvar_stick_buffer_to_window') if not window_number: window_number = get_default_stick_window_number() if window_number: weechat.command('', '/window %s' % window_number) - return weechat.WEECHAT_RC_OK - + weechat.command('', '/buffer %s' % buffer_name) + return weechat.WEECHAT_RC_OK_EAT + else: + return weechat.WEECHAT_RC_OK def cmd_cb(data, buffer, args): args = args.strip().lower().split(' ') @@ -229,6 +253,7 @@ def main(): weechat.hook_command(SCRIPT_NAME, SCRIPT_DESC, 'list', description, 'list %-', 'cmd_cb', '') weechat.hook_command_run('/buffer *', 'buffer_switch_cb', '') + weechat.hook_command_run('/input jump_smart', 'buffer_switch_cb', '') if __name__ == '__main__': if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, From 6276e927595abae6428598c217c2884d60200942 Mon Sep 17 00:00:00 2001 From: yeled Date: Thu, 6 Apr 2017 20:46:13 +0200 Subject: [PATCH 018/642] url_olde.py 0.5: fix URL catching --- python/url_olde.py | 124 ++++++++++++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 34 deletions(-) diff --git a/python/url_olde.py b/python/url_olde.py index 97b03c5a..41e7d40e 100644 --- a/python/url_olde.py +++ b/python/url_olde.py @@ -1,6 +1,4 @@ """ -Charlie Allom - database init code from https://weechat.org/scripts/source/triggerreply.py.html regex is from http://stackoverflow.com/questions/6883049/regex-to-find-urls-in-string-in-python @@ -8,26 +6,32 @@ ---- - set a preference value for ignoring: - nicks - - channels - purge sql rows after an age range (or fixed size) - - ignore parts/quits messages """ SCRIPT_NAME = "url_olde" SCRIPT_AUTHOR = "Charlie Allom sql later on - new_entry.append(uri) - new_entry.append(time.time()) - new_entry.append(nick) - new_entry.append(channel) - cursor.execute("SELECT date,uri,nick,channel from urls WHERE uri LIKE ?", (uri,)) - result=cursor.fetchone() - if result is None: - """ a new URL is seen! """ - #w.command(buffer, "/notice %s" % (new_entry)) #debug - cursor.execute("INSERT INTO urls(uri, date, nick, channel) VALUES (?,?,?,?)", new_entry) - database.commit() + full_uri = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&#+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', message) + channel = w.buffer_get_string(buffer, 'name') # current channel. + # w.prnt(w.current_buffer(), 'full_uri: %s ' % (full_uri)) # debug + for olde in full_uri: # iterate over each URI we get in the list from full_uri regex + if '/#/' in olde: # run this routine on rails style apps + url = olde + uri = url.rstrip("/)") # strip the final / and lesser-seen ) + new_entry = [] # create an ordered list of the following values we want to INSERT -> sql later on + new_entry.append(uri) + new_entry.append(time.time()) + new_entry.append(nick) + new_entry.append(channel) + # w.prnt(w.current_buffer(), 'uri: %s ' % (uri)) # debug + cursor.execute("SELECT date,uri,nick,channel from urls WHERE uri = ?", (uri,)) + result = cursor.fetchone() + if channel in str(url_olde_settings['ignored_channels']): + # w.prnt(w.current_buffer(), 'ignoring %s due to ignored_channels = %s' % (uri, str(url_olde_settings['ignored_channels']))) + return w.WEECHAT_RC_OK + if result is None: + """ a new URL is seen! """ + # w.command(buffer, "/notice %s" % (new_entry)) # debug + cursor.execute("INSERT INTO urls(uri, date, nick, channel) VALUES (?,?,?,?)", new_entry) + database.commit() + else: + """ we've got a match from sqlite """ + date, uri, nick, channel = result + timestamp = time.strftime('%Y-%m-%d %H:%M', time.localtime(date)) # convert it to YYYY-MM-DD + # w.command(buffer, "/notice DING %s" % str(result)) # debug + w.prnt_date_tags(buffer, 0, 'no_log,notify_none', 'olde!! already posted by %s in %s on %s' % (nick, channel, timestamp)) + else: # strip anchors + url, fragment = urldefrag(olde) + uri = url.rstrip("/)") # strip the final / and lesser-seen ) + new_entry = [] # create an ordered list of the following values we want to INSERT -> sql later on + new_entry.append(uri) + new_entry.append(time.time()) + new_entry.append(nick) + new_entry.append(channel) + # w.prnt(w.current_buffer(), 'uri: %s ' % (uri)) # debug + cursor.execute("SELECT date,uri,nick,channel from urls WHERE uri = ?", (uri,)) + result = cursor.fetchone() + if channel in str(url_olde_settings['ignored_channels']): + # w.prnt(w.current_buffer(), 'ignoring %s due to ignored_channels = %s' % (uri, str(url_olde_settings['ignored_channels']))) + return w.WEECHAT_RC_OK + if result is None: + """ a new URL is seen! """ + # w.command(buffer, "/notice %s" % (new_entry)) # debug + cursor.execute("INSERT INTO urls(uri, date, nick, channel) VALUES (?,?,?,?)", new_entry) + database.commit() + else: + """ we've got a match from sqlite """ + date, uri, nick, channel = result + timestamp = time.strftime('%Y-%m-%d %H:%M', time.localtime(date)) # convert it to YYYY-MM-DD + # w.command(buffer, "/notice DING %s" % str(result)) # debug + w.prnt_date_tags(buffer, 0, 'no_log,notify_none', 'olde!! already posted by %s in %s on %s' % (nick, channel, timestamp)) + return w.WEECHAT_RC_OK + + +def url_olde_load_config(): + global url_olde_settings_default, url_olde_settings + version = w.info_get('version_number', '') or 0 + for option, value in url_olde_settings_default.items(): + if w.config_is_set_plugin(option): + url_olde_settings[option] = w.config_get_plugin(option) else: - """ we've got a match from sqlite """ - date, uri, nick, channel = result - timestamp = time.strftime('%Y-%m-%d', time.localtime(date)) # convert it to YYYY-MM-DD - #w.command(buffer, "/notice DING %s" % str(result)) # debug - w.prnt_date_tags(buffer, 0, 'no_log,notify_none', 'olde!! already posted by %s in %s on %s' % (nick, channel, timestamp)) + w.config_set_plugin(option, value[0]) + url_olde_settings[option] = value[0] + if int(version) >= 0x00030500: + w.config_set_desc_plugin(option, value[1]) + + +def url_olde_config_cb(data, option, value): + """Called each time an option is changed.""" + url_olde_load_config() return w.WEECHAT_RC_OK if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, - SCRIPT_LICENSE, SCRIPT_DESC, '', ''): + SCRIPT_LICENSE, SCRIPT_DESC, '', ''): if IMPORT_ERR: w.prnt("", "You need sqlite3 to run this plugin.") DBFILE = "%s/olde.sqlite3" % w.info_get("weechat_dir", "") if not os.path.isfile(DBFILE): - create_db() # init on load if file doesn't exist. + create_db() # init on load if file doesn't exist. + # load the config + url_olde_load_config() + # config changes are reloaded + w.hook_config('plugins.var.python.' + SCRIPT_NAME + '.*', 'url_olde_config_cb', '') # catch urls in buffer and send to the cb - w.hook_print('', '', '://', 1, 'search_urls_cb', '') - - # test - #w.prnt(w.current_buffer(), "script loaded!") + w.hook_print('', 'irc_privmsg', '://', 1, 'search_urls_cb', '') From d1f97411682863c6fdf36cfcd34ba70220fe7264 Mon Sep 17 00:00:00 2001 From: Zephyr Quarto-Pellerin Date: Thu, 6 Apr 2017 20:50:19 +0200 Subject: [PATCH 019/642] gateway_rename.scm 1.2.1: fix minor bugs and remove some debug statements --- guile/gateway_rename.scm | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/guile/gateway_rename.scm b/guile/gateway_rename.scm index c58d4a51..494586bc 100644 --- a/guile/gateway_rename.scm +++ b/guile/gateway_rename.scm @@ -20,7 +20,7 @@ ;; 0.8 - Barebones, contained list of translations (use-modules ((srfi srfi-1) - #:select (any))) + #:select (any fold))) (use-modules ((srfi srfi-26) #:select (cut))) (use-modules (ice-9 regex)) @@ -29,17 +29,18 @@ (define *weechat/script-name* "gateway_rename") (define *weechat/script-author* "zv ") -(define *weechat/script-version* "1.2") +(define *weechat/script-version* "1.2.1") (define *weechat/script-license* "GPL3") (define *weechat/script-description* "Convert usernames of gateway connections their real names") + ;; A test-harness for checking if we are inside weechat (define-syntax if-weechat (syntax-rules () ((_ conseq alt) (if (defined? 'weechat:register) conseq alt)) ((_ conseq) (if (defined? 'weechat:register) conseq)))) -(if (defined? 'weechat:register) +(if-weechat (weechat:register *weechat/script-name* *weechat/script-author* *weechat/script-version* @@ -50,7 +51,7 @@ ;; `user-prefix' is a distinguishing username prefix for 'fake' users (define *user-prefix* "^") (define *gateway-config* "gateways") -(define *default-irc-gateways* "(freenode #radare r2tg )(freenode #test-channel zv-test )") +(define *default-irc-gateways* "(freenode #radare r2tg ) (freenode #test-channel zv-test NICK:)") (define (print . msgs) (if (defined? 'weechat:print) @@ -102,11 +103,12 @@ returned during /version" ;; take everything after username before message [username (nth-match 1)] [real-username (nth-match 3)] - ;; extract everything after the fake r2tg username - [message (string-copy msg - ;; skip the inserted space - (+ 1 (match:end result 2)) + ;; Extract everything after the gateway-user mask + [raw-message (string-copy msg + (match:end result 2) (string-length msg))] + ;; .. and be sure to strip any preceding characters + [message (string-trim raw-message)] ;; extract everything before the message but after the username [hostmask (string-copy msg (match:end result 1) @@ -128,7 +130,11 @@ returned during /version" "Build a regular expression that will match the nick, channel and \"\"-style mask" (let* ([mask-regexp ;; replace with <(\\S*?)> (regexp-substitute/global #f "NICK" mask 'pre "(\\S*?)" 'post "")] - [composed-str (format #f ":(~a)!\\S* PRIVMSG ~a :(~a)" gateway-nick channel mask-regexp)]) + [composed-str (format #f + ":(~a)!\\S* PRIVMSG ~a :(~a)" + gateway-nick + (if (equal? "*" channel) "\\S*" channel) + mask-regexp)]) (if emit-string composed-str (make-regexp composed-str)))) (define (extract-gateway-fields str) From 9d7f210115bc7113a643bc9ed2f3dfc66f02c15a Mon Sep 17 00:00:00 2001 From: rr- Date: Wed, 12 Apr 2017 16:08:19 +0200 Subject: [PATCH 020/642] fish 0.9.2: also put mark on outgoing messages --- python/fish.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/python/fish.py b/python/fish.py index fc812774..75df1352 100644 --- a/python/fish.py +++ b/python/fish.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +# Copyright (C) 2017 Marcin Kurczewski # Copyright (C) 2017 Ricardo Ferreira # Copyright (C) 2014 Charles Franklin # Copyright (C) 2012 Markus Näsman @@ -53,7 +54,7 @@ SCRIPT_NAME = "fish" SCRIPT_AUTHOR = "David Flatz " -SCRIPT_VERSION = "0.9.1" +SCRIPT_VERSION = "0.9.2" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "FiSH for weechat" CONFIG_FILE_NAME = SCRIPT_NAME @@ -162,13 +163,13 @@ def fish_config_init(): fish_config_option["mark_position"] = weechat.config_new_option( fish_config_file, fish_config_section["look"], "mark_position", - "integer", "put marker for encrypted INCOMING messages at start or end", + "integer", "put marker for encrypted messages at start or end", "off|begin|end", 0,2, "off", "off", 0, "", "", "", "", "", "") fish_config_option["mark_encrypted"] = weechat.config_new_option( fish_config_file, fish_config_section["look"], "mark_encrypted", - "string", "marker for encrypted INCOMING messages", "", 0, 0, + "string", "marker for encrypted messages", "", 0, 0, "*", "*", 0, "", "", "", "", "", "") # color @@ -797,7 +798,7 @@ def fish_modifier_out_privmsg_cb(data, modifier, server_name, string): fish_cyphers[targetl] = b else: b = fish_cyphers[targetl] - cypher = blowcrypt_pack(match.group(3), b) + cypher = blowcrypt_pack(fish_msg_wo_marker(match.group(3)), b) fish_announce_encrypted(buffer, target) @@ -835,6 +836,18 @@ def fish_modifier_out_topic_cb(data, modifier, server_name, string): return "%s%s" % (match.group(1), cypher) +def fish_modifier_input_text(data, modifier, server_name, string): + if weechat.string_is_command_char(string): + return string + buffer = weechat.current_buffer() + name = weechat.buffer_get_string(buffer, "name") + target = name.replace(".", "/") + targetl = target.lower() + if targetl not in fish_keys: + return string + return "%s" % (fish_msg_w_marker(string)) + + def fish_unload_cb(): fish_config_write() @@ -1095,7 +1108,7 @@ def fish_list_keys(buffer): global fish_keys weechat.prnt(buffer, "\tFiSH Keys: form target(server): key") - + if len(fish_keys) == 0: weechat.prnt(buffer, "NO KEYS!\n") return @@ -1113,6 +1126,16 @@ def fish_msg_w_marker(msg): return "%s%s" % (marker, msg) else: return msg + + +def fish_msg_wo_marker(msg): + marker = weechat.config_string(fish_config_option["mark_encrypted"]) + if weechat.config_string(fish_config_option["mark_position"]) == "end": + return msg[0:-len(marker)] + elif weechat.config_string(fish_config_option["mark_position"]) == "begin": + return msg[len(marker):] + else: + return msg # # MAIN # @@ -1156,4 +1179,5 @@ def fish_msg_w_marker(msg): weechat.hook_modifier("irc_in_332", "fish_modifier_in_332_cb", "") weechat.hook_modifier("irc_out_privmsg", "fish_modifier_out_privmsg_cb", "") weechat.hook_modifier("irc_out_topic", "fish_modifier_out_topic_cb", "") + weechat.hook_modifier("input_text_for_buffer", "fish_modifier_input_text", "") weechat.hook_config("fish.secure.key", "fish_secure_key_cb", "") From 08914a4637007fd736c5370cf46e92fcf703e958 Mon Sep 17 00:00:00 2001 From: "Matthew M. Boedicker" Date: Wed, 19 Apr 2017 21:20:47 -0700 Subject: [PATCH 021/642] otr.py 1.9.0: fix log command help and completion Set buffer local vars with OTR conversation status. --- python/otr.py | 184 +++++++++++++++++++++++++------------------------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/python/otr.py b/python/otr.py index 404e96ab..0af9e333 100644 --- a/python/otr.py +++ b/python/otr.py @@ -163,7 +163,7 @@ def to_str(self, strng): SCRIPT_AUTHOR = 'Matthew M. Boedicker' SCRIPT_LICENCE = 'GPL3' -SCRIPT_VERSION = '1.8.0' +SCRIPT_VERSION = '1.9.0' OTR_DIR_NAME = 'otr' @@ -542,6 +542,14 @@ def read_private_key(key_file_path): with open(key_file_path, 'rb') as key_file: return potr.crypt.PK.parsePrivateKey(key_file.read())[0] +def get_context(account_name, context_name): + """Return a context from an account.""" + return ACCOUNTS[account_name].getContext(context_name) + +def get_server_context(server, peer_nick): + """Return the context for the current server user and peer.""" + return get_context(current_user(server), irc_user(peer_nick, server)) + class AccountDict(collections.defaultdict): """Dictionary that adds missing keys as IrcOtrAccount instances.""" @@ -1182,10 +1190,7 @@ def message_in_cb(data, modifier, modifier_data, string): server = PYVER.to_unicode(modifier_data) - from_user = irc_user(parsed['from_nick'], server) - local_user = current_user(server) - - context = ACCOUNTS[local_user].getContext(from_user) + context = get_server_context(server, parsed['from_nick']) context.in_assembler.add(parsed['text']) @@ -1245,10 +1250,7 @@ def message_out_cb(data, modifier, modifier_data, string): server = PYVER.to_unicode(modifier_data) - to_user = irc_user(parsed['to_nick'], server) - local_user = current_user(server) - - context = ACCOUNTS[local_user].getContext(to_user) + context = get_server_context(server, parsed['to_nick']) is_query = OTR_QUERY_RE.search(parsed['text']) parsed_text_bytes = to_bytes(parsed['text']) @@ -1347,8 +1349,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args(arg_parts[1:3], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) # We need to wall disable_logging() here so that no OTR-related # buffer messages get logged at any point. disable_logging() will # be called again when effectively switching to encrypted, but @@ -1372,8 +1373,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args(arg_parts[1:3], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) context.disconnect() result = weechat.WEECHAT_RC_OK @@ -1382,8 +1382,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args(arg_parts[1:3], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) if context.is_encrypted(): context.print_buffer( 'This conversation is encrypted.', 'success') @@ -1423,8 +1422,7 @@ def command_cb(data, buf, args): if secret: secret = PYVER.to_str(secret) - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) context.smpGotSecret(secret) result = weechat.WEECHAT_RC_OK @@ -1455,8 +1453,7 @@ def command_cb(data, buf, args): else: return weechat.WEECHAT_RC_ERROR - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) if secret: secret = PYVER.to_str(secret) @@ -1487,8 +1484,7 @@ def command_cb(data, buf, args): else: return weechat.WEECHAT_RC_ERROR - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) if context.in_smp: try: @@ -1506,8 +1502,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args(arg_parts[1:3], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) if context.crypto.theirPubkey is not None: context.setCurrentTrust('verified') @@ -1525,8 +1520,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args(arg_parts[1:3], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) if context.crypto.theirPubkey is not None: context.setCurrentTrust('') @@ -1546,8 +1540,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args([], buf) if len(arg_parts) == 1: if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) if context.is_encrypted(): if context.is_logged(): @@ -1571,8 +1564,7 @@ def command_cb(data, buf, args): if len(arg_parts) == 2: if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) if arg_parts[1] == 'start' and \ not context.is_logged() and \ @@ -1609,8 +1601,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args([], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) context.print_buffer(context.format_policies()) else: @@ -1622,8 +1613,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args([], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) context.print_buffer(format_default_policies()) else: @@ -1635,8 +1625,7 @@ def command_cb(data, buf, args): nick, server = default_peer_args([], buf) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) policy_var = context.policy_config_option(arg_parts[1].lower()) @@ -1660,8 +1649,7 @@ def command_cb(data, buf, args): value=arg_parts[3])) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) context.print_buffer(format_default_policies()) else: @@ -1690,72 +1678,87 @@ def otr_statusbar_cb(data, item, window): # will be empty. buf = weechat.current_buffer() - result = '' + if not buffer_is_private(buf): + return '' - if buffer_is_private(buf): - local_user = irc_user( - buffer_get_string(buf, 'localvar_nick'), - buffer_get_string(buf, 'localvar_server')) + local_user = irc_user( + buffer_get_string(buf, 'localvar_nick'), + buffer_get_string(buf, 'localvar_server')) - remote_user = irc_user( - buffer_get_string(buf, 'localvar_channel'), - buffer_get_string(buf, 'localvar_server')) + remote_user = irc_user( + buffer_get_string(buf, 'localvar_channel'), + buffer_get_string(buf, 'localvar_server')) - context = ACCOUNTS[local_user].getContext(remote_user) + context = get_context(local_user, remote_user) - encrypted_str = config_string('look.bar.state.encrypted') - unencrypted_str = config_string('look.bar.state.unencrypted') - authenticated_str = config_string('look.bar.state.authenticated') - unauthenticated_str = config_string('look.bar.state.unauthenticated') - logged_str = config_string('look.bar.state.logged') - notlogged_str = config_string('look.bar.state.notlogged') + encrypted_str = config_string('look.bar.state.encrypted') + unencrypted_str = config_string('look.bar.state.unencrypted') + authenticated_str = config_string('look.bar.state.authenticated') + unauthenticated_str = config_string('look.bar.state.unauthenticated') + logged_str = config_string('look.bar.state.logged') + notlogged_str = config_string('look.bar.state.notlogged') - bar_parts = [] + bar_parts = [] - if context.is_encrypted(): - if encrypted_str: - bar_parts.append(''.join([ - config_color('status.encrypted'), - encrypted_str, - config_color('status.default')])) + if context.is_encrypted(): + if encrypted_str: + bar_parts.append(''.join([ + config_color('status.encrypted'), + encrypted_str, + config_color('status.default')])) - if context.is_verified(): - if authenticated_str: - bar_parts.append(''.join([ - config_color('status.authenticated'), - authenticated_str, - config_color('status.default')])) - elif unauthenticated_str: + if context.is_verified(): + if authenticated_str: bar_parts.append(''.join([ - config_color('status.unauthenticated'), - unauthenticated_str, + config_color('status.authenticated'), + authenticated_str, config_color('status.default')])) + elif unauthenticated_str: + bar_parts.append(''.join([ + config_color('status.unauthenticated'), + unauthenticated_str, + config_color('status.default')])) - if context.is_logged(): - if logged_str: - bar_parts.append(''.join([ - config_color('status.logged'), - logged_str, - config_color('status.default')])) - elif notlogged_str: + if context.is_logged(): + if logged_str: bar_parts.append(''.join([ - config_color('status.notlogged'), - notlogged_str, + config_color('status.logged'), + logged_str, config_color('status.default')])) - - elif unencrypted_str: + elif notlogged_str: bar_parts.append(''.join([ - config_color('status.unencrypted'), - unencrypted_str, + config_color('status.notlogged'), + notlogged_str, config_color('status.default')])) - result = config_string('look.bar.state.separator').join(bar_parts) + elif unencrypted_str: + bar_parts.append(''.join([ + config_color('status.unencrypted'), + unencrypted_str, + config_color('status.default')])) + + result = config_string('look.bar.state.separator').join(bar_parts) - if result: - result = '{color}{prefix}{result}'.format( - color=config_color('status.default'), - prefix=config_string('look.bar.prefix'), - result=result) + if result: + result = '{color}{prefix}{result}'.format( + color=config_color('status.default'), + prefix=config_string('look.bar.prefix'), + result=result) + + if context.is_encrypted(): + weechat.buffer_set(buf, 'localvar_set_otr_encrypted', 'true') + else: + weechat.buffer_set(buf, 'localvar_set_otr_encrypted', 'false') + + if context.is_verified(): + weechat.buffer_set(buf, 'localvar_set_otr_authenticated', 'true') + else: + weechat.buffer_set(buf, 'localvar_set_otr_authenticated', 'false') + + if context.is_logged(): + weechat.buffer_set(buf, 'localvar_set_otr_logged', 'true') + else: + weechat.buffer_set(buf, 'localvar_set_otr_logged', 'false') return result @@ -1806,8 +1809,7 @@ def buffer_closing_cb(data, signal, signal_data): nick, server = default_peer_args([], signal_data) if nick is not None and server is not None: - context = ACCOUNTS[current_user(server)].getContext( - irc_user(nick, server)) + context = get_server_context(server, nick) context.disconnect() result = weechat.WEECHAT_RC_OK @@ -2030,7 +2032,7 @@ def excepthook(typ, value, traceback): 'smp abort [NICK SERVER] || ' 'trust [NICK SERVER] || ' 'distrust [NICK SERVER] || ' - 'log [on|off] || ' + 'log [start|stop] || ' 'policy [POLICY on|off] || ' 'fingerprint [SEARCH|all]', '', @@ -2043,7 +2045,7 @@ def excepthook(typ, value, traceback): 'smp abort %(nick) %(irc_servers) %-||' 'trust %(nick) %(irc_servers) %-||' 'distrust %(nick) %(irc_servers) %-||' - 'log on|off %-||' + 'log start|stop %-||' 'policy %(otr_policy) on|off %-||' 'fingerprint all %-||', 'command_cb', From 1d595b9be8b677adf1aeee7689f575ddc6a20f0f Mon Sep 17 00:00:00 2001 From: shmibs Date: Tue, 25 Apr 2017 21:38:10 +0200 Subject: [PATCH 022/642] notify_send.pl 1.5: make default command discard stdout/stderr --- perl/notify_send.pl | 99 +++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/perl/notify_send.pl b/perl/notify_send.pl index 8d063aa8..08626bed 100644 --- a/perl/notify_send.pl +++ b/perl/notify_send.pl @@ -1,40 +1,48 @@ -# WeeChat pm and highlight notifications via notify-send +# run system commands on WeeChat pm and highlight, with per-nick smart delays # # modelled after 'WeeChat ubus notifications' by Arvid Picciani # # license: GPL3 -# contact: shmibs@gmail.com +# contact: shmibs@shmibbles.me # history: -# 1.4 another small bug-fix -# 1.3 a small fix to formatting $message -# 1.2 use config options -# 1.1 restructured code for (greater) sanity -# 1.0 first working version +# 1.5 have default command discard output and errors +# 1.4 another small bug-fix +# 1.3 a small fix to formatting $message +# 1.2 use config options +# 1.1 restructured code for (greater) sanity +# 1.0 first working version use strict; use warnings; use constant SCRIPT_NAME => 'notify_send'; -weechat::register(SCRIPT_NAME, 'shmibs', '1.4', 'GPL3', 'execute a user-defined system command upon highlight or private message (with smart delays to avoid spam)', '', ''); +weechat::register(SCRIPT_NAME, 'shmibs', '1.5', 'GPL3', 'execute a user-defined' + . 'system command upon highlight or private message (with smart delays to' + . 'avoid spam)', '', ''); # global var declarations my %pv_times; my %highlight_times; -my %settings_default=( - 'wait_pm' => [ '180', 'necessary time delay between private messages (seconds) for command to be executed' ], - 'wait_highlight' => [ '60', 'necessary time delay between highlights (seconds) for command to be executed' ], - 'ignore_nicks' => [ '', 'comma-separated list of nicks to ignore' ], - 'command' => [ 'notify-send $type: $name', 'system command to be executed ($type, $name, and $message will be interpreted as values)' ] +my %settings_default = ( + 'wait_pm' => [ '180', 'necessary time delay between private messages' + . '(seconds) for command to be executed' ], + 'wait_highlight' => [ '60', 'necessary time delay between highlights' + . '(seconds) for command to be executed' ], + 'ignore_nicks' => [ '', 'comma-separated list of nicks to ignore' ], + 'command' => [ 'notify-send $type: $name &>/dev/null', 'system' + . 'command to be executed ($type, $name, and $message' + . 'will be interpreted as values)' ] ); -my %settings=(); +my %settings = (); -#------------------------------------[ START CONFIGURATION ]------------------------------------ +#----------------------------[ START CONFIGURATION ]---------------------------- sub config_changed { - my ($pointer, $name, $value) = @_; - $name = substr($name, length("plugins.var.perl.".SCRIPT_NAME."."), length($name)); - $settings{$name} = $value; - return weechat::WEECHAT_RC_OK; + my ($pointer, $name, $value) = @_; + $name = substr($name, length("plugins.var.perl." . SCRIPT_NAME . "."), + length($name)); + $settings{$name} = $value; + return weechat::WEECHAT_RC_OK; } sub config_init{ @@ -47,21 +55,24 @@ sub config_init{ $settings{$option} = weechat::config_get_plugin($option); } if ($version >= 0x00030500) { - weechat::config_set_desc_plugin($option, $settings_default{$option}[1]." (default: \"".$settings_default{$option}[0]."\")"); + weechat::config_set_desc_plugin($option, + $settings_default{$option}[1] . " (default: \"" + . $settings_default{$option}[0] . "\")"); } } } config_init(); -weechat::hook_config("plugins.var.perl.".SCRIPT_NAME.".*", "config_changed", ""); +weechat::hook_config("plugins.var.perl." . SCRIPT_NAME . ".*", + "config_changed", ""); -#-------------------------------------[ END CONFIGURATION ]------------------------------------- +#-----------------------------[ END CONFIGURATION ]----------------------------- -my @signals=qw(weechat_pv weechat_highlight); +my @signals = qw(weechat_pv weechat_highlight); # message received hook foreach(@signals) { - weechat::hook_signal($_,'new_notification',''); + weechat::hook_signal($_, 'new_notification', ''); } sub new_notification { @@ -69,12 +80,12 @@ sub new_notification { # $_[2] is the actual content # get the username and message contents - my $name=substr($_[2],0,index($_[2],' ')); - my $message=substr($_[2],index($_[2],' ')); + my $name = substr($_[2], 0, index($_[2], ' ') ); + my $message = substr($_[2], index($_[2], ' ') ); if($name eq ' *'){ - $name=substr($_[2],index($_[2],' *')+3); - $message=substr($name,index($name,' ')); - $name=substr($name,0,index($name,' ')); + $name = substr($_[2], index($_[2], ' *') + 3); + $message = substr($name, index($name, ' ') ); + $name = substr($name,0, index($name, ' ') ); } $message =~ s/ //; $name =~ s/@|\+//; @@ -82,33 +93,34 @@ sub new_notification { # get the type of the message my $type; if($_[1] eq 'weechat_pv') { - $type='PM'; + $type = 'PM'; } else { - $type='HL'; + $type = 'HL'; } # boolean to determine whether or not a notification should # be sent - my $send='true'; + my $send = 'true'; # ignore messages from nicks in ignore_nicks option - foreach(split(/,/,$settings{'ignore_nicks'})) { - $send='false' if($name eq $_); + foreach( split(/,/,$settings{'ignore_nicks'}) ) { + $send = 'false' if($name eq $_); } # determine whether a notification of the same type has been # made recently. if so, ignore it if($type eq 'PM'){ if(exists $pv_times{$name}) { - if(time-$pv_times{$name} < int($settings{'wait_pm'})) { - $send='false'; + if(time - $pv_times{$name} < int($settings{'wait_pm'}) ) { + $send = 'false'; } } $pv_times{$name} = time; } else { if(exists $highlight_times{$name}) { - if(time-$highlight_times{$name} < int($settings{'wait_highlight'})) { - $send='false'; + if(time - $highlight_times{$name} < + int($settings{'wait_highlight'}) ) { + $send = 'false'; } } $highlight_times{$name} = time; @@ -116,11 +128,12 @@ sub new_notification { # run system command if($send eq 'true') { - my ($command,$args) = split(/ /,$settings{'command'},2); - $args =~ s/\$type/$type/g; - $args =~ s/\$name/$name/g; - $args =~ s/\$message/$message/g; - system($command, $args); + my @args = split(/ /,$settings{'command'}); + my $command = shift(@args); + s/\$type/$type/g for @args; + s/\$name/$name/g for @args; + s/\$message/$message/g for @args; + system($command, @args); } return weechat::WEECHAT_RC_OK; From 3034042d77b251efc9eeeaf72ccd17bb3ebba16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Tue, 25 Apr 2017 21:48:08 +0200 Subject: [PATCH 023/642] url_hinter.rb 0.3: add option "launcher" --- ruby/url_hinter.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ruby/url_hinter.rb b/ruby/url_hinter.rb index 5ccd689d..c4fe59e9 100644 --- a/ruby/url_hinter.rb +++ b/ruby/url_hinter.rb @@ -27,6 +27,7 @@ # see also # https://github.com/tkengo/weechat-url-hinter/blob/master/README.md # +# v0.3 : add option "launcher" require 'singleton' @@ -34,7 +35,7 @@ # Register url-hinter plugin to weechat and do initialization. # def weechat_init - Weechat.register('url_hinter', 'Kengo Tateish', '0.2', 'GPL3', 'Open an url in the weechat buffer to type a hint', '', '') + Weechat.register('url_hinter', 'Kengo Tateish', '0.3', 'GPL3', 'Open an url in the weechat buffer to type a hint', '', '') Weechat.hook_command( 'url_hinter', 'Search url strings, and highlight them, and if you type a hint key, open the url related to hint key.', @@ -45,6 +46,11 @@ def weechat_init 'launch_url_hinter', '' ); + option = 'launcher' + if Weechat.config_is_set_plugin(option) == 0 + Weechat.config_set_plugin(option, 'open') + end + Weechat.config_get_plugin(option) Weechat::WEECHAT_RC_OK end @@ -129,7 +135,9 @@ def reset_hint_mode # open specified url # def open_url(url) - Weechat.hook_process("open #{url}", 10000, '', '') + launcher = Weechat.config_get_plugin('launcher') + + Weechat.hook_process("#{launcher} #{url}", 10000, '', '') end #---------------------------- @@ -173,7 +181,8 @@ def reserve(key) end def open_all_reserved_url - Weechat.hook_process("open #{@open_target_urls.join(' ')}", 10000, '', '') if @open_target_urls.any? + launcher = Weechat.config_get_plugin('launcher') + Weechat.hook_process("#{launcher} #{@open_target_urls.join(' ')}", 10000, '', '') if @open_target_urls.any? end def has_key?(key) From 6f79e2595c686b04c3dfa46f84a604f2207e914c Mon Sep 17 00:00:00 2001 From: anonymous2ch Date: Tue, 25 Apr 2017 22:02:02 +0200 Subject: [PATCH 024/642] url_olde.py 0.6: improve URL matching --- python/url_olde.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/url_olde.py b/python/url_olde.py index 41e7d40e..0b528187 100644 --- a/python/url_olde.py +++ b/python/url_olde.py @@ -1,6 +1,7 @@ +# -*- coding: utf-8 -*- """ database init code from https://weechat.org/scripts/source/triggerreply.py.html -regex is from http://stackoverflow.com/questions/6883049/regex-to-find-urls-in-string-in-python +regex is from http://stackoverflow.com/questions/520031/whats-the-cleanest-way-to-extract-urls-from-a-string-using-python TODO ---- @@ -11,7 +12,7 @@ SCRIPT_NAME = "url_olde" SCRIPT_AUTHOR = "Charlie Allom Date: Tue, 25 Apr 2017 22:18:46 +0200 Subject: [PATCH 025/642] iset.pl 4.3: add option "use_color" (closes #93) --- perl/iset.pl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/perl/iset.pl b/perl/iset.pl index 163dfb5e..d6c93642 100644 --- a/perl/iset.pl +++ b/perl/iset.pl @@ -1,6 +1,6 @@ # -# Copyright (C) 2008-2014 Sebastien Helleu -# Copyright (C) 2010-2015 Nils Görs +# Copyright (C) 2008-2017 Sebastien Helleu +# Copyright (C) 2010-2017 Nils Görs # # 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 @@ -19,6 +19,8 @@ # # History: # +# 2017-04-14, nils_2 +# version 4.3: add option "use_color" (https://github.com/weechat/scripts/issues/93) # 2016-07-08, nils_2 # version 4.2: add diff function # 2016-02-06, Sebastien Helleu : @@ -130,7 +132,7 @@ use strict; my $PRGNAME = "iset"; -my $VERSION = "4.2"; +my $VERSION = "4.3"; my $DESCR = "Interactive Set for configuration options"; my $AUTHOR = "Sebastien Helleu "; my $LICENSE = "GPL3"; @@ -619,6 +621,10 @@ sub iset_refresh_line } } my $value = $options_values[$y]; + if (weechat::config_boolean($options_iset{"use_color"}) == 1 and $options_types[$y] eq "color") + { + $value = weechat::color($options_values[$y]) . $options_values[$y]; + } if ($options_is_null[$y]) { $value = "null"; @@ -1509,6 +1515,10 @@ sub iset_config_init $iset_config_file, $section_look, "use_mute", "boolean", "/mute command will be used in input bar", "", 0, 0, "off", "off", 0, "", "", "", "", "", ""); + $options_iset{"use_color"} = weechat::config_new_option( + $iset_config_file, $section_look, + "use_color", "boolean", "display the color value in the corresponding color", "", 0, 0, + "off", "off", 0, "", "", "full_refresh_cb", "", "", ""); } sub iset_config_reload_cb From aaed43cf7f2bb3232a6d73edf5ac6b97bedf515a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Tue, 25 Apr 2017 22:30:25 +0200 Subject: [PATCH 026/642] query_blocker.pl 1.1: add function to ignore server --- perl/query_blocker.pl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/perl/query_blocker.pl b/perl/query_blocker.pl index 09dca245..4ff0250a 100644 --- a/perl/query_blocker.pl +++ b/perl/query_blocker.pl @@ -4,7 +4,7 @@ # # ----------------------------------------------------------------------------- # Copyright (c) 2009-2014 by rettub -# Copyright (c) 2011-2016 by nils_2 +# Copyright (c) 2011-2017 by nils_2 # # 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 @@ -38,7 +38,11 @@ # # ----------------------------------------------------------------------------- # History: -# 2016-12-11, mumixam : +# 2017-04-14, nils_2: +# version 1.1: +# ADD: function to ignore server (https://github.com/weechat/scripts/issues/79) +# +# 2016-12-11, mumixam: # version 1.0: # FIX: message starting with color not caught # @@ -102,7 +106,7 @@ my $SCRIPT = 'query_blocker'; my $AUTHOR = 'rettub '; -my $VERSION = '1.0'; +my $VERSION = '1.1'; my $LICENSE = 'GPL3'; my $DESCRIPTION = 'Simple blocker for private message (i.e. spam)'; my $COMMAND = "query_blocker"; # new command name @@ -158,6 +162,7 @@ - to allow private messages from certain nicks, put them into the whitelist, type '/$COMMAND add nick' (you can use nick-completion). if you start a query, the nick will be added as a temporary nick. the nick will be removed when you close query - to remove a nick from the whitelist, type '/$COMMAND del nick' (you can use nick-completion). + - you can add a localvar for a specific server to disable $COMMAND for this server: /buffer set localvar_set_query_blocker 1 NOTE: If you load $SCRIPT the first time, blocking of private messages is disabled, you have to enable blocking, type '/$COMMAND on'. EO_HELP @@ -378,6 +383,9 @@ sub modifier_irc_in_privmsg { my ( $data, $signal, $server, $arg ) = @_; my $my_nick = weechat::info_get( 'irc_nick', $server ); + # by default, blocking is enabled for all server. except the one with a localvar + return $arg if (weechat::buffer_get_string(weechat::buffer_search("irc", "server.".$server), 'localvar_query_blocker')); + # check for query message if ( $arg =~ m/:(.+?)!.+? PRIVMSG $my_nick :(.*)/i ) { my $query_nick = $1; From f3809a6df4f3f580a7d10c83ac7d83d0a54840e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Wed, 26 Apr 2017 22:16:09 +0200 Subject: [PATCH 027/642] queryman.py 0.4: allow manual saving of query list, code refactoring --- python/queryman.py | 223 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 188 insertions(+), 35 deletions(-) diff --git a/python/queryman.py b/python/queryman.py index c979f93f..cb0903b4 100644 --- a/python/queryman.py +++ b/python/queryman.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2013-2016 by nils_2 +# Copyright (c) 2013-2017 by nils_2 , Filip H.F. "FiXato" Slagter # # save and restore query buffers after /quit # @@ -19,7 +19,15 @@ # # idea by lasers@freenode.#weechat # -# 2016-02-27: nils_2, (freenode.#weechat) +# 2017-04-14: nils_2 & FiXato, (freenode.#weechat) +# 0.4 : big rewrite: +# : added extra hooks: +# - query buffers are now also stored when opening/closing queries +# - queries only restored on connect; no longer on every reconnect +# : current buffer position is retained +# : manual saving of query list (https://github.com/weechat/scripts/issues/196) +# +# 2015-02-27: nils_2, (freenode.#weechat) # 0.3 : make script consistent with "buffer_switch_autojoin" option (idea haasn) # # 2013-11-07: nils_2, (freenode.#weechat) @@ -45,21 +53,77 @@ SCRIPT_NAME = 'queryman' SCRIPT_AUTHOR = 'nils_2 ' -SCRIPT_VERSION = '0.3' +SCRIPT_VERSION = '0.4' SCRIPT_LICENSE = 'GPL' -SCRIPT_DESC = 'save and restore query buffers after /quit' +SCRIPT_DESC = 'save and restore query buffers after /quit and on open/close of queries' +DEBUG = False -query_buffer_list = [] queryman_filename = 'queryman.txt' +servers_opening = set([]) +servers_closing = set([]) +stored_query_buffers_per_server = {} # ================================[ callback ]=============================== +# signal_data = buffer pointer +def buffer_closing_signal_cb(data, signal, signal_data): + global servers_closing + + buf_type = weechat.buffer_get_string(signal_data,'localvar_type') + # When closing a server buffer, save all buffers + if buf_type == 'server': + # Prevent closing private buffers on this server, from triggering saving. + servers_closing.add(weechat.buffer_get_string(signal_data, 'localvar_server')) + + # # FIXME: This shouldn't be necessary, as buffers are already save when opened/closed + # reset_stored_query_buffers() + # save_stored_query_buffers_to_file() + # Only save the query buffers + elif buf_type == 'private': + server_name = weechat.buffer_get_string(signal_data, 'localvar_server') + # Don't trigger when all buffer's closing because its server buffer is closing + if server_name not in servers_closing: + channel_name = weechat.buffer_get_string(signal_data, 'localvar_channel') + remove_channel_from_stored_list(server_name, channel_name) + save_stored_query_buffers_to_file() + return weechat.WEECHAT_RC_OK + def quit_signal_cb(data, signal, signal_data): - save_query_buffer_to_file() + reset_stored_query_buffers() + save_stored_query_buffers_to_file() + return weechat.WEECHAT_RC_OK + +# signal_data = buffer pointer +def irc_pv_opened_cb(data, signal, signal_data): + server_name = weechat.buffer_get_string(signal_data, 'localvar_server') + channel_name = weechat.buffer_get_string(signal_data, 'localvar_channel') + add_channel_to_stored_list(server_name, channel_name) + save_stored_query_buffers_to_file() + return weechat.WEECHAT_RC_OK + +# signal_data = server name +def remove_server_from_servers_closing_cb(data, signal, signal_data): + global servers_closing + if signal_data in servers_closing: + servers_closing.remove(signal_data) return weechat.WEECHAT_RC_OK -# signal_data contains servername +# signal_data = buffer pointer +def irc_server_opened_cb(data, signal, signal_data): + global servers_opening + + server_name = weechat.buffer_get_string(signal_data, 'localvar_server') + servers_opening.add(server_name) + return weechat.WEECHAT_RC_OK + +# signal_data = servername def irc_server_connected_signal_cb(data, signal, signal_data): - load_query_buffer_irc_server_opened(signal_data) + global servers_opening + + # Only reopen the query buffers if the server buffer was recently opened + if signal_data in servers_opening: + open_stored_query_buffers_for_server(signal_data) + servers_opening.remove(signal_data) + return weechat.WEECHAT_RC_OK # ================================[ file ]=============================== @@ -68,63 +132,152 @@ def get_filename_with_path(): path = weechat.info_get("weechat_dir", "") return os.path.join(path,queryman_filename) -def load_query_buffer_irc_server_opened(server_connected): - global query_buffer_list +# ======== [ Stored Query Buffers List ] ========== +def get_stored_list_of_query_buffers(): + global stored_query_buffers_per_server filename = get_filename_with_path() + stored_query_buffers_per_server = {} if os.path.isfile(filename): f = open(filename, 'rb') for line in f: - servername,nick = line.split(' ') - if servername == server_connected: - noswitch = "" - switch_autojoin = weechat.config_get("irc.look.buffer_switch_autojoin") - if not weechat.config_boolean(switch_autojoin): - noswitch = "-noswitch" - weechat.command('','/query %s -server %s %s' % ( noswitch, servername, nick )) + server_name,nick = line.strip().split(' ') + stored_query_buffers_per_server.setdefault(server_name, set([])) + stored_query_buffers_per_server[server_name].add(nick) f.close() else: - weechat.prnt('','%s%s: Error loading query buffer from "%s"' % (weechat.prefix('error'), SCRIPT_NAME, filename)) + debug_print('Error loading query buffer from "%s"' % filename) + return stored_query_buffers_per_server -def save_query_buffer_to_file(): - global query_buffer_list +def remove_channel_from_stored_list(server_name, channel_name): + global stored_query_buffers_per_server - ptr_infolist_buffer = weechat.infolist_get('buffer', '', '') + if server_name in stored_query_buffers_per_server and channel_name in stored_query_buffers_per_server[server_name]: + stored_query_buffers_per_server[server_name].remove(channel_name) + if not len(stored_query_buffers_per_server[server_name]): + stored_query_buffers_per_server.pop(server_name, None) + +def add_channel_to_stored_list(server_name, channel_name): + global stored_query_buffers_per_server + + if server_name not in stored_query_buffers_per_server: + stored_query_buffers_per_server[server_name] = set([]) + if channel_name not in stored_query_buffers_per_server[server_name]: + stored_query_buffers_per_server[server_name].add(channel_name) + +def open_query_buffer(server_name, nick): + starting_buffer = weechat.current_buffer() + noswitch = "" + switch_autojoin = weechat.config_get("irc.look.buffer_switch_autojoin") + if not weechat.config_boolean(switch_autojoin): + noswitch = "-noswitch" + weechat.command('','/query %s -server %s %s' % ( noswitch, server_name, nick )) + weechat.buffer_set(starting_buffer, 'display', 'auto') +def open_stored_query_buffers_for_server(server_connected): + global stored_query_buffers_per_server + + if server_connected in stored_query_buffers_per_server: + for nick in stored_query_buffers_per_server[server_connected].copy(): + open_query_buffer(server_connected, nick) + +def get_current_query_buffers(): + stored_query_buffers_per_server = {} + + ptr_infolist_buffer = weechat.infolist_get('buffer', '', '') while weechat.infolist_next(ptr_infolist_buffer): ptr_buffer = weechat.infolist_pointer(ptr_infolist_buffer,'pointer') - type = weechat.buffer_get_string(ptr_buffer, 'localvar_type') - if type == 'private': - server = weechat.buffer_get_string(ptr_buffer, 'localvar_server') - channel = weechat.buffer_get_string(ptr_buffer, 'localvar_channel') - query_buffer_list.insert(0,"%s %s" % (server,channel)) + buf_type = weechat.buffer_get_string(ptr_buffer, 'localvar_type') + if buf_type == 'private': + server_name = weechat.buffer_get_string(ptr_buffer, 'localvar_server') + channel_name = weechat.buffer_get_string(ptr_buffer, 'localvar_channel') + stored_query_buffers_per_server.setdefault(server_name, set([])) + stored_query_buffers_per_server[server_name].add(channel_name) weechat.infolist_free(ptr_infolist_buffer) + return stored_query_buffers_per_server + +def reset_stored_query_buffers(): + global stored_query_buffers_per_server + stored_query_buffers_per_server = get_current_query_buffers() + + +def remove_data_file(): filename = get_filename_with_path() + if os.path.isfile(filename): + os.remove(filename) + +def save_stored_query_buffers_to_file(): + global stored_query_buffers_per_server - if len(query_buffer_list): + filename = get_filename_with_path() + if len(stored_query_buffers_per_server): + debug_print("Storing %s servers:" % len(stored_query_buffers_per_server)) try: f = open(filename, 'w') - i = 0 - while i < len(query_buffer_list): - f.write('%s\n' % query_buffer_list[i]) - i = i + 1 + for (server_name, channels) in stored_query_buffers_per_server.items(): + debug_print("Storing %s channels in server %s" % (len(channels), server_name)) + for channel_name in channels: + line = "%s %s" % (server_name,channel_name) + debug_print(' - %s' % line) + f.write("%s\n" % line) f.close() except: - weechat.prnt('','%s%s: Error writing query buffer to "%s"' % (weechat.prefix('error'), SCRIPT_NAME, filename)) + print_error('Error writing query buffer to "%s"' % filename) raise else: # no query buffer(s). remove file - if os.path.isfile(filename): - os.remove(filename) + debug_print("No stored query buffers; removing data file") + remove_data_file() return +def print_error(message): + weechat.prnt('','%s%s: %s' % (weechat.prefix('error'), SCRIPT_NAME, message)) + +def debug_print(message): + if not DEBUG: + return + weechat.prnt('','DEBUG/%s: %s' % (SCRIPT_NAME, message)) + +def hook_command_cb(data, buffer, args): + if args == "": # no args given. quit + return weechat.WEECHAT_RC_OK + argv = args.strip().split(" ") + if argv[0].lower() == 'save': + save_stored_query_buffers_to_file() + return weechat.WEECHAT_RC_OK + # ================================[ main ]=============================== if __name__ == '__main__': if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''): version = weechat.info_get('version_number', '') or 0 if int(version) >= 0x00030700: + weechat.hook_command(SCRIPT_NAME,SCRIPT_DESC, + 'save', + 'save : manual saving of the query list\n', + '', + 'hook_command_cb', '') + + stored_query_buffers_per_server = get_stored_list_of_query_buffers() + for (server_name, channels) in get_current_query_buffers().items(): + # Reopen the buffers for the channels in the servers we already have open: + open_stored_query_buffers_for_server(server_name) + + stored_query_buffers_per_server.setdefault(server_name, set([])) + debug_print("Already have %s channels for server %s: %s" % (len(stored_query_buffers_per_server[server_name]), server_name, ','.join(stored_query_buffers_per_server[server_name]))) + debug_print("Adding: %s" % channels) + stored_query_buffers_per_server[server_name].update(channels) + debug_print("Now have %s channels for server %s: %s" % (len(stored_query_buffers_per_server[server_name]), server_name, ','.join(stored_query_buffers_per_server[server_name]))) + save_stored_query_buffers_to_file() weechat.hook_signal('quit', 'quit_signal_cb', '') - weechat.hook_signal('irc_server_connected', 'irc_server_connected_signal_cb', '') +# weechat.hook_signal('relay_client_disconnected', 'quit_signal_cb', '') +# weechat.hook_signal('relay_client_connected', 'irc_server_connected_signal_cb', '') + weechat.hook_signal('irc_server_opened', 'irc_server_opened_cb', '') + weechat.hook_signal('irc_server_connected', 'irc_server_connected_signal_cb','') + weechat.hook_signal('irc_server_disconnected', 'remove_server_from_servers_closing_cb', '') + + # TODO: make these triggers optional? + weechat.hook_signal('irc_pv_opened', 'irc_pv_opened_cb', '') + weechat.hook_signal('buffer_closing', 'buffer_closing_signal_cb', '') From d6ccfd6c9456bba34060821c64be1dd644c9dbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Mon, 1 May 2017 08:59:08 +0200 Subject: [PATCH 028/642] update_notifier.py 0.5: fix URL with infos, fix download of infos, fix PEP8 errors and refactor code (closes #211) --- python/update_notifier.py | 356 ++++++++++++++++++++------------------ 1 file changed, 187 insertions(+), 169 deletions(-) diff --git a/python/update_notifier.py b/python/update_notifier.py index 99cdfd8d..3c4dbdee 100644 --- a/python/update_notifier.py +++ b/python/update_notifier.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2010-2010 by drubin +# Copyright (c) 2010 by drubin +# Copyright (c) 2017 Sébastien Helleu # # 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 @@ -16,29 +17,35 @@ # along with this program. If not, see . -# Allows you to visually see if there are updates to your weechat system - -#Versions -# 0.4 drubin - changed default weechat hostname -# 0.3 nils_2 - third release. -# - *fixed bug* every time the item_bar was updated, script did a read access to homepage -# - get python 2.x binary for hook_process (fix problem when python 3.x is default python -# version, requires WeeChat >= 0.3.4) -# - new option "git pull". executes "git pull" if "true" and a new git version is available -# 0.2 nils_2 - second release. -# - countdown to next stable release added. -# - now using hook_signal(day_changed) instead of hook_timer() -# └-> option "update_interval" is obsolet now. -# - hook_config() added. -# - type missmatched removed if git_compile_location wasn't set -# 0.1 drubin - First release. -# - Basic functionality with url getting and compairing version. - -SCRIPT_NAME = "update_notifier" -SCRIPT_AUTHOR = "drubin " -SCRIPT_VERSION = "0.4" +# Allows you to visually see if there are updates to your weechat system. + +# Versions: +# +# 0.5 flashcode - fix URL with infos, fix download of infos, fix PEP8 errors +# and refactor code +# 0.4 drubin - changed default weechat hostname +# 0.3 nils_2 - third release. +# - *fixed bug* every time the item_bar was updated, script did a +# read access to homepage +# - get python 2.x binary for hook_process (fix problem when +# python 3.x is default python +# version, requires WeeChat >= 0.3.4) +# - new option "git pull". executes "git pull" if "true" and a +# new git version is available +# 0.2 nils_2 - second release. +# - countdown to next stable release added. +# - now using hook_signal(day_changed) instead of hook_timer() +# └-> option "update_interval" is obsolet now. +# - hook_config() added. +# - type missmatched removed if git_compile_location wasn't set +# 0.1 drubin - First release. +# - Basic functionality with url getting and compairing version. + +SCRIPT_NAME = "update_notifier" +SCRIPT_AUTHOR = "drubin " +SCRIPT_VERSION = "0.5" SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Notifiers users of updates to weechat." +SCRIPT_DESC = "Notifiers users of updates to weechat." import_ok = True import os @@ -46,109 +53,153 @@ try: import weechat except ImportError: - print "This script must be run under WeeChat." - print "Get WeeChat now at: http://www.weechat.org/" + print("This script must be run under WeeChat.") + print("Get WeeChat now at: https://weechat.org/") import_ok = False - - + + # script options settings = { - "uses_git" : "false", - "uses_devel" : "false", - "git_pull" : "false", - "git_compile_location" : "", - "update_text" : "New devel version available", - "update_text_stable" : "New stable version %s available", - "start_counting" : "30", - "start_countdown" : "10", - "next_stable_text" : "%d day(s) left to version %s", - "color_default" : "default", - "color_countdown" : "red", + "uses_git": "false", + "uses_devel": "false", + "git_pull": "false", + "git_compile_location": "", + "update_text": "New devel version available", + "update_text_stable": "New stable version %s available", + "start_counting": "30", + "start_countdown": "10", + "next_stable_text": "%d day(s) left to version %s", + "color_default": "default", + "color_countdown": "red", } -infos = ["stable","stable_number","git","next_stable","next_stable_number","next_stable_date"] - -#Not a config because it should not change ever -BASE_URL = "http://www.weechat.org/info/" -BAR_NAME = "updnotf" +# Not a config because it should not change ever +URL_INFOS = "https://weechat.org/dev/info/all/" +INFOS_FILENAME = "infos.txt" +BAR_ITEM_NAME = "updnotf" -#List of proccesses -un_hook_process = {} - -#How long to wait for download +# How long to wait for download TIMEOUT_DOWNLOAD = 60 * 1000 + def un_cache_dir(): - filename = weechat.info_get("weechat_dir", "") + os.sep+ SCRIPT_NAME + filename = os.path.join(weechat.info_get("weechat_dir", ""), SCRIPT_NAME) if not os.path.isdir(filename): os.makedirs(filename, mode=0700) return filename - -def get_version(isnumber = False): - """Gets the version number of weechat, both number and string""" - if isnumber: + + +def get_version(as_number=False): + """Gets the version number of weechat, both number and string.""" + if as_number: return weechat.info_get("version_number", "") else: return weechat.info_get("version", "") - -def full_file_name(filename): - path = un_cache_dir() + os.sep + filename - return path -def un_timer_cache(date,remaning,dropit): - un_update_cache() - return weechat.WEECHAT_RC_OK +def get_filename_infos(): + """Return the path to the file with infos.""" + return os.path.join(un_cache_dir(), INFOS_FILENAME) + + +def get_cur_git_version(): + path = weechat.config_get_plugin("git_compile_location") + if path == "": + return None + + f = os.popen("cd %s && git rev-parse HEAD" % path) + stuff = f.readline() + f.close() + return stuff.strip() + + +def do_git_pull(): + if (weechat.config_get_plugin("git_pull") == "false"): + return + path = weechat.config_get_plugin("git_compile_location") + if path != "": + f = os.popen("cd %s && git pull 2>&1" % path) + stuff = f.readline() + weechat.prnt("", weechat.prefix("action") + + weechat.color(weechat.config_color( + weechat.config_get("weechat.color.chat_nick_self"))) + + SCRIPT_NAME + ":") + weechat.prnt("", stuff) + f.close() + + +def get_info_ver(info): + with open(get_filename_infos(), "r") as f: + for line in f.readlines(): + items = line.strip().split(":", 1) + if len(items) == 2 and items[0] == info: + return items[1] + return None + + +def un_download_cb(filename, command, rc, stdout, stderr): + """Callback on download of URL.""" + + if rc != 0: + return weechat.WEECHAT_RC_OK + + weechat.bar_item_update(BAR_ITEM_NAME) -def un_update_cache(): - for info in infos: - un_download_url(BASE_URL+info,info) - """ Callback for building update item. """ version = get_version() - version_num = get_version(True) + version_num = get_version(as_number=True) compare_version = "" compare_version_num = "" update_avaliable = False global next_stable_text next_stable_text = "" -# check for stable version first + # check for stable version first start_counting = weechat.config_get_plugin("start_counting") if start_counting != "": next_stable_date = get_info_ver("next_stable_date") lt = localtime() - year, month, day = lt[0:3] # today - next_stable_date = next_stable_date.split("-") # next_stable_date - next_stable_date = int(next_stable_date[0]),int(next_stable_date[1]),int(next_stable_date[2]),0,0,0,0,0,0 + year, month, day = lt[0:3] # today + next_stable_date = next_stable_date.split("-") # next_stable_date + next_stable_date = (int(next_stable_date[0]), int(next_stable_date[1]), + int(next_stable_date[2]), 0, 0, 0, 0, 0, 0) next_stable_date = mktime(next_stable_date) - today = year,month,day, 0, 0, 0, 0, 0, 0 + today = year, month, day, 0, 0, 0, 0, 0, 0 today = mktime(today) - diff_day = (next_stable_date - today)/60/60/24 # calculate days till next_stable_date + # calculate days till next_stable_date + diff_day = (next_stable_date - today)/60/60/24 diff_day = "%1i" % (diff_day) -# diff_day = 0 # test to pop up new stable text + # diff_day = 0 # test to pop up new stable text if (int(diff_day) > 0) and (int(diff_day) <= int(start_counting)): - used_color = weechat.config_get_plugin("color_default") - if (int(diff_day) <= int(weechat.config_get_plugin("start_countdown"))): # TEN and counting.... - used_color = weechat.config_get_plugin("color_countdown") - - next_stable_text = weechat.config_get_plugin("next_stable_text") - next_stable = get_info_ver("next_stable") - if next_stable_text == "": - next_stable_text = ("days left:" + weechat.color(used_color) + "%d" + weechat.color("reset") + " to stable: %s") % (int(diff_day),next_stable) - else: - next_stable_text = next_stable_text.replace("%d", weechat.color(used_color) + "%d" + weechat.color("reset")) - if next_stable_text.find("%s") >= 1: # check for %s - next_stable_text = next_stable_text % (int(diff_day),next_stable) - else: - next_stable_text = next_stable_text % int(diff_day) - update_avaliable = False - elif (int(diff_day) <= 0): # today a new stable version is available - stable_number = get_info_ver("stable") - next_stable_text = weechat.config_get_plugin("update_text_stable") - if next_stable_text.find("%s") >= 1: # %s in string? - next_stable_text = (next_stable_text % stable_number) - return next_stable_text # new stable version + used_color = weechat.config_get_plugin("color_default") + # TEN and counting.... + if (int(diff_day) <= + int(weechat.config_get_plugin("start_countdown"))): + used_color = weechat.config_get_plugin("color_countdown") + + next_stable_text = weechat.config_get_plugin("next_stable_text") + next_stable = get_info_ver("next_stable") + if next_stable_text == "": + next_stable_text = ("days left:" + weechat.color(used_color) + + "%d" + weechat.color("reset") + + " to stable: %s") % ( + int(diff_day), next_stable) + else: + next_stable_text = next_stable_text.replace( + "%d", weechat.color(used_color) + "%d" + + weechat.color("reset")) + if next_stable_text.find("%s") >= 1: # check for %s + next_stable_text = next_stable_text % ( + int(diff_day), next_stable) + else: + next_stable_text = next_stable_text % int(diff_day) + update_avaliable = False + elif (int(diff_day) <= 0): # today a new stable version is available + stable_number = get_info_ver("stable") + next_stable_text = weechat.config_get_plugin("update_text_stable") + if "%s" in next_stable_text >= 1: # %s in string? + next_stable_text = (next_stable_text % stable_number) + return weechat.WEECHAT_RC_OK if weechat.config_get_plugin("uses_devel") == "true": compare_version_num = get_info_ver("next_stable_number") @@ -156,100 +207,67 @@ def un_update_cache(): update_avaliable = int(compare_version_num) > int(version_num) elif weechat.config_get_plugin("uses_git") == "true": git_cur = get_cur_git_version() - if git_cur != False: # path to git dir exists? - git_ver = get_info_ver("git") # yes - compare_version = git_cur - update_avaliable = get_cur_git_version() != git_ver - if update_avaliable != False: - do_git_pull() # call git pull + if git_cur is not None: # path to git dir exists? + git_ver = get_info_ver("git") # yes + compare_version = git_cur + update_avaliable = get_cur != git_ver + if update_avaliable: + do_git_pull() # call git pull else: - update_avaliable = False + update_avaliable = False else: compare_version_num = get_info_ver("stable_number") compare_version = get_info_ver("stable") update_avaliable = int(compare_version_num) > int(version_num) - + if update_avaliable: - next_stable_text = weechat.config_get_plugin("update_text") - return next_stable_text - else: - if next_stable_text != "": - return next_stable_text - else: - next_stable_text = "" - return next_stable_text + next_stable_text = weechat.config_get_plugin("update_text") - -def get_cur_git_version(): - path = weechat.config_get_plugin("git_compile_location") - if path != "": - f = os.popen("cd %s && git rev-parse HEAD" % path) - stuff = f.readline() - f.close() - return stuff.strip() - else: - return False + return weechat.WEECHAT_RC_OK -def do_git_pull(): - if (weechat.config_get_plugin("git_pull") == "false"): - return - path = weechat.config_get_plugin("git_compile_location") - if path != "": - f = os.popen("cd %s && git pull 2>&1" % path) - stuff = f.readline() - weechat.prnt("",weechat.prefix("action") + weechat.color(weechat.config_color(weechat.config_get("weechat.color.chat_nick_self"))) + SCRIPT_NAME + ":") - weechat.prnt("",stuff) - f.close() - -def get_info_ver(info): - filecontents = file(full_file_name(info)).read().strip() - return filecontents - -def un_download_url(url, filename): - pathfile = full_file_name(filename) - python2_bin = weechat.info_get("python2_bin", "") or "python" - un_hook_process[filename] = weechat.hook_process( - python2_bin + " -c \"import urllib, urllib2\n" - "req = urllib2.Request('" + url + "')\n" - "try:\n" - " response = urllib2.urlopen(req)\n" - " file = open('" + pathfile + "', 'w')\n" - " file.write(response.read())\n" - " response.close()\n" - " file.close()\n" - "except urllib2.URLError, e:\n" - " print 'error:%s' % e.code\n" - "\"", - TIMEOUT_DOWNLOAD, "un_download_cb", filename) -def un_download_cb(filename, command, rc, stdout, stderr): - un_hook_process[filename] = "" - #Update configs.. - weechat.bar_item_update(BAR_NAME) +def un_update_cache(): + """Callback for building update item.""" + weechat.hook_process_hashtable("url:%s" % URL_INFOS, + {"file_out": get_filename_infos()}, + TIMEOUT_DOWNLOAD, + "un_download_cb", "") + + +def un_day_changed(data, signal, signal_data): + un_update_cache() + return weechat.WEECHAT_RC_OK + + +def un_config_changed(data, option, value): + un_update_cache() return weechat.WEECHAT_RC_OK - + + def up_item_cb(data, buffer, args): - return next_stable_text + return next_stable_text + def un_cmd(data, buffer, args): un_update_cache() - return weechat.WEECHAT_RC_OK -if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "", ""): + +if __name__ == '__main__' and import_ok and \ + weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, "", ""): for option, default_value in settings.iteritems(): - if weechat.config_get_plugin(option) == "": - if option != "start_counting": + if not weechat.config_is_set_plugin(option): weechat.config_set_plugin(option, default_value) - + weechat.hook_command("upgrade_check", "Checks for upgrades", "", - "", - "", "un_cmd", "") - - - weechat.bar_item_new(BAR_NAME, 'up_item_cb', '') - weechat.hook_signal("day_changed","un_timer_cache","") - weechat.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", "un_timer_cache", "") - #Update the cache when it starts up. + "The bar item is called \"%s\"." % BAR_ITEM_NAME, + "", "un_cmd", "") + + weechat.bar_item_new(BAR_ITEM_NAME, 'up_item_cb', '') + weechat.hook_signal("day_changed", "un_day_changed", "") + weechat.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", + "un_config_changed", "") + + # Update the cache when it starts up. un_update_cache() From 726bee45c728b25a56f36f65cd897ba3875da103 Mon Sep 17 00:00:00 2001 From: Ferus Castor Date: Sun, 7 May 2017 16:37:24 +0200 Subject: [PATCH 029/642] New script buffer_dmenu.py: list buffers in dmenu or rofi, change active window to selected buffer --- python/buffer_dmenu.py | 215 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 python/buffer_dmenu.py diff --git a/python/buffer_dmenu.py b/python/buffer_dmenu.py new file mode 100644 index 00000000..32d67aad --- /dev/null +++ b/python/buffer_dmenu.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 by Ferus Castor +# +# 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 . +# + + +# Select a buffer from dmenu or rofi +# To call externally (IE: from i3), enable weechat fifo and run: +# $ echo "core.weechat */buffer_dmenu" >> $(find ~/.weechat -type p) +# +# Optionally requires i3-py [py2] (or i3ipc [py3]) to focus weechat in i3 +# +# History: +# 2017-05-03, Ferus +# version 0.1.1: fix argument error for config_set_plugin +# 2016-05-01, Ferus +# version 0.1: initial release - requires WeeChat ≥ 0.3.7 + +# TODO: +# Option to remove certain buffer types +# Implement `focus` for other window managers +# if buffer == currentbuffer: switch to previous buffer + +#pylint: disable=I0011,W0603,W1401 + +SCRIPT_NAME = "buffer_dmenu" +SCRIPT_AUTHOR = "Ferus " +SCRIPT_VERSION = "0.1.1" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "List buffers in dmenu (or rofi), changes active window to selected buffer" +SCRIPT_COMMAND = "buffer_dmenu" + +import os +import sys +import subprocess + +try: + import weechat as w +except ImportError as e: + print("This script must be run under WeeChat.") + exit(1) + +try: + if sys.version_info[0] == 3: + import i3ipc as i3 + else: + import i3 + have_i3 = True +except ImportError as e: + have_i3 = False + +settings = {"launcher": ("dmenu", "launcher to use (supported: dmenu/rofi)") + ,"focus" : ("false", "whether to immediately focus the terminal after selecting buffer") + ,"focus.wm" : ("i3", "wm focus logic to use (supported: i3)") + ,"dmenu.command" : ("dmenu -b -i -l 20", "command used to call dmenu") + ,"rofi.command" : ("rofi -p '# ' -dmenu -lines 10 -columns 8 -auto-select -mesg 'Pick a buffer to jump to:'", "command used to call rofi") + ,"title.regex" : ("WeeChat \d+\.\d+", "regex used to match weechat's title window") +} + + +def check_dmenu(): + devnull = open(os.devnull) + retcode = subprocess.call(["which", "dmenu"], stdout=devnull, stderr=devnull) + return True if retcode == 0 else False + + +def check_rofi(): + devnull = open(os.devnull) + retcode = subprocess.call(["which", "rofi"], stdout=devnull, stderr=devnull) + return True if retcode == 0 else False + + +def get_launcher(): + launcher = w.config_get_plugin("launcher") + command = None + if launcher == "dmenu": + if check_dmenu(): + command = w.config_get_plugin("dmenu.command") + elif launcher == "rofi": + if check_rofi(): + command = w.config_get_plugin("rofi.command") + return command + + +def launch(options): + launcher = get_launcher() + if launcher: + call(launcher, options) + return True + + +def focus(): + if w.config_string_to_boolean(w.config_get_plugin("focus")): + if w.config_get_plugin("focus.wm") == "i3": + focus_i3() + + +def focus_i3(): + if have_i3: + regex = w.config_get_plugin("title.regex") + + #py3 + if sys.version_info[0] == 3: + i3conn = i3.Connection() + weechat = i3conn.get_tree().find_named(regex)[0] + weechat.command('focus') + + #py2 + else: + i3.focus(title=regex) + + +def call(command, options): + options = "\n".join(options).encode("utf-8") + + w.hook_process_hashtable("sh" + ,{"arg1": "-c","arg2": 'echo "{0}" | {1}'.format(options, command)} + ,10 * 1000 + ,"launch_process_cb" + ,"" + ) + + +process_output = "" +def launch_process_cb(data, command, rc, out, err): + global process_output + if out != "": + process_output += out + if int(rc) >= 0: + selected = process_output.decode("utf-8").strip("\n") + number, name = selected.split(":") + process_output = "" + switch_to_buffer(name) + focus() + return w.WEECHAT_RC_OK + + +def get_open_buffers(): + buffers = [] + infolist = w.infolist_get("buffer", "", "") + if infolist: + while w.infolist_next(infolist): + name = w.infolist_string(infolist, "name") + number = w.infolist_integer(infolist, "number") + _ = "{0}:{1}".format(number, name).decode("utf-8") + buffers.append(_) + w.infolist_free(infolist) + return buffers + + +def get_hotlist_buffers(): + buffers = [] + infolist = w.infolist_get("hotlist", "", "") + if infolist: + while w.infolist_next(infolist): + number = w.infolist_integer(infolist, "buffer_number") + buffer = w.infolist_pointer(infolist, "buffer_pointer") + name = w.buffer_get_string(buffer, "name") + _ = "{0}:{1}".format(number, name).decode("utf-8") + buffers.append(_) + w.infolist_free(infolist) + return buffers + + +def switch_to_buffer(buffer_name): + w.command("", "/buffer {0}".format(buffer_name)) + + +def dmenu_cmd_cb(data, buffer, args): + """ Command /buffers_dmenu """ + if args == "hotlist": + buffers = get_hotlist_buffers() + else: + buffers = get_open_buffers() + + if not launch(buffers): + return w.WEECHAT_RC_ERROR + return w.WEECHAT_RC_OK + + +if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + version = w.info_get('version_number', '') or 0 + for option, value in settings.items(): + if not w.config_is_set_plugin(option): + w.config_set_plugin(option, value[0]) + if int(version) >= 0x00030500: + w.config_set_desc_plugin(option, '{0} (default: "{1}")'.format(value[1], value[0])) + + w.hook_command(SCRIPT_COMMAND + ,"Show a list of all buffers in dmenu" + ,"[hotlist]" + ," hotlist: shows hotlist buffers only\n" + "\n" + "To call externally (IE: from i3), enable weechat fifo and run:\n" + " $ echo 'core.weechat */buffer_dmenu' >> $(find ~/.weechat -type p)\n" + "\n" + "To focus the terminal containing WeeChat for the following WM:\n" + " i3: requires i3-py [py2] (or i3ipc [py3]) from pip\n" + ,"" + ,"dmenu_cmd_cb" + ,"" + ) From 52dec5c0951be6f0b45391a59263e40f9c331d08 Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Sun, 7 May 2017 21:53:49 +0200 Subject: [PATCH 030/642] weetweet.py 1.2.6: make script python3 compatible --- python/weetweet.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/python/weetweet.py b/python/weetweet.py index 668aab1c..7e8e9f78 100644 --- a/python/weetweet.py +++ b/python/weetweet.py @@ -40,6 +40,9 @@ try: import weechat + #We need to import sys to see which python version weechat runs (2 or 3) + import sys + is_py3 = sys.version_info >= (3, 0) except: #import html parser so we can convert html strings to plain text try: @@ -232,12 +235,8 @@ def remove_from_nicklist(buf, nick, group=""): def parse_for_nicks(text,buffer): #Parse text for twitter nicks and add them to nicklist regex = re.compile(r'@([A-Za-z0-9_]+)') - reset = weechat.color('reset') - for word in text.split(): - match = re.search(regex,word) - if str(type(match)) == "": - nick = word[match.start(1):match.end(0)] - add_to_nicklist(buffer,nick,tweet_nicks_group[buffer]) + for nick in regex.findall(text): + add_to_nicklist(buffer,nick,tweet_nicks_group[buffer]) def print_tweet_data(buffer,tweets,data): @@ -274,7 +273,7 @@ def print_tweet_data(buffer,tweets,data): except: pass -def trim_tweet_data(tweet_data, screen_name, alt_rt_style): +def trim_tweet_data(tweet_data, screen_name, alt_rt_style, is_py3): # Because of the huge amount of data, we need to cut down on most of it because we only really want # a small subset of it. This also prevents the output buffer from overflowing when fetching many tweets # at once. @@ -295,10 +294,12 @@ def trim_tweet_data(tweet_data, screen_name, alt_rt_style): message['retweeted_status']['text']) mes_list = [calendar.timegm(time.strptime(message['created_at'],'%a %b %d %H:%M:%S +0000 %Y')), message['user']['screen_name'], - message['id_str'], + message['id_str']] + if is_py3: + mes_list.append(h.unescape(message['text'])) + else: #convert text to bytes so python2 can read it correctly - #TODO remove the encode when weechat is running python3 as default - h.unescape(message['text']).encode('utf-8')] + mes_list.append(h.unescape(message['text']).encode('utf-8')) if message["in_reply_to_status_id_str"] != None: mes_list.append(message["in_reply_to_status_id_str"]) @@ -374,9 +375,13 @@ def twitter_stream_cb(buffer,fd): alt_rt_style = int(script_options['alt_rt_style']), home_replies = int(script_options['home_replies']), token = script_options["oauth_token"], - secret = script_options["oauth_secret"]) + secret = script_options["oauth_secret"], + is_py3 = is_py3) - conn.sendall(bytes(str(options))) + if is_py3: + conn.sendall(bytes(str(options),"utf-8")) + else: + conn.sendall(bytes(str(options))) else: #https://dev.twitter.com/docs/streaming-apis/messages #TODO handle stream events @@ -426,6 +431,7 @@ def connect(): alt_rt_style = option_dict['alt_rt_style'] screen_name = option_dict['screen_name'] name = option_dict['name'] + is_py3 = option_dict['is_py3'] if len(cmd_args) >= 4: stream_args = cmd_args[3:] @@ -493,7 +499,7 @@ def connect(): elif tweet is Hangup: stream_end_message = "Hangup" elif tweet.get('text'): - tweet = trim_tweet_data([tweet],screen_name,alt_rt_style) + tweet = trim_tweet_data([tweet],screen_name,alt_rt_style,is_py3) client = connect() client.sendall(bytes(str(tweet),"utf-8")) client.close() @@ -648,6 +654,7 @@ def get_twitter_data(cmd_args): # Return the requested tweets no_home_replies = True alt_rt_style = False + is_py3 = False screen_name = "" h = html.parser.HTMLParser() @@ -660,6 +667,8 @@ def get_twitter_data(cmd_args): no_home_replies = False if "alt_rt_style" in option_list: alt_rt_style = True + if "is_py3" in option_list: + is_py3 = True screen_name = option_list[0] except: pass @@ -864,7 +873,7 @@ def get_twitter_data(cmd_args): except: return "Unexpected error in get_twitter_data:%s\n Call: %s" % (sys.exc_info(), cmd_args[3]) - return trim_tweet_data(tweet_data,screen_name,alt_rt_style) + return trim_tweet_data(tweet_data,screen_name,alt_rt_style,is_py3) # callback for data received in input def buffer_input_cb(data, buffer, input_data): @@ -875,6 +884,8 @@ def buffer_input_cb(data, buffer, input_data): options.append("alt_rt_style") if script_options['home_replies']: options.append("home_replies") + if is_py3: + options.append("is_py3") if input_data[0] == ':': if data != "silent": @@ -1232,7 +1243,7 @@ def finish_init(): "f " + script_options['screen_name'] + " []", 10 * 1000, "oauth_proc_cb", "friends") if __name__ == "__main__" and weechat_call: - weechat.register( SCRIPT_NAME , "DarkDefender", "1.2.5", "GPL3", "Weechat twitter client", "", "") + weechat.register( SCRIPT_NAME , "DarkDefender", "1.2.6", "GPL3", "Weechat twitter client", "", "") if not import_ok: weechat.prnt("", "Can't load twitter python lib >= " + required_twitter_version) From e72493f0dd6da8f6d2cef344f0041ed790fb6160 Mon Sep 17 00:00:00 2001 From: Jochen Sprickerhof Date: Mon, 8 May 2017 14:01:47 +0200 Subject: [PATCH 031/642] New script whatsapp.py: WhatsApp protocol --- python/whatsapp.py | 1643 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1643 insertions(+) create mode 100644 python/whatsapp.py diff --git a/python/whatsapp.py b/python/whatsapp.py new file mode 100644 index 00000000..fbacc865 --- /dev/null +++ b/python/whatsapp.py @@ -0,0 +1,1643 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 Jochen Sprickerhof +# Copyright (C) 2009-2013 Sebastien Helleu +# Copyright (C) 2010 xt +# Copyright (C) 2010 Aleksey V. Zapparov +# +# 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 . +# + +# +# whatsapp protocol for WeeChat. +# (this script requires WeeChat 0.3.0 (or newer) and the yowsup library) +# +# For help, see /help whatsapp +# Happy chat, enjoy :) +# +# History: +# 2015-12-30, Jochen Sprickerhof : +# version 0.1: Reworked for Whatsapp +# 2013-09-30, Nils Görs : +# version 1.6: add support of /secure for passwords and jid +# : fix stdout/stderr when no JID was set +# 2013-05-14, Billiam : +# version 1.5: fix unicode encoding error in /jabber buddies +# 2013-05-03, Sebastien Helleu : +# version 1.4: add tags in user messages: notify_xxx, no_highlight, +# nick_xxx, prefix_nick_xxx, log1 +# 2012-05-12, Sebastian Rydberg : +# version 1.3: Added support for fetching names from roster +# 2012-04-11, Sebastien Helleu : +# version 1.2: fix deletion of server options +# 2012-03-09, Sebastien Helleu : +# version 1.1: fix reload of config file +# 2012-01-03, Sebastien Helleu : +# version 1.0: changes for future compatibility with Python 3.x +# 2011-12-15, Sebastien Helleu : +# version 0.9: fix utf-8 encoding problem on jid +# 2011-03-21, Isaac Raway : +# version 0.8: search chat buffer before opening it +# 2011-02-13, Sebastien Helleu : +# version 0.7: use new help format for command arguments +# 2010-11-23, xt +# version 0.6: change format of sent ping, to match RFC +# 2010-10-05, xt, +# version 0.5: no highlight for status/presence messages +# 2010-10-01, xt, +# version 0.4: +# add kick and invite +# 2010-08-03, Aleksey V. Zapparov : +# version 0.3: +# add /jabber priority [priority] +# add /jabber status [message] +# add /jabber presence [online|chat|away|xa|dnd] +# 2010-08-02, Aleksey V. Zapparov : +# version 0.2.1: +# fix prexence is set for current resource instead of sending +# special presences for all buddies +# 2010-08-02, Aleksey V. Zapparov : +# version 0.2: +# add priority and away_priority of resource +# 2010-08-02, Sebastien Helleu : +# version 0.1: first official version +# 2010-08-01, ixti : +# fix bug with non-ascii resources +# 2010-06-09, iiijjjiii : +# add connect server and port options (required for google talk) +# add private option permitting messages to be displayed in separate +# chat buffers or in a single server buffer +# add jid aliases +# add keepalive ping +# 2010-03-17, xt : +# add autoreconnect option, autoreconnects on protocol error +# 2010-03-17, xt : +# add autoconnect option, add new command /jmsg with -server option +# 2009-02-22, Sebastien Helleu : +# first version (unofficial) +# + +SCRIPT_NAME = "whatsapp" +SCRIPT_AUTHOR = "Jochen Sprickerhof " +SCRIPT_VERSION = "0.1" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Whatsapp protocol for WeeChat" +SCRIPT_COMMAND = SCRIPT_NAME + +import re + +import_ok = True + +try: + import weechat +except: + print("This script must be run under WeeChat.") + print("Get WeeChat now at: http://www.weechat.org/") + import_ok = False + +try: + from yowsup.common import YowConstants + from yowsup.layers import YowLayerEvent + from yowsup.layers.auth import AuthError + from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback + from yowsup.layers.network import YowNetworkLayer + from yowsup.layers.protocol_contacts.protocolentities.iq_statuses_get import GetStatusesIqProtocolEntity + from yowsup.layers.protocol_contacts.protocolentities.iq_statuses_result import ResultStatusesIqProtocolEntity + from yowsup.layers.protocol_iq import YowIqProtocolLayer + from yowsup.layers.protocol_iq.protocolentities.iq import IqProtocolEntity + from yowsup.layers.protocol_iq.protocolentities.iq_ping import PingIqProtocolEntity + from yowsup.layers.protocol_messages.protocolentities.message_text import TextMessageProtocolEntity + from yowsup.layers.protocol_presence.protocolentities.presence_available import AvailablePresenceProtocolEntity + from yowsup.layers.protocol_presence.protocolentities.presence_subscribe import SubscribePresenceProtocolEntity + from yowsup.layers.protocol_presence.protocolentities.presence_unavailable import UnavailablePresenceProtocolEntity + from yowsup.layers.protocol_presence.protocolentities.presence_unsubscribe import UnsubscribePresenceProtocolEntity + from yowsup.layers.protocol_profiles.protocolentities import SetStatusIqProtocolEntity + from yowsup.stacks import YowStackBuilder +except: + print("Package python-yowsup (yowsup) must be installed to use whatsapp protocol.") + print("Get yowsup with your package manager, or at this URL: https://github.com/tgalal/yowsup") + import_ok = False + +# ==============================[ global vars ]=============================== + +whatsapp_servers = [] +whatsapp_server_options = { + "jid" : { "type" : "string", + "desc" : "whatsapp id (user@server.tld)", + "min" : 0, + "max" : 0, + "string_values": "", + "default" : "", + "value" : "", + "check_cb" : "", + "change_cb" : "", + "delete_cb" : "", + }, + "password" : { "type" : "string", + "desc" : "password for whatsapp id on server", + "min" : 0, + "max" : 0, + "string_values": "", + "default" : "", + "value" : "", + "check_cb" : "", + "change_cb" : "", + "delete_cb" : "", + }, + "autoconnect" : { "type" : "boolean", + "desc" : "automatically connect to server when script is starting", + "min" : 0, + "max" : 0, + "string_values": "", + "default" : "off", + "value" : "off", + "check_cb" : "", + "change_cb" : "", + "delete_cb" : "", + }, + "autoreconnect": { "type" : "boolean", + "desc" : "automatically reconnect to server when disconnected", + "min" : 0, + "max" : 0, + "string_values": "", + "default" : "on", + "value" : "on", + "check_cb" : "", + "change_cb" : "", + "delete_cb" : "", + }, + "private" : { "type" : "boolean", + "desc" : "display messages in separate chat buffers instead of a single server buffer", + "min" : 0, + "max" : 0, + "string_values": "", + "default" : "on", + "value" : "on", + "check_cb" : "", + "change_cb" : "", + "delete_cb" : "", + }, + "recipes" : { "type" : "boolean", + "desc" : "Send recipes for messages", + "min" : 0, + "max" : 0, + "string_values": "", + "default" : "on", + "value" : "on", + "check_cb" : "", + "change_cb" : "", + "delete_cb" : "", + }, + "read" : { "type" : "boolean", + "desc" : "Send read notifications", + "min" : 0, + "max" : 0, + "string_values": "", + "default" : "on", + "value" : "on", + "check_cb" : "", + "change_cb" : "", + "delete_cb" : "", + }, + "ping_interval": { "type" : "integer", + "desc" : "Number of seconds between server pings. 0 = disable", + "min" : 0, + "max" : 9999999, + "string_values": "", + "default" : "50", + "value" : "50", + "check_cb" : "ping_interval_check_cb", + "change_cb" : "", + "delete_cb" : "", + }, + "ping_timeout" : { "type" : "integer", + "desc" : "Number of seconds to allow ping to respond before timing out", + "min" : 0, + "max" : 9999999, + "string_values": "", + "default" : "10", + "value" : "10", + "check_cb" : "ping_timeout_check_cb", + "change_cb" : "", + "delete_cb" : "", + }, + } +whatsapp_config_file = None +whatsapp_config_section = {} +whatsapp_config_option = {} +whatsapp_jid_aliases = {} # { 'alias1': 'jid1', 'alias2': 'jid2', ... } + +# =================================[ config ]================================= + +def whatsapp_config_init(): + """ Initialize config file: create sections and options in memory. """ + global whatsapp_config_file, whatsapp_config_section + whatsapp_config_file = weechat.config_new("whatsapp", "whatsapp_config_reload_cb", "") + if not whatsapp_config_file: + return + # look + whatsapp_config_section["look"] = weechat.config_new_section( + whatsapp_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") + if not whatsapp_config_section["look"]: + weechat.config_free(whatsapp_config_file) + return + whatsapp_config_option["debug"] = weechat.config_new_option( + whatsapp_config_file, whatsapp_config_section["look"], + "debug", "boolean", "display debug messages", "", 0, 0, + "off", "off", 0, "", "", "", "", "", "") + # color + whatsapp_config_section["color"] = weechat.config_new_section( + whatsapp_config_file, "color", 0, 0, "", "", "", "", "", "", "", "", "", "") + if not whatsapp_config_section["color"]: + weechat.config_free(whatsapp_config_file) + return + whatsapp_config_option["message_join"] = weechat.config_new_option( + whatsapp_config_file, whatsapp_config_section["color"], + "message_join", "color", "color for text in join messages", "", 0, 0, + "green", "green", 0, "", "", "", "", "", "") + whatsapp_config_option["message_quit"] = weechat.config_new_option( + whatsapp_config_file, whatsapp_config_section["color"], + "message_quit", "color", "color for text in quit messages", "", 0, 0, + "red", "red", 0, "", "", "", "", "", "") + # server + whatsapp_config_section["server"] = weechat.config_new_section( + whatsapp_config_file, "server", 0, 0, + "whatsapp_config_server_read_cb", "", "whatsapp_config_server_write_cb", "", + "", "", "", "", "", "") + if not whatsapp_config_section["server"]: + weechat.config_free(whatsapp_config_file) + return + whatsapp_config_section["jid_aliases"] = weechat.config_new_section( + whatsapp_config_file, "jid_aliases", 0, 0, + "whatsapp_config_jid_aliases_read_cb", "", + "whatsapp_config_jid_aliases_write_cb", "", + "", "", "", "", "", "") + if not whatsapp_config_section["jid_aliases"]: + weechat.config_free(whatsapp_config_file) + return + +def whatsapp_config_reload_cb(data, config_file): + """ Reload config file. """ + return weechat.config_reload(config_file) + +def whatsapp_config_server_read_cb(data, config_file, section, option_name, value): + """ Read server option in config file. """ + global whatsapp_servers + rc = weechat.WEECHAT_CONFIG_OPTION_SET_ERROR + items = option_name.split(".", 1) + if len(items) == 2: + server = whatsapp_search_server_by_name(items[0]) + if not server: + server = Server(items[0]) + whatsapp_servers.append(server) + stackbuilder = YowStackBuilder() + # disable status ping as weechat seems to have a problem with threads + stackbuilder.setProp(YowIqProtocolLayer.PROP_PING_INTERVAL, 0) + stackbuilder.pushDefaultLayers(True).push(server).build() + if server: + rc = weechat.config_option_set(server.options[items[1]], value, 1) + return rc + +def whatsapp_config_server_write_cb(data, config_file, section_name): + """ Write server section in config file. """ + global whatsapp_servers + weechat.config_write_line(config_file, section_name, "") + for server in whatsapp_servers: + for name, option in sorted(server.options.items()): + weechat.config_write_option(config_file, option) + return weechat.WEECHAT_RC_OK + +def whatsapp_config_jid_aliases_read_cb(data, config_file, section, option_name, value): + """ Read jid_aliases option in config file. """ + global whatsapp_jid_aliases + whatsapp_jid_aliases[option_name] = value + option = weechat.config_new_option( + config_file, section, + option_name, "string", "jid alias", "", 0, 0, + "", value, 0, "", "", "", "", "", "") + if not option: + return weechat.WEECHAT_CONFIG_OPTION_SET_ERROR + return weechat.WEECHAT_CONFIG_OPTION_SET_OK_CHANGED + +def whatsapp_config_jid_aliases_write_cb(data, config_file, section_name): + """ Write jid_aliases section in config file. """ + global whatsapp_jid_aliases + weechat.config_write_line(config_file, section_name, "") + for alias, jid in sorted(whatsapp_jid_aliases.items()): + weechat.config_write_line(config_file, alias, jid) + return weechat.WEECHAT_RC_OK + +def whatsapp_config_read(): + """ Read whatsapp config file (whatsapp.conf). """ + global whatsapp_config_file + return weechat.config_read(whatsapp_config_file) + +def whatsapp_config_write(): + """ Write whatsapp config file (whatsapp.conf). """ + global whatsapp_config_file + return weechat.config_write(whatsapp_config_file) + +def whatsapp_debug_enabled(): + """ Return True if debug is enabled. """ + global whatsapp_config_options + if weechat.config_boolean(whatsapp_config_option["debug"]): + return True + return False + +def whatsapp_config_color(color): + """ Return color code for a whatsapp color option. """ + global whatsapp_config_option + if color in whatsapp_config_option: + return weechat.color(weechat.config_color(whatsapp_config_option[color])) + return "" + +def ping_timeout_check_cb(server_name, option, value): + global whatsapp_config_file, whatsapp_config_section + ping_interval_option = weechat.config_search_option( + whatsapp_config_file, + whatsapp_config_section["server"], + "%s.ping_interval" % (server_name) + ) + ping_interval = weechat.config_integer(ping_interval_option) + if int(ping_interval) and int(value) >= int(ping_interval): + weechat.prnt("", "\nwhatsapp: unable to update 'ping_timeout' for server %s" % (server_name)) + weechat.prnt("", "whatsapp: to prevent multiple concurrent pings, ping_interval must be greater than ping_timeout") + return weechat.WEECHAT_CONFIG_OPTION_SET_ERROR + return weechat.WEECHAT_CONFIG_OPTION_SET_OK_CHANGED + +def ping_interval_check_cb(server_name, option, value): + global whatsapp_config_file, whatsapp_config_section + ping_timeout_option = weechat.config_search_option( + whatsapp_config_file, + whatsapp_config_section["server"], + "%s.ping_timeout" % (server_name) + ) + ping_timeout = weechat.config_integer(ping_timeout_option) + if int(value) and int(ping_timeout) >= int(value): + weechat.prnt("", "\nwhatsapp: unable to update 'ping_interval' for server %s" % (server_name)) + weechat.prnt("", "whatsapp: to prevent multiple concurrent pings, ping_interval must be greater than ping_timeout") + return weechat.WEECHAT_CONFIG_OPTION_SET_ERROR + return weechat.WEECHAT_CONFIG_OPTION_SET_OK_CHANGED + +# ================================[ servers ]================================= + +class Server(YowInterfaceLayer): + """ Class to manage a server: buffer, connection, send/recv data. """ + + def __init__(self, name, **kwargs): + """ Init server """ + global whatsapp_config_file, whatsapp_config_section, whatsapp_server_options + + super(Server, self).__init__() + self.connected = False + + self.name = name + # create options (user can set them with /set) + self.options = {} + # if the value is provided, use it, otherwise use the default + values = {} + for option_name, props in whatsapp_server_options.items(): + values[option_name] = props["default"] + values['name'] = name + values.update(**kwargs) + for option_name, props in whatsapp_server_options.items(): + self.options[option_name] = weechat.config_new_option( + whatsapp_config_file, whatsapp_config_section["server"], + self.name + "." + option_name, props["type"], props["desc"], + props["string_values"], props["min"], props["max"], + props["default"], values[option_name], 0, + props["check_cb"], self.name, props["change_cb"], "", + props["delete_cb"], "") + # internal data + self.jid = None + self.sock = None + self.hook_fd = None + self.buffer = "" + self.chats = [] + self.buddies = [] + self.buddy = None + self.ping_timer = None # weechat.hook_timer for sending pings + self.ping_timeout_timer = None # weechat.hook_timer for monitoring ping timeout + + def option_string(self, option_name): + """ Return a server option, as string. """ + return weechat.config_string(self.options[option_name]) + + def option_boolean(self, option_name): + """ Return a server option, as boolean. """ + return weechat.config_boolean(self.options[option_name]) + + def option_integer(self, option_name): + """ Return a server option, as string. """ + return weechat.config_integer(self.options[option_name]) + + @ProtocolEntityCallback("receipt") + def onReceipt(self, entity): + self.toLower(entity.ack()) + + @ProtocolEntityCallback("notification") + def onNotification(self, notification): + notificationData = notification.__str__() + if notificationData: + weechat.prnt('', "Notification: %s" % notificationData) + else: + weechat.prnt('', "From :%s, Type: %s" % (notification.getFrom(), notification.getType())) + if weechat.config_boolean(self.options['recipes']): + self.toLower(notification.ack()) + + def connect(self): + """ Connect to whatsapp server. """ + if not self.buffer: + bufname = "%s.server.%s" % (SCRIPT_NAME, self.name) + self.buffer = weechat.buffer_search("python", bufname) + if not self.buffer: + self.buffer = weechat.buffer_new(bufname, + "whatsapp_buffer_input_cb", "", + "whatsapp_buffer_close_cb", "") + if self.buffer: + weechat.buffer_set(self.buffer, "short_name", self.name) + weechat.buffer_set(self.buffer, "localvar_set_type", "server") + weechat.buffer_set(self.buffer, "localvar_set_server", self.name) + weechat.buffer_set(self.buffer, "nicklist", "1") + weechat.buffer_set(self.buffer, "nicklist_display_groups", "1") + weechat.buffer_set(self.buffer, "display", "auto") + + self.buddy = Buddy(jid=eval_expression(self.option_string("jid")), server=self) + + credentials = (eval_expression(self.option_string("jid")), eval_expression(self.option_string("password"))) + self.getStack().setCredentials(credentials) + self.getStack().broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT)) + + # set blocking, so we don't send in the select loop as asyncore does it + self.sock = self.getStack().getLayer(0).socket.setblocking(1) + # push initial connect message through the socket (would have been done in the select loop otherwise + self.getStack().getLayer(0).handle_write_event() + + self.sock = self.getStack().getLayer(0).socket.fileno() + self.hook_fd = weechat.hook_fd(self.sock, 1, 0, 0, "whatsapp_fd_cb", "") + + weechat.buffer_set(self.buffer, "highlight_words", self.buddy.alias) + weechat.buffer_set(self.buffer, "localvar_set_nick", self.buddy.alias) + hook_away = weechat.hook_command_run("/away -all*", "whatsapp_away_command_run_cb", "") + + # Whatsapp doesn't send context, so we use aliases as contacts instead + for jid in whatsapp_jid_aliases.values(): + if jid != self.buddy.jid: + self.add_buddy(jid) + + def is_connected(self): + """Return connect status""" + if not self.connected: + return False + else: + return True + + def add_chat(self, buddy): + """Create a chat buffer for a buddy""" + chat = Chat(self, buddy, switch_to_buffer=False) + self.chats.append(chat) + return chat + + def del_buddy(self, jid): + """ Remove a buddy and/or deny authorization request """ + entity = UnsubscribePresenceProtocolEntity(jid) + self.toLower(entity) + + def print_debug_server(self, message): + """ Print debug message on server buffer. """ + if whatsapp_debug_enabled(): + weechat.prnt(self.buffer, "%swhatsapp: %s" % (weechat.prefix("network"), message)) + + def print_debug_handler(self, handler_name, node): + """ Print debug message for a handler on server buffer. """ + self.print_debug_server("%s_handler, xml message:\n%s" + % (handler_name, node)) + + def print_error(self, message): + """ Print error message on server buffer. """ + if whatsapp_debug_enabled(): + weechat.prnt(self.buffer, "%swhatsapp: %s" % (weechat.prefix("error"), message)) + + @ProtocolEntityCallback("chatstate") + def presence_handler(self, node): + print(node) + print(dir(node)) + self.print_debug_handler("presence", node) + buddy = self.search_buddy_list(node.getFrom(), by='jid') + if not buddy: + buddy = self.add_buddy(jid=node.getFrom()) + action='update' + node_type = node.getType() + if node_type in ["error", "unavailable"]: + action='remove' + if action == 'update': + away = node.getShow() in ["away", "xa"] + status = ' ' + if node.getStatus(): + status = node.getStatus() + buddy.set_status(status=status, away=away) + self.update_nicklist(buddy=buddy, action=action) + return + + @ProtocolEntityCallback("iq") + def iq_handler(self, node): + """ Receive iq message. """ + self.print_debug_handler("iq", node) + #weechat.prnt(self.buffer, "whatsapp: iq handler") + if isinstance(node, ResultStatusesIqProtocolEntity): + for jid in node.statuses: + buddy = self.search_buddy_list(jid, by='jid') + if not buddy: + buddy = self.add_buddy(jid=jid) + buddy.set_status(status=node.statuses[jid][0]) + + elif isinstance(node, IqProtocolEntity): + self.delete_ping_timeout_timer() # Disable the timeout feature + if not self.is_connected() and weechat.config_boolean(self.options['autoreconnect']): + self.connect() + + def onEvent(self, layerEvent): + weechat.prnt('', layerEvent.getName()) + if layerEvent.getName() == YowNetworkLayer.EVENT_STATE_CONNECTED: + self.connected = True + return True + elif layerEvent.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECTED: + weechat.prnt('', "Disconnected: %s" % layerEvent.getArg("reason")) + if not self.is_connected() and weechat.config_boolean(self.options['autoreconnect']): + self.connect() + return True + + @ProtocolEntityCallback("message") + def message_handler(self, node): + """ Receive message. """ + self.print_debug_handler("message", node) + messageOut = "" + if node.getType() == "text": + messageOut = self.getTextMessageBody(node) + elif node.getType() == "media": + messageOut = self.getMediaMessageBody(node) + else: + messageOut = "Unknown message type %s " % node.getType() + self.print_debug_handler("send", messageOut.toProtocolTreeNode()) + + jid = node.getFrom() if not node.isGroupMessage() else "%s/%s" % (node.getParticipant(False), node.getFrom()) + body = messageOut + + if weechat.config_boolean(self.options['recipes']): + self.toLower(node.ack(weechat.config_boolean(self.options['recipes']))) + self.print_debug_handler("Sent delivered receipt", "Message %s" % node.getId()) + + if not jid or not body: + return + buddy = self.search_buddy_list(jid, by='jid') + if not buddy: + buddy = self.add_buddy(jid=jid) + # If a chat buffer exists for the buddy, receive the message with that + # buffer even if private is off. The buffer may have been created with + # /query. + recv_object = self + if not buddy.chat and weechat.config_boolean(self.options['private']): + self.add_chat(buddy) + if buddy.chat: + recv_object = buddy.chat + recv_object.recv_message(buddy, body) + + def getTextMessageBody(self, message): + return message.getBody() + + def getMediaMessageBody(self, message): + if message.getMediaType() in ("image", "audio", "video"): + return self.getDownloadableMediaMessageBody(message) + else: + return "Media Type: %s" % message.getMediaType() + + def getDownloadableMediaMessageBody(self, message): + return "Media Type:{media_type}, Size:{media_size}, URL:{media_url}".format( + media_type=message.getMediaType(), + media_size=message.getMediaSize(), + media_url=message.getMediaUrl() + ) + + def recv(self): + """ Receive something from whatsapp server. """ + try: + self.getStack().getLayer(0).handle_read() + except AuthError as e: + weechat.prnt('', '%s: Error from server: %s' %(SCRIPT_NAME, e)) + self.disconnect() + if weechat.config_boolean(self.options['autoreconnect']): + autoreconnect_delay = 30 + weechat.command('', '/wait %s /%s connect %s' % + (autoreconnect_delay, SCRIPT_COMMAND, self.name)) + + def recv_message(self, buddy, message): + """ Receive a message from buddy. """ + weechat.prnt_date_tags(self.buffer, 0, + "notify_private,nick_%s,prefix_nick_%s,log1" % + (buddy.alias, + weechat.config_string(weechat.config_get("weechat.color.chat_nick_other"))), + "%s%s\t%s" % (weechat.color("chat_nick_other"), + buddy.alias, + message)) + + def print_status(self, nickname, status): + """ Print a status in server window and in chat. """ + weechat.prnt_date_tags(self.buffer, 0, "no_highlight", "%s%s has status %s" % + (weechat.prefix("action"), + nickname, + status)) + for chat in self.chats: + if nickname in chat.buddy.alias: + chat.print_status(status) + break + + def send_message(self, buddy, message): + """ Send a message to buddy. + + The buddy argument can be either a jid string, + eg username@domain.tld/resource or a Buddy object instance. + """ + recipient = buddy + if isinstance(buddy, Buddy): + recipient = buddy.jid + if not self.is_connected(): + weechat.prnt(self.buffer, "%swhatsapp: unable to send message, connection is down" + % weechat.prefix("error")) + return + outgoingMessage = TextMessageProtocolEntity(message, to=self.stringify_jid(recipient)) + self.toLower(outgoingMessage) + + def send_message_from_input(self, input=''): + """ Send a message from input text on server buffer. """ + # Input must be of format "name: message" where name is a jid, bare_jid + # or alias. The colon can be replaced with a comma as well. + # Split input into name and message. + if not re.compile(r'.+[:,].+').match(input): + weechat.prnt(self.buffer, "%swhatsapp: %s" % (weechat.prefix("network"), + "Invalid send format. Use jid: message" + )) + return + name, message = re.split('[:,]', input, maxsplit=1) + buddy = self.search_buddy_list(name, by='alias') + if not buddy: + weechat.prnt(self.buffer, + "%swhatsapp: Invalid jid: %s" % (weechat.prefix("network"), + name)) + return + # Send activity indicates user is no longer away, set it so + if self.buddy and self.buddy.away: + self.set_away('') + self.send_message(buddy=buddy, message=message) + try: + sender = self.buddy.alias + except: + sender = self.jid + weechat.prnt_date_tags(self.buffer, 0, + "notify_none,no_highlight,nick_%s,prefix_nick_%s,log1" % + (sender, + weechat.config_string(weechat.config_get("weechat.color.chat_nick_self"))), + "%s%s\t%s" % (weechat.color("chat_nick_self"), + sender, + message.strip())) + + def set_away(self, message): + """ Set/unset away on server. + + If a message is provided, status is set to 'away'. + If no message, then status is set to 'online'. + """ + if message: + entity = UnavailablePresenceProtocolEntity() + self.toLower(entity) + else: + entity = AvailablePresenceProtocolEntity() + self.toLower(entity) + self.set_presence(message) + + def set_presence(self, status=None): + message = status if status else '' + entity = SetStatusIqProtocolEntity(message) + self.toLower(entity) + + def add_buddy(self, jid): + """ Add a new buddy """ + full_jid = self.stringify_jid(jid) + entity = SubscribePresenceProtocolEntity(full_jid) + self.toLower(entity) + entity = GetStatusesIqProtocolEntity([full_jid]) + self.toLower(entity) + + buddy = Buddy(jid=jid, server=self) + self.buddies.append(buddy) + self.update_nicklist(buddy=buddy, action='update') + return buddy + + def display_buddies(self): + """ Display buddies. """ + weechat.prnt(self.buffer, "") + weechat.prnt(self.buffer, "Buddies:") + + len_max = { 'alias': 5, 'jid': 5 } + lines = [] + for buddy in sorted(self.buddies, key=lambda x: x.jid.getStripped()): + alias = '' + if buddy.alias != buddy.jid: + alias = buddy.alias + buddy_jid_string = buddy.jid.getStripped() + lines.append( { + 'jid': buddy_jid_string, + 'alias': alias, + 'status': buddy.away_string(), + }) + if len(alias) > len_max['alias']: + len_max['alias'] = len(alias) + if len(buddy_jid_string) > len_max['jid']: + len_max['jid'] = len(buddy_jid_string) + prnt_format = " %s%-" + str(len_max['jid']) + "s %-" + str(len_max['alias']) + "s %s" + weechat.prnt(self.buffer, prnt_format % ('', 'JID', 'Alias', 'Status')) + for line in lines: + weechat.prnt(self.buffer, prnt_format % (weechat.color("chat_nick"), + line['jid'], + line['alias'], + line['status'], + )) + + def stringify_jid(self, jid): + """ Serialise JID into string. + + Args: + jid: xmpp.protocol.JID, JID instance to serialize + + Notes: + Method is based on original JID.__str__ but with hack to allow + non-ascii in resource names. + """ + if '@' in jid: + return jid + elif "-" in jid: + return "%s@g.us" % jid + + return "%s@s.whatsapp.net" % jid + + def search_buddy_list(self, name, by='jid'): + """ Search for a buddy by name. + + Args: + name: string, the buddy name to search, eg the jid or alias + by: string, either 'alias' or 'jid', determines which Buddy + property to match on, default 'jid' + + Notes: + If the 'by' parameter is set to 'jid', the search matches on all + Buddy object jid properties, followed by all bare_jid properties. + Once a match is found it is returned. + + If the 'by' parameter is set to 'alias', the search matches on all + Buddy object alias properties. + + Generally, set the 'by' parameter to 'jid' when the jid is provided + from a server, for example from a received message. Set 'by' to + 'alias' when the jid is provided by the user. + """ + if by == 'jid': + for buddy in self.buddies: + if self.stringify_jid(buddy.jid) == name: + return buddy + for buddy in self.buddies: + if buddy.jid == name: + return buddy + else: + for buddy in self.buddies: + if buddy.alias == name: + return buddy + return None + + def update_nicklist(self, buddy=None, action=None): + """Update buddy in nicklist + Args: + buddy: Buddy object instance + action: string, one of 'update' or 'remove' + """ + if not buddy: + return + if not action in ['remove', 'update']: + return + ptr_nick_gui = weechat.nicklist_search_nick(self.buffer, "", buddy.alias) + weechat.nicklist_remove_nick(self.buffer, ptr_nick_gui) + msg = '' + prefix = '' + color = '' + away = '' + if action == 'update': + nick_color = "bar_fg" + if buddy.away: + nick_color = "weechat.color.nicklist_away" + weechat.nicklist_add_nick(self.buffer, "", buddy.alias, + nick_color, "", "", 1) + if not ptr_nick_gui: + msg = 'joined' + prefix = 'join' + color = 'message_join' + away = buddy.away_string() + if action == 'remove': + msg = 'quit' + prefix = 'quit' + color = 'message_quit' + if msg: + weechat.prnt(self.buffer, "%s%s%s%s has %s %s" + % (weechat.prefix(prefix), + weechat.color("chat_nick"), + buddy.alias, + whatsapp_config_color(color), + msg, + away)) + return + + def add_ping_timer(self): + if self.ping_timer: + self.delete_ping_timer() + if not self.option_integer('ping_interval'): + return + self.ping_timer = weechat.hook_timer( self.option_integer('ping_interval') * 1000, + 0, 0, "whatsapp_ping_timer", self.name) + return + + def delete_ping_timer(self): + if self.ping_timer: + weechat.unhook(self.ping_timer) + self.ping_timer = None + return + + def add_ping_timeout_timer(self): + if self.ping_timeout_timer: + self.delete_ping_timeout_timer() + if not self.option_integer('ping_timeout'): + return + self.ping_timeout_timer = weechat.hook_timer( + self.option_integer('ping_timeout') * 1000, 0, 1, + "whatsapp_ping_timeout_timer", self.name) + return + + def delete_ping_timeout_timer(self): + if self.ping_timeout_timer: + weechat.unhook(self.ping_timeout_timer) + self.ping_timeout_timer = None + return + + def ping(self): + if not self.is_connected(): + if not self.connect(): + return + iq = PingIqProtocolEntity(to = YowConstants.DOMAIN) + self.toLower(iq) + self.print_debug_handler("ping", iq) + self.add_ping_timeout_timer() + return + + def ping_time_out(self): + self.delete_ping_timeout_timer() + # A ping timeout indicates a server connection problem. Disconnect + # completely. + self.disconnect() + return + + def disconnect(self): + """ Disconnect from whatsapp server. """ + if self.hook_fd != None: + weechat.unhook(self.hook_fd) + self.hook_fd = None + self.getStack().broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_DISCONNECT)) + self.jid = None + self.sock = None + self.buddy = None + weechat.nicklist_remove_all(self.buffer) + + def close_buffer(self): + """ Close server buffer. """ + if self.buffer != "": + weechat.buffer_close(self.buffer) + self.buffer = "" + + def delete(self, deleteOptions=False): + """ Delete server. """ + for chat in self.chats: + chat.delete() + self.delete_ping_timer() + self.delete_ping_timeout_timer() + self.disconnect() + self.close_buffer() + if deleteOptions: + for name, option in self.options.items(): + weechat.config_option_free(option) + +def eval_expression(option_name): + """ Return a evaluated expression """ + if int(version) >= 0x00040200: + return weechat.string_eval_expression(option_name,{},{},{}) + else: + return option_name + +def whatsapp_search_server_by_name(name): + """ Search a server by name. """ + global whatsapp_servers + for server in whatsapp_servers: + if server.name == name: + return server + return None + +def whatsapp_search_context(buffer): + """ Search a server / chat for a buffer. """ + global whatsapp_servers + context = { "server": None, "chat": None } + for server in whatsapp_servers: + if server.buffer == buffer: + context["server"] = server + return context + for chat in server.chats: + if chat.buffer == buffer: + context["server"] = server + context["chat"] = chat + return context + return context + +def whatsapp_search_context_by_name(server_name): + """Search for buffer given name of server. """ + + bufname = "%s.server.%s" % (SCRIPT_NAME, server_name) + return whatsapp_search_context(weechat.buffer_search("python", bufname)) + + +# =================================[ chats ]================================== + +class Chat: + """ Class to manage private chat with buddy or MUC. """ + + def __init__(self, server, buddy, switch_to_buffer): + """ Init chat """ + self.server = server + self.buddy = buddy + buddy.chat = self + bufname = "%s.%s.%s" % (SCRIPT_NAME, server.name, self.buddy.alias) + self.buffer = weechat.buffer_search("python", bufname) + if not self.buffer: + self.buffer = weechat.buffer_new(bufname, + "whatsapp_buffer_input_cb", "", + "whatsapp_buffer_close_cb", "") + self.buffer_title = self.buddy.alias + if self.buffer: + weechat.buffer_set(self.buffer, "title", self.buffer_title) + weechat.buffer_set(self.buffer, "short_name", self.buddy.alias) + weechat.buffer_set(self.buffer, "localvar_set_type", "private") + weechat.buffer_set(self.buffer, "localvar_set_server", server.name) + weechat.buffer_set(self.buffer, "localvar_set_channel", self.buddy.alias) + weechat.hook_signal_send("logger_backlog", + weechat.WEECHAT_HOOK_SIGNAL_POINTER, self.buffer) + if switch_to_buffer: + weechat.buffer_set(self.buffer, "display", "auto") + + def recv_message(self, buddy, message): + """ Receive a message from buddy. """ + if buddy.alias != self.buffer_title: + self.buffer_title = buddy.alias + weechat.buffer_set(self.buffer, "title", "%s" % self.buffer_title) + weechat.prnt_date_tags(self.buffer, 0, + "notify_private,nick_%s,prefix_nick_%s,log1" % + (buddy.alias, + weechat.config_string(weechat.config_get("weechat.color.chat_nick_other"))), + "%s%s\t%s" % (weechat.color("chat_nick_other"), + buddy.alias, + message)) + + def send_message(self, message): + """ Send message to buddy. """ + if not self.server.is_connected(): + weechat.prnt(self.buffer, "%swhatsapp: unable to send message, connection is down" + % weechat.prefix("error")) + return + self.server.send_message(self.buddy, message) + weechat.prnt_date_tags(self.buffer, 0, + "notify_none,no_highlight,nick_%s,prefix_nick_%s,log1" % + (self.server.buddy.alias, + weechat.config_string(weechat.config_get("weechat.color.chat_nick_self"))), + "%s%s\t%s" % (weechat.color("chat_nick_self"), + self.server.buddy.alias, + message)) + def print_status(self, status): + """ Print a status message in chat. """ + weechat.prnt(self.buffer, "%s%s has status %s" % + (weechat.prefix("action"), + self.buddy.alias, + status)) + + def close_buffer(self): + """ Close chat buffer. """ + if self.buffer != "": + weechat.buffer_close(self.buffer) + self.buffer = "" + + def delete(self): + """ Delete chat. """ + self.close_buffer() + +# =================================[ buddies ]================================== + +class Buddy: + """ Class to manage buddies. """ + def __init__(self, jid=None, chat=None, server=None ): + """ Init buddy + + Args: + jid: xmpp.protocol.JID object instance or string + chat: Chat object instance + server: Server object instance + + The jid argument can be provided either as a xmpp.protocol.JID object + instance or as a string, eg "username@domain.tld/resource". If a string + is provided, it is converted and stored as a xmpp.protocol.JID object + instance. + """ + + # The jid argument of xmpp.protocol.JID can be either a string or a + # xmpp.protocol.JID object instance itself. + self.jid = jid + self.chat = chat + self.server = server + self.name = '' + self.alias = '' + self.away = True + self.status = '' + + self.set_alias() + return + + def away_string(self): + """ Return a string with away and status, with color codes. """ + if not self: + return '' + if not self.away: + return '' + str_colon = ": " + if not self.status: + str_colon = "" + status = self.status.replace('\n', ' ') if self.status else '' + return "%s(%saway%s%s%s)" % (weechat.color("chat_delimiters"), + weechat.color("chat"), + str_colon, + status, + weechat.color("chat_delimiters")) + + def set_alias(self): + """Set the buddy alias. + + If an alias is defined in whatsapp_jid_aliases, it is used. Otherwise the + alias is set to self.jid or self.name if it exists. + """ + self.alias = self.jid + if not self.jid: + self.alias = '' + if self.name: + self.alias = self.name + global whatsapp_jid_aliases + for alias, jid in whatsapp_jid_aliases.items(): + if jid == self.jid: + self.alias = alias + break + return + + def set_name(self, name=''): + self.name = name + self.set_alias() + return + + def set_status(self, away=True, status=''): + """Set the buddy status. + + Two properties define the buddy status. + away - boolean, indicates whether the buddy is away or not. + status - string, a message indicating the away status, eg 'in a meeting' + Comparable to xmpp presence element. + """ + if not away and not status: + status = 'online' + # If the status has changed print a message on the server buffer + if self.away != away or self.status != status: + self.server.print_status(self.alias, status) + self.away = away + self.status = status + return + +# ================================[ commands ]================================ + +def whatsapp_hook_commands_and_completions(): + """ Hook commands and completions. """ + weechat.hook_command(SCRIPT_COMMAND, "Manage whatsapp servers", + "list || add " + " || connect|disconnect|del [] || alias [add|del ]" + " || away [] || buddies ||" + " || status []" + " || debug || set []", + " list: list servers and chats\n" + " add: add a server\n" + " connect: connect to server using password\n" + "disconnect: disconnect from server\n" + " del: delete server\n" + " alias: manage jid aliases\n" + " away: set away with a message (if no message, away is unset)\n" + " status: set status message\n" + " buddies: display buddies on server\n" + " debug: toggle whatsapp debug on/off (for all servers)\n" + "\n" + "Without argument, this command lists servers and chats.\n" + "\n" + "Examples:\n" + " Add a server: /whatsapp add myserver user@server.tld password\n" + " Add gtalk server: /whatsapp add myserver user@gmail.com password talk.google.com:5223\n" + " Connect to server: /whatsapp connect myserver\n" + " Disconnect: /whatsapp disconnect myserver\n" + " Delete server: /whatsapp del myserver\n" + "\n" + "Aliases:\n" + " List aliases: /whatsapp alias \n" + " Add an alias: /whatsapp alias add alias_name jid\n" + " Delete an alias: /whatsapp alias del alias_name\n" + "\n" + "Other whatsapp commands:\n" + " Chat with a buddy (pv buffer): /query\n" + " Add buddy to roster: /winvite\n" + " Remove buddy from roster: /wkick\n" + " Send message to buddy: /wmsg", + "list %(whatsapp_servers)" + " || add %(whatsapp_servers)" + " || connect %(whatsapp_servers)" + " || disconnect %(whatsapp_servers)" + " || del %(whatsapp_servers)" + " || alias add|del %(whatsapp_jid_aliases)" + " || away" + " || status" + " || buddies" + " || debug", + "whatsapp_cmd_whatsapp", "") + weechat.hook_command("query", "Chat with a whatsapp buddy", + "", + "buddy: buddy id", + "", + "whatsapp_cmd_query", "") + weechat.hook_command("wmsg", "Send a message to a buddy", + "[-server ] ", + "server: name of whatsapp server buddy is on\n" + " buddy: buddy id\n" + " text: text to send", + "", + "whatsapp_cmd_wmsg", "") + weechat.hook_command("winvite", "Add a buddy to your roster", + "", + "buddy: buddy id", + "", + "whatsapp_cmd_winvite", "") + weechat.hook_command("wkick", "Remove a buddy from your roster, or deny auth", + "", + "buddy: buddy id", + "", + "whatsapp_cmd_wkick", "") + weechat.hook_completion("whatsapp_servers", "list of whatsapp servers", + "whatsapp_completion_servers", "") + weechat.hook_completion("whatsapp_jid_aliases", "list of whatsapp jid aliases", + "whatsapp_completion_jid_aliases", "") + +def whatsapp_list_servers_chats(name): + """ List servers and chats. """ + global whatsapp_servers + weechat.prnt("", "") + if len(whatsapp_servers) > 0: + weechat.prnt("", "whatsapp servers:") + for server in whatsapp_servers: + if name == "" or server.name.find(name) >= 0: + conn_server = '' + connected = "" + if server.sock >= 0: + connected = "(connected)" + + weechat.prnt("", " %s - %s %s %s" % (server.name, + eval_expression(server.option_string("jid")), conn_server, connected)) + for chat in server.chats: + weechat.prnt("", " chat with %s" % (chat.buddy)) + else: + weechat.prnt("", "whatsapp: no server defined") + +def whatsapp_cmd_whatsapp(data, buffer, args): + """ Command '/whatsapp'. """ + global whatsapp_servers, whatsapp_config_option + if args == "" or args == "list": + whatsapp_list_servers_chats("") + else: + argv = args.split(" ") + argv1eol = "" + pos = args.find(" ") + if pos > 0: + argv1eol = args[pos+1:] + if argv[0] == "list": + whatsapp_list_servers_chats(argv[1]) + elif argv[0] == "add": + if len(argv) >= 4: + server = whatsapp_search_server_by_name(argv[1]) + if server: + weechat.prnt("", "whatsapp: server '%s' already exists" % argv[1]) + else: + kwargs = {'jid': argv[2], 'password': argv[3]} + server = Server(argv[1], **kwargs) + whatsapp_servers.append(server) + stackbuilder = YowStackBuilder() + # disable status ping as weechat seems to have a problem with threads + stackbuilder.setProp(YowIqProtocolLayer.PROP_PING_INTERVAL, 0) + stackbuilder.pushDefaultLayers(True).push(server).build() + weechat.prnt("", "whatsapp: server '%s' created" % argv[1]) + else: + weechat.prnt("", "whatsapp: unable to add server, missing arguments") + weechat.prnt("", "whatsapp: usage: /whatsapp add name jid password") + elif argv[0] == "alias": + alias_command = AliasCommand(buffer, argv=argv[1:]) + alias_command.run() + elif argv[0] == "connect": + server = None + if len(argv) >= 2: + server = whatsapp_search_server_by_name(argv[1]) + if not server: + weechat.prnt("", "whatsapp: server '%s' not found" % argv[1]) + else: + context = whatsapp_search_context(buffer) + if context["server"]: + server = context["server"] + if server: + if weechat.config_boolean(server.options['autoreconnect']): + server.ping() # This will connect and update ping status + server.add_ping_timer() + else: + server.connect() + elif argv[0] == "disconnect": + server = None + if len(argv) >= 2: + server = whatsapp_search_server_by_name(argv[1]) + if not server: + weechat.prnt("", "whatsapp: server '%s' not found" % argv[1]) + else: + context = whatsapp_search_context(buffer) + if context["server"]: + server = context["server"] + context = whatsapp_search_context(buffer) + if server: + server.delete_ping_timer() + server.disconnect() + elif argv[0] == "del": + if len(argv) >= 2: + server = whatsapp_search_server_by_name(argv[1]) + if server: + server.delete(deleteOptions=True) + whatsapp_servers.remove(server) + weechat.prnt("", "whatsapp: server '%s' deleted" % argv[1]) + else: + weechat.prnt("", "whatsapp: server '%s' not found" % argv[1]) + elif argv[0] == "send": + if len(argv) >= 3: + context = whatsapp_search_context(buffer) + if context["server"]: + buddy = context['server'].search_buddy_list(argv[1], by='alias') + message = ' '.join(argv[2:]) + context["server"].send_message(buddy, message) + elif argv[0] == "read": + whatsapp_config_read() + elif argv[0] == "away": + context = whatsapp_search_context(buffer) + if context["server"]: + context["server"].set_away(argv1eol) + elif argv[0] == "status": + context = whatsapp_search_context(buffer) + if context["server"]: + if len(argv) == 1: + weechat.prnt("", "whatsapp: status = %s" % context["server"].presence.getStatus()) + else: + context["server"].set_presence(status=argv1eol) + elif argv[0] == "buddies": + context = whatsapp_search_context(buffer) + if context["server"]: + context["server"].display_buddies() + elif argv[0] == "debug": + weechat.config_option_set(whatsapp_config_option["debug"], "toggle", 1) + if whatsapp_debug_enabled(): + weechat.prnt("", "whatsapp: debug is now ON") + else: + weechat.prnt("", "whatsapp: debug is now off") + else: + weechat.prnt("", "whatsapp: unknown action") + return weechat.WEECHAT_RC_OK + +def whatsapp_cmd_query(data, buffer, args): + """ Command '/query'. """ + if args: + context = whatsapp_search_context(buffer) + if context["server"]: + buddy = context["server"].search_buddy_list(args, by='alias') + if not buddy: + buddy = context["server"].add_buddy(jid=args) + if not buddy.chat: + context["server"].add_chat(buddy) + weechat.buffer_set(buddy.chat.buffer, "display", "auto") + return weechat.WEECHAT_RC_OK + +def whatsapp_cmd_wmsg(data, buffer, args): + """ Command '/wmsg'. """ + if args: + argv = args.split() + if len(argv) < 2: + return weechat.WEECHAT_RC_OK + if argv[0] == '-server': + context = whatsapp_search_context_by_name(argv[1]) + recipient = argv[2] + message = " ".join(argv[3:]) + else: + context = whatsapp_search_context(buffer) + recipient = argv[0] + message = " ".join(argv[1:]) + if context["server"]: + buddy = context['server'].search_buddy_list(recipient, by='alias') + context["server"].send_message(buddy, message) + + return weechat.WEECHAT_RC_OK + +def whatsapp_cmd_winvite(data, buffer, args): + """ Command '/winvite'. """ + if args: + context = whatsapp_search_context(buffer) + if context["server"]: + context["server"].add_buddy(args) + return weechat.WEECHAT_RC_OK + +def whatsapp_cmd_wkick(data, buffer, args): + """ Command '/wkick'. """ + if args: + context = whatsapp_search_context(buffer) + if context["server"]: + context["server"].del_buddy(args) + return weechat.WEECHAT_RC_OK + +def whatsapp_away_command_run_cb(data, buffer, command): + """ Callback called when /away -all command is run """ + global whatsapp_servers + words = command.split(None, 2) + if len(words) < 2: + return + message = '' + if len(words) > 2: + message = words[2] + for server in whatsapp_servers: + server.set_away(message) + return weechat.WEECHAT_RC_OK + +class AliasCommand(object): + """Class representing a whatsapp alias command, ie /whatsapp alias ...""" + + def __init__(self, buffer, argv=None): + """ + Args: + bufffer: the weechat buffer the command was run in + argv: list, the arguments provided with the command. + Example, if the command is "/whatsapp alias add abc abc@server.tld" + argv = ['add', 'abc', 'abc@server.tld'] + """ + self.buffer = buffer + self.argv = [] + if argv: + self.argv = argv + self.action = '' + self.jid = '' + self.alias = '' + self.parse() + return + + def add(self): + """Run a "/whatsapp alias add" command""" + global whatsapp_jid_aliases + if not self.alias or not self.jid: + weechat.prnt("", "\nwhatsapp: unable to add alias, missing arguments") + weechat.prnt("", "whatsapp: usage: /whatsapp alias add alias_name jid") + return + # Restrict the character set of aliases. The characters must be writable to + # config file. + invalid_re = re.compile(r'[^a-zA-Z0-9\[\]\\\^_\-{|}@\.]') + if invalid_re.search(self.alias): + weechat.prnt("", "\nwhatsapp: invalid alias: %s" % self.alias) + weechat.prnt("", "whatsapp: use only characters: a-z A-Z 0-9 [ \ ] ^ _ - { | } @ .") + return + # Ensure alias and jid are reasonable length. + max_len = 64 + if len(self.alias) > max_len: + weechat.prnt("", "\nwhatsapp: invalid alias: %s" % self.alias) + weechat.prnt("", "whatsapp: must be no more than %s characters long" % max_len) + return + if len(self.jid) > max_len: + weechat.prnt("", "\nwhatsapp: invalid jid: %s" % self.jid) + weechat.prnt("", "whatsapp: must be no more than %s characters long" % max_len) + return + jid = self.jid + alias = self.alias + if alias in whatsapp_jid_aliases.keys(): + weechat.prnt("", "\nwhatsapp: unable to add alias: %s" % (alias)) + weechat.prnt("", "whatsapp: alias already exists, delete first") + return + if jid in whatsapp_jid_aliases.values(): + weechat.prnt("", "\nwhatsapp: unable to add alias: %s" % (alias)) + for a, j in whatsapp_jid_aliases.items(): + if j == jid: + weechat.prnt("", "whatsapp: jid '%s' is already aliased as '%s', delete first" % + (j, a)) + break + whatsapp_jid_aliases[alias] = jid + self.alias_reset(jid) + return + + def alias_reset(self, jid): + """Reset objects related to the jid modified by an an alias command + + Update any existing buddy objects, server nicklists, and chat objects + that may be using the buddy with the provided jid. + """ + global whatsapp_servers + for server in whatsapp_servers: + buddy = server.search_buddy_list(jid, by='jid') + if not buddy: + continue + server.update_nicklist(buddy=buddy, action='remove') + buddy.set_alias() + server.update_nicklist(buddy=buddy, action='update') + if buddy.chat: + switch_to_buffer = False + if buddy.chat.buffer == self.buffer: + switch_to_buffer = True + buddy.chat.delete() + new_chat = server.add_chat(buddy) + if switch_to_buffer: + weechat.buffer_set(new_chat.buffer, "display", "auto") + return + + def delete(self): + """Run a "/whatsapp alias del" command""" + global whatsapp_jid_aliases + if not self.alias: + weechat.prnt("", "\nwhatsapp: unable to delete alias, missing arguments") + weechat.prnt("", "whatsapp: usage: /whatsapp alias del alias_name") + return + if not self.alias in whatsapp_jid_aliases: + weechat.prnt("", "\nwhatsapp: unable to delete alias '%s', not found" % (self.alias)) + return + jid = whatsapp_jid_aliases[self.alias] + del whatsapp_jid_aliases[self.alias] + self.alias_reset(jid) + return + + def list(self): + """Run a "/whatsapp alias" command to list aliases""" + global whatsapp_jid_aliases + weechat.prnt("", "") + if len(whatsapp_jid_aliases) <= 0: + weechat.prnt("", "whatsapp: no aliases defined") + return + weechat.prnt("", "whatsapp jid aliases:") + len_alias = 5 + len_jid = 5 + for alias, jid in whatsapp_jid_aliases.items(): + if len_alias < len(alias): + len_alias = len(alias) + if len_jid < len(jid): + len_jid = len(jid) + prnt_format = " %-" + str(len_alias) + "s %-" + str(len_jid) + "s" + weechat.prnt("", prnt_format % ('Alias', 'JID')) + for alias, jid in sorted(whatsapp_jid_aliases.items()): + weechat.prnt("", prnt_format % (alias, jid)) + return + + def parse(self): + """Parse the alias command into components""" + if len(self.argv) <= 0: + return + self.action = self.argv[0] + if len(self.argv) > 1: + # Pad argv list to prevent IndexError exceptions + while len(self.argv) < 3: self.argv.append('') + self.alias = self.argv[1] + self.jid = self.argv[2] + return + + def run(self): + """Execute the alias command.""" + if self.action == 'add': + self.add() + elif self.action == 'del': + self.delete() + self.list() + return + +def whatsapp_completion_servers(data, completion_item, buffer, completion): + """ Completion with whatsapp server names. """ + global whatsapp_servers + for server in whatsapp_servers: + weechat.hook_completion_list_add(completion, server.name, + 0, weechat.WEECHAT_LIST_POS_SORT) + return weechat.WEECHAT_RC_OK + +def whatsapp_completion_jid_aliases(data, completion_item, buffer, completion): + """ Completion with whatsapp alias names. """ + global whatsapp_jid_aliases + for alias, jid in sorted(whatsapp_jid_aliases.items()): + weechat.hook_completion_list_add(completion, alias, + 0, weechat.WEECHAT_LIST_POS_SORT) + return weechat.WEECHAT_RC_OK + +# ==================================[ fd ]==================================== + +def whatsapp_fd_cb(data, fd): + """ Callback for reading socket. """ + global whatsapp_servers + for server in whatsapp_servers: + if server.sock == int(fd): + server.recv() + return weechat.WEECHAT_RC_OK + +# ================================[ buffers ]================================= + +def whatsapp_buffer_input_cb(data, buffer, input_data): + """ Callback called for input data on a whatsapp buffer. """ + context = whatsapp_search_context(buffer) + if context["server"] and context["chat"]: + context["chat"].send_message(input_data) + elif context["server"]: + if input_data == "buddies" or "buddies".startswith(input_data): + context["server"].display_buddies() + else: + context["server"].send_message_from_input(input=input_data) + return weechat.WEECHAT_RC_OK + +def whatsapp_buffer_close_cb(data, buffer): + """ Callback called when a whatsapp buffer is closed. """ + context = whatsapp_search_context(buffer) + if context["server"] and context["chat"]: + if context["chat"].buddy: + context["chat"].buddy.chat = None + context["chat"].buffer = "" + context["server"].chats.remove(context["chat"]) + elif context["server"]: + context["server"].buffer = "" + return weechat.WEECHAT_RC_OK + +# ==================================[ timers ]================================== + +def whatsapp_ping_timeout_timer(server_name, remaining_calls): + server = whatsapp_search_server_by_name(server_name) + if server: + server.ping_time_out() + return weechat.WEECHAT_RC_OK + +def whatsapp_ping_timer(server_name, remaining_calls): + server = whatsapp_search_server_by_name(server_name) + if server: + server.ping() + return weechat.WEECHAT_RC_OK + +# ==================================[ main ]================================== + +if __name__ == "__main__" and import_ok: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, + "whatsapp_unload_script", ""): + + version = weechat.info_get("version_number", "") or 0 + whatsapp_hook_commands_and_completions() + whatsapp_config_init() + whatsapp_config_read() + for server in whatsapp_servers: + if weechat.config_boolean(server.options['autoreconnect']): + server.ping() # This will connect and update ping status + server.add_ping_timer() + else: + if weechat.config_boolean(server.options['autoconnect']): + server.connect() + +# ==================================[ end ]=================================== + +def whatsapp_unload_script(): + """ Function called when script is unloaded. """ + global whatsapp_servers + whatsapp_config_write() + for server in whatsapp_servers: + server.disconnect() + server.delete() + return weechat.WEECHAT_RC_OK From c3119add0c1c0c5254cf6d4b532018d37d96a984 Mon Sep 17 00:00:00 2001 From: Mitescu George Dan Date: Mon, 8 May 2017 16:11:28 +0200 Subject: [PATCH 032/642] New script notifym.pl: highly configurable send-notify script for user, channel and server messages --- perl/notifym.pl | 129 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 perl/notifym.pl diff --git a/perl/notifym.pl b/perl/notifym.pl new file mode 100644 index 00000000..60ac470a --- /dev/null +++ b/perl/notifym.pl @@ -0,0 +1,129 @@ +# +# Copyright (c) 2016 Mitescu George Dan +# Copyright (c) 2016 Berechet Mihai +# +# 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 . +# + +my $SCRIPT_NAME = "notifym"; +my $VERSION = "1.1"; + +# use Data::Dumper; + +weechat::register($SCRIPT_NAME, "dmitescu", $VERSION, "GPL3", + "Script which uses libnotify to alert the user about certain events.", + "", ""); + +my %options_def = ( 'notify_pv' => ['on', + 'Notify on private message.'], + 'notify_mentions' => ['on', + 'Notify on mention in all channel.'], + 'notify_channels' => ['off', + 'Notify all messages from whitelisted channels.'], + 'notify_servers' => ['off', + 'Notify all messages from whitelisted servers.'], + 'channel_whitelist' => ['.*', + 'Channel white-list. (perl regex required)'], + 'server_whitelist' => ['.*', + 'Server white-list. (perl regex required)'] + ); + +my %options = (); + +# Initiates options if non-existent and loads them +sub init { + foreach my $opt (keys %options_def) { + if (!weechat::config_is_set_plugin($opt)) { + weechat::config_set_plugin($opt, $options_def{$opt}[0]); + } + $options{$opt} = weechat::config_get_plugin($opt); + weechat::config_set_desc_plugin($opt, $options_def{$opt}[1] + . " (default: \"" . $options_def{$opt}[0] + . "\")"); + } +} + +# On update option, load it into the hash +sub update_config_handler { + my ($data, $option, $value) = @_; + $name = substr($option, + length("plugins.var.perl.".$SCRIPT_NAME."."), + length($option)); + $options{$name} = $value; + # weechat::print("", $name . " is now " . $value . "!"); + return weechat::WEECHAT_RC_OK; +} + +# Function to send notification +sub send_notification { + my ($urgency, $summary, $body) = @_; + my $retval = system("notify-send -u $urgency \"$summary\" \"$body\""); +} + +# Verify matching options +sub opt_match { + my ($str, $option) = @_; + return $str =~ /$options{$option}/; +} + +# Handlers for signals : +# Private message + +sub message_handler { + my ($data, $signal, $signal_data) = @_; + # my @pta = split(":", $signal_data); + # weechat::print("", Dumper(\%options)); + my ($server, $command) = $signal =~ /(.*),irc_in_(.*)/; + if ($command eq 'PRIVMSG') { + my $hash_in = {"message" => $signal_data}; + my $hash_data = weechat::info_get_hashtable("irc_message_parse", $hash_in); + + my $nick = $hash_data->{"nick"}; + my $text = $hash_data->{"text"}; + my $chan = $hash_data->{"channel"}; + + if (($options{'notify_servers'} eq 'on') && + opt_match($server, 'server_whitelist')) { + # Server match + send_notification("normal", "$nick:", "$text"); + } elsif (($options{'notify_channels'} eq 'on') && + opt_match($chan, 'channel_whitelist')){ + # Channel match + send_notification("normal", "$nick:", "$text"); + } elsif ($options{'notify_pv'} eq 'on') { + # Private message match + my $mynick = weechat::info_get("irc_nick", $server); + if ($chan eq $mynick) { + send_notification("critical", "$nick says:", "$text"); + } + } else { + } + + # Mention match + my $mynick = weechat::info_get("irc_nick", $server); + if (index($text, $mynick) != -1) { + send_notification("critical", "$nick mentioned you!", ""); + } + # weechat::print("", Dumper($hash_data)); + } + return weechat::WEECHAT_RC_OK; +} + +# Main execution point + +init(); +send_notification("critical", "Starting NotifyM plugin!", ""); +weechat::hook_config("plugins.var.perl." . $SCRIPT_NAME . ".*", + "update_config_handler", ""); +weechat::hook_signal("*,irc_in_*", "message_handler", ""); From c6e66a1d8048d81a20716ee0d730c206e2d55560 Mon Sep 17 00:00:00 2001 From: Guido Berhoerster Date: Mon, 8 May 2017 17:35:12 +0200 Subject: [PATCH 033/642] New script notification.py: notify events through desktop notifications and an optional status icon --- python/notification.py | 866 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 866 insertions(+) create mode 100644 python/notification.py diff --git a/python/notification.py b/python/notification.py new file mode 100644 index 00000000..a814548d --- /dev/null +++ b/python/notification.py @@ -0,0 +1,866 @@ +# +# Copyright (C) 2014 Guido Berhoerster +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# 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 . +# + +import os +import sys +import time +import re +import select +import signal +import errno +import fcntl +import cgi +import multiprocessing + + +SCRIPT_NAME = 'notification' +APPLICATION = 'Weechat' +VERSION = '1' +AUTHOR = 'Guido Berhoerster' +COPYRIGHT = '(C) 2014 Guido Berhoerster' +SUBTITLE = 'Notification Plugin for Weechat' +HOMEPAGE = 'https://code.guido-berhoerster.org/addons/weechat-scripts/weechat-notification-script/' +EMAIL = 'guido+weechat@berhoerster.name' +DESCRIPTION = 'Notifies of a number of events through desktop notifications ' \ + 'and an optional status icon' +DEFAULT_SETTINGS = { + 'status_icon': ('weechat', 'path or name of the status icon'), + 'notification_icon': ('weechat', 'path or name of the icon shown in ' + 'notifications'), + 'preferred_toolkit': ('', 'preferred UI toolkit'), + 'notify_on_displayed_only': ('on', 'only notify of messages that are ' + 'actually displayed'), + 'notify_on_privmsg': ('on', 'notify when receiving a private message'), + 'notify_on_highlight': ('on', 'notify when a messages is highlighted'), + 'notify_on_dcc_request': ('on', 'notify on DCC requests') +} +BUFFER_SIZE = 1024 + + +class NetstringParser(object): + """Netstring Stream Parser""" + + IN_LENGTH = 0 + IN_STRING = 1 + + def __init__(self, on_string_complete): + self.on_string_complete = on_string_complete + self.length = 0 + self.input_buffer = '' + self.state = self.IN_LENGTH + + def parse(self, data): + self.input_buffer += data + ret = True + while ret: + if self.state == self.IN_LENGTH: + ret = self.parse_length() + else: + ret = self.parse_string() + + def parse_length(self): + length, delimiter, self.input_buffer = self.input_buffer.partition(':') + if not delimiter: + return False + try: + self.length = int(length) + except ValueError: + raise SyntaxError('Invalid length: %s' % length) + self.state = self.IN_STRING + return True + + def parse_string(self): + input_buffer_len = len(self.input_buffer) + if input_buffer_len < self.length + 1: + return False + string = self.input_buffer[0:self.length] + if self.input_buffer[self.length] != ',': + raise SyntaxError('Missing delimiter') + self.input_buffer = self.input_buffer[self.length + 1:] + self.length = 0 + self.state = self.IN_LENGTH + self.on_string_complete(string) + return True + + +def netstring_encode(*args): + return ''.join(['%d:%s,' % (len(element), element) for element in + args]) + +def netstring_decode(netstring): + result = [] + def append_result(data): + result.append(data) + np = NetstringParser(append_result) + np.parse(netstring) + return result + +def dispatch_weechat_callback(*args): + return weechat_callbacks[args[0]](*args) + +def create_weechat_callback(method): + global weechat_callbacks + + method_id = str(id(method)) + weechat_callbacks[method_id] = method + return method_id + + +class Notifier(object): + """Simple notifier which discards all notifications, base class for all + other notifiers + """ + + def __init__(self, icon): + flags = fcntl.fcntl(sys.stdin, fcntl.F_GETFL) + fcntl.fcntl(sys.stdin, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + self.parser = NetstringParser(self.on_command_received) + + def on_command_received(self, raw_command): + command_args = netstring_decode(raw_command) + if len(command_args) > 1: + command = command_args[0] + args = netstring_decode(command_args[1]) + else: + command = command_args[0] + args = [] + getattr(self, command)(*args) + + def notify(self, summary, message, icon): + pass + + def reset(self): + pass + + def run(self): + poll = select.poll() + poll.register(sys.stdin, select.POLLIN | select.POLLPRI) + + while True: + try: + events = poll.poll() + except select.error as e: + if e.args and e.args[0] == errno.EINTR: + continue + else: + raise e + for fd, event in events: + if event & (select.POLLIN | select.POLLPRI): + buffer_ = os.read(fd, BUFFER_SIZE) + if buffer_ != '': + self.parser.parse(buffer_) + if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL): + sys.exit(1) + + +class Gtk2Notifier(Notifier): + """GTK 2 notifier based on pygtk and pynotify""" + + def __init__(self, icon): + super(Gtk2Notifier, self).__init__(icon) + + pynotify.init(APPLICATION) + + gobject.io_add_watch(sys.stdin, gobject.IO_IN | gobject.IO_PRI, + self.on_input) + + if not icon: + icon_name = None + icon_pixbuf = None + elif icon.startswith('/'): + icon_name = None + try: + icon_pixbuf = gtk.gdk.Pixbuf.new_from_file(icon) + except gobject.GError: + icon_pixbuf = None + else: + icon_name = icon + icon_pixbuf = None + + if icon_name or icon_pixbuf: + self.status_icon = gtk.StatusIcon() + self.status_icon.set_title(APPLICATION) + self.status_icon.set_tooltip_text(APPLICATION) + self.status_icon.connect('activate', self.on_activate) + if icon_name: + self.status_icon.set_from_icon_name(icon_name) + elif icon_pixbuf: + self.status_icon.set_from_pixbuf(icon_pixbuf) + else: + self.status_icon = None + + def on_input(self, fd, cond): + if cond & (gobject.IO_IN | gobject.IO_PRI): + try: + buffer_ = os.read(fd.fileno(), BUFFER_SIZE) + if buffer_ != '': + self.parser.parse(buffer_) + except EOFError: + gtk.main_quit() + return False + + if cond & (gobject.IO_ERR | gobject.IO_HUP): + gtk.main_quit() + return False + + return True + + def on_activate(self, widget): + self.reset() + + def on_notification_closed(self, notification): + if notification.get_closed_reason() == 2: + self.reset() + + def notify(self, summary, message, icon): + if self.status_icon: + self.status_icon.set_tooltip_text('%s: %s' % (APPLICATION, + summary)) + self.status_icon.set_blinking(True) + + if icon and icon.startswith('/'): + icon_name = None + try: + icon_pixbuf = gtk.gdk.Pixbuf.new_from_file(icon) + except gobject.GError: + icon_pixbuf = None + else: + icon_name = icon + icon_pixbuf = None + + if 'body-markup' in pynotify.get_server_caps(): + body = cgi.escape(message) + else: + body = message + + notification = pynotify.Notification(summary, body, icon_name) + if icon_pixbuf is not None: + notification.set_image_from_pixbuf(icon_pixbuf) + notification.connect('closed', self.on_notification_closed) + notification.show() + + def reset(self): + if self.status_icon: + self.status_icon.set_tooltip_text(APPLICATION) + self.status_icon.set_blinking(False) + + def run(self): + gtk.main() + + +class Gtk3Notifier(Notifier): + """GTK3 notifier based on GObject Introspection Bindings for GTK 3 and + libnotify + """ + + def __init__(self, icon): + super(Gtk3Notifier, self).__init__(icon) + + Notify.init(APPLICATION) + + GLib.io_add_watch(sys.stdin, GLib.IO_IN | GLib.IO_PRI, self.on_input) + + if not icon: + self.icon_name = None + self.icon_pixbuf = None + elif icon.startswith('/'): + self.icon_name = None + try: + self.icon_pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon) + except GLib.GError: + self.icon_pixbuf = None + else: + self.icon_name = icon + self.icon_pixbuf = None + + if self.icon_name or self.icon_pixbuf: + # create blank, fully transparent pixbuf in order to simulate + # blinking + self.blank_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, + True, 8, 22, 22) + self.blank_pixbuf.fill(0x00) + + self.blink_on = True + self.blink_timeout_id = None + + self.status_icon = Gtk.StatusIcon.new() + self.status_icon.set_title(APPLICATION) + self.status_icon.set_tooltip_text(APPLICATION) + self.status_icon.connect('activate', self.on_activate) + self.update_icon() + else: + self.status_icon = None + + def on_input(self, fd, cond): + if cond & (GLib.IO_IN | GLib.IO_PRI): + try: + self.parser.parse(os.read(fd.fileno(), BUFFER_SIZE)) + except EOFError: + Gtk.main_quit() + return False + + if cond & (GLib.IO_ERR | GLib.IO_HUP): + Gtk.main_quit() + return False + + return True + + def on_activate(self, widget): + self.reset() + + def update_icon(self): + if not self.blink_on: + self.status_icon.set_from_pixbuf(self.blank_pixbuf) + elif self.icon_name: + self.status_icon.set_from_icon_name(self.icon_name) + elif self.icon_pixbuf: + self.status_icon.set_from_pixbuf(self.icon_pixbuf) + + def on_blink_timeout(self): + self.blink_on = not self.blink_on + self.update_icon() + return True + + def on_notification_closed(self, notification): + if notification.get_closed_reason() == 2: + self.reset() + + def notify(self, summary, message, icon): + if self.status_icon: + self.status_icon.set_tooltip_text('%s: %s' % (APPLICATION, + summary)) + if self.blink_timeout_id is None: + self.blink_timeout_id = GLib.timeout_add(500, + self.on_blink_timeout) + + if icon and icon.startswith('/'): + icon_name = None + try: + icon_pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon) + except GLib.GError: + icon_pixbuf = None + else: + icon_name = icon + icon_pixbuf = None + + if 'body-markup' in Notify.get_server_caps(): + body = cgi.escape(message) + else: + body = message + + notification = Notify.Notification.new(summary, body, icon_name) + if icon_pixbuf is not None: + notification.set_image_from_pixbuf(icon_pixbuf) + notification.connect('closed', self.on_notification_closed) + notification.show() + + def reset(self): + if self.status_icon: + self.status_icon.set_tooltip_text(APPLICATION) + if self.blink_timeout_id is not None: + GLib.source_remove(self.blink_timeout_id) + self.blink_timeout_id = None + self.blink_on = True + self.update_icon() + + def run(self): + Gtk.main() + + +class Qt4Notifier(Notifier): + """Qt 4 notifier""" + + def __init__(self, icon): + super(Qt4Notifier, self).__init__(icon) + + signal.signal(signal.SIGINT, self.on_sigint) + + self.qapplication = QtGui.QApplication([]) + + self.readable_notifier = QtCore.QSocketNotifier(sys.stdin.fileno(), + QtCore.QSocketNotifier.Read) + self.readable_notifier.activated.connect(self.on_input) + self.readable_notifier.setEnabled(True) + + if not icon: + self.icon = None + elif icon.startswith('/'): + self.icon = QtGui.QIcon(icon) + else: + self.icon = QtGui.QIcon.fromTheme(icon) + + if self.icon: + # create blank, fully transparent pixbuf in order to simulate + # blinking + self.blank_icon = QtGui.QIcon() + + self.blink_on = True + self.blinking_timer = QtCore.QTimer() + self.blinking_timer.setInterval(500) + self.blinking_timer.timeout.connect(self.on_blink_timeout) + + self.status_icon = QtGui.QSystemTrayIcon() + self.status_icon.setToolTip(APPLICATION) + self.update_icon() + self.status_icon.setVisible(True) + self.status_icon.activated.connect(self.on_activated) + self.status_icon.messageClicked.connect(self.on_message_clicked) + else: + self.status_icon = None + + def on_sigint(self, signo, frame): + self.qapplication.exit(0) + + def on_input(self, fd): + try: + self.parser.parse(os.read(fd, BUFFER_SIZE)) + except EOFError: + self.qapplication.exit(1) + + def on_activated(self, reason): + self.reset() + + def on_message_clicked(self): + self.reset() + + def on_blink_timeout(self): + self.blink_on = not self.blink_on + self.update_icon() + + def update_icon(self): + if not self.blink_on: + self.status_icon.setIcon(self.blank_icon) + else: + self.status_icon.setIcon(self.icon) + + def notify(self, summary, message, icon): + if self.status_icon: + self.status_icon.setToolTip('%s: %s' % (APPLICATION, + cgi.escape(summary))) + self.blinking_timer.start() + if self.status_icon.supportsMessages(): + self.status_icon.showMessage(summary, message, + QtGui.QSystemTrayIcon.NoIcon) + + def reset(self): + if self.status_icon: + self.blinking_timer.stop() + self.blink_on = True + self.update_icon() + self.status_icon.setToolTip(APPLICATION) + + def run(self): + sys.exit(self.qapplication.exec_()) + + +class KDE4Notifier(Notifier): + """KDE 4 notifier based on PyKDE4""" + + def __init__(self, icon): + super(KDE4Notifier, self).__init__(icon) + + signal.signal(signal.SIGINT, self.on_sigint) + + aboutData = kdecore.KAboutData(APPLICATION.lower(), '', + kdecore.ki18n(APPLICATION), VERSION, kdecore.ki18n(SUBTITLE), + kdecore.KAboutData.License_GPL_V3, kdecore.ki18n(COPYRIGHT), + kdecore.ki18n (''), HOMEPAGE, EMAIL) + kdecore.KCmdLineArgs.init(aboutData) + self.kapplication = kdeui.KApplication() + + self.readable_notifier = QtCore.QSocketNotifier(sys.stdin.fileno(), + QtCore.QSocketNotifier.Read) + self.readable_notifier.activated.connect(self.on_input) + self.readable_notifier.setEnabled(True) + + if not icon: + icon_qicon = None + icon_name = None + elif icon.startswith('/'): + icon_qicon = QtGui.QIcon(icon) + icon_name = None + else: + icon_qicon = None + icon_name = icon + + if icon_name or icon_pixmap: + self.status_notifier = kdeui.KStatusNotifierItem(self.kapplication) + self.status_notifier.setCategory( + kdeui.KStatusNotifierItem.Communications) + if icon_name: + self.status_notifier.setIconByName(icon_name) + self.status_notifier.setToolTip(icon_name, APPLICATION, + SUBTITLE) + else: + self.status_notifier.setIconByPixmap(icon_qicon) + self.status_notifier.setToolTip(icon_qicon, APPLICATION, + SUBTITLE) + self.status_notifier.setStandardActionsEnabled(False) + self.status_notifier.setStatus(kdeui.KStatusNotifierItem.Active) + self.status_notifier.setTitle(APPLICATION) + self.status_notifier.activateRequested.connect( + self.on_activate_requested) + else: + self.status_notifier = None + + def on_sigint(self, signo, frame): + self.kapplication.exit(0) + + def on_input(self, fd): + try: + self.parser.parse(os.read(fd, BUFFER_SIZE)) + except EOFError: + self.kapplication.exit(1) + + def on_activate_requested(self, active, pos): + self.reset() + + def notify(self, summary, message, icon): + if self.status_notifier: + self.status_notifier.setToolTipSubTitle(cgi.escape(summary)) + self.status_notifier.setStatus( + kdeui.KStatusNotifierItem.NeedsAttention) + + if icon: + if icon.startswith('/'): + pixmap = QtGui.QPixmap.load(icon) + else: + pixmap = kdeui.KIcon(icon).pixmap(kdeui.KIconLoader.SizeHuge, + kdeui.KIconLoader.SizeHuge) + else: + pixmap = QtGui.QPixmap() + kdeui.KNotification.event(kdeui.KNotification.Notification, summary, + cgi.escape(message), pixmap) + + def reset(self): + if self.status_notifier: + self.status_notifier.setStatus(kdeui.KStatusNotifierItem.Active) + self.status_notifier.setToolTipTitle(APPLICATION) + self.status_notifier.setToolTipSubTitle(SUBTITLE) + + def run(self): + sys.exit(self.kapplication.exec_()) + + +class NotificationProxy(object): + """Proxy object for interfacing with the notifier process""" + + def __init__(self, preferred_toolkit, status_icon): + self.script_file = os.path.realpath(__file__) + self._status_icon = status_icon + self._preferred_toolkit = preferred_toolkit + self.notifier_process_hook = None + self.spawn_timer_hook = None + self.next_spawn_time = 0.0 + + self.spawn_notifier_process() + + @property + def status_icon(self): + return self._status_icon + + @status_icon.setter + def status_icon(self, value): + self._status_icon = value + self.terminate_notifier_process() + self.spawn_notifier_process() + + @property + def preferred_toolkit(self): + return self._preferred_toolkit + + @preferred_toolkit.setter + def preferred_toolkit(self, value): + self._preferred_toolkit = value + self.terminate_notifier_process() + self.spawn_notifier_process() + + def on_notifier_process_event(self, data, command, return_code, output, + error_output): + if return_code != weechat.WEECHAT_HOOK_PROCESS_RUNNING: + if return_code == weechat.WEECHAT_HOOK_PROCESS_ERROR: + error = '%sfailed to run notifier' % weechat.prefix("error") + else: + error = '%snotifier exited with exit status %d' % \ + (weechat.prefix("error"), return_code) + if output: + error += '\nstdout:%s' % output + if error_output: + error += '\nstderr:%s' % error_output + weechat.prnt('', error) + self.notifier_process_hook = None + self.spawn_notifier_process() + return weechat.WEECHAT_RC_OK + + def on_spawn_timer(self, data, remaining): + self.spawn_timer_hook = None + if not self.notifier_process_hook: + self.spawn_notifier_process() + return weechat.WEECHAT_RC_OK + + def spawn_notifier_process(self): + if self.notifier_process_hook or self.spawn_timer_hook: + return + + # do not try to respawn a notifier more than once every ten seconds + now = time.time() + if long(self.next_spawn_time - now) > 0: + self.spawn_timer_hook = \ + weechat.hook_timer(long((self.next_spawn_time - now) * + 1000), 0, 1, 'dispatch_weechat_callback', + create_weechat_callback(self.on_spawn_timer)) + return + + self.next_spawn_time = now + 10 + self.notifier_process_hook = \ + weechat.hook_process_hashtable(sys.executable, {'arg1': + self.script_file, 'arg2': self.preferred_toolkit, 'arg3': + self.status_icon, 'stdin': '1'}, 0, + 'dispatch_weechat_callback', + create_weechat_callback(self.on_notifier_process_event)) + + def terminate_notifier_process(self): + if self.spawn_timer_hook: + weechat.unhook(self.spawn_timer_hook) + self.spawn_timer_hook = None + if self.notifier_process_hook: + weechat.unhook(self.notifier_process_hook) + self.notifier_process_hook = None + self.next_spawn_time = 0.0 + + def send(self, command, *args): + if self.notifier_process_hook: + if args: + weechat.hook_set(self.notifier_process_hook, 'stdin', + netstring_encode(netstring_encode(command, + netstring_encode(*args)))) + else: + weechat.hook_set(self.notifier_process_hook, 'stdin', + netstring_encode(netstring_encode(command))) + + def notify(self, summary, message, icon): + self.send('notify', summary, message, icon) + + def reset(self): + self.send('reset') + + +class NotificationPlugin(object): + """Weechat plugin""" + + def __init__(self): + self.DCC_SEND_RE = re.compile(r':(?P\S+) PRIVMSG \S+ :' + r'\x01DCC SEND (?P\S+) \d+ \d+ (?P\d+)') + self.DCC_CHAT_RE = re.compile(r':(?P\S+) PRIVMSG \S+ :' + r'\x01DCC CHAT ') + + weechat.register(SCRIPT_NAME, AUTHOR, VERSION, 'GPL3', DESCRIPTION, '', + '') + + for option, (value, description) in DEFAULT_SETTINGS.iteritems(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value) + weechat.config_set_desc_plugin(option, '%s (default: "%s")' % + (description, value)) + + self.notification_proxy = NotificationProxy( + weechat.config_get_plugin('preferred_toolkit'), + weechat.config_get_plugin('status_icon')) + + weechat.hook_print('', 'irc_privmsg', '', 1, + 'dispatch_weechat_callback', + create_weechat_callback(self.on_message)) + weechat.hook_signal('key_pressed', 'dispatch_weechat_callback', + create_weechat_callback(self.on_key_pressed)) + weechat.hook_signal('irc_dcc', 'dispatch_weechat_callback', + create_weechat_callback(self.on_dcc)) + weechat.hook_config('plugins.var.python.%s.*' % SCRIPT_NAME, + 'dispatch_weechat_callback', + create_weechat_callback(self.on_config_changed)) + + def on_message(self, data, buffer, date, tags, displayed, highlight, + prefix, message): + if weechat.config_get_plugin('notify_on_displayed_only') == 'on' and \ + int(displayed) != 1: + return weechat.WEECHAT_RC_OK + + formatted_date = time.strftime('%H:%M', time.localtime(float(date))) + if 'notify_private' in tags.split(',') and \ + weechat.config_get_plugin('notify_on_privmsg') == 'on': + summary = 'Private message from %s at %s' % (prefix, + formatted_date) + self.notification_proxy.notify(summary, message, + weechat.config_get_plugin('notification_icon')) + elif int(highlight) == 1 and \ + weechat.config_get_plugin('notify_on_highlight') == 'on': + summary = 'Highlighted message from %s at %s' % (prefix, + formatted_date) + self.notification_proxy.notify(summary, message, + weechat.config_get_plugin('notification_icon')) + + return weechat.WEECHAT_RC_OK + + def on_dcc(self, data, signal, signal_data): + if weechat.config_get_plugin('notify_on_dcc') != 'on': + return weechat.WEECHAT_RC_OK + + matches = self.DCC_SEND_RE.match(signal_data) + if matches: + summary = 'DCC send request from %s' % matches.group('sender') + message = 'Filname: %s, Size: %d bytes' % \ + (matches.group('filename'), int(matches.group('size'))) + self.notification_proxy.notify(summary, message, + weechat.config_get_plugin('notification_icon')) + return weechat.WEECHAT_RC_OK + + matches = self.DCC_CHAT_RE.match(signal_data) + if matches: + summary = 'DCC chat request from %s' % matches.group('sender') + message = '' + self.notification_proxy.notify(summary, message, + weechat.config_get_plugin('notification_icon')) + return weechat.WEECHAT_RC_OK + + return weechat.WEECHAT_RC_OK + + def on_key_pressed(self, data, signal, signal_data): + self.notification_proxy.reset() + return weechat.WEECHAT_RC_OK + + def on_config_changed(self, data, option, value): + if option.endswith('.preferred_toolkit'): + self.notification_proxy.preferred_toolkit = value + elif option.endswith('.status_icon'): + self.notification_proxy.status_icon = value + return weechat.WEECHAT_RC_OK + + +def import_modules(modules): + for module_name, fromlist in modules: + if fromlist: + module = __import__(module_name, fromlist=fromlist) + for identifier in fromlist: + globals()[identifier] = getattr(module, identifier) + else: + globals()[module_name] = __import__(module_name) + +def try_import_modules(modules): + try: + import_modules(modules) + except ImportError: + sys.exit(1) + sys.exit(0) + + +if __name__ == '__main__': + if sys.argv[0] == '__weechat_plugin__': + # running as Weechat plugin + import weechat + + weechat_callbacks = {} + + plugin = NotificationPlugin() + elif len(sys.argv) == 3: + # running as the notifier process + preferred_toolkit = sys.argv[1] + icon = sys.argv[2] + + # required modules for each toolkit + toolkits_modules = { + 'gtk3': [ + ('gi.repository', [ + 'GLib', + 'GdkPixbuf', + 'Gtk', + 'Notify' + ]) + ], + 'gtk2': [ + ('pygtk', []), + ('gobject', []), + ('gtk', []), + ('pynotify', []) + ], + 'qt4': [ + ('PyQt4', [ + 'QtGui', + 'QtCore' + ]) + ], + 'kde4': [ + ('PyQt4', [ + 'QtGui', + 'QtCore' + ]), + ('PyKDE4', [ + 'kdecore', + 'kdeui' + ]) + ], + '': [] + } + available_toolkits = [] + selected_toolkit = '' + + # find available toolkits by spawning a process for each toolkit which + # tries to import all required modules and returns an exit status of 1 + # in case of an import error + for toolkit in toolkits_modules: + process = multiprocessing.Process(target=try_import_modules, + args=(toolkits_modules[toolkit],)) + process.start() + process.join(3) + if process.is_alive(): + process.terminate() + process.join() + if process.exitcode == 0: + available_toolkits.append(toolkit) + + # select toolkit based on either explicit preference or the + # availability of modules and the used desktop environment + if preferred_toolkit: + if preferred_toolkit in available_toolkits: + selected_toolkit = preferred_toolkit + else: + if 'KDE_FULL_SESSION' in os.environ: + # preferred order if running KDE4 + toolkits = ['kde4', 'qt4', 'gtk3', 'gtk2'] + else: + # preferred order for all other desktop environments + toolkits = ['gtk3', 'gtk2', 'qt4', 'kde4'] + for toolkit in toolkits: + if toolkit in available_toolkits: + selected_toolkit = toolkit + break + + # import required toolkit modules + import_modules(toolkits_modules[selected_toolkit]) + + # run selected notifier + if selected_toolkit == 'gtk3': + notifier = Gtk3Notifier(icon) + elif selected_toolkit == 'gtk2': + notifier = Gtk2Notifier(icon) + elif selected_toolkit == 'qt4': + notifier = Qt4Notifier(icon) + elif selected_toolkit == 'kde4': + notifier = KDE4Notifier(icon) + else: + notifier = Notifier(icon) + notifier.run() + else: + sys.exit(1) From 083e659b70ad995437b0c181a140036f7fd3355f Mon Sep 17 00:00:00 2001 From: timss Date: Mon, 8 May 2017 17:54:08 +0200 Subject: [PATCH 034/642] New script lastfm2.py: send your latest Last.fm track to the current buffer --- python/lastfm2.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 python/lastfm2.py diff --git a/python/lastfm2.py b/python/lastfm2.py new file mode 100644 index 00000000..292fc6d9 --- /dev/null +++ b/python/lastfm2.py @@ -0,0 +1,130 @@ +# Copyright (c) 2015 by timss +# +# 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 . + +SCRIPT_NAME = 'lastfm2' +SCRIPT_AUTHOR = "timss " +SCRIPT_VERSION = '0.1' +SCRIPT_LICENSE = 'GPL3' +SCRIPT_DESC = "Sends latest played track for a Last.fm user to the current buffer" + +SCRIPT_COMMAND = 'lastfm' +SCRIPT_HELP = \ +"""Sends latest played track for a Last.fm user to the current buffer. + + /lastfm + +By default, the script will use the username set in {SCRIPT_NAME} configuration: + + /set plugins.var.python.{SCRIPT_NAME}.user yourusername + +In addition, an username may be specified as an argument: + + /lastfm anotherusername + +The command which output will be sent to the buffer may be customized as well: + + /set plugins.var.python.{SCRIPT_NAME}.command I'm listening to {{track}} + +Finally, the command when specifying another username can also be set: + + /set plugins.var.python.{SCRIPT_NAME}.command_arg {{user}} is litening to {{track}} + +Inspiration and credit: + - lastfm.py, Adam Saponara + - lastfmnp.py, i7c + - lastfmapi.py, Christophe De Troyer + +""".format(SCRIPT_NAME=SCRIPT_NAME) + +try: + import weechat + import_ok = True +except ImportError: + print("This script must be run under WeeChat.") + print("Get WeeChat now at: http://www.weechat.org/") + import_ok = False + +import json + +def init_config(): + """Set plugin options to defaults if not already done""" + config = { + 'user': '', + 'command': '/me is listening to {track}', + 'command_arg': '{user} is listening to {track}', + 'api_key': 'ae51c9df97d4e90c35ffd302e987efd2', + 'api_url': 'https://ws.audioscrobbler.com/2.0/?method=user.getRecentTracks&user={user}&limit=1&api_key={api_key}&format=json', + 'timeout': '10000' + } + + for option, default in config.items(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, default) + +def get_recent_track(data, command, rc, out, err): + """Get last track played (artist - name)""" + if rc == weechat.WEECHAT_HOOK_PROCESS_ERROR: + weechat.prnt('', "Error with command '{}'".format(command)) + elif rc > 0: + weechat.prnt('', "rc = {}".format(rc)) + + try: + data = json.loads(out) + + if data.has_key('error'): + weechat.prnt('', "Last.fm API error: '{}'".format(data['message'])) + else: + artist = data['recenttracks']['track'][0]['artist']['#text'] + name = data['recenttracks']['track'][0]['name'] + track = "{} - {}".format(artist, name) + user = data['recenttracks']['@attr']['user'].lower() + + # print username or not, depending on config/arg + if user == weechat.config_get_plugin('user').lower(): + cmd = weechat.config_get_plugin('command') + else: + cmd = weechat.config_get_plugin('command_arg') + + # format isn't picky, ignores {user} if not present + cmd = cmd.format(user=user, track=track) + + weechat.command(weechat.current_buffer(), cmd) + except IndexError, KeyError: + weechat.prnt('', "Error parsing Last.fm data") + + return weechat.WEECHAT_RC_OK + +def lastfm_cmd(data, buffer, args): + """Print last track played""" + api_key = weechat.config_get_plugin('api_key') + api_url = weechat.config_get_plugin('api_url') + timeout = weechat.config_get_plugin('timeout') + + # use user in argument, or in config + if args: + user = args + else: + user = weechat.config_get_plugin('user') + + url = 'url:' + api_url.format(user=user.lower(), api_key=api_key) + weechat.hook_process(url, int(timeout), 'get_recent_track', '') + + return weechat.WEECHAT_RC_OK + +if __name__ == '__main__' and import_ok: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''): + init_config() + weechat.hook_command(SCRIPT_COMMAND, SCRIPT_HELP, '', '', '', 'lastfm_cmd', '') + From 733154ae4d82dfb391ea6436e19c166a8ceae30f Mon Sep 17 00:00:00 2001 From: Ewa Baumgarten Date: Mon, 8 May 2017 17:57:53 +0200 Subject: [PATCH 035/642] New script cleanbuffer.rb: clear a buffer, on znc / locally or both --- ruby/cleanbuffer.rb | 81 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 ruby/cleanbuffer.rb diff --git a/ruby/cleanbuffer.rb b/ruby/cleanbuffer.rb new file mode 100644 index 00000000..6c1179f8 --- /dev/null +++ b/ruby/cleanbuffer.rb @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +=begin +cleanbuffer.rb, a script that tells znc to flush the current buffer +Copyright (C) 2016 Ewa Baumgarten + +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 [http://www.gnu.org/licenses/]. +=end + + +SCRIPT_NAME = 'cleanbuffer' +SCRIPT_AUTHOR = 'manavortex' +SCRIPT_DESC = 'Clears the current buffer, both in weechat and on the znc bouncer' +SCRIPT_VERSION = '0.1' +SCRIPT_LICENSE = 'GPL3' +SCRIPT_ARGS = "[znc|weechat|all]" +ARGUMENTS_DESC = <<-EOD +call with /clean or /clean znc to clean buffer on znc, call /clean buffer to clean both +EOD + +def weechat_init + + Weechat.register SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '' + + Weechat.hook_command 'clean', 'cleans the current buffer, on znc or weechat', + ' znc | weechat | all ', + [ 'znc: cleans the content of the current buffer with the znc bouncer', + 'weechat: cleans the content of the current buffer locally', + 'all: purges buffer', + ].join("\n"), + [ + 'znc', + 'weechat', + 'all' + ].join(' || '), + 'clean_callback', '' + + return Weechat::WEECHAT_RC_OK + +end + +def clean_callback data, buffer, cmd + case cmd.downcase + when 'znc' + znc_clean_buffer(buffer, false) + when 'weechat' + clean_local_buffer(buffer) + when 'all' + znc_clean_buffer(buffer, true) + else + Weechat::WEECHAT_RC_ERROR + end +end + +def znc_clean_buffer(buffer, wipe) + + buffername = Weechat.buffer_get_string(buffer, "name") + Weechat.command("", ("/msg *status ClearBuffer " << buffername)) + + if wipe then + clean_local_buffer(buffer) + end + + #Weechat.command("", ('buffer ' << buffername << ' successfully cleaned!')) + return Weechat::WEECHAT_RC_OK +end + +def clean_local_buffer(buffer) + Weechat.command("", "/buffer clear") +end + From 8a371c9204a420e0083fd9dbca2bc14894210652 Mon Sep 17 00:00:00 2001 From: Trevor 'tee' Slocum Date: Mon, 8 May 2017 18:10:53 +0200 Subject: [PATCH 036/642] New script buffer_bind.py: bind meta- to the current buffer --- python/buffer_bind.py | 57 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 python/buffer_bind.py diff --git a/python/buffer_bind.py b/python/buffer_bind.py new file mode 100644 index 00000000..418abedd --- /dev/null +++ b/python/buffer_bind.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# 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 . +# + +import weechat as w + +SCRIPT_NAME = "buffer_bind" +SCRIPT_AUTHOR = "Trevor 'tee' Slocum " +SCRIPT_VERSION = "1.0" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Bind meta- to the current buffer" +SCRIPT_NOTE = """Case sensitivity is controlled via plugins.var.python.%s.case_sensitive (default: off) + +%s is a port of irssi's window_alias written by veli@piipiip.net""" % (SCRIPT_NAME, SCRIPT_NAME) + +SETTINGS = { + "case_sensitive": "off" +} + + +def command_buffer_bind(data, buffer, args): + if len(args) == 1 and args[0] != "": + bindkey = args[0] + buffername = w.buffer_get_string(buffer, "name") + + bind_keys = [bindkey] + if w.config_get_plugin("case_sensitive") == "off" and bindkey.isalpha(): + bind_keys.append(bindkey.swapcase()) + for bind_keys_i in bind_keys: + w.command(buffer, "/key bind meta-%s /buffer %s" % (bind_keys_i, buffername)) + + w.prnt(buffer, "Buffer %s is now accessible with meta-%s" % (buffername, bindkey)) + else: + w.command(buffer, "/help %s" % SCRIPT_NAME) + + return w.WEECHAT_RC_OK_EAT + + +if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, + SCRIPT_DESC, "", ""): + for option, value in SETTINGS.items(): + if not w.config_is_set_plugin(option): + w.config_set_plugin(option, value) + + w.hook_command(SCRIPT_NAME, SCRIPT_DESC, "", SCRIPT_NOTE, "key", "command_buffer_bind", "") From d3e55b44fb27534701c1aefd7a8c9162840995ca Mon Sep 17 00:00:00 2001 From: "Hairo R. Carela" Date: Mon, 8 May 2017 18:23:24 +0200 Subject: [PATCH 037/642] New script aformat.py: alternate text formatting, useful for relays without formatting features (glowing-bear, weechat-android, ...) --- python/aformat.py | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 python/aformat.py diff --git a/python/aformat.py b/python/aformat.py new file mode 100644 index 00000000..f48fc1af --- /dev/null +++ b/python/aformat.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 Hairo R. Carela +# +# Everyone is permitted to copy and distribute verbatim or modified +# copies of this license document, and changing it is allowed as long +# as the name is changed. +# +# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +# +# 0. You just DO WHAT THE FUCK YOU WANT TO. +# +# Alternate way of text formatting, useful for relays without text formatting +# features (Glowingbear, WeechatAndroid, etc) +# +# Usage: +# /aformat *text* for bold text +# /aformat /text/ for italic text +# /aformat _text_ for underlined text +# /aformat |text| for reversed (black on white) text +# +# History: +# 2016-09-24: +# v0.1: Initial release +# +# TODO: +# - Colors support + +import sys + +try: + import weechat + from weechat import WEECHAT_RC_OK + import_ok = True +except ImportError: + print "This script must be run under WeeChat." + print "Get WeeChat now at: http://www.weechat.org/" + import_ok = False + +SCRIPT_NAME = "aformat" +SCRIPT_AUTHOR = "Hairo R. Carela " +SCRIPT_VERSION = "0.1" +SCRIPT_LICENSE = "WTFPL" +SCRIPT_DESC = ("Alternate way of text formatting, see /help for instructions") + +PY3 = sys.version > '3' + +class format: + # Special byte sequences, using weechat.color("stuff") had some unwanted + # results, i'll look into it if needed. Colors are unused for now + # PURPLE = '\x0306' + # BLUE = '\x0302' + # GREEN = '\x0303' + # YELLOW = '\x0308' + # RED = '\x0304' + BOLD = '\x02' + ITALIC = '\x1D' + UNDERLINE = '\x1F' + REVERSE = '\x16' + END = '\x0F' + +if PY3: + unichr = chr + def send(buf, text): + weechat.command(buf, "/input send {}".format(text)) +else: + def send(buf, text): + weechat.command(buf, "/input send {}".format(text.encode("utf-8"))) + +def cb_aformat_cmd(data, buf, args): + if not PY3: + args = args.decode("utf-8") + + # Get the indexes of the separators (*/_|) in the string + bolds = [i for i, ltr in enumerate(args) if ltr == "*"] + italics = [i for i, ltr in enumerate(args) if ltr == "/"] + underlines = [i for i, ltr in enumerate(args) if ltr == "_"] + reverses = [i for i, ltr in enumerate(args) if ltr == "|"] + + if len(bolds) != 0: + for i, v in enumerate(bolds): + if i%2 == 0: + args = args[:v] + format.BOLD + args[v+1:] + else: + args = args[:v] + format.END + args[v+1:] + + if len(italics) != 0: + for i, v in enumerate(italics): + if i%2 == 0: + args = args[:v] + format.ITALIC + args[v+1:] + else: + args = args[:v] + format.END + args[v+1:] + + if len(underlines) != 0: + for i, v in enumerate(underlines): + if i%2 == 0: + args = args[:v] + format.UNDERLINE + args[v+1:] + else: + args = args[:v] + format.END + args[v+1:] + + if len(reverses) != 0: + for i, v in enumerate(reverses): + if i%2 == 0: + args = args[:v] + format.REVERSE + args[v+1:] + else: + args = args[:v] + format.END + args[v+1:] + + send(buf, args) + return weechat.WEECHAT_RC_OK + + +if import_ok and __name__ == "__main__": + weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, '', '') + weechat.hook_command("aformat", "Alternate way of text formatting, useful for relays without text formatting features (Glowingbear, WeechatAndroid, etc)", + "text <*/_|> text <*/_|> more text", + " *: bold text\n" + " /: italic text\n" + " _: underlined text\n" + " |: reversed (black on white) text\n\n" + " eg.: typing: /aformat This /must/ be the *work* of an _enemy_ |stand|\n" + " will output: This {0}must{4} be the {1}work{4} of an {2}enemy{4} {3}stand{4}".format(weechat.color("italic"), weechat.color("bold"), weechat.color("underline"), weechat.color("reverse"), weechat.color("reset")), + "", "cb_aformat_cmd", "") From 3833ac4219026c8f366957e9995304b77331ec9b Mon Sep 17 00:00:00 2001 From: Alvar Date: Mon, 8 May 2017 19:25:10 +0200 Subject: [PATCH 038/642] New script weechataboo.scm: replace emotion-tags with random emoticons --- guile/weechataboo.scm | 132 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 guile/weechataboo.scm diff --git a/guile/weechataboo.scm b/guile/weechataboo.scm new file mode 100644 index 00000000..ff621993 --- /dev/null +++ b/guile/weechataboo.scm @@ -0,0 +1,132 @@ +; WeeChat-Script to replace emotion-tags with random emoticons. +; Copyright (C) 2017 Alvar +; +; 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 . +; +; For usage see `/help weechataboo` + +(use-modules (srfi srfi-1)) + +; -> List +; Returns a list of available emotions. +(define (emotion-categories) + (string-split (weechat:config_get_plugin "emotions") #\,)) + +; String -> String +; Returns an emoticon for a known emotion. +(define (emotion->emoticon emo) + (let + ((emotions (string-split (weechat:config_get_plugin emo) #\,)) + (random-emotion (lambda (l) + (list-ref l (random (length l)))))) + (random-emotion emotions))) + +; String -> String +; Replaces in the given string every ~~EMOTION with a fitting emoticon. +(define (emoticonize-line line) + (let* + ((as-tag (lambda (emo) (string-append "~~" emo))) + (has-emotions? (lambda (txt) + (any (lambda (emo) + (number? (string-contains txt (as-tag emo)))) + (emotion-categories)))) + (replace (lambda (emo txt) + (let ((pos (string-contains txt (as-tag emo)))) + (if (number? pos) + (string-replace + txt (emotion->emoticon emo) + pos (+ (string-length (as-tag emo)) pos)) + txt)))) + (new-line (fold replace line (emotion-categories)))) + (if (has-emotions? new-line) + (emoticonize-line new-line) + new-line))) + +; Pointer String String -> Weechat-Return +; This function was registered to be called when an input was submitted and +; will try to replace ~~EMOTIONs to emoticons. +(define (command-run data buffer command) + (let* + ((input (weechat:buffer_get_string buffer "input")) + (output (emoticonize-line input))) + (weechat:buffer_set buffer "input" output)) + weechat:WEECHAT_RC_OK) + +; Pointer String List -> Weechat-Return +; Function which tells you to RTFM. +(define (weechataboo-func data buffer args) + (weechat:print "" "See /help weechataboo") + weechat:WEECHAT_RC_OK) + +; -> () +; Function to be executed when there is no config yet. Creates a dummy one. +(define (initial-setup) + (let* + ; Some defaults which may be useful‥ + ((emotions + '(("angry" "눈_눈,(¬_¬),(`ε´),(¬▂¬),(▽д▽)") + ("blush" "(´ω`*),(‘-’*),(/ε\*),(*゚∀゚*),(*´ェ`*)") + ("cry" "(;へ:),(πーπ),(iДi),(;Д;),(╥_╥)") + ("dance" "ヾ(^^ゞ),(ノ^o^)ノ,⌎⌈╹우╹⌉⌍,└|゚ε゚|┐,┌|゚з゚|┘,(〜 ̄△ ̄)〜") + ("drink" "(^-^)_日,(*^◇^)_旦,(  ゜Д゜)⊃旦,~~旦_(-ω-`。)") + ("excited" "(≧∇≦*),ヽ(^Д^)ノ,(* >ω<)") + ("gross" "(咒),( ≖ิ‿≖ิ ),ʅ(◔౪◔ ) ʃ") + ("happy" "≖‿≖,(^ω^),(^ω^),ヽ(ヅ)ノ,(¬‿¬)") + ("heart" "♡^▽^♡,✿♥‿♥✿,(。♥‿♥。),ヽ(o♡o)/") + ("hug" "⊂(・﹏・⊂),(っ´▽`)っ,(づ ̄ ³ ̄)づ,⊂(´・ω・`⊂)") + ("kiss" "|°з°|,(*^3^),(´ε`*)") + ("lenny" "( ͡ ͜ʖ ͡ ),( ͡~ ͜ʖ ͡°),( ͡~ ͜ʖ ͡~),ヽ( ͝° ͜ʖ͡°)ノ,(つ ͡° ͜ʖ ͡°)つ") + ("magic" "(っ・ω・)っ≡≡≡≡≡≡☆,ヽ༼ຈل͜ຈ༽⊃─☆*:・゚") + ("sheep" "@^ェ^@,@・ェ・@") + ("shrug" "┐(´д`)┌,╮(╯∀╰)╭,┐(´∀`)┌,ʅ(́◡◝)ʃ,ヽ(~~~ )ノ") + ("shock" "(゚д゚;)") + ("shy" "(/ω\),(‘-’*),(´~`ヾ),(〃´∀`)") + ("smug" "( ̄ω ̄),( ̄ー ̄),( ̄ー ̄),(^~^)") + ("wink" "ヾ(^∇^),ヾ(☆▽☆),(。-ω-)ノ,( ・ω・)ノ"))) + (names (string-join (map car emotions) ","))) + (and + (weechat:config_set_plugin "emotions" names) + (for-each + (lambda (emo) + (weechat:config_set_plugin (car emo) (cadr emo))) + emotions)))) + +; -> Weechat-Return +; Function to be called when the plugin is unloaded. Will hopefully clean +; up all settings. +(define (clean-up) + (for-each weechat:config_unset_plugin (emotion-categories)) + (weechat:config_unset_plugin "emotions") + weechat:WEECHAT_RC_OK) + + +(weechat:register + "weechataboo" "Alvar" "0.1" "GPL3" + "Replace emotion-tags with random emoticons" "clean-up" "") + +(and (eq? (weechat:config_is_set_plugin "emotions") 0) + (initial-setup)) + +(weechat:hook_command_run "/input return" "command-run" "") +(weechat:hook_command + "weechataboo" + (string-append "This script automatically replaces written emotion-keywords\n" + "with a random emoticon from a list of matching ones. The\n" + "keyword must have two tildes (~~) as a prefix.\n" + "Example: ~~wink\n\n" + "All values are comma separated. Please make sure that every\n" + "emotion in the `emotions`-list has its own entry!\n\n" + "→ Keywords: /set plugins.var.guile.weechataboo.emotions\n" + "→ Emoticons: /set plugins.var.guile.weechataboo.$EMOTION\n") + "" "" "" "weechataboo-func" "") From 6334f5604c289863e1785e316a6ffba99ecbb752 Mon Sep 17 00:00:00 2001 From: Guido Berhoerster Date: Mon, 8 May 2017 21:08:35 +0200 Subject: [PATCH 039/642] New script terminal_title.py: display user defined information in the terminal title --- python/terminal_title.py | 129 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 python/terminal_title.py diff --git a/python/terminal_title.py b/python/terminal_title.py new file mode 100644 index 00000000..b85dc964 --- /dev/null +++ b/python/terminal_title.py @@ -0,0 +1,129 @@ +# +# Copyright (C) 2010 by Guido Berhoerster +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# 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 . +# + +import os +import sys +import re +import string +from collections import Mapping +import weechat + +SCRIPT_NAME = 'terminal-title' +VERSION = '1' +AUTHOR = 'Guido Berhoerster' +DESCRIPTION = 'Displays user defined information in the terminal title' +DEFAULT_SETTINGS = { + 'title': ('WeeChat %version [%buffer_count] %buffer_number: ' + '%buffer_name{%buffer_nicklist_count} [%hotlist]', + 'items displayed in the terminal title') +} +TERM_TEMPLATES = [ + ('xterm', "\033]0;%s\007"), + ('screen', "\033_%s\033\\") +] +TERM_TEMPLATE = None + + +class TermTitleMapping(Mapping): + substitutions = { + 'buffer_title': + lambda : weechat.buffer_get_string(weechat.current_buffer(), + "title"), + 'buffer_name': + lambda : weechat.buffer_get_string(weechat.current_buffer(), + "name"), + 'buffer_plugin': + lambda : weechat.buffer_get_string(weechat.current_buffer(), + "plugin"), + 'buffer_number': + lambda : weechat.buffer_get_integer(weechat.current_buffer(), + "number"), + 'buffer_nicklist_count': + lambda : weechat.buffer_get_integer(weechat.current_buffer(), + "nicklist_visible_count"), + 'buffer_count': lambda : buffer_count(), + 'hotlist': lambda : hotlist(), + 'version': lambda : weechat.info_get("version", "") + } + + def __getitem__(self, key): + return self.substitutions[key]() + + def __iter__(self): + return self.substitutions.iterkeys() + + def __len__(self): + return len(self.substitutions) + + +class TermTitleTemplate(string.Template): + delimiter = '%' + + +def buffer_count(): + buffer_count = 0 + buffer = weechat.infolist_get("buffer", "", "") + while weechat.infolist_next(buffer): + buffer_count += 1 + weechat.infolist_free(buffer) + return buffer_count + +def hotlist(): + hotlist_items = [] + hotlist = weechat.infolist_get("hotlist", "", "") + while weechat.infolist_next(hotlist): + buffer_number = weechat.infolist_integer(hotlist, "buffer_number") + buffer = weechat.infolist_pointer(hotlist, "buffer_pointer") + short_name = weechat.buffer_get_string(buffer, "short_name") + hotlist_items.append("%s:%s" % (buffer_number, short_name)) + weechat.infolist_free(hotlist) + return ",".join(hotlist_items) + +def set_term_title_hook(data, signal, signal_data): + title_template_str = weechat.config_get_plugin('title') + title_template = TermTitleTemplate(title_template_str) + title_str = title_template.safe_substitute(TermTitleMapping()) + sys.__stdout__.write(TERM_TEMPLATE % title_str) + sys.__stdout__.flush() + + return weechat.WEECHAT_RC_OK + +def config_hook(data, option, value): + set_term_title_hook("", "", "") + + return weechat.WEECHAT_RC_OK + +if __name__ == '__main__': + weechat.register(SCRIPT_NAME, AUTHOR, VERSION, 'GPL3', DESCRIPTION, '', '') + + for option, (value, description) in DEFAULT_SETTINGS.iteritems(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value) + weechat.config_set_desc_plugin(option, '%s (default: "%s")' % + (description, value)) + + term = os.environ.get("TERM", None) + if term: + for term_name, term_template in TERM_TEMPLATES: + if term.startswith(term_name): + TERM_TEMPLATE = term_template + for hook in ['buffer_switch', 'buffer_title_changed', + 'hotlist_changed', 'upgrade_ended']: + weechat.hook_signal(hook, 'set_term_title_hook', '') + weechat.hook_config('plugins.var.python.%s.*' % SCRIPT_NAME, + 'config_hook', '') + set_term_title_hook('', '', '') + break From cd6b3b6eb315c9b5ee239f123ebadc3584e78fe7 Mon Sep 17 00:00:00 2001 From: p3lim Date: Mon, 8 May 2017 21:13:09 +0200 Subject: [PATCH 040/642] New script detach_away.py: automatically set away message based on number of relays connected --- python/detach_away.py | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 python/detach_away.py diff --git a/python/detach_away.py b/python/detach_away.py new file mode 100644 index 00000000..0c54e2e2 --- /dev/null +++ b/python/detach_away.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 p3lim +# +# https://github.com/p3lim/weechat-detach-away + +try: + import weechat +except ImportError: + from sys import exit + print('This script has to run under WeeChat (https://weechat.org/).') + exit(1) + +from urllib import urlencode + +SCRIPT_NAME = 'detach_away' +SCRIPT_AUTHOR = 'p3lim' +SCRIPT_VERSION = '0.1.0' +SCRIPT_LICENSE = 'MIT' +SCRIPT_DESC = 'Automatically sets away message based on number of relays connected' + +SETTINGS = { + 'message': ( + 'I am away', + 'away message'), + 'debugging': ( + 'off', + 'debug flag'), +} + +num_relays = 0 + +def DEBUG(): + return weechat.config_get_plugin('debug') == 'on' + +def set_away(is_away, message=''): + if is_away: + message = weechat.config_get_plugin('message') + + weechat.command('', '/away -all ' + message) + +def relay_connected(data, signal, signal_data): + global num_relays + + if DEBUG(): + weechat.prnt('', 'DETACH_AWAY: last #relays: ' + str(num_relays)) + + if int(num_relays) == 0: + set_away(False) + + num_relays = weechat.info_get('relay_client_count', 'connected') + return weechat.WEECHAT_RC_OK + +def relay_disconnected(data, signal, signal_data): + global num_relays + + if DEBUG(): + weechat.prnt('', 'DETACH_AWAY: last #relays: ' + str(num_relays)) + + if int(num_relays) > 0: + set_away(True) + + num_relays = weechat.info_get('relay_client_count', 'connected') + return weechat.WEECHAT_RC_OK + +# register plugin +weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '') + +# register for relay status +weechat.hook_signal('relay_client_connected', 'relay_connected', '') +weechat.hook_signal('relay_client_disconnected', 'relay_disconnected', '') + +# register configuration defaults +for option, value in SETTINGS.items(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value[0]) + + weechat.config_set_desc_plugin(option, '%s (default: "%s")' % (value[1], value[0])) From 42d1bff365dceec8bb3b82bfb54afcc0103e1049 Mon Sep 17 00:00:00 2001 From: p3lim Date: Mon, 8 May 2017 21:16:09 +0200 Subject: [PATCH 041/642] New script pushjet.py: send highlights and mentions through pushjet.io --- python/pushjet.py | 186 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 python/pushjet.py diff --git a/python/pushjet.py b/python/pushjet.py new file mode 100644 index 00000000..6b8798ad --- /dev/null +++ b/python/pushjet.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 p3lim +# +# https://github.com/p3lim/weechat-pushjet + +try: + import weechat +except ImportError: + from sys import exit + print('This script has to run under WeeChat (https://weechat.org/).') + exit(1) + +from urllib import urlencode + +SCRIPT_NAME = 'pushjet' +SCRIPT_AUTHOR = 'p3lim' +SCRIPT_VERSION = '0.1.0' +SCRIPT_LICENSE = 'MIT' +SCRIPT_DESC = 'Send highlights and mentions through Pushjet.io' + +SETTINGS = { + 'host': ( + 'https://api.pushjet.io', + 'host for the pushjet api'), + 'secret': ( + '', + 'secret for the pushjet api'), + 'level': ( + '4', + 'severity level for the message, from 1 to 5 (low to high)'), + 'timeout': ( + '30', + 'timeout for the message sending in seconds (>= 1)'), + 'separator': ( + ': ', + 'separator between nick and message in notifications'), + 'notify_on_highlight': ( + 'on', + 'push notifications for highlights in buffers (on/off)'), + 'notify_on_privmsg': ( + 'on', + 'push notifications for private messages (on/off)'), + 'notify_when': ( + 'always', + 'when to push notifications (away/detached/always/never)'), + 'ignore_buffers': ( + '', + 'comma-separated list of buffers to ignore'), + 'ignore_nicks': ( + '', + 'comma-separated list of users to not push notifications from'), +} + +def send_message(title, message): + secret = weechat.config_get_plugin('secret') + if secret != '': + data = { + 'secret': secret, + 'level': int(weechat.config_get_plugin('level')), + 'title': title, + 'message': message, + } + + host = weechat.config_get_plugin('host').rstrip('/') + '/message' + timeout = int(weechat.config_get_plugin('timeout')) * 1000 + + if timeout <= 0: + timeout = 1 + + data = urlencode(data) + cmd = 'python -c \'from urllib2 import Request, urlopen; r = urlopen(Request("%s", "%s")); print r.getcode()\'' % (host, data) + weechat.hook_process(cmd, timeout, 'send_message_callback', '') + +def send_message_callback(data, command, return_code, out, err): + if return_code != 0: + # something went wrong + return weechat.WEECHAT_RC_ERROR + + return weechat.WEECHAT_RC_OK + +def get_sender(tags, prefix): + # attempt to find sender from tags + # nicks are always prefixed with 'nick_' + for tag in tags: + if tag.startswith('nick_'): + return tag[5:] + + # fallback method to find sender from prefix + # nicks in prefixes are prefixed with optional modes (e.g @ for operators) + # so we have to strip away those first, if they exist + if prefix.startswith(('~', '&', '@', '%', '+', '-', ' ')): + return prefix[1:] + + return prefix + +def get_buffer_names(buffer): + buffer_names = [] + buffer_names.append(weechat.buffer_get_string(buffer, 'short_name')) + buffer_names.append(weechat.buffer_get_string(buffer, 'name')) + return buffer_names + +def should_send(buffer, tags, nick, highlighted): + if not nick: + # a nick is required to form a correct message, bail + return False + + if highlighted: + if weechat.config_get_plugin('notify_on_highlight') != 'on': + # notifying on highlights is disabled, bail + return False + elif weechat.buffer_get_string(buffer, 'localvar_type') == 'private': + if weechat.config_get_plugin('notify_on_privmsg') != 'on': + # notifying on private messages is disabled, bail + return False + else: + # not a highlight or private message, bail + return False + + notify_when = weechat.config_get_plugin('notify_when') + if notify_when == 'never': + # user has opted to not be notified, bail + return False + elif notify_when == 'away': + # user has opted to only be notified when away + infolist_args = ( + weechat.buffer_get_string(buffer, 'localvar_channel'), + weechat.buffer_get_string(buffer, 'localvar_server'), + weechat.buffer_get_string(buffer, 'localvar_nick') + ) + + if not None in infolist_args: + infolist = weechat.infolist_get('irc_nick', '', ','.join(infolist_args)) + if infolist: + away_status = weechat.infolist_integer(infolist, 'away') + if not away_status: + # user is not away, bail + return False + elif notify_when == 'detached': + # user has opted to only be notified when detached (relays) + num_relays = weechat.info_get('relay_client_count', 'connected') + if num_relays == 0: + # no relays connected, bail + return False + + if nick == weechat.buffer_get_string(buffer, 'localvar_nick'): + # the sender was the current user, bail + return False + + if nick in weechat.config_get_plugin('ignore_nicks').split(','): + # the sender was on the ignore list, bail + return False + + for buffer_name in get_buffer_names(buffer): + if buffer_name in weechat.config_get_plugin('ignore_buffers').split(','): + # the buffer was on the ignore list, bail + return False + + return True + +def message_callback(data, buffer, date, tags, displayed, highlight, prefix, message): + nick = get_sender(tags, prefix) + + if should_send(buffer, tags, nick, int(highlight)): + message = '%s%s%s' % (nick, weechat.config_get_plugin('separator'), message) + + if int(highlight): + buffer_names = get_buffer_names(buffer) + send_message(buffer_names[0] or buffer_names[1], message) + else: + send_message('Private Message', message) + + return weechat.WEECHAT_RC_OK + +# register plugin +weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '') + +# grab all messages in any buffer +weechat.hook_print('', '', '', 1, 'message_callback', '') + +# register configuration defaults +for option, value in SETTINGS.items(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value[0]) + + weechat.config_set_desc_plugin(option, '%s (default: "%s")' % (value[1], value[0])) From 5bb2ec03627a0857538e8cc698872f59c7f11f5e Mon Sep 17 00:00:00 2001 From: Kevin Siml Date: Mon, 8 May 2017 21:19:56 +0200 Subject: [PATCH 042/642] New script pushsafer.rb: send private/highlight messages to Android, iOS and Windows 10 devices via Pushsafer --- ruby/pushsafer.rb | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 ruby/pushsafer.rb diff --git a/ruby/pushsafer.rb b/ruby/pushsafer.rb new file mode 100644 index 00000000..dc8ae6fc --- /dev/null +++ b/ruby/pushsafer.rb @@ -0,0 +1,200 @@ +# Pushsafer.com Kevin Siml +# https://github.com/appzer/pushsafer-weechat +# http://www.pushsafer.com +# +# pushsafer for Weechat +# --------------- +# +# Send private messages and highlights to your Android, iOS & Windows 10 devices via +# the Pushsafer service (https://www.pushsafer.com) +# +# Install +# ------- +# +# Load the pushsafer-weechat.rb plugin into Weechat. Place it in the +# ~/.weechat/ruby directory: +# +# /ruby load pushsafer-weechat.rb +# +# It also requires a Pushsafer account. +# +# Setup +# ----- +# +# Set your Pushsafer private or alias key. +# +# /set plugins.var.ruby.pushsafer-weechat.privatekey 123456789abcdefgh +# +# Options +# ------- +# +# plugins.var.ruby.pushsafer-weechat.privatekey +# +# The private key for your Pushsafer service. +# Default: Empty string +# +# plugins.var.ruby.pushsafer-weechat.interval +# +# The interval between notifications. Doesn't notify if the last +# notification was within x seconds. +# Default: 60 seconds +# +# plugins.var.ruby.pushsafer-weechat.away +# +# Check whether the client is to /away for the current buffer and +# notifies if they're away. Set to on for this to happen. +# Default: off +# +# plugins.var.ruby.pushsafer-weechat.sound +# +# Set your notification sound +# options (Current listing located at https://www.pushsafer.com/en/pushapi) +# a number 0-28 0 = silent, blank = device default +# Default: blank +# +# plugins.var.ruby.pushsafer-weechat.device +# +# Set your notification device +# options (Current listing located at https://www.pushsafer.com/en/pushapi) +# your device or device group id, if empty = to all devices +# Default: blank +# +# plugins.var.ruby.pushsafer-weechat.icon +# +# Set your notification icon +# options (Current listing located at https://www.pushsafer.com/en/pushapi) +# a number 1-98 +# Default: blank +# +# plugins.var.ruby.pushsafer-weechat.vibration +# +# Set your notification vibration +# options (Current listing located at https://www.pushsafer.com/en/pushapi) +# a number 0-3 +# Default: blank +# +# plugins.var.ruby.pushsafer-weechat.time2live +# +# Set your notification time to live +# options (Current listing located at https://www.pushsafer.com/en/pushapi) +# a number 0-43200: Time in minutes, after which message automatically gets purged. +# Default: blank +# +# plugins.var.ruby.pushsafer-weechat.url +# +# Set your notification url +# options (Current listing located at https://www.pushsafer.com/en/pushapi) +# a url or url scheme +# Default: blank +# +# plugins.var.ruby.pushsafer-weechat.urltitle +# +# Set your notification url title +# options (Current listing located at https://www.pushsafer.com/en/pushapi) +# title of url +# Default: blank + + +# fix for weechat UTF_7 encoding issue +require 'enc/encdb.so' + +require 'rubygems' +require 'net/https' + +SCRIPT_NAME = 'pushsafer-weechat' +SCRIPT_AUTHOR = 'Pushsafer.com Kevin Siml ' +SCRIPT_DESC = 'Send highlights and private messages in channels to your Android, Windows 10 or IOS device via Pushsafer' +SCRIPT_VERSION = '0.1' +SCRIPT_LICENSE = 'APL' + +DEFAULTS = { + 'privatekey' => "", + 'interval' => "60", + 'sound' => "", + 'device' => "", + 'icon' => "", + 'vibration' => "", + 'time2live' => "", + 'url' => "", + 'urltitle' => "", + 'away' => 'off' +} + +def weechat_init + Weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", "") + DEFAULTS.each_pair do |option, def_value| + cur_value = Weechat.config_get_plugin(option) + Weechat.config_set_plugin(option, def_value) if cur_value.nil? || cur_value.empty? + end + + @last = Time.now - Weechat.config_get_plugin('interval').to_i + + Weechat.print("", "pushsafer-weechat: Please set your private key with: /set plugins.var.ruby.pushsafer-weechat.privatekey") + Weechat.hook_signal("weechat_highlight", "notify", "") + Weechat.hook_signal("weechat_pv", "notify", "") + + return Weechat::WEECHAT_RC_OK +end + +def notify(data, signal, signal_data) + + @last = Time.now unless @last + + # Only check if we're away if the plugin says to, only notify if we are + # away. + if Weechat.config_get_plugin('away') == 'on' + buffer = Weechat.current_buffer + isaway = Weechat.buffer_get_string(buffer, "localvar_away") != "" + + return Weechat::WEECHAT_RC_OK unless isaway + end + + if signal == "weechat_pv" + event = "Weechat Private message from #{signal_data.split.first}" + elsif signal == "weechat_highlight" + event = "Weechat Highlight from #{signal_data.split.first}" + end + + if (Time.now - @last) > Weechat.config_get_plugin('interval').to_i + url = URI.parse("https://www.pushsafer.com/api") + req = Net::HTTP::Post.new(url.path) + req.set_form_data({ + :k => Weechat.config_get_plugin('privatekey'), + :s => Weechat.config_get_plugin('sound'), + :d => Weechat.config_get_plugin('device'), + :i => Weechat.config_get_plugin('icon'), + :v => Weechat.config_get_plugin('vibration'), + :l => Weechat.config_get_plugin('time2live'), + :u => Weechat.config_get_plugin('url'), + :ut => Weechat.config_get_plugin('urltitle'), + :t => event, + :m => signal_data[/^\S+\t(.*)/, 1] + }) + res = Net::HTTP.new(url.host, url.port) + res.use_ssl = true + res.verify_mode = OpenSSL::SSL::VERIFY_NONE + res.start { |http| http.request(req) } + @last = Time.now + else + Weechat.print("", "weechat-pushsafer: Skipping notification, too soon since last notification") + end + + return Weechat::WEECHAT_RC_OK +end + +__END__ +__LICENSE__ + +Copyright 2017 Pushsafer.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. From e04a43850d501119c65f721390c7dd9ca530e5c6 Mon Sep 17 00:00:00 2001 From: Ricky Brent Date: Mon, 8 May 2017 21:26:21 +0200 Subject: [PATCH 043/642] New script automerge.py: automatically merge new buffers according to defined rules --- python/automerge.py | 195 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 python/automerge.py diff --git a/python/automerge.py b/python/automerge.py new file mode 100644 index 00000000..101d8d7f --- /dev/null +++ b/python/automerge.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 by Ricky Brent +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +"""Automatically merge new irc buffers according to defined rules. + +History: + * 2017-03-22, Ricky Brent : + version 0.1: initial release +""" + +from __future__ import print_function +import re +try: + import weechat + IMPORT_OK = True +except ImportError: + print('Script must be run under weechat. http://www.weechat.org') + IMPORT_OK = False + +VERSION = '0.1' +NAME = 'automerge' +AUTHOR = 'Ricky Brent ' +DESC = 'Merge new irc buffers according to defined rules.' + +DELIMITER1 = '|@|' +DELIMITER2 = '|!|' + +CMD_DESC = '''List, add, delete or apply automerge rules. + +Adding a rule takes two parameters: a regular expression to match the target, and \ +a regular expression, integer, or the special string 'server' to match the \ +destination. + +Optionally, the first parameter can be omitted; in this case, the active buffer name will be used. + +Rules can be deleted by their regular expression or their index.''' +CMD_LIST = ['list', 'add', 'delete', 'bufferlist', 'apply'] +CMD_COMPLETE = '||'.join(CMD_LIST) + +def find_merge_id(buf, merge): + """Find the id of the buffer to merge to.""" + mid = -1 + if merge.isdigit(): + mid = merge + elif merge == "server": + server = weechat.buffer_get_string(buf, 'localvar_server') + infolist = weechat.infolist_get("buffer", "", "") + while weechat.infolist_next(infolist) and mid < 0: + if weechat.infolist_string(infolist, "plugin_name") == "irc": + buf2 = weechat.infolist_pointer(infolist, "pointer") + server2 = weechat.buffer_get_string(buf2, 'localvar_server') + if server == server2: + mid = weechat.infolist_integer(infolist, 'number') + weechat.infolist_free(infolist) + else: + infolist = weechat.infolist_get("buffer", "", "") + prog = re.compile(merge) + while weechat.infolist_next(infolist) and mid < 0: + if prog.match(weechat.infolist_string(infolist, "full_name")): + mid = weechat.infolist_integer(infolist, 'number') + weechat.infolist_free(infolist) + return mid + +def get_rules(): + """Return a list of rules.""" + rules = weechat.config_get_plugin('rules') + if rules: + return rules.split(DELIMITER1) + else: + return [] + +def cb_signal_apply_rules(data, signal, buf): + """Callback for signal applying rules to the buffer.""" + name = weechat.buffer_get_string(buf, "full_name") + rules = get_rules() + for rule in rules: + pattern, merge = rule.split(DELIMITER2) + if re.match(pattern, name): + mid = find_merge_id(buf, merge) + if mid >= 0: + weechat.command(buf, "/merge " + str(mid)) + return weechat.WEECHAT_RC_OK + +def cb_command(data, buf, args): + """Handle user commands; add/remove/list rules.""" + list_args = args.split(" ") + commands = { + 'list': cb_command_list, + 'bufferlist': cb_command_bufferlist, + 'add': cb_command_add, + 'delete': cb_command_delete, + 'del': cb_command_delete, + 'apply': cb_command_apply + } + if len(list_args) == 0: + weechat.command(buf, '/help ' + NAME) + return weechat.WEECHAT_RC_OK + elif list_args[0] in commands: + commands[list_args[0]](data, buf, list_args) + else: + weechat.prnt(buf, ("[" + NAME + "] Bad option for /" + NAME + " " + "command, try '/help " + NAME + "' for more info.")) + return weechat.WEECHAT_RC_OK + +def cb_command_list(data, buf, list_args): + """Print a list all rules.""" + weechat.prnt('', "[" + NAME + "] rules (list)") + rules = get_rules() + if len(rules) == 0: + return weechat.WEECHAT_RC_OK + for idx, rule in enumerate(rules): + pattern, merge = rule.split(DELIMITER2) + weechat.prnt('', ' ' + str(idx) + ": " + pattern + ' = ' + merge) + return weechat.WEECHAT_RC_OK + +def cb_command_bufferlist(data, buf, list_args): + """Print a list of all buffer names.""" + infolist = weechat.infolist_get("buffer", "", "") + weechat.prnt('', "[" + NAME + "] buffer list") + while weechat.infolist_next(infolist): + weechat.prnt('', ' ' + weechat.infolist_string(infolist, "full_name")) + weechat.infolist_free(infolist) + return weechat.WEECHAT_RC_OK + +def cb_command_add(data, buf, list_args): + """Add a rule.""" + rules = get_rules() + if len(list_args) == 3: + rule = list_args[1] + match = list_args[2] + elif len(list_args) == 2: + rule = weechat.buffer_get_string(buf, "name") + match = list_args[1] + else: + return bad_command(buf) + rules.append(DELIMITER2.join([rule, match])) + weechat.config_set_plugin('rules', DELIMITER1.join(rules)) + weechat.prnt('', "[" + NAME + "] rule added: " + rule + " => " + match) + return weechat.WEECHAT_RC_OK + +def cb_command_delete(data, buf, list_args): + """Delete a rule.""" + rules = get_rules() + if len(list_args) == 2: + rules2 = [] + for idx, rule in enumerate(rules): + pattern, dummy = rule.split(DELIMITER2) + if str(idx) != list_args[1] and pattern != list_args[1]: + rules2.append(rule) + weechat.config_set_plugin('rules', DELIMITER1.join(rules2)) + if len(rules2) == len(rules): + weechat.prnt('', "[" + NAME + "] rule not found") + else: + weechat.prnt('', "[" + NAME + "] rule deleted") + return weechat.WEECHAT_RC_OK + +def cb_command_apply(data, buf, list_args): + """Apply the rules the all existing buffers; useful when testing a new rule.""" + infolist = weechat.infolist_get("buffer", "", "") + while weechat.infolist_next(infolist): + buf2 = weechat.infolist_pointer(infolist, "pointer") + cb_signal_apply_rules(data, None, buf2) + weechat.infolist_free(infolist) + return weechat.WEECHAT_RC_OK + +def bad_command(buf): + """Print an error message about the command.""" + weechat.prnt(buf, ("[" + NAME + "] Bad option for /" + NAME + " " + "command, try '/help " + NAME + "' for more info.")) + return weechat.WEECHAT_RC_OK + +if IMPORT_OK: + weechat.register(NAME, AUTHOR, VERSION, 'GPL2', DESC, '', '') + weechat.hook_signal('irc_channel_opened', 'cb_signal_apply_rules', '') + weechat.hook_signal('irc_pv_opened', 'cb_signal_apply_rules', '') + weechat.config_set_desc_plugin('rules', 'Rules to follow when automerging.') + if not weechat.config_is_set_plugin('rules'): + weechat.config_set_plugin('rules', '') + weechat.hook_command(NAME, CMD_DESC, '[' + '|'.join(CMD_LIST) + ']', + '', CMD_COMPLETE, 'cb_command', '') From 849719a9c0d071eca7a2ddea86d1367dd77dc666 Mon Sep 17 00:00:00 2001 From: butlerx Date: Mon, 8 May 2017 21:41:42 +0200 Subject: [PATCH 044/642] New script giphy.py: insert a giphy URL based on a command and search; use giphy's random, search and translate from WeeChat --- python/giphy.py | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 python/giphy.py diff --git a/python/giphy.py b/python/giphy.py new file mode 100644 index 00000000..adf048ac --- /dev/null +++ b/python/giphy.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +# Insert a giphy URL based on a command and search +# Use giphys random, search and translate from weechat +# Usage: /giphy search Search Term +# Usage: /giphy msg message +# Usage: /giphy random Search Term +# Usage: /gipgy Search Term +# +# History: +# +# 2017-04-18, butlerx +# Version 1.0.0: initial version +# + +import requests +import weechat + +SCRIPT_NAME = "giphy" +SCRIPT_AUTHOR = "butlerx " +SCRIPT_VERSION = "1.0" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Insert giphy gif" + +URL = "http://api.giphy.com/v1/gifs/" +API = "&api_key=dc6zaTOxFJmzC" +RANDOM = "random?tag=%s" +TRANSLATE = "translate?s=%s" +SEARCH = "search?limit=1&q=%s" + + +def giphy(data, buf, args): + search_string = args.split() + arg = search_string.pop(0) + search_string = "+".join(search_string) + if arg == "search": + image_url = search(URL + SEARCH + API, search_string) + elif arg == "msg": + image_url = translate(URL + TRANSLATE + API, search_string) + elif arg == "random": + image_url = random(URL + RANDOM + API, search_string) + else: + search_string = arg + "+" + search_string + image_url = random(URL + RANDOM + API, search_string) + weechat.command(buf, "giphy %s -- %s" % (search_string, image_url)) + return weechat.WEECHAT_RC_OK + + +def translate(api, search): + response = requests.get(api % search) + data = response.json() + try: + # Translate + image_url = data["data"]["images"]["original"]["url"] + except TypeError: + image_url = "No GIF good enough" + return image_url + + +def random(api, search): + response = requests.get(api % search) + data = response.json() + try: + # Random + image_url = data["data"]["image_url"] + except TypeError: + image_url = "No GIF good enough" + return image_url + + +def search(api, search): + response = requests.get(api % search) + data = response.json() + try: + image_url = data["data"][0]["images"]["original"]["url"] + except TypeError: + image_url = "No GIF good enough" + return image_url + + +if __name__ == "__main__": + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + weechat.hook_command("giphy", "Insert a giphy GIF", "", + "", "", "giphy", "") From fd5a77ea715a25652e6d6f4195e6e4670acc9d04 Mon Sep 17 00:00:00 2001 From: Manu Koell Date: Mon, 8 May 2017 21:46:28 +0200 Subject: [PATCH 045/642] New script autoconf.py: auto save/load changed options in a .weerc file --- python/autoconf.py | 167 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 python/autoconf.py diff --git a/python/autoconf.py b/python/autoconf.py new file mode 100644 index 00000000..51fc1e39 --- /dev/null +++ b/python/autoconf.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Manu Koell +# +# 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 . +# + +import os +import re + +from fnmatch import fnmatch + +try: + import weechat as w + +except Exception: + print("This script must be run under WeeChat.") + print("Get WeeChat now at: http://www.weechat.org/") + quit() + +NAME = "autoconf" +AUTHOR = "Manu Koell " +VERSION = "0.1" +LICENSE = "GPL3" +DESCRIPTION = "auto save/load changed options in a ~/.weerc file, useful to share dotfiles with" + +EXCLUDES = [ + '*.nicks', + '*.username', '*.sasl_username', + '*.password', '*.sasl_password', + 'irc.server.*.autoconnect', + 'irc.server.*.autojoin' +] + +SETTINGS = { + 'autosave': ('on', 'auto save config on quit'), + 'autoload': ('on', 'auto load config on start'), + 'ignore': ( + ','.join(EXCLUDES), + 'comma separated list of patterns to exclude'), + 'file': ('~/.weerc', 'config file location') +} + +RE = { + 'option': re.compile('\s*(.*) = (.*) \(default') +} + + +def cstrip(text): + """strip color codes""" + + return w.string_remove_color(text, '') + +def get_config(args): + """get path to config file""" + + try: + conf = args[1] + except Exception: + conf = w.config_get_plugin('file') + + return os.path.expanduser(conf) + +def load_conf(args): + """send config to fifo pipe""" + + fifo = w.info_get('fifo_filename', '') + conf = get_config(args) + + if os.path.isfile(conf): + w.command('', '/exec -sh -norc cat %s > %s' % (conf, fifo)) + +def save_conf(args): + """match options and save to config file""" + + try: + f = open(get_config(args), 'w+') + + except Exception, e: + w.prnt('', '%sError: %s' % (w.prefix('error'), e)) + + return w.WEECHAT_RC_ERROR + + header = [ + '#', + '# WeeChat %s (compiled on %s)' % (w.info_get('version', ''), w.info_get('date', '')), + '#', + '# Use /autoconf load or cat this file to the FIFO pipe.', + '#', + '# For more info, see https://weechat.org/scripts/source/autoconf.py.html', + '#', + '' + ] + + for ln in header: + f.write('%s\n' % ln) + + w.command('', '/buffer clear') + w.command('', '/set diff') + + infolist = w.infolist_get('buffer_lines', '', '') + + while w.infolist_next(infolist): + message = cstrip(w.infolist_string(infolist, 'message')) + ignore = w.config_get_plugin('ignore').split(',') + option = re.match(RE['option'], message) + + if option: + if not any(fnmatch(option.group(1), p.strip()) for p in ignore): + f.write('*/set %s %s\n' % (option.group(1), option.group(2))) + + f.close() + + w.infolist_free(infolist) + +def autoconf_cb(data, buffer, args): + """the /autoconf command""" + + args = args.split() + + if 'save' in args: + save_conf(args) + + elif 'load' in args: + load_conf(args) + + else: + # show help message + w.command('', '/help ' + NAME) + + return w.WEECHAT_RC_OK + +def quit_cb(data, signal, signal_data): + """save config on quit""" + + save_conf(None) + + return w.WEECHAT_RC_OK + +if __name__ == '__main__': + if w.register(NAME, AUTHOR, VERSION, LICENSE, DESCRIPTION, "", ""): + w.hook_command(NAME, DESCRIPTION, 'save [path] || load [path]', '', 'save || load', 'autoconf_cb', '') + + # set default config + for option, value in SETTINGS.items(): + if not w.config_is_set_plugin(option): + w.config_set_plugin(option, value[0]) + w.config_set_desc_plugin(option, '%s (default: "%s")' % (value[1], value[0])) + + if 'on' in w.config_get_plugin('autoload'): + load_conf(None) + + if 'on' in w.config_get_plugin('autosave'): + w.hook_signal('quit', 'quit_cb', '') + + From c55aa68e9a85e784a341e0deefec18652a0075a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Karlsrud?= Date: Thu, 11 May 2017 14:34:12 +0200 Subject: [PATCH 046/642] irssinotifier.py 0.8: add the ability to store certain potentially sensitive settings as secure data --- python/irssinotifier.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/python/irssinotifier.py b/python/irssinotifier.py index bc9d705e..62b5247e 100644 --- a/python/irssinotifier.py +++ b/python/irssinotifier.py @@ -18,6 +18,9 @@ # Requires Weechat >= 0.3.7, openssl # Released under GNU GPL v3 # +# 2017-05-11, paalka +# version 0.8: - add the ability to store the API token and +# encryption key as secured data. # 2016-01-11, dbendit # version 0.7: - ignore_nicks option # 2014-05-10, Sébastien Helleu @@ -54,7 +57,7 @@ weechat.register("irssinotifier", "Caspar Clemens Mierau ", - "0.7", + "0.8", "GPL3", "irssinotifier: Send push notifications to Android's IrssiNotifier about your private message and highligts.", "", @@ -124,6 +127,11 @@ def notify_show(data, bufferp, uber_empty, tagsn, isdisplayed, def encrypt(text): encryption_password = weechat.config_get_plugin("encryption_password") + + # decrypt the password if it is stored as secured data + if encryption_password.startswith("${sec."): + encryption_password = weechat.string_eval_expression(encryption_password, {}, {}, {}) + command="openssl enc -aes-128-cbc -salt -base64 -A -pass env:OpenSSLEncPW" opensslenv = os.environ.copy(); opensslenv['OpenSSLEncPW'] = encryption_password @@ -135,6 +143,11 @@ def encrypt(text): def show_notification(chan, nick, message): API_TOKEN = weechat.config_get_plugin("api_token") + + # decrypt the API token if it is stored as secured data + if API_TOKEN.startswith("${sec."): + API_TOKEN = weechat.string_eval_expression(API_TOKEN, {}, {}, {}) + if API_TOKEN != "": url = "https://irssinotifier.appspot.com/API/Message" postdata = urllib.urlencode({'apiToken':API_TOKEN,'nick':encrypt(nick),'channel':encrypt(chan),'message':encrypt(message),'version':13}) From 9ba7719316e5659eafad05c10877ee17f46d3a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Wed, 17 May 2017 21:25:08 +0200 Subject: [PATCH 047/642] quick_force_color.py 0.6: miscellaneous fixes and usability improvements --- python/quick_force_color.py | 57 +++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/python/quick_force_color.py b/python/quick_force_color.py index 315e2e72..723cf653 100644 --- a/python/quick_force_color.py +++ b/python/quick_force_color.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2016 by nils_2 +# Copyright (c) 2012-2017 by nils_2 # # quickly add/del/change entry in nick_color_force # @@ -17,8 +17,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-05-18: ticalc-travis (https://github.com/weechatter/weechat-scripts/pull/18) +# 0.6 : Clean up some redundant code +# : Add nicks to irc.look.nick_color_force in sorted order for easier manual editing +# : Display proper feedback on incorrect commands +# : Fix inconsistencies in help syntax +# : Don't retain nicks that have been manually removed from nick_color_force +# : Provide feedback messages for successful operations # 2016-04-17: nils_2,(freenode.#weechat) -# 0.5 : make script compatible with option weechat.look.nick_color_force (weechat >= 1.5) +# 0.5 : make script compatible with option weechat.look.nick_color_force (weechat >=1.5) # 2013-01-25: nils_2,(freenode.#weechat) # 0.4 : make script compatible with Python 3.x # 2012-07-08: obiwahn, (freenode) @@ -49,7 +56,7 @@ SCRIPT_NAME = "quick_force_color" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.5" +SCRIPT_VERSION = "0.6" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "quickly add/del/change entry in nick_color_force" @@ -64,14 +71,19 @@ nick_option_new = "weechat.look.nick_color_force" nick_option = "" # ================================[ callback ]=============================== +def print_usage(buffer): + weechat.prnt(buffer, "Usage: /%s list [nick] | add nick color | del nick" % SCRIPT_NAME) + def nick_colors_cmd_cb(data, buffer, args): global colored_nicks if args == "": # no args given. quit + print_usage(buffer) return weechat.WEECHAT_RC_OK argv = args.strip().split(" ") if (len(argv) == 0) or (len(argv) >= 4): # maximum of 3 args!! + print_usage(buffer) return weechat.WEECHAT_RC_OK bufpointer = weechat.window_get_pointer(buffer,'buffer') # current buffer @@ -81,39 +93,41 @@ def nick_colors_cmd_cb(data, buffer, args): if argv[0].lower() == 'list': # list all nicks if len(colored_nicks) == 0: weechat.prnt(buffer,'%sno nicks in \"%s\"...' % (weechat.prefix("error"),nick_option)) - return weechat.WEECHAT_RC_OK - if len(argv) == 2: + elif len(argv) == 2: if argv[1] in colored_nicks: color = colored_nicks[argv[1]] # get color from given nick weechat.prnt(buffer,"%s%s: %s" % (weechat.color(color),argv[1],color)) else: weechat.prnt(buffer,"no color set for: %s" % (argv[1])) - return weechat.WEECHAT_RC_OK - weechat.prnt(buffer,"List of nicks in : %s" % nick_option) -# for nick,color in colored_nicks.items(): - for nick,color in list(colored_nicks.items()): - weechat.prnt(buffer,"%s%s: %s" % (weechat.color(color),nick,color)) - return weechat.WEECHAT_RC_OK + else: + weechat.prnt(buffer,"List of nicks in : %s" % nick_option) + for nick,color in list(colored_nicks.items()): + weechat.prnt(buffer,"%s%s: %s" % (weechat.color(color),nick,color)) - if (argv[0].lower() == 'add') and (len(argv) == 3): - if argv[1] in colored_nicks: # search if nick exists - colored_nicks[argv[1]] = argv[2] + elif (argv[0].lower() == 'add') and (len(argv) == 3): + if argv[1] in colored_nicks: + weechat.prnt(buffer, "Changing nick '%s' to color %s%s" % (argv[1], weechat.color(argv[2]), argv[2])) else: - colored_nicks[argv[1]] = argv[2] # add [nick] = [color] + weechat.prnt(buffer, "Adding nick '%s' with color %s%s" % (argv[1], weechat.color(argv[2]), argv[2])) + colored_nicks[argv[1]] = argv[2] save_new_force_nicks() - if (argv[0].lower() == 'del') and (len(argv) == 2): + elif (argv[0].lower() == 'del') and (len(argv) == 2): if argv[1] in colored_nicks: # search if nick exists del colored_nicks[argv[1]] save_new_force_nicks() + weechat.prnt(buffer, "Removed nick '%s'" % argv[1]) + else: + weechat.prnt(buffer, "Nick '%s' not found in nick_color_force" % argv[1]) + else: + print_usage(buffer) return weechat.WEECHAT_RC_OK def save_new_force_nicks(): global colored_nicks -# new_nick_color_force = ';'.join([ ':'.join(item) for item in colored_nicks.items()]) - new_nick_color_force = ';'.join([ ':'.join(item) for item in list(colored_nicks.items())]) + new_nick_color_force = ';'.join([ ':'.join(item) for item in sorted(colored_nicks.items())]) config_pnt = weechat.config_get(nick_option) weechat.config_option_set(config_pnt,new_nick_color_force,1) @@ -133,7 +147,8 @@ def force_nick_colors_completion_cb(data, completion_item, buffer, completion): def create_list(): global nick_color_force,colored_nicks # colored_nicks = dict([elem.split(':') for elem in nick_color_force.split(';')]) - nick_color_force = weechat.config_string(weechat.config_get(nick_option)) # get list + colored_nicks = {} + nick_color_force = weechat.config_string(weechat.config_get(nick_option)) # get list if nick_color_force != '': nick_color_force = nick_color_force.strip(';') # remove ';' at beginning and end of string for elem in nick_color_force.split(';'): # split nick1:color;nick2:color @@ -151,10 +166,10 @@ def create_list(): version = weechat.info_get('version_number', '') or 0 if int(version) >= 0x00030400: weechat.hook_command(SCRIPT_NAME,SCRIPT_DESC, - 'add || del || list', + 'add || del || list []', 'add : add a nick with its color to nick_color_force\n' 'del : delete given nick with its color from nick_color_force\n' - 'list : list all forced nicks with its assigned color or optional from one nick\n\n' + 'list [] : list all forced nicks with its assigned color or optional from one nick\n\n' 'Examples:\n' ' add nick nils_2 with color red:\n' ' /' + SCRIPT_NAME + ' add nils_2 red\n' From 7ce4270cbed28a9a71e02b663b6a9127783acaba Mon Sep 17 00:00:00 2001 From: das_aug Date: Wed, 17 May 2017 21:28:50 +0200 Subject: [PATCH 048/642] irssinotifier.py 0.8.1: add "-md md5" in openssl commandline --- python/irssinotifier.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/irssinotifier.py b/python/irssinotifier.py index 62b5247e..f763f356 100644 --- a/python/irssinotifier.py +++ b/python/irssinotifier.py @@ -18,6 +18,9 @@ # Requires Weechat >= 0.3.7, openssl # Released under GNU GPL v3 # +# 2017-05-17, das_aug +# version 0.8.1 - change openssl commandline to how the android app uses it now +# (add "-md md5") # 2017-05-11, paalka # version 0.8: - add the ability to store the API token and # encryption key as secured data. @@ -57,7 +60,7 @@ weechat.register("irssinotifier", "Caspar Clemens Mierau ", - "0.8", + "0.8.1", "GPL3", "irssinotifier: Send push notifications to Android's IrssiNotifier about your private message and highligts.", "", @@ -132,7 +135,7 @@ def encrypt(text): if encryption_password.startswith("${sec."): encryption_password = weechat.string_eval_expression(encryption_password, {}, {}, {}) - command="openssl enc -aes-128-cbc -salt -base64 -A -pass env:OpenSSLEncPW" + command="openssl enc -aes-128-cbc -salt -base64 -md md5 -A -pass env:OpenSSLEncPW" opensslenv = os.environ.copy(); opensslenv['OpenSSLEncPW'] = encryption_password output,errors = Popen(shlex.split(command),stdin=PIPE,stdout=PIPE,stderr=PIPE,env=opensslenv).communicate(text+" ") From d23e4b5ce8e300fc92c5d73750808b4500eaebd9 Mon Sep 17 00:00:00 2001 From: butlerx Date: Fri, 26 May 2017 11:19:52 +0100 Subject: [PATCH 049/642] giphy.py 1.0.1: remove + from output --- python/giphy.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/python/giphy.py b/python/giphy.py index adf048ac..74caa280 100644 --- a/python/giphy.py +++ b/python/giphy.py @@ -9,6 +9,8 @@ # # History: # +# 2017-04-19, butlerx +# Version 1.0.1: remove + from message # 2017-04-18, butlerx # Version 1.0.0: initial version # @@ -16,20 +18,21 @@ import requests import weechat -SCRIPT_NAME = "giphy" -SCRIPT_AUTHOR = "butlerx " -SCRIPT_VERSION = "1.0" +SCRIPT_NAME = "giphy" +SCRIPT_AUTHOR = "butlerx " +SCRIPT_VERSION = "1.0.1" SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Insert giphy gif" +SCRIPT_DESC = "Insert giphy gif" -URL = "http://api.giphy.com/v1/gifs/" -API = "&api_key=dc6zaTOxFJmzC" -RANDOM = "random?tag=%s" -TRANSLATE = "translate?s=%s" -SEARCH = "search?limit=1&q=%s" +URL = "http://api.giphy.com/v1/gifs/" +API = "&api_key=dc6zaTOxFJmzC" +RANDOM = "random?tag=%s" +TRANSLATE = "translate?s=%s" +SEARCH = "search?limit=1&q=%s" def giphy(data, buf, args): + """ Parse args to decide what api to use """ search_string = args.split() arg = search_string.pop(0) search_string = "+".join(search_string) @@ -42,12 +45,14 @@ def giphy(data, buf, args): else: search_string = arg + "+" + search_string image_url = random(URL + RANDOM + API, search_string) - weechat.command(buf, "giphy %s -- %s" % (search_string, image_url)) + weechat.command(buf, "giphy %s -- %s" % + (search_string.replace("+", " ").strip(), image_url)) return weechat.WEECHAT_RC_OK -def translate(api, search): - response = requests.get(api % search) +def translate(api, search_term): + """Query giphy translate api for search""" + response = requests.get(api % search_term) data = response.json() try: # Translate @@ -57,8 +62,9 @@ def translate(api, search): return image_url -def random(api, search): - response = requests.get(api % search) +def random(api, search_term): + """Query giphy random api for search""" + response = requests.get(api % search_term) data = response.json() try: # Random @@ -68,8 +74,9 @@ def random(api, search): return image_url -def search(api, search): - response = requests.get(api % search) +def search(api, search_term): + """Query giphy search api for search""" + response = requests.get(api % search_term) data = response.json() try: image_url = data["data"][0]["images"]["original"]["url"] From 73ca780c2c81726d5fe318a284ff25dc7e545b58 Mon Sep 17 00:00:00 2001 From: Jos Ahrens Date: Sat, 3 Jun 2017 18:30:29 +0200 Subject: [PATCH 050/642] whois_on_query.py 0.6.1: fix typo --- python/whois_on_query.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/whois_on_query.py b/python/whois_on_query.py index 47214376..be5572c5 100644 --- a/python/whois_on_query.py +++ b/python/whois_on_query.py @@ -24,6 +24,8 @@ # # History: # +# 2017-05-28, Jos Ahrens : +# version 0.6.1: Corrected a typo in help description for option self_query # 2012-01-03, Sebastien Helleu : # version 0.6: make script compatible with Python 3.x # 2011-10-17, Sebastien Helleu : @@ -52,13 +54,13 @@ SCRIPT_NAME = 'whois_on_query' SCRIPT_AUTHOR = 'Sebastien Helleu ' -SCRIPT_VERSION = '0.6' +SCRIPT_VERSION = '0.6.1' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Whois on query' # script options woq_settings_default = { - 'command' : ('/whois $nick $nick', 'the command sent to do the whois ($nick is repladed by nick)'), + 'command' : ('/whois $nick $nick', 'the command sent to do the whois ($nick is replaced by nick)'), 'self_query': ('off', 'if on, send whois for self queries'), } From 22477845fca57f4072843e38f74132c7910fdfa6 Mon Sep 17 00:00:00 2001 From: Alex Fluter Date: Sun, 4 Jun 2017 16:36:45 +0200 Subject: [PATCH 051/642] New script gribble.py: automatically authenticate to gribble for Bitcoin OTC exchange --- python/gribble.py | 115 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 python/gribble.py diff --git a/python/gribble.py b/python/gribble.py new file mode 100644 index 00000000..35e50163 --- /dev/null +++ b/python/gribble.py @@ -0,0 +1,115 @@ +# gribble - automatically authenticate to gribble for bitcoin otc exchange +# by Alex Fluter +# irc nick @fluter +# +# before load this script, set your gpg passphrase by +# /secure set gpg_passphrase xxxxxx + +import re +import subprocess +import urllib2 +import weechat + +NAME = "gribble" +AUTHOR = "fluter " +VERSION = "0.1" +LICENSE = "Apache" +DESCRIPTION = "Script to talk to gribble" + +gribble_channel = "#bitcoin-fr" +gribble_nick = "gribble" +ident_nick = "fluter" +options = { + # the channel name to watch to trigger this script + "channel": "#bitcoin-otc", + # the key of the secure data storing gpg passphrase + "pass_key": "gpg_passphrase" +} +gribble_buffer = None + + +hook_msg = None + +def init(): + global gribble_buffer + gribble_buffer = weechat.buffer_new(NAME, "", "", "", "") + weechat.prnt(gribble_buffer, "Options:") + for opt, val in options.iteritems(): + if not weechat.config_is_set_plugin(opt): + weechat.config_set_plugin(opt, val) + else: + options[opt] = weechat.config_get_plugin(opt) + weechat.prnt(gribble_buffer, " %s: %s" % (opt, options[opt])) + +def privmsg(server, to, msg): + buffer = weechat.info_get("irc_buffer", server) + weechat.command(buffer, "/msg %s %s" % (to, msg)) + +def join_cb(data, signal, signal_data): + dict_in = {"message": signal_data} + dict_out = weechat.info_get_hashtable("irc_message_parse", dict_in) + channel = dict_out["channel"] + if channel != options["channel"]: + return weechat.WEECHAT_RC_OK + + server = signal.split(",")[0] + nick = dict_out["nick"] + me = weechat.info_get("irc_nick", server) + if nick != me: + return weechat.WEECHAT_RC_OK + + weechat.prnt(gribble_buffer, "Channel %s joined" % channel) + hook_msg = weechat.hook_signal("*,irc_in2_PRIVMSG", "privmsg_cb", "") + weechat.prnt(gribble_buffer, "Sent eauth to %s" % gribble_nick) + privmsg(server, gribble_nick, "eauth %s" % ident_nick) + + return weechat.WEECHAT_RC_OK + +def privmsg_cb(data, signal, signal_data): + dict_in = {"message": signal_data} + dict_out = weechat.info_get_hashtable("irc_message_parse", dict_in) + if not weechat.info_get("irc_is_nick", dict_out["channel"]): + return weechat.WEECHAT_RC_OK + + server = signal.split(",")[0] + nick = dict_out["channel"] + me = weechat.info_get("irc_nick", server) + if nick != me: + return weechat.WEECHAT_RC_OK + + if dict_out["nick"] != gribble_nick: + return weechat.WEECHAT_RC_OK + + msg = dict_out["text"] + m = re.match("^.*Get your encrypted OTP from (.*)$", msg) + if m is None: + return weechat.WEECHAT_RC_OK + weechat.prnt(gribble_buffer, "Got OTP") + otp_url = m.group(1) + otp = urllib2.urlopen(otp_url).read() + # the passphrase is stored encrypted in secure data + expr = "${sec.data.%s}" % options["pass_key"] + gpg_pass = weechat.string_eval_expression(expr, "", "", "") + if gpg_pass == "": + weechat.prnt(gribble_buffer, "no gpg pass found in secure data") + return weechat.WEECHAT_RC_OK + + p = subprocess.Popen(["gpg", "--batch", "--decrypt", "--passphrase", gpg_pass], + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + out, err = p.communicate(otp) + if err != "": + weechat.prnt(gribble_buffer, "gpg output: " + err) + if out != "": + privmsg(server, gribble_nick, "everify %s" % out) + + return weechat.WEECHAT_RC_OK + +def main(): + init() + hook_join = weechat.hook_signal("*,irc_in2_JOIN", "join_cb", "foo") + +weechat.register(NAME, AUTHOR, VERSION, LICENSE, DESCRIPTION, "", "") +weechat.prnt("", "%s %s loaded" % (NAME, VERSION)) +main() From ce49273a17181f302f1992b2a9a99aa7ff64950e Mon Sep 17 00:00:00 2001 From: ux Date: Mon, 5 Jun 2017 21:46:53 +0200 Subject: [PATCH 052/642] autoconnect.py 0.3.2: left strip the colons before the channel name --- python/autoconnect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/autoconnect.py b/python/autoconnect.py index 2bd179a5..169bed92 100644 --- a/python/autoconnect.py +++ b/python/autoconnect.py @@ -19,7 +19,7 @@ SCRIPT_NAME = "autoconnect" SCRIPT_AUTHOR = "arno " -SCRIPT_VERSION = "0.3.1" +SCRIPT_VERSION = "0.3.2" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "reopens servers and channels opened last time weechat closed" SCRIPT_COMMAND = "autoconnect" @@ -59,7 +59,7 @@ def joinpart_cb(data, signal, signal_data): weechat.command("", "/mute /set irc.server.%s.autoconnect on" % (server,)) # get all channels joined (without passphrases) - chans = [j.split()[0].strip() for j in signal_data.split(None, 2)[2].split(',')] + chans = [j.split()[0].strip().lstrip(':') for j in signal_data.split(None, 2)[2].split(',')] autojoin_channels.add(','.join(chans)) elif signal.endswith("irc_in2_PART"): From c2c0d47e81735f4796ba83c77ef7070c91011ad1 Mon Sep 17 00:00:00 2001 From: raspbeguy Date: Thu, 8 Jun 2017 07:01:20 +0200 Subject: [PATCH 053/642] tts.py 0.2.1: do not use the shell to run commands in hook_process_hashtable --- python/tts.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/python/tts.py b/python/tts.py index 2656c86a..544993d2 100644 --- a/python/tts.py +++ b/python/tts.py @@ -47,7 +47,7 @@ SCRIPT_AUTHOR = 'raspbeguy' # Version of the script. -SCRIPT_VERSION = '0.2.0' +SCRIPT_VERSION = '0.2.1' # License under which the script is distributed. SCRIPT_LICENSE = 'MIT' @@ -510,22 +510,27 @@ def tts(text): engine = weechat.config_get_plugin('tts_engine') lang = weechat.config_get_plugin('language') if engine == 'espeak': - command = 'espeak "%s" --stdout ' % text + args = {'arg1':text} if lang: - command += '-v %s ' % lang - command += '| paplay' + args['arg2'] = '-v' + args['arg3'] = lang + hook = weechat.hook_process_hashtable('espeak',args,0,'my_process_cb','') elif engine == 'festival': - command = 'echo "%s" | festival --tts ' % text + args = {'stdin':'1', 'arg1':'festival', 'arg2':'--tts'} if lang: - command += '--language %s' % lang + args['arg3'] = '--language' + args['arg4'] = lang + hook = weechat.hook_process_hashtable('festival',args,0,'my_process_cb','') + weechat.hook_set(hook, "stdin", text) + weechat.hook_set(hook, "stdin_close", "") elif engine == 'picospeaker': - command = 'echo "%s" | picospeaker ' % text + args = {'stdin':'1'} if lang: - command += '-l %s' % lang - hook = weechat.hook_process_hashtable("sh", - {"arg1": "-c", - "arg2": command}, - 0, "my_process_cb", "") + args['arg1'] = '-l' + args['arg2'] = lang + hook = weechat.hook_process_hashtable('picospeaker',args,0,'my_process_cb','') + weechat.hook_set(hook, "stdin", text) + weechat.hook_set(hook, "stdin_close", "") if __name__ == '__main__': # Registration. From 78b01236d80cccd6fca888870ecdd467de231ecf Mon Sep 17 00:00:00 2001 From: mumixam Date: Sat, 10 Jun 2017 01:20:07 -0500 Subject: [PATCH 054/642] twitch.py 0.3: fixed whois output of utf8 display names --- python/twitch.py | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/python/twitch.py b/python/twitch.py index ad43069c..d908b855 100644 --- a/python/twitch.py +++ b/python/twitch.py @@ -29,6 +29,8 @@ # # # History: # +# 2017-06-10, mumixam +# v0.3: fixed whois output of utf8 display names # 2016-11-03, mumixam # v0.2: added detailed /help # 2016-10-30, mumixam @@ -38,7 +40,7 @@ SCRIPT_NAME = "twitch" SCRIPT_AUTHOR = "mumixam" -SCRIPT_VERSION = "0.2" +SCRIPT_VERSION = "0.3" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "twitch.tv Chat Integration" OPTIONS={ @@ -53,6 +55,7 @@ from datetime import datetime, timedelta import time import string +import ast clientid='awtv6n371jb7uayyc4jaljochyjbfxs' params = '?client_id='+clientid @@ -100,15 +103,21 @@ def gameshort(game): return('<' + games.split(';')[-1] + '>') return '<' + game + '>' +def makeutf8(data): + data = data.encode('utf8') + if not isinstance(data, str): + data=str(data,'utf8') + return data def channel_api(data, command, rc, stdout, stderr): - global name + data = ast.literal_eval(data) try: jsonDict = json.loads(stdout.strip()) except Exception as e: - weechat.prnt(data, 'TWITCH: Error with twitch API') + weechat.prnt(data['buffer'], 'TWITCH: Error with twitch API') return weechat.WEECHAT_RC_OK currentbuf = weechat.current_buffer() + name = data['name'] pcolor = weechat.color('chat_prefix_network') ccolor = weechat.color('chat') dcolor = weechat.color('chat_delimiters') @@ -118,29 +127,28 @@ def channel_api(data, command, rc, stdout, stderr): pformat = weechat.config_string( weechat.config_get("weechat.look.prefix_network")) if len(jsonDict) == 22: - name = jsonDict['display_name'] + dname = jsonDict['display_name'] create = jsonDict['created_at'].split('T')[0] status = jsonDict['status'] follows = jsonDict['followers'] partner = str(jsonDict['partner']) - output = '%s%s %s[%s%s%s]%s %sAccount Created%s: %s' % ( + output = '%s%s %s[%s%s%s]%s %sDisplay Name%s: %s' % ( + pcolor, pformat, dcolor, ncolor, name, dcolor, ccolor, ul, rul, dname) + output += '\n%s%s %s[%s%s%s]%s %sAccount Created%s: %s' % ( pcolor, pformat, dcolor, ncolor, name, dcolor, ccolor, ul, rul, create) if status: output += '\n%s%s %s[%s%s%s]%s %sStatus%s: %s' % ( pcolor, pformat, dcolor, ncolor, name, dcolor, ccolor, ul, rul, status) output += '\n%s%s %s[%s%s%s]%s %sPartnered%s: %s %sFollowers%s: %s' % ( pcolor, pformat, dcolor, ncolor, name, dcolor, ccolor, ul, rul, partner, ul, rul, follows) - output = output.encode('utf8') - if not isinstance(output, str): - output=str(output,'utf8') - weechat.prnt(data, output) + weechat.prnt(data['buffer'], makeutf8(output)) url = 'https://api.twitch.tv/kraken/users/' + \ name.lower() + '/follows/channels' urlh = weechat.hook_process( - "url:" + url+params, 7 * 1000, "channel_api", currentbuf) + "url:" + url+params, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': name, 'dname': dname})) if len(jsonDict) == 18: - name = jsonDict['display_name'] + dname = jsonDict['display_name'] s64id = jsonDict['steam_id'] if s64id: sid3 = int(s64id) - 76561197960265728 @@ -150,23 +158,23 @@ def channel_api(data, command, rc, stdout, stderr): output = '%s%s %s[%s%s%s]%s %ssteamID64%s: %s %ssteamID3%s: %s %ssteamID%s: %s' % ( pcolor, pformat, dcolor, ncolor, name, dcolor, ccolor, ul, rul, s64id, ul, rul, sid3, ul, rul, id32bit) - weechat.prnt(data, output) + weechat.prnt(data['buffer'], makeutf8(output)) if len(jsonDict) == 3: if 'status' in jsonDict.keys(): if jsonDict['status'] == 404 or jsonDict['status'] == 422: user = jsonDict['message'].split()[1].replace("'", "") - weechat.prnt(data, '%s%s %s[%s%s%s]%s No such user' % ( + weechat.prnt(data['buffer'], '%s%s %s[%s%s%s]%s No such user' % ( pcolor, pformat, dcolor, ncolor, user, dcolor, ccolor)) else: - url = 'https://api.twitch.tv/api/channels/' + name.lower() + url = 'https://api.twitch.tv/api/channels/' + data['name'].lower() urlh = weechat.hook_process( - "url:" + url+params, 7 * 1000, "channel_api", currentbuf) + "url:" + url+params, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': name, 'dname': data['name']})) count = jsonDict['_total'] if count: output = '%s%s %s[%s%s%s]%s %sFollowing%s: %s' % ( pcolor, pformat, dcolor, ncolor, name, dcolor, ccolor, ul, rul, count) - weechat.prnt(data, output) + weechat.prnt(data['buffer'], makeutf8(output)) return weechat.WEECHAT_RC_OK @@ -427,11 +435,11 @@ def twitch_whois(data, modifier, server_name, string): if not server_name in OPTIONS['servers'].split(): return string msg = weechat.info_get_hashtable("irc_message_parse", {"message": string}) - username = msg['nick'] + username = msg['nick'].lower() currentbuf = weechat.current_buffer() url = 'https://api.twitch.tv/kraken/channels/' + username url_hook = weechat.hook_process( - "url:" + url+params, 7 * 1000, "channel_api", currentbuf) + "url:" + url+params, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': username})) return "" def config_setup(): From e279343b2d2b48a7420fdc1fff1d2d80c18d4c37 Mon Sep 17 00:00:00 2001 From: Bertrand Ciroux Date: Sat, 10 Jun 2017 19:40:32 +0200 Subject: [PATCH 055/642] New script i3lock_away.py: set away status if i3lock is running --- python/i3lock_away.py | 160 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 python/i3lock_away.py diff --git a/python/i3lock_away.py b/python/i3lock_away.py new file mode 100644 index 00000000..68243866 --- /dev/null +++ b/python/i3lock_away.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 Bertrand Ciroux +# +# 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 . +# +# Set away status if i3lock is running +# This script a copy the slock_away.py script, by Peter A. Shevtsov. +# The only change is the detection of the i3lock process instead of the slock one. +# +# History: +# +# 2017-06-07, Bertrand Ciroux : +# version 0.1: initial release +# + +SCRIPT_NAME = "i3lock_away" +SCRIPT_AUTHOR = "Bertrand Ciroux " +SCRIPT_VERSION = "0.1" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Set away status if i3lock is running" + +SCRIPT_COMMAND = "i3lock_away" + +import_ok = True + +try: + import weechat +except ImportError: + print "This script must be run under WeeChat." + print "Get WeeChat now at: http://www.weechat.org/" + import_ok = False + +try: + import subprocess +except ImportError: + print "Missing package(s) for %s: %s" % (SCRIPT_NAME, message) + import_ok = False + +TIMER = None + +settings = { + 'away_message': 'Away', + 'interval': '20', # How often to check for inactivity (in seconds) + 'away': '0' +} + + +def set_back(overridable_messages): + """Removes away status for servers + where one of the overridable_messages is set""" + if (weechat.config_get_plugin('away') == '0'): + return # No need to come back again + serverlist = weechat.infolist_get('irc_server', '', '') + if serverlist: + buffers = [] + while weechat.infolist_next(serverlist): + if (weechat.infolist_string(serverlist, 'away_message') + in overridable_messages): + ptr = weechat.infolist_pointer(serverlist, 'buffer') + if ptr: + buffers.append(ptr) + weechat.infolist_free(serverlist) + for buffer in buffers: + weechat.command(buffer, "/away") + weechat.config_set_plugin('away', '0') + + +def set_away(message, overridable_messages=[]): + """Sets away status, but respectfully + (so it doesn't change already set statuses""" + if (weechat.config_get_plugin('away') == '1'): + return # No need to go away again + # (this prevents some repeated messages) + serverlist = weechat.infolist_get('irc_server', '', '') + if serverlist: + buffers = [] + while weechat.infolist_next(serverlist): + if weechat.infolist_integer(serverlist, 'is_away') == 0: + ptr = weechat.infolist_pointer(serverlist, 'buffer') + if ptr: + buffers.append(ptr) + elif (weechat.infolist_string(serverlist, 'away_message') + in overridable_messages): + buffers.append(weechat.infolist_pointer(serverlist, 'buffer')) + weechat.infolist_free(serverlist) + for buffer in buffers: + weechat.command(buffer, "/away %s" % message) + weechat.config_set_plugin('away', '1') + + +def i3lock_away_cb(data, buffer, args): + """Callback for /i3lock_away command""" + response = { + 'msg': lambda status: + weechat.config_set_plugin('away_message', status) + } + if args: + words = args.strip().partition(' ') + if words[0] in response: + response[words[0]](words[2]) + else: + weechat.prnt('', "i3lock_away error: %s not a recognized command. " + "Try /help i3lock_away" % words[0]) + weechat.prnt('', "i3lock_away: away message: \"%s\"" % + weechat.config_get_plugin('away_message')) + return weechat.WEECHAT_RC_OK + + +def auto_check(data, remaining_calls): + """Callback from timer""" + check() + return weechat.WEECHAT_RC_OK + + +def check(): + """Check for existance of process and set away if it isn't there""" + pidof = subprocess.Popen("pidof i3lock", + shell=True, stdout=subprocess.PIPE) + pidof.wait() + if pidof.returncode == 0: + set_away(weechat.config_get_plugin('away_message'), []) + else: + set_back([weechat.config_get_plugin('away_message')]) + + +def check_timer(): + """Sets or unsets the timer + based on whether or not the plugin is enabled""" + global TIMER + if TIMER: + weechat.unhook(TIMER) + TIMER = weechat.hook_timer( + int(weechat.config_get_plugin('interval')) * 1000, + 0, 0, "auto_check", "") + +if __name__ == "__main__" and import_ok: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + for option, default_value in settings.iteritems(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, default_value) + + weechat.hook_command(SCRIPT_COMMAND, + SCRIPT_DESC, + "msg ", + "msg: set the away message\n", + "", "i3lock_away_cb", "") + check_timer() From 56b27cdf368833bb33bc3b60a93e6327c993d27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Wed, 21 Jun 2017 06:58:45 +0200 Subject: [PATCH 056/642] buffer_autoset.py 1.0: rename command /autosetbuffer to /buffer_autoset --- python/buffer_autoset.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/python/buffer_autoset.py b/python/buffer_autoset.py index 305bd6b2..c33d3ad9 100644 --- a/python/buffer_autoset.py +++ b/python/buffer_autoset.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2010-2015 Sébastien Helleu +# Copyright (C) 2010-2017 Sébastien Helleu # # 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 @@ -22,6 +22,8 @@ # # History: # +# 2017-06-21, Sébastien Helleu : +# version 1.0: rename command /autosetbuffer to /buffer_autoset # 2015-09-28, Simmo Saan : # version 0.9: instantly apply properties # 2015-07-12, Sébastien Helleu : @@ -46,11 +48,11 @@ SCRIPT_NAME = "buffer_autoset" SCRIPT_AUTHOR = "Sébastien Helleu " -SCRIPT_VERSION = "0.9" +SCRIPT_VERSION = "1.0" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Auto-set buffer properties when a buffer is opened" -SCRIPT_COMMAND = "autosetbuffer" +SCRIPT_COMMAND = SCRIPT_NAME import_ok = True @@ -143,7 +145,7 @@ def bas_config_write(): # ================================[ command ]================================= def bas_cmd(data, buffer, args): - """Callback for /autosetbuffer command.""" + """Callback for /buffer_autoset command.""" args = args.strip() if args == "": weechat.command("", "/set %s.buffer.*" % CONFIG_FILE_NAME) @@ -172,7 +174,7 @@ def bas_completion_current_buffer_cb(data, completion_item, buffer, completion): """ Complete with current buffer name (plugin.name), - for command '/autosetbuffer'. + for command '/buffer_autoset'. """ name = "%s.%s" % (weechat.buffer_get_string(buffer, "plugin"), weechat.buffer_get_string(buffer, "name")) @@ -182,7 +184,7 @@ def bas_completion_current_buffer_cb(data, completion_item, buffer, def bas_completion_options_cb(data, completion_item, buffer, completion): - """Complete with config options, for command '/autosetbuffer'.""" + """Complete with config options, for command '/buffer_autoset'.""" options = weechat.infolist_get("option", "", "%s.buffer.*" % CONFIG_FILE_NAME) if options: @@ -240,11 +242,12 @@ def bas_signal_buffer_opened_cb(data, signal, signal_data): weechat.buffer_get_string(buffer, "full_name")) return weechat.WEECHAT_RC_OK + def bas_config_option_cb(data, option, value): if not weechat.config_boolean(bas_options["look_instant"]): return weechat.WEECHAT_RC_OK - if not weechat.config_get(option): # option was deleted + if not weechat.config_get(option): # option was deleted return weechat.WEECHAT_RC_OK option = option[len("%s.buffer." % CONFIG_FILE_NAME):] @@ -267,6 +270,7 @@ def bas_config_option_cb(data, option, value): return weechat.WEECHAT_RC_OK + # ==================================[ main ]================================== if __name__ == "__main__" and import_ok: From 43479d21362ab40c8a7bf9ec5d16b0b42a864183 Mon Sep 17 00:00:00 2001 From: butlerx Date: Fri, 2 Jun 2017 15:43:22 +0100 Subject: [PATCH 057/642] spotify.py 0.8: add now required oauth support --- python/spotify.py | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/python/spotify.py b/python/spotify.py index b444aae9..602ff47b 100644 --- a/python/spotify.py +++ b/python/spotify.py @@ -21,9 +21,11 @@ # If someone posts a spotify track URL in a configured channel # this script will post back which track it is using spotify.url.fi service -# +# # # History: +# 2017-06-02, butlerx +# version 0.8: add now required oauth support # 2016-01-22, creadak # version 0.7: Updated for the new spotify API # 2011-03-11, Sebastien Helleu @@ -41,34 +43,30 @@ # version 0.1: initial # -import weechat as w import re -import json -import urllib import datetime +import weechat as w +import spotipy +from spotipy.oauth2 import SpotifyClientCredentials SCRIPT_NAME = "spotify" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "0.7" +SCRIPT_VERSION = "0.8" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Look up spotify urls" settings = { - "buffers" : 'freenode.#mychan,', # comma separated list of buffers - "emit_notice" : 'off', # on or off, use notice or msg + "buffers" : 'freenode.#mychan,', # comma separated list of buffers + "emit_notice" : 'off', # on or off, use notice or msg + "client_id" : 'client_id', + "client_secret" : 'client_secret' } settings_help = { - "buffers": 'A comma separated list of buffers the script should check', - "emit_notice": 'If on, this script will use /notice, if off, it will use /msg to post info' -} - -gateway = "https://api.spotify.com" - -endpoints = { - "track": 'v1/tracks', - "album": 'v1/albums', - "artist": 'v1/artists' + "buffers" : 'A comma separated list of buffers the script should check', + "emit_notice" : 'If on, this script will use /notice, if off, it will use /msg to post info', + "client_id" : 'required client id token go to https://developer.spotify.com/my-applications/#!/applications to generate your own', + "client_secret" : 'required client secret token go to https://developer.spotify.com/my-applications/#!/applications to generate your own' } spotify_track_res = (re.compile(r'spotify:(?P\w+):(?P\w{22})'), @@ -107,6 +105,8 @@ def spotify_print_cb(data, buffer, time, tags, displayed, highlight, prefix, mes buffer_name = w.buffer_get_string(buffer, "name") server, channel = buffer_name.split('.') buffers_to_check = w.config_get_plugin('buffers').split(',') + client_credentials_manager = SpotifyClientCredentials(w.config_get_plugin('client_id'), w.config_get_plugin('client_secret')) + spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager) command = "msg" if notice == "on": @@ -116,15 +116,20 @@ def spotify_print_cb(data, buffer, time, tags, displayed, highlight, prefix, mes return w.WEECHAT_RC_OK for type, id in get_spotify_ids(message): - data = json.load(urllib.urlopen('%s/%s/%s' % (gateway, endpoints[type], id))) - reply = parse_response(data, type) + if type == 'album': + results = spotify.album(id) + elif type == 'track': + results = spotify.track(id) + elif type == 'artist': + results = spotify.artist(id) + reply = parse_response(results, type) w.command('', "/%s -server %s %s %s" % (command, server, channel, reply)) return w.WEECHAT_RC_OK if __name__ == "__main__": if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "", ""): + SCRIPT_DESC, "", ""): # Set default settings for option, default in settings.iteritems(): if not w.config_is_set_plugin(option): @@ -133,5 +138,5 @@ def spotify_print_cb(data, buffer, time, tags, displayed, highlight, prefix, mes # Set help text for option, description in settings_help.iteritems(): w.config_set_desc_plugin(option, description) - + w.hook_print("", "", "spotify", 1, "spotify_print_cb", "") From 520918a128859f4a0933461f098434b9e3ce4541 Mon Sep 17 00:00:00 2001 From: pr3d4t0r Date: Sun, 2 Jul 2017 13:49:59 +0200 Subject: [PATCH 058/642] btc_ticker.py 1.2.0: add Ethereum support, scrub the Python in anticipation to Py3-only version --- python/btc_ticker.py | 74 +++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/python/btc_ticker.py b/python/btc_ticker.py index aae8279c..67a45df7 100644 --- a/python/btc_ticker.py +++ b/python/btc_ticker.py @@ -1,10 +1,10 @@ #!/usr/bin/env python # coding=utf-8 -# Copyright (c) 2014, Eugene Ciurana (pr3d4t0r) +# Copyright (c) 2014, 2017 Eugene Ciurana (pr3d4t0r) # All rights reserved. # -# Version 1.1.1 +# Version 1.2.0 # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: @@ -32,27 +32,29 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Main repository, version history: https://github.com/pr3d4t0r/weechat-btc-ticker +# +# Version history: https://github.com/pr3d4t0r/weechat-btc-ticker -from time import gmtime, strftime +from time import gmtime, strftime -import json +import json -import weechat +import weechat -# *** Symbolic constants *** +# *** constants *** BTCE_API_TIME_OUT = 15000 # ms -BTCE_API_URI = u'url:https://btc-e.com/api/2/%s_%s/ticker' +BTCE_API_URI = 'url:https://btc-e.com/api/2/%s_%s/ticker' -DEFAULT_CRYPTO_CURRENCY = u'btc' -DEFAULT_FIAT_CURRENCY = u'usd' +DEFAULT_CRYPTO_CURRENCY = 'btc' +DEFAULT_FIAT_CURRENCY = 'usd' -VALID_CRYPTO_CURRENCIES = [ DEFAULT_CRYPTO_CURRENCY, u'ltc' ] -VALID_FIAT_CURRENCIES = [ DEFAULT_FIAT_CURRENCY, u'eur', u'rur' ] +VALID_CRYPTO_CURRENCIES = [ DEFAULT_CRYPTO_CURRENCY, 'ltc', 'eth' ] +VALID_FIAT_CURRENCIES = [ DEFAULT_FIAT_CURRENCY, 'eur', 'rur' ] -COMMAND_NICK = u'tick' +COMMAND_NICK = 'tick' # *** Functions *** @@ -61,35 +63,31 @@ def extractRelevantInfoFrom(rawTicker): payload = json.loads(rawTicker) result = dict() - result[u'avg'] = payload[u'ticker'][u'avg'] - result[u'buy'] = payload[u'ticker'][u'buy'] - result[u'high'] = payload[u'ticker'][u'high'] - result[u'last'] = payload[u'ticker'][u'last'] - result[u'low'] = payload[u'ticker'][u'low'] - result[u'sell'] = payload[u'ticker'][u'sell'] - result[u'updated'] = unicode(payload[u'ticker'][u'updated']) + for key in payload['ticker']: + result[key] = payload['ticker'][key] - result[u'time'] = strftime(u'%Y-%b-%d %H:%M:%S Z', gmtime(payload[u'ticker'][u'updated'])) + result['updated'] = unicode(payload['ticker']['updated']) + result['time'] = strftime('%Y-%b-%d %H:%M:%S Z', gmtime(payload['ticker']['updated'])) return result def display(buffer, ticker, currencyLabel, fiatCurrencyLabel): - output = (u'%s:%s sell = %4.2f, buy = %4.2f, last = %4.2f; high = %4.2f, low = %4.2f, avg = %4.2f || via BTC-e on %s' % \ + output = ('%s:%s sell = %4.2f, buy = %4.2f, last = %4.2f; high = %4.2f, low = %4.2f, avg = %4.2f || via BTC-e on %s' % \ (currencyLabel, fiatCurrencyLabel, \ - ticker[u'sell'], ticker[u'buy'], ticker[u'last'], \ - ticker[u'high'], ticker[u'low'], ticker[u'avg'], \ - ticker[u'time'])) + ticker['sell'], ticker['buy'], ticker['last'], \ + ticker['high'], ticker['low'], ticker['avg'], \ + ticker['time'])) - weechat.command(buffer, u'/say %s' % output) + weechat.command(buffer, '/say %s' % output) -def displayCurrentTicker(buffer, rawTicker, cryptoCurrency, fiatCurrency): - if rawTicker is not None: +def displayCurrentTicker(buffer, rawTicker, cryptoCurrency, fiatCurrency, serviceURI): + if rawTicker: ticker = extractRelevantInfoFrom(rawTicker) display(buffer, ticker, cryptoCurrency.upper(), fiatCurrency.upper()) else: - weechat.prnt(buffer, u'%s\t*** UNABLE TO READ DATA FROM: %s ***' % (COMMAND_NICK, serviceURI)) + weechat.prnt(buffer, '%s\t*** UNABLE TO READ DATA FROM: %s ***' % (COMMAND_NICK, serviceURI)) def tickerPayloadHandler(tickerData, service, returnCode, out, err): @@ -97,7 +95,7 @@ def tickerPayloadHandler(tickerData, service, returnCode, out, err): weechat.prnt(u"", u"%s\tError with service call '%s'" % (COMMAND_NICK, service)) return weechat.WEECHAT_RC_OK - tickerInfo = tickerData.split(u' ') + tickerInfo = tickerData.split(' ') displayCurrentTicker('', out, tickerInfo[0], tickerInfo[1]) return weechat.WEECHAT_RC_OK @@ -105,29 +103,29 @@ def tickerPayloadHandler(tickerData, service, returnCode, out, err): def fetchJSONTickerFor(cryptoCurrency, fiatCurrency): serviceURI = BTCE_API_URI % (cryptoCurrency, fiatCurrency) - tickerData = cryptoCurrency+u' '+fiatCurrency + tickerData = cryptoCurrency+' '+fiatCurrency - weechat.hook_process(serviceURI, BTCE_API_TIME_OUT, u'tickerPayloadHandler', tickerData) + weechat.hook_process(serviceURI, BTCE_API_TIME_OUT, 'tickerPayloadHandler', tickerData) def displayCryptoCurrencyTicker(data, buffer, arguments): cryptoCurrency = DEFAULT_CRYPTO_CURRENCY fiatCurrency = DEFAULT_FIAT_CURRENCY - if len(arguments) > 0: - tickerArguments = arguments.split(u' ') # no argparse module; these aren't CLI, but WeeChat's arguments + if len(arguments): + tickerArguments = arguments.split(' ') # no argparse module; these aren't CLI, but WeeChat's arguments if len(tickerArguments) >= 1: if tickerArguments[0].lower() in VALID_CRYPTO_CURRENCIES: cryptoCurrency = tickerArguments[0].lower() else: - weechat.prnt(buffer, u'%s\tInvalid crypto currency; using default %s' % (COMMAND_NICK, DEFAULT_CRYPTO_CURRENCY)) + weechat.prnt(buffer, '%s\tInvalid crypto currency; using default %s' % (COMMAND_NICK, DEFAULT_CRYPTO_CURRENCY)) if len(tickerArguments) == 2: if tickerArguments[1].lower() in VALID_FIAT_CURRENCIES: fiatCurrency = tickerArguments[1].lower() else: - weechat.prnt(buffer, u'%s\tInvalid fiat currency; using default %s' % (COMMAND_NICK, DEFAULT_FIAT_CURRENCY)) + weechat.prnt(buffer, '%s\tInvalid fiat currency; using default %s' % (COMMAND_NICK, DEFAULT_FIAT_CURRENCY)) fetchJSONTickerFor(cryptoCurrency, fiatCurrency) @@ -136,7 +134,7 @@ def displayCryptoCurrencyTicker(data, buffer, arguments): # *** main *** -weechat.register(u'btc_ticker', u'pr3d4t0r', u'1.1.1', u'BSD', u'Display a crypto currency spot price ticker (BTC, LTC) in the active buffer', u'', u'UTF-8') +weechat.register('btc_ticker', 'pr3d4t0r', '1.2.0', 'BSD', 'Display a crypto currency spot price ticker (BTC, LTC) in the active buffer', '', 'UTF-8') -weechat.hook_command(COMMAND_NICK, u'Display Bitcoin or other crypto currency spot exchange value in a fiat currency like USD or EUR',\ - u'[btc|ltc|nmc [usd|eur|rur] ]', u' btc = Bitcoin\n ltc = Litecoin\n nmc = Namecoin\n usd = US dollar\n eur = euro\n rur = Russian ruble', u'', u'displayCryptoCurrencyTicker', u'') +weechat.hook_command(COMMAND_NICK, 'Display common crypto currency spot exchange values conveted to fiat currencies like USD or EUR',\ + '[btc|ltc|nmc [usd|eur|rur] ]', ' btc = Bitcoin\n ltc = Litecoin\n eth = Ethereum\n nmc = Namecoin\n usd = US dollar\n eur = euro\n rur = Russian ruble', '', 'displayCryptoCurrencyTicker', '') From 4c8e8f7bac7b9bd4fc175736ad1d71b4f74c1103 Mon Sep 17 00:00:00 2001 From: Aoede Date: Sun, 2 Jul 2017 16:48:05 +0200 Subject: [PATCH 059/642] prism.py 0.2.11: add -k switch to add black background --- python/prism.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python/prism.py b/python/prism.py index cbde043d..05732deb 100644 --- a/python/prism.py +++ b/python/prism.py @@ -8,6 +8,8 @@ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION # # 0. You just DO WHAT THE FUCK YOU WANT TO. +# 2017-06-28, Aoede +# v0.2.11: add -k switch to add black background # 2015-11-16, wowaname # v0.2.9, 0.2.10: wrote an actual parser rather than regex # 2014-09-03, Matthew Martin @@ -34,7 +36,7 @@ SCRIPT_NAME = "prism" SCRIPT_AUTHOR = "Alex Barrett " -SCRIPT_VERSION = "0.2.10" +SCRIPT_VERSION = "0.2.11" SCRIPT_LICENSE = "WTFPL" SCRIPT_DESC = "Taste the rainbow." @@ -59,12 +61,13 @@ SCRIPT_LICENSE, SCRIPT_DESC, "", ""): w.hook_command("prism", SCRIPT_DESC, - "[-rwmbe] text|-c[wbe] text", + "[-rwmbek] text|-c[wbe] text", " -r: randomizes the order of the color sequence\n" " -w: color entire words instead of individual characters\n" " -m: append /me to beginning of output\n" " -b: backwards text (entire string is reversed)\n" " -e: eye-destroying colors (randomized background colors)\n" + " -k: add black background (note: -e overrides this)\n" " -c: specify a separator to turn on colorization\n" " eg. -c : /topic :howdy howdy howdy\n" " text: text to be colored", @@ -103,6 +106,7 @@ def prism_cmd_cb(data, buffer, args): regex = regex_words if 'w' in opts else regex_chars inc = 'r' not in opts bs = 'e' in opts + k = 'k' in opts input = input[::-1] if 'b' in opts else input output = u"" @@ -112,6 +116,8 @@ def prism_cmd_cb(data, buffer, args): color_code = unicode(colors[color_index % color_count]).rjust(2, "0") if bs == 1: output += u'\x03' + color_code + ',' + find_another_color(color_code) + token + elif k == 1: + output += u'\x03' + color_code + ',' + '1'.rjust(2, "0") + token else: output += u"\x03" + color_code + token From e5c7fe587f30e138bcffa2167b68794c3fd1d7dd Mon Sep 17 00:00:00 2001 From: Jochen Saalfeld Date: Thu, 4 May 2017 10:46:45 +0200 Subject: [PATCH 060/642] shortenurl.py 0.6.1: fix api call to is.gd --- python/shortenurl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/shortenurl.py b/python/shortenurl.py index e3a10860..e325a714 100644 --- a/python/shortenurl.py +++ b/python/shortenurl.py @@ -14,6 +14,8 @@ # along with this program. If not, see . # History +# 2017-05-04, Jochen Saalfeld +# version 0.6.1: Fix support for is.gd, since the API changed # 2014-08-18, Ilkka Laukkanen # version 0.6: Add support for bit.ly via Python Bitly # (https://code.google.com/p/python-bitly/) @@ -40,11 +42,11 @@ SCRIPT_NAME = "shortenurl" SCRIPT_AUTHOR = "John Anderson " -SCRIPT_VERSION = "0.6.0" +SCRIPT_VERSION = "0.6.1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Shorten long incoming and outgoing URLs" -ISGD = 'http://is.gd/api.php?%s' +ISGD = 'https://is.gd/api.php?longurl=%s' TINYURL = 'http://tinyurl.com/api-create.php?%s' # script options From e420523f823901be24a5d02e0987bf542996682f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Sun, 23 Jul 2017 10:04:47 +0200 Subject: [PATCH 061/642] grep.py 0.7.8: fix modulo by zero when nick is empty string (closes #230) --- python/grep.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/python/grep.py b/python/grep.py index 30bf2a6c..24ab0c4b 100644 --- a/python/grep.py +++ b/python/grep.py @@ -66,6 +66,9 @@ # # History: # +# 2017-07-23, Sébastien Helleu +# version 0.7.8: fix modulo by zero when nick is empty string +# # 2016-06-23, mickael9 # version 0.7.7: fix get_home function # @@ -206,7 +209,7 @@ SCRIPT_NAME = "grep" SCRIPT_AUTHOR = "Elián Hanisch " -SCRIPT_VERSION = "0.7.7" +SCRIPT_VERSION = "0.7.8" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Search in buffers and logs" SCRIPT_COMMAND = "grep" @@ -378,13 +381,15 @@ def color_nick(nick): else: mode = mode_color = '' # nick color - nick_color = weechat.info_get('irc_nick_color', nick) - if not nick_color: - # probably we're in WeeChat 0.3.0 - #debug('no irc_nick_color') - color_nicks_number = config_int('weechat.look.color_nicks_number') - idx = (sum(map(ord, nick))%color_nicks_number) + 1 - nick_color = wcolor(config_string('weechat.color.chat_nick_color%02d' %idx)) + nick_color = '' + if nick: + nick_color = weechat.info_get('irc_nick_color', nick) + if not nick_color: + # probably we're in WeeChat 0.3.0 + #debug('no irc_nick_color') + color_nicks_number = config_int('weechat.look.color_nicks_number') + idx = (sum(map(ord, nick))%color_nicks_number) + 1 + nick_color = wcolor(config_string('weechat.color.chat_nick_color%02d' %idx)) return ''.join((prefix_c, prefix, mode_color, mode, nick_color, nick, suffix_c, suffix)) ### Config and value validation ### From 6a8e5a8d6ca193f5935c4d76f2a7a7d79f5ff266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Wed, 26 Jul 2017 19:55:55 +0200 Subject: [PATCH 062/642] urlserver.py 2.2: fix write on socket with python 3.x --- python/urlserver.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/python/urlserver.py b/python/urlserver.py index 7d2e0ace..f54211ef 100644 --- a/python/urlserver.py +++ b/python/urlserver.py @@ -44,6 +44,8 @@ # # History: # +# 2017-07-26, Sébastien Helleu : +# v2.2: fix write on socket with python 3.x # 2016-11-01, Sébastien Helleu : # v2.1: add option "msg_filtered" # 2016-01-20, Yves Stadler : @@ -110,7 +112,7 @@ SCRIPT_NAME = 'urlserver' SCRIPT_AUTHOR = 'Sébastien Helleu ' -SCRIPT_VERSION = '2.1' +SCRIPT_VERSION = '2.2' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Shorten URLs with own HTTP server' @@ -645,9 +647,11 @@ def urlserver_server_fd_cb(data, fd): '' % urlserver['urls'][number][3]) else: - conn.sendall('HTTP/1.1 302\r\n' - 'Location: %s\r\n\r\n' % - urlserver['urls'][number][3]) + conn.sendall( + 'HTTP/1.1 302\r\n' + 'Location: {}\r\n\r\n' + .format(urlserver['urls'][number][3]) + .encode('utf-8')) else: urlserver_server_reply_auth_required(conn) replysent = True From 1921971048074dc52dcfda73bba930ce091b6f69 Mon Sep 17 00:00:00 2001 From: "Matthew M. Boedicker" Date: Sun, 30 Jul 2017 14:40:39 -0700 Subject: [PATCH 063/642] otr.py 1.9.1: Fix traceback caused by non-ASCII nick Fix potr ErrorReceived printing bug. Argument parsing errors now raise exceptions. Fix require_encryption policy hint. --- python/otr.py | 555 ++++++++++++++++++++++++++++---------------------- 1 file changed, 313 insertions(+), 242 deletions(-) diff --git a/python/otr.py b/python/otr.py index 0af9e333..619f73ad 100644 --- a/python/otr.py +++ b/python/otr.py @@ -1,29 +1,32 @@ # -*- coding: utf-8 -*- -# otr - WeeChat script for Off-the-Record IRC messaging -# -# DISCLAIMER: To the best of my knowledge this script securely provides OTR -# messaging in WeeChat, but I offer no guarantee. Please report any security -# holes you find. -# -# Copyright (c) 2012-2015 Matthew M. Boedicker -# Nils Görs -# Daniel "koolfy" Faucon -# Felix Eckhofer -# -# Report issues at https://github.com/mmb/weechat-otr -# -# 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. +"""otr - WeeChat script for Off-the-Record IRC messaging + +DISCLAIMER: To the best of my knowledge this script securely provides OTR +messaging in WeeChat, but I offer no guarantee. Please report any security +holes you find. + +Copyright (c) 2012-2015 Matthew M. Boedicker + Nils Görs + Daniel "koolfy" Faucon + Felix Eckhofer -# 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. +Report issues at https://github.com/mmb/weechat-otr -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +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 . +""" + +# pylint: disable=too-many-lines from __future__ import unicode_literals @@ -38,6 +41,9 @@ import shutil import sys +import potr +import weechat + class PythonVersion2(object): """Python 2 version of code that must differ between Python 2 and 3.""" @@ -68,8 +74,7 @@ def to_unicode(self, strng): """Convert a utf-8 encoded string to a Unicode.""" if isinstance(strng, unicode): return strng - else: - return strng.decode('utf-8', 'replace') + return strng.decode('utf-8', 'replace') def to_str(self, strng): """Convert a Unicode to a utf-8 encoded string.""" @@ -87,7 +92,7 @@ def __init__(self, minor): import html.parser self.html_parser = html.parser if self.minor >= 4: - self.html_parser_init_kwargs = { 'convert_charrefs' : True } + self.html_parser_init_kwargs = {'convert_charrefs' : True} else: self.html_parser_init_kwargs = {} @@ -110,8 +115,7 @@ def to_unicode(self, strng): """Convert a utf-8 encoded string to unicode.""" if isinstance(strng, bytes): return strng.decode('utf-8', 'replace') - else: - return strng + return strng def to_str(self, strng): """Convert a Unicode to a utf-8 encoded string.""" @@ -122,10 +126,6 @@ def to_str(self, strng): else: PYVER = PythonVersion2() -import weechat - -import potr - SCRIPT_NAME = 'otr' SCRIPT_DESC = 'Off-the-Record messaging for IRC' SCRIPT_HELP = """{description} @@ -163,7 +163,7 @@ def to_str(self, strng): SCRIPT_AUTHOR = 'Matthew M. Boedicker' SCRIPT_LICENCE = 'GPL3' -SCRIPT_VERSION = '1.9.0' +SCRIPT_VERSION = '1.9.1' OTR_DIR_NAME = 'otr' @@ -171,9 +171,9 @@ def to_str(self, strng): POLICIES = { 'allow_v2' : 'allow OTR protocol version 2, effectively enable OTR ' - 'since v2 is the only supported version', + 'since v2 is the only supported version', 'require_encryption' : 'refuse to send unencrypted messages when OTR is ' - 'enabled', + 'enabled', 'log' : 'enable logging of OTR conversations', 'send_tag' : 'advertise your OTR capability using the whitespace tag', 'html_escape' : 'escape HTML special characters in outbound messages', @@ -190,9 +190,6 @@ def to_str(self, strng): IRC_SANITIZE_TABLE = dict((ord(char), None) for char in '\n\r\x00') -global otr_debug_buffer -otr_debug_buffer = None - # Patch potr.proto.TaggedPlaintext to not end plaintext tags in a space. # # When POTR adds OTR tags to plaintext it puts them at the end of the message. @@ -204,6 +201,7 @@ def to_str(self, strng): # The patched version also skips OTR tagging for CTCP messages because it # breaks the CTCP format. def patched__bytes__(self): + """Patched potr.proto.TaggedPlainText.__bytes__.""" # Do not tag CTCP messages. if self.msg.startswith(b'\x01') and \ self.msg.endswith(b'\x01'): @@ -280,25 +278,19 @@ def get_prefix(): def debug(msg): """Send a debug message to the OTR debug buffer.""" - debug_option = weechat.config_get(config_prefix('general.debug')) - global otr_debug_buffer - - if weechat.config_boolean(debug_option): - if not otr_debug_buffer: - otr_debug_buffer = weechat.buffer_new("OTR Debug", "", "", - "debug_buffer_close_cb", "") - weechat.buffer_set(otr_debug_buffer, 'title', 'OTR Debug') - weechat.buffer_set(otr_debug_buffer, 'localvar_set_no_log', '1') - prnt(otr_debug_buffer, ('{script} debug\t{text}'.format( - script=SCRIPT_NAME, - text=PYVER.unicode(msg) - ))) - -def debug_buffer_close_cb(data, buf): - """Set the OTR debug buffer to None.""" - global otr_debug_buffer - otr_debug_buffer = None - return weechat.WEECHAT_RC_OK + debug_option = config_get_prefixed('general.debug') + + if not weechat.config_boolean(debug_option): + return + + debug_buffer = weechat.buffer_search('python', 'OTR Debug') + if not debug_buffer: + debug_buffer = weechat.buffer_new('OTR Debug', '', '', '', '') + weechat.buffer_set(debug_buffer, 'title', 'OTR Debug') + weechat.buffer_set(debug_buffer, 'localvar_set_no_log', '1') + + prnt(debug_buffer, ('{script} debug\t{text}'.format( + script=SCRIPT_NAME, text=PYVER.unicode(msg)))) def current_user(server_name): """Get the nick and server of the current user on a server.""" @@ -307,8 +299,8 @@ def current_user(server_name): def irc_user(nick, server): """Build an IRC user string from a nick and server.""" return '{nick}@{server}'.format( - nick=nick.lower(), - server=server) + nick=nick.lower(), + server=server) def isupport_value(server, feature): """Get the value of an IRC server feature.""" @@ -328,8 +320,8 @@ def is_a_channel(channel, server): return channel.startswith(prefixes) -# Exception class for PRIVMSG parsing exceptions. class PrivmsgParseException(Exception): + """Exception class for PRIVMSG parsing exceptions.""" pass def parse_irc_privmsg(message, server): @@ -396,18 +388,26 @@ def first_instance(objs, klass): def config_prefix(option): """Add the config prefix to an option and return the full option name.""" return '{script}.{option}'.format( - script=SCRIPT_NAME, - option=option) + script=SCRIPT_NAME, + option=option) def config_color(option): """Get the color of a color config option.""" - return weechat.color(weechat.config_color(weechat.config_get( - config_prefix('color.{}'.format(option))))) + return weechat.color(weechat.config_color(config_get_prefixed( + 'color.{}'.format(option)))) def config_string(option): """Get the string value of a config option with utf-8 decode.""" return PYVER.to_unicode(weechat.config_string( - weechat.config_get(config_prefix(option)))) + config_get_prefixed(option))) + +def config_get(option): + """Get the value of a WeeChat config option.""" + return weechat.config_get(PYVER.to_str(option)) + +def config_get_prefixed(option): + """Get the value of a script prefixed WeeChat config option.""" + return config_get(config_prefix(option)) def buffer_get_string(buf, prop): """Wrap weechat.buffer_get_string() with utf-8 encode/decode.""" @@ -466,9 +466,9 @@ def format_default_policies(): for policy, desc in sorted(POLICIES.items()): buf.write(' {policy} ({desc}) : {value}\n'.format( - policy=policy, - desc=desc, - value=config_string('policy.default.{}'.format(policy)))) + policy=policy, + desc=desc, + value=config_string('policy.default.{}'.format(policy)))) buf.write('Change default policies with: /otr policy default NAME on|off') @@ -504,8 +504,8 @@ def show_account_fingerprints(): table_formatter = TableFormatter() for account in accounts(): table_formatter.add_row([ - account.name, - str(account.getPrivkey())]) + account.name, + str(account.getPrivkey())]) print_buffer('', table_formatter.format()) def show_peer_fingerprints(grep=None): @@ -524,10 +524,10 @@ def show_peer_fingerprints(grep=None): for fingerprint, trust in sorted(peer_data.items()): if grep is None or grep in peer: table_formatter.add_row([ - peer, - account.name, - potr.human_hash(fingerprint), - trust_descs[trust], + peer, + account.name, + potr.human_hash(fingerprint), + trust_descs[trust], ]) print_buffer('', table_formatter.format()) @@ -608,8 +608,8 @@ def __init__(self, account, peername): def policy_config_option(self, policy): """Get the option name of a policy option for this context.""" return config_prefix('.'.join([ - 'policy', self.peer_server, self.user.nick, self.peer_nick, - policy.lower()])) + 'policy', self.peer_server, self.user.nick, self.peer_nick, + policy.lower()])) def getPolicy(self, key): """Get the value of a policy option for this context.""" @@ -620,20 +620,18 @@ def getPolicy(self, key): elif key_lower == 'send_tag' and self.no_send_tag(): result = False else: - option = weechat.config_get( - PYVER.to_str(self.policy_config_option(key))) + option = config_get(self.policy_config_option(key)) if option == '': - option = weechat.config_get( - PYVER.to_str(self.user.policy_config_option(key))) + option = config_get(self.user.policy_config_option(key)) if option == '': - option = weechat.config_get(config_prefix('.'.join( - ['policy', self.peer_server, key_lower]))) + option = config_get_prefixed('.'.join( + ['policy', self.peer_server, key_lower])) if option == '': - option = weechat.config_get( - config_prefix('policy.default.{}'.format(key_lower))) + option = config_get_prefixed( + 'policy.default.{}'.format(key_lower)) result = bool(weechat.config_boolean(option)) @@ -733,14 +731,14 @@ def print_buffer(self, msg, level='info'): # add [nick] prefix if we have only a server buffer for the query if self.peer_nick and not buffer_is_private(buf): msg = '[{nick}] {msg}'.format( - nick=self.peer_nick, - msg=msg) + nick=self.peer_nick, + msg=msg) print_buffer(buf, msg, level) def hint(self, msg): """Print a message to the buffer but only when hints are enabled.""" - hints_option = weechat.config_get(config_prefix('general.hints')) + hints_option = config_get_prefixed('general.hints') if weechat.config_boolean(hints_option): self.print_buffer(msg, 'hint') @@ -782,7 +780,7 @@ def handle_tlvs(self, tlvs): self.print_buffer( """Peer has requested SMP verification: {msg} Respond with: /otr smp respond """.format( - msg=PYVER.to_unicode(smp1q.msg))) + msg=PYVER.to_unicode(smp1q.msg))) elif first_instance(tlvs, potr.proto.SMP2TLV): if not self.in_smp: debug('Received unexpected SMP2') @@ -799,23 +797,24 @@ def handle_tlvs(self, tlvs): if self.smpIsSuccess(): if self.smp_question: - self.smp_finish('SMP verification succeeded.', - 'success') + self.smp_finish( + 'SMP verification succeeded.', 'success') if not self.is_verified: self.print_buffer( - """You may want to authenticate your peer by asking your own question: -/otr smp ask <'question'> 'secret'""") - + 'You may want to authenticate your peer by ' + 'asking your own question:\n' + "/otr smp ask <'question'> 'secret'") else: - self.smp_finish('SMP verification succeeded.', - 'success') + self.smp_finish( + 'SMP verification succeeded.', 'success') else: self.smp_finish('SMP verification failed.', 'error') def verify_instructions(self): """Generate verification instructions for user.""" - return """You can verify that this contact is who they claim to be in one of the following ways: + return """You can verify that this contact is who they claim to be in +one of the following ways: 1) Verify each other's fingerprints using a secure channel: Your fingerprint : {your_fp} @@ -832,13 +831,11 @@ def verify_instructions(self): executing these commands from the appropriate conversation buffer """.format( - your_fp=self.user.getPrivkey(), - peer=self.peer, - peer_nick=self.peer_nick, - peer_server=self.peer_server, - peer_fp=potr.human_hash( - self.crypto.theirPubkey.cfingerprint()), - ) + your_fp=self.user.getPrivkey(), + peer=self.peer, + peer_nick=self.peer_nick, + peer_server=self.peer_server, + peer_fp=potr.human_hash(self.crypto.theirPubkey.cfingerprint())) def is_encrypted(self): """Return True if the conversation with this context's peer is @@ -859,9 +856,9 @@ def format_policies(self): for policy, desc in sorted(POLICIES.items()): buf.write(' {policy} ({desc}) : {value}\n'.format( - policy=policy, - desc=desc, - value='on' if self.getPolicy(policy) else 'off')) + policy=policy, + desc=desc, + value='on' if self.getPolicy(policy) else 'off')) buf.write('Change policies with: /otr policy NAME on|off') @@ -892,7 +889,7 @@ def get_log_level(self): buf = self.buffer() - if not weechat.config_get(self.get_logger_option_name(buf)): + if not config_get(self.get_logger_option_name()): result = -1 else: result = 0 @@ -906,8 +903,9 @@ def get_log_level(self): return result - def get_logger_option_name(self, buf): - """Returns the logger config option for the specified buffer.""" + def get_logger_option_name(self): + """Returns the logger config option for this context's buffer.""" + buf = self.buffer() name = buffer_get_string(buf, 'name') plugin = buffer_get_string(buf, 'plugin') @@ -925,7 +923,8 @@ def disable_logging(self): if self.is_logged(): weechat.command(self.buffer(), '/mute logger disable') self.print_buffer( - 'Logs have been temporarily disabled for the session. They will be restored upon finishing the OTR session.') + 'Logs have been temporarily disabled for the session. ' + 'They will be restored upon finishing the OTR session.') return previous_log_level @@ -948,7 +947,7 @@ def restore_logging(self, previous_log_level): previous_log_level)) if previous_log_level == -1: - logger_option_name = self.get_logger_option_name(buf) + logger_option_name = self.get_logger_option_name() self.print_buffer( 'Restoring buffer logging value to default', 'warning') weechat.command(buf, '/mute unset {}'.format( @@ -998,11 +997,11 @@ def no_send_tag(self): debug(('no_send_tag', no_send_tag_regex, self.peer_nick)) if no_send_tag_regex: return re.match(no_send_tag_regex, self.peer_nick, re.IGNORECASE) - + def __repr__(self): - return PYVER.to_str(('<{} {:x} peer_nick={c.peer_nick} ' - 'peer_server={c.peer_server}>').format( - self.__class__.__name__, id(self), c=self)) + return PYVER.to_str(( + '<{} {:x} peer_nick={c.peer_nick} peer_server={c.peer_server}>' + ).format(self.__class__.__name__, id(self), c=self)) class IrcOtrAccount(potr.context.Account): """Account class for OTR over IRC.""" @@ -1075,8 +1074,7 @@ def saveTrusts(self): debug(('trust write', uid, self.name, IrcOtrAccount.PROTOCOL, fpr, trust)) fpr_file.write(PYVER.to_str('\t'.join( - (uid, self.name, IrcOtrAccount.PROTOCOL, fpr, - trust)))) + (uid, self.name, IrcOtrAccount.PROTOCOL, fpr, trust)))) fpr_file.write('\n') def end_all_private(self): @@ -1088,7 +1086,7 @@ def end_all_private(self): def policy_config_option(self, policy): """Get the option name of a policy option for this account.""" return config_prefix('.'.join([ - 'policy', self.server, self.nick, policy.lower()])) + 'policy', self.server, self.nick, policy.lower()])) class IrcHTMLParser(PYVER.html_parser.HTMLParser): """A simple HTML parser that throws away anything but newlines and links""" @@ -1161,7 +1159,7 @@ def __init__(self): def add_row(self, row): """Add a row to the table.""" self.rows.append(row) - row_widths = [ len(s) for s in row ] + row_widths = [len(s) for s in row] if self.max_widths is None: self.max_widths = row_widths else: @@ -1169,12 +1167,12 @@ def add_row(self, row): def format(self): """Return the formatted table as a string.""" - return '\n'.join([ self.format_row(row) for row in self.rows ]) + return '\n'.join([self.format_row(row) for row in self.rows]) def format_row(self, row): """Format a single row as a string.""" return ' |'.join( - [ s.ljust(self.max_widths[i]) for i, s in enumerate(row) ]) + [s.ljust(self.max_widths[i]) for i, s in enumerate(row)]) def message_in_cb(data, modifier, modifier_data, string): """Incoming message callback""" @@ -1212,7 +1210,7 @@ def message_in_cb(data, modifier, modifier_data, string): context.handle_tlvs(tlvs) except potr.context.ErrorReceived as err: context.print_buffer('Received OTR error: {}'.format( - PYVER.to_unicode(err.args[0].error)), 'error') + PYVER.to_unicode(err.args[0])), 'error') except potr.context.NotEncryptedError: context.print_buffer( 'Received encrypted data but no private session established.', @@ -1279,12 +1277,12 @@ def message_out_cb(data, modifier, modifier_data, string): not is_query and \ context.getPolicy('require_encryption'): context.print_buffer( - 'Your message will not be sent, because policy requires an ' - 'encrypted connection.', 'error') + 'Your message will not be sent, because policy requires an ' + 'encrypted connection.', 'error') context.hint( - 'Wait for the OTR connection or change the policy to allow ' - 'clear-text messages:\n' - '/policy set require_encryption off') + 'Wait for the OTR connection or change the policy to allow ' + 'clear-text messages:\n' + '/otr policy require_encryption off') try: ret = context.sendMessage( @@ -1301,12 +1299,14 @@ def message_out_cb(data, modifier, modifier_data, string): except potr.context.NotEncryptedError as err: if err.args[0] == potr.context.EXC_FINISHED: context.print_buffer( - """Your message was not sent. End your private conversation:\n/otr finish""", + 'Your message was not sent. End your private ' + 'conversation:\n/otr finish', 'error') else: raise weechat.bar_item_update(SCRIPT_NAME) + # pylint: disable=bare-except except: try: print_buffer('', traceback.format_exc(), 'error') @@ -1315,6 +1315,7 @@ def message_out_cb(data, modifier, modifier_data, string): context.print_buffer( 'Failed to send message. See core buffer for traceback.', 'error') + # pylint: disable=bare-except except: pass @@ -1339,11 +1340,7 @@ def command_cb(data, buf, args): """Parse and dispatch WeeChat OTR commands.""" result = weechat.WEECHAT_RC_ERROR - try: - arg_parts = [PYVER.to_unicode(arg) for arg in shlex.split(args)] - except: - debug("Command parsing error.") - return result + arg_parts = [PYVER.to_unicode(arg) for arg in shlex.split(args)] if len(arg_parts) in (1, 3) and arg_parts[0] in ('start', 'refresh'): nick, server = default_peer_args(arg_parts[1:3], buf) @@ -1360,11 +1357,13 @@ def command_cb(data, buf, args): else: context.previous_log_level = context.get_log_level() - context.hint('Sending OTR query... Please await confirmation of the OTR session being started before sending a message.') + context.hint( + 'Sending OTR query... Please await confirmation of the OTR ' + 'session being started before sending a message.') if not context.getPolicy('send_tag'): context.hint( - 'To try OTR on all conversations with {peer}: /otr policy send_tag on'.format( - peer=context.peer)) + 'To try OTR on all conversations with {peer}: /otr ' + 'policy send_tag on'.format(peer=context.peer)) privmsg(server, nick, '?OTR?') @@ -1491,8 +1490,8 @@ def command_cb(data, buf, args): context.smpAbort() except potr.context.NotEncryptedError: context.print_buffer( - 'There is currently no encrypted session with {}.'.format( - context.peer), 'error') + 'There is currently no encrypted session with {}.' + .format(context.peer), 'error') else: debug('SMP aborted') context.smp_finish('SMP aborted.') @@ -1512,8 +1511,9 @@ def command_cb(data, buf, args): weechat.bar_item_update(SCRIPT_NAME) else: context.print_buffer( - 'No fingerprint for {peer}. Start an OTR conversation first: /otr start'.format( - peer=context.peer), 'error') + 'No fingerprint for {peer}. Start an OTR conversation ' + 'first: /otr start'.format(peer=context.peer), + 'error') result = weechat.WEECHAT_RC_OK elif len(arg_parts) in (1, 3) and arg_parts[0] == 'distrust': @@ -1531,8 +1531,8 @@ def command_cb(data, buf, args): weechat.bar_item_update(SCRIPT_NAME) else: context.print_buffer( - 'No fingerprint for {peer}. Start an OTR conversation first: /otr start'.format( - peer=context.peer), 'error') + 'No fingerprint for {peer}. Start an OTR conversation ' + 'first: /otr start'.format(peer=context.peer), 'error') result = weechat.WEECHAT_RC_OK @@ -1571,7 +1571,13 @@ def command_cb(data, buf, args): context.is_encrypted(): if context.previous_log_level is None: context.previous_log_level = context.get_log_level() - context.print_buffer('From this point on, this conversation will be logged. Please keep in mind that by doing so you are potentially putting yourself and your interlocutor at risk. You can disable this by doing /otr log stop', 'warning') + context.print_buffer( + 'From this point on, this conversation will be ' + 'logged. Please keep in mind that by doing so you ' + 'are potentially putting yourself and your ' + 'interlocutor at risk. You can disable this by doing ' + '/otr log stop', + 'warning') weechat.command(buf, '/mute logger set 9') result = weechat.WEECHAT_RC_OK @@ -1581,7 +1587,9 @@ def command_cb(data, buf, args): if context.previous_log_level is None: context.previous_log_level = context.get_log_level() weechat.command(buf, '/mute logger set 0') - context.print_buffer('From this point on, this conversation will NOT be logged ANYMORE.') + context.print_buffer( + 'From this point on, this conversation will NOT be ' + 'logged ANYMORE.') result = weechat.WEECHAT_RC_OK elif not context.is_encrypted(): @@ -1703,39 +1711,39 @@ def otr_statusbar_cb(data, item, window): if context.is_encrypted(): if encrypted_str: bar_parts.append(''.join([ - config_color('status.encrypted'), - encrypted_str, - config_color('status.default')])) + config_color('status.encrypted'), + encrypted_str, + config_color('status.default')])) if context.is_verified(): if authenticated_str: bar_parts.append(''.join([ - config_color('status.authenticated'), - authenticated_str, - config_color('status.default')])) + config_color('status.authenticated'), + authenticated_str, + config_color('status.default')])) elif unauthenticated_str: bar_parts.append(''.join([ - config_color('status.unauthenticated'), - unauthenticated_str, - config_color('status.default')])) + config_color('status.unauthenticated'), + unauthenticated_str, + config_color('status.default')])) if context.is_logged(): if logged_str: bar_parts.append(''.join([ - config_color('status.logged'), - logged_str, - config_color('status.default')])) + config_color('status.logged'), + logged_str, + config_color('status.default')])) elif notlogged_str: bar_parts.append(''.join([ - config_color('status.notlogged'), - notlogged_str, - config_color('status.default')])) + config_color('status.notlogged'), + notlogged_str, + config_color('status.default')])) elif unencrypted_str: bar_parts.append(''.join([ - config_color('status.unencrypted'), - unencrypted_str, - config_color('status.default')])) + config_color('status.unencrypted'), + unencrypted_str, + config_color('status.default')])) result = config_string('look.bar.state.separator').join(bar_parts) @@ -1827,14 +1835,24 @@ def init_config(): CONFIG_FILE, 'general', 0, 0, '', '', '', '', '', '', '', '', '', '') for option, typ, desc, default in [ - ('debug', 'boolean', 'OTR script debugging', 'off'), - ('hints', 'boolean', 'Give helpful hints how to use this script and how to stay secure while using OTR (recommended)', 'on'), - ('defaultkey', 'string', - 'default private key to use for new accounts (nick@server)', ''), - ('no_send_tag_regex', 'string', - 'do not OTR whitespace tag messages to nicks matching this regex ' - '(case insensitive)', - '^(alis|chanfix|global|.+serv|\*.+)$'), + ('debug', + 'boolean', + 'OTR script debugging', + 'off'), + ('hints', + 'boolean', + 'Give helpful hints how to use this script and how to stay ' + 'secure while using OTR (recommended)', + 'on'), + ('defaultkey', + 'string', + 'default private key to use for new accounts (nick@server)', + ''), + ('no_send_tag_regex', + 'string', + 'do not OTR whitespace tag messages to nicks matching this regex ' + '(case insensitive)', + '^(alis|chanfix|global|.+serv|\*.+)$'), ]: weechat.config_new_option( CONFIG_FILE, CONFIG_SECTIONS['general'], option, typ, desc, '', 0, @@ -1844,25 +1862,54 @@ def init_config(): CONFIG_FILE, 'color', 0, 0, '', '', '', '', '', '', '', '', '', '') for option, desc, default, update_cb in [ - ('status.default', 'status bar default color', 'default', - 'bar_config_update_cb'), - ('status.encrypted', 'status bar encrypted indicator color', 'green', - 'bar_config_update_cb'), - ('status.unencrypted', 'status bar unencrypted indicator color', - 'lightred', 'bar_config_update_cb'), - ('status.authenticated', 'status bar authenticated indicator color', - 'green', 'bar_config_update_cb'), - ('status.unauthenticated', 'status bar unauthenticated indicator color', - 'lightred', 'bar_config_update_cb'), - ('status.logged', 'status bar logged indicator color', 'lightred', - 'bar_config_update_cb'), - ('status.notlogged', 'status bar not logged indicator color', - 'green', 'bar_config_update_cb'), - ('buffer.hint', 'text color for hints', 'lightblue', ''), - ('buffer.info', 'text color for informational messages', 'default', ''), - ('buffer.success', 'text color for success messages', 'lightgreen', ''), - ('buffer.warning', 'text color for warnings', 'yellow', ''), - ('buffer.error', 'text color for errors', 'lightred', ''), + ('status.default', + 'status bar default color', + 'default', + 'bar_config_update_cb'), + ('status.encrypted', + 'status bar encrypted indicator color', + 'green', + 'bar_config_update_cb'), + ('status.unencrypted', + 'status bar unencrypted indicator color', + 'lightred', + 'bar_config_update_cb'), + ('status.authenticated', + 'status bar authenticated indicator color', + 'green', + 'bar_config_update_cb'), + ('status.unauthenticated', + 'status bar unauthenticated indicator color', + 'lightred', + 'bar_config_update_cb'), + ('status.logged', + 'status bar logged indicator color', + 'lightred', + 'bar_config_update_cb'), + ('status.notlogged', + 'status bar not logged indicator color', + 'green', + 'bar_config_update_cb'), + ('buffer.hint', + 'text color for hints', + 'lightblue', + ''), + ('buffer.info', + 'text color for informational messages', + 'default', + ''), + ('buffer.success', + 'text color for success messages', + 'lightgreen', + ''), + ('buffer.warning', + 'text color for warnings', + 'yellow', + ''), + ('buffer.error', + 'text color for errors', + 'lightred', + ''), ]: weechat.config_new_option( CONFIG_FILE, CONFIG_SECTIONS['color'], option, 'color', desc, '', 0, @@ -1872,32 +1919,45 @@ def init_config(): CONFIG_FILE, 'look', 0, 0, '', '', '', '', '', '', '', '', '', '') for option, desc, default, update_cb in [ - ('bar.prefix', 'prefix for OTR status bar item', 'OTR:', - 'bar_config_update_cb'), - ('bar.state.encrypted', - 'shown in status bar when conversation is encrypted', 'SEC', - 'bar_config_update_cb'), - ('bar.state.unencrypted', - 'shown in status bar when conversation is not encrypted', '!SEC', - 'bar_config_update_cb'), - ('bar.state.authenticated', - 'shown in status bar when peer is authenticated', 'AUTH', - 'bar_config_update_cb'), - ('bar.state.unauthenticated', - 'shown in status bar when peer is not authenticated', '!AUTH', - 'bar_config_update_cb'), - ('bar.state.logged', - 'shown in status bar when peer conversation is being logged to disk', - 'LOG', - 'bar_config_update_cb'), - ('bar.state.notlogged', - 'shown in status bar when peer conversation is not being logged to disk', - '!LOG', - 'bar_config_update_cb'), - ('bar.state.separator', 'separator for states in the status bar', ',', - 'bar_config_update_cb'), - ('prefix', 'prefix used for messages from otr (note: content is evaluated, see /help eval)', - '${color:default}:! ${color:brown}otr${color:default} !:', ''), + ('bar.prefix', + 'prefix for OTR status bar item', + 'OTR:', + 'bar_config_update_cb'), + ('bar.state.encrypted', + 'shown in status bar when conversation is encrypted', + 'SEC', + 'bar_config_update_cb'), + ('bar.state.unencrypted', + 'shown in status bar when conversation is not encrypted', + '!SEC', + 'bar_config_update_cb'), + ('bar.state.authenticated', + 'shown in status bar when peer is authenticated', + 'AUTH', + 'bar_config_update_cb'), + ('bar.state.unauthenticated', + 'shown in status bar when peer is not authenticated', + '!AUTH', + 'bar_config_update_cb'), + ('bar.state.logged', + 'shown in status bar when peer conversation is being logged to ' + 'disk', + 'LOG', + 'bar_config_update_cb'), + ('bar.state.notlogged', + 'shown in status bar when peer conversation is not being logged ' + 'to disk', + '!LOG', + 'bar_config_update_cb'), + ('bar.state.separator', + 'separator for states in the status bar', + ',', + 'bar_config_update_cb'), + ('prefix', + 'prefix used for messages from otr (note: content is evaluated, ' + 'see /help eval)', + '${color:default}:! ${color:brown}otr${color:default} !:', + ''), ]: weechat.config_new_option( CONFIG_FILE, CONFIG_SECTIONS['look'], option, 'string', desc, '', @@ -1908,13 +1968,24 @@ def init_config(): 'policy_create_option_cb', '', '', '') for option, desc, default in [ - ('default.allow_v2', 'default allow OTR v2 policy', 'on'), - ('default.require_encryption', 'default require encryption policy', - 'off'), - ('default.log', 'default enable logging to disk', 'off'), - ('default.send_tag', 'default send tag policy', 'off'), - ('default.html_escape', 'default HTML escape policy', 'off'), - ('default.html_filter', 'default HTML filter policy', 'on'), + ('default.allow_v2', + 'default allow OTR v2 policy', + 'on'), + ('default.require_encryption', + 'default require encryption policy', + 'off'), + ('default.log', + 'default enable logging to disk', + 'off'), + ('default.send_tag', + 'default send tag policy', + 'off'), + ('default.html_escape', + 'default HTML escape policy', + 'off'), + ('default.html_filter', + 'default HTML filter policy', + 'on'), ]: weechat.config_new_option( CONFIG_FILE, CONFIG_SECTIONS['policy'], option, 'boolean', desc, '', @@ -1973,42 +2044,42 @@ def weechat_version_ok(): error_message = ( '{script_name} requires WeeChat version >= 0.4.2. The current ' 'version is {current_version}.').format( - script_name=SCRIPT_NAME, - current_version=weechat.info_get('version', '')) + script_name=SCRIPT_NAME, + current_version=weechat.info_get('version', '')) prnt('', error_message) return False - else: - return True + return True SCRIPT_VERSION = git_info() or SCRIPT_VERSION def dependency_versions(): """Return a string containing the versions of all dependencies.""" return ('weechat-otr {script_version}, ' - 'potr {potr_major}.{potr_minor}.{potr_patch}-{potr_sub}, ' - 'Python {python_version}, ' - 'WeeChat {weechat_version}' - ).format( - script_version=SCRIPT_VERSION, - potr_major=potr.VERSION[0], - potr_minor=potr.VERSION[1], - potr_patch=potr.VERSION[2], - potr_sub=potr.VERSION[3], - python_version=platform.python_version(), - weechat_version=weechat.info_get('version', '')) - -def excepthook(typ, value, traceback): + 'potr {potr_major}.{potr_minor}.{potr_patch}-{potr_sub}, ' + 'Python {python_version}, ' + 'WeeChat {weechat_version}' + ).format( + script_version=SCRIPT_VERSION, + potr_major=potr.VERSION[0], + potr_minor=potr.VERSION[1], + potr_patch=potr.VERSION[2], + potr_sub=potr.VERSION[3], + python_version=platform.python_version(), + weechat_version=weechat.info_get('version', '')) + +def excepthook(typ, value, tracebak): + """Add dependency versions to tracebacks.""" sys.stderr.write('Versions: ') sys.stderr.write(dependency_versions()) sys.stderr.write('\n') - sys.__excepthook__(typ, value, traceback) + sys.__excepthook__(typ, value, tracebak) sys.excepthook = excepthook if weechat.register( - SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENCE, SCRIPT_DESC, - 'shutdown', ''): + SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENCE, SCRIPT_DESC, + 'shutdown', ''): if weechat_version_ok(): init_config() From 93639694800ea10d3c2d8c11936578c30667a4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sun, 20 Aug 2017 22:42:08 +0200 Subject: [PATCH 064/642] =?UTF-8?q?bufsize.py=200.8:=20add=20support=20for?= =?UTF-8?q?=20buffer=5Ffilters=5Fenabled=20and=20buffer=5Ffilters=5Fdisabl?= =?UTF-8?q?ed=20(WeeChat=20=E2=89=A5=202.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/bufsize.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/bufsize.py b/python/bufsize.py index d0f82d2f..153c9bc4 100644 --- a/python/bufsize.py +++ b/python/bufsize.py @@ -18,8 +18,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-08-17: nils_2 (freenode.#weechat) +# 0.8: add support for buffer_filters_enabled and buffer_filters_disabled (WeeChat ≥ 2.0) # 2016-12-16: nils_2 (freenode.#weechat) -# 0.7: add option show_scroll (idea by earnestly) +# 0.7: add option show_scroll (idea by earnestly) # 2016-04-23: wdbw # 0.6.2 : fix: type of filters_enabled # 2014-02-24: nesthib (freenode.#weechat) @@ -56,7 +58,7 @@ SCRIPT_NAME = "bufsize" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.7" +SCRIPT_VERSION = "0.8" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "scroll indicator; displaying number of lines below last line, overall lines in buffer, number of current line and percent displayed" @@ -235,7 +237,7 @@ def toggle_refresh(pointer, name, value): weechat.hook_signal('buffer_line_added','update_cb','') weechat.hook_signal('window_scrolled','update_cb','') weechat.hook_signal('buffer_switch','update_cb','') - weechat.hook_signal('filters_*','filtered_update_cb','') + weechat.hook_signal('*filters*','filtered_update_cb','') weechat.hook_command_run('/buffer clear*','update_cb','') weechat.hook_command_run('/window page*','update_cb','') weechat.hook_command_run('/input zoom_merged_buffer','update_cb','') From a3af80e99bb68c514716109cd4fd74c62c9d59b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sun, 20 Aug 2017 22:44:40 +0200 Subject: [PATCH 065/642] text_item.py 0.7: add type "!all", internal changes --- python/text_item.py | 49 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/python/text_item.py b/python/text_item.py index 4b63a815..6857f241 100644 --- a/python/text_item.py +++ b/python/text_item.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2016 by nils_2 +# Copyright (c) 2012-2017 by nils_2 # # add a plain text or evaluated content to item bar # @@ -17,6 +17,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-08-19: nils_2, (freenode.#weechat) +# 0.7 : add type "!all", internal changes +# # 2016-12-12: nils_2, (freenode.#weechat) # 0.6 : fix problem with multiple windows (reported by Ram-Z) # @@ -50,7 +53,7 @@ SCRIPT_NAME = "text_item" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.6" +SCRIPT_VERSION = "0.7" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "add a plain text or evaluated content to item bar" @@ -65,6 +68,7 @@ def add_hook(signal, item): # signal already exists? if signal in hooks: return +# weechat.prnt("","signal_type: %s" % signal) hooks[item] = weechat.hook_signal(signal, "bar_item_update", "") def unhook(hook): @@ -76,19 +80,15 @@ def unhook(hook): def toggle_refresh(pointer, name, value): option_name = name[len('plugins.var.python.' + SCRIPT_NAME + '.'):] # get optionname - # option was removed? remove bar_item from struct! + # option was removed? remove bar_item from struct if not weechat.config_get_plugin(option_name): ptr_bar = weechat.bar_item_search(option_name) if ptr_bar: weechat.bar_item_remove(ptr_bar) - return weechat.WEECHAT_RC_OK - else: - return weechat.WEECHAT_RC_OK + return weechat.WEECHAT_RC_OK - # check if option is new or simply changed - if weechat.bar_item_search(option_name): - weechat.bar_item_update(option_name) - else: + # check if option is new or changed + if not weechat.bar_item_search(option_name): weechat.bar_item_new(option_name,'update_item',option_name) weechat.bar_item_update(option_name) @@ -122,18 +122,14 @@ def update_item (data, item, window): window = weechat.current_window() value = weechat.config_get_plugin(data) - - if value: - value = check_buffer_type(window, data, value) - else: - return "" - if not value: return "" + value = check_buffer_type(window, data, value) + return substitute_colors(value,window) -# update item +# update item from weechat.hook_signal() def bar_item_update(signal, callback, callback_data): ptr_infolist_option = weechat.infolist_get('option','','plugins.var.python.' + SCRIPT_NAME + '.*') @@ -184,6 +180,10 @@ def check_buffer_type(window, data, value): if channel_type == 'all' or weechat.buffer_get_string(bufpointer,'localvar_type') == channel_type: return value + if channel_type == '!all': + a = ["channel","server","private"] + if weechat.buffer_get_string(bufpointer,'localvar_type') in a: + return value return "" # ================================[ main ]=============================== @@ -195,19 +195,20 @@ def check_buffer_type(window, data, value): '===========\n' 'Template:\n' '/set plugins.var.python.text_item. | <${color:name/number}>\n\n' - ' type : all, channel, server, private\n' - ' (you can use: /buffer localvar)\n\n' - ' signal (eg.): buffer_switch, buffer_closing, print, \n' + ' type : all, channel, server, private and !all (take effect on channel, server and private buffer)\n' + ' (see: /buffer localvar)\n\n' + ' signal (eg.): buffer_switch, buffer_closing, print, mouse_enabled\n' ' (for a list of all possible signals, see API doc weechat_hook_signal())\n\n' - 'Example:\n' - '=======\n' + 'Examples:\n' 'creates an option for a text item named "nick_text". The item will be created for "channel" buffers. ' 'The text displayed in the status-bar is "Nicks:" (yellow colored!):\n' ' /set plugins.var.python.text_item.nick_text "channel ${color:yellow}Nicks:"\n\n' 'now you have to add the item "nick_text" to the bar.items (use auto-completion or iset.pl!)\n' ' /set weechat.bar.status.items nick_text\n\n' - 'creates an option to display the terminal width and height in an item bar. item will be updated on signal "signal_sigwinch"\n' - ' /set plugins.var.python.text_item.dimension "all|signal_sigwinch width: ${info:term_width} height: ${info:term_height}"\n', + 'creates an option to display the terminal width and height in an item bar. item will be updated on signal "signal_sigwinch":\n' + ' /set plugins.var.python.text_item.dimension "all|signal_sigwinch width: ${info:term_width} height: ${info:term_height}"\n' + 'creates an option to display the status from "/filter toggle" and "/filter toggle @" command, item name is "filter_item":\n' + ' /set plugins.var.python.text_item.filter_item "!all|*filters* ${if:${info:filters_enabled}==1?${color:yellow}F:${color:243}F}${if:${buffer.filter}==1?${color:yellow}@:${color:243}@}"\n', '', '', '') From a11239a9d259e72e9b1eaaedb4f67315dfd5b741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sun, 27 Aug 2017 14:25:19 +0200 Subject: [PATCH 066/642] keepnick.py 1.4: eval_expression for nicks, use irc.server..password, add short /help --- python/keepnick.py | 67 ++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/python/keepnick.py b/python/keepnick.py index 2ed858d8..1b03649a 100644 --- a/python/keepnick.py +++ b/python/keepnick.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2014 by nils_2 +# Copyright (c) 2012-2017 by nils_2 # Copyright (c) 2006 by EgS # # script to keep your nick and recover it in case it's occupied @@ -18,6 +18,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-08-17: nils_2 (freenode.#weechat) +# 1.4 : eval_expression for nicks +# : use irc.server..password +# ; add short /help +# # 2016-05-12: picasso (freenode.#weechat) # 1.3 : monitor quits and nick changes # @@ -46,7 +51,7 @@ # 2012-02-08: nils_2, (freenode.#weechat) # 0.5 : sync with 0.3.x API (requested by CAHbI4) # -# requires: WeeChat version 0.3.4 +# requires: WeeChat version 0.4.2 # # Development is currently hosted at # https://github.com/weechatter/weechat-scripts @@ -82,7 +87,7 @@ # -------------------------------[ Constants ]------------------------------------- SCRIPT_NAME = "keepnick" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "1.3" +SCRIPT_VERSION = "1.4" SCRIPT_LICENCE = "GPL3" SCRIPT_DESC = "keep your nick and recover it in case it's occupied" @@ -92,8 +97,8 @@ 'timeout' : ('60','timeout (in seconds) to wait for an answer from server.'), 'serverlist' : ('','comma separated list of servers to look at. Try to register a nickname on server (see: /msg NickServ help).regular expression are allowed (eg. ".*" = matches ALL server,"freen.*" = matches freenode, freenet....)'), 'text' : ('Nickstealer left Network: %s!','text that will be displayed if your nick will not be occupied anymore. (\"%s\" is a placeholder for the servername)'), - 'nickserv' : ('/msg -server $server NICKSERV IDENTIFY $passwd','Use SASL authentification, if possible. This command will be used to IDENTIFY you on server (following placeholder can be used: \"$server\" for servername; \"$passwd\" for password. The password will be stored in a separate option for every single server: \"plugins.var.python.%s..password\"). Using the "/secure" function, you\'ll have to add a format described in "/help secure" to password option (eg: ${sec.data.keepnick_freenode_password})' % SCRIPT_NAME), - 'command' : ('/nick %s','This command will be used to rename your nick (\"%s\" will be filled with your nickname for specific server)'), + 'nickserv' : ('/msg -server $server NICKSERV IDENTIFY $passwd','Use SASL authentification, if possible. This command will be used to IDENTIFY you on server (following placeholder can be used: \"$server\" for servername; \"$passwd\" for password). You can create an option for every server to store password: \"plugins.var.python.%s..password\", otherwise the \"irc.server..password\" option will be used.' % SCRIPT_NAME), + 'command' : ('/nick %s','This command will be used to rename your nick (\"%s\" will be replaced with your nickname)'), 'debug' : ('off', 'When enabled, will output verbose debugging information during script operation'), } HOOK = { 'timer': '', 'redirect': '', 'quit': '', 'nick': '' } @@ -103,7 +108,7 @@ # calling /ison all x seconds using hook:timer() def ison(servername,nick,nicklist): command = ISON % ' '.join(nicklist) - debug_print("Checking nicks with command: %s" % command) + debug_print("Checking nicks on server %s with command: %s" % (servername, command) ) weechat.hook_hsignal_send('irc_redirect_command', { 'server': servername, 'pattern': 'ison', 'signal': SCRIPT_NAME, 'count': '1', 'string': servername, 'timeout': OPTIONS['timeout'], 'cmd_filter': '' }) weechat.hook_signal_send('irc_input_send', weechat.WEECHAT_HOOK_SIGNAL_STRING, '%s;;;;%s' % (servername,command)) @@ -119,7 +124,7 @@ def redirect_isonhandler(data, signal, hashtable): ISON_nicks = [nick.lower() for nick in ISON_nicks.split()] for nick in server_nicks(hashtable['server']): - mynick = weechat.info_get('irc_nick',hashtable['server']) + mynick = string_eval_expression( weechat.info_get('irc_nick',hashtable['server']) ) if nick.lower() == mynick.lower(): debug_print("I already have nick %s; not changing" % mynick) @@ -196,14 +201,10 @@ def my_nick_on_server(servername): def grabnick_and_auth(servername, nick): global OPTIONS - # get password for given server (evaluated) - if int(version) >= 0x00040200: - password = weechat.string_eval_expression( - weechat.config_get_plugin('%s.password' % servername), {}, - {}, {}) - else: - password = weechat.config_get_plugin( - '%s.password' % servername) + + password = string_eval_expression( weechat.config_get_plugin('%s.password' % servername) ) + if not password: + password = string_eval_expression( weechat.config_get("irc.server.%s.password" % servername) ) grabnick(servername, nick) # get your nick back @@ -213,6 +214,9 @@ def grabnick_and_auth(servername, nick): run_msg = t.safe_substitute(server=servername, passwd=password) weechat.command('', run_msg) +def string_eval_expression(string): + return weechat.string_eval_expression(string,{},{},{}) + def grabnick(servername, nick): if nick and servername: weechat.prnt(weechat.current_buffer(),OPTIONS['text'] % servername) @@ -277,16 +281,27 @@ def toggle_refresh(pointer, name, value): remove_hooks() # user switched timer off return weechat.WEECHAT_RC_OK +def print_usage(data, buffer, args): + weechat.prnt(buffer, "%s\t%s: script already running..." % ( string_eval_expression(weechat.config_string(weechat.config_get("weechat.look.prefix_error"))), SCRIPT_NAME) ) + return weechat.WEECHAT_RC_OK + # ================================[ main ]=============================== if __name__ == '__main__': - weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENCE, SCRIPT_DESC, '','') - - version = weechat.info_get("version_number", "") or 0 - if int(version) >= 0x00030400: - if int(OPTIONS['delay'][0]) > 0 and int(OPTIONS['timeout'][0]) > 0: - init_options() - install_hooks() - weechat.hook_config( 'plugins.var.python.' + SCRIPT_NAME + '.*', 'toggle_refresh', '' ) - else: - weechat.prnt('','%s%s %s' % (weechat.prefix('error'),SCRIPT_NAME,': needs version 0.3.4 or higher')) - weechat.command('','/wait 1ms /python unload %s' % SCRIPT_NAME) + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENCE, SCRIPT_DESC, '',''): + weechat.hook_command(SCRIPT_NAME,SCRIPT_DESC, + '', + 'You have to edit options with: /set *keepnick*\n' + 'I suggest using /iset script or /fset plugin.\n', + '', + 'print_usage', + '') + + version = weechat.info_get("version_number", "") or 0 + if int(version) >= 0x00040200: + if int(OPTIONS['delay'][0]) > 0 and int(OPTIONS['timeout'][0]) > 0: + init_options() + install_hooks() + weechat.hook_config( 'plugins.var.python.' + SCRIPT_NAME + '.*', 'toggle_refresh', '' ) + else: + weechat.prnt('','%s%s %s' % (weechat.prefix('error'),SCRIPT_NAME,': needs version 0.4.2 or higher')) + weechat.command('','/wait 1ms /python unload %s' % SCRIPT_NAME) From 0669250d51646511394e3a6df9bb85b8eb64c75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sun, 27 Aug 2017 14:26:57 +0200 Subject: [PATCH 067/642] text_item.py 0.7.1: improve /help text --- python/text_item.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/text_item.py b/python/text_item.py index 6857f241..47b284fb 100644 --- a/python/text_item.py +++ b/python/text_item.py @@ -17,6 +17,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-08-23: nils_2, (freenode.#weechat) +# 0.7.1 : improve /help text +# # 2017-08-19: nils_2, (freenode.#weechat) # 0.7 : add type "!all", internal changes # @@ -53,7 +56,7 @@ SCRIPT_NAME = "text_item" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.7" +SCRIPT_VERSION = "0.7.1" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "add a plain text or evaluated content to item bar" @@ -68,7 +71,6 @@ def add_hook(signal, item): # signal already exists? if signal in hooks: return -# weechat.prnt("","signal_type: %s" % signal) hooks[item] = weechat.hook_signal(signal, "bar_item_update", "") def unhook(hook): @@ -195,7 +197,7 @@ def check_buffer_type(window, data, value): '===========\n' 'Template:\n' '/set plugins.var.python.text_item. | <${color:name/number}>\n\n' - ' type : all, channel, server, private and !all (take effect on channel, server and private buffer)\n' + ' type : channel, server, private, all (all kind of buffers e.g. /color, /fset...) and !all (channel, server and private buffer)\n' ' (see: /buffer localvar)\n\n' ' signal (eg.): buffer_switch, buffer_closing, print, mouse_enabled\n' ' (for a list of all possible signals, see API doc weechat_hook_signal())\n\n' From 11bf9daee16a6212859eab902bdbd13d9d021573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sun, 27 Aug 2017 14:28:59 +0200 Subject: [PATCH 068/642] quick_force_color.py 0.6.1: print nicks in sorted order --- python/quick_force_color.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/quick_force_color.py b/python/quick_force_color.py index 723cf653..c3553b8a 100644 --- a/python/quick_force_color.py +++ b/python/quick_force_color.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-08-17: nils_2,(freenode.#weechat) +# 0.6.1: print nicks in sorted order # 2017-05-18: ticalc-travis (https://github.com/weechatter/weechat-scripts/pull/18) # 0.6 : Clean up some redundant code # : Add nicks to irc.look.nick_color_force in sorted order for easier manual editing @@ -56,7 +58,7 @@ SCRIPT_NAME = "quick_force_color" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.6" +SCRIPT_VERSION = "0.6.1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "quickly add/del/change entry in nick_color_force" @@ -102,7 +104,8 @@ def nick_colors_cmd_cb(data, buffer, args): else: weechat.prnt(buffer,"List of nicks in : %s" % nick_option) - for nick,color in list(colored_nicks.items()): + + for nick,color in sorted(list(colored_nicks.items())): weechat.prnt(buffer,"%s%s: %s" % (weechat.color(color),nick,color)) elif (argv[0].lower() == 'add') and (len(argv) == 3): From bde6ef63fc9d6134e3864a4b6d13f5101020b23f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sat, 2 Sep 2017 13:37:32 +0200 Subject: [PATCH 069/642] keepnick.py 1.4.1: fix eval_expression for nicks, add evaluation for options --- python/keepnick.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/python/keepnick.py b/python/keepnick.py index 1b03649a..915014fc 100644 --- a/python/keepnick.py +++ b/python/keepnick.py @@ -19,6 +19,10 @@ # along with this program. If not, see . # # 2017-08-17: nils_2 (freenode.#weechat) +# 1.4.1: fix eval_expression for nicks +# : add evaluation for options +# +# 2017-08-17: nils_2 (freenode.#weechat) # 1.4 : eval_expression for nicks # : use irc.server..password # ; add short /help @@ -87,7 +91,7 @@ # -------------------------------[ Constants ]------------------------------------- SCRIPT_NAME = "keepnick" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "1.4" +SCRIPT_VERSION = "1.4.1" SCRIPT_LICENCE = "GPL3" SCRIPT_DESC = "keep your nick and recover it in case it's occupied" @@ -95,9 +99,9 @@ OPTIONS = { 'delay' : ('600','delay (in seconds) to look at occupied nick (0 means OFF). It is not recommended to flood the server with /ison requests)'), 'timeout' : ('60','timeout (in seconds) to wait for an answer from server.'), - 'serverlist' : ('','comma separated list of servers to look at. Try to register a nickname on server (see: /msg NickServ help).regular expression are allowed (eg. ".*" = matches ALL server,"freen.*" = matches freenode, freenet....)'), + 'serverlist' : ('','comma separated list of servers to look at. Try to register a nickname on server (see: /msg NickServ help).regular expression are allowed (eg. ".*" = matches ALL server,"freen.*" = matches freenode, freenet....) (this option is evaluated).'), 'text' : ('Nickstealer left Network: %s!','text that will be displayed if your nick will not be occupied anymore. (\"%s\" is a placeholder for the servername)'), - 'nickserv' : ('/msg -server $server NICKSERV IDENTIFY $passwd','Use SASL authentification, if possible. This command will be used to IDENTIFY you on server (following placeholder can be used: \"$server\" for servername; \"$passwd\" for password). You can create an option for every server to store password: \"plugins.var.python.%s..password\", otherwise the \"irc.server..password\" option will be used.' % SCRIPT_NAME), + 'nickserv' : ('/msg -server $server NICKSERV IDENTIFY $passwd','Use SASL authentification, if possible. This command will be used to IDENTIFY you on server (following placeholder can be used: \"$server\" for servername; \"$passwd\" for password). You can create an option for every server to store password: \"plugins.var.python.%s..password\", otherwise the \"irc.server..password\" option will be used (this option is evaluated).' % SCRIPT_NAME), 'command' : ('/nick %s','This command will be used to rename your nick (\"%s\" will be replaced with your nickname)'), 'debug' : ('off', 'When enabled, will output verbose debugging information during script operation'), } @@ -124,7 +128,7 @@ def redirect_isonhandler(data, signal, hashtable): ISON_nicks = [nick.lower() for nick in ISON_nicks.split()] for nick in server_nicks(hashtable['server']): - mynick = string_eval_expression( weechat.info_get('irc_nick',hashtable['server']) ) + mynick = weechat.info_get('irc_nick',hashtable['server']) # current nick on server if nick.lower() == mynick.lower(): debug_print("I already have nick %s; not changing" % mynick) @@ -139,14 +143,15 @@ def redirect_isonhandler(data, signal, hashtable): def server_nicks(servername): infolist = weechat.infolist_get('irc_server','',servername) weechat.infolist_next(infolist) - nicks = weechat.infolist_string(infolist, 'nicks') + nicks = string_eval_expression( weechat.infolist_string(infolist, 'nicks') ) # nicks in config weechat.infolist_free(infolist) return nicks.split(',') def server_enabled(servername): - serverlist = OPTIONS['serverlist'].split(',') - server_matched = re.search(r"\b({})\b".format("|".join(serverlist)), - servername) + serverlist = string_eval_expression( OPTIONS['serverlist'] ).split(',') + + server_matched = re.search(r"\b({})\b".format("|".join(serverlist)),servername) + if servername in serverlist or server_matched: return True else: @@ -210,7 +215,7 @@ def grabnick_and_auth(servername, nick): if password != '' and OPTIONS['nickserv'] != '': # command stored in "keepnick.nickserv" option - t = Template(OPTIONS['nickserv']) + t = Template(string_eval_expression( OPTIONS['nickserv']) ) run_msg = t.safe_substitute(server=servername, passwd=password) weechat.command('', run_msg) From 505d8d886e4e4ee0c2acae89758f3e262067bb05 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 2 Sep 2017 21:04:52 +0200 Subject: [PATCH 070/642] launcher.pl 0.6: fix escaping of "signal_data" --- perl/launcher.pl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/perl/launcher.pl b/perl/launcher.pl index 2ced3d28..70d3926e 100644 --- a/perl/launcher.pl +++ b/perl/launcher.pl @@ -22,6 +22,8 @@ # # History: # +# 2017-08-29, Alex Xu (Hello71) : +# version 0.6: fix escaping of "signal_data" # 2011-02-13, Sebastien Helleu : # version 0.5: use new help format for command arguments # 2010-05-29, Sebastien Helleu : @@ -36,7 +38,7 @@ use strict; -my $version = "0.5"; +my $version = "0.6"; my $command_suffix = " &"; weechat::register("launcher", "FlashCode ", $version, "GPL3", @@ -109,9 +111,8 @@ sub signal my $command = weechat::config_get_plugin("signal.$_[1]"); if ($command ne "") { - $signal_data =~ s/([\$`"])/\\$1/g; - $signal_data =~ s/\n/ /g; - $command =~ s/\$signal_data/"$signal_data"/g; + $signal_data =~ s/'/'\''/g; + $command =~ s/\$signal_data/'$signal_data'/g; system($command.$command_suffix); } return weechat::WEECHAT_RC_OK; From 7976842c2848411d12664ebe7c68b23259fb483e Mon Sep 17 00:00:00 2001 From: Veaceslav Mindru Date: Sat, 2 Sep 2017 21:31:11 +0200 Subject: [PATCH 071/642] url_hinter.rb 0.4: fix wrong encoding errors --- ruby/url_hinter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby/url_hinter.rb b/ruby/url_hinter.rb index c4fe59e9..63a7dbe0 100644 --- a/ruby/url_hinter.rb +++ b/ruby/url_hinter.rb @@ -35,7 +35,7 @@ # Register url-hinter plugin to weechat and do initialization. # def weechat_init - Weechat.register('url_hinter', 'Kengo Tateish', '0.3', 'GPL3', 'Open an url in the weechat buffer to type a hint', '', '') + Weechat.register('url_hinter', 'Kengo Tateish', '0.4', 'GPL3', 'Open an url in the weechat buffer to type a hint', '', '') Weechat.hook_command( 'url_hinter', 'Search url strings, and highlight them, and if you type a hint key, open the url related to hint key.', @@ -372,6 +372,6 @@ def has_url? end def urls - remove_color_message.scan(/https?:\/\/[^  \(\)\r\n]*/).uniq + remove_color_message.encode("UTF-8", invalid: :replace, undef: :replace).scan(/https?:\/\/[^  \(\)\r\n]*/).uniq end end From 7cf9fabb44997b0d6fb83f9aab4957f6d87e71c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Wed, 6 Sep 2017 21:33:23 +0200 Subject: [PATCH 072/642] keepnick.py 1.4.2: add missing call to weechat.config_string --- python/keepnick.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/keepnick.py b/python/keepnick.py index 915014fc..2f6dcf16 100644 --- a/python/keepnick.py +++ b/python/keepnick.py @@ -18,6 +18,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-09-06: nils_2 (freenode.#weechat) +# 1.4.2: fix missing weechat.config_string() +# # 2017-08-17: nils_2 (freenode.#weechat) # 1.4.1: fix eval_expression for nicks # : add evaluation for options @@ -91,7 +94,7 @@ # -------------------------------[ Constants ]------------------------------------- SCRIPT_NAME = "keepnick" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "1.4.1" +SCRIPT_VERSION = "1.4.2" SCRIPT_LICENCE = "GPL3" SCRIPT_DESC = "keep your nick and recover it in case it's occupied" @@ -209,7 +212,7 @@ def grabnick_and_auth(servername, nick): password = string_eval_expression( weechat.config_get_plugin('%s.password' % servername) ) if not password: - password = string_eval_expression( weechat.config_get("irc.server.%s.password" % servername) ) + password = string_eval_expression( weechat.config_string(weechat.config_get("irc.server.%s.password" % servername)) ) grabnick(servername, nick) # get your nick back From 43de5cf776c63c3ceaef7cbc45e93d1ded57811f Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Sat, 9 Sep 2017 20:45:40 +0200 Subject: [PATCH 073/642] launcher.pl 0.7: properly fix escaping of "signal_data" --- perl/launcher.pl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/perl/launcher.pl b/perl/launcher.pl index 70d3926e..047d4e10 100644 --- a/perl/launcher.pl +++ b/perl/launcher.pl @@ -22,6 +22,8 @@ # # History: # +# 2017-09-07, Alex Xu (Hello71) : +# version 0.7: properly fix escaping of "signal_data" # 2017-08-29, Alex Xu (Hello71) : # version 0.6: fix escaping of "signal_data" # 2011-02-13, Sebastien Helleu : @@ -38,7 +40,7 @@ use strict; -my $version = "0.6"; +my $version = "0.7"; my $command_suffix = " &"; weechat::register("launcher", "FlashCode ", $version, "GPL3", @@ -111,7 +113,7 @@ sub signal my $command = weechat::config_get_plugin("signal.$_[1]"); if ($command ne "") { - $signal_data =~ s/'/'\''/g; + $signal_data =~ s/'/'\\''/g; $command =~ s/\$signal_data/'$signal_data'/g; system($command.$command_suffix); } From a2b20d501d15b23652191a198e7181828584bd88 Mon Sep 17 00:00:00 2001 From: pr3d4t0r Date: Wed, 4 Oct 2017 22:29:11 +0200 Subject: [PATCH 074/642] btc_ticker.py 2.0.0: replace the btc-e API with Cryptonator's --- python/btc_ticker.py | 66 +++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/python/btc_ticker.py b/python/btc_ticker.py index 67a45df7..b67fd4ba 100644 --- a/python/btc_ticker.py +++ b/python/btc_ticker.py @@ -4,7 +4,7 @@ # Copyright (c) 2014, 2017 Eugene Ciurana (pr3d4t0r) # All rights reserved. # -# Version 1.2.0 +# Version 2.0.0 # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: @@ -36,7 +36,7 @@ # Version history: https://github.com/pr3d4t0r/weechat-btc-ticker -from time import gmtime, strftime +from datetime import datetime import json @@ -45,14 +45,13 @@ # *** constants *** -BTCE_API_TIME_OUT = 15000 # ms -BTCE_API_URI = 'url:https://btc-e.com/api/2/%s_%s/ticker' - +CRYPTOCUR_API_TIME_OUT = 15000 # ms +CRYPTOCUR_API_URI = 'url:https://api.cryptonator.com/api/ticker/%s-%s' DEFAULT_CRYPTO_CURRENCY = 'btc' DEFAULT_FIAT_CURRENCY = 'usd' -VALID_CRYPTO_CURRENCIES = [ DEFAULT_CRYPTO_CURRENCY, 'ltc', 'eth' ] -VALID_FIAT_CURRENCIES = [ DEFAULT_FIAT_CURRENCY, 'eur', 'rur' ] +VALID_CRYPTO_CURRENCIES = [ DEFAULT_CRYPTO_CURRENCY, 'ltc', 'eth', 'nmc', 'zec', 'dash', 'xrp', 'xmr', ] +VALID_FIAT_CURRENCIES = [ DEFAULT_FIAT_CURRENCY, 'eur', 'rur', ] COMMAND_NICK = 'tick' @@ -61,51 +60,52 @@ def extractRelevantInfoFrom(rawTicker): payload = json.loads(rawTicker) - result = dict() - - for key in payload['ticker']: - result[key] = payload['ticker'][key] - - result['updated'] = unicode(payload['ticker']['updated']) - result['time'] = strftime('%Y-%b-%d %H:%M:%S Z', gmtime(payload['ticker']['updated'])) + result = payload['ticker'] return result -def display(buffer, ticker, currencyLabel, fiatCurrencyLabel): - output = ('%s:%s sell = %4.2f, buy = %4.2f, last = %4.2f; high = %4.2f, low = %4.2f, avg = %4.2f || via BTC-e on %s' % \ - (currencyLabel, fiatCurrencyLabel, \ - ticker['sell'], ticker['buy'], ticker['last'], \ - ticker['high'], ticker['low'], ticker['avg'], \ - ticker['time'])) +def display(buffer, ticker): + baseCurrency = ticker['base'] + targetCurrency = ticker['target'] + price = float(ticker['price']) + volume = float(ticker['volume']) + change = float(ticker['change']) + now = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') + + output = '%s:%s price = %5.2f, volume = %5.2f, change = %4.2f on %s' % ( + baseCurrency, + targetCurrency, + price, + volume, + change, + now) weechat.command(buffer, '/say %s' % output) -def displayCurrentTicker(buffer, rawTicker, cryptoCurrency, fiatCurrency, serviceURI): +def displayCurrentTicker(buffer, rawTicker): if rawTicker: ticker = extractRelevantInfoFrom(rawTicker) - display(buffer, ticker, cryptoCurrency.upper(), fiatCurrency.upper()) + display(buffer, ticker) else: - weechat.prnt(buffer, '%s\t*** UNABLE TO READ DATA FROM: %s ***' % (COMMAND_NICK, serviceURI)) + weechat.prnt(buffer, '%s\t*** UNABLE TO READ DATA FROM: %s ***' % (COMMAND_NICK, CRYPTOCUR_API_URI)) -def tickerPayloadHandler(tickerData, service, returnCode, out, err): +def tickerPayloadHandler(_, service, returnCode, out, err): if returnCode == weechat.WEECHAT_HOOK_PROCESS_ERROR: weechat.prnt(u"", u"%s\tError with service call '%s'" % (COMMAND_NICK, service)) return weechat.WEECHAT_RC_OK - tickerInfo = tickerData.split(' ') - displayCurrentTicker('', out, tickerInfo[0], tickerInfo[1]) + displayCurrentTicker('', out) return weechat.WEECHAT_RC_OK def fetchJSONTickerFor(cryptoCurrency, fiatCurrency): - serviceURI = BTCE_API_URI % (cryptoCurrency, fiatCurrency) - tickerData = cryptoCurrency+' '+fiatCurrency + serviceURI = CRYPTOCUR_API_URI % (cryptoCurrency, fiatCurrency) - weechat.hook_process(serviceURI, BTCE_API_TIME_OUT, 'tickerPayloadHandler', tickerData) + weechat.hook_process(serviceURI, CRYPTOCUR_API_TIME_OUT, 'tickerPayloadHandler', "") def displayCryptoCurrencyTicker(data, buffer, arguments): @@ -134,7 +134,11 @@ def displayCryptoCurrencyTicker(data, buffer, arguments): # *** main *** -weechat.register('btc_ticker', 'pr3d4t0r', '1.2.0', 'BSD', 'Display a crypto currency spot price ticker (BTC, LTC) in the active buffer', '', 'UTF-8') +weechat.register('btc_ticker', 'pr3d4t0r', '2.0.0', 'BSD', 'Display a crypto currency spot price ticker (BTC, LTC, ETH) in the active buffer', '', 'UTF-8') + +cryptoCurrencies = '|'.join(sorted(VALID_CRYPTO_CURRENCIES)) +fiatCurrencies = '|'.join(VALID_FIAT_CURRENCIES) +argsWeeChat = '[%s [%s] ]' % (cryptoCurrencies, fiatCurrencies) weechat.hook_command(COMMAND_NICK, 'Display common crypto currency spot exchange values conveted to fiat currencies like USD or EUR',\ - '[btc|ltc|nmc [usd|eur|rur] ]', ' btc = Bitcoin\n ltc = Litecoin\n eth = Ethereum\n nmc = Namecoin\n usd = US dollar\n eur = euro\n rur = Russian ruble', '', 'displayCryptoCurrencyTicker', '') + argsWeeChat, ' btc = Bitcoin\n eth = Ethereum\n ltc = Litecoin\n zec = Zcash\n\n usd = US dollar\n eur = euro\n rur = Russian ruble', '', 'displayCryptoCurrencyTicker', '') From 2193e27f7bfeae66800ffcb7033696d8469f26bf Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Tue, 10 Oct 2017 20:24:52 +0200 Subject: [PATCH 075/642] autosort.py 3.0: switch to eval expressions for sorting rules. * Switch to evaluated expressions for sorting. * Add `/autosort debug` command. * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules. * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules. * Make tab completion context aware. --- python/autosort.py | 911 ++++++++++++++++++++++----------------------- 1 file changed, 438 insertions(+), 473 deletions(-) diff --git a/python/autosort.py b/python/autosort.py index 6854b9e6..cade603b 100644 --- a/python/autosort.py +++ b/python/autosort.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2013-2014 Maarten de Vries +# Copyright (C) 2013-2017 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 @@ -20,11 +20,17 @@ # 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 +# https://github.com/de-vri-es/weechat-autosort # # # Changelog: +# 3.0: +# * Switch to evaluated expressions for sorting. +# * Add `/autosort debug` command. +# * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules. +# * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules. +# * Make tab completion context aware. # 2.8: # * Fix compatibility with python 3 regarding unicode handling. # 2.7: @@ -49,24 +55,47 @@ # -import weechat -import re import json +import math +import re +import time +import weechat SCRIPT_NAME = 'autosort' SCRIPT_AUTHOR = 'Maarten de Vries ' -SCRIPT_VERSION = '2.8' +SCRIPT_VERSION = '3.0' SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Automatically or manually keep your buffers sorted and grouped by server.' +SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.' config = None hooks = [] +timer = None + +if hasattr(time, 'perf_counter'): + perf_counter = time.perf_counter +else: + perf_counter = time.clock + +def casefold(string): + if hasattr(string, 'casefold'): return string.casefold() + # Fall back to lowercasing for python2. + return string.lower() + +def list_swap(values, a, b): + values[a], values[b] = values[b], values[a] + +def list_move(values, old_index, new_index): + values.insert(new_index, values.pop(old_index)) + +def list_find(collection, value): + for i, elem in enumerate(collection): + if elem == value: return i + return None class HumanReadableError(Exception): pass - def parse_int(arg, arg_name = 'argument'): ''' Parse an integer and provide a more human readable error. ''' arg = arg.strip() @@ -75,216 +104,57 @@ def parse_int(arg, arg_name = 'argument'): except ValueError: raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg)) +def decode_rules(blob): + parsed = json.loads(blob) + if not isinstance(parsed, list): + log('Malformed rules, expected a JSON encoded list of strings, but got a {0}. No rules have been loaded. Please fix the setting manually.'.format(type(parsed))) + return [] -class Pattern: - ''' A simple glob-like pattern for matching buffer names. ''' - - def __init__(self, pattern, case_sensitive): - ''' 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 '\\'") - - if case_sensitive: - self.regex = re.compile('^' + regex + '$') - else: - self.regex = re.compile('^' + regex + '$', flags = re.IGNORECASE) - 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): - ''' 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, case_sensitive): - ''' 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], case_sensitive) - 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, case_sensitive): - ''' 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, case_sensitive) - 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) + for i, entry in enumerate(parsed): + if not isinstance(entry, (str, unicode)): + log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry))) + return [] - return result + return parsed +def decode_helpers(blob): + parsed = json.loads(blob) + if not isinstance(parsed, dict): + log('Malformed helpers, expected a JSON encoded dictonary but got a {0}. No helpers have been loaded. Please fix the setting manually.'.format(type(parsed))) + return {} -def encode_replacements(replacements): - ''' Encode a list of replacement patterns as JSON. ''' - return json.dumps(replacements.raw()) - + for key, value in parsed.items(): + if not isinstance(value, (str, unicode)): + log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix seting manually.'.format(key, type(value))) + return {} + return parsed class Config: ''' The autosort configuration. ''' default_rules = json.dumps([ - ('core', 0), - ('irc', 2), - ('*', 1), - - ('irc.irc_raw', 0), - ('irc.server', 1), + '${core_first}', + '${irc_last}', + '${buffer.plugin.name}', + '${irc_raw_first}', + '${server}', + '${info:autosort_order,${type},server,*,channel,private}', + '${hashless_name}', + '${buffer.full_name}', ]) - default_replacements = '[]' - default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed' + default_helpers = json.dumps({ + 'core_first': '${if:${buffer.full_name}!=core.weechat}', + 'irc_first': '${if:${buffer.plugin.name}!=irc}', + 'irc_last': '${if:${buffer.plugin.name}==irc}', + 'irc_raw_first': '${if:${buffer.full_name}!=irc.irc_raw}', + 'irc_raw_last': '${if:${buffer.full_name}==irc.irc_raw}', + 'hashless_name': '${info:autosort_replace,#,,${buffer.name}}', + }) + + default_signal_delay = 5 + + default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed' def __init__(self, filename): ''' Initialize the configuration. ''' @@ -292,19 +162,20 @@ def __init__(self, filename): self.filename = filename self.config_file = weechat.config_new(self.filename, '', '') self.sorting_section = None + self.v3_section = None self.case_sensitive = False - self.group_irc = True self.rules = [] - self.replacements = [] + self.helpers = {} self.signals = [] + self.signal_delay = Config.default_signal_delay, self.sort_on_config = True self.__case_sensitive = None - self.__group_irc = None self.__rules = None - self.__replacements = None + self.__helpers = None self.__signals = None + self.__signal_delay = None self.__sort_on_config = None if not self.config_file: @@ -312,6 +183,7 @@ def __init__(self, filename): return self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '') + self.v3_section = weechat.config_new_section(self.config_file, 'v3', False, False, '', '', '', '', '', '', '', '', '', '') if not self.sorting_section: log('Failed to initialize section "sorting" of configuration file.') @@ -326,39 +198,54 @@ def __init__(self, filename): '', '', '', '', '', '' ) - self.__group_irc = weechat.config_new_option( + 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, + 'rules', 'string', + 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.', + '', 0, 0, '', '', 0, '', '', '', '', '', '' ) - self.__rules = weechat.config_new_option( + weechat.config_new_option( self.config_file, self.sorting_section, + 'replacements', 'string', + 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.', + '', 0, 0, '', '', 0, + '', '', '', '', '', '' + ) + + self.__rules = weechat.config_new_option( + self.config_file, self.v3_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.__helpers = weechat.config_new_option( + self.config_file, self.v3_section, + 'helpers', 'string', + 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.', + '', 0, 0, Config.default_helpers, Config.default_helpers, 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.', + 'A space separated list of signals that will cause autosort to resort your buffer list.', '', 0, 0, Config.default_signals, Config.default_signals, 0, '', '', '', '', '', '' ) + self.__signal_delay = weechat.config_new_option( + self.config_file, self.sorting_section, + 'signal_delay', 'integer', + 'Delay in milliseconds to wait after a signal before sorting the buffer list. This prevents triggering many times if multiple signals arrive in a short time. It can also be needed to wait for buffer localvars to be available.', + '', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0, + '', '', '', '', '', '' + ) + self.__sort_on_config = weechat.config_new_option( self.config_file, self.sorting_section, 'sort_on_config_change', 'boolean', @@ -379,24 +266,24 @@ 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) + rules_blob = weechat.config_string(self.__rules) + helpers_blob = weechat.config_string(self.__helpers) + signals_blob = weechat.config_string(self.__signals) - self.rules = RuleList.decode(rules_blob, self.case_sensitive) - self.replacements = decode_replacements(replacements_blob) + self.rules = decode_rules(rules_blob) + self.helpers = decode_helpers(helpers_blob) self.signals = signals_blob.split() + self.signal_delay = weechat.config_integer(self.__signal_delay) 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) + weechat.config_option_set(self.__rules, json.dumps(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 save_helpers(self, run_callback = True): + ''' Save the current helpers to the configuration. ''' + weechat.config_option_set(self.__helpers, json.dumps(self.helpers), run_callback) def pad(sequence, length, padding = None): @@ -410,74 +297,70 @@ def log(message, buffer = 'NULL'): 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, number - 1)) - - weechat.infolist_free(buffer_list) - return buffers - - -def preprocess(buffer, config): + hdata = weechat.hdata_get('buffer') + buffer = weechat.hdata_get_list(hdata, "gui_buffers"); + + result = [] + while buffer: + number = weechat.hdata_integer(hdata, buffer, 'number') + result.append((number, buffer)) + buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer') + return hdata, result + +class MergedBuffers(list): + """ A list of merged buffers, possibly of size 1. """ + def __init__(self, number): + super(MergedBuffers, self).__init__() + self.number = number + +def merge_buffer_list(buffers): ''' - Preprocess a buffers names. + Group merged buffers together. + The output is a list of MergedBuffers. ''' - - # Make sure the name is a unicode string. - # On python3 this is a NOP since the string type is already decoded as UTF-8. - if isinstance(buffer, bytes): - buffer = buffer.decode('utf-8') - - 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. ''' + if not buffers: return [] + result = {} + for number, buffer in buffers: + if number not in result: result[number] = MergedBuffers(number) + result[number].append(buffer) + return result.values() + +def sort_buffers(hdata, buffers, rules, helpers, case_sensitive): + for merged in buffers: + for buffer in merged: + name = weechat.hdata_string(hdata, buffer, 'name') + + return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive)) + +def buffer_sort_key(rules, helpers, case_sensitive): + ''' Create a sort key function for a list of lists of merged buffers. ''' def key(buffer): - result = [] - name = '' - for word in preprocess(buffer[0], config): - name += ('.' if name else '') + word - result.append((rules.get_score(name), word)) + extra_vars = {} + for helper_name, helper in sorted(helpers.items()): + expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {}) + extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded) + result = [] + for rule in rules: + expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {}) + result.append(expanded if case_sensitive else casefold(expanded)) return result return key +def merged_sort_key(rules, helpers, case_sensitive): + buffer_key = buffer_sort_key(rules, helpers, case_sensitive) + def key(merged): + best = None + for buffer in merged: + this = buffer_key(buffer) + if best is None or this < best: best = this + return best + return key -def apply_buffer_order(order): +def apply_buffer_order(buffers): ''' Sort the buffers in weechat according to the given order. ''' - indices = list(order) - reverse = [0] * len(indices) - for i, index in enumerate(indices): - reverse[index] = i - - for i in range(len(indices)): - wanted = indices[i] - if wanted == i: continue - # Weechat buffers are 1-indexed, but our indices aren't. - weechat.command('', '/buffer swap {0} {1}'.format(i + 1, wanted + 1)) - indices[reverse[i]] = wanted - reverse[wanted] = reverse[i] - + for i, buffer in enumerate(buffers): + weechat.buffer_set(buffer[0], "number", str(i + 1)) def split_args(args, expected, optional = 0): ''' Split an argument string in the desired number of arguments. ''' @@ -486,31 +369,57 @@ def split_args(args, expected, optional = 0): raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split))) return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '') +def do_sort(): + hdata, buffers = get_buffers() + buffers = merge_buffer_list(buffers) + buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive) + apply_buffer_order(buffers) def command_sort(buffer, command, args): ''' Sort the buffers and print a confirmation. ''' - on_buffers_changed() - log("Finished sorting buffers.", buffer) + start = perf_counter() + do_sort() + elapsed = perf_counter() - start + log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed)) return weechat.WEECHAT_RC_OK +def command_debug(buffer, command, args): + hdata, buffers = get_buffers() + buffers = merge_buffer_list(buffers) + + # Show evaluation results. + log('Individual evaluation results:') + start = perf_counter() + key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive) + results = [] + for merged in buffers: + for buffer in merged: + fullname = weechat.hdata_string(hdata, buffer, 'full_name') + if isinstance(fullname, bytes): fullname = fullname.decode('utf-8') + results.append((fullname, key(buffer))) + elapsed = perf_counter() - start + + for fullname, result in results: + log('{0}: {1}'.format(fullname, result)) + log('Computing evalutaion results took {0:.4f} seconds.'.format(elapsed)) + + 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]) + output += ' {0}: {1}\n'.format(i, rule) if not len(config.rules): output += ' No sorting rules configured.\n' - log(output, buffer) + log(output ) 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.case_sensitive) - - config.rules.append(rule) + config.rules.append(args) config.save_rules() command_rule_list(buffer, command, '') @@ -521,7 +430,6 @@ 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.case_sensitive) config.rules.insert(index, rule) config.save_rules() @@ -533,7 +441,6 @@ 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.case_sensitive) config.rules[index] = rule config.save_rules() @@ -558,7 +465,7 @@ def command_rule_move(buffer, command, args): index_a = parse_int(index_a, 'index') index_b = parse_int(index_b, 'index') - config.rules.move(index_a, index_b) + list_move(config.rules, index_a, index_b) config.save_rules() command_rule_list(buffer, command, '') return weechat.WEECHAT_RC_OK @@ -570,94 +477,74 @@ def command_rule_swap(buffer, command, args): index_a = parse_int(index_a, 'index') index_b = parse_int(index_b, 'index') - config.rules.swap(index_a, index_b) + list_swap(config.rules, 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) +def command_helper_list(buffer, command, args): + ''' Show the list of helpers. ''' + output = 'Helper variables:\n' - return weechat.WEECHAT_RC_OK + width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys())) - -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, '') + for name, expression in sorted(config.helpers.items()): + output += ' {0:>{width}}: {1}\n'.format(name, expression, width=width) + if not len(config.helpers): + output += ' No helper variables configured.' + log(output) 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') +def command_helper_set(buffer, command, args): + ''' Add/update a helper to the helper list. ''' + name, expression = split_args(args, 2) - 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.helpers[name] = expression + config.save_helpers() + command_helper_list(buffer, command, '') - config.replacements[index] = (pattern, replacement) - config.save_replacements() - command_replacement_list(buffer, command, '') return weechat.WEECHAT_RC_OK +def command_helper_delete(buffer, command, args): + ''' Delete a helper from the helper list. ''' + name = args.strip() -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, '') + del config.helpers[name] + config.save_helpers() + command_helper_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') +def command_helper_rename(buffer, command, args): + ''' Rename a helper to a new position. ''' + old_name, new_name = split_args(args, 2) - config.replacements.move(index_a, index_b) - config.save_replacements() - command_replacement_list(buffer, command, '') + try: + config.helpers[new_name] = config.helpers[old_name] + del config.helpers[old_name] + except KeyError: + raise HumanReadableError('No such helper: {0}'.format(old_name)) + config.save_helpers() + command_helper_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') +def command_helper_swap(buffer, command, args): + ''' Swap two helpers. ''' + a, b = split_args(args, 2) + try: + config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b] + except KeyError as e: + raise HumanReadableError('No such helper: {0}'.format(e.args[0])) - config.replacements.swap(index_a, index_b) - config.save_replacements() - command_replacement_list(buffer, command, '') + config.helpers.swap(index_a, index_b) + config.save_helpers() + command_helper_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, '') @@ -676,30 +563,92 @@ def call_command(buffer, command, args, subcommands): log('{0}: command not found'.format(' '.join(command))) return weechat.WEECHAT_RC_ERROR - -def on_buffers_changed(*args, **kwargs): +def on_signal(*args, **kwargs): + global timer ''' Called whenever the buffer list changes. ''' - buffers = get_buffers() - buffers.sort(key=buffer_sort_key(config.rules)) - apply_buffer_order([i for _, i in buffers]) + if timer is not None: + weechat.unhook(timer) + timer = None + weechat.hook_timer(config.signal_delay, 0, 1, "on_timeout", "") return weechat.WEECHAT_RC_OK +def on_timeout(pointer, remaining_calls): + global timer + timer = None + do_sort() + return weechat.WEECHAT_RC_OK -def on_config_changed(*args, **kwargs): - ''' Called whenever the configuration changes. ''' - config.reload() - +def apply_config(): # 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', '')) + hooks.append(weechat.hook_signal(signal, 'on_signal', '')) if config.sort_on_config: - on_buffers_changed() + do_sort() + +def on_config_changed(*args, **kwargs): + ''' Called whenever the configuration changes. ''' + config.reload() + apply_config() return weechat.WEECHAT_RC_OK +def parse_arg(args): + if not args: return None, None + + result = '' + escaped = False + for i, c in enumerate(args): + if not escaped: + if c == '\\': + escaped = True + continue + elif c == ',': + return result, args[i+1:] + result += c + escaped = False + return result, None + +def parse_args(args, max = None): + result = [] + i = 0 + while max is None or i < max: + arg, args = parse_arg(args) + if arg is None: break + result.append(arg) + i += 1 + return result, args + +def on_info_replace(pointer, name, arguments): + arguments, rest = parse_args(arguments, 3) + if rest or len(arguments) < 3: + log('usage: ${{info:{0},old,new,text}}'.format(name)) + return '' + old, new, text = arguments + + return text.replace(old, new) + +def on_info_order(pointer, name, arguments): + arguments, rest = parse_args(arguments) + if len(arguments) < 1: + log('usage: ${{info:{0},value,first,second,third,...}}'.format(name)) + return '' + + value = arguments[0] + keys = arguments[1:] + if not keys: return '0' + + # Find the value in the keys (or '*' if we can't find it) + result = list_find(keys, value) + if result is None: result = list_find(keys, '*') + if result is None: result = len(keys) + + # Pad result with leading zero to make sure string sorting works. + width = int(math.log10(len(keys))) + 1 + return '{0:0{1}}'.format(result, width) + def on_autosort_command(data, buffer, args): ''' Called when the autosort command is invoked. ''' @@ -707,6 +656,7 @@ def on_autosort_command(data, buffer, args): return call_command(buffer, ['/autosort'], args, { ' ': command_sort, 'sort': command_sort, + 'debug': command_debug, 'rules': { ' ': command_rule_list, @@ -718,22 +668,71 @@ def on_autosort_command(data, buffer, args): '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, + 'helpers': { + ' ': command_helper_list, + 'list': command_helper_list, + 'set': command_helper_set, + 'delete': command_helper_delete, + 'rename': command_helper_rename, + 'swap': command_helper_swap, }, - 'sort': on_buffers_changed, }) except HumanReadableError as e: - log(e, buffer) + log(e) return weechat.WEECHAT_RC_ERROR +def add_completions(completion, words): + for word in words: + weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END) + +def autosort_complete_rules(words, completion): + if len(words) == 0: + add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update']) + if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'): + add_completions(completion, map(str, range(len(config.rules)))) + if len(words) == 2 and words[0] in ('move', 'swap'): + add_completions(completion, map(str, range(len(config.rules)))) + if len(words) == 2 and words[0] in ('update'): + try: + add_completions(completion, [config.rules[int(words[1])]]) + except KeyError: pass + except ValueError: pass + else: + add_completions(completion, ['']) + return weechat.WEECHAT_RC_OK + +def autosort_complete_helpers(words, completion): + if len(words) == 0: + add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap']) + elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'): + add_completions(completion, sorted(config.helpers.keys())) + elif len(words) == 2 and words[0] == 'swap': + add_completions(completion, sorted(config.helpers.keys())) + elif len(words) == 2 and words[0] == 'rename': + add_completions(completion, sorted(config.helpers.keys())) + elif len(words) == 2 and words[0] == 'set': + try: + add_completions(completion, [config.helpers[words[1]]]) + except KeyError: pass + return weechat.WEECHAT_RC_OK + +def on_autosort_complete(data, name, buffer, completion): + cmdline = weechat.buffer_get_string(buffer, "input") + cursor = weechat.buffer_get_integer(buffer, "input_pos") + prefix = cmdline[:cursor] + words = prefix.split()[1:] + + # If the current word isn't finished yet, + # ignore it for coming up with completion suggestions. + if prefix[-1] != ' ': words = words[:-1] + + if len(words) == 0: + add_completions(completion, ['debug', 'helpers', 'rules', 'sort']) + elif words[0] == 'rules': + return autosort_complete_rules(words[1:], completion) + elif words[0] == 'helpers': + return autosort_complete_helpers(words[1:], completion) + return weechat.WEECHAT_RC_OK 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. @@ -744,20 +743,23 @@ def on_autosort_command(data, buffer, args): /autosort sort Manually trigger the buffer sorting. +/autosort debug +Show the evaluation results of the sort rules for each buffer. + ## Sorting rules /autosort rules list Print the list of sort rules. -/autosort rules add = +/autosort rules add Add a new rule at the end of the list. -/autosort rules insert = +/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 update +Update a rule in the list with a new expression. /autosort rules delete Delete a rule from the list. @@ -769,28 +771,22 @@ def on_autosort_command(data, buffer, args): Swap two rules in the list -## Replacement patterns +## Helper variables -/autosort replacements list -Print the list of replacement patterns. +/autosort helpers list +Print the list of helper variables. -/autosort replacements add -Add a new replacement pattern at the end of the list. +/autosort helpers set +Add or update a helper variable with the given name. -/autosort replacements insert -Insert a new replacement pattern at the given index in the list. +/autosort helpers delete +Delete a helper variable. -/autosort replacements update -Update a replacement pattern in the list. +/autosort helpers rename +Rename a helper variable. -/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 +/autosort helpers swap +Swap the expressions of two helper variables in the list. # Introduction @@ -799,14 +795,26 @@ def on_autosort_command(data, buffer, args): 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. +## Sort rules +Autosort evaluates a list of eval expressions (see /help eval) and sorts the buffers based on evaluated result. +Earlier rules will be considered first. +Only if earlier rules produced identical results is the result of the next rule considered for sorting purposes. + +You can debug your sort rules with the `/autosort debug` command, which will print the evaluation results of each rule for each buffer. -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. +NOTE: The sort rules for version 3 are not compatible with version 2 or vice versa. +You will have to manually port your old rules to version 3 if you have any. + +## Helper variables +You may define helper variables for the main sort rules to keep your rules readable. +They can be used in the main sort rules as variables. +For example, a helper variable named `foo` can be accessed in a main rule with the string `${foo}`. + +## Replacing substrings +There is no default method for replacing text inside eval expressions. +However, autosort adds a `replace` info hook that can be used inside eval expressions: `${info:autosort_replace,from,to,text}`. +For example, `${info:autosort_replace,#,,${buffer.name}}` will evaluate to the buffer name with all hash signs stripped. +You can escape commas and backslashes inside the arguments by prefixing them with a backslash. ## Automatic or manual sorting By default, autosort will automatically sort your buffer list whenever a buffer is opened, merged, unmerged or renamed. @@ -815,71 +823,28 @@ def on_autosort_command(data, buffer, args): 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. +command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)' -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 -''' +info_replace_description = 'Replace all occurences of `from` with `to` in the string `text`.' +info_replace_arguments = 'from,to,text' -command_completion = 'sort||rules list|add|insert|update|delete|move|swap||replacements list|add|insert|update|delete|move|swap' +info_order_description = ( + 'Get a zero padded index of a value in a list of possible values.' + 'If the value is not found, the index for `*` is returned.' + 'If there is no `*` in the list, the highest index + 1 is returned.' +) +info_order_arguments = 'value,first,second,third,...' 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() + weechat.hook_config('autosort.*', 'on_config_changed', '') + weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '') + weechat.hook_command('autosort', command_description, '', '', command_completion, 'on_autosort_command', '') + weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '') + weechat.hook_info('autosort_order', info_order_description, info_order_arguments, 'on_info_order', '') + + apply_config() From dcef401da77b925e43a6dfe761f7464093442535 Mon Sep 17 00:00:00 2001 From: uncled1023 Date: Sat, 14 Oct 2017 08:51:29 +0200 Subject: [PATCH 076/642] New script teknik.py: interact with the Teknik Services, including file uploads, pastes, and URL shortening --- python/teknik.py | 132 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 python/teknik.py diff --git a/python/teknik.py b/python/teknik.py new file mode 100644 index 00000000..3c332f62 --- /dev/null +++ b/python/teknik.py @@ -0,0 +1,132 @@ +# Teknik created by Uncled1023 +from __future__ import print_function + +import_success = True + +import sys +import os +import threading +import json +import Tkinter as tk +import tkFileDialog + +try: + import weechat +except ImportError: + print('This script must be run under WeeChat.') + print('Get WeeChat now at: http://www.weechat.org/') + import_success = False + +# Requires Install +try: + from teknik import uploads as teknik +except ImportError as e: + print('Missing package(s) for %s: %s' % ('Teknik', e)) + import_success = False + +# Weechat Registration +weechat.register("Teknik", "Uncled1023", "1.0.0", "BSD", "Interact with the Teknik Services, including file uploads, pastes, and url shortening.", "script_closed", "") + +def upload_file(data): + try: + args = json.loads(data) + if args['file'] is not None and os.path.exists(args['file']): + # Try to upload the file + jObj = teknik.UploadFile(args['apiUrl'], args['file'], args['apiUsername'], args['apiToken']) + return json.dumps(jObj) + except: + e = sys.exc_info()[0] + print("Exception: %s" %e, file=sys.stderr) + return '' + +def process_upload(data, command, return_code, out, err): + if return_code == weechat.WEECHAT_HOOK_PROCESS_ERROR: + weechat.prnt("", "Error with command '%s'" % command) + return weechat.WEECHAT_RC_OK + if return_code > 0: + weechat.prnt("", "return_code = %d" % return_code) + if out != "": + results = json.loads(out) + # Either print the result to the input box, or write the error message to the window + if 'error' in results: + weechat.prnt("", 'Error: ' + results['error']['message']) + elif 'result' in results: + buffer = weechat.current_buffer() + weechat.buffer_set(buffer, 'input', results['result']['url']) + else: + weechat.prnt("", 'Unknown Error') + if err != "": + weechat.prnt("", "stderr: %s" % err) + return weechat.WEECHAT_RC_OK + +def teknik_set_url(url): + weechat.config_set_plugin('plugins.var.python.teknik.api_url', url) + +def teknik_set_token(token): + weechat.config_set_plugin('plugins.var.python.teknik.token', token) + +def teknik_set_username(username): + weechat.config_set_plugin('plugins.var.python.teknik.username', username) + +def script_closed(): + # Clean Up Session + return weechat.WEECHAT_RC_OK + +def teknik_command(data, buffer, args): + args = args.strip() + if args == "": + weechat.prnt("", "Error: You must specify a command") + else: + argv = args.split(" ") + command = argv[0].lower() + + # Upload a File + if command == 'upload': + if len(argv) < 2: + weechat.prnt("", "Error: You must specify a file") + else: + # Get current config values + apiUrl = weechat.config_string(weechat.config_get('plugins.var.python.teknik.api_url')) + apiUsername = weechat.config_string(weechat.config_get('plugins.var.python.teknik.username')) + apiToken = weechat.config_string(weechat.config_get('plugins.var.python.teknik.token')) + + data = {'file': argv[1], 'apiUrl': apiUrl, 'apiUsername': apiUsername, 'apiToken': apiToken} + hook = weechat.hook_process('func:upload_file', 0, "process_upload", json.dumps(data)) + + # Set a config option + elif command == 'set': + if len(argv) < 2: + weechat.prnt("", "Error: You must specify the option to set") + else: + option = argv[1].lower() + if option == 'username': + if len(argv) < 3: + weechat.prnt("", "Error: You must specify a username") + else: + teknik_set_username(argv[2]) + elif option == 'token': + if len(argv) < 3: + weechat.prnt("", "Error: You must specify an auth token") + else: + teknik_set_token(argv[2]) + elif option == 'url': + if len(argv) < 3: + weechat.prnt("", "Error: You must specify an api url") + else: + teknik_set_url(argv[2]) + else: + weechat.prnt("", "Error: Unrecognized Option") + else: + weechat.prnt("", "Error: Unrecognized Command") + + return weechat.WEECHAT_RC_OK + +if __name__ == "__main__" and import_success: + hook = weechat.hook_command("teknik", "Allows uploading of a file to Teknik and sharing the url directly to the chat.", + "[upload ] | [set username|token|url ]", + ' file: The file you want to upload' + ' username: The username for your Teknik account' + ' auth_token: The authentication token for your Teknik Account' + ' api_url: The URL for the Upload API', + "", + "teknik_command", "") From 5e4266068c38791b82c152169bee02ec665851b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sun, 15 Oct 2017 11:00:57 +0200 Subject: [PATCH 077/642] keepnick.py 1.5: fix empty string breaks output, add evaluation for option "text" and use variable "$server" instead of "%s" --- python/keepnick.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python/keepnick.py b/python/keepnick.py index 2f6dcf16..8259de5f 100644 --- a/python/keepnick.py +++ b/python/keepnick.py @@ -18,6 +18,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-10-14: Kaijo & nils_2 (freenode.#weechat) +# 1.5 : fix empty string breaks output +# : add evaluation for option "text" and use variable "$server" instead of "%s" +# # 2017-09-06: nils_2 (freenode.#weechat) # 1.4.2: fix missing weechat.config_string() # @@ -94,7 +98,7 @@ # -------------------------------[ Constants ]------------------------------------- SCRIPT_NAME = "keepnick" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "1.4.2" +SCRIPT_VERSION = "1.5" SCRIPT_LICENCE = "GPL3" SCRIPT_DESC = "keep your nick and recover it in case it's occupied" @@ -103,7 +107,7 @@ OPTIONS = { 'delay' : ('600','delay (in seconds) to look at occupied nick (0 means OFF). It is not recommended to flood the server with /ison requests)'), 'timeout' : ('60','timeout (in seconds) to wait for an answer from server.'), 'serverlist' : ('','comma separated list of servers to look at. Try to register a nickname on server (see: /msg NickServ help).regular expression are allowed (eg. ".*" = matches ALL server,"freen.*" = matches freenode, freenet....) (this option is evaluated).'), - 'text' : ('Nickstealer left Network: %s!','text that will be displayed if your nick will not be occupied anymore. (\"%s\" is a placeholder for the servername)'), + 'text' : ('Nickstealer left Network: $server!','text to display, when you get your nick back. (\"$server and $nick\" can be used) (this option is evaluated).'), 'nickserv' : ('/msg -server $server NICKSERV IDENTIFY $passwd','Use SASL authentification, if possible. This command will be used to IDENTIFY you on server (following placeholder can be used: \"$server\" for servername; \"$passwd\" for password). You can create an option for every server to store password: \"plugins.var.python.%s..password\", otherwise the \"irc.server..password\" option will be used (this option is evaluated).' % SCRIPT_NAME), 'command' : ('/nick %s','This command will be used to rename your nick (\"%s\" will be replaced with your nickname)'), 'debug' : ('off', 'When enabled, will output verbose debugging information during script operation'), @@ -227,7 +231,10 @@ def string_eval_expression(string): def grabnick(servername, nick): if nick and servername: - weechat.prnt(weechat.current_buffer(),OPTIONS['text'] % servername) + if OPTIONS['text']: + t = Template( string_eval_expression(OPTIONS['text']) ) + text = t.safe_substitute(server=servername, nick=nick) + weechat.prnt(weechat.current_buffer(), text) weechat.command(weechat.buffer_search('irc','%s.%s' % ('server',servername)), OPTIONS['command'] % nick) # ================================[ weechat hook ]=============================== From 24d3f9db81d3621dd7f75d8f6e6cfc411a49f907 Mon Sep 17 00:00:00 2001 From: "Louis.Beziaud" Date: Sat, 28 Oct 2017 08:22:05 +0200 Subject: [PATCH 078/642] colorize_nicks.py 24: allow UTF-8 nicks --- python/colorize_nicks.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/python/colorize_nicks.py b/python/colorize_nicks.py index 1460f013..5d268b54 100644 --- a/python/colorize_nicks.py +++ b/python/colorize_nicks.py @@ -21,6 +21,8 @@ # # # History: +# 2017-06-20: lbeziaud +# version 24: colorize utf8 nicks # 2017-03-01, arza # version 23: don't colorize nicklist group names # 2016-05-01, Simmo Saan @@ -79,11 +81,13 @@ SCRIPT_NAME = "colorize_nicks" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "23" +SCRIPT_VERSION = "24" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Use the weechat nick colors in the chat area" -VALID_NICK = r'([@~&!%+])?([-a-zA-Z0-9\[\]\\`_^\{|\}]+)' +# Based on the recommendations in RFC 7613. A valid nick is composed +# of anything but " ,*?.!@". +VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@]+)' valid_nick_re = re.compile(VALID_NICK) ignore_channels = [] ignore_nicks = [] @@ -189,6 +193,21 @@ def colorize_cb(data, modifier, modifier_data, line): if len(nick) < min_length or nick in ignore_nicks: continue + # If the matched word is not a known nick, we try to match the + # word without its first or last character (if not a letter). + # This is necessary as "foo:" is a valid nick, which could be + # adressed as "foo::". + if nick not in colored_nicks[buffer]: + if not nick[-1].isalpha() and not nick[0].isalpha(): + if nick[1:-1] in colored_nicks[buffer]: + nick = nick[1:-1] + elif not nick[0].isalpha(): + if nick[1:] in colored_nicks[buffer]: + nick = nick[1:] + elif not nick[-1].isalpha(): + if nick[:-1] in colored_nicks[buffer]: + nick = nick[:-1] + # Check that nick is in the dictionary colored_nicks if nick in colored_nicks[buffer]: nick_color = colored_nicks[buffer][nick] From 400a3021d9f4b8eaa6368b86c046eee07190caff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Thomas?= Date: Sat, 28 Oct 2017 08:30:11 +0200 Subject: [PATCH 079/642] grep.py 0.8: use function in hook_process, add option ""timeout_secs" (closes #239) The script now requires WeeChat >= 1.5. --- python/grep.py | 193 ++++++++++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 99 deletions(-) diff --git a/python/grep.py b/python/grep.py index 24ab0c4b..64ece623 100644 --- a/python/grep.py +++ b/python/grep.py @@ -53,6 +53,9 @@ # It can be used for force or disable background process, using '0' forces to always grep in # background, while using '' (empty string) will disable it. # +# * plugins.var.python.grep.timeout_secs: +# Timeout (in seconds) for background grepping. +# # * plugins.var.python.grep.default_tail_head: # Config option for define default number of lines returned when using --head or --tail options. # Can be overriden in the command with --number option. @@ -66,6 +69,11 @@ # # History: # +# 2017-09-20, mickael9 +# version 0.8: +# * use weechat 1.5+ api for background processing (old method was unsafe and buggy) +# * add timeout_secs setting (was previously hardcoded to 5 mins) +# # 2017-07-23, Sébastien Helleu # version 0.7.8: fix modulo by zero when nick is empty string # @@ -198,7 +206,12 @@ ### from os import path -import sys, getopt, time, os, re, tempfile +import sys, getopt, time, os, re + +try: + import cPickle as pickle +except ImportError: + import pickle try: import weechat @@ -209,20 +222,21 @@ SCRIPT_NAME = "grep" SCRIPT_AUTHOR = "Elián Hanisch " -SCRIPT_VERSION = "0.7.8" +SCRIPT_VERSION = "0.8" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Search in buffers and logs" SCRIPT_COMMAND = "grep" ### Default Settings ### settings = { -'clear_buffer' : 'off', -'log_filter' : '', -'go_to_buffer' : 'on', -'max_lines' : '4000', -'show_summary' : 'on', -'size_limit' : '2048', -'default_tail_head' : '10', + 'clear_buffer' : 'off', + 'log_filter' : '', + 'go_to_buffer' : 'on', + 'max_lines' : '4000', + 'show_summary' : 'on', + 'size_limit' : '2048', + 'default_tail_head' : '10', + 'timeout_secs' : '300', } ### Class definitions ### @@ -943,104 +957,85 @@ def show_matching_lines(): elif size_limit == '': background = False + regexp = make_regexp(pattern, matchcase) + + global grep_options, log_pairs + grep_options = (head, tail, after_context, before_context, + count, regexp, hilight, exact, invert) + + log_pairs = [(strip_home(log), log) for log in search_in_files] + if not background: # run grep normally - regexp = make_regexp(pattern, matchcase) - for log in search_in_files: - log_name = strip_home(log) - matched_lines[log_name] = grep_file(log, head, tail, after_context, before_context, - count, regexp, hilight, exact, invert) + for log_name, log in log_pairs: + matched_lines[log_name] = grep_file(log, *grep_options) buffer_update() else: - # we hook a process so grepping runs in background. - #debug('on background') - global hook_file_grep, script_path, bytecode - timeout = 1000*60*5 # 5 min - - quotify = lambda s: '"%s"' %s - files_string = ', '.join(map(quotify, search_in_files)) - - global tmpFile - # we keep the file descriptor as a global var so it isn't deleted until next grep - tmpFile = tempfile.NamedTemporaryFile(prefix=SCRIPT_NAME, - dir=weechat.info_get('weechat_dir', '')) - cmd = grep_process_cmd %dict(logs=files_string, head=head, pattern=pattern, tail=tail, - hilight=hilight, after_context=after_context, before_context=before_context, - exact=exact, matchcase=matchcase, home_dir=home_dir, script_path=script_path, - count=count, invert=invert, bytecode=bytecode, filename=tmpFile.name, - python=weechat.info_get('python2_bin', '') or 'python') - - #debug(cmd) - hook_file_grep = weechat.hook_process(cmd, timeout, 'grep_file_callback', tmpFile.name) - global pattern_tmpl + global hook_file_grep, grep_stdout, grep_stderr, pattern_tmpl + grep_stdout = grep_stderr = '' + hook_file_grep = weechat.hook_process( + 'func:grep_process', + get_config_int('timeout_secs') * 1000, + 'grep_process_cb', + '' + ) if hook_file_grep: - buffer_create("Searching for '%s' in %s worth of data..." %(pattern_tmpl, - human_readable_size(size))) + buffer_create("Searching for '%s' in %s worth of data..." % ( + pattern_tmpl, + human_readable_size(size) + )) else: buffer_update() -# defined here for commodity -grep_process_cmd = """%(python)s -%(bytecode)sc ' -import sys, cPickle, os -sys.path.append("%(script_path)s") # add WeeChat script dir so we can import grep -from grep import make_regexp, grep_file, strip_home -logs = (%(logs)s, ) -try: - regexp = make_regexp("%(pattern)s", %(matchcase)s) - d = {} - for log in logs: - log_name = strip_home(log, "%(home_dir)s") - lines = grep_file(log, %(head)s, %(tail)s, %(after_context)s, %(before_context)s, - %(count)s, regexp, "%(hilight)s", %(exact)s, %(invert)s) - d[log_name] = lines - fd = open("%(filename)s", "wb") - cPickle.dump(d, fd, -1) - fd.close() -except Exception, e: - print >> sys.stderr, e' -""" + +def grep_process(*args): + result = {} + try: + global grep_options, log_pairs + for log_name, log in log_pairs: + result[log_name] = grep_file(log, *grep_options) + except Exception, e: + result = e + + return pickle.dumps(result) grep_stdout = grep_stderr = '' -def grep_file_callback(filename, command, rc, stdout, stderr): - global hook_file_grep, grep_stderr, grep_stdout - global matched_lines - #debug("rc: %s\nstderr: %s\nstdout: %s" %(rc, repr(stderr), repr(stdout))) - if stdout: - grep_stdout += stdout - if stderr: - grep_stderr += stderr - if int(rc) >= 0: - - def set_buffer_error(): - grep_buffer = buffer_create() - title = weechat.buffer_get_string(grep_buffer, 'title') - title = title + ' %serror' %color_title - weechat.buffer_set(grep_buffer, 'title', title) + +def grep_process_cb(data, command, return_code, out, err): + global grep_stdout, grep_stderr, matched_lines, hook_file_grep + + grep_stdout += out + grep_stderr += err + + def set_buffer_error(message): + error(message) + grep_buffer = buffer_create() + title = weechat.buffer_get_string(grep_buffer, 'title') + title = title + ' %serror' % color_title + weechat.buffer_set(grep_buffer, 'title', title) + + if return_code == weechat.WEECHAT_HOOK_PROCESS_ERROR: + set_buffer_error("Background grep timed out") + hook_file_grep = None + return WEECHAT_RC_OK + + elif return_code >= 0: + hook_file_grep = None + if grep_stderr: + set_buffer_error(grep_stderr) + return WEECHAT_RC_OK try: - if grep_stderr: - error(grep_stderr) - set_buffer_error() - #elif grep_stdout: - #debug(grep_stdout) - elif path.exists(filename): - import cPickle - try: - #debug(file) - fd = open(filename, 'rb') - d = cPickle.load(fd) - matched_lines.update(d) - fd.close() - except Exception, e: - error(e) - set_buffer_error() - else: - buffer_update() - global tmpFile - tmpFile = None - finally: - grep_stdout = grep_stderr = '' - hook_file_grep = None + data = pickle.loads(grep_stdout) + if isinstance(data, Exception): + raise data + matched_lines.update(data) + except Exception, e: + set_buffer_error(repr(e)) + return WEECHAT_RC_OK + else: + buffer_update() + return WEECHAT_RC_OK def get_grep_file_status(): @@ -1415,18 +1410,18 @@ def positive_number(opt, val): tail = n def cmd_grep_stop(buffer, args): - global hook_file_grep, pattern, matched_lines, tmpFile + global hook_file_grep, pattern, matched_lines if hook_file_grep: if args == 'stop': weechat.unhook(hook_file_grep) hook_file_grep = None - s = 'Search for \'%s\' stopped.' %pattern + + s = 'Search for \'%s\' stopped.' % pattern say(s, buffer) grep_buffer = weechat.buffer_search('python', SCRIPT_NAME) if grep_buffer: weechat.buffer_set(grep_buffer, 'title', s) - del matched_lines - tmpFile = None + matched_lines = {} else: say(get_grep_file_status(), buffer) raise Exception From 77c05755d219b4185eb95225ee64b1a9804658d5 Mon Sep 17 00:00:00 2001 From: butlerx Date: Sat, 28 Oct 2017 08:35:58 +0200 Subject: [PATCH 080/642] spotify.py 0.9: add support for oauth keys being stored in secure data --- python/spotify.py | 80 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/python/spotify.py b/python/spotify.py index 602ff47b..00c5823e 100644 --- a/python/spotify.py +++ b/python/spotify.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -#Copyright (c) 2009 by xt +# Copyright (c) 2009 by xt # # 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 @@ -24,6 +24,8 @@ # # # History: +# 2017-09-30, butlerx +# version 0.9: Add support for oauth keys being stored in secure data # 2017-06-02, butlerx # version 0.8: add now required oauth support # 2016-01-22, creadak @@ -43,48 +45,66 @@ # version 0.1: initial # -import re import datetime -import weechat as w +import re + import spotipy +import weechat as w from spotipy.oauth2 import SpotifyClientCredentials -SCRIPT_NAME = "spotify" -SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "0.8" +SCRIPT_NAME = "spotify" +SCRIPT_AUTHOR = "xt " +SCRIPT_VERSION = "0.9" SCRIPT_LICENSE = "GPL" -SCRIPT_DESC = "Look up spotify urls" +SCRIPT_DESC = "Look up spotify urls" settings = { - "buffers" : 'freenode.#mychan,', # comma separated list of buffers - "emit_notice" : 'off', # on or off, use notice or msg - "client_id" : 'client_id', - "client_secret" : 'client_secret' + "buffers": 'freenode.#mychan,', # comma separated list of buffers + "emit_notice": 'off', # on or off, use notice or msg + "client_id": 'client_id', + "client_secret": 'client_secret' } settings_help = { - "buffers" : 'A comma separated list of buffers the script should check', - "emit_notice" : 'If on, this script will use /notice, if off, it will use /msg to post info', - "client_id" : 'required client id token go to https://developer.spotify.com/my-applications/#!/applications to generate your own', - "client_secret" : 'required client secret token go to https://developer.spotify.com/my-applications/#!/applications to generate your own' + "buffers": + 'A comma separated list of buffers the script should check', + "emit_notice": + 'If on, this script will use /notice, if off, it will use /msg to post info', + "client_id": + 'required client id token go to https://developer.spotify.com/my-applications/#!/applications to generate your own', + "client_secret": + 'required client secret token go to https://developer.spotify.com/my-applications/#!/applications to generate your own' } -spotify_track_res = (re.compile(r'spotify:(?P\w+):(?P\w{22})'), - re.compile(r'https?://open.spotify.com/(?P\w+)/(?P\w{22})')) +spotify_track_res = ( + re.compile(r'spotify:(?P\w+):(?P\w{22})'), + re.compile(r'https?://open.spotify.com/(?P\w+)/(?P\w{22})')) + def get_spotify_ids(s): for r in spotify_track_res: for type, track in r.findall(s): yield type, track + +def get_oauth(arg): + token = w.config_get_plugin(arg) + if token.startswith('${sec.data'): + return w.string_eval_expression(token, {}, {}, {}) + else: + return token + + def parse_response(data, type): if type == 'track': name = data['name'] album = data['album']['name'] artist = data['artists'][0]['name'] - duration = str(datetime.timedelta(milliseconds=data['duration_ms'])).split('.')[0] + duration = str( + datetime.timedelta(milliseconds=data['duration_ms'])).split('.')[0] popularity = data['popularity'] - return "%s - %s / %s %s %d%%" % (artist, name, album, duration, popularity) + return "%s - %s / %s %s %d%%" % (artist, name, album, duration, + popularity) elif type == 'album': name = data['name'] artist = data['artists'][0]['name'] @@ -94,25 +114,32 @@ def parse_response(data, type): for track in data['tracks']['items']: length += track['duration_ms'] duration = str(datetime.timedelta(milliseconds=length)).split('.')[0] - return "%s - %s (%s) - %d tracks (%s)" % (artist, name, released, tracks, duration) + return "%s - %s (%s) - %d tracks (%s)" % (artist, name, released, + tracks, duration) elif type == 'artist': name = data['name'] followers = data['followers']['total'] return "%s - %s followers" % (name, followers) -def spotify_print_cb(data, buffer, time, tags, displayed, highlight, prefix, message): + +def spotify_print_cb(data, buffer, time, tags, displayed, highlight, prefix, + message): notice = w.config_get_plugin('emit_notice') buffer_name = w.buffer_get_string(buffer, "name") server, channel = buffer_name.split('.') buffers_to_check = w.config_get_plugin('buffers').split(',') - client_credentials_manager = SpotifyClientCredentials(w.config_get_plugin('client_id'), w.config_get_plugin('client_secret')) - spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager) + client_credentials_manager = SpotifyClientCredentials( + get_oauth('client_id'), get_oauth('client_secret')) + spotify = spotipy.Spotify( + client_credentials_manager=client_credentials_manager) command = "msg" if notice == "on": command = "notice" - if buffer_name.lower() not in [buffer.lower() for buffer in buffers_to_check]: + if buffer_name.lower() not in [ + buffer.lower() for buffer in buffers_to_check + ]: return w.WEECHAT_RC_OK for type, id in get_spotify_ids(message): @@ -123,10 +150,11 @@ def spotify_print_cb(data, buffer, time, tags, displayed, highlight, prefix, mes elif type == 'artist': results = spotify.artist(id) reply = parse_response(results, type) - w.command('', "/%s -server %s %s %s" % (command, server, channel, reply)) - + w.command('', "/%s -server %s %s %s" % (command, server, channel, + reply)) return w.WEECHAT_RC_OK + if __name__ == "__main__": if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): From 8d62aa6fd522066cd4c2dd7255271f2ac1df562d Mon Sep 17 00:00:00 2001 From: Stravy Date: Sat, 28 Oct 2017 08:41:51 +0200 Subject: [PATCH 081/642] strmon.pl 0.5.3: replace notifo support by Notify My Android support, as notifo is down now --- perl/strmon.pl | 248 ++++++++++++++++++++++++------------------------- 1 file changed, 119 insertions(+), 129 deletions(-) diff --git a/perl/strmon.pl b/perl/strmon.pl index 7fc343f5..11f093f0 100644 --- a/perl/strmon.pl +++ b/perl/strmon.pl @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# +# use IO::Socket::INET; use WWW::Curl::Easy; use URI::Escape; @@ -22,7 +22,7 @@ use strict; use vars qw( %cmode $strmon_buffer $command_buffer $strmon_help $version $daemon_file $strmon_tag ); -$version = "0.5.2"; +$version = "0.5.3"; weechat::register( "strmon", "Stravy", $version, "GPL", "Messages monitoring and notifications", "", "" ); @@ -34,7 +34,7 @@ $strmon_tag='strmon_message'; $daemon_file=<<'AFP'; -#!/usr/bin/perl +#!/usr/bin/perl # # Copyright (c) 2009 by Stravy # @@ -61,7 +61,7 @@ # # Default directory to look for images is $HOME/.config/strmon_daemon/pics # Default directory to look for sounds is $HOME/.config/strmon_daemon/sounds -# these directories can be manually changed in this script by modifying +# these directories can be manually changed in this script by modifying # variables $picdir and $sounddir # use strict; @@ -72,7 +72,7 @@ use vars qw($VERSION @ISA $picdir $sounddir); $VERSION = '0.2'; @ISA = qw(Net::Daemon); # to inherit from Net::Daemon - + $picdir=$ENV{'HOME'}."/.config/strmon_daemon/pics"; $sounddir=$ENV{'HOME'}."/.config/strmon_daemon/sounds"; @@ -103,7 +103,7 @@ $sock->close(); return; } - $line =~ s/\s+$//; # Remove CRLF + $line =~ s/\s+$//; # Remove CRLF my($rc); my $message=do_the_work($line); $rc = printf $sock ("$message\n"); @@ -138,7 +138,7 @@ } if ($unformated) { - $ret=do_unformated($ligne); + $ret=do_unformated($ligne); } else { $ret=do_message($mod,$pic,$sound,$bgcolor,$fgcolor,$chancolor,$nickcolor,$nchan,$chan,$nick,$ligne); @@ -173,16 +173,16 @@ $ret="Let's be silent"; return $ret; } else - { + { $text=format_text($text); - + my $message=""; $message="$chan "; $message.="$nick "; $pic=$picdir."/".$pic unless ($pic=~/^\//); - $message.="
"; + $message.="
"; $message.="$text"; - + unless ($mod==2) { my $command="notify-send \"$message\" "; @@ -196,7 +196,7 @@ } $ret="Notification done"; return $ret; - } + } } sub do_unformated @@ -207,7 +207,7 @@ { my @list=`ls $picdir`; chop @list; - $ret=join(",",@list); + $ret=join(",",@list); } elsif ($ligne=~/^daemon soundlist/) { my @list=`ls $sounddir`; @@ -257,9 +257,9 @@ Although it can be run by itself, full advantage of strmon is achieved using companion script strmon_daemon.pl which allow sound and osd notifications, it is embedded in this script and can be generated with command : - /strmon daemon write + /strmon daemon write that will write script strmon_daemon.pl into \$HOME. -By default use of this daemon is deactivated, you can print current state, +By default use of this daemon is deactivated, you can print current state, enable or disable it with command: /strmon daemon [on|off] @@ -270,7 +270,7 @@ \$HOME/.config/strmon_daemon/pics/default.png \$HOME/.config/strmon_daemon/sounds/default.ogg -Principle of operation : +Principle of operation : 1) start notification daemon strmon_daemon.pl on local machine. 2) If you run weechat on the local machine, just load strmon.pl script into weechat and that's it. @@ -281,10 +281,12 @@ localhost:9867 on the local machine thus allowing strmon.pl weechat script to access the notification daemon on local machine. -In this version, notifo support has been added (smartphone notification), -see http://notifo.com/ to get an account. +In this version, notifo support (smartphone notification) has been replaced +by 'Notify My Android' as notifo no longer exist. +See https://www.notifymyandroid.com to get an account, unfortunately you will +only have 5 notifications per day for the free version. -strmon is configured by entering commands either with +strmon is configured by entering commands either with /strmon command in any buffer, either with command @@ -319,7 +321,7 @@ color {bgcolor|fgcolor|chanelcolor|nickcolor} color without argument : print default colors used for osd notifications with argument : set the specified color to be used as : - + bgcolor : color used as background fgcolor : text color used for the content of the message chanelcolor : text color used for chanel name @@ -331,15 +333,13 @@ ex : color fgcolor #ffffff color fgcolor black - notifo [on|off] - notifo test - notifo user [username] - notifo secret [API_secret] - without arguments : print current status of notifo use. + nma [on|off] + nma test + nma apikey [APIKEY] + without arguments : print current status of nma (Notify My Android) use. test : try to send a test notification - on|off : set use of notifo notifications - user [username] : print or set username for notifo account - secret [API_secret] : print or set user's api_secret for notifo account + on|off : set use of nma notifications + apikey [APIKEY] : print or set the apikey for nma account daemon [on|off] daemon write @@ -351,7 +351,7 @@ without arguments : print current status of daemon use. Note that if it is 'off' the only other possible commands are on|off, port and write. - on|off : set use of notification daemon + on|off : set use of notification daemon write : will write file \$HOME/strmon_daemon.pl test : try to contact the server with a test notification (bypassing default operation mode) @@ -371,15 +371,15 @@ sound sound soundfile without arguments : print current default sound - with argument : set the specified sound as default sound - + with argument : set the specified sound as default sound + nick nickname nick nickname test - nick nickname mode {normal|silent|nosound|novisual} + nick nickname mode {normal|silent|nosound|novisual} nick nickname pic picfile nick nickname sound soundfile nick nickname {bgcolor|fgcolor|chanelcolor|nickcolor} color - without arguments : show options specific to one nickname + without arguments : show options specific to one nickname with argument : set option specific to one nickname, or make a test notification from nickname. @@ -412,7 +412,7 @@ renumbered after that. number {normal|silent|nosound|novisual} : set the notification mode for the monitor given by its number - hl {on|off} [normal|silent|nosound|novisual] : activate/deactivate + hl {on|off} [normal|silent|nosound|novisual] : activate/deactivate monitoring of highlights and optionally set notification mode. tag add tagname [normal|silent|nosound|novisual] : monitor messages @@ -422,7 +422,7 @@ set notification mode. nick add nickname [normal|silent|nosound|novisual] : monitor messages from the specified nickname - + AFP $strmon_buffer = ""; @@ -447,7 +447,7 @@ sub strmon_buffer_input { my $cb_buffer=$_[1]; my $cb_data=$_[2]; - + #weechat::print($cb_buffer, $cb_data); $cb_data=~s/\s*$//; $cb_data=~s/^\s*//; @@ -470,10 +470,10 @@ sub strmon_buffer_input { # color strmon_color_command($args); - } elsif ($main eq 'notifo') + } elsif ($main eq 'nma') { - # notifo - strmon_notifo_command($args); + # nma + strmon_nma_command($args); } elsif ($main eq 'daemon') { # daemon @@ -495,7 +495,7 @@ sub strmon_buffer_input # filtertags strmon_filtertags_command($args); } elsif ($main eq 'filternicks') - { + { strmon_filternicks_command($args); } elsif ($main eq 'monitor') { @@ -568,52 +568,41 @@ sub strmon_color_command } -sub strmon_notifo_command +sub strmon_nma_command { my $args=shift @_; - my $usenotifo=weechat::config_get_plugin("usenotifo"); - my $user=weechat::config_get_plugin("notifo_user"); - my $secret=weechat::config_get_plugin("notifo_secret"); + my $usenma=weechat::config_get_plugin("usenma"); + my $apikey=weechat::config_get_plugin("nma_apikey"); $args=~/^(\S*)\s*(.*)$/; my $first=$1; my $second=$2; if ($first eq '') { # print usage - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Use of notifo is currently : $usenotifo"); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"Use of nma is currently : $usenma"); } elsif (($first eq 'on') || ($first eq 'off')) { - weechat::config_set_plugin('usenotifo',$first); - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Use of notifo set to : $first"); - } elsif ($first eq 'user') - { - if ($second eq '') - { - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Notifo user is : $user"); - } else - { - weechat::config_set_plugin('notifo_user',$second); - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Notifo user set to : $second"); - } - } elsif ($first eq 'secret') + weechat::config_set_plugin('usenma',$first); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"Use of nma set to : $first"); + } elsif ($first eq 'apikey') { if ($second eq '') { - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Notifo secret is : $secret"); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"nma apikey is : $apikey"); } else { - weechat::config_set_plugin('notifo_secret',$second); - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Notifo secret set to : $second"); + weechat::config_set_plugin('nma_apikey',$second); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"nma apikey set to : $second"); } } elsif ($first eq 'test') { - # test notifo + # test nma my $testdata='1 irc.#test Nickname : This is a test message'; - strmon_notifo_execute($testdata); - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Notifo test done"); + strmon_nma_execute($testdata); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"nma test done"); } else { - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Wrong argument for notifo command."); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"Wrong argument for nma command."); } return weechat::WEECHAT_RC_OK; @@ -642,17 +631,17 @@ sub strmon_daemon_command print $sock "daemon $first\n"; my $answer=$sock->getline(); $sock->shutdown(2); - my @liste=split(',',$answer); + my @liste=split(',',$answer); foreach (@liste) { weechat::print_date_tags($command_buffer,time,$strmon_tag,$_); - } + } } else { weechat::print_date_tags($command_buffer,time,$strmon_tag,"Problem contacting daemon"); } - - } elsif (($first eq 'play') || ($first eq 'show')) + + } elsif (($first eq 'play') || ($first eq 'show')) { if ($usedaemon ne 'on') { @@ -683,7 +672,7 @@ sub strmon_daemon_command my $nc=weechat::config_get_plugin('default_nick_color'); my $pi=weechat::config_get_plugin('default_picture'); my $so=weechat::config_get_plugin('default_sound'); - + if (my $sock = IO::Socket::INET->new(PeerAddr => 'localhost', PeerPort => $port+0, Proto => 'tcp')) @@ -708,9 +697,9 @@ sub strmon_daemon_command { weechat::print_date_tags($command_buffer,time,$strmon_tag,"Argument should be an integer"); } - + } elsif ( ($first eq '') || ($first eq 'on') || ($first eq 'off') ) - { + { if ($first eq '') { weechat::print_date_tags($command_buffer,time,$strmon_tag,"Use of daemon is currently : $usedaemon"); @@ -797,10 +786,10 @@ sub strmon_nick_command weechat::print_date_tags($command_buffer,time,$strmon_tag,"$nickname bgcolor : $bc\n$nickname fgcolor : $fc\n$nickname chanelcolor : $cc\n$nickname nickcolor : $nc"); } } - + } else { - # + # $args=~/^(\S+)\s*(.*)$/; my $first=$1; my $args=$2; @@ -834,7 +823,7 @@ sub strmon_nick_command if ($first eq 'test') { strmon_notify($cmode{$mo},$pi,$so,$bc,$fc,$cc,$nc,"1 irc.#test $nickname : This is a test message from $nickname"); - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Test notification from $nickname done"); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"Test notification from $nickname done"); } elsif ( ($first eq 'mode') && ($args ne '') ) { @@ -885,7 +874,7 @@ sub strmon_filtertags_command if ($args eq '') { my $tags=weechat::config_get_plugin('filtertags'); - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Currently filtered tags : $tags"); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"Currently filtered tags : $tags"); } elsif ($args=~/^list\s*(.*)$/) { $args=$1; @@ -916,7 +905,7 @@ sub strmon_filternicks_command if ($args eq '') { my $nicks=weechat::config_get_plugin('filternicks'); - weechat::print_date_tags($command_buffer,time,$strmon_tag,"Currently filtered nicks : $nicks"); + weechat::print_date_tags($command_buffer,time,$strmon_tag,"Currently filtered nicks : $nicks"); } elsif ($args=~/^list\s*(.*)$/) { $args=$1; @@ -951,7 +940,7 @@ sub strmon_monitor_command if ($args eq '') { # no arguments, just print the list - weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Monitor highlights is ".weechat::color('magenta').$hl.weechat::color('chat').", with notification mode ".weechat::color('green').$hlm.weechat::color("reset")); + weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Monitor highlights is ".weechat::color('magenta').$hl.weechat::color('chat').", with notification mode ".weechat::color('green').$hlm.weechat::color("reset")); weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."There are ".weechat::color('yellow').scalar(@taglist).weechat::color('chat')." tags monitored :".weechat::color("reset")); my $ntags=0; foreach (@taglist) @@ -1023,7 +1012,7 @@ sub strmon_monitor_command } } elsif ($second=~/\d+/) { - # + # if ( ($second > scalar(@taglist)) || ($second <= 0) ) { weechat::print_date_tags($command_buffer,time,$strmon_tag,"Given number does not match an existing tag"); @@ -1041,7 +1030,7 @@ sub strmon_monitor_command { $taglist[$second-1]="$t:$args"; weechat::config_set_plugin('monitortags',join(',',@taglist)); - weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Notification mode set to ".weechat::color('green').$m.weechat::color('chat')." for tag ".weechat::color('magenta').$t.weechat::color('reset')); + weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Notification mode set to ".weechat::color('green').$m.weechat::color('chat')." for tag ".weechat::color('magenta').$t.weechat::color('reset')); } } else { @@ -1079,7 +1068,7 @@ sub strmon_monitor_command } } elsif ($second=~/\d+/) { - # + # if ( ($second > scalar(@nicklist)) || ($second <= 0) ) { weechat::print_date_tags($command_buffer,time,$strmon_tag,"Given number does not match an existing nick"); @@ -1097,7 +1086,7 @@ sub strmon_monitor_command { $nicklist[$second-1]="$n:$args"; weechat::config_set_plugin('monitornicks',join(',',@nicklist)); - weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Notification mode set to ".weechat::color('green').$m.weechat::color('chat')." for nick ".weechat::color('magenta').$n.weechat::color('reset')); + weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Notification mode set to ".weechat::color('green').$m.weechat::color('chat')." for nick ".weechat::color('magenta').$n.weechat::color('reset')); } } else { @@ -1135,7 +1124,7 @@ sub strmon_monitor_command } } elsif ($second=~/\d+/) { - # + # if ( ($second > scalar(@buflist)) || ($second <= 0) ) { weechat::print_date_tags($command_buffer,time,$strmon_tag,"Given number does not match an existing monitored buffer"); @@ -1153,7 +1142,7 @@ sub strmon_monitor_command { $buflist[$second-1]="$b:$args"; weechat::config_set_plugin('monitorbuf',join(',',@buflist)); - weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Notification mode changed to ".weechat::color('green').$args.weechat::color('chat')." for ".weechat::color('magenta').$b.weechat::color('reset')); + weechat::print_date_tags($command_buffer,time,$strmon_tag,weechat::color('chat')."Notification mode changed to ".weechat::color('green').$args.weechat::color('chat')." for ".weechat::color('magenta').$b.weechat::color('reset')); } } else { @@ -1168,7 +1157,7 @@ sub strmon_monitor_command } else { weechat::print_date_tags($command_buffer,time,$strmon_tag,"Bad argument for monitor command"); - } + } } return weechat::WEECHAT_RC_OK; } @@ -1191,22 +1180,16 @@ sub strmon_buffer_open sub strmon_default_settings { # set default values -# use notifo -if (! weechat::config_is_set_plugin("usenotifo")) +# use nma +if (! weechat::config_is_set_plugin("usenma")) { - weechat::config_set_plugin("usenotifo","off"); + weechat::config_set_plugin("usenma","off"); } -# notifo user -if (! weechat::config_is_set_plugin("notifo_user")) +# nma apikey +if (! weechat::config_is_set_plugin("nma_apikey")) { - weechat::config_set_plugin("notifo_user","nouser"); - } - -# notifo secret -if (! weechat::config_is_set_plugin("notifo_secret")) - { - weechat::config_set_plugin("notifo_secret","nosecret"); + weechat::config_set_plugin("nma_apikey","nokey"); } # use daemon @@ -1308,11 +1291,11 @@ sub strmon_buffer_close return weechat::WEECHAT_RC_OK; } -sub strmon_notifo_execute +sub strmon_nma_execute { (my $data) = @_; my $nout=weechat::string_remove_color($data,""); - # do not notify unformatted messages (such as channel messages when monitoring + # do not notify unformatted messages (such as channel messages when monitoring # a buffer) return unless($nout=~/^(\d+)\s(\S+)\s(\S+)\s:\s(.*)$/); my $nchan=$1; @@ -1324,23 +1307,26 @@ sub strmon_notifo_execute my @fields; # Try to find an url in the message to send with the notification - my $url="http://www.google.com/"; + my $url=""; if ($msg=~/(https?:\/\/\S+)/) { $url=$1; - } + } - push @fields,"label=".uri_escape($chan); - push @fields,"msg=".uri_escape($msg); - push @fields,"title=".uri_escape($nick); - push @fields,"uri=".uri_escape($url); + my $apikey=weechat::config_get_plugin("nma_apikey"); + push @fields,"apikey=$apikey"; + push @fields,"application=Weechat"; + push @fields,"event=".uri_escape("Chan:$chan Nick:$nick"); + push @fields,"description=".uri_escape($msg); + if ($url ne "") + { + push @fields,"url=".uri_escape($url); + } my $pdata=join("&",@fields); $curl->setopt(CURLOPT_POSTFIELDS, $pdata); - $curl->setopt(CURLOPT_URL, 'https://api.notifo.com/v1/send_notification'); - my $credential=weechat::config_get_plugin("notifo_user").":".weechat::config_get_plugin("notifo_secret"); - $curl->setopt(CURLOPT_USERPWD,$credential); + $curl->setopt(CURLOPT_URL, 'https://www.notifymyandroid.com/publicapi/notify'); $curl->setopt(CURLOPT_SSL_VERIFYPEER,0); # redirect response into variable $response_body @@ -1354,14 +1340,24 @@ sub strmon_notifo_execute # Write an output in case of problems if ($retcode == 0) { # parse result - $response_body=~/^\{"status":"(.+)","response_code":(\d+),"response_message":"(.+)"\}/; - my $rstatus=$1; - my $rcode=$2; - my $rmsg=$3; + $response_body=~/^.+code=\"(\d+)\"/; + my $rcode=$1; + + if ($rcode==200) { + # Message sent do not write anything + + } elsif ($rcode==400) { + weechat::print_date_tags($command_buffer,time,$strmon_tag,"nma error 400 : The data supplied is in the wrong format, invalid length or null"); + } elsif ($rcode==401) { + weechat::print_date_tags($command_buffer,time,$strmon_tag,"nma error 401 : None of the API keys provided were valid."); + } elsif ($rcode==402) { + weechat::print_date_tags($command_buffer,time,$strmon_tag,"nma error 402 : Maximum number of API calls per hour exceeded."); + } elsif ($rcode==500) { + weechat::print_date_tags($command_buffer,time,$strmon_tag,"nma error 500 : Internal server error. Please contact our support if the problem persists."); + } else { + weechat::print_date_tags($command_buffer,time,$strmon_tag,"Unknown nma error code : $rcode"); + } - if ($rstatus ne 'success') { - weechat::print_date_tags($command_buffer,time,$strmon_tag,"A notifo error happened : Status = $rstatus Code = $rcode Msg = $rmsg"); - } } else { weechat::print_date_tags($command_buffer,time,$strmon_tag,"A curl error happened : ".$curl->strerror($retcode)." ($retcode)"); @@ -1376,8 +1372,8 @@ sub strmon_notify (my $mode, my $pic, my $sound, my $bg_color, my $fg_color, my $chan_color, my $nick_color, my $data) = @_; my $ret=0; my $usedaemon=weechat::config_get_plugin('usedaemon'); - my $usenotifo=weechat::config_get_plugin('usenotifo'); - + my $usenma=weechat::config_get_plugin('usenma'); + # Daemon notification if ($usedaemon eq 'on') { @@ -1393,10 +1389,10 @@ sub strmon_notify } } - # notifo notification - if ($usenotifo eq 'on') + # nma notification + if ($usenma eq 'on') { - strmon_notifo_execute($data); + strmon_nma_execute($data); $ret=1; } return $ret; @@ -1433,7 +1429,7 @@ sub strmon_event } } - # get a "clean" buffer name + # get a "clean" buffer name my $bufname = weechat::string_remove_color(weechat::buffer_get_string($cb_bufferp, 'name') ,""); # get a "clean" nick name @@ -1453,7 +1449,7 @@ sub strmon_event } # initialize pic - my $picture=weechat::config_get_plugin("default_picture"); + my $picture=weechat::config_get_plugin("default_picture"); # initialize sound my $sound=weechat::config_get_plugin("default_sound"); @@ -1507,7 +1503,7 @@ sub strmon_event weechat::print_date_tags($strmon_buffer,time,$strmon_tag, $outstr); $mode = $mode | $cmode{weechat::config_get_plugin("globalmode")} | $cmode{$hlm}; strmon_notify($mode,$picture,$sound,$bg_color,$fg_color,$chan_color,$nick_color,$outstr); - return weechat::WEECHAT_RC_OK; + return weechat::WEECHAT_RC_OK; } @@ -1555,9 +1551,3 @@ sub strmon_event return weechat::WEECHAT_RC_OK; } - - - - - - From c122f573bff2522012be012d4fb354db3660ed89 Mon Sep 17 00:00:00 2001 From: Stravy Date: Sat, 28 Oct 2017 08:47:03 +0200 Subject: [PATCH 082/642] strmon.pl 0.5.4: fix comment --- perl/strmon.pl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/perl/strmon.pl b/perl/strmon.pl index 11f093f0..e395d16b 100644 --- a/perl/strmon.pl +++ b/perl/strmon.pl @@ -22,7 +22,7 @@ use strict; use vars qw( %cmode $strmon_buffer $command_buffer $strmon_help $version $daemon_file $strmon_tag ); -$version = "0.5.3"; +$version = "0.5.4"; weechat::register( "strmon", "Stravy", $version, "GPL", "Messages monitoring and notifications", "", "" ); @@ -264,8 +264,7 @@ /strmon daemon [on|off] The script strmon_daemon.pl uses programs mplayer (http://www.mplayerhq.hu/) -and qnotify (http://www.homac.de/cgi-bin/qnotify/index.pl), which must be -installed on local machine. +and notify-send, which must be installed on local machine. strmon_daemon.pl also needs the following files to exist on local machine : \$HOME/.config/strmon_daemon/pics/default.png \$HOME/.config/strmon_daemon/sounds/default.ogg From 2b5296af445fe0599fb8679502a5038067ea837d Mon Sep 17 00:00:00 2001 From: massa1240 Date: Sat, 28 Oct 2017 08:52:16 +0200 Subject: [PATCH 083/642] emoji.lua 4: add support of :+1: and :-1: --- lua/emoji.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/emoji.lua b/lua/emoji.lua index 75d4b443..2fc52164 100644 --- a/lua/emoji.lua +++ b/lua/emoji.lua @@ -25,6 +25,8 @@ Usage: Changelog: + version 4, 2017-10-12, massa1240 + * add support of :+1: and :-1: version 3, 2016-11-04, xt * add a slack specific shortcode version 2, 2016-10-31, xt @@ -65,7 +67,7 @@ print "}" local SCRIPT_NAME = "emoji" local SCRIPT_AUTHOR = "xt " -local SCRIPT_VERSION = "3" +local SCRIPT_VERSION = "4" local SCRIPT_LICENSE = "GPL3" local SCRIPT_DESC = "Emoji output helper" @@ -76,10 +78,12 @@ local emoji = { four="4⃣",kiss_ww="👩❤💋👩",maple_leaf="🍁",waxing_gibbous_moon="🌔",bike="🚲",recycle="♻",family_mwgb="👨👩👧👦",flag_dk="🇩🇰",thought_balloon="💭",oncoming_automobile="🚘",guardsman_tone5="💂🏿",tickets="🎟",school="🏫",house_abandoned="🏚",blue_book="📘",video_game="🎮",triumph="😤",suspension_railway="🚟",umbrella="☔",levitate="🕴",cactus="🌵",monorail="🚝",stars="🌠",new="🆕",herb="🌿",pouting_cat="😾",blue_heart="💙",["100"]="💯",leaves="🍃",family_mwbb="👨👩👦👦",information_desk_person_tone2="💁🏼",dragon_face="🐲",track_next="⏭",cloud_snow="🌨",flag_jp="🇯🇵",children_crossing="🚸",information_desk_person_tone1="💁🏻",arrow_up_down="↕",mount_fuji="🗻",massage_tone1="💆🏻",flag_mq="🇲🇶",massage_tone3="💆🏽",massage_tone2="💆🏼",massage_tone5="💆🏿",flag_je="🇯🇪",flag_jm="🇯🇲",flag_jo="🇯🇴",red_car="🚗",hospital="🏥",red_circle="🔴",princess="👸",tm="™",curly_loop="➰",boy_tone5="👦🏿",pouch="👝",boy_tone3="👦🏽",boy_tone1="👦🏻",izakaya_lantern="🏮",fist_tone5="✊🏿",fist_tone4="✊🏾",fist_tone1="✊🏻",fist_tone3="✊🏽",fist_tone2="✊🏼",arrow_lower_left="↙",game_die="🎲",pushpin="📌",dividers="🗂",dolphin="🐬",night_with_stars="🌃",cruise_ship="🛳",white_medium_small_square="◽",kissing_closed_eyes="😚",earth_americas="🌎",["end"]="🔚",mouse="🐭",rewind="⏪",beach="🏖",pizza="🍕",briefcase="💼",customs="🛃",heartpulse="💗",sparkler="🎇",sparkles="✨",hand_splayed_tone1="🖐🏻",snowman2="☃",tulip="🌷",speaking_head="🗣",ambulance="🚑",office="🏢",clapper="🎬",keyboard="⌨",japan="🗾",post_office="🏣",dizzy_face="😵",imp="👿",flag_ve="🇻🇪",coffee="☕",flag_vg="🇻🇬",flag_va="🇻🇦",flag_vc="🇻🇨",flag_vn="🇻🇳",flag_vi="🇻🇮",open_mouth="😮",flag_vu="🇻🇺",page_with_curl="📃",bank="🏦",bread="🍞",oncoming_police_car="🚔",capricorn="♑",point_left="👈",tokyo_tower="🗼",fishing_pole_and_fish="🎣",thumbsdown="👎",telescope="🔭",spider="🕷",u7121="🈚",camera_with_flash="📸",lifter="🏋",sweet_potato="🍠",lock_with_ink_pen="🔏",ok_woman_tone2="🙆🏼",ok_woman_tone3="🙆🏽",smirk="😏",baggage_claim="🛄",cherry_blossom="🌸",sparkle="❇",zap="⚡",construction_site="🏗",dancers="👯",flower_playing_cards="🎴",hatching_chick="🐣",free="🆓",bullettrain_side="🚄",poultry_leg="🍗",grapes="🍇",smirk_cat="😼",lollipop="🍭",water_buffalo="🐃",black_medium_small_square="◾",atm="🏧",gift_heart="💝",older_woman_tone5="👵🏿",older_woman_tone4="👵🏾",older_woman_tone1="👵🏻",older_woman_tone3="👵🏽",older_woman_tone2="👵🏼",scissors="✂",woman_tone2="👩🏼",basketball="🏀",hammer_pick="⚒",top="🔝",clock630="🕡",raising_hand_tone5="🙋🏿",railway_track="🛤",nail_care="💅",crossed_flags="🎌",minibus="🚐",white_sun_cloud="🌥",shower="🚿",smile_cat="😸",dog2="🐕",loud_sound="🔊",kaaba="🕋",runner="🏃",ram="🐏",writing_hand="✍",rat="🐀",rice_scene="🎑",milky_way="🌌",vulcan_tone5="🖖🏿",necktie="👔",kissing_cat="😽",snowflake="❄",paintbrush="🖌",crystal_ball="🔮",mountain_bicyclist_tone4="🚵🏾",mountain_bicyclist_tone3="🚵🏽",mountain_bicyclist_tone2="🚵🏼",mountain_bicyclist_tone1="🚵🏻",koko="🈁",flag_it="🇮🇹",flag_iq="🇮🇶",flag_is="🇮🇸",flag_ir="🇮🇷",flag_im="🇮🇲",flag_il="🇮🇱",flag_io="🇮🇴",flag_in="🇮🇳",flag_ie="🇮🇪",flag_id="🇮🇩",flag_ic="🇮🇨",ballot_box_with_check="☑",mountain_bicyclist_tone5="🚵🏿",metal="🤘",dog="🐶",pineapple="🍍",no_good_tone3="🙅🏽",no_good_tone2="🙅🏼",no_good_tone1="🙅🏻",scream="😱",no_good_tone5="🙅🏿",no_good_tone4="🙅🏾",flag_ua="🇺🇦",bomb="💣",flag_ug="🇺🇬",flag_um="🇺🇲",flag_us="🇺🇸",construction_worker_tone1="👷🏻",radio="📻",flag_uy="🇺🇾",flag_uz="🇺🇿",person_with_blond_hair_tone1="👱🏻",cupid="💘",mens="🚹",rice="🍚",point_right_tone1="👉🏻",point_right_tone3="👉🏽",point_right_tone2="👉🏼",sunglasses="😎",point_right_tone4="👉🏾",watch="⌚",frowning="😦",watermelon="🍉",wedding="💒",person_frowning_tone4="🙍🏾",person_frowning_tone5="🙍🏿",person_frowning_tone2="🙍🏼",person_frowning_tone3="🙍🏽",person_frowning_tone1="🙍🏻",flag_gw="🇬🇼",flag_gu="🇬🇺",flag_gt="🇬🇹",flag_gs="🇬🇸",flag_gr="🇬🇷",flag_gq="🇬🇶",flag_gp="🇬🇵",flag_gy="🇬🇾",flag_gg="🇬🇬",flag_gf="🇬🇫",microscope="🔬",flag_gd="🇬🇩",flag_gb="🇬🇧",flag_ga="🇬🇦",flag_gn="🇬🇳",flag_gm="🇬🇲",flag_gl="🇬🇱",japanese_ogre="👹",flag_gi="🇬🇮",flag_gh="🇬🇭",man_with_turban="👳",star_and_crescent="☪",writing_hand_tone3="✍🏽",dromedary_camel="🐪",hash="#⃣",hammer="🔨",hourglass="⌛",postbox="📮",writing_hand_tone5="✍🏿",writing_hand_tone4="✍🏾",wc="🚾",aquarius="♒",couple_with_heart="💑",ok_woman="🙆",raised_hands_tone4="🙌🏾",cop="👮",raised_hands_tone1="🙌🏻",cow="🐮",raised_hands_tone3="🙌🏽",white_large_square="⬜",pig_nose="🐽",ice_skate="⛸",hotsprings="♨",tone5="🏿",three="3⃣",beer="🍺",stadium="🏟",airplane_departure="🛫",heavy_division_sign="➗",flag_black="🏴",mushroom="🍄",record_button="⏺",vulcan="🖖",dash="💨",wind_chime="🎐",anchor="⚓",seven="7⃣",flag_hr="🇭🇷",roller_coaster="🎢",pen_ballpoint="🖊",sushi="🍣",flag_ht="🇭🇹",flag_hu="🇭🇺",flag_hk="🇭🇰",dizzy="💫",flag_hn="🇭🇳",flag_hm="🇭🇲",arrow_forward="▶",violin="🎻",orthodox_cross="☦",id="🆔",heart_decoration="💟",first_quarter_moon="🌓",satellite="📡",tone3="🏽",christmas_tree="🎄",unicorn="🦄",broken_heart="💔",ocean="🌊",hearts="♥",snowman="⛄",person_with_blond_hair_tone4="👱🏾",person_with_blond_hair_tone5="👱🏿",person_with_blond_hair_tone2="👱🏼",person_with_blond_hair_tone3="👱🏽",yen="💴",straight_ruler="📏",sleepy="😪",green_apple="🍏",white_medium_square="◻",flag_fr="🇫🇷",grey_exclamation="❕",innocent="😇",flag_fm="🇫🇲",flag_fo="🇫🇴",flag_fi="🇫🇮",flag_fj="🇫🇯",flag_fk="🇫🇰",menorah="🕎",yin_yang="☯",clock130="🕜",gift="🎁",prayer_beads="📿",stuck_out_tongue="😛",om_symbol="🕉",city_dusk="🌆",massage_tone4="💆🏾",couple_ww="👩❤👩",crown="👑",sparkling_heart="💖",clubs="♣",person_with_pouting_face="🙎",newspaper2="🗞",fog="🌫",dango="🍡",large_orange_diamond="🔶",flag_tn="🇹🇳",flag_to="🇹🇴",point_up="☝",flag_tm="🇹🇲",flag_tj="🇹🇯",flag_tk="🇹🇰",flag_th="🇹🇭",flag_tf="🇹🇫",flag_tg="🇹🇬",corn="🌽",flag_tc="🇹🇨",flag_ta="🇹🇦",flag_tz="🇹🇿",flag_tv="🇹🇻",flag_tw="🇹🇼",flag_tt="🇹🇹",flag_tr="🇹🇷",eight_spoked_asterisk="✳",trophy="🏆",black_small_square="▪",o="⭕",no_bell="🔕",curry="🍛",alembic="⚗",sob="😭",waxing_crescent_moon="🌒",tiger2="🐅",two="2⃣",sos="🆘",compression="🗜",heavy_multiplication_x="✖",tennis="🎾",fireworks="🎆",skull_crossbones="☠",astonished="😲",congratulations="㊗",grey_question="❔",arrow_upper_left="↖",arrow_double_up="⏫",triangular_flag_on_post="🚩",gemini="♊",door="🚪",ship="🚢",point_down_tone3="👇🏽",point_down_tone4="👇🏾",point_down_tone5="👇🏿",movie_camera="🎥",ng="🆖",couple_mm="👨❤👨",football="🏈",asterisk="*⃣",taurus="♉",articulated_lorry="🚛",police_car="🚓",flushed="😳",spades="♠",cloud_lightning="🌩",wine_glass="🍷",clock830="🕣",punch_tone2="👊🏼",punch_tone3="👊🏽",punch_tone1="👊🏻",department_store="🏬",punch_tone4="👊🏾",punch_tone5="👊🏿",crocodile="🐊",white_square_button="🔳",hole="🕳",boy_tone2="👦🏼",mountain_cableway="🚠",melon="🍈",persevere="😣",trident="🔱",head_bandage="🤕",u7a7a="🈳",cool="🆒",high_brightness="🔆",deciduous_tree="🌳",white_flower="💮",gun="🔫",flag_sk="🇸🇰",flag_sj="🇸🇯",flag_si="🇸🇮",flag_sh="🇸🇭",flag_so="🇸🇴",flag_sn="🇸🇳",flag_sm="🇸🇲",flag_sl="🇸🇱",flag_sc="🇸🇨",flag_sb="🇸🇧",flag_sa="🇸🇦",flag_sg="🇸🇬",flag_tl="🇹🇱",flag_se="🇸🇪",arrow_left="⬅",flag_sz="🇸🇿",flag_sy="🇸🇾",small_orange_diamond="🔸",flag_ss="🇸🇸",flag_sr="🇸🇷",flag_sv="🇸🇻",flag_st="🇸🇹",file_folder="📁",flag_td="🇹🇩",["1234"]="🔢",smiling_imp="😈",surfer_tone2="🏄🏼",surfer_tone3="🏄🏽",surfer_tone4="🏄🏾",surfer_tone5="🏄🏿",amphora="🏺",baseball="⚾",boy="👦",flag_es="🇪🇸",raised_hands="🙌",flag_eu="🇪🇺",flag_et="🇪🇹",heavy_plus_sign="➕",bow="🙇",flag_ea="🇪🇦",flag_ec="🇪🇨",flag_ee="🇪🇪",light_rail="🚈",flag_eg="🇪🇬",flag_eh="🇪🇭",massage="💆",man_with_gua_pi_mao_tone4="👲🏾",man_with_gua_pi_mao_tone3="👲🏽",outbox_tray="📤",clock330="🕞",projector="📽",sake="🍶",confounded="😖",angry="😠",iphone="📱",sweat_smile="😅",aries="♈",ear_of_rice="🌾",mouse2="🐁",bicyclist_tone4="🚴🏾",bicyclist_tone5="🚴🏿",guardsman="💂",bicyclist_tone1="🚴🏻",bicyclist_tone2="🚴🏼",bicyclist_tone3="🚴🏽",envelope="✉",money_with_wings="💸",beers="🍻",heart_exclamation="❣",notepad_spiral="🗒",cat="🐱",running_shirt_with_sash="🎽",ferry="⛴",spy="🕵",chart_with_upwards_trend="📈",green_heart="💚",confused="😕",angel_tone4="👼🏾",scorpius="♏",sailboat="⛵",elephant="🐘",map="🗺",disappointed_relieved="😥",flag_xk="🇽🇰",motorway="🛣",sun_with_face="🌞",birthday="🎂",mag="🔍",date="📅",dove="🕊",man="👨",octopus="🐙",wheelchair="♿",truck="🚚",sa="🈂",shield="🛡",haircut="💇",last_quarter_moon_with_face="🌜",rosette="🏵",currency_exchange="💱",mailbox_with_no_mail="📭",bath="🛀",clock930="🕤",bowling="🎳",turtle="🐢",pause_button="⏸",construction_worker="👷",unlock="🔓",anger_right="🗯",beetle="🐞",girl="👧",sunrise="🌅",exclamation="❗",flag_dz="🇩🇿",family_mmgg="👨👨👧👧",factory="🏭",flag_do="🇩🇴",flag_dm="🇩🇲",flag_dj="🇩🇯",mouse_three_button="🖱",flag_dg="🇩🇬",flag_de="🇩🇪",star_of_david="✡",reminder_ribbon="🎗",grimacing="😬",thumbsup_tone3="👍🏽",thumbsup_tone2="👍🏼",thumbsup_tone1="👍🏻",musical_note="🎵",thumbsup_tone5="👍🏿",thumbsup_tone4="👍🏾",high_heel="👠",green_book="📗",headphones="🎧",flag_aw="🇦🇼",stop_button="⏹",yum="😋",flag_aq="🇦🇶",warning="⚠",cheese="🧀",ophiuchus="⛎",revolving_hearts="💞",one="1⃣",ring="💍",point_right="👉",sheep="🐑",bookmark="🔖",spider_web="🕸",eyes="👀",flag_ro="🇷🇴",flag_re="🇷🇪",flag_rs="🇷🇸",sweat_drops="💦",flag_ru="🇷🇺",flag_rw="🇷🇼",middle_finger="🖕",race_car="🏎",evergreen_tree="🌲",biohazard="☣",girl_tone3="👧🏽",scream_cat="🙀",computer="💻",hourglass_flowing_sand="⏳",flag_lb="🇱🇧",tophat="🎩",clock1230="🕧",tractor="🚜",u6709="🈶",u6708="🈷",crying_cat_face="😿",angel="👼",ant="🐜",information_desk_person="💁",anger="💢",mailbox_with_mail="📬",pencil2="✏",wink="😉",thermometer="🌡",relaxed="☺",printer="🖨",credit_card="💳",checkered_flag="🏁",family_mmg="👨👨👧",pager="📟",family_mmb="👨👨👦",radioactive="☢",fried_shrimp="🍤",link="🔗",walking="🚶",city_sunset="🌇",shopping_bags="🛍",hockey="🏒",arrow_up="⬆",gem="💎",negative_squared_cross_mark="❎",worried="😟",walking_tone5="🚶🏿",walking_tone1="🚶🏻",hear_no_evil="🙉",convenience_store="🏪",seat="💺",girl_tone1="👧🏻",cloud_rain="🌧",girl_tone2="👧🏼",girl_tone5="👧🏿",girl_tone4="👧🏾",parking="🅿",pisces="♓",calendar="📆",loudspeaker="📢",camping="🏕",bicyclist="🚴",label="🏷",diamonds="♦",older_man_tone1="👴🏻",older_man_tone3="👴🏽",older_man_tone2="👴🏼",older_man_tone5="👴🏿",older_man_tone4="👴🏾",microphone2="🎙",raising_hand="🙋",hot_pepper="🌶",guitar="🎸",tropical_drink="🍹",upside_down="🙃",restroom="🚻",pen_fountain="🖋",comet="☄",cancer="♋",jeans="👖",flag_qa="🇶🇦",boar="🐗",turkey="🦃",person_with_blond_hair="👱",oden="🍢",stuck_out_tongue_closed_eyes="😝",helicopter="🚁",control_knobs="🎛",performing_arts="🎭",tiger="🐯",foggy="🌁",sound="🔉",flag_cz="🇨🇿",flag_cy="🇨🇾",flag_cx="🇨🇽",speech_balloon="💬",seedling="🌱",flag_cr="🇨🇷",envelope_with_arrow="📩",flag_cp="🇨🇵",flag_cw="🇨🇼",flag_cv="🇨🇻",flag_cu="🇨🇺",flag_ck="🇨🇰",flag_ci="🇨🇮",flag_ch="🇨🇭",flag_co="🇨🇴",flag_cn="🇨🇳",flag_cm="🇨🇲",u5408="🈴",flag_cc="🇨🇨",flag_ca="🇨🇦",flag_cg="🇨🇬",flag_cf="🇨🇫",flag_cd="🇨🇩",purse="👛",telephone="☎",sleeping="😴",point_down_tone1="👇🏻",frowning2="☹",point_down_tone2="👇🏼",muscle_tone4="💪🏾",muscle_tone5="💪🏿",synagogue="🕍",muscle_tone1="💪🏻",muscle_tone2="💪🏼",muscle_tone3="💪🏽",clap_tone5="👏🏿",clap_tone4="👏🏾",clap_tone1="👏🏻",train2="🚆",clap_tone2="👏🏼",oil="🛢",diamond_shape_with_a_dot_inside="💠",barber="💈",metal_tone3="🤘🏽",ice_cream="🍨",rowboat_tone4="🚣🏾",burrito="🌯",metal_tone1="🤘🏻",joystick="🕹",rowboat_tone1="🚣🏻",taxi="🚕",u7533="🈸",racehorse="🐎",snowboarder="🏂",thinking="🤔",wave_tone1="👋🏻",wave_tone2="👋🏼",wave_tone3="👋🏽",wave_tone4="👋🏾",wave_tone5="👋🏿",desktop="🖥",stopwatch="⏱",pill="💊",skier="⛷",orange_book="📙",dart="🎯",disappointed="😞",grin="😁",place_of_worship="🛐",japanese_goblin="👺",arrows_counterclockwise="🔄",laughing="😆",clap="👏",left_right_arrow="↔",japanese_castle="🏯",nail_care_tone4="💅🏾",nail_care_tone5="💅🏿",nail_care_tone2="💅🏼",nail_care_tone3="💅🏽",nail_care_tone1="💅🏻",raised_hand_tone4="✋🏾",raised_hand_tone5="✋🏿",raised_hand_tone1="✋🏻",raised_hand_tone2="✋🏼",raised_hand_tone3="✋🏽",point_left_tone3="👈🏽",point_left_tone2="👈🏼",tanabata_tree="🎋",point_left_tone5="👈🏿",point_left_tone4="👈🏾",o2="🅾",knife="🔪",volcano="🌋",kissing_heart="😘",on="🔛",ok="🆗",package="📦",island="🏝",arrow_right="➡",chart_with_downwards_trend="📉",haircut_tone3="💇🏽",wolf="🐺",ox="🐂",dagger="🗡",full_moon_with_face="🌝",syringe="💉",flag_by="🇧🇾",flag_bz="🇧🇿",flag_bq="🇧🇶",flag_br="🇧🇷",flag_bs="🇧🇸",flag_bt="🇧🇹",flag_bv="🇧🇻",flag_bw="🇧🇼",flag_bh="🇧🇭",flag_bi="🇧🇮",flag_bj="🇧🇯",flag_bl="🇧🇱",flag_bm="🇧🇲",flag_bn="🇧🇳",flag_bo="🇧🇴",flag_ba="🇧🇦",flag_bb="🇧🇧",flag_bd="🇧🇩",flag_be="🇧🇪",flag_bf="🇧🇫",flag_bg="🇧🇬",satellite_orbital="🛰",radio_button="🔘",arrow_heading_down="⤵",rage="😡",whale2="🐋",vhs="📼",hand_splayed_tone3="🖐🏽",strawberry="🍓",["non-potable_water"]="🚱",hand_splayed_tone5="🖐🏿",star2="🌟",toilet="🚽",ab="🆎",cinema="🎦",floppy_disk="💾",princess_tone4="👸🏾",princess_tone5="👸🏿",princess_tone2="👸🏼",nerd="🤓",telephone_receiver="📞",princess_tone1="👸🏻",arrow_double_down="⏬",clock1030="🕥",flag_pr="🇵🇷",flag_ps="🇵🇸",poop="💩",flag_pw="🇵🇼",flag_pt="🇵🇹",flag_py="🇵🇾",pear="🍐",m="Ⓜ",flag_pa="🇵🇦",flag_pf="🇵🇫",flag_pg="🇵🇬",flag_pe="🇵🇪",flag_pk="🇵🇰",flag_ph="🇵🇭",flag_pn="🇵🇳",flag_pl="🇵🇱",flag_pm="🇵🇲",mask="😷",hushed="😯",sunrise_over_mountains="🌄",partly_sunny="⛅",dollar="💵",helmet_with_cross="⛑",smoking="🚬",no_bicycles="🚳",man_with_gua_pi_mao="👲",tv="📺",open_hands="👐",rotating_light="🚨",information_desk_person_tone4="💁🏾",information_desk_person_tone5="💁🏿",part_alternation_mark="〽",pray_tone5="🙏🏿",pray_tone4="🙏🏾",pray_tone3="🙏🏽",pray_tone2="🙏🏼",pray_tone1="🙏🏻",smile="😄",large_blue_circle="🔵",man_tone4="👨🏾",man_tone5="👨🏿",fax="📠",woman="👩",man_tone1="👨🏻",man_tone2="👨🏼",man_tone3="👨🏽",eye_in_speech_bubble="👁🗨",blowfish="🐡",card_box="🗃",ticket="🎫",ramen="🍜",twisted_rightwards_arrows="🔀",swimmer_tone4="🏊🏾",swimmer_tone5="🏊🏿",swimmer_tone1="🏊🏻",swimmer_tone2="🏊🏼",swimmer_tone3="🏊🏽",saxophone="🎷",bath_tone1="🛀🏻",notebook_with_decorative_cover="📔",bath_tone3="🛀🏽",ten="🔟",raising_hand_tone4="🙋🏾",tea="🍵",raising_hand_tone1="🙋🏻",raising_hand_tone2="🙋🏼",raising_hand_tone3="🙋🏽",zero="0⃣",ribbon="🎀",santa_tone1="🎅🏻",abc="🔤",clock="🕰",purple_heart="💜",bow_tone1="🙇🏻",no_smoking="🚭",flag_cl="🇨🇱",surfer="🏄",newspaper="📰",busstop="🚏",new_moon="🌑",traffic_light="🚥",thumbsup="👍",no_entry="⛔",name_badge="📛",classical_building="🏛",hamster="🐹",pick="⛏",two_women_holding_hands="👭",family_mmbb="👨👨👦👦",family="👪",rice_cracker="🍘",wind_blowing_face="🌬",inbox_tray="📥",tired_face="😫",carousel_horse="🎠",eye="👁",poodle="🐩",chestnut="🌰",slight_smile="🙂",mailbox_closed="📪",cloud_tornado="🌪",jack_o_lantern="🎃",lifter_tone3="🏋🏽",lifter_tone2="🏋🏼",lifter_tone1="🏋🏻",lifter_tone5="🏋🏿",lifter_tone4="🏋🏾",nine="9⃣",chocolate_bar="🍫",v="✌",man_with_turban_tone4="👳🏾",man_with_turban_tone5="👳🏿",man_with_turban_tone2="👳🏼",man_with_turban_tone3="👳🏽",man_with_turban_tone1="👳🏻",family_wwbb="👩👩👦👦",hamburger="🍔",accept="🉑",airplane="✈",dress="👗",speedboat="🚤",ledger="📒",goat="🐐",flag_ae="🇦🇪",flag_ad="🇦🇩",flag_ag="🇦🇬",flag_af="🇦🇫",flag_ac="🇦🇨",flag_am="🇦🇲",flag_al="🇦🇱",flag_ao="🇦🇴",flag_ai="🇦🇮",flag_au="🇦🇺",flag_at="🇦🇹",fork_and_knife="🍴",fast_forward="⏩",flag_as="🇦🇸",flag_ar="🇦🇷",cow2="🐄",flag_ax="🇦🇽",flag_az="🇦🇿",a="🅰",volleyball="🏐",dragon="🐉",wrench="🔧",point_up_2="👆",egg="🍳",small_red_triangle="🔺",soon="🔜",bow_tone4="🙇🏾",joy_cat="😹",pray="🙏",dark_sunglasses="🕶",rugby_football="🏉",soccer="⚽",dolls="🎎",monkey_face="🐵",clap_tone3="👏🏽",bar_chart="📊",european_castle="🏰",military_medal="🎖",frame_photo="🖼",rice_ball="🍙",trolleybus="🚎",older_woman="👵",information_source="ℹ",postal_horn="📯",house="🏠",fish="🐟",bride_with_veil="👰",fist="✊",lipstick="💄",fountain="⛲",cyclone="🌀",thumbsdown_tone2="👎🏼",thumbsdown_tone3="👎🏽",thumbsdown_tone1="👎🏻",thumbsdown_tone4="👎🏾",thumbsdown_tone5="👎🏿",cookie="🍪",heartbeat="💓",blush="😊",fire_engine="🚒",feet="🐾",horse="🐴",blossom="🌼",crossed_swords="⚔",station="🚉",clock730="🕢",banana="🍌",relieved="😌",hotel="🏨",park="🏞",aerial_tramway="🚡",flag_sd="🇸🇩",panda_face="🐼",b="🅱",flag_sx="🇸🇽",six_pointed_star="🔯",shaved_ice="🍧",chipmunk="🐿",mountain="⛰",koala="🐨",white_small_square="▫",open_hands_tone2="👐🏼",open_hands_tone3="👐🏽",u55b6="🈺",open_hands_tone1="👐🏻",open_hands_tone4="👐🏾",open_hands_tone5="👐🏿",baby_tone5="👶🏿",baby_tone4="👶🏾",baby_tone3="👶🏽",baby_tone2="👶🏼",baby_tone1="👶🏻",chart="💹",beach_umbrella="⛱",basketball_player_tone5="⛹🏿",basketball_player_tone4="⛹🏾",basketball_player_tone1="⛹🏻",basketball_player_tone3="⛹🏽",basketball_player_tone2="⛹🏼",mans_shoe="👞",shinto_shrine="⛩",ideograph_advantage="🉐",airplane_arriving="🛬",golf="⛳",minidisc="💽",hugging="🤗",crayon="🖍",point_down="👇",copyright="©",person_with_pouting_face_tone2="🙎🏼",person_with_pouting_face_tone3="🙎🏽",person_with_pouting_face_tone1="🙎🏻",person_with_pouting_face_tone4="🙎🏾",person_with_pouting_face_tone5="🙎🏿",busts_in_silhouette="👥",alarm_clock="⏰",couplekiss="💏",circus_tent="🎪",sunny="☀",incoming_envelope="📨",yellow_heart="💛",cry="😢",x="❌",arrow_up_small="🔼",art="🎨",surfer_tone1="🏄🏻",bride_with_veil_tone4="👰🏾",bride_with_veil_tone5="👰🏿",bride_with_veil_tone2="👰🏼",bride_with_veil_tone3="👰🏽",bride_with_veil_tone1="👰🏻",hibiscus="🌺",black_joker="🃏",raised_hand="✋",no_mouth="😶",basketball_player="⛹",champagne="🍾",no_entry_sign="🚫",older_man="👴",moyai="🗿",mailbox="📫",slight_frown="🙁",statue_of_liberty="🗽",mega="📣",eggplant="🍆",rose="🌹",bell="🔔",battery="🔋",wastebasket="🗑",dancer="💃",page_facing_up="📄",church="⛪",underage="🔞",secret="㊙",clock430="🕟",fork_knife_plate="🍽",u7981="🈲",fire="🔥",cold_sweat="😰",flag_er="🇪🇷",family_mwgg="👨👩👧👧",heart_eyes="😍",guardsman_tone1="💂🏻",guardsman_tone2="💂🏼",guardsman_tone3="💂🏽",guardsman_tone4="💂🏾",earth_africa="🌍",arrow_right_hook="↪",spy_tone2="🕵🏼",closed_umbrella="🌂",bikini="👙",vertical_traffic_light="🚦",kissing="😗",loop="➿",potable_water="🚰",pound="💷",["fleur-de-lis"]="⚜",key2="🗝",heavy_dollar_sign="💲",shamrock="☘",boy_tone4="👦🏾",shirt="👕",kimono="👘",left_luggage="🛅",meat_on_bone="🍖",ok_woman_tone4="🙆🏾",ok_woman_tone5="🙆🏿",arrow_heading_up="⤴",calendar_spiral="🗓",snail="🐌",ok_woman_tone1="🙆🏻",arrow_down_small="🔽",leopard="🐆",paperclips="🖇",cityscape="🏙",woman_tone1="👩🏻",slot_machine="🎰",woman_tone3="👩🏽",woman_tone4="👩🏾",woman_tone5="👩🏿",euro="💶",musical_score="🎼",triangular_ruler="📐",flags="🎏",five="5⃣",love_hotel="🏩",hotdog="🌭",speak_no_evil="🙊",eyeglasses="👓",dancer_tone4="💃🏾",dancer_tone5="💃🏿",vulcan_tone4="🖖🏾",bridge_at_night="🌉",writing_hand_tone1="✍🏻",couch="🛋",vulcan_tone1="🖖🏻",vulcan_tone2="🖖🏼",vulcan_tone3="🖖🏽",womans_hat="👒",sandal="👡",cherries="🍒",full_moon="🌕",flag_om="🇴🇲",play_pause="⏯",couple="👫",money_mouth="🤑",womans_clothes="👚",globe_with_meridians="🌐",bath_tone5="🛀🏿",bangbang="‼",stuck_out_tongue_winking_eye="😜",heart="❤",bamboo="🎍",mahjong="🀄",waning_gibbous_moon="🌖",back="🔙",point_up_2_tone4="👆🏾",point_up_2_tone5="👆🏿",lips="👄",point_up_2_tone1="👆🏻",point_up_2_tone2="👆🏼",point_up_2_tone3="👆🏽",candle="🕯",middle_finger_tone3="🖕🏽",middle_finger_tone2="🖕🏼",middle_finger_tone1="🖕🏻",middle_finger_tone5="🖕🏿",middle_finger_tone4="🖕🏾",heavy_minus_sign="➖",nose="👃",zzz="💤",stew="🍲",santa="🎅",tropical_fish="🐠",point_up_tone1="☝🏻",point_up_tone3="☝🏽",point_up_tone2="☝🏼",point_up_tone5="☝🏿",point_up_tone4="☝🏾",field_hockey="🏑",school_satchel="🎒",womens="🚺",baby_symbol="🚼",baby_chick="🐤",ok_hand_tone2="👌🏼",ok_hand_tone3="👌🏽",ok_hand_tone1="👌🏻",ok_hand_tone4="👌🏾",ok_hand_tone5="👌🏿",family_mmgb="👨👨👧👦",last_quarter_moon="🌗",tada="🎉",clock530="🕠",question="❓",registered="®",level_slider="🎚",black_circle="⚫",atom="⚛",penguin="🐧",electric_plug="🔌",skull="💀",kiss_mm="👨❤💋👨",walking_tone4="🚶🏾",fries="🍟",up="🆙",walking_tone3="🚶🏽",walking_tone2="🚶🏼",athletic_shoe="👟",hatched_chick="🐥",black_nib="✒",black_large_square="⬛",bow_and_arrow="🏹",rainbow="🌈",metal_tone5="🤘🏿",metal_tone4="🤘🏾",lemon="🍋",metal_tone2="🤘🏼",peach="🍑",peace="☮",steam_locomotive="🚂",oncoming_bus="🚍",heart_eyes_cat="😻",smiley="😃",haircut_tone1="💇🏻",haircut_tone2="💇🏼",u6e80="🈵",haircut_tone4="💇🏾",haircut_tone5="💇🏿",black_medium_square="◼",closed_book="📕",desert="🏜",expressionless="😑",dvd="📀",construction_worker_tone2="👷🏼",construction_worker_tone3="👷🏽",construction_worker_tone4="👷🏾",construction_worker_tone5="👷🏿",mag_right="🔎",bento="🍱",scroll="📜",flag_nl="🇳🇱",flag_no="🇳🇴",flag_ni="🇳🇮",european_post_office="🏤",flag_ne="🇳🇪",flag_nf="🇳🇫",flag_ng="🇳🇬",flag_na="🇳🇦",flag_nc="🇳🇨",alien="👽",first_quarter_moon_with_face="🌛",flag_nz="🇳🇿",flag_nu="🇳🇺",golfer="🏌",flag_np="🇳🇵",flag_nr="🇳🇷",anguished="😧",mosque="🕌",point_left_tone1="👈🏻",ear_tone1="👂🏻",ear_tone2="👂🏼",ear_tone3="👂🏽",ear_tone4="👂🏾",ear_tone5="👂🏿",eight_pointed_black_star="✴",wave="👋",runner_tone5="🏃🏿",runner_tone4="🏃🏾",runner_tone3="🏃🏽",runner_tone2="🏃🏼",runner_tone1="🏃🏻",railway_car="🚃",notes="🎶",no_good="🙅",trackball="🖲",spaghetti="🍝",love_letter="💌",clipboard="📋",baby_bottle="🍼",bird="🐦",card_index="📇",punch="👊",leo="♌",house_with_garden="🏡",family_wwgg="👩👩👧👧",family_wwgb="👩👩👧👦",see_no_evil="🙈",metro="🚇",popcorn="🍿",apple="🍎",scales="⚖",sleeping_accommodation="🛌",clock230="🕝",tools="🛠",cloud="☁",honey_pot="🍯",ballot_box="🗳",frog="🐸",camera="📷",crab="🦀",video_camera="📹",pencil="📝",thunder_cloud_rain="⛈",mountain_bicyclist="🚵",tangerine="🍊",train="🚋",rabbit="🐰",baby="👶",palm_tree="🌴",capital_abcd="🔠",put_litter_in_its_place="🚮",coffin="⚰",abcd="🔡",lock="🔒",pig2="🐖",family_mwg="👨👩👧",point_right_tone5="👉🏿",trumpet="🎺",film_frames="🎞",six="6⃣",leftwards_arrow_with_hook="↩",earth_asia="🌏",heavy_check_mark="✔",notebook="📓",taco="🌮",tomato="🍅",robot="🤖",mute="🔇",symbols="🔣",motorcycle="🏍",thermometer_face="🤒",paperclip="📎",moneybag="💰",neutral_face="😐",white_sun_rain_cloud="🌦",snake="🐍",kiss="💋",blue_car="🚙",confetti_ball="🎊",tram="🚊",repeat_one="🔂",smiley_cat="😺",beginner="🔰",mobile_phone_off="📴",books="📚",["8ball"]="🎱",cocktail="🍸",flag_ge="🇬🇪",horse_racing_tone2="🏇🏼",flag_mh="🇲🇭",flag_mk="🇲🇰",flag_mm="🇲🇲",flag_ml="🇲🇱",flag_mo="🇲🇴",flag_mn="🇲🇳",flag_ma="🇲🇦",flag_mc="🇲🇨",flag_me="🇲🇪",flag_md="🇲🇩",flag_mg="🇲🇬",flag_mf="🇲🇫",flag_my="🇲🇾",flag_mx="🇲🇽",flag_mz="🇲🇿",mountain_snow="🏔",flag_mp="🇲🇵",flag_ms="🇲🇸",flag_mr="🇲🇷",flag_mu="🇲🇺",flag_mt="🇲🇹",flag_mw="🇲🇼",flag_mv="🇲🇻",timer="⏲",passport_control="🛂",small_blue_diamond="🔹",lion_face="🦁",white_check_mark="✅",bouquet="💐",track_previous="⏮",monkey="🐒",tone4="🏾",closed_lock_with_key="🔐",family_wwb="👩👩👦",family_wwg="👩👩👧",tone1="🏻",crescent_moon="🌙",shell="🐚",gear="⚙",tone2="🏼",small_red_triangle_down="🔻",nut_and_bolt="🔩",umbrella2="☂",unamused="😒",fuelpump="⛽",bed="🛏",bee="🐝",round_pushpin="📍",flag_white="🏳",microphone="🎤",bus="🚌",eight="8⃣",handbag="👜",medal="🏅",arrows_clockwise="🔃",urn="⚱",bookmark_tabs="📑",new_moon_with_face="🌚",fallen_leaf="🍂",horse_racing="🏇",chicken="🐔",ear="👂",wheel_of_dharma="☸",arrow_lower_right="↘",man_with_gua_pi_mao_tone5="👲🏿",scorpion="🦂",waning_crescent_moon="🌘",man_with_gua_pi_mao_tone2="👲🏼",man_with_gua_pi_mao_tone1="👲🏻",bug="🐛",virgo="♍",libra="♎",angel_tone1="👼🏻",angel_tone3="👼🏽",angel_tone2="👼🏼",angel_tone5="👼🏿",sagittarius="♐",bear="🐻",information_desk_person_tone3="💁🏽",no_mobile_phones="📵",hand_splayed="🖐",motorboat="🛥",calling="📲",interrobang="⁉",oncoming_taxi="🚖",flag_lt="🇱🇹",flag_lu="🇱🇺",flag_lr="🇱🇷",flag_ls="🇱🇸",flag_ly="🇱🇾",bellhop="🛎",arrow_down="⬇",flag_lc="🇱🇨",flag_la="🇱🇦",flag_lk="🇱🇰",flag_li="🇱🇮",ferris_wheel="🎡",hand_splayed_tone2="🖐🏼",large_blue_diamond="🔷",cat2="🐈",icecream="🍦",tent="⛺",joy="😂",hand_splayed_tone4="🖐🏾",file_cabinet="🗄",key="🔑",weary="😩",bath_tone2="🛀🏼",flag_lv="🇱🇻",low_brightness="🔅",rowboat_tone5="🚣🏿",rowboat_tone2="🚣🏼",rowboat_tone3="🚣🏽",four_leaf_clover="🍀",space_invader="👾",cl="🆑",cd="💿",bath_tone4="🛀🏾",flag_za="🇿🇦",swimmer="🏊",wavy_dash="〰",flag_zm="🇿🇲",flag_zw="🇿🇼",raised_hands_tone5="🙌🏿",two_hearts="💕",bulb="💡",cop_tone4="👮🏾",cop_tone5="👮🏿",cop_tone2="👮🏼",cop_tone3="👮🏽",cop_tone1="👮🏻",open_file_folder="📂",homes="🏘",raised_hands_tone2="🙌🏼",fearful="😨",grinning="😀",bow_tone5="🙇🏿",santa_tone3="🎅🏽",santa_tone2="🎅🏼",santa_tone5="🎅🏿",santa_tone4="🎅🏾",bow_tone2="🙇🏼",bow_tone3="🙇🏽",bathtub="🛁",ping_pong="🏓",u5272="🈹",rooster="🐓",vs="🆚",bullettrain_front="🚅",airplane_small="🛩",white_circle="⚪",balloon="🎈",cross="✝",princess_tone3="👸🏽",speaker="🔈",zipper_mouth="🤐",u6307="🈯",whale="🐳",pensive="😔",signal_strength="📶",muscle="💪",rocket="🚀",camel="🐫",boot="👢",flashlight="🔦",spy_tone4="🕵🏾",spy_tone5="🕵🏿",ski="🎿",spy_tone3="🕵🏽",musical_keyboard="🎹",spy_tone1="🕵🏻",rolling_eyes="🙄",clock1="🕐",clock2="🕑",clock3="🕒",clock4="🕓",clock5="🕔",clock6="🕕",clock7="🕖",clock8="🕗",clock9="🕘",doughnut="🍩",dancer_tone1="💃🏻",dancer_tone2="💃🏼",dancer_tone3="💃🏽",candy="🍬",two_men_holding_hands="👬",badminton="🏸",bust_in_silhouette="👤",writing_hand_tone2="✍🏼",sunflower="🌻",["e-mail"]="📧",chains="⛓",kissing_smiling_eyes="😙",fish_cake="🍥",no_pedestrians="🚷",v_tone4="✌🏾",v_tone5="✌🏿",v_tone1="✌🏻",v_tone2="✌🏼",v_tone3="✌🏽",arrow_backward="◀",clock12="🕛",clock10="🕙",clock11="🕚",sweat="😓",mountain_railway="🚞",tongue="👅",black_square_button="🔲",do_not_litter="🚯",nose_tone4="👃🏾",nose_tone5="👃🏿",nose_tone2="👃🏼",nose_tone3="👃🏽",nose_tone1="👃🏻",horse_racing_tone5="🏇🏿",horse_racing_tone4="🏇🏾",horse_racing_tone3="🏇🏽",ok_hand="👌",horse_racing_tone1="🏇🏻",custard="🍮",rowboat="🚣",white_sun_small_cloud="🌤",flag_kr="🇰🇷",cricket="🏏",flag_kp="🇰🇵",flag_kw="🇰🇼",flag_kz="🇰🇿",flag_ky="🇰🇾",construction="🚧",flag_kg="🇰🇬",flag_ke="🇰🇪",flag_ki="🇰🇮",flag_kh="🇰🇭",flag_kn="🇰🇳",flag_km="🇰🇲",cake="🍰",flag_wf="🇼🇫",mortar_board="🎓",pig="🐷",flag_ws="🇼🇸",person_frowning="🙍",arrow_upper_right="↗",book="📖",clock1130="🕦",boom="💥",["repeat"]="🔁",star="⭐",rabbit2="🐇",footprints="👣",ghost="👻",droplet="💧",vibration_mode="📳",flag_ye="🇾🇪",flag_yt="🇾🇹", } emoji['slightly_smiling_face'] = ':)' +emoji['+1'] = "👍" +emoji['-1'] = "👎" local function str2emoji(str) if not str then return '' end - return (str:gsub(':[a-zA-Z0-9%-_]+:', function(word) + return (str:gsub(':[a-zA-Z0-9%-_+]+:', function(word) return emoji[word:match(':(.+):')] or word end)) end From 5943c913bd17422d22f31598b1a9eac6374516d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sat, 28 Oct 2017 08:57:15 +0200 Subject: [PATCH 084/642] infolist.py 0.6: evaluate arguments of /infolist command --- python/infolist.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/python/infolist.py b/python/infolist.py index 471f63c7..5c57ecdf 100644 --- a/python/infolist.py +++ b/python/infolist.py @@ -18,6 +18,8 @@ # Display infolist in a buffer. # # History: +# 2017-10-22, nils_2 : +# version 0.6: add string_eval_expression() # 2012-10-02, nils_2 : # version 0.5: switch to infolist buffer (if exists) when command /infolist # is called with arguments, add some examples to help page @@ -35,7 +37,7 @@ SCRIPT_NAME = "infolist" SCRIPT_AUTHOR = "Sebastien Helleu " -SCRIPT_VERSION = "0.5" +SCRIPT_VERSION = "0.6" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Display infolist in a buffer" @@ -165,6 +167,8 @@ def infolist_buffer_new(): def infolist_cmd(data, buffer, args): global infolist_buffer + args = string_eval_expression(args) + if infolist_buffer == "": infolist_buffer_new() if infolist_buffer != "" and args != "": @@ -173,6 +177,9 @@ def infolist_cmd(data, buffer, args): return weechat.WEECHAT_RC_OK +def string_eval_expression(string): + return weechat.string_eval_expression(string,{},{},{}) + if __name__ == "__main__" and import_ok: if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): @@ -190,6 +197,8 @@ def infolist_cmd(data, buffer, args): " Show information about nick \"FlashCode\" in channel \"#weechat\" on server \"freenode\":\n" " /infolist irc_nick freenode,#weechat,FlashCode\n" " Show nicklist from a specific buffer:\n" - " /infolist nicklist " + " /infolist nicklist \n" + " Show current buffer:\n" + " /infolist buffer ${buffer}" "", "%(infolists)", "infolist_cmd", "") From 08728360b5e14b9fe186d1970ca0346c2b7b37dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sat, 28 Oct 2017 09:27:43 +0200 Subject: [PATCH 085/642] keepnick.py 1.6: fix IRC message parsing error The script now requires WeeChat >= 1.3. --- python/keepnick.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/python/keepnick.py b/python/keepnick.py index 8259de5f..6a2d35c3 100644 --- a/python/keepnick.py +++ b/python/keepnick.py @@ -18,6 +18,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-10-19: nils_2 (freenode.#weechat) +# 1.6 : fix parsing error, now using weechat.info_get_hashtable() (reported by Mikaela) +# # 2017-10-14: Kaijo & nils_2 (freenode.#weechat) # 1.5 : fix empty string breaks output # : add evaluation for option "text" and use variable "$server" instead of "%s" @@ -32,7 +35,7 @@ # 2017-08-17: nils_2 (freenode.#weechat) # 1.4 : eval_expression for nicks # : use irc.server..password -# ; add short /help +# : add short /help # # 2016-05-12: picasso (freenode.#weechat) # 1.3 : monitor quits and nick changes @@ -62,7 +65,7 @@ # 2012-02-08: nils_2, (freenode.#weechat) # 0.5 : sync with 0.3.x API (requested by CAHbI4) # -# requires: WeeChat version 0.4.2 +# requires: WeeChat version 1.3 # # Development is currently hosted at # https://github.com/weechatter/weechat-scripts @@ -98,7 +101,7 @@ # -------------------------------[ Constants ]------------------------------------- SCRIPT_NAME = "keepnick" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "1.5" +SCRIPT_VERSION = "1.6" SCRIPT_LICENCE = "GPL3" SCRIPT_DESC = "keep your nick and recover it in case it's occupied" @@ -129,10 +132,10 @@ def redirect_isonhandler(data, signal, hashtable): if hashtable['output'] == '': return weechat.WEECHAT_RC_OK - # ISON_nicks contains nicks that are online on server (separated with space) - # nicks in ISON_nicks are lowercase - message,ISON_nicks = hashtable['output'].split(':')[1:] - ISON_nicks = [nick.lower() for nick in ISON_nicks.split()] + parsed = weechat.info_get_hashtable( "irc_message_parse",dict(message=hashtable['output']) ) + # variable ISON_nicks contains online nicks on server (separated with space) + # nicks in variable ISON_nicks are lowercase and 'text' contains the nick + ISON_nicks = [ nick.lower() for nick in parsed['text'].split() ] for nick in server_nicks(hashtable['server']): mynick = weechat.info_get('irc_nick',hashtable['server']) # current nick on server @@ -312,11 +315,11 @@ def print_usage(data, buffer, args): '') version = weechat.info_get("version_number", "") or 0 - if int(version) >= 0x00040200: + if int(version) >= 0x01030000: if int(OPTIONS['delay'][0]) > 0 and int(OPTIONS['timeout'][0]) > 0: init_options() install_hooks() weechat.hook_config( 'plugins.var.python.' + SCRIPT_NAME + '.*', 'toggle_refresh', '' ) else: - weechat.prnt('','%s%s %s' % (weechat.prefix('error'),SCRIPT_NAME,': needs version 0.4.2 or higher')) + weechat.prnt('','%s%s %s' % (weechat.prefix('error'),SCRIPT_NAME,': needs version 1.3 or higher')) weechat.command('','/wait 1ms /python unload %s' % SCRIPT_NAME) From 328bdd79ba196af91d41f15a87c3a4b1b825c5fc Mon Sep 17 00:00:00 2001 From: pr3d4t0r Date: Sat, 28 Oct 2017 09:51:31 +0200 Subject: [PATCH 086/642] btc_ticker.py 2.0.1: update the altcoins list --- python/btc_ticker.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/btc_ticker.py b/python/btc_ticker.py index b67fd4ba..44b8ef41 100644 --- a/python/btc_ticker.py +++ b/python/btc_ticker.py @@ -4,7 +4,7 @@ # Copyright (c) 2014, 2017 Eugene Ciurana (pr3d4t0r) # All rights reserved. # -# Version 2.0.0 +# Version - see _VERSION global # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: @@ -50,8 +50,9 @@ DEFAULT_CRYPTO_CURRENCY = 'btc' DEFAULT_FIAT_CURRENCY = 'usd' -VALID_CRYPTO_CURRENCIES = [ DEFAULT_CRYPTO_CURRENCY, 'ltc', 'eth', 'nmc', 'zec', 'dash', 'xrp', 'xmr', ] +VALID_CRYPTO_CURRENCIES = [ DEFAULT_CRYPTO_CURRENCY, 'eth', 'bch', 'xrp', 'xem', 'ltc', 'dash', 'neo', 'etc', ] VALID_FIAT_CURRENCIES = [ DEFAULT_FIAT_CURRENCY, 'eur', 'rur', ] +_VERSION = '2.0.1' COMMAND_NICK = 'tick' @@ -134,11 +135,11 @@ def displayCryptoCurrencyTicker(data, buffer, arguments): # *** main *** -weechat.register('btc_ticker', 'pr3d4t0r', '2.0.0', 'BSD', 'Display a crypto currency spot price ticker (BTC, LTC, ETH) in the active buffer', '', 'UTF-8') +weechat.register('btc_ticker', 'pr3d4t0r', _VERSION, 'BSD', 'Display a crypto currency spot price ticker (BTC, ETH, LTC) in the active buffer', '', 'UTF-8') cryptoCurrencies = '|'.join(sorted(VALID_CRYPTO_CURRENCIES)) fiatCurrencies = '|'.join(VALID_FIAT_CURRENCIES) argsWeeChat = '[%s [%s] ]' % (cryptoCurrencies, fiatCurrencies) weechat.hook_command(COMMAND_NICK, 'Display common crypto currency spot exchange values conveted to fiat currencies like USD or EUR',\ - argsWeeChat, ' btc = Bitcoin\n eth = Ethereum\n ltc = Litecoin\n zec = Zcash\n\n usd = US dollar\n eur = euro\n rur = Russian ruble', '', 'displayCryptoCurrencyTicker', '') + argsWeeChat, ' btc = Bitcoin\n eth = Ethereum\n bch = Bitcoin Cash\n xrp = Ripple\n xem = NEM\n ltc = Litecoin\n dash = Dash\n neo = NEO\n etc = Ethereum Classic\n\n usd = US dollar\n eur = euro\n rur = Russian ruble', '', 'displayCryptoCurrencyTicker', '') From 83eb5eae65a4450e6a2f3f63d29c34ea76aa5ba2 Mon Sep 17 00:00:00 2001 From: Matthias Adamczyk Date: Sat, 28 Oct 2017 10:40:40 +0200 Subject: [PATCH 087/642] New script buffer_autohide.py: automatically hide/unhide buffers according to IRC activity --- python/buffer_autohide.py | 187 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 python/buffer_autohide.py diff --git a/python/buffer_autohide.py b/python/buffer_autohide.py new file mode 100644 index 00000000..e1a1a2c7 --- /dev/null +++ b/python/buffer_autohide.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# MIT License +# +# Copyright (c) 2017 Matthias Adamczyk +# +# 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. +""" +Automatically hide read IRC buffers and unhide them on new activity. + +Requires WeeChat version 1.0 or higher. + +Configuration: + plugins.var.python.buffer_autohide.hide_inactive: Hide inactive buffers (default: "off") + plugins.var.python.buffer_autohide.hide_private: Hide private buffers (default: "off") + plugins.var.python.buffer_autohide.unhide_low: Unhide a buffer when a low message (like JOIN, + PART, etc.) has been received (default: "off") + +History: +2017-03-19: Matthias Adamczyk + version 0.1: Initial release + +https://github.com/notmatti/buffer_autohide +""" +import_ok = True +try: + import weechat + from weechat import WEECHAT_RC_OK +except ImportError: + print("Script must be run under weechat. https://weechat.org") + import_ok = False + +SCRIPT_NAME = "buffer_autohide" +SCRIPT_AUTHOR = "Matthias Adamczyk " +SCRIPT_VERSION = "0.1" +SCRIPT_LICENSE = "MIT" +SCRIPT_DESC = "Automatically hide read IRC buffers and unhide them on new activity" +SCRIPT_COMMAND = SCRIPT_NAME + +CURRENT_BUFFER = "0x0" # pointer string representation + + +def config_init(): + """Add configuration options to weechat.""" + config = { + "hide_inactive": ("off", "Hide inactive buffers"), + "hide_private": ("off", "Hide private buffers"), + "unhide_low": ("off", + "Unhide a buffer when a low message (like JOIN, PART, etc.) has been received"), + } + + for option, default_value in config.items(): + if weechat.config_get_plugin(option) == "": + weechat.config_set_plugin(option, default_value[0]) + weechat.config_set_desc_plugin( + option, '{} (default: "{}")'.format(default_value[1], default_value[0])) + + +def hotlist_dict(): + """Return the contents of the hotlist as a dictionary. + + The returned dictionary has the following structure: + >>> hotlist = { + ... "0x0": { # string representation of the buffer pointer + ... "count_low": 0, + ... "count_message": 0, + ... "count_private": 0, + ... "count_highlight": 0, + ... } + ... } + """ + hotlist = {} + infolist = weechat.infolist_get("hotlist", "", "") + while weechat.infolist_next(infolist): + buffer_pointer = weechat.infolist_pointer(infolist, "buffer_pointer") + hotlist[buffer_pointer] = {} + hotlist[buffer_pointer]["count_low"] = weechat.infolist_integer( + infolist, "count_00") + hotlist[buffer_pointer]["count_message"] = weechat.infolist_integer( + infolist, "count_01") + hotlist[buffer_pointer]["count_private"] = weechat.infolist_integer( + infolist, "count_02") + hotlist[buffer_pointer]["count_highlight"] = weechat.infolist_integer( + infolist, "count_03") + weechat.infolist_free(infolist) + return hotlist + + +def hide_buffer_cb(signal, data, signal_data): + """Hide the previous IRC buffer when switching buffers. + + If configuration option ``hide_private`` is enabled, + private buffers will be hidden as well. + """ + global CURRENT_BUFFER + + previous_buffer = CURRENT_BUFFER + CURRENT_BUFFER = weechat.current_buffer() + + plugin = weechat.buffer_get_string(previous_buffer, "plugin") + full_name = weechat.buffer_get_string(previous_buffer, "full_name") + server = weechat.buffer_get_string(previous_buffer, "localvar_server") + channel = weechat.buffer_get_string(previous_buffer, "localvar_channel") + + if plugin != "irc" or full_name.startswith("irc.server"): + return WEECHAT_RC_OK + + buffer_type = weechat.buffer_get_string( + weechat.info_get("irc_buffer", "{},{}".format(server, channel)), + "localvar_type") + + if (buffer_type == "private" + and weechat.config_get_plugin("hide_private") == "off"): + return WEECHAT_RC_OK + + if weechat.config_get_plugin("hide_inactive") == "off": + nicks_count = 0 + infolist = weechat.infolist_get( + "irc_channel", "", "{},{}".format(server, channel)) + if infolist: + weechat.infolist_next(infolist) + nicks_count = weechat.infolist_integer(infolist, "nicks_count") + weechat.infolist_free(infolist) + if nicks_count == 0: + return WEECHAT_RC_OK + + weechat.buffer_set(previous_buffer, "hidden", "1") + return WEECHAT_RC_OK + + +def unhide_buffer_cb(data, signal, signal_data): + """Unhide a buffer on new activity. + + This callback unhides the buffer in which a new message has been received. + If configuration option ``unhide_low`` is enabled, + buffers with only low messages (like JOIN, PART, etc.) will be unhidden as well. + """ + server = signal.split(",")[0] + message = weechat.info_get_hashtable( + "irc_message_parse", + {"message": signal_data}) + channel = message["channel"] + hotlist = hotlist_dict() + buffer = weechat.info_get("irc_buffer", "{},{}".format(server, channel)) + + if not buffer in hotlist.keys(): + # just some background noise + return WEECHAT_RC_OK + + if (weechat.config_get_plugin("unhide_low") == "on" + and hotlist[buffer]["count_low"] > 0 + or hotlist[buffer]["count_message"] > 0 + or hotlist[buffer]["count_private"] > 0 + or hotlist[buffer]["count_highlight"] > 0): + weechat.buffer_set(buffer, "hidden", "0") + + return WEECHAT_RC_OK + + +if (__name__ == '__main__' + and import_ok + and weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, + SCRIPT_DESC, '', '')): + weechat_version = weechat.info_get("version_number", "") or 0 + if int(weechat_version) >= 0x01000000: + config_init() + CURRENT_BUFFER = weechat.current_buffer() + weechat.hook_signal("buffer_switch", "hide_buffer_cb", "") + weechat.hook_signal("*,irc_in2_*", "unhide_buffer_cb", "") + else: + weechat.prnt("", "{}{} requires WeeChat version 1.0 or higher".format( + weechat.prefix('error'), SCRIPT_NAME)) From 072865357a98817bdd37f8879008b238d826f686 Mon Sep 17 00:00:00 2001 From: mumixam Date: Thu, 2 Nov 2017 08:56:06 -0500 Subject: [PATCH 088/642] twitch.py 0.4: added debug for api calls and minor bugfixes --- python/twitch.py | 58 +++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/python/twitch.py b/python/twitch.py index d908b855..88e8ea12 100644 --- a/python/twitch.py +++ b/python/twitch.py @@ -26,9 +26,12 @@ # settings: # plugins.var.python.twitch.servers (default: twitch) # plugins.var.python.twitch.prefix_nicks (default: 1) +# plugins.var.python.twitch.debug (default: 0) # # # History: # +# 2017-11-02, mumixam +# v0.4: added debug mode for API calls, minor bugfixes # 2017-06-10, mumixam # v0.3: fixed whois output of utf8 display names # 2016-11-03, mumixam @@ -40,12 +43,13 @@ SCRIPT_NAME = "twitch" SCRIPT_AUTHOR = "mumixam" -SCRIPT_VERSION = "0.3" +SCRIPT_VERSION = "0.4" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "twitch.tv Chat Integration" OPTIONS={ 'servers': ('twitch','Name of server(s) which script will be active on, space seperated'), - 'prefix_nicks': ('1','Prefix nicks based on ircv3 tags for mods/subs, This can be cpu intensive on very active chats [1 for enabled, 0 for disabled]') + 'prefix_nicks': ('1','Prefix nicks based on ircv3 tags for mods/subs, This can be cpu intensive on very active chats [1 for enabled, 0 for disabled]'), + 'debug': ('0','Debug mode') } @@ -59,7 +63,7 @@ clientid='awtv6n371jb7uayyc4jaljochyjbfxs' params = '?client_id='+clientid - +curlopt = {"timeout": "5"} def days_hours_minutes(td): age = '' hours = td.seconds // 3600 @@ -82,8 +86,8 @@ def twitch_main(data, buffer, args): if not (server in OPTIONS['servers'].split() and type == 'channel'): return weechat.WEECHAT_RC_OK url = 'https://api.twitch.tv/kraken/streams/' + username - url_hook_process = weechat.hook_process( - "url:" + url+params, 7 * 1000, "stream_api", buffer) + weechat.hook_process_hashtable( + "url:" + url+params, curlopt, 7 * 1000, "stream_api", buffer) return weechat.WEECHAT_RC_OK @@ -114,7 +118,12 @@ def channel_api(data, command, rc, stdout, stderr): try: jsonDict = json.loads(stdout.strip()) except Exception as e: - weechat.prnt(data['buffer'], 'TWITCH: Error with twitch API') + weechat.prnt(data['buffer'], '%stwitch.py: error communicating with twitch api' % weechat.prefix('error')) + if OPTIONS['debug']: + weechat.prnt(data['buffer'],'%stwitch.py: return code: %s' % (weechat.prefix('error'),rc)) + weechat.prnt(data['buffer'],'%stwitch.py: stdout: %s' % (weechat.prefix('error'),stdout)) + weechat.prnt(data['buffer'],'%stwitch.py: stderr: %s' % (weechat.prefix('error'),stderr)) + weechat.prnt(data['buffer'],'%stwitch.py: exception: %s' % (weechat.prefix('error'),e)) return weechat.WEECHAT_RC_OK currentbuf = weechat.current_buffer() name = data['name'] @@ -144,8 +153,8 @@ def channel_api(data, command, rc, stdout, stderr): weechat.prnt(data['buffer'], makeutf8(output)) url = 'https://api.twitch.tv/kraken/users/' + \ name.lower() + '/follows/channels' - urlh = weechat.hook_process( - "url:" + url+params, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': name, 'dname': dname})) + weechat.hook_process_hashtable( + "url:" + url+params, curlopt, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': name, 'dname': dname})) if len(jsonDict) == 18: dname = jsonDict['display_name'] @@ -168,8 +177,8 @@ def channel_api(data, command, rc, stdout, stderr): pcolor, pformat, dcolor, ncolor, user, dcolor, ccolor)) else: url = 'https://api.twitch.tv/api/channels/' + data['name'].lower() - urlh = weechat.hook_process( - "url:" + url+params, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': name, 'dname': data['name']})) + weechat.hook_process_hashtable( + "url:" + url+params, curlopt, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': name, 'dname': data['name']})) count = jsonDict['_total'] if count: output = '%s%s %s[%s%s%s]%s %sFollowing%s: %s' % ( @@ -182,7 +191,12 @@ def stream_api(data, command, rc, stdout, stderr): try: jsonDict = json.loads(stdout.strip()) except Exception as e: - weechat.prnt(data, 'TWITCH: Error with twitch API') + weechat.prnt(data, '%stwitch.py: error communicating with twitch api' % weechat.prefix('error')) + if OPTIONS['debug']: + weechat.prnt(data,'%stwitch.py: return code: %s' % (weechat.prefix('error'),rc)) + weechat.prnt(data,'%stwitch.py: stdout: %s' % (weechat.prefix('error'),stdout)) + weechat.prnt(data,'%stwitch.py: stderr: %s' % (weechat.prefix('error'),stderr)) + weechat.prnt(data,'%stwitch.py: exception: %s' % (weechat.prefix('error'),e)) return weechat.WEECHAT_RC_OK currentbuf = weechat.current_buffer() title_fg = weechat.color( @@ -206,7 +220,7 @@ def stream_api(data, command, rc, stdout, stderr): weechat.prnt(data, 'ERROR: The page could not be found, or has been deleted by its owner.') return weechat.WEECHAT_RC_OK if not 'stream' in jsonDict.keys(): - weechat.prnt(data, 'TWITCH: Error with twitch API') + weechat.prnt(data, 'twitch.py: Error with twitch API (stream key missing from json)') return weechat.WEECHAT_RC_OK if not jsonDict['stream']: line = "STREAM: %sOFFLINE%s %sCHECKED AT: %s" % ( @@ -291,7 +305,7 @@ def twitch_clearchat(data, modifier, modifier_data, string): rul = weechat.color("-underline") if user: if 'ban-duration' in tags: - if tags['ban-reason']: + if 'ban-reason' in tags and tags['ban-reason']: bn=tags['ban-reason'].replace('\s',' ') weechat.prnt(buffer,"%s--%s %s has been timed out for %s seconds %sReason%s: %s" % (pcolor, ccolor, user, tags['ban-duration'], ul, rul, bn)) @@ -402,7 +416,7 @@ def twitch_privmsg(data, modifier, server_name, string): 'irc_message_parse', {"message": string}) if message['channel'].startswith('#'): return string - newmsg = 'PRIVMSG jtv :.w ' + message['nick'] + ' ' + message['text'] + newmsg = 'PRIVMSG #%s :/w %s %s' % (message['nick'],message['nick'],message['text']) return newmsg @@ -438,8 +452,8 @@ def twitch_whois(data, modifier, server_name, string): username = msg['nick'].lower() currentbuf = weechat.current_buffer() url = 'https://api.twitch.tv/kraken/channels/' + username - url_hook = weechat.hook_process( - "url:" + url+params, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': username})) + url_hook = weechat.hook_process_hashtable( + "url:" + url+params, curlopt, 7 * 1000, "channel_api", str({'buffer': currentbuf, 'name': username})) return "" def config_setup(): @@ -449,7 +463,7 @@ def config_setup(): weechat.config_set_plugin(option, value[0]) OPTIONS[option] = value[0] else: - if option == 'prefix_nicks': + if option == 'prefix_nicks' or option == 'debug': OPTIONS[option] = weechat.config_string_to_boolean( weechat.config_get_plugin(option)) else: @@ -457,7 +471,7 @@ def config_setup(): def config_change(pointer, name, value): option = name.replace('plugins.var.python.'+SCRIPT_NAME+'.','') - if option == 'prefix_nicks': + if option == 'prefix_nicks' or option == 'debug': value=weechat.config_string_to_boolean(value) OPTIONS[option] = value return weechat.WEECHAT_RC_OK @@ -469,6 +483,7 @@ def config_change(pointer, name, value): " settings:\n" " plugins.var.python.twitch.servers (default: twitch)\n" " plugins.var.python.twitch.prefix_nicks (default: 1)\n" + " plugins.var.python.twitch.debug (default: 0)\n" "\n\n" " This script checks stream status of any channel on any servers listed\n" " in the \"plugins.var.python.twitch.servers\" setting. When you switch\n" @@ -482,12 +497,15 @@ def config_change(pointer, name, value): "\n\n" " This script also will prefix users nicks (@ for mod, % for sub,\n" " and ~ for broadcaster). This will break the traditional function\n" - " of /ignore add nightbot and will require you to prefix nicks if you\n" - " want to ignore someone /ignore add re:[~@%]{0,3}nightbot should ignore\n" + " of `/ignore add nightbot` and will require you to prefix nicks if you\n" + " want to ignore someone `/ignore add re:[~@%]{0,3}nightbot` should ignore\n" " a nick with all or none of the prefixes used by this script.\n" " NOTE: This may cause high cpu usage in very active chat and/or on slower cpus.\n" " This can also be disabled by setting\n /set plugins.var.python.twitch.prefix_nicks off\n" "\n\n" + " If you are experiencing errors you can enable debug mode by setting\n" + " /set plugins.var.python.twitch.debug on\n" + "\n\n" " Required server settings:\n" " /server add twitch irc.twitch.tv\n" " /set irc.server.twitch.capabilities \"twitch.tv/membership,twitch.tv/commands,twitch.tv/tags\"\n" From 70e3dd46985ab3fa61f92bb53c6b08b05cabaf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Fri, 3 Nov 2017 18:22:44 +0100 Subject: [PATCH 089/642] fix script/issue #236 add "%h" variable in option 'file' --- python/autoconf.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/python/autoconf.py b/python/autoconf.py index 51fc1e39..18f489f6 100644 --- a/python/autoconf.py +++ b/python/autoconf.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-11-03: fix script/issue #236 +# v0.2: add "%h" variable in option 'file' import os import re @@ -31,7 +33,7 @@ NAME = "autoconf" AUTHOR = "Manu Koell " -VERSION = "0.1" +VERSION = "0.2" LICENSE = "GPL3" DESCRIPTION = "auto save/load changed options in a ~/.weerc file, useful to share dotfiles with" @@ -49,14 +51,9 @@ 'ignore': ( ','.join(EXCLUDES), 'comma separated list of patterns to exclude'), - 'file': ('~/.weerc', 'config file location') + 'file': ('%h/.weerc', 'config file location ("%h" will be replaced by WeeChat home, "~/.weechat" by default)') } -RE = { - 'option': re.compile('\s*(.*) = (.*) \(default') -} - - def cstrip(text): """strip color codes""" @@ -68,8 +65,7 @@ def get_config(args): try: conf = args[1] except Exception: - conf = w.config_get_plugin('file') - + conf = w.config_get_plugin('file').replace("%h",w.info_get("weechat_dir", "")) return os.path.expanduser(conf) def load_conf(args): @@ -151,6 +147,10 @@ def quit_cb(data, signal, signal_data): if __name__ == '__main__': if w.register(NAME, AUTHOR, VERSION, LICENSE, DESCRIPTION, "", ""): w.hook_command(NAME, DESCRIPTION, 'save [path] || load [path]', '', 'save || load', 'autoconf_cb', '') + default_txt = w.gettext("default: ") # check if string is translated + RE = { + 'option': re.compile('\s*(.*) = (.*) \(%s' % default_txt) + } # set default config for option, value in SETTINGS.items(): @@ -163,5 +163,3 @@ def quit_cb(data, signal, signal_data): if 'on' in w.config_get_plugin('autosave'): w.hook_signal('quit', 'quit_cb', '') - - From a05b132f485e1136fe68cc75f2a9483540b103d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Fri, 3 Nov 2017 18:22:44 +0100 Subject: [PATCH 090/642] autoconf.py 0.2: add "%h" variable in option "file", fix translation of "default: " (closes #236) --- python/autoconf.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/python/autoconf.py b/python/autoconf.py index 51fc1e39..18f489f6 100644 --- a/python/autoconf.py +++ b/python/autoconf.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-11-03: fix script/issue #236 +# v0.2: add "%h" variable in option 'file' import os import re @@ -31,7 +33,7 @@ NAME = "autoconf" AUTHOR = "Manu Koell " -VERSION = "0.1" +VERSION = "0.2" LICENSE = "GPL3" DESCRIPTION = "auto save/load changed options in a ~/.weerc file, useful to share dotfiles with" @@ -49,14 +51,9 @@ 'ignore': ( ','.join(EXCLUDES), 'comma separated list of patterns to exclude'), - 'file': ('~/.weerc', 'config file location') + 'file': ('%h/.weerc', 'config file location ("%h" will be replaced by WeeChat home, "~/.weechat" by default)') } -RE = { - 'option': re.compile('\s*(.*) = (.*) \(default') -} - - def cstrip(text): """strip color codes""" @@ -68,8 +65,7 @@ def get_config(args): try: conf = args[1] except Exception: - conf = w.config_get_plugin('file') - + conf = w.config_get_plugin('file').replace("%h",w.info_get("weechat_dir", "")) return os.path.expanduser(conf) def load_conf(args): @@ -151,6 +147,10 @@ def quit_cb(data, signal, signal_data): if __name__ == '__main__': if w.register(NAME, AUTHOR, VERSION, LICENSE, DESCRIPTION, "", ""): w.hook_command(NAME, DESCRIPTION, 'save [path] || load [path]', '', 'save || load', 'autoconf_cb', '') + default_txt = w.gettext("default: ") # check if string is translated + RE = { + 'option': re.compile('\s*(.*) = (.*) \(%s' % default_txt) + } # set default config for option, value in SETTINGS.items(): @@ -163,5 +163,3 @@ def quit_cb(data, signal, signal_data): if 'on' in w.config_get_plugin('autosave'): w.hook_signal('quit', 'quit_cb', '') - - From 8c43c68ca1caff2d6d4b65c866124f77056400d5 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Sat, 11 Nov 2017 08:30:30 +0100 Subject: [PATCH 091/642] autosort.py 3.1: use colors to format the help text --- python/autosort.py | 161 +++++++++++++++++++++++++++++---------------- 1 file changed, 104 insertions(+), 57 deletions(-) diff --git a/python/autosort.py b/python/autosort.py index cade603b..c71aedba 100644 --- a/python/autosort.py +++ b/python/autosort.py @@ -25,6 +25,8 @@ # # Changelog: +# 3.1: +# * Use colors to format the help text. # 3.0: # * Switch to evaluated expressions for sorting. # * Add `/autosort debug` command. @@ -63,7 +65,7 @@ SCRIPT_NAME = 'autosort' SCRIPT_AUTHOR = 'Maarten de Vries ' -SCRIPT_VERSION = '3.0' +SCRIPT_VERSION = '3.1' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.' @@ -734,95 +736,116 @@ def on_autosort_complete(data, name, buffer, completion): return autosort_complete_helpers(words[1:], completion) return weechat.WEECHAT_RC_OK -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. +command_description = r'''{*white}# General commands{reset} -# Commands - -## Miscellaneous -/autosort sort +{*white}/autosort {brown}sort{reset} Manually trigger the buffer sorting. -/autosort debug +{*white}/autosort {brown}debug{reset} Show the evaluation results of the sort rules for each buffer. -## Sorting rules +{*white}# Sorting rule commands{reset} -/autosort rules list +{*white}/autosort{brown} rules list{reset} Print the list of sort rules. -/autosort rules add +{*white}/autosort {brown}rules add {cyan}{reset} Add a new rule at the end of the list. -/autosort rules insert +{*white}/autosort {brown}rules insert {cyan} {reset} Insert a new rule at the given index in the list. -/autosort rules update +{*white}/autosort {brown}rules update {cyan} {reset} Update a rule in the list with a new expression. -/autosort rules delete +{*white}/autosort {brown}rules delete {cyan} Delete a rule from the list. -/autosort rules move +{*white}/autosort {brown}rules move {cyan} {reset} Move a rule from one position in the list to another. -/autosort rules swap +{*white}/autosort {brown}rules swap {cyan} {reset} Swap two rules in the list -## Helper variables +{*white}# Helper variable commands{reset} -/autosort helpers list +{*white}/autosort {brown}helpers list Print the list of helper variables. -/autosort helpers set +{*white}/autosort {brown}helpers set {cyan} Add or update a helper variable with the given name. -/autosort helpers delete +{*white}/autosort {brown}helpers delete {cyan} Delete a helper variable. -/autosort helpers rename +{*white}/autosort {brown}helpers rename {cyan} Rename a helper variable. -/autosort helpers swap +{*white}/autosort {brown}helpers swap {cyan} Swap the expressions of two helper variables 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. - -## Sort rules -Autosort evaluates a list of eval expressions (see /help eval) and sorts the buffers based on evaluated result. -Earlier rules will be considered first. -Only if earlier rules produced identical results is the result of the next rule considered for sorting purposes. - -You can debug your sort rules with the `/autosort debug` command, which will print the evaluation results of each rule for each buffer. - -NOTE: The sort rules for version 3 are not compatible with version 2 or vice versa. -You will have to manually port your old rules to version 3 if you have any. - -## Helper variables -You may define helper variables for the main sort rules to keep your rules readable. -They can be used in the main sort rules as variables. -For example, a helper variable named `foo` can be accessed in a main rule with the string `${foo}`. - -## Replacing substrings -There is no default method for replacing text inside eval expressions. -However, autosort adds a `replace` info hook that can be used inside eval expressions: `${info:autosort_replace,from,to,text}`. -For example, `${info:autosort_replace,#,,${buffer.name}}` will evaluate to the buffer name with all hash signs stripped. -You can escape commas and backslashes inside the arguments by prefixing them with a backslash. - -## 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. +{*white}# Description +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. + +{*white}# Sort rules{reset} +Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the +buffers based on evaluated result. Earlier rules will be considered first. Only +if earlier rules produced identical results is the result of the next rule +considered for sorting purposes. + +You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will +print the evaluation results of each rule for each buffer. + +{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice +versa. You will have to manually port your old rules to version 3 if you have any. + +{*white}# Helper variables{reset} +You may define helper variables for the main sort rules to keep your rules +readable. They can be used in the main sort rules as variables. For example, +a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the +string `{cyan}${{foo}}{reset}`. + +{*white}# Replacing substrings{reset} +There is no default method for replacing text inside eval expressions. However, +autosort adds a `replace` info hook that can be used inside eval expressions: + {cyan}${{info:autosort_replace,from,to,text}}{reset} + +For example, to strip all hashes from a buffer name, you could write: + {cyan}${{info:autosort_replace,#,,${{buffer.name}}}}{reset} + +You can escape commas and backslashes inside the arguments by prefixing them with +a backslash. + +{*white}# Automatic or manual sorting{reset} +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 `{cyan}autosort.sorting.signals{reset}` +option to add or remove any signal you like. + +If you remove all signals you can still sort your buffers manually with the +`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option +`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled. + +{*white}# Recommended settings +For the best visual effect, consider setting the following options: + {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset} + {*white}/set {cyan}buffers.look.indenting{reset} {brown}on{reset} + +The first setting allows server buffers to be sorted independently, which is +needed to create a hierarchical tree view of the server and channel buffers. +The second one indents channel and private buffers in the buffer list of the +`{*default}buffers.pl{reset}` script. + +If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree +structure with the following setting (modify to suit your need): + {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset} ''' command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)' @@ -841,9 +864,33 @@ def on_autosort_complete(data, name, buffer, completion): if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): config = Config('autosort') + colors = { + 'default': weechat.color('default'), + 'reset': weechat.color('reset'), + 'black': weechat.color('black'), + 'red': weechat.color('red'), + 'green': weechat.color('green'), + 'brown': weechat.color('brown'), + 'yellow': weechat.color('yellow'), + 'blue': weechat.color('blue'), + 'magenta': weechat.color('magenta'), + 'cyan': weechat.color('cyan'), + 'white': weechat.color('white'), + '*default': weechat.color('*default'), + '*black': weechat.color('*black'), + '*red': weechat.color('*red'), + '*green': weechat.color('*green'), + '*brown': weechat.color('*brown'), + '*yellow': weechat.color('*yellow'), + '*blue': weechat.color('*blue'), + '*magenta': weechat.color('*magenta'), + '*cyan': weechat.color('*cyan'), + '*white': weechat.color('*white'), + } + weechat.hook_config('autosort.*', 'on_config_changed', '') weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '') - weechat.hook_command('autosort', command_description, '', '', command_completion, 'on_autosort_command', '') + weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '') weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '') weechat.hook_info('autosort_order', info_order_description, info_order_arguments, 'on_info_order', '') From f9ad5c99103f7c2f14653d094f5ead3c3a5ce080 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Sat, 11 Nov 2017 15:08:36 +0100 Subject: [PATCH 092/642] autosort 3.2: Fix python3 compatiblity. --- python/autosort.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/autosort.py b/python/autosort.py index c71aedba..2af26415 100644 --- a/python/autosort.py +++ b/python/autosort.py @@ -25,6 +25,8 @@ # # Changelog: +# 3.2: +# * Fix python3 compatiblity. # 3.1: # * Use colors to format the help text. # 3.0: @@ -60,12 +62,13 @@ import json import math import re +import sys import time import weechat SCRIPT_NAME = 'autosort' SCRIPT_AUTHOR = 'Maarten de Vries ' -SCRIPT_VERSION = '3.1' +SCRIPT_VERSION = '3.2' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.' @@ -74,6 +77,12 @@ hooks = [] timer = None +# Make sure that unicode, bytes and str are always available in python2 and 3. +# For python 2, str == bytes +# For python 3, str == unicode +if sys.version_info[0] >= 3: + unicode = str + if hasattr(time, 'perf_counter'): perf_counter = time.perf_counter else: From 107c3379c7c55e4be0fe421218deb6d3acb8877e Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Sun, 12 Nov 2017 13:14:47 +0100 Subject: [PATCH 093/642] autosort.py 3.3: Fix debug command for unicode buffer names. Changelog: * Fix the /autosort debug command for unicode. * Update the default rules to work better with Slack. --- python/autosort.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/python/autosort.py b/python/autosort.py index 2af26415..08a6c5ba 100644 --- a/python/autosort.py +++ b/python/autosort.py @@ -25,6 +25,9 @@ # # Changelog: +# 3.3: +# * Fix the /autosort debug command for unicode. +# * Update the default rules to work better with Slack. # 3.2: # * Fix python3 compatiblity. # 3.1: @@ -68,7 +71,7 @@ SCRIPT_NAME = 'autosort' SCRIPT_AUTHOR = 'Maarten de Vries ' -SCRIPT_VERSION = '3.2' +SCRIPT_VERSION = '3.3' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.' @@ -83,6 +86,19 @@ if sys.version_info[0] >= 3: unicode = str +def ensure_str(input): + ''' + Make sure the given type if the correct string type for the current python version. + That means bytes for python2 and unicode for python3. + ''' + if not isinstance(input, str): + if isinstance(input, bytes): + return input.encode('utf-8') + if isinstance(input, unicode): + return input.decode('utf-8') + return input + + if hasattr(time, 'perf_counter'): perf_counter = time.perf_counter else: @@ -148,9 +164,9 @@ class Config: '${irc_last}', '${buffer.plugin.name}', '${irc_raw_first}', - '${server}', - '${info:autosort_order,${type},server,*,channel,private}', - '${hashless_name}', + '${if:${plugin}==irc?${server}}', + '${if:${plugin}==irc?${info:autosort_order,${type},server,*,channel,private}}', + '${if:${plugin}==irc?${hashless_name}}', '${buffer.full_name}', ]) @@ -406,12 +422,13 @@ def command_debug(buffer, command, args): for merged in buffers: for buffer in merged: fullname = weechat.hdata_string(hdata, buffer, 'full_name') - if isinstance(fullname, bytes): fullname = fullname.decode('utf-8') results.append((fullname, key(buffer))) elapsed = perf_counter() - start for fullname, result in results: - log('{0}: {1}'.format(fullname, result)) + fullname = ensure_str(fullname) + result = [ensure_str(x) for x in result] + log('{0}: {1}'.format(fullname, result)) log('Computing evalutaion results took {0:.4f} seconds.'.format(elapsed)) return weechat.WEECHAT_RC_OK From 154fbabd763c4db918242aebbdb370f84e0fc23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Thu, 23 Nov 2017 22:38:39 +0100 Subject: [PATCH 094/642] screen_away.py 0.15: make script python3 compatible, fix problem with empty "command_on_*" options, add option "no_output" --- python/screen_away.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/python/screen_away.py b/python/screen_away.py index 8f68505d..ab17b810 100644 --- a/python/screen_away.py +++ b/python/screen_away.py @@ -24,6 +24,10 @@ # (this script requires WeeChat 0.3.0 or newer) # # History: +# 2017-11-20, Nils Görs +# version 0.15: make script python3 compatible +# : fix problem with empty "command_on_*" options +# : add option "no_output" # 2014-08-02, Nils Görs # version 0.14: add time to detach message. (idea by Mikaela) # 2014-06-19, Anders Bergh @@ -66,7 +70,7 @@ SCRIPT_NAME = "screen_away" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "0.14" +SCRIPT_VERSION = "0.15" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Set away status on screen detach" @@ -80,6 +84,7 @@ 'ignore': ('', 'Comma-separated list of servers to ignore.'), 'set_away': ('on', 'Set user as away.'), 'ignore_relays': ('off', 'Only check screen status and ignore relay interfaces'), + 'no_output': ('off','no detach/attach information will be displayed in buffer'), } TIMER = None @@ -144,7 +149,8 @@ def screen_away_timer_cb(buffer, args): w.infolist_free(infolist) if (attached and AWAY) or (check_relays and CONNECTED_RELAY and not attached and AWAY): - w.prnt('', '%s: Screen attached. Clearing away status' % SCRIPT_NAME) + if not w.config_string_to_boolean(w.config_get_plugin('no_output')): + w.prnt('', '%s: Screen attached. Clearing away status' % SCRIPT_NAME) for server, nick in get_servers(): if set_away: w.command(server, "/away") @@ -152,20 +158,23 @@ def screen_away_timer_cb(buffer, args): nick = nick[:-len(suffix)] w.command(server, "/nick %s" % nick) AWAY = False - for cmd in w.config_get_plugin("command_on_attach").split(";"): - w.command("", cmd) + if w.config_get_plugin("command_on_attach"): + for cmd in w.config_get_plugin("command_on_attach").split(";"): + w.command("", cmd) elif not attached and not AWAY: if not CONNECTED_RELAY: - w.prnt('', '%s: Screen detached. Setting away status' % SCRIPT_NAME) + if not w.config_string_to_boolean(w.config_get_plugin('no_output')): + w.prnt('', '%s: Screen detached. Setting away status' % SCRIPT_NAME) for server, nick in get_servers(): if suffix and not nick.endswith(suffix): w.command(server, "/nick %s%s" % (nick, suffix)); if set_away: w.command(server, "/away %s %s" % (w.config_get_plugin('message'), time.strftime(w.config_get_plugin('time_format')))) AWAY = True - for cmd in w.config_get_plugin("command_on_detach").split(";"): - w.command("", cmd) + if w.config_get_plugin("command_on_detach"): + for cmd in w.config_get_plugin("command_on_detach").split(";"): + w.command("", cmd) return w.WEECHAT_RC_OK @@ -173,7 +182,7 @@ def screen_away_timer_cb(buffer, args): if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): version = w.info_get('version_number', '') or 0 - for option, default_desc in settings.iteritems(): + for option, default_desc in settings.items(): if not w.config_is_set_plugin(option): w.config_set_plugin(option, default_desc[0]) if int(version) >= 0x00030500: From c2a750a1b77109196a034752110b0f3132578c13 Mon Sep 17 00:00:00 2001 From: manavortex Date: Sun, 26 Nov 2017 10:49:53 +0100 Subject: [PATCH 095/642] url_hinter.rb 0.41: force the string to UTF-8 --- ruby/url_hinter.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ruby/url_hinter.rb b/ruby/url_hinter.rb index 63a7dbe0..fd80eddc 100644 --- a/ruby/url_hinter.rb +++ b/ruby/url_hinter.rb @@ -1,4 +1,4 @@ -# coding: utf-8 +# -*- coding: utf-8 -*- # # Copyright (c) 2014 Kengo Tateishi # https://github.com/tkengo/weechat-url-hinter @@ -35,7 +35,7 @@ # Register url-hinter plugin to weechat and do initialization. # def weechat_init - Weechat.register('url_hinter', 'Kengo Tateish', '0.4', 'GPL3', 'Open an url in the weechat buffer to type a hint', '', '') + Weechat.register('url_hinter', 'Kengo Tateish', '0.41', 'GPL3', 'Open an url in the weechat buffer to type a hint', '', '') Weechat.hook_command( 'url_hinter', 'Search url strings, and highlight them, and if you type a hint key, open the url related to hint key.', @@ -192,7 +192,7 @@ def has_key?(key) private def get_hint_keys - option = 'hintkeys' + option = 'hintkeys' if Weechat.config_is_set_plugin(option) == 0 Weechat.config_set_plugin(option, 'jfhkgyuiopqwertnmzxcvblasd') end @@ -350,7 +350,9 @@ def message=(new_message) end def remove_color_message - Weechat.string_remove_color(message.dup, '') + ret = Weechat.string_remove_color(message.dup, '') + ret.force_encoding("UTF-8") + return ret end def next From 945ff78644f84c2614db7f55bf630dead4468f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Thu, 14 Dec 2017 07:55:50 +0100 Subject: [PATCH 096/642] histman.py 0.6: rename command "/autosetbuffer" by "/buffer_autoset" in example --- python/histman.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/histman.py b/python/histman.py index 68486c92..0e2dab64 100644 --- a/python/histman.py +++ b/python/histman.py @@ -17,6 +17,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 2017-12-14: Sébastien Helleu +# 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 # @@ -49,7 +52,7 @@ SCRIPT_NAME = 'histman' SCRIPT_AUTHOR = 'nils_2 ' -SCRIPT_VERSION = '0.5' +SCRIPT_VERSION = '0.6' SCRIPT_LICENSE = 'GPL' SCRIPT_DESC = 'save and restore global and/or buffer command history' @@ -395,9 +398,9 @@ def toggle_refresh(pointer, name, value): ' save the command history manually (for example with /cron script):\n' ' /' + SCRIPT_NAME + ' save\n' ' save and restore command history for buffer #weechat on freenode (text only):\n' - ' /autosetbuffer add irc.freenode.#weechat localvar_set_save_history text\n' + ' /buffer_autoset add irc.freenode.#weechat localvar_set_save_history text\n' ' save and restore command history for weechat core buffer (commands only):\n' - ' /autosetbuffer add core.weechat localvar_set_save_history command\n', + ' /buffer_autoset add core.weechat localvar_set_save_history command\n', 'save %-' '|| list %-', 'histman_cmd_cb', '') From a307dcd44767f9d688a08e8f293f38d53b6ed199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Thu, 14 Dec 2017 07:57:12 +0100 Subject: [PATCH 097/642] noirccolors.py 0.3: replace "autosetbuffer" by "buffer_autoset.py" in description --- python/noirccolors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/noirccolors.py b/python/noirccolors.py index 3af95689..ee5f97dd 100644 --- a/python/noirccolors.py +++ b/python/noirccolors.py @@ -2,9 +2,9 @@ SCRIPT_NAME = "noirccolors" SCRIPT_AUTHOR = "Fredrick Brennan " -SCRIPT_VERSION = "0.2" +SCRIPT_VERSION = "0.3" SCRIPT_LICENSE = "Public domain" -SCRIPT_DESC = "Remove IRC colors from buffers with the localvar 'noirccolors' set. To disable IRC colors in the current buffer, type /buffer set localvar_set_noirccolors true. You can also set this with autosetbuffer. :)" +SCRIPT_DESC = "Remove IRC colors from buffers with the localvar 'noirccolors' set. To disable IRC colors in the current buffer, type /buffer set localvar_set_noirccolors true. You can also set this with script buffer_autoset.py. :)" w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '') From 6cd048a9b09d4fe6652e08d50c8db80e05c4a3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Thu, 14 Dec 2017 08:05:13 +0100 Subject: [PATCH 098/642] stick_buffer.py 0.6: rename command "/autosetbuffer" by "/buffer_autoset" in example --- python/stick_buffer.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/stick_buffer.py b/python/stick_buffer.py index 239be8da..93ef1687 100644 --- a/python/stick_buffer.py +++ b/python/stick_buffer.py @@ -20,6 +20,9 @@ # # idea by shad0VV@freenode.#weechat # +# 2017-12-14: Sébastien Helleu +# 0.6 : rename command "/autosetbuffer" by "/buffer_autoset" in example +# # 2017-04-02: nils_2, (freenode.#weechat) # 0.5 : support of "/input jump_smart" and "/buffer +/-" (reported: squigz) # @@ -52,7 +55,7 @@ SCRIPT_NAME = "stick_buffer" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.5" +SCRIPT_VERSION = "0.6" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Stick buffers to particular windows, like irssi" @@ -239,7 +242,7 @@ def main(): Stick buffer #weechat to window 2: /buffer #weechat /buffer set localvar_set_stick_buffer_to_window 2 - /autosetbuffer add irc.freenode.#weechat stick_buffer_to_window 2 + /buffer_autoset add irc.freenode.#weechat stick_buffer_to_window 2 Set the default stick-to window to window 5: /set plugins.var.python.{script_name}.default_stick_window 5 List buffers with persistent stickiness: From 79ff5e7ccd485201211663740ad6316cd634e77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Thu, 14 Dec 2017 08:06:05 +0100 Subject: [PATCH 099/642] unhighlight.py 0.1.1: rename command "/autosetbuffer" by "/buffer_autoset" in example --- python/unhighlight.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/unhighlight.py b/python/unhighlight.py index 3a82a62f..64349000 100644 --- a/python/unhighlight.py +++ b/python/unhighlight.py @@ -26,7 +26,7 @@ SCRIPT_NAME = 'unhighlight' SCRIPT_AUTHOR = 'xiagu' -SCRIPT_VERSION = '0.1.0' +SCRIPT_VERSION = '0.1.1' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Allows per-buffer specification of a regex that prevents highlights.' @@ -93,7 +93,7 @@ def main(): Unhighlight SASL authentication messages for double logins: /buffer weechat /buffer set localvar_set_unhighlight_regex SaslServ - /autosetbuffer add core.weechat localvar_set_unhighlight_regex SaslServ + /buffer_autoset add core.weechat localvar_set_unhighlight_regex SaslServ List buffers with autoset unhighlights: /{script_name} list Show this help: From 0a55a647dd4fd102165c4101ab304850133d6de7 Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Fri, 22 Dec 2017 17:22:53 +0100 Subject: [PATCH 100/642] weetweet.py 1.2.7: Update for weechat 2.0.1 API changes Other changes: More robust twitter stream reconnection code Allow script to work on multi user machines --- python/weetweet.py | 52 +++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/python/weetweet.py b/python/weetweet.py index 7e8e9f78..7bd07304 100644 --- a/python/weetweet.py +++ b/python/weetweet.py @@ -24,6 +24,7 @@ import time import calendar import socket +import getpass # This twitter plugin can be extended even more. Just look at the twitter api # doc here: https://dev.twitter.com/docs/api/1.1 @@ -119,7 +120,7 @@ "of the user that you reply to in the tweet text. If this is not " + "the case this will be treated like a normal tweet instead.", new_tweets="Get new tweets from your home_timeline. This is only " + - "useful if you have disabled the auto updater", + "useful if you have disconnected from the home twitter stream", follow_user=", Add user to people you follow", unfollow_user=", Remove user for people you follow", following="[||| ], Show 'friends' of or " + @@ -337,6 +338,12 @@ def stream_message(buffer,tweet): #TODO make the event printing better weechat.prnt_date_tags(buffer, 0, "no_highlight", "%s%s" % (weechat.prefix("network"), tweet['source']['screen_name'] + " " + event_str + " " + tweet['target']['screen_name'] + extra_str)) + elif 'friends' in tweet: + #This should be the initital message you get listing friend ids when connecting to the twitter stream + weechat.prnt(buffer, "%s%s" % (weechat.prefix("network"), "Connected to twitter streaming API.")) + #Get new tweets since the last update (we might have missed some if this is after a reconnect) + #Get latest tweets from timeline + my_command_cb("silent", buffer, "new") else: weechat.prnt(buffer, "%s%s" % (weechat.prefix("network"), "recv stream data: " + str(tweet))) @@ -344,7 +351,7 @@ def stream_message(buffer,tweet): def twitter_stream_cb(buffer,fd): #accept connection - server = sock_fd_dict[sock_fd_dict[fd]] + server = sock_fd_dict[ sock_fd_dict[str(fd)] ] conn, addr = server.accept() tweet = "" data = True @@ -371,7 +378,7 @@ def twitter_stream_cb(buffer,fd): #We need to send over the stream options options = dict(screen_name = script_options['screen_name'], - name = sock_fd_dict[fd], + name = sock_fd_dict[str(fd)], alt_rt_style = int(script_options['alt_rt_style']), home_replies = int(script_options['home_replies']), token = script_options["oauth_token"], @@ -442,10 +449,11 @@ def connect(): # configuration. So it's defined here if the defaults change. stream_options = dict( timeout=None, block=True, heartbeat_timeout=90 ) - # Reconnect timer, when zero it will not try to reconnect - re_timer = 1 + # Reconnect timer list [1sec, 5sec, 10sec, 1min, 2min] + re_timer = [1,2,10,60,120] + re_timer_idx = 0 - while re_timer: + while True: try: if name == "twitter": #home timeline stream @@ -504,8 +512,8 @@ def connect(): client.sendall(bytes(str(tweet),"utf-8")) client.close() stream_end_message = "Text message" - # Reset the reconnect timer when we get a new message - re_timer = 1 + # Reset the reconnect timer index when we get a new message + re_timer_idx = 0 else: #Got a other type of message client = connect() @@ -514,14 +522,12 @@ def connect(): stream_end_message = "Unhandled type message" client = connect() - client.sendall(bytes('"Disconnected, trying to reconnect."',"utf-8")) + client.sendall(bytes('"Disconnected, trying to reconnect in {0} sec. Reason: {1}"'.format(re_timer[re_timer_idx], stream_end_message),"utf-8")) client.close() - if re_timer > 5: - re_timer = 0 - else: - time.sleep(re_timer) - re_timer += 4 + time.sleep(re_timer[re_timer_idx]) + if re_timer_idx < (len(re_timer) - 1): + re_timer_idx += 1 return "Stream shut down after: " + stream_end_message + ". You'll have to restart the stream manually. (:re_home, if home stream)" @@ -558,7 +564,7 @@ def create_stream(name, args = ""): setup_buffer(buffer) if not sock_fd_dict.get(name): - file_name = tempfile.gettempdir() + "/we_tw_" + name + file_name = tempfile.gettempdir() + "/we_tw_" + getpass.getuser() + "_" + name if os.path.exists(file_name): os.remove(file_name) @@ -1161,8 +1167,6 @@ def oauth_proc_cb(data, command, rc, out, err): for nick in process_output: add_to_nicklist(buffer,nick) - #Get latest tweets from timeline - my_command_cb("silent", buffer, "new") elif data == "auth1": #First auth step to request pin code oauth_token, oauth_token_secret = parse_oauth_tokens(out) @@ -1237,13 +1241,13 @@ def finish_init(): return setup_buffer(buffer) - #Add friends to nick list and print new tweets + #Add friends to nick list weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + script_options["oauth_token"] + " " + script_options["oauth_secret"] + " " + "f " + script_options['screen_name'] + " []", 10 * 1000, "oauth_proc_cb", "friends") if __name__ == "__main__" and weechat_call: - weechat.register( SCRIPT_NAME , "DarkDefender", "1.2.6", "GPL3", "Weechat twitter client", "", "") + weechat.register( SCRIPT_NAME , "DarkDefender", "1.2.7", "GPL3", "Weechat twitter client", "", "") if not import_ok: weechat.prnt("", "Can't load twitter python lib >= " + required_twitter_version) @@ -1251,7 +1255,7 @@ def finish_init(): else: hook_commands_and_completions() - # Set register script options if not available + #Set register script options if not available for option, default_value in script_options.items(): if not weechat.config_is_set_plugin(option): @@ -1263,11 +1267,10 @@ def finish_init(): weechat.config_set_plugin(option, default_value) read_config() - # hook for config changes - + #Hook for config changes weechat.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", "config_cb", "") - # create buffer + #Create buffer twit_buf = weechat.buffer_new("twitter", "buffer_input_cb", "", "buffer_close_cb", "") #Hook text input so we can update the bar item @@ -1275,7 +1278,8 @@ def finish_init(): if script_options['auth_complete']: finish_init() - #create home_timeline stream + #Create home_timeline stream. + #This will indirectly trigger the ":new" command if stream sucessfully starts create_stream("twitter") else: weechat.prnt(twit_buf,"""You have to register this plugin with twitter for it to work. From bd97ff07ff6f4463e37a14926b61556f4abb6d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Tyborowski?= Date: Fri, 29 Dec 2017 00:02:14 +0100 Subject: [PATCH 101/642] mqtt_notify.py 0.2: fix a typo causing AttributeError --- python/mqtt_notify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mqtt_notify.py b/python/mqtt_notify.py index b1540bb2..eb224770 100644 --- a/python/mqtt_notify.py +++ b/python/mqtt_notify.py @@ -12,7 +12,7 @@ SCRIPT_NAME = 'mqtt_notify' SCRIPT_AUTHOR = 'Guillaume Subiron ' -SCRIPT_VERSION = '0.1' +SCRIPT_VERSION = '0.2' SCRIPT_LICENSE = 'WTFPL' SCRIPT_DESC = 'Sends notifications using MQTT' @@ -44,7 +44,7 @@ def on_msg(*a): msg['buffer'] = w.buffer_get_string(msg['buffer'], 'short_name') cli = mqtt.Client() - if w.config.get_plugin('mqtt_user'): + if w.config_get_plugin('mqtt_user'): cli.username_pw_set(w.config_get_plugin('mqtt_user'), password=w.config_get_plugin('mqtt_password')) cli.connect(w.config_get_plugin('mqtt_host'), From 763a315c301ff989b891a0155f507ebc65fd816a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Fri, 29 Dec 2017 00:15:34 +0100 Subject: [PATCH 102/642] colorize_lines.pl 3.5: add options "highlight_words" and "highlight_words_color" --- perl/colorize_lines.pl | 76 ++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/perl/colorize_lines.pl b/perl/colorize_lines.pl index 78f11017..73e7a405 100644 --- a/perl/colorize_lines.pl +++ b/perl/colorize_lines.pl @@ -1,5 +1,5 @@ # -# Copyright (c) 2010-2015 by Nils Görs +# Copyright (c) 2010-2017 by Nils Görs # Copyleft (ɔ) 2013 by oakkitten # # colors the channel text with nick color and also highlight the whole line @@ -44,6 +44,7 @@ # /buffer set localvar_set_colorize_lines *yellow # history: +# 3.5: new options "highlight_words" and "highlight_words_color" (idea by jokrebel) # 3.4: new options "tags" and "ignore_tags" # 3.3: use localvar "colorize_lines" for buffer related color (idea by tomoe-mami) # 3.2: minor logic fix @@ -106,29 +107,33 @@ use strict; my $PRGNAME = "colorize_lines"; -my $VERSION = "3.4"; +my $VERSION = "3.5"; my $AUTHOR = "Nils Görs "; my $LICENCE = "GPL3"; my $DESCR = "Colorize users' text in chat area with their nick color, including highlights"; -my %config = ("buffers" => "all", # all, channel, query - "blacklist_buffers" => "", # "a,b,c" - "lines" => "on", - "highlight" => "on", # on, off, nicks - "nicks" => "", # "d,e,f", "/file" - "own_lines" => "on", # on, off, only - "tags" => "irc_privmsg", - "ignore_tags" => "irc_ctcp", +my %config = ("buffers" => "all", # all, channel, query + "blacklist_buffers" => "", # "a,b,c" + "lines" => "on", + "highlight" => "on", # on, off, nicks + "nicks" => "", # "d,e,f", "/file" + "own_lines" => "on", # on, off, only + "tags" => "irc_privmsg", + "ignore_tags" => "irc_ctcp", + "highlight_words" => "off", # on, off + "highlight_words_color" => "black,darkgray", ); -my %help_desc = ("buffers" => "Buffer type affected by the script (all/channel/query, default: all)", - "blacklist_buffers" => "Comma-separated list of channels to be ignored (e.g. freenode.#weechat,*.#python)", - "lines" => "Apply nickname color to the lines (off/on/nicks). The latter will limit highlighting to nicknames in option 'nicks'", - "highlight" => "Apply highlight color to the highlighted lines (off/on/nicks). The latter will limit highlighting to nicknames in option 'nicks'", - "nicks" => "Comma-separater list of nicks (e.g. freenode.cat,*.dog) OR file name starting with '/' (e.g. /file.txt). In the latter case, nicknames will get loaded from that file inside weechat folder (e.g. from ~/.weechat/file.txt). Nicknames in file are newline-separated (e.g. freenode.dog\\n*.cat)", - "own_lines" => "Apply nickname color to own lines (off/on/only). The latter turns off all other kinds of coloring altogether", - "tags" => "Comma-separated list of tags to accept (see /debug tags)", - "ignore_tags" => "Comma-separated list of tags to ignore (see /debug tags)", +my %help_desc = ("buffers" => "Buffer type affected by the script (all/channel/query, default: all)", + "blacklist_buffers" => "Comma-separated list of channels to be ignored (e.g. freenode.#weechat,*.#python)", + "lines" => "Apply nickname color to the lines (off/on/nicks). The latter will limit highlighting to nicknames in option 'nicks'", + "highlight" => "Apply highlight color to the highlighted lines (off/on/nicks). The latter will limit highlighting to nicknames in option 'nicks'", + "nicks" => "Comma-separater list of nicks (e.g. freenode.cat,*.dog) OR file name starting with '/' (e.g. /file.txt). In the latter case, nicknames will get loaded from that file inside weechat folder (e.g. from ~/.weechat/file.txt). Nicknames in file are newline-separated (e.g. freenode.dog\\n*.cat)", + "own_lines" => "Apply nickname color to own lines (off/on/only). The latter turns off all other kinds of coloring altogether", + "tags" => "Comma-separated list of tags to accept (see /debug tags)", + "ignore_tags" => "Comma-separated list of tags to ignore (see /debug tags)", + "highlight_words" => "highlight word(s) in text, matching word(s) in weechat.look.highlight", + "highlight_words_color" => "color for highlight word in text (fg:bg)", ); my @ignore_tags_array; @@ -223,12 +228,41 @@ sub colorize_cb $color = $channel_color if ($channel_color); } else { # oh well - return $string; + return $string if ($config{highlight_words} ne "on"); } } - + my $right_nocolor = weechat::string_remove_color($right, ""); + if (( + $config{highlight_words} eq "on" + ) && ($my_nick ne $nick) && ( + weechat::string_has_highlight($right_nocolor, weechat::config_string(weechat::config_get("weechat.look.highlight"))) + )) + { + my $high_word_color = weechat::color(weechat::config_get_plugin("highlight_words_color")); + my $reset = weechat::color('reset'); + my @highlight_array = split(/,/,weechat::config_string(weechat::config_get("weechat.look.highlight"))); + my @line_array = split(/ /,$right); + + foreach my $l (@line_array) { + foreach my $h (@highlight_array) { + my $i = $h; + # check word case insensitiv || check if word matches without "(?-i)" at beginning + if ( lc($l) eq lc($h) || (index($h,"(?-i)") != -1 && ($l eq substr($i,5,length($i)-5,""))) ) { + $right =~ s/\Q$l\E/$high_word_color$l$reset/; + # word starts with (?-i) and has a wildcard ? + } elsif ((index($h,"(?-i)") != -1) && (index($h,"*") != -1) ){ + my $i = $h; + my $t = substr($i,5,length($i)-5,""); + my $regex = weechat::string_mask_to_regex($t); + $right =~ s/\Q$l\E/$high_word_color$l$reset/ if ($l =~ /^$regex$/i); # use * without sensitive + }elsif ((index($h,"*") == 0 || index($h,"*") == length($h)-1)){# wildcard at beginning or end ? + my $regex = weechat::string_mask_to_regex($h); + $right =~ s/\Q$l\E/$high_word_color$l$reset/ if ($l =~ /^$regex$/i); + } + } + } + } ######################################## inject colors and go! - my $out = ""; if ($action) { # remove the first color reset - after * nick From f2ca0fef5c0340bc989118169302aee8f12aef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Sat, 6 Jan 2018 15:42:18 +0100 Subject: [PATCH 103/642] Set pull request as recommended way to update a script --- Contributing.adoc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Contributing.adoc b/Contributing.adoc index 0f3b6399..1c4ce90b 100644 --- a/Contributing.adoc +++ b/Contributing.adoc @@ -31,11 +31,13 @@ Then the author will send a pull request on this repository. There are two ways to send a new release for a script: -* Send a pull request, the commit message must have the name of script, - version, and changes, like that: + +* Send a pull request (*recommended*). + + The commit message must have the name of script, version, and changes, + like that: + `script.py 0.3: fix...` + - Please submit only one script per pull request. -* Use the form at: . + Only one script is allowed per pull request. +* Use the form at: (please use this form + only if you can not send a pull request). When sending a new version : From 20aa7573aa67208fdb702221af941e7d2dd8cc7f Mon Sep 17 00:00:00 2001 From: ecly Date: Sun, 7 Jan 2018 23:15:30 +0100 Subject: [PATCH 104/642] vimode.py 0.5.1: add nicklist scrolling with nt/nT and flip J/K, gt/gT --- python/vimode.py | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/python/vimode.py b/python/vimode.py index b4f596bd..b21a02a3 100644 --- a/python/vimode.py +++ b/python/vimode.py @@ -25,7 +25,10 @@ import os import re import subprocess -from StringIO import StringIO +try: + from StringIO import StringIO +except ImportError: + from io import StringIO import time import weechat @@ -36,7 +39,7 @@ SCRIPT_NAME = "vimode" SCRIPT_AUTHOR = "GermainZ " -SCRIPT_VERSION = "0.5" +SCRIPT_VERSION = "0.5.1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = ("Add vi/vim-like modes and keybindings to WeeChat.") @@ -50,7 +53,7 @@ # Halp! Halp! Halp! GITHUB_BASE = "https://github.com/GermainZ/weechat-vimode/blob/master/" README_URL = GITHUB_BASE + "README.md" -FAQ_KEYBINDINGS = GITHUB_BASE + "FAQ#problematic-key-bindings.md" +FAQ_KEYBINDINGS = GITHUB_BASE + "FAQ.md#problematic-key-bindings" FAQ_ESC = GITHUB_BASE + "FAQ.md#esc-key-not-being-detected-instantly" # Holds the text of the command-line mode (currently only Ex commands ":"). @@ -778,14 +781,16 @@ def key_comma(buf, input_line, cur, count): 'I': key_I, 'yy': key_yy, 'p': "/input clipboard_paste", - '/': "/input search_text", - 'gt': "/buffer +1", - 'K': "/buffer +1", - 'gT': "/buffer -1", - 'J': "/buffer -1", + '/': "/input search_text_here", + 'gt': "/buffer -1", + 'K': "/buffer -1", + 'gT': "/buffer +1", + 'J': "/buffer +1", 'r': key_r, 'R': key_R, '~': key_tilda, + 'nt': "/bar scroll nicklist * -100%", + 'nT': "/bar scroll nicklist * +100%", '\x01[[A': "/input history_previous", '\x01[[B': "/input history_next", '\x01[[C': "/input move_next_char", @@ -980,7 +985,8 @@ def cb_key_combo_default(data, signal, signal_data): # This is to avoid crashing WeeChat on script reloads/unloads, # because no hooks must still be running when a script is # reloaded or unloaded. - if VI_KEYS[vi_keys] == "/input return": + if (VI_KEYS[vi_keys] == "/input return" and + input_line.startswith("/script ")): return weechat.WEECHAT_RC_OK weechat.command("", VI_KEYS[vi_keys]) current_cur = weechat.buffer_get_integer(buf, "input_pos") @@ -1124,6 +1130,7 @@ def cb_exec_cmd(data, remaining_calls): weechat.command("", "/cursor go {},{}".format(x, y)) # Check againt defined commands. else: + raw_data = data data = data.split(" ", 1) cmd = data[0] args = "" @@ -1131,11 +1138,22 @@ def cb_exec_cmd(data, remaining_calls): args = data[1] if cmd in VI_COMMANDS: weechat.command("", "%s %s" % (VI_COMMANDS[cmd], args)) - # No vi commands defined, run the command as a WeeChat command. else: + # Check for commands not sepearated by space (e.g. "b2") + for i in range(1, len(raw_data)): + tmp_cmd = raw_data[:i] + tmp_args = raw_data[i:] + if tmp_cmd in VI_COMMANDS and tmp_args.isdigit(): + weechat.command("", "%s %s" % (VI_COMMANDS[tmp_cmd], + tmp_args)) + return weechat.WEECHAT_RC_OK + # No vi commands found, run the command as WeeChat command weechat.command("", "/{} {}".format(cmd, args)) return weechat.WEECHAT_RC_OK +def cb_vimode_go_to_normal(data, buf, args): + set_mode("NORMAL") + return weechat.WEECHAT_RC_OK # Script commands. # ---------------- @@ -1409,7 +1427,7 @@ def check_warnings(): print_warning("Please upgrade to WeeChat ≥ 1.0.0. Previous versions" " are not supported.") # Set up script options. - for option, value in vimode_settings.items(): + for option, value in list(vimode_settings.items()): if weechat.config_is_set_plugin(option): vimode_settings[option] = weechat.config_get_plugin(option) else: @@ -1444,3 +1462,6 @@ def check_warnings(): " --list: only list changes", "help || bind_keys |--list", "cb_vimode_cmd", "") + weechat.hook_command("vimode_go_to_normal", ("This command can be used for" + " key bindings to go to normal mode."), "", "", "", + "cb_vimode_go_to_normal", "") From 997454f96dabbfbb5c4b7c3e617f1b1ec8e8be00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Tue, 9 Jan 2018 21:39:51 +0100 Subject: [PATCH 105/642] stalker.pl 1.6: use hook_process_hashtable for /WHOIS, replace hook_proccess by hook_process_hashtable --- perl/stalker.pl | 75 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/perl/stalker.pl b/perl/stalker.pl index ce2bb65e..4bc3af76 100644 --- a/perl/stalker.pl +++ b/perl/stalker.pl @@ -1,5 +1,5 @@ # -# Copyright (c) 2013-2014 by Nils Görs +# Copyright (c) 2013-2018 by Nils Görs # Copyright (c) 2013-2014 by Stefan Wold # based on irssi script stalker.pl from Kaitlyn Parkhurst (SymKat) # https://github.com/symkat/Stalker @@ -20,6 +20,10 @@ # along with this program. If not, see . # # History: +# version 1.6:nils_2@freenode.#weechat +# 2018-01-09: add: use hook_process_hashtable() for /WHOIS +# : imp: use hook_process_hashtable() instead hook_process() for security reasons +# # version 1.5:nils_2@freenode.#weechat # 2015-06-15: add: new option del_date # @@ -101,7 +105,7 @@ use DBI; my $SCRIPT_NAME = "stalker"; -my $SCRIPT_VERSION = "1.5"; +my $SCRIPT_VERSION = "1.6"; my $SCRIPT_AUTHOR = "Nils Görs "; my $SCRIPT_LICENCE = "GPL3"; my $SCRIPT_DESC = "Records and correlates nick!user\@host information"; @@ -148,7 +152,7 @@ 'ignore_whois' => 'When enabled, /WHOIS won\'t be monitored. (default: off)', 'tags' => 'comma separated list of tags used for messages printed by stalker. See documentation for possible tags (e.g. \'no_log\', \'no_highlight\'). This option does not effect DEBUG messages.', 'additional_join_info' => 'add a line below the JOIN message that will display alternative nicks (tags: "irc_join", "irc_smart_filter" will be add to additional_join_info). You can use a localvar to drop additional join info for specific buffer(s) "stalker_drop_additional_join_info" (default: off)', - 'timeout' => 'timeout in seconds for hook_process(), used with option "additional_join_info". On slower machines, like raspberry pi, increase time. (default: 1)', + 'timeout' => 'timeout in seconds for hook_process_hashtable(), used with option "additional_join_info". On slower machines, like raspberry pi, increase time. (default: 1)', 'flood_timer' => 'Time in seconds for which flood protection is active. Once max_nicks is reached, joins will be ignored for the remaining duration of the timer. (default:10)', 'flood_max_nicks' => 'Maximum number of joins to allow in flood_timer length of time. Once maximum number of joins is reached, joins will be ignored until the timer ends (default:20)', ); @@ -181,7 +185,7 @@ ], ); -# ---------------[ external routines for hook_process() ]--------------------- +# ---------------[ external routines for hook_process_hashtable() ]--------------------- if ($#ARGV == 8 ) # (0-8) nine arguments given? { my $db_filename = $ARGV[0]; @@ -515,7 +519,7 @@ sub add_record my ( $nick, $user, $host, $serv ) = @_; return unless ($nick and $user and $host and $serv); - # Check if we already have this record, before using a hook_process() + # Check if we already have this record, before using a hook_process_hashtable() my $sth = $DBH_child->prepare( "SELECT nick FROM records WHERE nick = ? AND user = ? AND host = ? AND serv = ?" ); $sth->execute( $nick, $user, $host, $serv ); my $result = $sth->fetchrow_hashref; @@ -533,7 +537,22 @@ sub add_record my $db_filename = weechat_dir(); DEBUG("info", "Start hook_process(), to add $nick $user\@$host on $serv to database"); - weechat::hook_process("perl $filename $db_filename 'db_add_record' '$nick' '$user' '$host' '$serv' 'dummy' 'dummy' 'dummy'", 1000 * $options{'timeout'},"db_add_record_cb",""); + + weechat::hook_process_hashtable("perl", + { + "arg1" => $filename, + "arg2" => $db_filename, + "arg3" => 'db_add_record', + "arg4" => $nick, + "arg5" => $user, + "arg6" => $host, + "arg7" => $serv, + "arg8" => 'dummy', + "arg9" => 'dummy', + "arg10" => 'dummy', + }, 1000 * $options{'timeout'},"db_add_record_cb",""); + + } # function called when data from child is available, or when child has ended, arguments and return value @@ -1102,7 +1121,6 @@ sub irc_in2_whois_cb my (undef, undef, undef, $nick, $user, $host, undef) = split(' ', $callback_data); my $msgbuffer_whois = weechat::config_string(weechat::config_get('irc.msgbuffer.whois')); - DEBUG('info', 'weechat_hook_signal(): WHOIS'); # check for nick_regex @@ -1144,11 +1162,32 @@ sub irc_in2_whois_cb } my $use_regex = 0; - my $nicks_found = join( ", ", (get_nick_records('yes', 'nick', $nick, $server, $use_regex))); -# my $nicks_found = join( ", ", (get_nick_records('no', 'nick', $nick, $server, $use_regex))); + my $filename = get_script_filename(); + return weechat::WEECHAT_RC_OK if ($filename eq ""); + my $db_filename = weechat_dir(); + my $name = weechat::buffer_get_string($ptr_buffer,'localvar_name'); + DEBUG("info", "Start hook_process(), get additional for WHOIS() info from $nick with $user\@$host on $name"); + + weechat::hook_process_hashtable("perl", + { + "arg1" => $filename, + "arg2" => $db_filename, + "arg3" => 'additional_join_info', + "arg4" => $nick, + "arg5" => $user, + "arg6" => $host, + "arg7" => $server, + "arg8" => $options{'max_recursion'}, + "arg9" => $options{'ignore_guest_nicks'}, + "arg10" => $options{'guest_nick_regex'}, + }, 1000 * $options{'timeout'},"hook_process_get_nicks_records_cb","$nick $ptr_buffer 'dummy'"); + +# my $nicks_found = join( ", ", (get_nick_records('yes', 'nick', $nick, $server, $use_regex))); + + return weechat::WEECHAT_RC_OK; # only the given nick is returned? - return weechat::WEECHAT_RC_OK if ($nicks_found eq $nick or $nicks_found eq ""); +# return weechat::WEECHAT_RC_OK if ($nicks_found eq $nick or $nicks_found eq ""); # more than one nick was returned from sqlite my $prefix_network = weechat::prefix('network'); @@ -1259,7 +1298,21 @@ sub irc_in2_join_cb my $db_filename = weechat_dir(); DEBUG("info", "Start hook_process(), get additional info from $nick with $user\@$host on $name"); - weechat::hook_process("perl $filename $db_filename 'additional_join_info' '$nick' '$user' '$host' '$server' $options{'max_recursion'} $options{'ignore_guest_nicks'} '$options{'guest_nick_regex'}'", 1000 * $options{'timeout'},"hook_process_get_nicks_records_cb","$nick $buffer $my_tags"); + + weechat::hook_process_hashtable("perl", + { + "arg1" => $filename, + "arg2" => $db_filename, + "arg3" => 'additional_join_info', + "arg4" => $nick, + "arg5" => $user, + "arg6" => $host, + "arg7" => $server, + "arg8" => $options{'max_recursion'}, + "arg9" => $options{'ignore_guest_nicks'}, + "arg10" => $options{'guest_nick_regex'}, + }, 1000 * $options{'timeout'},"hook_process_get_nicks_records_cb","$nick $ptr_buffer $my_tags"); + } return weechat::WEECHAT_RC_OK; } From d1ade95b4149505fef56ac7e5727d5b4e3d5c99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Sat, 13 Jan 2018 20:36:20 +0100 Subject: [PATCH 106/642] stalker.pl 1.6.1: fix wrong variable name --- perl/stalker.pl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/perl/stalker.pl b/perl/stalker.pl index 4bc3af76..36da1613 100644 --- a/perl/stalker.pl +++ b/perl/stalker.pl @@ -20,6 +20,9 @@ # along with this program. If not, see . # # History: +# version 1.6.1:nils_2@freenode.#weechat +# 2018-01-11: fix: wrong variable name +# # version 1.6:nils_2@freenode.#weechat # 2018-01-09: add: use hook_process_hashtable() for /WHOIS # : imp: use hook_process_hashtable() instead hook_process() for security reasons @@ -105,7 +108,7 @@ use DBI; my $SCRIPT_NAME = "stalker"; -my $SCRIPT_VERSION = "1.6"; +my $SCRIPT_VERSION = "1.6.1"; my $SCRIPT_AUTHOR = "Nils Görs "; my $SCRIPT_LICENCE = "GPL3"; my $SCRIPT_DESC = "Records and correlates nick!user\@host information"; @@ -1311,7 +1314,7 @@ sub irc_in2_join_cb "arg8" => $options{'max_recursion'}, "arg9" => $options{'ignore_guest_nicks'}, "arg10" => $options{'guest_nick_regex'}, - }, 1000 * $options{'timeout'},"hook_process_get_nicks_records_cb","$nick $ptr_buffer $my_tags"); + }, 1000 * $options{'timeout'},"hook_process_get_nicks_records_cb","$nick $buffer $my_tags"); } return weechat::WEECHAT_RC_OK; From 366f329812c6f8e93ec7025cbb0d050968521b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Wed, 14 Feb 2018 22:10:30 +0100 Subject: [PATCH 107/642] hotlist2extern.pl 0.9: add evaluation for options --- perl/hotlist2extern.pl | 286 +++++++++++++++-------------------------- 1 file changed, 102 insertions(+), 184 deletions(-) diff --git a/perl/hotlist2extern.pl b/perl/hotlist2extern.pl index 3d99e238..501a7f9e 100644 --- a/perl/hotlist2extern.pl +++ b/perl/hotlist2extern.pl @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2010 by Nils Görs +# Copyright (c) 2009-2018 by Nils Görs # # waiting for hotlist to change and then execute a user specified command # or writes the hotlist to screen title. @@ -22,6 +22,7 @@ # # Script inspirated and tested by LaoLang_cool # +# 0.9 : add eval_expression() for format options # 0.8 : escape special characters in hotlist (arza) # 0.7 : using %h for weechat-dir instead of hardcoded path in script (flashcode) # 0.6 : new option "use_title" to print hotlist in screen title. @@ -32,69 +33,27 @@ # 0.3 : usersettings won't be loaded, sorry! :-( # : added a more complex sort routine (from important to unimportant and also numeric) # : added options: "delimiter", "priority_remove" and "hotlist_remove_format" -# -# -# use the following settings for hotlist_format: -# %h = weechat-dir (~/.weechat) -# %H = filled with highlight_char if a highlight message was written in channel. For example: * -# %N = filled with buffer number: 1 2 3 .... -# %S = filled with short name of channel: #weechat -# -# export the hotlist_format to your external command. -# %X -# -# Usage: -# template to use for display (for example: "1:freenode *2:#weechat"): -# /set plugins.var.perl.hotlist2extern.hotlist_format "%H%N:%S" -# -# Output (for example: "WeeChat Act: %H%N:%S"): -# /set plugins.var.perl.hotlist2extern.external_command_hotlist "echo WeeChat Act: %X >%h/hotlist_output.txt" -# -# Output if there is no activity (for example: "WeeChat: no activity"): -# /set plugins.var.perl.hotlist2extern.external_command_hotlist_empty "echo 'WeeChat: no activity ' >%h/hotlist_output.txt" -# -# charset for a highlight message: -# /set plugins.var.perl.hotlist2extern.highlight_char "*" -# -# template that shall be remove when message priority is low. (for example, the buffer name will be removed and only the buffer -# number will be display instead! (1 *2:#weechat): -# /set plugins.var.perl.hotlist2extern.hotlist_remove_format ":%S" -# -# message priority when using hotlist_remove_format (-1 means off) -# /set plugins.var.perl.hotlist2extern.priority_remove 0 -# -# display messages with level: -# 0=crappy msg (join/part) and core buffer informations, 1=msg, 2=pv, 3=nick highlight -# /set plugins.var.perl.hotlist2extern.lowest_priority 0 -# -# delimiter to use: -# /set plugins.var.perl.hotlist2extern.delimiter "," -# -# hotlist will be printed to screen title: -# /set plugins.var.perl.hotlist2extern.use_title "on" use strict; -my $hotlist_format = "%H%N:%S"; -my $hotlist_remove_format = ":%S"; -my $external_command_hotlist = "echo WeeChat Act: %X >%h/hotlist_output.txt"; -my $external_command_hotlist_empty = "echo \'WeeChat: no activity \' >%h/hotlist_output.txt"; -my $highlight_char = "*"; -my $lowest_priority = 0; -my $priority_remove = 0; -my $delimiter = ","; -my $use_title = "on"; - -my $prgname = "hotlist2extern"; -my $version = "0.8"; -my $description = "Give hotlist to an external file/program/screen title"; -my $current_buffer = ""; +my $SCRIPT_NAME = "hotlist2extern"; +my $SCRIPT_VERSION = "0.9"; +my $SCRIPT_DESC = "Give hotlist to an external file/program/screen title"; +my $SCRIPT_AUTHOR = "Nils Görs "; + +# default values +my %options = ( + "hotlist_format" => "%H%N:%S", + "hotlist_remove_format" => ":%S", + "external_command_hotlist" => "echo WeeChat Act: %X >%h/hotlist_output.txt", + "external_command_hotlist_empty" => "echo \'WeeChat: no activity \' >%h/hotlist_output.txt", + "highlight_char" => "*", + "lowest_priority" => "0", + "priority_remove" => "0", + "delimiter" => ",", + "use_title" => "on", +); -my $plugin_name = ""; my $weechat_dir = ""; -my $buffer_name = ""; -my $buffer_number = 0; -my $buffer_pointer = 0; -my $short_name = ""; my $res = ""; my $res2 = ""; my $priority = 0; @@ -106,36 +65,36 @@ sub hotlist_changed{ @table = (); $table = ""; - $current_buffer = weechat::current_buffer; # get current buffer + my $current_buffer = weechat::current_buffer; # get current buffer my $hotlist = weechat::infolist_get("hotlist","",""); # Pointer to Infolist while (weechat::infolist_next($hotlist)) { $priority = weechat::infolist_integer($hotlist, "priority"); - $res = $hotlist_format; # save hotlist format - $res2 = $external_command_hotlist; # save external_hotlist format + $res = $options{hotlist_format}; # save hotlist format + $res2 = $options{external_command_hotlist}; # save external_hotlist format - $plugin_name = weechat::infolist_string($hotlist,"plugin_name"); - $buffer_name = weechat::infolist_string($hotlist,"buffer_name"); - $buffer_number = weechat::infolist_integer($hotlist,"buffer_number"); # get number of buffer - $buffer_pointer = weechat::infolist_pointer($hotlist, "buffer_pointer"); # Pointer to buffer - $short_name = weechat::buffer_get_string($buffer_pointer, "short_name"); # get short_name of buffer + my $plugin_name = weechat::infolist_string($hotlist,"plugin_name"); + my $buffer_name = weechat::infolist_string($hotlist,"buffer_name"); + my $buffer_number = weechat::infolist_integer($hotlist,"buffer_number"); # get number of buffer + my $buffer_pointer = weechat::infolist_pointer($hotlist, "buffer_pointer"); # Pointer to buffer + my $short_name = weechat::buffer_get_string($buffer_pointer, "short_name"); # get short_name of buffer - unless ($priority < $lowest_priority){ - create_output(); + unless ($priority < $options{lowest_priority}){ + create_output($buffer_number, $short_name); } } weechat::infolist_free($hotlist); $table = @table; if ($table eq 0){ - unless ($external_command_hotlist_empty eq ""){ # does we have a command for empty string? - if ($use_title eq "on"){ - weechat::window_set_title($external_command_hotlist_empty); + unless ($options{external_command_hotlist_empty} eq ""){ # does we have a command for empty string? + if ($options{use_title} eq "on"){ + weechat::window_set_title(eval_expression($options{external_command_hotlist_empty})); }else{ - if (grep (/\%h/,$external_command_hotlist_empty)){ # does %h is in string? - $external_command_hotlist_empty =~ s/%h/$weechat_dir/; # add weechat-dir + if (grep (/\%h/,$options{external_command_hotlist_empty})){ # does %h is in string? + $options{external_command_hotlist_empty} =~ s/%h/$weechat_dir/; # add weechat-dir } - system($external_command_hotlist_empty); + system(eval_expression($options{external_command_hotlist_empty})); } } } @@ -143,33 +102,34 @@ sub hotlist_changed{ } sub create_output{ - $res = $hotlist_format; # save hotlist format - $res2 = $external_command_hotlist; # save external_hotlist format + my ($buffer_number, $short_name) = @_; + $res = eval_expression($options{hotlist_format}); # save hotlist format + $res2 = eval_expression($options{external_command_hotlist}); # save external_hotlist format if ($priority == 3){ # priority is highlight - if (grep (/\%H/,$hotlist_format)){ # check with original!!! - $res =~ s/\%H/$highlight_char/g; + if (grep (/\%H/,$options{hotlist_format})){ # check with original!!! + $res =~ s/\%H/$options{highlight_char}/g; } }else{ # priority != 3 $res =~ s/\%H//g; # remove all %H } - if ($priority <= $priority_remove){ - $res =~ s/$hotlist_remove_format//; # remove hotlist_remove_format - if (grep (/\%S/,$hotlist_format)){ # does %S is in string? (check with original!!!) + if ($priority <= $options{priority_remove}){ + $res =~ s/$options{hotlist_remove_format}//; # remove hotlist_remove_format + if (grep (/\%S/,$options{hotlist_format})){ # does %S is in string? (check with original!!!) $res =~ s/%S/$short_name/; # add short_name } - if (grep (/\%N/,$hotlist_format)){ + if (grep (/\%N/,$options{hotlist_format})){ $res =~ s/%N/$buffer_number/; # add buffer_number } }else{ - if (grep (/\%S/,$hotlist_format)){ # does %S is in string? (check with original!!!) + if (grep (/\%S/,$options{hotlist_format})){ # does %S is in string? (check with original!!!) $res =~ s/%S/$short_name/; # add short_name } - if (grep (/\%N/,$hotlist_format)){ + if (grep (/\%N/,$options{hotlist_format})){ $res =~ s/%N/$buffer_number/; # add buffer_number } } - if ($res ne $hotlist_format and $res ne ""){ # did $res changed? + if ($res ne $options{hotlist_format} and $res ne ""){ # did $res changed? my $res2 = $res; # save search string. $res2=qq(\Q$res2); # kill metachars, for searching first unless (grep /^$res2$/, @table){ # does we have added $res to @table? @@ -179,16 +139,16 @@ sub create_output{ $res=qq(\Q$res); # kill metachars first if (grep /^$res$/, @table){ # does we have added $res to @table? - my $export = join("$delimiter", sort_routine(@table)); + my $export = join("$options{delimiter}", sort_routine(@table)); $export = qq(\Q$export); # escape special characters - if (grep (/\%X/,$external_command_hotlist)){ # check for %X option. + if (grep (/\%X/,$options{external_command_hotlist})){ # check for %X option. $res2 =~ s/%X/$export/; - if (grep (/\%h/,$external_command_hotlist)){ # does %h is in string? + if (grep (/\%h/,$options{external_command_hotlist})){ # does %h is in string? $res2 =~ s/%h/$weechat_dir/; # add weechat-dir } - if ($use_title eq "on"){ + if ($options{use_title} eq "on"){ weechat::window_set_title($res2); }else{ system($res2); @@ -199,7 +159,7 @@ sub create_output{ } # first sort channels with highlight, then channels with -# action and the rest will be put it at the end of list +# action and the rest will be placed at the end of list sub sort_routine { my @zeilen = @_; my @sortiert = map { $_->[0] } @@ -214,102 +174,60 @@ sub _extern{ return weechat::WEECHAT_RC_OK; } -sub init{ -# set value of script (for example starting script the first time) - if (!weechat::config_is_set_plugin("external_command_hotlist")){ - weechat::config_set_plugin("external_command_hotlist", $external_command_hotlist); - }else{ - $external_command_hotlist = weechat::config_get_plugin("external_command_hotlist"); - } - if (!weechat::config_is_set_plugin("external_command_hotlist_empty")){ - weechat::config_set_plugin("external_command_hotlist_empty", $external_command_hotlist_empty); - }else{ - $external_command_hotlist_empty = weechat::config_get_plugin("external_command_hotlist_empty"); - } - if (!weechat::config_is_set_plugin("highlight_char")){ - weechat::config_set_plugin("highlight_char", $highlight_char); - }else{ - $highlight_char = weechat::config_get_plugin("highlight_char"); - } - if (!weechat::config_is_set_plugin("lowest_priority")){ - weechat::config_set_plugin("lowest_priority", $lowest_priority); - }else{ - $lowest_priority = weechat::config_get_plugin("lowest_priority"); - } - if (!weechat::config_is_set_plugin("hotlist_format")){ - weechat::config_set_plugin("hotlist_format", $hotlist_format); - }else{ - $hotlist_format = weechat::config_get_plugin("hotlist_format"); - } - if (!weechat::config_is_set_plugin("hotlist_remove_format")){ - weechat::config_set_plugin("hotlist_remove_format", $hotlist_remove_format); - }else{ - $hotlist_remove_format = weechat::config_get_plugin("hotlist_remove_format"); - } - if (!weechat::config_is_set_plugin("priority_remove")){ - weechat::config_set_plugin("priority_remove", $priority_remove); - }else{ - $priority_remove = weechat::config_get_plugin("priority_remove"); - } - if (!weechat::config_is_set_plugin("delimiter")){ - weechat::config_set_plugin("delimiter", $delimiter); - }else{ - $delimiter = weechat::config_get_plugin("delimiter"); - } - if (!weechat::config_is_set_plugin("use_title")){ - weechat::config_set_plugin("use_title", $use_title); - }else{ - $use_title = weechat::config_get_plugin("use_title"); - } - $weechat_dir = weechat::info_get("weechat_dir", ""); +sub eval_expression{ + my ( $string ) = @_; + $string = weechat::string_eval_expression($string, {}, {},{}); + return $string; } -sub toggle_config_by_set{ -my ( $pointer, $name, $value ) = @_; +sub init_config{ + foreach my $option(keys %options){ + if (!weechat::config_is_set_plugin($option)){ + weechat::config_set_plugin($option, $options{$option}); + } + else{ + $options{$option} = weechat::config_get_plugin($option); + } + } +} - if ($name eq "plugins.var.perl.$prgname.external_command_hotlist"){ - $external_command_hotlist = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.external_command_hotlist_empty"){ - $external_command_hotlist_empty = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.highlight_char"){ - $highlight_char = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.lowest_priority"){ - $lowest_priority = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.hotlist_format"){ - $hotlist_format = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.hotlist_remove_format"){ - $hotlist_remove_format = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.priority_remove"){ - $priority_remove = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.delimiter"){ - $delimiter = $value; - return weechat::WEECHAT_RC_OK; - } - if ($name eq "plugins.var.perl.$prgname.use_title"){ - $use_title = $value; +sub toggle_config_by_set{ + my ( $pointer, $name, $value ) = @_; + $name = substr($name,length("plugins.var.perl.$SCRIPT_NAME."),length($name)); + $options{$name} = $value; return weechat::WEECHAT_RC_OK; - } } # first function called by a WeeChat-script. -weechat::register($prgname, "Nils Görs ", $version, - "GPL3", $description, "", ""); - - init(); # get user settings - - weechat::hook_signal("hotlist_changed", "hotlist_changed", ""); - weechat::hook_config( "plugins.var.perl.$prgname.*", "toggle_config_by_set", "" ); +weechat::register($SCRIPT_NAME, $SCRIPT_AUTHOR, $SCRIPT_VERSION, + "GPL3", $SCRIPT_DESC, "", ""); + +weechat::hook_command($SCRIPT_NAME, $SCRIPT_DESC, + "", + "This script allows you to export the hotlist to a file or screen title.\n". + "use the following intern variables for the hotlist_format:\n". + " %h = weechat-dir (~/.weechat), better use \${info:weechat_dir}\n". + " %H = replaces with highlight_char, if a highlight message was received. For example: *\n". + " %N = replaces with buffer number: 1 2 3 ....\n". + " %S = replaces with short name of channel: #weechat\n". + " %X = export the whole hotlist_format to your external command.\n". + "\n". + "configure script with: /fset plugins.var.perl.hotlist2extern\n". + "print hotlist to screen title: plugins.var.perl.hotlist2extern.use_title\n". + "delimiter to use : plugins.var.perl.hotlist2extern.delimiter\n". + "charset for highlight message: plugins.var.perl.hotlist2extern.highlight_char\n". + "message priority for hotlist_remove_format (-1 means off): plugins.var.perl.hotlist2extern.priority_remove\n". + "display messages level : plugins.var.perl.hotlist2extern.lowest_priority\n". + "following options are evaluated:\n". + "template for display : plugins.var.perl.hotlist2extern.hotlist_format\n". + "template for low priority : plugins.var.perl.hotlist2extern.hotlist_remove_format\n". + "Output format : plugins.var.perl.hotlist2extern.external_command_hotlist\n". + "Output format 'no activity' : plugins.var.perl.hotlist2extern.external_command_hotlist_empty\n". + "", + "", "", ""); + + +init_config(); # /set +$weechat_dir = weechat::info_get("weechat_dir", ""); +weechat::hook_signal("hotlist_changed", "hotlist_changed", ""); +weechat::hook_config( "plugins.var.perl.$SCRIPT_NAME.*", "toggle_config_by_set", "" ); From 0e5bacd46171fa56c9e9cd706d6db737adffd01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Mon, 19 Feb 2018 21:51:03 +0100 Subject: [PATCH 108/642] Remove script beep.pl (moved to unofficial scripts, not needed with WeeChat >= 1.0) --- perl/beep.pl | 257 --------------------------------------------------- 1 file changed, 257 deletions(-) delete mode 100644 perl/beep.pl diff --git a/perl/beep.pl b/perl/beep.pl deleted file mode 100644 index 69096c21..00000000 --- a/perl/beep.pl +++ /dev/null @@ -1,257 +0,0 @@ -# -# Copyright (C) 2006-2012 Sebastien Helleu -# Copyright (C) 2011 Nils Görs -# Copyright (C) 2011 ArZa -# -# 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 . -# -# -# Beep (terminal bell) and/or run command on highlight/private message or new DCC. -# -# History: -# 2012-06-05, ldvx: -# version 1.1: Added wildcard support for whitelist_nicks. -# 2012-05-09, ldvx : -# version 1.0: Added beep_pv_blacklist, beep_highlight_blacklist, blacklist_nicks, -# and wildcard support for blacklist_nicks. -# 2012-05-02, ldvx : -# version 0.9: fix regex for nick in tags, add options "whitelist_channels" -# and "bell_always" -# 2012-04-19, ldvx : -# version 0.8: add whitelist, trigger, use hook_process for commands, -# rename option "beep_command" to "beep_command_pv", add help for options -# 2011-04-16, ArZa : -# version 0.7: fix default beep command -# 2011-03-11, nils_2 : -# version 0.6: add additional command options for dcc and highlight -# 2011-03-09, nils_2 : -# version 0.5: add option for beep command and dcc -# 2009-05-02, Sebastien Helleu : -# version 0.4: sync with last API changes -# 2008-11-05, Sebastien Helleu : -# version 0.3: conversion to WeeChat 0.3.0+ -# 2007-08-10, Sebastien Helleu : -# version 0.2: upgraded licence to GPL 3 -# 2006-09-02, Sebastien Helleu : -# version 0.1: initial release - -use strict; -my $SCRIPT_NAME = "beep"; -my $VERSION = "1.1"; - -# default values in setup file (~/.weechat/plugins.conf) -my %options_default = ('beep_pv' => ['on', 'beep on private message'], - 'beep_pv_whitelist' => ['off', 'turn whitelist for private messages on or off'], - 'beep_pv_blacklist' => ['off', 'turn blacklist for private messages on or off'], - 'beep_highlight' => ['on', 'beep on highlight'], - 'beep_highlight_whitelist' => ['off', 'turn whitelist for highlights on or off'], - 'beep_highlight_blacklist' => ['off', 'turn blacklist for highlights on or off'], - 'beep_dcc' => ['on', 'beep on dcc'], - 'beep_trigger_pv' => ['', 'word that will trigger execution of beep_command_pv (it empty, anything will trigger)'], - 'beep_trigger_highlight' => ['', 'word that will trigger execution of beep_command_highlight (if empty, anything will trigger)'], - 'beep_command_pv' => ['$bell', 'command for beep on private message, special value "$bell" is allowed, as well as "$bell;command"'], - 'beep_command_highlight' => ['$bell', 'command for beep on highlight, special value "$bell" is allowed, as well as "$bell;command"'], - 'beep_command_dcc' => ['$bell', 'command for beep on dcc, special value "$bell" is allowed, as well as "$bell;command"'], - 'beep_command_timeout' => ['30000', 'timeout for command run (in milliseconds, 0 = never kill (not recommended))'], - 'whitelist_nicks' => ['', 'comma-separated list of "server.nick": if not empty, only these nicks will trigger execution of commands (example: "freenode.nick1,freenode.nick2")'], - 'blacklist_nicks' => ['', 'comma-separated list of "server.nick": if not empty, these nicks will not be able to trigger execution of commands. Cannot be used in conjuction with whitelist (example: "freenode.nick1,freenode.nick2")'], - 'whitelist_channels' => ['', 'comma-separated list of "server.#channel": if not empty, only these channels will trigger execution of commands (example: "freenode.#weechat,freenode.#channel2")'], - 'bell_always' => ['', 'use $bell on private messages and/or highlights regardless of trigger and whitelist settings (example: "pv,highlight")'], -); -my %options = (); - -weechat::register($SCRIPT_NAME, "FlashCode ", $VERSION, - "GPL3", "Beep (terminal bell) and/or run command on highlight/private message or new DCC", "", ""); -init_config(); - -weechat::hook_config("plugins.var.perl.$SCRIPT_NAME.*", "toggle_config_by_set", ""); -weechat::hook_print("", "", "", 1, "pv_and_highlight", ""); -weechat::hook_signal("irc_dcc", "dcc", ""); - -sub pv_and_highlight -{ - my ($data, $buffer, $date, $tags, $displayed, $highlight, $prefix, $message) = @_; - - # return if message is filtered - return weechat::WEECHAT_RC_OK if ($displayed != 1); - - # return if nick in message is own nick - my $nick = ""; - $nick = $2 if ($tags =~ m/(^|,)nick_([^,]*)(,|$)/); - return weechat::WEECHAT_RC_OK if (weechat::buffer_get_string($buffer, "localvar_nick") eq $nick); - - # highlight - if ($highlight) - { - # Always print visual bel, regardless of whitelist and trigger settings - # beep_command_highlight does not need to contain $bell - if ($options{bell_always} =~ m/(^|,)highlight(,|$)/) - { - print STDERR "\a"; - } - # Channels whitelist for highlights - if ($options{beep_highlight} eq "on") - { - if ($options{whitelist_channels} ne "") - { - my $serverandchannel = weechat::buffer_get_string($buffer, "localvar_server"). "." . - weechat::buffer_get_string($buffer, "localvar_channel"); - if ($options{beep_trigger_highlight} eq "" or $message =~ m/\b$options{beep_trigger_highlight}\b/) - { - if ($options{whitelist_channels} =~ /(^|,)$serverandchannel(,|$)/) - { - beep_exec_command($options{beep_command_highlight}); - } - # What if we are highlighted and we're in a PM? For now, do nothing. - } - } - else - { - # Execute $bell and/or command with trigger and whitelist/blacklist settings - beep_trigger_whitelist_blacklist($buffer, $message, $nick, $options{beep_trigger_highlight}, - $options{beep_highlight_whitelist}, $options{beep_highlight_blacklist}, - $options{beep_command_highlight}); - } - } - } - # private message - elsif (weechat::buffer_get_string($buffer, "localvar_type") eq "private" and $tags =~ m/(^|,)notify_private(,|$)/) - { - # Always print visual bel, regardless of whitelist and trigger settings - # beep_command_pv does not need to contain $bell - if ($options{bell_always} =~ m/(^|,)pv(,|$)/) - { - print STDERR "\a"; - } - # Execute $bell and/or command with trigger and whitelist/blacklist settings - if ($options{beep_pv} eq "on") - { - beep_trigger_whitelist_blacklist($buffer, $message, $nick, $options{beep_trigger_pv}, - $options{beep_pv_whitelist}, $options{beep_pv_blacklist}, - $options{beep_command_pv}); - } - } - return weechat::WEECHAT_RC_OK; -} - -sub dcc -{ - beep_exec_command($options{beep_command_dcc}) if ($options{beep_dcc} eq "on"); - return weechat::WEECHAT_RC_OK; -} - -sub beep_trigger_whitelist_blacklist -{ - my ($buffer, $message, $nick, $trigger, $whitelist, $blacklist, $command) = @_; - - if ($trigger eq "" or $message =~ m/\b$trigger\b/) - { - my $serverandnick = weechat::buffer_get_string($buffer, "localvar_server").".".$nick; - if ($whitelist eq "on" and $options{whitelist_nicks} ne "") - { - # What to do if there's a wildcard in the whitelit - if ($options{whitelist_nicks} =~ m/\*/ and - match_in_wild_card($serverandnick, $options{whitelist_nicks})) - { - beep_exec_command($command); - } - # What to do if there's no wildcard in the whitelist - elsif ($options{whitelist_nicks} =~ /(^|,)$serverandnick(,|$)/) - { - beep_exec_command($command); - } - } - elsif ($blacklist eq "on" and $options{blacklist_nicks} ne "") - { - # What to do if there's a wildcard in the blacklist - if ($options{blacklist_nicks} =~ m/\*/ and - !match_in_wild_card($serverandnick, $options{blacklist_nicks})) - { - beep_exec_command($command); - } - # What to do if there's no wildcard in the blacklist - elsif ($options{blacklist_nicks} !~ /(^|,)$serverandnick(,|$)/) - { - beep_exec_command($command); - } - } - # What to do if we are not using whitelist of blacklist feature - elsif ($whitelist eq "off" and $blacklist eq "off") - { - beep_exec_command($command); - } - } -} - -sub beep_exec_command -{ - my $command = $_[0]; - if ($command =~ /^\$bell/) - { - print STDERR "\a"; - ($command) = $command =~ /^\$bell;(.+)$/; - } - weechat::hook_process($command, $options{beep_command_timeout}, "my_process", "") if ($command); -} - -sub match_in_wild_card -{ - my ($serverandnick, $white_or_black) = @_; - my $nick_iter; - my @array_of_nicks = split(",", $white_or_black); - - foreach $nick_iter (@array_of_nicks) - { - $nick_iter =~ s/\*/[^,]*/g; - if ($serverandnick =~ /$nick_iter/) - { - return 1; - } - } - return 0; -} - -sub my_process -{ - return weechat::WEECHAT_RC_OK; -} - -sub toggle_config_by_set -{ - my ($pointer, $name, $value) = @_; - $name = substr($name, length("plugins.var.perl.".$SCRIPT_NAME."."), length($name)); - $options{$name} = $value; - return weechat::WEECHAT_RC_OK; -} - -sub init_config -{ - my $version = weechat::info_get("version_number", "") || 0; - foreach my $option (keys %options_default) - { - if (!weechat::config_is_set_plugin($option)) - { - weechat::config_set_plugin($option, $options_default{$option}[0]); - $options{$option} = $options_default{$option}[0]; - } - else - { - $options{$option} = weechat::config_get_plugin($option); - } - if ($version >= 0x00030500) - { - weechat::config_set_desc_plugin($option, $options_default{$option}[1]." (default: \"".$options_default{$option}[0]."\")"); - } - } -} From 4360a011d6c84430efa2856017628ea054c8c771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Mon, 19 Feb 2018 21:55:01 +0100 Subject: [PATCH 109/642] Remove script shell.py (moved to unofficial scripts, not needed with WeeChat >= 1.0) --- python/shell.py | 305 ------------------------------------------------ 1 file changed, 305 deletions(-) delete mode 100644 python/shell.py diff --git a/python/shell.py b/python/shell.py deleted file mode 100644 index 145cf6ca..00000000 --- a/python/shell.py +++ /dev/null @@ -1,305 +0,0 @@ -# -*- coding: utf-8 -*- -# ============================================================================= -# shell.py (c) March 2006, 2009 by Kolter -# -# Licence : GPL v2 -# Description : running shell commands in WeeChat -# Syntax : try /help shell to get some help on this script -# Precond : needs weechat >= 0.3.0 to run -# -# -# ### changelog ### -# -# * version 0.8, 2013-07-27, Sebastien Helleu : -# - don't remove empty lines in output of command -# * version 0.7, 2012-11-26, Sebastien Helleu : -# - use hashtable for command arguments (for WeeChat >= 0.4.0) -# * version 0.6, 2012-11-21, Sebastien Helleu : -# - call shell in hook_process (WeeChat >= 0.3.9.2 does not call shell any more) -# * version 0.5, 2011-10-01, Sebastien Helleu : -# - add shell buffer -# * version 0.4, 2009-05-02, Sebastien Helleu : -# - sync with last API changes -# * version 0.3, 2009-03-06, Sebastien Helleu : -# - use of hook_process to run background process -# - add option -t to kill process after seconds -# - show process running, kill it with -kill -# * version 0.2, 2009-01-31, Sebastien Helleu : -# - conversion to WeeChat 0.3.0+ -# * version 0.1, 2006-03-13, Kolter : -# - first release -# -# ============================================================================= - -import weechat, os, datetime - -SCRIPT_NAME = 'shell' -SCRIPT_AUTHOR = 'Kolter' -SCRIPT_VERSION = '0.8' -SCRIPT_LICENSE = 'GPL2' -SCRIPT_DESC = 'Run shell commands in WeeChat' - -SHELL_CMD = 'shell' -SHELL_PREFIX = '[shell] ' - -cmd_hook_process = '' -cmd_command = '' -cmd_start_time = None -cmd_buffer = '' -cmd_shell_buffer = '' -cmd_stdout = '' -cmd_stderr = '' -cmd_send_to_buffer = '' -cmd_timeout = 0 - -if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, '', ''): - weechat.hook_command( - SHELL_CMD, - 'Running shell commands in WeeChat', - '[-o|-n] [-t seconds] || -show || -kill', - ' -o: send output to current buffer (simulate user entry ' - 'with command output - dangerous, be careful when using this option)\n' - ' -n: display output in a new empty buffer\n' - '-t seconds: auto-kill process after timeout (seconds) if process ' - 'is still running\n' - ' command: shell command or builtin like cd, getenv, setenv, unsetenv\n' - ' -show: show running process\n' - ' -kill: kill running process', - '-o|-n|-t|cd|getenv|setenv|unsetenv|-show||-kill -o|-n|-t|cd|getenv|setenv|unsetenv', - 'shell_cmd', '') - -def shell_init(): - """Initialize some variables.""" - global cmd_hook_process, cmd_command, cmd_start_time, cmd_buffer, cmd_stdout, cmd_stderr - cmd_hook_process = '' - cmd_command = '' - cmd_start_time = None - cmd_buffer = '' - cmd_stdout = '' - cmd_stderr = '' - -def shell_set_title(): - """Set title on shell buffer (with working directory).""" - global cmd_shell_buffer - if cmd_shell_buffer: - weechat.buffer_set(cmd_shell_buffer, 'title', - '%s.py %s | "q": close buffer | Working dir: %s' % (SCRIPT_NAME, SCRIPT_VERSION, os.getcwd())) - -def shell_process_cb(data, command, rc, stdout, stderr): - """Callback for hook_process().""" - global cmd_hook_process, cmd_buffer, cmd_stdout, cmd_stderr, cmd_send_to_buffer - cmd_stdout += stdout - cmd_stderr += stderr - if int(rc) >= 0: - if cmd_stdout: - lines = cmd_stdout.rstrip().split('\n') - if cmd_send_to_buffer == 'current': - for line in lines: - weechat.command(cmd_buffer, '%s' % line) - else: - weechat.prnt(cmd_buffer, '') - if cmd_send_to_buffer != 'new': - weechat.prnt(cmd_buffer, '%sCommand "%s" (rc %d), stdout:' - % (SHELL_PREFIX, data, int(rc))) - for line in lines: - weechat.prnt(cmd_buffer, ' \t%s' % line) - if cmd_stderr: - lines = cmd_stderr.rstrip().split('\n') - if cmd_send_to_buffer == 'current': - for line in lines: - weechat.command(cmd_buffer, '%s' % line) - else: - weechat.prnt(cmd_buffer, '') - if cmd_send_to_buffer != 'new': - weechat.prnt(cmd_buffer, '%s%sCommand "%s" (rc %d), stderr:' - % (weechat.prefix('error'), SHELL_PREFIX, data, int(rc))) - for line in lines: - weechat.prnt(cmd_buffer, '%s%s' % (weechat.prefix('error'), line)) - cmd_hook_process = '' - shell_set_title() - return weechat.WEECHAT_RC_OK - -def shell_show_process(buffer): - """Show running process.""" - global cmd_command, cmd_start_time - if cmd_hook_process: - weechat.prnt(buffer, '%sprocess running: "%s" (started on %s)' - % (SHELL_PREFIX, cmd_command, cmd_start_time.ctime())) - else: - weechat.prnt(buffer, '%sno process running' % SHELL_PREFIX) - -def shell_kill_process(buffer): - """Kill running process.""" - global cmd_hook_process, cmd_command - if cmd_hook_process: - weechat.unhook(cmd_hook_process) - weechat.prnt(buffer, '%sprocess killed (command "%s")' % (SHELL_PREFIX, cmd_command)) - shell_init() - else: - weechat.prnt(buffer, '%sno process running' % SHELL_PREFIX) - -def shell_chdir(buffer, directory): - """Change working directory.""" - if not directory: - if os.environ.has_key('HOME'): - directory = os.environ['HOME'] - try: - os.chdir(directory) - except: - weechat.prnt(buffer, '%san error occured while running command "cd %s"' % (SHELL_PREFIX, directory)) - else: - weechat.prnt(buffer, '%schdir to "%s" ok, new path: %s' % (SHELL_PREFIX, directory, os.getcwd())) - shell_set_title() - -def shell_getenv(buffer, var): - """Get environment variable.""" - global cmd_send_to_buffer - var = var.strip() - if not var: - weechat.prnt(buffer, '%swrong syntax, try "getenv VAR"' % (SHELL_PREFIX)) - return - - value = os.getenv(var) - if value == None: - weechat.prnt(buffer, '%s$%s is not set' % (SHELL_PREFIX, var)) - else: - if cmd_send_to_buffer == 'current': - weechat.command(buffer, '$%s=%s' % (var, os.getenv(var))) - else: - weechat.prnt(buffer, '%s$%s=%s' % (SHELL_PREFIX, var, os.getenv(var))) - -def shell_setenv(buffer, expr): - """Set an environment variable.""" - global cmd_send_to_buffer - expr = expr.strip() - lexpr = expr.split('=') - - if (len(lexpr) < 2): - weechat.prnt(buffer, '%swrong syntax, try "setenv VAR=VALUE"' % (SHELL_PREFIX)) - return - - os.environ[lexpr[0].strip()] = '='.join(lexpr[1:]) - if cmd_send_to_buffer != 'current': - weechat.prnt(buffer, '%s$%s is now set to "%s"' % (SHELL_PREFIX, lexpr[0], '='.join(lexpr[1:]))) - -def shell_unsetenv(buffer, var): - """Remove environment variable.""" - var = var.strip() - if not var: - weechat.prnt(buffer, '%swrong syntax, try "unsetenv VAR"' % (SHELL_PREFIX)) - return - - if os.environ.has_key(var): - del os.environ[var] - weechat.prnt(buffer, '%s$%s is now unset' % (SHELL_PREFIX, var)) - else: - weechat.prnt(buffer, '%s$%s is not set' % (SHELL_PREFIX, var)) - -def shell_exec(buffer, command): - """Execute a command.""" - global cmd_hook_process, cmd_command, cmd_start_time, cmd_buffer - global cmd_stdout, cmd_stderr, cmd_send_to_buffer, cmd_timeout - if cmd_hook_process: - weechat.prnt(buffer, - '%sanother process is running! (use "/%s -kill" to kill it)' - % (SHELL_PREFIX, SHELL_CMD)) - return - if cmd_send_to_buffer == 'new': - weechat.prnt(buffer, '-->\t%s%s$ %s%s' - % (weechat.color('chat_buffer'), os.getcwd(), weechat.color('reset'), command)) - weechat.prnt(buffer, '') - args = command.split(' ') - if args[0] == 'cd': - shell_chdir(buffer, ' '.join(args[1:])) - elif args[0] == 'getenv': - shell_getenv (buffer, ' '.join(args[1:])) - elif args[0] == 'setenv': - shell_setenv (buffer, ' '.join(args[1:])) - elif args[0] == 'unsetenv': - shell_unsetenv (buffer, ' '.join(args[1:])) - else: - shell_init() - cmd_command = command - cmd_start_time = datetime.datetime.now() - cmd_buffer = buffer - version = weechat.info_get("version_number", "") or 0 - if int(version) >= 0x00040000: - cmd_hook_process = weechat.hook_process_hashtable('sh', { 'arg1': '-c', 'arg2': command }, - cmd_timeout * 1000, 'shell_process_cb', command) - else: - cmd_hook_process = weechat.hook_process("sh -c '%s'" % command, cmd_timeout * 1000, 'shell_process_cb', command) - -def shell_input_buffer(data, buffer, input): - """Input callback on shell buffer.""" - global cmd_send_to_buffer - if input in ('q', 'Q'): - weechat.buffer_close(buffer) - return weechat.WEECHAT_RC_OK - cmd_send_to_buffer = 'new' - weechat.prnt(buffer, '') - command = weechat.string_input_for_buffer(input) - shell_exec(buffer, command) - return weechat.WEECHAT_RC_OK - -def shell_close_buffer(data, buffer): - """Close callback on shell buffer.""" - global cmd_shell_buffer - cmd_shell_buffer = '' - return weechat.WEECHAT_RC_OK - -def shell_new_buffer(): - """Create shell buffer.""" - global cmd_shell_buffer - cmd_shell_buffer = weechat.buffer_search('python', 'shell') - if not cmd_shell_buffer: - cmd_shell_buffer = weechat.buffer_new('shell', 'shell_input_buffer', '', 'shell_close_buffer', '') - if cmd_shell_buffer: - shell_set_title() - weechat.buffer_set(cmd_shell_buffer, 'localvar_set_no_log', '1') - weechat.buffer_set(cmd_shell_buffer, 'time_for_each_line', '0') - weechat.buffer_set(cmd_shell_buffer, 'input_get_unknown_commands', '1') - weechat.buffer_set(cmd_shell_buffer, 'display', '1') - return cmd_shell_buffer - -def shell_cmd(data, buffer, args): - """Callback for /shell command.""" - global cmd_send_to_buffer, cmd_timeout - largs = args.split(' ') - - # strip spaces - while '' in largs: - largs.remove('') - while ' ' in largs: - largs.remove(' ') - - cmdbuf = buffer - - if len(largs) == 0: - shell_new_buffer() - else: - if largs[0] == '-show': - shell_show_process(cmdbuf) - elif largs[0] == '-kill': - shell_kill_process(cmdbuf) - else: - cmd_send_to_buffer = '' - cmd_timeout = 0 - while largs: - if largs[0] == '-o': - cmd_send_to_buffer = 'current' - largs = largs[1:] - continue - if largs[0] == '-n': - cmd_send_to_buffer = 'new' - cmdbuf = shell_new_buffer() - largs = largs[1:] - continue - if largs[0] == '-t' and len(largs) > 2: - cmd_timeout = int(largs[1]) - largs = largs[2:] - continue - break - if len(largs) > 0: - shell_exec(cmdbuf, ' '.join(largs)) - return weechat.WEECHAT_RC_OK From 205d280adce6b7cabef60a81da8112b54c45d038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Mon, 19 Feb 2018 22:12:47 +0100 Subject: [PATCH 110/642] Remove script allquery.py (moved to unofficial scripts, not needed with WeeChat >= 1.0) --- python/allquery.py | 150 --------------------------------------------- 1 file changed, 150 deletions(-) delete mode 100644 python/allquery.py diff --git a/python/allquery.py b/python/allquery.py deleted file mode 100644 index e789bd6a..00000000 --- a/python/allquery.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2011-2013 by F. Besser -# -# 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 . -# -# -# History: -# 2013-09-01, nils_2@freenode.#weechat: -# version 0.2: add support of servername for "-exclude" -# : make script behave like /allchan and /allserver command -# : add function "-current" -# : case-insensitive search for query/server -# -# 2011-09-05, F. Besser : -# version 0.1: script created -# -# Development is on: -# https://github.com/fbesser/weechat_scripts -# -# (this script requires WeeChat 0.3.0 or newer) -# - - -SCRIPT_NAME = "allquery" -SCRIPT_AUTHOR = "fbesser" -SCRIPT_VERSION = "0.2" -SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Executes command on all irc query buffer" - -SCRIPT_COMMAND = "allquery" - -import_ok = True - -try: - import weechat -except ImportError: - print('This script must be run under WeeChat.') - print('Get WeeChat now at: http://www.weechat.org/') - import_ok = False - -try: - import re -except ImportError, message: - print('Missing package(s) for %s: %s' % (SCRIPT_NAME, message)) - import_ok = False - - -def make_list(argument): - """ Make a list out of argument string of format -argument=value0,value1""" - arglist = argument.lower().split("=", 1) - arguments = arglist[1].split(",") - return arguments - -def allquery_command_cb(data, buffer, args): - """ Callback for /allquery command """ - args = args.strip() - if args == "": - weechat.command("", "/help %s" % SCRIPT_COMMAND) - return weechat.WEECHAT_RC_OK - argv = args.split(" ") - - exclude_nick = None - current_server = None - - if '-current' in argv: - current_server = weechat.buffer_get_string(weechat.current_buffer(), "localvar_server") - # remove "-current" + whitespace from argumentlist - args = args.replace("-current", "") - args = args.lstrip() - argv.remove("-current") - - # search for "-exclude" in arguments - i = 0 - for entry in argv[0:]: - if entry.startswith("-exclude="): - exclude_nick = make_list(argv[i]) - command = " ".join(argv[i+1::]) - break - i +=1 - else: - command = args - - # no command found. - if not command: - return weechat.WEECHAT_RC_OK - - if not command.startswith("/"): - command = "/%s" % command - - infolist = weechat.infolist_get("buffer", "", "") - while weechat.infolist_next(infolist): - if weechat.infolist_string(infolist, "plugin_name") == "irc": - ptr = weechat.infolist_pointer(infolist, "pointer") - server = weechat.buffer_get_string(ptr, "localvar_server") - query = weechat.buffer_get_string(ptr, "localvar_channel") - execute_command = re.sub(r'\$nick', query, command) - if weechat.buffer_get_string(ptr, "localvar_type") == "private": - if current_server is not None: - if server == current_server: - exclude_nick_and_server(ptr,query,server,exclude_nick,execute_command) - else: - exclude_nick_and_server(ptr,query,server,exclude_nick,execute_command) - weechat.infolist_free(infolist) - return weechat.WEECHAT_RC_OK - - -def exclude_nick_and_server(ptr, query, server, exclude_nick, execute_command): - server = "%s.*" % server # servername + ".*" - if exclude_nick is not None: - if not query.lower() in exclude_nick and not server.lower() in exclude_nick: - weechat.command(ptr, execute_command) - else: - weechat.command(ptr, execute_command) - - -if __name__ == '__main__' and import_ok: - if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, - SCRIPT_LICENSE, SCRIPT_DESC, "", ""): - - weechat.hook_command(SCRIPT_COMMAND, SCRIPT_DESC, - '[-current] [-exclude=[,...]] []', - ' -current: execute command for query of current server only\n' - ' -exclude: exclude some querys and/or server from executed command\n' - ' command: command executed in query buffers\n' - ' arguments: arguments for command (special variables $nick will be replaced by its value)\n\n' - 'Examples:\n' - ' close all query buffers:\n' - ' /' + SCRIPT_COMMAND + ' buffer close\n' - ' close all query buffers, but don\'t close FlashCode:\n' - ' /' + SCRIPT_COMMAND + ' -exclude=FlashCode buffer close\n' - ' close all query buffers, except for server freenode:\n' - ' /' + SCRIPT_COMMAND + ' -exclude=freenode.* buffer close\n' - ' msg to all query buffers:\n' - ' /' + SCRIPT_COMMAND + ' say Hello\n' - ' notice to all query buffers:\n' - ' /' + SCRIPT_COMMAND + ' notice $nick Hello', - '%(commands)', - 'allquery_command_cb', '') From acf05c13950cb77853215cf368360fa27ad684e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Mon, 19 Feb 2018 22:19:35 +0100 Subject: [PATCH 111/642] Remove script echo.pl (moved to unofficial scripts, not needed with WeeChat >= 0.4.3) --- perl/echo.pl | 92 ---------------------------------------------------- 1 file changed, 92 deletions(-) delete mode 100644 perl/echo.pl diff --git a/perl/echo.pl b/perl/echo.pl deleted file mode 100644 index b0b812ee..00000000 --- a/perl/echo.pl +++ /dev/null @@ -1,92 +0,0 @@ -# echo.pl by ArZa : Print a line and additionally set activity level - -# This program is free software: you can modify/redistribute it under the terms of -# GNU General Public License by Free Software Foundation, either version 3 or later -# which you can get from . -# This program is distributed in the hope that it will be useful, but without any warranty. - -weechat::register("echo", "ArZa ", "0.1", "GPL3", "Print a line and additionally set activity level", "", ""); -weechat::hook_command( - "echo", - "Print a line and additionally set activity level. Local variables are expanded when starting with \$ and can be escaped with \\.", - "[ -p/-plugin ] [ -b/-buffer | -c/-core ] [ -l/-level ] [text]", -"-plugin: plugin where printed, default: current plugin --buffer: buffer where printed, default: current buffer, e.g. #weechat or freenode.#weechat) - -core: print to the core buffer - -level: number of the activity level, default: low: - 0=low, 1=message, 2=private, 3=highlight -Examples: - /echo This is a test message - /echo -b freenode.#weechat -level 3 Highlight! - /echo -core This goes to the core buffer - /echo -buffer #weechat -l 1 My variable \\\$name is \$name on \$channel", - "-buffer %(buffer_names) || -core || -level 1|2|3 || -plugin %(plugins_names)", "echo", "" - -); - -sub echo { - - my @args=split(/ /, $_[2]); - my $i=0; - my ($plugin, $buffer, $level) = ("", "", ""); - - while($i<=$#args){ # go through command options - if($args[$i] eq "-b" || $args[$i] eq "-buffer"){ - $i++; - $buffer=$args[$i] if $args[$i]; - }elsif($args[$i] eq "-p" || $args[$i] eq "-plugin"){ - $i++; - $plugin=$args[$i] if $args[$i]; - }elsif($args[$i] eq "-c" || $args[$i] eq "-core"){ - $buffer=weechat::buffer_search_main(); - }elsif($args[$i] eq "-l" || $args[$i] eq "-level"){ - $i++; - $level=$args[$i] if $args[$i]; - }else{ - last; - } - $i++; - } - - if($plugin ne ""){ # use specific plugin if set - $buffer=weechat::buffer_search($plugin, $buffer); - }elsif($buffer ne ""){ - if($buffer=~/^\d+$/){ # if got a number - my $infolist = weechat::infolist_get("buffer", "", ""); - while(weechat::infolist_next($infolist)){ # find the buffer for the number - if(weechat::infolist_integer($infolist, "number") eq $buffer){ - $buffer=weechat::buffer_search( weechat::infolist_string($infolist, "plugin"), weechat::infolist_string($infolist, "name") ); - last; - } - } - weechat::infolist_free($infolist); - }elsif( weechat::buffer_search ( weechat::buffer_get_string( weechat::current_buffer(), "plugin" ), $buffer ) ){ # if buffer found in current plugin - $buffer=weechat::buffer_search ( weechat::buffer_get_string( weechat::current_buffer(), "plugin" ), $buffer ); - }else{ # search even more to find the correct buffer - my $infolist = weechat::infolist_get("buffer", "", ""); - while(weechat::infolist_next($infolist)){ # find the buffer for a short_name - if(lc(weechat::infolist_string($infolist, "short_name")) eq lc($buffer)){ - $buffer=weechat::buffer_search( weechat::infolist_string($infolist, "plugin"), weechat::infolist_string($infolist, "name") ); - last; - } - } - weechat::infolist_free($infolist); - } - } - $buffer=weechat::current_buffer() if $buffer eq "" || $buffer eq $args[$i-1]; # otherwise use the current buffer - - my $j=$i; - $args[$j]=~s/^\\\-/-/ if $args[$j]; # "\-" -> "-" in the beginning - while($j<=$#args){ # go through text - if($args[$j]=~/^\$/){ # replace variables - $args[$j]=weechat::buffer_string_replace_local_var($buffer, $args[$j]); - }elsif($args[$j]=~/^\\[\$\\]/){ # escape variables - $args[$j]=~s/^\\//; - } - $j++; - } - - weechat::print($buffer, join(' ', @args[$i..$#args])); # print the text - weechat::buffer_set($buffer, "hotlist", $level); # set hotlist level - -} From e957b792d748c0849bec338e85c0e8c65ce20b72 Mon Sep 17 00:00:00 2001 From: Brady Trainor Date: Fri, 2 Mar 2018 23:09:13 -0800 Subject: [PATCH 112/642] automerge.py 0.2: fix the merge command --- python/automerge.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/automerge.py b/python/automerge.py index 101d8d7f..5c6ccb6b 100644 --- a/python/automerge.py +++ b/python/automerge.py @@ -21,6 +21,8 @@ History: * 2017-03-22, Ricky Brent : version 0.1: initial release + * 2018-03-02, Brady Trainor : + version 0.2: fix the merge command """ from __future__ import print_function @@ -32,7 +34,7 @@ print('Script must be run under weechat. http://www.weechat.org') IMPORT_OK = False -VERSION = '0.1' +VERSION = '0.2' NAME = 'automerge' AUTHOR = 'Ricky Brent ' DESC = 'Merge new irc buffers according to defined rules.' @@ -93,7 +95,7 @@ def cb_signal_apply_rules(data, signal, buf): if re.match(pattern, name): mid = find_merge_id(buf, merge) if mid >= 0: - weechat.command(buf, "/merge " + str(mid)) + weechat.command(buf, "/buffer merge " + str(mid)) return weechat.WEECHAT_RC_OK def cb_command(data, buf, args): From d8ecd2d10dd05d9b85863c19379b31d60e291dec Mon Sep 17 00:00:00 2001 From: Trygve Aaberge Date: Sat, 3 Mar 2018 16:38:17 +0100 Subject: [PATCH 113/642] slack.py 2.0.0: Merge with mainline This is version 2.0.0 of slack.py (currently the most recent), copied over from the wee-slack repo. --- python/slack.py | 5315 ++++++++++++++++++++++++++++++----------------- 1 file changed, 3365 insertions(+), 1950 deletions(-) diff --git a/python/slack.py b/python/slack.py index fab8a079..0e15d135 100644 --- a/python/slack.py +++ b/python/slack.py @@ -1,1679 +1,2825 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2014-1016 Ryan Huber +# Copyright (c) 2014-2018 Ryan Huber +# Copyright (c) 2015-2018 Tollef Fog Heen # Released under the MIT license. # +from __future__ import unicode_literals + +from collections import OrderedDict from functools import wraps +from itertools import islice + +import textwrap import time import json -import os import pickle import sha +import os import re import urllib -import HTMLParser import sys import traceback import collections import ssl +import random +import string -from websocket import create_connection,WebSocketConnectionClosedException +from websocket import create_connection, WebSocketConnectionClosedException # hack to make tests possible.. better way? try: - import weechat as w + import weechat except: pass SCRIPT_NAME = "slack" SCRIPT_AUTHOR = "Ryan Huber " -SCRIPT_VERSION = "0.99.10" +SCRIPT_VERSION = "2.0.0" SCRIPT_LICENSE = "MIT" SCRIPT_DESC = "Extends weechat for typing notification/search/etc on slack.com" BACKLOG_SIZE = 200 SCROLLBACK_SIZE = 500 -CACHE_VERSION = "4" +RECORD_DIR = "/tmp/weeslack-debug" SLACK_API_TRANSLATOR = { "channel": { "history": "channels.history", - "join": "channels.join", - "leave": "channels.leave", + "join": "conversations.join", + "leave": "conversations.leave", "mark": "channels.mark", "info": "channels.info", }, "im": { "history": "im.history", - "join": "im.open", - "leave": "im.close", + "join": "conversations.open", + "leave": "conversations.close", "mark": "im.mark", }, + "mpim": { + "history": "mpim.history", + "join": "mpim.open", # conversations.open lacks unread_count_display + "leave": "conversations.close", + "mark": "mpim.mark", + "info": "groups.info", + }, "group": { "history": "groups.history", - "join": "channels.join", - "leave": "groups.leave", + "join": "conversations.join", + "leave": "conversations.leave", "mark": "groups.mark", + "info": "groups.info" + }, + "thread": { + "history": None, + "join": None, + "leave": None, + "mark": None, } + } -NICK_GROUP_HERE = "0|Here" -NICK_GROUP_AWAY = "1|Away" +###### Decorators have to be up here -sslopt_ca_certs = {} -if hasattr(ssl, "get_default_verify_paths") and callable(ssl.get_default_verify_paths): - ssl_defaults = ssl.get_default_verify_paths() - if ssl_defaults.cafile is not None: - sslopt_ca_certs = {'ca_certs': ssl_defaults.cafile} -def dbg(message, fout=False, main_buffer=False): +def slack_buffer_or_ignore(f): """ - send debug output to the slack-debug buffer and optionally write to a file. + Only run this function if we're in a slack buffer, else ignore """ - message = "DEBUG: {}".format(message) - #message = message.encode('utf-8', 'replace') - if fout: - file('/tmp/debug.log', 'a+').writelines(message + '\n') - if main_buffer: - w.prnt("", "slack: " + message) - else: - if slack_debug is not None: - w.prnt(slack_debug, message) + @wraps(f) + def wrapper(data, current_buffer, *args, **kwargs): + if current_buffer not in EVENTROUTER.weechat_controller.buffers: + return w.WEECHAT_RC_OK + return f(data, current_buffer, *args, **kwargs) + return wrapper -class SearchList(list): +def slack_buffer_required(f): """ - A normal python list with some syntactic sugar for searchability + Only run this function if we're in a slack buffer, else print error """ - def __init__(self): - self.hashtable = {} - super(SearchList, self).__init__(self) - - def find(self, name): - if name in self.hashtable: - return self.hashtable[name] - #this is a fallback to __eq__ if the item isn't in the hashtable already - if self.count(name) > 0: - self.update_hashtable() - return self[self.index(name)] - - def append(self, item, aliases=[]): - super(SearchList, self).append(item) - self.update_hashtable() - - def update_hashtable(self): - for child in self: - if hasattr(child, "get_aliases"): - for alias in child.get_aliases(): - if alias is not None: - self.hashtable[alias] = child - - def find_by_class(self, class_name): - items = [] - for child in self: - if child.__class__ == class_name: - items.append(child) - return items - - def find_by_class_deep(self, class_name, attribute): - items = [] - for child in self: - if child.__class__ == self.__class__: - items += child.find_by_class_deep(class_name, attribute) - else: - items += (eval('child.' + attribute).find_by_class(class_name)) - return items + @wraps(f) + def wrapper(data, current_buffer, *args, **kwargs): + if current_buffer not in EVENTROUTER.weechat_controller.buffers: + return w.WEECHAT_RC_ERROR + return f(data, current_buffer, *args, **kwargs) + return wrapper -class SlackServer(object): +def utf8_decode(f): """ - Root object used to represent connection and state of the connection to a slack group. + Decode all arguments from byte strings to unicode strings. Use this for + functions called from outside of this script, e.g. callbacks from weechat. """ - def __init__(self, token): - self.nick = None - self.name = None - self.team = None - self.domain = None - self.server_buffer_name = None - self.login_data = None - self.buffer = None - self.token = token - self.ws = None - self.ws_hook = None - self.users = SearchList() - self.bots = SearchList() - self.channels = SearchList() - self.connecting = False - self.connected = False - self.connection_attempt_time = 0 - self.communication_counter = 0 - self.message_buffer = {} - self.ping_hook = None - self.alias = None - - self.identifier = None - self.connect_to_slack() - - def __eq__(self, compare_str): - if compare_str == self.identifier or compare_str == self.token or compare_str == self.buffer: - return True - else: - return False + @wraps(f) + def wrapper(*args, **kwargs): + return f(*decode_from_utf8(args), **decode_from_utf8(kwargs)) + return wrapper - def __str__(self): - return "{}".format(self.identifier) - def __repr__(self): - return "{}".format(self.identifier) +NICK_GROUP_HERE = "0|Here" +NICK_GROUP_AWAY = "1|Away" - def add_user(self, user): - self.users.append(user, user.get_aliases()) - users.append(user, user.get_aliases()) +sslopt_ca_certs = {} +if hasattr(ssl, "get_default_verify_paths") and callable(ssl.get_default_verify_paths): + ssl_defaults = ssl.get_default_verify_paths() + if ssl_defaults.cafile is not None: + sslopt_ca_certs = {'ca_certs': ssl_defaults.cafile} - def add_bot(self, bot): - self.bots.append(bot) +EMOJI = [] - def add_channel(self, channel): - self.channels.append(channel, channel.get_aliases()) - channels.append(channel, channel.get_aliases()) +###### Unicode handling - def get_aliases(self): - aliases = filter(None, [self.identifier, self.token, self.buffer, self.alias]) - return aliases - def find(self, name, attribute): - attribute = eval("self." + attribute) - return attribute.find(name) +def encode_to_utf8(data): + if isinstance(data, unicode): + return data.encode('utf-8') + if isinstance(data, bytes): + return data + elif isinstance(data, collections.Mapping): + return type(data)(map(encode_to_utf8, data.iteritems())) + elif isinstance(data, collections.Iterable): + return type(data)(map(encode_to_utf8, data)) + else: + return data + + +def decode_from_utf8(data): + if isinstance(data, bytes): + return data.decode('utf-8') + if isinstance(data, unicode): + return data + elif isinstance(data, collections.Mapping): + return type(data)(map(decode_from_utf8, data.iteritems())) + elif isinstance(data, collections.Iterable): + return type(data)(map(decode_from_utf8, data)) + else: + return data + + +class WeechatWrapper(object): + def __init__(self, wrapped_class): + self.wrapped_class = wrapped_class + + # Helper method used to encode/decode method calls. + def wrap_for_utf8(self, method): + def hooked(*args, **kwargs): + result = method(*encode_to_utf8(args), **encode_to_utf8(kwargs)) + # Prevent wrapped_class from becoming unwrapped + if result == self.wrapped_class: + return self + return decode_from_utf8(result) + return hooked + + # Encode and decode everything sent to/received from weechat. We use the + # unicode type internally in wee-slack, but has to send utf8 to weechat. + def __getattr__(self, attr): + orig_attr = self.wrapped_class.__getattribute__(attr) + if callable(orig_attr): + return self.wrap_for_utf8(orig_attr) + else: + return decode_from_utf8(orig_attr) - def get_communication_id(self): - if self.communication_counter > 999: - self.communication_counter = 0 - self.communication_counter += 1 - return self.communication_counter + # Ensure all lines sent to weechat specifies a prefix. For lines after the + # first, we want to disable the prefix, which is done by specifying a space. + def prnt_date_tags(self, buffer, date, tags, message): + message = message.replace("\n", "\n \t") + return self.wrap_for_utf8(self.wrapped_class.prnt_date_tags)(buffer, date, tags, message) - def send_to_websocket(self, data, expect_reply=True): - data["id"] = self.get_communication_id() - message = json.dumps(data) - try: - if expect_reply: - self.message_buffer[data["id"]] = data - self.ws.send(message) - dbg("Sent {}...".format(message[:100])) - except: - dbg("Unexpected error: {}\nSent: {}".format(sys.exc_info()[0], data)) - self.connected = False - def ping(self): - request = {"type": "ping"} - self.send_to_websocket(request) +##### Helpers - def should_connect(self): - """ - If we haven't tried to connect OR we tried and never heard back and it - has been 125 seconds consider the attempt dead and try again - """ - if self.connection_attempt_time == 0 or self.connection_attempt_time + 125 < int(time.time()): - return True - else: - return False +def get_nick_color_name(nick): + info_name_prefix = "irc_" if int(weechat_version) < 0x1050000 else "" + return w.info_get(info_name_prefix + "nick_color_name", nick) - def connect_to_slack(self): - t = time.time() - #Double check that we haven't exceeded a long wait to connect and try again. - if self.connecting and self.should_connect(): - self.connecting = False - if not self.connecting: - async_slack_api_request("slack.com", self.token, "rtm.start", {"ts": t}) - self.connection_attempt_time = int(time.time()) - self.connecting = True - def connected_to_slack(self, login_data): - if login_data["ok"]: - self.team = login_data["team"]["domain"] - self.domain = login_data["team"]["domain"] + ".slack.com" - dbg("connected to {}".format(self.domain)) - self.identifier = self.domain - - alias = w.config_get_plugin("server_alias.{}".format(login_data["team"]["domain"])) - if alias: - self.server_buffer_name = alias - self.alias = alias - else: - self.server_buffer_name = self.domain +##### BEGIN NEW - self.nick = login_data["self"]["name"] - self.create_local_buffer() +IGNORED_EVENTS = [ + # "pref_change", + # "reconnect_url", +] - if self.create_slack_websocket(login_data): - if self.ping_hook: - w.unhook(self.ping_hook) - self.communication_counter = 0 - self.ping_hook = w.hook_timer(1000 * 5, 0, 0, "slack_ping_cb", self.domain) - if len(self.users) == 0 or len(self.channels) == 0: - self.create_slack_mappings(login_data) +###### New central Event router - self.connected = True - self.connecting = False - self.print_connection_info(login_data) - if len(self.message_buffer) > 0: - for message_id in self.message_buffer.keys(): - if self.message_buffer[message_id]["type"] != 'ping': - resend = self.message_buffer.pop(message_id) - dbg("Resent failed message.") - self.send_to_websocket(resend) - #sleep to prevent being disconnected by websocket server - time.sleep(1) - else: - self.message_buffer.pop(message_id) - return True - else: - token_start = self.token[:10] - error = """ -!! slack.com login error: {} - The problematic token starts with {} - Please check your API token with - "/set plugins.var.python.slack_extension.slack_api_token (token)" - -""".format(login_data["error"], token_start) - w.prnt("", error) - self.connected = False - - def print_connection_info(self, login_data): - self.buffer_prnt('Connected to Slack', backlog=True) - self.buffer_prnt('{:<20} {}'.format("Websocket URL", login_data["url"]), backlog=True) - self.buffer_prnt('{:<20} {}'.format("User name", login_data["self"]["name"]), backlog=True) - self.buffer_prnt('{:<20} {}'.format("User ID", login_data["self"]["id"]), backlog=True) - self.buffer_prnt('{:<20} {}'.format("Team name", login_data["team"]["name"]), backlog=True) - self.buffer_prnt('{:<20} {}'.format("Team domain", login_data["team"]["domain"]), backlog=True) - self.buffer_prnt('{:<20} {}'.format("Team id", login_data["team"]["id"]), backlog=True) - - def create_local_buffer(self): - if not w.buffer_search("", self.server_buffer_name): - self.buffer = w.buffer_new(self.server_buffer_name, "buffer_input_cb", "", "", "") - if w.config_string(w.config_get('irc.look.server_buffer')) == 'merge_with_core': - w.buffer_merge(self.buffer, w.buffer_search_main()) - w.buffer_set(self.buffer, "nicklist", "1") +class EventRouter(object): - def create_slack_websocket(self, data): - web_socket_url = data['url'] - try: - self.ws = create_connection(web_socket_url, sslopt=sslopt_ca_certs) - self.ws_hook = w.hook_fd(self.ws.sock._sock.fileno(), 1, 0, 0, "slack_websocket_cb", self.identifier) - self.ws.sock.setblocking(0) - return True - except Exception as e: - print("websocket connection error: {}".format(e)) - return False + def __init__(self): + """ + complete + Eventrouter is the central hub we use to route: + 1) incoming websocket data + 2) outgoing http requests and incoming replies + 3) local requests + It has a recorder that, when enabled, logs most events + to the location specified in RECORD_DIR. + """ + self.queue = [] + self.slow_queue = [] + self.slow_queue_timer = 0 + self.teams = {} + self.context = {} + self.weechat_controller = WeechatController(self) + self.previous_buffer = "" + self.reply_buffer = {} + self.cmds = {k[8:]: v for k, v in globals().items() if k.startswith("command_")} + self.proc = {k[8:]: v for k, v in globals().items() if k.startswith("process_")} + self.handlers = {k[7:]: v for k, v in globals().items() if k.startswith("handle_")} + self.local_proc = {k[14:]: v for k, v in globals().items() if k.startswith("local_process_")} + self.shutting_down = False + self.recording = False + self.recording_path = "/tmp" + + def record(self): + """ + complete + Toggles the event recorder and creates a directory for data if enabled. + """ + self.recording = not self.recording + if self.recording: + if not os.path.exists(RECORD_DIR): + os.makedirs(RECORD_DIR) - def create_slack_mappings(self, data): - - for item in data["users"]: - self.add_user(User(self, item["name"], item["id"], item["presence"], item["deleted"], is_bot=item.get('is_bot', False))) - - for item in data["bots"]: - self.add_bot(Bot(self, item["name"], item["id"], item["deleted"])) - - for item in data["channels"]: - if "last_read" not in item: - item["last_read"] = 0 - if "members" not in item: - item["members"] = [] - if "topic" not in item: - item["topic"] = {} - item["topic"]["value"] = "" - if not item["is_archived"]: - self.add_channel(Channel(self, item["name"], item["id"], item["is_member"], item["last_read"], "#", item["members"], item["topic"]["value"])) - for item in data["groups"]: - if "last_read" not in item: - item["last_read"] = 0 - if not item["is_archived"]: - if item["name"].startswith("mpdm-"): - self.add_channel(MpdmChannel(self, item["name"], item["id"], item["is_open"], item["last_read"], "#", item["members"], item["topic"]["value"])) - else: - self.add_channel(GroupChannel(self, item["name"], item["id"], item["is_open"], item["last_read"], "#", item["members"], item["topic"]["value"])) - for item in data["ims"]: - if "last_read" not in item: - item["last_read"] = 0 - if item["unread_count"] > 0: - item["is_open"] = True - name = self.users.find(item["user"]).name - self.add_channel(DmChannel(self, name, item["id"], item["is_open"], item["last_read"])) - for item in data['self']['prefs']['muted_channels'].split(','): - if item == '': - continue - if self.channels.find(item) is not None: - self.channels.find(item).muted = True - - for item in self.channels: - item.get_history() - - def buffer_prnt(self, message='no message', user="SYSTEM", backlog=False): - message = message.encode('ascii', 'ignore') - if backlog: - tags = "no_highlight,notify_none,logger_backlog_end" - else: - tags = "" - if user == "SYSTEM": - user = w.config_string(w.config_get('weechat.look.prefix_network')) - if self.buffer: - w.prnt_date_tags(self.buffer, 0, tags, "{}\t{}".format(user, message)) + def record_event(self, message_json, file_name_field, subdir=None): + """ + complete + Called each time you want to record an event. + message_json is a json in dict form + file_name_field is the json key whose value you want to be part of the file name + """ + now = time.time() + if subdir: + directory = "{}/{}".format(RECORD_DIR, subdir) else: - pass - #w.prnt("", "%s\t%s" % (user, message)) - -def buffer_input_cb(b, buffer, data): - channel = channels.find(buffer) - if not channel: - return w.WEECHAT_RC_OK_EAT - reaction = re.match("(\d*)(\+|-):(.*):", data) - if not reaction and not data.startswith('s/'): - channel.send_message(data) - #channel.buffer_prnt(channel.server.nick, data) - elif reaction: - if reaction.group(2) == "+": - channel.send_add_reaction(int(reaction.group(1) or 1), reaction.group(3)) - elif reaction.group(2) == "-": - channel.send_remove_reaction(int(reaction.group(1) or 1), reaction.group(3)) - elif data.count('/') == 3: - old, new = data.split('/')[1:3] - channel.change_previous_message(old.decode("utf-8"), new.decode("utf-8")) - channel.mark_read(True) - return w.WEECHAT_RC_ERROR - - -class Channel(object): - """ - Represents a single channel and is the source of truth - for channel <> weechat buffer - """ - def __init__(self, server, name, identifier, active, last_read=0, prepend_name="", members=[], topic=""): - self.name = prepend_name + name - self.current_short_name = prepend_name + name - self.identifier = identifier - self.active = active - self.last_read = float(last_read) - self.members = set(members) - self.topic = topic + directory = RECORD_DIR + if not os.path.exists(directory): + os.makedirs(directory) + mtype = message_json.get(file_name_field, 'unknown') + f = open('{}/{}-{}.json'.format(directory, now, mtype), 'w') + f.write("{}".format(json.dumps(message_json))) + f.close() + + def store_context(self, data): + """ + A place to store data and vars needed by callback returns. We need this because + weechat's "callback_data" has a limited size and weechat will crash if you exceed + this size. + """ + identifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(40)) + self.context[identifier] = data + dbg("stored context {} {} ".format(identifier, data.url)) + return identifier - self.members_table = {} - self.channel_buffer = None - self.type = "channel" - self.server = server - self.typing = {} - self.last_received = None - self.messages = [] - self.scrolling = False - self.last_active_user = None - self.muted = False - if active: - self.create_buffer() - self.attach_buffer() - self.create_members_table() - self.update_nicklist() - self.set_topic(self.topic) - buffer_list_update_next() + def retrieve_context(self, identifier): + """ + A place to retrieve data and vars needed by callback returns. We need this because + weechat's "callback_data" has a limited size and weechat will crash if you exceed + this size. + """ + data = self.context.get(identifier, None) + if data: + # dbg("retrieved context {} ".format(identifier)) + return data - def __str__(self): - return self.name + def delete_context(self, identifier): + """ + Requests can span multiple requests, so we may need to delete this as a last step + """ + if identifier in self.context: + # dbg("deleted eontext {} ".format(identifier)) + del self.context[identifier] - def __repr__(self): - return self.name + def shutdown(self): + """ + complete + This toggles shutdown mode. Shutdown mode tells us not to + talk to Slack anymore. Without this, typing /quit will trigger + a race with the buffer close callback and may result in you + leaving every slack channel. + """ + self.shutting_down = not self.shutting_down - def __eq__(self, compare_str): - if compare_str == self.fullname() or compare_str == self.name or compare_str == self.identifier or compare_str == self.name[1:] or (compare_str == self.channel_buffer and self.channel_buffer is not None): - return True + def register_team(self, team): + """ + complete + Adds a team to the list of known teams for this EventRouter. + """ + if isinstance(team, SlackTeam): + self.teams[team.get_team_hash()] = team else: - return False + raise InvalidType(type(team)) - def get_aliases(self): - aliases = [self.fullname(), self.name, self.identifier, self.name[1:], ] - if self.channel_buffer is not None: - aliases.append(self.channel_buffer) - return aliases + def reconnect_if_disconnected(self): + for team_id, team in self.teams.iteritems(): + if not team.connected: + team.connect() + dbg("reconnecting {}".format(team)) - def create_members_table(self): - for user in self.members: - self.members_table[user] = self.server.users.find(user) + def receive_ws_callback(self, team_hash): + """ + incomplete (reconnect) + This is called by the global method of the same name. + It is triggered when we have incoming data on a websocket, + which needs to be read. Once it is read, we will ensure + the data is valid JSON, add metadata, and place it back + on the queue for processing as JSON. + """ + try: + # Read the data from the websocket associated with this team. + data = decode_from_utf8(self.teams[team_hash].ws.recv()) + message_json = json.loads(data) + metadata = WeeSlackMetadata({ + "team": team_hash, + }).jsonify() + message_json["wee_slack_metadata"] = metadata + if self.recording: + self.record_event(message_json, 'type', 'websocket') + self.receive_json(json.dumps(message_json)) + except WebSocketConnectionClosedException: + # TODO: handle reconnect here + self.teams[team_hash].set_disconnected() + return w.WEECHAT_RC_OK + except ssl.SSLWantReadError: + # Expected to happen occasionally on SSL websockets. + return w.WEECHAT_RC_OK + except Exception: + dbg("socket issue: {}\n".format(traceback.format_exc())) + return w.WEECHAT_RC_OK - def create_buffer(self): - channel_buffer = w.buffer_search("", "{}.{}".format(self.server.server_buffer_name, self.name)) - if channel_buffer: - self.channel_buffer = channel_buffer - else: - self.channel_buffer = w.buffer_new("{}.{}".format(self.server.server_buffer_name, self.name), "buffer_input_cb", self.name, "", "") - if self.type == "im": - w.buffer_set(self.channel_buffer, "localvar_set_type", 'private') - else: - w.buffer_set(self.channel_buffer, "localvar_set_type", 'channel') - if self.server.alias: - w.buffer_set(self.channel_buffer, "localvar_set_server", self.server.alias) + def receive_httprequest_callback(self, data, command, return_code, out, err): + """ + complete + Receives the result of an http request we previously handed + off to weechat (weechat bundles libcurl). Weechat can fragment + replies, so it buffers them until the reply is complete. + It is then populated with metadata here so we can identify + where the request originated and route properly. + """ + request_metadata = self.retrieve_context(data) + try: + dbg("RECEIVED CALLBACK with request of {} id of {} and code {} of length {}".format(request_metadata.request, request_metadata.response_id, return_code, len(out))) + except: + dbg(request_metadata) + return + if return_code == 0: + if len(out) > 0: + if request_metadata.response_id in self.reply_buffer: + # dbg("found response id in reply_buffer", True) + self.reply_buffer[request_metadata.response_id] += out + else: + # dbg("didn't find response id in reply_buffer", True) + self.reply_buffer[request_metadata.response_id] = "" + self.reply_buffer[request_metadata.response_id] += out + try: + j = json.loads(self.reply_buffer[request_metadata.response_id]) + except: + pass + # dbg("Incomplete json, awaiting more", True) + try: + j["wee_slack_process_method"] = request_metadata.request_normalized + j["wee_slack_request_metadata"] = pickle.dumps(request_metadata) + self.reply_buffer.pop(request_metadata.response_id) + if self.recording: + self.record_event(j, 'wee_slack_process_method', 'http') + self.receive_json(json.dumps(j)) + self.delete_context(data) + except: + dbg("HTTP REQUEST CALLBACK FAILED", True) + pass + # We got an empty reply and this is weird so just ditch it and retry else: - w.buffer_set(self.channel_buffer, "localvar_set_server", self.server.team) - w.buffer_set(self.channel_buffer, "localvar_set_channel", self.name) - w.buffer_set(self.channel_buffer, "short_name", self.name) - buffer_list_update_next() - - def attach_buffer(self): - channel_buffer = w.buffer_search("", "{}.{}".format(self.server.server_buffer_name, self.name)) - if channel_buffer != main_weechat_buffer: - self.channel_buffer = channel_buffer - w.buffer_set(self.channel_buffer, "localvar_set_nick", self.server.nick) - w.buffer_set(self.channel_buffer, "highlight_words", self.server.nick) + dbg("length was zero, probably a bug..") + self.delete_context(data) + self.receive(request_metadata) + elif return_code != -1: + self.reply_buffer.pop(request_metadata.response_id, None) + self.delete_context(data) else: - self.channel_buffer = None - channels.update_hashtable() - self.server.channels.update_hashtable() + if request_metadata.response_id not in self.reply_buffer: + self.reply_buffer[request_metadata.response_id] = "" + self.reply_buffer[request_metadata.response_id] += out - def detach_buffer(self): - if self.channel_buffer is not None: - w.buffer_close(self.channel_buffer) - self.channel_buffer = None - channels.update_hashtable() - self.server.channels.update_hashtable() + def receive_json(self, data): + """ + complete + Receives a raw JSON string from and unmarshals it + as dict, then places it back on the queue for processing. + """ + dbg("RECEIVED JSON of len {}".format(len(data))) + message_json = json.loads(data) + self.queue.append(message_json) - def update_nicklist(self, user=None): - if not self.channel_buffer: - return + def receive(self, dataobj): + """ + complete + Receives a raw object and places it on the queue for + processing. Object must be known to handle_next or + be JSON. + """ + dbg("RECEIVED FROM QUEUE") + self.queue.append(dataobj) - w.buffer_set(self.channel_buffer, "nicklist", "1") + def receive_slow(self, dataobj): + """ + complete + Receives a raw object and places it on the slow queue for + processing. Object must be known to handle_next or + be JSON. + """ + dbg("RECEIVED FROM QUEUE") + self.slow_queue.append(dataobj) - #create nicklists for the current channel if they don't exist - #if they do, use the existing pointer - here = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_HERE) - if not here: - here = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_HERE, "weechat.color.nicklist_group", 1) - afk = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_AWAY) - if not afk: - afk = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_AWAY, "weechat.color.nicklist_group", 1) + def handle_next(self): + """ + complete + Main handler of the EventRouter. This is called repeatedly + via callback to drain events from the queue. It also attaches + useful metadata and context to events as they are processed. + """ + if len(self.slow_queue) > 0 and ((self.slow_queue_timer + 1) < time.time()): + # for q in self.slow_queue[0]: + dbg("from slow queue", 0) + self.queue.append(self.slow_queue.pop()) + # self.slow_queue = [] + self.slow_queue_timer = time.time() + if len(self.queue) > 0: + j = self.queue.pop(0) + # Reply is a special case of a json reply from websocket. + kwargs = {} + if isinstance(j, SlackRequest): + if j.should_try(): + if j.retry_ready(): + local_process_async_slack_api_request(j, self) + else: + self.slow_queue.append(j) + else: + dbg("Max retries for Slackrequest") - if user: - user = self.members_table[user] - nick = w.nicklist_search_nick(self.channel_buffer, "", user.name) - #since this is a change just remove it regardless of where it is - w.nicklist_remove_nick(self.channel_buffer, nick) - #now add it back in to whichever.. - if user.presence == 'away': - w.nicklist_add_nick(self.channel_buffer, afk, user.name, user.color_name, "", "", 1) else: - w.nicklist_add_nick(self.channel_buffer, here, user.name, user.color_name, "", "", 1) - #if we didn't get a user, build a complete list. this is expensive. - else: - try: - for user in self.members: - user = self.members_table[user] - if user.deleted: - continue - if user.presence == 'away': - w.nicklist_add_nick(self.channel_buffer, afk, user.name, user.color_name, "", "", 1) + if "reply_to" in j: + dbg("SET FROM REPLY") + function_name = "reply" + elif "type" in j: + dbg("SET FROM type") + function_name = j["type"] + elif "wee_slack_process_method" in j: + dbg("SET FROM META") + function_name = j["wee_slack_process_method"] + else: + dbg("SET FROM NADA") + function_name = "unknown" + + # Here we are passing the actual objects. No more lookups. + meta = j.get("wee_slack_metadata", None) + if meta: + try: + if isinstance(meta, basestring): + dbg("string of metadata") + team = meta.get("team", None) + if team: + kwargs["team"] = self.teams[team] + if "user" in j: + kwargs["user"] = self.teams[team].users[j["user"]] + if "channel" in j: + kwargs["channel"] = self.teams[team].channels[j["channel"]] + except: + dbg("metadata failure") + + if function_name not in IGNORED_EVENTS: + dbg("running {}".format(function_name)) + if function_name.startswith("local_") and function_name in self.local_proc: + self.local_proc[function_name](j, self, **kwargs) + elif function_name in self.proc: + self.proc[function_name](j, self, **kwargs) + elif function_name in self.handlers: + self.handlers[function_name](j, self, **kwargs) else: - w.nicklist_add_nick(self.channel_buffer, here, user.name, user.color_name, "", "", 1) - except Exception as e: - dbg("DEBUG: {} {} {}".format(self.identifier, self.name, e)) + raise ProcessNotImplemented(function_name) - def fullname(self): - return "{}.{}".format(self.server.server_buffer_name, self.name) - def has_user(self, name): - return name in self.members +def handle_next(*args): + """ + complete + This is just a place to call the event router globally. + This is a dirty hack. There must be a better way. + """ + try: + EVENTROUTER.handle_next() + except: + if config.debug_mode: + traceback.print_exc() + else: + pass + return w.WEECHAT_RC_OK - def user_join(self, name): - self.members.add(name) - self.create_members_table() - self.update_nicklist() - def user_leave(self, name): - if name in self.members: - self.members.remove(name) - self.create_members_table() - self.update_nicklist() +class WeechatController(object): + """ + Encapsulates our interaction with weechat + """ - def set_active(self): - self.active = True + def __init__(self, eventrouter): + self.eventrouter = eventrouter + self.buffers = {} + self.previous_buffer = None + self.buffer_list_stale = False - def set_inactive(self): - self.active = False + def iter_buffers(self): + for b in self.buffers: + yield (b, self.buffers[b]) - def set_typing(self, user): - if self.channel_buffer: - if w.buffer_get_integer(self.channel_buffer, "hidden") == 0: - self.typing[user] = time.time() - buffer_list_update_next() + def register_buffer(self, buffer_ptr, channel): + """ + complete + Adds a weechat buffer to the list of handled buffers for this EventRouter + """ + if isinstance(buffer_ptr, basestring): + self.buffers[buffer_ptr] = channel + else: + raise InvalidType(type(buffer_ptr)) - def unset_typing(self, user): - if self.channel_buffer: - if w.buffer_get_integer(self.channel_buffer, "hidden") == 0: - try: - del self.typing[user] - buffer_list_update_next() - except: - pass + def unregister_buffer(self, buffer_ptr, update_remote=False, close_buffer=False): + """ + complete + Adds a weechat buffer to the list of handled buffers for this EventRouter + """ + if isinstance(buffer_ptr, basestring): + try: + self.buffers[buffer_ptr].destroy_buffer(update_remote) + if close_buffer: + w.buffer_close(buffer_ptr) + del self.buffers[buffer_ptr] + except: + dbg("Tried to close unknown buffer") + else: + raise InvalidType(type(buffer_ptr)) - def send_message(self, message): - message = self.linkify_text(message) - dbg(message) - request = {"type": "message", "channel": self.identifier, "text": message, "_server": self.server.domain} - self.server.send_to_websocket(request) - - def linkify_text(self, message): - message = message.split(' ') - for item in enumerate(message): - targets = re.match('.*([@#])([\w.]+\w)(\W*)', item[1]) - if targets and targets.groups()[0] == '@': - named = targets.groups() - if named[1] in ["group", "channel", "here"]: - message[item[0]] = "".format(named[1]) - if self.server.users.find(named[1]): - message[item[0]] = "<@{}>{}".format(self.server.users.find(named[1]).identifier, named[2]) - if targets and targets.groups()[0] == '#': - named = targets.groups() - if self.server.channels.find(named[1]): - message[item[0]] = "<#{}|{}>{}".format(self.server.channels.find(named[1]).identifier, named[1], named[2]) - dbg(message) - return " ".join(message) + def get_channel_from_buffer_ptr(self, buffer_ptr): + return self.buffers.get(buffer_ptr, None) - def set_topic(self, topic): - self.topic = topic.encode('utf-8') - w.buffer_set(self.channel_buffer, "title", self.topic) + def get_all(self, buffer_ptr): + return self.buffers - def open(self, update_remote=True): - self.create_buffer() - self.active = True - self.get_history() - if "info" in SLACK_API_TRANSLATOR[self.type]: - async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["info"], {"name": self.name.lstrip("#")}) - if update_remote: - if "join" in SLACK_API_TRANSLATOR[self.type]: - async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["join"], {"name": self.name.lstrip("#")}) - async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["join"], {"user": users.find(self.name).identifier}) - - def close(self, update_remote=True): - #remove from cache so messages don't reappear when reconnecting - if self.active: - self.active = False - self.current_short_name = "" - self.detach_buffer() - if update_remote: - t = time.time() - async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["leave"], {"channel": self.identifier}) + def get_previous_buffer_ptr(self): + return self.previous_buffer - def closed(self): - self.channel_buffer = None - self.last_received = None - self.close() + def set_previous_buffer(self, data): + self.previous_buffer = data - def is_someone_typing(self): - for user in self.typing.keys(): - if self.typing[user] + 4 > time.time(): - return True - if len(self.typing) > 0: - self.typing = {} - buffer_list_update_next() - return False + def check_refresh_buffer_list(self): + return self.buffer_list_stale and self.last_buffer_list_update + 1 < time.time() - def get_typing_list(self): - typing = [] - for user in self.typing.keys(): - if self.typing[user] + 4 > time.time(): - typing.append(user) - return typing + def set_refresh_buffer_list(self, setting): + self.buffer_list_stale = setting - def mark_read(self, update_remote=True): - t = time.time() +###### New Local Processors - if self.channel_buffer: - w.buffer_set(self.channel_buffer, "unread", "") - if update_remote: - self.last_read = time.time() - self.update_read_marker(self.last_read) - def update_read_marker(self, time): - async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["mark"], {"channel": self.identifier, "ts": time}) +def local_process_async_slack_api_request(request, event_router): + """ + complete + Sends an API request to Slack. You'll need to give this a well formed SlackRequest object. + DEBUGGING!!! The context here cannot be very large. Weechat will crash. + """ + if not event_router.shutting_down: + weechat_request = 'url:{}'.format(request.request_string()) + weechat_request += '&nonce={}'.format(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(4))) + params = {'useragent': 'wee_slack {}'.format(SCRIPT_VERSION)} + request.tried() + context = event_router.store_context(request) + # TODO: let flashcode know about this bug - i have to 'clear' the hashtable or retry requests fail + w.hook_process_hashtable('url:', params, config.slack_timeout, "", context) + w.hook_process_hashtable(weechat_request, params, config.slack_timeout, "receive_httprequest_callback", context) - def rename(self): - if self.is_someone_typing(): - new_name = ">{}".format(self.name[1:]) - else: - new_name = self.name - if self.channel_buffer: - if self.current_short_name != new_name: - self.current_short_name = new_name - w.buffer_set(self.channel_buffer, "short_name", new_name) +###### New Callbacks - def buffer_prnt(self, user='unknown_user', message='no message', time=0): - """ - writes output (message) to a buffer (channel) - """ - set_read_marker = False - time_float = float(time) - tags = "nick_" + user - # XXX: we should not set log1 for robots. - if time_float != 0 and self.last_read >= time_float: - tags += ",no_highlight,notify_none,logger_backlog_end" - set_read_marker = True - elif message.find(self.server.nick.encode('utf-8')) > -1: - tags = ",notify_highlight,log1" - elif user != self.server.nick and self.name in self.server.users: - tags = ",notify_private,notify_message,log1,irc_privmsg" - elif self.muted: - tags = ",no_highlight,notify_none,logger_backlog_end" - elif user in [x.strip() for x in w.prefix("join"), w.prefix("quit")]: - tags = ",irc_smart_filter" - else: - tags = ",notify_message,log1,irc_privmsg" - #don't write these to local log files - #tags += ",no_log" - time_int = int(time_float) - if self.channel_buffer: - prefix_same_nick = w.config_string(w.config_get('weechat.look.prefix_same_nick')) - if user == self.last_active_user and prefix_same_nick != "": - if colorize_nicks and self.server.users.find(user): - name = self.server.users.find(user).color + prefix_same_nick - else: - name = prefix_same_nick - else: - nick_prefix = w.config_string(w.config_get('weechat.look.nick_prefix')) - nick_suffix = w.config_string(w.config_get('weechat.look.nick_suffix')) - if self.server.users.find(user): - name = self.server.users.find(user).formatted_name() - self.last_active_user = user - # XXX: handle bots properly here. - else: - name = user - self.last_active_user = None - name = nick_prefix + name + nick_suffix - name = name.decode('utf-8') - #colorize nicks in each line - chat_color = w.config_string(w.config_get('weechat.color.chat')) - if type(message) is not unicode: - message = message.decode('UTF-8', 'replace') - curr_color = w.color(chat_color) - if colorize_nicks and colorize_messages and self.server.users.find(user): - curr_color = self.server.users.find(user).color - message = curr_color + message - for user in self.server.users: - if user.name in message: - message = user.name_regex.sub( - r'\1\2{}\3'.format(user.formatted_name() + curr_color), - message) - - message = HTMLParser.HTMLParser().unescape(message) - data = u"{}\t{}".format(name, message).encode('utf-8') - w.prnt_date_tags(self.channel_buffer, time_int, tags, data) - - if set_read_marker: - self.mark_read(False) - else: - self.open(False) - self.last_received = time - self.unset_typing(user) - - def buffer_redraw(self): - if self.channel_buffer and not self.scrolling: - w.buffer_clear(self.channel_buffer) - self.messages.sort() - for message in self.messages: - process_message(message.message_json, False) - - def set_scrolling(self): - self.scrolling = True - - def unset_scrolling(self): - self.scrolling = False - - def has_message(self, ts): - return self.messages.count(ts) > 0 - - def change_message(self, ts, text=None, suffix=''): - if self.has_message(ts): - message_index = self.messages.index(ts) - - if text is not None: - self.messages[message_index].change_text(text) - text = render_message(self.messages[message_index].message_json, True) - - #if there is only one message with this timestamp, modify it directly. - #we do this because time resolution in weechat is less than slack - int_time = int(float(ts)) - if self.messages.count(str(int_time)) == 1: - modify_buffer_line(self.channel_buffer, text + suffix, int_time) - #otherwise redraw the whole buffer, which is expensive - else: - self.buffer_redraw() - return True +@utf8_decode +def receive_httprequest_callback(data, command, return_code, out, err): + """ + complete + This is a dirty hack. There must be a better way. + """ + # def url_processor_cb(data, command, return_code, out, err): + EVENTROUTER.receive_httprequest_callback(data, command, return_code, out, err) + return w.WEECHAT_RC_OK - def add_reaction(self, ts, reaction, user): - if self.has_message(ts): - message_index = self.messages.index(ts) - self.messages[message_index].add_reaction(reaction, user) - self.change_message(ts) - return True - def remove_reaction(self, ts, reaction, user): - if self.has_message(ts): - message_index = self.messages.index(ts) - self.messages[message_index].remove_reaction(reaction, user) - self.change_message(ts) - return True +@utf8_decode +def receive_ws_callback(*args): + """ + complete + The first arg is all we want here. It contains the team + hash which is set when we _hook the descriptor. + This is a dirty hack. There must be a better way. + """ + EVENTROUTER.receive_ws_callback(args[0]) + return w.WEECHAT_RC_OK - def send_add_reaction(self, msg_number, reaction): - self.send_change_reaction("reactions.add", msg_number, reaction) - def send_remove_reaction(self, msg_number, reaction): - self.send_change_reaction("reactions.remove", msg_number, reaction) +@utf8_decode +def reconnect_callback(*args): + EVENTROUTER.reconnect_if_disconnected() + return w.WEECHAT_RC_OK - def send_change_reaction(self, method, msg_number, reaction): - if 0 < msg_number < len(self.messages): - timestamp = self.messages[-msg_number].message_json["ts"] - data = {"channel": self.identifier, "timestamp": timestamp, "name": reaction} - async_slack_api_request(self.server.domain, self.server.token, method, data) - def change_previous_message(self, old, new): - message = self.my_last_message() - if new == "" and old == "": - async_slack_api_request(self.server.domain, self.server.token, 'chat.delete', {"channel": self.identifier, "ts": message['ts']}) - else: - new_message = message["text"].replace(old, new) - async_slack_api_request(self.server.domain, self.server.token, 'chat.update', {"channel": self.identifier, "ts": message['ts'], "text": new_message.encode("utf-8")}) +@utf8_decode +def buffer_closing_callback(signal, sig_type, data): + """ + complete + Receives a callback from weechat when a buffer is being closed. + We pass the eventrouter variable name in as a string, as + that is the only way we can do dependency injection via weechat + callback, hence the eval. + """ + eval(signal).weechat_controller.unregister_buffer(data, True, False) + return w.WEECHAT_RC_OK - def my_last_message(self): - for message in reversed(self.messages): - if "user" in message.message_json and "text" in message.message_json and message.message_json["user"] == self.server.users.find(self.server.nick).identifier: - return message.message_json - def cache_message(self, message_json, from_me=False): +@utf8_decode +def buffer_input_callback(signal, buffer_ptr, data): + """ + incomplete + Handles everything a user types in the input bar. In our case + this includes add/remove reactions, modifying messages, and + sending messages. + """ + eventrouter = eval(signal) + channel = eventrouter.weechat_controller.get_channel_from_buffer_ptr(buffer_ptr) + if not channel: + return w.WEECHAT_RC_ERROR + + reaction = re.match("^(\d*)(\+|-):(.*):\s*$", data) + substitute = re.match("^(\d*)s/", data) + if reaction: + if reaction.group(2) == "+": + channel.send_add_reaction(int(reaction.group(1) or 1), reaction.group(3)) + elif reaction.group(2) == "-": + channel.send_remove_reaction(int(reaction.group(1) or 1), reaction.group(3)) + elif substitute: + msgno = int(substitute.group(1) or 1) + try: + old, new, flags = re.split(r'(? ">channel" and + user presence via " name" <-> "+name". + """ + eventrouter = eval(data) + # global buffer_list_update + + for b in eventrouter.weechat_controller.iter_buffers(): + b[1].refresh() +# buffer_list_update = True +# if eventrouter.weechat_controller.check_refresh_buffer_list(): +# # gray_check = False +# # if len(servers) > 1: +# # gray_check = True +# eventrouter.weechat_controller.set_refresh_buffer_list(False) + return w.WEECHAT_RC_OK + + +def quit_notification_callback(signal, sig_type, data): + stop_talking_to_slack() + return w.WEECHAT_RC_OK + + +@utf8_decode +def typing_notification_cb(signal, sig_type, data): + msg = w.buffer_get_string(data, "input") + if len(msg) > 8 and msg[:1] != "/": + global typing_timer + now = time.time() + if typing_timer + 4 < now: + current_buffer = w.current_buffer() + channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) + if channel and channel.type != "thread": + identifier = channel.identifier + request = {"type": "typing", "channel": identifier} + channel.team.send_to_websocket(request, expect_reply=False) + typing_timer = now + return w.WEECHAT_RC_OK + + +@utf8_decode +def typing_update_cb(data, remaining_calls): + w.bar_item_update("slack_typing_notice") + return w.WEECHAT_RC_OK + + +@utf8_decode +def slack_never_away_cb(data, remaining_calls): + if config.never_away: + for t in EVENTROUTER.teams.values(): + slackbot = t.get_channel_map()['slackbot'] + channel = t.channels[slackbot] + request = {"type": "typing", "channel": channel.identifier} + channel.team.send_to_websocket(request, expect_reply=False) + return w.WEECHAT_RC_OK + + +@utf8_decode +def typing_bar_item_cb(data, current_buffer, args): + """ + Privides a bar item indicating who is typing in the current channel AND + why is typing a DM to you globally. + """ + typers = [] + current_buffer = w.current_buffer() + current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) + + # first look for people typing in this channel + if current_channel: + # this try is mostly becuase server buffers don't implement is_someone_typing + try: + if current_channel.type != 'im' and current_channel.is_someone_typing(): + typers += current_channel.get_typing_list() + except: + pass + + # here is where we notify you that someone is typing in DM + # regardless of which buffer you are in currently + for t in EVENTROUTER.teams.values(): + for channel in t.channels.values(): + if channel.type == "im": + if channel.is_someone_typing(): + typers.append("D/" + channel.slack_name) + pass + + typing = ", ".join(typers) + if typing != "": + typing = w.color('yellow') + "typing: " + typing + + return typing + + +@utf8_decode +def nick_completion_cb(data, completion_item, current_buffer, completion): + """ + Adds all @-prefixed nicks to completion list + """ + + current_buffer = w.current_buffer() + current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) + + if current_channel is None or current_channel.members is None: + return w.WEECHAT_RC_OK + for m in current_channel.members: + u = current_channel.team.users.get(m, None) + if u: + w.hook_completion_list_add(completion, "@" + u.name, 1, w.WEECHAT_LIST_POS_SORT) + return w.WEECHAT_RC_OK + + +@utf8_decode +def emoji_completion_cb(data, completion_item, current_buffer, completion): + """ + Adds all :-prefixed emoji to completion list + """ + + current_buffer = w.current_buffer() + current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) + + if current_channel is None: + return w.WEECHAT_RC_OK + for e in current_channel.team.emoji_completions: + w.hook_completion_list_add(completion, ":" + e + ":", 0, w.WEECHAT_LIST_POS_SORT) + return w.WEECHAT_RC_OK + + +@utf8_decode +def complete_next_cb(data, current_buffer, command): + """Extract current word, if it is equal to a nick, prefix it with @ and + rely on nick_completion_cb adding the @-prefixed versions to the + completion lists, then let Weechat's internal completion do its + thing + + """ + + current_buffer = w.current_buffer() + current_channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) + + # channel = channels.find(current_buffer) + if not hasattr(current_channel, 'members') or current_channel is None or current_channel.members is None: + return w.WEECHAT_RC_OK + + line_input = w.buffer_get_string(current_buffer, "input") + current_pos = w.buffer_get_integer(current_buffer, "input_pos") - 1 + input_length = w.buffer_get_integer(current_buffer, "input_length") + + word_start = 0 + word_end = input_length + # If we're on a non-word, look left for something to complete + while current_pos >= 0 and line_input[current_pos] != '@' and not line_input[current_pos].isalnum(): + current_pos = current_pos - 1 + if current_pos < 0: + current_pos = 0 + for l in range(current_pos, 0, -1): + if line_input[l] != '@' and not line_input[l].isalnum(): + word_start = l + 1 + break + for l in range(current_pos, input_length): + if not line_input[l].isalnum(): + word_end = l + break + word = line_input[word_start:word_end] + + for m in current_channel.members: + u = current_channel.team.users.get(m, None) + if u and u.name == word: + # Here, we cheat. Insert a @ in front and rely in the @ + # nicks being in the completion list + w.buffer_set(current_buffer, "input", line_input[:word_start] + "@" + line_input[word_start:]) + w.buffer_set(current_buffer, "input_pos", str(w.buffer_get_integer(current_buffer, "input_pos") + 1)) + return w.WEECHAT_RC_OK_EAT + return w.WEECHAT_RC_OK + + +def script_unloaded(): + stop_talking_to_slack() + return w.WEECHAT_RC_OK + + +def stop_talking_to_slack(): + """ + complete + Prevents a race condition where quitting closes buffers + which triggers leaving the channel because of how close + buffer is handled + """ + EVENTROUTER.shutdown() + return w.WEECHAT_RC_OK + +##### New Classes + + +class SlackRequest(object): + """ + complete + Encapsulates a Slack api request. Valuable as an object that we can add to the queue and/or retry. + makes a SHA of the requst url and current time so we can re-tag this on the way back through. + """ + + def __init__(self, token, request, post_data={}, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + self.tries = 0 + self.start_time = time.time() + self.domain = 'api.slack.com' + self.request = request + self.request_normalized = re.sub(r'\W+', '', request) + self.token = token + post_data["token"] = token + self.post_data = post_data + self.params = {'useragent': 'wee_slack {}'.format(SCRIPT_VERSION)} + self.url = 'https://{}/api/{}?{}'.format(self.domain, request, urllib.urlencode(encode_to_utf8(post_data))) + self.response_id = sha.sha("{}{}".format(self.url, self.start_time)).hexdigest() + self.retries = kwargs.get('retries', 3) +# def __repr__(self): +# return "URL: {} Tries: {} ID: {}".format(self.url, self.tries, self.response_id) + + def request_string(self): + return "{}".format(self.url) + + def tried(self): + self.tries += 1 + self.response_id = sha.sha("{}{}".format(self.url, time.time())).hexdigest() + + def should_try(self): + return self.tries < self.retries + + def retry_ready(self): + return (self.start_time + (self.tries**2)) < time.time() + + +class SlackTeam(object): + """ + incomplete + Team object under which users and channels live.. Does lots. + """ + + def __init__(self, eventrouter, token, websocket_url, subdomain, nick, myidentifier, users, bots, channels, **kwargs): + self.ws_url = websocket_url + self.connected = False + self.connecting = False + # self.ws = None + self.ws_counter = 0 + self.ws_replies = {} + self.eventrouter = eventrouter + self.token = token + self.team = self + self.subdomain = subdomain + self.domain = subdomain + ".slack.com" + self.preferred_name = self.domain + self.nick = nick + self.myidentifier = myidentifier + try: + if self.channels: + for c in channels.keys(): + if not self.channels.get(c): + self.channels[c] = channels[c] + except: + self.channels = channels + self.users = users + self.bots = bots + self.team_hash = SlackTeam.generate_team_hash(self.nick, self.subdomain) + self.name = self.domain + self.channel_buffer = None + self.got_history = True + self.create_buffer() + self.set_muted_channels(kwargs.get('muted_channels', "")) + for c in self.channels.keys(): + channels[c].set_related_server(self) + channels[c].check_should_open() + # self.channel_set_related_server(c) + # Last step is to make sure my nickname is the set color + self.users[self.myidentifier].force_color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) + # This highlight step must happen after we have set related server + self.set_highlight_words(kwargs.get('highlight_words', "")) + self.load_emoji_completions() + + def __eq__(self, compare_str): + if compare_str == self.token or compare_str == self.domain or compare_str == self.subdomain: + return True + else: + return False + + def load_emoji_completions(self): + self.emoji_completions = list(EMOJI) + if self.emoji_completions: + s = SlackRequest(self.token, "emoji.list", {}, team_hash=self.team_hash) + self.eventrouter.receive(s) + + def add_channel(self, channel): + self.channels[channel["id"]] = channel + channel.set_related_server(self) + + # def connect_request_generate(self): + # return SlackRequest(self.token, 'rtm.start', {}) + + # def close_all_buffers(self): + # for channel in self.channels: + # self.eventrouter.weechat_controller.unregister_buffer(channel.channel_buffer, update_remote=False, close_buffer=True) + # #also close this server buffer + # self.eventrouter.weechat_controller.unregister_buffer(self.channel_buffer, update_remote=False, close_buffer=True) + + def create_buffer(self): + if not self.channel_buffer: + if config.short_buffer_names: + self.preferred_name = self.subdomain + elif config.server_aliases not in ['', None]: + name = config.server_aliases.get(self.subdomain, None) + if name: + self.preferred_name = name + else: + self.preferred_name = self.domain + self.channel_buffer = w.buffer_new("{}".format(self.preferred_name), "buffer_input_callback", "EVENTROUTER", "", "") + self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) + w.buffer_set(self.channel_buffer, "localvar_set_type", 'server') + w.buffer_set(self.channel_buffer, "localvar_set_nick", self.nick) + w.buffer_set(self.channel_buffer, "localvar_set_server", self.preferred_name) + if w.config_string(w.config_get('irc.look.server_buffer')) == 'merge_with_core': + w.buffer_merge(self.channel_buffer, w.buffer_search_main()) + + def set_muted_channels(self, muted_str): + self.muted_channels = {x for x in muted_str.split(',')} + + def set_highlight_words(self, highlight_str): + self.highlight_words = {x for x in highlight_str.split(',')} + if len(self.highlight_words) > 0: + for v in self.channels.itervalues(): + v.set_highlights() + + def formatted_name(self, **kwargs): + return self.domain + + def buffer_prnt(self, data): + w.prnt_date_tags(self.channel_buffer, SlackTS().major, tag("team"), data) + + def get_channel_map(self): + return {v.slack_name: k for k, v in self.channels.iteritems()} + + def get_username_map(self): + return {v.name: k for k, v in self.users.iteritems()} + + def get_team_hash(self): + return self.team_hash + + @staticmethod + def generate_team_hash(nick, subdomain): + return str(sha.sha("{}{}".format(nick, subdomain)).hexdigest()) + + def refresh(self): + self.rename() + + def rename(self): + pass + + # def attach_websocket(self, ws): + # self.ws = ws + + def is_user_present(self, user_id): + user = self.users.get(user_id) + if user.presence == 'active': + return True + else: + return False + + def mark_read(self, ts=None, update_remote=True, force=False): + pass + + def connect(self): + if not self.connected and not self.connecting: + self.connecting = True + if self.ws_url: + try: + ws = create_connection(self.ws_url, sslopt=sslopt_ca_certs) + self.hook = w.hook_fd(ws.sock._sock.fileno(), 1, 0, 0, "receive_ws_callback", self.get_team_hash()) + ws.sock.setblocking(0) + self.ws = ws + # self.attach_websocket(ws) + self.set_connected() + self.connecting = False + except Exception as e: + dbg("websocket connection error: {}".format(decode_from_utf8(e))) + self.connecting = False + return False + else: + # The fast reconnect failed, so start over-ish + for chan in self.channels: + self.channels[chan].got_history = False + s = initiate_connection(self.token, retries=999) + self.eventrouter.receive(s) + self.connecting = False + # del self.eventrouter.teams[self.get_team_hash()] + self.set_reconnect_url(None) + + def set_connected(self): + self.connected = True + + def set_disconnected(self): + w.unhook(self.hook) + self.connected = False + + def set_reconnect_url(self, url): + self.ws_url = url + + def next_ws_transaction_id(self): + if self.ws_counter > 999: + self.ws_counter = 0 + self.ws_counter += 1 + return self.ws_counter + + def send_to_websocket(self, data, expect_reply=True): + data["id"] = self.next_ws_transaction_id() + message = json.dumps(data) + try: + if expect_reply: + self.ws_replies[data["id"]] = data + self.ws.send(encode_to_utf8(message)) + dbg("Sent {}...".format(message[:100])) + except: + print "WS ERROR" + dbg("Unexpected error: {}\nSent: {}".format(sys.exc_info()[0], data)) + self.set_connected() + + def update_member_presence(self, user, presence): + user.presence = presence + + for c in self.channels: + c = self.channels[c] + if user.id in c.members: + c.update_nicklist(user.id) + + def subscribe_users_presence(self): + # FIXME: There is a limitation in the API to the size of the + # json we can send. + # We should try to be smarter to fetch the users whom we want to + # subscribe to. + users = self.users.keys()[0:750] + self.send_to_websocket({ + "type": "presence_sub", + "ids": users, + }, expect_reply=False) + +class SlackChannel(object): + """ + Represents an individual slack channel. + """ + + def __init__(self, eventrouter, **kwargs): + # We require these two things for a valid object, + # the rest we can just learn from slack + self.active = False + for key, value in kwargs.items(): + setattr(self, key, value) + self.eventrouter = eventrouter + self.slack_name = kwargs["name"] + self.slack_purpose = kwargs.get("purpose", {"value": ""}) + self.topic = kwargs.get("topic", {}).get("value", "") + self.identifier = kwargs["id"] + self.last_read = SlackTS(kwargs.get("last_read", SlackTS())) + self.channel_buffer = None + self.team = kwargs.get('team', None) + self.got_history = False + self.messages = OrderedDict() + self.hashed_messages = {} + self.new_messages = False + self.typing = {} + self.type = 'channel' + self.set_name(self.slack_name) + # short name relates to the localvar we change for typing indication + self.current_short_name = self.name + self.set_members(kwargs.get('members', [])) + self.unread_count_display = 0 + + def __eq__(self, compare_str): + if compare_str == self.slack_name or compare_str == self.formatted_name() or compare_str == self.formatted_name(style="long_default"): + return True + else: + return False + + def __repr__(self): + return "Name:{} Identifier:{}".format(self.name, self.identifier) + + def set_name(self, slack_name): + self.name = "#" + slack_name + + def refresh(self): + return self.rename() + + def rename(self): + if self.channel_buffer: + new_name = self.formatted_name(typing=self.is_someone_typing(), style="sidebar") + if self.current_short_name != new_name: + self.current_short_name = new_name + w.buffer_set(self.channel_buffer, "short_name", new_name) + return True + return False + + def set_members(self, members): + self.members = set(members) + self.update_nicklist() + + def get_members(self): + return self.members + + def set_unread_count_display(self, count): + self.unread_count_display = count + self.new_messages = bool(self.unread_count_display) + for c in range(self.unread_count_display): + if self.type == "im": + w.buffer_set(self.channel_buffer, "hotlist", "2") + else: + w.buffer_set(self.channel_buffer, "hotlist", "1") + + def formatted_name(self, style="default", typing=False, **kwargs): + if typing and config.channel_name_typing_indicator: + prepend = ">" + elif self.type == "group": + prepend = config.group_name_prefix + else: + prepend = "#" + select = { + "default": prepend + self.slack_name, + "sidebar": prepend + self.slack_name, + "base": self.slack_name, + "long_default": "{}.{}{}".format(self.team.preferred_name, prepend, self.slack_name), + "long_base": "{}.{}".format(self.team.preferred_name, self.slack_name), + } + return select[style] + + def render_topic(self): + if self.channel_buffer: + if self.topic != "": + topic = self.topic + else: + topic = self.slack_purpose['value'] + w.buffer_set(self.channel_buffer, "title", topic) + + def set_topic(self, value): + self.topic = value + self.render_topic() + + def update_from_message_json(self, message_json): + for key, value in message_json.items(): + setattr(self, key, value) + + def open(self, update_remote=True): + if update_remote: + if "join" in SLACK_API_TRANSLATOR[self.type]: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"channel": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + self.create_buffer() + self.active = True + self.get_history() + # self.create_buffer() + + def check_should_open(self, force=False): + if hasattr(self, "is_archived") and self.is_archived: + return + + if force: + self.create_buffer() + return + + # Only check is_member if is_open is not set, because in some cases + # (e.g. group DMs), is_member should be ignored in favor of is_open. + is_open = self.is_open if hasattr(self, "is_open") else self.is_member + if is_open or self.unread_count_display: + self.create_buffer() + if config.background_load_all_history: + self.get_history(slow_queue=True) + + def set_related_server(self, team): + self.team = team + + def set_highlights(self): + # highlight my own name and any set highlights + if self.channel_buffer: + highlights = self.team.highlight_words.union({'@' + self.team.nick, self.team.myidentifier, "!here", "!channel", "!everyone"}) + h_str = ",".join(highlights) + w.buffer_set(self.channel_buffer, "highlight_words", h_str) + + def create_buffer(self): + """ + incomplete (muted doesn't work) + Creates the weechat buffer where the channel magic happens. + """ + if not self.channel_buffer: + self.active = True + self.channel_buffer = w.buffer_new(self.formatted_name(style="long_default"), "buffer_input_callback", "EVENTROUTER", "", "") + self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) + if self.type == "im": + w.buffer_set(self.channel_buffer, "localvar_set_type", 'private') + else: + w.buffer_set(self.channel_buffer, "localvar_set_type", 'channel') + w.buffer_set(self.channel_buffer, "localvar_set_channel", self.formatted_name()) + w.buffer_set(self.channel_buffer, "localvar_set_nick", self.team.nick) + w.buffer_set(self.channel_buffer, "short_name", self.formatted_name(style="sidebar", enable_color=True)) + self.render_topic() + self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + if self.channel_buffer: + # if self.team.server_alias: + # w.buffer_set(self.channel_buffer, "localvar_set_server", self.team.server_alias) + # else: + w.buffer_set(self.channel_buffer, "localvar_set_server", self.team.preferred_name) + # else: + # self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) + self.update_nicklist() + + if "info" in SLACK_API_TRANSLATOR[self.type]: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["info"], {"channel": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + + if self.type == "im": + if "join" in SLACK_API_TRANSLATOR[self.type]: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"users": self.user, "return_im": True}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + + def destroy_buffer(self, update_remote): + if self.channel_buffer is not None: + self.channel_buffer = None + self.messages = OrderedDict() + self.hashed_messages = {} + self.got_history = False + # if update_remote and not eventrouter.shutting_down: + self.active = False + if update_remote and not self.eventrouter.shutting_down: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["leave"], {"channel": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + + def buffer_prnt(self, nick, text, timestamp=str(time.time()), tagset=None, tag_nick=None, **kwargs): + data = "{}\t{}".format(nick, text) + ts = SlackTS(timestamp) + last_read = SlackTS(self.last_read) + # without this, DMs won't open automatically + if not self.channel_buffer and ts > last_read: + self.open(update_remote=False) + if self.channel_buffer: + # backlog messages - we will update the read marker as we print these + backlog = True if ts <= last_read else False + if tagset: + tags = tag(tagset, user=tag_nick) + self.new_messages = True + + # we have to infer the tagset because we weren't told + elif ts <= last_read: + tags = tag("backlog", user=tag_nick) + elif self.type in ["im", "mpdm"]: + if nick != self.team.nick: + tags = tag("dm", user=tag_nick) + self.new_messages = True + else: + tags = tag("dmfromme") + else: + tags = tag("default", user=tag_nick) + self.new_messages = True + + try: + if config.unhide_buffers_with_activity and not self.is_visible() and (self.identifier not in self.team.muted_channels): + w.buffer_set(self.channel_buffer, "hidden", "0") + + w.prnt_date_tags(self.channel_buffer, ts.major, tags, data) + modify_print_time(self.channel_buffer, ts.minorstr(), ts.major) + if backlog: + self.mark_read(ts, update_remote=False, force=True) + except: + dbg("Problem processing buffer_prnt") + + def send_message(self, message, request_dict_ext={}): + # team = self.eventrouter.teams[self.team] + message = linkify_text(message, self.team, self) + dbg(message) + request = {"type": "message", "channel": self.identifier, "text": message, "_team": self.team.team_hash, "user": self.team.myidentifier} + request.update(request_dict_ext) + self.team.send_to_websocket(request) + self.mark_read(update_remote=False, force=True) + + def store_message(self, message, team, from_me=False): + if not self.active: + return if from_me: - message_json["user"] = self.server.users.find(self.server.nick).identifier - self.messages.append(Message(message_json)) - if len(self.messages) > SCROLLBACK_SIZE: - self.messages = self.messages[-SCROLLBACK_SIZE:] + message.message_json["user"] = team.myidentifier + self.messages[SlackTS(message.ts)] = message + + sorted_messages = sorted(self.messages.items()) + messages_to_delete = sorted_messages[:-SCROLLBACK_SIZE] + messages_to_keep = sorted_messages[-SCROLLBACK_SIZE:] + for message_hash in [m[1].hash for m in messages_to_delete]: + if message_hash in self.hashed_messages: + del self.hashed_messages[message_hash] + self.messages = OrderedDict(messages_to_keep) + + def change_message(self, ts, text=None, suffix=None): + ts = SlackTS(ts) + if ts in self.messages: + m = self.messages[ts] + if text: + m.change_text(text) + if suffix: + m.change_suffix(suffix) + text = m.render(force=True) + modify_buffer_line(self.channel_buffer, text, ts.major, ts.minor) + return True + + def edit_nth_previous_message(self, n, old, new, flags): + message = self.my_last_message(n) + if new == "" and old == "": + s = SlackRequest(self.team.token, "chat.delete", {"channel": self.identifier, "ts": message['ts']}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + else: + num_replace = 1 + if 'g' in flags: + num_replace = 0 + new_message = re.sub(old, new, message["text"], num_replace) + if new_message != message["text"]: + s = SlackRequest(self.team.token, "chat.update", {"channel": self.identifier, "ts": message['ts'], "text": new_message}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + + def my_last_message(self, msgno): + for key in self.main_message_keys_reversed(): + m = self.messages[key] + if "user" in m.message_json and "text" in m.message_json and m.message_json["user"] == self.team.myidentifier: + msgno -= 1 + if msgno == 0: + return m.message_json + + def is_visible(self): + return w.buffer_get_integer(self.channel_buffer, "hidden") == 0 + + def get_history(self, slow_queue=False): + if not self.got_history: + # we have probably reconnected. flush the buffer + if self.team.connected: + w.buffer_clear(self.channel_buffer) + self.buffer_prnt('', 'getting channel history...', tagset='backlog') + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["history"], {"channel": self.identifier, "count": BACKLOG_SIZE}, team_hash=self.team.team_hash, channel_identifier=self.identifier, clear=True) + if not slow_queue: + self.eventrouter.receive(s) + else: + self.eventrouter.receive_slow(s) + self.got_history = True + + def send_add_reaction(self, msg_number, reaction): + self.send_change_reaction("reactions.add", msg_number, reaction) - def get_history(self): - if self.active: - for message in message_cache[self.identifier]: - process_message(json.loads(message), True) - if self.last_received != None: - async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["history"], {"channel": self.identifier, "oldest": self.last_received, "count": BACKLOG_SIZE}) + def send_remove_reaction(self, msg_number, reaction): + self.send_change_reaction("reactions.remove", msg_number, reaction) + + def send_change_reaction(self, method, msg_number, reaction): + if 0 < msg_number < len(self.messages): + keys = self.main_message_keys_reversed() + timestamp = next(islice(keys, msg_number - 1, None)) + data = {"channel": self.identifier, "timestamp": timestamp, "name": reaction} + s = SlackRequest(self.team.token, method, data) + self.eventrouter.receive(s) + + def main_message_keys_reversed(self): + return (key for key in reversed(self.messages) + if type(self.messages[key]) == SlackMessage) + + # Typing related + def set_typing(self, user): + if self.channel_buffer and self.is_visible(): + self.typing[user] = time.time() + self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + + def unset_typing(self, user): + if self.channel_buffer and self.is_visible(): + u = self.typing.get(user, None) + if u: + self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + + def is_someone_typing(self): + """ + Walks through dict of typing folks in a channel and fast + returns if any of them is actively typing. If none are, + nulls the dict and returns false. + """ + for user, timestamp in self.typing.iteritems(): + if timestamp + 4 > time.time(): + return True + if len(self.typing) > 0: + self.typing = {} + self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + return False + + def get_typing_list(self): + """ + Returns the names of everyone in the channel who is currently typing. + """ + typing = [] + for user, timestamp in self.typing.iteritems(): + if timestamp + 4 > time.time(): + typing.append(user) else: - async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["history"], {"channel": self.identifier, "count": BACKLOG_SIZE}) + del self.typing[user] + return typing + def mark_read(self, ts=None, update_remote=True, force=False): + if not ts: + ts = next(self.main_message_keys_reversed(), SlackTS()) + if self.new_messages or force: + if self.channel_buffer: + w.buffer_set(self.channel_buffer, "unread", "") + w.buffer_set(self.channel_buffer, "hotlist", "-1") + if update_remote: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["mark"], {"channel": self.identifier, "ts": ts}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + self.new_messages = False + + def user_joined(self, user_id): + # ugly hack - for some reason this gets turned into a list + self.members = set(self.members) + self.members.add(user_id) + self.update_nicklist(user_id) + + def user_left(self, user_id): + self.members.discard(user_id) + self.update_nicklist(user_id) -class GroupChannel(Channel): + def update_nicklist(self, user=None): + if not self.channel_buffer: + return + if self.type not in ["channel", "group", "mpim"]: + return + w.buffer_set(self.channel_buffer, "nicklist", "1") + # create nicklists for the current channel if they don't exist + # if they do, use the existing pointer + here = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_HERE) + if not here: + here = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_HERE, "weechat.color.nicklist_group", 1) + afk = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_AWAY) + if not afk: + afk = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_AWAY, "weechat.color.nicklist_group", 1) - def __init__(self, server, name, identifier, active, last_read=0, prepend_name="", members=[], topic=""): - super(GroupChannel, self).__init__(server, name, identifier, active, last_read, prepend_name, members, topic) - self.type = "group" + if user and len(self.members) < 1000: + user = self.team.users[user] + if user.deleted: + return + nick = w.nicklist_search_nick(self.channel_buffer, "", user.name) + # since this is a change just remove it regardless of where it is + w.nicklist_remove_nick(self.channel_buffer, nick) + # now add it back in to whichever.. + nick_group = afk + if self.team.is_user_present(user.identifier): + nick_group = here + if user.identifier in self.members: + w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1) + + # if we didn't get a user, build a complete list. this is expensive. + else: + if len(self.members) < 1000: + try: + for user in self.members: + user = self.team.users[user] + if user.deleted: + continue + nick_group = afk + if self.team.is_user_present(user.identifier): + nick_group = here + w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1) + except Exception as e: + dbg("DEBUG: {} {} {}".format(self.identifier, self.name, decode_from_utf8(e))) + else: + w.nicklist_remove_all(self.channel_buffer) + for fn in ["1| too", "2| many", "3| users", "4| to", "5| show"]: + w.nicklist_add_group(self.channel_buffer, '', fn, w.color('white'), 1) + + def hash_message(self, ts): + ts = SlackTS(ts) + + def calc_hash(msg): + return sha.sha(str(msg.ts)).hexdigest() + + if ts in self.messages and not self.messages[ts].hash: + message = self.messages[ts] + tshash = calc_hash(message) + hl = 3 + shorthash = tshash[:hl] + while any(x.startswith(shorthash) for x in self.hashed_messages): + hl += 1 + shorthash = tshash[:hl] + + if shorthash[:-1] in self.hashed_messages: + col_msg = self.hashed_messages.pop(shorthash[:-1]) + col_new_hash = calc_hash(col_msg)[:hl] + col_msg.hash = col_new_hash + self.hashed_messages[col_new_hash] = col_msg + self.change_message(str(col_msg.ts)) + if col_msg.thread_channel: + col_msg.thread_channel.rename() + + self.hashed_messages[shorthash] = message + message.hash = shorthash + + +class SlackDMChannel(SlackChannel): + """ + Subclass of a normal channel for person-to-person communication, which + has some important differences. + """ -class MpdmChannel(Channel): + def __init__(self, eventrouter, users, **kwargs): + dmuser = kwargs["user"] + kwargs["name"] = users[dmuser].name + super(SlackDMChannel, self).__init__(eventrouter, **kwargs) + self.type = 'im' + self.update_color() + self.set_name(self.slack_name) - def __init__(self, server, name, identifier, active, last_read=0, prepend_name="", members=[], topic=""): - name = ",".join("-".join(name.split("-")[1:-1]).split("--")) - super(MpdmChannel, self).__init__(server, name, identifier, active, last_read, prepend_name, members, topic) - self.type = "group" + def set_name(self, slack_name): + self.name = slack_name -class DmChannel(Channel): + def get_members(self): + return {self.user} - def __init__(self, server, name, identifier, active, last_read=0, prepend_name=""): - super(DmChannel, self).__init__(server, name, identifier, active, last_read, prepend_name) - self.type = "im" + def create_buffer(self): + if not self.channel_buffer: + super(SlackDMChannel, self).create_buffer() + w.buffer_set(self.channel_buffer, "localvar_set_type", 'private') - def rename(self): - global colorize_private_chats + def update_color(self): + if config.colorize_private_chats: + self.color_name = get_nick_color_name(self.name) + self.color = w.color(self.color_name) + else: + self.color = "" + self.color_name = "" - if self.server.users.find(self.name).presence == "active": - new_name = self.server.users.find(self.name).formatted_name('+', colorize_private_chats) + def formatted_name(self, style="default", typing=False, present=True, enable_color=False, **kwargs): + if config.colorize_private_chats and enable_color: + print_color = self.color else: - new_name = self.server.users.find(self.name).formatted_name(' ', colorize_private_chats) + print_color = "" + if not present: + prepend = " " + else: + prepend = "+" + select = { + "default": self.slack_name, + "sidebar": prepend + self.slack_name, + "base": self.slack_name, + "long_default": "{}.{}".format(self.team.preferred_name, self.slack_name), + "long_base": "{}.{}".format(self.team.preferred_name, self.slack_name), + } + return print_color + select[style] + + def open(self, update_remote=True): + self.create_buffer() + # self.active = True + self.get_history() + if "info" in SLACK_API_TRANSLATOR[self.type]: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["info"], {"name": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + if update_remote: + if "join" in SLACK_API_TRANSLATOR[self.type]: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"users": self.user, "return_im": True}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + self.create_buffer() + def rename(self): if self.channel_buffer: + new_name = self.formatted_name(style="sidebar", present=self.team.is_user_present(self.user), enable_color=config.colorize_private_chats) if self.current_short_name != new_name: self.current_short_name = new_name w.buffer_set(self.channel_buffer, "short_name", new_name) + return True + return False + + def refresh(self): + return self.rename() - def update_nicklist(self, user=None): - pass -class User(object): +class SlackGroupChannel(SlackChannel): + """ + A group channel is a private discussion group. + """ - def __init__(self, server, name, identifier, presence="away", deleted=False, is_bot=False): - self.server = server - self.name = name - self.identifier = identifier - self.deleted = deleted - self.presence = presence + def __init__(self, eventrouter, **kwargs): + super(SlackGroupChannel, self).__init__(eventrouter, **kwargs) + self.type = "group" + self.set_name(self.slack_name) - self.channel_buffer = w.info_get("irc_buffer", "{}.{}".format(domain, self.name)) - self.update_color() - self.name_regex = re.compile(r"([\W]|\A)(@{0,1})" + self.name + "('s|[^'\w]|\Z)") - self.is_bot = is_bot + def set_name(self, slack_name): + self.name = config.group_name_prefix + slack_name - if deleted: - return - self.nicklist_pointer = w.nicklist_add_nick(server.buffer, "", self.name, self.color_name, "", "", 1) - if self.presence == 'away': - w.nicklist_nick_set(self.server.buffer, self.nicklist_pointer, "visible", "0") + # def formatted_name(self, prepend="#", enable_color=True, basic=False): + # return prepend + self.slack_name + + +class SlackMPDMChannel(SlackChannel): + """ + An MPDM channel is a special instance of a 'group' channel. + We change the name to look less terrible in weechat. + """ + + def __init__(self, eventrouter, **kwargs): + super(SlackMPDMChannel, self).__init__(eventrouter, **kwargs) + n = kwargs.get('name') + self.set_name(n) + self.type = "mpim" + + def open(self, update_remote=True): + self.create_buffer() + self.active = True + self.get_history() + if "info" in SLACK_API_TRANSLATOR[self.type]: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["info"], {"channel": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + if update_remote and 'join' in SLACK_API_TRANSLATOR[self.type]: + s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]['join'], {'users': ','.join(self.members)}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + self.eventrouter.receive(s) + # self.create_buffer() + + @staticmethod + def adjust_name(n): + return "|".join("-".join(n.split("-")[1:-1]).split("--")) + + def set_name(self, n): + self.name = self.adjust_name(n) + + def formatted_name(self, style="default", typing=False, **kwargs): + adjusted_name = self.adjust_name(self.slack_name) + if typing and config.channel_name_typing_indicator: + prepend = ">" else: - w.nicklist_nick_set(self.server.buffer, self.nicklist_pointer, "visible", "1") -# w.nicklist_add_nick(server.buffer, "", self.formatted_name(), "", "", "", 1) + prepend = "@" + select = { + "default": adjusted_name, + "sidebar": prepend + adjusted_name, + "base": adjusted_name, + "long_default": "{}.{}".format(self.team.preferred_name, adjusted_name), + "long_base": "{}.{}".format(self.team.preferred_name, adjusted_name), + } + return select[style] - def __str__(self): - return self.name + def rename(self): + pass - def __repr__(self): - return self.name - def __eq__(self, compare_str): - try: - if compare_str == self.name or compare_str == "@" + self.name or compare_str == self.identifier: - return True - else: - return False - except: - return False +class SlackThreadChannel(object): + """ + A thread channel is a virtual channel. We don't inherit from + SlackChannel, because most of how it operates will be different. + """ - def get_aliases(self): - return [self.name, "@" + self.name, self.identifier] + def __init__(self, eventrouter, parent_message): + self.eventrouter = eventrouter + self.parent_message = parent_message + self.channel_buffer = None + # self.identifier = "" + # self.name = "#" + kwargs['name'] + self.type = "thread" + self.got_history = False + self.label = None + self.members = self.parent_message.channel.members + self.team = self.parent_message.team + # self.set_name(self.slack_name) + # def set_name(self, slack_name): + # self.name = "#" + slack_name + + def formatted_name(self, style="default", **kwargs): + hash_or_ts = self.parent_message.hash or self.parent_message.ts + styles = { + "default": " +{}".format(hash_or_ts), + "long_default": "{}.{}".format(self.parent_message.channel.formatted_name(style="long_default"), hash_or_ts), + "sidebar": " +{}".format(hash_or_ts), + } + return styles[style] + + def refresh(self): + self.rename() + + def mark_read(self, ts=None, update_remote=True, force=False): + if self.channel_buffer: + w.buffer_set(self.channel_buffer, "unread", "") + w.buffer_set(self.channel_buffer, "hotlist", "-1") - def set_active(self): - if self.deleted: - return + def buffer_prnt(self, nick, text, timestamp, **kwargs): + data = "{}\t{}".format(nick, text) + ts = SlackTS(timestamp) + if self.channel_buffer: + # backlog messages - we will update the read marker as we print these + # backlog = False + # if ts <= SlackTS(self.last_read): + # tags = tag("backlog") + # backlog = True + # elif self.type in ["im", "mpdm"]: + # tags = tag("dm") + # self.new_messages = True + # else: + tags = tag("default") + # self.new_messages = True + w.prnt_date_tags(self.channel_buffer, ts.major, tags, data) + modify_print_time(self.channel_buffer, ts.minorstr(), ts.major) + # if backlog: + # self.mark_read(ts, update_remote=False, force=True) - self.presence = "active" - for channel in self.server.channels: - if channel.has_user(self.identifier): - channel.update_nicklist(self.identifier) - w.nicklist_nick_set(self.server.buffer, self.nicklist_pointer, "visible", "1") - dm_channel = self.server.channels.find(self.name) - if dm_channel and dm_channel.active: - buffer_list_update_next() - - def set_inactive(self): - if self.deleted: - return + def get_history(self): + self.got_history = True + for message in self.parent_message.submessages: + + # message = SlackMessage(message_json, team, channel) + text = message.render() + # print text + + suffix = '' + if 'edited' in message.message_json: + suffix = ' (edited)' + # try: + # channel.unread_count += 1 + # except: + # channel.unread_count = 1 + self.buffer_prnt(message.sender, text + suffix, message.ts) - self.presence = "away" - for channel in self.server.channels: - if channel.has_user(self.identifier): - channel.update_nicklist(self.identifier) - w.nicklist_nick_set(self.server.buffer, self.nicklist_pointer, "visible", "0") - dm_channel = self.server.channels.find(self.name) - if dm_channel and dm_channel.active: - buffer_list_update_next() + def send_message(self, message): + # team = self.eventrouter.teams[self.team] + message = linkify_text(message, self.team, self) + dbg(message) + request = {"type": "message", "channel": self.parent_message.channel.identifier, "text": message, "_team": self.team.team_hash, "user": self.team.myidentifier, "thread_ts": str(self.parent_message.ts)} + self.team.send_to_websocket(request) + self.mark_read(update_remote=False, force=True) - def update_color(self): - if colorize_nicks: - if self.name == self.server.nick: - self.color_name = w.config_string(w.config_get('weechat.color.chat_nick_self')) - else: - self.color_name = w.info_get('irc_nick_color_name', self.name) - self.color = w.color(self.color_name) - else: - self.color = "" - self.color_name = "" + def open(self, update_remote=True): + self.create_buffer() + self.active = True + self.get_history() + # if "info" in SLACK_API_TRANSLATOR[self.type]: + # s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["info"], {"name": self.identifier}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + # self.eventrouter.receive(s) + # if update_remote: + # if "join" in SLACK_API_TRANSLATOR[self.type]: + # s = SlackRequest(self.team.token, SLACK_API_TRANSLATOR[self.type]["join"], {"name": self.name}, team_hash=self.team.team_hash, channel_identifier=self.identifier) + # self.eventrouter.receive(s) + self.create_buffer() - def formatted_name(self, prepend="", enable_color=True): - if colorize_nicks and enable_color: - print_color = self.color - else: - print_color = "" - return print_color + prepend + self.name + def rename(self): + if self.channel_buffer and not self.label: + w.buffer_set(self.channel_buffer, "short_name", self.formatted_name(style="sidebar", enable_color=True)) - def create_dm_channel(self): - async_slack_api_request(self.server.domain, self.server.token, "im.open", {"user": self.identifier}) + def create_buffer(self): + """ + incomplete (muted doesn't work) + Creates the weechat buffer where the thread magic happens. + """ + if not self.channel_buffer: + self.channel_buffer = w.buffer_new(self.formatted_name(style="long_default"), "buffer_input_callback", "EVENTROUTER", "", "") + self.eventrouter.weechat_controller.register_buffer(self.channel_buffer, self) + w.buffer_set(self.channel_buffer, "localvar_set_type", 'channel') + w.buffer_set(self.channel_buffer, "localvar_set_nick", self.team.nick) + w.buffer_set(self.channel_buffer, "localvar_set_channel", self.formatted_name()) + w.buffer_set(self.channel_buffer, "short_name", self.formatted_name(style="sidebar", enable_color=True)) + time_format = w.config_string(w.config_get("weechat.look.buffer_time_format")) + parent_time = time.localtime(SlackTS(self.parent_message.ts).major) + topic = '{} {} | {}'.format(time.strftime(time_format, parent_time), self.parent_message.sender, self.parent_message.render() ) + w.buffer_set(self.channel_buffer, "title", topic) + + # self.eventrouter.weechat_controller.set_refresh_buffer_list(True) + + # try: + # if self.unread_count != 0: + # for c in range(1, self.unread_count): + # if self.type == "im": + # w.buffer_set(self.channel_buffer, "hotlist", "2") + # else: + # w.buffer_set(self.channel_buffer, "hotlist", "1") + # else: + # pass + # #dbg("no unread in {}".format(self.name)) + # except: + # pass + # dbg("exception no unread count") + # if self.unread_count != 0 and not self.muted: + # w.buffer_set(self.channel_buffer, "hotlist", "1") + + def destroy_buffer(self, update_remote): + if self.channel_buffer is not None: + self.channel_buffer = None + self.got_history = False + # if update_remote and not eventrouter.shutting_down: + self.active = False -class Bot(object): - def __init__(self, server, name, identifier, deleted=False): - self.server = server - self.name = name - self.identifier = identifier - self.deleted = deleted - self.update_color() +class SlackUser(object): + """ + Represends an individual slack user. Also where you set their name formatting. + """ - def __eq__(self, compare_str): - if compare_str == self.identifier or compare_str == self.name: - return True + def __init__(self, **kwargs): + # We require these two things for a valid object, + # the rest we can just learn from slack + self.identifier = kwargs["id"] + self.profile = {} # in case it's not in kwargs + for key, value in kwargs.items(): + setattr(self, key, value) + + if self.profile.get("display_name"): + self.slack_name = self.profile["display_name"] + self.name = self.profile["display_name"].replace(' ', '') else: - return False - - def __str__(self): - return "{}".format(self.identifier) + # No display name set. Fall back to the deprecated username field. + self.slack_name = kwargs["name"] + self.name = self.slack_name + self.update_color() def __repr__(self): - return "{}".format(self.identifier) + return "Name:{} Identifier:{}".format(self.name, self.identifier) + + def force_color(self, color_name): + self.color_name = color_name + self.color = w.color(self.color_name) def update_color(self): - if colorize_nicks: - self.color_name = w.info_get('irc_nick_color_name', self.name.encode('utf-8')) - self.color = w.color(self.color_name) - else: - self.color_name = "" - self.color = "" + # This will automatically be none/"" if the user has disabled nick + # colourization. + self.color_name = get_nick_color_name(self.name) + self.color = w.color(self.color_name) def formatted_name(self, prepend="", enable_color=True): - if colorize_nicks and enable_color: - print_color = self.color + if enable_color: + return self.color + prepend + self.name else: - print_color = "" - return print_color + prepend + self.name + return prepend + self.name + + +class SlackBot(SlackUser): + """ + Basically the same as a user, but split out to identify and for future + needs + """ + def __init__(self, **kwargs): + super(SlackBot, self).__init__(**kwargs) -class Message(object): - def __init__(self, message_json): +class SlackMessage(object): + """ + Represents a single slack message and associated context/metadata. + These are modifiable and can be rerendered to change a message, + delete a message, add a reaction, add a thread. + Note: these can't be tied to a SlackUser object because users + can be deleted, so we have to store sender in each one. + """ + def __init__(self, message_json, team, channel, override_sender=None): + self.team = team + self.channel = channel self.message_json = message_json - self.ts = message_json['ts'] - #split timestamp into time and counter - self.ts_time, self.ts_counter = message_json['ts'].split('.') + self.submessages = [] + self.thread_channel = None + self.hash = None + if override_sender: + self.sender = override_sender + self.sender_plain = override_sender + else: + senders = self.get_sender() + self.sender, self.sender_plain = senders[0], senders[1] + self.suffix = '' + self.ts = SlackTS(message_json['ts']) + text = self.message_json.get('text') + if text and text.startswith('_') and text.endswith('_') and 'subtype' not in message_json: + message_json['text'] = text[1:-1] + message_json['subtype'] = 'me_message' + if message_json.get('subtype') == 'me_message' and not message_json['text'].startswith(self.sender): + message_json['text'] = self.sender + ' ' + self.message_json['text'] + + def __hash__(self): + return hash(self.ts) + + def render(self, force=False): + if len(self.submessages) > 0: + return "{} {} {}".format(render(self.message_json, self.team, self.channel, force), self.suffix, "{}[ Thread: {} Replies: {} ]".format(w.color(config.thread_suffix_color), self.hash or self.ts, len(self.submessages))) + return "{} {}".format(render(self.message_json, self.team, self.channel, force), self.suffix) def change_text(self, new_text): - if not isinstance(new_text, unicode): - new_text = unicode(new_text, 'utf-8') self.message_json["text"] = new_text + dbg(self.message_json) + + def change_suffix(self, new_suffix): + self.suffix = new_suffix + dbg(self.message_json) + + def get_sender(self): + name = "" + name_plain = "" + if self.message_json.get('bot_id') in self.team.bots: + name = "{} :]".format(self.team.bots[self.message_json["bot_id"]].formatted_name()) + name_plain = "{}".format(self.team.bots[self.message_json["bot_id"]].formatted_name(enable_color=False)) + elif 'user' in self.message_json: + if self.message_json['user'] == self.team.myidentifier: + name = self.team.users[self.team.myidentifier].name + name_plain = self.team.users[self.team.myidentifier].name + elif self.message_json['user'] in self.team.users: + u = self.team.users[self.message_json['user']] + if u.is_bot: + name = "{} :]".format(u.formatted_name()) + else: + name = "{}".format(u.formatted_name()) + name_plain = "{}".format(u.formatted_name(enable_color=False)) + elif 'username' in self.message_json: + name = "-{}-".format(self.message_json["username"]) + name_plain = "{}".format(self.message_json["username"]) + elif 'service_name' in self.message_json: + name = "-{}-".format(self.message_json["service_name"]) + name_plain = "{}".format(self.message_json["service_name"]) + else: + name = "" + name_plain = "" + return (name, name_plain) def add_reaction(self, reaction, user): - if "reactions" in self.message_json: + m = self.message_json.get('reactions', None) + if m: found = False - for r in self.message_json["reactions"]: + for r in m: if r["name"] == reaction and user not in r["users"]: r["users"].append(user) found = True - if not found: - self.message_json["reactions"].append({u"name": reaction, u"users": [user]}) + self.message_json["reactions"].append({"name": reaction, "users": [user]}) else: - self.message_json["reactions"] = [{u"name": reaction, u"users": [user]}] + self.message_json["reactions"] = [{"name": reaction, "users": [user]}] def remove_reaction(self, reaction, user): - if "reactions" in self.message_json: - for r in self.message_json["reactions"]: + m = self.message_json.get('reactions', None) + if m: + for r in m: if r["name"] == reaction and user in r["users"]: r["users"].remove(user) else: pass - def __eq__(self, other): - return self.ts_time == other or self.ts == other - - def __repr__(self): - return "{} {} {} {}\n".format(self.ts_time, self.ts_counter, self.ts, self.message_json) - - def __lt__(self, other): - return self.ts < other.ts -# Only run this function if we're in a slack buffer, else ignore -def slack_buffer_or_ignore(f): - @wraps(f) - def wrapper(current_buffer, *args, **kwargs): - server = servers.find(current_domain_name()) - if not server: - return w.WEECHAT_RC_OK - return f(current_buffer, *args, **kwargs) - return wrapper +class SlackThreadMessage(SlackMessage): + def __init__(self, parent_id, *args): + super(SlackThreadMessage, self).__init__(*args) + self.parent_id = parent_id -def slack_command_cb(data, current_buffer, args): - a = args.split(' ', 1) - if len(a) > 1: - function_name, args = a[0], " ".join(a[1:]) - else: - function_name, args = a[0], None - try: - command = cmds[function_name](current_buffer, args) - except KeyError: - w.prnt("", "Command not found: " + function_name) - return w.WEECHAT_RC_OK +class WeeSlackMetadata(object): + """ + A simple container that we pickle/unpickle to hold data. + """ + def __init__(self, meta): + self.meta = meta -@slack_buffer_or_ignore -def me_command_cb(data, current_buffer, args): - if channels.find(current_buffer): - channel = channels.find(current_buffer) - nick = channel.server.nick - message = "_{}_".format(args) - buffer_input_cb("", current_buffer, message) - return w.WEECHAT_RC_OK + def jsonify(self): + return self.meta -@slack_buffer_or_ignore -def join_command_cb(data, current_buffer, args): - args = args.split() - if len(args) < 2: - w.prnt(current_buffer, "Missing channel argument") - return w.WEECHAT_RC_OK_EAT - elif command_talk(current_buffer, args[1]): - return w.WEECHAT_RC_OK_EAT - else: - return w.WEECHAT_RC_OK +class SlackTS(object): -@slack_buffer_or_ignore -def part_command_cb(data, current_buffer, args): - if channels.find(current_buffer) or servers.find(current_buffer): - args = args.split() - if len(args) > 1: - channel = args[1:] - servers.find(current_domain_name()).channels.find(channel).close(True) + def __init__(self, ts=None): + if ts: + self.major, self.minor = [int(x) for x in ts.split('.', 1)] else: - channels.find(current_buffer).close(True) - return w.WEECHAT_RC_OK_EAT - else: - return w.WEECHAT_RC_OK - - -# Wrap command_ functions that require they be performed in a slack buffer -def slack_buffer_required(f): - @wraps(f) - def wrapper(current_buffer, *args, **kwargs): - server = servers.find(current_domain_name()) - if not server: - w.prnt(current_buffer, "This command must be used in a slack buffer") - return w.WEECHAT_RC_ERROR - return f(current_buffer, *args, **kwargs) - return wrapper - -def command_register(current_buffer, args): - CLIENT_ID="2468770254.51917335286" - CLIENT_SECRET="dcb7fe380a000cba0cca3169a5fe8d70" #this is not really a secret - if not args: - message = """ -#### Retrieving a Slack token via OAUTH #### - -1) Paste this into a browser: https://slack.com/oauth/authorize?client_id=2468770254.51917335286&scope=client -2) Select the team you wish to access from wee-slack in your browser. -3) Click "Authorize" in the browser **IMPORTANT: the redirect will fail, this is expected** -4) Copy the "code" portion of the URL to your clipboard -5) Return to weechat and run `/slack register [code]` -6) Add the returned token per the normal wee-slack setup instructions - - -""" - w.prnt(current_buffer, message) - else: - aargs = args.split(None, 2) - if len(aargs) <> 1: - w.prnt(current_buffer, "ERROR: invalid args to register") + self.major = int(time.time()) + self.minor = 0 + + def __cmp__(self, other): + if isinstance(other, SlackTS): + if self.major < other.major: + return -1 + elif self.major > other.major: + return 1 + elif self.major == other.major: + if self.minor < other.minor: + return -1 + elif self.minor > other.minor: + return 1 + else: + return 0 else: - #w.prnt(current_buffer, "https://slack.com/api/oauth.access?client_id={}&client_secret={}&code={}".format(CLIENT_ID, CLIENT_SECRET, aargs[0])) - ret = urllib.urlopen("https://slack.com/api/oauth.access?client_id={}&client_secret={}&code={}".format(CLIENT_ID, CLIENT_SECRET, aargs[0])).read() - d = json.loads(ret) - if d["ok"] == True: - w.prnt(current_buffer, "Success! Access token is: " + d['access_token']) - else: - w.prnt(current_buffer, "Failed! Error is: " + d['error']) + s = self.__str__() + if s < other: + return -1 + elif s > other: + return 1 + elif s == other: + return 0 + def __hash__(self): + return hash("{}.{}".format(self.major, self.minor)) -@slack_buffer_or_ignore -def msg_command_cb(data, current_buffer, args): - dbg("msg_command_cb") - aargs = args.split(None, 2) - who = aargs[1] - - command_talk(current_buffer, who) + def __repr__(self): + return str("{0}.{1:06d}".format(self.major, self.minor)) - if len(aargs) > 2: - message = aargs[2] - server = servers.find(current_domain_name()) - if server: - channel = server.channels.find(who) - channel.send_message(message) - return w.WEECHAT_RC_OK_EAT + def split(self, *args, **kwargs): + return [self.major, self.minor] + def majorstr(self): + return str(self.major) -@slack_buffer_required -def command_upload(current_buffer, args): - """ - Uploads a file to the current buffer - /slack upload [file_path] - """ - post_data = {} - channel = current_buffer_name(short=True) - domain = current_domain_name() - token = servers.find(domain).token + def minorstr(self): + return str(self.minor) - if servers.find(domain).channels.find(channel): - channel_identifier = servers.find(domain).channels.find(channel).identifier +###### New handlers - if channel_identifier: - post_data["token"] = token - post_data["channels"] = channel_identifier - post_data["file"] = args - async_slack_api_upload_request(token, "files.upload", post_data) -def command_talk(current_buffer, args): +def handle_rtmstart(login_data, eventrouter): """ - Open a chat with the specified user - /slack talk [user] + This handles the main entry call to slack, rtm.start """ + metadata = pickle.loads(login_data["wee_slack_request_metadata"]) - server = servers.find(current_domain_name()) - if server: - channel = server.channels.find(args) - if channel is None: - user = server.users.find(args) - if user: - user.create_dm_channel() - else: - server.buffer_prnt("User or channel {} not found.".format(args)) - else: - channel.open() - if w.config_get_plugin('switch_buffer_on_join') != '0': - w.buffer_set(channel.channel_buffer, "display", "1") - return True - else: - return False + if not login_data["ok"]: + w.prnt("", "ERROR: Failed connecting to Slack with token {}: {}" + .format(metadata.token, login_data["error"])) + return -def command_join(current_buffer, args): - """ - Join the specified channel - /slack join [channel] - """ - domain = current_domain_name() - if domain == "": - if len(servers) == 1: - domain = servers[0] - else: - w.prnt(current_buffer, "You are connected to multiple Slack instances, please execute /join from a server buffer. i.e. (domain).slack.com") - return - channel = servers.find(domain).channels.find(args) - if channel != None: - servers.find(domain).channels.find(args).open() - else: - w.prnt(current_buffer, "Channel not found.") + # Let's reuse a team if we have it already. + th = SlackTeam.generate_team_hash(login_data['self']['name'], login_data['team']['domain']) + if not eventrouter.teams.get(th): + users = {} + for item in login_data["users"]: + users[item["id"]] = SlackUser(**item) -@slack_buffer_required -def command_channels(current_buffer, args): - """ - List all the channels for the slack instance (name, id, active) - /slack channels - """ - server = servers.find(current_domain_name()) - for channel in server.channels: - line = "{:<25} {} {}".format(channel.name, channel.identifier, channel.active) - server.buffer_prnt(line) + bots = {} + for item in login_data["bots"]: + bots[item["id"]] = SlackBot(**item) + channels = {} + for item in login_data["channels"]: + channels[item["id"]] = SlackChannel(eventrouter, **item) -def command_nodistractions(current_buffer, args): - global hide_distractions - hide_distractions = not hide_distractions - if distracting_channels != ['']: - for channel in distracting_channels: - try: - channel_buffer = channels.find(channel).channel_buffer - if channel_buffer: - w.buffer_set(channels.find(channel).channel_buffer, "hidden", str(int(hide_distractions))) - except: - dbg("Can't hide channel {}".format(channel), main_buffer=True) + for item in login_data["ims"]: + channels[item["id"]] = SlackDMChannel(eventrouter, users, **item) + for item in login_data["groups"]: + if item["name"].startswith('mpdm-'): + channels[item["id"]] = SlackMPDMChannel(eventrouter, **item) + else: + channels[item["id"]] = SlackGroupChannel(eventrouter, **item) + + t = SlackTeam( + eventrouter, + metadata.token, + login_data['url'], + login_data["team"]["domain"], + login_data["self"]["name"], + login_data["self"]["id"], + users, + bots, + channels, + muted_channels=login_data["self"]["prefs"]["muted_channels"], + highlight_words=login_data["self"]["prefs"]["highlight_words"], + ) + eventrouter.register_team(t) -def command_distracting(current_buffer, args): - global distracting_channels - distracting_channels = [x.strip() for x in w.config_get_plugin("distracting_channels").split(',')] - if channels.find(current_buffer) is None: - w.prnt(current_buffer, "This command must be used in a channel buffer") - return - fullname = channels.find(current_buffer).fullname() - if distracting_channels.count(fullname) == 0: - distracting_channels.append(fullname) else: - distracting_channels.pop(distracting_channels.index(fullname)) - new = ','.join(distracting_channels) - w.config_set_plugin('distracting_channels', new) - + t = eventrouter.teams.get(th) + t.set_reconnect_url(login_data['url']) + t.connect() -@slack_buffer_required -def command_users(current_buffer, args): - """ - List all the users for the slack instance (name, id, away) - /slack users - """ - server = servers.find(current_domain_name()) - for user in server.users: - line = "{:<40} {} {}".format(user.formatted_name(), user.identifier, user.presence) - server.buffer_prnt(line) + t.buffer_prnt('Connected to Slack') + t.buffer_prnt('{:<20} {}'.format("Websocket URL", login_data["url"])) + t.buffer_prnt('{:<20} {}'.format("User name", login_data["self"]["name"])) + t.buffer_prnt('{:<20} {}'.format("User ID", login_data["self"]["id"])) + t.buffer_prnt('{:<20} {}'.format("Team name", login_data["team"]["name"])) + t.buffer_prnt('{:<20} {}'.format("Team domain", login_data["team"]["domain"])) + t.buffer_prnt('{:<20} {}'.format("Team id", login_data["team"]["id"])) + dbg("connected to {}".format(t.domain)) -def command_setallreadmarkers(current_buffer, args): - """ - Sets the read marker for all channels - /slack setallreadmarkers - """ - for channel in channels: - channel.mark_read() -def command_changetoken(current_buffer, args): - w.config_set_plugin('slack_api_token', args) +def handle_emojilist(emoji_json, eventrouter, **kwargs): + if emoji_json["ok"]: + request_metadata = pickle.loads(emoji_json["wee_slack_request_metadata"]) + team = eventrouter.teams[request_metadata.team_hash] + team.emoji_completions.extend(emoji_json["emoji"].keys()) -def command_test(current_buffer, args): - w.prnt(current_buffer, "worked!") +def handle_channelsinfo(channel_json, eventrouter, **kwargs): + request_metadata = pickle.loads(channel_json["wee_slack_request_metadata"]) + team = eventrouter.teams[request_metadata.team_hash] + channel = team.channels[request_metadata.channel_identifier] + channel.set_unread_count_display(channel_json['channel']['unread_count_display']) + channel.set_members(channel_json['channel']['members']) +def handle_groupsinfo(group_json, eventrouter, **kwargs): + request_metadata = pickle.loads(group_json["wee_slack_request_metadata"]) + team = eventrouter.teams[request_metadata.team_hash] + group = team.channels[request_metadata.channel_identifier] + unread_count_display = group_json['group']['unread_count_display'] + group_id = group_json['group']['id'] + group.set_unread_count_display(unread_count_display) -@slack_buffer_required -def command_away(current_buffer, args): - """ - Sets your status as 'away' - /slack away - """ - server = servers.find(current_domain_name()) - async_slack_api_request(server.domain, server.token, 'presence.set', {"presence": "away"}) +def handle_conversationsopen(conversation_json, eventrouter, object_name='channel', **kwargs): + request_metadata = pickle.loads(conversation_json["wee_slack_request_metadata"]) + # Set unread count if the channel isn't new (channel_identifier exists) + if hasattr(request_metadata, 'channel_identifier'): + channel_id = request_metadata.channel_identifier + team = eventrouter.teams[request_metadata.team_hash] + conversation = team.channels[channel_id] + unread_count_display = conversation_json[object_name]['unread_count_display'] + conversation.set_unread_count_display(unread_count_display) -@slack_buffer_required -def command_back(current_buffer, args): - """ - Sets your status as 'back' - /slack back - """ - server = servers.find(current_domain_name()) - async_slack_api_request(server.domain, server.token, 'presence.set', {"presence": "active"}) +def handle_mpimopen(mpim_json, eventrouter, object_name='group', **kwargs): + handle_conversationsopen(mpim_json, eventrouter, object_name, **kwargs) -@slack_buffer_required -def command_markread(current_buffer, args): - """ - Marks current channel as read - /slack markread - """ - # refactor this - one liner i think - channel = current_buffer_name(short=True) - domain = current_domain_name() - if servers.find(domain).channels.find(channel): - servers.find(domain).channels.find(channel).mark_read() - -def command_flushcache(current_buffer, args): - global message_cache - message_cache = collections.defaultdict(list) - cache_write_cb("","") - -def command_cachenow(current_buffer, args): - cache_write_cb("","") - -def command_neveraway(current_buffer, args): - global never_away - if never_away: - never_away = False - dbg("unset never_away", main_buffer=True) - else: - never_away = True - dbg("set never_away", main_buffer=True) +def handle_groupshistory(message_json, eventrouter, **kwargs): + handle_history(message_json, eventrouter, **kwargs) -def command_printvar(current_buffer, args): - w.prnt("", "{}".format(eval(args))) +def handle_channelshistory(message_json, eventrouter, **kwargs): + handle_history(message_json, eventrouter, **kwargs) -def command_p(current_buffer, args): - w.prnt("", "{}".format(eval(args))) +def handle_imhistory(message_json, eventrouter, **kwargs): + handle_history(message_json, eventrouter, **kwargs) -def command_debug(current_buffer, args): - create_slack_debug_buffer() +def handle_mpimhistory(message_json, eventrouter, **kwargs): + handle_history(message_json, eventrouter, **kwargs) -def command_debugstring(current_buffer, args): - global debug_string - if args == '': - debug_string = None +def handle_history(message_json, eventrouter, **kwargs): + request_metadata = pickle.loads(message_json["wee_slack_request_metadata"]) + kwargs['team'] = eventrouter.teams[request_metadata.team_hash] + kwargs['channel'] = kwargs['team'].channels[request_metadata.channel_identifier] + try: + clear = request_metadata.clear + except: + clear = False + dbg(clear) + kwargs['output_type'] = "backlog" + if clear: + w.buffer_clear(kwargs['channel'].channel_buffer) + for message in reversed(message_json["messages"]): + process_message(message, eventrouter, **kwargs) + +###### New/converted process_ and subprocess_ methods + +def process_hello(message_json, eventrouter, **kwargs): + kwargs['team'].subscribe_users_presence() + +def process_reconnect_url(message_json, eventrouter, **kwargs): + kwargs['team'].set_reconnect_url(message_json['url']) + + +def process_manual_presence_change(message_json, eventrouter, **kwargs): + process_presence_change(message_json, eventrouter, **kwargs) + + +def process_presence_change(message_json, eventrouter, **kwargs): + if "user" in kwargs: + # TODO: remove once it's stable + user = kwargs["user"] + team = kwargs["team"] + team.update_member_presence(user, message_json["presence"]) + if "users" in message_json: + team = kwargs["team"] + for user_id in message_json["users"]: + user = team.users[user_id] + team.update_member_presence(user, message_json["presence"]) + + +def process_pref_change(message_json, eventrouter, **kwargs): + team = kwargs["team"] + if message_json['name'] == 'muted_channels': + team.set_muted_channels(message_json['value']) + elif message_json['name'] == 'highlight_words': + team.set_highlight_words(message_json['value']) else: - debug_string = args + dbg("Preference change not implemented: {}\n".format(message_json['name'])) -def command_search(current_buffer, args): - pass -# if not slack_buffer: -# create_slack_buffer() -# w.buffer_set(slack_buffer, "display", "1") -# query = args -# w.prnt(slack_buffer,"\nSearched for: %s\n\n" % (query)) -# reply = slack_api_request('search.messages', {"query":query}).read() -# data = json.loads(reply) -# for message in data['messages']['matches']: -# message["text"] = message["text"].encode('ascii', 'ignore') -# formatted_message = "%s / %s:\t%s" % (message["channel"]["name"], message['username'], message['text']) -# w.prnt(slack_buffer,str(formatted_message)) - - -def command_nick(current_buffer, args): - pass -# urllib.urlopen("https://%s/account/settings" % (domain)) -# browser.select_form(nr=0) -# browser.form['username'] = args -# reply = browser.submit() +def process_user_typing(message_json, eventrouter, **kwargs): + channel = kwargs["channel"] + team = kwargs["team"] + if channel: + channel.set_typing(team.users.get(message_json["user"]).name) + w.bar_item_update("slack_typing_notice") -def command_help(current_buffer, args): - help_cmds = { k[8:]: v.__doc__ for k, v in globals().items() if k.startswith("command_") } +def process_team_join(message_json, eventrouter, **kwargs): + user = message_json['user'] + team = kwargs["team"] + team.users[user["id"]] = SlackUser(**user) - if args: - try: - help_cmds = {args: help_cmds[args]} - except KeyError: - w.prnt("", "Command not found: " + args) - return - for cmd, helptext in help_cmds.items(): - w.prnt('', w.color("bold") + cmd) - w.prnt('', (helptext or 'No help text').strip()) - w.prnt('', '') +def process_pong(message_json, eventrouter, **kwargs): + pass -# Websocket handling methods -def command_openweb(current_buffer, args): - trigger = w.config_get_plugin('trigger_value') - if trigger != "0": - if args is None: - channel = channels.find(current_buffer) - url = "{}/messages/{}".format(channel.server.server_buffer_name, channel.name) - topic = w.buffer_get_string(channel.channel_buffer, "title") - w.buffer_set(channel.channel_buffer, "title", "{}:{}".format(trigger, url)) - w.hook_timer(1000, 0, 1, "command_openweb", json.dumps({"topic": topic, "buffer": current_buffer})) - else: - #TODO: fix this dirty hack because i don't know the right way to send multiple args. - args = current_buffer - data = json.loads(args) - channel_buffer = channels.find(data["buffer"]).channel_buffer - w.buffer_set(channel_buffer, "title", data["topic"]) - return w.WEECHAT_RC_OK +def process_message(message_json, eventrouter, store=True, **kwargs): + channel = kwargs["channel"] + team = kwargs["team"] + # try: + # send these subtype messages elsewhere + known_subtypes = [ + 'thread_message', + 'message_replied', + 'message_changed', + 'message_deleted', + 'channel_join', + 'channel_leave', + 'channel_topic', + # 'group_join', + # 'group_leave', + ] + if "thread_ts" in message_json and "reply_count" not in message_json: + message_json["subtype"] = "thread_message" + + subtype = message_json.get("subtype", None) + if subtype and subtype in known_subtypes: + f = eval('subprocess_' + subtype) + f(message_json, eventrouter, channel, team) -@slack_buffer_or_ignore -def topic_command_cb(data, current_buffer, args): - n = len(args.split()) - if n < 2: - channel = channels.find(current_buffer) - if channel: - w.prnt(current_buffer, 'Topic for {} is "{}"'.format(channel.name, channel.topic)) - return w.WEECHAT_RC_OK_EAT - elif command_topic(current_buffer, args.split(None, 1)[1]): - return w.WEECHAT_RC_OK_EAT else: - return w.WEECHAT_RC_ERROR + message = SlackMessage(message_json, team, channel) + text = message.render() + dbg("Rendered message: %s" % text) + dbg("Sender: %s (%s)" % (message.sender, message.sender_plain)) + + # Handle actions (/me). + # We don't use `subtype` here because creating the SlackMessage may + # have changed the subtype based on the detected message contents. + if message.message_json.get('subtype') == 'me_message': + try: + channel.unread_count_display += 1 + except: + channel.unread_count_display = 1 + channel.buffer_prnt(w.prefix("action").rstrip(), text, message.ts, tag_nick=message.sender_plain, **kwargs) -def command_topic(current_buffer, args): - """ - Change the topic of a channel - /slack topic [] [|-delete] - """ - server = servers.find(current_domain_name()) - if server: - arrrrgs = args.split(None, 1) - if arrrrgs[0].startswith('#'): - channel = server.channels.find(arrrrgs[0]) - topic = arrrrgs[1] else: - channel = server.channels.find(current_buffer) - topic = args + suffix = '' + if 'edited' in message_json: + suffix = ' (edited)' + try: + channel.unread_count_display += 1 + except: + channel.unread_count_display = 1 + channel.buffer_prnt(message.sender, text + suffix, message.ts, tag_nick=message.sender_plain, **kwargs) + + if store: + channel.store_message(message, team) + dbg("NORMAL REPLY {}".format(message_json)) + # except: + # channel.buffer_prnt("WEE-SLACK-ERROR", json.dumps(message_json), message_json["ts"], **kwargs) + # traceback.print_exc() + + +def subprocess_thread_message(message_json, eventrouter, channel, team): + # print ("THREADED: " + str(message_json)) + parent_ts = message_json.get('thread_ts', None) + if parent_ts: + parent_message = channel.messages.get(SlackTS(parent_ts), None) + if parent_message: + message = SlackThreadMessage(parent_ts, message_json, team, channel) + parent_message.submessages.append(message) + channel.hash_message(parent_ts) + channel.store_message(message, team) + channel.change_message(parent_ts) + + text = message.render() + # channel.buffer_prnt(message.sender, text, message.ts, **kwargs) + if parent_message.thread_channel: + parent_message.thread_channel.buffer_prnt(message.sender, text, message.ts) + +# channel = channels.find(message_json["channel"]) +# server = channel.server +# #threadinfo = channel.get_message(message_json["thread_ts"]) +# message = Message(message_json, server=server, channel=channel) +# dbg(message, main_buffer=True) +# +# orig = channel.get_message(message_json['thread_ts']) +# if orig[0]: +# channel.get_message(message_json['thread_ts'])[2].add_thread_message(message) +# else: +# dbg("COULDN'T find orig message {}".format(message_json['thread_ts']), main_buffer=True) + + # if threadinfo[0]: + # channel.messages[threadinfo[1]].become_thread() + # message_json["item"]["ts"], message_json) + # channel.change_message(message_json["thread_ts"], None, message_json["text"]) + # channel.become_thread(message_json["item"]["ts"], message_json) + - if channel: - if topic == "-delete": - async_slack_api_request(server.domain, server.token, 'channels.setTopic', {"channel": channel.identifier, "topic": ""}) +def subprocess_channel_join(message_json, eventrouter, channel, team): + joinprefix = w.prefix("join") + message = SlackMessage(message_json, team, channel, override_sender=joinprefix) + channel.buffer_prnt(joinprefix, message.render(), message_json["ts"], tagset='joinleave') + channel.user_joined(message_json['user']) + + +def subprocess_channel_leave(message_json, eventrouter, channel, team): + leaveprefix = w.prefix("quit") + message = SlackMessage(message_json, team, channel, override_sender=leaveprefix) + channel.buffer_prnt(leaveprefix, message.render(), message_json["ts"], tagset='joinleave') + channel.user_left(message_json['user']) + # channel.update_nicklist(message_json['user']) + # channel.update_nicklist() + + +def subprocess_message_replied(message_json, eventrouter, channel, team): + pass + + +def subprocess_message_changed(message_json, eventrouter, channel, team): + m = message_json.get("message", None) + if m: + new_message = m + # message = SlackMessage(new_message, team, channel) + if "attachments" in m: + message_json["attachments"] = m["attachments"] + if "text" in m: + if "text" in message_json: + message_json["text"] += m["text"] + dbg("added text!") else: - async_slack_api_request(server.domain, server.token, 'channels.setTopic', {"channel": channel.identifier, "topic": topic}) - return True - else: - return False + message_json["text"] = m["text"] + if "fallback" in m: + if "fallback" in message_json: + message_json["fallback"] += m["fallback"] + else: + message_json["fallback"] = m["fallback"] + + new_message["text"] += unwrap_attachments(message_json, new_message["text"]) + if "edited" in new_message: + channel.change_message(new_message["ts"], new_message["text"], ' (edited)') else: - return False + channel.change_message(new_message["ts"], new_message["text"]) +def subprocess_message_deleted(message_json, eventrouter, channel, team): + channel.change_message(message_json["deleted_ts"], "(deleted)", '') -def slack_websocket_cb(server, fd): - try: - data = servers.find(server).ws.recv() - message_json = json.loads(data) - # this magic attaches json that helps find the right dest - message_json['_server'] = server - except WebSocketConnectionClosedException: - servers.find(server).ws.close() - return w.WEECHAT_RC_OK - except Exception: - dbg("socket issue: {}\n".format(traceback.format_exc())) - return w.WEECHAT_RC_OK - # dispatch here - if "reply_to" in message_json: - function_name = "reply" - elif "type" in message_json: - function_name = message_json["type"] - else: - function_name = "unknown" + +def subprocess_channel_topic(message_json, eventrouter, channel, team): + text = unhtmlescape(unfurl_refs(message_json["text"], ignore_alt_text=False)) + channel.buffer_prnt(w.prefix("network").rstrip(), text, message_json["ts"], tagset="muted") + channel.set_topic(unhtmlescape(message_json["topic"])) + + +def process_reply(message_json, eventrouter, **kwargs): + dbg('processing reply') + team = kwargs["team"] + identifier = message_json["reply_to"] try: - proc[function_name](message_json) - except KeyError: - if function_name: - dbg("Function not implemented: {}\n{}".format(function_name, message_json)) + original_message_json = team.ws_replies[identifier] + del team.ws_replies[identifier] + if "ts" in message_json: + original_message_json["ts"] = message_json["ts"] else: - dbg("Function not implemented\n{}".format(message_json)) - w.bar_item_update("slack_typing_notice") - return w.WEECHAT_RC_OK + dbg("no reply ts {}".format(message_json)) -def process_reply(message_json): - global unfurl_ignore_alt_text + c = original_message_json.get('channel', None) + channel = team.channels[c] + m = SlackMessage(original_message_json, team, channel) - server = servers.find(message_json["_server"]) - identifier = message_json["reply_to"] - item = server.message_buffer.pop(identifier) - if 'text' in item and type(item['text']) is not unicode: - item['text'] = item['text'].decode('UTF-8', 'replace') - if "type" in item: - if item["type"] == "message" and "channel" in item.keys(): - item["ts"] = message_json["ts"] - channels.find(item["channel"]).cache_message(item, from_me=True) - text = unfurl_refs(item["text"], ignore_alt_text=unfurl_ignore_alt_text) - - channels.find(item["channel"]).buffer_prnt(item["user"], text, item["ts"]) - dbg("REPLY {}".format(item)) - -def process_pong(message_json): - pass + # if "type" in message_json: + # if message_json["type"] == "message" and "channel" in message_json.keys(): + # message_json["ts"] = message_json["ts"] + # channels.find(message_json["channel"]).store_message(m, from_me=True) + # channels.find(message_json["channel"]).buffer_prnt(server.nick, m.render(), m.ts) -def process_pref_change(message_json): - server = servers.find(message_json["_server"]) - if message_json['name'] == u'muted_channels': - muted = message_json['value'].split(',') - for c in server.channels: - if c.identifier in muted: - c.muted = True - else: - c.muted = False + process_message(m.message_json, eventrouter, channel=channel, team=team) + channel.mark_read(update_remote=True, force=True) + dbg("REPLY {}".format(message_json)) + except KeyError: + dbg("Unexpected reply {}".format(message_json)) + + +def process_channel_marked(message_json, eventrouter, **kwargs): + """ + complete + """ + channel = kwargs["channel"] + ts = message_json.get("ts", None) + if ts: + channel.mark_read(ts=ts, force=True, update_remote=False) else: - dbg("Preference change not implemented: {}\n".format(message_json['name'])) + dbg("tried to mark something weird {}".format(message_json)) -def process_team_join(message_json): - server = servers.find(message_json["_server"]) - item = message_json["user"] - server.add_user(User(server, item["name"], item["id"], item["presence"])) - server.buffer_prnt("New user joined: {}".format(item["name"])) +def process_group_marked(message_json, eventrouter, **kwargs): + process_channel_marked(message_json, eventrouter, **kwargs) -def process_manual_presence_change(message_json): - process_presence_change(message_json) -def process_presence_change(message_json): - server = servers.find(message_json["_server"]) - identifier = message_json.get("user", server.nick) - if message_json["presence"] == 'active': - server.users.find(identifier).set_active() - else: - server.users.find(identifier).set_inactive() +def process_im_marked(message_json, eventrouter, **kwargs): + process_channel_marked(message_json, eventrouter, **kwargs) -def process_channel_marked(message_json): - channel = channels.find(message_json["channel"]) - channel.mark_read(False) - w.buffer_set(channel.channel_buffer, "hotlist", "-1") +def process_mpim_marked(message_json, eventrouter, **kwargs): + process_channel_marked(message_json, eventrouter, **kwargs) -def process_group_marked(message_json): - channel = channels.find(message_json["channel"]) - channel.mark_read(False) - w.buffer_set(channel.channel_buffer, "hotlist", "-1") +def process_channel_joined(message_json, eventrouter, **kwargs): + item = message_json["channel"] + kwargs['team'].channels[item["id"]].update_from_message_json(item) + kwargs['team'].channels[item["id"]].open() -def process_channel_created(message_json): - server = servers.find(message_json["_server"]) +def process_channel_created(message_json, eventrouter, **kwargs): item = message_json["channel"] - if server.channels.find(message_json["channel"]["name"]): - server.channels.find(message_json["channel"]["name"]).open(False) - else: - item = message_json["channel"] - server.add_channel(Channel(server, item["name"], item["id"], False, prepend_name="#")) - server.buffer_prnt("New channel created: {}".format(item["name"])) + c = SlackChannel(eventrouter, team=kwargs["team"], **item) + kwargs['team'].channels[item["id"]] = c + kwargs['team'].buffer_prnt('Channel created: {}'.format(c.slack_name)) -def process_channel_left(message_json): - server = servers.find(message_json["_server"]) - server.channels.find(message_json["channel"]).close(False) +def process_channel_rename(message_json, eventrouter, **kwargs): + item = message_json["channel"] + channel = kwargs['team'].channels[item["id"]] + channel.slack_name = message_json['channel']['name'] -def process_channel_join(message_json): - server = servers.find(message_json["_server"]) - channel = server.channels.find(message_json["channel"]) - text = unfurl_refs(message_json["text"], ignore_alt_text=False) - channel.buffer_prnt(w.prefix("join").rstrip(), text, message_json["ts"]) - channel.user_join(message_json["user"]) +def process_im_created(message_json, eventrouter, **kwargs): + team = kwargs['team'] + item = message_json["channel"] + c = SlackDMChannel(eventrouter, team=team, users=team.users, **item) + team.channels[item["id"]] = c + kwargs['team'].buffer_prnt('IM channel created: {}'.format(c.name)) -def process_channel_topic(message_json): - server = servers.find(message_json["_server"]) - channel = server.channels.find(message_json["channel"]) - text = unfurl_refs(message_json["text"], ignore_alt_text=False) - channel.buffer_prnt(w.prefix("network").rstrip(), text, message_json["ts"]) - channel.set_topic(message_json["topic"]) +def process_im_open(message_json, eventrouter, **kwargs): + channel = kwargs['channel'] + item = message_json + kwargs['team'].channels[item["channel"]].check_should_open(True) + w.buffer_set(channel.channel_buffer, "hotlist", "2") -def process_channel_joined(message_json): - server = servers.find(message_json["_server"]) - if server.channels.find(message_json["channel"]["name"]): - server.channels.find(message_json["channel"]["name"]).open(False) - else: - item = message_json["channel"] - server.add_channel(Channel(server, item["name"], item["id"], item["is_open"], item["last_read"], "#", item["members"], item["topic"]["value"])) +def process_im_close(message_json, eventrouter, **kwargs): + item = message_json + cbuf = kwargs['team'].channels[item["channel"]].channel_buffer + eventrouter.weechat_controller.unregister_buffer(cbuf, False, True) -def process_channel_leave(message_json): - server = servers.find(message_json["_server"]) - channel = server.channels.find(message_json["channel"]) - text = unfurl_refs(message_json["text"], ignore_alt_text=False) - channel.buffer_prnt(w.prefix("quit").rstrip(), text, message_json["ts"]) - channel.user_leave(message_json["user"]) +def process_group_joined(message_json, eventrouter, **kwargs): + item = message_json["channel"] + if item["name"].startswith("mpdm-"): + c = SlackMPDMChannel(eventrouter, team=kwargs["team"], **item) + else: + c = SlackGroupChannel(eventrouter, team=kwargs["team"], **item) + kwargs['team'].channels[item["id"]] = c + kwargs['team'].channels[item["id"]].open() -def process_channel_archive(message_json): - server = servers.find(message_json["_server"]) - channel = server.channels.find(message_json["channel"]) - channel.detach_buffer() +def process_reaction_added(message_json, eventrouter, **kwargs): + channel = kwargs['team'].channels[message_json["item"]["channel"]] + if message_json["item"].get("type") == "message": + ts = SlackTS(message_json['item']["ts"]) + message = channel.messages.get(ts, None) + if message: + message.add_reaction(message_json["reaction"], message_json["user"]) + channel.change_message(ts) + else: + dbg("reaction to item type not supported: " + str(message_json)) -def process_group_join(message_json): - process_channel_join(message_json) +def process_reaction_removed(message_json, eventrouter, **kwargs): + channel = kwargs['team'].channels[message_json["item"]["channel"]] + if message_json["item"].get("type") == "message": + ts = SlackTS(message_json['item']["ts"]) -def process_group_leave(message_json): - process_channel_leave(message_json) + message = channel.messages.get(ts, None) + if message: + message.remove_reaction(message_json["reaction"], message_json["user"]) + channel.change_message(ts) + else: + dbg("Reaction to item type not supported: " + str(message_json)) -def process_group_topic(message_json): - process_channel_topic(message_json) +def process_emoji_changed(message_json, eventrouter, **kwargs): + team = kwargs['team'] + team.load_emoji_completions() +###### New module/global methods -def process_group_left(message_json): - server = servers.find(message_json["_server"]) - server.channels.find(message_json["channel"]).close(False) +def render_formatting(text): + text = re.sub(r'(^| )\*([^*]+)\*([^a-zA-Z0-9_]|$)', + r'\1{}\2{}\3'.format(w.color(config.render_bold_as), + w.color('-' + config.render_bold_as)), + text) + text = re.sub(r'(^| )_([^_]+)_([^a-zA-Z0-9_]|$)', + r'\1{}\2{}\3'.format(w.color(config.render_italic_as), + w.color('-' + config.render_italic_as)), + text) + return text -def process_group_joined(message_json): - server = servers.find(message_json["_server"]) - if server.channels.find(message_json["channel"]["name"]): - server.channels.find(message_json["channel"]["name"]).open(False) +def render(message_json, team, channel, force=False): + # If we already have a rendered version in the object, just return that. + if not force and message_json.get("_rendered_text", ""): + return message_json["_rendered_text"] else: - item = message_json["channel"] - server.add_channel(GroupChannel(server, item["name"], item["id"], item["is_open"], item["last_read"], "#", item["members"], item["topic"]["value"])) + # server = servers.find(message_json["_server"]) + if "fallback" in message_json: + text = message_json["fallback"] + elif "text" in message_json: + if message_json['text'] is not None: + text = message_json["text"] + else: + text = "" + else: + text = "" -def process_group_archive(message_json): - channel = server.channels.find(message_json["channel"]) - channel.detach_buffer() + text = unfurl_refs(text) + text += unfurl_refs(unwrap_attachments(message_json, text)) -def process_im_close(message_json): - server = servers.find(message_json["_server"]) - server.channels.find(message_json["channel"]).close(False) + text = text.lstrip() + text = unhtmlescape(text.replace("\t", " ")) + if message_json.get('mrkdwn', True): + text = render_formatting(text) +# if self.threads: +# text += " [Replies: {} Thread ID: {} ] ".format(len(self.threads), self.thread_id) +# #for thread in self.threads: -def process_im_open(message_json): - server = servers.find(message_json["_server"]) - server.channels.find(message_json["channel"]).open() + text += create_reaction_string(message_json.get("reactions", "")) + message_json["_rendered_text"] = text + return text -def process_im_marked(message_json): - channel = channels.find(message_json["channel"]) - channel.mark_read(False) - if channel.channel_buffer is not None: - w.buffer_set(channel.channel_buffer, "hotlist", "-1") +def linkify_text(message, team, channel): + # The get_username_map function is a bit heavy, but this whole + # function is only called on message send.. + usernames = team.get_username_map() + channels = team.get_channel_map() + message = (message + # Replace IRC formatting chars with Slack formatting chars. + .replace('\x02', '*') + .replace('\x1D', '_') + .replace('\x1F', config.map_underline_to) + # Escape chars that have special meaning to Slack. Note that we do not + # (and should not) perform full HTML entity-encoding here. + # See https://api.slack.com/docs/message-formatting for details. + .replace('&', '&') + .replace('<', '<') + .replace('>', '>') + .split(' ')) + for item in enumerate(message): + targets = re.match('^\s*([@#])([\w.-]+[\w. -])(\W*)', item[1]) + if targets and targets.groups()[0] == '@': + named = targets.groups() + if named[1] in ["group", "channel", "here"]: + message[item[0]] = "".format(named[1]) + else: + try: + if usernames[named[1]]: + message[item[0]] = "<@{}>{}".format(usernames[named[1]], named[2]) + except: + message[item[0]] = "@{}{}".format(named[1], named[2]) + if targets and targets.groups()[0] == '#': + named = targets.groups() + try: + if channels[named[1]]: + message[item[0]] = "<#{}|{}>{}".format(channels[named[1]], named[1], named[2]) + except: + message[item[0]] = "#{}{}".format(named[1], named[2]) + # dbg(message) + return " ".join(message) -def process_im_created(message_json): - server = servers.find(message_json["_server"]) - item = message_json["channel"] - channel_name = server.users.find(item["user"]).name - if server.channels.find(channel_name): - server.channels.find(channel_name).open(False) - else: - item = message_json["channel"] - server.add_channel(DmChannel(server, channel_name, item["id"], item["is_open"], item["last_read"])) - server.buffer_prnt("New direct message channel created: {}".format(item["name"])) +def unfurl_refs(text, ignore_alt_text=None, auto_link_display=None): + """ + input : <@U096Q7CQM|someuser> has joined the channel + ouput : someuser has joined the channel + """ + # Find all strings enclosed by <> + # - + # - <#C2147483705|#otherchannel> + # - <@U2147483697|@othernick> + # Test patterns lives in ./_pytest/test_unfurl.py -def process_user_typing(message_json): - server = servers.find(message_json["_server"]) - channel = server.channels.find(message_json["channel"]) - if channel: - channel.set_typing(server.users.find(message_json["user"]).name) + if ignore_alt_text is None: + ignore_alt_text = config.unfurl_ignore_alt_text + if auto_link_display is None: + auto_link_display = config.unfurl_auto_link_display + matches = re.findall(r"(<[@#]?(?:[^>]*)>)", text) + for m in matches: + # Replace them with human readable strings + text = text.replace( + m, unfurl_ref(m[1:-1], ignore_alt_text, auto_link_display)) + return text -def process_bot_enable(message_json): - process_bot_integration(message_json) +def unfurl_ref(ref, ignore_alt_text, auto_link_display): + id = ref.split('|')[0] + display_text = ref + if ref.find('|') > -1: + if ignore_alt_text: + display_text = resolve_ref(id) + else: + if id.startswith("#C"): + display_text = "#{}".format(ref.split('|')[1]) + elif id.startswith("@U"): + display_text = ref.split('|')[1] + else: + url, desc = ref.split('|', 1) + match_url = r"^\w+:(//)?{}$".format(re.escape(desc)) + url_matches_desc = re.match(match_url, url) + if url_matches_desc and auto_link_display == "text": + display_text = desc + elif url_matches_desc and auto_link_display == "url": + display_text = url + else: + display_text = "{} ({})".format(url, desc) + else: + display_text = resolve_ref(ref) + return display_text -def process_bot_disable(message_json): - process_bot_integration(message_json) +def unhtmlescape(text): + return text.replace("<", "<") \ + .replace(">", ">") \ + .replace("&", "&") -def process_bot_integration(message_json): - server = servers.find(message_json["_server"]) - channel = server.channels.find(message_json["channel"]) - time = message_json['ts'] - text = "{} {}".format(server.users.find(message_json['user']).formatted_name(), - render_message(message_json)) - bot_name = get_user(message_json, server) - bot_name = bot_name.encode('utf-8') - channel.buffer_prnt(bot_name, text, time) +def unwrap_attachments(message_json, text_before): + text_before_unescaped = unhtmlescape(text_before) + attachment_texts = [] + a = message_json.get("attachments", None) + if a: + if text_before: + attachment_texts.append('') + for attachment in a: + # Attachments should be rendered roughly like: + # + # $pretext + # $author: (if rest of line is non-empty) $title ($title_link) OR $from_url + # $author: (if no $author on previous line) $text + # $fields + t = [] + prepend_title_text = '' + if 'author_name' in attachment: + prepend_title_text = attachment['author_name'] + ": " + if 'pretext' in attachment: + t.append(attachment['pretext']) + title = attachment.get('title', None) + title_link = attachment.get('title_link', '') + if title_link in text_before_unescaped: + title_link = '' + if title and title_link: + t.append('%s%s (%s)' % (prepend_title_text, title, title_link,)) + prepend_title_text = '' + elif title and not title_link: + t.append('%s%s' % (prepend_title_text, title,)) + prepend_title_text = '' + from_url = attachment.get('from_url', '') + if from_url not in text_before_unescaped and from_url != title_link: + t.append(from_url) -# todo: does this work? + atext = attachment.get("text", None) + if atext: + tx = re.sub(r' *\n[\n ]+', '\n', atext) + t.append(prepend_title_text + tx) + prepend_title_text = '' + fields = attachment.get("fields", None) + if fields: + for f in fields: + if f['title'] != '': + t.append('%s %s' % (f['title'], f['value'],)) + else: + t.append(f['value']) + fallback = attachment.get("fallback", None) + if t == [] and fallback: + t.append(fallback) + attachment_texts.append("\n".join([x.strip() for x in t if x])) + return "\n".join(attachment_texts) -def process_error(message_json): - pass -def process_reaction_added(message_json): - if message_json["item"].get("type") == "message": - channel = channels.find(message_json["item"]["channel"]) - channel.add_reaction(message_json["item"]["ts"], message_json["reaction"], message_json["user"]) - else: - dbg("Reaction to item type not supported: " + str(message_json)) +def resolve_ref(ref): + # TODO: This hack to use eventrouter needs to go + # this resolver should probably move to the slackteam or eventrouter itself + # global EVENTROUTER + if 'EVENTROUTER' in globals(): + e = EVENTROUTER + if ref.startswith('@U') or ref.startswith('@W'): + for t in e.teams.keys(): + if ref[1:] in e.teams[t].users: + # try: + return "@{}".format(e.teams[t].users[ref[1:]].name) + # except: + # dbg("NAME: {}".format(ref)) + elif ref.startswith('#C'): + for t in e.teams.keys(): + if ref[1:] in e.teams[t].channels: + # try: + return "{}".format(e.teams[t].channels[ref[1:]].name) + # except: + # dbg("CHANNEL: {}".format(ref)) + + # Something else, just return as-is + return ref -def process_reaction_removed(message_json): - if message_json["item"].get("type") == "message": - channel = channels.find(message_json["item"]["channel"]) - channel.remove_reaction(message_json["item"]["ts"], message_json["reaction"], message_json["user"]) - else: - dbg("Reaction to item type not supported: " + str(message_json)) def create_reaction_string(reactions): count = 0 @@ -1684,7 +2830,7 @@ def create_reaction_string(reactions): for r in reactions: if len(r["users"]) > 0: count += 1 - if show_reaction_nicks: + if config.show_reaction_nicks: nicks = [resolve_ref("@{}".format(user)) for user in r["users"]] users = "({})".format(",".join(nicks)) else: @@ -1695,560 +2841,615 @@ def create_reaction_string(reactions): reaction_string = '' return reaction_string -def modify_buffer_line(buffer, new_line, time): - time = int(float(time)) + +def modify_buffer_line(buffer, new_line, timestamp, time_id): # get a pointer to this buffer's lines own_lines = w.hdata_pointer(w.hdata_get('buffer'), buffer, 'own_lines') if own_lines: - #get a pointer to the last line + # get a pointer to the last line line_pointer = w.hdata_pointer(w.hdata_get('lines'), own_lines, 'last_line') - #hold the structure of a line and of line data + # hold the structure of a line and of line data struct_hdata_line = w.hdata_get('line') struct_hdata_line_data = w.hdata_get('line_data') + # keep track of the number of lines with the matching time and id + number_of_matching_lines = 0 while line_pointer: - #get a pointer to the data in line_pointer via layout of struct_hdata_line + # get a pointer to the data in line_pointer via layout of struct_hdata_line data = w.hdata_pointer(struct_hdata_line, line_pointer, 'data') if data: - date = w.hdata_time(struct_hdata_line_data, data, 'date') - prefix = w.hdata_string(struct_hdata_line_data, data, 'prefix') - - if int(date) == int(time): - #w.prnt("", "found matching time date is {}, time is {} ".format(date, time)) - w.hdata_update(struct_hdata_line_data, data, {"message": new_line}) + line_timestamp = w.hdata_time(struct_hdata_line_data, data, 'date') + line_time_id = w.hdata_integer(struct_hdata_line_data, data, 'date_printed') + # prefix = w.hdata_string(struct_hdata_line_data, data, 'prefix') + + if timestamp == int(line_timestamp) and int(time_id) == line_time_id: + number_of_matching_lines += 1 + elif number_of_matching_lines > 0: + # since number_of_matching_lines is non-zero, we have + # already reached the message and can stop traversing break - else: - pass - #move backwards one line and try again - exit the while if you hit the end + else: + dbg(('Encountered line without any data while trying to modify ' + 'line. This is not handled, so aborting modification.')) + return w.WEECHAT_RC_ERROR + # move backwards one line and try again - exit the while if you hit the end line_pointer = w.hdata_move(struct_hdata_line, line_pointer, -1) + + # split the message into at most the number of existing lines + lines = new_line.split('\n', number_of_matching_lines - 1) + # updating a line with a string containing newlines causes the lines to + # be broken when viewed in bare display mode + lines = [line.replace('\n', ' | ') for line in lines] + # pad the list with empty strings until the number of elements equals + # number_of_matching_lines + lines += [''] * (number_of_matching_lines - len(lines)) + + if line_pointer: + for line in lines: + line_pointer = w.hdata_move(struct_hdata_line, line_pointer, 1) + data = w.hdata_pointer(struct_hdata_line, line_pointer, 'data') + w.hdata_update(struct_hdata_line_data, data, {"message": line}) return w.WEECHAT_RC_OK -def render_message(message_json, force=False): - global unfurl_ignore_alt_text - #If we already have a rendered version in the object, just return that. - if not force and message_json.get("_rendered_text", ""): - return message_json["_rendered_text"] - else: - server = servers.find(message_json["_server"]) - if "fallback" in message_json: - text = message_json["fallback"] - elif "text" in message_json: - if message_json['text'] is not None: - text = message_json["text"] +def modify_print_time(buffer, new_id, time): + """ + This overloads the time printed field to let us store the slack + per message unique id that comes after the "." in a slack ts + """ + + # get a pointer to this buffer's lines + own_lines = w.hdata_pointer(w.hdata_get('buffer'), buffer, 'own_lines') + if own_lines: + # get a pointer to the last line + line_pointer = w.hdata_pointer(w.hdata_get('lines'), own_lines, 'last_line') + # hold the structure of a line and of line data + struct_hdata_line = w.hdata_get('line') + struct_hdata_line_data = w.hdata_get('line_data') + + prefix = '' + while not prefix and line_pointer: + # get a pointer to the data in line_pointer via layout of struct_hdata_line + data = w.hdata_pointer(struct_hdata_line, line_pointer, 'data') + if data: + prefix = w.hdata_string(struct_hdata_line_data, data, 'prefix') + w.hdata_update(struct_hdata_line_data, data, {"date_printed": new_id}) else: - text = u"" + dbg('Encountered line without any data while setting message id.') + return w.WEECHAT_RC_ERROR + # move backwards one line and repeat, so all the lines of the message are set + # exit when you reach a prefix, which means you have reached the + # first line of the message, or if you hit the end + line_pointer = w.hdata_move(struct_hdata_line, line_pointer, -1) + + return w.WEECHAT_RC_OK + + +def tag(tagset, user=None): + if user: + user.replace(" ", "_") + default_tag = "nick_" + user + else: + default_tag = 'nick_unknown' + tagsets = { + # messages in the team/server buffer, e.g. "new channel created" + "team": "no_highlight,log3", + # when replaying something old + "backlog": "irc_privmsg,no_highlight,notify_none,logger_backlog", + # when posting messages to a muted channel + "muted": "irc_privmsg,no_highlight,notify_none,log1", + # when receiving a direct message + "dm": "irc_privmsg,notify_private,log1", + "dmfromme": "irc_privmsg,no_highlight,notify_none,log1", + # when this is a join/leave, attach for smart filter ala: + # if user in [x.strip() for x in w.prefix("join"), w.prefix("quit")] + "joinleave": "irc_smart_filter,no_highlight,log4", + # catchall ? + "default": "irc_privmsg,notify_message,log1", + } + return "{},slack_{},{}".format(default_tag, tagset, tagsets[tagset]) + +###### New/converted command_ commands + + +@slack_buffer_or_ignore +@utf8_decode +def part_command_cb(data, current_buffer, args): + e = EVENTROUTER + args = args.split() + if len(args) > 1: + team = e.weechat_controller.buffers[current_buffer].team + cmap = team.get_channel_map() + channel = "".join(args[1:]) + if channel in cmap: + buffer_ptr = team.channels[cmap[channel]].channel_buffer + e.weechat_controller.unregister_buffer(buffer_ptr, update_remote=True, close_buffer=True) + else: + e.weechat_controller.unregister_buffer(current_buffer, update_remote=True, close_buffer=True) + return w.WEECHAT_RC_OK_EAT + + +def parse_topic_command(command): + args = command.split()[1:] + channel_name = None + topic = None + + if args: + if args[0].startswith('#'): + channel_name = args[0][1:] + topic = args[1:] else: - text = u"" + topic = args - text = unfurl_refs(text, ignore_alt_text=unfurl_ignore_alt_text) + if topic == []: + topic = None + if topic: + topic = ' '.join(topic) + if topic == '-delete': + topic = '' - text_before = (len(text) > 0) - text += unfurl_refs(unwrap_attachments(message_json, text_before), ignore_alt_text=unfurl_ignore_alt_text) + return channel_name, topic - text = text.lstrip() - text = text.replace("\t", " ") - text = text.replace("<", "<") - text = text.replace(">", ">") - text = text.replace("&", "&") - text = text.encode('utf-8') - - if "reactions" in message_json: - text += create_reaction_string(message_json["reactions"]) - message_json["_rendered_text"] = text - return text +@slack_buffer_or_ignore +@utf8_decode +def topic_command_cb(data, current_buffer, command): + """ + Change the topic of a channel + /topic [] [|-delete] + """ + + channel_name, topic = parse_topic_command(command) + + team = EVENTROUTER.weechat_controller.buffers[current_buffer].team + if channel_name: + channel = team.channels.get(team.get_channel_map().get(channel_name)) + else: + channel = EVENTROUTER.weechat_controller.buffers[current_buffer] + + if not channel: + w.prnt(team.channel_buffer, "#{}: No such channel".format(channel_name)) + return w.WEECHAT_RC_OK_EAT + + if topic is None: + w.prnt(channel.channel_buffer, 'Topic for {} is "{}"'.format(channel.name, channel.topic)) + else: + s = SlackRequest(team.token, "channels.setTopic", {"channel": channel.identifier, "topic": topic}, team_hash=team.team_hash) + EVENTROUTER.receive(s) + return w.WEECHAT_RC_OK_EAT + + +@slack_buffer_or_ignore +@utf8_decode +def me_command_cb(data, current_buffer, args): + message = "_{}_".format(args.split(' ', 1)[1]) + buffer_input_callback("EVENTROUTER", current_buffer, message) + return w.WEECHAT_RC_OK_EAT + + +def command_register(data, current_buffer, args): + CLIENT_ID = "2468770254.51917335286" + CLIENT_SECRET = "dcb7fe380a000cba0cca3169a5fe8d70" # Not really a secret. + if args == 'register': + message = textwrap.dedent(""" + #### Retrieving a Slack token via OAUTH #### + + 1) Paste this into a browser: https://slack.com/oauth/authorize?client_id=2468770254.51917335286&scope=client + 2) Select the team you wish to access from wee-slack in your browser. + 3) Click "Authorize" in the browser **IMPORTANT: the redirect will fail, this is expected** + 4) Copy the "code" portion of the URL to your clipboard + 5) Return to weechat and run `/slack register [code]` + """) + w.prnt("", message) + return + + try: + _, oauth_code = args.split() + except ValueError: + w.prnt("", + "ERROR: wrong number of arguments given for register command") + return + + uri = ( + "https://slack.com/api/oauth.access?" + "client_id={}&client_secret={}&code={}" + ).format(CLIENT_ID, CLIENT_SECRET, oauth_code) + ret = urllib.urlopen(uri).read() + d = json.loads(ret) + if not d["ok"]: + w.prnt("", + "ERROR: Couldn't get Slack OAuth token: {}".format(d['error'])) + return + + if config.is_default('slack_api_token'): + w.config_set_plugin('slack_api_token', d['access_token']) + else: + # Add new token to existing set, joined by comma. + tok = config.get_string('slack_api_token') + w.config_set_plugin('slack_api_token', + ','.join([tok, d['access_token']])) + + w.prnt("", "Success! Added team \"%s\"" % (d['team_name'],)) + w.prnt("", "Please reload wee-slack with: /script reload slack") + + +@slack_buffer_or_ignore +@utf8_decode +def msg_command_cb(data, current_buffer, args): + dbg("msg_command_cb") + aargs = args.split(None, 2) + who = aargs[1] + if who == "*": + who = EVENTROUTER.weechat_controller.buffers[current_buffer].slack_name + else: + command_talk(data, current_buffer, who) + + if len(aargs) > 2: + message = aargs[2] + team = EVENTROUTER.weechat_controller.buffers[current_buffer].team + cmap = team.get_channel_map() + if who in cmap: + channel = team.channels[cmap[who]] + channel.send_message(message) + return w.WEECHAT_RC_OK_EAT -def process_message(message_json, cache=True): - try: - # send these subtype messages elsewhere - known_subtypes = ["message_changed", 'message_deleted', 'channel_join', 'channel_leave', 'channel_topic', 'group_join', 'group_leave', 'group_topic', 'bot_enable', 'bot_disable'] - if "subtype" in message_json and message_json["subtype"] in known_subtypes: - proc[message_json["subtype"]](message_json) - else: - server = servers.find(message_json["_server"]) - channel = channels.find(message_json["channel"]) +@slack_buffer_required +@utf8_decode +def command_channels(data, current_buffer, args): + e = EVENTROUTER + team = e.weechat_controller.buffers[current_buffer].team + + team.buffer_prnt("Channels:") + for channel in team.get_channel_map(): + team.buffer_prnt(" {}".format(channel)) + return w.WEECHAT_RC_OK_EAT - #do not process messages in unexpected channels - if not channel.active: - channel.open(False) - dbg("message came for closed channel {}".format(channel.name)) - return - time = message_json['ts'] - text = render_message(message_json) - name = get_user(message_json, server) - name = name.encode('utf-8') +@slack_buffer_required +@utf8_decode +def command_users(data, current_buffer, args): + e = EVENTROUTER + team = e.weechat_controller.buffers[current_buffer].team + + team.buffer_prnt("Users:") + for user in team.users.values(): + team.buffer_prnt(" {:<25}({})".format(user.name, user.presence)) + return w.WEECHAT_RC_OK_EAT - #special case with actions. - if text.startswith("_") and text.endswith("_"): - text = text[1:-1] - if name != channel.server.nick: - text = name + " " + text - channel.buffer_prnt(w.prefix("action").rstrip(), text, time) - else: - suffix = '' - if 'edited' in message_json: - suffix = ' (edited)' - channel.buffer_prnt(name, text + suffix, time) +@slack_buffer_or_ignore +@utf8_decode +def command_talk(data, current_buffer, args): + """ + Open a chat with the specified user(s) + /slack talk [,[,...]] + """ + e = EVENTROUTER + team = e.weechat_controller.buffers[current_buffer].team + channel_name = args.split(' ')[1] + + if channel_name.startswith('#'): + channel_name = channel_name[1:] + + # Try finding the channel by name + chan = team.channels.get(team.get_channel_map().get(channel_name)) + + # If the channel doesn't exist, try finding a DM or MPDM instead + if not chan: + # Get the IDs of the users + u = team.get_username_map() + users = set() + for user in channel_name.split(','): + if user.startswith('@'): + user = user[1:] + if user in u: + users.add(u[user]) + + if users: + if len(users) > 1: + channel_type = 'mpim' + # Add the current user since MPDMs include them as a member + users.add(team.myidentifier) + else: + channel_type = 'im' - if cache: - channel.cache_message(message_json) + # Try finding the channel by type and members + for channel in team.channels.itervalues(): + if (channel.type == channel_type and + channel.get_members() == users): + chan = channel + break - except Exception: - channel = channels.find(message_json["channel"]) - dbg("cannot process message {}\n{}".format(message_json, traceback.format_exc())) - if channel and ("text" in message_json) and message_json['text'] is not None: - channel.buffer_prnt('unknown', message_json['text']) + # If the DM or MPDM doesn't exist, create it + if not chan: + s = SlackRequest(team.token, SLACK_API_TRANSLATOR[channel_type]['join'], {'users': ','.join(users)}, team_hash=team.team_hash) + EVENTROUTER.receive(s) + if chan: + chan.open() + if config.switch_buffer_on_join: + w.buffer_set(chan.channel_buffer, "display", "1") + return w.WEECHAT_RC_OK_EAT + return w.WEECHAT_RC_OK_EAT -def process_message_changed(message_json): - m = message_json["message"] - if "message" in message_json: - if "attachments" in m: - message_json["attachments"] = m["attachments"] - if "text" in m: - if "text" in message_json: - message_json["text"] += m["text"] - dbg("added text!") - else: - message_json["text"] = m["text"] - if "fallback" in m: - if "fallback" in message_json: - message_json["fallback"] += m["fallback"] - else: - message_json["fallback"] = m["fallback"] - text_before = (len(m['text']) > 0) - m["text"] += unwrap_attachments(message_json, text_before) - channel = channels.find(message_json["channel"]) - if "edited" in m: - channel.change_message(m["ts"], m["text"], ' (edited)') - else: - channel.change_message(m["ts"], m["text"]) +def command_showmuted(data, current_buffer, args): + current = w.current_buffer() + w.prnt(EVENTROUTER.weechat_controller.buffers[current].team.channel_buffer, str(EVENTROUTER.weechat_controller.buffers[current].team.muted_channels)) -def process_message_deleted(message_json): - channel = channels.find(message_json["channel"]) - channel.change_message(message_json["deleted_ts"], "(deleted)") +@utf8_decode +def thread_command_callback(data, current_buffer, args): + current = w.current_buffer() + channel = EVENTROUTER.weechat_controller.buffers.get(current) + if channel: + args = args.split() + if args[0] == '/thread': + if len(args) == 2: + try: + pm = channel.messages[SlackTS(args[1])] + except: + pm = channel.hashed_messages[args[1]] + tc = SlackThreadChannel(EVENTROUTER, pm) + pm.thread_channel = tc + tc.open() + # tc.create_buffer() + if config.switch_buffer_on_join: + w.buffer_set(tc.channel_buffer, "display", "1") + return w.WEECHAT_RC_OK_EAT + elif args[0] == '/reply': + count = int(args[1]) + msg = " ".join(args[2:]) + mkeys = channel.main_message_keys_reversed() + parent_id = str(next(islice(mkeys, count - 1, None))) + channel.send_message(msg, request_dict_ext={"thread_ts": parent_id}) + return w.WEECHAT_RC_OK_EAT + w.prnt(current, "Invalid thread command.") + return w.WEECHAT_RC_OK_EAT -def unwrap_attachments(message_json, text_before): - attachment_text = '' - if "attachments" in message_json: - if text_before: - attachment_text = u'\n' - for attachment in message_json["attachments"]: - # Attachments should be rendered roughly like: - # - # $pretext - # $author: (if rest of line is non-empty) $title ($title_link) OR $from_url - # $author: (if no $author on previous line) $text - # $fields - t = [] - prepend_title_text = '' - if 'author_name' in attachment: - prepend_title_text = attachment['author_name'] + ": " - if 'pretext' in attachment: - t.append(attachment['pretext']) - if "title" in attachment: - if 'title_link' in attachment: - t.append('%s%s (%s)' % (prepend_title_text, attachment["title"], attachment["title_link"],)) - else: - t.append(prepend_title_text + attachment["title"]) - prepend_title_text = '' - elif "from_url" in attachment: - t.append(attachment["from_url"]) - if "text" in attachment: - tx = re.sub(r' *\n[\n ]+', '\n', attachment["text"]) - t.append(prepend_title_text + tx) - prepend_title_text = '' - if 'fields' in attachment: - for f in attachment['fields']: - if f['title'] != '': - t.append('%s %s' % (f['title'], f['value'],)) - else: - t.append(f['value']) - if t == [] and "fallback" in attachment: - t.append(attachment["fallback"]) - attachment_text += "\n".join([x.strip() for x in t if x]) - return attachment_text +@utf8_decode +def rehistory_command_callback(data, current_buffer, args): + current = w.current_buffer() + channel = EVENTROUTER.weechat_controller.buffers.get(current) + channel.got_history = False + w.buffer_clear(channel.channel_buffer) + channel.get_history() + return w.WEECHAT_RC_OK_EAT -def resolve_ref(ref): - if ref.startswith('@U'): - if users.find(ref[1:]): - try: - return "@{}".format(users.find(ref[1:]).name) - except: - dbg("NAME: {}".format(ref)) - elif ref.startswith('#C'): - if channels.find(ref[1:]): - try: - return "{}".format(channels.find(ref[1:]).name) - except: - dbg("CHANNEL: {}".format(ref)) +@slack_buffer_required +@utf8_decode +def hide_command_callback(data, current_buffer, args): + c = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) + if c: + name = c.formatted_name(style='long_default') + if name in config.distracting_channels: + w.buffer_set(c.channel_buffer, "hidden", "1") + return w.WEECHAT_RC_OK_EAT - # Something else, just return as-is - return ref -def unfurl_ref(ref, ignore_alt_text=False): - id = ref.split('|')[0] - display_text = ref - if ref.find('|') > -1: - if ignore_alt_text: - display_text = resolve_ref(id) - else: - if id.startswith("#C") or id.startswith("@U"): - display_text = ref.split('|')[1] - else: - url, desc = ref.split('|', 1) - display_text = u"{} ({})".format(url, desc) +@utf8_decode +def slack_command_cb(data, current_buffer, args): + a = args.split(' ', 1) + if len(a) > 1: + function_name, args = a[0], args else: - display_text = resolve_ref(ref) - return display_text + function_name, args = a[0], args -def unfurl_refs(text, ignore_alt_text=False): - """ - input : <@U096Q7CQM|someuser> has joined the channel - ouput : someuser has joined the channel - """ - # Find all strings enclosed by <> - # - - # - <#C2147483705|#otherchannel> - # - <@U2147483697|@othernick> - # Test patterns lives in ./_pytest/test_unfurl.py - matches = re.findall(r"(<[@#]?(?:[^<]*)>)", text) - for m in matches: - # Replace them with human readable strings - text = text.replace(m, unfurl_ref(m[1:-1], ignore_alt_text)) - return text + try: + EVENTROUTER.cmds[function_name]("", current_buffer, args) + except KeyError: + w.prnt("", "Command not found: " + function_name) + return w.WEECHAT_RC_OK -def get_user(message_json, server): - if 'bot_id' in message_json and message_json['bot_id'] is not None: - name = u"{} :]".format(server.bots.find(message_json["bot_id"]).formatted_name()) - elif 'user' in message_json: - u = server.users.find(message_json['user']) - if u.is_bot: - name = u"{} :]".format(u.formatted_name()) - else: - name = u.name - elif 'username' in message_json: - name = u"-{}-".format(message_json["username"]) - elif 'service_name' in message_json: - name = u"-{}-".format(message_json["service_name"]) +@slack_buffer_required +def command_distracting(data, current_buffer, args): + channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer, None) + if channel: + fullname = channel.formatted_name(style="long_default") + if config.distracting_channels.count(fullname) == 0: + config.distracting_channels.append(fullname) else: - name = u"" - return name - -# END Websocket handling methods - - -def typing_bar_item_cb(data, buffer, args): - typers = [x for x in channels if x.is_someone_typing()] - if len(typers) > 0: - direct_typers = [] - channel_typers = [] - for dm in channels.find_by_class(DmChannel): - direct_typers.extend(dm.get_typing_list()) - direct_typers = ["D/" + x for x in direct_typers] - current_channel = w.current_buffer() - channel = channels.find(current_channel) - try: - if channel and channel.__class__ != DmChannel: - channel_typers = channels.find(current_channel).get_typing_list() - except: - w.prnt("", "Bug on {}".format(channel)) - typing_here = ", ".join(channel_typers + direct_typers) - if len(typing_here) > 0: - color = w.color('yellow') - return color + "typing: " + typing_here - return "" + config.distracting_channels.pop(config.distracting_channels.index(fullname)) + save_distracting_channels() -def typing_update_cb(data, remaining_calls): - w.bar_item_update("slack_typing_notice") - return w.WEECHAT_RC_OK +def save_distracting_channels(): + w.config_set_plugin('distracting_channels', ','.join(config.distracting_channels)) -def buffer_list_update_cb(data, remaining_calls): - global buffer_list_update +@slack_buffer_required +def command_slash(data, current_buffer, args): + """ + Support for custom slack commands + /slack slash /customcommand arg1 arg2 arg3 + """ + e = EVENTROUTER + channel = e.weechat_controller.buffers.get(current_buffer, None) + if channel: + team = channel.team - now = time.time() - if buffer_list_update and previous_buffer_list_update + 1 < now: - gray_check = False - if len(servers) > 1: - gray_check = True - for channel in channels: - channel.rename() - buffer_list_update = False - return w.WEECHAT_RC_OK + if args is None: + server.buffer_prnt("Usage: /slack slash /someslashcommand [arguments...].") + return -def buffer_list_update_next(): - global buffer_list_update - buffer_list_update = True + split_args = args.split(None, 2) + command = split_args[1] + text = split_args[2] if len(split_args) > 2 else "" -def hotlist_cache_update_cb(data, remaining_calls): - # this keeps the hotlist dupe up to date for the buffer switch, but is prob technically a race condition. (meh) - global hotlist - prev_hotlist = hotlist - hotlist = w.infolist_get("hotlist", "", "") - w.infolist_free(prev_hotlist) - return w.WEECHAT_RC_OK + s = SlackRequest(team.token, "chat.command", {"command": command, "text": text, 'channel': channel.identifier}, team_hash=team.team_hash, channel_identifier=channel.identifier) + EVENTROUTER.receive(s) -def buffer_closing_cb(signal, sig_type, data): - if channels.find(data): - channels.find(data).closed() - return w.WEECHAT_RC_OK +@slack_buffer_required +def command_mute(data, current_buffer, args): + current = w.current_buffer() + channel_id = EVENTROUTER.weechat_controller.buffers[current].identifier + team = EVENTROUTER.weechat_controller.buffers[current].team + if channel_id not in team.muted_channels: + team.muted_channels.add(channel_id) + else: + team.muted_channels.discard(channel_id) + s = SlackRequest(team.token, "users.prefs.set", {"name": "muted_channels", "value": ",".join(team.muted_channels)}, team_hash=team.team_hash, channel_identifier=channel_id) + EVENTROUTER.receive(s) -def buffer_switch_cb(signal, sig_type, data): - global previous_buffer, hotlist - # this is to see if we need to gray out things in the buffer list - if channels.find(previous_buffer): - channels.find(previous_buffer).mark_read() +@slack_buffer_required +def command_openweb(data, current_buffer, args): + # if done from server buffer, open slack for reals + channel = EVENTROUTER.weechat_controller.buffers[current_buffer] + if isinstance(channel, SlackTeam): + url = "https://{}".format(channel.team.domain) + else: + now = SlackTS() + url = "https://{}/archives/{}/p{}000000".format(channel.team.domain, channel.slack_name, now.majorstr()) + w.prnt_date_tags(channel.team.channel_buffer, SlackTS().major, "openweb,logger_backlog_end,notify_none", url) - channel_name = current_buffer_name() - previous_buffer = data - return w.WEECHAT_RC_OK +def command_nodistractions(data, current_buffer, args): + global hide_distractions + hide_distractions = not hide_distractions + if config.distracting_channels != ['']: + for channel in config.distracting_channels: + dbg('hiding channel {}'.format(channel)) + # try: + for c in EVENTROUTER.weechat_controller.buffers.itervalues(): + if c == channel: + dbg('found channel {} to hide'.format(channel)) + w.buffer_set(c.channel_buffer, "hidden", str(int(hide_distractions))) + # except: + # dbg("Can't hide channel {} .. removing..".format(channel), main_buffer=True) +# config.distracting_channels.pop(config.distracting_channels.index(channel)) +# save_distracting_channels() -def typing_notification_cb(signal, sig_type, data): - if len(w.buffer_get_string(data, "input")) > 8: - global typing_timer - now = time.time() - if typing_timer + 4 < now: - channel = channels.find(current_buffer_name()) - if channel: - identifier = channel.identifier - request = {"type": "typing", "channel": identifier} - channel.server.send_to_websocket(request, expect_reply=False) - typing_timer = now - return w.WEECHAT_RC_OK -def slack_ping_cb(data, remaining): - """ - Periodic websocket ping to detect broken connection. - """ - servers.find(data).ping() +@slack_buffer_required +def command_upload(data, current_buffer, args): + channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) + url = 'https://slack.com/api/files.upload' + fname = args.split(' ', 1) + file_path = os.path.expanduser(fname[1]) + team = EVENTROUTER.weechat_controller.buffers[current_buffer].team + if ' ' in file_path: + file_path = file_path.replace(' ', '\ ') + + command = 'curl -F file=@{} -F channels={} -F token={} {}'.format(file_path, channel.identifier, team.token, url) + w.hook_process(command, config.slack_timeout, '', '') + + +@utf8_decode +def away_command_cb(data, current_buffer, args): + # TODO: reimplement all.. maybe + (all, message) = re.match("^/away(?:\s+(-all))?(?:\s+(.+))?", args).groups() + if message is None: + command_back(data, current_buffer, args) + else: + command_away(data, current_buffer, args) return w.WEECHAT_RC_OK -def slack_connection_persistence_cb(data, remaining_calls): +@slack_buffer_required +def command_away(data, current_buffer, args): """ - Reconnect if a connection is detected down + Sets your status as 'away' + /slack away """ - for server in servers: - if not server.connected: - server.buffer_prnt("Disconnected from slack, trying to reconnect..") - if server.ws_hook is not None: - w.unhook(server.ws_hook) - server.connect_to_slack() - return w.WEECHAT_RC_OK - - -def slack_never_away_cb(data, remaining): - global never_away - if never_away: - for server in servers: - identifier = server.channels.find("slackbot").identifier - request = {"type": "typing", "channel": identifier} - #request = {"type":"typing","channel":"slackbot"} - server.send_to_websocket(request, expect_reply=False) - return w.WEECHAT_RC_OK + team = EVENTROUTER.weechat_controller.buffers[current_buffer].team + s = SlackRequest(team.token, "presence.set", {"presence": "away"}, team_hash=team.team_hash) + EVENTROUTER.receive(s) -def nick_completion_cb(data, completion_item, buffer, completion): +@slack_buffer_required +def command_status(data, current_buffer, args): """ - Adds all @-prefixed nicks to completion list + Lets you set your Slack Status (not to be confused with away/here) + /slack status [emoji] [status_message] """ + e = EVENTROUTER + channel = e.weechat_controller.buffers.get(current_buffer, None) + if channel: + team = channel.team - channel = channels.find(buffer) - if channel is None or channel.members is None: - return w.WEECHAT_RC_OK - for m in channel.members: - user = channel.server.users.find(m) - w.hook_completion_list_add(completion, "@" + user.name, 1, w.WEECHAT_LIST_POS_SORT) - return w.WEECHAT_RC_OK - - -def complete_next_cb(data, buffer, command): - """Extract current word, if it is equal to a nick, prefix it with @ and - rely on nick_completion_cb adding the @-prefixed versions to the - completion lists, then let Weechat's internal completion do its - thing + if args is None: + server.buffer_prnt("Usage: /slack status [status emoji] [status text].") + return - """ + split_args = args.split(None, 2) + emoji = split_args[1] if len(split_args) > 1 else "" + text = split_args[2] if len(split_args) > 2 else "" - channel = channels.find(buffer) - if channel is None or channel.members is None: - return w.WEECHAT_RC_OK - input = w.buffer_get_string(buffer, "input") - current_pos = w.buffer_get_integer(buffer, "input_pos") - 1 - input_length = w.buffer_get_integer(buffer, "input_length") - word_start = 0 - word_end = input_length - # If we're on a non-word, look left for something to complete - while current_pos >= 0 and input[current_pos] != '@' and not input[current_pos].isalnum(): - current_pos = current_pos - 1 - if current_pos < 0: - current_pos = 0 - for l in range(current_pos, 0, -1): - if input[l] != '@' and not input[l].isalnum(): - word_start = l + 1 - break - for l in range(current_pos, input_length): - if not input[l].isalnum(): - word_end = l - break - word = input[word_start:word_end] - for m in channel.members: - user = channel.server.users.find(m) - if user.name == word: - # Here, we cheat. Insert a @ in front and rely in the @ - # nicks being in the completion list - w.buffer_set(buffer, "input", input[:word_start] + "@" + input[word_start:]) - w.buffer_set(buffer, "input_pos", str(w.buffer_get_integer(buffer, "input_pos") + 1)) - return w.WEECHAT_RC_OK_EAT - return w.WEECHAT_RC_OK + profile = {"status_text":text,"status_emoji":emoji} -# Slack specific requests + s = SlackRequest(team.token, "users.profile.set", {"profile": profile}, team_hash=team.team_hash) + EVENTROUTER.receive(s) -# NOTE: switched to async because sync slowed down the UI -def async_slack_api_request(domain, token, request, post_data, priority=False): - if not STOP_TALKING_TO_SLACK: - post_data["token"] = token - url = 'url:https://{}/api/{}?{}'.format(domain, request, urllib.urlencode(post_data)) - context = pickle.dumps({"request": request, "token": token, "post_data": post_data}) - params = { 'useragent': 'wee_slack {}'.format(SCRIPT_VERSION) } - dbg("URL: {} context: {} params: {}".format(url, context, params)) - w.hook_process_hashtable(url, params, 20000, "url_processor_cb", context) - -def async_slack_api_upload_request(token, request, post_data, priority=False): - if not STOP_TALKING_TO_SLACK: - url = 'https://slack.com/api/{}'.format(request) - file_path = os.path.expanduser(post_data["file"]) - command = 'curl -F file=@{} -F channels={} -F token={} {}'.format(file_path, post_data["channels"], token, url) - context = pickle.dumps({"request": request, "token": token, "post_data": post_data}) - w.hook_process(command, 20000, "url_processor_cb", context) - -# funny, right? -big_data = {} - -def url_processor_cb(data, command, return_code, out, err): - global big_data - data = pickle.loads(data) - identifier = sha.sha("{}{}".format(data, command)).hexdigest() - if identifier not in big_data: - big_data[identifier] = '' - big_data[identifier] += out - if return_code == 0: - try: - my_json = json.loads(big_data[identifier]) - except: - dbg("request failed, doing again...") - dbg("response length: {} identifier {}\n{}".format(len(big_data[identifier]), identifier, data)) - my_json = False - big_data.pop(identifier, None) +@slack_buffer_required +def command_back(data, current_buffer, args): + """ + Sets your status as 'back' + /slack back + """ + team = EVENTROUTER.weechat_controller.buffers[current_buffer].team + s = SlackRequest(team.token, "presence.set", {"presence": "active"}, team_hash=team.team_hash) + EVENTROUTER.receive(s) - if my_json: - if data["request"] == 'rtm.start': - servers.find(data["token"]).connected_to_slack(my_json) - servers.update_hashtable() - else: - if "channel" in data["post_data"]: - channel = data["post_data"]["channel"] - token = data["token"] - if "messages" in my_json: - messages = my_json["messages"].reverse() - for message in my_json["messages"]: - message["_server"] = servers.find(token).domain - message["channel"] = servers.find(token).channels.find(channel).identifier - process_message(message) - if "channel" in my_json: - if "members" in my_json["channel"]: - channels.find(my_json["channel"]["id"]).members = set(my_json["channel"]["members"]) - else: - if return_code != -1: - big_data.pop(identifier, None) - dbg("return code: {}, data: {}, output: {}, error: {}".format(return_code, data, out, err)) +@slack_buffer_required +@utf8_decode +def label_command_cb(data, current_buffer, args): + channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) + if channel and channel.type == 'thread': + aargs = args.split(None, 2) + new_name = " +" + aargs[1] + channel.label = new_name + w.buffer_set(channel.channel_buffer, "short_name", new_name) - return w.WEECHAT_RC_OK -def cache_write_cb(data, remaining): - cache_file = open("{}/{}".format(WEECHAT_HOME, CACHE_NAME), 'w') - cache_file.write(CACHE_VERSION + "\n") - for channel in channels: - if channel.active: - for message in channel.messages: - cache_file.write("{}\n".format(json.dumps(message.message_json))) +@utf8_decode +def set_unread_cb(data, current_buffer, command): + for channel in EVENTROUTER.weechat_controller.buffers.values(): + channel.mark_read() return w.WEECHAT_RC_OK -def cache_load(): - global message_cache - try: - file_name = "{}/{}".format(WEECHAT_HOME, CACHE_NAME) - cache_file = open(file_name, 'r') - if cache_file.readline() == CACHE_VERSION + "\n": - dbg("Loading messages from cache.", main_buffer=True) - for line in cache_file: - j = json.loads(line) - message_cache[j["channel"]].append(line) - dbg("Completed loading messages from cache.", main_buffer=True) - except ValueError: - w.prnt("", "Failed to load cache file, probably illegal JSON.. Ignoring") - pass - except IOError: - w.prnt("", "cache file not found") - pass - -# END Slack specific requests -# Utility Methods +@slack_buffer_or_ignore +@utf8_decode +def set_unread_current_buffer_cb(data, current_buffer, command): + channel = EVENTROUTER.weechat_controller.buffers.get(current_buffer) + channel.mark_read() + return w.WEECHAT_RC_OK -def current_domain_name(): - buffer = w.current_buffer() - if servers.find(buffer): - return servers.find(buffer).domain - else: - #number = w.buffer_get_integer(buffer, "number") - name = w.buffer_get_string(buffer, "name") - name = ".".join(name.split(".")[:-1]) - return name +def command_p(data, current_buffer, args): + args = args.split(' ', 1)[1] + w.prnt("", "{}".format(eval(args))) +###### NEW EXCEPTIONS -def current_buffer_name(short=False): - buffer = w.current_buffer() - #number = w.buffer_get_integer(buffer, "number") - name = w.buffer_get_string(buffer, "name") - if short: - try: - name = name.split('.')[-1] - except: - pass - return name +class ProcessNotImplemented(Exception): + """ + Raised when we try to call process_(something), but + (something) has not been defined as a function. + """ + def __init__(self, function_name): + super(ProcessNotImplemented, self).__init__(function_name) -def closed_slack_buffer_cb(data, buffer): - global slack_buffer - slack_buffer = None - return w.WEECHAT_RC_OK +class InvalidType(Exception): + """ + Raised when we do type checking to ensure objects of the wrong + type are not used improperly. + """ + def __init__(self, type_str): + super(InvalidType, self).__init__(type_str) -def create_slack_buffer(): - global slack_buffer - slack_buffer = w.buffer_new("slack", "", "", "closed_slack_buffer_cb", "") - w.buffer_set(slack_buffer, "notify", "0") - #w.buffer_set(slack_buffer, "display", "1") - return w.WEECHAT_RC_OK +###### New but probably old and need to migrate def closed_slack_debug_buffer_cb(data, buffer): @@ -2267,168 +3468,382 @@ def create_slack_debug_buffer(): w.buffer_set(slack_debug, "notify", "0") -def config_changed_cb(data, option, value): - global slack_api_token, distracting_channels, colorize_nicks, colorize_private_chats, slack_debug, debug_mode, \ - unfurl_ignore_alt_text, colorize_messages, show_reaction_nicks - - slack_api_token = w.config_get_plugin("slack_api_token") - - if slack_api_token.startswith('${sec.data'): - slack_api_token = w.string_eval_expression(slack_api_token, {}, {}, {}) - - distracting_channels = [x.strip() for x in w.config_get_plugin("distracting_channels").split(',')] - colorize_nicks = w.config_get_plugin('colorize_nicks') == "1" - colorize_messages = w.config_get_plugin("colorize_messages") == "1" - debug_mode = w.config_get_plugin("debug_mode").lower() - if debug_mode != '' and debug_mode != 'false': - create_slack_debug_buffer() - colorize_private_chats = w.config_string_to_boolean(w.config_get_plugin("colorize_private_chats")) - show_reaction_nicks = w.config_string_to_boolean(w.config_get_plugin("show_reaction_nicks")) +def load_emoji(): + try: + DIR = w.info_get("weechat_dir", "") + with open('{}/weemoji.json'.format(DIR), 'r') as ef: + return json.loads(ef.read())["emoji"] + except Exception as e: + dbg("Couldn't load emoji list: {}".format(e), 5) + return [] + + +def setup_hooks(): + cmds = {k[8:]: v for k, v in globals().items() if k.startswith("command_")} + + w.bar_item_new('slack_typing_notice', 'typing_bar_item_cb', '') + + w.hook_timer(1000, 0, 0, "typing_update_cb", "") + w.hook_timer(1000, 0, 0, "buffer_list_update_callback", "EVENTROUTER") + w.hook_timer(3000, 0, 0, "reconnect_callback", "EVENTROUTER") + w.hook_timer(1000 * 60 * 5, 0, 0, "slack_never_away_cb", "") + + w.hook_signal('buffer_closing', "buffer_closing_callback", "EVENTROUTER") + w.hook_signal('buffer_switch', "buffer_switch_callback", "EVENTROUTER") + w.hook_signal('window_switch', "buffer_switch_callback", "EVENTROUTER") + w.hook_signal('quit', "quit_notification_callback", "") + if config.send_typing_notice: + w.hook_signal('input_text_changed', "typing_notification_cb", "") + + w.hook_command( + # Command name and description + 'slack', 'Plugin to allow typing notification and sync of read markers for slack.com', + # Usage + '[command] [command options]', + # Description of arguments + 'Commands:\n' + + '\n'.join(cmds.keys()) + + '\nUse /slack help [command] to find out more\n', + # Completions + '|'.join(cmds.keys()), + # Function name + 'slack_command_cb', '') + # w.hook_command('me', '', 'stuff', 'stuff2', '', 'me_command_cb', '') + + w.hook_command_run('/me', 'me_command_cb', '') + w.hook_command_run('/query', 'command_talk', '') + w.hook_command_run('/join', 'command_talk', '') + w.hook_command_run('/part', 'part_command_cb', '') + w.hook_command_run('/leave', 'part_command_cb', '') + w.hook_command_run('/topic', 'topic_command_cb', '') + w.hook_command_run('/thread', 'thread_command_callback', '') + w.hook_command_run('/reply', 'thread_command_callback', '') + w.hook_command_run('/rehistory', 'rehistory_command_callback', '') + w.hook_command_run('/hide', 'hide_command_callback', '') + w.hook_command_run('/msg', 'msg_command_cb', '') + w.hook_command_run('/label', 'label_command_cb', '') + w.hook_command_run("/input complete_next", "complete_next_cb", "") + w.hook_command_run("/input set_unread", "set_unread_cb", "") + w.hook_command_run("/input set_unread_current_buffer", "set_unread_current_buffer_cb", "") + w.hook_command_run('/away', 'away_command_cb', '') + + w.hook_completion("nicks", "complete @-nicks for slack", "nick_completion_cb", "") + w.hook_completion("emoji", "complete :emoji: for slack", "emoji_completion_cb", "") + + # Hooks to fix/implement + # w.hook_signal('buffer_opened', "buffer_opened_cb", "") + # w.hook_signal('window_scrolled', "scrolled_cb", "") + # w.hook_timer(3000, 0, 0, "slack_connection_persistence_cb", "") + +##### END NEW + + +def dbg(message, level=0, main_buffer=False, fout=False): + """ + send debug output to the slack-debug buffer and optionally write to a file. + """ + # TODO: do this smarter + # return + if level >= config.debug_level: + global debug_string + message = "DEBUG: {}".format(message) + if fout: + file('/tmp/debug.log', 'a+').writelines(message + '\n') + if main_buffer: + # w.prnt("", "---------") + w.prnt("", "slack: " + message) + else: + if slack_debug and (not debug_string or debug_string in message): + # w.prnt(slack_debug, "---------") + w.prnt(slack_debug, message) + +###### Config code + +Setting = collections.namedtuple('Setting', ['default', 'desc']) + +class PluginConfig(object): + # Default settings. + # These are, initially, each a (default, desc) tuple; the former is the + # default value of the setting, in the (string) format that weechat + # expects, and the latter is the user-friendly description of the setting. + # At __init__ time these values are extracted, the description is used to + # set or update the setting description for use with /help, and the default + # value is used to set the default for any settings not already defined. + # Following this procedure, the keys remain the same, but the values are + # the real (python) values of the settings. + default_settings = { + 'background_load_all_history': Setting( + default='false', + desc='Load history for each channel in the background as soon as it' + ' opens, rather than waiting for the user to look at it.'), + 'channel_name_typing_indicator': Setting( + default='true', + desc='Change the prefix of a channel from # to > when someone is' + ' typing in it. Note that this will (temporarily) affect the sort' + ' order if you sort buffers by name rather than by number.'), + 'colorize_private_chats': Setting( + default='false', + desc='Whether to use nick-colors in DM windows.'), + 'debug_mode': Setting( + default='false', + desc='Open a dedicated buffer for debug messages and start logging' + ' to it. How verbose the logging is depends on log_level.'), + 'debug_level': Setting( + default='3', + desc='Show only this level of debug info (or higher) when' + ' debug_mode is on. Lower levels -> more messages.'), + 'distracting_channels': Setting( + default='', + desc='List of channels to hide.'), + 'group_name_prefix': Setting( + default='&', + desc='The prefix of buffer names for groups (private channels).'), + 'map_underline_to': Setting( + default='_', + desc='When sending underlined text to slack, use this formatting' + ' character for it. The default ("_") sends it as italics. Use' + ' "*" to send bold instead.'), + 'never_away': Setting( + default='false', + desc='Poke Slack every five minutes so that it never marks you "away".'), + 'record_events': Setting( + default='false', + desc='Log all traffic from Slack to disk as JSON.'), + 'render_bold_as': Setting( + default='bold', + desc='When receiving bold text from Slack, render it as this in weechat.'), + 'render_italic_as': Setting( + default='italic', + desc='When receiving bold text from Slack, render it as this in weechat.' + ' If your terminal lacks italic support, consider using "underline" instead.'), + 'send_typing_notice': Setting( + default='true', + desc='Alert Slack users when you are typing a message in the input bar ' + '(Requires reload)'), + 'server_aliases': Setting( + default='', + desc='A comma separated list of `subdomain:alias` pairs. The alias' + ' will be used instead of the actual name of the slack (in buffer' + ' names, logging, etc). E.g `work:no_fun_allowed` would make your' + ' work slack show up as `no_fun_allowed` rather than `work.slack.com`.'), + 'short_buffer_names': Setting( + default='false', + desc='Use `foo.#channel` rather than `foo.slack.com.#channel` as the' + ' internal name for Slack buffers. Overrides server_aliases.'), + 'show_reaction_nicks': Setting( + default='false', + desc='Display the name of the reacting user(s) alongside each reactji.'), + 'slack_api_token': Setting( + default='INSERT VALID KEY HERE!', + desc='List of Slack API tokens, one per Slack instance you want to' + ' connect to. See the README for details on how to get these.'), + 'slack_timeout': Setting( + default='20000', + desc='How long (ms) to wait when communicating with Slack.'), + 'switch_buffer_on_join': Setting( + default='true', + desc='When /joining a channel, automatically switch to it as well.'), + 'thread_suffix_color': Setting( + default='lightcyan', + desc='Color to use for the [thread: XXX] suffix on messages that' + ' have threads attached to them.'), + 'unfurl_ignore_alt_text': Setting( + default='false', + desc='When displaying ("unfurling") links to channels/users/etc,' + ' ignore the "alt text" present in the message and instead use the' + ' canonical name of the thing being linked to.'), + 'unfurl_auto_link_display': Setting( + default='both', + desc='When displaying ("unfurling") links to channels/users/etc,' + ' determine what is displayed when the text matches the url' + ' without the protocol. This happens when Slack automatically' + ' creates links, e.g. from words separated by dots or email' + ' addresses. Set it to "text" to only display the text written by' + ' the user, "url" to only display the url or "both" (the default)' + ' to display both.'), + 'unhide_buffers_with_activity': Setting( + default='false', + desc='When activity occurs on a buffer, unhide it even if it was' + ' previously hidden (whether by the user or by the' + ' distracting_channels setting).'), + } - unfurl_ignore_alt_text = False - if w.config_get_plugin('unfurl_ignore_alt_text') != "0": - unfurl_ignore_alt_text = True + # Set missing settings to their defaults. Load non-missing settings from + # weechat configs. + def __init__(self): + self.settings = {} + # Set all descriptions, replace the values in the dict with the + # default setting value rather than the (setting,desc) tuple. + # Use items() rather than iteritems() so we don't need to worry about + # invalidating the iterator. + for key, (default, desc) in self.default_settings.items(): + w.config_set_desc_plugin(key, desc) + self.settings[key] = default + + # Migrate settings from old versions of Weeslack... + self.migrate() + # ...and then set anything left over from the defaults. + for key, default in self.settings.iteritems(): + if not w.config_get_plugin(key): + w.config_set_plugin(key, default) + self.config_changed(None, None, None) - return w.WEECHAT_RC_OK + def __str__(self): + return "".join([x + "\t" + str(self.settings[x]) + "\n" for x in self.settings.keys()]) -def quit_notification_cb(signal, sig_type, data): - stop_talking_to_slack() - return w.WEECHAT_RC_OK + def config_changed(self, data, key, value): + for key in self.settings: + self.settings[key] = self.fetch_setting(key) + if self.debug_mode: + create_slack_debug_buffer() + return w.WEECHAT_RC_OK -def script_unloaded(): - stop_talking_to_slack() - return w.WEECHAT_RC_OK + def fetch_setting(self, key): + if hasattr(self, 'get_' + key): + try: + return getattr(self, 'get_' + key)(key) + except: + return self.settings[key] + else: + # Most settings are on/off, so make get_boolean the default + return self.get_boolean(key) + + def __getattr__(self, key): + return self.settings[key] + + def get_boolean(self, key): + return w.config_string_to_boolean(w.config_get_plugin(key)) + + def get_string(self, key): + return w.config_get_plugin(key) + + def get_int(self, key): + return int(w.config_get_plugin(key)) + + def is_default(self, key): + default = self.default_settings.get(key).default + return w.config_get_plugin(key) == default + + get_debug_level = get_int + get_group_name_prefix = get_string + get_map_underline_to = get_string + get_render_bold_as = get_string + get_render_italic_as = get_string + get_slack_timeout = get_int + get_thread_suffix_color = get_string + get_unfurl_auto_link_display = get_string + + def get_distracting_channels(self, key): + return [x.strip() for x in w.config_get_plugin(key).split(',')] + + def get_server_aliases(self, key): + alias_list = w.config_get_plugin(key) + if len(alias_list) > 0: + return dict(item.split(":") for item in alias_list.split(",")) + + def get_slack_api_token(self, key): + token = w.config_get_plugin("slack_api_token") + if token.startswith('${sec.data'): + return w.string_eval_expression(token, {}, {}, {}) + else: + return token -def stop_talking_to_slack(): - """ - Prevents a race condition where quitting closes buffers - which triggers leaving the channel because of how close - buffer is handled - """ - global STOP_TALKING_TO_SLACK - STOP_TALKING_TO_SLACK = True - cache_write_cb("", "") - return w.WEECHAT_RC_OK + def migrate(self): + """ + This is to migrate the extension name from slack_extension to slack + """ + if not w.config_get_plugin("migrated"): + for k in self.settings.keys(): + if not w.config_is_set_plugin(k): + p = w.config_get("plugins.var.python.slack_extension.{}".format(k)) + data = w.config_string(p) + if data != "": + w.config_set_plugin(k, data) + w.config_set_plugin("migrated", "true") + + +# to Trace execution, add `setup_trace()` to startup +# and to a function and sys.settrace(trace_calls) to a function +def setup_trace(): + global f + now = time.time() + f = open('{}/{}-trace.json'.format(RECORD_DIR, now), 'w') -def scrolled_cb(signal, sig_type, data): - try: - if w.window_get_integer(data, "scrolling") == 1: - channels.find(w.current_buffer()).set_scrolling() - else: - channels.find(w.current_buffer()).unset_scrolling() - except: - pass - return w.WEECHAT_RC_OK -# END Utility Methods +def trace_calls(frame, event, arg): + global f + if event != 'call': + return + co = frame.f_code + func_name = co.co_name + if func_name == 'write': + # Ignore write() calls from print statements + return + func_line_no = frame.f_lineno + func_filename = co.co_filename + caller = frame.f_back + caller_line_no = caller.f_lineno + caller_filename = caller.f_code.co_filename + print >> f, 'Call to %s on line %s of %s from line %s of %s' % \ + (func_name, func_line_no, func_filename, + caller_line_no, caller_filename) + f.flush() + return + +def initiate_connection(token, retries=3): + return SlackRequest(token, + 'rtm.start', + {"batch_presence_aware": 1 }, + retries=retries) # Main if __name__ == "__main__": + w = WeechatWrapper(weechat) + if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "script_unloaded", ""): - version = w.info_get("version_number", "") or 0 - if int(version) < 0x1030000: + weechat_version = w.info_get("version_number", "") or 0 + if int(weechat_version) < 0x1030000: w.prnt("", "\nERROR: Weechat version 1.3+ is required to use {}.\n\n".format(SCRIPT_NAME)) else: - WEECHAT_HOME = w.info_get("weechat_dir", "") - CACHE_NAME = "slack.cache" - STOP_TALKING_TO_SLACK = False - - if not w.config_get_plugin('slack_api_token'): - w.config_set_plugin('slack_api_token', "INSERT VALID KEY HERE!") - if not w.config_get_plugin('distracting_channels'): - w.config_set_plugin('distracting_channels', "") - if not w.config_get_plugin('debug_mode'): - w.config_set_plugin('debug_mode', "") - if not w.config_get_plugin('colorize_nicks'): - w.config_set_plugin('colorize_nicks', "1") - if not w.config_get_plugin('colorize_messages'): - w.config_set_plugin('colorize_messages', "0") - if not w.config_get_plugin('colorize_private_chats'): - w.config_set_plugin('colorize_private_chats', "0") - if not w.config_get_plugin('trigger_value'): - w.config_set_plugin('trigger_value', "0") - if not w.config_get_plugin('unfurl_ignore_alt_text'): - w.config_set_plugin('unfurl_ignore_alt_text', "0") - if not w.config_get_plugin('switch_buffer_on_join'): - w.config_set_plugin('switch_buffer_on_join', "1") - if not w.config_get_plugin('show_reaction_nicks'): - w.config_set_plugin('show_reaction_nicks', "0") - - if w.config_get_plugin('channels_not_on_current_server_color'): - w.config_option_unset('channels_not_on_current_server_color') + global EVENTROUTER + EVENTROUTER = EventRouter() + # setup_trace() + + # WEECHAT_HOME = w.info_get("weechat_dir", "") # Global var section slack_debug = None - config_changed_cb("", "", "") - - cmds = {k[8:]: v for k, v in globals().items() if k.startswith("command_")} - proc = {k[8:]: v for k, v in globals().items() if k.startswith("process_")} + config = PluginConfig() + config_changed_cb = config.config_changed typing_timer = time.time() - domain = None - previous_buffer = None - slack_buffer = None - - buffer_list_update = False - previous_buffer_list_update = 0 + # domain = None + # previous_buffer = None + # slack_buffer = None - never_away = False + # never_away = False hide_distractions = False - hotlist = w.infolist_get("hotlist", "", "") - main_weechat_buffer = w.info_get("irc_buffer", "{}.{}".format(domain, "DOESNOTEXIST!@#$")) - - message_cache = collections.defaultdict(list) - cache_load() - - servers = SearchList() - for token in slack_api_token.split(','): - server = SlackServer(token) - servers.append(server) - channels = SearchList() - users = SearchList() + # hotlist = w.infolist_get("hotlist", "", "") + # main_weechat_buffer = w.info_get("irc_buffer", "{}.{}".format(domain, "DOESNOTEXIST!@#$")) w.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", "config_changed_cb", "") - w.hook_timer(3000, 0, 0, "slack_connection_persistence_cb", "") + w.hook_modifier("input_text_for_buffer", "input_text_for_buffer_cb", "") + + EMOJI.extend(load_emoji()) + setup_hooks() # attach to the weechat hooks we need - w.hook_timer(1000, 0, 0, "typing_update_cb", "") - w.hook_timer(1000, 0, 0, "buffer_list_update_cb", "") - w.hook_timer(1000, 0, 0, "hotlist_cache_update_cb", "") - w.hook_timer(1000 * 60 * 29, 0, 0, "slack_never_away_cb", "") - w.hook_timer(1000 * 60 * 5, 0, 0, "cache_write_cb", "") - w.hook_signal('buffer_closing', "buffer_closing_cb", "") - w.hook_signal('buffer_switch', "buffer_switch_cb", "") - w.hook_signal('window_switch', "buffer_switch_cb", "") - w.hook_signal('input_text_changed', "typing_notification_cb", "") - w.hook_signal('quit', "quit_notification_cb", "") - w.hook_signal('window_scrolled', "scrolled_cb", "") - w.hook_command( - # Command name and description - 'slack', 'Plugin to allow typing notification and sync of read markers for slack.com', - # Usage - '[command] [command options]', - # Description of arguments - 'Commands:\n' + - '\n'.join(cmds.keys()) + - '\nUse /slack help [command] to find out more\n', - # Completions - '|'.join(cmds.keys()), - # Function name - 'slack_command_cb', '') - # w.hook_command('me', 'me_command_cb', '') - w.hook_command('me', '', '', '', '', 'me_command_cb', '') - w.hook_command_run('/query', 'join_command_cb', '') - w.hook_command_run('/join', 'join_command_cb', '') - w.hook_command_run('/part', 'part_command_cb', '') - w.hook_command_run('/leave', 'part_command_cb', '') - w.hook_command_run('/topic', 'topic_command_cb', '') - w.hook_command_run('/msg', 'msg_command_cb', '') - w.hook_command_run("/input complete_next", "complete_next_cb", "") - w.hook_completion("nicks", "complete @-nicks for slack", - "nick_completion_cb", "") - w.bar_item_new('slack_typing_notice', 'typing_bar_item_cb', '') + + tokens = config.slack_api_token.split(',') + for t in tokens: + s = initiate_connection(t) + EVENTROUTER.receive(s) + if config.record_events: + EVENTROUTER.record() + EVENTROUTER.handle_next() + w.hook_timer(10, 0, 0, "handle_next", "") # END attach to the weechat hooks we need From 1cba393a5b7e8625e2864d4fdd3eae3b1742f42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Tue, 6 Mar 2018 21:53:34 +0100 Subject: [PATCH 114/642] buddylist.pl 1.9: add cursor mode support --- perl/buddylist.pl | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/perl/buddylist.pl b/perl/buddylist.pl index 1e4101b2..c5920621 100644 --- a/perl/buddylist.pl +++ b/perl/buddylist.pl @@ -1,5 +1,5 @@ # -# Copyright (c) 2010-2013 by Nils Görs +# Copyright (c) 2010-2018 by Nils Görs # # display the status and visited buffers of your buddies in a buddylist bar # @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# 1.9 : added: cursor support # 1.8 : fixed: problem with temporary server # : added: %h variable for filename # 1.7 : fixed: perl error when adding and removing nick @@ -81,7 +82,7 @@ use strict; my $prgname = "buddylist"; -my $version = "1.8"; +my $version = "1.9"; my $description = "display status from your buddies a bar-item."; # -------------------------------[ config ]------------------------------------- @@ -121,6 +122,9 @@ "\@chat(*)>item(buddylist):button1-gesture-*" => "hsignal:buddylist_mouse", "\@item(buddylist):button1-gesture-*" => "hsignal:buddylist_mouse"); +my %cursor_keys = ( "\@item(buddylist):q" => "hsignal:buddylist_cursor", + "\@item(buddylist):w" => "hsignal:buddylist_cursor"); + my $debug_redir_out = "off"; # ------------------------------[ internal ]----------------------------------- @@ -196,7 +200,9 @@ if ($weechat_version >= 0x00030600){ weechat::hook_focus($prgname, "hook_focus_buddylist", ""); weechat::hook_hsignal("buddylist_mouse","buddylist_hsignal_mouse", ""); + weechat::hook_hsignal("buddylist_cursor","buddylist_hsignal_cursor", ""); weechat::key_bind("mouse", \%mouse_keys); + weechat::key_bind("cursor", \%cursor_keys); } @@ -248,10 +254,13 @@ "\n". "'plugins.var.perl.buddylist.use.redirection' : using redirection to get status of buddies (needs weechat >=0.3.4) (default: on).\n". "\n\n". - "Mouse-support (standard key bindings):\n". + "Mouse-support:\n". " click left mouse-button on buddy to open a query buffer.\n". " add a buddy by dragging a nick with left mouse-button from nicklist or chat-area and drop on buddylist.\n". " remove a buddy by dragging a buddy with left mouse-button from buddylist and drop it on any area.\n". + "Cursor-Mode:\n". + " q open query with nick (/query)\n". + " w query information about user (/whois)\n". "\n\n". "Troubleshooting:\n". "If buddylist will not be refreshed in nicklist-mode, check the following WeeChat options:\n". @@ -1521,7 +1530,7 @@ sub buddy_list_completion_cb{ return weechat::WEECHAT_RC_OK; } -# -------------------------------[ mouse support ]------------------------------------- +# --------------------------[ mouse and cursor support ]-------------------------------- sub hook_focus_buddylist{ my %info = %{$_[1]}; my $bar_item_line = int($info{"_bar_item_line"}); @@ -1529,9 +1538,9 @@ sub hook_focus_buddylist{ return if ($#buddylist_focus == -1); my $flag = 0; - # if button1 was pressed on "offline" buddy, do nothing!!! - if ( ($info{"_bar_item_name"} eq $prgname) && ($bar_item_line >= 0) && ($bar_item_line <= $#buddylist_focus) && ($info{"_key"} eq "button1" ) ){ - $hash = $buddylist_focus[$bar_item_line]; + # mouse or key pressed on "offline" buddy, do nothing!!! + if ( ($info{"_bar_item_name"} eq $prgname) && ($bar_item_line >= 0) && ($bar_item_line <= $#buddylist_focus) ){ + $hash = $buddylist_focus[$bar_item_line]; my $hash_focus = $hash; while ( my ($key,$value) = each %$hash_focus ){ if ( $key eq "status" and $value eq "2" ){ @@ -1574,4 +1583,20 @@ sub buddylist_hsignal_mouse{ weechat::bar_item_update($prgname); return weechat::WEECHAT_RC_OK; } -# this is the end + +sub buddylist_hsignal_cursor{ + my ($data, $signal, %hash) = ($_[0], $_[1], %{$_[2]}); + + # no server? + return weechat::WEECHAT_RC_OK if (not defined $hash{"server"}); + + # check which key was pressed and do some magic! + if ( $hash{"_key"} eq "q" ){ + weechat::command("", "/query -server " . $hash{"server"} . " " . $hash{"nick"}); + }elsif ( $hash{"_key"} eq "w" ){ + weechat::command(weechat::buffer_search("==","irc.server.".$hash{"server"}), "/WHOIS " . $hash{"nick"}); + } + # STOP cursor mode + weechat::command("", "/cursor stop"); + return weechat::WEECHAT_RC_OK; +} From b488e56560cebdf363fe217389d953e64c4f497c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Wed, 21 Mar 2018 19:06:02 +0100 Subject: [PATCH 115/642] colorize_nicks 25: fix unable to run function colorize_config_reload_cb (issues #260) --- python/colorize_nicks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/colorize_nicks.py b/python/colorize_nicks.py index 5d268b54..fc53be31 100644 --- a/python/colorize_nicks.py +++ b/python/colorize_nicks.py @@ -21,6 +21,8 @@ # # # History: +# 2018-03-18: nils_2 +# version 25: fix unable to run function colorize_config_reload_cb() # 2017-06-20: lbeziaud # version 24: colorize utf8 nicks # 2017-03-01, arza @@ -81,7 +83,7 @@ SCRIPT_NAME = "colorize_nicks" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "24" +SCRIPT_VERSION = "25" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Use the weechat nick colors in the chat area" @@ -108,7 +110,7 @@ def colorize_config_init(): ''' global colorize_config_file, colorize_config_option colorize_config_file = weechat.config_new(CONFIG_FILE_NAME, - "colorize_config_reload_cb", "") + "", "") if colorize_config_file == "": return From 8ee5d999b56bb24a379378ca49441da4053417bd Mon Sep 17 00:00:00 2001 From: "Matthew M. Boedicker" Date: Mon, 26 Mar 2018 20:42:41 -0700 Subject: [PATCH 116/642] otr.py 1.9.2: Fix WeeChat crash on /reload --- python/otr.py | 68 +++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/python/otr.py b/python/otr.py index 619f73ad..0ccfb35a 100644 --- a/python/otr.py +++ b/python/otr.py @@ -121,7 +121,8 @@ def to_str(self, strng): """Convert a Unicode to a utf-8 encoded string.""" return strng -if sys.version_info.major >= 3: +# We cannot use version_info.major as this is only supported on python >= 2.7 +if sys.version_info[0] >= 3: PYVER = PythonVersion3(sys.version_info.minor) else: PYVER = PythonVersion2() @@ -163,7 +164,7 @@ def to_str(self, strng): SCRIPT_AUTHOR = 'Matthew M. Boedicker' SCRIPT_LICENCE = 'GPL3' -SCRIPT_VERSION = '1.9.1' +SCRIPT_VERSION = '1.9.2' OTR_DIR_NAME = 'otr' @@ -268,7 +269,7 @@ def print_buffer(buf, message, level='info'): using color according to level.""" prnt(buf, '{prefix}\t{msg}'.format( prefix=get_prefix(), - msg=colorize(message, 'buffer.{}'.format(level)))) + msg=colorize(message, 'buffer.{0}'.format(level)))) def get_prefix(): """Returns configured message prefix.""" @@ -394,7 +395,7 @@ def config_prefix(option): def config_color(option): """Get the color of a color config option.""" return weechat.color(weechat.config_color(config_get_prefixed( - 'color.{}'.format(option)))) + 'color.{0}'.format(option)))) def config_string(option): """Get the string value of a config option with utf-8 decode.""" @@ -468,7 +469,7 @@ def format_default_policies(): buf.write(' {policy} ({desc}) : {value}\n'.format( policy=policy, desc=desc, - value=config_string('policy.default.{}'.format(policy)))) + value=config_string('policy.default.{0}'.format(policy)))) buf.write('Change default policies with: /otr policy default NAME on|off') @@ -533,7 +534,7 @@ def show_peer_fingerprints(grep=None): def private_key_file_path(account_name): """Return the private key file path for an account.""" - return os.path.join(OTR_DIR, '{}.key3'.format(account_name)) + return os.path.join(OTR_DIR, '{0}.key3'.format(account_name)) def read_private_key(key_file_path): """Return the private key in a private key file.""" @@ -631,7 +632,7 @@ def getPolicy(self, key): if option == '': option = config_get_prefixed( - 'policy.default.{}'.format(key_lower)) + 'policy.default.{0}'.format(key_lower)) result = bool(weechat.config_boolean(option)) @@ -646,7 +647,7 @@ def inject(self, msg, appdata=None): else: msg = PYVER.to_unicode(msg) - debug(('inject', msg, 'len {}'.format(len(msg)), appdata)) + debug(('inject', msg, 'len {0}'.format(len(msg)), appdata)) privmsg(self.peer_server, self.peer_nick, msg) @@ -684,7 +685,7 @@ def setState(self, newstate): if trust is None: fpr = str(self.getCurrentKey()) - self.print_buffer('New fingerprint: {}'.format(fpr), 'warning') + self.print_buffer('New fingerprint: {0}'.format(fpr), 'warning') self.setCurrentTrust('') if bool(trust): @@ -711,7 +712,7 @@ def maxMessageSize(self, appdata=None): """Return the max message size for this context.""" # remove 'PRIVMSG :' from max message size result = self.user.maxMessageSize - 10 - len(self.peer_nick) - debug('max message size {}'.format(result)) + debug('max message size {0}'.format(result)) return result @@ -941,16 +942,16 @@ def restore_logging(self, previous_log_level): if (previous_log_level >= 0) and (previous_log_level < 10): self.print_buffer( - 'Restoring buffer logging value to: {}'.format( + 'Restoring buffer logging value to: {0}'.format( previous_log_level), 'warning') - weechat.command(buf, '/mute logger set {}'.format( + weechat.command(buf, '/mute logger set {0}'.format( previous_log_level)) if previous_log_level == -1: logger_option_name = self.get_logger_option_name() self.print_buffer( 'Restoring buffer logging value to default', 'warning') - weechat.command(buf, '/mute unset {}'.format( + weechat.command(buf, '/mute unset {0}'.format( logger_option_name)) del self.previous_log_level @@ -1000,7 +1001,7 @@ def no_send_tag(self): def __repr__(self): return PYVER.to_str(( - '<{} {:x} peer_nick={c.peer_nick} peer_server={c.peer_server}>' + '<{0} {1:x} peer_nick={c.peer_nick} peer_server={c.peer_server}>' ).format(self.__class__.__name__, id(self), c=self)) class IrcOtrAccount(potr.context.Account): @@ -1022,7 +1023,7 @@ def __init__(self, name): self.defaultQuery = self.defaultQuery.replace("\n", ' ') self.key_file_path = private_key_file_path(name) - self.fpr_file_path = os.path.join(OTR_DIR, '{}.fpr'.format(name)) + self.fpr_file_path = os.path.join(OTR_DIR, '{0}.fpr'.format(name)) self.load_trusts() @@ -1124,7 +1125,7 @@ def handle_endtag(self, tag): if self.result[self.linkstart:] == self.linktarget: self.result += ']' else: - self.result += ']({})'.format(self.linktarget) + self.result += ']({0})'.format(self.linktarget) self.linktarget = '' def handle_data(self, data): @@ -1137,7 +1138,7 @@ def handle_entityref(self, name): self.result += PYVER.unichr( PYVER.html_entities.name2codepoint[name]) except KeyError: - self.result += '&{};'.format(name) + self.result += '&{0};'.format(name) def handle_charref(self, name): """Called for character references, such as '""" @@ -1147,7 +1148,7 @@ def handle_charref(self, name): else: self.result += PYVER.unichr(int(name)) except ValueError: - self.result += '&#{};'.format(name) + self.result += '&#{0};'.format(name) class TableFormatter(object): """Format lists of string into aligned tables.""" @@ -1209,7 +1210,7 @@ def message_in_cb(data, modifier, modifier_data, string): context.handle_tlvs(tlvs) except potr.context.ErrorReceived as err: - context.print_buffer('Received OTR error: {}'.format( + context.print_buffer('Received OTR error: {0}'.format( PYVER.to_unicode(err.args[0])), 'error') except potr.context.NotEncryptedError: context.print_buffer( @@ -1385,9 +1386,9 @@ def command_cb(data, buf, args): if context.is_encrypted(): context.print_buffer( 'This conversation is encrypted.', 'success') - context.print_buffer("Your fingerprint is: {}".format( + context.print_buffer("Your fingerprint is: {0}".format( context.user.getPrivkey())) - context.print_buffer("Your peer's fingerprint is: {}".format( + context.print_buffer("Your peer's fingerprint is: {0}".format( potr.human_hash(context.crypto.theirPubkey.cfingerprint()))) if context.is_verified(): context.print_buffer( @@ -1463,7 +1464,7 @@ def command_cb(data, buf, args): context.smpInit(secret, question) except potr.context.NotEncryptedError: context.print_buffer( - 'There is currently no encrypted session with {}.'.format( + 'There is currently no encrypted session with {0}.'.format( context.peer), 'error') else: if question: @@ -1490,7 +1491,7 @@ def command_cb(data, buf, args): context.smpAbort() except potr.context.NotEncryptedError: context.print_buffer( - 'There is currently no encrypted session with {}.' + 'There is currently no encrypted session with {0}.' .format(context.peer), 'error') else: debug('SMP aborted') @@ -1826,7 +1827,7 @@ def buffer_closing_cb(data, signal, signal_data): def init_config(): """Set up configuration options and load config file.""" global CONFIG_FILE - CONFIG_FILE = weechat.config_new(SCRIPT_NAME, 'config_reload_cb', '') + CONFIG_FILE = weechat.config_new(SCRIPT_NAME, '', '') global CONFIG_SECTIONS CONFIG_SECTIONS = {} @@ -1993,13 +1994,6 @@ def init_config(): weechat.config_read(CONFIG_FILE) -def config_reload_cb(data, config_file): - """/reload callback to reload config from file.""" - free_all_config() - init_config() - - return weechat.WEECHAT_CONFIG_READ_OK - def free_all_config(): """Free all config options, sections and config file.""" for section in CONFIG_SECTIONS.values(): @@ -2022,13 +2016,19 @@ def git_info(): if os.path.isdir(git_dir): import subprocess try: - result = PYVER.to_unicode(subprocess.check_output([ + # We can't use check_output here without breaking compatibility + # for Python 2.6, but we ignore the return value anyway, so Popen + # is only slightly more complicated: + process = subprocess.Popen([ 'git', '--git-dir', git_dir, '--work-tree', script_dir, 'describe', '--dirty', '--always', - ])).lstrip('v').rstrip() - except (OSError, subprocess.CalledProcessError): + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = process.communicate()[0] + if output: + result = PYVER.to_unicode(output).lstrip('v').rstrip() + except OSError: pass return result From ed6d0c0613b58197d913d0358e2db60315971d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Sat, 31 Mar 2018 21:44:40 +0200 Subject: [PATCH 117/642] Remove script buffer_swap.py (moved to unofficial scripts, not needed with WeeChat >= 0.3.9) --- python/buffer_swap.py | 135 ------------------------------------------ 1 file changed, 135 deletions(-) delete mode 100644 python/buffer_swap.py diff --git a/python/buffer_swap.py b/python/buffer_swap.py deleted file mode 100644 index 3863ae03..00000000 --- a/python/buffer_swap.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -# -# buffer_swap, version 0.3 for WeeChat version 0.3 -# Latest development version: https://github.com/FiXato/weechat_scripts -# -# Swaps given 2 buffers. Requested by kakazza -# -## Example: -# Swaps buffers 3 and 5 -# /swap 3 5 -# -# Swaps current buffer with the #weechat buffer -# /swap #weechat -# -## History: -### 2011-09-18: FiXato: -# -# * version 0.1: initial release. -# * Allow switching 2 given buffers -# -# * version 0.2: cleanup -# * Made the command example more clear that it requires 2 buffer *numbers* -# * After switching, now switches back to your original buffer. -# -# * version 0.3: current buffer support -# * If you only specify 1 buffer, the current buffer will be used -# -## Acknowledgements: -# * Sebastien "Flashcode" Helleu, for developing the kick-ass chat/IRC -# client WeeChat -# -## TODO: -# - Check if given buffers exist. -# -## Copyright (c) 2011 Filip H.F. "FiXato" Slagter, -# -# http://google.com/profiles/FiXato -# -# 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 = "buffer_swap" -SCRIPT_AUTHOR = "Filip H.F. 'FiXato' Slagter " -SCRIPT_VERSION = "0.3" -SCRIPT_LICENSE = "MIT" -SCRIPT_DESC = "Swaps given 2 buffers" -SCRIPT_COMMAND = "swap" -SCRIPT_CLOSE_CB = "close_cb" - -import_ok = True - -try: - import weechat -except ImportError: - print "This script must be run under WeeChat." - import_ok = False - -def close_cb(*kwargs): - return weechat.WEECHAT_RC_OK - -def command_main(data, buffer, args): - args = args.split() - curr_buffer = weechat.current_buffer() - curr_buffer_number = weechat.buffer_get_integer(curr_buffer, "number") - - if len(args) != 1 and len(args) != 2: - weechat.prnt("", "You need to specify 1 or 2 buffers") - return weechat.WEECHAT_RC_ERROR - - if len(args) == 2: - weechat.command("", "/buffer %s" % args[0]) - first_buffer = weechat.current_buffer() - first_buffer_number = weechat.buffer_get_integer(first_buffer, "number") - - weechat.command("", "/buffer %s" % args[1]) - second_buffer = weechat.current_buffer() - second_buffer_number = weechat.buffer_get_integer(second_buffer, "number") - else: - first_buffer = weechat.current_buffer() - first_buffer_number = weechat.buffer_get_integer(first_buffer, "number") - - weechat.command("", "/buffer %s" % args[0]) - second_buffer = weechat.current_buffer() - second_buffer_number = weechat.buffer_get_integer(second_buffer, "number") - - weechat.buffer_set(first_buffer, "number", str(second_buffer_number)) - weechat.buffer_set(second_buffer, "number", str(first_buffer_number)) - - weechat.command("", "/buffer %s" % str(curr_buffer_number)) - - return weechat.WEECHAT_RC_OK - -if __name__ == "__main__" and import_ok: - if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, - SCRIPT_LICENSE, SCRIPT_DESC, SCRIPT_CLOSE_CB, ""): - # # Set default settings - # for option, default_value in cs_settings.iteritems(): - # if not weechat.config_is_set_plugin(option): - # weechat.config_set_plugin(option, default_value) - - weechat.hook_command(SCRIPT_COMMAND, - SCRIPT_DESC, - "[buffer] ", - "Swaps given buffers: \n" - "the /swap command accepts anything that /buffer would accept for switching buffers\n" - "/swap 3 10\n" - "would swap buffer 3 and 10 of place\n" - "/swap 3\n" - "would swap current buffer with buffer number 10\n" - "/swap 3 #weechat\n" - "would swap buffer 3 and the #weechat buffer of place\n" - "/swap #weechat\n" - "would swap current buffer with the #weechat buffer", - - "", - - "command_main", "") - - From f3339c5dc772f60f3603916c17589789ee64677b Mon Sep 17 00:00:00 2001 From: jmui Date: Mon, 2 Apr 2018 11:36:06 +0200 Subject: [PATCH 118/642] New script emojis.py: send a random emoji to the current buffer --- python/emojis.py | 474 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 python/emojis.py diff --git a/python/emojis.py b/python/emojis.py new file mode 100644 index 00000000..16603950 --- /dev/null +++ b/python/emojis.py @@ -0,0 +1,474 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 jmui +# +# 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 . +# +# +# +# A simple script to print a random emoji chosen from a list +# +# Usage: /emojis +# +# +# History: +# 2017-11-17 +# 0.1 First version +# +# + +SCRIPT_NAME = "emojis" +SCRIPT_AUTHOR = "jmui " +SCRIPT_VERSION = "0.1" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Send a random emoji to the current buffer" + + +import_ok = True + +try: + import weechat +except ImportError: + print("Script must be used in WeeChat.") + import_ok = False + +from random import choice + + +emojiList = [ + '¢‿¢', + '©¿© o', + 'ª{•̃̾_•̃̾}ª', + '¬_¬', + '¯\(º_o)/¯', + '¯\(º o)/¯', + '¯\_(⊙︿⊙)_/¯', + '¯\_(ツ)_/¯', + '°ω°', + '°Д°', + '°‿‿°', + '´ ▽ ` )ノ', + '¿ⓧ_ⓧﮌ', + 'Ò,ó', + 'ó‿ó', + 'ô⌐ô', + 'ôヮô', + 'ŎםŎ', + 'ŏﺡó', + 'ʕ•̫͡•ʔ', + 'ʕ•ᴥ•ʔ', + 'ʘ‿ʘ', + '˚•_•˚', + '˚⌇˚', + '˚▱˚', + 'Σ ◕ ◡ ◕', + 'Σ (゚Д゚;)', + 'Σ(゚Д゚;≡;゚д゚)', + 'Σ(゚Д゚ )', + 'Σ(||゚Д゚)', + 'Φ,Φ', + 'δﺡό', + 'σ_σ', + 'д_д', + 'ф_ф', + 'щ(゚Д゚щ)', + 'щ(ಠ益ಠщ)', + 'щ(ಥДಥщ)', + 'Ծ_Ծ', + '٩๏̯͡๏۶', + '٩๏̯͡๏)۶', + '٩◔̯◔۶', + '٩(×̯×)۶', + '٩(̾●̮̮̃̾•̃̾)۶', + '٩(͡๏̯͡๏)۶', + '٩(͡๏̯ ͡๏)۶', + '٩(ಥ_ಥ)۶', + '٩(•̮̮̃•̃)۶', + '٩(●̮̮̃•̃)۶', + '٩(●̮̮̃●̃)۶', + '٩(。͡•‿•。)۶', + '٩(-̮̮̃•̃)۶', + '٩(-̮̮̃-̃)۶', + '۞_۞', + '۞_۟۞', + '۹ↁﮌↁ', + '۹⌤_⌤۹', + '॓_॔', + '१✌◡✌५', + '१|˚–˚|५', + 'ਉ_ਉ', + 'ଘ_ଘ', + 'இ_இ', + 'ఠ_ఠ', + 'రృర', + 'ಠ¿ಠi', + 'ಠ‿ಠ', + 'ಠ⌣ಠ', + 'ಠ╭╮ಠ', + 'ಠ▃ಠ', + 'ಠ◡ಠ', + 'ಠ益ಠ', + 'ಠ益ಠ', + 'ಠ︵ಠ凸', + 'ಠ , ಥ', + 'ಠ.ಠ', + 'ಠoಠ', + 'ಠ_ృ', + 'ಠ_ಠ', + 'ಠ_๏', + 'ಠ~ಠ', + 'ಡ_ಡ', + 'ತಎತ', + 'ತ_ತ', + 'ಥдಥ', + 'ಥ‿ಥ', + 'ಥ⌣ಥ', + 'ಥ◡ಥ', + 'ಥ﹏ಥ', + 'ಥ_ಥ', + 'ಭ_ಭ', + 'ರ_ರ', + 'ಸ , ໖', + 'ಸ_ಸ', + 'ക_ക', + 'อ้_อ้', + 'อ_อ', + 'โ๏௰๏ใ ื', + '๏̯͡๏﴿', + '๏̯͡๏', + '๏̯͡๏﴿', + '๏[-ิิ_•ิ]๏', + '๏_๏', + '໖_໖', + '༺‿༻', + 'ლ(´ڡ`ლ)', + 'ლ(́◉◞౪◟◉‵ლ)', + 'ლ(ಠ益ಠლ)', + 'ლ(╹◡╹ლ)', + 'ლ(◉◞౪◟◉‵ლ)', + 'ლ,ᔑ•ﺪ͟͠•ᔐ.ლ', + 'ᄽὁȍ ̪ őὀᄿ', + 'ᕕ( ᐛ )ᕗ', + 'ᕙ(⇀‸↼‶)ᕗ', + 'ᕦ(ò_óˇ)ᕤ', + 'ᶘ ᵒᴥᵒᶅ', + '‘︿’', + '•▱•', + '•✞_✞•', + '•(⌚_⌚)•', + '•_•)', + '‷̗ↂ凸ↂ‴̖', + '‹•.•›', + '‹› ‹(•¿•)› ‹›', + '‹(ᵒᴥᵒ­­­­­)›', + '‹(•¿•)›', + 'ↁ_ↁ', + '⇎_⇎', + '∩(︶▽︶)∩', + '∩( ・ω・)∩', + '≖‿≖', + '≧ヮ≦', + '⊂•⊃_⊂•⊃', + '⊂⌒~⊃。Д。)⊃', + '⊂(◉‿◉)つ', + '⊂(゚Д゚,,⊂⌒`つ', + '⊙ω⊙', + '⊙▂⊙', + '⊙▃⊙', + '⊙△⊙', + '⊙︿⊙', + '⊙﹏⊙', + '⊙0⊙', + '⊛ठ̯⊛', + '⋋ō_ō`', + '━━━ヽ(ヽ(゚ヽ(゚∀ヽ(゚∀゚ヽ(゚∀゚)ノ゚∀゚)ノ∀゚)ノ゚)ノ)ノ━━━', + '┌∩┐(◕_◕)┌∩┐', + '┌( ಠ_ಠ)┘', + '┌( ಥ_ಥ)┘', + '╚(•⌂•)╝', + '╭╮╭╮☜{•̃̾_•̃̾}☞╭╮╭╮', + '╭✬⌢✬╮', + '╮(─▽─)╭', + '╯‵Д′)╯彡┻━┻', + '╰☆╮', + '□_□', + '►_◄', + '◃┆◉◡◉┆▷', + '◉△◉', + '◉︵◉', + '◉_◉', + '○_○', + '●¿●\ ~', + '●_●', + '◔̯◔', + '◔ᴗ◔', + '◔ ⌣ ◔', + '◔_◔', + '◕ω◕', + '◕‿◕', + '◕◡◕', + '◕ ◡ ◕', + '◖♪_♪|◗', + '◖|◔◡◉|◗', + '◘_◘', + '◙‿◙', + '◜㍕◝', + '◪_◪', + '◮_◮', + '☁ ☝ˆ~ˆ☂', + '☆¸☆', + '☉‿⊙', + '☉_☉', + '☐_☐', + '☜(⌒▽⌒)☞', + '☜(゚ヮ゚☜)', + '☜-(ΘLΘ)-☞', + '☝☞✌', + '☮▁▂▃▄☾ ♛ ◡ ♛ ☽▄▃▂▁☮', + '☹_☹', + '☻_☻', + '☼.☼', + '☾˙❀‿❀˙☽', + '♥‿♥', + '♥╣[-_-]╠♥', + '♥╭╮♥', + '♥◡♥', + '✌♫♪˙❤‿❤˙♫♪✌', + '✌.ʕʘ‿ʘʔ.✌', + '✌.|•͡˘‿•͡˘|.✌', + '✖‿✖', + '✖_✖', + '❐‿❑', + '⨀_⨀', + '⨀_Ꙩ', + '⨂_⨂', + '〆(・∀・@)', + '《〠_〠》', + '【•】_【•】', + '〠_〠', + '〴⋋_⋌〵', + 'の� �の', + 'ニガー? ━━━━━━(゚∀゚)━━━━━━ ニガー?', + 'ヽ(´ー` )ノ', + 'ヽ(๏∀๏ )ノ', + 'ヽ(`Д´)ノ', + 'ヽ(o`皿′o)ノ', + 'ヽ(`Д´)ノ', + 'ㅎ_ㅎ', + '乂◜◬◝乂', + '凸ಠ益ಠ)凸', + '句_句', + 'Ꙩ⌵Ꙩ', + 'Ꙩ_Ꙩ', + 'ꙩ_ꙩ', + 'Ꙫ_Ꙫ', + 'ꙫ_ꙫ', + 'ꙮ_ꙮ', + '흫_흫', + '句_句', + '﴾͡๏̯͡๏﴿', + '¯\(ºдಠ)/¯', + '(·×·)', + '(⌒Д⌒)', + '(╹ェ╹)', + '(♯・∀・)⊃', + '( ´∀`)☆', + '( ´∀`)', + '(゜Д゜)', + '(・∀・)', + '(・A・)', + '(゚∀゚)', + '( ̄へ ̄)', + '( ´☣///_ゝ///☣`)', + '( つ Д `)', + '_☆( ´_⊃`)☆_', + '。◕‿‿◕。', + '。◕ ‿ ◕。', + '!⑈ˆ~ˆ!⑈', + '!(`・ω・。)', + '(¬‿¬)', + '(¬▂¬)', + '(¬_¬)', + '(°ℇ °)', + '(°∀°)', + '(´ω`)', + '(´◉◞౪◟◉)', + '(´ヘ`;)', + '(´・ω・`)', + '(´ー`)', + '(ʘ‿ʘ)', + '(ʘ_ʘ)', + '(˚இ˚)', + '(͡๏̯͡๏)', + '(ΘεΘ;)', + '(ι´Д`)ノ', + '(Ծ‸ Ծ)', + '(॓_॔)', + '(० ्०)', + '(ு८ு_ .:)', + '(ಠ‾ಠ)', + '(ಠ‿ʘ)', + '(ಠ‿ಠ)', + '(ಠ⌣ಠ)', + '(ಠ益ಠ ╬)', + '(ಠ益ಠ)', + '(ಠ_ృ)', + '(ಠ_ಠ)', + '(ಥ﹏ಥ)', + '(ಥ_ಥ)', + '(๏̯͡๏ )', + '(ღ˘⌣˘ღ) ♫・*:.。. .。.:*・', + '(ღ˘⌣˘ღ)', + '(ᵔᴥᵔ)', + '(•ω•)', + '(•‿•)', + '(•⊙ω⊙•)', + '(• ε •)', + '(∩▂∩)', + '(∩︵∩)', + '(∪ ◡ ∪)', + '(≧ω≦)', + '(≧◡≦)', + '(≧ロ≦)', + '(⊙ヮ⊙)', + '(⊙_◎)', + '(⋋▂⋌)', + '(⌐■_■)', + '(─‿‿─)', + '(┛◉Д◉)┛┻━┻', + '(╥_╥)', + '(╬ಠ益ಠ)', + '(╬◣д◢)', + '(╬ ಠ益ಠ)', + '(╯°□°)╯︵ ┻━┻', + '(╯ಊ╰)', + '(╯◕_◕)╯', + '(╯︵╰,)', + '(╯3╰)', + '(╯_╰)', + '(╹◡╹)凸', + '(▰˘◡˘▰)', + '(●´ω`●)', + '(●´∀`●)', + '(◑‿◐)', + '(◑◡◑)', + '(◕‿◕✿)', + '(◕‿◕)', + '(◕‿-)', + '(◕︵◕)', + '(◕ ^ ◕)', + '(◕_◕)', + '(◜௰◝)', + '(◡‿◡✿)', + '(◣_◢)', + '(☞゚∀゚)☞', + '(☞゚ヮ゚)☞', + '(☞゚ ∀゚ )☞', + '(☼◡☼)', + '(☼_☼)', + '(✌゚∀゚)☞', + '(✖╭╮✖)', + '(✪㉨✪)', + '(✿◠‿◠)', + '(✿ ♥‿♥)', + '( ・∀・)', + '( ・ัω・ั)?', + '( ゚∀゚)o彡゜', + '(。・_・。)', + '(つд`)', + '(づ。◕‿‿◕。)づ', + '(ノಠ益ಠ)ノ彡┻━┻', + '(ノ ◑‿◑)ノ', + '(ノ_・。)', + '(・∀・ )', + '(屮゚Д゚)屮', + '(︶ω︶)', + '(︶︹︺)', + '(;一_一)', + '(`・ω・´)”', + '(。◕‿‿◕。)', + '(。◕‿◕。)', + '(。◕ ‿ ◕。)', + '(。♥‿♥。)', + '(。・ω..・)っ', + '(・ェ-)', + '(ノ◕ヮ◕)ノ*:・゚✧', + '(゚Д゚)', + '(゚Д゚)y─┛~~', + '(゚∀゚)', + '(゚ヮ゚)', + '( ̄□ ̄)', + '( ̄。 ̄)', + '( ̄ー ̄)', + '( ̄(エ) ̄)', + '( °٢° )', + '( ´_ゝ`)', + '( ͡° ͜ʖ ͡°)', + '( ͡~ ͜ʖ ͡°)', + '( ಠ◡ಠ )', + '( •_•)>⌐■-■', + '(  ゚,_ゝ゚)', + '( ・ิз・ิ)', + '( ゚д゚)、', + '( ^▽^)σ)~O~)', + '((((゜д゜;))))', + '(*´д`*)', + '(*..Д`)', + '(*..д`*)', + '(*~▽~)', + '(-’๏_๏’-)', + '(-_- )ノ', + '(/◔ ◡ ◔)/', + '(///_ಥ)', + '(;´Д`)', + '(=ω=;)', + '(=゜ω゜)', + '(>\'o\')> ♥ <(\'o\'<)', + '(n˘v˘•)¬', + '(o´ω`o)', + '(V)(°,,°)(V)', + '(\/) (°,,°) (\/)', + '(^▽^)', + '(`・ω・´)', + '(~ ̄▽ ̄)~', + '\= (゚д゚)ウ', + '@_@', + 'd(*⌒▽⌒*)b', + 'o(≧∀≦)o', + 'o(≧o≦)o', + 'q(❂‿❂)p', + 'y=ー( ゚д゚)・∵.', + '\˚ㄥ˚\ ', + '\ᇂ_ᇂ\ ', + '\(ಠ ὡ ಠ )/', + '\(◕ ◡ ◕\)', + '^̮^', + '^ㅂ^', + '_(͡๏̯͡๏)_', + '{´◕ ◡ ◕`}', + '\{ಠ_ಠ\}__,,|,', + '{◕ ◡ ◕}', +] + + +def print_face(data, buf, args): + weechat.command(buf, choice(emojiList)) + return weechat.WEECHAT_RC_OK + + +if __name__ == "__main__" and import_ok: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + weechat.hook_command(SCRIPT_NAME, SCRIPT_DESC, "", "", "", + "print_face", "") From 04e601fe79a8aa804f08439727326881771d480a Mon Sep 17 00:00:00 2001 From: Vlad Stoica Date: Fri, 6 Apr 2018 21:50:58 +0200 Subject: [PATCH 119/642] triggerreply.py 0.3: add support of regex in triggers --- python/triggerreply.py | 195 +++++++++++++++++++++++++++-------------- 1 file changed, 131 insertions(+), 64 deletions(-) diff --git a/python/triggerreply.py b/python/triggerreply.py index 352e40c8..75a539c6 100644 --- a/python/triggerreply.py +++ b/python/triggerreply.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2014 by Vlad Stoica +Copyright (c) 2014-2018 by Vlad Stoica 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 @@ -21,127 +21,194 @@ ability to ignore channels. 16-08-2015 - Vlad Stoica Fixed a bug where replies couldn't have `:' in them. +15-02-2018 - Vlad Stoica +Added regex support in triggers, and edited syntax of adding triggers. +The command is now 'add "trigger" "reply"'. Quote marks can be escaped +in triggers or replies by prefixing them with a backslash ('\'). For +example, 'add "\"picture\"" "say \"cheese\"!"' is a valid command and +will reply with 'say "cheese"!' whenever it finds '"picture"' sent. Bugs: not that i'm aware of. """ -#pylint: disable-msg=too-many-arguments - try: import weechat import sqlite3 - IMPORT_ERR = 0 + import re except ImportError: - IMPORT_ERR = 1 + raise ImportError("Failed importing weechat, sqlite3, re") import os SCRIPT_NAME = "triggerreply" SCRIPT_AUTHOR = "Vlad Stoica " -SCRIPT_VERSION = "0.2" +SCRIPT_VERSION = "0.3" SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Auto replies when someone sends a specified trigger." +SCRIPT_DESC = "Auto replies when someone sends a specified trigger. Now with 100% more regex!" + -def phelp(): +def print_help(): """ print the help message """ - weechat.prnt("", "Triggerreply (trigge.rs) plugin. Automatically \ -replies over specified triggers") - weechat.prnt("", "------------") - weechat.prnt("", "Usage: /triggerreply [list | add trigger:reply \ -| remove trigger | ignore server.#channel | parse server.#channel]") + weechat.prnt("", """ +Triggerreply (trigge.rs) plugin. Automatically replies over specified triggers. +------------ +Usage: /triggerreply [list | add | remove | ignore | parse] ARGUMENTS + +Commands: + list - lists the triggers with replies, and ignored channels + add - two arguments: "trigger" and "reply" + - adds a trigger with the specified reply + - if '-r' is specified, then the trigger will be parsed as regular expression + remove - one argument: "trigger" + - remove a trigger + ignore - one argument: "server.#channel" + - ignores a particular channel from a server + parse - one argument: "server.#channel" + - removes a channel from ignored list + +Examples: + /triggerreply add "^[Hh](i|ello|ey)[ .!]*" "Hey there!" + /triggerreply add "lol" "not funny tho" + /triggerreply remove lol + /triggerreply ignore rizon.#help + /triggerreply parse rizon.#help +""") -def create_db(): + +def create_db(delete=False): """ create the sqlite database """ - tmpcon = sqlite3.connect(DBFILE) - cur = tmpcon.cursor() + if delete: + os.remove(db_file) + temp_con = sqlite3.connect(db_file) + cur = temp_con.cursor() cur.execute("CREATE TABLE triggers(id INTEGER PRIMARY KEY, trig VARCHAR, reply VARCHAR);") cur.execute("INSERT INTO triggers(trig, reply) VALUES ('trigge.rs', 'Automatic reply');") cur.execute("CREATE TABLE banchans(id INTEGER PRIMARY KEY, ignored VARCHAR);") cur.execute("INSERT INTO banchans(ignored) VALUES ('rizon.#help');") - tmpcon.commit() + temp_con.commit() cur.close() -def search_trig_cb(data, buffer, date, tags, displayed, highlight, prefix, message): + +def search_trig_cb(data, buf, date, tags, displayed, highlight, prefix, message): """ function for parsing sent messages """ - database = sqlite3.connect(DBFILE) + if weechat.buffer_get_string(buf, "name") == 'weechat': + return weechat.WEECHAT_RC_OK + + database = sqlite3.connect(db_file) database.text_factory = str cursor = database.cursor() - ignored_chan = False + for row in cursor.execute("SELECT ignored from banchans;"): - if weechat.buffer_get_string(buffer, "name") == row[0]: - ignored_chan = True - if not ignored_chan: - for row in cursor.execute("SELECT reply FROM triggers WHERE trig = ?", (str(message),)): - weechat.command(buffer, "/say %s" % row) + if weechat.buffer_get_string(buf, "name") == row[0]: + return weechat.WEECHAT_RC_OK + + for row in cursor.execute("SELECT * FROM triggers"): + try: + r = re.compile(row[1]) + if r.search(message) is not None: + weechat.command(buf, "/say %s" % row[2]) + except: + if row[1] == message: + weechat.command(buf, "/say %s" % row[2]) + return weechat.WEECHAT_RC_OK + def command_input_callback(data, buffer, argv): """ function called when `/triggerreply args' is run """ - database = sqlite3.connect(DBFILE) + database = sqlite3.connect(db_file) cursor = database.cursor() command = argv.split() + if len(command) == 0: - phelp() - elif command[0] == "list": + print_help() + return weechat.WEECHAT_RC_ERROR + + if command[0] == "list": weechat.prnt("", "List of triggers with replies:") for row in cursor.execute("SELECT * FROM triggers;"): weechat.prnt("", str(row[0]) + ". " + row[1] + " -> " + row[2]) + weechat.prnt("", "\nList of ignored channels:") for row in cursor.execute("SELECT ignored FROM banchans;"): weechat.prnt("", row[0]) elif command[0] == "add": + if len(argv) == len(command[0]): + print_help() + return weechat.WEECHAT_RC_ERROR + + if argv.count('"') < 4: + print_help() + return weechat.WEECHAT_RC_ERROR + + pos = [] + for k, v in enumerate(argv): + if v == '"' and argv[k - 1] != '\\': + pos.append(k) + + if len(pos) != 4: + print_help() + return weechat.WEECHAT_RC_ERROR + + trigger = argv[pos[0] + 1:pos[1]].replace('\\"', '"') + reply = argv[pos[2] + 1:pos[3]].replace('\\"', '"') + try: - trigger = argv[4:].split(":")[0] - #reply = ''.join(argv[4:].split(":")[1:]) - reply = argv[4:].replace(trigger+":", '') cursor.execute("INSERT INTO triggers(trig, reply) VALUES (?,?)", (trigger, reply,)) except: - weechat.prnt("", "Could not add trigger.\n") - weechat.prnt("", "Usage: /triggerreply add trigger:reply") - weechat.prnt("", "Example: /triggerreply add lol:hue hue") - else: - database.commit() - weechat.prnt("", "Trigger added successfully!") + print_help() + return weechat.WEECHAT_RC_ERROR + + database.commit() + weechat.prnt("", "Trigger added successfully!") elif command[0] == "remove": + if len(argv) == len(command[0]): + print_help() + return weechat.WEECHAT_RC_ERROR + try: cursor.execute("DELETE FROM triggers WHERE trig = ?", (argv[7:],)) except: - weechat.prnt("", "Could not remove trigger.") - weechat.prnt("", "Usage: /triggerreply remove trigger") - weechat.prnt("", "Example: /triggerreply remove hue") - else: - database.commit() - weechat.prnt("", "Trigger successfully removed.") + print_help() + return weechat.WEECHAT_RC_ERROR + + database.commit() + weechat.prnt("", "Trigger successfully removed.") elif command[0] == "ignore": + if len(argv) == len(command[0]): + print_help() + return weechat.WEECHAT_RC_ERROR + try: cursor.execute("INSERT INTO banchans(ignored) VALUES (?)", (command[1],)) except: - weechat.prnt("", "Could not add channel to ignored list.") - weechat.prnt("", "Usage: /triggerreply ignore server.#channel") - weechat.prnt("", "Example: /triggerreply ignore freenode.#mychan") - else: - database.commit() - weechat.prnt("", "Channel successfully added to ignore list!") + print_help() + return weechat.WEECHAT_RC_ERROR + + database.commit() + weechat.prnt("", "Channel successfully added to ignore list!") elif command[0] == "parse": + if len(argv) == len(command[0]): + print_help() + return weechat.WEECHAT_RC_ERROR + try: cursor.execute("DELETE FROM banchans WHERE ignored = ?", (command[1],)) except: - weechat.prnt("", "Could not remove channel from ignored.") - weechat.prnt("", "Usage: /triggerreply parse server.#channel") - weechat.prnt("", "Example: /triggerreply parse freenode.#mychan") - else: - database.commit() - weechat.prnt("", "Channel successfully removed from ignored.") + print_help() + return weechat.WEECHAT_RC_ERROR + + database.commit() + weechat.prnt("", "Channel successfully removed from ignored.") + return weechat.WEECHAT_RC_OK -if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, - "", ""): - if IMPORT_ERR: - weechat.prnt("", "You need sqlite3 to run this plugin.") - DBFILE = "%s/trigge.rs" % weechat.info_get("weechat_dir", "") - if not os.path.isfile(DBFILE): - create_db() +if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + db_file = "%s/trigge.rs" % weechat.info_get("weechat_dir", "") + + if not os.path.isfile(db_file): + create_db() weechat.hook_print("", "", "", 1, "search_trig_cb", "") - weechat.hook_command(SCRIPT_NAME, SCRIPT_DESC, "See `/triggerreply' for more information.", "", - "", "command_input_callback", "") + weechat.hook_command(SCRIPT_NAME, SCRIPT_DESC, "See `/triggerreply' for more information.", "", "", + "command_input_callback", "") From ffda2e03486d1e90983a35ee3505b84d10fd5244 Mon Sep 17 00:00:00 2001 From: Joey Pabalinas Date: Sat, 7 Apr 2018 07:51:16 +0200 Subject: [PATCH 120/642] colorize_nicks.py 26: fix freezes with too many nicks in one line (closes #197) With `greedy_matching` enabled, too many nicks on one line completely locks up weechat. Add a `match_limit` configuration variable (defaults to 20) which is used as a hard limit for number of greedy matches allowed, at which point the plugin will fall back to the lazy matching algorithm. Fixes issue #197. Signed-off-by: Joey Pabalinas --- python/colorize_nicks.py | 80 ++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/python/colorize_nicks.py b/python/colorize_nicks.py index fc53be31..d0cdc0ea 100644 --- a/python/colorize_nicks.py +++ b/python/colorize_nicks.py @@ -21,6 +21,8 @@ # # # History: +# 2018-04-06: Joey Pabalinas +# version 26: fix freezes with too many nicks in one line # 2018-03-18: nils_2 # version 25: fix unable to run function colorize_config_reload_cb() # 2017-06-20: lbeziaud @@ -83,7 +85,7 @@ SCRIPT_NAME = "colorize_nicks" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "25" +SCRIPT_VERSION = "26" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Use the weechat nick colors in the chat area" @@ -144,6 +146,10 @@ def colorize_config_init(): colorize_config_file, section_look, "greedy_matching", "boolean", "If off, then use lazy matching instead", "", 0, 0, "on", "on", 0, "", "", "", "", "", "") + colorize_config_option["match_limit"] = weechat.config_new_option( + colorize_config_file, section_look, "match_limit", + "integer", "Fall back to lazy matching if greedy matches exceeds this number", "", + 20, 1000, "", "", 0, "", "", "", "", "", "") colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option( colorize_config_file, section_look, "ignore_nicks_in_urls", "boolean", "If on, don't colorize nicks inside URLs", "", 0, @@ -214,33 +220,50 @@ def colorize_cb(data, modifier, modifier_data, line): if nick in colored_nicks[buffer]: nick_color = colored_nicks[buffer][nick] - # Let's use greedy matching. Will check against every word in a line. - if w.config_boolean(colorize_config_option['greedy_matching']): - for word in line.split(): - if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \ - word.startswith(('http://', 'https://')): - continue - - if nick in word: - # Is there a nick that contains nick and has a greater lenght? - # If so let's save that nick into var biggest_nick - biggest_nick = "" - for i in colored_nicks[buffer]: - if nick in i and nick != i and len(i) > len(nick): - if i in word: - # If a nick with greater len is found, and that word - # also happens to be in word, then let's save this nick - biggest_nick = i - # If there's a nick with greater len, then let's skip this - # As we will have the chance to colorize when biggest_nick - # iterates being nick. - if len(biggest_nick) > 0 and biggest_nick in word: - pass - elif len(word) < len(biggest_nick) or len(biggest_nick) == 0: - new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset)) - line = line.replace(word, new_word) - # Let's use lazy matching for nick - else: + try: + # Let's use greedy matching. Will check against every word in a line. + if w.config_boolean(colorize_config_option['greedy_matching']): + cnt = 0 + limit = w.config_integer(colorize_config_option['match_limit']) + + for word in line.split(): + cnt += 1 + assert cnt < limit + # if cnt > limit: + # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.'); + + if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \ + word.startswith(('http://', 'https://')): + continue + + if nick in word: + # Is there a nick that contains nick and has a greater lenght? + # If so let's save that nick into var biggest_nick + biggest_nick = "" + for i in colored_nicks[buffer]: + cnt += 1 + assert cnt < limit + + if nick in i and nick != i and len(i) > len(nick): + if i in word: + # If a nick with greater len is found, and that word + # also happens to be in word, then let's save this nick + biggest_nick = i + # If there's a nick with greater len, then let's skip this + # As we will have the chance to colorize when biggest_nick + # iterates being nick. + if len(biggest_nick) > 0 and biggest_nick in word: + pass + elif len(word) < len(biggest_nick) or len(biggest_nick) == 0: + new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset)) + line = line.replace(word, new_word) + + # Switch to lazy matching + else: + raise AssertionError + + except AssertionError: + # Let's use lazy matching for nick nick_color = colored_nicks[buffer][nick] # The two .? are in case somebody writes "nick:", "nick,", etc # to address somebody @@ -249,6 +272,7 @@ def colorize_cb(data, modifier, modifier_data, line): if match is not None: new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):] line = new_line + return line def colorize_input_cb(data, modifier, modifier_data, line): From f299d65c3805587808e160d3067eee3ed59bf5ca Mon Sep 17 00:00:00 2001 From: Tony763 Date: Sat, 7 Apr 2018 08:07:25 +0200 Subject: [PATCH 121/642] mnotify.py 0.4: fix bugs, add new features Fix: Changed dcc regex to match new notify appears (weechat notify now contain IP) - only ddc get request worked Fix: Dcc send info about file even with spaces in filename. Feature: DCC get request show name with ip, network and size of file. Feature: Added dcc send offer and dcc send start notify Feature: Setting for notify is divided to off (don't send), on (always send), away (send only when away). Changed default settings to match new scheme Feature: Added small help - I get hard time to find out why no message is send All regex checked on https://pythex.org/ --- python/mnotify.py | 214 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 164 insertions(+), 50 deletions(-) diff --git a/python/mnotify.py b/python/mnotify.py index 692c77d0..e09580a8 100644 --- a/python/mnotify.py +++ b/python/mnotify.py @@ -10,7 +10,25 @@ # based on: # growl.py # Copyright (c) 2011 Sorin Ionescu - +# +# Changelog: +# Ver: 0.4 by Antonin Skala tony762@gmx.com 3.2018 +# +# Changed dcc regex to match new notify appears (weechat notify now contain IP) +# Added dcc send offer and dcc send start notify +# Setting for notify is divided to off (don't send), on (always send), +# away (send only when away). +# Changed default settings to match new scheme +# DCC get request show name with ip, network and size of file. +# +# Help: +# Install and configure msmtp first (msmtp.sourceforge.net/) +# List and Change plugin settings by /set plugins.var.python.mnotify.* +# Change language to english -otherwise this will not work +# /set env LANG en_US.UTF-8 +# /save +# /upgrade +# # ----------------------------------------------------------------------------- # Imports @@ -25,7 +43,7 @@ SCRIPT_NAME = 'mnotify' SCRIPT_AUTHOR = 'maker' -SCRIPT_VERSION = '0.3' +SCRIPT_VERSION = '0.4' SCRIPT_LICENSE = 'Beerware License' SCRIPT_DESC = 'Sends mail notifications upon events.' @@ -34,21 +52,19 @@ # ----------------------------------------------------------------------------- SETTINGS = { 'show_public_message': 'off', - 'show_private_message': 'on', + 'show_private_message': 'away', 'show_public_action_message': 'off', - 'show_private_action_message': 'on', + 'show_private_action_message': 'away', 'show_notice_message': 'off', - 'show_invite_message': 'on', - 'show_highlighted_message': 'on', - 'show_server': 'on', - 'show_channel_topic': 'on', + 'show_invite_message': 'away', + 'show_highlighted_message': 'off', + 'show_server': 'away', + 'show_channel_topic': 'off', 'show_dcc': 'on', - 'show_upgrade_ended': 'on', - 'sticky': 'off', - 'sticky_away': 'on', - 'sendmail': 'msmtp', - 'email_to': '', - 'email_from': 'irc ' + 'show_upgrade_ended': 'off', + 'sendmail': '/usr/bin/msmtp', + 'email_to': 'somebody@somwhere.xx', + 'email_from': 'irc@somwhere.xx' } @@ -70,23 +86,27 @@ 'away status': re.compile(r'^You ((\w+).){2,3}marked as being away', re.UNICODE), 'dcc chat request': - re.compile(r'^xfer: incoming chat request from (\w+)', re.UNICODE), + re.compile(r'^xfer: incoming chat request from ([^\s]+) ', re.UNICODE), 'dcc chat closed': - re.compile(r'^xfer: chat closed with (\w+)', re.UNICODE), + re.compile(r'^xfer: chat closed with ([^\s]+) \(', re.UNICODE), 'dcc get request': re.compile( - r'^xfer: incoming file from (\w+) [^:]+: ((?:,\w|[^,])+),', + r'^xfer: incoming file from (^\s|.+), name: ((?:,\w|[^,])+), (\d+) bytes', re.UNICODE), 'dcc get completed': - re.compile(r'^xfer: file ([^\s]+) received from \w+: OK', re.UNICODE), + re.compile(r'^xfer: file ((?:,\w|[^,])+) received from ([^\s]+) ((?:,\w|[^,]+)): OK$', re.UNICODE), 'dcc get failed': re.compile( - r'^xfer: file ([^\s]+) received from \w+: FAILED', + r'^xfer: file ((?:,\w|[^,])+) received from ([^\s]+) ((?:,\w|[^,]+)): FAILED$', re.UNICODE), + 'dcc send offer': + re.compile(r'^xfer: offering file to ([^\s]+) ((?:,\w|[^,])+), name: ((?:,\w|[^,])+) \(local', re.UNICODE), + 'dcc send start': + re.compile(r'^xfer: sending file to ([^\s]+) ((?:,\w|.)+), name: ((?:,\w|[^,])+) \(local', re.UNICODE), 'dcc send completed': - re.compile(r'^xfer: file ([^\s]+) sent to \w+: OK', re.UNICODE), + re.compile(r'^xfer: file ((?:,\w|[^,])+) sent to ([^\s]+) ((?:,\w|[^,]+)): OK$', re.UNICODE), 'dcc send failed': - re.compile(r'^xfer: file ([^\s]+) sent to \w+: FAILED', re.UNICODE), + re.compile(r'^xfer: file ((?:,\w|[^,])+) sent to ([^\s]+) ((?:,\w|[^,]+)): FAILED$', re.UNICODE), } @@ -102,6 +122,8 @@ 'dcc get request': 'notify_dcc_get_request', 'dcc get completed': 'notify_dcc_get_completed', 'dcc get failed': 'notify_dcc_get_failed', + 'dcc send offer': 'notify_dcc_send_offer', + 'dcc send start': 'notify_dcc_send_start', 'dcc send completed': 'notify_dcc_send_completed', 'dcc send failed': 'notify_dcc_send_failed', } @@ -118,7 +140,9 @@ # ----------------------------------------------------------------------------- def cb_irc_server_connected(data, signal, signal_data): '''Notify when connected to IRC server.''' - if weechat.config_get_plugin('show_server') == 'on': + if (weechat.config_get_plugin('show_server') == 'on' + or (weechat.config_get_plugin('show_server') == "away" + and STATE['is_away'])): a_notify( 'Server', 'Server Connected', @@ -128,7 +152,9 @@ def cb_irc_server_connected(data, signal, signal_data): def cb_irc_server_disconnected(data, signal, signal_data): '''Notify when disconnected to IRC server.''' - if weechat.config_get_plugin('show_server') == 'on': + if (weechat.config_get_plugin('show_server') == 'on' + or (weechat.config_get_plugin('show_server') == "away" + and STATE['is_away'])): a_notify( 'Server', 'Server Disconnected', @@ -138,7 +164,9 @@ def cb_irc_server_disconnected(data, signal, signal_data): def cb_notify_upgrade_ended(data, signal, signal_data): '''Notify on end of WeeChat upgrade.''' - if weechat.config_get_plugin('show_upgrade_ended') == 'on': + if (weechat.config_get_plugin('show_upgrade_ended') == 'on' + or (weechat.config_get_plugin('show_upgrade_ended') == "away" + and STATE['is_away'])): a_notify( 'WeeChat', 'WeeChat Upgraded', @@ -148,7 +176,9 @@ def cb_notify_upgrade_ended(data, signal, signal_data): def notify_highlighted_message(buffername, prefix, message): '''Notify on highlighted message.''' - if weechat.config_get_plugin("show_highlighted_message") == "on": + if (weechat.config_get_plugin("show_highlighted_message") == "on" + or (weechat.config_get_plugin("show_highlighted_message") == "away" + and STATE['is_away'])): a_notify( 'Highlight', 'Highlighted on {0} by {1}'.format(buffername, prefix), @@ -169,7 +199,9 @@ def notify_public_message_or_action(buffername, prefix, message, highlighted): else: if highlighted: notify_highlighted_message(buffername, prefix, message) - elif weechat.config_get_plugin("show_public_message") == "on": + elif (weechat.config_get_plugin("show_public_message") == "on" + or (weechat.config_get_plugin("show_public_message") == "away" + and STATE['is_away'])): a_notify( 'Public', 'Public Message on {0}'.format(buffername), @@ -195,7 +227,9 @@ def notify_private_message_or_action(buffername, prefix, message, highlighted): else: if highlighted: notify_highlighted_message(buffername, prefix, message) - elif weechat.config_get_plugin("show_private_message") == "on": + elif (weechat.config_get_plugin("show_private_message") == "on" + or (weechat.config_get_plugin("show_private_message") == "away" + and STATE['is_away'])): a_notify( 'Private', 'Private Message', @@ -206,7 +240,9 @@ def notify_public_action_message(buffername, prefix, message, highlighted): '''Notify on public action message.''' if highlighted: notify_highlighted_message(buffername, prefix, message) - elif weechat.config_get_plugin("show_public_action_message") == "on": + elif (weechat.config_get_plugin("show_public_action_message") == "on" + or (weechat.config_get_plugin("show_public_action_message") == "away" + and STATE['is_away'])): a_notify( 'Action', 'Public Action Message on {0}'.format(buffername), @@ -218,13 +254,16 @@ def notify_private_action_message(buffername, prefix, message, highlighted): '''Notify on private action message.''' if highlighted: notify_highlighted_message(buffername, prefix, message) - elif weechat.config_get_plugin("show_private_action_message") == "on": + elif (weechat.config_get_plugin("show_private_action_message") == "on" + or (weechat.config_get_plugin("show_private_action_message") == "away" + and STATE['is_away'])): a_notify( 'Action', 'Private Action Message', '{0}: {1}'.format(prefix, message), ) + def notify_notice_message(buffername, prefix, message, highlighted): '''Notify on notice message.''' regex = re.compile(r'^([^\s]*) [^:]*: (.+)$', re.UNICODE) @@ -234,7 +273,9 @@ def notify_notice_message(buffername, prefix, message, highlighted): message = match.group(2) if highlighted: notify_highlighted_message(buffername, prefix, message) - elif weechat.config_get_plugin("show_notice_message") == "on": + elif (weechat.config_get_plugin("show_notice_message") == "on" + or (weechat.config_get_plugin("show_notice_message") == "away" + and STATE['is_away'])): a_notify( 'Notice', 'Notice Message', @@ -243,7 +284,9 @@ def notify_notice_message(buffername, prefix, message, highlighted): def notify_invite_message(buffername, prefix, message, highlighted): '''Notify on channel invitation message.''' - if weechat.config_get_plugin("show_invite_message") == "on": + if (weechat.config_get_plugin("show_invite_message") == "on" + or (weechat.config_get_plugin("show_invite_message") == "away" + and STATE['is_away'])): regex = re.compile( r'^You have been invited to ([^\s]+) by ([^\s]+)$', re.UNICODE) match = regex.match(message) @@ -258,10 +301,12 @@ def notify_invite_message(buffername, prefix, message, highlighted): def notify_channel_topic(buffername, prefix, message, highlighted): '''Notify on channel topic change.''' - if weechat.config_get_plugin("show_channel_topic") == "on": + if (weechat.config_get_plugin("show_channel_topic") == "on" + or (weechat.config_get_plugin("show_channel_topic") == "away" + and STATE['is_away'])): regex = re.compile( r'^\w+ has (?:changed|unset) topic for ([^\s]+)' + - '(?:(?: from "(?:.+)")? to "(.+)")?', + r'(?:(?: from "(?:.+)")? to "(.+)")?', re.UNICODE) match = regex.match(message) if match: @@ -275,7 +320,9 @@ def notify_channel_topic(buffername, prefix, message, highlighted): def notify_dcc_chat_request(match): '''Notify on DCC chat request.''' - if weechat.config_get_plugin("show_dcc") == "on": + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): nick = match.group(1) a_notify( 'DCC', @@ -285,7 +332,9 @@ def notify_dcc_chat_request(match): def notify_dcc_chat_closed(match): '''Notify on DCC chat termination.''' - if weechat.config_get_plugin("show_dcc") == "on": + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): nick = match.group(1) a_notify( 'DCC', @@ -295,41 +344,93 @@ def notify_dcc_chat_closed(match): def notify_dcc_get_request(match): 'Notify on DCC get request.' - if weechat.config_get_plugin("show_dcc") == "on": + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): nick = match.group(1) file_name = match.group(2) + file_size = int(match.group(3)) a_notify( 'DCC', 'File Transfer Request', - '{0} wants to send you {1}.'.format(nick, file_name)) + '{0} wants to send you {1} and size is {2}.'.format(nick, file_name, humanbytes(file_size))) def notify_dcc_get_completed(match): 'Notify on DCC get completion.' - if weechat.config_get_plugin("show_dcc") == "on": + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): + nick = match.group(2) file_name = match.group(1) - a_notify('DCC', 'Download Complete', file_name) + a_notify( + 'DCC', + 'Download Complete', + 'Downloading {1} from {0} completed.'.format(nick, file_name)) def notify_dcc_get_failed(match): 'Notify on DCC get failure.' - if weechat.config_get_plugin("show_dcc") == "on": + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): file_name = match.group(1) - a_notify('DCC', 'Download Failed', file_name) + a_notify( + 'DCC', + 'Download Failed', + 'Downloading {1} from {0} failed.'.format(nick, file_name)) + + +def notify_dcc_send_offer(match): + 'Notify on DCC send offer.' + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): + nick = match.group(1) + file_name = match.group(3) + a_notify( + 'DCC', + 'Offering File Upload', + 'Offering {1} to {0}.'.format(nick, file_name)) + + +def notify_dcc_send_start(match): + 'Notify on DCC send start.' + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): + nick = match.group(1) + file_name = match.group(3) + a_notify( + 'DCC', + 'Start File Upload', + 'Uploading {1} to {0}.'.format(nick, file_name)) def notify_dcc_send_completed(match): 'Notify on DCC send completion.' - if weechat.config_get_plugin("show_dcc") == "on": + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): + nick = match.group(2) file_name = match.group(1) - a_notify('DCC', 'Upload Complete', file_name) + a_notify( + 'DCC', + 'Upload Complete', + 'Upload {1} to {0} completed.'.format(nick, file_name)) def notify_dcc_send_failed(match): 'Notify on DCC send failure.' - if weechat.config_get_plugin("show_dcc") == "on": + if (weechat.config_get_plugin("show_dcc") == "on" + or (weechat.config_get_plugin("show_dcc") == "away" + and STATE['is_away'])): + nick = match.group(2) file_name = match.group(1) - a_notify('DCC', 'Upload Failed', file_name) + a_notify( + 'DCC', + 'Upload Failed', + 'Upload {1} to {0} failed.'.format(nick, file_name)) # ----------------------------------------------------------------------------- @@ -384,12 +485,25 @@ def cb_process_message( return weechat.WEECHAT_RC_OK -def a_notify(notification, subject, message): - if STATE['is_away'] and weechat.config_get_plugin('sticky_away') == 'off': - return - if not STATE['is_away'] and weechat.config_get_plugin('sticky') == 'off': - return +def humanbytes(B): + B = float(B) + KB = float(1024) + MB = float(KB ** 2) # 1,048,576 + GB = float(KB ** 3) # 1,073,741,824 + TB = float(KB ** 4) # 1,099,511,627,776 + if B < KB: + return '{0} {1}'.format(B,'Bytes' if 0 == B > 1 else 'Byte') + elif KB <= B < MB: + return '{0:.2f} KB'.format(B/KB) + elif MB <= B < GB: + return '{0:.2f} MB'.format(B/MB) + elif GB <= B < TB: + return '{0:.2f} GB'.format(B/GB) + elif TB <= B: + return '{0:.2f} TB'.format(B/TB) + +def a_notify(notification, subject, message): msg = MIMEText(message) msg['From'] = weechat.config_get_plugin('email_from') msg['To'] = weechat.config_get_plugin('email_to') From 8a546673fc57e4dcd21b0bb936bb3679af3d3698 Mon Sep 17 00:00:00 2001 From: Dominik Heidler Date: Sat, 7 Apr 2018 08:17:39 +0200 Subject: [PATCH 122/642] urlgrab.py 2.9: add support of Python 3 --- python/urlgrab.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/python/urlgrab.py b/python/urlgrab.py index b4dc8e6b..8fdaa3a5 100644 --- a/python/urlgrab.py +++ b/python/urlgrab.py @@ -120,6 +120,8 @@ # for '/url copy' # - V2.8 Simmo Saan : # - Changed print hook to ignore filtered lines +# - V2.9 Dominik Heidler : +# - Updated script for python3 support (now python2 and 3 are both supported) # # Copyright (C) 2005 David Rubin # @@ -139,20 +141,27 @@ # USA. # +from __future__ import print_function import sys import os try: import weechat import_ok = True except: - print "This script must be run under WeeChat." - print "Get WeeChat now at: http://www.weechat.org/" + print("This script must be run under WeeChat.") + print("Get WeeChat now at: http://www.weechat.org/") import_ok = False import subprocess import time -import urllib +try: + from urllib import quote +except ImportError: + from urllib.parse import quote import re -from UserDict import UserDict +try: + from UserDict import UserDict +except ImportError: + from collections import UserDict octet = r'(?:2(?:[0-4]\d|5[0-5])|1\d\d|\d{1,2})' @@ -165,7 +174,7 @@ SCRIPT_NAME = "urlgrab" SCRIPT_AUTHOR = "David Rubin " -SCRIPT_VERSION = "2.8" +SCRIPT_VERSION = "2.9" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Url functionality Loggin, opening of browser, selectable links" CONFIG_FILE_NAME= "urlgrab" @@ -358,7 +367,7 @@ def addUrl(self, bufferp,url ): index['buffer'], index['url'])) dout.close() except : - print "failed to log url check that %s is valid path" % urlGrabSettings['url_log'] + print("failed to log url check that %s is valid path" % urlGrabSettings['url_log']) pass # check for buffer @@ -452,7 +461,7 @@ def urlGrabCopy(bufferd, index): def urlGrabOpenUrl(url): global urlGrab, urlGrabSettings - argl = urlGrabSettings.createCmd( urllib.quote(url, '/:#%?&+=') ) + argl = urlGrabSettings.createCmd( quote(url, '/:#%?&+=') ) weechat.hook_process(argl,60000, "ug_open_cb", "") def ug_open_cb(data, command, code, out, err): @@ -684,4 +693,4 @@ def ug_unload_script(): weechat.hook_completion("urlgrab_urls", "list of URLs", "completion_urls_cb", "") else: - print "failed to load weechat" + print("failed to load weechat") From 9beb6a35af369da9ea48f68933297edabf0a8051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Tue, 10 Apr 2018 21:31:17 +0200 Subject: [PATCH 123/642] buffer_autoclose.py 0.5: fix infolist_time with WeeChat >= 2.2 --- python/buffer_autoclose.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/python/buffer_autoclose.py b/python/buffer_autoclose.py index 303f5b37..2dfefd93 100644 --- a/python/buffer_autoclose.py +++ b/python/buffer_autoclose.py @@ -20,6 +20,9 @@ # (this script requires WeeChat 0.3.0 or newer) # # History: +# 2018-04-10, Sébastien Helleu +# version 0.5: fix infolist_time for WeeChat >= 2.2 (WeeChat returns a long +# integer instead of a string) # 2016-02-05, ixti # version 0.4: Add Python3 support # 2009-12-15, xt @@ -34,7 +37,7 @@ SCRIPT_NAME = "buffer_autoclose" SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "0.4" +SCRIPT_VERSION = "0.5" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Automatically close inactive private message buffers" @@ -73,6 +76,10 @@ def get_last_line_date(buffer): infolist = w.infolist_get('buffer_lines', buffer, '') while w.infolist_prev(infolist): date = w.infolist_time(infolist, 'date') + # since WeeChat 2.2, infolist_time returns a long integer instead of + # a string + if not isinstance(date, str): + date = time.strftime('%F %T', time.localtime(int(date))) if date != '1970-01-01 01:00:00': # Some lines like "Day changed to" message doesn't have date # set so loop until we find a message that does From 7284f24fe4cfeb23ea10c6ea3747c29fa0677350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Tue, 10 Apr 2018 21:33:06 +0200 Subject: [PATCH 124/642] cmd_help.py 0.5: fix infolist_time with WeeChat >= 2.2 --- python/cmd_help.py | 273 ++++++++++++++++++++++++++++++++------------- 1 file changed, 194 insertions(+), 79 deletions(-) diff --git a/python/cmd_help.py b/python/cmd_help.py index b0de8d9a..679a05cd 100644 --- a/python/cmd_help.py +++ b/python/cmd_help.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2012 Sebastien Helleu +# Copyright (C) 2011-2018 Sébastien Helleu # Copyright (C) 2012 ArZa # # This program is free software; you can redistribute it and/or modify @@ -23,22 +23,25 @@ # # History: # +# 2018-04-10, Sébastien Helleu : +# version 0.5: fix infolist_time for WeeChat >= 2.2 (WeeChat returns a long +# integer instead of a string), fix PEP8 errors # 2012-01-04, ArZa : # version 0.4: settings for right align and space before help -# 2012-01-03, Sebastien Helleu : +# 2012-01-03, Sébastien Helleu : # version 0.3: make script compatible with Python 3.x -# 2011-05-18, Sebastien Helleu : +# 2011-05-18, Sébastien Helleu : # version 0.2: add options for aliases, start on load, list of commands to # ignore; add default value in help of script options -# 2011-05-15, Sebastien Helleu : +# 2011-05-15, Sébastien Helleu : # version 0.1: initial release # -SCRIPT_NAME = 'cmd_help' -SCRIPT_AUTHOR = 'Sebastien Helleu ' -SCRIPT_VERSION = '0.4' +SCRIPT_NAME = 'cmd_help' +SCRIPT_AUTHOR = 'Sébastien Helleu ' +SCRIPT_VERSION = '0.5' SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Contextual command line help' +SCRIPT_DESC = 'Contextual command line help' SCRIPT_COMMAND = 'cmd_help' @@ -53,43 +56,116 @@ try: import re + import time except ImportError as message: print('Missing package(s) for %s: %s' % (SCRIPT_NAME, message)) import_ok = False -cmdhelp_hooks = { 'modifier' : '', - 'timer' : '', - 'command_run': '' } +cmdhelp_hooks = { + 'modifier': '', + 'timer': '', + 'command_run': '', +} cmdhelp_option_infolist = '' cmdhelp_option_infolist_fields = {} # script options cmdhelp_settings_default = { - 'display_no_help' : ['on', 'display "No help" when command is not found'], - 'start_on_load' : ['off', 'auto start help when script is loaded'], - 'stop_on_enter' : ['on', 'enter key stop help'], - 'timer' : ['0', 'number of seconds help is displayed (0 = display until help is toggled)'], - 'prefix' : ['[', 'string displayed before help'], - 'suffix' : [']', 'string displayed after help'], - 'format_option' : ['(${white:type}) ${description_nls}', 'format of help for options: free text with identifiers using format: ${name} or ${color:name}: color is a WeeChat color (optional), name is a field of infolist "option"'], - 'max_options' : ['5', 'max number of options displayed in list'], - 'ignore_commands' : ['map,me,die,restart', 'comma-separated list of commands (without leading "/") to ignore'], - 'color_alias' : ['white', 'color for text "Alias"'], - 'color_alias_name' : ['green', 'color for alias name'], - 'color_alias_value': ['green', 'color for alias value'], - 'color_delimiters' : ['lightgreen', 'color for delimiters'], - 'color_no_help' : ['red', 'color for text "No help"'], - 'color_list_count' : ['white', 'color for number of commands/options in list found'], - 'color_list' : ['green', 'color for list of commands/options'], - 'color_arguments' : ['cyan', 'color for command arguments'], - 'color_option_name': ['yellow', 'color for name of option found (by adding "*" to option name)'], - 'color_option_help': ['brown', 'color for help on option'], - 'right_align' : ['off', 'align help to right'], - 'right_padding' : ['15', 'padding to right when aligned to right'], - 'space' : ['2', 'minimum space before help'], + 'display_no_help': [ + 'on', + 'display "No help" when command is not found', + ], + 'start_on_load': [ + 'off', + 'auto start help when script is loaded', + ], + 'stop_on_enter': [ + 'on', + 'enter key stop help', + ], + 'timer': [ + '0', + ('number of seconds help is displayed (0 = display until help is ' + 'toggled)'), + ], + 'prefix': [ + '[', + 'string displayed before help', + ], + 'suffix': [ + ']', + 'string displayed after help', + ], + 'format_option': [ + '(${white:type}) ${description_nls}', + ('format of help for options: free text with identifiers using ' + 'format: ${name} or ${color:name}: color is a WeeChat ' + 'color (optional), name is a field of infolist "option"'), + ], + 'max_options': [ + '5', + 'max number of options displayed in list', + ], + 'ignore_commands': [ + 'map,me,die,restart', + 'comma-separated list of commands (without leading "/") to ignore', + ], + 'color_alias': [ + 'white', + 'color for text "Alias"', + ], + 'color_alias_name': [ + 'green', + 'color for alias name', + ], + 'color_alias_value': [ + 'green', + 'color for alias value', + ], + 'color_delimiters': [ + 'lightgreen', + 'color for delimiters', + ], + 'color_no_help': [ + 'red', + 'color for text "No help"', + ], + 'color_list_count': [ + 'white', + 'color for number of commands/options in list found', + ], + 'color_list': [ + 'green', + 'color for list of commands/options', + ], + 'color_arguments': [ + 'cyan', + 'color for command arguments', + ], + 'color_option_name': [ + 'yellow', + 'color for name of option found (by adding "*" to option name)', + ], + 'color_option_help': [ + 'brown', + 'color for help on option', + ], + 'right_align': [ + 'off', + 'align help to right', + ], + 'right_padding': [ + '15', + 'padding to right when aligned to right', + ], + 'space': [ + '2', + 'minimum space before help', + ], } cmdhelp_settings = {} + def unhook(hooks): """Unhook something hooked by this script.""" global cmdhelp_hooks @@ -98,6 +174,7 @@ def unhook(hooks): weechat.unhook(cmdhelp_hooks[hook]) cmdhelp_hooks[hook] = '' + def config_cb(data, option, value): """Called when a script option is changed.""" global cmdhelp_settings, cmdhelp_hooks @@ -108,12 +185,13 @@ def config_cb(data, option, value): cmdhelp_settings[name] = value if name == 'stop_on_enter': if value == 'on' and not cmdhelp_hooks['command_run']: - cmdhelp_hooks['command_run'] = weechat.hook_command_run('/input return', - 'command_run_cb', '') + cmdhelp_hooks['command_run'] = weechat.hook_command_run( + '/input return', 'command_run_cb', '') elif value != 'on' and cmdhelp_hooks['command_run']: unhook(('command_run',)) return weechat.WEECHAT_RC_OK + def command_run_cb(data, buffer, command): """Callback for "command_run" hook.""" global cmdhelp_hooks, cmdhelp_settings @@ -121,9 +199,11 @@ def command_run_cb(data, buffer, command): unhook(('timer', 'modifier')) return weechat.WEECHAT_RC_OK + def format_option(match): """Replace ${xxx} by its value in option format.""" - global cmdhelp_settings, cmdhelp_option_infolist, cmdhelp_option_infolist_fields + global cmdhelp_settings, cmdhelp_option_infolist + global cmdhelp_option_infolist_fields string = match.group() end = string.find('}') if end < 0: @@ -146,30 +226,40 @@ def format_option(match): elif fieldtype == 'p': string = weechat.infolist_pointer(cmdhelp_option_infolist, field) elif fieldtype == 't': - string = weechat.infolist_time(cmdhelp_option_infolist, field) + date = weechat.infolist_time(cmdhelp_option_infolist, field) + # since WeeChat 2.2, infolist_time returns a long integer instead of + # a string + if not isinstance(date, str): + date = time.strftime('%F %T', time.localtime(int(date))) + string = date return '%s%s%s' % (color1, string, color2) + def get_option_list_and_desc(option, displayname): """Get list of options and description for option(s).""" - global cmdhelp_settings, cmdhelp_option_infolist, cmdhelp_option_infolist_fields + global cmdhelp_settings, cmdhelp_option_infolist + global cmdhelp_option_infolist_fields options = [] description = '' cmdhelp_option_infolist = weechat.infolist_get('option', '', option) if cmdhelp_option_infolist: cmdhelp_option_infolist_fields = {} while weechat.infolist_next(cmdhelp_option_infolist): - options.append(weechat.infolist_string(cmdhelp_option_infolist, 'full_name')) + options.append(weechat.infolist_string(cmdhelp_option_infolist, + 'full_name')) if not description: fields = weechat.infolist_fields(cmdhelp_option_infolist) for field in fields.split(','): items = field.split(':', 1) if len(items) == 2: cmdhelp_option_infolist_fields[items[1]] = items[0] - description = re.compile(r'\$\{[^\}]+\}').sub(format_option, cmdhelp_settings['format_option']) + description = re.compile(r'\$\{[^\}]+\}').sub( + format_option, cmdhelp_settings['format_option']) if displayname: description = '%s%s%s: %s' % ( weechat.color(cmdhelp_settings['color_option_name']), - weechat.infolist_string(cmdhelp_option_infolist, 'full_name'), + weechat.infolist_string(cmdhelp_option_infolist, + 'full_name'), weechat.color(cmdhelp_settings['color_option_help']), description) weechat.infolist_free(cmdhelp_option_infolist) @@ -177,9 +267,11 @@ def get_option_list_and_desc(option, displayname): cmdhelp_option_infolist_fields = {} return options, description + def get_help_option(input_args): """Get help about option or values authorized for option.""" - global cmdhelp_settings, cmdhelp_option_infolist, cmdhelp_option_infolist_fields + global cmdhelp_settings, cmdhelp_option_infolist + global cmdhelp_option_infolist_fields pos = input_args.find(' ') if pos > 0: option = input_args[0:pos] @@ -191,7 +283,7 @@ def get_help_option(input_args): if len(options) > 1: try: max_options = int(cmdhelp_settings['max_options']) - except: + except ValueError: max_options = 5 if len(options) > max_options: text = '%s...' % ', '.join(options[0:max_options]) @@ -203,8 +295,11 @@ def get_help_option(input_args): weechat.color(cmdhelp_settings['color_list']), text) if description: - return '%s%s' % (weechat.color(cmdhelp_settings['color_option_help']), description) - return '%sNo help for option %s' % (weechat.color(cmdhelp_settings['color_no_help']), option) + return '%s%s' % (weechat.color(cmdhelp_settings['color_option_help']), + description) + return '%sNo help for option %s' % ( + weechat.color(cmdhelp_settings['color_no_help']), option) + def get_command_arguments(input_args, cmd_args): """Get command arguments according to command arguments given in input.""" @@ -225,6 +320,7 @@ def get_command_arguments(input_args, cmd_args): return partial return cmd_args + def get_help_command(plugin, input_cmd, input_args): """Get help for command in input.""" global cmdhelp_settings @@ -236,7 +332,8 @@ def get_help_command(plugin, input_cmd, input_args): cmd_args = '' cmd_desc = '' while weechat.infolist_next(infolist): - cmd_plugin_name = weechat.infolist_string(infolist, 'plugin_name') or 'core' + cmd_plugin_name = (weechat.infolist_string(infolist, 'plugin_name') or + 'core') cmd_command = weechat.infolist_string(infolist, 'command') cmd_args = weechat.infolist_string(infolist, 'args_nls') cmd_desc = weechat.infolist_string(infolist, 'description') @@ -244,17 +341,21 @@ def get_help_command(plugin, input_cmd, input_args): break weechat.infolist_free(infolist) if cmd_plugin_name == 'alias': - return '%sAlias %s%s%s => %s%s' % (weechat.color(cmdhelp_settings['color_alias']), - weechat.color(cmdhelp_settings['color_alias_name']), - cmd_command, - weechat.color(cmdhelp_settings['color_alias']), - weechat.color(cmdhelp_settings['color_alias_value']), - cmd_desc) + return '%sAlias %s%s%s => %s%s' % ( + weechat.color(cmdhelp_settings['color_alias']), + weechat.color(cmdhelp_settings['color_alias_name']), + cmd_command, + weechat.color(cmdhelp_settings['color_alias']), + weechat.color(cmdhelp_settings['color_alias_value']), + cmd_desc, + ) if input_args: cmd_args = get_command_arguments(input_args, cmd_args) if not cmd_args: return None - return '%s%s' % (weechat.color(cmdhelp_settings['color_arguments']), cmd_args) + return '%s%s' % (weechat.color(cmdhelp_settings['color_arguments']), + cmd_args) + def get_list_commands(plugin, input_cmd, input_args): """Get list of commands (beginning with current input).""" @@ -264,7 +365,8 @@ def get_list_commands(plugin, input_cmd, input_args): plugin_names = [] while weechat.infolist_next(infolist): commands.append(weechat.infolist_string(infolist, 'command')) - plugin_names.append(weechat.infolist_string(infolist, 'plugin_name') or 'core') + plugin_names.append( + weechat.infolist_string(infolist, 'plugin_name') or 'core') weechat.infolist_free(infolist) if commands: if len(commands) > 1 or commands[0].lower() != input_cmd.lower(): @@ -281,6 +383,7 @@ def get_list_commands(plugin, input_cmd, input_args): ', '.join(commands2)) return None + def input_modifier_cb(data, modifier, modifier_data, string): """Modifier that will add help on command line (for display only).""" global cmdhelp_settings @@ -303,7 +406,8 @@ def input_modifier_cb(data, modifier, modifier_data, string): current_buffer = weechat.current_buffer() current_window = weechat.current_window() plugin = weechat.buffer_get_pointer(current_buffer, 'plugin') - msg_help = get_help_command(plugin, command[1:], arguments) or get_list_commands(plugin, command[1:], arguments) + msg_help = (get_help_command(plugin, command[1:], arguments) or + get_list_commands(plugin, command[1:], arguments)) if not msg_help: if cmdhelp_settings['display_no_help'] != 'on': return string @@ -315,7 +419,8 @@ def input_modifier_cb(data, modifier, modifier_data, string): if cmdhelp_settings['right_align'] == 'on': win_width = weechat.window_get_integer(current_window, 'win_width') - input_length = weechat.buffer_get_integer(current_buffer, 'input_length') + input_length = weechat.buffer_get_integer(current_buffer, + 'input_length') help_length = len(weechat.string_remove_color(msg_help, "")) min_space = int(cmdhelp_settings['space']) padding = int(cmdhelp_settings['right_padding']) @@ -334,6 +439,7 @@ def input_modifier_cb(data, modifier, modifier_data, string): weechat.color(color_delimiters), cmdhelp_settings['suffix']) + def timer_cb(data, remaining_calls): """Timer callback.""" global cmdhelp_hooks @@ -342,32 +448,35 @@ def timer_cb(data, remaining_calls): weechat.bar_item_update('input_text') return weechat.WEECHAT_RC_OK + def cmd_help_toggle(): """Toggle help on/off.""" global cmdhelp_hooks, cmdhelp_settings if cmdhelp_hooks['modifier']: unhook(('timer', 'modifier')) else: - cmdhelp_hooks['modifier'] = weechat.hook_modifier('input_text_display_with_cursor', - 'input_modifier_cb', '') + cmdhelp_hooks['modifier'] = weechat.hook_modifier( + 'input_text_display_with_cursor', 'input_modifier_cb', '') timer = cmdhelp_settings['timer'] if timer and timer != '0': try: value = float(timer) if value > 0: weechat.hook_timer(value * 1000, 0, 1, 'timer_cb', '') - except: + except ValueError: pass weechat.bar_item_update('input_text') + def cmd_help_cb(data, buffer, args): """Callback for /cmd_help command.""" cmd_help_toggle() return weechat.WEECHAT_RC_OK + if __name__ == '__main__' and import_ok: - if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, '', ''): + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, '', ''): # set allowed fields in option "format_option" fields = [] infolist = weechat.infolist_get('option', '', 'weechat.plugin.*') @@ -380,7 +489,8 @@ def cmd_help_cb(data, buffer, args): fields.append(items[1]) weechat.infolist_free(infolist) if fields: - cmdhelp_settings_default['format_option'][1] += ': %s' % ', '.join(fields) + cmdhelp_settings_default['format_option'][1] += ( + ': %s' % ', '.join(fields)) # set default settings version = weechat.info_get("version_number", "") or 0 @@ -391,30 +501,35 @@ def cmd_help_cb(data, buffer, args): weechat.config_set_plugin(option, value[0]) cmdhelp_settings[option] = value[0] if int(version) >= 0x00030500: - weechat.config_set_desc_plugin(option, '%s (default: "%s")' % (value[1], value[0])) + weechat.config_set_desc_plugin( + option, + '%s (default: "%s")' % (value[1], value[0])) # detect config changes - weechat.hook_config('plugins.var.python.%s.*' % SCRIPT_NAME, 'config_cb', '') + weechat.hook_config('plugins.var.python.%s.*' % SCRIPT_NAME, + 'config_cb', '') # add hook to catch "enter" key if cmdhelp_settings['stop_on_enter'] == 'on': - cmdhelp_hooks['command_run'] = weechat.hook_command_run('/input return', - 'command_run_cb', '') + cmdhelp_hooks['command_run'] = weechat.hook_command_run( + '/input return', 'command_run_cb', '') # add command - weechat.hook_command(SCRIPT_COMMAND, - 'Contextual command line help.', - '', - 'This comand toggles help on command line.\n\n' - 'It is recommended to bind this command on a key, for example F1:\n' - ' /key bind /cmd_help\n' - 'which will give, according to your terminal something like:\n' - ' /key bind meta-OP /cmd_help\n' - ' or:\n' - ' /key bind meta2-11~ /cmd_help\n\n' - 'To try: type "/server" (without pressing enter) and press F1 ' - '(then you can add arguments and enjoy dynamic help!)', - '', 'cmd_help_cb', '') + weechat.hook_command( + SCRIPT_COMMAND, + 'Contextual command line help.', + '', + 'This comand toggles help on command line.\n\n' + 'It is recommended to bind this command on a key, for example ' + 'F1:\n' + ' /key bind /cmd_help\n' + 'which will give, according to your terminal something like:\n' + ' /key bind meta-OP /cmd_help\n' + ' or:\n' + ' /key bind meta2-11~ /cmd_help\n\n' + 'To try: type "/server" (without pressing enter) and press F1 ' + '(then you can add arguments and enjoy dynamic help!)', + '', 'cmd_help_cb', '') # auto start help if cmdhelp_settings['start_on_load'] == 'on': From 56d563109c2643b73eaa2e70feb3ce0ccf010fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Tue, 10 Apr 2018 21:34:25 +0200 Subject: [PATCH 125/642] grep.py 0.8.1: fix infolist_time with WeeChat >= 2.2 --- python/grep.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/python/grep.py b/python/grep.py index 64ece623..19415311 100644 --- a/python/grep.py +++ b/python/grep.py @@ -69,6 +69,10 @@ # # History: # +# 2018-04-10, Sébastien Helleu +# version 0.8.1: fix infolist_time for WeeChat >= 2.2 (WeeChat returns a long +# integer instead of a string) +# # 2017-09-20, mickael9 # version 0.8: # * use weechat 1.5+ api for background processing (old method was unsafe and buggy) @@ -222,7 +226,7 @@ SCRIPT_NAME = "grep" SCRIPT_AUTHOR = "Elián Hanisch " -SCRIPT_VERSION = "0.8" +SCRIPT_VERSION = "0.8.1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Search in buffers and logs" SCRIPT_COMMAND = "grep" @@ -844,6 +848,10 @@ def function(infolist): prefix = string_remove_color(infolist_string(infolist, 'prefix'), '') message = string_remove_color(infolist_string(infolist, 'message'), '') date = infolist_time(infolist, 'date') + # since WeeChat 2.2, infolist_time returns a long integer + # instead of a string + if not isinstance(date, str): + date = time.strftime('%F %T', time.localtime(int(date))) return '%s\t%s\t%s' %(date, prefix, message) return function get_line = make_get_line_funcion() From 0428fd67d88bd1e3b74d084d52e8bc4d3ae2783d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Tue, 10 Apr 2018 21:35:11 +0200 Subject: [PATCH 126/642] infolist.py 0.7: fix infolist_time with WeeChat >= 2.2 --- python/infolist.py | 139 +++++++++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 54 deletions(-) diff --git a/python/infolist.py b/python/infolist.py index 5c57ecdf..7d88a14f 100644 --- a/python/infolist.py +++ b/python/infolist.py @@ -1,5 +1,6 @@ +# -*- coding: utf-8 -*- # -# Copyright (C) 2008-2012 Sebastien Helleu +# Copyright (C) 2008-2018 Sébastien Helleu # # 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 @@ -18,58 +19,72 @@ # Display infolist in a buffer. # # History: +# +# 2018-04-10, Sébastien Helleu : +# version 0.7: fix infolist_time for WeeChat >= 2.2 (WeeChat returns a long +# integer instead of a string), fix PEP8 errors # 2017-10-22, nils_2 : # version 0.6: add string_eval_expression() # 2012-10-02, nils_2 : # version 0.5: switch to infolist buffer (if exists) when command /infolist # is called with arguments, add some examples to help page -# 2012-01-03, Sebastien Helleu : +# 2012-01-03, Sébastien Helleu : # version 0.4: make script compatible with Python 3.x # 2010-01-23, m4v : # version 0.3: user can give a pointer as argument -# 2010-01-18, Sebastien Helleu : +# 2010-01-18, Sébastien Helleu : # version 0.2: use tag "no_filter" for lines displayed, fix display bug # when infolist is empty -# 2009-11-30, Sebastien Helleu : +# 2009-11-30, Sébastien Helleu : # version 0.1: first version -# 2008-12-12, Sebastien Helleu : +# 2008-12-12, Sébastien Helleu : # script creation -SCRIPT_NAME = "infolist" -SCRIPT_AUTHOR = "Sebastien Helleu " -SCRIPT_VERSION = "0.6" +SCRIPT_NAME = "infolist" +SCRIPT_AUTHOR = "Sébastien Helleu " +SCRIPT_VERSION = "0.7" SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Display infolist in a buffer" +SCRIPT_DESC = "Display infolist in a buffer" import_ok = True - try: import weechat -except: +except ImportError: print("This script must be run under WeeChat.") print("Get WeeChat now at: http://www.weechat.org/") import_ok = False +try: + import time +except ImportError as message: + print('Missing package(s) for %s: %s' % (SCRIPT_NAME, message)) + import_ok = False + + infolist_buffer = "" -infolist_var_type = { "i": "int", - "s": "str", - "p": "ptr", - "t": "tim", - "b": "buf", - } +infolist_var_type = { + "i": "int", + "s": "str", + "p": "ptr", + "t": "tim", + "b": "buf", +} def infolist_buffer_set_title(buffer): # get list of infolists available - list = "" + list_infolists = "" infolist = weechat.infolist_get("hook", "", "infolist") while weechat.infolist_next(infolist): - list += " %s" % weechat.infolist_string(infolist, "infolist_name") + list_infolists += " %s" % weechat.infolist_string(infolist, + "infolist_name") weechat.infolist_free(infolist) # set buffer title weechat.buffer_set(buffer, "title", - "%s %s | Infolists:%s" % (SCRIPT_NAME, SCRIPT_VERSION, list)) + "%s %s | Infolists:%s" % ( + SCRIPT_NAME, SCRIPT_VERSION, list_infolists)) + def infolist_display(buffer, args): global infolist_var_type @@ -93,9 +108,10 @@ def infolist_display(buffer, args): item_count = 0 weechat.buffer_clear(buffer) - weechat.prnt_date_tags(buffer, 0, "no_filter", - "Infolist '%s', with pointer '%s' and arguments '%s':" % (items[0], - infolist_pointer, infolist_args)) + weechat.prnt_date_tags( + buffer, 0, "no_filter", + "Infolist '%s', with pointer '%s' and arguments '%s':" % ( + items[0], infolist_pointer, infolist_args)) weechat.prnt(buffer, "") count = 0 while weechat.infolist_next(infolist): @@ -121,15 +137,22 @@ def infolist_display(buffer, args): value = weechat.infolist_pointer(infolist, name) elif type == "t": value = weechat.infolist_time(infolist, name) + # since WeeChat 2.2, infolist_time returns a long integer + # instead of a string + if not isinstance(value, str): + str_date = time.strftime('%F %T', + time.localtime(int(value))) + value = '%d (%s)' % (value, str_date) name_end = "." * (30 - len(name)) - weechat.prnt_date_tags(buffer, 0, "no_filter", - "%s%s%s: %s%s%s %s%s%s%s%s%s" % - (prefix, name, name_end, - weechat.color("brown"), infolist_var_type[type], - weechat.color("chat"), - weechat.color("chat"), quote, - weechat.color("cyan"), value, - weechat.color("chat"), quote)) + weechat.prnt_date_tags( + buffer, 0, "no_filter", + "%s%s%s: %s%s%s %s%s%s%s%s%s" % + (prefix, name, name_end, + weechat.color("brown"), infolist_var_type[type], + weechat.color("chat"), + weechat.color("chat"), quote, + weechat.color("cyan"), value, + weechat.color("chat"), quote)) prefix = "" count += 1 if count == 0: @@ -137,6 +160,7 @@ def infolist_display(buffer, args): weechat.infolist_free(infolist) return weechat.WEECHAT_RC_OK + def infolist_buffer_input_cb(data, buffer, input_data): if input_data == "q" or input_data == "Q": weechat.buffer_close(buffer) @@ -144,12 +168,14 @@ def infolist_buffer_input_cb(data, buffer, input_data): infolist_display(buffer, input_data) return weechat.WEECHAT_RC_OK + def infolist_buffer_close_cb(data, buffer): global infolist_buffer infolist_buffer = "" return weechat.WEECHAT_RC_OK + def infolist_buffer_new(): global infolist_buffer @@ -164,6 +190,7 @@ def infolist_buffer_new(): weechat.buffer_set(infolist_buffer, "time_for_each_line", "0") weechat.buffer_set(infolist_buffer, "display", "1") + def infolist_cmd(data, buffer, args): global infolist_buffer @@ -173,32 +200,36 @@ def infolist_cmd(data, buffer, args): infolist_buffer_new() if infolist_buffer != "" and args != "": infolist_display(infolist_buffer, args) - weechat.buffer_set(infolist_buffer, "display", "1"); + weechat.buffer_set(infolist_buffer, "display", "1") return weechat.WEECHAT_RC_OK + def string_eval_expression(string): - return weechat.string_eval_expression(string,{},{},{}) + return weechat.string_eval_expression(string, {}, {}, {}) + if __name__ == "__main__" and import_ok: - if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "", ""): - weechat.hook_command("infolist", "Display infolist in a buffer", - "[infolist [pointer] [arguments]]", - " infolist: name of infolist\n" - " pointer: optional pointer for infolist (\"\" for none)\n" - "arguments: optional arguments for infolist\n\n" - "Command without argument will open buffer used " - "to display infolists.\n\n" - "On infolist buffer, you can enter name of an " - "infolist, with optional arguments.\n" - "Enter 'q' to close infolist buffer.\n\n" - "Examples:\n" - " Show information about nick \"FlashCode\" in channel \"#weechat\" on server \"freenode\":\n" - " /infolist irc_nick freenode,#weechat,FlashCode\n" - " Show nicklist from a specific buffer:\n" - " /infolist nicklist \n" - " Show current buffer:\n" - " /infolist buffer ${buffer}" - "", - "%(infolists)", "infolist_cmd", "") + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + weechat.hook_command( + "infolist", "Display infolist in a buffer", + "[infolist [pointer] [arguments]]", + " infolist: name of infolist\n" + " pointer: optional pointer for infolist (\"\" for none)\n" + "arguments: optional arguments for infolist\n\n" + "Command without argument will open buffer used " + "to display infolists.\n\n" + "On infolist buffer, you can enter name of an " + "infolist, with optional arguments.\n" + "Enter 'q' to close infolist buffer.\n\n" + "Examples:\n" + " Show information about nick \"FlashCode\" in channel " + "\"#weechat\" on server \"freenode\":\n" + " /infolist irc_nick freenode,#weechat,FlashCode\n" + " Show nicklist from a specific buffer:\n" + " /infolist nicklist \n" + " Show current buffer:\n" + " /infolist buffer ${buffer}" + "", + "%(infolists)", "infolist_cmd", "") From 8508f53ce2d452cfc884e6b0cea68c234b2285b8 Mon Sep 17 00:00:00 2001 From: "Kim B. Heino" Date: Sun, 15 Apr 2018 10:26:26 +0200 Subject: [PATCH 127/642] buffer_autoset.py 1.1: on startup apply settings to already opened buffers (closes #163) For example jabber buffers are already opened when buffer_autoset.py runs. This should fix issue #163. --- python/buffer_autoset.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python/buffer_autoset.py b/python/buffer_autoset.py index c33d3ad9..38dbc4b7 100644 --- a/python/buffer_autoset.py +++ b/python/buffer_autoset.py @@ -22,6 +22,8 @@ # # History: # +# 2018-04-14, Kim B. Heino: +# version 1.1: on startup apply settings to already opened buffers # 2017-06-21, Sébastien Helleu : # version 1.0: rename command /autosetbuffer to /buffer_autoset # 2015-09-28, Simmo Saan : @@ -48,7 +50,7 @@ SCRIPT_NAME = "buffer_autoset" SCRIPT_AUTHOR = "Sébastien Helleu " -SCRIPT_VERSION = "1.0" +SCRIPT_VERSION = "1.1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Auto-set buffer properties when a buffer is opened" @@ -328,8 +330,13 @@ def bas_config_option_cb(data, option, value): weechat.hook_config("%s.buffer.*" % CONFIG_FILE_NAME, "bas_config_option_cb", "") - # core buffer is already open on script startup, check manually! - bas_signal_buffer_opened_cb("", "", weechat.buffer_search_main()) + # apply settings to all already opened buffers + buffers = weechat.infolist_get("buffer", "", "") + if buffers: + while weechat.infolist_next(buffers): + buffer = weechat.infolist_pointer(buffers, "pointer") + bas_signal_buffer_opened_cb("", "", buffer) + weechat.infolist_free(buffers) # ==================================[ end ]=================================== From a88076558ff79c24f0bc6a7fa882d8c5e00a79c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Fri, 11 May 2018 14:25:18 +0200 Subject: [PATCH 128/642] urlgrab.py 3.0: fix Python 3 compatibility The method "has_key" does not exist any more in Python 3 for dicts. --- python/urlgrab.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/urlgrab.py b/python/urlgrab.py index 8fdaa3a5..6b1745ef 100644 --- a/python/urlgrab.py +++ b/python/urlgrab.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # # UrlGrab, for weechat version >= 0.3.0 # @@ -107,7 +108,7 @@ # - Removed '/url help' command, because /help is the standard way # - V2.0 Xilov: replace "/url help" by "/help url" # - V2.1 nand: Changed default: firefox %s to firefox '%s' (localcmd) -# - V2.2 Sebastien Helleu : fix reload of config file +# - V2.2 Sébastien Helleu : fix reload of config file # - V2.3 nand: Allowed trailing )s for unmatched (s in URLs # - V2.4 nand: Escaped URLs via URL-encoding instead of shell escaping, fixes ' # - V2.5 nand: Fixed some URLs that got incorrectly mangled by escaping @@ -122,6 +123,8 @@ # - Changed print hook to ignore filtered lines # - V2.9 Dominik Heidler : # - Updated script for python3 support (now python2 and 3 are both supported) +# - V3.0 Sébastien Helleu : +# - Fix python 3 compatibility (replace "has_key" by "in") # # Copyright (C) 2005 David Rubin # @@ -174,7 +177,7 @@ SCRIPT_NAME = "urlgrab" SCRIPT_AUTHOR = "David Rubin " -SCRIPT_VERSION = "2.9" +SCRIPT_VERSION = "3.0" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Url functionality Loggin, opening of browser, selectable links" CONFIG_FILE_NAME= "urlgrab" @@ -399,7 +402,7 @@ def getUrl(self, bufferp, index): def prnt(self, buff): found = True - if self.urls.has_key(buff): + if buff in self.urls: if len(self.urls[buff]) > 0: i = 1 for url in self.urls[buff]: From 712b6773d1ae49771b5030341433fb55dd89a6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Fri, 11 May 2018 14:28:48 +0200 Subject: [PATCH 129/642] autosavekey.py 0.4: make script compatible with Python 3, add /help text --- python/autosavekey.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/python/autosavekey.py b/python/autosavekey.py index bc8b4bfe..2a3b0adc 100644 --- a/python/autosavekey.py +++ b/python/autosavekey.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2013-2015 by nils_2 +# Copyright (c) 2013-2018 by nils_2 # # save channel key from protected channel(s) to autojoin or secure data # @@ -19,6 +19,10 @@ # # idea by freenode.elsae # +# 2018-05-11: nils_2, (freenode.#weechat) +# 0.4 : make script python3 compatible +# : add /help text +# # 2015-05-09: nils_2, (freenode.#weechat) # 0.3 : fix: ValueError (reported by: Darpa) # @@ -37,15 +41,15 @@ import weechat,re except Exception: - print "This script must be run under WeeChat." - print "Get WeeChat now at: http://www.weechat.org/" + print("This script must be run under WeeChat.") + print("Get WeeChat now at: http://www.weechat.org/") quit() SCRIPT_NAME = "autosavekey" SCRIPT_AUTHOR = "nils_2 " -SCRIPT_VERSION = "0.3" +SCRIPT_VERSION = "0.4" SCRIPT_LICENSE = "GPL" -SCRIPT_DESC = "save channel key from protected channel(s) to autojoin or secure data" +SCRIPT_DESC = "save channel key from protected channel(s) to autojoin option or secure data" OPTIONS = { 'mute' : ('off','execute command silently, only error messages will be displayed.'), 'secure' : ('off','change channel key in secure data.'), @@ -216,6 +220,11 @@ def check_key_for_secure(argv_keys,position): if argv_keys[position][0:11] == '${sec.data.': sec_data = 1 return sec_data + +def cmd_autosavekey(data, buffer, args): + weechat.command('', '/help %s' % SCRIPT_NAME) + return weechat.WEECHAT_RC_OK + # ================================[ weechat options & description ]=============================== def init_options(): for option,value in OPTIONS.items(): @@ -235,6 +244,13 @@ def toggle_refresh(pointer, name, value): # ================================[ main ]=============================== if __name__ == "__main__": if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''): + weechat.hook_command(SCRIPT_NAME,SCRIPT_DESC, + '', + 'You have to edit options with: /set *autosavekey*\n' + 'I suggest using /iset script or /fset plugin.\n', + '', + 'cmd_autosavekey', + '') version = weechat.info_get("version_number", "") or 0 if int(version) >= 0x00030200: From b8e258dde240a35b4b48f08484ee6fa37948addf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20G=C3=B6rs?= Date: Fri, 1 Jun 2018 20:54:21 +0200 Subject: [PATCH 130/642] unset_unused.pl 0.4: add support for PHP and JavaScript --- perl/unset_unused.pl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/perl/unset_unused.pl b/perl/unset_unused.pl index 9d0a8b67..4c6475ae 100644 --- a/perl/unset_unused.pl +++ b/perl/unset_unused.pl @@ -1,5 +1,5 @@ # -# Copyright (c) 2011-2013 by Nils Görs +# Copyright (c) 2011-2018 by Nils Görs # # unset script option(s) from not installed scripts # @@ -17,10 +17,9 @@ # along with this program. If not, see . # # +# 18-04-18: 0.4: add support for php and javascript # 14-10-04: 0.3: fixed: problem with unset options (reported by GermainZ) -# -# 13-07-27: 0.2 : added: support for guile_script -# +# 13-07-27: 0.2: added: support for guile_script # 11-08-28: 0.1 # # Development is currently hosted at @@ -29,19 +28,21 @@ use strict; my $PRGNAME = "unset_unused"; -my $VERSION = "0.3"; +my $VERSION = "0.4"; my $AUTHOR = "Nils Görs "; my $LICENCE = "GPL3"; my $DESCR = "unset script option(s) from not installed scripts (YOU ARE USING THIS SCRIPT AT YOUR OWN RISK!)"; my $weechat_version = ""; my @option_list; my %script_plugins = ( - "python" => "python_script", - "perl" => "perl_script", - "ruby" => "ruby_script", - "tcl" => "tcl_script", - "lua" => "lua_script", - "guile" => "guile_script", + "python" => "python_script", + "perl" => "perl_script", + "ruby" => "ruby_script", + "tcl" => "tcl_script", + "lua" => "lua_script", + "guile" => "guile_script", + "php" => "php_script", + "javascript" => "javascript_script", ); my $option_struct; @@ -142,10 +143,11 @@ sub my_command_cb{ weechat::hook_command($PRGNAME, $DESCR, "list || unset\n", - " list : list all unused script options\n". - " unset : reset config options (without warning!)\n\n". - "If \"plugins.desc.\" exists, it will be removed, too.\n". - "save your settings with \"/save plugins\" or restore settings with \"/reload plugins\"". + " list: list all script options from not installed scripts (run this command first!)\n". + " unset: remove script options from not installed scripts (without warning!)\n\n". + "If \"plugins.desc.\" exists, it will be removed, too.\n\n". + "save your settings with \"/save plugins\" or restore settings with \"/reload plugins\"\n". + "You can also use \"/unset -mask\", see /help unset". "\n", "list %-||". "unset %-", From c80a4a2e12b3c7585d369492741fe92314f9b8b3 Mon Sep 17 00:00:00 2001 From: Serge van Ginderachter Date: Sat, 2 Jun 2018 16:58:40 +0200 Subject: [PATCH 131/642] mqtt_notify.py: v0.5: major update - feature: add variables to customize the data fiels in the print hooks, default values are backwards compatible - feature: add messages on connecting/disconnecting to mqtt - feature: add script unload hook function to clean up the mqtt connection - feature: add variable for mqtt client name - feature: add buffer_long and buffer_full in message json - fix: move mqtt connect code to global scope, avoiding new connections on each message causing lost messages due to network/socket errors, and optimized for mqtt client loop_start/stop - fix: rename mqtt_timeout to mqtt_keepalive --- python/mqtt_notify.py | 150 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 121 insertions(+), 29 deletions(-) diff --git a/python/mqtt_notify.py b/python/mqtt_notify.py index eb224770..b312bf6b 100644 --- a/python/mqtt_notify.py +++ b/python/mqtt_notify.py @@ -1,56 +1,148 @@ # -*- coding: utf-8 -*- # vim: ai ts=4 sts=4 et sw=4 nu - +# +# History +# +# 2018-05-06, Serge van GInderachter +# v0.2: - fix: correct a typo causing AttributeError +# 2016-10-30, Guillaume Subiron +# v0.1: New script mqtt_notify.py: +# send notifications using the MQTT protocol +# from __future__ import (unicode_literals, absolute_import, division, print_function) +try: + import weechat + import_ok = True +except ImportError: + weechat.prnt('', 'mqtt_notify: this script must be run under WeeChat.') + weechat.prnt('', 'Get WeeChat now at: http://www.weechat.org/') + import_ok = False + +try: + import paho.mqtt.client as paho + import json + import socket +except ImportError as message: + weechat.prnt('', 'mqtt_notify: missing package(s): %s' % (message)) + import_ok = False +import sys -import weechat as w -import paho.mqtt.client as mqtt -import json +# @srgvg on Github: +SCRIPT_MAINTAINER = 'Serge van Ginderachter ' SCRIPT_NAME = 'mqtt_notify' SCRIPT_AUTHOR = 'Guillaume Subiron ' -SCRIPT_VERSION = '0.2' +SCRIPT_VERSION = '0.5' SCRIPT_LICENSE = 'WTFPL' SCRIPT_DESC = 'Sends notifications using MQTT' -w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, '', '') - DEFAULT_OPTIONS = { 'mqtt_host': 'localhost', 'mqtt_port': '1883', - 'mqtt_timeout': '60', + 'mqtt_keepalive': '60', 'mqtt_user': '', 'mqtt_password': '', 'mqtt_channel': 'weechat', + 'mqtt_client_name': 'weechat_mqtt_notify', + 'mqtt_message_data': '', # string passed in the data field of the callback + 'mqtt_private_data': 'private', } -for key, val in DEFAULT_OPTIONS.items(): - if not w.config_is_set_plugin(key): - w.config_set_plugin(key, val) -w.hook_print("", "notify_message", "", 1, "on_msg", "") -w.hook_print("", "notify_private", "", 1, "on_msg", "private") -w.hook_print("", "notify_highlight", "", 1, "on_msg", "") # Not sure if needed +def mqtt_on_connect(client, userdata, flags, rc): + + if rc == 0: + weechat.prnt('', 'mqtt_notify: connected successfully') + else: + weechat.prnt( + '', + 'mqtt_notify: failed connecting - return code %s' % + rc) + + +def mqtt_on_disconnect(client, userdata, rc): + if rc != 0: + weechat.prnt( + '', + 'mqtt_notify: unexpected disconnection - return code %s' % + rc) + else: + weechat.prnt('', 'mqtt_notify: disconnected') + + +def weechat_on_msg_cb(*a): -def on_msg(*a): keys = ['data', 'buffer', 'timestamp', 'tags', 'displayed', 'highlight', 'sender', 'message'] msg = dict(zip(keys, a)) - msg['buffer'] = w.buffer_get_string(msg['buffer'], 'short_name') - - cli = mqtt.Client() - if w.config_get_plugin('mqtt_user'): - cli.username_pw_set(w.config_get_plugin('mqtt_user'), - password=w.config_get_plugin('mqtt_password')) - cli.connect(w.config_get_plugin('mqtt_host'), - int(w.config_get_plugin('mqtt_port')), - int(w.config_get_plugin('mqtt_timeout'))) - cli.publish(w.config_get_plugin('mqtt_channel'), - json.dumps(msg), retain=True) - - return w.WEECHAT_RC_OK + + msg['buffer_long'] = weechat.buffer_get_string(msg['buffer'], 'name') + msg['buffer_full'] = weechat.buffer_get_string(msg['buffer'], 'full_name') + msg['buffer'] = weechat.buffer_get_string(msg['buffer'], 'short_name') + + mqttclient.publish(weechat.config_get_plugin('mqtt_channel'), + json.dumps(msg), retain=True) + + return weechat.WEECHAT_RC_OK + + +def mqtt_notify_script_unload(): + + mqttclient.loop_stop() + mqttclient.disconnect() + return weechat.WEECHAT_RC_OK + + +if import_ok: + + weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, 'mqtt_notify_script_unload', + '') + + for key, val in DEFAULT_OPTIONS.items(): + if not weechat.config_is_set_plugin(key): + weechat.config_set_plugin(key, val) + + # Setup the MQTT client + mqttclient = paho.Client(client_id=weechat.config_get_plugin( + 'mqtt_client_name'), clean_session=False) + mqttclient.on_connect = mqtt_on_connect + mqttclient.on_disconnect = mqtt_on_disconnect + + if weechat.config_get_plugin('mqtt_user'): + mqttclient.username_pw_set(weechat.config_get_plugin('mqtt_user'), + password=weechat.config_get_plugin( + 'mqtt_password')) + try: + mqttclient.connect_async(weechat.config_get_plugin('mqtt_host'), + int(weechat.config_get_plugin('mqtt_port')), + int(weechat.config_get_plugin( + 'mqtt_keepalive'))) + mqttclient.loop_start() + except socket.error as err: + # mqttclient loop runs in background thread + # and wil keep trying to reconnect + pass + + weechat.hook_print("", "notify_message", "", 1, "weechat_on_msg_cb", + weechat.config_get_plugin("mqtt_message_data")) + weechat.hook_print("", "notify_private", "", 1, "weechat_on_msg_cb", + weechat.config_get_plugin("mqtt_private_data")) From 8b4be1f365e8cbea078b5f3651d2748d0a6c12bd Mon Sep 17 00:00:00 2001 From: mumixam Date: Wed, 6 Jun 2018 00:10:26 -0500 Subject: [PATCH 132/642] twitch.py: v0.5: enable curl verbose mode when debug is active, add option to disable ssl/tls verification, replace newline char it with space in title. --- python/twitch.py | 60 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/python/twitch.py b/python/twitch.py index 88e8ea12..2f11ccd5 100644 --- a/python/twitch.py +++ b/python/twitch.py @@ -27,11 +27,15 @@ # plugins.var.python.twitch.servers (default: twitch) # plugins.var.python.twitch.prefix_nicks (default: 1) # plugins.var.python.twitch.debug (default: 0) +# plugins.var.python.twitch.ssl_verify (default: 1) # # # History: # +# 2018-06-03, mumixam +# v0.5: enable curl verbose mode when debug is active, add option to disable ssl/tls verification, +# if stream title contains newline char replace it with space # 2017-11-02, mumixam -# v0.4: added debug mode for API calls, minor bugfixes +# v0.4: added debug mode for API calls, minor bugfixes # 2017-06-10, mumixam # v0.3: fixed whois output of utf8 display names # 2016-11-03, mumixam @@ -43,13 +47,14 @@ SCRIPT_NAME = "twitch" SCRIPT_AUTHOR = "mumixam" -SCRIPT_VERSION = "0.4" +SCRIPT_VERSION = "0.5" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "twitch.tv Chat Integration" -OPTIONS={ +OPTIONS={ 'servers': ('twitch','Name of server(s) which script will be active on, space seperated'), 'prefix_nicks': ('1','Prefix nicks based on ircv3 tags for mods/subs, This can be cpu intensive on very active chats [1 for enabled, 0 for disabled]'), - 'debug': ('0','Debug mode') + 'debug': ('0','Debug mode'), + 'ssl_verify': ('1', 'Verify SSL/TLS certs') } @@ -63,7 +68,13 @@ clientid='awtv6n371jb7uayyc4jaljochyjbfxs' params = '?client_id='+clientid -curlopt = {"timeout": "5"} +curlopt = { + "timeout": "5", + "verbose": "0", + "ssl_verifypeer": "1", + "ssl_verifyhost": "2" +} + def days_hours_minutes(td): age = '' hours = td.seconds // 3600 @@ -192,7 +203,7 @@ def stream_api(data, command, rc, stdout, stderr): jsonDict = json.loads(stdout.strip()) except Exception as e: weechat.prnt(data, '%stwitch.py: error communicating with twitch api' % weechat.prefix('error')) - if OPTIONS['debug']: + if OPTIONS['debug']: weechat.prnt(data,'%stwitch.py: return code: %s' % (weechat.prefix('error'),rc)) weechat.prnt(data,'%stwitch.py: stdout: %s' % (weechat.prefix('error'),stdout)) weechat.prnt(data,'%stwitch.py: stderr: %s' % (weechat.prefix('error'),stderr)) @@ -239,8 +250,8 @@ def stream_api(data, command, rc, stdout, stderr): output = 'STREAM: %sLIVE%s' % (green, title_fg) if 'game' in jsonDict['stream']: if jsonDict['stream']['game']: - game = gameshort(jsonDict['stream']['game']).encode('utf8') - output += ' %s with' % game + game = gameshort(jsonDict['stream']['game']) + output += ' %s with' % makeutf8(game) if 'viewers' in jsonDict['stream']: viewers = jsonDict['stream']['viewers'] output += ' %s viewers started' % viewers @@ -256,7 +267,7 @@ def stream_api(data, command, rc, stdout, stderr): followers = jsonDict['stream']['channel']['followers'] output += ' [%s followers]' % followers if 'status' in jsonDict['stream']['channel']: - titleutf8=jsonDict['stream']['channel']['status'].encode('utf8') + titleutf8=jsonDict['stream']['channel']['status'].replace('\n',' ').encode('utf8') titleascii=jsonDict['stream']['channel']['status'].encode('ascii','replace') if not isinstance(titleutf8, str): titleascii=str(titleascii,'utf8') @@ -463,16 +474,40 @@ def config_setup(): weechat.config_set_plugin(option, value[0]) OPTIONS[option] = value[0] else: - if option == 'prefix_nicks' or option == 'debug': + if option == 'prefix_nicks' or option == 'debug' or option == 'ssl_verify': OPTIONS[option] = weechat.config_string_to_boolean( weechat.config_get_plugin(option)) + if option == 'debug': + if value == 0: + curlopt['verbose'] = "0" + else: + curlopt['verbose'] = "1" + if option == 'ssl_verify': + if value == 0: + curlopt['ssl_verifypeer'] = "0" + curlopt['ssl_verifyhost'] = "0" + else: + curlopt['ssl_verifypeer'] = "1" + curlopt['ssl_verifyhost'] = "2" else: OPTIONS[option] = weechat.config_get_plugin(option) def config_change(pointer, name, value): option = name.replace('plugins.var.python.'+SCRIPT_NAME+'.','') - if option == 'prefix_nicks' or option == 'debug': + if option == 'prefix_nicks' or option == 'debug' or option == 'ssl_verify': value=weechat.config_string_to_boolean(value) + if option == 'debug': + if value == 0: + curlopt['verbose'] = "0" + if value == 1: + curlopt['verbose'] = "1" + if option == 'ssl_verify': + if value == 0: + curlopt['ssl_verifypeer'] = "0" + curlopt['ssl_verifyhost'] = "0" + if value == 1: + curlopt['ssl_verifypeer'] = "1" + curlopt['ssl_verifyhost'] = "2" OPTIONS[option] = value return weechat.WEECHAT_RC_OK @@ -484,6 +519,7 @@ def config_change(pointer, name, value): " plugins.var.python.twitch.servers (default: twitch)\n" " plugins.var.python.twitch.prefix_nicks (default: 1)\n" " plugins.var.python.twitch.debug (default: 0)\n" + " plugins.var.python.twitch.ssl_verify (default: 0)\n" "\n\n" " This script checks stream status of any channel on any servers listed\n" " in the \"plugins.var.python.twitch.servers\" setting. When you switch\n" @@ -505,6 +541,8 @@ def config_change(pointer, name, value): "\n\n" " If you are experiencing errors you can enable debug mode by setting\n" " /set plugins.var.python.twitch.debug on\n" + " You can also try disabling SSL/TLS cert verification.\n" + " /set plugins.var.python.twitch.ssl_verify off\n" "\n\n" " Required server settings:\n" " /server add twitch irc.twitch.tv\n" From 231b5559228eea86ea47e4dc23cd88b5cbcfc9e6 Mon Sep 17 00:00:00 2001 From: Stefan Wold Date: Wed, 6 Jun 2018 18:41:55 +0200 Subject: [PATCH 133/642] undernet_totp.py 0.4.0: added user friendly script configuration --- python/undernet_totp.py | 221 +++++++++++++++++++++++++++++++++++----- 1 file changed, 196 insertions(+), 25 deletions(-) diff --git a/python/undernet_totp.py b/python/undernet_totp.py index 3fc99e48..48dcc025 100644 --- a/python/undernet_totp.py +++ b/python/undernet_totp.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2013 - 2015 Stefan Wold +# Copyright (C) 2013 - 2018 Stefan Wold # # 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 @@ -25,22 +25,20 @@ # This allows OTP login when using irc.server.*.command to automatically # sign in to the X service when connecting to an undernet server. # -# -# Configuration: -# /set plugins.var.python.undernet-totp.otp_server_names ",,..." -# Set servers for which to automatically enable OTP login -# # Commands: -# /uotp [server] -# Generate an OTP for server, output in core buffer. -# +# /uotp otp [server] +# /uotp list +# /uotp add +# /uotp remove +# /uotp enable +# /uotp disable SCRIPT_NAME = "undernet_totp" SCRIPT_AUTHOR = "Stefan Wold " -SCRIPT_VERSION = "0.3.1" +SCRIPT_VERSION = "0.4.0" SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Enables automatic OTP (OATH-TOTP) support for UnderNET's X and Login on Connect (LoC) authentication." +SCRIPT_DESC = "Automatic OTP (OATH-TOTP) authentication with UnderNET's channel services (X) and Login on Connect (LoC)." SCRIPT_COMMAND = "uotp" HOOKS = {} @@ -75,6 +73,9 @@ def print_debug(message): if weechat.config_get_plugin('debug') == 'on': weechat.prnt("", "%s DEBUG: %s" % (SCRIPT_NAME, message)) +def sprint(message, buffer=""): + weechat.prnt(buffer, "%s: %s" % (SCRIPT_NAME, message)) + def unhook(hook): global HOOKS @@ -91,6 +92,7 @@ def unhook_all(server): def hook_all(server): + print_debug("hook_all(%s)" % server) global HOOKS notice = server + '.notice' @@ -140,7 +142,6 @@ def get_otp_cb(data, buffer, server): else: server = enabled_servers() - for _server in server: otp = generate_totp(_server) if otp is not None: @@ -149,23 +150,42 @@ def get_otp_cb(data, buffer, server): return weechat.WEECHAT_RC_OK +def get_irc_servers(): + """ Returns a list of configured IRC servers in weechat""" + serverptrlist = weechat.infolist_get('irc_server', '', '') + serverlist = [] + while weechat.infolist_next(serverptrlist): + serverlist.append(weechat.infolist_string(serverptrlist, 'name')) + return serverlist + + def enabled_servers(): - def server_exists(server): - print_debug('enabled_servers(%s)' % server) - if weechat.config_get('irc.server.%s.addresses' % server) is not '': - return True - return False + """ Return a list of TOTP enabled servers. """ + serverlist = get_irc_servers() + return [s for s in get_config_as_list('otp_server_names') if s in serverlist] + - servers = weechat.config_get_plugin('otp_server_names') - return [s.strip() for s in servers.split(',') if server_exists(s.strip())] +def disabled_servers(): + """ Return a list of configured TOTP servers that are currently disabled. """ + serverlist = get_irc_servers() + server_seed_list = [server for server in serverlist + if weechat.string_eval_expression("${sec.data.%s_seed}" % server, {}, {}, {}) + and server not in get_config_as_list('otp_server_names')] + return [s for s in server_seed_list if s in serverlist] -def generate_totp(server, period=30): +def configured_servers(): + """ Return a lost of servers with an existing seed. """ + serverlist = get_irc_servers() + return [s for s in serverlist if weechat.string_eval_expression("${sec.data.%s_seed}" % s, {}, {}, {})] + + +def generate_totp(server, period=30, buffer=""): print_debug('generate_totp(%s)' % server) seed = weechat.string_eval_expression("${sec.data.%s_seed}" % server, {}, {}, {}) - if seed is "": - weechat.prnt("", "No OATH-TOTP secret set, use: /secure set %s_seed " % server) + if not seed: + sprint("No OATH-TOTP secret set, use: /uotp add %s " % server, buffer) return None if len(seed) == 40: # Assume hex format @@ -181,6 +201,129 @@ def generate_totp(server, period=30): return '%06d' % otp +def config_update_cb(data, option, value): + """ Reload hooks on configuration change. """ + print_debug("config_cb(%s)" % value) + [hook_all(s.strip()) for s in value.split(',')] + return weechat.WEECHAT_RC_OK + + +def options_cb(data, buffer, args): + """ Script configuration callback """ + if not args: + weechat.command("", "/help %s" % SCRIPT_COMMAND) + args = args.strip().split(' ') + opt = args[0] + opt_args = args[1:] + + if opt == 'otp': + if opt_args: + servers = [opt_args[0]] + else: + servers = enabled_servers() + for server in servers: + otp = generate_totp(server, buffer=buffer) + if otp: + sprint("%s = %s" % (server, otp), buffer) + elif opt == 'list': + sprint("List of configured servers", buffer) + for server in enabled_servers(): + weechat.prnt(buffer, " - %s [enabled]" % server) + for server in disabled_servers(): + weechat.prnt(buffer, " - %s [disabled]" % server) + elif opt == 'add': + if len(opt_args) >= 2: + if opt_args[0] not in enabled_servers() and opt_args[0] in get_irc_servers(): + #weechat.command("", "/secure set %s_seed %s" % (opt_args[0], opt_args[1])) + try: + add_server(opt_args[0], opt_args[1:]) + sprint("server '%s' was successfully added" % opt_args[0], buffer) + except Exception as ex: + sprint("invalid TOTP seed provided", buffer) + elif opt_args[0] not in get_irc_servers(): + sprint("No server named '%s' was found, see /help server" % opt_args[0], buffer) + else: + sprint("OTP already configured for '%s', to change remove the existing one first." % opt_args[0], buffer) + else: + sprint("/uotp -- invalid argument, valid command is /uotp add ", buffer) + elif opt == 'remove': + if opt_args[0] in enabled_servers() or opt_args[0] in disabled_servers(): + remove_server(opt_args[0], True) + sprint("server '%s' was successfully removed" % opt_args[0], buffer) + else: + sprint("failed to remove server, '%s' not found" % opt_args[0], buffer) + elif opt == 'enable': + if opt_args and opt_args[0] not in enabled_servers(): + if opt_args[0] in get_irc_servers(): + add_server(opt_args[0]) + sprint("server '%s' was successfully enabled" % opt_args[0], buffer) + else: + sprint("No server named '%s' was found, see /help server" % opt_args[0], buffer) + else: + sprint("OTP is already enabled for the server '%s'." % opt_args[0], buffer) + elif opt == 'disable': + if opt_args and opt_args[0] in enabled_servers(): + remove_server(opt_args[0]) + else: + sprint("OTP does not seem to be enabled for '%s'" % opt_args[0], buffer) + elif opt: + sprint("/uotp: invalid option -- '%s'" % opt, buffer) + weechat.command("", "/help %s" % SCRIPT_COMMAND) + + return weechat.WEECHAT_RC_OK + + +def get_config_as_list(option): + """ Return comma-separated