clix/avatar.lua

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