From 50057d5149dda340b3b47aca4096f4a6ec66b9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Fri, 6 Aug 2021 16:12:15 +0200 Subject: Come up with sillier names for the binaries I'm not entirely sure, but it looks like some people might not like jokes about the Holocaust. On a more serious note, the project has become more serious over the 7 or so years of its existence. --- plugins/xC/auto-rejoin.lua | 48 ++++++++++++ plugins/xC/censor.lua | 90 ++++++++++++++++++++++ plugins/xC/fancy-prompt.lua | 105 ++++++++++++++++++++++++++ plugins/xC/last-fm.lua | 178 ++++++++++++++++++++++++++++++++++++++++++++ plugins/xC/ping-timeout.lua | 32 ++++++++ plugins/xC/prime.lua | 68 +++++++++++++++++ plugins/xC/slack.lua | 147 ++++++++++++++++++++++++++++++++++++ plugins/xC/thin-cursor.lua | 27 +++++++ plugins/xC/utm-filter.lua | 62 +++++++++++++++ 9 files changed, 757 insertions(+) create mode 100644 plugins/xC/auto-rejoin.lua create mode 100644 plugins/xC/censor.lua create mode 100644 plugins/xC/fancy-prompt.lua create mode 100644 plugins/xC/last-fm.lua create mode 100644 plugins/xC/ping-timeout.lua create mode 100644 plugins/xC/prime.lua create mode 100644 plugins/xC/slack.lua create mode 100644 plugins/xC/thin-cursor.lua create mode 100644 plugins/xC/utm-filter.lua (limited to 'plugins/xC') diff --git a/plugins/xC/auto-rejoin.lua b/plugins/xC/auto-rejoin.lua new file mode 100644 index 0000000..f42fb2e --- /dev/null +++ b/plugins/xC/auto-rejoin.lua @@ -0,0 +1,48 @@ +-- +-- auto-rejoin.lua: join back automatically when someone kicks you +-- +-- Copyright (c) 2016, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +local timeout +xC.setup_config { + timeout = { + type = "integer", + comment = "auto rejoin timeout", + default = "0", + + on_change = function (v) + timeout = v + end, + validate = function (v) + if v < 0 then error ("timeout must not be negative", 0) end + end, + }, +} + +async, await = xC.async, coroutine.yield +xC.hook_irc (function (hook, server, line) + local msg = xC.parse (line) + if msg.command ~= "KICK" then return line end + + local who = msg.prefix:match ("^[^!]*") + local channel, whom = table.unpack (msg.params) + if who ~= whom and whom == server.user.nickname then + async.go (function () + await (async.timer_ms (timeout * 1000)) + server:send ("JOIN " .. channel) + end) + end + return line +end) diff --git a/plugins/xC/censor.lua b/plugins/xC/censor.lua new file mode 100644 index 0000000..49cab5b --- /dev/null +++ b/plugins/xC/censor.lua @@ -0,0 +1,90 @@ +-- +-- censor.lua: black out certain users' messages +-- +-- Copyright (c) 2016 - 2021, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +local to_pattern = function (mask) + if not mask:match ("!") then mask = mask .. "!*" end + if not mask:match ("@") then mask = mask .. "@*" end + + -- That is, * acts like a wildcard, otherwise everything is escaped + return "^" .. mask:gsub ("[%^%$%(%)%%%.%[%]%+%-%?]", "%%%0") + :gsub ("%*", ".*") .. "$" +end + +local patterns = {} +local read_masks = function (v) + patterns = {} + local add = function (who, where) + local channels = patterns[who] or {} + table.insert (channels, where) + patterns[who] = channels + end + for item in v:lower ():gmatch ("[^,]+") do + local who, where = item:match ("^([^/]+)/*(.*)") + if who then add (to_pattern (who), where == "" or where) end + end +end + +local quote +xC.setup_config { + masks = { + type = "string_array", + default = "\"\"", + comment = "user masks (optionally \"/#channel\") to censor", + on_change = read_masks + }, + quote = { + type = "string", + default = "\"\\x0301,01\"", + comment = "formatting prefix for censored messages", + on_change = function (v) quote = v end + }, +} + +local decolor = function (text) + local rebuilt, last = {""}, 1 + for start in text:gmatch ('()\x03') do + table.insert (rebuilt, text:sub (last, start - 1)) + local sub = text:sub (start + 1) + last = start + (sub:match ('^%d%d?,%d%d?()') or sub:match ('^%d?%d?()')) + end + return table.concat (rebuilt) .. text:sub (last) +end + +local censor = function (line) + -- Taking a shortcut to avoid lengthy message reassembly + local start, text = line:match ("^(.- PRIVMSG .- :)(.*)$") + local ctcp, rest = text:match ("^(\x01%g+ )(.*)") + text = ctcp and ctcp .. quote .. decolor (rest) or quote .. decolor (text) + return start .. text +end + +xC.hook_irc (function (hook, server, line) + local msg = xC.parse (line) + if msg.command ~= "PRIVMSG" then return line end + + local channel = msg.params[1]:lower () + for who, where in pairs (patterns) do + if msg.prefix:lower ():match (who) then + for _, x in pairs (where) do + if x == true or x == channel then + return censor (line) + end + end + end + end + return line +end) diff --git a/plugins/xC/fancy-prompt.lua b/plugins/xC/fancy-prompt.lua new file mode 100644 index 0000000..8ec697a --- /dev/null +++ b/plugins/xC/fancy-prompt.lua @@ -0,0 +1,105 @@ +-- +-- fancy-prompt.lua: the fancy multiline prompt you probably want +-- +-- Copyright (c) 2016, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- +-- Beware that it is a hack and only goes about 90% of the way, which is why +-- this functionality is only available as a plugin in the first place +-- (well, and also for customizability). +-- +-- The biggest problem is that the way we work with Readline is incompatible +-- with multiline prompts, and normal newlines just don't work. This is being +-- circumvented by using an overflowing single-line prompt with a specially +-- crafted character in the rightmost column that prevents the bar's background +-- from spilling all over the last line. +-- +-- There is also a problem with C-r search rendering not clearing out the +-- background but to really fix that mode, we'd have to fully reimplement it +-- since its alternative prompt very often gets overriden by accident anyway. + +xC.hook_prompt (function (hook) + local current = xC.current_buffer + local chan = current.channel + local s = current.server + + local bg_color = "255" + local current_n = 0 + local active = "" + for i, buffer in ipairs (xC.buffers) do + if buffer == current then + current_n = i + elseif buffer.new_messages_count ~= buffer.new_unimportant_count then + if active ~= "" then active = active .. "," end + if buffer.highlighted then + active = active .. "!" + bg_color = "224" + end + active = active .. i + end + end + if active ~= "" then active = "(" .. active .. ")" end + local x = current_n .. ":" .. current.name + if chan and chan.users_len ~= 0 then + local params = "" + for mode, param in pairs (chan.param_modes) do + params = params .. " +" .. mode .. " " .. param + end + local modes = chan.no_param_modes .. params:sub (3) + if modes ~= "" then x = x .. "(+" .. modes .. ")" end + x = x .. "{" .. chan.users_len .. "}" + end + if current.hide_unimportant then x = x .. "" end + + local lines, cols = xC.get_screen_size () + x = x .. " " .. active .. string.rep (" ", cols) + + -- Readline 7.0.003 seems to be broken and completely corrupts the prompt. + -- However 8.0.004 seems to be fine with these, as is libedit 20191231-3.1. + --x = x:gsub("[\128-\255]", "?") + + -- Cut off extra characters and apply formatting, including the hack. + -- FIXME: this doesn't count with full-width or zero-width characters. + -- We might want to export wcwidth() above term_from_utf8 somehow. + local overflow = utf8.offset (x, cols - 1) + if overflow then x = x:sub (1, overflow) end + x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" .. + x .. "\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02 \x01\x1b[0;1m\x02" + + local user_prefix = function (chan, user) + for i, chan_user in ipairs (chan.users) do + if chan_user.user == user then return chan_user.prefixes end + end + return "" + end + if s then + x = x .. "[" + local state = s.state + if state == "disconnected" or state == "connecting" then + x = x .. "(" .. state .. ")" + elseif state ~= "registered" then + x = x .. "(unregistered)" + else + local user, modes = s.user, s.user_mode + if chan then x = x .. user_prefix (chan, user) end + x = x .. user.nickname + if modes ~= "" then x = x .. "(" .. modes .. ")" end + end + x = x .. "] " + else + -- There needs to be at least one character so that the cursor + -- doesn't get damaged by our hack in that last column + x = x .. "> " + end + return x +end) diff --git a/plugins/xC/last-fm.lua b/plugins/xC/last-fm.lua new file mode 100644 index 0000000..3bdfed2 --- /dev/null +++ b/plugins/xC/last-fm.lua @@ -0,0 +1,178 @@ +-- +-- last-fm.lua: "now playing" feature using the last.fm API +-- +-- Dependencies: lua-cjson (from luarocks e.g.) +-- +-- I call this style closure-oriented programming +-- +-- Copyright (c) 2016, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +local cjson = require "cjson" + +-- Setup configuration to load last.fm API credentials from +local user, api_key +xC.setup_config { + user = { + type = "string", + comment = "last.fm username", + on_change = function (v) user = v end + }, + api_key = { + type = "string", + comment = "last.fm API key", + on_change = function (v) api_key = v end + }, +} + +-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +-- Generic error reporting +local report_error = function (buffer, error) + buffer:log ("last-fm error: " .. error) +end + +-- Process data return by the server and extract the now playing song +local process = function (buffer, data, action) + -- There's no reasonable Lua package to parse HTTP that I could find + local s, e, v, status, message = string.find (data, "(%S+) (%S+) .+\r\n") + if not s then return "server returned unexpected data" end + if status ~= "200" then return status .. " " .. message end + + local s, e = string.find (data, "\r\n\r\n") + if not s then return "server returned unexpected data" end + + local parser = cjson.new () + data = parser.decode (string.sub (data, e + 1)) + if not data.recenttracks or not data.recenttracks.track then + return "invalid response" end + + -- Need to make some sense of the XML automatically converted to JSON + local text_of = function (node) + if type (node) ~= "table" then return node end + return node["#text"] ~= "" and node["#text"] or nil + end + + local name, artist, album + for i, track in ipairs (data.recenttracks.track) do + if track["@attr"] and track["@attr"].nowplaying then + if track.name then name = text_of (track.name) end + if track.artist then artist = text_of (track.artist) end + if track.album then album = text_of (track.album) end + end + end + + if not name then + action (false) + else + local np = "\"" .. name .. "\"" + if artist then np = np .. " by " .. artist end + if album then np = np .. " from " .. album end + action (np) + end +end + +-- Set up the connection and make the request +local on_connected = function (buffer, c, host, action) + -- Buffer data in the connection object + c.data = "" + c.on_data = function (data) + c.data = c.data .. data + end + + -- And process it after we receive everything + c.on_eof = function () + error = process (buffer, c.data, action) + if error then report_error (buffer, error) end + c:close () + end + c.on_error = function (e) + report_error (buffer, e) + end + + -- Make the unencrypted HTTP request + local url = "/2.0/?method=user.getrecenttracks&user=" .. user .. + "&limit=1&api_key=" .. api_key .. "&format=json" + c:send ("GET " .. url .. " HTTP/1.1\r\n") + c:send ("User-agent: last-fm.lua\r\n") + c:send ("Host: " .. host .. "\r\n") + c:send ("Connection: close\r\n") + c:send ("\r\n") +end + +-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +-- Avoid establishing more than one connection at a time +local running + +-- Initiate a connection to last.fm servers +async, await = xC.async, coroutine.yield +local make_request = function (buffer, action) + if not user or not api_key then + report_error (buffer, "configuration is incomplete") + return + end + + if running then running:cancel () end + running = async.go (function () + local c, host, e = await (async.dial ("ws.audioscrobbler.com", 80)) + if e then + report_error (buffer, e) + else + on_connected (buffer, c, host, action) + end + running = nil + end) +end + +-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +local now_playing + +local tell_song = function (buffer) + if now_playing == nil then + buffer:log ("last-fm: I don't know what you're listening to") + elseif not now_playing then + buffer:log ("last-fm: not playing anything right now") + else + buffer:log ("last-fm: now playing: " .. now_playing) + end +end + +local send_song = function (buffer) + if not now_playing then + tell_song (buffer) + else + buffer:execute ("/me is listening to " .. now_playing) + end +end + +-- Hook input to simulate new commands +xC.hook_input (function (hook, buffer, input) + if input == "/np" then + make_request (buffer, function (np) + now_playing = np + send_song (buffer) + end) + elseif input == "/np?" then + make_request (buffer, function (np) + now_playing = np + tell_song (buffer) + end) + elseif input == "/np!" then + send_song (buffer) + else + return input + end +end) diff --git a/plugins/xC/ping-timeout.lua b/plugins/xC/ping-timeout.lua new file mode 100644 index 0000000..c455c57 --- /dev/null +++ b/plugins/xC/ping-timeout.lua @@ -0,0 +1,32 @@ +-- +-- ping-timeout.lua: ping timeout readability enhancement plugin +-- +-- Copyright (c) 2015 - 2016, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +xC.hook_irc (function (hook, server, line) + local msg = xC.parse (line) + local start, timeout = line:match ("^(.* :Ping timeout:) (%d+) seconds$") + if msg.command ~= "QUIT" or not start then + return line + end + + local minutes = timeout // 60 + if minutes == 0 then + return line + end + + local seconds = timeout % 60 + return ("%s %d minutes, %d seconds"):format (start, minutes, seconds) +end) diff --git a/plugins/xC/prime.lua b/plugins/xC/prime.lua new file mode 100644 index 0000000..23740ee --- /dev/null +++ b/plugins/xC/prime.lua @@ -0,0 +1,68 @@ +-- +-- prime.lua: highlight prime numbers in messages +-- +-- Copyright (c) 2020, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +local smallest, highlight = 0, "\x1f" +xC.setup_config { + smallest = { + type = "integer", + default = "0", + comment = "smallest number to scan for primality", + on_change = function (v) smallest = math.max (v, 2) end + }, + highlight = { + type = "string", + default = "\"\\x1f\"", + comment = "the attribute to use for highlights", + on_change = function (v) highlight = v end + }, +} + +-- The prime test is actually very fast, so there is no DoS concern +local do_intercolour = function (text) + return tostring (text:gsub ("%f[%w_]%d+", function (n) + if tonumber (n) < smallest then return nil end + for i = 2, n ^ (1 / 2) do if (n % i) == 0 then return nil end end + return highlight .. n .. highlight + end)) +end + +local do_interlink = function (text) + local rebuilt, last = {""}, 1 + for start in text:gmatch ('()\x03') do + table.insert (rebuilt, do_intercolour (text:sub (last, start - 1))) + local sub = text:sub (start + 1) + last = start + (sub:match ('^%d%d?,%d%d?()') or sub:match ('^%d?%d?()')) + table.insert (rebuilt, text:sub (start, last - 1)) + end + return table.concat (rebuilt) .. do_intercolour (text:sub (last)) +end + +local do_message = function (text) + local rebuilt, last = {""}, 1 + for run, link, endpos in text:gmatch ('(.-)(%f[%g]https?://%g+)()') do + last = endpos + table.insert (rebuilt, do_interlink (run) .. link) + end + return table.concat (rebuilt) .. do_interlink (text:sub (last)) +end + +-- XXX: sadly it won't typically highlight primes in our own messages, +-- unless IRCv3 echo-message is on +xC.hook_irc (function (hook, server, line) + local start, message = line:match ("^(.- PRIVMSG .- :)(.*)$") + return message and start .. do_message (message) or line +end) diff --git a/plugins/xC/slack.lua b/plugins/xC/slack.lua new file mode 100644 index 0000000..c1a08de --- /dev/null +++ b/plugins/xC/slack.lua @@ -0,0 +1,147 @@ +-- +-- slack.lua: try to fix up UX when using the Slack IRC gateway +-- +-- Copyright (c) 2017, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +local servers = {} +local read_servers = function (v) + servers = {} + for name in v:lower ():gmatch "[^,]+" do + servers[name] = true + end +end + +-- This is a reverse list of Slack's automatic emoji, noseless forms +local unemojify, emoji, emoji_default = false, {}, { + heart = "<3", + broken_heart = "", + neutral_face = ":|", + open_mouth = ":o", + angry = ">:(", + slightly_smiling_face = ":)", + disappointed = ":(", + confused = ":/", + stuck_out_tongue = ":p", + stuck_out_tongue_winking_eye = ";p", +} +local load_emoji = function (extra) + emoji = {} + for k, v in pairs (emoji_default) do emoji[k] = v end + for k, v in extra:gmatch "([^,]+) ([^,]+)" do emoji[k] = v end +end + +xC.setup_config { + servers = { + type = "string_array", + default = "\"\"", + comment = "list of server names that are Slack IRC gateways", + on_change = read_servers + }, + unemojify = { + type = "boolean", + default = "true", + comment = "convert emoji to normal ASCII emoticons", + on_change = function (v) unemojify = v end + }, + extra_emoji = { + type = "string_array", + default = "\"grinning :)),joy :'),innocent o:),persevere >_<\"", + comment = "overrides or extra emoji for unemojify", + on_change = function (v) load_emoji (v) end + } +} + +-- We can handle external messages about what we've supposedly sent just fine, +-- so let's get rid of that "[username] some message sent from the web UI" crap +xC.hook_irc (function (hook, server, line) + local msg, us = xC.parse (line), server.user + if not servers[server.name] or msg.command ~= "PRIVMSG" or not us + or msg.params[1]:lower () ~= us.nickname:lower () then return line end + + -- Taking a shortcut to avoid lengthy message reassembly + local quoted_nick = us.nickname:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") + local text = line:match ("^.- PRIVMSG .- :%[" .. quoted_nick .. "%] (.*)$") + if not text then return line end + return ":" .. us.nickname .. "!" .. server.irc_user_host .. " PRIVMSG " + .. msg.prefix:match "^[^!@]*" .. " :" .. text +end) + +-- Unfuck emoji and :nick!nick@irc.tinyspeck.com MODE #channel +v nick : active +xC.hook_irc (function (hook, server, line) + if not servers[server.name] then return line end + if unemojify then + local start, text = line:match ("^(.- PRIVMSG .- :)(.*)$") + if start then return start .. text:gsub (":([a-z_]+):", function (name) + if emoji[name] then return emoji[name] end + return ":" .. name .. ":" + end) end + end + return line:gsub ("^(:%S+ MODE .+) : .*", "%1") +end) + +-- The gateway simply ignores the NAMES command altogether +xC.hook_input (function (hook, buffer, input) + if not buffer.channel or not servers[buffer.server.name] + or not input:match "^/names%s*$" then return input end + + local users = buffer.channel.users + table.sort (users, function (a, b) + if a.prefixes > b.prefixes then return true end + if a.prefixes < b.prefixes then return false end + return a.user.nickname < b.user.nickname + end) + + local names = "Users on " .. buffer.channel.name .. ":" + for i, chan_user in ipairs (users) do + names = names .. " " .. chan_user.prefixes .. chan_user.user.nickname + end + buffer:log (names) +end) + +xC.hook_completion (function (hook, data, word) + local chan = xC.current_buffer.channel + local server = xC.current_buffer.server + if not chan or not servers[server.name] then return end + + -- In /commands there is typically no desire at all to add the at sign + if data.location == 1 and data.words[1]:match "^/" then return end + + -- Handle both when the at sign is already there and when it is not + local needle = word:gsub ("^@", ""):lower () + + local t = {} + local try = function (name) + if data.location == 0 then name = name .. ":" end + if name:sub (1, #needle):lower () == needle then + table.insert (t, "@" .. name) + end + end + for _, chan_user in ipairs (chan.users) do + try (chan_user.user.nickname) + end + for _, special in ipairs { "channel", "here" } do + try (special) + end + return t +end) diff --git a/plugins/xC/thin-cursor.lua b/plugins/xC/thin-cursor.lua new file mode 100644 index 0000000..d0fbf38 --- /dev/null +++ b/plugins/xC/thin-cursor.lua @@ -0,0 +1,27 @@ +-- +-- thin-cursor.lua: set a thin cursor +-- +-- Copyright (c) 2016, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- +-- If tmux doesn't work, add the following to its configuration: +-- set -as terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[2 q' +-- Change the "2" as per http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + +local out = io.output () +out:write ("\x1b[6 q"):flush () + +-- By registering a global variable, we get notified about plugin unload +x = setmetatable ({}, { __gc = function () + out:write ("\x1b[2 q"):flush () +end }) diff --git a/plugins/xC/utm-filter.lua b/plugins/xC/utm-filter.lua new file mode 100644 index 0000000..82c4522 --- /dev/null +++ b/plugins/xC/utm-filter.lua @@ -0,0 +1,62 @@ +-- +-- utm-filter.lua: filter out Google Analytics bullshit from URLs +-- +-- Copyright (c) 2015, Přemysl Eric Janouch +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +-- A list of useless URL parameters that don't affect page function +local banned = { + gclid = 1, + + utm_source = 1, + utm_medium = 1, + utm_term = 1, + utm_content = 1, + utm_campaign = 1, +} + +-- Go through a parameter list and throw out any banned elements +local do_args = function (args) + local filtered = {} + for part in args:gmatch ("[^&]+") do + if not banned[part:match ("^[^=]*")] then + table.insert (filtered, part) + end + end + return table.concat (filtered, "&") +end + +-- Filter parameters in both the query and the fragment part of an URL +local do_single_url = function (url) + return url:gsub ('^([^?#]*)%?([^#]*)', function (start, query) + local clean = do_args (query) + return #clean > 0 and start .. "?" .. clean or start + end, 1):gsub ('^([^#]*)#(.*)', function (start, fragment) + local clean = do_args (fragment) + return #clean > 0 and start .. "#" .. clean or start + end, 1) +end + +local do_text = function (text) + return text:gsub ('%f[%g]https?://%g+', do_single_url) +end + +xC.hook_irc (function (hook, server, line) + local start, message = line:match ("^(.* :)(.*)$") + return message and start .. do_text (message) or line +end) + +xC.hook_input (function (hook, buffer, input) + return do_text (input) +end) -- cgit v1.2.3