Übernehme Änderungen von Brawl345/Brawlbot-v2

Hoffentlich ._.
This commit is contained in:
2016-08-15 23:14:28 +02:00
parent 7a98601d3e
commit dbf323a02a
54 changed files with 1512 additions and 865 deletions

View File

@ -3,7 +3,6 @@
otouto's bindings for the Telegram bot API.
https://core.telegram.org/bots/api
Copyright 2016 topkecleon. Published under the AGPLv3.
See the "Bindings" section of README.md for usage information.
]]--
@ -48,7 +47,7 @@ function bindings:request(method, parameters, file)
end
local response = {}
local body, boundary = MP_ENCODE(parameters)
local success = HTTPS.request{
local success, code = HTTPS.request{
url = self.BASE_URL .. method,
method = 'POST',
headers = {
@ -60,7 +59,7 @@ function bindings:request(method, parameters, file)
}
local data = table.concat(response)
if not success then
print(method .. ': Connection error.')
print(method .. ': Connection error. [' .. code .. ']')
return false, false
else
local result = JSON.decode(data)
@ -82,4 +81,4 @@ function bindings.gen(_, key)
end
setmetatable(bindings, { __index = bindings.gen })
return bindings
return bindings

View File

@ -3,20 +3,20 @@ local bot = {}
bindings = require('miku.bindings')
utilities = require('miku.utilities')
bot.version = '160801'
bot.version = '160815'
function bot:init(config) -- The function run when the bot is started or reloaded.
cred_data = load_cred()
assert(
config.bot_api_key and config.bot_api_key ~= '',
config.bot_api_key,
'You did not set your bot token in the config!'
)
self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/'
-- Fetch bot information. Try until it succeeds.
repeat
print('Fetching bot information...')
print('Sammel Bot-Informationen...')
self.info = bindings.getMe(self)
until self.info
self.info = self.info.result
@ -26,33 +26,23 @@ function bot:init(config) -- The function run when the bot is started or reloade
self.database = utilities.load_data(self.info.username..'.db')
end
-- Table to cache user info (usernames, IDs, etc).
self.database.users = self.database.users or {}
-- Table to store userdata (nicknames, lastfm usernames, etc).
self.database.userdata = self.database.userdata or {}
-- Save the bot's version in the database to make migration simpler.
self.database.version = bot.version
-- Add updated bot info to the user info cache.
self.database.users = self.database.users or {} -- Table to cache userdata.
self.database.users[tostring(self.info.id)] = self.info
self.plugins = {} -- Load plugins.
enabled_plugins = load_plugins()
for k,v in pairs(enabled_plugins) do
local p = require('miku.plugins.'..v)
-- print('loading plugin',v)
table.insert(self.plugins, p)
self.plugins[k] = p
self.plugins[k].name = v
if p.init then p.init(self, config) end
end
print('Bot wurde erfolgreich gestartet!\n@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')')
self.last_update = self.last_update or 0 -- Set loop variables: Update offset,
self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job,
self.last_database_save = self.last_database_save or os.date('%H') -- the time of the last database save,
-- Set loop variables
self.last_update = self.last_update or 0 -- Update offset.
self.last_cron = self.last_cron or os.date('%M') -- Last cron job.
self.last_database_save = self.last_database_save or os.date('%H') -- Last db save.
self.is_started = true -- and whether or not the bot should be running.
end
function bot:on_msg_receive(msg, config) -- The fn run whenever a message is received.
@ -62,18 +52,6 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec
if msg.date < os.time() - 5 then return end -- Do not process old messages.
-- Cache user info for those involved.
self.database.users[tostring(msg.from.id)] = msg.from
if msg.reply_to_message then
self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from
elseif msg.forward_from then
self.database.users[tostring(msg.forward_from.id)] = msg.forward_from
elseif msg.new_chat_member then
self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member
elseif msg.left_chat_member then
self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member
end
msg = utilities.enrich_message(msg)
if msg.reply_to_message then
@ -92,11 +70,12 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec
msg.text_lower = msg.text:lower()
end
msg = pre_process_msg(self, msg, config)
if not msg then return end -- deleted by banning
if is_service_msg(msg) then
msg = service_modify_msg(msg)
end
for _, plugin in ipairs(self.plugins) do
match_plugins(self, msg, config, plugin)
end
@ -115,6 +94,41 @@ function bot:on_callback_receive(callback, msg, config) -- whenever a new callba
if not callback.data:find(':') or not callback.data:find('@'..self.info.username..' ') then
return
end
-- Check if user is blocked
local user_id = callback.from.id
local chat_id = msg.chat.id
if redis:get('blocked:'..user_id) then
utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true)
return
end
-- Check if user is banned
local banned = redis:get('banned:'..chat_id..':'..user_id)
if banned then
utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true)
return
end
-- Check if whitelist is enabled and user/chat is whitelisted
local whitelist = redis:get('whitelist:enabled')
if whitelist and not is_sudo(msg, config) then
local hash = 'whitelist:user#id'..user_id
local allowed = redis:get(hash) or false
if not allowed then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
local allowed = redis:get('whitelist:chat#id'.. chat_id)
if not allowed then
utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true)
return
end
else
utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true)
return
end
end
end
callback.data = string.gsub(callback.data, '@'..self.info.username..' ', "")
local called_plugin = callback.data:match('(.*):.*')
local param = callback.data:sub(callback.data:find(':')+1)
@ -123,7 +137,8 @@ function bot:on_callback_receive(callback, msg, config) -- whenever a new callba
msg = utilities.enrich_message(msg)
for _, plugin in ipairs(self.plugins) do
for n=1, #self.plugins do
local plugin = self.plugins[n]
if plugin.name == called_plugin then
if is_plugin_disabled_on_chat(plugin.name, msg) then return end
plugin:callback(callback, msg, self, config, param)
@ -135,27 +150,44 @@ end
function bot:process_inline_query(inline_query, config) -- When an inline query is received
-- remove comment to enable debugging
-- vardump(inline_query)
-- PLEASE READ: Blocking every single InlineQuery IS NOT POSSIBLE!
-- When the request is cached, the user can still send this query
-- but he WON'T be able to make new requests.
local user_id = inline_query.from.id
if redis:get('blocked:'..user_id) then
utilities.answer_inline_query(self, inline_query, nil, 0, true)
return
end
if not config.enable_inline_for_everyone then
local is_whitelisted = redis:get('whitelist:user#id'..inline_query.from.id)
if not is_whitelisted then return end
if not is_whitelisted then utilities.answer_inline_query(self, inline_query, nil, 0, true) return end
end
if inline_query.query:match('"') then
inline_query.query = inline_query.query:gsub('"', '\\"')
end
for _, plugin in ipairs(self.plugins) do
for n=1, #self.plugins do
local plugin = self.plugins[n]
match_inline_plugins(self, inline_query, config, plugin)
end
-- Stop the spinning circle
utilities.answer_inline_query(self, inline_query, nil, 0, true)
end
function bot:run(config)
bot.init(self, config) -- Actually start the script.
bot.init(self, config)
while self.is_started do -- Start a loop while the bot should be running.
local res = bindings.getUpdates(self, { timeout=20, offset = self.last_update+1 } )
while self.is_started do
-- Update loop
local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update+1 } )
if res then
for _,v in ipairs(res.result) do -- Go through every new message.
-- Iterate over every new message.
for n=1, #res.result do
local v = res.result[n]
self.last_update = v.update_id
if v.inline_query then
bot.process_inline_query(self, v.inline_query, config)
@ -169,10 +201,12 @@ function bot:run(config)
print('Connection error while fetching updates.')
end
if self.last_cron ~= os.date('%M') then -- Run cron jobs every minute.
-- Run cron jobs every minute.
if self.last_cron ~= os.date('%M') then
self.last_cron = os.date('%M')
utilities.save_data(self.info.username..'.db', self.database) -- Save the database.
for i,v in ipairs(self.plugins) do
for n=1, #self.plugins do
local v = self.plugins[n]
if v.cron then -- Call each plugin's cron function, if it has one.
local result, err = pcall(function() v.cron(self, config) end)
if not result then
@ -194,7 +228,8 @@ end
-- Apply plugin.pre_process function
function pre_process_msg(self, msg, config)
for _,plugin in ipairs(self.plugins) do
for n=1, #self.plugins do
local plugin = self.plugins[n]
if plugin.pre_process and msg then
-- print('Preprocess '..plugin.name) -- remove comment to restore old behaviour
new_msg = plugin:pre_process(msg, self, config)
@ -204,7 +239,9 @@ function pre_process_msg(self, msg, config)
end
function match_inline_plugins(self, inline_query, config, plugin)
for _, trigger in ipairs(plugin.inline_triggers or {}) do
local match_table = plugin.inline_triggers or {}
for n=1, #match_table do
local trigger = plugin.inline_triggers[n]
if string.match(string.lower(inline_query.query), trigger) then
local success, result = pcall(function()
for k, pattern in pairs(plugin.inline_triggers) do
@ -216,50 +253,33 @@ function match_inline_plugins(self, inline_query, config, plugin)
print('Inline: '..plugin.name..' ausgelöst')
return plugin.inline_callback(self, inline_query, config, matches)
end)
if not success then
print(result)
end
end
end
end
function match_plugins(self, msg, config, plugin)
for _, trigger in ipairs(plugin.triggers or {}) do
local match_table = plugin.triggers or {}
for n=1, #match_table do
local trigger = plugin.triggers[n]
if string.match(msg.text_lower, trigger) then
-- Check if Plugin is disabled
if is_plugin_disabled_on_chat(plugin.name, msg) then return end
local success, result = pcall(function()
-- trying to port matches to miku
for k, pattern in pairs(plugin.triggers) do
matches = match_pattern(pattern, msg.text)
if matches then
break;
end
local pattern = plugin.triggers[n]
local matches = match_pattern(pattern, msg.text)
if matches then
print('msg matches: ', pattern, ' for "'..plugin.name..'"')
return plugin.action(self, msg, config, matches)
end
print(plugin.name..' ausgelöst')
return plugin.action(self, msg, config, matches)
end)
if not success then
-- If the plugin has an error message, send it. If it does
-- not, use the generic one specified in config. If it's set
-- to false, do nothing.
if plugin.error then
utilities.send_reply(self, msg, plugin.error)
elseif plugin.error == nil then
utilities.send_reply(self, msg, config.errors.generic, true)
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
return
end
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
-- if one pattern matches, end
return
end
-- If the action returns a table, make that table the new msg.
if type(result) == 'table' then
msg = result
-- If the action returns true, continue.
elseif result ~= true then
return
end
end
end
end
@ -294,7 +314,7 @@ function create_plugin_set()
'banhammer',
'channels',
'plugins',
'help',
'help'
}
print ('Aktiviere Plugins und speicher in telegram:enabled_plugins')
for _,plugin in pairs(enabled_plugins) do

View File

@ -31,13 +31,15 @@ end
function ninegag:inline_callback(inline_query, config)
local res, code = http.request(url)
if code ~= 200 then return end
if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end
local gag = json.decode(res)
local results = '['
local id = 50
for n in pairs(gag) do
local title = gag[n].title:gsub('"', '\\"')
results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..gag[n].src..'","thumb_url":"'..gag[n].src..'","caption":"'..title..'","reply_markup":{"inline_keyboard":[[{"text":"9GAG aufrufen","url":"'..gag[n].url..'"}]]}}'
results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..gag[n].src..'","thumb_url":"'..gag[n].src..'","caption":"'..title..'","reply_markup":{"inline_keyboard":[[{"text":"9GAG aufrufen","url":"'..gag[n].url..'"}]]}}'
id = id+1
if n < #gag then
results = results..','
end
@ -58,4 +60,4 @@ function ninegag:action(msg, config)
utilities.send_photo(self, msg.chat.id, file, title, msg.message_id, '{"inline_keyboard":[[{"text":"Post aufrufen","url":"'..post_url..'"}]]}')
end
return ninegag
return ninegag

View File

@ -5,33 +5,16 @@ local bot = require('miku.bot')
about.command = 'about'
about.doc = '`Sendet Informationen über den Bot.`'
about.triggers = {
function about:init(config)
about.text = config.about_text .. '\n[Mikudayobot](https://github.com/Akamaru/Mikubot-V2) v'..bot.version..' von @Akamaru, basierend auf [otouto](https://github.com/topkecleon/otouto) von topkecleon.'
about.triggers = {
'/[Aa][Bb][Oo][Uu][Tt]',
'/[Ss][Tt][Aa][Rr][Tt]'
}
function about:action(msg, config)
-- Filthy hack, but here is where we'll stop forwarded messages from hitting
-- other plugins.
-- disabled to restore old behaviour
-- if msg.forward_from then return end
local output = config.about_text .. '\n[Mikudayobot](https://github.com/Akamaru/Mikubot-V2) v'..bot.version..' von @Akamaru, basierend auf [otouto](https://github.com/topkecleon/otouto) von topkecleon.'
if
(msg.new_chat_member and msg.new_chat_member.id == self.info.id)
or msg.text_lower:match('^'..config.cmd_pat..'about$')
or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower()..'$')
or msg.text_lower:match('^'..config.cmd_pat..'start$')
or msg.text_lower:match('^'..config.cmd_pat..'start@'..self.info.username:lower()..'$')
then
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
return true
end
return about
function about:action(msg, config)
utilities.send_message(self, msg.chat.id, about.text, true, nil, true)
end
return about

View File

@ -27,10 +27,10 @@ function adfly:inline_callback(inline_query, config, matches)
url = redis:get(hash)
end
if not url then return end
if url == 'NOTFOUND' then return end
if not url then utilities.answer_inline_query(self, inline_query) return end
if url == 'NOTFOUND' then utilities.answer_inline_query(self, inline_query) return end
local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]'
local results = '[{"type":"article","id":"1","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]'
utilities.answer_inline_query(self, inline_query, results, 3600, true)
end
@ -54,4 +54,4 @@ function adfly:action(msg, config, matches)
end
end
return adfly
return adfly

View File

