weechat-scripts-20180330/0000755000175000017500000000000013257413202013542 5ustar manumanuweechat-scripts-20180330/lua/0000755000175000017500000000000013257413201014322 5ustar manumanuweechat-scripts-20180330/lua/mpdbitl.lua0000644000175000017500000004053413257413201016466 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-20180330/lua/urlselect.lua0000644000175000017500000013062613257413201017037 0ustar manumanu--[[ urlselect - A script for interactively select URL and perform an action on it To activate, run /urlselect. View the README at https://github.com/tomoe-mami/weechat-scripts/tree/master/urlselect for more information. Author: tomoe-mami/singalaut License: WTFPL Requires: Weechat 1.0+ ]] local w = weechat local g = { script = { name = "urlselect", version = "0.4", author = "tomoe-mami ", license = "WTFPL", description = "Interactively select URL" }, defaults = { scan_merged_buffers = { type = "boolean", value = "0", description = "Scan URLs from buffers that are merged with the current one" }, tags = { type = "list", value = "notify_message,notify_private,notify_highlight", description = "Comma separated list of tags. If not empty, script will only " .. "scan URLs from messages with any of these tags" }, time_format = { type = "string", value = "%H:%M:%S", description = "Format of time" }, status_timeout = { type = "number", value = "1300", description = "Timeout for displaying status notification (in milliseconds)" }, buffer_name = { type = "string", value = "normal", description = "Type of name to use inside urlselect_buffer_name item. " .. "Valid values are \"full\", \"normal\", and \"short\"" }, use_simple_matching = { type = "boolean", value = "0", description = "Use simple pattern matching when scanning for URLs" }, url_color = { type = "string", value = "_lightblue", description = "Color for URL" }, nick_color = { type = "string", value = "", description = "Color for nickname. Leave empty to use Weechat's nick color" }, highlight_color = { type = "string", value = "${weechat.color.chat_highlight},${weechat.color.chat_highlight_bg}", description = "Nickname color for highlighted message" }, index_color = { type = "string", value = "brown", description = "Color for URL index" }, message_color = { type = "string", value = "default", description = "Color for message text" }, time_color = { type = "string", value = "default", description = "Color for time" }, title_color = { type = "string", value = "default", description = "Color for bar title" }, key_color = { type = "string", value = "cyan", description = "Color for list of keys" }, buffer_number_color = { type = "string", value = "brown", description = "Color for buffer number" }, buffer_name_color = { type = "string", value = "green", description = "Color for buffer name" }, help_color = { type = "string", value = "default", description = "Color for help text" }, status_color = { type = "string", value = "black,green", description = "Color for status notification" }, search_scope = { type = "string", value = "url", valid_values = { url = true, msg = true, nick = true, ["nick+msg"] = true }, description = "Default search scope. Valid values are: url, msg, nick or nick+msg" }, search_prompt_color = { type = "string", value = "default", description = "Color for search prompt" }, search_scope_color = { type = "string", value = "green", description = "Color for current search scope" } }, config = {}, active = false, list = "", bar_items = { list = { "index", "nick", "url", "time", "duplicate", "message", "buffer_name", "buffer_number"}, extra = { "title", "help", "status", "search"} }, custom_commands = {}, hooks = {}, current_status = "", enable_help = false, last_index = 0, enable_search = false, search_scope = 1, scope_list = {"url", "msg", "nick", "nick+msg"} } g.bar = { main = { name = g.script.name }, search = { name = g.script.name .. "_search" }, help = { name = g.script.name .. "_help" } } g.keys = { normal = { ["meta2-B"] = "navigate next", ["meta2-A"] = "navigate previous", ["meta2-1~"] = "navigate last", ["meta2-4~"] = "navigate first", ["ctrl-P"] = "navigate previous-highlight", ["ctrl-N"] = "navigate next-highlight", ["ctrl-S"] = "hsignal", ["ctrl-C"] = "deactivate", ["ctrl-F"] = "search", ["meta-OP"] = "help", ["meta2-11~"] = "help" }, search = { ["ctrl-I"] = "scope next", ["meta2-Z"] = "scope previous", ["ctrl-N"] = "scope nick", ["ctrl-T"] = "scope msg", ["ctrl-U"] = "scope url", ["ctrl-B"] = "scope nick+msg" } } function unload_cb() if g.search_scope and g.scope_list[g.search_scope] then w.config_set_plugin("search_scope", g.scope_list[g.search_scope]) end end function set_default_open_command_cb(_, cmd, ret, out, err) if ret == w.WEECHAT_HOOK_PROCESS_ERROR or ret >= 0 then local open_cmd = "xdg-open" if out and out:match("^([^%s]+)") == "Darwin" then open_cmd = "open" end w.config_set_plugin("cmd.o", "/exec -bg -nosh " .. open_cmd .. " ${url}") w.config_set_plugin("label.o", open_cmd) end end function setup() assert( w.register( g.script.name, g.script.author, g.script.version, g.script.license, g.script.description, "unload_cb", ""), "Unable to register script. Perhaps it's already loaded before?") local wee_ver = tonumber(w.info_get("version_number", "") or 0) if wee_ver < 0x01000000 then error("This script requires WeeChat v1.0 or higher") end local first_run, total_cmd = init_config() setup_hooks() if total_cmd == 0 and first_run then print("No custom commands configured. Adding default custom command...") w.hook_process("uname -s", 5000, "set_default_open_command_cb", "") w.config_set_plugin("cmd.i", "/input insert ${url}\\x20") w.config_set_plugin("label.i", "insert into input bar") end setup_bar() if g.config.search_scope then cmd_action_search_scope(nil, g.config.search_scope) end end function print(msg, param) if not param or type(param) ~= "table" then param = {} end param.script_name = g.script.name if not param.no_eval then msg = w.string_eval_expression(msg, {}, param, {}) end local prefix = g.script.name if param.prefix_type then prefix = w.color("chat_prefix_" .. param.prefix_type) .. prefix end w.print("", prefix .. "\t" .. msg) end function get_or_set_option(name, info, value) local is_set = true if not value then if w.config_is_set_plugin(name) ~= 1 then is_set = false if info.type == "string" then value = w.string_eval_expression(info.value, {}, {}, {}) else value = info.value end w.config_set_plugin(name, value) if info.description then w.config_set_desc_plugin(name, info.description) end else value = w.config_get_plugin(name) end end if info.type == "list" then local list = {} for item in value:gmatch("([^,]+)") do table.insert(list, item:lower()) end value = list elseif info.type == "string" and info.valid_values then if not info.valid_values[value] then value = info.value end elseif info.type == "boolean" or info.type == "number" then value = tonumber(value) if info.type == "boolean" then value = value and value ~= 0 end end return value, is_set end function init_config() local total_cmd, not_set, first_run = 0 for name, info in pairs(g.defaults) do g.config[name], is_set = get_or_set_option(name, info) if first_run == nil and not is_set then first_run = true end end local prefix = "plugins.var.lua." .. g.script.name .. ".cmd." local cfg = w.infolist_get("option", "", prefix .. "*") if cfg and cfg ~= "" then while w.infolist_next(cfg) == 1 do local opt_name = w.infolist_string(cfg, "full_name") local opt_value = w.infolist_string(cfg, "value") local key = opt_name:sub(#prefix + 1) if key then local label = w.config_get_plugin("label." .. key) if set_custom_command(key, opt_value, label, true) then total_cmd = total_cmd + 1 end end end w.infolist_free(cfg) end return first_run, total_cmd end function set_custom_command(key, cmd, label, silent) if not key or not key:match("^[0-9a-z]$") then w.config_unset_plugin("cmd." .. key) if not silent then print( "You can only bind 1 character for custom command. " .. "Valid type of character are digit (0-9) and lowercase " .. "alphabet (a-z) ", { prefix_type = "error" }) end return false else local key_code = "meta-" .. key if not cmd or cmd == "" then if g.keys.normal[key_code] then g.keys.normal[key_code] = nil end if g.custom_commands[key] then g.custom_commands[key] = nil end if not silent then print("Key ${color:bold}${key}${color:-bold} removed", { key = key }) end else g.keys.normal[key_code] = "run " .. key g.custom_commands[key] = { command = cmd } if not label then label = w.config_get_plugin("label." .. key) end if label and label ~= "" then g.custom_commands[key].label = label end if not silent then print( "Key ${color:bold}alt-${key}${color:-bold} bound to command: " .. "${color:bold}${cmd}${color:-bold}", { key = key, cmd = cmd }) end end return true end end function set_custom_label(key, label) if key and key ~= "" and g.custom_commands[key] then if not label or label == "" then g.custom_commands[key].label = nil else g.custom_commands[key].label = label end end end function setup_hooks() w.hook_config("plugins.var.lua." .. g.script.name .. ".*", "config_cb", "") w.hook_command( g.script.name, "Control urlselect script", "[activate [current|merged]] " .. "|| bind [-label