Merge pull request #45 from yagop/develop

New version 0.8.2! Changes about config and plugins.
This commit is contained in:
Yago 2015-01-06 21:20:56 +01:00
commit 0cc9ea0041
18 changed files with 784 additions and 220 deletions

4
.gitignore vendored
View File

@ -1 +1,3 @@
res/
res/
data/
bot/config.lua

View File

@ -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.2'
function on_msg_receive (msg)
vardump(msg)
@ -13,7 +14,6 @@ function on_msg_receive (msg)
return
end
update_user_stats(msg)
do_action(msg)
mark_read(get_receiver(msg), ok_cb, false)
@ -22,15 +22,21 @@ end
function ok_cb(extra, success, result)
end
-- Callback to remove tmp files
function rmtmp_cb(file_path, success, result)
os.remove(file_path)
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()
-- 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
@ -42,6 +48,20 @@ function msg_valid(msg)
end
end
function do_lex(msg, text)
for name, desc in pairs(plugins) do
if (desc.lex ~= nil) then
result = desc.lex(msg, text)
if (result ~= nil) then
print ("Mutating to " .. result)
text = result
end
end
end
-- print("Text mutated to " .. text)
return text
end
-- Where magic happens
function do_action(msg)
local receiver = get_receiver(msg)
@ -52,19 +72,28 @@ function do_action(msg)
text = '['..msg.media.type..']'
end
-- print("Received msg", text)
msg.text = do_lex(msg, text)
for name, desc in pairs(plugins) do
-- print("Trying module", name)
for k, pattern in pairs(desc.patterns) do
-- print("Trying", text, "against", pattern)
matches = { string.match(text, pattern) }
if matches[1] then
print(" matches",pattern)
print(" matches", pattern)
if desc.run ~= nil then
result = desc.run(msg, matches)
print(" sending", result)
if (result) then
_send_msg(receiver, result)
return
-- If plugin is for privileged user
if desc.privileged and not is_sudo(msg) then
local text = 'This plugin requires privileged user'
send_msg(receiver, text, ok_cb, false)
else
result = desc.run(msg, matches)
-- print(" sending", result)
if (result) then
result = do_lex(msg, result)
_send_msg(receiver, result)
end
end
end
end
@ -88,60 +117,51 @@ function _send_msg( destination, text)
end
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
-- Save the content of _config to config.lua
function save_config( )
serialize_to_file(_config, './data/config.lua')
print ('saved config into ./data/config.lua')
end
function update_user_stats(msg)
-- Save user to _users table
local from_id = tostring(msg.from.id)
local to_id = tostring(msg.to.id)
local user_name = get_name(msg)
-- If last name is nil dont save last_name.
local user_last_name = msg.from.last_name
local user_print_name = msg.from.print_name
if _users[to_id] == nil then
_users[to_id] = {}
end
if _users[to_id][from_id] == nil then
_users[to_id][from_id] = {
name = user_name,
last_name = user_last_name,
print_name = user_print_name,
msg_num = 1
}
function load_config( )
local f = io.open('./data/config.lua', "r")
-- If config.lua doesnt exists
if not f then
print ("Created new config file: data/config.lua")
create_config()
else
local actual_num = _users[to_id][from_id].msg_num
_users[to_id][from_id].msg_num = actual_num + 1
-- And update last_name
_users[to_id][from_id].last_name = user_last_name
f:close()
end
local config = loadfile ("./data/config.lua")()
for v,user in pairs(config.sudo_users) do
print("Allowed user: " .. user)
end
return config
end
function load_user_stats()
local f = io.open('res/users.json', "r+")
-- If file doesn't exists
if f == nil then
f = io.open('res/users.json', "w+")
f:write("{}") -- Write empty table
f:close()
return {}
else
local c = f:read "*a"
f:close()
return json:decode(c)
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}
}
serialize_to_file(config, './data/config.lua')
print ('saved config into ./data/config.lua')
end
function on_our_id (id)
@ -163,22 +183,12 @@ 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
-- load all plugins in the plugins/ directory
-- Enable plugins in config.json
function load_plugins()
for k, v in pairs(scandir("plugins")) do
-- Load only lua files
if (v:match(".lua$")) then
print("Loading plugin", v)
t = loadfile("plugins/" .. v)()
table.insert(plugins, t)
end
for k, v in pairs(_config.enabled_plugins) do
print("Loading plugin", v)
t = loadfile("plugins/"..v..'.lua')()
table.insert(plugins, t)
end
end
@ -199,10 +209,3 @@ end
-- Start and load values
our_id = 0
now = os.time()
config = load_config()
_users = load_user_stats()
-- load plugins
plugins = {}
load_plugins()