@ -5,7 +5,7 @@ local afk = {}
function afk:init(config)
afk.triggers = {
"^/([Aa][Ff][Kk])$",
"^/([Aa][Ff][Kk])$",
"^/([Aa][Ff][Kk]) (.*)$"
}
afk.doc = [[*
@ -68,7 +68,7 @@ function afk:pre_process(msg, self)
local user_id = msg.from.id
local chat_id = msg.chat.id
local hash = 'afk:'..chat_id..':'..user_id
local uhash = 'user:'..user_id
if afk:is_offline(hash) then
local afk_text = afk:get_afk_text(hash)
@ -80,18 +80,27 @@ function afk:pre_process(msg, self)
local duration = makeHumanTime(afk_time)
redis:hset(hash, 'afk', false)
local show_afk_keyboard = redis:hget(uhash, 'afk_keyboard')
if afk_text then
redis:hset(hash, 'afk_text', false)
utilities.send_reply(self, msg, user_name..' ist wieder da! (war: <b>'..afk_text..'</b> für '..duration..')', 'HTML', '{"hide_keyboard":true,"selective":true}')
if show_afk_keyboard == 'true' then
utilities.send_reply(self, msg, user_name..' ist wieder da! (war: <b>'..afk_text..'</b> für '..duration..')', 'HTML', '{"hide_keyboard":true,"selective":true}')
else
utilities.send_message(self, chat_id, user_name..' ist wieder da! (war: <b>'..afk_text..'</b> für '..duration..')', true, nil, 'HTML')
end
else
utilities.send_reply(self, msg, user_name..' ist wieder da! (war '..duration..' weg)', nil, '{"hide_keyboard":true,"selective":true}')
if show_afk_keyboard == 'true' then
utilities.send_reply(self, msg, user_name..' ist wieder da! (war '..duration..' weg)', nil, '{"hide_keyboard":true,"selective":true}')
else
utilities.send_message(self, chat_id, user_name..' ist wieder da! (war '..duration..' weg)')
end
end
end
return msg
end
function afk:action(msg)
function afk:action(msg, config, matches)
if msg.chat.type == "private" then
utilities.send_reply(self, msg, "Mir ist's egal, ob du AFK bist.")
return

View File

@ -36,11 +36,13 @@ function bImages:getImages(query)
local results = '['
local id = 300
for n in pairs(images) do
if images[n].encodingFormat == 'jpeg' then -- Inline-Querys MUST use JPEG photos!
local photo_url = images[n].contentUrl
local thumb_url = images[n].thumbnailUrl
results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..photo_url..'","thumb_url":"'..thumb_url..'","photo_width":'..images[n].width..',"photo_height":'..images[n].height..',"reply_markup":{"inline_keyboard":[[{"text":"Bing aufrufen","url":"'..images[n].webSearchUrl..'"},{"text":"Bild öffnen","url":"'..photo_url..'"}]]}},'
results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..photo_url..'","thumb_url":"'..thumb_url..'","photo_width":'..images[n].width..',"photo_height":'..images[n].height..',"reply_markup":{"inline_keyboard":[[{"text":"Bing aufrufen","url":"'..images[n].webSearchUrl..'"},{"text":"Bild öffnen","url":"'..photo_url..'"}]]}},'
id = id+1
end
end
@ -57,7 +59,7 @@ function bImages:inline_callback(inline_query, config, matches)
results = bImages:getImages(query)
end
if not results then return end
if not results then utilities.answer_inline_query(self, inline_query) return end
utilities.answer_inline_query(self, inline_query, results, 3600)
end

View File

@ -12,7 +12,17 @@ function banhammer:init(config)
"^/(whitelist) (delete) (chat)$",
"^/(ban) (user) (%d+)$",
"^/(ban) (delete) (%d+)$",
"^/(kick) (%d+)$"
"^/(block) (user) (%d+)$",
"^/(block) (delete) (%d+)$",
"^/(whitelist)$",
"^/(whitelist) (delete)$",
"^/(ban)$",
"^/(ban) (delete)$",
"^/(block)$",
"^/(block) (delete)$",
"^/(kick) (%d+)$",
"^/(kick)$",
"^/(leave)$"
}
banhammer.doc = [[*
]]..config.cmd_pat..[[whitelist* _<enable>_/_<disable>_: Aktiviert/deaktiviert Whitelist
@ -22,7 +32,11 @@ function banhammer:init(config)
*]]..config.cmd_pat..[[whitelist* delete chat: Lösche ganze Gruppe von der Whitelist
*]]..config.cmd_pat..[[ban* user _<user#id>_: Kicke User vom Chat und kicke ihn, wenn er erneut beitritt
*]]..config.cmd_pat..[[ban* delete _<user#id>_: Entbanne User
*]]..config.cmd_pat..[[kick* _<user#id>_: Kicke User aus dem Chat]]
*]]..config.cmd_pat..[[block* user _<user#id>_: Blocke User vom Bot
*]]..config.cmd_pat..[[block* delete _<user#id>_: Entblocke User
*]]..config.cmd_pat..[[kick* _<user#id>_: Kicke User aus dem Chat
*]]..config.cmd_pat..[[leave*: Bot verlässt die Gruppe
Alternativ kann auch auf die Nachricht des Users geantwortet werden, die Befehle sind dnn die obrigen ohne `user` bzw.`delete`.]]
end
function banhammer:kick_user(user_id, chat_id, self, onlykick)
@ -54,8 +68,8 @@ end
function banhammer:unban_user(user_id, chat_id, self, chat_type)
local hash = 'banned:'..chat_id..':'..user_id
redis:del(hash)
if chat_type == 'supergroup' then -- how can bots be admins anyway?
local request = bindings.request(self, 'unbanChatMember', {
if chat_type == 'supergroup' then
bindings.request(self, 'unbanChatMember', {
chat_id = chat_id,
user_id = user_id
} )
@ -96,76 +110,94 @@ function banhammer:pre_process(msg, self, config)
end
-- BANNED USER TALKING
local user_id = msg.from.id
local chat_id = msg.chat.id
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
local user_id = msg.from.id
local chat_id = msg.chat.id
local banned = banhammer:is_banned(user_id, chat_id)
if banned then
print('Banned user talking!')
banhammer:ban_user(user_id, chat_id, self)
msg.text = ''
return
end
end
-- BLOCKED USER TALKING (block = user can't use bot, but won't be kicked from group)
local hash = 'blocked:'..user_id
local issudo = is_sudo(msg, config)
local blocked = redis:get(hash)
if blocked and not issudo then
print('User '..user_id..' blocked')
return
end
-- WHITELIST
-- WHITELIST
local hash = 'whitelist:enabled'
local whitelist = redis:get(hash)
local issudo = is_sudo(msg, config)
-- Allow all sudo users even if whitelist is allowed
if whitelist and not issudo then
print('Whitelist enabled and not sudo')
-- Check if user or chat is whitelisted
local allowed = banhammer:is_user_whitelisted(msg.from.id)
local has_been_warned = redis:hget('user:'..msg.from.id, 'has_been_warned')
local allowed = banhammer:is_user_whitelisted(user_id)
local has_been_warned = redis:hget('user:'..user_id, 'has_been_warned')
if not allowed then
print('User '..msg.from.id..' not whitelisted')
print('User '..user_id..' not whitelisted')
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
allowed = banhammer:is_chat_whitelisted(msg.chat.id)
allowed = banhammer:is_chat_whitelisted(chat_id)
if not allowed then
print ('Chat '..msg.chat.id..' not whitelisted')
print ('Chat '..chat_id..' not whitelisted')
else
print ('Chat '..msg.chat.id..' whitelisted :)')
print ('Chat '..chat_id..' whitelisted :)')
end
else
if not has_been_warned then
utilities.send_reply(self, msg, "Dies ist ein privater Bot, der erst nach einer Freischaltung benutzt werden kann.\nThis is a private bot, which can only be after an approval.")
redis:hset('user:'..msg.from.id, 'has_been_warned', true)
redis:hset('user:'..user_id, 'has_been_warned', true)
else
print('User has already been warned!')
end
end
else
print('User '..msg.from.id..' allowed :)')
print('User '..user_id..' allowed :)')
end
if not allowed then
msg.text = ''
msg.text_lower = ''
msg.entities = ''
return
end
-- else
-- print('Whitelist not enabled or is sudo')
end
return msg
end
function banhammer:action(msg, config, matches)
if msg.from.id ~= config.admin then
if not is_sudo(msg, config) then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
if matches[1] == 'leave' then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
bindings.request(self, 'leaveChat', {
chat_id = msg.chat.id
} )
return
end
end
if matches[1] == 'ban' then
local user_id = matches[3]
local chat_id = msg.chat.id
if not user_id then
if not msg.reply_to_message then
return
end
user_id = msg.reply_to_message.from.id
end
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
if matches[2] == 'user' then
if matches[2] == 'user' or not matches[2] then
local text = banhammer:ban_user(user_id, chat_id, self)
utilities.send_reply(self, msg, text)
return
@ -183,7 +215,14 @@ function banhammer:action(msg, config, matches)
if matches[1] == 'kick' then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
banhammer:kick_user(matches[2], msg.chat.id, self, true)
local user_id = matches[2]
if not user_id then
if not msg.reply_to_message then
return
end
user_id = msg.reply_to_message.from.id
end
banhammer:kick_user(user_id, msg.chat.id, self, true)
return
else
utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe')
@ -205,6 +244,28 @@ function banhammer:action(msg, config, matches)
utilities.send_reply(self, msg, 'Whitelist deaktiviert')
return
end
if not matches[2] then
if not msg.reply_to_message then
return
end
local user_id = msg.reply_to_message.from.id
local hash = 'whitelist:user#id'..user_id
redis:set(hash, true)
utilities.send_reply(self, msg, 'User '..user_id..' whitelisted')
return
end
if matches[2] == 'delete' and not matches[3] then
if not msg.reply_to_message then
return
end
local user_id = msg.reply_to_message.from.id
local hash = 'whitelist:user#id'..user_id
redis:del(hash)
utilities.send_reply(self, msg, 'User '..user_id..' von der Whitelist entfernt!')
return
end
if matches[2] == 'user' then
local hash = 'whitelist:user#id'..matches[3]
@ -243,6 +304,46 @@ function banhammer:action(msg, config, matches)
return
end
end
end
if matches[1] == 'block' then
if matches[2] == 'user' and matches[3] then
local hash = 'blocked:'..matches[3]
redis:set(hash, true)
utilities.send_reply(self, msg, 'User '..matches[3]..' darf den Bot nun nicht mehr nutzen.')
return
end
if matches[2] == 'delete' and matches[3] then
local hash = 'blocked:'..matches[3]
redis:del(hash)
utilities.send_reply(self, msg, 'User '..matches[3]..' darf den Bot wieder nutzen.')
return
end
if not matches[2] then
if not msg.reply_to_message then
return
end
local user_id = msg.reply_to_message.from.id
local hash = 'blocked:'..user_id
redis:set(hash, true)
utilities.send_reply(self, msg, 'User '..user_id..' darf den Bot nun nicht mehr nutzen.')
return
end
if matches[2] == 'delete' and not matches[3] then
if not msg.reply_to_message then
return
end
local user_id = msg.reply_to_message.from.id
local hash = 'blocked:'..user_id
redis:del(hash)
utilities.send_reply(self, msg, 'User '..user_id..' darf den Bot wieder nutzen.')
return
end
end
end

View File

@ -38,9 +38,9 @@ function bitly:inline_callback(inline_query, config, matches)
url = data.long_url
end
if not url then return end
if not url then utilities.answer_inline_query(self, inline_query) return end
local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]'
local results = '[{"type":"article","id":"2","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]'
utilities.answer_inline_query(self, inline_query, results, 3600)
end
@ -62,4 +62,4 @@ function bitly:action(msg, config, matches)
end
end
return bitly
return bitly

View File

@ -5,7 +5,7 @@ cats.command = 'kitty [gif]'
function cats:init(config)
if not cred_data.cat_apikey then
print('Fehlender Key: cat_apikey.')
print('cats.lua will be enabled, but there are more features with a key.')
print('cats.lua wird aktiviert, aber mit einem Key gibt es mehr Features.')
end
cats.triggers = {
@ -29,8 +29,10 @@ local apikey = cred_data.cat_apikey or "" -- apply for one here: http://thecatap
function cats:inline_callback(inline_query, config, matches)
if matches[1] == 'gif' then
img_type = 'gif'
id = 100
else
img_type = 'jpg'
id = 200
end
local url = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D%27http%3A%2F%2Fthecatapi.com%2Fapi%2Fimages%2Fget%3Fformat%3Dxml%26results_per_page%3D50%26type%3D'..img_type..'%26apikey%3D'..apikey..'%27&format=json' -- no way I'm using XML, plz die
local res, code = https.request(url)
@ -43,9 +45,11 @@ function cats:inline_callback(inline_query, config, matches)
for n in pairs(data) do
if img_type == 'gif' then
results = results..'{"type":"gif","id":"'..math.random(100000000000000000)..'","gif_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}'
results = results..'{"type":"gif","id":"'..id..'","gif_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}'
id = id+1
else
results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}'
results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}'
id = id+1
end
if n < #data then
results = results..','

View File

@ -2,22 +2,30 @@ local cleverbot = {}
function cleverbot:init(config)
cleverbot.triggers = {
"^/[Cc][Bb][Oo][Tt] (.*)$"
"^/[Cc][Bb][Oo][Tt] (.*)$",
"^[Mm][Ii][Kk][Uu][Bb][Oo][Tt], (.+)$",
}
cleverbot.doc = [[*
]]..config.cmd_pat..[[cbot* _<Text>_*: Befragt den Cleverbot]]
cleverbot.url = config.chatter.cleverbot_api
end
cleverbot.command = 'cbot <Text>'
function cleverbot:action(msg, config)
local text = msg.text
local url = "https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text="..URL.escape(text)
local query = https.request(url)
if query == nil then utilities.send_reply(self, msg, 'Ein Fehler ist aufgetreten :(') return end
local decode = json.decode(query)
local answer = string.gsub(decode.clever, "&Auml;", "Ä")
function cleverbot:action(msg, config, matches)
utilities.send_typing(self, msg.chat.id, 'typing')
local text = matches[1]
local query, code = https.request(cleverbot.url..URL.escape(text))
if code ~= 200 then
utilities.send_reply(self, msg, 'Ich möchte jetzt nicht reden...')
return
end
local data = json.decode(query)
if not data.clever then
utilities.send_reply(self, msg, 'Ich möchte jetzt nicht reden...')
return
end
local answer = string.gsub(data.clever, "&Auml;", "Ä")
local answer = string.gsub(answer, "&auml;", "ä")
local answer = string.gsub(answer, "&Ouml;", "Ö")
local answer = string.gsub(answer, "&ouml;", "ö")
@ -27,4 +35,4 @@ function cleverbot:action(msg, config)
utilities.send_reply(self, msg, answer)
end
return cleverbot
return cleverbot

View File

@ -4,57 +4,119 @@ currency.command = 'cash [Menge] <von> <zu>'
function currency:init(config)
currency.triggers = {
"^/[Cc][Aa][Ss][Hh] ([A-Za-z]+)$",
"^/[Cc][Aa][Ss][Hh] ([A-Za-z]+) ([A-Za-z]+)$",
"^/[Cc][Aa][Ss][Hh] (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$",
"^(/[Ee][Uu][Rr])$"
"^/cash ([A-Za-z]+)$",
"^/cash ([A-Za-z]+) ([A-Za-z]+)$",
"^/cash (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$",
"^(/cash)$"
}
currency.inline_triggers = {
"^c ([A-Za-z]+)$",
"^c ([A-Za-z]+) ([A-Za-z]+)$",
"^c (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$"
}
currency.doc = [[*
]]..config.cmd_pat..[[cash* _[Menge]_ _<von>_ _<zu>_
*]]..config.cmd_pat..[[cash* _<von>_: Rechnet in Euro um
*]]..config.cmd_pat..[[cash* _<von>_ _<zu>_: Rechnet mit der Einheit 1
Beispiel: _]]..config.cmd_pat..[[cash 5 USD EUR_]]
end
function currency:action(msg, config)
if not matches[2] then
from = string.upper(matches[1])
to = 'EUR'
local BASE_URL = 'https://api.fixer.io'
function currency:inline_callback(inline_query, config, matches)
if not matches[2] then -- first pattern
base = 'EUR'
to = string.upper(matches[1])
amount = 1
elseif matches[3] then
from = string.upper(matches[2])
elseif matches[3] then -- third pattern
base = string.upper(matches[2])
to = string.upper(matches[3])
amount = matches[1]
else
from = string.upper(matches[1])
else -- second pattern
base = string.upper(matches[1])
to = string.upper(matches[2])
amount = 1
end
local value, iserr = currency:convert_money(base, to, amount)
if iserr then utilities.answer_inline_query(self, inline_query) return end
local output = amount..' '..base..' = *'..value..' '..to..'*'
if tonumber(amount) == 1 then
title = amount..' '..base..' entspricht'
else
title = amount..' '..base..' entsprechen'
end
local results = '[{"type":"article","id":"20","title":"'..title..'","description":"'..value..' '..to..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/currency/cash.jpg","thumb_width":157,"thumb_height":140,"input_message_content":{"message_text":"'..output..'","parse_mode":"Markdown"}}]'
utilities.answer_inline_query(self, inline_query, results, 3600)
end
function currency:convert_money(base, to, amount)
local url = BASE_URL..'/latest?base='..base..'&symbols='..to
local amount = string.gsub(amount, ",", ".")
amount = tonumber(amount)
local result = 1
local BASE_URL = 'https://www.google.com/finance/converter'
local amount = tonumber(amount)
local res, code = https.request(url)
if code ~= 200 and code ~= 422 then
return 'NOCONNECT', true
end
local res, code = https.request(url)
local data = json.decode(res)
if data.error then
return 'WRONGBASE', true
end
local rate = data.rates[to]
if not rate then
return 'WRONGCONVERTRATE', true
end
if amount == 1 then
value = round(rate, 2)
else
value = round(rate * amount, 2)
end
local value = tostring(string.gsub(value, "%.", ","))
return value
end
function currency:action(msg, config, matches)
if matches[1] == '/cash' then
utilities.send_reply(self, msg, currency.doc, true)
return
elseif not matches[2] then -- first pattern
base = 'EUR'
to = string.upper(matches[1])
amount = 1
elseif matches[3] then -- third pattern
base = string.upper(matches[2])
to = string.upper(matches[3])
amount = matches[1]
else -- second pattern
base = string.upper(matches[1])
to = string.upper(matches[2])
amount = 1
end
if from == to then
utilities.send_reply(self, msg, 'Jaja, sehr witzig...')
return
end
local url = BASE_URL..'?from='..from..'&to='..to..'&a='..amount
local str, res = https.request(url)
if res ~= 200 then
local value = currency:convert_money(base, to, amount)
if value == 'NOCONNECT' then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local str = str:match('<span class=bld>(.*) %u+</span>')
if not str then
utilities.send_reply(self, msg, 'Keine gültige Währung - sieh dir die Währungsliste bei [Google Finanzen](https://www.google.com/finance/converter) an.', true)
elseif value == 'WRONGBASE' then
utilities.send_reply(self, msg, 'Keine gültige Basiswährung.')
return
elseif value == 'WRONGCONVERTRATE' then
utilities.send_reply(self, msg, 'Keine gültige Umwandlungswährung.')
return
end
local result = string.format('%.2f', str)
local result = string.gsub(result, "%.", ",")
local amount = tostring(string.gsub(amount, "%.", ","))
local output = amount..' '..from..' = *'..result..' '..to..'*'
local output = amount..' '..base..' = *'..value..' '..to..'*'
utilities.send_reply(self, msg, output, true)
end

View File

@ -18,15 +18,15 @@ function echo:inline_callback(inline_query, config, matches)
-- enable custom markdown button
if text:match('%[.*%]%(.*%)') or text:match('%*.*%*') or text:match('_.*_') or text:match('`.*`') then
results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/custom.jpg","title":"Eigenes Markdown","description":"'..text..'","input_message_content":{"message_text":"'..text..'","parse_mode":"Markdown"}},'
results = results..'{"type":"article","id":"3","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/custom.jpg","title":"Eigenes Markdown","description":"'..text..'","input_message_content":{"message_text":"'..text..'","parse_mode":"Markdown"}},'
end
local results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/fett.jpg","title":"Fett","description":"*'..text..'*","input_message_content":{"message_text":"<b>'..text..'</b>","parse_mode":"HTML"}},{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/kursiv.jpg","title":"Kursiv","description":"_'..text..'_","input_message_content":{"message_text":"<i>'..text..'</i>","parse_mode":"HTML"}},{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/fixedsys.jpg","title":"Feste Breite","description":"`'..text..'`","input_message_content":{"message_text":"<code>'..text..'</code>","parse_mode":"HTML"}}]'
local results = results..'{"type":"article","id":"4","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/fett.jpg","title":"Fett","description":"*'..text..'*","input_message_content":{"message_text":"<b>'..text..'</b>","parse_mode":"HTML"}},{"type":"article","id":"5","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/kursiv.jpg","title":"Kursiv","description":"_'..text..'_","input_message_content":{"message_text":"<i>'..text..'</i>","parse_mode":"HTML"}},{"type":"article","id":"6","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/fixedsys.jpg","title":"Feste Breite","description":"`'..text..'`","input_message_content":{"message_text":"<code>'..text..'</code>","parse_mode":"HTML"}}]'
utilities.answer_inline_query(self, inline_query, results, 0)
end
function echo:action(msg)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true)
else
@ -40,4 +40,4 @@ function echo:action(msg)
end
end
return echo
return echo

