This repository has been archived on 2021-04-24. You can view files and clone it, but cannot push or open issues or pull requests.
Mikubot/bot/utils.lua

849 lines
23 KiB
Lua
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

http = require("socket.http")
https = require("ssl.https")
ltn12 = require "ltn12"
URL = require("socket.url")
feedparser = require ("feedparser")
json = (loadfile "./libs/JSON.lua")()
serpent = (loadfile "./libs/serpent.lua")()
mimetype = (loadfile "./libs/mimetype.lua")()
redis = (loadfile "./libs/redis.lua")()
http.TIMEOUT = 5
function get_receiver(msg)
if msg.to.type == 'user' then
return 'user#id'..msg.from.id
end
if msg.to.type == 'chat' then
return 'chat#id'..msg.to.id
end
if msg.to.type == 'encr_chat' then
return msg.to.print_name
end
end
function is_chat_msg( msg )
if msg.to.type == 'chat' then
return true
end
return false
end
function string.random(length)
local str = "";
for i = 1, length do
math.random(97, 122)
str = str..string.char(math.random(97, 122));
end
return str;
end
function string:split(sep)
local sep, fields = sep or ":", {}
local pattern = string.format("([^%s]+)", sep)
self:gsub(pattern, function(c) fields[#fields+1] = c end)
return fields
end
-- Removes spaces
function string.trim(s)
return s:gsub("^%s*(.-)%s*$", "%1")
end
function get_http_file_name(url, headers)
-- Eg: foo.var
local file_name = url:match("[^%w]+([%.%w]+)$")
-- Any delimited alphanumeric on the url
file_name = file_name or url:match("[^%w]+(%w+)[^%w]+$")
-- Random name, hope content-type works
file_name = file_name or str:random(5)
local content_type = headers["content-type"]
local extension = nil
if content_type then
extension = mimetype.get_mime_extension(content_type)
end
if extension then
file_name = file_name.."."..extension
end
local disposition = headers["content-disposition"]
if disposition then
-- attachment; filename=CodeCogsEqn.png
file_name = disposition:match('filename=([^;]+)') or file_name
file_name = string.gsub(file_name, "\"", "")
end
return file_name
end
-- Saves file to tmp/. If file_name isn't provided,
-- will get the text after the last "#" for filename
-- and content-type for extension
function download_to_file(url, file_name)
print("Download URL: "..url)
local respbody = {}
local options = {
url = url,
sink = ltn12.sink.table(respbody),
redirect = true
}
-- nil, code, headers, status
local response = nil
if url:starts('https') then
options.redirect = false
response = {https.request(options)}
else
response = {http.request(options)}
end
local code = response[2]
local headers = response[3]
local status = response[4]
if code ~= 200 then return nil end
file_name = file_name or get_http_file_name(url, headers)
local file_path = "tmp/"..file_name
print("Gespeichert in: "..file_path)
file = io.open(file_path, "w+")
file:write(table.concat(respbody))
file:close()
return file_path
end
function vardump(value)
print(serpent.block(value, {comment=false}))
end
-- taken from http://stackoverflow.com/a/11130774/3163199
function scandir(directory)
local i, t, popen = 0, {}, io.popen
for filename in popen('ls -a "'..directory..'"'):lines() do
i = i + 1
t[i] = filename
end
return t
end
-- http://www.lua.org/manual/5.2/manual.html#pdf-io.popen
function run_command(str)
local cmd = io.popen(str)
local result = cmd:read('*all')
cmd:close()
return result
end
function is_sudo(msg)
local var = false
-- Check if user id is in sudoers table
for v,user in pairs(sudo_users) do
if string.match(user, msg.from.id) then
var = true
end
end
return var
end
function can_use_bot(msg)
local var = false
-- Check users id in config
for v,user in pairs(_config.can_use_bot) do
if user == msg.from.id then
var = true
end
end
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.
-- Set uglify true to minify the file.
function serialize_to_file(data, file, uglify)
file = io.open(file, 'w+')
local serialized
if not uglify then
serialized = serpent.block(data, {
comment = false,
name = '_'
})
else
serialized = serpent.dump(data)
end
file:write(serialized)
file:close()
end
-- Returns true if the string is empty
function string:isempty()
return self == nil or self == ''
end
-- Returns true if the string is blank
function string:isblank()
self = self:trim()
return self:isempty()
end
-- DEPRECATED!!!!!
function string.starts(String, Start)
print("string.starts(String, Start) is DEPRECATED use string:starts(text) instead")
return Start == string.sub(String,1,string.len(Start))
end
-- Returns true if String starts with Start
function string:starts(text)
return text == string.sub(self,1,string.len(text))
end
-- Send image to user and delete it when finished.
-- cb_function and cb_extra are optionals callback
function _send_photo(receiver, file_path, cb_function, cb_extra)
local cb_extra = {
file_path = file_path,
cb_function = cb_function,
cb_extra = cb_extra
}
-- Call to remove with optional callback
send_photo(receiver, file_path, cb_function, cb_extra)
end
-- Download the image and send to receiver, it will be deleted.
-- cb_function and cb_extra are optionals callback
function send_photo_from_url(receiver, url, cb_function, cb_extra, sendNotErrMsg)
-- If callback not provided
cb_function = cb_function or ok_cb
cb_extra = cb_extra or false
local file_path = download_to_file(url, false)
if not file_path then -- Error
if sendNotErrMsg then
return false
else
local text = 'Fehler beim Laden des Bildes'
send_msg(receiver, text, cb_function, cb_extra)
end
else
print("Datei Pfad: "..file_path)
_send_photo(receiver, file_path, cb_function, cb_extra)
return true
end
end
-- Same as send_photo_from_url but as callback function
function send_photo_from_url_callback(cb_extra, success, result, sendErrMsg)
local receiver = cb_extra.receiver
local url = cb_extra.url
local file_path = download_to_file(url, false)
if not file_path then -- Error
local text = 'Fehler beim Herunterladen des Bildes'
send_msg(receiver, text, ok_cb, false)
else
print("Datei Pfad: "..file_path)
_send_photo(receiver, file_path, ok_cb, false)
end
end
-- Same as above, but with send_as_document
function send_document_from_url_callback(cb_extra, success, result)
local receiver = cb_extra.receiver
local url = cb_extra.url
local file_path = download_to_file(url, false)
if not file_path then -- Error
local text = 'Fehler beim Herunterladen des Dokumentes'
send_msg(receiver, text, ok_cb, false)
else
print("File path: "..file_path)
_send_document(receiver, file_path, ok_cb, false)
end
end
-- Send multiple images asynchronous.
-- param urls must be a table.
function send_photos_from_url(receiver, urls)
local cb_extra = {
receiver = receiver,
urls = urls,
remove_path = nil
}
send_photos_from_url_callback(cb_extra)
end
-- Use send_photos_from_url.
-- This function might be difficult to understand.
function send_photos_from_url_callback(cb_extra, success, result)
-- cb_extra is a table containing receiver, urls and remove_path
local receiver = cb_extra.receiver
local urls = cb_extra.urls
local remove_path = cb_extra.remove_path
-- The previously image to remove
if remove_path ~= nil then
os.remove(remove_path)
print(remove_path.." gelöscht!")
end
-- Nil or empty, exit case (no more urls)
if urls == nil or #urls == 0 then
return false
end
-- Take the head and remove from urls table
local head = table.remove(urls, 1)
local file_path = download_to_file(head, false)
local cb_extra = {
receiver = receiver,
urls = urls,
remove_path = file_path
}
-- Send first and postpone the others as callback
send_photo(receiver, file_path, send_photos_from_url_callback, cb_extra)
end
-- Callback to remove a file
function rmtmp_cb(cb_extra, success, result)
local file_path = cb_extra.file_path
local cb_function = cb_extra.cb_function or ok_cb
local cb_extra = cb_extra.cb_extra
if file_path ~= nil then
os.remove(file_path)
print(file_path.." gelöscht!")
end
-- Finally call the callback
cb_function(cb_extra, success, result)
end
-- Send document to user and delete it when finished.
-- cb_function and cb_extra are optionals callback
function _send_document(receiver, file_path, cb_function, cb_extra)
local cb_extra = {
file_path = file_path,
cb_function = cb_function or ok_cb,
cb_extra = cb_extra or false
}
-- Call to remove with optional callback
send_document(receiver, file_path, rmtmp_cb, cb_extra)
end
-- Download the image and send to receiver, it will be deleted.
-- cb_function and cb_extra are optionals callback
function send_document_from_url(receiver, url, cb_function, cb_extra, sendNotErrMsg)
-- If callback not provided
cb_function = cb_function or ok_cb
cb_extra = cb_extra or false
local file_path = download_to_file(url, false)
if not file_path then -- Error
if sendNotErrMsg then
return false
else
local text = 'Fehler beim Herunterladen des Dokumentes'
send_msg(receiver, text, cb_function, cb_extra)
end
else
print("Datei Pfad: "..file_path)
_send_document(receiver, file_path, cb_function, cb_extra)
return true
end
end
-- Parameters in ?a=1&b=2 style
function format_http_params(params, is_get)
local str = ''
-- If is get add ? to the beginning
if is_get then str = '?' end
local first = true -- Frist param
for k,v in pairs (params) do
if v then -- nil value
if first then
first = false
str = str..k.. "="..v
else
str = str.."&"..k.. "="..v
end
end
end
return str
end
-- Check if user can use the plugin and warns user
-- Returns true if user was warned and false if not warned (is allowed)
function warns_user_not_allowed(plugin, msg)
if not user_allowed(plugin, msg) then
local text = 'Das darf nur mein Meister!'
local receiver = get_receiver(msg)
send_msg(receiver, text, ok_cb, false)
return true
else
return false
end
end
-- Check if user can use the plugin
function user_allowed(plugin, msg)
if plugin.privileged and not is_sudo(msg) then
return false
end
return true
end
function send_order_msg(destination, msgs)
local cb_extra = {
destination = destination,
msgs = msgs
}
send_order_msg_callback(cb_extra, true)
end
function send_large_msg(destination, text)
local cb_extra = {
destination = destination,
text = text
}
send_large_msg_callback(cb_extra, true)
end
-- If text is longer than 4096 chars, send multiple msg.
-- https://core.telegram.org/method/messages.sendMessage
function send_large_msg_callback(cb_extra, success, result)
local text_max = 4096
local destination = cb_extra.destination
local text = cb_extra.text
local text_len = string.len(text)
local num_msg = math.ceil(text_len / text_max)
if num_msg <= 1 then
send_msg(destination, text, ok_cb, false)
else
local my_text = string.sub(text, 1, 4096)
local rest = string.sub(text, 4096, text_len)
local cb_extra = {
destination = destination,
text = rest
}
send_msg(destination, my_text, send_large_msg_callback, cb_extra)
end
end
-- Returns a table with matches or nil
--function match_pattern(pattern, text, lower_case)
function match_pattern(pattern, text)
if text then
local matches = { string.match(text, pattern) }
if next(matches) then
return matches
end
end
-- nil
end
function sleep(n)
os.execute("sleep " .. tonumber(n))
end
-- Function to read data from files
function load_from_file(file, default_data)
local f = io.open(file, "r+")
-- If file doesn't exists
if f == nil then
-- Create a new empty table
default_data = default_data or {}
serialize_to_file(default_data, file)
print ('Erstelle Datei', file)
else
print ('Daten geladen von', file)
f:close()
end
return loadfile (file)()
end
function run_bash(str)
local cmd = io.popen(str)
local result = cmd:read('*all')
cmd:close()
return result
end
function run_sh(msg)
name = get_name(msg)
text = ''
bash = msg.text:sub(4,-1)
text = run_bash(bash)
return text
end
function round(num, idp)
if idp and idp>0 then
local mult = 10^idp
return math.floor(num * mult + 0.5) / mult
end
return math.floor(num + 0.5)
end
function unescape(str)
-- Character encoding
str = string.gsub(str, "&acute;", "´")
str = string.gsub(str, "&bull;", "")
str = string.gsub(str, "&gt;", ">")
str = string.gsub(str, "&hellip;", "")
str = string.gsub(str, "&lt;", "<")
str = string.gsub(str, "&mdash;", "")
str = string.gsub(str, "&nabla;", "")
str = string.gsub(str, "&nbsp;", " ")
str = string.gsub(str, "&ndash;", "")
str = string.gsub(str, "&Psi;", "ψ")
str = string.gsub(str, "&psi;", "ψ")
str = string.gsub(str, "&quot;", '"')
str = string.gsub(str, "&raquo;", "»")
str = string.gsub(str, "&reg;", "®")
str = string.gsub(str, "&szlig;", "ß")
str = string.gsub(str, "&trade;", "")
str = string.gsub(str, "&#038;", "&")
str = string.gsub(str, "&#039;", "'")
str = string.gsub(str, "&#34;", '"')
str = string.gsub(str, "&#39;", "'")
str = string.gsub(str, "&#124;", "|")
str = string.gsub(str, "&#160;", " ")
str = string.gsub(str, "&#174;", "®")
str = string.gsub(str, "&#187;", "»")
str = string.gsub(str, "&#223;", "ß")
str = string.gsub(str, "&#8211;", "")
str = string.gsub(str, "&#8217;", "'")
str = string.gsub(str, "&#8220;", "")
str = string.gsub(str, "&#8221;", "")
str = string.gsub(str, "&#8222;", "")
str = string.gsub(str, "&#8230;", "")
str = string.gsub(str, "&#8249;", "")
str = string.gsub(str, "&#8364;", "")
str = string.gsub(str, "&#9834;", "")
str = string.gsub(str, "<EFBFBD>", "")
-- Ä Ö Ü
str = string.gsub(str, "&auml;", "ä")
str = string.gsub(str, "&Auml;", "Ä")
str = string.gsub(str, "&#228;", "ä")
str = string.gsub(str, "&#196;", "Ä")
str = string.gsub(str, "&ouml;", "ö")
str = string.gsub(str, "&Ouml;", "Ö")
str = string.gsub(str, "&#246;", "ö")
str = string.gsub(str, "&#214;", "Ö")
str = string.gsub(str, "&uuml;", "ü")
str = string.gsub(str, "&Uuml;", "Ü")
str = string.gsub(str, "&#252;", "ü")
str = string.gsub(str, "&#220;", "Ü")
--str = string.gsub( str, '&#(%d+);', function(n) return string.char(n) end )
--str = string.gsub( str, '&#x(%d+);', function(n) return string.char(tonumber(n,16)) end )
str = string.gsub( str, '&amp;', '&' ) -- Be sure to do this after all others
return str
end
function gerRating(str)
str = string.gsub(str, "de/0", "FSK0")
str = string.gsub(str, "TV%-G", "FSK0")
str = string.gsub(str, "TV%-Y$", "FSK0")
str = string.gsub(str, "G %- All Ages", "FSK0")
str = string.gsub(str, "de/6", "FSK6")
str = string.gsub(str, "TV%-Y7", "FSK6")
str = string.gsub(str, "TV%-PG", "FSK6")
str = string.gsub(str, "PG %- Children", "FSK6")
str = string.gsub(str, "de/12", "FSK12")
str = string.gsub(str, "de/16", "FSK16")
str = string.gsub(str, "TV%-14", "FSK16")
str = string.gsub(str, "PG%-13 %- Teens 13 or older", "FSK16")
str = string.gsub(str, "de/18", "FSK18")
str = string.gsub(str, "TV%-MA", "FSK18")
str = string.gsub(str, "R %- 17%+ %(violence & profanity%)", "FSK18")
str = string.gsub(str, "R%+ %- Mild Nudity", "FSK18")
str = string.gsub(str, "Rx %- Hentai", "FSK18")
return str
end
function convertNumbers(str)
str = string.gsub(str, "^1$", "01")
str = string.gsub(str, "^2$", "02")
str = string.gsub(str, "^3$", "03")
str = string.gsub(str, "^4$", "04")
str = string.gsub(str, "^5$", "05")
str = string.gsub(str, "^6$", "06")
str = string.gsub(str, "^7$", "07")
str = string.gsub(str, "^8$", "08")
str = string.gsub(str, "^9$", "09")
return str
end
-- See http://stackoverflow.com/a/14899740
function unescape_html(str)
local map = {
["lt"] = "<",
["gt"] = ">",
["amp"] = "&",
["quot"] = '"',
["apos"] = "'"
}
new = string.gsub(str, '(&(#?x?)([%d%a]+);)', function(orig, n, s)
var = map[s] or n == "#" and string.char(s)
var = var or n == "#x" and string.char(tonumber(s,16))
var = var or orig
return var
end)
return new
end
function post_petition(url, arguments)
local url, h = string.gsub(url, "http://", "")
local url, hs = string.gsub(url, "https://", "")
local post_prot = "http"
if hs == 1 then
post_prot = "https"
end
local response_body = {}
local request_constructor = {
url = post_prot..'://'..url,
method = "POST",
sink = ltn12.sink.table(response_body),
headers = {},
redirect = false
}
local source = arguments
if type(arguments) == "table" then
local source = helpers.url_encode_arguments(arguments)
end
request_constructor.headers["Content-Type"] = "application/x-www-form-urlencoded"
request_constructor.headers["Content-Length"] = tostring(#source)
request_constructor.source = ltn12.source.string(source)
if post_prot == "http" then
ok, response_code, response_headers, response_status_line = http.request(request_constructor)
else
ok, response_code, response_headers, response_status_line = https.request(request_constructor)
end
if not ok then
return nil
end
response_body = json:decode(table.concat(response_body))
return response_body
end
function get_redis_hash(msg, var)
if msg.to.type == 'chat' then
return 'chat:'..msg.to.id..':'..var
end
if msg.to.type == 'user' then
return 'user:'..msg.from.id..':'..var
end
end
function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
function comma_value(amount)
local formatted = amount
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1.%2')
if (k==0) then
break
end
end
return formatted
end
function string.ends(str, fin)
return fin=='' or string.sub(str,-string.len(fin)) == fin
end
function get_location(user_id)
local hash = 'user:'..user_id
local set_location = redis:hget(hash, 'location')
if set_location == 'false' or set_location == nil then
return false
else
return set_location
end
end
function cache_data(plugin, query, data, timeout, typ)
-- How to: cache_data(pluginname, query_name, data_to_cache, expire_in_seconds)
if not timeout then timeout = 86400 end
local hash = 'telegram:cache:'..plugin..':'..query
print('Caching "'..query..'" from plugin '..plugin..' (expires in '..timeout..' seconds)')
if typ == 'key' then
redis:set(hash, data)
elseif typ == 'set' then
-- make sure that you convert your data into a table:
-- {"foo", "bar", "baz"} instead of
-- {"bar" = "foo", "foo" = "bar", "bar" = "baz"}
-- because other formats are not supported by redis (or I haven't found a way to store them)
for _,str in pairs(data) do
redis:sadd(hash, str)
end
else
redis:hmset(hash, data)
end
redis:expire(hash, timeout)
end
--[[
Ordered table iterator, allow to iterate on the natural order of the keys of a
table.
-- http://lua-users.org/wiki/SortedIteration
]]
function __genOrderedIndex( t )
local orderedIndex = {}
for key in pairs(t) do
table.insert( orderedIndex, key )
end
table.sort( orderedIndex )
return orderedIndex
end
function orderedNext(t, state)
-- Equivalent of the next function, but returns the keys in the alphabetic
-- order. We use a temporary ordered key table that is stored in the
-- table being iterated.
key = nil
--print("orderedNext: state = "..tostring(state) )
if state == nil then
-- the first time, generate the index
t.__orderedIndex = __genOrderedIndex( t )
key = t.__orderedIndex[1]
else
-- fetch the next value
for i = 1,table.getn(t.__orderedIndex) do
if t.__orderedIndex[i] == state then
key = t.__orderedIndex[i+1]
end
end
end
if key then
return key, t[key]
end
-- no more value to return, cleanup
t.__orderedIndex = nil
return
end
function orderedPairs(t)
-- Equivalent of the pairs() function on tables. Allows to iterate
-- in order
return orderedNext, t, nil
end
-- converts total amount of seconds (e.g. 65 seconds) to human redable time (e.g. 1:05 minutes)
function makeHumanTime(totalseconds)
local seconds = totalseconds % 60
local minutes = math.floor(totalseconds / 60)
local minutes = minutes % 60
local hours = math.floor(totalseconds / 3600)
if minutes == 00 and hours == 00 then
return seconds..' Sekunden'
elseif hours == 00 and minutes ~= 00 then
return string.format("%02d:%02d", minutes, seconds)..' Minuten'
elseif hours ~= 00 then
return string.format("%02d:%02d:%02d", hours, minutes, seconds)..' Stunden'
end
end
function formatMilliseconds(milliseconds)
local totalseconds = math.floor( milliseconds / 1000 )
milliseconds = milliseconds % 1000
local seconds = totalseconds % 60
local minutes = math.floor( totalseconds / 60 )
local hours = math.floor( minutes / 60 )
local days = math.floor( hours / 24 )
minutes = minutes % 60
hours = hours % 24
--return string.format( "%03d:%02d:%02d:%02d:%03d", days, hours, minutes, seconds, milliseconds )
if minutes == 00 and hours == 00 then
return seconds..' Sekunden'
elseif hours == 00 and minutes ~= 00 then
return string.format("%02d:%02d", minutes, seconds)..' Minuten'
elseif hours ~= 00 then
return string.format("%02d:%02d:%02d", hours, minutes, seconds)..' Stunden'
end
end
function is_blacklisted(msg)
_blacklist = redis:smembers("telegram:img_blacklist")
local var = false
for v,word in pairs(_blacklist) do
if string.find(string.lower(msg), string.lower(word)) then
print("Wort steht auf der Blacklist!")
var = true
break
end
end
return var
end
function convert_timestamp(timestamp, format)
local converted_date = run_command('date -d @'..timestamp..' +'..format)
local converted_date = string.gsub(converted_date, '%\n', '')
return converted_date
end
function pretty_float(x)
if x % 1 == 0 then
return tostring(math.floor(x))
else
return tostring(x)
end
end