Ü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] [*.lua]
charset = utf-8 charset = utf-8
indent_style = tab indent_style = space
indent_size = 4

View File

@ -3,12 +3,12 @@
Der multifunktionale Telegram-Bot. 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 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 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 ##Anleitung
@ -22,7 +22,7 @@ Brawlbot v2 ist freie Software; du darfst in modifizieren und weiterverbreiten,
# Für User # Für User
## Setup ## Setup
### Ubuntu und Debian ### 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 ### Setup
Du benötigst **Lua 5.2+**, eine aktive **Redis-Instanz** und die folgenden **LuaRocks-Module**: 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 ## Plugins
Brawlbot benutzt ein Plugin-System, ähnlich Yagops [Telegram-Bot](http://github.com/yagop/telegram-bot). 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? | | Komponente | Beschreibung | Benötigt? |
|:------------------|:---------------------------------------------|:----------| |:------------------|:---------------------------------------------|:----------|
| `plugin:action` | Hauptfunktion. Benötigt `msg` als Argument, empfohlen wird auch `matches` als drittes Argument nach `config` | J | | `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:init` | Optionale Funkion, die beim Start geladen wird | N |
| `plugin:cron` | Wird jede Minute ausgeführt | N | | `plugin:cron` | Wird jede Minute ausgeführt | N |
| `plugin.command` | Einfaches Kommando mit Syntax. Wird bei `/hilfe` gelistet | N | | `plugin.command` | Einfaches Kommando mit Syntax. Wird bei `/hilfe` gelistet | N |
| `plugin.doc` | Plugin-Hilfe. Wird mit `/help $kommando` gelistet | N | | `plugin.doc` | Plugin-Hilfe. Wird mit `/help $kommando` gelistet | N |
| `plugin.error` | Plugin-spezifische Fehlermeldung | 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`. 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. 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. Einige Funktionen, die oft benötigt werden, sind in `utilites.lua` verfügbar.
@ -185,6 +187,4 @@ Das ist die Datenbank-Struktur:
`database.userdata` speichert Daten von verschiedenen Plugins, hierzu wird aber für Brawlbot-Plugins Redis verwendet. `database.userdata` speichert Daten von verschiedenen Plugins, hierzu wird aber für Brawlbot-Plugins Redis verwendet.
`database.version` speichert die Bot-Version. `database.version` speichert die Bot-Version.
* * *

View File

@ -14,7 +14,7 @@ return {
cli_port = 4567, cli_port = 4567,
-- The block of text returned by /start. -- The block of text returned by /start.
about_text = [[ about_text = [[
Dies ist die BETA-Version von Brawlbot v2. Dies ist die BETA-Version von Mikubot v2.
Sende /hilfe, um zu starten Sende /hilfe, um zu starten
]], ]],
@ -35,43 +35,20 @@ Sende /hilfe, um zu starten
syntax = 'Invalide Syntax.', syntax = 'Invalide Syntax.',
chatter_connection = 'Ich möchte gerade nicht reden', chatter_connection = 'Ich möchte gerade nicht reden',
chatter_response = 'Ich weiß nicht, was ich darauf antworten soll.' chatter_response = 'Ich weiß nicht, was ich darauf antworten soll.'
} },
remind = {
persist = true,
max_length = 1000,
max_duration = 526000,
max_reminders_group = 10,
max_reminders_private = 50
},
plugins = { -- To enable a plugin, add its name to the list. chatter = {
'control', cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=',
'blacklist', connection = 'I don\'t feel like talking right now.',
'about', response = 'I don\'t know what to say to that.'
'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'
}
} }

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
while true; do while true; do
lua main.lua lua main.lua
echo 'Miku wurde gestoppt. ^C zum beenden.' echo 'Miku wurde gestoppt. ^C zum beenden.'
sleep 5s sleep 5s
done done

View File

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

View File

@ -3,20 +3,20 @@ local bot = {}
bindings = require('miku.bindings') bindings = require('miku.bindings')
utilities = require('miku.utilities') utilities = require('miku.utilities')
bot.version = '160801' bot.version = '160815'
function bot:init(config) -- The function run when the bot is started or reloaded. function bot:init(config) -- The function run when the bot is started or reloaded.
cred_data = load_cred() cred_data = load_cred()
assert( assert(
config.bot_api_key and config.bot_api_key ~= '', config.bot_api_key,
'You did not set your bot token in the config!' 'You did not set your bot token in the config!'
) )
self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/'
-- Fetch bot information. Try until it succeeds. -- Fetch bot information. Try until it succeeds.
repeat repeat
print('Fetching bot information...') print('Sammel Bot-Informationen...')
self.info = bindings.getMe(self) self.info = bindings.getMe(self)
until self.info until self.info
self.info = self.info.result 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') self.database = utilities.load_data(self.info.username..'.db')
end 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. self.plugins = {} -- Load plugins.
enabled_plugins = load_plugins() enabled_plugins = load_plugins()
for k,v in pairs(enabled_plugins) do for k,v in pairs(enabled_plugins) do
local p = require('miku.plugins.'..v) local p = require('miku.plugins.'..v)
-- print('loading plugin',v) -- print('loading plugin',v)
table.insert(self.plugins, p) self.plugins[k] = p
self.plugins[k].name = v self.plugins[k].name = v
if p.init then p.init(self, config) end if p.init then p.init(self, config) end
end end
print('Bot wurde erfolgreich gestartet!\n@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')') 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, -- Set loop variables
self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job, self.last_update = self.last_update or 0 -- Update offset.
self.last_database_save = self.last_database_save or os.date('%H') -- the time of the last database save, 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. self.is_started = true -- and whether or not the bot should be running.
end end
function bot:on_msg_receive(msg, config) -- The fn run whenever a message is received. 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. 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) msg = utilities.enrich_message(msg)
if msg.reply_to_message then if msg.reply_to_message then
@ -92,11 +70,12 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec
msg.text_lower = msg.text:lower() msg.text_lower = msg.text:lower()
end end
msg = pre_process_msg(self, msg, config) msg = pre_process_msg(self, msg, config)
if not msg then return end -- deleted by banning
if is_service_msg(msg) then if is_service_msg(msg) then
msg = service_modify_msg(msg) msg = service_modify_msg(msg)
end end
for _, plugin in ipairs(self.plugins) do for _, plugin in ipairs(self.plugins) do
match_plugins(self, msg, config, plugin) match_plugins(self, msg, config, plugin)
end end
@ -115,6 +94,41 @@ function bot:on_callback_receive(callback, msg, config) -- whenever a new callba
if not callback.data:find(':') or not callback.data:find('@'..self.info.username..' ') then if not callback.data:find(':') or not callback.data:find('@'..self.info.username..' ') then
return return
end 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..' ', "") callback.data = string.gsub(callback.data, '@'..self.info.username..' ', "")
local called_plugin = callback.data:match('(.*):.*') local called_plugin = callback.data:match('(.*):.*')
local param = callback.data:sub(callback.data:find(':')+1) 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) 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 plugin.name == called_plugin then
if is_plugin_disabled_on_chat(plugin.name, msg) then return end if is_plugin_disabled_on_chat(plugin.name, msg) then return end
plugin:callback(callback, msg, self, config, param) plugin:callback(callback, msg, self, config, param)
@ -135,27 +150,44 @@ end
function bot:process_inline_query(inline_query, config) -- When an inline query is received function bot:process_inline_query(inline_query, config) -- When an inline query is received
-- remove comment to enable debugging -- remove comment to enable debugging
-- vardump(inline_query) -- 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 if not config.enable_inline_for_everyone then
local is_whitelisted = redis:get('whitelist:user#id'..inline_query.from.id) 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 end
if inline_query.query:match('"') then if inline_query.query:match('"') then
inline_query.query = inline_query.query:gsub('"', '\\"') inline_query.query = inline_query.query:gsub('"', '\\"')
end 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) match_inline_plugins(self, inline_query, config, plugin)
end end
-- Stop the spinning circle
utilities.answer_inline_query(self, inline_query, nil, 0, true)
end end
function bot:run(config) 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
local res = bindings.getUpdates(self, { timeout=20, offset = self.last_update+1 } ) -- Update loop
local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update+1 } )
if res then 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 self.last_update = v.update_id
if v.inline_query then if v.inline_query then
bot.process_inline_query(self, v.inline_query, config) bot.process_inline_query(self, v.inline_query, config)
@ -169,10 +201,12 @@ function bot:run(config)
print('Connection error while fetching updates.') print('Connection error while fetching updates.')
end 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') self.last_cron = os.date('%M')
utilities.save_data(self.info.username..'.db', self.database) -- Save the database. 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. if v.cron then -- Call each plugin's cron function, if it has one.
local result, err = pcall(function() v.cron(self, config) end) local result, err = pcall(function() v.cron(self, config) end)
if not result then if not result then
@ -194,7 +228,8 @@ end
-- Apply plugin.pre_process function -- Apply plugin.pre_process function
function pre_process_msg(self, msg, config) 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 if plugin.pre_process and msg then
-- print('Preprocess '..plugin.name) -- remove comment to restore old behaviour -- print('Preprocess '..plugin.name) -- remove comment to restore old behaviour
new_msg = plugin:pre_process(msg, self, config) new_msg = plugin:pre_process(msg, self, config)
@ -204,7 +239,9 @@ function pre_process_msg(self, msg, config)
end end
function match_inline_plugins(self, inline_query, config, plugin) 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 if string.match(string.lower(inline_query.query), trigger) then
local success, result = pcall(function() local success, result = pcall(function()
for k, pattern in pairs(plugin.inline_triggers) do for k, pattern in pairs(plugin.inline_triggers) do
@ -216,50 +253,33 @@ function match_inline_plugins(self, inline_query, config, plugin)
print('Inline: '..plugin.name..' ausgelöst') print('Inline: '..plugin.name..' ausgelöst')
return plugin.inline_callback(self, inline_query, config, matches) return plugin.inline_callback(self, inline_query, config, matches)
end) end)
if not success then
print(result)
end end
end end
end
end end
function match_plugins(self, msg, config, plugin) 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 if string.match(msg.text_lower, trigger) then
-- Check if Plugin is disabled -- Check if Plugin is disabled
if is_plugin_disabled_on_chat(plugin.name, msg) then return end if is_plugin_disabled_on_chat(plugin.name, msg) then return end
local success, result = pcall(function() local success, result = pcall(function()
-- trying to port matches to miku -- trying to port matches to miku
for k, pattern in pairs(plugin.triggers) do local pattern = plugin.triggers[n]
matches = match_pattern(pattern, msg.text) local matches = match_pattern(pattern, msg.text)
if matches then if matches then
break; print('msg matches: ', pattern, ' for "'..plugin.name..'"')
end return plugin.action(self, msg, config, matches)
end end
print(plugin.name..' ausgelöst')
return plugin.action(self, msg, config, matches)
end) end)
if not success then if not success then
-- If the plugin has an error message, send it. If it does utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
-- not, use the generic one specified in config. If it's set return
-- 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 end
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) -- if one pattern matches, end
return return
end end
-- If the action returns a table, make that table the new msg.
if type(result) == 'table' then
msg = result
-- If the action returns true, continue.
elseif result ~= true then
return
end
end
end end
end end
@ -294,7 +314,7 @@ function create_plugin_set()
'banhammer', 'banhammer',
'channels', 'channels',
'plugins', 'plugins',
'help', 'help'
} }
print ('Aktiviere Plugins und speicher in telegram:enabled_plugins') print ('Aktiviere Plugins und speicher in telegram:enabled_plugins')
for _,plugin in pairs(enabled_plugins) do for _,plugin in pairs(enabled_plugins) do

View File

@ -31,13 +31,15 @@ end
function ninegag:inline_callback(inline_query, config) function ninegag:inline_callback(inline_query, config)
local res, code = http.request(url) 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 gag = json.decode(res)
local results = '[' local results = '['
local id = 50
for n in pairs(gag) do for n in pairs(gag) do
local title = gag[n].title:gsub('"', '\\"') 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 if n < #gag then
results = results..',' results = results..','
end end
@ -58,4 +60,4 @@ function ninegag:action(msg, config)
utilities.send_photo(self, msg.chat.id, file, title, msg.message_id, '{"inline_keyboard":[[{"text":"Post aufrufen","url":"'..post_url..'"}]]}') utilities.send_photo(self, msg.chat.id, file, title, msg.message_id, '{"inline_keyboard":[[{"text":"Post aufrufen","url":"'..post_url..'"}]]}')
end end
return ninegag return ninegag

View File

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

View File

@ -27,10 +27,10 @@ function adfly:inline_callback(inline_query, config, matches)
url = redis:get(hash) url = redis:get(hash)
end end
if not url then return end if not url then utilities.answer_inline_query(self, inline_query) return end
if url == 'NOTFOUND' then 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) utilities.answer_inline_query(self, inline_query, results, 3600, true)
end end
@ -54,4 +54,4 @@ function adfly:action(msg, config, matches)
end end
end end
return adfly return adfly

View File

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

View File

@ -36,11 +36,13 @@ function bImages:getImages(query)
local results = '[' local results = '['
local id = 300
for n in pairs(images) do for n in pairs(images) do
if images[n].encodingFormat == 'jpeg' then -- Inline-Querys MUST use JPEG photos! if images[n].encodingFormat == 'jpeg' then -- Inline-Querys MUST use JPEG photos!
local photo_url = images[n].contentUrl local photo_url = images[n].contentUrl
local thumb_url = images[n].thumbnailUrl 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
end end
@ -57,7 +59,7 @@ function bImages:inline_callback(inline_query, config, matches)
results = bImages:getImages(query) results = bImages:getImages(query)
end 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) utilities.answer_inline_query(self, inline_query, results, 3600)
end end

View File

@ -12,7 +12,17 @@ function banhammer:init(config)
"^/(whitelist) (delete) (chat)$", "^/(whitelist) (delete) (chat)$",
"^/(ban) (user) (%d+)$", "^/(ban) (user) (%d+)$",
"^/(ban) (delete) (%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 = [[* banhammer.doc = [[*
]]..config.cmd_pat..[[whitelist* _<enable>_/_<disable>_: Aktiviert/deaktiviert Whitelist ]]..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..[[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* 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..[[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 end
function banhammer:kick_user(user_id, chat_id, self, onlykick) 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) function banhammer:unban_user(user_id, chat_id, self, chat_type)
local hash = 'banned:'..chat_id..':'..user_id local hash = 'banned:'..chat_id..':'..user_id
redis:del(hash) redis:del(hash)
if chat_type == 'supergroup' then -- how can bots be admins anyway? if chat_type == 'supergroup' then
local request = bindings.request(self, 'unbanChatMember', { bindings.request(self, 'unbanChatMember', {
chat_id = chat_id, chat_id = chat_id,
user_id = user_id user_id = user_id
} ) } )
@ -96,76 +110,94 @@ function banhammer:pre_process(msg, self, config)
end end
-- BANNED USER TALKING -- BANNED USER TALKING
local user_id = msg.from.id
local chat_id = msg.chat.id
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
local user_id = msg.from.id
local chat_id = msg.chat.id
local banned = banhammer:is_banned(user_id, chat_id) local banned = banhammer:is_banned(user_id, chat_id)
if banned then if banned then
print('Banned user talking!') print('Banned user talking!')
banhammer:ban_user(user_id, chat_id, self) banhammer:ban_user(user_id, chat_id, self)
msg.text = '' return
end end
end end
-- BLOCKED USER TALKING (block = user can't use bot, but won't be kicked from group)
local hash = 'blocked:'..user_id
local issudo = is_sudo(msg, config)
local blocked = redis:get(hash)
if blocked and not issudo then
print('User '..user_id..' blocked')
return
end
-- WHITELIST -- WHITELIST
local hash = 'whitelist:enabled' local hash = 'whitelist:enabled'
local whitelist = redis:get(hash) local whitelist = redis:get(hash)
local issudo = is_sudo(msg, config)
-- Allow all sudo users even if whitelist is allowed -- Allow all sudo users even if whitelist is allowed
if whitelist and not issudo then if whitelist and not issudo then
print('Whitelist enabled and not sudo') print('Whitelist enabled and not sudo')
-- Check if user or chat is whitelisted -- Check if user or chat is whitelisted
local allowed = banhammer:is_user_whitelisted(msg.from.id) local allowed = banhammer:is_user_whitelisted(user_id)
local has_been_warned = redis:hget('user:'..msg.from.id, 'has_been_warned') local has_been_warned = redis:hget('user:'..user_id, 'has_been_warned')
if not allowed then 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 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 if not allowed then
print ('Chat '..msg.chat.id..' not whitelisted') print ('Chat '..chat_id..' not whitelisted')
else else
print ('Chat '..msg.chat.id..' whitelisted :)') print ('Chat '..chat_id..' whitelisted :)')
end end
else else
if not has_been_warned then 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.") 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 else
print('User has already been warned!') print('User has already been warned!')
end end
end end
else else
print('User '..msg.from.id..' allowed :)') print('User '..user_id..' allowed :)')
end end
if not allowed then if not allowed then
msg.text = '' return
msg.text_lower = ''
msg.entities = ''
end end
-- else
-- print('Whitelist not enabled or is sudo')
end end
return msg return msg
end end
function banhammer:action(msg, config, matches) 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) utilities.send_reply(self, msg, config.errors.sudo)
return return
end 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 if matches[1] == 'ban' then
local user_id = matches[3] local user_id = matches[3]
local chat_id = msg.chat.id 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 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) local text = banhammer:ban_user(user_id, chat_id, self)
utilities.send_reply(self, msg, text) utilities.send_reply(self, msg, text)
return return
@ -183,7 +215,14 @@ function banhammer:action(msg, config, matches)
if matches[1] == 'kick' then if matches[1] == 'kick' then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' 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 return
else else
utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe') utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe')
@ -205,6 +244,28 @@ function banhammer:action(msg, config, matches)
utilities.send_reply(self, msg, 'Whitelist deaktiviert') utilities.send_reply(self, msg, 'Whitelist deaktiviert')
return return
end 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 if matches[2] == 'user' then
local hash = 'whitelist:user#id'..matches[3] local hash = 'whitelist:user#id'..matches[3]
@ -243,6 +304,46 @@ function banhammer:action(msg, config, matches)
return return
end end
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
end end

View File

@ -38,9 +38,9 @@ function bitly:inline_callback(inline_query, config, matches)
url = data.long_url url = data.long_url
end 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) utilities.answer_inline_query(self, inline_query, results, 3600)
end end
@ -62,4 +62,4 @@ function bitly:action(msg, config, matches)
end end
end end
return bitly return bitly

View File

@ -5,7 +5,7 @@ cats.command = 'kitty [gif]'
function cats:init(config) function cats:init(config)
if not cred_data.cat_apikey then if not cred_data.cat_apikey then
print('Fehlender Key: cat_apikey.') 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 end
cats.triggers = { 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) function cats:inline_callback(inline_query, config, matches)
if matches[1] == 'gif' then if matches[1] == 'gif' then
img_type = 'gif' img_type = 'gif'
id = 100
else else
img_type = 'jpg' img_type = 'jpg'
id = 200
end 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 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) local res, code = https.request(url)
@ -43,9 +45,11 @@ function cats:inline_callback(inline_query, config, matches)
for n in pairs(data) do for n in pairs(data) do
if img_type == 'gif' then 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 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 end
if n < #data then if n < #data then
results = results..',' results = results..','

View File

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

View File

@ -4,57 +4,119 @@ currency.command = 'cash [Menge] <von> <zu>'
function currency:init(config) function currency:init(config)
currency.triggers = { currency.triggers = {
"^/[Cc][Aa][Ss][Hh] ([A-Za-z]+)$", "^/cash ([A-Za-z]+)$",
"^/[Cc][Aa][Ss][Hh] ([A-Za-z]+) ([A-Za-z]+)$", "^/cash ([A-Za-z]+) ([A-Za-z]+)$",
"^/[Cc][Aa][Ss][Hh] (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$", "^/cash (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$",
"^(/[Ee][Uu][Rr])$" "^(/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 = [[* currency.doc = [[*
]]..config.cmd_pat..[[cash* _[Menge]_ _<von>_ _<zu>_ ]]..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_]] Beispiel: _]]..config.cmd_pat..[[cash 5 USD EUR_]]
end end
function currency:action(msg, config) local BASE_URL = 'https://api.fixer.io'
if not matches[2] then
from = string.upper(matches[1]) function currency:inline_callback(inline_query, config, matches)
to = 'EUR' if not matches[2] then -- first pattern
base = 'EUR'
to = string.upper(matches[1])
amount = 1 amount = 1
elseif matches[3] then elseif matches[3] then -- third pattern
from = string.upper(matches[2]) base = string.upper(matches[2])
to = string.upper(matches[3]) to = string.upper(matches[3])
amount = matches[1] amount = matches[1]
else else -- second pattern
from = string.upper(matches[1]) base = string.upper(matches[1])
to = string.upper(matches[2]) to = string.upper(matches[2])
amount = 1 amount = 1
end 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, ",", ".") local amount = string.gsub(amount, ",", ".")
amount = tonumber(amount) local amount = tonumber(amount)
local result = 1 local res, code = https.request(url)
local BASE_URL = 'https://www.google.com/finance/converter' 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 if from == to then
utilities.send_reply(self, msg, 'Jaja, sehr witzig...') utilities.send_reply(self, msg, 'Jaja, sehr witzig...')
return return
end end
local url = BASE_URL..'?from='..from..'&to='..to..'&a='..amount local value = currency:convert_money(base, to, amount)
local str, res = https.request(url) if value == 'NOCONNECT' then
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection) utilities.send_reply(self, msg, config.errors.connection)
return return
end elseif value == 'WRONGBASE' then
utilities.send_reply(self, msg, 'Keine gültige Basiswährung.')
local str = str:match('<span class=bld>(.*) %u+</span>') return
if not str then elseif value == 'WRONGCONVERTRATE' 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) utilities.send_reply(self, msg, 'Keine gültige Umwandlungswährung.')
return return
end end
local result = string.format('%.2f', str)
local result = string.gsub(result, "%.", ",")
local amount = tostring(string.gsub(amount, "%.", ",")) local output = amount..' '..base..' = *'..value..' '..to..'*'
local output = amount..' '..from..' = *'..result..' '..to..'*'
utilities.send_reply(self, msg, output, true) utilities.send_reply(self, msg, output, true)
end end

View File

@ -18,15 +18,15 @@ function echo:inline_callback(inline_query, config, matches)
-- enable custom markdown button -- enable custom markdown button
if text:match('%[.*%]%(.*%)') or text:match('%*.*%*') or text:match('_.*_') or text:match('`.*`') then 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 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) utilities.answer_inline_query(self, inline_query, results, 0)
end end
function echo:action(msg) function echo:action(msg)
local input = utilities.input(msg.text) local input = utilities.input_from_msg(msg)
if not input then if not input then
utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true) utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true)
else else
@ -40,4 +40,4 @@ function echo:action(msg)
end end
end end
return echo return echo

View File

@ -26,7 +26,7 @@ function expand:inline_callback(inline_query, config, matches)
description = url description = url
end 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) utilities.answer_inline_query(self, inline_query, results, 3600)
end end
@ -55,4 +55,4 @@ function expand:action(msg, config, matches)
end end
end end
return expand return expand

View File

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

View File

@ -1,5 +1,7 @@
local forecast = {} local forecast = {}
require("./miku/plugins/weather")
function forecast:init(config) function forecast:init(config)
if not cred_data.forecastio_apikey then if not cred_data.forecastio_apikey then
print('Fehlender Key: forecastio_apikey.') print('Fehlender Key: forecastio_apikey.')
@ -21,6 +23,12 @@ function forecast:init(config)
"^(/forecasth)$", "^(/forecasth)$",
"^(/forecasth) (.*)$" "^(/forecasth) (.*)$"
} }
forecast.inline_triggers = {
"^(f) (.+)$",
"^(fh) (.+)$",
"^(fh)$",
"^(f)$"
}
forecast.doc = [[* forecast.doc = [[*
]]..config.cmd_pat..[[f*: Wettervorhersage für deinen Wohnort _(/location set <Ort>)_ ]]..config.cmd_pat..[[f*: Wettervorhersage für deinen Wohnort _(/location set <Ort>)_
*]]..config.cmd_pat..[[f* _<Ort>_: Wettervorhersage für diesen 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 apikey = cred_data.forecastio_apikey
local google_apikey = cred_data.google_apikey local google_apikey = cred_data.google_apikey
function get_city_name(lat, lng) function forecast:get_condition_symbol(weather_data)
local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng) if weather_data.icon == 'clear-day' then
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
return '☀️' return '☀️'
elseif weather.data[n].icon == 'clear-night' then elseif weather_data.icon == 'clear-night' then
return '🌙' return '🌙'
elseif weather.data[n].icon == 'rain' then elseif weather_data.icon == 'rain' then
return '☔️' return '☔️'
elseif weather.data[n].icon == 'snow' then elseif weather_data.icon == 'snow' then
return '❄️' return '❄️'
elseif weather.data[n].icon == 'sleet' then elseif weather_data.icon == 'sleet' then
return '🌨' return '🌨'
elseif weather.data[n].icon == 'wind' then elseif weather_data.icon == 'wind' then
return '💨' return '💨'
elseif weather.data[n].icon == 'fog' then elseif weather_data.icon == 'fog' then
return '🌫' return '🌫'
elseif weather.data[n].icon == 'cloudy' then elseif weather_data.icon == 'cloudy' then
return '☁️☁️' return '☁️☁️'
elseif weather.data[n].icon == 'partly-cloudy-day' then elseif weather_data.icon == 'partly-cloudy-day' then
return '🌤' return '🌤'
elseif weather.data[n].icon == 'partly-cloudy-night' then elseif weather_data.icon == 'partly-cloudy-night' then
return '🌙☁️' return '🌙☁️'
else else
return '' return ''
@ -75,22 +70,34 @@ function get_condition_symbol(weather, n)
end end
function get_temp(weather, n, hourly) function get_temp(weather, n, hourly)
local weather_data = weather.data[n]
if hourly then if hourly then
local temperature = string.gsub(round(weather.data[n].temperature, 1), "%.", ",") local temperature = string.gsub(round(weather_data.temperature, 1), "%.", ",")
local condition = weather.data[n].summary local condition = weather_data.summary
return temperature..'°C | '..get_condition_symbol(weather, n)..' '..condition return temperature..'°C | '..forecast:get_condition_symbol(weather_data)..' '..condition
else else
local day = string.gsub(round(weather.data[n].temperatureMax, 1), "%.", ",") local day = string.gsub(round(weather_data.temperatureMax, 1), "%.", ",")
local night = string.gsub(round(weather.data[n].temperatureMin, 1), "%.", ",") local night = string.gsub(round(weather_data.temperatureMin, 1), "%.", ",")
local condition = weather.data[n].summary local condition = weather_data.summary
return '☀️ '..day..'°C | 🌙 '..night..'°C | '..get_condition_symbol(weather, n)..' '..condition return '☀️ '..day..'°C | 🌙 '..night..'°C | '..forecast:get_condition_symbol(weather_data)..' '..condition
end end
end end
function forecast:get_forecast(lat, lng) function forecast:get_forecast(lat, lng, is_inline)
print('Finde Wetter in '..lat..', '..lng) print('Finde Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:forecast:'..lat..','..lng) local hash = 'telegram:cache:forecast:'..lat..','..lng
if text then print('...aus dem Cache..') return text end 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' 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", method = "GET",
sink = ltn12.sink.table(response_body) 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 if not ok then return nil end
local data = json.decode(table.concat(response_body)) local weather = json.decode(table.concat(response_body)).daily
local ttl = string.sub(response_headers["cache-control"], 9) local ttl = tonumber(string.sub(response_headers["cache-control"], 9))
local weather = data.daily
local city = get_city_name(lat, lng) 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 = '*Heute:* '..get_temp(weather, 1)
local text = text..'\n*Morgen:* '..get_temp(weather, 2) 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 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
end end
local text = string.gsub(text, "Mon", "Mo") local text = text:gsub("Mon", "Mo")
local text = string.gsub(text, "Tue", "Di") local text = text:gsub("Tue", "Di")
local text = string.gsub(text, "Wed", "Mi") local text = text:gsub("Wed", "Mi")
local text = string.gsub(text, "Thu", "Do") local text = text:gsub("Thu", "Do")
local text = string.gsub(text, "Fri", "Fr") local text = text:gsub("Fri", "Fr")
local text = string.gsub(text, "Sat", "Sa") local text = text:gsub("Sat", "Sa")
local text = string.gsub(text, "Sun", "So") local text = text:gsub("Sun", "So")
cache_data('forecast', lat..','..lng, header..text, tonumber(ttl), 'key') print('Caching data...')
redis:hset(hash, 'city', city)
redis:hset(hash, 'summary', weather_summary)
redis:hset(hash, 'text', header..text)
redis:expire(hash, ttl)
return header..text if is_inline then
return city, weather_summary, header..text, ttl
else
return header..text
end
end 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) print('Finde stündliches Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:forecast:'..lat..','..lng..':hourly') local hash = 'telegram:cache:forecast:'..lat..','..lng..':hourly'
if text then print('...aus dem Cache..') return text end 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' 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", method = "GET",
sink = ltn12.sink.table(response_body) 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 if not ok then return nil end
local data = json.decode(table.concat(response_body)) local weather = json.decode(table.concat(response_body)).hourly
local ttl = string.sub(response_headers["cache-control"], 9) local ttl = tonumber(string.sub(response_headers["cache-control"], 9))
local weather = data.hourly
local city = get_city_name(lat, lng) 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 = "" 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 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
end end
cache_data('forecast', lat..','..lng..':hourly', header..text, tonumber(ttl), 'key') print('Caching data...')
redis:hset(hash, 'city', city)
redis:hset(hash, 'summary', weather_summary)
redis:hset(hash, 'text', header..text)
redis:expire(hash, ttl)
return header..text if is_inline then
return city, weather_summary, header..text, ttl
else
return header..text
end
end
function forecast:inline_callback(inline_query, config, matches)
local user_id = inline_query.from.id
if matches[2] then
city = matches[2]
is_personal = false
else
local set_location = get_location(user_id)
is_personal = true
if not set_location then
city = 'Berlin, Deutschland'
else
city = set_location
end
end
local lat, lng = get_city_coordinates(city, config)
if not lat and not lng then utilities.answer_inline_query(self, inline_query) return end
if matches[1] == 'f' then
title, description, text, ttl = forecast:get_forecast(lat, lng, true)
else
title, description, text, ttl = forecast:get_forecast_hourly(lat, lng, true)
end
if not title and not description and not text and not ttl then utilities.answer_inline_query(self, inline_query) return end
local text = text:gsub('\n', '\\n')
local results = '[{"type":"article","id":"28062013","title":"'..title..'","description":"'..description..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/weather/cloudy.jpg","thumb_width":80,"thumb_height":80,"input_message_content":{"message_text":"'..text..'", "parse_mode":"Markdown"}}]'
utilities.answer_inline_query(self, inline_query, results, ttl, is_personal)
end end
function forecast:action(msg, config, matches) function forecast:action(msg, config, matches)
local user_id = msg.from.id local user_id = msg.from.id
local city = get_location(user_id)
if matches[2] then if matches[2] then
city = matches[2] city = matches[2]
@ -184,22 +244,11 @@ function forecast:action(msg, config, matches)
end end
end end
local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') local lat, lng = get_city_coordinates(city, config)
local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng')
if not lat and not lng then if not lat and not lng then
print('Koordinaten nicht eingespeichert, frage Google...') utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
coords = utilities.get_coords(city, config)
lat = coords.lat
lng = coords.lon
end
if not lat and not lng then
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
return return
end 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 if matches[1] == '/forecasth' or matches[1] == '/fh' then
text = forecast:get_forecast_hourly(lat, lng) text = forecast:get_forecast_hourly(lat, lng)
@ -212,4 +261,4 @@ function forecast:action(msg, config, matches)
utilities.send_reply(self, msg, text, true) utilities.send_reply(self, msg, text, true)
end end
return forecast return forecast

View File

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

View File

@ -131,7 +131,7 @@ end
function gImages_nsfw:cache_result(results, text) function gImages_nsfw:cache_result(results, text)
local cache = {} local cache = {}
for v in pairs(results) do for v in pairs(results) do
table.insert(cache, results[v].link) cache[v] = results[v].link
end end
for n, link in pairs(cache) do for n, link in pairs(cache) do
redis:hset('telegram:cache:gImages_nsfw:'..link, 'mime', results[n].mime) 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) function gMaps:init(config)
gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Ll][Oo][Cc]', true).table gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Ll][Oo][Cc]', true).table
gMaps.inline_triggers = {
"^[Ll][Oo][Cc] (.+)"
}
gMaps.doc = [[* gMaps.doc = [[*
]]..config.cmd_pat..[[loc* _<Ort>_: Sendet Ort via Google Maps]] ]]..config.cmd_pat..[[loc* _<Ort>_: Sendet Ort via Google Maps]]
end end
@ -16,25 +19,32 @@ function gMaps:get_staticmap(area, lat, lon)
return file return file
end 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) function gMaps:action(msg, config)
local input = utilities.input(msg.text) local input = utilities.input_from_msg(msg)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then utilities.send_reply(self, msg, gMaps.doc, true)
input = msg.reply_to_message.text return
else end
utilities.send_message(self, msg.chat.id, gMaps.doc, true, msg.message_id, true)
return utilities.send_typing(self, msg.chat.id, 'find_location')
end local coords = utilities.get_coords(input, config)
end if type(coords) == 'string' then
utilities.send_typing(self, msg.chat.id, 'find_location') utilities.send_reply(self, msg, coords)
local coords = utilities.get_coords(input, config) return
if type(coords) == 'string' then end
utilities.send_reply(self, msg, coords)
return
end
utilities.send_location(self, msg.chat.id, coords.lat, coords.lon, msg.message_id) utilities.send_location(self, msg.chat.id, coords.lat, coords.lon, msg.message_id)
utilities.send_photo(self, msg.chat.id, gMaps:get_staticmap(input, coords.lat, coords.lon), nil, msg.message_id) utilities.send_photo(self, msg.chat.id, gMaps:get_staticmap(input, coords.lat, coords.lon), nil, msg.message_id)
end end
return gMaps return gMaps

View File

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

View File

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

View File

@ -30,12 +30,14 @@ function giphy:inline_callback(inline_query, config, matches)
else else
data = giphy:get_gifs(matches[2]) data = giphy:get_gifs(matches[2])
end end
if not data then return end if not data then utilities.answer_inline_query(self, inline_query) return end
if not data[1] then return end if not data[1] then utilities.answer_inline_query(self, inline_query) return end
local results = '[' local results = '['
local id = 450
for n in pairs(data) do 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 if n < #data then
results = results..',' results = results..','
end end

View File

@ -25,9 +25,9 @@ end
function googl:inline_callback(inline_query, config, matches) function googl:inline_callback(inline_query, config, matches)
local shorturl = matches[1] local shorturl = matches[1]
local text, longUrl = googl:send_googl_info(shorturl) 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) utilities.answer_inline_query(self, inline_query, results, 1)
end end

View File

@ -5,6 +5,13 @@ gps.command = 'gps <Breitengrad>,<Längengrad>'
function gps:init(config) function gps:init(config)
gps.triggers = { gps.triggers = {
"^/[Gg][Pp][Ss] ([^,]*)[,%s]([^,]*)$", "^/[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.de/maps/@([^,]*)[,%s]([^,]*)",
"google.com/maps/@([^,]*)[,%s]([^,]*)", "google.com/maps/@([^,]*)[,%s]([^,]*)",
"google.de/maps/place/@([^,]*)[,%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]] ]]..config.cmd_pat..[[gps* _<Breitengrad>_,_<Längengrad>_: Sendet Karte mit diesen Koordinaten]]
end 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) function gps:action(msg, config, matches)
utilities.send_typing(self, msg.chat.id, 'upload_photo') utilities.send_typing(self, msg.chat.id, 'upload_photo')
local lat = matches[1] local lat = matches[1]
@ -32,4 +48,4 @@ function gps:action(msg, config, matches)
utilities.send_location(self, msg.chat.id, lat, lon, msg.message_id) utilities.send_location(self, msg.chat.id, lat, lon, msg.message_id)
end end
return gps return gps

View File

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

View File

@ -3,11 +3,16 @@ local id = {}
id.command = 'id' id.command = 'id'
function id:init(config) function id:init(config)
id.triggers = { id.triggers = {
"^/id$", "^/id$",
"^/ids? (chat)$" "^/ids? (chat)$"
} }
id.doc = [[```Zeige dir deine ID und die IDs aller Gruppenmitglieder an.``]]
id.inline_triggers = {
"^id$"
}
id.doc = [[```Zeige dir deine ID und die IDs aller Gruppenmitglieder an.``]]
end end
function id:get_member_count(self, msg, chat_id) function id:get_member_count(self, msg, chat_id)
@ -41,7 +46,15 @@ function id:get_user(user_id, chat_id)
return user_info return user_info
end 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 matches[1] == "/id" then
if msg.reply_to_message then if msg.reply_to_message then
@ -86,7 +99,7 @@ function id:action(msg)
for i = 1, #users do for i = 1, #users do
local user_id = users[i] local user_id = users[i]
local user_info = id:get_user(user_id, chat_id) local user_info = id:get_user(user_id, chat_id)
table.insert(users_info, user_info) users_info[#users_info+1] = user_info
end end
-- get all administrators and the creator -- get all administrators and the creator
@ -94,7 +107,7 @@ function id:action(msg)
local admins = {} local admins = {}
for num in pairs(administrators.result) do for num in pairs(administrators.result) do
if administrators.result[num].status ~= 'creator' then 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 else
creator_id = administrators.result[num].user.id creator_id = administrators.result[num].user.id
end end

View File

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

View File

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

View File

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

View File

@ -1,8 +1,11 @@
local patterns = {} local patterns = {}
patterns.triggers = { function patterns:init(config)
'^/?s/.-/.-$' patterns.command = 's/<Pattern>/<Ersetzung>'
} patterns.triggers = {
config.cmd_pat .. '?s/.-/.-$'
}
end
function patterns:action(msg) function patterns:action(msg)
if not msg.reply_to_message then return true end 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) function plugin_manager:init(config)
plugin_manager.triggers = { plugin_manager.triggers = {
"^/plugins$", "^/plugins$",
"^/plugins? (enable) ([%w_%.%-]+) (chat) (%d+)$",
"^/plugins? (enable) ([%w_%.%-]+) (chat)$",
"^/plugins? (disable) ([%w_%.%-]+) (chat) (%d+)$",
"^/plugins? (disable) ([%w_%.%-]+) (chat)$",
"^/plugins? (enable) ([%w_%.%-]+)$", "^/plugins? (enable) ([%w_%.%-]+)$",
"^/plugins? (disable) ([%w_%.%-]+)$", "^/plugins? (disable) ([%w_%.%-]+)$",
"^/plugins? (enable) ([%w_%.%-]+) (chat) (%d+)",
"^/plugins? (enable) ([%w_%.%-]+) (chat)",
"^/plugins? (disable) ([%w_%.%-]+) (chat) (%d+)",
"^/plugins? (disable) ([%w_%.%-]+) (chat)",
"^/plugins? (reload)$", "^/plugins? (reload)$",
"^/(reload)$" "^/(reload)$"
} }

View File

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

View File

@ -4,41 +4,73 @@ preview.command = 'preview <link>'
function preview:init(config) function preview:init(config)
preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table
preview.doc = [[``` preview.inline_triggers = {
]]..config.cmd_pat..[[preview <link> "^pr (https?://[%w-_%.%?%.:/%+=&%~%%#]+)$"
Returns a full-message, "unlinked" preview. }
```]] 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 end
function preview:action(msg) function preview:action(msg)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, preview.doc, true)
return
end
local input = utilities.input(msg.text) input = utilities.get_word(input, 1)
if not input:match('^https?://.+') then
input = 'http://' .. input
end
if not input then local res = http.request(input)
utilities.send_message(self, msg.chat.id, preview.doc, true, nil, true) if not res then
return utilities.send_reply(self, msg, 'Bitte gebe einen validen Link an.')
end return
end
input = utilities.get_word(input, 1) if res:len() == 0 then
if not input:match('^https?://.+') then utilities.send_reply(self, msg, 'Sorry, dieser Link lässt uns keine Vorschau erstellen.')
input = 'http://' .. input return
end end
local res = http.request(input)
if not res then
utilities.send_reply(self, msg, 'Please provide a valid link.')
return
end
if res:len() == 0 then
utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.')
return
end
-- Invisible zero-width, non-joiner.
local output = '[](' .. input .. ')'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
-- Invisible zero-width, non-joiner.
local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>'
utilities.send_message(self, msg.chat.id, output, false, nil, 'HTML')
end end
return preview return preview

View File

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

View File

@ -7,88 +7,79 @@ function remind:init(config)
remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table
remind.doc = [[* 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 end
function remind:action(msg) function remind:action(msg, config)
-- Ensure there are arguments. If not, send doc.
local input = utilities.input(msg.text) local input = utilities.input(msg.text)
if not input then 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 return
end end
-- Ensure first arg is a number. If not, send doc. local duration = tonumber(utilities.get_word(input, 1))
local duration = utilities.get_word(input, 1) if not duration then
if not tonumber(duration) then utilities.send_reply(self, msg, remind.doc, true)
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
return return
end end
-- Duration must be between one minute and one day (approximately).
duration = tonumber(duration)
if duration < 1 then if duration < 1 then
duration = 1 duration = 1
elseif duration > 1440 then elseif duration > config.remind.max_duration then
duration = 1440 duration = config.remind.max_duration
end end
-- Ensure there is a second arg.
local message = utilities.input(input) local message = utilities.input(input)
if not message then if not message then
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true) utilities.send_reply(self, msg, remind.doc, true)
return
end
if #message > config.remind.max_length then
utilities.send_reply(self, msg, 'Die maximale Länge einer Erinnerung ist ' .. config.remind.max_length .. '.')
return return
end end
-- Make a database entry for the group/user if one does not exist. local chat_id_str = tostring(msg.chat.id)
self.database.reminders[msg.chat.id_str] = self.database.reminders[msg.chat.id_str] or {} local output
-- Limit group reminders to 10 and private reminders to 50. 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[msg.chat.id_str]) > 9 then if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then
utilities.send_reply(self, msg, 'Diese Gruppe hat schon zehn Erinnerungen!') output = 'Sorry, du kannst keine Erinnerungen mehr hinzufügen.'
return elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then
elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then output = 'Sorry, diese Gruppe kann keine Erinnerungen mehr hinzufügen.'
utilities.send_reply(msg, 'Du hast schon 50 Erinnerungen!') else
return -- Put together the reminder with the expiration, message, and message to reply to.
local timestamp = os.time() + duration * 60
local reminder = {
time = timestamp,
message = message
}
table.insert(self.database.reminders[chat_id_str], reminder)
local human_readable_time = convert_timestamp(timestamp, '%H:%M:%S')
output = 'Ich werde dich um *'..human_readable_time..' Uhr* erinnern.'
end end
-- Put together the reminder with the expiration, message, and message to reply to.
local timestamp = os.time() + duration * 60
local reminder = {
time = timestamp,
message = message
}
table.insert(self.database.reminders[msg.chat.id_str], reminder)
local human_readable_time = convert_timestamp(timestamp, '%H:%M:%S')
local output = 'Ich werde dich um *'..human_readable_time..' Uhr* erinnern.'
utilities.send_reply(self, msg, output, true) utilities.send_reply(self, msg, output, true)
end end
function remind:cron() function remind:cron(config)
local time = os.time() local time = os.time()
-- Iterate over the group entries in the reminders database. -- Iterate over the group entries in the reminders database.
for chat_id, group in pairs(self.database.reminders) do for chat_id, group in pairs(self.database.reminders) do
local new_group = {}
-- Iterate over each reminder. -- 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. -- If the reminder is past-due, send it and nullify it.
-- Otherwise, add it to the replacement table. -- Otherwise, add it to the replacement table.
if time > reminder.time then if time > reminder.time then
local output = '*ERINNERUNG:*\n"' .. utilities.md_escape(reminder.message) .. '"' local output = '*ERINNERUNG:*\n"' .. utilities.md_escape(reminder.message) .. '"'
local res = utilities.send_message(self, chat_id, output, true, nil, true) local res = utilities.send_message(self, chat_id, output, true, nil, true)
-- If the message fails to send, save it for later. -- If the message fails to send, save it for later (if enabled in config).
if not res then if res or not config.remind.persist then
table.insert(new_group, reminder) group[k] = nil
end end
else
table.insert(new_group, reminder)
end end
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
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 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 = '¯\\\\\\_(ツ)_/¯' face = '¯\\\\\\_(ツ)_/¯'
end 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) utilities.answer_inline_query(self, inline_query, results, 9999)
end end
function respond:action(msg, config, matches) function respond:action(msg, config, matches)
local user_name = get_name(msg) local user_name = get_name(msg)
local receiver = msg.chat.id 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 user_name == "DefenderX" then user_name = "Deffu" end
if string.match(msg.text, "[Ff][Gg][Tt].? [Ss][Ww][Ii][Ff][Tt]") then 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, '🐸🐸🐸') utilities.send_message(self, receiver, '🐸🐸🐸')
return return
elseif string.match(msg.text, "[Ii][Nn][Ll][Oo][Vv][Ee]") then 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) utilities.send_document(self, receiver, file)
return return
elseif string.match(msg.text, "[Ww][Aa][Tt]") then elseif string.match(msg.text, "[Ww][Aa][Tt]") then
local WAT_URL = GDRIVE_URL..'/wat' local WAT_URL = BASE_URL..'/wat'
local wats = { local wats = {
"/wat1.jpg", "/wat1.jpg",
"/wat2.jpg", "/wat2.jpg",

View File

@ -13,7 +13,7 @@ function rss:init(config)
"^/rss (sub) (https?://[%w-_%.%?%.:/%+=&%~]+)$", "^/rss (sub) (https?://[%w-_%.%?%.:/%+=&%~]+)$",
"^/rss (del) (%d+) @(.*)$", "^/rss (del) (%d+) @(.*)$",
"^/rss (del) (%d+)$", "^/rss (del) (%d+)$",
"^/rss (del)", "^/rss (del)$",
"^/rss (sync)$" "^/rss (sync)$"
} }
rss.doc = [[* 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 = '' local text = ''
for k,user in pairs(users_info) do for k,user in pairs(users_info) do
text = text..user.name..': '..comma_value(user.msgs)..'\n' local msg_num = user.msgs
text = string.gsub(text, "%_", " ") -- Bot API doesn't use underscores anymore! Yippie! local percent = tostring(round(msg_num / all_msgs * 100, 1))
text = text..user.name..': '..comma_value(msg_num)..' <code>('..percent:gsub('%.', ',')..'%)</code>\n'
end end
if text:isempty() then return 'Keine Stats für diesen Chat verfügbar!'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 return text
end end
@ -129,7 +130,7 @@ function stats:action(msg, config, matches)
return return
else else
local chat_id = msg.chat.id 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 return
end end
end end
@ -139,7 +140,7 @@ function stats:action(msg, config, matches)
utilities.send_reply(self, msg, config.errors.sudo) utilities.send_reply(self, msg, config.errors.sudo)
return return
else else
utilities.send_reply(self, msg, stats:chat_stats(matches[3]), true) utilities.send_reply(self, msg, stats:chat_stats(matches[3]), 'HTML')
return return
end end
end end

View File

@ -42,7 +42,7 @@ function tagesschau:inline_callback(inline_query, config, matches)
local article = matches[1] local article = matches[1]
local full_url = 'http://www.tagesschau.de/'..article..'.html' local full_url = 'http://www.tagesschau.de/'..article..'.html'
local text, img_url, headline, shorttext = tagesschau:get_tagesschau_article(article) 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 if text:match('"') then
text = text:gsub('"', '\\"') text = text:gsub('"', '\\"')
@ -55,7 +55,7 @@ function tagesschau:inline_callback(inline_query, config, matches)
end end
local text = text:gsub('\n', '\\n') 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) utilities.answer_inline_query(self, inline_query, results, 7200)
end end

View File

@ -4,6 +4,10 @@ time.command = 'time <Ort>'
function time:init(config) function time:init(config)
time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table
time.inline_triggers = {
"^time (.+)",
"^time"
}
time.doc = [[* time.doc = [[*
]]..config.cmd_pat..[[time*: Aktuelle Zeit in Deutschland ]]..config.cmd_pat..[[time*: Aktuelle Zeit in Deutschland
*]]..config.cmd_pat..[[time* _<Ort>_: Gibt Zeit an diesem Ort aus]] *]]..config.cmd_pat..[[time* _<Ort>_: Gibt Zeit an diesem Ort aus]]
@ -45,8 +49,49 @@ function time:localize(output)
return output return output
end 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) function time:action(msg, config)
local input = utilities.input(msg.text) local input = utilities.input_from_msg(msg)
if not input then if not input then
local output = os.date("%A, %d. %B %Y, *%H:%M:%S Uhr*") local output = os.date("%A, %d. %B %Y, *%H:%M:%S Uhr*")
utilities.send_reply(self, msg, time:localize(output), true) utilities.send_reply(self, msg, time:localize(output), true)
@ -58,33 +103,10 @@ function time:action(msg, config)
utilities.send_reply(self, msg, coords) utilities.send_reply(self, msg, coords)
return return
end end
local output = time:get_time(coords)
local now = os.time() if not output then utilities.send_reply(self, msg, config.errors.connection) return end
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 .. ')_'
utilities.send_reply(self, msg, output, true) utilities.send_reply(self, msg, output, true)
end end
return time return time

View File

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

View File

@ -14,12 +14,14 @@ function twitter_send:init(config)
end end
twitter_send.triggers = { twitter_send.triggers = {
"^/tw (auth) (%d+)", "^/tw (auth) (%d+)$",
"^/tw (unauth)$", "^/tw (unauth)$",
"^/tw (verify)$", "^/tw (verify)$",
"^/tw (.+)", "^/tw (.+)$",
"^/(twwhitelist add) (%d+)", "^/(twwhitelist add) (%d+)$",
"^/(twwhitelist del) (%d+)" "^/(twwhitelist del) (%d+)$",
"^/(twwhitelist add)$",
"^/(twwhitelist del)$"
} }
twitter_send.doc = [[* twitter_send.doc = [[*
]]..config.cmd_pat..[[tw* _<Text>_: Sendet einen Tweet an den Account, der im Chat angemeldet ist ]]..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', values.oauth_token)
redis:hset(hash, 'oauth_token_secret', values.oauth_token_secret) 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 end
function twitter_send:reset_twitter_auth(hash, frominvalid) function twitter_send:reset_twitter_auth(hash, frominvalid)
redis:hdel(hash, 'oauth_token') redis:hdel(hash, 'oauth_token')
redis:hdel(hash, 'oauth_token_secret') redis:hdel(hash, 'oauth_token_secret')
if frominvalid then if frominvalid then
return '*Authentifizierung nicht erfolgreich, wird zurückgesetzt...*' return '<b>Authentifizierung nicht erfolgreich, wird zurückgesetzt...</b>'
else 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
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 screen_name = data.user.screen_name
local status_id = data.id_str 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 end
function twitter_send:add_to_twitter_whitelist(user_id) function twitter_send:add_to_twitter_whitelist(user_id)
@ -245,22 +249,36 @@ function twitter_send:del_from_twitter_whitelist(user_id)
end end
function twitter_send:action(msg, config, matches) 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 if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo) utilities.send_reply(self, msg, config.errors.sudo)
return return
else 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 return
end end
end end
if matches[1] == "twwhitelist del" and matches[2] then if matches[1] == "twwhitelist del" then
if msg.from.id ~= config.admin then if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo) utilities.send_reply(self, msg, config.errors.sudo)
return return
else 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 return
end end
end end
@ -302,7 +320,7 @@ function twitter_send:action(msg, config, matches)
end end
end end
if string.len(matches[2]) > 7 then utilities.send_reply(self, msg, 'Invalide PIN!') return 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') local message_id = redis:hget(hash, 'login_msg')
utilities.edit_message(self, msg.chat.id, message_id, '*Anmeldung abgeschlossen!*', true, true) utilities.edit_message(self, msg.chat.id, message_id, '*Anmeldung abgeschlossen!*', true, true)
redis:hdel(hash, 'login_msg') redis:hdel(hash, 'login_msg')
@ -316,7 +334,7 @@ function twitter_send:action(msg, config, matches)
return return
end end
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 return
end 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) utilities.send_reply(self, msg, '*Du darfst keine Tweets senden.* Entweder wurdest du noch gar nicht freigeschaltet oder ausgeschlossen.', true)
return return
else 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 return
end end
else 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 return
end end
end end

View File

@ -1,4 +1,4 @@
local twitter_user = {} local twitter_user = {}
require "./miku/encoding" require "./miku/encoding"
@ -58,7 +58,7 @@ function twitter_user:resolve_url(url)
end end
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 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_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url)
local response = json.decode(response_body) local response = json.decode(response_body)
@ -111,4 +111,4 @@ function twitter_user:action(msg)
end end
end end
return twitter_user return twitter_user

View File

@ -7,7 +7,7 @@ venue.triggers = {
local apikey = cred_data.google_apikey local apikey = cred_data.google_apikey
function venue:pre_process(msg, self) 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 lat = msg.venue.location.latitude
local lng = msg.venue.location.longitude local lng = msg.venue.location.longitude
@ -24,4 +24,4 @@ end
function venue:action(msg) function venue:action(msg)
end end
return venue return venue

View File

@ -17,6 +17,10 @@ function weather:init(config)
"^/w$", "^/w$",
"^/w (.*)$" "^/w (.*)$"
} }
weather.inline_triggers = {
"^w (.+)$",
"^w$"
}
weather.doc = [[* weather.doc = [[*
]]..config.cmd_pat..[[wetter*: Wetter für deinen Wohnort _(/location set [Ort])_ ]]..config.cmd_pat..[[wetter*: Wetter für deinen Wohnort _(/location set [Ort])_
*]]..config.cmd_pat..[[wetter* _<Ort>_: Wetter für diesen Ort *]]..config.cmd_pat..[[wetter* _<Ort>_: Wetter für diesen Ort
@ -42,10 +46,43 @@ function get_city_name(lat, lng)
return city return city
end 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) print('Finde Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:weather:'..lat..','..lng) local hash = 'telegram:cache:weather:'..lat..','..lng
if text then print('...aus dem Cache') return text end
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' 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", method = "GET",
sink = ltn12.sink.table(response_body) 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 if not ok then return nil end
local data = json.decode(table.concat(response_body)) 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 local weather = data.currently
@ -66,26 +103,28 @@ function weather:get_weather(lat, lng)
local temperature = string.gsub(round(weather.temperature, 1), "%.", ",") local temperature = string.gsub(round(weather.temperature, 1), "%.", ",")
local feelslike = string.gsub(round(weather.apparentTemperature, 1), "%.", ",") local feelslike = string.gsub(round(weather.apparentTemperature, 1), "%.", ",")
local temp = '*Wetter in '..city..':*\n'..temperature..' °C' local temp = '*Wetter in '..city..':*\n'..temperature..' °C'
local conditions = ' | '..weather.summary local weather_summary = weather.summary
if weather.icon == 'clear-day' then local conditions = ' | '..weather_summary
local weather_icon = weather.icon
if weather_icon == 'clear-day' then
conditions = conditions..' ☀️' conditions = conditions..' ☀️'
elseif weather.icon == 'clear-night' then elseif weather_icon == 'clear-night' then
conditions = conditions..' 🌙' conditions = conditions..' 🌙'
elseif weather.icon == 'rain' then elseif weather_icon == 'rain' then
conditions = conditions..' ☔️' conditions = conditions..' ☔️'
elseif weather.icon == 'snow' then elseif weather_icon == 'snow' then
conditions = conditions..' ❄️' conditions = conditions..' ❄️'
elseif weather.icon == 'sleet' then elseif weather_icon == 'sleet' then
conditions = conditions..' 🌨' conditions = conditions..' 🌨'
elseif weather.icon == 'wind' then elseif weather_icon == 'wind' then
conditions = conditions..' 💨' conditions = conditions..' 💨'
elseif weather.icon == 'fog' then elseif weather.icon == 'fog' then
conditions = conditions..' 🌫' conditions = conditions..' 🌫'
elseif weather.icon == 'cloudy' then elseif weather_icon == 'cloudy' then
conditions = conditions..' ☁️☁️' conditions = conditions..' ☁️☁️'
elseif weather.icon == 'partly-cloudy-day' then elseif weather_icon == 'partly-cloudy-day' then
conditions = conditions..' 🌤' conditions = conditions..' 🌤'
elseif weather.icon == 'partly-cloudy-night' then elseif weather_icon == 'partly-cloudy-night' then
conditions = conditions..' 🌙☁️' conditions = conditions..' 🌙☁️'
else else
conditions = conditions..'' conditions = conditions..''
@ -98,8 +137,56 @@ function weather:get_weather(lat, lng)
text = text..'\n(gefühlt: '..feelslike..' °C)' text = text..'\n(gefühlt: '..feelslike..' °C)'
end end
cache_data('weather', lat..','..lng, text, tonumber(ttl), 'key') print('Caching data...')
return text 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 end
function weather:action(msg, config, matches) function weather:action(msg, config, matches)
@ -116,22 +203,11 @@ function weather:action(msg, config, matches)
end end
end end
local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') local lat, lng = get_city_coordinates(city, config)
local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng')
if not lat and not lng then if not lat and not lng then
print('Koordinaten nicht eingespeichert, frage Google...') utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
coords = utilities.get_coords(city, config)
lat = coords.lat
lng = coords.lon
end
if not lat and not lng then
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
return return
end 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) local text = weather:get_weather(lat, lng)
if not text then if not text then
@ -140,4 +216,4 @@ function weather:action(msg, config, matches)
utilities.send_reply(self, msg, text, true) utilities.send_reply(self, msg, text, true)
end end
return weather return weather

View File

@ -57,7 +57,7 @@ function wikipedia:getWikiServer(lang)
end end
--[[ --[[
-- return decoded json table from Wikipedia -- return decoded JSON table from Wikipedia
--]] --]]
function wikipedia:loadPage(text, lang, intro, plain, is_search) function wikipedia:loadPage(text, lang, intro, plain, is_search)
local request, sink = {}, {} local request, sink = {}, {}
@ -107,7 +107,7 @@ function wikipedia:loadPage(text, lang, intro, plain, is_search)
end end
-- extract intro passage in wiki page -- extract intro passage in wiki page
function wikipedia:wikintro(text, lang) function wikipedia:wikintro(text, lang, is_inline)
local text = decodetext(text) local text = decodetext(text)
local result = self:loadPage(text, lang, true, true) local result = self:loadPage(text, lang, true, true)
@ -124,13 +124,28 @@ function wikipedia:wikintro(text, lang)
local lang = lang or "de" local lang = lang or "de"
local title = page.title local title = page.title
local title_enc = URL.escape(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 else
local text = text.." nicht gefunden" if is_inline then
return text return nil
else
local text = text.." nicht gefunden"
return text
end
end end
else else
return "Ein Fehler ist aufgetreten." if is_inline then
return nil
else
return "Ein Fehler ist aufgetreten."
end
end end
end end
@ -165,17 +180,20 @@ function wikipedia:inline_callback(inline_query, config, matches)
lang = 'de' lang = 'de'
query = matches[1] query = matches[1]
end 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) 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'
if code ~= 200 then return end 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 local data = json.decode(res).query
if data.searchinfo.totalhits == 0 then return end
local results = '[' local results = '['
local id = 700
for num in pairs(data.search) do for num in pairs(data.search) do
local title = data.search[num].title local title, result, keyboard = wikipedia:wikintro(data.search[num].title, lang, true)
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}}' 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 if num < #data.search then
results = results..',' results = results..','
end end
@ -216,4 +234,4 @@ function wikipedia:action(msg, config, matches)
utilities.send_reply(self, msg, result, true, keyboard) utilities.send_reply(self, msg, result, true, keyboard)
end end
return wikipedia return wikipedia

View File

@ -147,10 +147,10 @@ function youtube:inline_callback(inline_query, config, matches)
local query = matches[1] 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 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) 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) 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 = "" local video_ids = ""
-- We get all videoIds from search... -- 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 if not video_results.items[1] then return end
local results = '[' local results = '['
local id = 800
for num in pairs(video_results.items) do for num in pairs(video_results.items) do
local video_url = 'https://www.youtube.com/watch?v='..video_results.items[num].id local video_url = 'https://www.youtube.com/watch?v='..video_results.items[num].id
local thumb_url = get_yt_thumbnail(video_results.items[num]) 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 uploader = video_results.items[num].snippet.channelTitle
local description = uploader..', '..viewCount..' Views, '..readable_dur..likeCount..dislikeCount..commentCount 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 if num < #video_results.items then
results = results..',' results = results..','
end end
@ -207,6 +209,10 @@ end
function youtube:action(msg, config, matches) function youtube:action(msg, config, matches)
local yt_code = matches[1] local yt_code = matches[1]
local data = get_yt_data(yt_code) 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) send_youtube_data(data, msg, self)
return return
end end

View File

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

View File

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