| 1 | local b64 = require"prosody.util.encodings".base64.encode; |
| 2 | local unb64 = require"prosody.util.encodings".base64.decode; |
| 3 | local st = require "prosody.util.stanza"; |
| 4 | local sha1 = require "prosody.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 |