View File

@ -1,13 +0,0 @@
{
"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": ""
}
}

View File

@ -61,12 +61,18 @@ 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
end
-- Callback to remove a file
function rmtmp_cb(file_path, success, result)
os.remove(file_path)
end
function vardump(value, depth, key)
local linePrefix = ""
local spaces = ""
@ -125,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
@ -133,10 +139,50 @@ function is_sudo(msg)
return var
end
-- Returns the name of the sender
function get_name(msg)
local name = msg.from.first_name
if name == nil then
name = msg.from.id
end
return name
end
-- Returns at table of lua files inside plugins
function plugins_names( )
local files = {}
for k, v in pairs(scandir("plugins")) do
-- Ends with .lua
if (v:match(".lua$")) then
table.insert(files, v)
end
end
return files
end
-- Function name explains what it does.
function file_exists(name)
local f = io.open(name,"r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
-- Save into file the data serialized for lua.
function serialize_to_file(data, file)
file = io.open(file, 'w+')
local serialized = serpent.block(data, {
comment = false,
name = "_"
})
file:write(serialized)
file:close()
end
-- Retruns true if the string is empty
function string:isempty()
return self == nil or self == ''
end

View File

@ -14,4 +14,4 @@ if [ ! -f ./tg/bin/telegram-cli ]; then
exit
fi
./tg/bin/telegram-cli -k tg/tg-server.pub -s ./bot/bot.lua
./tg/bin/telegram-cli -k tg/tg-server.pub -s ./bot/bot.lua -W -l 1

View File

@ -14,13 +14,13 @@
-- the web-page links above, and the 'AUTHOR_NOTE' string below are
-- maintained. Enjoy.
--
local VERSION = 20140920.13 -- version history at end of file
local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20140920.13 ]-"
local VERSION = 20141223.14 -- version history at end of file
local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-"
--
-- The 'AUTHOR_NOTE' variable exists so that information about the source
-- of the package is maintained even in compiled versions. It's included in
-- OBJDEF mostly to quiet warnings about unused variables.
-- of the package is maintained even in compiled versions. It's also
-- included in OBJDEF below mostly to quiet warnings about unused variables.
--
local OBJDEF = {
VERSION = VERSION,
@ -33,7 +33,7 @@ local OBJDEF = {
-- http://www.json.org/
--
--
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines
-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
--
-- local lua_value = JSON:decode(raw_json_text)
--
@ -41,9 +41,11 @@ local OBJDEF = {
-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
--
--
-- DECODING
--
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines
-- DECODING (from a JSON string to a Lua table)
--
--
-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
--
-- local lua_value = JSON:decode(raw_json_text)
--
@ -58,22 +60,27 @@ local OBJDEF = {
-- { "Larry", "Curly", "Moe" }
--
--
-- The encode and decode routines accept an optional second argument, "etc", which is not used
-- during encoding or decoding, but upon error is passed along to error handlers. It can be of any
-- type (including nil).
-- The encode and decode routines accept an optional second argument,
-- "etc", which is not used during encoding or decoding, but upon error
-- is passed along to error handlers. It can be of any type (including nil).
--
--
--
-- ERROR HANDLING
--
-- With most errors during decoding, this code calls
--
-- JSON:onDecodeError(message, text, location, etc)
--
-- with a message about the error, and if known, the JSON text being parsed and the byte count
-- where the problem was discovered. You can replace the default JSON:onDecodeError() with your
-- own function.
-- with a message about the error, and if known, the JSON text being
-- parsed and the byte count where the problem was discovered. You can
-- replace the default JSON:onDecodeError() with your own function.
--
-- The default onDecodeError() merely augments the message with data about the text and the
-- location if known (and if a second 'etc' argument had been provided to decode(), its value is
-- tacked onto the message as well), and then calls JSON.assert(), which itself defaults to Lua's
-- built-in assert(), and can also be overridden.
-- The default onDecodeError() merely augments the message with data
-- about the text and the location if known (and if a second 'etc'
-- argument had been provided to decode(), its value is tacked onto the
-- message as well), and then calls JSON.assert(), which itself defaults
-- to Lua's built-in assert(), and can also be overridden.
--
-- For example, in an Adobe Lightroom plugin, you might use something like
--
@ -95,9 +102,10 @@ local OBJDEF = {
--
-- JSON:onDecodeOfHTMLError(message, text, nil, etc)
--
-- The use of the fourth 'etc' argument allows stronger coordination between decoding and error
-- reporting, especially when you provide your own error-handling routines. Continuing with the
-- the Adobe Lightroom plugin example:
-- The use of the fourth 'etc' argument allows stronger coordination
-- between decoding and error reporting, especially when you provide your
-- own error-handling routines. Continuing with the the Adobe Lightroom
-- plugin example:
--
-- function JSON:onDecodeError(message, text, location, etc)
-- local note = "Internal Error: invalid JSON data"
@ -121,42 +129,136 @@ local OBJDEF = {
--
--
--
--
-- DECODING AND STRICT TYPES
--
-- Because both JSON objects and JSON arrays are converted to Lua tables, it's not normally
-- possible to tell which a JSON type a particular Lua table was derived from, or guarantee
-- decode-encode round-trip equivalency.
-- Because both JSON objects and JSON arrays are converted to Lua tables,
-- it's not normally possible to tell which original JSON type a
-- particular Lua table was derived from, or guarantee decode-encode
-- round-trip equivalency.
--
-- However, if you enable strictTypes, e.g.
--
-- JSON = (loadfile "JSON.lua")() --load the routines
-- JSON = assert(loadfile "JSON.lua")() --load the routines
-- JSON.strictTypes = true
--
-- then the Lua table resulting from the decoding of a JSON object or JSON array is marked via Lua
-- metatable, so that when re-encoded with JSON:encode() it ends up as the appropriate JSON type.
-- then the Lua table resulting from the decoding of a JSON object or
-- JSON array is marked via Lua metatable, so that when re-encoded with
-- JSON:encode() it ends up as the appropriate JSON type.
--
-- (This is not the default because other routines may not work well with tables that have a
-- metatable set, for example, Lightroom API calls.)
-- (This is not the default because other routines may not work well with
-- tables that have a metatable set, for example, Lightroom API calls.)
--
--
-- ENCODING
-- ENCODING (from a lua table to a JSON string)
--
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines
-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
--
-- local raw_json_text = JSON:encode(lua_table_or_value)
-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false })
--
-- On error during encoding, this code calls:
--
-- JSON:onEncodeError(message, etc)
-- JSON:onEncodeError(message, etc)
--
-- which you can override in your local JSON object.
--
-- If the Lua table contains both string and numeric keys, it fits neither JSON's
-- idea of an object, nor its idea of an array. To get around this, when any string
-- key exists (or when non-positive numeric keys exist), numeric keys are converted
-- to strings.
-- The 'etc' in the error call is the second argument to encode()
-- and encode_pretty(), or nil if it wasn't provided.
--
--
-- PRETTY-PRINTING
--
-- An optional third argument, a table of options, allows a bit of
-- configuration about how the encoding takes place:
--
-- pretty = JSON:encode(val, etc, {
-- pretty = true, -- if false, no other options matter
-- indent = " ", -- this provides for a three-space indent per nesting level
-- align_keys = false, -- see below
-- })
--
-- encode() and encode_pretty() are identical except that encode_pretty()
-- provides a default options table if none given in the call:
--
-- { pretty = true, align_keys = false, indent = " " }
--
-- For example, if
--
-- JSON:encode(data)
--
-- produces:
--
-- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11}
--
-- then
--
-- JSON:encode_pretty(data)
--
-- produces:
--
-- {
-- "city": "Kyoto",
-- "climate": {
-- "avg_temp": 16,
-- "humidity": "high",
-- "snowfall": "minimal"
-- },
-- "country": "Japan",
-- "wards": 11
-- }
--
-- The following three lines return identical results:
-- JSON:encode_pretty(data)
-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " })
-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " })
--
-- An example of setting your own indent string:
--
-- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " })
--
-- produces:
--
-- {
-- | "city": "Kyoto",
-- | "climate": {
-- | | "avg_temp": 16,
-- | | "humidity": "high",
-- | | "snowfall": "minimal"
-- | },
-- | "country": "Japan",
-- | "wards": 11
-- }
--
-- An example of setting align_keys to true:
--
-- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true })
--
-- produces:
--
-- {
-- "city": "Kyoto",
-- "climate": {
-- "avg_temp": 16,
-- "humidity": "high",
-- "snowfall": "minimal"
-- },
-- "country": "Japan",
-- "wards": 11
-- }
--
-- which I must admit is kinda ugly, sorry. This was the default for
-- encode_pretty() prior to version 20141223.14.
--
--
-- AMBIGUOUS SITUATIONS DURING THE ENCODING
--
-- During the encode, if a Lua table being encoded contains both string
-- and numeric keys, it fits neither JSON's idea of an object, nor its
-- idea of an array. To get around this, when any string key exists (or
-- when non-positive numeric keys exist), numeric keys are converted to
-- strings.
--
-- For example,
-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
@ -165,6 +267,9 @@ local OBJDEF = {
--
-- To prohibit this conversion and instead make it an error condition, set
-- JSON.noKeyConversion = true
--
--
@ -181,6 +286,9 @@ local OBJDEF = {
--
---------------------------------------------------------------------------
local default_pretty_indent = " "
local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent }
local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray
local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject
@ -692,8 +800,13 @@ end
--
-- Encode
--
-- 'options' is nil, or a table with possible keys:
-- pretty -- if true, return a pretty-printed version
-- indent -- a string (usually of spaces) used to indent each nested level
-- align_keys -- if true, align all the keys when formatting a table
--
local encode_value -- must predeclare because it calls itself
function encode_value(self, value, parents, etc, indent) -- non-nil indent means pretty-printing
function encode_value(self, value, parents, etc, options, indent)
if value == nil then
return 'null'
@ -739,6 +852,13 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
--
local T = value
if type(options) ~= 'table' then
options = {}
end
if type(indent) ~= 'string' then
indent = ""
end
if parents[T] then
self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
else
@ -754,13 +874,13 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
--
local ITEMS = { }
for i = 1, maximum_number_key do
table.insert(ITEMS, encode_value(self, T[i], parents, etc, indent))
table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent))
end
if indent then
if options.pretty then
result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
else
result_value = "[" .. table.concat(ITEMS, ",") .. "]"
result_value = "[" .. table.concat(ITEMS, ",") .. "]"
end
elseif object_keys then
@ -769,22 +889,24 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
--
local TT = map or T
if indent then
if options.pretty then
local KEYS = { }
local max_key_length = 0
for _, key in ipairs(object_keys) do
local encoded = encode_value(self, tostring(key), parents, etc, "")
max_key_length = math.max(max_key_length, #encoded)
local encoded = encode_value(self, tostring(key), parents, etc, options, indent)
if options.align_keys then
max_key_length = math.max(max_key_length, #encoded)
end
table.insert(KEYS, encoded)
end
local key_indent = indent .. " "
local subtable_indent = indent .. string.rep(" ", max_key_length + 2 + 4)
local key_indent = indent .. tostring(options.indent or "")
local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "")
local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s"
local COMBINED_PARTS = { }
for i, key in ipairs(object_keys) do
local encoded_val = encode_value(self, TT[key], parents, etc, subtable_indent)
local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)
table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))
end
result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}"
@ -793,8 +915,8 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
local PARTS = { }
for _, key in ipairs(object_keys) do
local encoded_val = encode_value(self, TT[key], parents, etc, indent)
local encoded_key = encode_value(self, tostring(key), parents, etc, indent)
local encoded_val = encode_value(self, TT[key], parents, etc, options, indent)
local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent)
table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
end
result_value = "{" .. table.concat(PARTS, ",") .. "}"
@ -813,18 +935,18 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
end
function OBJDEF:encode(value, etc)
function OBJDEF:encode(value, etc, options)
if type(self) ~= 'table' or self.__index ~= OBJDEF then
OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
end
return encode_value(self, value, {}, etc, nil)
return encode_value(self, value, {}, etc, options or nil)
end
function OBJDEF:encode_pretty(value, etc)
function OBJDEF:encode_pretty(value, etc, options)
if type(self) ~= 'table' or self.__index ~= OBJDEF then
OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc)
end
return encode_value(self, value, {}, etc, "")
return encode_value(self, value, {}, etc, options or default_pretty_options)
end
function OBJDEF.__tostring()
@ -850,6 +972,16 @@ return OBJDEF:new()
--
-- Version history:
--
-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really
-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines
-- more flexible, and changed the default encode_pretty() to be more generally useful.
--
-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control
-- how the encoding takes place.
--
-- Updated docs to add assert() call to the loadfile() line, just as good practice so that
-- if there is a problem loading JSON.lua, the appropriate error message will percolate up.
--
-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string,
-- so that the source of the package, and its version number, are visible in compiled copies.
--

