plugins/factoids.lua

1
local st = require "prosody.util.stanza";
2
local storage_backends = {};
3
function riddim.plugins.factoids(bot)
4
	local factoids = {};
5
	local conf_backend = bot.config.factoid_backend;
6
	local backend;
7
 
8
	-- if backend == nil then forget everything on restart
9
 
10
	if conf_backend and storage_backends[conf_backend] then
11
		backend = storage_backends[conf_backend](bot, factoids);
12
	end
13
 
14
	bot:hook("unhandled-command", function(cmd)
15
		local name, param = cmd.command, cmd.param;
16
		local factoid = factoids[name];
17
		if factoid then
18
			return factoid;
19
		elseif param and #param > 4 and param:match("^is ") then
20
			factoids[name] = param:sub(4);
21
			return "I'll remember that.";
22
		end
23
	end);
24
	bot:hook("commands/forget", function(cmd)
25
		local name = cmd.param;
26
		local factoid = factoids[name];
27
		if factoid then
28
			factoids[name] = nil;
29
			if backend and backend.del then
30
				backend.del(name);
31
			end
32
			return ("Okay, I'll forget that %s is %s"):format(name, factoid);
33
		end
34
	end);
35
end
36
 
37
-- A simple file backend
38
function storage_backends.file(bot, factoids)
39
	local factoids_file = bot.config.factoids_file or "./factoids.txt";
40
	local function format_line(k, v)
41
		return ("%s=%s\n"):format(k, v)
42
	end
43
	local actions = {
44
		load = function()
45
			local fd = io.open(factoids_file, "r");
46
			if fd then
47
				for line in fd:lines() do
48
					local k, v = line:match("^(.+)=(.*)$");
49
					if k and v then
50
						factoids[k] = v;
51
					end
52
				end
53
				fd:close();
54
			end
55
		end,
56
		save = function()
57
			local fd = io.open(factoids_file, "w");
58
			if fd then
59
				for k,v in pairs(factoids) do
60
					fd:write(format_line(k, v));
61
				end
62
				fd:close();
63
			end
64
		end,
65
		add = function(k, v)
66
			local fd = io.open(factoids_file, "a");
67
			fd:write(format_line(k, v));
68
			fd:close();
69
		end,
70
	};
71
	function actions.del(...) actions.save(); end
72
	actions.load();
73
	setmetatable(factoids, {
74
		__newindex = function(t, k, v)
75
			v = v:gsub("\n"," ");
76
			rawset(t, k, v);
77
			actions.add(k, v);
78
		end,
79
	});
80
	return actions;
81
end
82
 
83
-- A variant of the above except serializing lua code
84
function storage_backends.lua(bot, factoids)
85
	local factoids_file = bot.config.factoids_file or "./factoids.dat";
86
	local function format_line(k, v)
87
		return ("_G[%q] = %q;\n"):format(k, v);
88
	end
89
	local actions = {
90
		load = function()
91
			local chunk, err = loadfile(factoids_file, "t", {_G = factoids});
92
			if chunk then
93
				chunk();
94
			else
95
				bot.stream:err(tostring(err));
96
			end
97
		end,
98
		save = function()
99
			local fd, err = io.open(factoids_file, "w");
100
			if err then print(err) end
101
			if fd then
102
				for k,v in pairs(factoids) do
103
					fd:write(format_line(k, v));
104
				end
105
				fd:close();
106
			end
107
		end,
108
		add = function(k,v)
109
			local fd, err = io.open(factoids_file, "a");
110
			if err then print(err) end
111
			if fd then
112
				fd:write(format_line(k, v));
113
				fd:close();
114
			end
115
		end,
116
		del = function(k)
117
			local fd, err = io.open(factoids_file, "a");
118
			if err then print(err) end
119
			if fd then
120
				fd:write(("_G[%q] = nil;\n"):format(k));
121
				fd:close();
122
			end
123
		end,
124
	}
125
	actions.load();
126
	setmetatable(factoids, {
127
		__newindex = function(t, k, v)
128
			rawset(t, k, v);
129
			actions.add(k, v);
130
		end,
131
	});
132
	return actions;
133
end
134
 
135
-- Yet another variant, using util.serialization
136
function storage_backends.serialize(bot, factoids)
137
	local serialize   = require "prosody.util.serialization".serialize;
138
	local deserialize = require "prosody.util.serialization".deserialize;
139
	local factoids_file = bot.config.factoids_file or "./factoids.dat";
140
	local actions = {
141
		load = function()
142
			local fd = io.open(factoids_file, "r");
143
			if fd then
144
				local old_mt = getmetatable(factoids);
145
				setmetatable(factoids, {});
146
				factoids = deserialize(fd:read("*a"));
147
				setmetatable(factoids, old_mt);
148
				df:close();
149
			end
150
		end,
151
		save = function()
152
			local fd, err = io.open(factoids_file, "w");
153
			if err then print(err) end
154
			if fd then
155
				fd:write(serialize(factoids));
156
				fd:close();
157
			end
158
		end,
159
	}
160
	function actions.add(...) actions.save(); end
161
	function actions.del(...) actions.save(); end
162
	actions.load();
163
	setmetatable(factoids, {
164
		__newindex = function(t, k, v)
165
			rawset(t, k, v);
166
			actions.add(k, v);
167
		end,
168
	});
169
	return actions;
170
end
171
 
172
-- A backend using XEP 49 storage
173
function storage_backends.iq_private(bot, factoids)
174
	bot.stream:add_plugin("private");
175
	local factoids_node = "factoids";
176
	local factoids_xmlns = "http://code.zash.se/riddim/plugins/factoids";
177
	local actions = {
178
		load = function()
179
			bot.stream:private_get(factoids_node, factoids_xmlns, function (storage)
180
				if storage then
181
					for factoid in storage:children() do
182
						factoids[factoid.attr.name or "something-invalid"] = factoid:get_text();
183
					end
184
				end
185
			end);
186
		end,
187
		save = function()
188
			local wrapper = st.stanza(factoids_node, { xmlns = factoids_xmlns });
189
			for name, text in pairs(factoids) do
190
				wrapper:tag("factoid", { name = name }):text(text):up();
191
			end
192
			bot.stream:private_set(factoids_node, factoids_xmlns, wrapper);
193
		end,
194
	}
195
	function actions.add(...) actions.save(); end
196
	function actions.del(...) actions.save(); end
197
	bot:hook("started", function()
198
		actions.load();
199
		setmetatable(factoids, {
200
			__newindex = function(t, k, v)
201
				rawset(t, k, v);
202
				actions.add(k, v);
203
			end,
204
		});
205
	end);
206
	return actions;
207
end