| 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 |