128
libs/serpent.lua Normal file
View File

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

View File

@ -1,13 +1,30 @@
local f = io.open('./res/values.json', "r+")
if f == nil then
f = io.open('./res/values.json', "w+")
f:write("{}") -- Write empty table
f:close()
_values = {}
else
local c = f:read "*a"
f:close()
_values = json:decode(c)
local _file_values = './data/values.lua'
function read_file_values( )
local f = io.open(_file_values, "r+")
-- If file doesn't exists
if f == nil then
-- Create a new empty table
print ('Created value file '.._file_values)
serialize_to_file({}, _file_values)
else
print ('Stats loaded: '.._file_values)
f:close()
end
return loadfile (_file_values)()
end
_values = read_file_values()
function fetch_value(chat, value_name)
if (_values[chat] == nil) then
return nil
end
if (value_name == nil ) then
return nil
end
local value = _values[chat][value_name]
return value
end
function get_value(chat, value_name)
@ -40,11 +57,27 @@ function run(msg, matches)
return get_value(chat_id, matches[1])
end
function lex(msg, text)
local chat_id = tostring(msg.to.id)
local s, e = text:find("%$%a+")
if (s == nil) then
return nil
end
local var = text:sub(s + 1, e)
local value = fetch_value(chat_id, var)
if (value == nil) then
value = "(unknown value " .. var .. ")"
end
return text:sub(0, s - 1) .. value .. text:sub(e + 1)
end
return {
description = "retrieves variables saved with !set",
usage = "!get (value_name)",
patterns = {
"^!get (%a+)$",
"^!get$"},
run = run
run = run,
lex = lex
}

