-- -- 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 = "3", sunglasses = "8)", anguished = "D:", cry = ":'(", monkey_face = ":o)", kiss = ":*", smiley = "=)", smile = ":D", wink = ";)", laughing = ":>", 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 degesch.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 degesch.hook_irc (function (hook, server, line) local msg, us = degesch.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 degesch.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 degesch.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) degesch.hook_completion (function (hook, data, word) local chan = degesch.current_buffer.channel local server = degesch.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)