View File

@ -26,7 +26,7 @@ function expand:inline_callback(inline_query, config, matches)
description = url
end
local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..title..'","description":"'..description..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]'
local results = '[{"type":"article","id":"7","title":"'..title..'","description":"'..description..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]'
utilities.answer_inline_query(self, inline_query, results, 3600)
end
@ -55,4 +55,4 @@ function expand:action(msg, config, matches)
end
end
return expand
return expand

View File

@ -8,13 +8,13 @@ function facebook:init(config)
end
facebook.triggers = {
'facebook.com/([A-Za-z0-9-._-]+)/(posts)/(%d+)',
'facebook.com/(permalink).php%?(story_fbid)=(%d+)&id=(%d+)',
'facebook.com/(photo).php%?fbid=(%d+)',
'facebook.com/([A-Za-z0-9-._-]+)/(photos)/a.(%d+[%d%.]*)/(%d+)',
'facebook.com/(video).php%?v=(%d+)',
'facebook.com/([A-Za-z0-9-._-]+)/(videos)/(%d+[%d%.]*)',
'facebook.com/([A-Za-z0-9-._-]+)'
"facebook.com/([A-Za-z0-9-._-]+)/(posts)/(%d+)",
"facebook.com/(permalink).php%?(story_fbid)=(%d+)&id=(%d+)",
"facebook.com/(photo).php%?fbid=(%d+)",
"facebook.com/([A-Za-z0-9-._-]+)/(photos)/a.(%d+[%d%.]*)/(%d+)",
"facebook.com/(video).php%?v=(%d+)",
"facebook.com/([A-Za-z0-9-._-]+)/(videos)/(%d+[%d%.]*)",
"facebook.com/([A-Za-z0-9-._-]+)/?$"
}
end
@ -22,7 +22,7 @@ local BASE_URL = 'https://graph.facebook.com/v2.5'
local fb_access_token = cred_data.fb_access_token
local makeOurDate = function(dateString)
local pattern = '(%d+)%/(%d+)%/(%d+)'
local pattern = "(%d+)%/(%d+)%/(%d+)"
local month, day, year = dateString:match(pattern)
return day..'.'..month..'.'..year
end
@ -43,17 +43,18 @@ function facebook:fb_post (id, story_id)
local from = data.from.name
local message = data.message
if not message then return nil end
local name = data.name
if data.link then
link = '\n<a href="'..data.link..'">'..data.name..'</a>'
else
link = ''
link = ""
end
if data.story then
story = ' ('..data.story..')'
else
story = ''
story = ""
end
local text = '<b>'..from..'</b>'..story..':\n'..message..'\n'..link
@ -104,13 +105,13 @@ function facebook:facebook_info(name)
if data.about then
about = '\n'..data.about
else
about = ''
about = ""
end
if data.general_info then
general_info = '\n'..data.general_info
else
general_info = ''
general_info = ""
end
if data.birthday and data.founded then
@ -120,7 +121,7 @@ function facebook:facebook_info(name)
elseif data.founded and not data.birthday then
birth = '\nGegründet: '..data.founded
else
birth = ''
birth = ""
end
local text = '<b>'..name..'</b> ('..category..')<i>'..about..'</i>'..general_info..birth

View File

@ -1,5 +1,7 @@
local forecast = {}
require("./miku/plugins/weather")
function forecast:init(config)
if not cred_data.forecastio_apikey then
print('Fehlender Key: forecastio_apikey.')
@ -21,6 +23,12 @@ function forecast:init(config)
"^(/forecasth)$",
"^(/forecasth) (.*)$"
}
forecast.inline_triggers = {
"^(f) (.+)$",
"^(fh) (.+)$",
"^(fh)$",
"^(f)$"
}
forecast.doc = [[*
]]..config.cmd_pat..[[f*: Wettervorhersage für deinen Wohnort _(/location set <Ort>)_
*]]..config.cmd_pat..[[f* _<Ort>_: Wettervorhersage für diesen Ort
@ -35,39 +43,26 @@ local BASE_URL = "https://api.forecast.io/forecast"
local apikey = cred_data.forecastio_apikey
local google_apikey = cred_data.google_apikey
function get_city_name(lat, lng)
local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng)
if city then return city end
local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey
local res, code = https.request(url)
if code ~= 200 then return 'Unbekannte Stadt' end
local data = json.decode(res).results[1]
local city = data.formatted_address
print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"')
redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city)
return city
end
function get_condition_symbol(weather, n)
if weather.data[n].icon == 'clear-day' then
function forecast:get_condition_symbol(weather_data)
if weather_data.icon == 'clear-day' then
return '☀️'
elseif weather.data[n].icon == 'clear-night' then
elseif weather_data.icon == 'clear-night' then
return '🌙'
elseif weather.data[n].icon == 'rain' then
elseif weather_data.icon == 'rain' then
return '☔️'
elseif weather.data[n].icon == 'snow' then
elseif weather_data.icon == 'snow' then
return '❄️'
elseif weather.data[n].icon == 'sleet' then
elseif weather_data.icon == 'sleet' then
return '🌨'
elseif weather.data[n].icon == 'wind' then
elseif weather_data.icon == 'wind' then
return '💨'
elseif weather.data[n].icon == 'fog' then
elseif weather_data.icon == 'fog' then
return '🌫'
elseif weather.data[n].icon == 'cloudy' then
elseif weather_data.icon == 'cloudy' then
return '☁️☁️'
elseif weather.data[n].icon == 'partly-cloudy-day' then
elseif weather_data.icon == 'partly-cloudy-day' then
return '🌤'
elseif weather.data[n].icon == 'partly-cloudy-night' then
elseif weather_data.icon == 'partly-cloudy-night' then
return '🌙☁️'
else
return ''
@ -75,22 +70,34 @@ function get_condition_symbol(weather, n)
end
function get_temp(weather, n, hourly)
local weather_data = weather.data[n]
if hourly then
local temperature = string.gsub(round(weather.data[n].temperature, 1), "%.", ",")
local condition = weather.data[n].summary
return temperature..'°C | '..get_condition_symbol(weather, n)..' '..condition
local temperature = string.gsub(round(weather_data.temperature, 1), "%.", ",")
local condition = weather_data.summary
return temperature..'°C | '..forecast:get_condition_symbol(weather_data)..' '..condition
else
local day = string.gsub(round(weather.data[n].temperatureMax, 1), "%.", ",")
local night = string.gsub(round(weather.data[n].temperatureMin, 1), "%.", ",")
local condition = weather.data[n].summary
return '☀️ '..day..'°C | 🌙 '..night..'°C | '..get_condition_symbol(weather, n)..' '..condition
local day = string.gsub(round(weather_data.temperatureMax, 1), "%.", ",")
local night = string.gsub(round(weather_data.temperatureMin, 1), "%.", ",")
local condition = weather_data.summary
return '☀️ '..day..'°C | 🌙 '..night..'°C | '..forecast:get_condition_symbol(weather_data)..' '..condition
end
end
function forecast:get_forecast(lat, lng)
function forecast:get_forecast(lat, lng, is_inline)
print('Finde Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:forecast:'..lat..','..lng)
if text then print('...aus dem Cache..') return text end
local hash = 'telegram:cache:forecast:'..lat..','..lng
local text = redis:hget(hash, 'text')
if text then
print('...aus dem Cache..')
if is_inline then
local ttl = redis:ttl(hash)
local city = redis:hget(hash, 'city')
local summary = redis:hget(hash, 'summary')
return city, summary, text, ttl
else
return text
end
end
local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,hourly,alerts,flags'
@ -100,43 +107,61 @@ function forecast:get_forecast(lat, lng)
method = "GET",
sink = ltn12.sink.table(response_body)
}
local ok, response_code, response_headers, response_status_line = https.request(request_constructor)
local ok, response_code, response_headers = https.request(request_constructor)
if not ok then return nil end
local data = json.decode(table.concat(response_body))
local ttl = string.sub(response_headers["cache-control"], 9)
local weather = data.daily
local weather = json.decode(table.concat(response_body)).daily
local ttl = tonumber(string.sub(response_headers["cache-control"], 9))
local city = get_city_name(lat, lng)
local weather_summary = weather.summary
local header = '*Vorhersage für '..city..':*\n_'..weather.summary..'_\n'
local header = '*Vorhersage für '..city..':*\n_'..weather_summary..'_\n'
local text = '*Heute:* '..get_temp(weather, 1)
local text = text..'\n*Morgen:* '..get_temp(weather, 2)
for day in pairs(weather.data) do
local weather_data = weather.data
for day in pairs(weather_data) do
if day > 2 then
text = text..'\n*'..convert_timestamp(weather.data[day].time, '%a, %d.%m')..'*: '..get_temp(weather, day)
text = text..'\n*'..convert_timestamp(weather_data[day].time, '%a, %d.%m')..'*: '..get_temp(weather, day)
end
end
local text = string.gsub(text, "Mon", "Mo")
local text = string.gsub(text, "Tue", "Di")
local text = string.gsub(text, "Wed", "Mi")
local text = string.gsub(text, "Thu", "Do")
local text = string.gsub(text, "Fri", "Fr")
local text = string.gsub(text, "Sat", "Sa")
local text = string.gsub(text, "Sun", "So")
local text = text:gsub("Mon", "Mo")
local text = text:gsub("Tue", "Di")
local text = text:gsub("Wed", "Mi")
local text = text:gsub("Thu", "Do")
local text = text:gsub("Fri", "Fr")
local text = text:gsub("Sat", "Sa")
local text = text:gsub("Sun", "So")
cache_data('forecast', lat..','..lng, header..text, tonumber(ttl), 'key')
print('Caching data...')
redis:hset(hash, 'city', city)
redis:hset(hash, 'summary', weather_summary)
redis:hset(hash, 'text', header..text)
redis:expire(hash, ttl)
return header..text
if is_inline then
return city, weather_summary, header..text, ttl
else
return header..text
end
end
function forecast:get_forecast_hourly(lat, lng)
function forecast:get_forecast_hourly(lat, lng, is_inline)
print('Finde stündliches Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:forecast:'..lat..','..lng..':hourly')
if text then print('...aus dem Cache..') return text end
local hash = 'telegram:cache:forecast:'..lat..','..lng..':hourly'
local text = redis:hget(hash, 'text')
if text then
print('...aus dem Cache..')
if is_inline then
local ttl = redis:ttl(hash)
local city = redis:hget(hash, 'city')
local summary = redis:hget(hash, 'summary')
return city, summary, text, ttl
else
return text
end
end
local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,daily,alerts,flags'
@ -146,32 +171,67 @@ function forecast:get_forecast_hourly(lat, lng)
method = "GET",
sink = ltn12.sink.table(response_body)
}
local ok, response_code, response_headers, response_status_line = https.request(request_constructor)
local ok, response_code, response_headers = https.request(request_constructor)
if not ok then return nil end
local data = json.decode(table.concat(response_body))
local ttl = string.sub(response_headers["cache-control"], 9)
local weather = data.hourly
local weather = json.decode(table.concat(response_body)).hourly
local ttl = tonumber(string.sub(response_headers["cache-control"], 9))
local city = get_city_name(lat, lng)
local weather_summary = weather.summary
local header = '*24-Stunden-Vorhersage für '..city..':*\n_'..weather.summary..'_'
local header = '*24-Stunden-Vorhersage für '..city..':*\n_'..weather_summary..'_'
local text = ""
for hour in pairs(weather.data) do
local weather_data = weather.data
for hour in pairs(weather_data) do
if hour < 26 then
text = text..'\n*'..convert_timestamp(weather.data[hour].time, '%H:%M Uhr')..'* | '..get_temp(weather, hour, true)
text = text..'\n*'..convert_timestamp(weather_data[hour].time, '%H:%M Uhr')..'* | '..get_temp(weather, hour, true)
end
end
cache_data('forecast', lat..','..lng..':hourly', header..text, tonumber(ttl), 'key')
print('Caching data...')
redis:hset(hash, 'city', city)
redis:hset(hash, 'summary', weather_summary)
redis:hset(hash, 'text', header..text)
redis:expire(hash, ttl)
return header..text
if is_inline then
return city, weather_summary, header..text, ttl
else
return header..text
end
end
function forecast:inline_callback(inline_query, config, matches)
local user_id = inline_query.from.id
if matches[2] then
city = matches[2]
is_personal = false
else
local set_location = get_location(user_id)
is_personal = true
if not set_location then
city = 'Berlin, Deutschland'
else
city = set_location
end
end
local lat, lng = get_city_coordinates(city, config)
if not lat and not lng then utilities.answer_inline_query(self, inline_query) return end
if matches[1] == 'f' then
title, description, text, ttl = forecast:get_forecast(lat, lng, true)
else
title, description, text, ttl = forecast:get_forecast_hourly(lat, lng, true)
end
if not title and not description and not text and not ttl then utilities.answer_inline_query(self, inline_query) return end
local text = text:gsub('\n', '\\n')
local results = '[{"type":"article","id":"28062013","title":"'..title..'","description":"'..description..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/weather/cloudy.jpg","thumb_width":80,"thumb_height":80,"input_message_content":{"message_text":"'..text..'", "parse_mode":"Markdown"}}]'
utilities.answer_inline_query(self, inline_query, results, ttl, is_personal)
end
function forecast:action(msg, config, matches)
local user_id = msg.from.id
local city = get_location(user_id)
if matches[2] then
city = matches[2]
@ -184,22 +244,11 @@ function forecast:action(msg, config, matches)
end
end
local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat')
local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng')
local lat, lng = get_city_coordinates(city, config)
if not lat and not lng then
print('Koordinaten nicht eingespeichert, frage Google...')
coords = utilities.get_coords(city, config)
lat = coords.lat
lng = coords.lon
end
if not lat and not lng then
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
return
end
redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat)
redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng)
if matches[1] == '/forecasth' or matches[1] == '/fh' then
text = forecast:get_forecast_hourly(lat, lng)
@ -212,4 +261,4 @@ function forecast:action(msg, config, matches)
utilities.send_reply(self, msg, text, true)
end
return forecast
return forecast