42
plugins/invite.lua Normal file
View File

@ -0,0 +1,42 @@
-- Invite other user to the chat group.
-- Use !invite name User_name or !invite id id_number
-- The User_name is the print_name (there are no spaces but _)
do
local function run(msg, matches)
-- User submitted a user name
if matches[1] == "name" then
user = matches[2]
user = string.gsub(user," ","_")
end
-- User submitted an id
if matches[1] == "id" then
user = matches[2]
user = 'user#id'..user
end
-- The message must come from a chat group
if msg.to.type == 'chat' then
chat = 'chat#id'..msg.to.id
else
return 'This isnt a chat group!'
end
print ("Trying to add: "..user.." to "..chat)
status = chat_add_user (chat, user, ok_cb, false)
if not status then
return "An error happened"
end
return "Added user: "..user.." to "..chat
end
return {
description = "Invite other user to the chat group",
usage = "!invite name [user_name], !invite id [user_id]",
patterns = {
"^!invite (name) (.*)$",
"^!invite (id) (%d+)$"
},
run = run
}
end

View File

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

113
plugins/plugins.lua Normal file
View File

@ -0,0 +1,113 @@
function enable_plugin( filename )
-- Check if plugin is enabled
if plugin_enabled(filename) then
return 'Plugin '..filename..' is enabled'
end
-- Checks if plugin exists
if plugin_exists(filename) then
-- Add to the config table
table.insert(_config.enabled_plugins, filename)
save_config()
-- Reload the plugins
return reload_plugins( )
else
return 'Plugin '..filename..' does not exists'
end
end
function disable_plugin( name )
-- Check if plugins exists
if not plugin_exists(name) then
return 'Plugin '..name..' does not exists'
end
local k = plugin_enabled(name)
-- Check if plugin is enabled
if not k then
return 'Plugin '..name..' not enabled'
end
-- Disable and reload
table.remove(_config.enabled_plugins, k)
save_config( )
return reload_plugins(true)
end
function reload_plugins( )
plugins = {}
load_plugins()
return list_plugins(true)
end
-- Retruns the key (index) in the config.enabled_plugins table
function plugin_enabled( name )
for k,v in pairs(_config.enabled_plugins) do
if name == v then
return k
end
end
-- If not found
return false
end
-- Returns true if file exists in plugins folder
function plugin_exists( name )
for k,v in pairs(plugins_names()) do
if name..'.lua' == v then
return true
end
end
return false
end
function list_plugins(only_enabled)
local text = ''
for k, v in pairs( plugins_names( )) do
-- ✔ enabled, ❌ disabled
local status = ''
-- Check if is enabled
for k2, v2 in pairs(_config.enabled_plugins) do
if v == v2..'.lua' then
status = ''
end
end
if not only_enabled or status == '' then
-- get the name
v = string.match (v, "(.*)%.lua")
text = text..v..' '..status..'\n'
end
end
return text
end
function run(msg, matches)
-- Show the available plugins
if matches[1] == '!plugins' then
return list_plugins()
end
-- Enable a plugin
if matches[1] == 'enable' then
print("enable: "..matches[2])
return enable_plugin(matches[2])
end
-- Disable a plugin
if matches[1] == 'disable' then
print("disable: "..matches[2])
return disable_plugin(matches[2])
end
-- Reload all the plugins!
if matches[1] == 'reload' then
return reload_plugins(true)
end
end
return {
description = "Enables, disables and reloads plugins",
usage = "!plugins, !plugins enable [plugin], !plugins disable [plugin], !plugins reload",
patterns = {
"^!plugins$",
"^!plugins? (enable) (.*)$",
"^!plugins? (disable) (.*)$",
"^!plugins? (reload)$"
},
run = run,
privileged = true
}

