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