init.lua

1
local verse = require "verse";
2
local st = require "prosody.util.stanza";
3
 
4
-- Global, so that plugins can add themselves to riddim.plugins
5
riddim = { plugins = {} };
6
 
7
local riddim_mt = {};
8
riddim_mt.__index = riddim_mt;
9
 
10
function riddim.new(stream, config)
11
	if not stream then
12
		error("riddim.new(): Verse stream required as first parameter", 2);
13
	end
14
	return setmetatable({ stream = stream, config = config or {}, plugins = {} }, riddim_mt);
15
end
16
 
17
-- self.conn is ready for stanzas
18
function riddim_mt:start()
19
	self.stream:hook("stanza", function (stanza)
20
		local body = stanza:get_child("body");
21
		local event = {
22
			sender = { jid = stanza.attr.from };
23
			body = (body and body:get_text()) or nil;
24
			stanza = stanza;
25
		};
26
		if stanza.name == "message" then
27
			local replied;
28
			local bot = self;
29
			function event:reply(reply)
30
				if replied then return false; end
31
				replied = true;
32
				return bot:send_message(stanza.attr.from, stanza.attr.type, reply);
33
			end
34
		end
35
		local ret;
36
		if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
37
			local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
38
			if xmlns then
39
				event.xmlns = xmlns;
40
				ret = self:event("iq/"..xmlns, event);
41
			end
42
		end
43
		if not ret then
44
			ret = self:event(stanza.name, event);
45
		end
46
		if ret and type(ret) == "table" and ret.name then
47
			self:send(ret);
48
		end
49
		return ret;
50
	end, 1);
51
	self:event("started");
52
end
53
 
54
function riddim_mt:send(s)
55
	return self.stream:send(s);
56
end
57
 
58
function riddim_mt:send_iq(s, callback, errback)
59
	return self.stream:send_iq(s, callback, errback);
60
end
61
 
62
function riddim_mt:event(name, ...)
63
	return self.stream:event("bot/"..name, ...);
64
end
65
	
66
function riddim_mt:hook(name, ...)
67
	return self.stream:hook("bot/"..name, ...);
68
end
69
 
70
function riddim_mt:send_message(to, type, text)
71
	self:send(st.message({ to = to, type = type }):tag("body"):text(text));
72
end
73
 
74
function riddim_mt:send_presence(to, type)
75
	self:send(st.presence({ to = to, type = type }));
76
end
77
 
78
function riddim_mt:add_plugin(name)
79
	if not self.plugins[name] then
80
		self.plugins[name] = require("riddim.plugins."..name);
81
		return riddim.plugins[name](self);
82
	end
83
end
84
 
85
function riddim_mt:http_request(u, opt, cb)
86
	local http = require "prosody.net.http";
87
	opt = opt or {};
88
	opt.sslctx = opt.sslctx or {
89
		mode = "client";
90
		protocol = "sslv23";
91
		capath = self.config.capath or "/etc/ssl/certs";
92
		cafile = self.config.cafile;
93
		verify = "peer";
94
	};
95
	return http.request(u, opt, cb);
96
end
97
	
98
-- Built-in bot starter
99
if not (... and package.loaded[...] ~= nil) then
100
	require "verse.client";
101
	
102
	-- Config loading
103
	local config = setmetatable({}, { __index = _G });
104
	local chunk, err = loadfile(arg[1] or "config.lua", "t", config);
105
	if not chunk then
106
		print("File or syntax error:", err);
107
		return 1;
108
	end
109
 
110
	local ok, err = pcall(chunk);
111
	if not ok then
112
		print("Error while processing config:", err);
113
		return 1;
114
	end
115
	setmetatable(config, nil);
116
 
117
	if not config.jid then
118
		io.write("Enter the bot's JID: ");
119
		config.jid = io.read("*l");
120
	end
121
	
122
	if not config.password then
123
		io.write("Enter the password for "..config.jid..": ");
124
		config.password = io.read("*l");
125
	end
126
 
127
	-- Initialize the HTTP client
128
	local http = require "prosody.net.http";
129
	http.default.options.sslctx = {
130
		mode = "client";
131
		protocol = "sslv23";
132
		capath = config.capath or "/etc/ssl/certs";
133
		cafile = config.cafile;
134
		verify = "peer";
135
	};
136
 
137
	-- Create the stream object and bot object
138
	local c = verse.new();
139
	local b = riddim.new(c, config);
140
	
141
	if config.debug then
142
		c:hook("incoming-raw", print);
143
		c:hook("outgoing-raw", print);
144
		verse.set_log_handler(print);
145
	else
146
		verse.set_log_handler(print, {"info","warn","error"});
147
	end
148
	
149
	for _, plugin in ipairs(config.plugins or {}) do
150
		b:add_plugin(plugin);
151
	end
152
	
153
	for _, plugin in ipairs(config.stream_plugins or {}) do
154
		c:add_plugin(plugin);
155
	end
156
	
157
	b:hook("started", function ()
158
		local presence = verse.presence()
159
		if b.caps then
160
			presence:add_child(b:caps())
161
		end
162
		b:send(presence);
163
	end);
164
	
165
	c:hook("ready", function () b:start(); end);
166
 
167
	c:hook("stream-error", function ()
168
		print("Stream closed with error, riddim will exit now");
169
		os.exit(2);
170
	end);
171
 
172
	local shutting_down = false;
173
	c:hook("disconnected", function ()
174
		if shutting_down then
175
			os.exit(0);
176
		end
177
		print("Disconnected from server, riddim will exit now");
178
		os.exit(1);
179
	end, -1);
180
 
181
	-- Graceful shutdown on SIGTERM (docker stop) and SIGINT (Ctrl-C):
182
	-- close the stream cleanly so the server sends unavailable presence
183
	-- and MUCs see the bot leave, then exit once the server closes the
184
	-- connection (or after a short deadline).
185
	local have_signal, signal = pcall(require, "prosody.util.signal");
186
	local hook_signal = have_signal and verse.server.hook_signal;
187
	if hook_signal then
188
		local function shutdown(signame)
189
			if shutting_down then return; end
190
			shutting_down = true;
191
			print("Received "..signame..", riddim will exit now");
192
			if c.conn then
193
				c:close("shutdown");
194
				verse.add_task(5, function () os.exit(0); end);
195
			else
196
				os.exit(0);
197
			end
198
		end
199
		for _, signame in ipairs({ "SIGTERM", "SIGINT" }) do
200
			hook_signal(signal[signame], function () shutdown(signame); end);
201
		end
202
	end
203
 
204
	if config.connect_host then
205
		c.connect_host = config.connect_host;
206
	end
207
	if config.connect_port then
208
		c.connect_port = config.connect_port;
209
	end
210
	
211
	c:connect_client(config.jid, config.password);
212
	
213
	verse.loop();
214
end
215
 
216
return riddim;