Übernehme Änderungen von Brawl345/Brawlbot-v2

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

View File

@ -6,4 +6,5 @@ insert_final_newline = true
[*.lua]
charset = utf-8
indent_style = tab
indent_style = space
indent_size = 4

View File

@ -3,12 +3,12 @@
Der multifunktionale Telegram-Bot.
[Entwickler auf Telegram](http://telegram.me/Brawl) | [Offizieller Kanal](https://telegram.me/brawlbot_updates)
[Offizielle Webseite](https://brawlbot.tk) | [Entwickler auf Telegram](http://telegram.me/Brawl) | [Offizieller Kanal](https://telegram.me/brawlbot_updates)
Brawlbot ist ein auf Plugins basierender Bot, der die [offizielle Telegram Bot API](http://core.telegram.org/bots/api) benutzt. Ursprünglich wurde er im Dezember 2014 auf Basis von Yagops [Telegram Bot](https://github.com/yagop/telegram-bot/) entwickelt, da aber die Entwicklung von tg-cli [zum Stillstand](https://brawlbot.tk/posts/ein-neuanfang) gekommen ist, wurden alle Plugins des bisher proprietären Brawlbots im Juni 2016 auf die Bot-API portiert und open-sourced.
**Brawlbot v2 basiert auf [otouto](https://github.com/topkecleon/otouto) von Topkecleon.**
Brawlbot v2 ist freie Software; du darfst in modifizieren und weiterverbreiten, allerdings musst du dich an die GNU Affero General Public License v3 halten, siehe **LICENSE** für Details.
Brawlbot v2 ist freie Software; du darfst ihn modifizieren und weiterverbreiten, allerdings musst du dich an die GNU Affero General Public License v3 halten, siehe **LICENSE** für Details.
##Anleitung
@ -22,7 +22,7 @@ Brawlbot v2 ist freie Software; du darfst in modifizieren und weiterverbreiten,
# Für User
## Setup
### Ubuntu und Debian
Ubuntu und Debian liefern Luarocks nur für Lua 5.1 aus. Um Luarocks für Lua 5.2 zu verwenden, folge bitte der [Anleitung auf StackOverflow](http://stackoverflow.com/a/20359102)
Ubuntu und Debian liefern Luarocks nur für Lua 5.1 aus. Um Luarocks für Lua 5.2 zu verwenden, folge bitte der [Anleitung auf StackOverflow](http://stackoverflow.com/a/20359102).
### Setup
Du benötigst **Lua 5.2+**, eine aktive **Redis-Instanz** und die folgenden **LuaRocks-Module**:
@ -80,22 +80,24 @@ Brawlbot erhält laufend neue Plugins und wird kontinuierlich weiterentwickelt!
## Plugins
Brawlbot benutzt ein Plugin-System, ähnlich Yagops [Telegram-Bot](http://github.com/yagop/telegram-bot).
Ein Plugin kann fünf Komponenten haben, aber nur zwei werden benötigt:
Ein Plugin kann zehn Komponenten haben, aber nur zwei werden benötigt:
| Komponente | Beschreibung | Benötigt? |
|:------------------|:---------------------------------------------|:----------|
| `plugin:action` | Hauptfunktion. Benötigt `msg` als Argument, empfohlen wird auch `matches` als drittes Argument nach `config` | J |
| `plugin.triggers` | Tabelle von Triggern, (Lua-Patterns), auf die der Bot reagiert | J |
| `plugin.triggers` | Tabelle von Triggern (Lua-Patterns), auf die der Bot reagiert | J |
| `plugin.inline_triggers` | Tabelle von Triggern (Lua-Patterns), auf die der Bot bei Inline-Querys reagiert | N |
| `plugin:init` | Optionale Funkion, die beim Start geladen wird | N |
| `plugin:cron` | Wird jede Minute ausgeführt | N |
| `plugin.command` | Einfaches Kommando mit Syntax. Wird bei `/hilfe` gelistet | N |
| `plugin.doc` | Plugin-Hilfe. Wird mit `/help $kommando` gelistet | N |
| `plugin.error` | Plugin-spezifische Fehlermeldung | N |
| `plugin:callback` | Aktion, die ausgeführt wird, nachdem auf einen Callback-Button gedrückt wird. Siehe `gImages.lua` für ein Beispiel. Argumente: `callback` (enthält Callback-Daten), `msg`, `self`, `config`, `input` (enthält Parameter ohne `callback`) | N |
| `plugin:inline_callback` | Aktion, die ausgeführt wird, wenn der Bot per Inline-Query ausgelöst wird. Argumente sind `inline_query` für die Daten, `config` und `matches` | N |
Die`bot:on_msg_receive` Funktion fügt einige nützte Variablen zur ` msg` Tabelle hinzu. Diese sind:`msg.from.id_str`, `msg.to.id_str`, `msg.chat.id_str`, `msg.text_lower`, `msg.from.name`.
Rückgabewerte für `plugin:action` sind optional, aber wenn eine Tabelle zurückgegeben wird, wird diese die neue `msg`,-Tabelle und `on_msg_receive` wird damit fortfahren.
Interaktionen mit der Bot-API sind sehr einfach. Siehe [Bindings](#bindings) für Details.
Einige Funktionen, die oft benötigt werden, sind in `utilites.lua` verfügbar.
@ -186,5 +188,3 @@ Das ist die Datenbank-Struktur:
`database.userdata` speichert Daten von verschiedenen Plugins, hierzu wird aber für Brawlbot-Plugins Redis verwendet.
`database.version` speichert die Bot-Version.
* * *

View File

@ -14,7 +14,7 @@ return {
cli_port = 4567,
-- The block of text returned by /start.
about_text = [[
Dies ist die BETA-Version von Brawlbot v2.
Dies ist die BETA-Version von Mikubot v2.
Sende /hilfe, um zu starten
]],
@ -35,43 +35,20 @@ Sende /hilfe, um zu starten
syntax = 'Invalide Syntax.',
chatter_connection = 'Ich möchte gerade nicht reden',
chatter_response = 'Ich weiß nicht, was ich darauf antworten soll.'
}
},
plugins = { -- To enable a plugin, add its name to the list.
'control',
'blacklist',
'about',
'ping',
'whoami',
'nick',
'echo',
'imgblacklist',
'gImages',
'gSearch',
'gMaps',
'wikipedia',
'hackernews',
'imdb',
'calc',
'urbandictionary',
'time',
'dice',
'reddit',
'xkcd',
'slap',
'commit',
'pun',
'currency',
'shout',
'set',
'get',
'patterns',
'9gag',
'shell',
'adfly',
'twitter',
-- Put new plugins above this line.
'help'
remind = {
persist = true,
max_length = 1000,
max_duration = 526000,
max_reminders_group = 10,
max_reminders_private = 50
},
chatter = {
cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=',
connection = 'I don\'t feel like talking right now.',
response = 'I don\'t know what to say to that.'
}
}

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)

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,6 +70,7 @@ 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)
@ -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)
@ -136,26 +151,43 @@ function bot:process_inline_query(inline_query, config) -- When an inline query
-- 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.
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,52 +253,35 @@ 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)
local pattern = plugin.triggers[n]
local matches = match_pattern(pattern, msg.text)
if matches then
break;
end
end
print(plugin.name..' ausgelöst')
print('msg matches: ', pattern, ' for "'..plugin.name..'"')
return plugin.action(self, msg, config, matches)
end
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)
end
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
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
-- if one pattern matches, end
return
end
end
end
end
function is_plugin_disabled_on_chat(plugin_name, msg)
local hash = get_redis_hash(msg, 'disabled_plugins')
@ -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

View File

@ -5,33 +5,16 @@ local bot = require('miku.bot')
about.command = 'about'
about.doc = '`Sendet Informationen über den Bot.`'
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
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

View File

@ -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)
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
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
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
local user_id = msg.from.id
local chat_id = msg.chat.id
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
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
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')
@ -206,6 +245,28 @@ function banhammer:action(msg, config, matches)
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]
redis:set(hash, true)
@ -244,6 +305,46 @@ function banhammer:action(msg, config, matches)
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
return banhammer

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

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;", "ö")

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

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

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)
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)
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,23 +244,12 @@ 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')
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
local lat, lng = get_city_coordinates(city, config)
if not lat and not lng then
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)
else

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)

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,16 +19,23 @@ 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)
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, gMaps.doc, true, msg.message_id, true)
utilities.send_reply(self, msg, gMaps.doc, 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

View File

@ -51,15 +51,11 @@ 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)
utilities.send_reply(self, msg, gImages.doc, true)
return
end
end
local results, stats = gSearch:googlethat(input, onfig)
if results == '403' then

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]