View File

@ -1,14 +0,0 @@
function run(msg, matches)
plugins = {}
load_plugins()
return 'Plugins reloaded'
end
return {
description = "Reloads bot plugins",
usage = "!reload",
patterns = {"^!reload$"},
run = run
}

View File

@ -1,3 +1,5 @@
local _file_values = './data/values.lua'
function save_value(chat, text )
var_name, var_value = string.match(text, "!set (%a+) (.+)")
if (var_name == nil or var_value == nil) then
@ -8,10 +10,8 @@ function save_value(chat, text )
end
_values[chat][var_name] = var_value
local json_text = json:encode_pretty(_values)
file = io.open ("./res/values.json", "w+")
file:write(json_text)
file:close()
-- Save values to file
serialize_to_file(_values, _file_values)
return "Saved "..var_name.." = "..var_value
end

View File

@ -1,28 +1,98 @@
function run(msg, matches)
vardump(_users)
-- Save stats to file
local json_users = json:encode_pretty(_users)
vardump(json_users)
file_users = io.open ("./res/users.json", "w")
file_users:write(json_users)
file_users:close()
-- Saves the number of messages from a user
-- Can check the number of messages with !stats
do
local socket = require('socket')
local _file_stats = './data/stats.lua'
local _stats
function update_user_stats(msg)
-- Save user to stats table
local from_id = tostring(msg.from.id)
local to_id = tostring(msg.to.id)
local user_name = get_name(msg)
print ('New message from '..user_name..'['..from_id..']'..' to '..to_id)
-- If last name is nil dont save last_name.
local user_last_name = msg.from.last_name
local user_print_name = msg.from.print_name
if _stats[to_id] == nil then
print ('New stats key to_id: '..to_id)
_stats[to_id] = {}
end
if _stats[to_id][from_id] == nil then
print ('New stats key from_id: '..to_id)
_stats[to_id][from_id] = {
name = user_name,
last_name = user_last_name,
print_name = user_print_name,
msg_num = 1
}
else
print ('Updated '..to_id..' '..from_id)
local actual_num = _stats[to_id][from_id].msg_num
_stats[to_id][from_id].msg_num = actual_num + 1
-- And update last_name
_stats[to_id][from_id].last_name = user_last_name
end
end
function read_file_stats( )
local f = io.open(_file_stats, "r+")
-- If file doesn't exists
if f == nil then
-- Create a new empty table
print ('Created user stats file '.._file_stats)
serialize_to_file({}, _file_stats)
else
print ('Stats loaded: '.._file_stats)
f:close()
end
return loadfile (_file_stats)()
end
local function save_stats()
-- Save stats to file
serialize_to_file(_stats, _file_stats)
end
local function get_stats_status( msg )
-- vardump(stats)
local text = ""
local to_id = tostring(msg.to.id)
for id, user in pairs(_users[to_id]) do
for id, user in pairs(_stats[to_id]) do
if user.last_name == nil then
text = text..user.name.." ["..id.."]: "..user.msg_num.."\n"
else
text = text..user.name.." "..user.last_name.." ["..id.."]: "..user.msg_num.."\n"
end
end
print("usuarios: "..text)
return text
end
local function run(msg, matches)
if matches[1] == "stats" then -- Hack
return get_stats_status(msg)
else
print ("update stats")
update_user_stats(msg)
save_stats()
end
end
_stats = read_file_stats()
return {
description = "Numer of messages by user",
usage = "!stats",
patterns = {"^!stats"},
patterns = {
".*",
"^!(stats)"
},
run = run
}
}
end