View File

@ -131,7 +131,7 @@ end
function gImages:cache_result(results, text)
local cache = {}
for v in pairs(results) do
table.insert(cache, results[v].link)
cache[v] = results[v].link
end
for n, link in pairs(cache) do
redis:hset('telegram:cache:gImages:'..link, 'mime', results[n].mime)
@ -234,4 +234,4 @@ function gImages:action(msg, config, matches)
end
end
return gImages
return gImages

View File

@ -131,7 +131,7 @@ end
function gImages_nsfw:cache_result(results, text)
local cache = {}
for v in pairs(results) do
table.insert(cache, results[v].link)
cache[v] = results[v].link
end
for n, link in pairs(cache) do
redis:hset('telegram:cache:gImages_nsfw:'..link, 'mime', results[n].mime)

View File

@ -4,6 +4,9 @@ gMaps.command = 'loc <Ort>'
function gMaps:init(config)
gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Ll][Oo][Cc]', true).table
gMaps.inline_triggers = {
"^[Ll][Oo][Cc] (.+)"
}
gMaps.doc = [[*
]]..config.cmd_pat..[[loc* _<Ort>_: Sendet Ort via Google Maps]]
end
@ -16,25 +19,32 @@ function gMaps:get_staticmap(area, lat, lon)
return file
end
function gMaps:inline_callback(inline_query, config, matches)
local place = matches[1]
local coords = utilities.get_coords(place, config)
if type(coords) == 'string' then utilities.answer_inline_query(self, inline_query) return end
local results = '[{"type":"venue","id":"10","latitude":'..coords.lat..',"longitude":'..coords.lon..',"title":"Ort","address":"'..coords.addr..'"}]'
utilities.answer_inline_query(self, inline_query, results, 10000)
end
function gMaps:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gMaps.doc, true, msg.message_id, true)
return
end
end
utilities.send_typing(self, msg.chat.id, 'find_location')
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, gMaps.doc, true)
return
end
utilities.send_typing(self, msg.chat.id, 'find_location')
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
utilities.send_location(self, msg.chat.id, coords.lat, coords.lon, msg.message_id)
utilities.send_photo(self, msg.chat.id, gMaps:get_staticmap(input, coords.lat, coords.lon), nil, msg.message_id)
end
return gMaps
return gMaps

View File

@ -51,14 +51,10 @@ function gSearch:stringlinks(results, stats)
end
function gSearch:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gSearch.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, gImages.doc, true)
return
end
local results, stats = gSearch:googlethat(input, onfig)
@ -76,4 +72,4 @@ function gSearch:action(msg, config)
end
return gSearch
return gSearch

View File

@ -1,6 +1,5 @@
-- YOU NEED THE FOLLOWING FOLDERS: photo, document, video, voice
-- PLEASE ADJUST YOUR PATH BELOW
-- Save your bot api key in redis set telegram:credentials!
local media_download = {}
@ -34,7 +33,7 @@ function media_download:download_to_file_permanently(url, file_name)
return true
end
function media_download:pre_process(msg, self)
function media_download:pre_process(msg, self, config)
if msg.photo then
local lv = #msg.photo -- find biggest photo, always the last value
file_id = msg.photo[lv].file_id
@ -89,7 +88,7 @@ function media_download:pre_process(msg, self)
end
-- Construct what we want
local download_url = 'https://api.telegram.org/file/bot'..cred_data.bot_api_key..'/'..request.result.file_path
local download_url = 'https://api.telegram.org/file/bot'..config.bot_api_key..'/'..request.result.file_path
local ok = media_download:download_to_file_permanently(download_url, file_path)
if not ok then
print('Download fehlgeschlagen!')

View File

@ -30,12 +30,14 @@ function giphy:inline_callback(inline_query, config, matches)
else
data = giphy:get_gifs(matches[2])
end
if not data then return end
if not data[1] then return end
if not data then utilities.answer_inline_query(self, inline_query) return end
if not data[1] then utilities.answer_inline_query(self, inline_query) return end
local results = '['
local id = 450
for n in pairs(data) do
results = results..'{"type":"mpeg4_gif","id":"'..math.random(100000000000000000)..'","mpeg4_url":"'..data[n].images.original.mp4..'","thumb_url":"'..data[n].images.fixed_height.url..'","mpeg4_width":'..data[n].images.original.width..',"mp4_height":'..data[n].images.original.height..'}'
results = results..'{"type":"mpeg4_gif","id":"'..id..'","mpeg4_url":"'..data[n].images.original.mp4..'","thumb_url":"'..data[n].images.fixed_height.url..'","mpeg4_width":'..data[n].images.original.width..',"mp4_height":'..data[n].images.original.height..'}'
id = id+1
if n < #data then
results = results..','
end

View File

@ -25,9 +25,9 @@ end
function googl:inline_callback(inline_query, config, matches)
local shorturl = matches[1]
local text, longUrl = googl:send_googl_info(shorturl)
if not longUrl then return end
if not longUrl then utilities.answer_inline_query(self, inline_query) return end
local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"Verlängerte URL","description":"'..longUrl..'","url":"'..longUrl..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..text..'"}}]'
local results = '[{"type":"article","id":"9","title":"Verlängerte URL","description":"'..longUrl..'","url":"'..longUrl..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..text..'"}}]'
utilities.answer_inline_query(self, inline_query, results, 1)
end

View File

@ -5,6 +5,13 @@ gps.command = 'gps <Breitengrad>,<Längengrad>'
function gps:init(config)
gps.triggers = {
"^/[Gg][Pp][Ss] ([^,]*)[,%s]([^,]*)$",
"google.de/maps/@([^,]*)[,%s]([^,]*)",
"google.com/maps/@([^,]*)[,%s]([^,]*)",
"google.de/maps/place/@([^,]*)[,%s]([^,]*)",
"google.com/maps/place/@([^,]*)[,%s]([^,]*)"
}
gps.inline_triggers = {
"^[Gg][Pp][Ss] ([^,]*)[,%s]([^,]*)$",
"google.de/maps/@([^,]*)[,%s]([^,]*)",
"google.com/maps/@([^,]*)[,%s]([^,]*)",
"google.de/maps/place/@([^,]*)[,%s]([^,]*)",
@ -14,6 +21,15 @@ function gps:init(config)
]]..config.cmd_pat..[[gps* _<Breitengrad>_,_<Längengrad>_: Sendet Karte mit diesen Koordinaten]]
end
function gps:inline_callback(inline_query, config, matches)
local lat = matches[1]
local lon = matches[2]
local results = '[{"type":"location","id":"8","latitude":'..lat..',"longitude":'..lon..',"title":"Standort"}]'
utilities.answer_inline_query(self, inline_query, results, 10000)
end
function gps:action(msg, config, matches)
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local lat = matches[1]
@ -32,4 +48,4 @@ function gps:action(msg, config, matches)
utilities.send_location(self, msg.chat.id, lat, lon, msg.message_id)
end
return gps
return gps

View File

@ -5,48 +5,81 @@ local help = {}
local help_text
function help:init(config)
help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Hh][Ii][Ll][Ff][Ee]', true):t('[Hh][Ee][Ll][Pp]', true).table
help.triggers = {
"^/[Hh][Ii][Ll][Ff][Ee] (.+)",
"^/[Hh][Ee][Ll][Pp] (.+)",
"^/(hilfe)_(.+)",
"^/[Hh][Ii][Ll][Ff][Ee]$"
}
help.inline_triggers = {
"^[Hh][Ii][Ll][Ff][Ee] (.+)",
"^[Hh][Ee][Ll][Pp] (.+)"
}
end
function help:action(msg, config)
function help:inline_callback(inline_query, config, matches)
local query = matches[1]
for n=1, #self.plugins do
local plugin = self.plugins[n]
if plugin.command and utilities.get_word(plugin.command, 1) == query and plugin.doc then
local doc = plugin.doc
local doc = doc:gsub('"', '\\"')
local doc = doc:gsub('\\n', '\\\n')
local chosen_plugin = utilities.get_word(plugin.command, 1)
local results = '[{"type":"article","id":"9","title":"Hilfe für '..chosen_plugin..'","description":"Hilfe für das Plugin \\"'..chosen_plugin..'\\" wird gepostet.","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/help/hilfe.jpg","input_message_content":{"message_text":"'..doc..'","parse_mode":"Markdown"}}]'
utilities.answer_inline_query(self, inline_query, results, 600, nil, nil, 'Hilfe anzeigen', 'hilfe_'..chosen_plugin)
end
end
utilities.answer_inline_query(self, inline_query)
end
function help:action(msg, config, matches)
if matches[2] then
input = matches[2]
elseif matches[1] ~= '/hilfe' then
input = matches[1]
else
input = nil
end
-- Attempts to send the help message via PM.
-- If msg is from a group, it tells the group whether the PM was successful.
if not input then
local commandlist = {}
help_text = '*Verfügbare Befehle:*\n'..config.cmd_pat
for _,plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
end
local help_text = '*Verfügbare Befehle:*\n'..config.cmd_pat
for n=1, #self.plugins do
local plugin = self.plugins[n]
if plugin.command then
commandlist[#commandlist+1] = plugin.command
end
end
table.insert(commandlist, 'hilfe [Befehl]')
commandlist[#commandlist+1] = 'hilfe [Befehl]'
table.sort(commandlist)
help_text = help_text .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nParameter: <benötigt> [optional]'
local help_text = help_text .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nParameter: <benötigt> [optional]'
local help_text = help_text:gsub('%[', '\\[')
help_text = help_text:gsub('%[', '\\[')
local input = utilities.input(msg.text_lower)
-- Attempts to send the help message via PM.
-- If msg is from a group, it tells the group whether the PM was successful.
if not input then
local res = utilities.send_message(self, msg.from.id, help_text, true, nil, true)
if not res then
utilities.send_reply(self, msg, 'Bitte schreibe mir zuerst [privat](http://telegram.me/' .. self.info.username .. '?start=help) für eine Hilfe.', true)
elseif msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'Ich habe dir die Hilfe per PN gesendet!.')
end
return
local res = utilities.send_message(self, msg.from.id, help_text, true, nil, true)
if not res then
utilities.send_reply(self, msg, 'Bitte schreibe mir zuerst [privat](http://telegram.me/' .. self.info.username .. '?start=help) für eine Hilfe.', true)
elseif msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'Ich habe dir die Hilfe privat gesendet!.')
end
return
end
for _,plugin in ipairs(self.plugins) do
if plugin.command and utilities.get_word(plugin.command, 1) == input and plugin.doc then
local output = '*Hilfe für* _' .. utilities.get_word(plugin.command, 1) .. '_ *:*' .. plugin.doc
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
for n=1, #self.plugins do
local plugin = self.plugins[n]
if plugin.command and utilities.get_word(plugin.command, 1) == input and plugin.doc then
local output = '*Hilfe für* _' .. utilities.get_word(plugin.command, 1) .. '_ *:*' .. plugin.doc
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
end
utilities.send_reply(self, msg, 'Für diesen Befehl gibt es keine Hilfe.')
utilities.send_reply(self, msg, 'Für diesen Befehl gibt es keine Hilfe.')
end
return help

View File

@ -3,11 +3,16 @@ local id = {}
id.command = 'id'
function id:init(config)
id.triggers = {
id.triggers = {
"^/id$",
"^/ids? (chat)$"
}
id.doc = [[```Zeige dir deine ID und die IDs aller Gruppenmitglieder an.``]]
}
id.inline_triggers = {
"^id$"
}
id.doc = [[```Zeige dir deine ID und die IDs aller Gruppenmitglieder an.``]]
end
function id:get_member_count(self, msg, chat_id)
@ -41,7 +46,15 @@ function id:get_user(user_id, chat_id)
return user_info
end
function id:action(msg)
function id:inline_callback(inline_query, config, matches)
local id = tostring(inline_query.from.id)
local name = utilities.build_name(inline_query.from.first_name, inline_query.from.last_name)
local results = '[{"type":"article","id":"30","title":"Deine Telegram-ID ist:","description":"'..id..'","input_message_content":{"message_text":"<b>'..name..'</b>: <code>'..id..'</code>","parse_mode":"HTML"}}]'
utilities.answer_inline_query(self, inline_query, results, 10000, true)
end
function id:action(msg, config, matches)
if matches[1] == "/id" then
if msg.reply_to_message then
@ -86,7 +99,7 @@ function id:action(msg)
for i = 1, #users do
local user_id = users[i]
local user_info = id:get_user(user_id, chat_id)
table.insert(users_info, user_info)
users_info[#users_info+1] = user_info
end
-- get all administrators and the creator
@ -94,7 +107,7 @@ function id:action(msg)
local admins = {}
for num in pairs(administrators.result) do
if administrators.result[num].status ~= 'creator' then
table.insert(admins, tostring(administrators.result[num].user.id))
admins[#admins+1] = tostring(administrators.result[num].user.id)
else
creator_id = administrators.result[num].user.id
end

View File

@ -5,7 +5,7 @@ images.triggers = {
"(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Jj][Pp][Ee]?[Gg])$"
}
function images:action(msg)
function images:action(msg, config, matches)
local url = matches[1]
local file, last_modified, nocache = get_cached_file(url, nil, msg.chat.id, 'upload_photo', self)
local result = utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
@ -17,4 +17,4 @@ function images:action(msg)
cache_file(result, url, last_modified)
end
return images
return images

View File

@ -3,51 +3,99 @@ local imdb = {}
imdb.command = 'imdb <query>'
function imdb:init(config)
imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Ii][Mm][Dd][Bb]', true).table
imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table
imdb.inline_triggers = {
"^imdb (.+)"
}
imdb.doc = [[*
]]..config.cmd_pat..[[imdb* _<Film>_
Sucht einen _Film_ bei IMDb]]
end
function imdb:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, imdb.doc, true, msg.message_id, true)
return
end
end
local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input)
local jstr, res = http.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = json.decode(jstr)
if jdat.Response ~= 'True' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')* von '..jdat.Director..'\n'
output = output .. string.gsub(jdat.imdbRating, '%.', ',') ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n'
output = output .. '_' .. jdat.Plot .. '_\n'
output = output .. '[IMDb-Seite besuchen](http://imdb.com/title/' .. jdat.imdbID .. ')'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
if jdat.Poster ~= "N/A" then
local file = download_to_file(jdat.Poster)
utilities.send_photo(self, msg.chat.id, file)
end
local BASE_URL = 'https://www.omdbapi.com'
function imdb:get_imdb_info(id)
local url = BASE_URL..'/?i='..id
local res, code = https.request(url)
if code ~= 200 then return end
local movie_info = json.decode(res)
return movie_info
end
return imdb
function imdb:inline_callback(inline_query, config, matches)
local query = matches[1]
local url = BASE_URL..'/?s='..URL.escape(query)
local res, code = https.request(url)
if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end
local data = json.decode(res)
if data.Response ~= "True" then utilities.answer_inline_query(self, inline_query) return end
local results = '['
local id = 500
for num in pairs(data.Search) do
if num > 5 then
break;
end
local imdb_id = data.Search[num].imdbID
local movie_info = imdb:get_imdb_info(imdb_id)
local title = movie_info.Title
local year = movie_info.Year
local text = '<b>'..movie_info.Title.. ' ('..movie_info.Year..')</b> von '..movie_info.Director..'\\n'..string.gsub(movie_info.imdbRating, '%.', ',')..'/10 | '..movie_info.Runtime..' | '.. movie_info.Genre
if movie_info.Plot then
text = text..'\\n<i>'..movie_info.Plot..'</i>'
description = movie_info.Plot
else
description = 'Keine Beschreibung verfügbar'
end
local text = text:gsub('"', '\\"')
local text = text:gsub("'", "\'")
local description = description:gsub('"', '\\"')
local description = description:gsub("'", "\'")
if movie_info.Poster == "N/A" then
img_url = 'https://anditest.perseus.uberspace.de/inlineQuerys/imdb/logo.jpg'
else
img_url = movie_info.Poster
end
results = results..'{"type":"article","id":"'..id..'","title":"'..title..' ('..year..')","description":"'..description..'","url":"http://imdb.com/title/'..imdb_id..'","hide_url":true,"thumb_url":"'..img_url..'","reply_markup":{"inline_keyboard":[[{"text":"IMDb-Seite aufrufen","url":"http://imdb.com/title/'..imdb_id..'"}]]},"input_message_content":{"message_text":"'..text..'","parse_mode":"HTML"}},'
id = id+1
end
local results = results:sub(0, -2)
local results = results..']'
utilities.answer_inline_query(self, inline_query, results, 10000)
end
function imdb:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, imdb.doc, true)
return
end
local url = BASE_URL..'/?t='..URL.escape(input)
local jstr, res = https.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = json.decode(jstr)
if jdat.Response ~= 'True' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local output = '<b>'..jdat.Title.. ' ('..jdat.Year..')</b> von '..jdat.Director..'\n'
output = output..string.gsub(jdat.imdbRating, '%.', ',')..'/10 | '..jdat.Runtime..' | '.. jdat.Genre..'\n'
output = output..'<i>' .. jdat.Plot .. '</i>'
utilities.send_reply(self, msg, output, 'HTML', '{"inline_keyboard":[[{"text":"IMDb-Seite aufrufen","url":"http://imdb.com/title/'.. jdat.imdbID..'"}]]}')
if jdat.Poster ~= "N/A" then
local file = download_to_file(jdat.Poster)
utilities.send_photo(self, msg.chat.id, file)
end
end
return imdb

