|
1 local b64 = require"util.encodings".base64.encode; |
|
2 local unb64 = require"util.encodings".base64.decode; |
|
3 local st = require "util.stanza"; |
|
4 local sha1 = require "util.hashes".sha1; |
|
5 |
|
6 return function(opts, arg) |
|
7 if opts.short_help then |
|
8 print("Manage PEP avatars"); |
|
9 return; |
|
10 end |
|
11 |
|
12 local subcommands = {}; |
|
13 |
|
14 function subcommands.fetch(conn) |
|
15 local waiting = {[true] = true}; |
|
16 for _, userjid in ipairs(arg) do |
|
17 waiting[userjid] = true; |
|
18 local userpep = conn.pubsub:service(userjid); |
|
19 userpep:node("urn:xmpp:avatar:metadata"):items(true, function(result) |
|
20 local metadata_tag = result:find("{http://jabber.org/protocol/pubsub}pubsub/items/item/{urn:xmpp:avatar:metadata}metadata"); |
|
21 if not metadata_tag or not metadata_tag:get_child("info") then |
|
22 if result.attr.type == "error" then |
|
23 conn:error("Got error from %s: %s:%s", userjid, result:get_error()); |
|
24 else |
|
25 conn:error("%s has no avatar", userjid) |
|
26 end |
|
27 waiting[userjid] = nil; |
|
28 if next(waiting) == nil then |
|
29 conn:close(); |
|
30 end |
|
31 return; |
|
32 end |
|
33 |
|
34 for info_tag in metadata_tag:childtags("info") do |
|
35 conn:info("Has avatar with type %s", info_tag.attr.type or "?") |
|
36 local filename = (opts.output or (userjid .. "_" .. info_tag.attr.id)) .. "." .. (info_tag.attr.type or "/dat"):match("/([^./+]+)"); |
|
37 local output = assert(io.open(filename, "w")); |
|
38 waiting[info_tag.attr.id] = true; |
|
39 conn:debug("Writing to %s", filename); |
|
40 userpep:node("urn:xmpp:avatar:data"):item(info_tag.attr.id, function(dataresult) |
|
41 local data = unb64(dataresult:find("{http://jabber.org/protocol/pubsub}pubsub/items/item/{urn:xmpp:avatar:data}data#")); |
|
42 if data then |
|
43 assert(output:write(data)); |
|
44 assert(output:close()); |
|
45 conn:info("Avatar of %s written to %s", userjid, filename); |
|
46 else |
|
47 conn:error("Got no data for %s id %s", userjid, info_tag.attr.id); |
|
48 end |
|
49 waiting[info_tag.attr.id] = nil; |
|
50 if next(waiting) == nil then |
|
51 conn:close(); |
|
52 end |
|
53 end); |
|
54 |
|
55 if not opts.all then |
|
56 break |
|
57 end |
|
58 end |
|
59 |
|
60 waiting[userjid] = nil; |
|
61 end) |
|
62 end |
|
63 waiting[true] = nil; |
|
64 end |
|
65 |
|
66 function subcommands.publish(conn) |
|
67 local waiting = {meta=true}; |
|
68 local userpep = conn.pubsub:service(nil); |
|
69 local metadata_tag = st.stanza("metadata", { xmlns = "urn:xmpp:avatar:metadata" }); |
|
70 local metadata_node = userpep:node("urn:xmpp:avatar:metadata"); |
|
71 local data_node = userpep:node("urn:xmpp:avatar:data"); |
|
72 local first_h = nil; |
|
73 local sha1pat = string.rep("%x", #sha1("",true)); |
|
74 |
|
75 for _, file in ipairs(arg) do |
|
76 local h, width, height, typ = file:match("_("..sha1pat..")_(%d+)x(%d+)%.(%w+)$"); |
|
77 if not h then h, typ = file:match("_("..sha1pat..")%.(%w+)$"); end |
|
78 if not h then typ = file:match("%.(%w+)$"); end |
|
79 |
|
80 local f = assert(io.open(file)); |
|
81 local data = f:read("*a"); |
|
82 f:close(); |
|
83 local bytes = string.format("%d", #data); |
|
84 |
|
85 if not h then h = sha1(data, true); end |
|
86 if not first_h then first_h = h; end |
|
87 if typ == "jpg" then typ = "jpeg"; end |
|
88 |
|
89 local data_tag = st.stanza("data", { xmlns = "urn:xmpp:avatar:data" }):text(b64(data)); |
|
90 waiting[h] = true; |
|
91 data_node:publish(h, nil, data_tag, function(ok) |
|
92 waiting[h] = nil; |
|
93 if next(waiting) == nil then |
|
94 conn:close(); |
|
95 end |
|
96 |
|
97 end); |
|
98 |
|
99 metadata_tag:tag("info", {id = h; type = "image/" .. typ; bytes = bytes; width = width; height = height}):up(); |
|
100 end |
|
101 |
|
102 metadata_node:publish(first_h, nil, metadata_tag, function(ok) |
|
103 waiting.meta = nil; |
|
104 if next(waiting) == nil then |
|
105 conn:close(); |
|
106 end |
|
107 end); |
|
108 |
|
109 end |
|
110 |
|
111 if ((#arg == 0) or opts.help) then |
|
112 print("Subcommands:"); |
|
113 print(" publish file_HASH_WxH.png"); |
|
114 print(" fetch user@example.com"); |
|
115 return 0; |
|
116 end |
|
117 if opts.output and opts.all then |
|
118 print("Can't download multiple avatars to a single file") |
|
119 return 1; |
|
120 end |
|
121 |
|
122 local subcommand = table.remove(arg, 1); |
|
123 local on_connect = subcommands[subcommand]; |
|
124 |
|
125 if not on_connect then |
|
126 print("No such command: " .. subcommand); |
|
127 return 1; |
|
128 end |
|
129 |
|
130 return clix_connect(opts, on_connect, {"pubsub"}) |
|
131 end |