clix/roster.lua

1
local verse = require "verse"
2
short_opts.i = "interactive";
3
local split_jid = require "prosody.util.jid".split;
4
local function printor(str)
5
	return function(ok, err)
6
		if ok then
7
			print(str)
8
		else
9
			print(err:get_error());
10
		end
11
	end
12
end
13
 
14
return function (opts, arg)
15
	if opts.short_help then
16
		print("Show or edit your roster");
17
		return;
18
	end
19
	if opts.help and not opts.interactive then
20
		return 0;
21
	end
22
	local function on_connect(conn)
23
		-- Fake socket object around stdin
24
		local stdin = {
25
			getfd = function () return 0; end;
26
			dirty = function (self) return false; end;
27
			settimeout = function () end;
28
			send = function (_, d) return #d, 0; end;
29
			close = function () end;
30
			receive = function (_, patt)
31
				local data = io.stdin:read(patt);
32
				if data == nil then
33
					conn:close();
34
				end
35
				if opts.echo then
36
					io.write(data, patt == "*l" and "\n" or "");
37
				end
38
				return data;
39
			end
40
		};
41
		local commands = {
42
			quit = function(param) conn:close(); end;
43
			add = function(param) conn.roster:add_contact(param) end;
44
			sub = function(param) conn:send(verse.presence{to=param, type="subscribe"}); end;
45
			unsub = function(param) conn:send(verse.presence{to=param, type="unsubscribe"}); end;
46
			auth = function(param) conn:send(verse.presence{to=param, type="subscribed"}); end;
47
			unauth = function(param) conn:send(verse.presence{to=param, type="unsubscribed"}); end;
48
			del = function(param) conn.roster:delete_contact(param) end;
49
			setnick = function(param)
50
				local jid, nick = param:match("^(%S+)%s+(.+)");
51
				local item = conn.roster.items[jid];
52
				if not item then print("no jid "..jid); return; end
53
				conn.roster:add_contact(jid, nick, item.groups or {}, printor("saved"));
54
			end;
55
			addgroup = function(param)
56
				local jid, group = param:match("^(%S+)%s+(.+)");
57
				local item = conn.roster.items[jid];
58
				local groups = item.groups or {};
59
				table.insert(groups, group);
60
				conn.roster:add_contact(jid, item.name, groups, printor("saved"));
61
			end;
62
			delgroup = function(param)
63
				local jid, group = param:match("^(%S+)%s+(.+)");
64
				local item = conn.roster.items[jid];
65
				local groups = item.groups;
66
				if not groups then return end;
67
				for i = 1,#groups do
68
					if groups[i] == group then
69
						table.remove(groups, i);
70
						break
71
					end
72
				end
73
				conn.roster:add_contact(jid, item.name, groups, printor("saved"));
74
			end;
75
			list = function(param)
76
				if param == "" then
77
					param = nil
78
				end
79
				for jid, item in pairs(conn.roster.items) do
80
					local name, host = item.name or split_jid(jid);
81
					local groups = table.concat(item.groups or {}, ", ");
82
					if not param or ( (name and name:find(param, 1, true)) or jid:find(param, 1, true) ) then
83
						print(jid, name or host, groups);
84
					end
85
				end
86
			end;
87
			listgroups = function(param)
88
				local groups = {};
89
				for jid, item in pairs(conn.roster.items) do
90
					for i = 1,#item.groups do
91
						groups[item.groups[i]] = ( groups[item.groups[i]] or 0 ) + 1;
92
					end
93
				end
94
				for group, size in pairs(groups) do
95
					print(group, size)
96
				end
97
			end;
98
			show = function(param)
99
				local item = conn.roster.items[param];
100
				if not item then
101
					print("No such contact");
102
					return;
103
				end
104
 
105
				for k,v in pairs(item) do
106
					print(k,type(v) == "table" and table.concat(v, ", ") or v)
107
				end
108
			end;
109
			export = function()
110
				local stored_roster = { [false] = { version = conn.roster.ver } }
111
				for jid, item in pairs(conn.roster.items) do
112
					stored_roster[jid] = {
113
						name = item.name;
114
						subscription = item.subscription;
115
						groups = { unpack(item.groups) };
116
					}
117
				end
118
				print("return "..require"prosody.util.serialization".serialize(stored_roster));
119
			end
120
		}
121
		function commands.help ()
122
			print("Roster commands");
123
			for cmd in pairs(commands) do
124
				print(" * " .. cmd);
125
			end
126
			print("Most commands takes a JID as argument.");
127
		end
128
		local function on_incoming(stdin, text)
129
			local cmd = text:match("^(%a*)");
130
			local param = text:match("%s+(.*)", #cmd);
131
			if commands[cmd] then
132
				commands[cmd](param);
133
			end
134
		end
135
		stdin = verse.server.wrapclient(stdin, "stdin", 0, {
136
			onincoming = on_incoming, ondisconnect = function () end
137
		}, "*l");
138
		conn:add_plugin("roster");
139
		conn.roster:fetch(function(roster)
140
			if not roster then
141
				print("There was an error fetching your roster");
142
				conn:close()
143
			end
144
 
145
			if not opts.interactive then
146
				local firstcmd = commands[arg[1] or "list"];
147
				if firstcmd then
148
					firstcmd(table.concat(arg, " ", 2, #arg))
149
				end
150
				conn:close();
151
			end
152
		end);
153
		local function notif(e)
154
			return function(item)
155
				return print(("%s %s"):format((item.name and item.name .. " (" .. item.jid .. ")") or item.jid, e));
156
			end
157
		end
158
		if opts.interactive then
159
			conn:hook("roster/item-added", notif("added"));
160
			conn:hook("roster/item-changed", notif("changed"));
161
			conn:hook("roster/item-removed", notif("removed"));
162
		end
163
		if opts.presence then
164
			conn:hook("presence", function(presence)
165
				if presence.attr.type == "subscribe" then
166
					print(("%s wants to subscribe to you"):format(presence.attr.from));
167
				--elseif presence.attr.type == "subscribed" then
168
					--print(("%s subscribed to you?"):format(presence.attr.from));
169
				end
170
			end)
171
		end
172
	end
173
	clix_connect(opts, on_connect);
174
end