View File

@ -1,7 +1,5 @@
local media = {}
mimetype = (loadfile "./miku/mimetype.lua")()
media.triggers = {
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(gif))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(mp4))$",
@ -23,10 +21,10 @@ media.triggers = {
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(webp))$"
}
function media:action(msg)
function media:action(msg, config, matches)
local url = matches[1]
local ext = matches[2]
local mime_type = mimetype.get_content_type_no_sub(ext)
local mime_type = mime.get_content_type_no_sub(ext)
local receiver = msg.chat.id
if mime_type == 'audio' then
@ -41,19 +39,14 @@ function media:action(msg)
if not file then return end
if ext == 'gif' then
print('Sende GIF')
result = utilities.send_document(self, receiver, file, nil, msg.message_id)
elseif ext == 'ogg' then
print('Sende OGG')
result = utilities.send_voice(self, receiver, file, nil, msg.message_id)
elseif mime_type == 'audio' then
print('Sende Audio')
result = utilities.send_audio(self, receiver, file, nil, msg.message_id)
elseif mime_type == 'video' then
print('Sende Video')
result = utilities.send_video(self, receiver, file, nil, msg.message_id)
else
print('Sende Datei')
result = utilities.send_document(self, receiver, file, nil, msg.message_id)
end

View File

@ -1,8 +1,11 @@
local patterns = {}
patterns.triggers = {
'^/?s/.-/.-$'
}
function patterns:init(config)
patterns.command = 's/<Pattern>/<Ersetzung>'
patterns.triggers = {
config.cmd_pat .. '?s/.-/.-$'
}
end
function patterns:action(msg)
if not msg.reply_to_message then return true end

View File

@ -5,12 +5,12 @@ local bot = require('miku.bot')
function plugin_manager:init(config)
plugin_manager.triggers = {
"^/plugins$",
"^/plugins? (enable) ([%w_%.%-]+) (chat) (%d+)$",
"^/plugins? (enable) ([%w_%.%-]+) (chat)$",
"^/plugins? (disable) ([%w_%.%-]+) (chat) (%d+)$",
"^/plugins? (disable) ([%w_%.%-]+) (chat)$",
"^/plugins? (enable) ([%w_%.%-]+)$",
"^/plugins? (disable) ([%w_%.%-]+)$",
"^/plugins? (enable) ([%w_%.%-]+) (chat) (%d+)",
"^/plugins? (enable) ([%w_%.%-]+) (chat)",
"^/plugins? (disable) ([%w_%.%-]+) (chat) (%d+)",
"^/plugins? (disable) ([%w_%.%-]+) (chat)",
"^/plugins? (reload)$",
"^/(reload)$"
}

View File

@ -0,0 +1,39 @@
-- This plugin goes through every message with a document and if the document is an image,
-- it downloads the file and resends it as image
local post_photo = {}
post_photo.triggers = {
'/nil'
}
function post_photo:pre_process(msg, self, config)
if not msg.document then return msg end -- Ignore
local mime_type = msg.document.mime_type
local valid_mimetypes = {['image/jpeg'] = true, ['image/png'] = true, ['image/bmp'] = true}
if not valid_mimetypes[mime_type] then return msg end
local file_id = msg.document.file_id
local file_size = msg.document.file_size
if file_size > 19922944 then
print('File is over 20 MB - can\'t download :(')
return
end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
-- Saving file to the Telegram Cloud
local request = bindings.request(self, 'getFile', {
file_id = file_id
} )
local download_url = 'https://api.telegram.org/file/bot'..config.bot_api_key..'/'..request.result.file_path
local file = download_to_file(download_url, msg.file_name)
utilities.send_photo(self, msg.chat.id, file, msg.caption, msg.message_id)
return msg
end
function post_photo:action(msg)
end
return post_photo

View File

@ -4,41 +4,73 @@ preview.command = 'preview <link>'
function preview:init(config)
preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table
preview.doc = [[```
]]..config.cmd_pat..[[preview <link>
Returns a full-message, "unlinked" preview.
```]]
preview.inline_triggers = {
"^pr (https?://[%w-_%.%?%.:/%+=&%~%%#]+)$"
}
preview.doc = [[*
]]..config.cmd_pat..[[preview* _<URL>_
Erstellt einen Preview-Link]]
end
function preview:inline_callback(inline_query, config, matches)
local preview_url = matches[1]
local res, code = https.request('https://brawlbot.tk/apis/simple_meta_api/?url='..URL.escape(preview_url))
if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end
local data = json.decode(res)
if data.remote_code >= 400 then utilities.answer_inline_query(self, inline_query) return end
if data.title then
title = data.title
else
title = 'Kein Titel'
end
if data.description then
description = data.description
description_in_text = '\n'..description
else
description_in_text = ''
description = 'Keine Beschreibung verfügbar'
end
if data.only_name then
only_name = data.only_name
else
only_name = preview_url:match('^%w+://([^/]+)') -- we only need the domain
end
local message_text = '<b>'..title..'</b>'..description_in_text..'\n'..only_name
local results = '[{"type":"article","id":"77","title":"'..title..'","description":"'..description..'","url":"'..preview_url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":150,"thumb_height":150,"hide_url":true,"reply_markup":{"inline_keyboard":[[{"text":"Webseite aufrufen","url":"'..preview_url..'"}]]},"input_message_content":{"message_text":"'..message_text..'","parse_mode":"HTML","disable_web_page_preview":true}}]'
utilities.answer_inline_query(self, inline_query, results, 3600, true)
end
function preview:action(msg)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, preview.doc, true)
return
end
local input = utilities.input(msg.text)
input = utilities.get_word(input, 1)
if not input:match('^https?://.+') then
input = 'http://' .. input
end
if not input then
utilities.send_message(self, msg.chat.id, preview.doc, true, nil, true)
return
end
local res = http.request(input)
if not res then
utilities.send_reply(self, msg, 'Bitte gebe einen validen Link an.')
return
end
input = utilities.get_word(input, 1)
if not input:match('^https?://.+') then
input = 'http://' .. input
end
local res = http.request(input)
if not res then
utilities.send_reply(self, msg, 'Please provide a valid link.')
return
end
if res:len() == 0 then
utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.')
return
end
-- Invisible zero-width, non-joiner.
local output = '[](' .. input .. ')'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
if res:len() == 0 then
utilities.send_reply(self, msg, 'Sorry, dieser Link lässt uns keine Vorschau erstellen.')
return
end
-- Invisible zero-width, non-joiner.
local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>'
utilities.send_message(self, msg.chat.id, output, false, nil, 'HTML')
end
return preview
return preview

View File

@ -71,11 +71,13 @@ end
function qr:inline_callback(inline_query, config, matches)
local text = matches[1]
if string.len(text) > 200 then return end
if string.len(text) > 200 then utilities.answer_inline_query(self, inline_query) return end
local image_url = qr:qr(text, nil, nil, 'jpg')
if not image_url then return end
if not image_url then utilities.answer_inline_query(self, inline_query) return end
local id = 600
local results = '[{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"},'
local results = '[{"type":"photo","id":"'..id..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"},'
local i = 0
while i < 29 do
@ -83,7 +85,8 @@ function qr:inline_callback(inline_query, config, matches)
local color = math.random(255)
local bgcolor = math.random(255)
local image_url = qr:qr(text, color, bgcolor, 'jpg')
results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"}'
id = id+1
results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"}'
if i < 29 then
results = results..','
end
@ -110,4 +113,4 @@ function qr:action(msg, config, matches)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
return qr
return qr

View File

@ -7,88 +7,79 @@ function remind:init(config)
remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table
remind.doc = [[*
]]..config.cmd_pat..[[remind* _<Länge>_ _<Nachricht>_: Erinnert dich in X Minuten an die Nachricht]]
]]..config.cmd_pat..[[remind* _<Länge>_ _<Nachricht>_
Erinnert dich in der angegeben Länge in Minuten an eine Nachricht.
Die maximale Länge einer Erinnerung beträgt %s Buchstaben, die maximale Zeit beträgt %s Minuten, die maximale Anzahl an Erinnerung für eine Gruppe ist %s und für private Chats %s.]]
remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private)
end
function remind:action(msg)
-- Ensure there are arguments. If not, send doc.
function remind:action(msg, config)
local input = utilities.input(msg.text)
if not input then
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
utilities.send_reply(self, msg, remind.doc, true)
return
end
-- Ensure first arg is a number. If not, send doc.
local duration = utilities.get_word(input, 1)
if not tonumber(duration) then
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
local duration = tonumber(utilities.get_word(input, 1))
if not duration then
utilities.send_reply(self, msg, remind.doc, true)
return
end
-- Duration must be between one minute and one day (approximately).
duration = tonumber(duration)
if duration < 1 then
duration = 1
elseif duration > 1440 then
duration = 1440
elseif duration > config.remind.max_duration then
duration = config.remind.max_duration
end
-- Ensure there is a second arg.
local message = utilities.input(input)
if not message then
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
utilities.send_reply(self, msg, remind.doc, true)
return
end
if #message > config.remind.max_length then
utilities.send_reply(self, msg, 'Die maximale Länge einer Erinnerung ist ' .. config.remind.max_length .. '.')
return
end
-- Make a database entry for the group/user if one does not exist.
self.database.reminders[msg.chat.id_str] = self.database.reminders[msg.chat.id_str] or {}
-- Limit group reminders to 10 and private reminders to 50.
if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 9 then
utilities.send_reply(self, msg, 'Diese Gruppe hat schon zehn Erinnerungen!')
return
elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then
utilities.send_reply(msg, 'Du hast schon 50 Erinnerungen!')
return
local chat_id_str = tostring(msg.chat.id)
local output
self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {}
if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then
output = 'Sorry, du kannst keine Erinnerungen mehr hinzufügen.'
elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then
output = 'Sorry, diese Gruppe kann keine Erinnerungen mehr hinzufügen.'
else
-- Put together the reminder with the expiration, message, and message to reply to.
local timestamp = os.time() + duration * 60
local reminder = {
time = timestamp,
message = message
}
table.insert(self.database.reminders[chat_id_str], reminder)
local human_readable_time = convert_timestamp(timestamp, '%H:%M:%S')
output = 'Ich werde dich um *'..human_readable_time..' Uhr* erinnern.'
end
-- Put together the reminder with the expiration, message, and message to reply to.
local timestamp = os.time() + duration * 60
local reminder = {
time = timestamp,
message = message
}
table.insert(self.database.reminders[msg.chat.id_str], reminder)
local human_readable_time = convert_timestamp(timestamp, '%H:%M:%S')
local output = 'Ich werde dich um *'..human_readable_time..' Uhr* erinnern.'
utilities.send_reply(self, msg, output, true)
end
function remind:cron()
function remind:cron(config)
local time = os.time()
-- Iterate over the group entries in the reminders database.
for chat_id, group in pairs(self.database.reminders) do
local new_group = {}
-- Iterate over each reminder.
for _, reminder in ipairs(group) do
for k, reminder in pairs(group) do
-- If the reminder is past-due, send it and nullify it.
-- Otherwise, add it to the replacement table.
if time > reminder.time then
local output = '*ERINNERUNG:*\n"' .. utilities.md_escape(reminder.message) .. '"'
local res = utilities.send_message(self, chat_id, output, true, nil, true)
-- If the message fails to send, save it for later.
if not res then
table.insert(new_group, reminder)
-- If the message fails to send, save it for later (if enabled in config).
if res or not config.remind.persist then
group[k] = nil
end
else
table.insert(new_group, reminder)
end
end
-- Nullify the original table and replace it with the new one.
self.database.reminders[chat_id] = new_group
-- Nullify the table if it is empty.
if #new_group == 0 then
self.database.reminders[chat_id] = nil
end
end
end

View File

