diff --git a/.gitignore b/.gitignore index d6d2cd0..76174e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -res/ \ No newline at end of file +res/ +bot/config.lua \ No newline at end of file diff --git a/bot/bot.lua b/bot/bot.lua index dbe56fc..226c084 100644 --- a/bot/bot.lua +++ b/bot/bot.lua @@ -1,10 +1,11 @@ http = require("socket.http") https = require("ssl.https") URL = require("socket.url") -json = (loadfile "./bot/JSON.lua")() +json = (loadfile "./libs/JSON.lua")() +serpent = (loadfile "./libs/serpent.lua")() require("./bot/utils") -VERSION = 'v0.7.7' +VERSION = '0.8.0' function on_msg_receive (msg) vardump(msg) @@ -22,10 +23,22 @@ end function ok_cb(extra, success, result) end +function on_binlog_replay_end () + started = 1 + -- Uncomment the line to enable cron plugins. + -- postpone (cron_plugins, false, 5.0) + -- See plugins/ping.lua as an example for cron + + _config = load_config() + _users = load_user_stats() + + -- load plugins + plugins = {} + load_plugins() +end + function msg_valid(msg) - -- if msg.from.id == our_id then - -- return true - -- end + -- Dont process outgoing messages if msg.out then return false end @@ -83,19 +96,60 @@ function _send_msg( destination, text) end end +-- Save the content of _config to config.lua +function save_config( ) + file = io.open('./bot/config.lua', 'w+') + local serialized = serpent.block(_config, { + comment = false, + name = "config" + }) + file:write(serialized) + file:close() +end -function load_config() - local f = assert(io.open('./bot/config.json', "r")) - local c = f:read "*a" - local config = json:decode(c) - if config.sh_enabled then - print ("!sh command is enabled") - for v,user in pairs(config.sudo_users) do - print("Allowed user: " .. user) - end - end - f:close() - return config + +function load_config( ) + local f = io.open('./bot/config.lua', "r") + -- If config.lua doesnt exists + if not f then + print ("Created new config file: bot/config.lua") + create_config() + end + f:close() + local config = loadfile ("./bot/config.lua")() + for v,user in pairs(config.sudo_users) do + print("Allowed user: " .. user) + end + return config +end + +-- Create a basic config.json file and saves it. +function create_config( ) + -- A simple config with basic plugins and ourserves as priviled user + config = { + enabled_plugins = { + "9gag", + "echo", + "get", + "set", + "images", + "img_google", + "location", + "media", + "plugins", + "stats", + "time", + "version", + "youtube" }, + sudo_users = {our_id} + } + file = io.open('./bot/config.lua', 'w+') + local serialized = serpent.block(config, { + comment = false, + name = "config" + }) + file:write(serialized) + file:close() end function update_user_stats(msg) @@ -158,18 +212,11 @@ end function on_get_difference_end () end -function on_binlog_replay_end () - started = 1 - -- Uncomment the line to enable cron plugins. - -- postpone (cron_plugins, false, 5.0) - -- See plugins/ping.lua as an example for cron -end - -- Enable plugins in config.json function load_plugins() - for k, v in pairs(config.enabled_plugins) do + for k, v in pairs(_config.enabled_plugins) do print("Loading plugin", v) - t = loadfile("plugins/" .. v)() + t = loadfile("plugins/"..v..'.lua')() table.insert(plugins, t) end end @@ -190,11 +237,4 @@ end -- Start and load values our_id = 0 -now = os.time() - -config = load_config() -_users = load_user_stats() - --- load plugins -plugins = {} -load_plugins() \ No newline at end of file +now = os.time() \ No newline at end of file diff --git a/bot/config.json b/bot/config.json deleted file mode 100644 index 52efcf4..0000000 --- a/bot/config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "enabled_plugins": [ "plugins.lua", "echo.lua", "hello.lua" ], - "rmtmp_delay": 20, - "google_api_key": "", - "log_file": "/var/www/html/log.txt", - "sh_enabled": false, - "sudo_users": [ 0, 1 ], - "twitter": { - "access_token": "", - "access_token_secret": "", - "consumer_key": "", - "consumer_secret": "" - } -} diff --git a/bot/utils.lua b/bot/utils.lua index 26da8ac..81079e4 100644 --- a/bot/utils.lua +++ b/bot/utils.lua @@ -61,7 +61,8 @@ function download_to_file( url , noremove ) file:close() if noremove == nil then - postpone(rmtmp_cb, file_path, config.rmtmp_delay) + print(file_path.."will be removed in 20 seconds") + postpone(rmtmp_cb, file_path, 20) end return file_path @@ -130,7 +131,7 @@ end function is_sudo(msg) local var = false -- Check users id in config - for v,user in pairs(config.sudo_users) do + for v,user in pairs(_config.sudo_users) do if user == msg.from.id then var = true end diff --git a/bot/JSON.lua b/libs/JSON.lua similarity index 100% rename from bot/JSON.lua rename to libs/JSON.lua diff --git a/libs/serpent.lua b/libs/serpent.lua new file mode 100644 index 0000000..dd08b7a --- /dev/null +++ b/libs/serpent.lua @@ -0,0 +1,128 @@ +local n, v = "serpent", 0.272 -- (C) 2012-13 Paul Kulchenko; MIT License +local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" +local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} +local badtype = {thread = true, userdata = true, cdata = true} +local keyword, globals, G = {}, {}, (_G or _ENV) +for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', + 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', + 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end +for k,v in pairs(G) do globals[v] = k end -- build func to name mapping +for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do + for k,v in pairs(G[g] or {}) do globals[v] = g..'.'..k end end + +local function s(t, opts) + local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum + local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge + local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) + local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge) + local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0 + local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)", + -- tostring(val) is needed because __tostring may return a non-string value + function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return syms[s] end)) end + local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or s) + or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 + or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end + local function comment(s,l) return comm and (l or 0) < comm and ' --[['..tostring(s)..']]' or '' end + local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal + and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end + local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] + local n = name == nil and '' or name + local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] + local safe = plain and n or '['..safestr(n)..']' + return (path or '')..(plain and path and '.' or '')..safe, safe end + local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding + local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} + local function padnum(d) return ("%0"..maxn.."d"):format(d) end + table.sort(k, function(a,b) + -- sort numeric keys first: k[key] is not nil for numerical keys + return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) + < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end + local function val2str(t, name, indent, insref, path, plainindex, level) + local ttype, level, mt = type(t), (level or 0), getmetatable(t) + local spath, sname = safename(path, name) + local tag = plainindex and + ((type(name) == "number") and '' or name..space..'='..space) or + (name ~= nil and sname..space..'='..space or '') + if seen[t] then -- already seen this element + sref[#sref+1] = spath..space..'='..space..seen[t] + return tag..'nil'..comment('ref', level) end + if type(mt) == 'table' and (mt.__serialize or mt.__tostring) then -- knows how to serialize itself + seen[t] = insref or spath + if mt.__serialize then t = mt.__serialize(t) else t = tostring(t) end + ttype = type(t) end -- new value falls through to be serialized + if ttype == "table" then + if level >= maxl then return tag..'{}'..comment('max', level) end + seen[t] = insref or spath + if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty + local maxn, o, out = math.min(#t, maxnum or #t), {}, {} + for key = 1, maxn do o[key] = key end + if not maxnum or #o < maxnum then + local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables + for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end + if maxnum and #o > maxnum then o[maxnum+1] = nil end + if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end + local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) + for n, key in ipairs(o) do + local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse + if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing + or opts.keyallow and not opts.keyallow[key] + or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types + or sparse and value == nil then -- skipping nils; do nothing + elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then + if not seen[key] and not globals[key] then + sref[#sref+1] = 'placeholder' + local sname = safename(iname, gensym(key)) -- iname is table for local variables + sref[#sref] = val2str(key,sname,indent,sname,iname,true) end + sref[#sref+1] = 'placeholder' + local path = seen[t]..'['..(seen[key] or globals[key] or gensym(key))..']' + sref[#sref] = path..space..'='..space..(seen[value] or val2str(value,nil,indent,path)) + else + out[#out+1] = val2str(value,key,indent,insref,seen[t],plainindex,level+1) + end + end + local prefix = string.rep(indent or '', level) + local head = indent and '{\n'..prefix..indent or '{' + local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) + local tail = indent and "\n"..prefix..'}' or '}' + return (custom and custom(tag,head,body,tail) or tag..head..body..tail)..comment(t, level) + elseif badtype[ttype] then + seen[t] = insref or spath + return tag..globerr(t, level) + elseif ttype == 'function' then + seen[t] = insref or spath + local ok, res = pcall(string.dump, t) + local func = ok and ((opts.nocode and "function() --[[..skipped..]] end" or + "((loadstring or load)("..safestr(res)..",'@serialized'))")..comment(t, level)) + return tag..(func or globerr(t, level)) + else return tag..safestr(t) end -- handle all other types + end + local sepr = indent and "\n" or ";"..space + local body = val2str(t, name, indent) -- this call also populates sref + local tail = #sref>1 and table.concat(sref, sepr)..sepr or '' + local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or '' + return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" +end + +local function deserialize(data, opts) + local f, res = (loadstring or load)('return '..data) + if not f then f, res = (loadstring or load)(data) end + if not f then return f, res end + if opts and opts.safe == false then return pcall(f) end + + local count, thread = 0, coroutine.running() + local h, m, c = debug.gethook(thread) + debug.sethook(function (e, l) count = count + 1 + if count >= 3 then error("cannot call functions") end + end, "c") + local res = {pcall(f)} + count = 0 -- set again, otherwise it's tripped on the next sethook + debug.sethook(thread, h, m, c) + return (table.unpack or unpack)(res) +end + +local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end +return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, + load = deserialize, + dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, + line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, + block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } diff --git a/plugins/location.lua b/plugins/location.lua index 7990a15..f6d876b 100644 --- a/plugins/location.lua +++ b/plugins/location.lua @@ -7,7 +7,9 @@ -- Globals -- If you have a google api key for the geocoding/timezone api -api_key = config.google_api_key or nil + +api_key = nil + base_api = "https://maps.googleapis.com/maps/api" function delay_s(delay) diff --git a/plugins/plugins.lua b/plugins/plugins.lua index 3525ee9..d656824 100644 --- a/plugins/plugins.lua +++ b/plugins/plugins.lua @@ -6,7 +6,7 @@ function enable_plugin( filename ) -- Checks if plugin exists if plugin_exists(filename) then -- Add to the config table - table.insert(config.enabled_plugins, filename) + table.insert(_config.enabled_plugins, filename) -- Reload the plugins return reload_plugins( ) else @@ -14,18 +14,18 @@ function enable_plugin( filename ) end end -function disable_plugin( filename ) +function disable_plugin( name ) -- Check if plugins exists - if not plugin_exists(filename) then - return 'Plugin '..filename..' does not exists' + if not plugin_exists(name) then + return 'Plugin '..name..' does not exists' end - local k = plugin_enabled(filename) + local k = plugin_enabled(name) -- Check if plugin is enabled if not k then - return 'Plugin '..filename..' not enabled' + return 'Plugin '..name..' not enabled' end -- Disable and reload - table.remove(config.enabled_plugins, k) + table.remove(_config.enabled_plugins, k) return reload_plugins(true) end @@ -37,7 +37,7 @@ end -- Retruns the key (index) in the config.enabled_plugins table function plugin_enabled( name ) - for k,v in pairs(config.enabled_plugins) do + for k,v in pairs(_config.enabled_plugins) do if name == v then return k end @@ -49,7 +49,7 @@ end -- Returns true if file exists in plugins folder function plugin_exists( name ) for k,v in pairs(plugins_names()) do - if name == v then + if name..'.lua' == v then return true end end @@ -62,13 +62,13 @@ function list_plugins(only_enabled) -- ✔ enabled, ❌ disabled local status = '❌' -- Check if is enabled - for k2, v2 in pairs(config.enabled_plugins) do - if v == v2 then + for k2, v2 in pairs(_config.enabled_plugins) do + if v == v2..'.lua' then status = '✔' end end if not only_enabled or status == '✔' then - text = text..v..' '..status..'\n' + text = text..v..'\b\t'..status..'\n' end end return text @@ -97,7 +97,7 @@ end return { description = "Enables, disables and reloads plugins", - usage = "!plugins, !plugins enable [plugin.lua], !plugins disable [plugin.lua], !plugins reload", + usage = "!plugins, !plugins enable [plugin], !plugins disable [plugin], !plugins reload", patterns = { "^!plugins$", "^!plugins? (enable) (.*)$", diff --git a/plugins/time.lua b/plugins/time.lua index 462a2e2..0bf0ed9 100644 --- a/plugins/time.lua +++ b/plugins/time.lua @@ -5,7 +5,8 @@ -- Globals -- If you have a google api key for the geocoding/timezone api -api_key = config.google_api_key or nil +api_key = nil + base_api = "https://maps.googleapis.com/maps/api" dateFormat = "%A %d %B - %H:%M:%S" diff --git a/plugins/twitter.lua b/plugins/twitter.lua index e1cc3b0..312e746 100644 --- a/plugins/twitter.lua +++ b/plugins/twitter.lua @@ -1,9 +1,9 @@ local OAuth = require "OAuth" -local consumer_key = config.twitter.consumer_key -local consumer_secret = config.twitter.consumer_secret -local access_token = config.twitter.access_token -local access_token_secret = config.twitter.access_token_secret +local consumer_key = "" +local consumer_secret = "" +local access_token = "" +local access_token_secret = "" local client = OAuth.new(consumer_key, consumer_secret, { RequestToken = "https://api.twitter.com/oauth/request_token", diff --git a/plugins/twitter_send.lua b/plugins/twitter_send.lua index d982247..e995e25 100644 --- a/plugins/twitter_send.lua +++ b/plugins/twitter_send.lua @@ -1,9 +1,9 @@ local OAuth = require "OAuth" -local consumer_key = config.twitter.consumer_key -local consumer_secret = config.twitter.consumer_secret -local access_token = config.twitter.access_token -local access_token_secret = config.twitter.access_token_secret +local consumer_key = "" +local consumer_secret = "" +local access_token = "" +local access_token_secret = "" local client = OAuth.new(consumer_key, consumer_secret, { RequestToken = "https://api.twitter.com/oauth/request_token",