|
1 local jid_split = require"util.jid".split; |
|
2 local datetime = require "util.datetime"; |
|
3 local st = require "util.stanza"; |
|
4 local uuid = require"util.uuid".generate; |
|
5 |
|
6 return function(opts, arg) |
|
7 if opts.help then |
|
8 print("clix moderate --to=room@muc.example.com") |
|
9 print("\t--start=timestamp") |
|
10 print("\t--end=timestamp") |
|
11 print("\t--from=nickname") |
|
12 print("\t--body-contains=\"some spam\"") |
|
13 print("\t--dry-run") |
|
14 return 0; |
|
15 elseif opts.short_help or arg[1] or not opts.room then |
|
16 print("Remove messages from a MUC"); |
|
17 return; |
|
18 end |
|
19 |
|
20 local function parse_datetime(s) |
|
21 if s:match("^%d%d:") then |
|
22 s = datetime.date().."T"..s; |
|
23 end |
|
24 if #s < 20 then |
|
25 s = s .. ("0000-01-01T00:00:00Z"):sub(#s+1) |
|
26 end |
|
27 return datetime.parse(s) |
|
28 end |
|
29 |
|
30 if opts.start then |
|
31 opts.start = parse_datetime(opts.start); |
|
32 end |
|
33 if opts["end"] then |
|
34 opts["end"] = parse_datetime(opts["end"]); |
|
35 end |
|
36 |
|
37 local function matches(message) |
|
38 local nick = select(3, jid_split(message.attr.from)); |
|
39 if opts.from and opts.from ~= nick then return end |
|
40 local body = message:get_child_text("body"); |
|
41 if opts.body_contains and not string.find(body or "", opts.body_contains, 1, true) then return end |
|
42 if opts.body_match and not string.find(body or "", opts.body_match) then return end |
|
43 return true; |
|
44 end |
|
45 |
|
46 local function on_connect(conn) |
|
47 local waiting = {}; -- to keep track of outstanding queries |
|
48 |
|
49 local function done(with) |
|
50 waiting[with] = nil; |
|
51 if next(waiting) == nil then |
|
52 conn:close(); |
|
53 end |
|
54 end |
|
55 |
|
56 local function moderate(id) |
|
57 waiting[id] = true; |
|
58 -- TODO maybe queue and send the next request when a response comes in? |
|
59 local mod_iq = st.iq({ id = uuid(); type = "set"; to = opts.room }) |
|
60 :tag("apply-to", { xmlns = "urn:xmpp:fasten:0"; id = id }) |
|
61 :tag("moderate", { xmlns = "urn:xmpp:message-moderate:0" }) |
|
62 :tag("retract", { xmlns = "urn:xmpp:message-retract:0" }):up() |
|
63 if opts.reason then |
|
64 mod_iq:tag("reason", { xmlns = "urn:xmpp:message-moderate:0" }):text(opts.reason):up() |
|
65 end |
|
66 mod_iq:reset(); |
|
67 |
|
68 if opts.dry_run then |
|
69 done(id); |
|
70 conn:debug("Would send: %s", mod_iq); |
|
71 return; |
|
72 end |
|
73 |
|
74 return conn:send_iq(mod_iq, function (ret) |
|
75 if ret.attr.type == "error" then |
|
76 local t, cond, msg = ret:get_error(); |
|
77 conn:error("Retracting message with id %s failed: %s(%s, %s)", msg or "", t, cond); |
|
78 end |
|
79 done(id); |
|
80 end); |
|
81 end |
|
82 |
|
83 local function handle_results(result, err) |
|
84 if not result then |
|
85 conn:error("Archive query failed: %s", err); |
|
86 return done(true); |
|
87 end |
|
88 for _, item in ipairs(result) do |
|
89 if matches(item.message) then |
|
90 conn:info("Moderate %s", item.message:top_tag()) |
|
91 moderate(item.id); |
|
92 else |
|
93 conn:debug("Skip %s", item.message:top_tag()) |
|
94 end |
|
95 end |
|
96 |
|
97 if result.complete == nil then -- COMPAT verse |
|
98 result.complete = opts.after == nil or result[1] == nil; |
|
99 end |
|
100 |
|
101 if not result.complete then |
|
102 -- Proceed to the next page |
|
103 opts.after = result.last; |
|
104 return conn:query_archive(opts.room, opts, handle_results); |
|
105 else |
|
106 -- All done, just wait for any outstanding moderation queries to complete |
|
107 done(true); |
|
108 end |
|
109 end |
|
110 |
|
111 waiting[true] = true; -- for the archive query |
|
112 conn:query_archive(opts.room, opts, handle_results); |
|
113 end |
|
114 |
|
115 clix_connect(opts, on_connect, { "archive" }); |
|
116 end |