@ -40,14 +40,14 @@ function respond:inline_callback(inline_query, config, matches)
elseif string.match(text, "[Nn][Bb][Cc]") or string.match(text, "[Ii][Dd][Cc]") or string.match(text, "[Kk][Aa]") or string.match(text, "[Ii][Dd][Kk]") then
face = '¯\\\\\\_(ツ)_/¯'
end
results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..face..'","input_message_content":{"message_text":"'..face..'"}}]'
results = '[{"type":"article","id":"8","title":"'..face..'","input_message_content":{"message_text":"'..face..'"}}]'
utilities.answer_inline_query(self, inline_query, results, 9999)
end
function respond:action(msg, config, matches)
local user_name = get_name(msg)
local receiver = msg.chat.id
local GDRIVE_URL = 'https://de2319bd4b4b51a5ef2939a7638c1d35646f49f8.googledrive.com/host/0B_mfIlDgPiyqU25vUHZqZE9IUXc'
local BASE_URL = 'https://anditest.perseus.uberspace.de/plugins/respond'
if user_name == "DefenderX" then user_name = "Deffu" end
if string.match(msg.text, "[Ff][Gg][Tt].? [Ss][Ww][Ii][Ff][Tt]") then
@ -78,11 +78,11 @@ function respond:action(msg, config, matches)
utilities.send_message(self, receiver, '🐸🐸🐸')
return
elseif string.match(msg.text, "[Ii][Nn][Ll][Oo][Vv][Ee]") then
local file = download_to_file(GDRIVE_URL..'/inlove.gif')
local file = download_to_file(BASE_URL..'/inlove.gif')
utilities.send_document(self, receiver, file)
return
elseif string.match(msg.text, "[Ww][Aa][Tt]") then
local WAT_URL = GDRIVE_URL..'/wat'
local WAT_URL = BASE_URL..'/wat'
local wats = {
"/wat1.jpg",
"/wat2.jpg",

View File

@ -13,7 +13,7 @@ function rss:init(config)
"^/rss (sub) (https?://[%w-_%.%?%.:/%+=&%~]+)$",
"^/rss (del) (%d+) @(.*)$",
"^/rss (del) (%d+)$",
"^/rss (del)",
"^/rss (del)$",
"^/rss (sync)$"
}
rss.doc = [[*

View File

@ -0,0 +1,66 @@
local migrate = {}
migrate.triggers = {
'^//tgservice migrate_to_chat_id$'
}
function migrate:action(msg, config, matches)
if not is_service_msg(msg) then return end -- Bad attempt at trolling!
local old_id = msg.chat.id
local new_id = msg.migrate_to_chat_id
print('Migrating every data from '..old_id..' to '..new_id..'...')
print('--- SUPERGROUP MIGRATION STARTED ---')
local keys = redis:keys('*'..old_id..'*')
for k,v in pairs(keys) do
local string_before_id = string.match(v, '(.+)'..old_id..'.+') or string.match(v, '(.+)'..old_id)
local string_after_id = string.match(v, '.+'..old_id..'(.+)') or ''
print(string_before_id..old_id..string_after_id..' -> '..string_before_id..new_id..string_after_id)
redis:rename(string_before_id..old_id..string_after_id, string_before_id..new_id..string_after_id)
end
-- Migrate GH feed
local keys = redis:keys('github:*:subs')
if keys then
for k,v in pairs(keys) do
local repo = string.match(v, "github:(.+):subs")
local is_in_set = redis:sismember('github:'..repo..':subs', old_id)
if is_in_set then
print('github:'..repo..':subs - Changing ID in set...')
redis:srem('github:'..repo..':subs', old_id)
redis:sadd('github:'..repo..':subs', new_id)
end
end
end
-- Migrate RSS feed
local keys = redis:keys('rss:*:subs')
if keys then
for k,v in pairs(keys) do
local feed = string.match(v, "rss:(.+):subs")
local is_in_set = redis:sismember('rss:'..feed..':subs', 'chat#id'..old_id)
if is_in_set then
print('rss:'..feed..':subs - Changing ID in set...')
redis:srem('rss:'..feed..':subs', 'chat#id'..old_id)
redis:sadd('rss:'..feed..':subs', 'chat#id'..new_id)
end
end
end
-- Migrate Tagesschau-Eilmeldungen
local does_tagesschau_set_exists = redis:exists('telegram:tagesschau:subs')
if does_tagesschau_set_exists then
local is_in_set = redis:sismember('telegram:tagesschau:subs', 'chat#id'..old_id)
if is_in_set then
print('telegram:tagesschau:subs - Changing ID in set...')
redis:srem('telegram:tagesschau:subs', 'chat#id'..old_id)
redis:sadd('telegram:tagesschau:subs', 'chat#id'..new_id)
end
end
print('--- SUPERGROUP MIGRATION ENDED ---')
utilities.send_message(self, new_id, 'Die User-ID dieser Gruppe ist nun '..new_id..'.\nAlle Daten wurden <20>bertragen.')
end
return migrate

View File

@ -70,11 +70,12 @@ function stats:chat_stats(chat_id)
local text = ''
for k,user in pairs(users_info) do
text = text..user.name..': '..comma_value(user.msgs)..'\n'
text = string.gsub(text, "%_", " ") -- Bot API doesn't use underscores anymore! Yippie!
local msg_num = user.msgs
local percent = tostring(round(msg_num / all_msgs * 100, 1))
text = text..user.name..': '..comma_value(msg_num)..' <code>('..percent:gsub('%.', ',')..'%)</code>\n'
end
if text:isempty() then return 'Keine Stats für diesen Chat verfügbar!'end
local text = utilities.md_escape(text)..'\n*TOTAL*: '..comma_value(all_msgs)
local text = text..'\n<b>TOTAL</b>: '..comma_value(all_msgs)
return text
end
@ -129,7 +130,7 @@ function stats:action(msg, config, matches)
return
else
local chat_id = msg.chat.id
utilities.send_reply(self, msg, stats:chat_stats(chat_id), true)
utilities.send_reply(self, msg, stats:chat_stats(chat_id), 'HTML')
return
end
end
@ -139,7 +140,7 @@ function stats:action(msg, config, matches)
utilities.send_reply(self, msg, config.errors.sudo)
return
else
utilities.send_reply(self, msg, stats:chat_stats(matches[3]), true)
utilities.send_reply(self, msg, stats:chat_stats(matches[3]), 'HTML')
return
end
end

View File

@ -42,7 +42,7 @@ function tagesschau:inline_callback(inline_query, config, matches)
local article = matches[1]
local full_url = 'http://www.tagesschau.de/'..article..'.html'
local text, img_url, headline, shorttext = tagesschau:get_tagesschau_article(article)
if text == 'HTTP-Fehler' or text == 'Artikel nicht gefunden!' then return end
if text == 'HTTP-Fehler' or text == 'Artikel nicht gefunden!' then utilities.answer_inline_query(self, inline_query) return end
if text:match('"') then
text = text:gsub('"', '\\"')
@ -55,7 +55,7 @@ function tagesschau:inline_callback(inline_query, config, matches)
end
local text = text:gsub('\n', '\\n')
local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..headline..'","description":"'..shorttext..'","url":"'..full_url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/tagesschau/tagesschau.jpg","thumb_width":150,"thumb_height":150,"hide_url":true,"reply_markup":{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"'..full_url..'"}]]},"input_message_content":{"message_text":"'..text..'","parse_mode":"HTML"}}]'
local results = '[{"type":"article","id":"11","title":"'..headline..'","description":"'..shorttext..'","url":"'..full_url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/tagesschau/tagesschau.jpg","thumb_width":150,"thumb_height":150,"hide_url":true,"reply_markup":{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"'..full_url..'"}]]},"input_message_content":{"message_text":"'..text..'","parse_mode":"HTML"}}]'
utilities.answer_inline_query(self, inline_query, results, 7200)
end

View File

@ -4,6 +4,10 @@ time.command = 'time <Ort>'
function time:init(config)
time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table
time.inline_triggers = {
"^time (.+)",
"^time"
}
time.doc = [[*
]]..config.cmd_pat..[[time*: Aktuelle Zeit in Deutschland
*]]..config.cmd_pat..[[time* _<Ort>_: Gibt Zeit an diesem Ort aus]]
@ -45,8 +49,49 @@ function time:localize(output)
return output
end
function time:get_time(coords)
local now = os.time()
local utc = os.time(os.date("!*t", now))
local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..utc..'&language=de'
local jstr, res = https.request(url)
if res ~= 200 then return nil end
local jdat = json.decode(jstr)
local place = string.gsub(jdat.timeZoneId, '_', ' ' )
local place = time:localize(place)
local timezoneid = '*'..place..'*'
local timestamp = now + jdat.rawOffset + jdat.dstOffset
local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600
if utcoff == math.abs(utcoff) then
utcoff = '+'.. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end
-- "%A, %d. %B %Y, %H:%M:%S Uhr"
local time_there = time:localize(os.date('!%A, %d. %B %Y, %H:%M:%S Uhr',timestamp))
local output = timezoneid..':\n'..time_there
return output..'\n_'..jdat.timeZoneName .. ' (UTC' .. utcoff .. ')_', place, time_there
end
function time:inline_callback(inline_query, config, matches)
if matches[1] == 'time' then
local desc_time = os.date("%A, %d. %B %Y, %H:%M:%S Uhr")
local cur_time = time:localize(os.date("%A, %d. %B %Y, *%H:%M:%S Uhr*"))
results = '[{"type":"article","id":"12","title":"Europa/Berlin","description":"'..desc_time..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/time/clock.jpg","input_message_content":{"message_text":"'..cur_time..'","parse_mode":"Markdown"}}]'
else
local coords = utilities.get_coords(matches[1], config)
if type(coords) == 'string' then utilities.answer_inline_query(self, inline_query) return end
local output, place, desc_time = time:get_time(coords)
if not output then utilities.answer_inline_query(self, inline_query) return end
results = '[{"type":"article","id":"13","title":"'..place..'","description":"'..desc_time..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/time/clock.jpg","input_message_content":{"message_text":"'..output..'","parse_mode":"Markdown"}}]'
end
utilities.answer_inline_query(self, inline_query, results, 1)
end
function time:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
local output = os.date("%A, %d. %B %Y, *%H:%M:%S Uhr*")
utilities.send_reply(self, msg, time:localize(output), true)
@ -58,33 +103,10 @@ function time:action(msg, config)
utilities.send_reply(self, msg, coords)
return
end
local now = os.time()
local utc = os.time(os.date("!*t", now))
local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..utc..'&language=de'
local jstr, res = https.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = json.decode(jstr)
local timezoneid = '*'..string.gsub(jdat.timeZoneId, '_', ' ' )..'*'
local timestamp = now + jdat.rawOffset + jdat.dstOffset
local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600
if utcoff == math.abs(utcoff) then
utcoff = '+'.. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end
-- "%A, %d. %B %Y, %H:%M:%S Uhr"
local output = timezoneid..':\n'..os.date('!%A, %d. %B %Y, %H:%M:%S Uhr',timestamp)
local output = time:localize(output)
local output = output..'\n_'..jdat.timeZoneName .. ' (UTC' .. utcoff .. ')_'
local output = time:get_time(coords)
if not output then utilities.send_reply(self, msg, config.errors.connection) return end
utilities.send_reply(self, msg, output, true)
end
return time
return time

View File

@ -2,20 +2,20 @@ local twitter = {}
function twitter:init(config)
if not cred_data.tw_consumer_key then
print('Missing config value: tw_consumer_key.')
print('twitter.lua will not be enabled.')
print('Fehlender Key: tw_consumer_key.')
print('twitter.lua wird nicht aktiviert.')
return
elseif not cred_data.tw_consumer_secret then
print('Missing config value: tw_consumer_secret.')
print('twitter.lua will not be enabled.')
print('Fehlender Key: tw_consumer_secret.')
print('twitter.lua wird nicht aktiviert.')
return
elseif not cred_data.tw_access_token then
print('Missing config value: tw_access_token.')
print('twitter.lua will not be enabled.')
print('Fehlender Key: tw_access_token.')
print('twitter.lua wird nicht aktiviert.')
return
elseif not cred_data.tw_access_token_secret then
print('Missing config value: tw_access_token_secret.')
print('twitter.lua will not be enabled.')
print('Fehlender Key: tw_access_token_secret.')
print('twitter.lua wird nicht aktiviert.')
return
end
@ -104,14 +104,14 @@ function twitter:action(msg, config, matches)
if v.video_info then
if not v.video_info.variants[3] then
local vid = v.video_info.variants[1].url
table.insert(videos, vid)
videos[#videos+1] = vid
else
local vid = v.video_info.variants[3].url
table.insert(videos, vid)
videos[#videos+1] = vid
end
end
text = text:gsub(url, "")
table.insert(images, pic)
images[#images+1] = pic
end
end

View File

@ -14,12 +14,14 @@ function twitter_send:init(config)
end
twitter_send.triggers = {
"^/tw (auth) (%d+)",
"^/tw (auth) (%d+)$",
"^/tw (unauth)$",
"^/tw (verify)$",
"^/tw (.+)",
"^/(twwhitelist add) (%d+)",
"^/(twwhitelist del) (%d+)"
"^/tw (.+)$",
"^/(twwhitelist add) (%d+)$",
"^/(twwhitelist del) (%d+)$",
"^/(twwhitelist add)$",
"^/(twwhitelist del)$"
}
twitter_send.doc = [[*
]]..config.cmd_pat..[[tw* _<Text>_: Sendet einen Tweet an den Account, der im Chat angemeldet ist
@ -91,16 +93,18 @@ function twitter_send:get_twitter_access_token(hash, oauth_verifier, oauth_token
redis:hset(hash, 'oauth_token', values.oauth_token)
redis:hset(hash, 'oauth_token_secret', values.oauth_token_secret)
return 'Erfolgreich eingeloggt als "@'..values.screen_name..'" (User-ID: '..values.user_id..')'
local screen_name = values.screen_name
return 'Erfolgreich eingeloggt als <a href="https://twitter.com/'..screen_name..'">@'..screen_name..'</a> (User-ID: '..values.user_id..')'
end
function twitter_send:reset_twitter_auth(hash, frominvalid)
redis:hdel(hash, 'oauth_token')
redis:hdel(hash, 'oauth_token_secret')
if frominvalid then
return '*Authentifizierung nicht erfolgreich, wird zurückgesetzt...*'
return '<b>Authentifizierung nicht erfolgreich, wird zurückgesetzt...</b>'
else
return '*Erfolgreich abgemeldet!* Entziehe den Zugriff endgültig in deinen [Twitter-Einstellungen](https://twitter.com/settings/applications)!'
return '<b>Erfolgreich abgemeldet!</b> Entziehe den Zugriff endgültig in deinen <a href="https://twitter.com/settings/applications">Twitter-Einstellungen</a>!'
end
end
@ -217,7 +221,7 @@ function twitter_send:send_tweet(tweet, oauth_token, oauth_token_secret, hash)
local screen_name = data.user.screen_name
local status_id = data.id_str
return '*Tweet #'..statusnumber..' gesendet!* [Auf Twitter ansehen](https://twitter.com/statuses/'..status_id..')'
return '<a href="https://twitter.com/'..screen_name..'/status/'..status_id..'">Tweet #'..statusnumber..' gesendet!</a>'
end
function twitter_send:add_to_twitter_whitelist(user_id)
@ -245,22 +249,36 @@ function twitter_send:del_from_twitter_whitelist(user_id)
end
function twitter_send:action(msg, config, matches)
if matches[1] == "twwhitelist add" and matches[2] then
if matches[1] == "twwhitelist add" then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
else
utilities.send_reply(self, msg, twitter_send:add_to_twitter_whitelist(matches[2]), true)
local user_id = matches[2]
if not user_id then
if not msg.reply_to_message then
return
end
user_id = msg.reply_to_message.from.id
end
utilities.send_reply(self, msg, twitter_send:add_to_twitter_whitelist(user_id), true)
return
end
end
if matches[1] == "twwhitelist del" and matches[2] then
if matches[1] == "twwhitelist del" then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
else
utilities.send_reply(self, msg, twitter_send:del_from_twitter_whitelist(matches[2]), true)
local user_id = matches[2]
if not user_id then
if not msg.reply_to_message then
return
end
user_id = msg.reply_to_message.from.id
end
utilities.send_reply(self, msg, twitter_send:del_from_twitter_whitelist(user_id), true)
return
end
end
@ -302,7 +320,7 @@ function twitter_send:action(msg, config, matches)
end
end
if string.len(matches[2]) > 7 then utilities.send_reply(self, msg, 'Invalide PIN!') return end
utilities.send_reply(self, msg, twitter_send:get_twitter_access_token(hash, matches[2], oauth_token, oauth_token_secret))
utilities.send_reply(self, msg, twitter_send:get_twitter_access_token(hash, matches[2], oauth_token, oauth_token_secret), 'HTML')
local message_id = redis:hget(hash, 'login_msg')
utilities.edit_message(self, msg.chat.id, message_id, '*Anmeldung abgeschlossen!*', true, true)
redis:hdel(hash, 'login_msg')
@ -316,7 +334,7 @@ function twitter_send:action(msg, config, matches)
return
end
end
utilities.send_reply(self, msg, twitter_send:reset_twitter_auth(hash), true)
utilities.send_reply(self, msg, twitter_send:reset_twitter_auth(hash), 'HTML')
return
end
@ -334,11 +352,11 @@ function twitter_send:action(msg, config, matches)
utilities.send_reply(self, msg, '*Du darfst keine Tweets senden.* Entweder wurdest du noch gar nicht freigeschaltet oder ausgeschlossen.', true)
return
else
utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true)
utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), 'HTML')
return
end
else
utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true)
utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), 'HTML')
return
end
end

View File

@ -1,4 +1,4 @@
local twitter_user = {}
local twitter_user = {}
require "./miku/encoding"
@ -58,7 +58,7 @@ function twitter_user:resolve_url(url)
end
end
function twitter_user:action(msg)
function twitter_user:action(msg, config, matches)
local twitter_url = "https://api.twitter.com/1.1/users/show/"..matches[1]..".json"
local response_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url)
local response = json.decode(response_body)
@ -111,4 +111,4 @@ function twitter_user:action(msg)
end
end
return twitter_user
return twitter_user

View File

@ -7,7 +7,7 @@ venue.triggers = {
local apikey = cred_data.google_apikey
function venue:pre_process(msg, self)
if not msg.venue then return end -- Ignore
if not msg.venue then return msg end -- Ignore
local lat = msg.venue.location.latitude
local lng = msg.venue.location.longitude
@ -24,4 +24,4 @@ end
function venue:action(msg)
end
return venue
return venue

View File

@ -17,6 +17,10 @@ function weather:init(config)
"^/w$",
"^/w (.*)$"
}
weather.inline_triggers = {
"^w (.+)$",
"^w$"
}
weather.doc = [[*
]]..config.cmd_pat..[[wetter*: Wetter für deinen Wohnort _(/location set [Ort])_
*]]..config.cmd_pat..[[wetter* _<Ort>_: Wetter für diesen Ort
@ -42,10 +46,43 @@ function get_city_name(lat, lng)
return city
end
function weather:get_weather(lat, lng)
function get_city_coordinates(city, config)
local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat')
local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng')
if not lat and not lng then
print('Koordinaten nicht eingespeichert, frage Google...')
coords = utilities.get_coords(city, config)
lat = coords.lat
lng = coords.lon
end
if not lat and not lng then
return nil
end
redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat)
redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng)
return lat, lng
end
function weather:get_weather(lat, lng, is_inline)
print('Finde Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:weather:'..lat..','..lng)
if text then print('...aus dem Cache') return text end
local hash = 'telegram:cache:weather:'..lat..','..lng
local text = redis:hget(hash, 'text')
if text then
print('...aus dem Cache')
if is_inline then
local ttl = redis:ttl(hash)
local city = redis:hget(hash, 'city')
local temperature = redis:hget(hash, 'temperature')
local weather_icon = redis:hget(hash, 'weather_icon')
local condition = redis:hget(hash, 'condition')
return city, condition..' bei '..temperature..' °C', weather_icon, text, ttl
else
return text
end
end
local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=minutely,hourly,daily,alerts,flags'
@ -55,10 +92,10 @@ function weather:get_weather(lat, lng)
method = "GET",
sink = ltn12.sink.table(response_body)
}
local ok, response_code, response_headers, response_status_line = https.request(request_constructor)
local ok, response_code, response_headers = https.request(request_constructor)
if not ok then return nil end
local data = json.decode(table.concat(response_body))
local ttl = string.sub(response_headers["cache-control"], 9)
local ttl = tonumber(string.sub(response_headers["cache-control"], 9))
local weather = data.currently
@ -66,26 +103,28 @@ function weather:get_weather(lat, lng)
local temperature = string.gsub(round(weather.temperature, 1), "%.", ",")
local feelslike = string.gsub(round(weather.apparentTemperature, 1), "%.", ",")
local temp = '*Wetter in '..city..':*\n'..temperature..' °C'
local conditions = ' | '..weather.summary
if weather.icon == 'clear-day' then
local weather_summary = weather.summary
local conditions = ' | '..weather_summary
local weather_icon = weather.icon
if weather_icon == 'clear-day' then
conditions = conditions..' ☀️'
elseif weather.icon == 'clear-night' then
elseif weather_icon == 'clear-night' then
conditions = conditions..' 🌙'
elseif weather.icon == 'rain' then
elseif weather_icon == 'rain' then
conditions = conditions..' ☔️'
elseif weather.icon == 'snow' then
elseif weather_icon == 'snow' then
conditions = conditions..' ❄️'
elseif weather.icon == 'sleet' then
elseif weather_icon == 'sleet' then
conditions = conditions..' 🌨'
elseif weather.icon == 'wind' then
elseif weather_icon == 'wind' then
conditions = conditions..' 💨'
elseif weather.icon == 'fog' then
conditions = conditions..' 🌫'
elseif weather.icon == 'cloudy' then
elseif weather_icon == 'cloudy' then
conditions = conditions..' ☁️☁️'
elseif weather.icon == 'partly-cloudy-day' then
elseif weather_icon == 'partly-cloudy-day' then
conditions = conditions..' 🌤'
elseif weather.icon == 'partly-cloudy-night' then
elseif weather_icon == 'partly-cloudy-night' then
conditions = conditions..' 🌙☁️'
else
conditions = conditions..''
@ -98,8 +137,56 @@ function weather:get_weather(lat, lng)
text = text..'\n(gefühlt: '..feelslike..' °C)'
end
cache_data('weather', lat..','..lng, text, tonumber(ttl), 'key')
return text
print('Caching data...')
redis:hset(hash, 'city', city)
redis:hset(hash, 'temperature', temperature)
redis:hset(hash, 'weather_icon', weather_icon)
redis:hset(hash, 'condition', weather_summary)
redis:hset(hash, 'text', text)
redis:expire(hash, ttl)
if is_inline then
return city, weather_summary..' bei '..temperature..' °C', weather_icon, text, ttl
else
return text
end
end
function weather:inline_callback(inline_query, config, matches)
local user_id = inline_query.from.id
if matches[1] ~= 'w' then
city = matches[1]
is_personal = false
else
local set_location = get_location(user_id)
is_personal = true
if not set_location then
city = 'Berlin, Deutschland'
else
city = set_location
end
end
local lat, lng = get_city_coordinates(city, config)
if not lat and not lng then utilities.answer_inline_query(self, inline_query) return end
local title, description, icon, text, ttl = weather:get_weather(lat, lng, true)
if not title and not description and not icon and not text and not ttl then utilities.answer_inline_query(self, inline_query) return end
local text = text:gsub('\n', '\\n')
local thumb_url = 'https://anditest.perseus.uberspace.de/inlineQuerys/weather/'
if icon == 'clear-day' or icon == 'partly-cloudy-day' then
thumb_url = thumb_url..'day.jpg'
elseif icon == 'clear-night' or icon == 'partly-cloudy-night' then
thumb_url = thumb_url..'night.jpg'
elseif icon == 'rain' then
thumb_url = thumb_url..'rain.jpg'
elseif icon == 'snow' then
thumb_url = thumb_url..'snow.jpg'
else
thumb_url = thumb_url..'cloudy.jpg'
end
local results = '[{"type":"article","id":"19122006","title":"'..title..'","description":"'..description..'","thumb_url":"'..thumb_url..'","thumb_width":80,"thumb_height":80,"input_message_content":{"message_text":"'..text..'", "parse_mode":"Markdown"}}]'
utilities.answer_inline_query(self, inline_query, results, ttl, is_personal)
end
function weather:action(msg, config, matches)
@ -116,22 +203,11 @@ function weather:action(msg, config, matches)
end
end
local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat')
local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng')
local lat, lng = get_city_coordinates(city, config)
if not lat and not lng then
print('Koordinaten nicht eingespeichert, frage Google...')
coords = utilities.get_coords(city, config)
lat = coords.lat
lng = coords.lon
end
if not lat and not lng then
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
return
end
redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat)
redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng)
local text = weather:get_weather(lat, lng)
if not text then
@ -140,4 +216,4 @@ function weather:action(msg, config, matches)
utilities.send_reply(self, msg, text, true)
end
return weather
return weather