View File

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

View File

@ -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",
@ -16,16 +16,23 @@ local client = OAuth.new(consumer_key, consumer_secret, {
function run(msg, matches)
if consumer_key:isempty() then
return "Twitter Consumer Key is empty, write it in plugins/twitter.lua"
end
if consumer_secret:isempty() then
return "Twitter Consumer Secret is empty, write it in plugins/twitter.lua"
end
if access_token:isempty() then
return "Twitter Access Token is empty, write it in plugins/twitter.lua"
end
if access_token_secret:isempty() then
return "Twitter Access Token Secret is empty, write it in plugins/twitter.lua"
end
local twitter_url = "https://api.twitter.com/1.1/statuses/show/" .. matches[1] .. ".json"
print(twitter_url)
local response_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url)
print(response_body)
local response = json:decode(response_body)
print("response = ", response)
local header = "Tweet from " .. response.user.name .. " (@" .. response.user.screen_name .. ")\n"
local text = response.text
@ -65,6 +72,4 @@ return {
usage = "",
patterns = {"https://twitter.com/[^/]+/status/([0-9]+)"},
run = run
}
}

View File

@ -1,31 +1,45 @@
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",
AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"},
AccessToken = "https://api.twitter.com/oauth/access_token"
}, {
}, {
OAuthToken = access_token,
OAuthTokenSecret = access_token_secret
})
function run(msg, matches)
if consumer_key:isempty() then
return "Twitter Consumer Key is empty, write it in plugins/twitter.lua"
end
if consumer_secret:isempty() then
return "Twitter Consumer Secret is empty, write it in plugins/twitter.lua"
end
if access_token:isempty() then
return "Twitter Access Token is empty, write it in plugins/twitter.lua"
end
if access_token_secret:isempty() then
return "Twitter Access Token Secret is empty, write it in plugins/twitter.lua"
end
if not is_sudo(msg) then
return "You aren't allowed to send tweets"
end
local response_code, response_headers, response_status_line, response_body =
local response_code, response_headers, response_status_line, response_body =
client:PerformRequest("POST", "https://api.twitter.com/1.1/statuses/update.json", {
status = matches[1]
})
status = matches[1]
})
if response_code ~= 200 then
return "Error: "..response_code
return "Error: "..response_code
end
return "Tweet sended"
return "Tweet sended"
end
return {