View File

@ -5,40 +5,73 @@ 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)
local commandlist = {}
help_text = '*Verfügbare Befehle:*\n'..config.cmd_pat
function help:inline_callback(inline_query, config, matches)
local query = matches[1]
for _,plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
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
table.insert(commandlist, 'hilfe [Befehl]')
table.sort(commandlist)
help_text = help_text .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nParameter: <benötigt> [optional]'
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
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 commandlist = {}
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
commandlist[#commandlist+1] = 'hilfe [Befehl]'
table.sort(commandlist)
local help_text = help_text .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nParameter: <benötigt> [optional]'
local help_text = help_text:gsub('%[', '\\[')
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!.')
utilities.send_reply(self, msg, 'Ich habe dir die Hilfe privat gesendet!.')
end
return
end
for _,plugin in ipairs(self.plugins) do
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)

View File

@ -7,6 +7,11 @@ function id:init(config)
"^/id$",
"^/ids? (chat)$"
}
id.inline_triggers = {
"^id$"
}
id.doc = [[```Zeige dir deine ID und die IDs aller Gruppenmitglieder an.``]]
end
@ -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)

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 BASE_URL = 'https://www.omdbapi.com'
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
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
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
utilities.send_message(self, msg.chat.id, imdb.doc, true, msg.message_id, true)
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
end
local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input)
local jstr, res = http.request(url)
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 = '*' .. jdat.Title .. ' ('.. jdat.Year ..')* von '..jdat.Director..'\n'
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 .. '_' .. jdat.Plot .. '_\n'
output = output .. '[IMDb-Seite besuchen](http://imdb.com/title/' .. jdat.imdbID .. ')'
output = output..'<i>' .. jdat.Plot .. '</i>'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
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 = {}
function patterns:init(config)
patterns.command = 's/<Pattern>/<Ersetzung>'
patterns.triggers = {
'^/?s/.-/.-$'
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,18 +4,51 @@ 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(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_message(self, msg.chat.id, preview.doc, true, nil, true)
utilities.send_reply(self, msg, preview.doc, true)
return
end
@ -26,19 +59,18 @@ function preview:action(msg)
local res = http.request(input)
if not res then
utilities.send_reply(self, msg, 'Please provide a valid link.')
utilities.send_reply(self, msg, 'Bitte gebe einen validen Link an.')
return
end
if res:len() == 0 then
utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.')
utilities.send_reply(self, msg, 'Sorry, dieser Link lässt uns keine Vorschau erstellen.')
return
end
-- Invisible zero-width, non-joiner.
local output = '[](' .. input .. ')'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>'
utilities.send_message(self, msg.chat.id, output, false, nil, 'HTML')
end
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 results = '[{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"},'
local id = 600
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

View File

@ -7,87 +7,78 @@ 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
-- 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!')
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
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[msg.chat.id_str], reminder)
table.insert(self.database.reminders[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.'
output = 'Ich werde dich um *'..human_readable_time..' Uhr* erinnern.'
end
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)
end
else
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
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 ü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,31 +103,8 @@ 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

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)

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

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,9 +137,57 @@ function weather:get_weather(lat, lng)
text = text..'\n(gefühlt: '..feelslike..' °C)'
end
cache_data('weather', lat..','..lng, text, tonumber(ttl), 'key')
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)
local user_id = msg.from.id
@ -116,23 +203,12 @@ 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')
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
local lat, lng = get_city_coordinates(city, config)
if not lat and not lng then
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
text = 'Konnte das Wetter von dieser Stadt nicht bekommen.'

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,15 +124,30 @@ 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
if is_inline then
return nil
else
local text = text.." nicht gefunden"
return text
end
end
else
if is_inline then
return nil
else
return "Ein Fehler ist aufgetreten."
end
end
end
-- search for term in wiki
function wikipedia:wikisearch(text, lang)
@ -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

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
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'
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
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)
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
return available_formats
end
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
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!')
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.send_video(self, msg.chat.id, file, nil, msg.message_id)
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
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.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)

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
@ -227,21 +216,12 @@ end
function utilities.get_word(s, i)
s = s or ''
i = i or 1
local t = {}
local n = 0
for w in s:gmatch('%g+') do
table.insert(t, w)
n = n + 1
if n == i then return w end
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
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
return {}
end
if f then
local s = f:read('*all')
f:close()
local data = json.decode(s)
return data
return json.decode(s)
else
return {}
end
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,9 +347,7 @@ 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)
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
@ -378,9 +360,9 @@ function utilities.get_coords(input, config)
return {
lat = jdat.results[1].geometry.location.lat,
lon = jdat.results[1].geometry.location.lng
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
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)