2 local st
= require
"util.stanza";
3 module
:depends("http");
4 local uuid_new
= require
"util.uuid".generate
;
5 local os_time
= os
.time
;
6 local t_remove
= table.remove;
7 local add_task
= require
"util.timer".add_task
;
8 local jid_bare
= require
"util.jid".bare
;
10 local function get_room_from_jid() end;
11 local is_component
= module
:get_host_type() == "component";
13 local mod_muc
= module
:depends
"muc";
14 local muc_rooms
= rawget(mod_muc
, "rooms");
15 get_room_from_jid
= rawget(mod_muc
, "get_room_from_jid") or
17 return muc_rooms
[jid
];
21 local utf8_pattern
= "[\194-\244][\128-\191]*$";
22 local function drop_invalid_utf8(seq
)
23 local start
= seq
:byte();
24 module
:log("debug", "utf8: %d, %d", start
, #seq
);
25 if (start
<= 223 and #seq
< 2)
26 or (start
>= 224 and start
<= 239 and #seq
< 3)
27 or (start
>= 240 and start
<= 244 and #seq
< 4)
34 local function utf8_length(str
)
35 local _
, count
= string.gsub(str
, "[^\128-\193]", "");
39 local pastebin_private_messages
= module
:get_option_boolean("pastebin_private_messages", not is_component
);
40 local length_threshold
= module
:get_option_number("pastebin_threshold", 500);
41 local line_threshold
= module
:get_option_number("pastebin_line_threshold", 4);
42 local max_summary_length
= module
:get_option_number("pastebin_summary_length", 150);
43 local html_preview
= module
:get_option_boolean("pastebin_html_preview", true);
45 local base_url
= module
:get_option_string("pastebin_url", module
:http_url()):gsub("/$", "").."/";
47 -- Seconds a paste should live for in seconds (config is in hours), default 24 hours
48 local expire_after
= math
.floor(module
:get_option_number("pastebin_expire_after", 24) * 3600);
50 local trigger_string
= module
:get_option_string("pastebin_trigger");
51 trigger_string
= (trigger_string
and trigger_string
.. " ");
55 local xmlns_xhtmlim
= "http://jabber.org/protocol/xhtml-im";
56 local xmlns_xhtml
= "http://www.w3.org/1999/xhtml";
58 function pastebin_text(text
)
59 local uuid
= uuid_new();
60 pastes
[uuid
] = { body
= text
, time
= os_time(), };
61 pastes
[#pastes
+1] = uuid
;
62 if not pastes
[2] then -- No other pastes, give the timer a kick
63 add_task(expire_after
, expire_pastes
);
65 return base_url
..uuid
;
68 function handle_request(event
, pasteid
)
69 event
.response
.headers
.content_type
= "text/plain; charset=utf-8";
72 return "Invalid paste id, perhaps it expired?";
75 --module:log("debug", "Received request, replying: %s", pastes[pasteid].text);
76 local paste
= pastes
[pasteid
];
79 return "Invalid paste id, perhaps it expired?";
85 local function replace_tag(s
, replacement
)
87 s
:maptags(function (tag)
88 if tag.name
== replacement
.name
and tag.attr
.xmlns
== replacement
.attr
.xmlns
then
99 s
:add_child(replacement
);
103 local line_count_pattern
= string.rep("[^\n]*\n", line_threshold
+ 1):sub(1,-2);
105 function check_message(data
)
106 local stanza
= data
.stanza
;
108 -- Only check for MUC presence when loaded on a component.
110 local room
= get_room_from_jid(jid_bare(stanza
.attr
.to
));
111 if not room
then return; end
113 local nick
= room
._jid_nick
[stanza
.attr
.from
];
114 if not nick
then return; end
117 local body
= stanza
:get_child_text();
119 if not body
then return; end
121 --module:log("debug", "Body(%s) length: %d", type(body), #(body or ""));
123 if ( #body
> length_threshold
and utf8_length(body
) > length_threshold
) or
124 (trigger_string
and body
:find(trigger_string
, 1, true) == 1) or
125 body
:find(line_count_pattern
) then
126 if trigger_string
and body
:sub(1, #trigger_string
) == trigger_string
then
127 body
= body
:sub(#trigger_string
+1);
129 local url
= pastebin_text(body
);
130 module
:log("debug", "Pasted message as %s", url
);
131 --module:log("debug", " stanza[bodyindex] = %q", tostring( stanza[bodyindex]));
132 local summary
= (body
:sub(1, max_summary_length
):gsub(utf8_pattern
, drop_invalid_utf8
) or ""):match("[^\n]+") or "";
133 summary
= summary
:match("^%s*(.-)%s*$");
134 local summary_prefixed
= summary
:match("[,:]$");
135 replace_tag(stanza
, st
.stanza("body"):text(summary
.. "\n" .. url
));
137 stanza
:add_child(st
.stanza("query", { xmlns
= "jabber:iq:oob" }):tag("url"):text(url
));
140 local line_count
= select(2, body
:gsub("\n", "%0")) + 1;
141 local link_text
= ("[view %spaste (%d line%s)]"):format(summary_prefixed
and "" or "rest of ", line_count
, line_count
== 1 and "" or "s");
142 local html
= st
.stanza("html", { xmlns
= xmlns_xhtmlim
}):tag("body", { xmlns
= xmlns_xhtml
});
143 html
:tag("p"):text(summary
.." "):up();
144 html
:tag("a", { href
= url
}):text(link_text
):up();
145 replace_tag(stanza
, html
);
150 module
:hook("message/bare", check_message
);
151 if pastebin_private_messages
then
152 module
:hook("message/full", check_message
);
155 module
:hook("muc-disco#info", function (event
)
156 local reply
, form
, formdata
= event
.reply
, event
.form
, event
.formdata
;
157 reply
:tag("feature", { var
= "https://modules.prosody.im/mod_pastebin" }):up();
158 table.insert(form
, { name
= "https://modules.prosody.im/mod_pastebin#max_lines", datatype
= "xs:integer" });
159 table.insert(form
, { name
= "https://modules.prosody.im/mod_pastebin#max_characters", datatype
= "xs:integer" });
160 formdata
["https://modules.prosody.im/mod_pastebin#max_lines"] = tostring(line_threshold
);
161 formdata
["https://modules.prosody.im/mod_pastebin#max_characters"] = tostring(length_threshold
);
164 function expire_pastes(time
)
165 time
= time
or os_time(); -- COMPAT with 0.5
167 pastes
[pastes
[1]]
= nil;
170 return (expire_after
- (time
- pastes
[pastes
[1]]
.time
)) + 1;
176 module
:provides("http", {
178 ["GET /*"] = handle_request
;
182 local function set_pastes_metatable()
183 -- luacheck: ignore 212/pastes 431/pastes
184 if expire_after
== 0 then
185 local dm
= require
"util.datamanager";
186 setmetatable(pastes
, {
187 __index
= function (pastes
, id
)
188 if type(id
) == "string" then
189 return dm
.load(id
, module
.host
, "pastebin");
192 __newindex
= function (pastes
, id
, data
)
193 if type(id
) == "string" then
194 dm
.store(id
, module
.host
, "pastebin", data
);
199 setmetatable(pastes
, nil);
203 module
.load
= set_pastes_metatable
;
205 function module
.save()
206 return { pastes
= pastes
};
209 function module
.restore(data
)
210 pastes
= data
.pastes
or pastes
;
211 set_pastes_metatable();