Compare commits
No commits in common. "master" and "cw1-plugin" have entirely different histories.
master
...
cw1-plugin
39
README.md
39
README.md
|
@ -1,10 +1,37 @@
|
||||||
hexchat-plugins
|
Hexchat CW1 plugin
|
||||||
===============
|
==================
|
||||||
|
|
||||||
Assorted collection of hexchat plugins.
|
Implements the CW1 CTCP.
|
||||||
|
|
||||||
Plugins are their own branches.
|
CW1 CTCP
|
||||||
|
========
|
||||||
|
|
||||||
Current plugins:
|
Syntax:
|
||||||
|
|
||||||
- [CW1 plugin](https://cybre.tech/SoniEx2/hexchat-plugins/src/branch/cw1-plugin)
|
CW1 <rot13 text>
|
||||||
|
|
||||||
|
The `CW1` CTCP is the Type-1 CW. On clients that don't support `CW1`, it falls back to displaying rot13 text.
|
||||||
|
|
||||||
|
Because rot13 only affects the English alphabet, it is recommended to use CW1 only for text using the Latin alphabet - everything else gets displayed as-is on clients that don't support `CW1`.
|
||||||
|
|
||||||
|
`CW1` may appear multiple times in a message.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
hello\x01CW1 pehry\x01world\x01CW1 !\x01
|
||||||
|
|
||||||
|
On a client that supports CW1:
|
||||||
|
|
||||||
|
hello [hidden text 1 (click to expand)] world [hidden text 2 (click to expand)]
|
||||||
|
|
||||||
|
expands to:
|
||||||
|
|
||||||
|
hello cruel world !
|
||||||
|
|
||||||
|
On a client that doesn't support CW1:
|
||||||
|
|
||||||
|
hello CW1 pehry world CW1 !
|
||||||
|
|
||||||
|
(Some clients may display delimiter boxes around `CW1 pehry` and `CW1 !`, making it easier to distinguish where the hidden text ends from where non-hidden text starts)
|
||||||
|
|
||||||
|
Rot13 is already used in many mental health channels to "hide" triggering content pending further action from the user, which makes it a reasonable choice for a content warning CTCP.
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
local hexchat = hexchat
|
||||||
|
hexchat.register("rot13", "3.0.1", "rot13")
|
||||||
|
|
||||||
|
local rot13 = {}
|
||||||
|
|
||||||
|
for i = string.byte('a'), string.byte('m') do
|
||||||
|
local a, b = string.char(i), string.char(i+13)
|
||||||
|
local A, B = a:upper(), b:upper()
|
||||||
|
rot13[a] = b
|
||||||
|
rot13[b] = a
|
||||||
|
rot13[A] = B
|
||||||
|
rot13[B] = A
|
||||||
|
end
|
||||||
|
|
||||||
|
local function do_rot13(arg, arg_eol)
|
||||||
|
if not arg[3] then
|
||||||
|
hexchat.print("Usage: /" .. arg[1] .. " <reason> <content>")
|
||||||
|
return hexchat.EAT_ALL
|
||||||
|
end
|
||||||
|
hexchat.command("say " .. arg[2] .. "\x01CW1 " .. arg_eol[3]:gsub("[A-Za-z]", rot13) .. "\x01")
|
||||||
|
return hexchat.EAT_ALL
|
||||||
|
end
|
||||||
|
|
||||||
|
hexchat.hook_command("cw-rot13", do_rot13)
|
||||||
|
|
||||||
|
local function startswith(str1, str2)
|
||||||
|
return str1:sub(1, #str2) == str2
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parsecw(msg)
|
||||||
|
local hidden = {}
|
||||||
|
msg = msg:gsub("()(\1([^\1]*)\1)()", function(start, ctcp, contents, finish)
|
||||||
|
if startswith(contents, "CW1 ") then
|
||||||
|
local cw = contents:sub(5)
|
||||||
|
hidden[#hidden + 1] = cw:gsub("[A-Za-z]", rot13)
|
||||||
|
return " [Hidden Text " .. #hidden .. "] "
|
||||||
|
end
|
||||||
|
return ctcp
|
||||||
|
end)
|
||||||
|
return msg, hidden
|
||||||
|
end
|
||||||
|
|
||||||
|
local invert_notice = false
|
||||||
|
local last_notice = false
|
||||||
|
|
||||||
|
local tunpack = unpack or table.unpack
|
||||||
|
|
||||||
|
local skip = false
|
||||||
|
local function mkparse(event, pos)
|
||||||
|
return function(word, attributes)
|
||||||
|
if skip then return end
|
||||||
|
if event:find("Notice$") then
|
||||||
|
if invert_notice then
|
||||||
|
word[pos] = '\1'.. word[pos]
|
||||||
|
if last_notice then
|
||||||
|
word[pos] = word[pos] .. '\1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
invert_notice = false
|
||||||
|
local old_msg = word[pos]
|
||||||
|
local hidden
|
||||||
|
word[pos], hidden = parsecw(word[pos])
|
||||||
|
if word[pos] ~= old_msg then
|
||||||
|
skip = true
|
||||||
|
hexchat.emit_print_attrs(attributes, event, tunpack(word))
|
||||||
|
for i, v in ipairs(hidden) do
|
||||||
|
hexchat.print("\00326*\tHidden Text " .. i .. "\3 > \8"..v.."\8 < (Copy and paste to expand)")
|
||||||
|
end
|
||||||
|
skip = false
|
||||||
|
return hexchat.EAT_ALL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hookparse(event, pos)
|
||||||
|
return hexchat.hook_print_attrs(event, mkparse(event, pos))
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
(function(f,...)return f(f,...) end)(function(f, a, b, ...)
|
||||||
|
if a then
|
||||||
|
hookparse(a, b)
|
||||||
|
return f(f, ...)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
"Channel Message", 2,
|
||||||
|
"Channel Msg Hilight", 2,
|
||||||
|
"Channel Notice", 3,
|
||||||
|
"Private Message", 2,
|
||||||
|
"Private Message to Dialog", 2,
|
||||||
|
"Notice", 2,
|
||||||
|
"Your Message", 2,
|
||||||
|
"Notice Send", 2,
|
||||||
|
"Message Send", 2,
|
||||||
|
nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- from here on: 3.0.1 bugfix
|
||||||
|
|
||||||
|
-- TODO maybe move these to a separate plugin. (or integrate them in hexchat)
|
||||||
|
|
||||||
|
local function tooct(n)
|
||||||
|
return string.format("%o", n)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_privmsg(word, word_eol, attrs)
|
||||||
|
-- fix up some PRIVMSGs, pass them on to our handlers above
|
||||||
|
local i_at = 0
|
||||||
|
for i,v in ipairs(word) do
|
||||||
|
if v == "PRIVMSG" then
|
||||||
|
i_at = i + 2
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if i_at == 0 then return end
|
||||||
|
if word_eol[i_at]:sub(1, 2):find(":[-+]") and tooct(hexchat.props["flags"]):find("[4-7].$") then -- bleh
|
||||||
|
word_eol[i_at] = ":" .. word_eol[i_at]:sub(3)
|
||||||
|
end
|
||||||
|
if word_eol[i_at]:sub(1, 6) == ":\1CW1 " then
|
||||||
|
if not word_eol[i_at]:find("\1", 7, true) == #word_eol[i_at] then
|
||||||
|
return hexchat.EAT_NONE -- let hexchat handle it normally
|
||||||
|
end
|
||||||
|
local target = word[i_at - 1]
|
||||||
|
local source
|
||||||
|
if word[1]:sub(1,1) ~= "@" then
|
||||||
|
source = word[1]
|
||||||
|
else
|
||||||
|
source = word[2]
|
||||||
|
end
|
||||||
|
source = source:match("^:([^!]+)!") -- this seems easy, right?
|
||||||
|
local context = hexchat.find_context(hexchat.get_info("server"), target)
|
||||||
|
if context then
|
||||||
|
local old_ctx = hexchat.get_context()
|
||||||
|
local serv_id = hexchat.props["id"]
|
||||||
|
-- sanity check
|
||||||
|
if hexchat.set_context(context) and hexchat.props["id"] == serv_id then
|
||||||
|
if hexchat.props["type"] == 2 then
|
||||||
|
local prefix = ""
|
||||||
|
for user in hexchat.iterate("users") do
|
||||||
|
if user.nick == source then
|
||||||
|
prefix = user.prefix
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- TODO fix highlights
|
||||||
|
hexchat.emit_print_attrs(attrs, "Channel Message", source, word_eol[i_at]:sub(2), prefix)
|
||||||
|
return hexchat.EAT_HEXCHAT
|
||||||
|
elseif hexchat.props["type"] == 3 then
|
||||||
|
hexchat.emit_print_attrs(attrs, "Private Message to Dialog", source, word_eol[i_at]:sub(2))
|
||||||
|
return hexchat.EAT_HEXCHAT
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- assume it's a query, because usually you only get channel messages if you're in the channel
|
||||||
|
if not hexchat.set_context(old_ctx) then return end -- how did we get this far without segfaulting?
|
||||||
|
end
|
||||||
|
if hexchat.prefs["gui_autoopen_dialog"] then
|
||||||
|
hexchat.command("query -nofocus " .. target)
|
||||||
|
local context = hexchat.find_context(hexchat.get_info("server"), target)
|
||||||
|
if context then
|
||||||
|
local old_ctx = hexchat.get_context()
|
||||||
|
local serv_id = hexchat.props["id"]
|
||||||
|
-- sanity check
|
||||||
|
if hexchat.set_context(context) and hexchat.props["id"] == serv_id then
|
||||||
|
-- meh, go hardmode on this (this shouldn't happen unless find_context returns the server context for some reason)
|
||||||
|
if hexchat.props["type"] ~= 3 then
|
||||||
|
local found = false
|
||||||
|
for chan in hexchat.iterate("channels") do
|
||||||
|
if chan.id == serv_id and chan.type == 3 and chan.channel == source then
|
||||||
|
found = true
|
||||||
|
hexchat.set_context(chan.context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- still not found? ragequit!
|
||||||
|
if not found then return hexchat.EAT_HEXCHAT end
|
||||||
|
end
|
||||||
|
hexchat.emit_print_attrs(attrs, "Private Message to Dialog", source, word_eol[i_at]:sub(2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return hexchat.EAT_HEXCHAT
|
||||||
|
end
|
||||||
|
hexchat.emit_print_attrs(attrs, "Private Message", source, word_eol[i_at]:sub(2))
|
||||||
|
return hexchat.EAT_HEXCHAT
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_notice(word, word_eol, attrs) -- this one is much simpler
|
||||||
|
local i_at = 0
|
||||||
|
for i,v in ipairs(word) do
|
||||||
|
if v == "NOTICE" then
|
||||||
|
i_at = i + 2
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if i_at == 0 then return end
|
||||||
|
if word_eol[i_at]:sub(1, 2):find(":[-+]") and tooct(hexchat.props["flags"]):find("[4-7].$") then -- bleh
|
||||||
|
word_eol[i_at] = ":" .. word_eol[i_at]:sub(3)
|
||||||
|
end
|
||||||
|
if word_eol[i_at]:sub(1, 6) == ":\1CW1 " then
|
||||||
|
-- workaround
|
||||||
|
invert_notice = true
|
||||||
|
last_notice = word_eol[i_at]:sub(-1,-1) == "\1"
|
||||||
|
-- then just let hexchat handle it
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
hexchat.hook_server_attrs("PRIVMSG", on_privmsg)
|
||||||
|
|
||||||
|
hexchat.hook_server_attrs("NOTICE", on_notice)
|
||||||
|
|
||||||
|
hexchat.print("rot13 loaded") -- this was in 3.0.0 too
|
Loading…
Reference in New Issue