weechat-scripts-20131007/0000755000175000017500000000000012224522674013546 5ustar manumanuweechat-scripts-20131007/lua/0000755000175000017500000000000012224522673014326 5ustar manumanuweechat-scripts-20131007/lua/mpdbitl.lua0000644000175000017500000004053412224522672016471 0ustar manumanu--[[ -- mpdbitl -- -- Script that automatically change bitlbee status message into current MPD -- track. Requires Weechat 0.3.5 or higher. -- -- Author: rumia -- License: WTFPL --]] require "socket" mpdbitl_config = { enable = true, hostname = "localhost", port = 6600, password = nil, timeout = 1, network = "localhost", channel = "&bitlbee", bitlbot = "root", accounts = "", format_playing = "", format_paused = "", format_stopped = "", format_none = "" } mpdbitl_sock = nil mpdbitl_song_id = nil mpdbitl_error = {} mpdbitl_config_file = nil mpdbitl_current_state = "stop" mpdbitl_config_file_name = "mpdbitl" mpdbitl_status_text = "" mpdbitl_timer = nil mpdbitl_caught_messages = 0 mpdbitl_msg_hook = nil -- 1: bitlbot, 2: account id/tag, 3: status message mpdbitl_status_command_normal = "/mute -all msg %s account %s set status %s" -- 1: nick/handle, 2: status message mpdbitl_status_command_alternate = "/mute -all msg %s %s" function mpdbitl_config_init() local structure = { general = { enable = { description = "Enable mpdbitl", default = true } }, mpd = { hostname = { description = "Hostname of MPD server", default = "localhost" }, port = { description = "Port used by MPD server", default = 6600, min = 1, max= 65535 }, password = { description = "Password used to authenticate to MPD server", default = "" }, timeout = { description = "Connection timeout (in seconds)", default = 3, min = 1, max = 65535 } }, bitlbee = { network = { description = "Network ID for Bitlbee server", default = "localhost" }, channel = { description = "Bitlbee main channel", default = "&bitlbee" }, accounts = { description = "Comma separated list of Bitlbee account IDs/tags/handles. " .. "To specify a handle, prefix the entry with @", default = {0} }, bitlbot = { description = "Bitlbee bot handle name", default = "root" }, format_playing = { description = "Status format when MPD is playing a song", default = "mpdbitl: {{artist}} — {{title}}" }, format_paused = { description = "Status format when MPD is paused", default = "mpdbitl (paused): {{artist}} — {{title}}" }, format_stopped = { description = "Status format when MPD is stoppedsong", default = "mpdbitl (stopped): {{artist}} — {{title}}" }, format_none = { description = "Status format when MPD is playlist is empty or MPD has reached " .. "the end of playlist and there's nothing else to play", default = "mpdbitl (not playing)" } } } mpdbitl_config_file = weechat.config_new(mpdbitl_config_file_name, "mpdbitl_config_reload", "") if mpdbitl_config_file == "" then return end for section_name, section_options in pairs(structure) do local section = weechat.config_new_section( mpdbitl_config_file, section_name, 0, 0, "", "", "", "", "", "", "", "", "", "") if section == "" then weechat.config_free(mpdbitl_config_file) return end for option, definition in pairs(section_options) do local lua_type = type(definition.default) if lua_type == "number" then mpdbitl_config[option] = weechat.config_new_option( mpdbitl_config_file, section, option, "integer", definition.description, "", (definition.min and definition.min or 0), (definition.max and definition.max or 0), definition.default, definition.default, 0, "", "", "", "", "", "") elseif lua_type == "boolean" then local default = definition.default and "on" or "off" mpdbitl_config[option] = weechat.config_new_option( mpdbitl_config_file, section, option, "boolean", definition.description, "", 0, 0, default, default, 0, "", "", "", "", "", "") elseif lua_type == "table" or lua_type == "string" then local default = definition.default if lua_type == "table" then default = table.concat( definition.default, (definition.separator and definition.separator or ",")) end mpdbitl_config[option] = weechat.config_new_option( mpdbitl_config_file, section, option, "string", definition.description, "", 0, 0, default, default, 0, "", "", "", "", "", "") end end end end function mpdbitl_config_reload(data, config_file) return weechat.config_reload(config_file) end function mpdbitl_config_read() return weechat.config_read(mpdbitl_config_file) end function mpdbitl_config_write() return weechat.config_write(mpdbitl_config_file) end function mpdbitl_msg(...) if arg and #arg > 0 then weechat.print("", string.format(unpack(arg))) end end function mpdbitl_connect() mpdbitl_sock = socket.tcp() mpdbitl_sock:settimeout(weechat.config_integer(mpdbitl_config.timeout), "t") local hostname = weechat.config_string(mpdbitl_config.hostname) local port = weechat.config_integer(mpdbitl_config.port) if not mpdbitl_sock:connect(hostname, port) then mpdbitl_msg("Could not connect to %s:%d", hostname, port) return false end local line = mpdbitl_sock:receive("*l") if not line then mpdbitl_msg("No response from MPD") return false end if not line:match("^OK MPD") then mpdbitl_msg("Unknown welcome message: %s", line) return false else local password = weechat.config_string(mpdbitl_config.password) if password and #password > 0 then password = password:gsub('\\', '\\\\') password = password:gsub('"', '\\"') local command = 'password "' .. password .. '"' if mpdbitl_send_command(command) then local response = mpdbitl_fetch_all_responses() if mpdbitl_error.message then mpdbitl_msg("MPD error: %s", mpdbitl_error.message) return false end end end return true end end function mpdbitl_disconnect() mpdbitl_send_command("close") mpdbitl_sock:close() end function mpdbitl_send_command(line) line = line .. "\n" local sent = mpdbitl_sock:send(line) return sent == #line end function mpdbitl_receive_single_response() local complete, key, value, _ local error = {} local line = mpdbitl_sock:receive("*l") if line then if line:match("^OK$") then complete = true elseif line:match("^ACK") then _, _, error.code, error.index, error.command, error.message = line:find("^ACK %[(%d+)@(%d+)%] {([^}]+)\} (.+)") complete = true else _, _, key, value = line:find("^([^:]+):%s(.+)") if key then key = string.gsub(key:lower(), "-", "_") end end end return key, value, complete, error end function mpdbitl_fetch_all_responses() local result = {} local complete, key, value repeat key, value, complete, mpdbitl_error = mpdbitl_receive_single_response() if key then result[key] = value end until complete if mpdbitl_error.message then mpdbitl_msg( "MPD Error %s (%s @ %u): %s", mpdbitl_error.code, mpdbitl_error.command, mpdbitl_error.index, mpdbitl_error.message) end return result end function mpdbitl_get_server_status() if mpdbitl_send_command("status") then return mpdbitl_fetch_all_responses() else return false end end function mpdbitl_get_current_song() if mpdbitl_send_command("currentsong") then local song = mpdbitl_fetch_all_responses() if song.time then local duration = tonumber(song.time) local hours = math.floor(duration / 3600) % 24 local minutes = math.floor(duration / 60) % 60 local seconds = duration % 60 song.time = string.format("%02d:%02d", minutes, seconds) if hours > 0 then song.time = string.format("%02d:%s", hours, song.time) end end return song else return false end end function mpdbitl_format_status_text(text, replacement) if not text or not replacement or #text < 1 or type(replacement) ~= "table" then return "" end return text:gsub("{{([^}]+)}}", function (key) if replacement[key] then return replacement[key] else return "" end end) end function mpdbitl_bar_item(data, item, window) return mpdbitl_status_text end function mpdbitl_escape_bitlbee_command_arg(arg) if type(arg) == 'number' then return arg elseif type(arg) == 'string' then return "'" .. arg:gsub("'", "\\'") .. "'" else return "''" end end function mpdbitl_catch_bitlbot_response(total_msg, modifier, msg_network, string) if not total_msg or total_msg == "" then return string end if type(total_msg) == "string" then total_msg = tonumber(total_msg) end if total_msg < 1 or mpdbitl_caught_messages >= total_msg then return string end local network = weechat.config_string(mpdbitl_config.network) if network ~= msg_network then return string end local parsed = weechat.info_get_hashtable( "irc_message_parse", {message = string}) if not parsed or type(parsed) ~= "table" then return string end local bitlbot = weechat.config_string(mpdbitl_config.bitlbot) if bitlbot ~= parsed.nick then return string end local expected_arg = string.format( "%s :status = `%s'", parsed.channel, mpdbitl_status_text) if parsed.arguments == expected_arg then mpdbitl_caught_messages = mpdbitl_caught_messages + 1 if mpdbitl_caught_messages >= total_msg then if mpdbitl_msg_hook and mpdbitl_msg_hook ~= "" then weechat.unhook(mpdbitl_msg_hook) end end return "" else return string end end function mpdbitl_change_bitlbee_status(data, remaining_calls) local network = weechat.config_string(mpdbitl_config.network) local channel = weechat.config_string(mpdbitl_config.channel) local buf_id = network .. "." .. channel local buffer = weechat.buffer_search("irc", buf_id) if buffer == "" then mpdbitl_msg("No buffer for %s", buf_id) return weechat.WEECHAT_RC_OK end local bitlbot = weechat.config_string(mpdbitl_config.bitlbot) if weechat.nicklist_search_nick(buffer, "", bitlbot) == "" then mpdbitl_msg("No such nick: %s", bitlbot) return weechat.WEECHAT_RC_ERROR end local change_status = false if mpdbitl_connect() then local server_status = mpdbitl_get_server_status() if server_status.state ~= mpdbitl_current_state or server_status.songid ~= mpdbitl_song_id then local format = "" if server_status.state == "play" then format = mpdbitl_config.format_playing elseif server_status.state == "pause" then format = mpdbitl_config.format_paused elseif server_status.state == "stop" then if not server_status.songid then format = mpdbitl_config.format_none else format = mpdbitl_config.format_stopped end else mpdbitl_msg("Unknown state: %s", server_status.state) mpdbitl_disconnect() return weechat.WEECHAT_RC_ERROR end change_status = true mpdbitl_current_state = server_status.state mpdbitl_song_id = server_status.songid mpdbitl_status_text = mpdbitl_format_status_text( weechat.config_string(format), mpdbitl_get_current_song()) end mpdbitl_disconnect() if change_status then local accounts = weechat.config_string(mpdbitl_config.accounts) local command_for_bitlbot = {} for account in accounts:gmatch("[^,]+") do local _, _, target = account:find("^@(.+)") if not target then command_for_bitlbot[#command_for_bitlbot + 1] = string.format( mpdbitl_status_command_normal, bitlbot, mpdbitl_escape_bitlbee_command_arg(account), mpdbitl_escape_bitlbee_command_arg(mpdbitl_status_text)) else weechat.command( buffer, string.format( mpdbitl_status_command_alternate, target, mpdbitl_status_text)) end end weechat.bar_item_update("mpdbitl_track") if #command_for_bitlbot > 0 then mpdbitl_caught_messages = 0 mpdbitl_msg_hook = weechat.hook_modifier( "irc_in2_PRIVMSG", "mpdbitl_catch_bitlbot_response", #command_for_bitlbot) for _, cmd in ipairs(command_for_bitlbot) do weechat.command(buffer, cmd) end end end return weechat.WEECHAT_RC_OK else return weechat.WEECHAT_RC_ERROR end end function mpdbitl_toggle() local current = weechat.config_boolean(mpdbitl_config.enable) local new_value = (current == 0 and 1 or 0) local result = weechat.config_option_set(mpdbitl_config.enable, new_value, 1) if new_value == 1 then mpdbitl_set_timer() else mpdbitl_unset_timer() end return weechat.WEECHAT_RC_OK end function mpdbitl_set_timer() if not mpdbitl_timer then mpdbitl_timer = weechat.hook_timer( 60 * 1000, 60, 0, "mpdbitl_change_bitlbee_status", "") end end function mpdbitl_unset_timer() if mpdbitl_timer then weechat.unhook(mpdbitl_timer) end end function mpdbitl_command(data, buffer, arg_string) local args = {} arg_string:gsub("([^ \t]+)", function (s) args[#args + 1] = s end) if #args < 1 then return weechat.WEECHAT_RC_OK end if args[1] == "toggle" then return mpdbitl_toggle() elseif args[1] == "change" then return mpdbitl_change_bitlbee_status() else mpdbitl_msg("Unknown command: %s", args[1]) return weechat.WEECHAT_RC_ERROR end end function mpdbitl_unload() mpdbitl_config_write() return weechat.WEECHAT_RC_OK end function mpdbitl_initialize() weechat.register( "mpdbitl", "rumia ", "1.2", "WTFPL", "Automatically change bitlbee status message into current MPD track", "mpdbitl_unload", "") mpdbitl_config_init() mpdbitl_config_read() weechat.bar_item_new("mpdbitl_track", "mpdbitl_bar_item", "") weechat.hook_command( "mpdbitl", "Change bitlbee status message into current MPD track", "toggle|change", "toggle: enable/disable script\n" .. "change: change bitlbee status immediately\n", "toggle || change", "mpdbitl_command", "") local enabled = weechat.config_boolean(mpdbitl_config.enable) if enabled == 1 then mpdbitl_set_timer() end end mpdbitl_initialize() weechat-scripts-20131007/lua/http_item.lua0000644000175000017500000000672412224522672017036 0ustar manumanu-- HTTP bar item, using lua patterns to get content -- -- Copyright 2013 Tor Hveem -- -- 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 . -- -- -- Usage: put [http_item] in your status bar items. (Or any other bar to your liking) -- "/set weechat.bar.status.items". -- local w = weechat SCRIPT_NAME = "http_item" SCRIPT_AUTHOR = "xt " SCRIPT_VERSION = "1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Bar item with HTTP source, using lua patterns to match content" BAR_ITEM_NAME = SCRIPT_NAME -- Settings settings = { url = 'http://weechat.org/info/stable/', pattern = '(%d+%.%d+%.%d+)', message_prefix = 'Latest WeeChat: ', message_postfix = '', message_color = 'default', interval = '5', -- poll every 5 minutes } -- other globals ITEM_TEXT = nil function http_bi_cb(data, item, window) -- Return the bar item string if ITEM_TEXT then return string.format('%s%s%s%s', w.config_get_plugin('message_prefix'), w.color(w.config_get_plugin('message_color')), ITEM_TEXT, w.config_get_plugin('message_postfix') ) end return '' end function http_bi_update() -- Function to manually update the bar item w.bar_item_update(BAR_ITEM_NAME) return w.WEECHAT_RC_OK end function debug(buf, str) -- helper function for debugging local debug = false if debug and str then w.print(buf, SCRIPT_NAME .. ': ' .. str) end return w.WEECHAT_RC_OK end function init_config() -- Set defaults for option, default_value in pairs(settings) do if w.config_is_set_plugin(option) == 0 then w.config_set_plugin(option, default_value) end end -- read options from weechat into our lua table for option, default_value in pairs(settings) do settings[option] = w.config_get_plugin(option) end return w.WEECHAT_RC_OK end function start_fetch() -- Get URL using weechat API for URL local url = w.config_get_plugin('url') -- 30 seconds timeout local timeout = 30*1000 debug('', url) w.hook_process('url:'..url, timeout, 'http_fetch_cb', '') return w.WEECHAT_RC_OK end function http_fetch_cb(data, command, return_code, out, err) if #out > 0 then out = out:match(w.config_get_plugin('pattern')) ITEM_TEXT = out debug('', ITEM_TEXT) -- Update bar item since we got new data w.bar_item_update(BAR_ITEM_NAME) end return w.WEECHAT_RC_OK end if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '') then init_config() -- create the bar item w.bar_item_new(BAR_ITEM_NAME, 'http_bi_cb', '') -- Initial fetch start_fetch() -- hook the fetch timer w.hook_timer( w.config_get_plugin('interval')*60*1000, 0, 0, 'start_fetch', '') end weechat-scripts-20131007/lua/text_effects.lua0000644000175000017500000000514312224522672017516 0ustar manumanu-- Copyright 2010 Vaughan Newton -- -- 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 = "text_effects" SCRIPT_AUTHOR = "Vaughan Newton " SCRIPT_VERSION = "1.1" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Adds effects to words surrounded by certain characters" local w = weechat w.register( SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", "" ) -- Effects effects = { ["*"] = "bold", ["_"] = "underline", } -- printf function function printf(buffer, fmt, ...) w.print(buffer, string.format(fmt, unpack(arg))) end -- Easy callback handling local nextCallbackID = 0 function callback(f) local name = "__callback_" .. nextCallbackID nextCallbackID = nextCallbackID + 1 _G[name] = f return name end -- Config config = { data = {} } setmetatable(config, { __index = function (tab, key) return w.config_string(tab.data[key]) end, }) -- Load config do config.file = w.config_new("text_effects", callback(function(data, file) return w.config_reload(file) end), "") if not config.file then return end config.look = w.config_new_section( config.file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") local c = config.data c.show_chars = w.config_new_option( config.file, config.look, "show_chars", "boolean", "Whether to show surrounding characters or not", "", 0, 0, "on", "on", 0, "", "", "", "", "", "") w.config_read(config.file) end -- Register modifier w.hook_modifier("weechat_print", callback(function(_, _, info, str) -- Add spaces to help pattern matching str = " " .. str .. " " local pattern = "(%s)([" for char, effect in pairs(effects) do pattern = pattern .. "%"..char end pattern = pattern .. "])([%w_]+)%2(%s)" str = str:gsub(pattern, function(sp1, char, word, sp2) local effect = effects[char] local c = (config.show_chars == "on") and char or "" return sp1 .. w.color(effect) .. c .. word .. c .. w.color("-"..effect) .. sp2 end) -- Remove spaces str = str:sub(2, -2) return str end), "") weechat-scripts-20131007/lua/oldswarner.lua0000644000175000017500000001036112224522673017212 0ustar manumanu-- Copyright 2013 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 -- 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 . -- --[[ This script will try to prevent you from posting URLs to a buffer that was recently posted, as recently as your weechat will remember lines. Usage: To get notice when trying to send old URL you will have to add a new item to your input bar. To override the default setting please run this command: /set weechat.bar.input.items "[input_prompt]+(away),[input_search],[input_paste],[input_olds],input_text" If you already have customized this setting, you will have to edit your own setting and add input_olds where you want it to be displayed. Changelog: version 2, 2013-09-21, xt * Use hdata instead of infolines to improve performance version 1, 2013-09-15, xt * initial version --]] SCRIPT_NAME = "oldswarner" SCRIPT_AUTHOR = "xt " SCRIPT_VERSION = "2" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Warn user if about to paste URL already existing in buffer" ITEM_NAME = 'input_olds' ITEM_TEXT = '' local w = weechat local patterns = { -- X://Y url "^(https?://%S+)", "^", "^", "^<(https?://%S+)>", "^(https?://%S+)>", "%f[%S](https?://%S+)", -- www.X.Y url "^(www%.[%w_-%%]+%.%S+)", "%f[%S](www%.[%w_-%%]+%.%S+)", } -- return a table containing all urls in a message function findURLs(message) local urls = {} local index = 1 for split in message:gmatch('%S+') do for i=1, #patterns do local _, count = split:gsub(patterns[i], function(url) table.insert(urls, url) end) if(count > 0) then index = index + 1 break end end end return urls end function is_url_in_buffer(buffer, url) lines = weechat.hdata_pointer(weechat.hdata_get('buffer'), buffer, 'own_lines') line = weechat.hdata_pointer(weechat.hdata_get('lines'), lines, 'first_line') hdata_line = weechat.hdata_get('line') hdata_line_data = weechat.hdata_get('line_data') while #line > 0 do data = weechat.hdata_pointer(hdata_line, line, 'data') message = weechat.hdata_string(hdata_line_data, data, 'message') if string.find(message, url) then return true end line = weechat.hdata_move(hdata_line, line, 1) end return false end function oldschecker(data, buffer, command) saved_input = weechat.buffer_get_string(buffer, "input") for _,url in pairs(findURLs(saved_input)) do if is_url_in_buffer(buffer, url) and not is_being_warned() then set_item_text() return weechat.WEECHAT_RC_OK_EAT end end clear_item_text() return w.WEECHAT_RC_OK end function set_item_text() message = 'URL already in buffer. Press enter again if you are sure' color = w.color(w.config_color(w.config_get('weechat.color.input_actions'))) ITEM_TEXT = string.format('%s%s%s', color, message, w.color('reset')) w.bar_item_update(ITEM_NAME) end function clear_item_text() ITEM_TEXT = '' w.bar_item_update(ITEM_NAME) end function input_olds_cb(data, item, window) return ITEM_TEXT end function is_being_warned() if ITEM_TEXT == '' then return false end return true end function p_init() if w.register( SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '') then w.hook_command_run('/input return', 'oldschecker', '') -- create the bar item w.bar_item_new(ITEM_NAME, 'input_olds_cb', '') end end p_init() weechat-scripts-20131007/ruby/0000755000175000017500000000000012224522674014527 5ustar manumanuweechat-scripts-20131007/ruby/mpdspam.rb0000644000175000017500000000416012224522673016515 0ustar manumanu# Author: Benedikt 'linopolus' Mueller # File: mpdspam.rb # weechat script to print the played song from mpd in the format '/me ♫ () # # ------------------------------------------------------------------------ # Copyright (c) 2009, Benedikt 'linopolus' Mueller # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY Benedikt Mueller ''AS IS'' AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ------------------------------------------------------------------------ # def weechat_init Weechat.register('mpdspam', 'linopolus', '1.0', 'BSD', 'print the played song from mpd in the format /me ♫ ()', '', '') Weechat.hook_command('mpdspam', 'display the currently played song of mpd', '', '', '', 'mpdspam', '') return Weechat::WEECHAT_RC_OK end def mpdspam(data, buffer, args) Weechat.command(Weechat.current_buffer, '/me ♫ ' + `mpc -p 23454 -f \'%artist% — %title% (%album%)\' | head -n 1`) end weechat-scripts-20131007/ruby/amqp_notify.rb0000644000175000017500000002776112224522673017416 0ustar manumanu# vim: set noet nosta sw=4 ts=4 : # # Mahlon E. Smith # http://www.martini.nu/ # (See below for LICENSE.) # # AMQP Notify # ----------- # # Catch private messages and highlights, relaying them onward to a # RabbitMQ AMQP exchange for client consumption. # # http://www.rabbitmq.com/ # # This may seem like a bit of overengineering, but when you have all # your chat/twitter/irc protocols going through Weechat on a remote # shell as I do, along with numerous client machines at different # locations, I found this to be a reliable way to make sure I /notice/ # when someone is trying to get my attention, and I'm not watching the # terminal at that exact moment. Works great with bitlbee! # # Writing a client to pull messages off the queue and send them to # growl/libnotify/dzen/sms/email/your-tv/whatever-your-heart-desires # is left as an exercise to the reader, but it should be as trivial as # receiving the message and unwrapping YAML. Fun! # # # Install instructions: # --------------------- # # This script requires the "Bunny" ruby module, available # from rubygems, and of course, your Weechat to be built with # ruby. # # Load into Weechat like any other plugin, after putting it into # your ~/.weechat/ruby directory: # # /ruby load amqp_notify.rb # # Options: # -------- # # plugins.var.ruby.amqp_notify.rabbitmq_host # # The hostname of the rabbitmq server/broker. # Default: Empty string # # plugins.var.ruby.amqp_notify.user # # Username credential for rabbitmq. # Default: Empty string # # plugins.var.ruby.amqp_notify.pass # # Password credential for rabbitmq. # Default: Empty string # # plugins.var.ruby.amqp_notify.vhost # # The virtual host within rabbitmq, if any. # Default: "/" # # plugins.var.ruby.amqp_notify.exchange_type # # What kind of exchange? direct|fanout|topic # Default: "fanout" # # The fanout type allows many different clients # to simultanously receive notifications. # # plugins.var.ruby.amqp_notify.exchange_name # # The name of the Weechat notification exchange. # Default: "chat-notify" # # plugins.var.ruby.amqp_notify.exchange_key # # A routing key, for 'topic' and 'direct' exchange types. # Default: Empty string # # plugins.var.ruby.amqp_notify.ignore_tags # # A comma separated list of message types to ignore # completely, regardless of away state. # Default: "irc_quit" # # plugins.var.ruby.amqp_notify.enabled # # A global on/off toggle. # Default: "off" # # plugins.var.ruby.amqp_notify.only_when_away # # Only relay messages to AMQP if you are set to /away. # Default: "on" # # # AMQP Payload # ------------ # # Highlighted message: # # {:type=>"channel", # :highlight=>true, # :message=>"Something said in #ruby-lang on my highlight list!", # :away=>false, # :channel=>"#ruby-lang", # :server=>"freenode", # :date=>"1294733587", # :tags=>["irc_privmsg", "notify_message", "log1"]} # # Private message: # # {:type=>"private", # :highlight=>false, # :message=>"Here we go, yo. So what's the scenario?", # :away=>false, # :channel=>"grangeromatic", # :server=>"bitlbee", # :date=>"1294733597", # :tags=>["irc_privmsg", "notify_private", "log1"]} # ### Convenience 'truth' module for Weechat config strings, because: ### ### self.enabled.true? ### ### reads a whole lot nicer than: ### ### Weechat.config_string_to_boolean(Weechat.config_get_plugin('enabled')) == "1" ### ### I resist the temptation to monkeypatch all of String during my ### time with Weechat. Heh. ### module Truthy def true? return Weechat.config_string_to_boolean( self.to_s ).to_i.zero? ? false : true end end ### The actual Weechat plugin. ### class AMQPNotify include Weechat DEBUG = false SIGNATURE = [ 'amqp_notify', 'Mahlon E. Smith', '0.1', 'BSD', 'Send private messages and highlights to an AMQP exchange.', 'weechat_unload', 'UTF-8' ] DEFAULT_OPTIONS = { :rabbitmq_host => 'localhost', :user => nil, :pass => nil, :vhost => '/', :exchange_type => 'fanout', :exchange_name => 'chat-notify', :exchange_key => nil, :ignore_tags => 'irc_quit', :enabled => 'off', :only_when_away => 'on' } ### Prepare configuration and set up initial communication with the AMQP exchange. ### def initialize @amqp = @exchange = nil DEFAULT_OPTIONS.each_pair do |option, value| # install default options if needed. # if Weechat.config_is_set_plugin( option.to_s ).zero? self.print_info "Setting value '%s' to %p" % [ option, value ] if DEBUG Weechat.config_set_plugin( option.to_s, value.to_s ) end # read in existing config values, attaching # them to instance variables. # val = Weechat.config_get_plugin( option.to_s ) val.extend( Truthy ) instance_variable_set( "@#{option}".to_sym, val ) self.class.send( :attr, option.to_sym, true ) end self.bind self.print_info "Initalized!" end # The RabbitMQ broker and publishing exchange. # attr :amqp, true attr :exchange, true ######################################################################## ### W E E C H A T H O O K S ######################################################################## ### Validate values for config changes, and take appropriate action ### on any changes that immediately require it. ### def config_changed( data, option, new_value ) option = option.match( /\.(\w+)$/ )[1] bounce_connection = false new_value.extend( Truthy ) case option # reset the connection if needed # when 'rabbitmq_host', 'user', 'pass', 'exchange_key', 'exchange_name' instance_variable_set( "@#{option}".to_sym, new_value ) bounce_connection = true # revert the setting change if the type is invalid, bounce # connection if it checks out. # when 'exchange_type' if %w[ direct topic fanout ].include?( new_value ) self.exchange_type = new_value bounce_connection = true else self.print_info "'%s' is not a valid exchange type." % [ new_value ] Weechat.config_set_plugin( option, self.exchange_type ) end # revert the setting change if the vhost doesn't begin with # a '/'. Otherwise, bounce the connection. # when 'vhost' if new_value =~ /^\// self.vhost = new_value bounce_connection = true else self.print_info "vhosts must begin with a slash (/)." Weechat.config_set_plugin( option, self.vhost ) end # Disconnect/reconnect to AMQP # when 'enabled' self.enabled = new_value new_value.true? ? self.bind : self.unbind # ... just change the setting, no validation/action needed. else instance_variable_set( "@#{option}".to_sym, new_value ) end # Cycle the connection with RabbitMQ with the updated settings. # if bounce_connection self.unbind self.bind end return WEECHAT_RC_OK end ### Process all incoming messages, filtering out anything we're not ### interested in seeing. ### def notify_msg( data, buffer, date, tags, visible, highlight, prefix, message ) return WEECHAT_RC_OK unless self.enabled.true? # Grab the channel metadata. data = {} %w[ away type channel server ].each do |meta| data[ meta.to_sym ] = Weechat.buffer_get_string( buffer, "localvar_#{meta}" ); end data[ :away ] = data[ :away ].empty? ? false : true # Are we currently marked as away? return WEECHAT_RC_OK if self.only_when_away.true? && ! data[ :away ] # Only bother with the message if it is a highlight, or a private message. return WEECHAT_RC_OK if highlight.to_i.zero? && data[ :type ] != 'private' # Are we specifically ignoring this message tag type? # ignored = self.ignore_tags.split( ',' ) tags = tags.split( ',' ) return WEECHAT_RC_OK unless ( ignored & tags ).empty? notify = { :highlight => ! highlight.to_i.zero?, :type => data[ :type ], :channel => data[ :channel ], :away => data[ :away ], :server => data[ :server ], :date => date, :tags => tags, :message => message } # Ship it off. # self.print_info "Message notification: %p" % [ notify ] if DEBUG self.exchange.publish( notify.to_yaml, :key => self.exchange_key ) return WEECHAT_RC_OK rescue => err self.disable "%s, %s" % [ err.class.name, err.message ] return WEECHAT_RC_OK end ######################################################################## ### I N S T A N C E M E T H O D S ######################################################################## ### Connect to the RabbitMQ broker. ### def bind return unless self.enabled.true? self.print_info "Attempting connection to %s" % [ self.rabbitmq_host ] self.amqp = Bunny.new( :host => self.rabbitmq_host, :user => self.user, :pass => self.pass, :vhost => self.vhost ) self.amqp.start self.exchange = self.amqp.exchange( self.exchange_name, :type => self.exchange_type ) rescue => err self.print_info "Unable to connect to AMQP: %s, %s" % [ err.class.name, err.message ] self.amqp = nil end ### Disconnect from the RabbitMQ broker. ### def unbind return if self.amqp.nil? self.amqp.stop end ### Disable the plugin on repeated error. ### TODO: Set a timer to attempt a re-connect? ### def disable( reason ) self.print_info "Disabling plugin due to error: %s" % [ reason ] Weechat.config_set_plugin( 'enabled', 'off' ) end ######### protected ######### ### Quick wrapper for sending info messages to the weechat main buffer. ### def print_info( msg ) Weechat.print '', "%sAMQP\t%s" % [ Weechat.color('yellow'), msg ] end end ### Weechat entry point. ### def weechat_init require 'rubygems' require 'bunny' require 'yaml' Weechat::register *AMQPNotify::SIGNATURE $amqp = AMQPNotify.new Weechat.hook_print( '', '', '', 1, 'notify_msg', '' ) Weechat.hook_config( 'plugins.var.ruby.amqp_notify.*', 'config_changed', '' ) return Weechat::WEECHAT_RC_OK rescue LoadError => err Weechat.print '', "amqp_notify: %s, %s\n$LOAD_PATH: %p" % [ err.class.name, err.message, $LOAD_PATH ] Weechat.print '', 'amqp_notify: Unable to initialize due to missing dependencies.' return Weechat::WEECHAT_RC_ERROR end ### Hook for manually unloading this script. ### def weechat_unload $amqp.unbind return Weechat::WEECHAT_RC_OK end ### Allow Weechat namespace callbacks to forward to the AMQPNotify object. ### require 'forwardable' extend Forwardable def_delegators :$amqp, :notify_msg, :config_changed __END__ __LICENSE__ Copyright (c) 2011, Mahlon E. Smith All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author, nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. weechat-scripts-20131007/ruby/weefish.rb0000644000175000017500000002064012224522673016507 0ustar manumanu## # weefish.rb # # Copyright (c) 2010 by Tobias Petersen # # 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 . # # FiSH encryption / decryption for Weechat # based on blowfish, it is compatible with other # FiSH scripts created for mIRC, irssi and XChat # # Thanks to someone without a name, for creating the FiSH script in ruby # No DH1080 key exchange, only manual key, sorry! # # Usage: # Set key for a given nick/channel, or the active nick/channel: # /setkey [nick/channel] # Delete key for a given nick/channel, or the active nick/channel: # /delkey [nick/channel] # # History: # 2012-02-08 bazerka # version 0.4: fix in_privmsg to work with user targeted privmsg. # bypass decrypting in_privmsg when no key is found. # 2011-03-08 tp # version 0.3: fixed crypt/blowfish bug for ruby >= 1.9 # 2010-09-06, tp # version 0.2: fixed some message printing # 2010-09-05, tp # version 0.1: initial release require "crypt/blowfish" def message buffer, message Weechat.print buffer, "#{Weechat.prefix('error')}#{Weechat.color('bold')}weefish:#{Weechat.color('-bold')} #{message}" end def weechat_init Weechat.register "weefish", "Tobias Petersen", "0.4", "GPL3", "FiSH encryption/decryption", "", "" Weechat.hook_modifier "irc_in_privmsg", "in_privmsg", "" Weechat.hook_modifier "irc_out_privmsg", "out_privmsg", "" Weechat.hook_command "setkey", "Set the encryption key for the active nick/channel", "[nick/channel] ", "", "", "setkey", "" Weechat.hook_command "delkey", "Delete the encryption key for the active nick/channel", "[nick/channel] ", "", "", "delkey", "" return Weechat::WEECHAT_RC_OK end if RUBY_VERSION.to_f >= 1.9 module ::Crypt class Blowfish def setup_blowfish @sBoxes = Array.new(4) { |i| INITIALSBOXES[i].clone } @pArray = INITIALPARRAY.clone keypos = 0 0.upto(17) { |i| data = 0 4.times { data = ((data << 8) | @key[keypos].ord) % ULONG keypos = (keypos.next) % @key.length } @pArray[i] = (@pArray[i] ^ data) % ULONG } l = 0 r = 0 0.step(17, 2) { |i| l, r = encrypt_pair(l, r) @pArray[i] = l @pArray[i+1] = r } 0.upto(3) { |i| 0.step(255, 2) { |j| l, r = encrypt_pair(l, r) @sBoxes[i][j] = l @sBoxes[i][j+1] = r } } end end end end def setkey data, buffer, key network, channel = Weechat.buffer_get_string(buffer, "name").split ".", 2 unless key.empty? if key.scan(" ").length == 0 if network == "server" message buffer, "No active nick/channel. Usage: /setkey " else message buffer, "Key for #{channel} (#{network}) successfully set!" Weechat.config_set_plugin "key.#{network}.#{channel}", key end else network = channel if network == "server" channel, key = key.split " ", 2 message buffer, "Key for #{channel} (#{network}) successfully set!" Weechat.config_set_plugin "key.#{network}.#{channel}", key end else message buffer, "No parameters. Usage: /setkey [nick/channel] " end end def delkey data, buffer, string network, channel = Weechat.buffer_get_string(buffer, "name").split ".", 2 if string.empty? if network == "server" message buffer, "No active nick/channel. Usage: /delkey " else Weechat.config_unset_plugin "key.#{network}.#{channel}" message buffer, "Key for #{channel} (#{network}) successfully deleted!" end else network = channel if network == "server" Weechat.config_unset_plugin "key.#{network}.#{string.split.first}" message buffer, "Key for #{string.split.first} (#{network}) successfully deleted!" end end def in_privmsg data, signal, server, args if args =~ /^(:(.*?)!.*? PRIVMSG (.*?) :)(\+OK|mcps) (.*)$/ # If the PRIVMSG target ($3) is our current nick, we need the source nick ($2) to select the key # config variable. Otherwise, assume it's a channel and use that to select the key instead. selector = $3 == Weechat.info_get("irc_nick", server) ? $2 : $3 key = Weechat.config_string Weechat.config_get("plugins.var.ruby.weefish.key.#{server}.#{selector}") # If we couldn't find a key, don't attempt to decrypt the message as Crypt::Blowfish will raise an # invalid key length error on initialisation. unless key.empty? fish = IRC::FiSH.new key if decrypted = fish.decrypt($5) return $1+decrypted end end end return args end def out_privmsg data, signal, server, args if args =~ /^(PRIVMSG (.*?) :)(.*)$/ key = Weechat.config_string Weechat.config_get("plugins.var.ruby.weefish.key.#{server}.#{$2}") unless key.empty? fish = IRC::FiSH.new key return "#{$1}+OK #{fish.encrypt $3}" else return args end end return args end module IRC class BadInputError < StandardError; end class MBase64 B64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" def self.decode encoded return nil if not encoded.length % 12 == 0 decoded = String.new k = -1 while (k < encoded.length - 1) do right = 0 left = 0 (0..5).each do |i| k = k + 1 right |= B64.index(encoded[k]) << (i * 6) end (0..5).each do |i| k = k + 1 left |= B64.index(encoded[k]) << (i * 6) end (0..3).each do |i| decoded += ((left & (0xFF << ((3 - i) * 8))) >> ((3 - i) * 8)).chr end (0..3).each do |i| decoded += ((right & (0xFF << ((3 - i) * 8))) >> ((3 - i) * 8)).chr end end return decoded end def self.encode decoded if not decoded.length % 8 == 0 raise IRC::BadInputError, "can only encode strings which are a multiple of 8 characters." end encoded = String.new k = -1 while (k < decoded.length - 1) do k = k.next left = (decoded[k].ord << 24) k = k.next left += (decoded[k].ord << 16) k = k.next left += (decoded[k].ord << 8) k = k.next left += decoded[k].ord k = k.next right = (decoded[k].ord << 24) k = k.next right += (decoded[k].ord << 16) k = k.next right += (decoded[k].ord << 8) k = k.next right += decoded[k].ord (0..5).each do encoded += B64[right & 0x3F].chr right = right >> 6 end (0..5).each do encoded += B64[left & 0x3F].chr left = left >> 6 end end return encoded end end class FiSH def initialize key @blowfish = Crypt::Blowfish.new key end def encrypt text text = pad(text, 8) result = "" num_block = text.length / 8 num_block.times do |n| block = text[n*8..(n+1)*8-1] enc = @blowfish.encrypt_block(block) result += MBase64.encode(enc) end return result end def decrypt text return nil if not text.length % 12 == 0 result = "" num_block = (text.length / 12).to_i num_block.times do |n| block = MBase64.decode( text[n*12..(n+1)*12-1] ) result += @blowfish.decrypt_block(block) end return result.gsub /\0*$/, "" end private def pad text, n=8 pad_num = n - (text.length % n) if pad_num > 0 and pad_num != n pad_num.times { text += 0.chr } end return text end end end weechat-scripts-20131007/ruby/challengeauth.rb0000644000175000017500000000723012224522674017662 0ustar manumanu# Copyright (c) 2013 Dominik Honnef # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # History: # 2013-04-20, Dominik Honnef # version 0.0.1: initial version require "openssl" QBot = "Q@CServe.quakenet.org" QBotHost = "Q!TheQBot@CServe.quakenet.org" Request = Struct.new(:username, :hash) def weechat_init @requests = {} Weechat.register("challengeauth", "Dominik Honnef", "0.0.1", "MIT", "Securely authenticate with QuakeNet by using CHALLENGEAUTH", "", "") Weechat.hook_command("challengeauth", "Authenticate with Q using CHALLENGEAUTH", "[username] [password]", "", "", "challengeauth", "") Weechat.hook_modifier("irc_in_notice", "challenge_notice", "") return Weechat::WEECHAT_RC_OK end def calculate_q_hash(username, hash, challenge) username = username.tr("A-Z[]\\\\^", "a-z{}|~") key = OpenSSL::Digest::SHA256.hexdigest("#{username}:#{hash}") return OpenSSL::HMAC.hexdigest("SHA256", key, challenge) end def get_server_buffer(server) Weechat.buffer_search("irc", "server." + server) end def challengeauth(data, buffer, args) plugin = Weechat.buffer_get_string(buffer, "localvar_plugin") if plugin != "irc" Weechat.print(buffer, "/challengeauth only works for IRC buffers.") return Weechat.WEECHAT_RC_ERROR end server = Weechat.buffer_get_string(buffer, "localvar_server") args = args.split(" ", 2) username = args[0] password = args[1] hash = OpenSSL::Digest::SHA256.hexdigest(password[0, 10]) @requests[server] = Request.new(username, hash) server_buffer = get_server_buffer(server) Weechat.print(server_buffer, "Authenticating as #{username}...") Weechat.command(server_buffer, "/quote PRIVMSG #{QBot} :CHALLENGE") return Weechat::WEECHAT_RC_OK end def challenge_notice(modifier, data, server, line) return line unless @requests.has_key?(server) parts = line.split(" ") return line unless parts.size > 5 host = parts[0][1..-1] command = parts[3][1..-1] challenge = parts[4] return line if host != QBotHost || command != "CHALLENGE" request = @requests[server] response = calculate_q_hash(request.username, request.hash, challenge) server_buffer = get_server_buffer(server) Weechat.print(server_buffer, "Sending challengeauth for #{request.username}...") Weechat.command(server_buffer, "/quote PRIVMSG %s :CHALLENGEAUTH %s %s HMAC-SHA-256" % [QBot, request.username, response]) @requests.delete(server) return line end weechat-scripts-20131007/ruby/gntp_notify.rb0000644000175000017500000000330012224522674017410 0ustar manumanu# Author: Justin Anderson # Email: jandersonis@gmail.com # Homepage: https://github.com/tinifni/gntp-notify # Version: 1.1 # License: GPL3 # Depends on ruby_gntp (https://github.com/snaka/ruby_gntp) require 'rubygems' require 'ruby_gntp' def weechat_init Weechat.register("gntp_notify", "Justin Anderson", "1.1", "GPL3", "GNTP Notify: Growl notifications using ruby_gntp.", "", "") hook_notifications @growl = GNTP.new("Weechat") @growl.register({ :notifications => [{:name => "Private", :enabled => true}, {:name => "Highlight", :enabled => true}] }) return Weechat::WEECHAT_RC_OK end def hook_notifications Weechat.hook_signal("weechat_pv", "show_private", "") Weechat.hook_signal("weechat_highlight", "show_highlight", "") end def unhook_notifications(data, signal, message) Weechat.unhook(show_private) Weechat.unhook(show_highlight) end def show_private(data, signal, message) message[0..1] == '--' ? sticky = false : sticky = true show_notification("Private", "Weechat Private Message", message, sticky) return Weechat::WEECHAT_RC_OK end def show_highlight(data, signal, message) message[0..1] == '--' ? sticky = false : sticky = true show_notification("Highlight", "Weechat", message, sticky) return Weechat::WEECHAT_RC_OK end def show_notification(name, title, message, sticky = true) @growl.notify({ :name => name, :title => title, :text => message, :icon => "https://github.com/tinifni/gntp-notify/raw/master/weechat.png", :sticky => sticky }) end weechat-scripts-20131007/ruby/xmms2.rb0000644000175000017500000000542412224522673016126 0ustar manumanu# -*- coding: utf-8 -*- # # Copyright (c) 2009, 2011 Łukasz P. Michalik # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2, 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. # # # This plugin requires Xmms2 ruby bindings to be installed. 'format' # configuration variable specifies how metadata is displayed. # Default_format is a good example, but don't abuse the syntax as you # might get garbage. It works with any key found in medialib for a # current ID (see `xmms2 info` for a list); non-existing keys are # ignored. # require 'xmmsclient' Default_format = "[np] ${title} - ${artist}" $xmms = nil def weechat_init Weechat.register('xmms2', 'Łukasz P. Michalik ', '0.2', 'GPL2', 'Xmms2 interaction plugin', '', '') Weechat.hook_command('xmms2', 'Trigger a /me currently playing info', '', '', '', 'xmms2', '') if (Weechat.config_get_plugin("format").empty?) Weechat.config_set_plugin("format", Default_format) end return Weechat::WEECHAT_RC_OK end def xmms2(data, buffer, args) if not $xmms $xmms = Xmms::Client.new('weechat') begin $xmms.connect(ENV['XMMS_PATH']) rescue Xmms::Client::ClientError Weechat.print("", "Failed to connect to XMMS2 daemon.") return Weechat::WEECHAT_RC_ERROR end end begin id = $xmms.playback_current_id.wait.value if (id == 0) Weechat.print(buffer, "Nothing is played!") return Weechat::WEECHAT_RC_OK end rescue Xmms::Rescue::ValueError Weechat.print(buffer, "Error retrieving current ID!") return Weechat::WEECHAT_RC_ERROR end begin info = $xmms.medialib_get_info(id).wait.value.to_propdict reg = /\$\{(\w+)\}/ format = Weechat.config_get_plugin("format") out = "" while m = reg.match(format) out << m.pre_match format = m.post_match key = m[1].intern if info.has_key? key out << info[key] end end rescue Xmms::Result::ValueError puts 'There was an error retrieving mediainfo for the current ID.' end Weechat.command(buffer, "/me #{out}") return Weechat::WEECHAT_RC_OK end weechat-scripts-20131007/ruby/hilites.rb0000644000175000017500000001072512224522673016521 0ustar manumanu# # HILITES # ruby script to display Weechat hilights in dzen. # also beeps # # # Author: Christian Brassat, aka. crshd # Email: christian@crshd.cc # # # Copyright (c) 2011 Christian Brassat # # 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. # # # USAGE: # First you need to create a FIFO file for the script to write to with # mkfifo $HOME/.weechat/pipe # # Then you can start dzen with something like this: # tail -f /home/crshd/.weechat/pipe | \ # dzen2 -ta l -tw 145 -h 17 -l 6 -y 1280 -bg "#151515" -fn "Pragmata:size=8" # SCRIPT_NAME = 'hilites' SCRIPT_AUTHOR = 'Christian Brassat ' SCRIPT_DESC = 'Send highlights in channels to a named pipe' SCRIPT_VERSION = '0.1' SCRIPT_LICENSE = 'MIT' DEFAULTS = { 'pipe_path' => "#{ENV['HOME']}/.weechat/pipe", 'beep_on_hilight' => "false", 'beep_on_private' => "true", 'beep_file' => "#{ENV['HOME']}/.weechat/beep.ogg", 'color1' => "435d65", 'color2' => "6e98a4", 'color3' => "2554a4", 'color4' => "909090", 'color5' => "606060", 'color6' => "d92918" } def weechat_init Weechat.register SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", "" DEFAULTS.each_pair { |option, def_value| cur_value = Weechat.config_get_plugin(option) if cur_value.nil? || cur_value.empty? Weechat.config_set_plugin(option, def_value) end } Weechat.hook_print("", "notify_message", "", 1, "hilite", "") Weechat.hook_print("", "notify_private", "", 1, "private", "") return Weechat::WEECHAT_RC_OK end def beep IO.popen("mplayer #{Weechat.config_get_plugin("beep_file")}") end def hilite( data, buffer, date, tags, visible, highlight, prefix, message ) if highlight == "1" data = {} %w[ away type channel server ].each do |meta| data[ meta.to_sym ] = Weechat.buffer_get_string( buffer, "localvar_#{meta}" ); end data[:away] = data[:away].empty? ? false : true if data[:type] == "channel" timestamp = Time.at(date.to_i).strftime("%H:%M") pipe = open( Weechat.config_get_plugin('pipe_path'), "w") pipe.printf("^tw()^fg(#%s) %s ^fg(#%s) %s \n %s ^fg(#%s)< %s > ^fg(#%s) %s ^fg(#%s) %s\n", Weechat.config_get_plugin("color1"), timestamp, Weechat.config_get_plugin("color2"), data[:channel], timestamp, Weechat.config_get_plugin("color3"), prefix, Weechat.config_get_plugin("color4"), data[:channel], Weechat.config_get_plugin("color5"), message ) pipe.close beep if Weechat.config_get_plugin("beep_on_hilight") == "true" end end return Weechat::WEECHAT_RC_OK end def private( data, buffer, date, tags, visible, highlight, prefix, message ) data = {} %w[ away type channel server ].each do |meta| data[ meta.to_sym ] = Weechat.buffer_get_string( buffer, "localvar_#{meta}" ); end data[:away] = data[:away].empty? ? false : true unless data[:channel] == data[:server] timestamp = Time.at(date.to_i).strftime("%H:%M") pipe = open( Weechat.config_get_plugin('pipe_path'), "w") pipe.printf("^tw()^fg(#%s) %s ^fg(#%s) %s \n %s ^fg(#%s)< %s > ^fg(#%s) %s ^fg(#%s) %s\n", Weechat.config_get_plugin("color1"), timestamp, Weechat.config_get_plugin("color6"), data[:channel], timestamp, Weechat.config_get_plugin("color6"), data[:channel], Weechat.config_get_plugin("color4"), "Private Message", Weechat.config_get_plugin("color5"), message ) pipe.close beep if Weechat.config_get_plugin("beep_on_private") == "true" end return Weechat::WEECHAT_RC_OK end # vim: set ts=2 sw=2 tw=0 : weechat-scripts-20131007/ruby/myuptime.rb0000644000175000017500000000176512224522673016735 0ustar manumanu# ================================================================================================== # myuptime.rb (c) April 2006 by David DEMONCHY (fusco) # # port to WeeChat 0.3.0 by Benjamin Neff (SuperTux88) # # Licence : GPL v2 # Description : Sends machine uptime to current channel # Syntax : /myuptime # => uptime : 00:16:58 up 11:09, 4 users, load average: 0.05, 0.21, 0.29 # # ================================================================================================== def weechat_init Weechat.register('myuptime', 'David DEMONCHY (fusco) ', '0.2', 'GPL2', 'Sends machine uptime to current channel', '', '') Weechat.hook_command('myuptime', 'Sends machine uptime to current channel', '', '', '', 'myuptime', '') return Weechat::WEECHAT_RC_OK end def myuptime(data, buffer, args) Weechat.command(buffer, "uptime "+ `hostname`.chomp + ":" + `uptime`.chomp) return Weechat::WEECHAT_RC_OK end weechat-scripts-20131007/ruby/zmq_notify.rb0000644000175000017500000002335412224522674017262 0ustar manumanu# vim: set noet nosta sw=4 ts=4 : # # Mahlon E. Smith # http://www.martini.nu/ # (See below for LICENSE.) # # ZMQ Notify # ---------- # # Catch private messages and highlights, relaying them onward to a # ZeroMQ publisher, for subscriber consumption. # # Writing a client to pull messages off the queue and send them to # growl/libnotify/dzen/sms/email/your-tv/whatever-your-heart-desires # is left as an exercise to the reader, but it should be as trivial as # receiving the message and unwrapping YAML. Fun! # # ctx = ZMQ::Context.new # zmq = ctx.socket( ZMQ::SUB ) # zmq.connect( "tcp://example.com:2428" ) # zmq.setsockopt( ZMQ::SUBSCRIBE, '' ) # # loop do # pp YAML.load( zmq.recv ) # end # # # # Install instructions: # --------------------- # # This script requires the "zmq" ruby module, available # from rubygems, and of course, your Weechat to be built with # ruby. # # Load into Weechat like any other plugin, after putting it into # your ~/.weechat/ruby directory: # # /ruby load zmq_notify.rb # # Options: # -------- # # plugins.var.ruby.zmq_notify.endpoint # # The ZMQ connection endpoint. The socket type is always PUB. # Default: tcp://*:2428 # # plugins.var.ruby.zmq_notify.ignore_tags # # A comma separated list of message types to ignore # completely, regardless of away state. # Default: "irc_quit" # # plugins.var.ruby.zmq_notify.enabled # # A global on/off toggle. # Default: "off" # # plugins.var.ruby.zmq_notify.only_when_away # # Only relay messages to the ZMQ socket if you are set to /away. # Default: "on" # # # ZMQ message payload # ------------------- # # Highlighted message: # # {:type=>"channel", # :highlight=>true, # :message=>"Something said in #ruby-lang on my highlight list!", # :away=>false, # :channel=>"#ruby-lang", # :server=>"freenode", # :date=>"1294733587", # :tags=>["irc_privmsg", "notify_message", "log1"]} # # Private message: # # {:type=>"private", # :highlight=>false, # :message=>"Here we go, yo. So what's the scenario?", # :away=>false, # :channel=>"grangeromatic", # :server=>"bitlbee", # :date=>"1294733597", # :tags=>["irc_privmsg", "notify_private", "log1"]} # ### Convenience 'truth' module for Weechat config strings, because: ### ### self.enabled.true? ### ### reads a whole lot nicer than: ### ### Weechat.config_string_to_boolean(Weechat.config_get_plugin('enabled')) == "1" ### ### I resist the temptation to monkeypatch all of String during my ### time with Weechat. Heh. ### module Truthy def true? return Weechat.config_string_to_boolean( self.to_s ).to_i.zero? ? false : true end end ### The actual Weechat plugin. ### class ZMQNotify include Weechat DEBUG = false SIGNATURE = [ 'zmq_notify', 'Mahlon E. Smith', '0.1', 'BSD', 'Send private messages and highlights to a ZMQ socket.', 'weechat_unload', 'UTF-8' ] DEFAULT_OPTIONS = { :endpoint => 'tcp://*:2428', :ignore_tags => 'irc_quit', :enabled => 'off', :only_when_away => 'on' } ### Prepare configuration and bind a ZMQ endpoint. ### def initialize @zmq = @ctx = nil DEFAULT_OPTIONS.each_pair do |option, value| # install default options if needed. # if Weechat.config_is_set_plugin( option.to_s ).zero? self.print_info "Setting value '%s' to %p" % [ option, value ] if DEBUG Weechat.config_set_plugin( option.to_s, value.to_s ) end # read in existing config values, attaching # them to instance variables. # val = Weechat.config_get_plugin( option.to_s ) val.extend( Truthy ) instance_variable_set( "@#{option}".to_sym, val ) self.class.send( :attr, option.to_sym, true ) end self.bind self.print_info "Initalized!" end # The ZMQ socket and context. # attr :zmq, true attr :ctx, true ######################################################################## ### W E E C H A T H O O K S ######################################################################## ### Validate values for config changes, and take appropriate action ### on any changes that immediately require it. ### def config_changed( data, option, new_value ) option = option.match( /\.(\w+)$/ )[1] bounce_connection = false new_value.extend( Truthy ) case option # reset the connection if needed # when 'endpoint' instance_variable_set( "@#{option}".to_sym, new_value ) bounce_connection = true # Disconnect/reconnect to endpoint # when 'enabled' self.enabled = new_value new_value.true? ? self.bind : self.unbind # ... just change the setting, no validation/action needed. else instance_variable_set( "@#{option}".to_sym, new_value ) end # Refresh the endpoint connection. # if bounce_connection self.unbind self.bind end return WEECHAT_RC_OK end ### Process all incoming messages, filtering out anything we're not ### interested in seeing. ### def notify_msg( data, buffer, date, tags, visible, highlight, prefix, message ) return WEECHAT_RC_OK unless self.enabled.true? # Grab the channel metadata. data = {} %w[ away type channel server ].each do |meta| data[ meta.to_sym ] = Weechat.buffer_get_string( buffer, "localvar_#{meta}" ); end data[ :away ] = data[ :away ].empty? ? false : true # Are we currently marked as away? return WEECHAT_RC_OK if self.only_when_away.true? && ! data[ :away ] # Only bother with the message if it is a highlight, or a private message. return WEECHAT_RC_OK if highlight.to_i.zero? && data[ :type ] != 'private' # Are we specifically ignoring this message tag type? # ignored = self.ignore_tags.split( ',' ) tags = tags.split( ',' ) return WEECHAT_RC_OK unless ( ignored & tags ).empty? notify = { :highlight => ! highlight.to_i.zero?, :type => data[ :type ], :channel => data[ :channel ], :away => data[ :away ], :server => data[ :server ], :date => date, :tags => tags, :message => message } # Ship it off. # self.print_info "Message notification: %p" % [ notify ] if DEBUG self.zmq.send( notify.to_yaml ) return WEECHAT_RC_OK rescue => err self.disable "%s, %s" % [ err.class.name, err.message ] return WEECHAT_RC_OK end ######################################################################## ### I N S T A N C E M E T H O D S ######################################################################## ### Instantiate a ZMQ endpoint. ### def bind return unless self.enabled.true? self.print_info "Setting up endpoint at %s" % [ self.endpoint ] self.ctx = ZMQ::Context.new if self.ctx.nil? self.zmq = self.ctx.socket( ZMQ::PUB ) self.zmq.bind( self.endpoint ) rescue => err self.print_info "Unable to create endpoint: %s, %s" % [ err.class.name, err.message ] self.zmq = nil end ### Tear down the ZMQ endpoint. ### def unbind return if self.zmq.nil? self.zmq.close end ### Disable the plugin on repeated error. ### TODO: Set a timer to attempt a re-connect? ### def disable( reason ) self.print_info "Disabling plugin due to error: %s" % [ reason ] Weechat.config_set_plugin( 'enabled', 'off' ) end ######### protected ######### ### Quick wrapper for sending info messages to the weechat main buffer. ### def print_info( msg ) Weechat.print '', "%sZMQ\t%s" % [ Weechat.color('yellow'), msg ] end end ### Weechat entry point. ### def weechat_init require 'rubygems' require 'zmq' require 'yaml' Weechat::register *ZMQNotify::SIGNATURE $zmq = ZMQNotify.new Weechat.hook_print( '', '', '', 1, 'notify_msg', '' ) Weechat.hook_config( 'plugins.var.ruby.zmq_notify.*', 'config_changed', '' ) return Weechat::WEECHAT_RC_OK rescue LoadError => err Weechat.print '', "zmq_notify: %s, %s\n$LOAD_PATH: %p" % [ err.class.name, err.message, $LOAD_PATH ] Weechat.print '', 'zmq_notify: Unable to initialize due to missing dependencies.' return Weechat::WEECHAT_RC_ERROR end ### Hook for manually unloading this script. ### def weechat_unload $zmq.unbind return Weechat::WEECHAT_RC_OK end ### Allow Weechat namespace callbacks to forward to the ZMQNotify object. ### require 'forwardable' extend Forwardable def_delegators :$zmq, :notify_msg, :config_changed __END__ __LICENSE__ Copyright (c) 2011, Mahlon E. Smith All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author, nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. weechat-scripts-20131007/ruby/input_lock.rb0000644000175000017500000001432612224522673017230 0ustar manumanu# vim: set noet nosta sw=4 ts=4 : # # Mahlon E. Smith # http://www.martini.nu/ # (See below for LICENSE.) # # Input Lock # ---------- # # Global toggle for disabling text input. Commands are still allowed. # This is a simple "don't look stupid" protector, if you're entering # text and don't realize your focus is on the wrong window. ;) # # # Install instructions: # --------------------- # # Load into Weechat like any other plugin, after putting it into # your ~/.weechat/ruby directory: # # /ruby load input_lock.rb # # It's strongly recommended to make some keybindings to easily toggle # the state of the lock. Here's what I use: ('i' for input!) # # /key bind meta-i /set plugins.var.ruby.input_lock.enabled off # /key bind meta-I /set plugins.var.ruby.input_lock.enabled on # # You can add notification of the current state by adding '[il]' to # your weechat.bar.input.items. # # # Options: # -------- # # plugins.var.ruby.input_lock.enable # # The state of the lock. # Default: "off" # ### Convenience 'truth' module for Weechat config strings. ### module Truthy def true? return Weechat.config_string_to_boolean( self.to_s ).to_i.zero? ? false : true end end ### The Weechat plugin. ### class InputLock include Weechat DEBUG = false SIGNATURE = [ 'input_lock', 'Mahlon E. Smith', '0.1', 'BSD', 'Reject all text input, to avoid accidental public stupidity.', '', '' ] DEFAULT_OPTIONS = { :enabled => 'off' } ### Prepare configuration. ### def initialize DEFAULT_OPTIONS.each_pair do |option, value| # install default options if needed. # if Weechat.config_is_set_plugin( option.to_s ).zero? self.print_info "Setting value '%s' to %p" % [ option, value ] if DEBUG Weechat.config_set_plugin( option.to_s, value.to_s ) end # read in existing config values, attaching # them to instance variables. # val = Weechat.config_get_plugin( option.to_s ) val.extend( Truthy ) instance_variable_set( "@#{option}".to_sym, val ) self.class.send( :attr, option.to_sym, true ) end Weechat.bar_item_new( 'il', 'il_bar_item', '' ) end ######################################################################## ### W E E C H A T H O O K S ######################################################################## ### Validate values for config changes, and take appropriate action ### on any changes that immediately require it. ### ### (This is overcomplex for the single 'enable toggle' case, but ### leaving the logic in place on the likely chance there will be ### added future options.) ### def config_changed( data, option, new_value ) option = option.match( /\.(\w+)$/ )[1] new_value.extend( Truthy ) case option when 'enabled' self.enabled = new_value self.update_bar self.print_info "Setting enabled to %p" % [ new_value ] if DEBUG end return WEECHAT_RC_OK end ### Decide whether or not to allow entered text into the input buffer. ### def input_lock( data, modifier, buffer, string ) # do nothing if not enabled return string unless self.enabled.true? # text in the weechat buffer is always allowed buf_plugin = Weechat.buffer_get_string( buffer, "localvar_plugin" ); buf_name = Weechat.buffer_get_string( buffer, "localvar_name" ); return string if buf_plugin == 'core' && buf_name == 'weechat' go_running = Weechat.info_get( "go_running", "" ); return string if go_running == '1' # allow commands to get through, everything else is squashed! return string.index('/').nil? ? '' : string end ### Refresh the lock state in the input bar. ### def update_bar( data=nil, item=nil, window=nil ) Weechat.bar_item_update( 'il' ) return WEECHAT_RC_OK end ### The content of the 'il' bar item. ### def il_bar_item( data, item, window ) return self.enabled.true? ? Weechat.color('red') + 'LOCK' + Weechat.color('') : '' end ######### protected ######### ### Quick wrapper for sending info messages to the weechat main buffer. ### def print_info( msg ) Weechat.print '', "%sLOCK\t%s" % [ Weechat.color('yellow'), msg ] end end ### Weechat entry point. ### def weechat_init Weechat::register *InputLock::SIGNATURE $lock = InputLock.new Weechat.hook_signal( 'input_text_changed', 'update_bar', '' ) Weechat.hook_signal( 'buffer_switch', 'update_bar', '' ) Weechat.hook_modifier( 'input_text_content', 'input_lock', '' ) Weechat.hook_config( 'plugins.var.ruby.input_lock.*', 'config_changed', '' ) return Weechat::WEECHAT_RC_OK end ### Allow Weechat namespace callbacks to forward to the InputLock object. ### require 'forwardable' extend Forwardable def_delegators :$lock, :config_changed, :input_lock, :update_bar, :il_bar_item __END__ __LICENSE__ Copyright (c) 2011, Mahlon E. Smith All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author, nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. weechat-scripts-20131007/ruby/minbif_typing_notice.rb0000644000175000017500000000760412224522673021261 0ustar manumanu# This script is inspired by "im_typing_notice" for irssi. # It creates a new bar item displaying when contacts are typing on supported protocol in minbif # It sends a notice to your contacts when you're typing a message. # # Author: CissWit cisswit at 6-8 dot fr # Version 1.0.1 # # Changelog : # # * 1.0.1 # Ignore the user "request" (no need to tell it we are typing) # # * 1.0 # Original version # # Licence GPL3 $h_typing = Hash.new $h_sending = Hash.new def weechat_init Weechat.register( "minbif_typing_notice", "CissWit", "1.0.1", "GPL3", "For minbif - displays when someone is typing a message to you, and notice them when you do.", "", "" ) Weechat.bar_item_new("typing_notice", "draw_typing", "") Weechat.hook_modifier("irc_in_privmsg", "modifier_ctcp", "") Weechat.hook_signal("input_text_changed", "input_changed", "") if Weechat.config_is_set_plugin("minbif_server") == 0 Weechat.config_set_plugin("minbif_server", "minbif") end Weechat.print("", "typing_notice: minbif typing notice") Weechat.print("", "typing_notice: Put [typing_notice] in your status bar (or the one you prefer) to show when contacts are typing message to you.") return Weechat::WEECHAT_RC_OK end def input_changed(data,signal,type_data) buffer = Weechat.current_buffer buffer_name = Weechat.buffer_get_string buffer, "name" if buffer_name =~ /^#{Weechat.config_get_plugin("minbif_server")}\.(.*)/ nick = $1 if nick == "request" return Weechat::WEECHAT_RC_OK end buffer_text = Weechat.buffer_get_string(buffer,"input") if(buffer_text == "" or buffer_text =~ /^\//) if $h_sending.key?(buffer) Weechat.command(buffer,"/mute all ctcp #{nick} TYPING 0") Weechat.unhook($h_sending[buffer]["timer"]) $h_sending.delete(buffer) end return Weechat::WEECHAT_RC_OK end return Weechat::WEECHAT_RC_OK unless !$h_sending.key?(buffer) Weechat.command(buffer,"/mute -all ctcp #{nick} TYPING 1") if $h_sending.key?(buffer) Weechat.unhook($h_sending[buffer]["timer"]) else $h_sending[buffer] = Hash.new end $h_sending[buffer]["timer"] = Weechat.hook_timer(7000,0,1,"sending_timeout",buffer) $h_sending[buffer]["time"] = Time.new end return Weechat::WEECHAT_RC_OK end def sending_timeout(buffer,n) if $h_sending.key?(buffer) buffer_name = Weechat.buffer_get_string buffer, "name" if buffer_name =~ /^#{Weechat.config_get_plugin("minbif_server")}\.(.*)/ Weechat.command(buffer,"/mute -all ctcp #{$1} TYPING 0") Weechat.unhook($h_sending[buffer]["timer"]) $h_sending.delete(buffer) end end return Weechat::WEECHAT_RC_OK end def draw_typing(osefa,osefb,osefc) buffer = Weechat.current_buffer if $h_typing.key?(buffer) return "TYPING" end return "" end def typing_timeout(buffer,n) if $h_typing.key?(buffer) Weechat.unhook($h_typing[buffer]) $h_typing.delete(buffer) end Weechat.bar_item_update("typing_notice") end def modifier_ctcp(data, modifier, modifier_data, string) if string =~ /:([^!]*)!([^\s]*)\sPRIVMSG\s([^\s]*)\s:\01TYPING\s([0-9])\01/ buffer = Weechat.buffer_search("irc", modifier_data + "." + $1) if $h_typing.key?(buffer) Weechat.unhook($h_typing[buffer]) end if $4 == "1" $h_typing[buffer] = Weechat.hook_timer(7000,0,1,"typing_timeout",buffer) elsif $4 == "0" if $h_typing.key?(buffer) $h_typing.delete(buffer) end elsif $4 == "2" Weechat.print("","- #{$4} - #{$1} - #{buffer} - is typing") end Weechat.bar_item_update("typing_notice") return "" end return string end weechat-scripts-20131007/ruby/url_shorten.rb0000644000175000017500000002171512224522673017425 0ustar manumanu# Copyright (c) 2013, Daniel Bretoi # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY Daniel Bretoi ''AS IS'' AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # * Simpler, more robust version of tinyurl (doesn't rely on html output) # * Echo's urls from all channels that are over the plugin's maxlen value # in a shortened format. # * If maxlen is not set, uses window width to compute value for maxlen # * allows for manual shortening of urls # * allows for custom service. Just set plugins.var.ruby.url_shorten.custom to # %s # * set config variable 'shortener' to one of: # # Value Service used # ----- ------------ # qurl http://qurl.com/ # tinyurl http://tinyurl.com/ # isgd http://is.gd/ # bitly http://bit.ly/ # waaai http://waa.ai/ # access configs with /set plugins.var.ruby.url_shorten.* # # Contributors: # Derek Carter # FlashCode # Kovensky # nils_2 # penryu require 'net/http' require 'net/https' require 'uri' SCRIPT_NAME = 'url_shorten' SCRIPT_AUTHOR = 'Daniel Bretoi ' SCRIPT_DESC = 'Shorten url' SCRIPT_VERSION = '1.9.0' SCRIPT_LICENSE = 'BSD' SCRIPT_REPO = 'https://github.com/danielb2/weechat-scripts' DEFAULTS = { 'maxlen' => '0', 'color' => 'red', 'shortener' => '', 'custom' => 'http://tinyurl.com/api-create.php?url=%s', 'bitly_login' => '', 'bitly_key' => '', 'yourls_url' => '', } def weechat_init Weechat.register SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", "" Weechat.hook_command SCRIPT_NAME, SCRIPT_DESC, "url", "url: url to shorten", "", SCRIPT_NAME, "" Weechat.hook_print "", "notify_message", "://", 1, "msg_shorten", "" Weechat.hook_print "", "notify_private", "://", 1, "msg_shorten", "" Weechat.hook_print "", "notify_highlight", "://", 1, "msg_shorten", "" set_defaults cwprint "[url] Shortener service set to: #{service}" configure_bitly configure_yourls return Weechat::WEECHAT_RC_OK end def configure_yourls if Weechat.config_get_plugin("shortener").eql?('yourls') # not quite ready for key based auth # should be researched http://code.google.com/p/yourls/wiki/PasswordlessAPI cfg_yourls_url = Weechat.config_get_plugin("yourls_url") if cfg_yourls_url.empty? yellow = Weechat.color("yellow") Weechat.print("", "#{yellow}WARNING: The yourls shortener requires a valid API.") Weechat.print("", "#{yellow}WARNING: Please configure the `yourls_url' option before using this script.") end end end def configure_bitly if Weechat.config_get_plugin("shortener").eql?('bitly') cfg_bitly_login = Weechat.config_get_plugin("bitly_login") cfg_bitly_key = Weechat.config_get_plugin("bitly_key") if cfg_bitly_login.empty? || cfg_bitly_key.empty? yellow = Weechat.color("yellow") Weechat.print("", "#{yellow}WARNING: The bit.ly shortener requires a valid API login and key.") Weechat.print("", "#{yellow}WARNING: Please configure the `bitly_login' and `bitly_key' options before using this script.") end end end def set_defaults DEFAULTS.each_pair { |option, def_value| cur_value = Weechat.config_get_plugin(option) if cur_value.nil? || cur_value.empty? Weechat.config_set_plugin(option, def_value) end } end def fetch(uri_str, limit = 10) raise ArgumentError, 'HTTP redirect too deep' if limit == 0 req_url = URI.parse(uri_str) http = Net::HTTP.new(req_url.host, req_url.port) http.use_ssl = (req_url.port == 443) http.verify_mode = OpenSSL::SSL::VERIFY_NONE http.open_timeout = 3 # in seconds http.read_timeout = 3 # in seconds response = Net::HTTP.get_response(req_url) case response when Net::HTTPSuccess then response.body when Net::HTTPRedirection then fetch(response['location'], limit - 1) else response.error! end rescue Exception => e return e.message end def url_encode(url) begin return URI.parse(url).to_s rescue URI::InvalidURIError return URI.encode(url) end end def qurl_shorten(url) # deprecate this one later shorten = 'http://www.qurl.com/automate.php?url=' Weechat.config_set_plugin('custom', shorten + '%s') fetch(shorten + url).gsub('www.','') end def waaai_shorten(url) # deprecate this one later shorten = 'http://waa.ai/api.php?url=' Weechat.config_set_plugin('custom', shorten + '%s') fetch(shorten + url) end def tinyurl_shorten(url) # deprecate this one later shorten = 'http://tinyurl.com/api-create.php?url=' Weechat.config_set_plugin('custom', shorten + '%s') fetch(shorten + url) end def isgd_shorten(url) # deprecate this one later shorten = 'http://is.gd/api.php?longurl=' Weechat.config_set_plugin('custom', shorten + '%s') fetch(shorten + url) end # current window print def cwprint(str) Weechat.print(Weechat.current_buffer, str.to_s) end def get_config_string(string) option = Weechat.config_get(string) Weechat.config_string(option) end def window_width time_stamp_width = Time.now.strftime(get_config_string('weechat.look.buffer_time_format')).size current_window_width = Weechat.window_get_integer(Weechat.current_window, "win_chat_width") max_nick_length = 16 current_window_width - max_nick_length - time_stamp_width end def yourls_shorten(url) # use yourls shortener # need to provide url config option require 'rubygems' require 'json/pure' params = ['action=shorturl'] params << 'format=simple' params << 'url=' + url yourls_url = Weechat.config_get_plugin('yourls_url') api_url = yourls_url + params.join('&') begin body_txt = fetch(api_url) rescue Exception => ex return "Failure yourls shortening url: " + ex.to_s end body_txt end def bitly_shorten(url) require 'rubygems' require 'json' params = ['longUrl=' + url] params << 'login=' + Weechat.config_get_plugin('bitly_login') params << 'apiKey=' + Weechat.config_get_plugin('bitly_key') api_url = 'http://api.bit.ly/shorten?version=2.0.1&' + params.join('&') begin url_data = JSON.parse(fetch(api_url)) rescue Exception => ex return "Failure shortening url: " + ex.to_s end if url_data['statusCode'].eql?('OK') begin res = url_data['results'] res[res.keys[0]]['shortUrl'] rescue NoMethodError => ex "Failure parsing bitly result: #{ex}" end else url_data['errorMessage'] end end def service custom = Weechat.config_get_plugin('custom') return custom if custom.size > 0 Weechat.config_get_plugin('shortener').tr('.','') end def shortener(url) return fetch(custom_url(url)) if custom_url(url).size > 0 begin return send("#{service}_shorten", url_encode(url)) rescue NoMethodError => e "Shortening service #{service} not supported... #{e}" end end def custom_url(url) sprintf Weechat.config_get_plugin('custom'), url_encode(url) end def regexp_url @regexp_url ||= Regexp.new('https?://[^\s>]*') @regexp_url end def url_shorten(data,buffer,msg) if (msg.empty?) return Weechat::WEECHAT_RC_OK end url = (msg.scan regexp_url).to_s short = shortener(url) color = Weechat.color(Weechat.config_get_plugin("color")) Weechat.print(Weechat.current_buffer, "[url]:\t#{color}#{short}"); return Weechat::WEECHAT_RC_OK end def msg_shorten(data,buffer,time,tags,displayed,highlight,prefix,message) if (message.empty?) return Weechat::WEECHAT_RC_OK end matchdata = message.match(regexp_url) return Weechat::WEECHAT_RC_OK unless matchdata url = matchdata[0].to_s maxlen = Weechat.config_get_plugin("maxlen").to_i maxlen = window_width if maxlen == 0 return Weechat::WEECHAT_RC_OK if url.length < maxlen short = shortener(url) color = Weechat.color(Weechat.config_get_plugin("color")) Weechat.print(buffer, "[url]:\t%s%s" % [color, short]) return Weechat::WEECHAT_RC_OK end weechat-scripts-20131007/ruby/dcc_send_relay.rb0000644000175000017500000000643312224522674020020 0ustar manumanu# Copyright (c) 2012 Dominik Honnef # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # This script relays incoming DCC SEND requests to a different nick. # This is for example useful if you have your main weechat running on # a server but want to download files via DCC directly to your local # computer. In that case, you would simply run a second weechat (or # any other client) on your local computer and configure this script # to forward them to it. # There are two options, one of which is required: # # - plugins.var.ruby.dcc_send_relay.relay_nick : The nickname to # forward the requests to # # - plugins.var.ruby.dcc_send_relay.relay_server : The server name to # forward the requests to. If this is not set, this script uses the # same server the request came from. If you're connected to multiple # networks, however, it is best to set this variable so that your # local client doesn't have to be connected to all those servers. # The value of the variable has to match the name of the server # buffer you want to use. # History: # 2012-03-06, Dominik Honnef # version 0.0.1: initial version def weechat_init Weechat.register("dcc_send_relay", "Dominik Honnef", "0.0.1", "MIT", "Relay DCC SEND requests to a different IRC client.", "", "") Weechat.hook_modifier "irc_in_privmsg", "dcc_send_cb", "" return Weechat::WEECHAT_RC_OK end def dcc_send_cb(data, signal, server, args) msg = Weechat.info_get_hashtable("irc_message_parse", "message" => args) message = msg["arguments"].split(":", 2) if message[1] !~ /^\001DCC SEND .+\001$/ return args end target_server = Weechat.config_get_plugin("relay_server") target_server = server if target_server.empty? server_buffer = Weechat.buffer_search("irc", "server." + target_server) target_nick = Weechat.config_get_plugin("relay_nick") if target_nick.empty? Weechat.print("", "Cannot relay DCC SEND without a configured target nick.") return args end if server_buffer.empty? Weechat.print("", "Couldn't find a server buffer for '#{target_server}'.") return args end Weechat.command(server_buffer, "/MSG %s %s" % [target_nick, message[1]]) return "" end weechat-scripts-20131007/python/0000755000175000017500000000000012224522672015065 5ustar manumanuweechat-scripts-20131007/python/quodnp.py0000644000175000017500000001051112224522657016746 0ustar manumanu# quodnp.py # # Copyright 2009 Brandon Hartshorn # # 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 Street, Fifth Floor, Boston, # MA 02110-1301, USA. import weechat, os, sys, re from stat import * SCRIPT_NAME = "quodnp" SCRIPT_AUTHOR = "Sharn" SCRIPT_VERSION = "0.5" SCRIPT_LICENSE = "GPL2" SCRIPT_DESC = "Full control of Quodlibet from Weechat" SCRIPT_COMMAND = "quodnp" if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): weechat.hook_command( SCRIPT_COMMAND, "Control of Quodlibet in Weechat", "np | next | prev | play-pause", " np: print song now playing to current buffer\n" "next/prev/play: control quodlibet - next song/previous song/play-pause" " respectively\n" "For np_format configuration, you can use anything avaliable" "from \"cat ~/.quodlibet/current\" - just ignore the ~#s\n" "and add a \"$\" - for example, to print the artist put $artist, or $album for album.", "np|next|prev|play-pause", "command_handle", "" ) # default options settings = { "autonp" : "off", "np_format" : "np: $artist - $title", "debug" : "off", } for option, default_value in settings.items(): if weechat.config_get_plugin(option) == "": weechat.config_set_plugin(option, default_value) def quodlibet_nowplaying(buffer): values = {} current_file = os.path.expanduser("~/.quodlibet/current") if os.path.isfile(current_file): open_file = open(current_file, "r") for line in open_file: key, val = line.lstrip("~#").strip().split("=", 1) if key == "bitrate": val = val[:3] + "Kbps" elif key == "length": val = val values.update({ key : val, }) weechat.command(weechat.current_buffer(), (format_output(weechat.config_get_plugin("np_format"), values))) open_file.close() else: weechat.prnt("", "Error opening " + current_file + ". Are you sure Quodlibet is running?") ouput = format_output(weechat.config_get_plugin("np_format"), values) def quodlibet_control(action): control_file = os.path.expanduser("~/.quodlibet/control") error = "Error opening " + control_file + ". Are you sure Quodlibet is running?" try: mode = os.stat(control_file)[ST_MODE] if S_ISFIFO(mode): open_file = open(control_file, "w") open_file.write(action) open_file.close() else: weechat.prnt("", error) except: weechat.prnt("", error) def format_output(format, values): out = "" n = 0 for match in re.finditer(findvar, format): if match is None: continue else: l, r = match.span() nam = match.group(1) out += format[n:l+1] + values.get(nam, "").strip() n = r return out + format[n:] findvar = re.compile(r'[^\\]\$([a-z_]+)(\b|[^a-z_])') def command_handle(data, buffer, args): largs = args.split(" ") if len(largs) > 1: weechat.prnt("", "This script can only use 1 argument at a time, see /help " + SCRIPT_COMMAND + " if you need help") elif largs[0] in ("next", "prev", "play-pause"): quodlibet_control(largs[0]) elif largs[0] == "np": quodlibet_nowplaying(buffer) else: if weechat.config_get_plugin("autonp") == "on": quodlibet_nowplaying(buffer) else: weechat.prnt("", "No action specified, see /help " + SCRIPT_COMMAND + " if you need help") return weechat.WEECHAT_RC_OK weechat-scripts-20131007/python/vimode.py0000644000175000017500000005067012224522672016732 0ustar manumanu# -*- coding: utf-8 -*- # # Copyright (C) 2013 Germain Z. # # 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 . # # # Description: # An attempt to add a vi-like mode to WeeChat, which provides some common vi # key bindings and commands, as well as normal/insert modes. # # Usage: # To switch to Normal mode, press Ctrl + Space. The Escape key is reported as # a modifier, so it can't be used for now. # To switch back to Insert mode, you can use i/a/A (or cw/ce/...). # To execute a command, simply precede it with a ':' while in normal mode, # for example: ":h" or ":s/foo/bar". # # Current key bindings: # j Scroll buffer up * (scrolls a few lines at a time) # k Scroll buffer down * (same as above) # w Forward to the beginning of word # e Forward to the end of word # b Backward to the beginning of word # gg Go to top of buffer * (no counts) # G Go to bottom of buffer # h Move cursor left # l Move cursor right # 0 Go to beginning of line # ^ Go to beginning of line * (doesn't take whitespace into # consideration, behaves like '0') # $ Go to end of line * (same as above) # x Delete character at cursor # dw Delete word # db Delete previous word # de Delete till end of word # d^ Delete till beginning of line # d$ Delete till end of line # dd Delete line # ce Delete till end of word, switch to insert mode # cw Delete word, switch to insert mode # cb Delete previous word, switch to insert mode # c^ Delete till beginning of line, switch to insert mode # c$ Delete till end of line, switch to insert mode # dd Delete line, switch to insert mode # / Launch WeeChat search mode # Counts (e.g. 'd2w', '2G') are supported. However, key bindings marked with a # '*' won't perform as intended for the time being. Explanation follows in # parentheses. # TODO: yy yw ye yb p, make 'e'/'w'/... return start/end positions to avoid # redundancy and make them usable by multiple modifiers (e.g. 'de', 'dw') # TODO (later): u U C-R r R % # better search (/), add: n N ? # TODO (even later): . # # Current commands: # :h Help (/help) # :set Set WeeChat config option (/set) # :q Closes current buffer (/close) # :qall Exits WeeChat (/exit) # :w Saves settings (/save) # :s/pattern/repl # :s/pattern/repl/g Search/Replace, supports regex (check docs for the Python # re module for more information). '&' in the replacement is # also substituted by the pattern. If the 'g' flag isn't # present, only the first match will be substituted. # Escapes are not interpreted for repl (e.g. '\&'), and '/' # isn't expected to be used/escaped anywhere in the command. # TODO: Improve this. # TODO: :! (probably using shell.py) # :w saves buffer's contents to file # :r puts file's content in input line/open in buffer? # TODO: Display matching commands with (basic) help, like Penta/Vimp do. # # History: # version 0.1: initial release # version 0.2: added esc to switch to normal mode, various key bindings and # commands. # import weechat import re import time SCRIPT_NAME = "vimode" SCRIPT_AUTHOR = "GermainZ " SCRIPT_VERSION = "0.2" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = ("An attempt to add a vi-like mode to WeeChat, which adds some" " common vi key bindings and commands, as well as normal/insert" " modes.") # Initialize variables: input_line = '' # used to communicate between functions, when only passing a # single string is allowed (e.g. for weechat.hook_timer). cmd_text = '' # holds the text of the command line. mode = "INSERT" # mode we start in (INSERT or COMMAND), insert by default. pressed_keys = '' # holds any pressed keys, regardless of their type. vi_buffer = '' # holds 'printable' pressed keys (e.g. arrow keys aren't added). last_time = time.time() # used to check if pressed_keys and vi_buffer need to #be reset. num = r"[0-9]*" # simple regex to detect number of repeats in keystrokes such # as "d2w" # Some common vi commands. # Others may be present in exec_cmd: vi_commands = {'h': "/help", 'qall': "/exit", 'q': "/close", 'w': "/save", 'set': "/set"} # Common vi key bindings. A dict holds necessary values further down. def key_G(repeat): """Simulate vi's behavior for the G key.""" if repeat > 0: # This is necessary to prevent weird scroll jumps. weechat.command('', "/window scroll_bottom") weechat.command('', "/window scroll %s" % repeat) else: weechat.command('', "/window scroll_bottom") def key_dw(repeat, key="dw"): """Simulate vi's behavior for dw or de.""" buf = weechat.current_buffer() input_line = weechat.buffer_get_string(buf, 'input') cur = weechat.buffer_get_integer(buf, "input_pos") input_line = list(input_line) for i in range(max([1, repeat])): done = False found_letter = False delete_word = True one_timer = False while done is False: if cur >= len(input_line): done = True elif input_line[cur] != ' ' and delete_word is True: found_letter = True del input_line[cur] elif input_line[cur] == ' ' and found_letter is False: if key == "dw": delete_word = False del input_line[cur] elif input_line[cur] == ' ' and found_letter is True: delete_word = False if key == "dw" or (one_timer is False and cur < len(input_line) and input_line[cur + 1] == ' ' and i < max([1, repeat]) - 1): one_timer = True del input_line[cur] else: if i == max([1, repeat]) - 1: cur += 1 else: del input_line[cur] else: done = True input_line = ''.join(input_line) weechat.buffer_set(buf, "input", input_line) def key_de(repeat): """Simulate vi's behavior for the de key.""" key_dw(repeat, "de") def key_w(repeat): """Simulate vi's behavior for the w key.""" buf = weechat.current_buffer() for _ in range(max([1, repeat])): input_line = weechat.buffer_get_string(buf, 'input') cur = weechat.buffer_get_integer(buf, "input_pos") weechat.command('', ("/input move_next_word\n/input move_next_word\n" "/input move_previous_word")) if len(input_line[cur:].split(' ')) == 1: weechat.command('', "/input move_end_of_line") def key_cw(repeat): """Simulate vi's behavior for the cw key.""" key_dw(repeat) set_mode("INSERT") def key_ce(repeat): """Simulate vi's behavior for the ce key.""" """Key ce.""" key_de(repeat) set_mode("INSERT") def key_cb(repeat): """Simulate vi's behavior for the cb key.""" for _ in range(max[1, repeat]): weechat.command('', "/input move_previous_word") set_mode("INSERT") def key_ccarret(repeat): """Simulate vi's behavior for the c^ key.""" for _ in range(max[1, repeat]): weechat.command('', "/input delete_beginning_of_line") set_mode("INSERT") def key_cdollar(repeat): """Simulate vi's behavior for the c$ key.""" for _ in range(max[1, repeat]): weechat.command('', "/input delete_end_of_line") set_mode("INSERT") def key_cd(repeat): """Simulate vi's behavior for the cd key.""" weechat.command('', "/input delete_line") set_mode("INSERT") # Common vi key bindings. If the value is a string, it's assumed it's a WeeChat # command, and a function otherwise. vi_keys = {'j': "/window scroll_down", 'k': "/window scroll_up", 'G': key_G, 'gg': "/window scroll_top", 'h': "/input move_previous_char", 'l': "/input move_next_char", 'w': key_w, 'e': ("/input move_next_char\n/input move_next_word\n" "/input move_previous_char"), 'b': "/input move_previous_word", '^': "/input move_beginning_of_line", '$': "/input move_end_of_line", 'x': "/input delete_next_char", 'dd': "/input delete_line", 'd$': "/input delete_end_of_line", 'd^': "/input delete_beginning_of_line", 'dw': key_dw, 'db': "/input delete_previous_word", 'de': key_de, 'cw': key_cw, 'ce': key_ce, 'cb': key_cb, 'c^': key_ccarret, 'c$': key_cdollar, 'cd': key_cd, '0': "/input move_beginning_of_line", '/': "/input search_text"} def set_mode(arg): """Set the current mode and update the bar mode indicator.""" global mode mode = arg weechat.bar_item_update("mode_indicator") def vi_buffer_cb(data, item, window): """Return the content of the vi buffer (pressed keys on hold).""" return vi_buffer def cmd_text_cb(data, item, window): """Return the text of the command line.""" return cmd_text def mode_indicator_cb(data, item, window): """Return the current mode (INSERT/COMMAND).""" return mode def exec_cmd(data, remaining_calls): """Translate and execute our custom commands to WeeChat command, with any passed arguments. input_line is set in key_pressed_cb and is used here to restore its value if we want, along with any potential replacements that should be made (e.g. for s/foo/bar type commands). """ global input_line data = list(data) del data[0] data = ''.join(data) data = data.split(' ', 1) cmd = data[0] if len(data) == 2: args = data[1] else: args = '' if cmd in vi_commands: weechat.command('', "%s %s" % (vi_commands[cmd], args)) # s/foo/bar command elif cmd.startswith("s/"): pattern = cmd.split('/')[1] repl = cmd.split('/')[2].replace('&', pattern) flag = None count = 1 if len(cmd.split('/')) > 3: flag = cmd.split('/')[3] if flag == 'g': count = 0 buf = weechat.current_buffer() input_line = re.sub(pattern, repl, input_line, count) weechat.buffer_set(buf, "input", input_line) else: weechat.prnt('', "Command '%s' not found." % cmd) return weechat.WEECHAT_RC_OK def input_set(data, remaining_calls): """Set the input line's content.""" buf = weechat.current_buffer() weechat.buffer_set(buf, "input", data) # move the cursor back to its position prior to setting the content weechat.command('', "/input move_next_char") return weechat.WEECHAT_RC_OK def input_rem_char(data, remaining_calls): """Remove one character from the input buffer. If data is set to 'cursor', the character at the cursor will be removed. Otherwise, data must be an integer and the character at that position will be removed instead. """ buf = weechat.current_buffer() input_line = weechat.buffer_get_string(buf, 'input') if data == "cursor": data = weechat.buffer_get_integer(buf, "input_pos") input_line = list(input_line) try: del input_line[int(data - 1)] # Not sure why nothing is being detected in some cases from the first time except IndexError: weechat.hook_timer(1, 0, 1, "input_rem_char", "cursor") return weechat.WEECHAT_RC_OK input_line = ''.join(input_line) weechat.buffer_set(buf, "input", input_line) # move the cursor back to its position before removing our character weechat.command('', "/input move_previous_char") return weechat.WEECHAT_RC_OK def handle_esc(data, remaining_calls): """Esc acts as a modifier and usually waits for another keypress. To circumvent that, simulate a keypress then remove what was inserted. """ global cmd_text weechat.command('', "/input insert %s" % data) weechat.hook_signal_send("key_pressed", weechat.WEECHAT_HOOK_SIGNAL_STRING, data) if cmd_text == ":[": cmd_text = ':' return weechat.WEECHAT_RC_OK esc_pressed = False def pressed_keys_check(data, remaining_calls): """Check the pressed keys and changes modes or detects bound keys accordingly. """ global pressed_keys, mode, vi_buffer, esc_pressed # If the last pressed key was Escape, this one will be detected as an arg # as Escape acts like a modifier (pressing Esc, then pressing i is detected # as pressing meta-i). We'll emulate it being pressed again, so that the # user's input is actually processed normally. if esc_pressed is True: esc_pressed = False weechat.hook_timer(50, 0, 1, "handle_esc", pressed_keys[-1]) if mode == "INSERT": # Ctrl + Space, or Escape if pressed_keys == "@" or pressed_keys == "[": set_mode("NORMAL") if pressed_keys == "[": esc_pressed = True elif mode == "NORMAL": # We strip all numbers and check if the the combo is recognized below, # then extract the numbers, if any, and pass them as the repeat factor. buffer_stripped = re.sub(num, '', vi_buffer) if vi_buffer in ['i', 'a', 'A']: set_mode("INSERT") if vi_buffer == 'a': weechat.command('', "/input move_next_char") elif vi_buffer == 'A': weechat.command('', "/input move_end_of_line") # Quick way to detect repeats (e.g. d5w). This isn't perfect, as things # like "5d2w1" are detected as "dw" repeated 521 times, but it should # be alright as long as the user doesn't try to break it on purpose. # Maximum number of repeats performed is 10000. elif buffer_stripped in vi_keys: repeat = ''.join(re.findall(num, vi_buffer)) if len(repeat) > 0: repeat = min([int(repeat), 10000]) else: repeat = 0 if isinstance(vi_keys[buffer_stripped], str): for _ in range(1 if repeat == 0 else repeat): weechat.command('', vi_keys[re.sub(num, '', vi_buffer)]) else: vi_keys[buffer_stripped](repeat) else: return weechat.WEECHAT_RC_OK clear_vi_buffers() return weechat.WEECHAT_RC_OK def clear_vi_buffers(data=None, remaining_calls=None): """Clear both pressed_keys and vi_buffer. If data is set to 'check_time', they'll only be cleared if enough time has gone by since they've been last set. This is useful as this function is called using a timer, so other keys might've been pressed before the timer is activated. """ global pressed_keys, vi_buffer if data == "check_time" and time.time() < last_time + 1.0: return weechat.WEECHAT_RC_OK pressed_keys = '' vi_buffer = '' weechat.bar_item_update("vi_buffer") return weechat.WEECHAT_RC_OK def is_printing(current, saved): """Is the character a visible, printing character that would normally show in the input box? Previously saved characters are taken into consideration as well for some key combinations, such as the arrows, which are detected as three separate events (^A[, [ and A/B/C/D). The keys buffers will be cleared if the character isn't visible. """ if current.startswith("") or saved.startswith(""): weechat.hook_timer(50, 0, 1, "clear_vi_buffers", '') return False return True def key_pressed_cb(data, signal, signal_data): """Handle key presses. Make sure inputted keys are removed from the input bar and added to the appropriate keys buffers or to the command line if it's active, activate it when needed, etc. """ global pressed_keys, last_time, cmd_text, input_line, vi_buffer if mode == "NORMAL": # The character is a printing character, so we'll want to remove it # so it doesn't add up to the normal input box. if is_printing(signal_data, pressed_keys): weechat.hook_timer(1, 0, 1, "input_rem_char", "cursor") # It's a command! if signal_data == ':': cmd_text += ':' # Command line is active, so we want to check for some special keys # to modify (backspace/normal keys) or submit (Return key) our command. elif cmd_text != '': # Backspace key if signal_data == "?": buf = weechat.current_buffer() input_line = weechat.buffer_get_string(buf, 'input') # Remove the last character from our command line cmd_text = list(cmd_text) del cmd_text[-1] cmd_text = ''.join(cmd_text) # We can't actually eat these keystrokes, so simply removing # the last character would result in the last two characters # being removed (once by the backspace key, once by our script) # Instead, we'll just set the input line in a millisecond to # its original value, leaving it untouched. weechat.hook_timer(1, 0, 1, "input_set", input_line) # Return key elif signal_data == "M": buf = weechat.current_buffer() # Clear the input line, therefore nullifying the effect of the # Return key, then set it back a millisecond later. # This leaves the input box untouched and allows us to execute # the command filled in in our command line. # We can only pass strings as data using hook_timer, so we'll # use the global variable input_line in our exec_cmd function # instead to reset the input box's value. input_line = weechat.buffer_get_string(buf, 'input') weechat.buffer_set(buf, "input", '') weechat.hook_timer(1, 0, 1, "exec_cmd", cmd_text) cmd_text = '' # The key is a normal key, so just append it to our command line. elif is_printing(signal_data, pressed_keys): cmd_text += signal_data # Show the command line when needed, hide it (and update vi_buffer since # we'd be looking for keystrokes instead) otherwise. if cmd_text != '': weechat.command('', "/bar show vi_cmd") weechat.bar_item_update("cmd_text") else: weechat.command('', "/bar hide vi_cmd") if is_printing(signal_data, pressed_keys): vi_buffer += signal_data pressed_keys += signal_data # Check for any matching bound keys. weechat.hook_timer(1, 0, 1, "pressed_keys_check", '') last_time = time.time() # Clear the buffers after some time. weechat.hook_timer(1000, 0, 1, "clear_vi_buffers", "check_time") weechat.bar_item_update("vi_buffer") return weechat.WEECHAT_RC_OK weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '') weechat.bar_item_new("mode_indicator", "mode_indicator_cb", '') weechat.bar_item_new("cmd_text", "cmd_text_cb", '') weechat.bar_item_new("vi_buffer", "vi_buffer_cb", '') vi_cmd = weechat.bar_new("vi_cmd", "off", "0", "root", '', "bottom", "vertical", "vertical", "0", "0", "default", "default", "default", "0", "cmd_text") weechat.hook_signal("key_pressed", "key_pressed_cb", '') weechat-scripts-20131007/python/bandwidth.py0000644000175000017500000002324012224522661017402 0ustar manumanu# -*- coding: utf-8 -*- # # Copyright (C) 2009 xt # Copyright (C) 2011 quazgaa # # 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 . # #------------------------------------------------------------------- # To make bandwidth monitor visible you need to put "[bandwidth]" # (without "") in your weechat.bar.status.items setting #------------------------------------------------------------------- # # History: # # 2011-12-02, quazgaa # version 1.0: Complete rewrite. Make script more featureful, robust, and accurate. # Thanks to FlashCode and ze for helping debug. # 2011-11-29, quazgaa # version 0.2.1: fixed: take refresh_rate into account for bandwidth calculation # 2009-10-15, xt : # version 0.2: error checking from output command # 2009-10-14, xt : # version 0.1: initial release inspired by nils' perl script # # this is a weechat script try: import weechat except: print "This script must be run under WeeChat." print "Get WeeChat now at: http://www.weechat.org/" raise SystemExit, 0 try: from time import time except: print "Error importing time module." raise SystemExit, 0 # defines SCRIPT_NAME = "bandwidth" SCRIPT_AUTHOR = "xt " SCRIPT_VERSION = "1.0" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Displays network interface bandwidth (KiB/s and MiB/s) on a bar" SCRIPT_SETTINGS = { "device" : ("eth0", "Network interface(s) to monitor, in order, separated by ';'"), "refresh_rate" : ("5", "Refresh rate in seconds"), "format" : (("%N(" + unichr(8595) + "%DV%DU/s " + unichr(8593) + "%UV%UU/s)").encode('utf-8'), "Output formatting: %N = network interface, %DV = downstream value, %DU = downstream units (K or M), %UV = upstream value, %UU = upstream units (K or M). Note: default setting uses UTF-8"), "separator" : (" ", "String displayed between output for multiple devices"), } STATS_FILE = "/proc/net/dev" # global variables last_device = [] last_down_bytes = [] last_up_bytes = [] last_time = 0 def main(): if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): version = int(weechat.info_get('version_number', '')) or 0 # unset unused setting from older versions of script if weechat.config_is_set_plugin('display_unit'): weechat.prnt("", "Option plugins.var.python.bandwidth.display_unit no longer used, removing.") weechat.config_unset_plugin('display_unit') # set default settings for option in SCRIPT_SETTINGS.iterkeys(): if not weechat.config_is_set_plugin(option): weechat.config_set_plugin(option, SCRIPT_SETTINGS[option][0]) if version >= 0x00030500: weechat.config_set_desc_plugin(option, SCRIPT_SETTINGS[option][1]) # ensure sane refresh_rate setting if int(weechat.config_get_plugin('refresh_rate')) < 1: weechat.prnt("", "{}Invalid value for option plugins.var.python.bandwidth.refresh_rate, setting to default of {}".format(weechat.prefix("error"), SCRIPT_SETTINGS['refresh_rate'][0])) weechat.config_set_plugin('refresh_rate', SCRIPT_SETTINGS['refresh_rate'][0]) # create the bandwidth monitor bar item weechat.bar_item_new('bandwidth', 'bandwidth_item_cb', '') # update it every plugins.var.python.bandwidth.refresh_rate seconds weechat.hook_timer(int(weechat.config_get_plugin('refresh_rate'))*1000, 0, 0, 'bandwidth_timer_cb', '') def bandwidth_timer_cb(data, remaining_calls): weechat.bar_item_update('bandwidth') return weechat.WEECHAT_RC_OK def bandwidth_item_cb(data, buffer, args): global last_device, last_down_bytes, last_up_bytes, last_time device = weechat.config_get_plugin('device').strip(';').split(';') output_format = weechat.config_get_plugin('format') separator = weechat.config_get_plugin('separator') invalid_settings = False # ensure sane settings if not device[0]: weechat.prnt("", "{}Option plugins.var.python.bandwidth.device should contain at least one device name, setting to default of {}".format(weechat.prefix("error"), SCRIPT_SETTINGS['device'][0])) weechat.config_set_plugin('device', SCRIPT_SETTINGS['device'][0]) invalid_settings = True if int(weechat.config_get_plugin('refresh_rate')) < 1: weechat.prnt("", "{}Invalid value for option plugins.var.python.bandwidth.refresh_rate, setting to default of {}".format(weechat.prefix("error"), SCRIPT_SETTINGS['refresh_rate'][0])) weechat.config_set_plugin('refresh_rate', SCRIPT_SETTINGS['refresh_rate'][0]) invalid_settings = True if '%DV' not in output_format and '%UV' not in output_format: weechat.prnt("", "{}Option plugins.var.python.bandwidth.format should contain at least one of: '%DV' or '%UV'. Setting to default of '{}'".format(weechat.prefix("error"), SCRIPT_SETTINGS['format'][0])) weechat.config_set_plugin('format', SCRIPT_SETTINGS['format'][0]) invalid_settings = True if invalid_settings: return '' # open the network device status information file try: f = open(STATS_FILE) except: weechat.prnt("", "{}Error opening {}".format(weechat.prefix("error"), STATS_FILE)) return '' else: current_time = time() try: foo = f.read() except: weechat.prnt("", "{}Error reading {}".format(weechat.prefix("error"), STATS_FILE)) f.close() return '' f.close() current_down_bytes = [] current_up_bytes = [] num_devices = len(device) num_last_devices = len(last_device) lines = foo.splitlines() new_device_list = False device_exist = False # get the downstream and upstream byte counts for i in xrange(num_devices): for line in lines: if (device[i] + ':') in line: field = line.split(':')[1].strip().split() current_down_bytes.append(float(field[0])) current_up_bytes.append(float(field[8])) device_exist = True break if device_exist: device_exist = False else: current_down_bytes.append(0) current_up_bytes.append(0) # check if the set of network devices to monitor has changed while script is running, if last_device: if num_last_devices != num_devices: new_device_list = True else: for i in xrange(num_devices): if device[i] != last_device[i]: new_device_list = True break # if so, clear the global variables, if new_device_list: del last_device[:] del last_down_bytes[:] del last_up_bytes[:] # set them afresh (also if script first starting), if not last_device: if num_devices: for i in xrange(num_devices): last_device.append(device[i]) last_down_bytes.append(current_down_bytes[i]) last_up_bytes.append(current_up_bytes[i]) last_time = current_time # and start from the beginning return '' # calculate downstream and upstream rates in KiB/s if num_devices: down_rate = [] up_rate = [] time_elapsed = current_time - last_time last_time = current_time for i in xrange(num_devices): down_rate.append((current_down_bytes[i] - last_down_bytes[i]) / time_elapsed / 1024) up_rate.append((current_up_bytes[i] - last_up_bytes[i]) / time_elapsed / 1024) last_down_bytes[i] = current_down_bytes[i] last_up_bytes[i] = current_up_bytes[i] output_item = [output_format for i in device] output = '' # determine downstream and upstream units; format the output for i in xrange(num_devices): if '%DU' in output_item[i]: if down_rate[i] >= 1024: down_rate[i] = round((down_rate[i]/1024), 1) down_rate_unit = 'M' else: down_rate[i] = int(round(down_rate[i])) down_rate_unit = 'K' output_item[i] = output_item[i].replace('%DU', down_rate_unit) if '%UU' in output_item[i]: if up_rate[i] >= 1024: up_rate[i] = round((up_rate[i]/1024), 1) up_rate_unit = 'M' else: up_rate[i] = int(round(up_rate[i])) up_rate_unit = 'K' output_item[i] = output_item[i].replace('%UU', up_rate_unit) output_item[i] = output_item[i].replace('%DV', str(down_rate[i])) output_item[i] = output_item[i].replace('%UV', str(up_rate[i])) output_item[i] = output_item[i].replace('%N', device[i]) if output: output += separator + output_item[i] else: output = output_item[i] # return the result return output if __name__ == "__main__": main() weechat-scripts-20131007/python/grep.py0000644000175000017500000017203112224522660016375 0ustar manumanu# -*- coding: utf-8 -*- ### # Copyright (c) 2009-2011 by Elián Hanisch # # 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 . ### ### # Search in Weechat buffers and logs (for Weechat 0.3.*) # # Inspired by xt's grep.py # Originally I just wanted to add some fixes in grep.py, but then # I got carried away and rewrote everything, so new script. # # Commands: # * /grep # Search in logs or buffers, see /help grep # * /logs: # Lists logs in ~/.weechat/logs, see /help logs # # Settings: # * plugins.var.python.grep.clear_buffer: # Clear the results buffer before each search. Valid values: on, off # # * plugins.var.python.grep.go_to_buffer: # Automatically go to grep buffer when search is over. Valid values: on, off # # * plugins.var.python.grep.log_filter: # Coma separated list of patterns that grep will use for exclude logs, e.g. # if you use '*server/*' any log in the 'server' folder will be excluded # when using the command '/grep log' # # * plugins.var.python.grep.show_summary: # Shows summary for each log. Valid values: on, off # # * plugins.var.python.grep.max_lines: # Grep will only print the last matched lines that don't surpass the value defined here. # # * plugins.var.python.grep.size_limit: # Size limit in KiB, is used for decide whenever grepping should run in background or not. If # the logs to grep have a total size bigger than this value then grep run as a new process. # 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.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. # # # TODO: # * try to figure out why hook_process chokes in long outputs (using a tempfile as a # workaround now) # * possibly add option for defining time intervals # # # History: # 2011-01-09 # version 0.7.2: bug fixes # # 2010-11-15 # version 0.7.1: # * use TempFile so temporal files are guaranteed to be deleted. # * enable Archlinux workaround. # # 2010-10-26 # version 0.7: # * added templates. # * using --only-match shows only unique strings. # * fixed bug that inverted -B -A switches when used with -t # # 2010-10-14 # version 0.6.8: by xt # * supress highlights when printing in grep buffer # # 2010-10-06 # version 0.6.7: by xt # * better temporary file: # use tempfile.mkstemp. to create a temp file in log dir, # makes it safer with regards to write permission and multi user # # 2010-04-08 # version 0.6.6: bug fixes # * use WEECHAT_LIST_POS_END in log file completion, makes completion faster # * disable bytecode if using python 2.6 # * use single quotes in command string # * fix bug that could change buffer's title when using /grep stop # # 2010-01-24 # version 0.6.5: disable bytecode is a 2.6 feature, instead, resort to delete the bytecode manually # # 2010-01-19 # version 0.6.4: bug fix # version 0.6.3: added options --invert --only-match (replaces --exact, which is still available # but removed from help) # * use new 'irc_nick_color' info # * don't generate bytecode when spawning a new process # * show active options in buffer title # # 2010-01-17 # version 0.6.2: removed 2.6-ish code # version 0.6.1: fixed bug when grepping in grep's buffer # # 2010-01-14 # version 0.6.0: implemented grep in background # * improved context lines presentation. # * grepping for big (or many) log files runs in a weechat_process. # * added /grep stop. # * added 'size_limit' option # * fixed a infolist leak when grepping buffers # * added 'default_tail_head' option # * results are sort by line count # * don't die if log is corrupted (has NULL chars in it) # * changed presentation of /logs # * log path completion doesn't suck anymore # * removed all tabs, because I learned how to configure Vim so that spaces aren't annoying # anymore. This was the script's original policy. # # 2010-01-05 # version 0.5.5: rename script to 'grep.py' (FlashCode ). # # 2010-01-04 # version 0.5.4.1: fix index error when using --after/before-context options. # # 2010-01-03 # version 0.5.4: new features # * added --after-context and --before-context options. # * added --context as a shortcut for using both -A -B options. # # 2009-11-06 # version 0.5.3: improvements for long grep output # * grep buffer input accepts the same flags as /grep for repeat a search with different # options. # * tweaks in grep's output. # * max_lines option added for limit grep's output. # * code in update_buffer() optimized. # * time stats in buffer title. # * added go_to_buffer config option. # * added --buffer for search only in buffers. # * refactoring. # # 2009-10-12, omero # version 0.5.2: made it python-2.4.x compliant # # 2009-08-17 # version 0.5.1: some refactoring, show_summary option added. # # 2009-08-13 # version 0.5: rewritten from xt's grep.py # * fixed searching in non weechat logs, for cases like, if you're # switching from irssi and rename and copy your irssi logs to %h/logs # * fixed "timestamp rainbow" when you /grep in grep's buffer # * allow to search in other buffers other than current or in logs # of currently closed buffers with cmd 'buffer' # * allow to search in any log file in %h/logs with cmd 'log' # * added --count for return the number of matched lines # * added --matchcase for case sensible search # * added --hilight for color matches # * added --head and --tail options, and --number # * added command /logs for list files in %h/logs # * added config option for clear the buffer before a search # * added config option for filter logs we don't want to grep # * added the posibility to repeat last search with another regexp by writing # it in grep's buffer # * changed spaces for tabs in the code, which is my preference # ### from os import path import sys, getopt, time, os, re, tempfile try: import weechat from weechat import WEECHAT_RC_OK, prnt, prnt_date_tags import_ok = True except ImportError: import_ok = False SCRIPT_NAME = "grep" SCRIPT_AUTHOR = "Elián Hanisch " SCRIPT_VERSION = "0.7.2" 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', } ### Class definitions ### class linesDict(dict): """ Class for handling matched lines in more than one buffer. linesDict[buffer_name] = matched_lines_list """ def __setitem__(self, key, value): assert isinstance(value, list) if key not in self: dict.__setitem__(self, key, value) else: dict.__getitem__(self, key).extend(value) def get_matches_count(self): """Return the sum of total matches stored.""" if dict.__len__(self): return sum(map(lambda L: L.matches_count, self.itervalues())) else: return 0 def __len__(self): """Return the sum of total lines stored.""" if dict.__len__(self): return sum(map(len, self.itervalues())) else: return 0 def __str__(self): """Returns buffer count or buffer name if there's just one stored.""" n = len(self.keys()) if n == 1: return self.keys()[0] elif n > 1: return '%s logs' %n else: return '' def items(self): """Returns a list of items sorted by line count.""" items = dict.items(self) items.sort(key=lambda i: len(i[1])) return items def items_count(self): """Returns a list of items sorted by match count.""" items = dict.items(self) items.sort(key=lambda i: i[1].matches_count) return items def strip_separator(self): for L in self.itervalues(): L.strip_separator() def get_last_lines(self, n): total_lines = len(self) #debug('total: %s n: %s' %(total_lines, n)) if n >= total_lines: # nothing to do return for k, v in reversed(self.items()): l = len(v) if n > 0: if l > n: del v[:l-n] v.stripped_lines = l-n n -= l else: del v[:] v.stripped_lines = l class linesList(list): """Class for list of matches, since sometimes I need to add lines that aren't matches, I need an independent counter.""" _sep = '...' def __init__(self, *args): list.__init__(self, *args) self.matches_count = 0 self.stripped_lines = 0 def append(self, item): """Append lines, can be a string or a list with strings.""" if isinstance(item, str): list.append(self, item) else: self.extend(item) def append_separator(self): """adds a separator into the list, makes sure it doen't add two together.""" s = self._sep if (self and self[-1] != s) or not self: self.append(s) def onlyUniq(self): s = set(self) del self[:] self.extend(s) def count_match(self, item=None): if item is None or isinstance(item, str): self.matches_count += 1 else: self.matches_count += len(item) def strip_separator(self): """removes separators if there are first or/and last in the list.""" if self: s = self._sep if self[0] == s: del self[0] if self[-1] == s: del self[-1] ### Misc functions ### now = time.time def get_size(f): try: return os.stat(f).st_size except OSError: return 0 sizeDict = {0:'b', 1:'KiB', 2:'MiB', 3:'GiB', 4:'TiB'} def human_readable_size(size): power = 0 while size > 1024: power += 1 size /= 1024.0 return '%.2f %s' %(size, sizeDict.get(power, '')) def color_nick(nick): """Returns coloured nick, with coloured mode if any.""" if not nick: return '' wcolor = weechat.color config_string = lambda s : weechat.config_string(weechat.config_get(s)) config_int = lambda s : weechat.config_integer(weechat.config_get(s)) # prefix and suffix prefix = config_string('irc.look.nick_prefix') suffix = config_string('irc.look.nick_suffix') prefix_c = suffix_c = wcolor(config_string('weechat.color.chat_delimiters')) if nick[0] == prefix: nick = nick[1:] else: prefix = prefix_c = '' if nick[-1] == suffix: nick = nick[:-1] suffix = wcolor(color_delimiter) + suffix else: suffix = suffix_c = '' # nick mode modes = '@!+%' if nick[0] in modes: mode, nick = nick[0], nick[1:] mode_color = wcolor(config_string('weechat.color.nicklist_prefix%d' \ %(modes.find(mode) + 1))) 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)) return ''.join((prefix_c, prefix, mode_color, mode, nick_color, nick, suffix_c, suffix)) ### Config and value validation ### boolDict = {'on':True, 'off':False} def get_config_boolean(config): value = weechat.config_get_plugin(config) try: return boolDict[value] except KeyError: default = settings[config] error("Error while fetching config '%s'. Using default value '%s'." %(config, default)) error("'%s' is invalid, allowed: 'on', 'off'" %value) return boolDict[default] def get_config_int(config, allow_empty_string=False): value = weechat.config_get_plugin(config) try: return int(value) except ValueError: if value == '' and allow_empty_string: return value default = settings[config] error("Error while fetching config '%s'. Using default value '%s'." %(config, default)) error("'%s' is not a number." %value) return int(default) def get_config_log_filter(): filter = weechat.config_get_plugin('log_filter') if filter: return filter.split(',') else: return [] def get_home(): home = weechat.config_string(weechat.config_get('logger.file.path')) return home.replace('%h', weechat.info_get('weechat_dir', '')) def strip_home(s, dir=''): """Strips home dir from the begging of the log path, this makes them sorter.""" if not dir: global home_dir dir = home_dir l = len(dir) if s[:l] == dir: return s[l:] return s ### Messages ### script_nick = SCRIPT_NAME def error(s, buffer=''): """Error msg""" prnt(buffer, '%s%s %s' %(weechat.prefix('error'), script_nick, s)) if weechat.config_get_plugin('debug'): import traceback if traceback.sys.exc_type: trace = traceback.format_exc() prnt('', trace) def say(s, buffer=''): """normal msg""" prnt_date_tags(buffer, 0, 'no_highlight', '%s\t%s' %(script_nick, s)) ### Log files and buffers ### cache_dir = {} # note: don't remove, needed for completion if the script was loaded recently def dir_list(dir, filter_list=(), filter_excludes=True, include_dir=False): """Returns a list of files in 'dir' and its subdirs.""" global cache_dir from os import walk from fnmatch import fnmatch #debug('dir_list: listing in %s' %dir) key = (dir, include_dir) try: return cache_dir[key] except KeyError: pass filter_list = filter_list or get_config_log_filter() dir_len = len(dir) if filter_list: def filter(file): file = file[dir_len:] # pattern shouldn't match home dir for pattern in filter_list: if fnmatch(file, pattern): return filter_excludes return not filter_excludes else: filter = lambda f : not filter_excludes file_list = [] extend = file_list.extend join = path.join def walk_path(): for basedir, subdirs, files in walk(dir): #if include_dir: # subdirs = map(lambda s : join(s, ''), subdirs) # files.extend(subdirs) files_path = map(lambda f : join(basedir, f), files) files_path = [ file for file in files_path if not filter(file) ] extend(files_path) walk_path() cache_dir[key] = file_list #debug('dir_list: got %s' %str(file_list)) return file_list def get_file_by_pattern(pattern, all=False): """Returns the first log whose path matches 'pattern', if all is True returns all logs that matches.""" if not pattern: return [] #debug('get_file_by_filename: searching for %s.' %pattern) # do envvar expandsion and check file file = path.expanduser(pattern) file = path.expandvars(file) if path.isfile(file): return [file] # lets see if there's a matching log global home_dir file = path.join(home_dir, pattern) if path.isfile(file): return [file] else: from fnmatch import fnmatch file = [] file_list = dir_list(home_dir) n = len(home_dir) for log in file_list: basename = log[n:] if fnmatch(basename, pattern): file.append(log) #debug('get_file_by_filename: got %s.' %file) if not all and file: file.sort() return [ file[-1] ] return file def get_file_by_buffer(buffer): """Given buffer pointer, finds log's path or returns None.""" #debug('get_file_by_buffer: searching for %s' %buffer) infolist = weechat.infolist_get('logger_buffer', '', '') if not infolist: return try: while weechat.infolist_next(infolist): pointer = weechat.infolist_pointer(infolist, 'buffer') if pointer == buffer: file = weechat.infolist_string(infolist, 'log_filename') if weechat.infolist_integer(infolist, 'log_enabled'): #debug('get_file_by_buffer: got %s' %file) return file #else: # debug('get_file_by_buffer: got %s but log not enabled' %file) finally: #debug('infolist gets freed') weechat.infolist_free(infolist) def get_file_by_name(buffer_name): """Given a buffer name, returns its log path or None. buffer_name should be in 'server.#channel' or '#channel' format.""" #debug('get_file_by_name: searching for %s' %buffer_name) # common mask options config_masks = ('logger.mask.irc', 'logger.file.mask') # since there's no buffer pointer, we try to replace some local vars in mask, like $channel and # $server, then replace the local vars left with '*', and use it as a mask for get the path with # get_file_by_pattern for config in config_masks: mask = weechat.config_string(weechat.config_get(config)) #debug('get_file_by_name: mask: %s' %mask) if '$name' in mask: mask = mask.replace('$name', buffer_name) elif '$channel' in mask or '$server' in mask: if '.' in buffer_name and \ '#' not in buffer_name[:buffer_name.find('.')]: # the dot isn't part of the channel name # ^ I'm asuming channel starts with #, i'm lazy. server, channel = buffer_name.split('.', 1) else: server, channel = '*', buffer_name if '$channel' in mask: mask = mask.replace('$channel', channel) if '$server' in mask: mask = mask.replace('$server', server) # change the unreplaced vars by '*' from string import letters if '%' in mask: # vars for time formatting mask = mask.replace('%', '$') if '$' in mask: masks = mask.split('$') masks = map(lambda s: s.lstrip(letters), masks) mask = '*'.join(masks) if mask[0] != '*': mask = '*' + mask #debug('get_file_by_name: using mask %s' %mask) file = get_file_by_pattern(mask) #debug('get_file_by_name: got file %s' %file) if file: return file return None def get_buffer_by_name(buffer_name): """Given a buffer name returns its buffer pointer or None.""" #debug('get_buffer_by_name: searching for %s' %buffer_name) pointer = weechat.buffer_search('', buffer_name) if not pointer: try: infolist = weechat.infolist_get('buffer', '', '') while weechat.infolist_next(infolist): short_name = weechat.infolist_string(infolist, 'short_name') name = weechat.infolist_string(infolist, 'name') if buffer_name in (short_name, name): #debug('get_buffer_by_name: found %s' %name) pointer = weechat.buffer_search('', name) return pointer finally: weechat.infolist_free(infolist) #debug('get_buffer_by_name: got %s' %pointer) return pointer def get_all_buffers(): """Returns list with pointers of all open buffers.""" buffers = [] infolist = weechat.infolist_get('buffer', '', '') while weechat.infolist_next(infolist): buffers.append(weechat.infolist_pointer(infolist, 'pointer')) weechat.infolist_free(infolist) grep_buffer = weechat.buffer_search('python', SCRIPT_NAME) if grep_buffer and grep_buffer in buffers: # remove it from list del buffers[buffers.index(grep_buffer)] return buffers ### Grep ### def make_regexp(pattern, matchcase=False): """Returns a compiled regexp.""" if pattern in ('.', '.*', '.?', '.+'): # because I don't need to use a regexp if we're going to match all lines return None # matching takes a lot more time if pattern starts or ends with .* and it isn't needed. if pattern[:2] == '.*': pattern = pattern[2:] if pattern[-2:] == '.*': pattern = pattern[:-2] try: if not matchcase: regexp = re.compile(pattern, re.IGNORECASE) else: regexp = re.compile(pattern) except Exception, e: raise Exception, 'Bad pattern, %s' %e return regexp def check_string(s, regexp, hilight='', exact=False): """Checks 's' with a regexp and returns it if is a match.""" if not regexp: return s elif exact: matchlist = regexp.findall(s) if matchlist: if isinstance(matchlist[0], tuple): # join tuples (when there's more than one match group in regexp) return [ ' '.join(t) for t in matchlist ] return matchlist elif hilight: matchlist = regexp.findall(s) if matchlist: if isinstance(matchlist[0], tuple): # flatten matchlist matchlist = [ item for L in matchlist for item in L if item ] matchlist = list(set(matchlist)) # remove duplicates if any # apply hilight color_hilight, color_reset = hilight.split(',', 1) for m in matchlist: s = s.replace(m, '%s%s%s' % (color_hilight, m, color_reset)) return s # no need for findall() here elif regexp.search(s): return s def grep_file(file, head, tail, after_context, before_context, count, regexp, hilight, exact, invert): """Return a list of lines that match 'regexp' in 'file', if no regexp returns all lines.""" if count: tail = head = after_context = before_context = False hilight = '' elif exact: before_context = after_context = False hilight = '' elif invert: hilight = '' #debug(' '.join(map(str, (file, head, tail, after_context, before_context)))) lines = linesList() # define these locally as it makes the loop run slightly faster append = lines.append count_match = lines.count_match separator = lines.append_separator if invert: def check(s): if check_string(s, regexp, hilight, exact): return None else: return s else: check = lambda s: check_string(s, regexp, hilight, exact) try: file_object = open(file, 'r') except IOError: # file doesn't exist return lines if tail or before_context: # for these options, I need to seek in the file, but is slower and uses a good deal of # memory if the log is too big, so we do this *only* for these options. file_lines = file_object.readlines() if tail: # instead of searching in the whole file and later pick the last few lines, we # reverse the log, search until count reached and reverse it again, that way is a lot # faster file_lines.reverse() # don't invert context switches before_context, after_context = after_context, before_context if before_context: before_context_range = range(1, before_context + 1) before_context_range.reverse() limit = tail or head line_idx = 0 while line_idx < len(file_lines): line = file_lines[line_idx] line = check(line) if line: if before_context: separator() trimmed = False for id in before_context_range: try: context_line = file_lines[line_idx - id] if check(context_line): # match in before context, that means we appended these same lines in a # previous match, so we delete them merging both paragraphs if not trimmed: del lines[id - before_context - 1:] trimmed = True else: append(context_line) except IndexError: pass append(line) count_match(line) if after_context: id, offset = 0, 0 while id < after_context + offset: id += 1 try: context_line = file_lines[line_idx + id] _context_line = check(context_line) if _context_line: offset = id context_line = _context_line # so match is hilighted with --hilight count_match() append(context_line) except IndexError: pass separator() line_idx += id if limit and lines.matches_count >= limit: break line_idx += 1 if tail: lines.reverse() else: # do a normal grep limit = head for line in file_object: line = check(line) if line: count or append(line) count_match(line) if after_context: id, offset = 0, 0 while id < after_context + offset: id += 1 try: context_line = file_object.next() _context_line = check(context_line) if _context_line: offset = id context_line = _context_line count_match() count or append(context_line) except StopIteration: pass separator() if limit and lines.matches_count >= limit: break file_object.close() return lines def grep_buffer(buffer, head, tail, after_context, before_context, count, regexp, hilight, exact, invert): """Return a list of lines that match 'regexp' in 'buffer', if no regexp returns all lines.""" lines = linesList() if count: tail = head = after_context = before_context = False hilight = '' elif exact: before_context = after_context = False #debug(' '.join(map(str, (tail, head, after_context, before_context, count, exact, hilight)))) # Using /grep in grep's buffer can lead to some funny effects # We should take measures if that's the case def make_get_line_funcion(): """Returns a function for get lines from the infolist, depending if the buffer is grep's or not.""" string_remove_color = weechat.string_remove_color infolist_string = weechat.infolist_string grep_buffer = weechat.buffer_search('python', SCRIPT_NAME) if grep_buffer and buffer == grep_buffer: def function(infolist): prefix = infolist_string(infolist, 'prefix') message = infolist_string(infolist, 'message') if prefix: # only our messages have prefix, ignore it return None return message else: infolist_time = weechat.infolist_time def function(infolist): prefix = string_remove_color(infolist_string(infolist, 'prefix'), '') message = string_remove_color(infolist_string(infolist, 'message'), '') date = infolist_time(infolist, 'date') return '%s\t%s\t%s' %(date, prefix, message) return function get_line = make_get_line_funcion() infolist = weechat.infolist_get('buffer_lines', buffer, '') if tail: # like with grep_file() if we need the last few matching lines, we move the cursor to # the end and search backwards infolist_next = weechat.infolist_prev infolist_prev = weechat.infolist_next else: infolist_next = weechat.infolist_next infolist_prev = weechat.infolist_prev limit = head or tail # define these locally as it makes the loop run slightly faster append = lines.append count_match = lines.count_match separator = lines.append_separator if invert: def check(s): if check_string(s, regexp, hilight, exact): return None else: return s else: check = lambda s: check_string(s, regexp, hilight, exact) if before_context: before_context_range = range(1, before_context + 1) before_context_range.reverse() while infolist_next(infolist): line = get_line(infolist) if line is None: continue line = check(line) if line: if before_context: separator() trimmed = False for id in before_context_range: if not infolist_prev(infolist): trimmed = True for id in before_context_range: context_line = get_line(infolist) if check(context_line): if not trimmed: del lines[id - before_context - 1:] trimmed = True else: append(context_line) infolist_next(infolist) count or append(line) count_match(line) if after_context: id, offset = 0, 0 while id < after_context + offset: id += 1 if infolist_next(infolist): context_line = get_line(infolist) _context_line = check(context_line) if _context_line: context_line = _context_line offset = id count_match() append(context_line) else: # in the main loop infolist_next will start again an cause an infinite loop # this will avoid it infolist_next = lambda x: 0 separator() if limit and lines.matches_count >= limit: break weechat.infolist_free(infolist) if tail: lines.reverse() return lines ### this is our main grep function hook_file_grep = None def show_matching_lines(): """ Greps buffers in search_in_buffers or files in search_in_files and updates grep buffer with the result. """ global pattern, matchcase, number, count, exact, hilight, invert global tail, head, after_context, before_context global search_in_files, search_in_buffers, matched_lines, home_dir global time_start matched_lines = linesDict() #debug('buffers:%s \nlogs:%s' %(search_in_buffers, search_in_files)) time_start = now() # buffers if search_in_buffers: regexp = make_regexp(pattern, matchcase) for buffer in search_in_buffers: buffer_name = weechat.buffer_get_string(buffer, 'name') matched_lines[buffer_name] = grep_buffer(buffer, head, tail, after_context, before_context, count, regexp, hilight, exact, invert) # logs if search_in_files: size_limit = get_config_int('size_limit', allow_empty_string=True) background = False if size_limit or size_limit == 0: size = sum(map(get_size, search_in_files)) if size > size_limit * 1024: background = True elif size_limit == '': background = False 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) 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 if hook_file_grep: 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' """ 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) 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 return WEECHAT_RC_OK def get_grep_file_status(): global search_in_files, matched_lines, time_start elapsed = now() - time_start if len(search_in_files) == 1: log = '%s (%s)' %(strip_home(search_in_files[0]), human_readable_size(get_size(search_in_files[0]))) else: size = sum(map(get_size, search_in_files)) log = '%s log files (%s)' %(len(search_in_files), human_readable_size(size)) return 'Searching in %s, running for %.4f seconds. Interrupt it with "/grep stop" or "stop"' \ ' in grep buffer.' %(log, elapsed) ### Grep buffer ### def buffer_update(): """Updates our buffer with new lines.""" global pattern_tmpl, matched_lines, pattern, count, hilight, invert, exact time_grep = now() buffer = buffer_create() if get_config_boolean('clear_buffer'): weechat.buffer_clear(buffer) matched_lines.strip_separator() # remove first and last separators of each list len_total_lines = len(matched_lines) max_lines = get_config_int('max_lines') if not count and len_total_lines > max_lines: weechat.buffer_clear(buffer) def _make_summary(log, lines, note): return '%s matches "%s%s%s"%s in %s%s%s%s' \ %(lines.matches_count, color_summary, pattern_tmpl, color_info, invert and ' (inverted)' or '', color_summary, log, color_reset, note) if count: make_summary = lambda log, lines : _make_summary(log, lines, ' (not shown)') else: def make_summary(log, lines): if lines.stripped_lines: if lines: note = ' (last %s lines shown)' %len(lines) else: note = ' (not shown)' else: note = '' return _make_summary(log, lines, note) global weechat_format if hilight: # we don't want colors if there's match highlighting format_line = lambda s : '%s %s %s' %split_line(s) else: def format_line(s): global nick_dict, weechat_format date, nick, msg = split_line(s) if weechat_format: try: nick = nick_dict[nick] except KeyError: # cache nick nick_c = color_nick(nick) nick_dict[nick] = nick_c nick = nick_c return '%s%s %s%s %s' %(color_date, date, nick, color_reset, msg) else: #no formatting return msg prnt(buffer, '\n') print_line('Search for "%s%s%s"%s in %s%s%s.' %(color_summary, pattern_tmpl, color_info, invert and ' (inverted)' or '', color_summary, matched_lines, color_reset), buffer) # print last lines if matched_lines.get_matches_count(): if count: # with count we sort by matches lines instead of just lines. matched_lines_items = matched_lines.items_count() else: matched_lines_items = matched_lines.items() matched_lines.get_last_lines(max_lines) for log, lines in matched_lines_items: if lines.matches_count: # matched lines if not count: # print lines weechat_format = True if exact: lines.onlyUniq() for line in lines: #debug(repr(line)) if line == linesList._sep: # separator prnt(buffer, context_sep) else: if '\x00' in line: # log was corrupted error("Found garbage in log '%s', maybe it's corrupted" %log) line = line.replace('\x00', '') prnt_date_tags(buffer, 0, 'no_highlight', format_line(line)) # summary if count or get_config_boolean('show_summary'): summary = make_summary(log, lines) print_line(summary, buffer) # separator if not count and lines: prnt(buffer, '\n') else: print_line('No matches found.', buffer) # set title global time_start time_end = now() # total time time_total = time_end - time_start # percent of the total time used for grepping time_grep_pct = (time_grep - time_start)/time_total*100 #debug('time: %.4f seconds (%.2f%%)' %(time_total, time_grep_pct)) if not count and len_total_lines > max_lines: note = ' (last %s lines shown)' %len(matched_lines) else: note = '' title = "Search in %s%s%s %s matches%s | pattern \"%s%s%s\"%s %s | %.4f seconds (%.2f%%)" \ %(color_title, matched_lines, color_reset, matched_lines.get_matches_count(), note, color_title, pattern_tmpl, color_reset, invert and ' (inverted)' or '', format_options(), time_total, time_grep_pct) weechat.buffer_set(buffer, 'title', title) if get_config_boolean('go_to_buffer'): weechat.buffer_set(buffer, 'display', '1') # free matched_lines so it can be removed from memory del matched_lines def split_line(s): """Splits log's line 's' in 3 parts, date, nick and msg.""" global weechat_format if weechat_format and s.count('\t') >= 2: date, nick, msg = s.split('\t', 2) # date, nick, message else: # looks like log isn't in weechat's format weechat_format = False # incoming lines won't be formatted date, nick, msg = '', '', s # remove tabs if '\t' in msg: msg = msg.replace('\t', ' ') return date, nick, msg def print_line(s, buffer=None, display=False): """Prints 's' in script's buffer as 'script_nick'. For displaying search summaries.""" if buffer is None: buffer = buffer_create() say('%s%s' %(color_info, s), buffer) if display and get_config_boolean('go_to_buffer'): weechat.buffer_set(buffer, 'display', '1') def format_options(): global matchcase, number, count, exact, hilight, invert global tail, head, after_context, before_context options = [] append = options.append insert = options.insert chars = 'cHmov' for i, flag in enumerate((count, hilight, matchcase, exact, invert)): if flag: append(chars[i]) if head or tail: n = get_config_int('default_tail_head') if head: append('h') if head != n: insert(-1, ' -') append('n') append(head) elif tail: append('t') if tail != n: insert(-1, ' -') append('n') append(tail) if before_context and after_context and (before_context == after_context): append(' -C') append(before_context) else: if before_context: append(' -B') append(before_context) if after_context: append(' -A') append(after_context) s = ''.join(map(str, options)).strip() if s and s[0] != '-': s = '-' + s return s def buffer_create(title=None): """Returns our buffer pointer, creates and cleans the buffer if needed.""" buffer = weechat.buffer_search('python', SCRIPT_NAME) if not buffer: buffer = weechat.buffer_new(SCRIPT_NAME, 'buffer_input', '', '', '') weechat.buffer_set(buffer, 'time_for_each_line', '0') weechat.buffer_set(buffer, 'nicklist', '0') weechat.buffer_set(buffer, 'title', title or 'grep output buffer') weechat.buffer_set(buffer, 'localvar_set_no_log', '1') elif title: weechat.buffer_set(buffer, 'title', title) return buffer def buffer_input(data, buffer, input_data): """Repeats last search with 'input_data' as regexp.""" try: cmd_grep_stop(buffer, input_data) except: return WEECHAT_RC_OK global search_in_buffers, search_in_files global pattern try: if pattern and (search_in_files or search_in_buffers): # check if the buffer pointers are still valid for pointer in search_in_buffers: infolist = weechat.infolist_get('buffer', pointer, '') if not infolist: del search_in_buffers[search_in_buffers.index(pointer)] weechat.infolist_free(infolist) try: cmd_grep_parsing(input_data) except Exception, e: error('Argument error, %s' %e, buffer=buffer) return WEECHAT_RC_OK try: show_matching_lines() except Exception, e: error(e) except NameError: error("There isn't any previous search to repeat.", buffer=buffer) return WEECHAT_RC_OK ### Commands ### def cmd_init(): """Resets global vars.""" global home_dir, cache_dir, nick_dict global pattern_tmpl, pattern, matchcase, number, count, exact, hilight, invert global tail, head, after_context, before_context hilight = '' head = tail = after_context = before_context = invert = False matchcase = count = exact = False pattern_tmpl = pattern = number = None home_dir = get_home() cache_dir = {} # for avoid walking the dir tree more than once per command nick_dict = {} # nick cache for don't calculate nick color every time def cmd_grep_parsing(args): """Parses args for /grep and grep input buffer.""" global pattern_tmpl, pattern, matchcase, number, count, exact, hilight, invert global tail, head, after_context, before_context global log_name, buffer_name, only_buffers, all opts, args = getopt.gnu_getopt(args.split(), 'cmHeahtivn:bA:B:C:o', ['count', 'matchcase', 'hilight', 'exact', 'all', 'head', 'tail', 'number=', 'buffer', 'after-context=', 'before-context=', 'context=', 'invert', 'only-match']) #debug(opts, 'opts: '); debug(args, 'args: ') if len(args) >= 2: if args[0] == 'log': del args[0] log_name = args.pop(0) elif args[0] == 'buffer': del args[0] buffer_name = args.pop(0) def tmplReplacer(match): """This function will replace templates with regexps""" s = match.groups()[0] tmpl_args = s.split() tmpl_key, _, tmpl_args = s.partition(' ') try: template = templates[tmpl_key] if callable(template): r = template(tmpl_args) if not r: error("Template %s returned empty string "\ "(WeeChat doesn't have enough data)." %t) return r else: return template except: return t args = ' '.join(args) # join pattern for keep spaces if args: pattern_tmpl = args pattern = _tmplRe.sub(tmplReplacer, args) debug('Using regexp: %s', pattern) if not pattern: raise Exception, 'No pattern for grep the logs.' def positive_number(opt, val): try: number = int(val) if number < 0: raise ValueError return number except ValueError: if len(opt) == 1: opt = '-' + opt else: opt = '--' + opt raise Exception, "argument for %s must be a positive integer." %opt for opt, val in opts: opt = opt.strip('-') if opt in ('c', 'count'): count = not count elif opt in ('m', 'matchcase'): matchcase = not matchcase elif opt in ('H', 'hilight'): # hilight must be always a string! if hilight: hilight = '' else: hilight = '%s,%s' %(color_hilight, color_reset) # we pass the colors in the variable itself because check_string() must not use # weechat's module when applying the colors (this is for grep in a hooked process) elif opt in ('e', 'exact', 'o', 'only-match'): exact = not exact invert = False elif opt in ('a', 'all'): all = not all elif opt in ('h', 'head'): head = not head tail = False elif opt in ('t', 'tail'): tail = not tail head = False elif opt in ('b', 'buffer'): only_buffers = True elif opt in ('n', 'number'): number = positive_number(opt, val) elif opt in ('C', 'context'): n = positive_number(opt, val) after_context = n before_context = n elif opt in ('A', 'after-context'): after_context = positive_number(opt, val) elif opt in ('B', 'before-context'): before_context = positive_number(opt, val) elif opt in ('i', 'v', 'invert'): invert = not invert exact = False # number check if number is not None: if number == 0: head = tail = False number = None elif head: head = number elif tail: tail = number else: n = get_config_int('default_tail_head') if head: head = n elif tail: tail = n def cmd_grep_stop(buffer, args): global hook_file_grep, pattern, matched_lines, tmpFile if hook_file_grep: if args == 'stop': weechat.unhook(hook_file_grep) hook_file_grep = None 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 else: say(get_grep_file_status(), buffer) raise Exception def cmd_grep(data, buffer, args): """Search in buffers and logs.""" global pattern, matchcase, head, tail, number, count, exact, hilight try: cmd_grep_stop(buffer, args) except: return WEECHAT_RC_OK if not args: weechat.command('', '/help %s' %SCRIPT_COMMAND) return WEECHAT_RC_OK cmd_init() global log_name, buffer_name, only_buffers, all log_name = buffer_name = '' only_buffers = all = False # parse try: cmd_grep_parsing(args) except Exception, e: error('Argument error, %s' %e) return WEECHAT_RC_OK # find logs log_file = search_buffer = None if log_name: log_file = get_file_by_pattern(log_name, all) if not log_file: error("Couldn't find any log for %s. Try /logs" %log_name) return WEECHAT_RC_OK elif all: search_buffer = get_all_buffers() elif buffer_name: search_buffer = get_buffer_by_name(buffer_name) if not search_buffer: # there's no buffer, try in the logs log_file = get_file_by_name(buffer_name) if not log_file: error("Logs or buffer for '%s' not found." %buffer_name) return WEECHAT_RC_OK else: search_buffer = [search_buffer] else: search_buffer = [buffer] # make the log list global search_in_files, search_in_buffers search_in_files = [] search_in_buffers = [] if log_file: search_in_files = log_file elif not only_buffers: #debug(search_buffer) for pointer in search_buffer: log = get_file_by_buffer(pointer) #debug('buffer %s log %s' %(pointer, log)) if log: search_in_files.append(log) else: search_in_buffers.append(pointer) else: search_in_buffers = search_buffer # grepping try: show_matching_lines() except Exception, e: error(e) return WEECHAT_RC_OK def cmd_logs(data, buffer, args): """List files in Weechat's log dir.""" cmd_init() global home_dir sort_by_size = False filter = [] try: opts, args = getopt.gnu_getopt(args.split(), 's', ['size']) if args: filter = args for opt, var in opts: opt = opt.strip('-') if opt in ('size', 's'): sort_by_size = True except Exception, e: error('Argument error, %s' %e) return WEECHAT_RC_OK # is there's a filter, filter_excludes should be False file_list = dir_list(home_dir, filter, filter_excludes=not filter) if sort_by_size: file_list.sort(key=get_size) else: file_list.sort() file_sizes = map(lambda x: human_readable_size(get_size(x)), file_list) # calculate column lenght if file_list: L = file_list[:] L.sort(key=len) bigest = L[-1] column_len = len(bigest) + 3 else: column_len = '' buffer = buffer_create() if get_config_boolean('clear_buffer'): weechat.buffer_clear(buffer) file_list = zip(file_list, file_sizes) msg = 'Found %s logs.' %len(file_list) print_line(msg, buffer, display=True) for file, size in file_list: separator = column_len and '.'*(column_len - len(file)) prnt(buffer, '%s %s %s' %(strip_home(file), separator, size)) if file_list: print_line(msg, buffer) return WEECHAT_RC_OK ### Completion ### def completion_log_files(data, completion_item, buffer, completion): #debug('completion: %s' %', '.join((data, completion_item, buffer, completion))) global home_dir l = len(home_dir) completion_list_add = weechat.hook_completion_list_add WEECHAT_LIST_POS_END = weechat.WEECHAT_LIST_POS_END for log in dir_list(home_dir): completion_list_add(completion, log[l:], 0, WEECHAT_LIST_POS_END) return WEECHAT_RC_OK def completion_grep_args(data, completion_item, buffer, completion): for arg in ('count', 'all', 'matchcase', 'hilight', 'exact', 'head', 'tail', 'number', 'buffer', 'after-context', 'before-context', 'context', 'invert', 'only-match'): weechat.hook_completion_list_add(completion, '--' + arg, 0, weechat.WEECHAT_LIST_POS_SORT) for tmpl in templates: weechat.hook_completion_list_add(completion, '%{' + tmpl, 0, weechat.WEECHAT_LIST_POS_SORT) return WEECHAT_RC_OK ### Templates ### # template placeholder _tmplRe = re.compile(r'%\{(\w+.*?)(?:\}|$)') # will match 999.999.999.999 but I don't care ipAddress = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' domain = r'[\w-]{2,}(?:\.[\w-]{2,})*\.[a-z]{2,}' url = r'\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?' % (domain, ipAddress) def make_url_regexp(args): #debug('make url: %s', args) if args: words = r'(?:%s)' %'|'.join(map(re.escape, args.split())) return r'(?:\w+://|www\.)[^\s]*%s[^\s]*(?:/[^\])>\s]*)?' %words else: return url def make_simple_regexp(pattern): s = '' for c in pattern: if c == '*': s += '.*' elif c == '?': s += '.' else: s += re.escape(c) return s templates = { 'ip': ipAddress, 'url': make_url_regexp, 'escape': lambda s: re.escape(s), 'simple': make_simple_regexp, 'domain': domain, } ### Main ### def delete_bytecode(): global script_path bytecode = path.join(script_path, SCRIPT_NAME + '.pyc') if path.isfile(bytecode): os.remove(bytecode) return WEECHAT_RC_OK if __name__ == '__main__' and import_ok and \ weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, \ SCRIPT_DESC, 'delete_bytecode', ''): home_dir = get_home() # for import ourselves global script_path script_path = path.dirname(__file__) sys.path.append(script_path) delete_bytecode() # check python version import sys global bytecode if sys.version_info > (2, 6): bytecode = 'B' else: bytecode = '' weechat.hook_command(SCRIPT_COMMAND, cmd_grep.__doc__, "[log | buffer | stop] [-a|--all] [-b|--buffer] [-c|--count] [-m|--matchcase] " "[-H|--hilight] [-o|--only-match] [-i|-v|--invert] [(-h|--head)|(-t|--tail) [-n|--number ]] " "[-A|--after-context ] [-B|--before-context ] [-C|--context ] ", # help """ log : Search in one log that matches in the logger path. Use '*' and '?' as wildcards. buffer : Search in buffer , if there's no buffer with it will try to search for a log file. stop: Stops a currently running search. -a --all: Search in all open buffers. If used with 'log ' search in all logs that matches . -b --buffer: Search only in buffers, not in file logs. -c --count: Just count the number of matched lines instead of showing them. -m --matchcase: Don't do case insensible search. -H --hilight: Colour exact matches in output buffer. -o --only-match: Print only the matching part of the line (unique matches). -v -i --invert: Print lines that don't match the regular expression. -t --tail: Print the last 10 matching lines. -h --head: Print the first 10 matching lines. -n --number : Overrides default number of lines for --tail or --head. -A --after-context : Shows lines of trailing context after matching lines. -B --before-context : Shows lines of leading context before matching lines. -C --context : Same as using both --after-context and --before-context simultaneously. : Expression to search. Grep buffer: Input line accepts most arguemnts of /grep, it'll repeat last search using the new arguments provided. You can't search in different logs from the buffer's input. Boolean arguments like --count, --tail, --head, --hilight, ... are toggleable Python regular expression syntax: See http://docs.python.org/lib/re-syntax.html Grep Templates: %{url [text]}: Matches anything like an url, or an url with text. %{ip}: Matches anything that looks like an ip. %{domain}: Matches anything like a domain. %{escape text}: Escapes text in pattern. %{simple pattern}: Converts a pattern with '*' and '?' wildcards into a regexp. Examples: Search for urls with the word 'weechat' said by 'nick' /grep nick\\t.*%{url weechat} Search for '*.*' string /grep %{escape *.*} """, # completion template "buffer %(buffers_names) %(grep_arguments)|%*" "||log %(grep_log_files) %(grep_arguments)|%*" "||stop" "||%(grep_arguments)|%*", 'cmd_grep' ,'') weechat.hook_command('logs', cmd_logs.__doc__, "[-s|--size] []", "-s --size: Sort logs by size.\n" " : Only show logs that match . Use '*' and '?' as wildcards.", '--size', 'cmd_logs', '') weechat.hook_completion('grep_log_files', "list of log files", 'completion_log_files', '') weechat.hook_completion('grep_arguments', "list of arguments", 'completion_grep_args', '') # settings for opt, val in settings.iteritems(): if not weechat.config_is_set_plugin(opt): weechat.config_set_plugin(opt, val) # colors color_date = weechat.color('brown') color_info = weechat.color('cyan') color_hilight = weechat.color('lightred') color_reset = weechat.color('reset') color_title = weechat.color('yellow') color_summary = weechat.color('lightcyan') color_delimiter = weechat.color('chat_delimiters') color_script_nick = weechat.color('chat_nick') # pretty [grep] script_nick = '%s[%s%s%s]%s' %(color_delimiter, color_script_nick, SCRIPT_NAME, color_delimiter, color_reset) script_nick_nocolor = '[%s]' %SCRIPT_NAME # paragraph separator when using context options context_sep = '%s\t%s--' %(script_nick, color_info) # ------------------------------------------------------------------------- # Debug if weechat.config_get_plugin('debug'): try: # custom debug module I use, allows me to inspect script's objects. import pybuffer debug = pybuffer.debugBuffer(globals(), '%s_debug' % SCRIPT_NAME) except: def debug(s, *args): if not isinstance(s, basestring): s = str(s) if args: s = s %args prnt('', '%s\t%s' %(script_nick, s)) else: def debug(*args): pass # vim:set shiftwidth=4 tabstop=4 softtabstop=4 expandtab textwidth=100: weechat-scripts-20131007/python/lnotify.py0000644000175000017500000000465112224522664017132 0ustar manumanu# Copyright (C) 2010 Kevin Morris # lnotify made to use for libnotify notifications # This script was adapted from 'notify' # Hope you guys like it :O # # 0.1.2 # added option to display weechat's icon by tomboy64 # # 0.1.3 # changed the way that icon to WeeChat notification is specified. # (No absolute path is needed) # /usr/bin/notify-send isn't needed anymore. # (pynotify is handling notifications now) # changed the way that lnotify works. When using gnome 3, every new # notification was creating a new notification instance. The way that # it is now, all WeeChat notifications are in a group (that have the # WeeChat icon and have WeeChat name). # Got report that it has better look for KDE users too. import weechat, string, pynotify weechat.register("lnotify", "kevr", "0.1.3", "GPL3", "lnotify - A libnotify script for weechat", "", "") # Set up here, go no further! settings = { "show_highlight" : "on", "show_priv_msg" : "on", "show_icon" : "weechat" } # Init everything if not pynotify.init("WeeChat"): print "Failed to load lnotify" for option, default_value in settings.items(): if weechat.config_get_plugin(option) == "": weechat.config_set_plugin(option, default_value) # Hook privmsg/hilights weechat.hook_print("", "irc_privmsg", "", 1, "get_notified", "") # Functions def get_notified(data, bufferp, uber_empty, tagsn, isdisplayed, ishilight, prefix, message): if (weechat.buffer_get_string(bufferp, "localvar_type") == "private" and weechat.config_get_plugin('show_priv_msg') == "on"): buffer = (weechat.buffer_get_string(bufferp, "short_name") or weechat.buffer_get_string(bufferp, "name")) if buffer == prefix: n = pynotify.Notification("WeeChat", "%s said: %s" % (prefix, message),weechat.config_get_plugin('show_icon')) if not n.show(): print "Failed to send notification" elif (ishilight == "1" and weechat.config_get_plugin('show_highlight') == "on"): buffer = (weechat.buffer_get_string(bufferp, "short_name") or weechat.buffer_get_string(bufferp, "name")) n = pynotify.Notification("WeeChat", "In %s, %s said: %s" % (buffer, prefix, message),weechat.config_get_plugin('show_icon')) if not n.show(): print "Failed to send notification" return weechat.WEECHAT_RC_OK weechat-scripts-20131007/python/weeget.py0000644000175000017500000012261712224522657016733 0ustar manumanu# -*- coding: utf-8 -*- # # Copyright (C) 2009-2012 Sebastien 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 # 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 . # # # WeeChat scripts manager. # (this script requires WeeChat >= 0.3.0 and python >= 2.6) # # IMPORTANT: this script is obsolete since WeeChat 0.3.9, it is replaced # by command /script (script plugin). # # History: # # 2013-03-06, Sebastien Helleu : # version 2.0: display "obsolete script" warning on WeeChat >= 0.3.9 # 2012-10-08, Sebastien Helleu : # version 1.9: remove obsolete database field "desc_fr" # (it was causing error with /weeget list xxx) # 2012-03-09, Sebastien Helleu : # version 1.8: fix reload of config file # 2012-02-27, Sebastien Helleu : # version 1.7: add support of scheme scripts # 2012-02-05, Sebastien Helleu : # version 1.6: use URL transfer from API (for WeeChat >= 0.3.7) # 2012-01-03, Sebastien Helleu : # version 1.5: make script compatible with Python 3.x # 2011-03-25, Sebastien Helleu : # version 1.4: add completion with installed scripts for action "remove" # 2011-03-10, Sebastien Helleu : # version 1.3: add script extension in script name completion and a new # completion with tags for actions "list" and "listinstalled" # 2011-02-13, Sebastien Helleu : # version 1.2: use new help format for command arguments # 2010-11-08, Sebastien Helleu : # version 1.1: get python 2.x binary for hook_process (fix problem # when python 3.x is default python version, requires # WeeChat >= 0.3.4) # 2010-02-22, Blake Winton : # version 1.0: add action "listinstalled" for command /weeget # 2010-01-25, Sebastien Helleu : # version 0.9: fix "running" status of scripts with /weeget check # 2009-09-30, Sebastien Helleu : # version 0.8: fix bugs and add missing info in "/weeget show", # display warning if url for plugins.xml.gz is old site # 2009-09-07, Sebastien Helleu : # version 0.7: update weechat site with new URL # 2009-05-02, Sebastien Helleu : # version 0.6: sync with last API changes # 2009-04-15, Sebastien Helleu : # version 0.5: display missing module(s) when import failed # 2009-04-11, Sebastien Helleu : # version 0.4: use new completion for command arguments # 2009-04-07, Sebastien Helleu : # version 0.3: fix bug with install/upgrade when weeget is updated with # other scripts: ensure that weeget is always the last # installed script # 2009-04-07, Sebastien Helleu : # version 0.2: add author's mail in script description # 2009-04-05, Sebastien Helleu : # version 0.1: initial release # SCRIPT_NAME = "weeget" SCRIPT_AUTHOR = "Sebastien Helleu " SCRIPT_VERSION = "2.0" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "WeeChat scripts manager" SCRIPT_COMMAND = "weeget" 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 sys, os, stat, time, gzip, hashlib, xml.dom.minidom except ImportError as message: print("Missing package(s) for %s: %s" % (SCRIPT_NAME, message)) import_ok = False CONFIG_FILE_NAME = "wg" SCRIPT_EXTENSION = { "perl" : "pl", "python": "py", "ruby" : "rb", "lua" : "lua", "tcl" : "tcl", "guile" : "scm", } # timeout for download of plugins.xml.gz TIMEOUT_UPDATE = 60 * 1000 # timeout for download of a script TIMEOUT_SCRIPT = 60 * 1000 # config file and options wg_config_file = "" wg_config_option = {} # action (install, remove, ..) and arguments wg_action = "" wg_action_args = "" # loaded scripts wg_loaded_scripts = {} # hook process and stdout wg_hook_process = { "update": "", "script": "" } wg_stdout = { "update": "", "script": "" } # scripts read from plugins.xml.gz wg_scripts = {} # list of script to install, and script currently installing wg_scripts_to_install = [] wg_current_script_install = {} # =================================[ config ]================================= def wg_config_init(): """ Initialization of configuration file. Sections: color, scripts. """ global wg_config_file, wg_config_option wg_config_file = weechat.config_new(CONFIG_FILE_NAME, "wg_config_reload_cb", "") if wg_config_file == "": return # section "color" section_color = weechat.config_new_section( wg_config_file, "color", 0, 0, "", "", "", "", "", "", "", "", "", "") if section_color == "": weechat.config_free(wg_config_file) return wg_config_option["color_script"] = weechat.config_new_option( wg_config_file, section_color, "script", "color", "Color for script names", "", 0, 0, "cyan", "cyan", 0, "", "", "", "", "", "") wg_config_option["color_installed"] = weechat.config_new_option( wg_config_file, section_color, "installed", "color", "Color for \"installed\" indicator", "", 0, 0, "yellow", "yellow", 0, "", "", "", "", "", "") wg_config_option["color_running"] = weechat.config_new_option( wg_config_file, section_color, "running", "color", "Color for \"running\" indicator", "", 0, 0, "lightgreen", "lightgreen", 0, "", "", "", "", "", "") wg_config_option["color_obsolete"] = weechat.config_new_option( wg_config_file, section_color, "obsolete", "color", "Color for \"obsolete\" indicator", "", 0, 0, "lightmagenta", "lightmagenta", 0, "", "", "", "", "", "") wg_config_option["color_unknown"] = weechat.config_new_option( wg_config_file, section_color, "unknown", "color", "Color for \"unknown status\" indicator", "", 0, 0, "lightred", "lightred", 0, "", "", "", "", "", "") wg_config_option["color_language"] = weechat.config_new_option( wg_config_file, section_color, "language", "color", "Color for language names", "", 0, 0, "lightblue", "lightblue", 0, "", "", "", "", "", "") # section "scripts" section_scripts = weechat.config_new_section( wg_config_file, "scripts", 0, 0, "", "", "", "", "", "", "", "", "", "") if section_scripts == "": weechat.config_free(wg_config_file) return wg_config_option["scripts_url"] = weechat.config_new_option( wg_config_file, section_scripts, "url", "string", "URL for file with list of plugins", "", 0, 0, "http://www.weechat.org/files/plugins.xml.gz", "http://www.weechat.org/files/plugins.xml.gz", 0, "", "", "", "", "", "") wg_config_option["scripts_dir"] = weechat.config_new_option( wg_config_file, section_scripts, "dir", "string", "Local cache directory for" + SCRIPT_NAME, "", 0, 0, "%h/" + SCRIPT_NAME, "%h/" + SCRIPT_NAME, 0, "", "", "", "", "", "") wg_config_option["scripts_cache_expire"] = weechat.config_new_option( wg_config_file, section_scripts, "cache_expire", "integer", "Local cache expiration time, in minutes " "(-1 = never expires, 0 = always expires)", "", -1, 60*24*365, "60", "60", 0, "", "", "", "", "", "") def wg_config_reload_cb(data, config_file): """ Reload configuration file. """ return weechat.config_reload(config_file) def wg_config_read(): """ Read configuration file. """ global wg_config_file return weechat.config_read(wg_config_file) def wg_config_write(): """ Write configuration file. """ global wg_config_file return weechat.config_write(wg_config_file) def wg_config_color(color): """ Get a color from configuration. """ global wg_config_option option = wg_config_option.get("color_" + color, "") if option == "": return "" return weechat.color(weechat.config_string(option)) def wg_config_get_dir(): """ Return weeget directory, with expanded WeeChat home dir. """ global wg_config_option return weechat.config_string( wg_config_option["scripts_dir"]).replace("%h", weechat.info_get("weechat_dir", "")) def wg_config_create_dir(): """ Create weeget directory. """ dir = wg_config_get_dir() if not os.path.isdir(dir): os.makedirs(dir, mode=0o700) def wg_config_get_cache_filename(): """ Get local cache filename, based on URL. """ global wg_config_option return wg_config_get_dir() + os.sep + \ os.path.basename(weechat.config_string(wg_config_option["scripts_url"])) # =============================[ download file ]============================== def wg_download_file(url, filename, timeout, callback, callback_data): """Download a file with an URL. Return hook_process created.""" version = weechat.info_get("version_number", "") or 0 if int(version) >= 0x00030700: return weechat.hook_process_hashtable("url:%s" % url, { "file_out": filename }, timeout, callback, callback_data) else: script = [ "import sys", "try:", " if sys.version_info >= (3,):", " import urllib.request", " response = urllib.request.urlopen('%s')" % url, " else:", " import urllib2", " response = urllib2.urlopen(urllib2.Request('%s'))" % url, " f = open('%s', 'wb')" % filename, " f.write(response.read())", " response.close()", " f.close()", "except Exception as e:", " print('error:' + str(e))" ] return weechat.hook_process("python -c \"%s\"" % "\n".join(script), timeout, callback, callback_data) # ================================[ scripts ]================================= def wg_search_script_by_name(name): """ Search a script in list by name. Name can be short name ('weeget') or full name ('weeget.py'). """ global wg_scripts for id, script in wg_scripts.items(): if script["name"] == name or script["full_name"] == name: return script return None def wg_get_loaded_scripts(): """ Get python dictionary with loaded scripts. Keys are filenames and values are path to script, for example: 'weeget.py': '/home/xxx/.weechat/python/weeget.py' """ global wg_loaded_scripts wg_loaded_scripts = {} for language in SCRIPT_EXTENSION.keys(): infolist = weechat.infolist_get(language + "_script", "", "") while weechat.infolist_next(infolist): filename = weechat.infolist_string(infolist, "filename") if filename != "": wg_loaded_scripts[os.path.basename(filename)] = filename weechat.infolist_free(infolist) def wg_is_local_script_loaded(filename): """ Check if a script filename (like 'python/weeget.py') is loaded. """ global wg_loaded_scripts filename2 = filename if filename2.startswith("autoload/"): filename2 = filename2[9:] for name, path in wg_loaded_scripts.items(): if path.endswith(filename) or path.endswith(filename2): return True return False def wg_get_local_script_status(script): """ Check if a script is installed. 'script' is a dictionary retrieved from scripts xml list. """ global wg_loaded_scripts status = { "installed": "", "obsolete": "", "running": "" } local_dir = weechat.info_get("weechat_dir", "") + os.sep + script["language"] local_name = local_dir + os.sep + "autoload" + os.sep + script["full_name"] if not os.path.isfile(local_name): local_name = local_dir + os.sep + script["full_name"] if os.path.isfile(local_name): status["installed"] = "1" f = open(local_name, "rb") md5 = hashlib.md5() md5.update(f.read()) f.close() local_md5 = md5.hexdigest() if local_md5 != script["md5sum"]: status["obsolete"] = "1" if script["full_name"] in wg_loaded_scripts.keys(): status["running"] = "1" return status def wg_get_local_scripts(): """ Get list of all local scripts (in languages and autoload dirs). Return a dictionary with language as key and list of paths as value, with autoloaded scripts at beginning of list, for example: { 'perl': [ 'autoload/buffers.pl', 'autoload/weetris.pl', 'beep.pl', 'launcher.pl' ], 'python': [ 'autoload/weeget.py', 'go.py', 'vdm.py' ] } """ files = {} for language in SCRIPT_EXTENSION.keys(): files[language] = [] autoloaded_files = [] rootdir = weechat.info_get("weechat_dir", "") + os.sep + language for root, dirs, listfiles in os.walk(rootdir): if root == rootdir: files[language] = listfiles elif root == rootdir + os.sep + "autoload": autoloaded_files = listfiles for file in autoloaded_files: if file in files[language]: files[language].remove(file) files[language].insert(0, "autoload" + os.sep + file) return files def wg_get_local_scripts_status(): """ Return list of all local scripts with status (unknown/obsolete/running). For example: [ 'perl/weetris.pl': { 'unknown': '', 'obsolete': '1', 'running': '' }, 'python/weeget.py': { 'unknown': '', 'obsolete': '', 'running': '1' } ] """ local_scripts_status = [] local_scripts = wg_get_local_scripts() if len(local_scripts) > 0: for language, files in local_scripts.items(): for file in files: script_status = { "unknown": "", "obsolete": "", "running": "" } name_with_ext = os.path.basename(file) script = wg_search_script_by_name(os.path.basename(file)) if script == None: script_status["unknown"] = "1" else: status = wg_get_local_script_status(script) if status["obsolete"]: script_status["obsolete"] = "1" if wg_is_local_script_loaded(file): script_status["running"] = "1" local_scripts_status.append((language + os.sep + file, script_status)) return local_scripts_status def wg_search_scripts(search): """ Search word in scripts, return list of matching scripts. """ global wg_scripts if search == "": return wg_scripts scripts_matching = {} for id, script in wg_scripts.items(): if script["name"].lower().find(search) >= 0 \ or script["language"].lower().find(search) >= 0 \ or script["desc_en"].lower().find(search) >= 0 \ or script["tags"].lower().find(search) >= 0: scripts_matching[id] = script return scripts_matching def wg_list_scripts(search, installed=False): """ List all scripts (with optional search string). If installed == True, then list only installed scripts. For each script, display status (installed/running/new version available), name of script, language and description. For example: ir buffers pl Sidebar with list of buffers. i N go py Quick jump to buffers. i weetris pl Tetris-like game. """ global wg_scripts search = search.strip().lower() scripts_matching = wg_search_scripts(search) if len(scripts_matching) == 0: weechat.prnt("", "%s: no script found" % SCRIPT_NAME) else: weechat.prnt("", "") if search != "": if installed: weechat.prnt("", "Scripts installed matching \"%s\":" % search) else: weechat.prnt("", "Scripts for WeeChat %s matching \"%s\":" % (weechat.info_get("version", ""), search)) else: if installed: weechat.prnt("", "Scripts installed:") else: weechat.prnt("", "Scripts for WeeChat %s:" % weechat.info_get("version", "")) sorted_scripts = sorted(scripts_matching.items(), key=lambda s: s[1]["name"]) length_max_name = 0 for item in sorted_scripts: length = len(item[1]["name"]) if length > length_max_name: length_max_name = length str_format = "%%s%%s%%s%%s%%s%%s%%s %%s%%-%ds %%s%%-3s %%s%%s" \ % length_max_name for item in sorted_scripts: script = item[1] str_installed = " " str_running = " " str_obsolete = " " status = wg_get_local_script_status(script) if installed and not status["installed"]: continue if status["installed"]: str_installed = "i" if status["running"]: str_running = "r" if status["obsolete"]: str_obsolete = "N" weechat.prnt("", str_format % (wg_config_color("installed"), str_installed, wg_config_color("running"), str_running, wg_config_color("obsolete"), str_obsolete, weechat.color("chat"), wg_config_color("script"), script["name"], wg_config_color("language"), SCRIPT_EXTENSION[script["language"]], weechat.color("chat"), script["desc_en"])) def wg_show_script(name): """ Show detailed info about a script (in repository). For example: Script: weeget.py, version 0.7, license: GPL3 Author: Sebastien Helleu Status: installed, running Date: added: 2009-04-05, updated: 2009-09-07 URL: http://www.weechat.org/files/scripts/weeget.py MD5: 4b0458dd5cc5c9a09ba8078f89830869 Desc: Scripts manager. Tags: scripts Requires: python 2.5 Min: 0.3.0 """ if len(wg_scripts) == 0: return script = wg_search_script_by_name(name) if script == None: weechat.prnt("", "%s: script \"%s%s%s\" not found" % (SCRIPT_NAME, wg_config_color("script"), name, weechat.color("chat"))) else: weechat.prnt("", "") weechat.prnt("", " Script: %s%s%s, version %s, license: %s" % (wg_config_color("script"), script["full_name"], weechat.color("chat"), script["version"], script["license"])) weechat.prnt("", " Author: %s <%s>" % (script["author"], script["mail"])) status = wg_get_local_script_status(script) str_status = "not installed" if status["installed"]: str_status = "installed" if status["running"]: str_status += ", running" else: str_status += ", not running" if status["obsolete"]: str_status += " (new version available)" weechat.prnt("", " Status: %s" % str_status) date_added = script.get("added", "")[:10] str_updated = script.get("updated", "") if str_updated != "": date_updated = script["updated"][:10] if date_updated == "0000-00-00" or date_updated == date_added: str_updated = "" if str_updated != "": weechat.prnt("", " Date: added: %s, updated: %s" % (date_added, date_updated)) else: weechat.prnt("", " Date: added: %s" % date_added) weechat.prnt("", " URL: %s" % script.get("url", "")) weechat.prnt("", " MD5: %s" % script.get("md5sum", "")) weechat.prnt("", " Desc: %s" % script.get("desc_en", "")) weechat.prnt("", " Tags: %s" % script.get("tags", "")) str_requires = script.get("requirements", "") if str_requires == "": str_requires = "(nothing)" weechat.prnt("", "Requires: %s" % str_requires) vmin = script.get("min_weechat", "") vmax = script.get("max_weechat", "") if vmin != "": weechat.prnt("", " Min: %s" % vmin) if vmax != "": weechat.prnt("", " Max: %s" % vmax) def wg_install_next_script(): """ Install first script in list wg_scripts_to_install and remove it from list. """ global wg_scripts, wg_scripts_to_install, wg_current_script_install global wg_hook_process if len(wg_scripts) == 0: return # be sure weeget is ALWAYS last script to install/update # otherwise we'll lose end of list when weeget is unloaded by WeeChat if SCRIPT_NAME in wg_scripts_to_install: wg_scripts_to_install.remove(SCRIPT_NAME) wg_scripts_to_install.append(SCRIPT_NAME) # loop until a script is installed, or end if list is empty while len(wg_scripts_to_install) > 0: name = wg_scripts_to_install.pop(0) script = wg_search_script_by_name(name) if script == None: weechat.prnt("", "%s: script \"%s%s%s\" not found" % (SCRIPT_NAME, wg_config_color("script"), name, weechat.color("chat"))) else: status = wg_get_local_script_status(script) if status["installed"] and not status["obsolete"]: weechat.prnt("", "%s: script \"%s%s%s\" is already " "installed and up to date" % (SCRIPT_NAME, wg_config_color("script"), script["full_name"], weechat.color("chat"))) else: weechat.prnt("", "%s: downloading \"%s%s%s\"..." % (SCRIPT_NAME, wg_config_color("script"), script["full_name"], weechat.color("chat"))) if wg_hook_process["script"] != "": weechat.unhook(wg_hook_process["script"]) wg_hook_process["script"] = "" wg_current_script_install = script filename = wg_config_get_dir() + os.sep + script["full_name"] wg_hook_process["script"] = wg_download_file(script["url"], filename, TIMEOUT_SCRIPT, "wg_process_script_cb", "") # this function will be called again when script will be # downloaded return def wg_install_scripts(names): """ Install scripts. """ global wg_scripts_to_install for name in names.split(" "): wg_scripts_to_install.append(name) wg_install_next_script() def wg_process_script_cb(data, command, rc, stdout, stderr): """ Callback when reading a script from website. """ global wg_hook_process, wg_stdout, wg_current_script_install, wg_loaded_scripts if stdout != "": wg_stdout["script"] += stdout if stderr != "": wg_stdout["script"] += stderr if int(rc) >= 0: if wg_stdout["script"].startswith("error:"): weechat.prnt("", "%s%s: error downloading script (%s)" % (weechat.prefix("error"), SCRIPT_NAME, wg_stdout["update"][6:].strip())) else: # ask C plugin to install/load script weechat.hook_signal_send(wg_current_script_install["language"] + "_script_install", weechat.WEECHAT_HOOK_SIGNAL_STRING, wg_config_get_dir() + os.sep + wg_current_script_install["full_name"]) wg_hook_process["script"] = "" wg_install_next_script() return weechat.WEECHAT_RC_OK def wg_check_scripts(): """ Check status of local script(s). For each script found, display status (unknown/running/new version available). For example: r python/autoload/vdm.py ?r python/autoload/dummy.py rN python/shell.py perl/buffers.pl """ local_scripts_status = wg_get_local_scripts_status() if len(local_scripts_status) == 0: return weechat.prnt("", "") weechat.prnt("", "Local scripts:") for file, status in local_scripts_status: str_unknown = " " str_running = " " str_obsolete = " " if status["unknown"]: str_unknown = "?" if status["running"]: str_running = "r" if status["obsolete"]: str_obsolete = "N" weechat.prnt("", "%s%s%s%s%s%s%s %s%s%s%s" % (wg_config_color("unknown"), str_unknown, wg_config_color("running"), str_running, wg_config_color("obsolete"), str_obsolete, weechat.color("chat"), os.path.dirname(file), os.sep, wg_config_color("script"), os.path.basename(file))) def wg_upgrade_scripts(): """ Upgrade scripts. """ global wg_scripts, wg_scripts_to_install if len(wg_scripts) == 0: return scripts_to_upgrade = [] for id, script in wg_scripts.items(): status = wg_get_local_script_status(script) if status["installed"] and status["obsolete"]: scripts_to_upgrade.append(script["name"]) if len(scripts_to_upgrade) == 0: weechat.prnt("", "%s: all scripts are up to date" % SCRIPT_NAME) else: wg_scripts_to_install.extend(scripts_to_upgrade) wg_install_next_script() def wg_remove_scripts(names): """ Remove scripts. """ if len(wg_scripts) == 0: return list_names = names.split(" ") scripts_to_remove = {} for language in SCRIPT_EXTENSION.keys(): scripts_to_remove[language] = [] for name in list_names: script = wg_search_script_by_name(name) if script == None: weechat.prnt("", "%s: script \"%s%s%s\" not found" % (SCRIPT_NAME, wg_config_color("script"), name, weechat.color("chat"))) else: if script["full_name"] not in scripts_to_remove[script["language"]]: scripts_to_remove[script["language"]].append(script["full_name"]) for language in SCRIPT_EXTENSION.keys(): if len(scripts_to_remove[language]) > 0: # ask C plugin to remove script file(s) weechat.hook_signal_send(language + "_script_remove", weechat.WEECHAT_HOOK_SIGNAL_STRING, ",".join(scripts_to_remove[language])) # ==================================[ xml ]=================================== def wg_execute_action(): """ Execute action. """ global wg_action, wg_action_args, wg_loaded_scripts if wg_action != "": wg_get_loaded_scripts() if wg_action == "list": wg_list_scripts(wg_action_args) elif wg_action == "listinstalled": wg_list_scripts(wg_action_args, installed=True) elif wg_action == "show": wg_show_script(wg_action_args) elif wg_action == "install": wg_install_scripts(wg_action_args) elif wg_action == "check": wg_check_scripts() elif wg_action == "upgrade": wg_upgrade_scripts() elif wg_action == "remove": wg_remove_scripts(wg_action_args) else: weechat.prnt("", "%s%s: unknown action \"%s\"" % (weechat.prefix("error"), SCRIPT_NAME, wg_action)) # reset action wg_action = "" wg_action_args = "" wg_loaded_scripts = {} def wg_check_version(script): """ Check if a script is designed for current running WeeChat version.""" version = weechat.info_get("version", "") version = version.split("-", 1)[0] vmin = script.get("min_weechat", "") vmax = script.get("max_weechat", "") if vmin != "" and version < vmin: return False if vmax != "" and version > vmax: return False return True def wg_parse_xml(): """ Parse XML scripts list and return dictionary with list, with key 'id'. Example of item return in dictionary : '119': { 'name' : 'weeget', 'version' : '0.1', 'url' : 'http://www.weechat.org/files/scripts/weeget.py', 'language' : 'python', 'license' : 'GPL3', 'md5sum' : 'd500714fc19b0e10cc4e339e70739e4ad500714fc19b0e10cc4e339e70739e4a', 'tags' : 'scripts', 'desc_en' : 'Scripts manager.', 'requirements': 'python 2.5', 'min_weechat' : '0.3.0', 'max_weechat' : '', 'author' : 'FlashCode', 'mail' : 'flashcode [at] flashtux [dot] org', 'added' : '2009-04-05 22:39:18', 'updated' : '0000-00-00 00:00:00' } """ global wg_scripts, wg_action, wg_action_args wg_scripts = {} try: f = gzip.open(wg_config_get_cache_filename(), "rb") string = f.read() f.close() except: weechat.prnt("", "%s%s: unable to read xml file" % (weechat.prefix("error"), SCRIPT_NAME)) else: try: dom = xml.dom.minidom.parseString(string) except: weechat.prnt("", "%s%s: unable to parse xml list of scripts" % (weechat.prefix("error"), SCRIPT_NAME)) # discard action wg_action = "" wg_action_args = "" else: for scriptNode in dom.getElementsByTagName("plugin"): id = scriptNode.getAttribute("id") script = {} for node in scriptNode.childNodes: if node.nodeType == node.ELEMENT_NODE: if node.firstChild != None: nodename = node.nodeName value = node.firstChild.data if sys.version_info < (3,): # python 2.x: convert unicode to str (in python 3.x, id and text are already strings) nodename = nodename.encode("utf-8") value = value.encode("utf-8") script[nodename] = value if script["language"] in SCRIPT_EXTENSION: script["full_name"] = script["name"] + "." + SCRIPT_EXTENSION[script["language"]] if wg_check_version(script): wg_scripts[id] = script wg_execute_action() def wg_process_update_cb(data, command, rc, stdout, stderr): """ Callback when reading XML cache file from website. """ global wg_hook_process, wg_stdout, wg_scripts if stdout != "": wg_stdout["update"] += stdout if stderr != "": wg_stdout["update"] += stderr if int(rc) >= 0: if wg_stdout["update"].startswith("error:"): weechat.prnt("", "%s%s: error downloading scripts (%s)" % (weechat.prefix("error"), SCRIPT_NAME, wg_stdout["update"][6:].strip())) else: weechat.prnt("", "%s: scripts downloaded" % SCRIPT_NAME) wg_parse_xml() wg_hook_process["update"] = "" return weechat.WEECHAT_RC_OK def wg_update_cache(): """ Download list of scripts and update local cache. """ global wg_config_option, wg_hook_process, wg_stdout # get data from website, via hook_process if wg_hook_process["update"] != "": weechat.unhook(wg_hook_process["update"]) wg_hook_process["update"] = "" weechat.prnt("", "%s: downloading list of scripts..." % SCRIPT_NAME) wg_stdout["update"] = "" wg_config_create_dir() url = weechat.config_string(wg_config_option["scripts_url"]) filename = wg_config_get_cache_filename() wg_hook_process["update"] = wg_download_file(url, filename, TIMEOUT_UPDATE, "wg_process_update_cb", "") def wg_read_scripts(download_list=True): """ Read scripts list (download list if needed and asked). """ global wg_scripts cache_file = wg_config_get_cache_filename() if os.path.isfile(cache_file): # check if local cache file is too old cache_expire = weechat.config_integer(wg_config_option["scripts_cache_expire"]) * 60 if cache_expire >= 0: diff_time = time.time() - os.stat(cache_file)[stat.ST_MTIME] if download_list and diff_time >= cache_expire: os.unlink(cache_file) wg_scripts.clear() if len(wg_scripts) > 0: wg_execute_action() else: if os.path.isfile(cache_file): wg_parse_xml() elif download_list: wg_update_cache() # ================================[ command ]================================= def wg_cmd(data, buffer, args): """ Callback for /weeget command. """ global wg_action, wg_action_args if args == "": weechat.command("", "/help %s" % SCRIPT_COMMAND) return weechat.WEECHAT_RC_OK argv = args.strip().split(" ", 1) if len(argv) == 0: return weechat.WEECHAT_RC_OK wg_action = "" wg_action_args = "" # check arguments if len(argv) < 2: if argv[0] == "show" or \ argv[0] == "install" or \ argv[0] == "remove": weechat.prnt("", "%s: too few arguments for action \"%s\"" % (SCRIPT_NAME, argv[0])) return weechat.WEECHAT_RC_OK # execute asked action if argv[0] == "update": wg_update_cache() else: wg_action = argv[0] wg_action_args = "" if len(argv) > 1: wg_action_args = argv[1] wg_read_scripts() return weechat.WEECHAT_RC_OK def wg_completion_scripts_cb(data, completion_item, buffer, completion): """ Complete with known script names, for command '/weeget'. """ global wg_scripts wg_read_scripts(download_list=False) if len(wg_scripts) > 0: for id, script in wg_scripts.items(): weechat.hook_completion_list_add(completion, script["full_name"], 0, weechat.WEECHAT_LIST_POS_SORT) return weechat.WEECHAT_RC_OK def wg_completion_scripts_installed_cb(data, completion_item, buffer, completion): """ Complete with names of scripts installed, for command '/weeget'. """ global wg_scripts wg_read_scripts(download_list=False) if len(wg_scripts) > 0: for id, script in wg_scripts.items(): status = wg_get_local_script_status(script) if status["installed"]: weechat.hook_completion_list_add(completion, script["full_name"], 0, weechat.WEECHAT_LIST_POS_SORT) return weechat.WEECHAT_RC_OK def wg_completion_scripts_tags_cb(data, completion_item, buffer, completion): """ Complete with known tags, for command '/weeget'. """ global wg_scripts wg_read_scripts(download_list=False) if len(wg_scripts) > 0: for id, script in wg_scripts.items(): if script["tags"]: for tag in script["tags"].split(","): weechat.hook_completion_list_add(completion, tag, 0, weechat.WEECHAT_LIST_POS_SORT) 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, "wg_unload_script", ""): wg_config_init() wg_config_read() if weechat.config_string(wg_config_option["scripts_url"]).find("weechat.flashtux.org") >= 0: weechat.prnt("", "%sWarning: old site still used in URL for plugins.xml.gz, you should do: /unset wg.scripts.url" % weechat.prefix("error")) str_installed = wg_config_color("installed") + "i" + weechat.color("chat") str_unknown = wg_config_color("unknown") + "?" + weechat.color("chat") str_running = wg_config_color("running") + "r" + weechat.color("chat") str_obsolete = wg_config_color("obsolete") + "N" + weechat.color("chat") weechat.hook_command(SCRIPT_COMMAND, "WeeChat scripts manager", "list|listinstalled [|] || show