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