plugins/ietf.lua

1
local http = require "prosody.net.http";
2
local t_insert, t_remove = table.insert, table.remove;
3
local now = os.time;
4
 
5
local debug = function() end;
6
 
7
local ttl = 3600;
8
local data = {
9
	rfc = {
10
		source = "http://www.ietf.org/download/rfc-index.txt",
11
		links = "https://tools.ietf.org/html/rfc%s",
12
	},
13
	draft = {
14
		source = "http://www.ietf.org/download/id-index.txt",
15
		links = "https://tools.ietf.org/html/%s",
16
	},
17
}
18
 
19
 
20
function data.rfc:update(cb)
21
	debug("fetch %s", self.source);
22
	http.request(self.source, {
23
		headers = {
24
			["If-Modified-Since"] = self.updated_at
25
				and os.date("!%a, %d %b %Y %H:%M:%S %Z", self.updated_at) or nil;
26
		}
27
	}, function (data, code)
28
		debug("got status %d", code);
29
		if code == 200 then
30
			debug("got %d bytes of data", #data);
31
			self.data = data
32
					:gsub("\n\n[^\n]+%b()\n%-+\n\n", "\n\n")
33
					:gsub("\n     ", " ")
34
					:gsub("\n\n  ", "\n")
35
					:gsub("\n  ", " ");
36
					-- TODO Can this be made better?
37
			self.updated_at = now();
38
		end
39
		self.expires = now() + ttl;
40
		cb();
41
	end);
42
end
43
 
44
data.draft.update = data.rfc.update;
45
 
46
function data.rfc:_search(string, cb)
47
	debug("really search for %s", string);
48
	local number = tonumber(string);
49
	local link, match, matches;
50
	local count = 0
51
	if number then
52
		number = ("%04d"):format(number);
53
		debug("search for RFC%s", number);
54
		link, match = self.data:match("\n(" .. number .. ")%s*([^\n]*)");
55
		count = 1
56
	else
57
		local pat = string:gsub("[-()%[]", "%%%1")
58
			:gsub("%%(%b[])",function(s) return (#s > 2 and "" or "%") .. s end)
59
			:gsub("\n+", " "):gsub("\\n", "")
60
			:gsub("%w", function(s) return '['..s:lower()..s:upper()..']' end)
61
		debug("fulltext search for \"%s\"", pat);
62
		--link, match = self.data:match("\n(%d%d%d%d) ([^\n]-"..pat.."[^\n]*)");
63
		for l,m in self.data:gmatch("\n(%d%d%d%d) ([^\n]-"..pat.."[^\n]*)") do
64
			link, match = l, m
65
			count = count + 1
66
			-- Note: This allways returns the last result.
67
			-- FIXME Decide on what to do if >1 results.
68
		end
69
		--[[
70
		matches = {};
71
		for link, match in g do
72
			t_insert(matches, {link=link, match=match});
73
		end
74
		matches = t_remove(matches);
75
		matches.link, matches.match;
76
		--]]
77
	end
78
 
79
	if match then
80
		debug("matched %d bytes, number is %s", #match, link);
81
		if #match > 300 then
82
			cb("Match was too big");
83
			return
84
		end
85
		local remove = {
86
			Also = true,
87
			Format = true,
88
			--Obsoleted = true,
89
			Obsoletes = true,
90
			--Updated = true,
91
			Updates = true,
92
			--Status = true,
93
		};
94
		match = match:gsub("%s*\n%s+", " ")
95
		match = match:gsub("%s*%b()", function(s)
96
			local first = s:match("%(([^: ]*)"); return first and remove[first] and "" or s
97
		end);
98
		link = self.links:format(link);
99
		match = match:gsub("%. ", ".\n", 1); -- Add a newline between title and authors
100
		if count > 1 then link = link .. " (" .. count .. " more matches)" end
101
		cb(match .. "\n" .. link);
102
	elseif number then
103
		cb("Sorry, no match, try " .. self.links:format(number));
104
	else
105
		cb("Sorry, no match");
106
	end
107
end
108
 
109
function data.draft:_search(string, cb)
110
	debug("really search for %s", string);
111
	local pat = string:gsub("[-()%[]", "%%%1")
112
		:gsub("%%(%b[])",function(s) return (#s > 2 and "" or "%") .. s end)
113
		:gsub("\n+", " "):gsub("\\n", "")
114
		:gsub("%w", function(s) return '['..s:lower()..s:upper()..']' end)
115
	debug("fulltext search for \"%s\"", pat);
116
	local match = self.data:match("\n([^\n]-"..pat.."[^\n]*)")
117
 
118
	if match then
119
		debug("match: %s", match);
120
		local match, link = match:match("(.-)%s(%b<>)")
121
		link = link and self.links:format(link:sub(2,-2)) or "no link";
122
		cb(match .. "\n" .. link);
123
	else
124
		cb("Sorry, no match");
125
	end
126
end
127
 
128
function data.rfc:search(string, cb)
129
	debug("search for %s", string);
130
	if not self.data then --or self.expires < now() then
131
		self:update(function() self:_search(string,cb) end);
132
		return
133
	else
134
		self:_search(string,cb)
135
	end
136
end
137
 
138
data.draft.search = data.rfc.search;
139
 
140
 
141
function riddim.plugins.ietf(bot)
142
	if bot.stream.debug then
143
		function debug(...)
144
			return bot.stream:debug(...)
145
		end
146
	end
147
 
148
	bot:hook("commands/rfc", function(command)
149
		local rfc = data.rfc;
150
		debug("search for %s", command.param);
151
		return rfc:search(command.param, function(match)
152
			command:reply(match)
153
		end)
154
	end)
155
 
156
	bot:hook("commands/draft", function(command)
157
		local draft = data.draft;
158
		debug("search for %s", command.param);
159
		return draft:search(command.param, function(match)
160
			command:reply(match)
161
		end)
162
	end)
163
end