View File

@ -57,7 +57,7 @@ function wikipedia:getWikiServer(lang)
end
--[[
-- return decoded json table from Wikipedia
-- return decoded JSON table from Wikipedia
--]]
function wikipedia:loadPage(text, lang, intro, plain, is_search)
local request, sink = {}, {}
@ -107,7 +107,7 @@ function wikipedia:loadPage(text, lang, intro, plain, is_search)
end
-- extract intro passage in wiki page
function wikipedia:wikintro(text, lang)
function wikipedia:wikintro(text, lang, is_inline)
local text = decodetext(text)
local result = self:loadPage(text, lang, true, true)
@ -124,13 +124,28 @@ function wikipedia:wikintro(text, lang)
local lang = lang or "de"
local title = page.title
local title_enc = URL.escape(title)
return '*'..title.."*:\n"..utilities.md_escape(page.extract), '{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"https://'..lang..'.wikipedia.org/wiki/'..title_enc..'"}]]}'
if is_inline then
local result = '<b>'..title..'</b>:\n'..page.extract
local result = result:gsub('\n', '\\n')
local result = result:gsub('"', '\\"')
return title, result, '{"inline_keyboard":[[{"text":"Wikipedia aufrufen","url":"https://'..lang..'.wikipedia.org/wiki/'..title_enc..'"}]]}'
else
return '*'..title..'*:\n'..utilities.md_escape(page.extract), '{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"https://'..lang..'.wikipedia.org/wiki/'..title_enc..'"}]]}'
end
else
local text = text.." nicht gefunden"
return text
if is_inline then
return nil
else
local text = text.." nicht gefunden"
return text
end
end
else
return "Ein Fehler ist aufgetreten."
if is_inline then
return nil
else
return "Ein Fehler ist aufgetreten."
end
end
end
@ -165,17 +180,20 @@ function wikipedia:inline_callback(inline_query, config, matches)
lang = 'de'
query = matches[1]
end
local url = 'https://'..lang..'.wikipedia.org/w/api.php?action=query&list=search&srsearch='..URL.escape(query)..'&format=json&prop=extracts&srprop=snippet'
local res, code = https.request(url)
if code ~= 200 then return end
local search_url = 'https://'..lang..'.wikipedia.org/w/api.php?action=query&list=search&srsearch='..URL.escape(query)..'&format=json&prop=extracts&srprop=snippet&&srlimit=5'
local res, code = https.request(search_url)
if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end
local data = json.decode(res).query
if data.searchinfo.totalhits == 0 then return end
local results = '['
local id = 700
for num in pairs(data.search) do
local title = data.search[num].title
results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..title..'","description":"'..wikipedia:snip_snippet(data.search[num].snippet)..'","url":"https://'..lang..'.wikipedia.org/wiki/'..URL.escape(title)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/wiki/logo.jpg","thumb_width":95,"thumb_height":86,"input_message_content":{"message_text":"https://'..lang..'.wikipedia.org/wiki/'..URL.escape(title)..'","disable_web_page_preview":true}}'
local title, result, keyboard = wikipedia:wikintro(data.search[num].title, lang, true)
if not title or not result or not keyboard then utilities.answer_inline_query(self, inline_query) return end
results = results..'{"type":"article","id":"'..id..'","title":"'..title..'","description":"'..wikipedia:snip_snippet(data.search[num].snippet)..'","url":"https://'..lang..'.wikipedia.org/wiki/'..URL.escape(title)..'","hide_url":true,"thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/wiki/logo.jpg","thumb_width":95,"thumb_height":86,"reply_markup":'..keyboard..',"input_message_content":{"message_text":"'..result..'","parse_mode":"HTML"}}'
id = id+1
if num < #data.search then
results = results..','
end
@ -216,4 +234,4 @@ function wikipedia:action(msg, config, matches)
utilities.send_reply(self, msg, result, true, keyboard)
end
return wikipedia
return wikipedia

View File

@ -147,10 +147,10 @@ function youtube:inline_callback(inline_query, config, matches)
local query = matches[1]
local url = BASE_URL..'/search?part=snippet&key='..apikey..'&maxResults=10&type=video&q='..URL.escape(query)..'&fields=items(id(videoId),snippet(publishedAt,title,thumbnails,channelTitle))'
local res,code = https.request(url)
if code ~= 200 then return end
if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end
local data = json.decode(res)
if not data.items[1] then return end
if not data.items[1] then utilities.answer_inline_query(self, inline_query) return end
local video_ids = ""
-- We get all videoIds from search...
@ -170,6 +170,7 @@ function youtube:inline_callback(inline_query, config, matches)
if not video_results.items[1] then return end
local results = '['
local id = 800
for num in pairs(video_results.items) do
local video_url = 'https://www.youtube.com/watch?v='..video_results.items[num].id
local thumb_url = get_yt_thumbnail(video_results.items[num])
@ -195,7 +196,8 @@ function youtube:inline_callback(inline_query, config, matches)
local uploader = video_results.items[num].snippet.channelTitle
local description = uploader..', '..viewCount..' Views, '..readable_dur..likeCount..dislikeCount..commentCount
results = results..'{"type":"video","id":"'..math.random(100000000000000000)..'","video_url":"'..video_url..'","mime_type":"text/html","thumb_url":"'..thumb_url..'","title":"'..video_title..'","description":"'..description..'","video_duration":'..video_duration..',"input_message_content":{"message_text":"'..video_url..'"}}'
results = results..'{"type":"video","id":"'..id..'","video_url":"'..video_url..'","mime_type":"text/html","thumb_url":"'..thumb_url..'","title":"'..video_title..'","description":"'..description..'","video_duration":'..video_duration..',"input_message_content":{"message_text":"'..video_url..'"}}'
id = id+1
if num < #video_results.items then
results = results..','
end
@ -207,6 +209,10 @@ end
function youtube:action(msg, config, matches)
local yt_code = matches[1]
local data = get_yt_data(yt_code)
if not data then
utilities.send_reply(self, msg, config.errors.results)
return
end
send_youtube_data(data, msg, self)
return
end

View File

@ -4,34 +4,86 @@ local youtube_dl = {}
function youtube_dl:init(config)
youtube_dl.triggers = {
"^/(mp4) (https?://[%w-_%.%?%.:/%+=&]+)$",
"^/(mp3) (https?://[%w-_%.%?%.:/%+=&]+)$"
"^/(mp4) https?://w?w?w?%.?youtu.be/([A-Za-z0-9-_-]+)",
"^/(mp4) https?://w?w?w?m?%.?youtube.com/embed/([A-Za-z0-9-_-]+)",
"^/(mp4) https?://w?w?w?m?%.?youtube.com/watch%?v=([A-Za-z0-9-_-]+)",
"^/(mp3) https?://w?w?w?m?%.?youtu.be/([A-Za-z0-9-_-]+)",
"^/(mp3) https?://w?w?w?m?%.?youtube.com/embed/([A-Za-z0-9-_-]+)",
"^/(mp3) https?://w?w?w?m?%.?youtube.com/watch%?v=([A-Za-z0-9-_-]+)"
}
youtube_dl.doc = [[*
]]..config.cmd_pat..[[mp3* _<URL>_: Lädt Audio von [untersützten Seiten](https://rg3.github.io/youtube-dl/supportedsites.html)
*]]..config.cmd_pat..[[mp4* _<URL>_: Lädt Video von [untersützten Seiten](https://rg3.github.io/youtube-dl/supportedsites.html)
]]..config.cmd_pat..[[mp3* _<URL>_: Lädt Audio von YouTube
*]]..config.cmd_pat..[[mp4* _<URL>_: Lädt Video von YouTube
]]
end
youtube_dl.command = 'mp3 <URL>, /mp4 <URL>'
function youtube_dl:convert_video(link)
local output = io.popen('youtube-dl -f mp4 --max-filesize 49m -o "/tmp/%(title)s.%(ext)s" '..link):read('*all')
print(output)
if string.match(output, '.* File is larger .*') then
return 'TOOBIG'
function youtube_dl:get_availabe_formats(id, hash)
local ytdl_json = io.popen('youtube-dl -j https://www.youtube.com/watch/?v='..id):read('*all')
if not ytdl_json then return end
local data = json.decode(ytdl_json)
if not data then return nil end
local available_formats = {}
redis:hset(hash, 'duration', data.duration)
-- Building table with infos
for n=1, #data.formats do
local vid_format = data.formats[n].format
local format_num = vid_format:match('^(%d+) ')
local valid_nums = {['17'] = true, ['36'] = true, ['43'] = true, ['18'] = true, ['22'] = true}
if not vid_format:match('DASH') and valid_nums[format_num] then -- We don't want DASH videos!
local format_info = {}
format_info.format = format_num
local hash = hash..':'..format_num
if format_num == '17' then
format_info.pretty_format = '144p'
elseif format_num == '36' then
format_info.pretty_format = '180p'
elseif format_num == '43' then
format_info.pretty_format = '360p WebM'
elseif format_num == '18' then
format_info.pretty_format = '360p MP4'
elseif format_num == '22' then
format_info.pretty_format = '720p'
end
format_info.ext = data.formats[n].ext
local url = data.formats[n].url
local headers = get_http_header(url)
local full_url = headers.location
if not full_url then return end
local headers = get_http_header(full_url) -- first was for 302, this get's use the size
if headers.location then -- There are some videos where there is a "chain" of 302... repeat this, until we get the LAST url!
repeat
headers = get_http_header(headers.location)
until not headers.location
end
format_info.url = full_url
local size = tonumber(headers["content-length"])
format_info.size = size
format_info.pretty_size = string.gsub(tostring(round(size / 1048576, 2)), '%.', ',')..' MB' -- 1048576 = 1024*1024
available_formats[#available_formats+1] = format_info
redis:hset(hash, 'ext', format_info.ext)
redis:hset(hash, 'format', format_info.pretty_format)
redis:hset(hash, 'url', full_url)
redis:hset(hash, 'size', size)
redis:hset(hash, 'height', data.formats[n].height)
redis:hset(hash, 'width', data.formats[n].width)
redis:hset(hash, 'pretty_size', format_info.pretty_size)
redis:expire(hash, 7889400)
end
end
local video = string.match(output, '%[download%] Destination: /tmp/(.*).mp4')
if not video then
video = string.match(output, '%[download%] /tmp/(.*).mp4 has already been downloaded')
end
return '/tmp/'..video..'.mp4'
return available_formats
end
function youtube_dl:convert_audio(link)
local output = io.popen('youtube-dl --max-filesize 49m -o "/tmp/%(title)s.%(ext)s" --extract-audio --audio-format mp3 '..link):read('*all')
print(output)
function youtube_dl:convert_audio(id)
local output = io.popen('youtube-dl --max-filesize 49m -o "/tmp/%(title)s.%(ext)s" --extract-audio --audio-format mp3 https://www.youtube.com/watch/?v='..id):read('*all')
if string.match(output, '.* File is larger .*') then
return 'TOOBIG'
end
@ -39,25 +91,105 @@ function youtube_dl:convert_audio(link)
return '/tmp/'..audio..'.mp3'
end
function youtube_dl:action(msg, config)
local link = matches[2]
function youtube_dl:callback(callback, msg, self, config, input)
utilities.answer_callback_query(self, callback, 'Informationen werden verarbeitet...')
local video_id = input:match('(.+)@')
local vid_format = input:match('@(%d+)')
local hash = 'telegram:cache:youtube_dl:mp4:'..video_id
local format_hash = hash..':'..vid_format
if not redis:exists(format_hash) then
youtube_dl:get_availabe_formats(video_id, hash)
end
local keyboard = redis:hget(hash, 'keyboard')
local duration = redis:hget(hash, 'duration')
local format_info = redis:hgetall(format_hash)
local full_url = format_info.url
local width = format_info.width
local height = format_info.height
local ext = format_info.ext
local pretty_size = format_info.pretty_size
local size = tonumber(format_info.size)
local format = format_info.format
local file = format_info.file_id
if size > 52420000 then
utilities.edit_message(self, msg.chat.id, msg.message_id, '<a href="'..full_url..'">Direktlink zum Video</a> ('..format..', '..pretty_size..')', nil, 'HTML', keyboard)
return
end
utilities.edit_message(self, msg.chat.id, msg.message_id, '<b>Video wird hochgeladen</b>', nil, 'HTML')
utilities.send_typing(self, msg.chat.id, 'upload_video')
if not file then
file = download_to_file(full_url, video_id..'.'..ext)
end
if not file then return end
local result = utilities.send_video(self, msg.chat.id, file, '('..format..')', msg.message_id, duration, width, height)
utilities.edit_message(self, msg.chat.id, msg.message_id, '<a href="'..full_url..'">Direktlink zum Video</a> ('..format..', '..pretty_size..')', nil, 'HTML', keyboard)
if not result then return end
local file_id = result.result.video.file_id
redis:hset(format_hash, 'file_id', file_id)
end
function youtube_dl:action(msg, config, matches)
if msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'Dieses Plugin kann nur im Privatchat benutzt werden')
return
end
local id = matches[2]
if matches[1] == 'mp4' then
utilities.send_typing(self, msg.chat.id, 'upload_video')
local file = youtube_dl:convert_video(link)
if file == 'TOOBIG' then
utilities.send_reply(self, msg, 'Das Video überschreitet die Grenze von 50 MB!')
return
local hash = 'telegram:cache:youtube_dl:mp4:'..id
local first_msg = utilities.send_reply(self, msg, '<b>Verfügbare Videoformate werden ausgelesen...</b>', 'HTML')
local callback_keyboard = redis:hget(hash, 'keyboard')
if not callback_keyboard then
utilities.send_typing(self, msg.chat.id, 'typing')
local available_formats = youtube_dl:get_availabe_formats(id, hash)
if not available_formats then
utilities.edit_message(self, msg.chat.id, first_msg.result.message_id, config.errors.results)
return
end
local callback_buttons = {}
for n=1, #available_formats do
local video = available_formats[n]
local format = video.format
local size = video.size
local pretty_size = video.pretty_size
if size > 52420000 then
pretty_format = video.pretty_format..' ('..pretty_size..', nur Link)'
else
pretty_format = video.pretty_format..' ('..pretty_size..')'
end
local button = '{"text":"'..pretty_format..'","callback_data":"@'..self.info.username..' youtube_dl:'..id..'@'..format..'"}'
callback_buttons[#callback_buttons+1] = button
end
local keyboard = '{"inline_keyboard":['
for button in pairs(callback_buttons) do
keyboard = keyboard..'['..callback_buttons[button]..']'
if button < #callback_buttons then
keyboard = keyboard..','
end
end
callback_keyboard = keyboard..']}'
redis:hset(hash, 'keyboard', callback_keyboard)
redis:expire(hash, 7889400)
end
utilities.send_video(self, msg.chat.id, file, nil, msg.message_id)
return
utilities.edit_message(self, msg.chat.id, first_msg.result.message_id, 'Wähle die gewünschte Auflösung.', nil, nil, callback_keyboard)
return
end
if matches[1] == 'mp3' then
local first_msg = utilities.send_reply(self, msg, '<b>Audio wird heruntergeladen...</b>', 'HTML')
utilities.send_typing(self, msg.chat.id, 'upload_audio')
local file = youtube_dl:convert_audio(link)
local file = youtube_dl:convert_audio(id)
if file == 'TOOBIG' then
utilities.send_reply(self, msg, 'Die MP3 überschreitet die Grenze von 50 MB!')
utilities.edit_message(self, msg.chat.id, first_msg.result.message_id, '<b>Die MP3 überschreitet die Grenze von 50 MB!</b>', nil, 'HTML')
return
end
utilities.send_audio(self, msg.chat.id, file, msg.message_id)
@ -65,4 +197,4 @@ function youtube_dl:action(msg, config)
end
end
return youtube_dl
return youtube_dl

View File

@ -10,12 +10,11 @@ socket = require('socket')
URL = require('socket.url')
json = require('dkjson')
pcall(json.use_lpeg)
serpent = require("serpent")
bindings = require('miku.bindings')
redis = (loadfile "./miku/redis.lua")()
mimetype = (loadfile "./miku/mimetype.lua")()
OAuth = require "OAuth"
helpers = require "OAuth.helpers"
serpent = require('serpent')
redis = (loadfile './miku/redis.lua')()
mime = (loadfile './miku/mimetype.lua')()
OAuth = require 'OAuth'
helpers = require 'OAuth.helpers'
http.timeout = 5
https.timeout = 5
@ -23,52 +22,42 @@ https.timeout = 5
-- For the sake of ease to new contributors and familiarity to old contributors,
-- we'll provide a couple of aliases to real bindings here.
function utilities:send_message(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown, reply_markup)
if use_markdown == true then
use_markdown = 'Markdown'
elseif not use_markdown then
use_markdown = nil
local parse_mode
if type(use_markdown) == 'string' then
parse_mode = use_markdown
elseif use_markdown == true then
parse_mode = 'Markdown'
end
return bindings.request(self, 'sendMessage', {
chat_id = chat_id,
text = text,
disable_web_page_preview = disable_web_page_preview,
reply_to_message_id = reply_to_message_id,
parse_mode = use_markdown,
parse_mode = parse_mode,
reply_markup = reply_markup
} )
end
-- https://core.telegram.org/bots/api#editmessagetext
function utilities:edit_message(chat_id, message_id, text, disable_web_page_preview, use_markdown, reply_markup)
if use_markdown == true then
use_markdown = 'Markdown'
elseif not use_markdown then
use_markdown = nil
local parse_mode
if type(use_markdown) == 'string' then
parse_mode = use_markdown
elseif use_markdown == true then
parse_mode = 'Markdown'
end
return bindings.request(self, 'editMessageText', {
chat_id = chat_id,
message_id = message_id,
text = text,
disable_web_page_preview = disable_web_page_preview,
parse_mode = use_markdown,
parse_mode = parse_mode,
reply_markup = reply_markup
} )
end
function utilities:send_reply(old_msg, text, use_markdown, reply_markup)
if use_markdown == true then
use_markdown = 'Markdown'
elseif not use_markdown then
use_markdown = nil
end
return bindings.request(self, 'sendMessage', {
chat_id = old_msg.chat.id,
text = text,
disable_web_page_preview = true,
reply_to_message_id = old_msg.message_id,
parse_mode = use_markdown,
reply_markup = reply_markup
} )
return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown, reply_markup)
end
-- NOTE: Telegram currently only allows file uploads up to 50 MB
@ -225,23 +214,14 @@ end
-- get the indexed word in a string
function utilities.get_word(s, i)
s = s or ''
i = i or 1
local t = {}
for w in s:gmatch('%g+') do
table.insert(t, w)
end
return t[i] or false
end
-- Like get_word(), but better.
-- Returns the actual index.
function utilities.index(s)
local t = {}
for w in s:gmatch('%g+') do
table.insert(t, w)
end
return t
s = s or ''
i = i or 1
local n = 0
for w in s:gmatch('%g+') do
n = n + 1
if n == i then return w end
end
return false
end
-- Returns the string after the first space.
@ -252,6 +232,10 @@ function utilities.input(s)
return s:sub(s:find(' ')+1)
end
function utilities.input_from_msg(msg)
return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false
end
-- Calculates the length of the given string as UTF-8 characters
function utilities.utf8_len(s)
local chars = 0
@ -341,19 +325,19 @@ function vardump(value)
print(serpent.block(value, {comment=false}))
end
-- Loads a json file as a table.
-- Loads a JSON file as a table.
function utilities.load_data(filename)
local f = io.open(filename)
if not f then
if f then
local s = f:read('*all')
f:close()
return json.decode(s)
else
return {}
end
local s = f:read('*all')
f:close()
local data = json.decode(s)
return data
end
-- Saves a table to a json file.
-- Saves a table to a JSON file.
function utilities.save_data(filename, data)
local s = json.encode(data)
local f = io.open(filename, 'w')
@ -363,24 +347,22 @@ end
-- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua.
function utilities.get_coords(input, config)
local url = 'https://maps.googleapis.com/maps/api/geocode/json?address='..URL.escape(input)..'&language=de'
local jstr, res = https.request(url)
if res ~= 200 then
return config.errors.connection
end
local url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input)
local jstr, res = https.request(url)
if res ~= 200 then
return config.errors.connection
end
local jdat = json.decode(jstr)
if jdat.status == 'ZERO_RESULTS' then
return config.errors.results
end
return {
lat = jdat.results[1].geometry.location.lat,
lon = jdat.results[1].geometry.location.lng
}
local jdat = json.decode(jstr)
if jdat.status == 'ZERO_RESULTS' then
return config.errors.results
end
return {
lat = jdat.results[1].geometry.location.lat,
lon = jdat.results[1].geometry.location.lng,
addr = jdat.results[1].formatted_address
}
end
-- Get the number of values in a key/value table.
@ -415,91 +397,15 @@ function utilities:resolve_username(input)
end
end
-- Simpler than above function; only returns an ID.
-- Returns nil if no ID is available.
function utilities:id_from_username(input)
input = input:gsub('^@', '')
for _, user in pairs(self.database.users) do
if user.username and user.username:lower() == input:lower() then
return user.id
end
end
end
-- Simpler than below function; only returns an ID.
-- Returns nil if no ID is available.
function utilities:id_from_message(msg)
if msg.reply_to_message then
return msg.reply_to_message.from.id
else
local input = utilities.input(msg.text)
if input then
if tonumber(input) then
return tonumber(input)
elseif input:match('^@') then
return utilities.id_from_username(self, input)
end
end
end
end
function utilities:user_from_message(msg, no_extra)
local input = utilities.input(msg.text_lower)
local target = {}
if msg.reply_to_message then
for k,v in pairs(self.database.users[msg.reply_to_message.from.id_str]) do
target[k] = v
end
elseif input and tonumber(input) then
target.id = tonumber(input)
if self.database.users[input] then
for k,v in pairs(self.database.users[input]) do
target[k] = v
end
end
elseif input and input:match('^@') then
local uname = input:gsub('^@', '')
for _,v in pairs(self.database.users) do
if v.username and uname == v.username:lower() then
for key, val in pairs(v) do
target[key] = val
end
end
end
if not target.id then
target.err = 'Sorry, I don\'t recognize that username.'
end
else
target.err = 'Please specify a user via reply, ID, or username.'
end
if not no_extra then
if target.id then
target.id_str = tostring(target.id)
end
if not target.first_name then
target.first_name = 'User'
end
target.name = utilities.build_name(target.first_name, target.last_name)
end
return target
end
function utilities:handle_exception(err, message, config)
if not err then err = '' end
local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. self.info.username .. ': ' .. err .. '\n' .. message .. '\n'
if config.log_chat then
output = '```' .. output .. '```'
utilities.send_message(self, config.log_chat, output, true, nil, true)
else
print(output)
end
if not err then err = '' end
local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. self.info.username .. ': ' .. err .. '\n' .. message .. '\n'
if config.log_chat then
output = '```' .. output .. '```'
utilities.send_message(self, config.log_chat, output, true, nil, true)
else
print(output)
end
end
-- MOVED TO DOWNLOAD_TO_FILE
@ -507,15 +413,17 @@ function utilities.download_file(url, filename)
return download_to_file(url, filename)
end
function utilities.markdown_escape(text)
text = text:gsub('_', '\\_')
text = text:gsub('%[', '\\[')
text = text:gsub('%*', '\\*')
text = text:gsub('`', '\\`')
return text
function utilities.md_escape(text)
return text:gsub('_', '\\_')
:gsub('%[', '\\['):gsub('%]', '\\]')
:gsub('%*', '\\*'):gsub('`', '\\`')
end
utilities.md_escape = utilities.markdown_escape
utilities.markdown_escape = utilities.md_escape
function utilities.html_escape(text)
return text:gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;')
end
utilities.triggers_meta = {}
utilities.triggers_meta.__index = utilities.triggers_meta
@ -591,7 +499,8 @@ utilities.char = {
arabic = '[\216-\219][\128-\191]',
rtl_override = '',
rtl_mark = '',
em_dash = ''
em_dash = '',
utf_8 = '[%z\1-\127\194-\244][\128-\191]',
}
-- taken from http://stackoverflow.com/a/11130774/3163199
@ -610,7 +519,7 @@ function plugins_names()
for k, v in pairs(scandir("miku/plugins")) do
-- Ends with .lua
if (v:match(".lua$")) then
table.insert(files, v)
files[#files+1] = v
end
end
return files
@ -723,6 +632,7 @@ function post_petition(url, arguments, headers)
if type(arguments) == "table" then
source = helpers.url_encode_arguments(arguments)
end
if not headers then
request_constructor.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF8"
request_constructor.headers["X-Accept"] = "application/json"
@ -800,8 +710,8 @@ function get_location(user_id)
end
end
function cache_data(plugin, query, data, timeout, typ)
-- How to: cache_data(pluginname, query_name, data_to_cache, expire_in_seconds)
function cache_data(plugin, query, data, timeout, typ, hash_field)
-- How to: cache_data(pluginname, query_name, data_to_cache, expire_in_seconds, type, hash_field (if hash))
local hash = 'telegram:cache:'..plugin..':'..query
if timeout then
print('Caching "'..query..'" from plugin '..plugin..' (expires in '..timeout..' seconds)')
@ -819,7 +729,7 @@ function cache_data(plugin, query, data, timeout, typ)
redis:sadd(hash, str)
end
else
redis:hmset(hash, data)
redis:hset(hash, hash_field, data)
end
if timeout then
redis:expire(hash, timeout)
@ -841,6 +751,8 @@ function cache_file(result, url, last_modified)
elseif result.result.photo then
local lv = #result.result.photo
file_id = result.result.photo[lv].file_id
elseif result.result.sticker then
file_id = result.result.sticker.file_id
end
print('Caching File...')
redis:hset(hash..':'..url, 'file_id', file_id)
@ -935,7 +847,7 @@ function get_cached_file(url, file_name, receiver, chat_action, self)
return nil
end
end
if header["last-modified"] then
last_modified = header["last-modified"]
elseif header["Last-Modified"] then