diff --git a/.editorconfig b/.editorconfig index a7715af..334d4e7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,4 +6,5 @@ insert_final_newline = true [*.lua] charset = utf-8 -indent_style = tab +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/README.md b/README.md index 8e9b72d..be0c44e 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,12 @@ Der multifunktionale Telegram-Bot. -[Entwickler auf Telegram](http://telegram.me/Brawl) | [Offizieller Kanal](https://telegram.me/brawlbot_updates) +[Offizielle Webseite](https://brawlbot.tk) | [Entwickler auf Telegram](http://telegram.me/Brawl) | [Offizieller Kanal](https://telegram.me/brawlbot_updates) Brawlbot ist ein auf Plugins basierender Bot, der die [offizielle Telegram Bot API](http://core.telegram.org/bots/api) benutzt. Ursprünglich wurde er im Dezember 2014 auf Basis von Yagops [Telegram Bot](https://github.com/yagop/telegram-bot/) entwickelt, da aber die Entwicklung von tg-cli [zum Stillstand](https://brawlbot.tk/posts/ein-neuanfang) gekommen ist, wurden alle Plugins des bisher proprietären Brawlbots im Juni 2016 auf die Bot-API portiert und open-sourced. **Brawlbot v2 basiert auf [otouto](https://github.com/topkecleon/otouto) von Topkecleon.** -Brawlbot v2 ist freie Software; du darfst in modifizieren und weiterverbreiten, allerdings musst du dich an die GNU Affero General Public License v3 halten, siehe **LICENSE** für Details. +Brawlbot v2 ist freie Software; du darfst ihn modifizieren und weiterverbreiten, allerdings musst du dich an die GNU Affero General Public License v3 halten, siehe **LICENSE** für Details. ##Anleitung @@ -22,7 +22,7 @@ Brawlbot v2 ist freie Software; du darfst in modifizieren und weiterverbreiten, # Für User ## Setup ### Ubuntu und Debian -Ubuntu und Debian liefern Luarocks nur für Lua 5.1 aus. Um Luarocks für Lua 5.2 zu verwenden, folge bitte der [Anleitung auf StackOverflow](http://stackoverflow.com/a/20359102) +Ubuntu und Debian liefern Luarocks nur für Lua 5.1 aus. Um Luarocks für Lua 5.2 zu verwenden, folge bitte der [Anleitung auf StackOverflow](http://stackoverflow.com/a/20359102). ### Setup Du benötigst **Lua 5.2+**, eine aktive **Redis-Instanz** und die folgenden **LuaRocks-Module**: @@ -80,22 +80,24 @@ Brawlbot erhält laufend neue Plugins und wird kontinuierlich weiterentwickelt! ## Plugins Brawlbot benutzt ein Plugin-System, ähnlich Yagops [Telegram-Bot](http://github.com/yagop/telegram-bot). -Ein Plugin kann fünf Komponenten haben, aber nur zwei werden benötigt: +Ein Plugin kann zehn Komponenten haben, aber nur zwei werden benötigt: | Komponente | Beschreibung | Benötigt? | |:------------------|:---------------------------------------------|:----------| | `plugin:action` | Hauptfunktion. Benötigt `msg` als Argument, empfohlen wird auch `matches` als drittes Argument nach `config` | J | -| `plugin.triggers` | Tabelle von Triggern, (Lua-Patterns), auf die der Bot reagiert | J | +| `plugin.triggers` | Tabelle von Triggern (Lua-Patterns), auf die der Bot reagiert | J | +| `plugin.inline_triggers` | Tabelle von Triggern (Lua-Patterns), auf die der Bot bei Inline-Querys reagiert | N | | `plugin:init` | Optionale Funkion, die beim Start geladen wird | N | | `plugin:cron` | Wird jede Minute ausgeführt | N | | `plugin.command` | Einfaches Kommando mit Syntax. Wird bei `/hilfe` gelistet | N | | `plugin.doc` | Plugin-Hilfe. Wird mit `/help $kommando` gelistet | N | | `plugin.error` | Plugin-spezifische Fehlermeldung | N | +| `plugin:callback` | Aktion, die ausgeführt wird, nachdem auf einen Callback-Button gedrückt wird. Siehe `gImages.lua` für ein Beispiel. Argumente: `callback` (enthält Callback-Daten), `msg`, `self`, `config`, `input` (enthält Parameter ohne `callback`) | N | +| `plugin:inline_callback` | Aktion, die ausgeführt wird, wenn der Bot per Inline-Query ausgelöst wird. Argumente sind `inline_query` für die Daten, `config` und `matches` | N | + Die`bot:on_msg_receive` Funktion fügt einige nützte Variablen zur ` msg` Tabelle hinzu. Diese sind:`msg.from.id_str`, `msg.to.id_str`, `msg.chat.id_str`, `msg.text_lower`, `msg.from.name`. -Rückgabewerte für `plugin:action` sind optional, aber wenn eine Tabelle zurückgegeben wird, wird diese die neue `msg`,-Tabelle und `on_msg_receive` wird damit fortfahren. - Interaktionen mit der Bot-API sind sehr einfach. Siehe [Bindings](#bindings) für Details. Einige Funktionen, die oft benötigt werden, sind in `utilites.lua` verfügbar. @@ -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.version` speichert die Bot-Version. - -* * * \ No newline at end of file +`database.version` speichert die Bot-Version. \ No newline at end of file diff --git a/config.lua.example b/config.lua.example index dff3c5b..e47fb20 100644 --- a/config.lua.example +++ b/config.lua.example @@ -14,7 +14,7 @@ return { cli_port = 4567, -- The block of text returned by /start. about_text = [[ -Dies ist die BETA-Version von Brawlbot v2. +Dies ist die BETA-Version von Mikubot v2. Sende /hilfe, um zu starten ]], @@ -35,43 +35,20 @@ Sende /hilfe, um zu starten syntax = 'Invalide Syntax.', chatter_connection = 'Ich möchte gerade nicht reden', chatter_response = 'Ich weiß nicht, was ich darauf antworten soll.' - } + }, + + 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. - 'control', - 'blacklist', - 'about', - 'ping', - 'whoami', - 'nick', - 'echo', - 'imgblacklist', - 'gImages', - 'gSearch', - 'gMaps', - 'wikipedia', - 'hackernews', - 'imdb', - 'calc', - 'urbandictionary', - 'time', - 'dice', - 'reddit', - 'xkcd', - 'slap', - 'commit', - 'pun', - 'currency', - 'shout', - 'set', - 'get', - 'patterns', - '9gag', - 'shell', - 'adfly', - 'twitter', - -- Put new plugins above this line. - 'help' - } + chatter = { + cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=', + connection = 'I don\'t feel like talking right now.', + response = 'I don\'t know what to say to that.' + } } \ No newline at end of file diff --git a/launch.sh b/launch.sh index 088fcb7..46350e1 100755 --- a/launch.sh +++ b/launch.sh @@ -1,7 +1,7 @@ #!/bin/sh while true; do - lua main.lua - echo 'Miku wurde gestoppt. ^C zum beenden.' - sleep 5s -done + lua main.lua + echo 'Miku wurde gestoppt. ^C zum beenden.' + sleep 5s +done \ No newline at end of file diff --git a/miku/bindings.lua b/miku/bindings.lua index 1b98a71..68739d2 100644 --- a/miku/bindings.lua +++ b/miku/bindings.lua @@ -3,7 +3,6 @@ otouto's bindings for the Telegram bot API. https://core.telegram.org/bots/api Copyright 2016 topkecleon. Published under the AGPLv3. - See the "Bindings" section of README.md for usage information. ]]-- @@ -48,7 +47,7 @@ function bindings:request(method, parameters, file) end local response = {} local body, boundary = MP_ENCODE(parameters) - local success = HTTPS.request{ + local success, code = HTTPS.request{ url = self.BASE_URL .. method, method = 'POST', headers = { @@ -60,7 +59,7 @@ function bindings:request(method, parameters, file) } local data = table.concat(response) if not success then - print(method .. ': Connection error.') + print(method .. ': Connection error. [' .. code .. ']') return false, false else local result = JSON.decode(data) @@ -82,4 +81,4 @@ function bindings.gen(_, key) end setmetatable(bindings, { __index = bindings.gen }) -return bindings +return bindings \ No newline at end of file diff --git a/miku/bot.lua b/miku/bot.lua index 2d13ddf..c82539a 100644 --- a/miku/bot.lua +++ b/miku/bot.lua @@ -3,20 +3,20 @@ local bot = {} bindings = require('miku.bindings') utilities = require('miku.utilities') -bot.version = '160801' +bot.version = '160815' function bot:init(config) -- The function run when the bot is started or reloaded. cred_data = load_cred() assert( - config.bot_api_key and config.bot_api_key ~= '', + config.bot_api_key, 'You did not set your bot token in the config!' ) self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' -- Fetch bot information. Try until it succeeds. repeat - print('Fetching bot information...') + print('Sammel Bot-Informationen...') self.info = bindings.getMe(self) until self.info self.info = self.info.result @@ -26,33 +26,23 @@ function bot:init(config) -- The function run when the bot is started or reloade self.database = utilities.load_data(self.info.username..'.db') end - -- Table to cache user info (usernames, IDs, etc). - self.database.users = self.database.users or {} - -- Table to store userdata (nicknames, lastfm usernames, etc). - self.database.userdata = self.database.userdata or {} - -- Save the bot's version in the database to make migration simpler. - self.database.version = bot.version - -- Add updated bot info to the user info cache. - self.database.users = self.database.users or {} -- Table to cache userdata. - self.database.users[tostring(self.info.id)] = self.info - self.plugins = {} -- Load plugins. enabled_plugins = load_plugins() for k,v in pairs(enabled_plugins) do local p = require('miku.plugins.'..v) -- print('loading plugin',v) - table.insert(self.plugins, p) + self.plugins[k] = p self.plugins[k].name = v if p.init then p.init(self, config) end end print('Bot wurde erfolgreich gestartet!\n@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')') - self.last_update = self.last_update or 0 -- Set loop variables: Update offset, - self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job, - self.last_database_save = self.last_database_save or os.date('%H') -- the time of the last database save, + -- Set loop variables + self.last_update = self.last_update or 0 -- Update offset. + self.last_cron = self.last_cron or os.date('%M') -- Last cron job. + self.last_database_save = self.last_database_save or os.date('%H') -- Last db save. self.is_started = true -- and whether or not the bot should be running. - end function bot:on_msg_receive(msg, config) -- The fn run whenever a message is received. @@ -62,18 +52,6 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec if msg.date < os.time() - 5 then return end -- Do not process old messages. - -- Cache user info for those involved. - self.database.users[tostring(msg.from.id)] = msg.from - if msg.reply_to_message then - self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from - elseif msg.forward_from then - self.database.users[tostring(msg.forward_from.id)] = msg.forward_from - elseif msg.new_chat_member then - self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member - elseif msg.left_chat_member then - self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member - end - msg = utilities.enrich_message(msg) if msg.reply_to_message then @@ -92,11 +70,12 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec msg.text_lower = msg.text:lower() end msg = pre_process_msg(self, msg, config) + if not msg then return end -- deleted by banning if is_service_msg(msg) then msg = service_modify_msg(msg) end - + for _, plugin in ipairs(self.plugins) do match_plugins(self, msg, config, plugin) end @@ -115,6 +94,41 @@ function bot:on_callback_receive(callback, msg, config) -- whenever a new callba if not callback.data:find(':') or not callback.data:find('@'..self.info.username..' ') then return end + + -- Check if user is blocked + local user_id = callback.from.id + local chat_id = msg.chat.id + if redis:get('blocked:'..user_id) then + utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true) + return + end + + -- Check if user is banned + local banned = redis:get('banned:'..chat_id..':'..user_id) + if banned then + utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true) + return + end + + -- Check if whitelist is enabled and user/chat is whitelisted + local whitelist = redis:get('whitelist:enabled') + if whitelist and not is_sudo(msg, config) then + local hash = 'whitelist:user#id'..user_id + local allowed = redis:get(hash) or false + if not allowed then + if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then + local allowed = redis:get('whitelist:chat#id'.. chat_id) + if not allowed then + utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true) + return + end + else + utilities.answer_callback_query(self, callback, 'Du darfst den Bot nicht nutzen!', true) + return + end + end + end + callback.data = string.gsub(callback.data, '@'..self.info.username..' ', "") local called_plugin = callback.data:match('(.*):.*') local param = callback.data:sub(callback.data:find(':')+1) @@ -123,7 +137,8 @@ function bot:on_callback_receive(callback, msg, config) -- whenever a new callba msg = utilities.enrich_message(msg) - for _, plugin in ipairs(self.plugins) do + for n=1, #self.plugins do + local plugin = self.plugins[n] if plugin.name == called_plugin then if is_plugin_disabled_on_chat(plugin.name, msg) then return end plugin:callback(callback, msg, self, config, param) @@ -135,27 +150,44 @@ end function bot:process_inline_query(inline_query, config) -- When an inline query is received -- remove comment to enable debugging -- vardump(inline_query) + + -- PLEASE READ: Blocking every single InlineQuery IS NOT POSSIBLE! + -- When the request is cached, the user can still send this query + -- but he WON'T be able to make new requests. + local user_id = inline_query.from.id + if redis:get('blocked:'..user_id) then + utilities.answer_inline_query(self, inline_query, nil, 0, true) + return + end if not config.enable_inline_for_everyone then local is_whitelisted = redis:get('whitelist:user#id'..inline_query.from.id) - if not is_whitelisted then return end + if not is_whitelisted then utilities.answer_inline_query(self, inline_query, nil, 0, true) return end end if inline_query.query:match('"') then inline_query.query = inline_query.query:gsub('"', '\\"') end - for _, plugin in ipairs(self.plugins) do + + for n=1, #self.plugins do + local plugin = self.plugins[n] match_inline_plugins(self, inline_query, config, plugin) end + + -- Stop the spinning circle + utilities.answer_inline_query(self, inline_query, nil, 0, true) end function bot:run(config) - bot.init(self, config) -- Actually start the script. + bot.init(self, config) - while self.is_started do -- Start a loop while the bot should be running. - local res = bindings.getUpdates(self, { timeout=20, offset = self.last_update+1 } ) + while self.is_started do + -- Update loop + local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update+1 } ) if res then - for _,v in ipairs(res.result) do -- Go through every new message. + -- Iterate over every new message. + for n=1, #res.result do + local v = res.result[n] self.last_update = v.update_id if v.inline_query then bot.process_inline_query(self, v.inline_query, config) @@ -169,10 +201,12 @@ function bot:run(config) print('Connection error while fetching updates.') end - if self.last_cron ~= os.date('%M') then -- Run cron jobs every minute. + -- Run cron jobs every minute. + if self.last_cron ~= os.date('%M') then self.last_cron = os.date('%M') utilities.save_data(self.info.username..'.db', self.database) -- Save the database. - for i,v in ipairs(self.plugins) do + for n=1, #self.plugins do + local v = self.plugins[n] if v.cron then -- Call each plugin's cron function, if it has one. local result, err = pcall(function() v.cron(self, config) end) if not result then @@ -194,7 +228,8 @@ end -- Apply plugin.pre_process function function pre_process_msg(self, msg, config) - for _,plugin in ipairs(self.plugins) do + for n=1, #self.plugins do + local plugin = self.plugins[n] if plugin.pre_process and msg then -- print('Preprocess '..plugin.name) -- remove comment to restore old behaviour new_msg = plugin:pre_process(msg, self, config) @@ -204,7 +239,9 @@ function pre_process_msg(self, msg, config) end function match_inline_plugins(self, inline_query, config, plugin) - for _, trigger in ipairs(plugin.inline_triggers or {}) do + local match_table = plugin.inline_triggers or {} + for n=1, #match_table do + local trigger = plugin.inline_triggers[n] if string.match(string.lower(inline_query.query), trigger) then local success, result = pcall(function() for k, pattern in pairs(plugin.inline_triggers) do @@ -216,50 +253,33 @@ function match_inline_plugins(self, inline_query, config, plugin) print('Inline: '..plugin.name..' ausgelöst') return plugin.inline_callback(self, inline_query, config, matches) end) - if not success then - print(result) end end - end end function match_plugins(self, msg, config, plugin) - for _, trigger in ipairs(plugin.triggers or {}) do + local match_table = plugin.triggers or {} + for n=1, #match_table do + local trigger = plugin.triggers[n] if string.match(msg.text_lower, trigger) then -- Check if Plugin is disabled if is_plugin_disabled_on_chat(plugin.name, msg) then return end local success, result = pcall(function() -- trying to port matches to miku - for k, pattern in pairs(plugin.triggers) do - matches = match_pattern(pattern, msg.text) - if matches then - break; - end + local pattern = plugin.triggers[n] + local matches = match_pattern(pattern, msg.text) + if matches then + print('msg matches: ', pattern, ' for "'..plugin.name..'"') + return plugin.action(self, msg, config, matches) end - print(plugin.name..' ausgelöst') - return plugin.action(self, msg, config, matches) end) if not success then - -- If the plugin has an error message, send it. If it does - -- not, use the generic one specified in config. If it's set - -- to false, do nothing. - if plugin.error then - utilities.send_reply(self, msg, plugin.error) - elseif plugin.error == nil then - utilities.send_reply(self, msg, config.errors.generic, true) + utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) + return end - utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) + -- if one pattern matches, end return end - - -- If the action returns a table, make that table the new msg. - if type(result) == 'table' then - msg = result - -- If the action returns true, continue. - elseif result ~= true then - return - end - end end end @@ -294,7 +314,7 @@ function create_plugin_set() 'banhammer', 'channels', 'plugins', - 'help', + 'help' } print ('Aktiviere Plugins und speicher in telegram:enabled_plugins') for _,plugin in pairs(enabled_plugins) do diff --git a/miku/plugins/9gag.lua b/miku/plugins/9gag.lua index a126c28..45d0d66 100644 --- a/miku/plugins/9gag.lua +++ b/miku/plugins/9gag.lua @@ -31,13 +31,15 @@ end function ninegag:inline_callback(inline_query, config) local res, code = http.request(url) - if code ~= 200 then return end + if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end local gag = json.decode(res) local results = '[' + local id = 50 for n in pairs(gag) do local title = gag[n].title:gsub('"', '\\"') - results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..gag[n].src..'","thumb_url":"'..gag[n].src..'","caption":"'..title..'","reply_markup":{"inline_keyboard":[[{"text":"9GAG aufrufen","url":"'..gag[n].url..'"}]]}}' + results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..gag[n].src..'","thumb_url":"'..gag[n].src..'","caption":"'..title..'","reply_markup":{"inline_keyboard":[[{"text":"9GAG aufrufen","url":"'..gag[n].url..'"}]]}}' + id = id+1 if n < #gag then results = results..',' end @@ -58,4 +60,4 @@ function ninegag:action(msg, config) utilities.send_photo(self, msg.chat.id, file, title, msg.message_id, '{"inline_keyboard":[[{"text":"Post aufrufen","url":"'..post_url..'"}]]}') end -return ninegag +return ninegag \ No newline at end of file diff --git a/miku/plugins/about.lua b/miku/plugins/about.lua index 41a4914..a20bf73 100644 --- a/miku/plugins/about.lua +++ b/miku/plugins/about.lua @@ -5,33 +5,16 @@ local bot = require('miku.bot') about.command = 'about' about.doc = '`Sendet Informationen über den Bot.`' -about.triggers = { +function about:init(config) + about.text = config.about_text .. '\n[Mikudayobot](https://github.com/Akamaru/Mikubot-V2) v'..bot.version..' von @Akamaru, basierend auf [otouto](https://github.com/topkecleon/otouto) von topkecleon.' + about.triggers = { '/[Aa][Bb][Oo][Uu][Tt]', '/[Ss][Tt][Aa][Rr][Tt]' } - -function about:action(msg, config) - - -- Filthy hack, but here is where we'll stop forwarded messages from hitting - -- other plugins. - -- disabled to restore old behaviour - -- if msg.forward_from then return end - - local output = config.about_text .. '\n[Mikudayobot](https://github.com/Akamaru/Mikubot-V2) v'..bot.version..' von @Akamaru, basierend auf [otouto](https://github.com/topkecleon/otouto) von topkecleon.' - - if - (msg.new_chat_member and msg.new_chat_member.id == self.info.id) - or msg.text_lower:match('^'..config.cmd_pat..'about$') - or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower()..'$') - or msg.text_lower:match('^'..config.cmd_pat..'start$') - or msg.text_lower:match('^'..config.cmd_pat..'start@'..self.info.username:lower()..'$') - then - utilities.send_message(self, msg.chat.id, output, true, nil, true) - return - end - - return true - end -return about +function about:action(msg, config) + utilities.send_message(self, msg.chat.id, about.text, true, nil, true) +end + +return about \ No newline at end of file diff --git a/miku/plugins/adfly.lua b/miku/plugins/adfly.lua index cbe23e3..49e3181 100644 --- a/miku/plugins/adfly.lua +++ b/miku/plugins/adfly.lua @@ -27,10 +27,10 @@ function adfly:inline_callback(inline_query, config, matches) url = redis:get(hash) end - if not url then return end - if url == 'NOTFOUND' then return end + if not url then utilities.answer_inline_query(self, inline_query) return end + if url == 'NOTFOUND' then utilities.answer_inline_query(self, inline_query) return end - local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]' + local results = '[{"type":"article","id":"1","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]' utilities.answer_inline_query(self, inline_query, results, 3600, true) end @@ -54,4 +54,4 @@ function adfly:action(msg, config, matches) end end -return adfly +return adfly \ No newline at end of file diff --git a/miku/plugins/afk.lua b/miku/plugins/afk.lua index 9200c1b..29463de 100644 --- a/miku/plugins/afk.lua +++ b/miku/plugins/afk.lua @@ -5,7 +5,7 @@ local afk = {} function afk:init(config) afk.triggers = { - "^/([Aa][Ff][Kk])$", + "^/([Aa][Ff][Kk])$", "^/([Aa][Ff][Kk]) (.*)$" } afk.doc = [[* @@ -68,7 +68,7 @@ function afk:pre_process(msg, self) local user_id = msg.from.id local chat_id = msg.chat.id local hash = 'afk:'..chat_id..':'..user_id - + local uhash = 'user:'..user_id if afk:is_offline(hash) then local afk_text = afk:get_afk_text(hash) @@ -80,18 +80,27 @@ function afk:pre_process(msg, self) local duration = makeHumanTime(afk_time) redis:hset(hash, 'afk', false) + local show_afk_keyboard = redis:hget(uhash, 'afk_keyboard') if afk_text then redis:hset(hash, 'afk_text', false) - utilities.send_reply(self, msg, user_name..' ist wieder da! (war: '..afk_text..' 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: '..afk_text..' für '..duration..')', 'HTML', '{"hide_keyboard":true,"selective":true}') + else + utilities.send_message(self, chat_id, user_name..' ist wieder da! (war: '..afk_text..' für '..duration..')', true, nil, 'HTML') + end else - utilities.send_reply(self, msg, user_name..' ist wieder da! (war '..duration..' weg)', nil, '{"hide_keyboard":true,"selective":true}') + if show_afk_keyboard == 'true' then + utilities.send_reply(self, msg, user_name..' ist wieder da! (war '..duration..' weg)', nil, '{"hide_keyboard":true,"selective":true}') + else + utilities.send_message(self, chat_id, user_name..' ist wieder da! (war '..duration..' weg)') + end end end return msg end -function afk:action(msg) +function afk:action(msg, config, matches) if msg.chat.type == "private" then utilities.send_reply(self, msg, "Mir ist's egal, ob du AFK bist.") return diff --git a/miku/plugins/bImages.lua b/miku/plugins/bImages.lua index 330325a..8ddf60a 100644 --- a/miku/plugins/bImages.lua +++ b/miku/plugins/bImages.lua @@ -36,11 +36,13 @@ function bImages:getImages(query) local results = '[' + local id = 300 for n in pairs(images) do if images[n].encodingFormat == 'jpeg' then -- Inline-Querys MUST use JPEG photos! local photo_url = images[n].contentUrl local thumb_url = images[n].thumbnailUrl - results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..photo_url..'","thumb_url":"'..thumb_url..'","photo_width":'..images[n].width..',"photo_height":'..images[n].height..',"reply_markup":{"inline_keyboard":[[{"text":"Bing aufrufen","url":"'..images[n].webSearchUrl..'"},{"text":"Bild öffnen","url":"'..photo_url..'"}]]}},' + results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..photo_url..'","thumb_url":"'..thumb_url..'","photo_width":'..images[n].width..',"photo_height":'..images[n].height..',"reply_markup":{"inline_keyboard":[[{"text":"Bing aufrufen","url":"'..images[n].webSearchUrl..'"},{"text":"Bild öffnen","url":"'..photo_url..'"}]]}},' + id = id+1 end end @@ -57,7 +59,7 @@ function bImages:inline_callback(inline_query, config, matches) results = bImages:getImages(query) end - if not results then return end + if not results then utilities.answer_inline_query(self, inline_query) return end utilities.answer_inline_query(self, inline_query, results, 3600) end diff --git a/miku/plugins/banhammer.lua b/miku/plugins/banhammer.lua index f0669de..7358070 100644 --- a/miku/plugins/banhammer.lua +++ b/miku/plugins/banhammer.lua @@ -12,7 +12,17 @@ function banhammer:init(config) "^/(whitelist) (delete) (chat)$", "^/(ban) (user) (%d+)$", "^/(ban) (delete) (%d+)$", - "^/(kick) (%d+)$" + "^/(block) (user) (%d+)$", + "^/(block) (delete) (%d+)$", + "^/(whitelist)$", + "^/(whitelist) (delete)$", + "^/(ban)$", + "^/(ban) (delete)$", + "^/(block)$", + "^/(block) (delete)$", + "^/(kick) (%d+)$", + "^/(kick)$", + "^/(leave)$" } banhammer.doc = [[* ]]..config.cmd_pat..[[whitelist* __/__: Aktiviert/deaktiviert Whitelist @@ -22,7 +32,11 @@ function banhammer:init(config) *]]..config.cmd_pat..[[whitelist* delete chat: Lösche ganze Gruppe von der Whitelist *]]..config.cmd_pat..[[ban* user __: Kicke User vom Chat und kicke ihn, wenn er erneut beitritt *]]..config.cmd_pat..[[ban* delete __: Entbanne User -*]]..config.cmd_pat..[[kick* __: Kicke User aus dem Chat]] +*]]..config.cmd_pat..[[block* user __: Blocke User vom Bot +*]]..config.cmd_pat..[[block* delete __: Entblocke User +*]]..config.cmd_pat..[[kick* __: Kicke User aus dem Chat +*]]..config.cmd_pat..[[leave*: Bot verlässt die Gruppe +Alternativ kann auch auf die Nachricht des Users geantwortet werden, die Befehle sind dnn die obrigen ohne `user` bzw.`delete`.]] end function banhammer:kick_user(user_id, chat_id, self, onlykick) @@ -54,8 +68,8 @@ end function banhammer:unban_user(user_id, chat_id, self, chat_type) local hash = 'banned:'..chat_id..':'..user_id redis:del(hash) - if chat_type == 'supergroup' then -- how can bots be admins anyway? - local request = bindings.request(self, 'unbanChatMember', { + if chat_type == 'supergroup' then + bindings.request(self, 'unbanChatMember', { chat_id = chat_id, user_id = user_id } ) @@ -96,76 +110,94 @@ function banhammer:pre_process(msg, self, config) end -- BANNED USER TALKING + local user_id = msg.from.id + local chat_id = msg.chat.id if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then - local user_id = msg.from.id - local chat_id = msg.chat.id local banned = banhammer:is_banned(user_id, chat_id) if banned then print('Banned user talking!') banhammer:ban_user(user_id, chat_id, self) - msg.text = '' + return end end + -- BLOCKED USER TALKING (block = user can't use bot, but won't be kicked from group) + local hash = 'blocked:'..user_id + local issudo = is_sudo(msg, config) + local blocked = redis:get(hash) + if blocked and not issudo then + print('User '..user_id..' blocked') + return + end - -- WHITELIST + -- WHITELIST local hash = 'whitelist:enabled' local whitelist = redis:get(hash) - local issudo = is_sudo(msg, config) -- Allow all sudo users even if whitelist is allowed if whitelist and not issudo then print('Whitelist enabled and not sudo') -- Check if user or chat is whitelisted - local allowed = banhammer:is_user_whitelisted(msg.from.id) - local has_been_warned = redis:hget('user:'..msg.from.id, 'has_been_warned') + local allowed = banhammer:is_user_whitelisted(user_id) + local has_been_warned = redis:hget('user:'..user_id, 'has_been_warned') if not allowed then - print('User '..msg.from.id..' not whitelisted') + print('User '..user_id..' not whitelisted') if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then - allowed = banhammer:is_chat_whitelisted(msg.chat.id) + allowed = banhammer:is_chat_whitelisted(chat_id) if not allowed then - print ('Chat '..msg.chat.id..' not whitelisted') + print ('Chat '..chat_id..' not whitelisted') else - print ('Chat '..msg.chat.id..' whitelisted :)') + print ('Chat '..chat_id..' whitelisted :)') end else if not has_been_warned then utilities.send_reply(self, msg, "Dies ist ein privater Bot, der erst nach einer Freischaltung benutzt werden kann.\nThis is a private bot, which can only be after an approval.") - redis:hset('user:'..msg.from.id, 'has_been_warned', true) + redis:hset('user:'..user_id, 'has_been_warned', true) else print('User has already been warned!') end end else - print('User '..msg.from.id..' allowed :)') + print('User '..user_id..' allowed :)') end if not allowed then - msg.text = '' - msg.text_lower = '' - msg.entities = '' + return end - -- else - -- print('Whitelist not enabled or is sudo') end return msg end function banhammer:action(msg, config, matches) - if msg.from.id ~= config.admin then + if not is_sudo(msg, config) then utilities.send_reply(self, msg, config.errors.sudo) return end + if matches[1] == 'leave' then + if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then + bindings.request(self, 'leaveChat', { + chat_id = msg.chat.id + } ) + return + end + end + if matches[1] == 'ban' then local user_id = matches[3] local chat_id = msg.chat.id + if not user_id then + if not msg.reply_to_message then + return + end + user_id = msg.reply_to_message.from.id + end if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then - if matches[2] == 'user' then + if matches[2] == 'user' or not matches[2] then local text = banhammer:ban_user(user_id, chat_id, self) utilities.send_reply(self, msg, text) return @@ -183,7 +215,14 @@ function banhammer:action(msg, config, matches) if matches[1] == 'kick' then if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then - banhammer:kick_user(matches[2], msg.chat.id, self, true) + local user_id = matches[2] + if not user_id then + if not msg.reply_to_message then + return + end + user_id = msg.reply_to_message.from.id + end + banhammer:kick_user(user_id, msg.chat.id, self, true) return else utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe') @@ -205,6 +244,28 @@ function banhammer:action(msg, config, matches) utilities.send_reply(self, msg, 'Whitelist deaktiviert') return end + + if not matches[2] then + if not msg.reply_to_message then + return + end + local user_id = msg.reply_to_message.from.id + local hash = 'whitelist:user#id'..user_id + redis:set(hash, true) + utilities.send_reply(self, msg, 'User '..user_id..' whitelisted') + return + end + + if matches[2] == 'delete' and not matches[3] then + if not msg.reply_to_message then + return + end + local user_id = msg.reply_to_message.from.id + local hash = 'whitelist:user#id'..user_id + redis:del(hash) + utilities.send_reply(self, msg, 'User '..user_id..' von der Whitelist entfernt!') + return + end if matches[2] == 'user' then local hash = 'whitelist:user#id'..matches[3] @@ -243,6 +304,46 @@ function banhammer:action(msg, config, matches) return end end + end + + if matches[1] == 'block' then + + if matches[2] == 'user' and matches[3] then + local hash = 'blocked:'..matches[3] + redis:set(hash, true) + utilities.send_reply(self, msg, 'User '..matches[3]..' darf den Bot nun nicht mehr nutzen.') + return + end + + if matches[2] == 'delete' and matches[3] then + local hash = 'blocked:'..matches[3] + redis:del(hash) + utilities.send_reply(self, msg, 'User '..matches[3]..' darf den Bot wieder nutzen.') + return + end + + if not matches[2] then + if not msg.reply_to_message then + return + end + local user_id = msg.reply_to_message.from.id + local hash = 'blocked:'..user_id + redis:set(hash, true) + utilities.send_reply(self, msg, 'User '..user_id..' darf den Bot nun nicht mehr nutzen.') + return + end + + if matches[2] == 'delete' and not matches[3] then + if not msg.reply_to_message then + return + end + local user_id = msg.reply_to_message.from.id + local hash = 'blocked:'..user_id + redis:del(hash) + utilities.send_reply(self, msg, 'User '..user_id..' darf den Bot wieder nutzen.') + return + end + end end diff --git a/miku/plugins/bitly.lua b/miku/plugins/bitly.lua index 59648c5..deb63a4 100644 --- a/miku/plugins/bitly.lua +++ b/miku/plugins/bitly.lua @@ -38,9 +38,9 @@ function bitly:inline_callback(inline_query, config, matches) url = data.long_url end - if not url then return end + if not url then utilities.answer_inline_query(self, inline_query) return end - local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]' + local results = '[{"type":"article","id":"2","title":"Verlängerte URL","description":"'..url..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]' utilities.answer_inline_query(self, inline_query, results, 3600) end @@ -62,4 +62,4 @@ function bitly:action(msg, config, matches) end end -return bitly +return bitly \ No newline at end of file diff --git a/miku/plugins/cats.lua b/miku/plugins/cats.lua index 7298325..30807f8 100644 --- a/miku/plugins/cats.lua +++ b/miku/plugins/cats.lua @@ -5,7 +5,7 @@ cats.command = 'kitty [gif]' function cats:init(config) if not cred_data.cat_apikey then print('Fehlender Key: cat_apikey.') - print('cats.lua will be enabled, but there are more features with a key.') + print('cats.lua wird aktiviert, aber mit einem Key gibt es mehr Features.') end cats.triggers = { @@ -29,8 +29,10 @@ local apikey = cred_data.cat_apikey or "" -- apply for one here: http://thecatap function cats:inline_callback(inline_query, config, matches) if matches[1] == 'gif' then img_type = 'gif' + id = 100 else img_type = 'jpg' + id = 200 end local url = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D%27http%3A%2F%2Fthecatapi.com%2Fapi%2Fimages%2Fget%3Fformat%3Dxml%26results_per_page%3D50%26type%3D'..img_type..'%26apikey%3D'..apikey..'%27&format=json' -- no way I'm using XML, plz die local res, code = https.request(url) @@ -43,9 +45,11 @@ function cats:inline_callback(inline_query, config, matches) for n in pairs(data) do if img_type == 'gif' then - results = results..'{"type":"gif","id":"'..math.random(100000000000000000)..'","gif_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}' + results = results..'{"type":"gif","id":"'..id..'","gif_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}' + id = id+1 else - results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}' + results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..data[n].url..'","thumb_url":"'..data[n].url..'"}' + id = id+1 end if n < #data then results = results..',' diff --git a/miku/plugins/cleverbot.lua b/miku/plugins/cleverbot.lua index 07f8be4..1cf2672 100644 --- a/miku/plugins/cleverbot.lua +++ b/miku/plugins/cleverbot.lua @@ -2,22 +2,30 @@ local cleverbot = {} function cleverbot:init(config) cleverbot.triggers = { - "^/[Cc][Bb][Oo][Tt] (.*)$" + "^/[Cc][Bb][Oo][Tt] (.*)$", + "^[Mm][Ii][Kk][Uu][Bb][Oo][Tt], (.+)$", } - - cleverbot.doc = [[* -]]..config.cmd_pat..[[cbot* __*: Befragt den Cleverbot]] + cleverbot.url = config.chatter.cleverbot_api end cleverbot.command = 'cbot ' -function cleverbot:action(msg, config) - local text = msg.text - local url = "https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text="..URL.escape(text) - local query = https.request(url) - if query == nil then utilities.send_reply(self, msg, 'Ein Fehler ist aufgetreten :(') return end - local decode = json.decode(query) - local answer = string.gsub(decode.clever, "Ä", "Ä") +function cleverbot:action(msg, config, matches) + utilities.send_typing(self, msg.chat.id, 'typing') + local text = matches[1] + local query, code = https.request(cleverbot.url..URL.escape(text)) + if code ~= 200 then + utilities.send_reply(self, msg, 'Ich möchte jetzt nicht reden...') + return + end + + local data = json.decode(query) + if not data.clever then + utilities.send_reply(self, msg, 'Ich möchte jetzt nicht reden...') + return + end + + local answer = string.gsub(data.clever, "Ä", "Ä") local answer = string.gsub(answer, "ä", "ä") local answer = string.gsub(answer, "Ö", "Ö") local answer = string.gsub(answer, "ö", "ö") @@ -27,4 +35,4 @@ function cleverbot:action(msg, config) utilities.send_reply(self, msg, answer) end -return cleverbot +return cleverbot \ No newline at end of file diff --git a/miku/plugins/currency.lua b/miku/plugins/currency.lua index 92b8a47..a9d4779 100644 --- a/miku/plugins/currency.lua +++ b/miku/plugins/currency.lua @@ -4,57 +4,119 @@ currency.command = 'cash [Menge] ' function currency:init(config) currency.triggers = { - "^/[Cc][Aa][Ss][Hh] ([A-Za-z]+)$", - "^/[Cc][Aa][Ss][Hh] ([A-Za-z]+) ([A-Za-z]+)$", - "^/[Cc][Aa][Ss][Hh] (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$", - "^(/[Ee][Uu][Rr])$" + "^/cash ([A-Za-z]+)$", + "^/cash ([A-Za-z]+) ([A-Za-z]+)$", + "^/cash (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$", + "^(/cash)$" + } + currency.inline_triggers = { + "^c ([A-Za-z]+)$", + "^c ([A-Za-z]+) ([A-Za-z]+)$", + "^c (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$" } currency.doc = [[* ]]..config.cmd_pat..[[cash* _[Menge]_ __ __ +*]]..config.cmd_pat..[[cash* __: Rechnet in Euro um +*]]..config.cmd_pat..[[cash* __ __: Rechnet mit der Einheit 1 Beispiel: _]]..config.cmd_pat..[[cash 5 USD EUR_]] end -function currency:action(msg, config) - if not matches[2] then - from = string.upper(matches[1]) - to = 'EUR' +local BASE_URL = 'https://api.fixer.io' + +function currency:inline_callback(inline_query, config, matches) + if not matches[2] then -- first pattern + base = 'EUR' + to = string.upper(matches[1]) amount = 1 - elseif matches[3] then - from = string.upper(matches[2]) + elseif matches[3] then -- third pattern + base = string.upper(matches[2]) to = string.upper(matches[3]) amount = matches[1] - else - from = string.upper(matches[1]) + else -- second pattern + base = string.upper(matches[1]) to = string.upper(matches[2]) amount = 1 end + local value, iserr = currency:convert_money(base, to, amount) + if iserr then utilities.answer_inline_query(self, inline_query) return end + + local output = amount..' '..base..' = *'..value..' '..to..'*' + if tonumber(amount) == 1 then + title = amount..' '..base..' entspricht' + else + title = amount..' '..base..' entsprechen' + end + local results = '[{"type":"article","id":"20","title":"'..title..'","description":"'..value..' '..to..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/currency/cash.jpg","thumb_width":157,"thumb_height":140,"input_message_content":{"message_text":"'..output..'","parse_mode":"Markdown"}}]' + utilities.answer_inline_query(self, inline_query, results, 3600) +end + +function currency:convert_money(base, to, amount) + local url = BASE_URL..'/latest?base='..base..'&symbols='..to local amount = string.gsub(amount, ",", ".") - amount = tonumber(amount) - local result = 1 - local BASE_URL = 'https://www.google.com/finance/converter' + local amount = tonumber(amount) + local res, code = https.request(url) + if code ~= 200 and code ~= 422 then + return 'NOCONNECT', true + end + + local res, code = https.request(url) + local data = json.decode(res) + if data.error then + return 'WRONGBASE', true + end + + local rate = data.rates[to] + if not rate then + return 'WRONGCONVERTRATE', true + end + + if amount == 1 then + value = round(rate, 2) + else + value = round(rate * amount, 2) + end + local value = tostring(string.gsub(value, "%.", ",")) + + return value +end + +function currency:action(msg, config, matches) + if matches[1] == '/cash' then + utilities.send_reply(self, msg, currency.doc, true) + return + elseif not matches[2] then -- first pattern + base = 'EUR' + to = string.upper(matches[1]) + amount = 1 + elseif matches[3] then -- third pattern + base = string.upper(matches[2]) + to = string.upper(matches[3]) + amount = matches[1] + else -- second pattern + base = string.upper(matches[1]) + to = string.upper(matches[2]) + amount = 1 + end + if from == to then utilities.send_reply(self, msg, 'Jaja, sehr witzig...') return end - - local url = BASE_URL..'?from='..from..'&to='..to..'&a='..amount - local str, res = https.request(url) - if res ~= 200 then + + local value = currency:convert_money(base, to, amount) + if value == 'NOCONNECT' then utilities.send_reply(self, msg, config.errors.connection) return - end - - local str = str:match('(.*) %u+') - if not str then - utilities.send_reply(self, msg, 'Keine gültige Währung - sieh dir die Währungsliste bei [Google Finanzen](https://www.google.com/finance/converter) an.', true) + elseif value == 'WRONGBASE' then + utilities.send_reply(self, msg, 'Keine gültige Basiswährung.') + return + elseif value == 'WRONGCONVERTRATE' then + utilities.send_reply(self, msg, 'Keine gültige Umwandlungswährung.') return end - local result = string.format('%.2f', str) - local result = string.gsub(result, "%.", ",") - local amount = tostring(string.gsub(amount, "%.", ",")) - local output = amount..' '..from..' = *'..result..' '..to..'*' + local output = amount..' '..base..' = *'..value..' '..to..'*' utilities.send_reply(self, msg, output, true) end diff --git a/miku/plugins/echo.lua b/miku/plugins/echo.lua index 11123c0..a26feb3 100644 --- a/miku/plugins/echo.lua +++ b/miku/plugins/echo.lua @@ -18,15 +18,15 @@ function echo:inline_callback(inline_query, config, matches) -- enable custom markdown button if text:match('%[.*%]%(.*%)') or text:match('%*.*%*') or text:match('_.*_') or text:match('`.*`') then - results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/custom.jpg","title":"Eigenes Markdown","description":"'..text..'","input_message_content":{"message_text":"'..text..'","parse_mode":"Markdown"}},' + results = results..'{"type":"article","id":"3","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/custom.jpg","title":"Eigenes Markdown","description":"'..text..'","input_message_content":{"message_text":"'..text..'","parse_mode":"Markdown"}},' end - local results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/fett.jpg","title":"Fett","description":"*'..text..'*","input_message_content":{"message_text":"'..text..'","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":"'..text..'","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":"'..text..'","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":"'..text..'","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":"'..text..'","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":"'..text..'","parse_mode":"HTML"}}]' utilities.answer_inline_query(self, inline_query, results, 0) end function echo:action(msg) - local input = utilities.input(msg.text) + local input = utilities.input_from_msg(msg) if not input then utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true) else @@ -40,4 +40,4 @@ function echo:action(msg) end end -return echo +return echo \ No newline at end of file diff --git a/miku/plugins/expand.lua b/miku/plugins/expand.lua index d416f93..1c951f6 100644 --- a/miku/plugins/expand.lua +++ b/miku/plugins/expand.lua @@ -26,7 +26,7 @@ function expand:inline_callback(inline_query, config, matches) description = url end - local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..title..'","description":"'..description..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]' + local results = '[{"type":"article","id":"7","title":"'..title..'","description":"'..description..'","url":"'..url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..url..'"}}]' utilities.answer_inline_query(self, inline_query, results, 3600) end @@ -55,4 +55,4 @@ function expand:action(msg, config, matches) end end -return expand +return expand \ No newline at end of file diff --git a/miku/plugins/facebook.lua b/miku/plugins/facebook.lua index e7142d1..bb13fea 100644 --- a/miku/plugins/facebook.lua +++ b/miku/plugins/facebook.lua @@ -8,13 +8,13 @@ function facebook:init(config) end facebook.triggers = { - 'facebook.com/([A-Za-z0-9-._-]+)/(posts)/(%d+)', - 'facebook.com/(permalink).php%?(story_fbid)=(%d+)&id=(%d+)', - 'facebook.com/(photo).php%?fbid=(%d+)', - 'facebook.com/([A-Za-z0-9-._-]+)/(photos)/a.(%d+[%d%.]*)/(%d+)', - 'facebook.com/(video).php%?v=(%d+)', - 'facebook.com/([A-Za-z0-9-._-]+)/(videos)/(%d+[%d%.]*)', - 'facebook.com/([A-Za-z0-9-._-]+)' + "facebook.com/([A-Za-z0-9-._-]+)/(posts)/(%d+)", + "facebook.com/(permalink).php%?(story_fbid)=(%d+)&id=(%d+)", + "facebook.com/(photo).php%?fbid=(%d+)", + "facebook.com/([A-Za-z0-9-._-]+)/(photos)/a.(%d+[%d%.]*)/(%d+)", + "facebook.com/(video).php%?v=(%d+)", + "facebook.com/([A-Za-z0-9-._-]+)/(videos)/(%d+[%d%.]*)", + "facebook.com/([A-Za-z0-9-._-]+)/?$" } end @@ -22,7 +22,7 @@ local BASE_URL = 'https://graph.facebook.com/v2.5' local fb_access_token = cred_data.fb_access_token local makeOurDate = function(dateString) - local pattern = '(%d+)%/(%d+)%/(%d+)' + local pattern = "(%d+)%/(%d+)%/(%d+)" local month, day, year = dateString:match(pattern) return day..'.'..month..'.'..year end @@ -43,17 +43,18 @@ function facebook:fb_post (id, story_id) local from = data.from.name local message = data.message + if not message then return nil end local name = data.name if data.link then link = '\n'..data.name..'' else - link = '' + link = "" end if data.story then story = ' ('..data.story..')' else - story = '' + story = "" end local text = ''..from..''..story..':\n'..message..'\n'..link @@ -104,13 +105,13 @@ function facebook:facebook_info(name) if data.about then about = '\n'..data.about else - about = '' + about = "" end if data.general_info then general_info = '\n'..data.general_info else - general_info = '' + general_info = "" end if data.birthday and data.founded then @@ -120,7 +121,7 @@ function facebook:facebook_info(name) elseif data.founded and not data.birthday then birth = '\nGegründet: '..data.founded else - birth = '' + birth = "" end local text = ''..name..' ('..category..')'..about..''..general_info..birth diff --git a/miku/plugins/forecast.lua b/miku/plugins/forecast.lua index 86046de..cd31686 100644 --- a/miku/plugins/forecast.lua +++ b/miku/plugins/forecast.lua @@ -1,5 +1,7 @@ local forecast = {} +require("./miku/plugins/weather") + function forecast:init(config) if not cred_data.forecastio_apikey then print('Fehlender Key: forecastio_apikey.') @@ -21,6 +23,12 @@ function forecast:init(config) "^(/forecasth)$", "^(/forecasth) (.*)$" } + forecast.inline_triggers = { + "^(f) (.+)$", + "^(fh) (.+)$", + "^(fh)$", + "^(f)$" + } forecast.doc = [[* ]]..config.cmd_pat..[[f*: Wettervorhersage für deinen Wohnort _(/location set )_ *]]..config.cmd_pat..[[f* __: Wettervorhersage für diesen Ort @@ -35,39 +43,26 @@ local BASE_URL = "https://api.forecast.io/forecast" local apikey = cred_data.forecastio_apikey local google_apikey = cred_data.google_apikey -function get_city_name(lat, lng) - local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng) - if city then return city end - local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey - local res, code = https.request(url) - if code ~= 200 then return 'Unbekannte Stadt' end - local data = json.decode(res).results[1] - local city = data.formatted_address - print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"') - redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city) - return city -end - -function get_condition_symbol(weather, n) - if weather.data[n].icon == 'clear-day' then +function forecast:get_condition_symbol(weather_data) + if weather_data.icon == 'clear-day' then return '☀️' - elseif weather.data[n].icon == 'clear-night' then + elseif weather_data.icon == 'clear-night' then return '🌙' - elseif weather.data[n].icon == 'rain' then + elseif weather_data.icon == 'rain' then return '☔️' - elseif weather.data[n].icon == 'snow' then + elseif weather_data.icon == 'snow' then return '❄️' - elseif weather.data[n].icon == 'sleet' then + elseif weather_data.icon == 'sleet' then return '🌨' - elseif weather.data[n].icon == 'wind' then + elseif weather_data.icon == 'wind' then return '💨' - elseif weather.data[n].icon == 'fog' then + elseif weather_data.icon == 'fog' then return '🌫' - elseif weather.data[n].icon == 'cloudy' then + elseif weather_data.icon == 'cloudy' then return '☁️☁️' - elseif weather.data[n].icon == 'partly-cloudy-day' then + elseif weather_data.icon == 'partly-cloudy-day' then return '🌤' - elseif weather.data[n].icon == 'partly-cloudy-night' then + elseif weather_data.icon == 'partly-cloudy-night' then return '🌙☁️' else return '' @@ -75,22 +70,34 @@ function get_condition_symbol(weather, n) end function get_temp(weather, n, hourly) + local weather_data = weather.data[n] if hourly then - local temperature = string.gsub(round(weather.data[n].temperature, 1), "%.", ",") - local condition = weather.data[n].summary - return temperature..'°C | '..get_condition_symbol(weather, n)..' '..condition + local temperature = string.gsub(round(weather_data.temperature, 1), "%.", ",") + local condition = weather_data.summary + return temperature..'°C | '..forecast:get_condition_symbol(weather_data)..' '..condition else - local day = string.gsub(round(weather.data[n].temperatureMax, 1), "%.", ",") - local night = string.gsub(round(weather.data[n].temperatureMin, 1), "%.", ",") - local condition = weather.data[n].summary - return '☀️ '..day..'°C | 🌙 '..night..'°C | '..get_condition_symbol(weather, n)..' '..condition + local day = string.gsub(round(weather_data.temperatureMax, 1), "%.", ",") + local night = string.gsub(round(weather_data.temperatureMin, 1), "%.", ",") + local condition = weather_data.summary + return '☀️ '..day..'°C | 🌙 '..night..'°C | '..forecast:get_condition_symbol(weather_data)..' '..condition end end -function forecast:get_forecast(lat, lng) +function forecast:get_forecast(lat, lng, is_inline) print('Finde Wetter in '..lat..', '..lng) - local text = redis:get('telegram:cache:forecast:'..lat..','..lng) - if text then print('...aus dem Cache..') return text end + local hash = 'telegram:cache:forecast:'..lat..','..lng + local text = redis:hget(hash, 'text') + if text then + print('...aus dem Cache..') + if is_inline then + local ttl = redis:ttl(hash) + local city = redis:hget(hash, 'city') + local summary = redis:hget(hash, 'summary') + return city, summary, text, ttl + else + return text + end + end local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,hourly,alerts,flags' @@ -100,43 +107,61 @@ function forecast:get_forecast(lat, lng) method = "GET", sink = ltn12.sink.table(response_body) } - local ok, response_code, response_headers, response_status_line = https.request(request_constructor) + local ok, response_code, response_headers = https.request(request_constructor) if not ok then return nil end - local data = json.decode(table.concat(response_body)) - local ttl = string.sub(response_headers["cache-control"], 9) - - - local weather = data.daily + local weather = json.decode(table.concat(response_body)).daily + local ttl = tonumber(string.sub(response_headers["cache-control"], 9)) local city = get_city_name(lat, lng) + local weather_summary = weather.summary - local header = '*Vorhersage für '..city..':*\n_'..weather.summary..'_\n' + local header = '*Vorhersage für '..city..':*\n_'..weather_summary..'_\n' local text = '*Heute:* '..get_temp(weather, 1) local text = text..'\n*Morgen:* '..get_temp(weather, 2) - for day in pairs(weather.data) do + local weather_data = weather.data + for day in pairs(weather_data) do if day > 2 then - text = text..'\n*'..convert_timestamp(weather.data[day].time, '%a, %d.%m')..'*: '..get_temp(weather, day) + text = text..'\n*'..convert_timestamp(weather_data[day].time, '%a, %d.%m')..'*: '..get_temp(weather, day) end end - local text = string.gsub(text, "Mon", "Mo") - local text = string.gsub(text, "Tue", "Di") - local text = string.gsub(text, "Wed", "Mi") - local text = string.gsub(text, "Thu", "Do") - local text = string.gsub(text, "Fri", "Fr") - local text = string.gsub(text, "Sat", "Sa") - local text = string.gsub(text, "Sun", "So") + local text = text:gsub("Mon", "Mo") + local text = text:gsub("Tue", "Di") + local text = text:gsub("Wed", "Mi") + local text = text:gsub("Thu", "Do") + local text = text:gsub("Fri", "Fr") + local text = text:gsub("Sat", "Sa") + local text = text:gsub("Sun", "So") - cache_data('forecast', lat..','..lng, header..text, tonumber(ttl), 'key') + print('Caching data...') + redis:hset(hash, 'city', city) + redis:hset(hash, 'summary', weather_summary) + redis:hset(hash, 'text', header..text) + redis:expire(hash, ttl) - return header..text + if is_inline then + return city, weather_summary, header..text, ttl + else + return header..text + end end -function forecast:get_forecast_hourly(lat, lng) +function forecast:get_forecast_hourly(lat, lng, is_inline) print('Finde stündliches Wetter in '..lat..', '..lng) - local text = redis:get('telegram:cache:forecast:'..lat..','..lng..':hourly') - if text then print('...aus dem Cache..') return text end + local hash = 'telegram:cache:forecast:'..lat..','..lng..':hourly' + local text = redis:hget(hash, 'text') + if text then + print('...aus dem Cache..') + if is_inline then + local ttl = redis:ttl(hash) + local city = redis:hget(hash, 'city') + local summary = redis:hget(hash, 'summary') + return city, summary, text, ttl + else + return text + end + end local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,daily,alerts,flags' @@ -146,32 +171,67 @@ function forecast:get_forecast_hourly(lat, lng) method = "GET", sink = ltn12.sink.table(response_body) } - local ok, response_code, response_headers, response_status_line = https.request(request_constructor) + local ok, response_code, response_headers = https.request(request_constructor) if not ok then return nil end - local data = json.decode(table.concat(response_body)) - local ttl = string.sub(response_headers["cache-control"], 9) - - - local weather = data.hourly + local weather = json.decode(table.concat(response_body)).hourly + local ttl = tonumber(string.sub(response_headers["cache-control"], 9)) local city = get_city_name(lat, lng) + local weather_summary = weather.summary - local header = '*24-Stunden-Vorhersage für '..city..':*\n_'..weather.summary..'_' + local header = '*24-Stunden-Vorhersage für '..city..':*\n_'..weather_summary..'_' local text = "" - for hour in pairs(weather.data) do + local weather_data = weather.data + for hour in pairs(weather_data) do if hour < 26 then - text = text..'\n*'..convert_timestamp(weather.data[hour].time, '%H:%M Uhr')..'* | '..get_temp(weather, hour, true) + text = text..'\n*'..convert_timestamp(weather_data[hour].time, '%H:%M Uhr')..'* | '..get_temp(weather, hour, true) end end - cache_data('forecast', lat..','..lng..':hourly', header..text, tonumber(ttl), 'key') + print('Caching data...') + redis:hset(hash, 'city', city) + redis:hset(hash, 'summary', weather_summary) + redis:hset(hash, 'text', header..text) + redis:expire(hash, ttl) - return header..text + if is_inline then + return city, weather_summary, header..text, ttl + else + return header..text + end +end + +function forecast:inline_callback(inline_query, config, matches) + local user_id = inline_query.from.id + if matches[2] then + city = matches[2] + is_personal = false + else + local set_location = get_location(user_id) + is_personal = true + if not set_location then + city = 'Berlin, Deutschland' + else + city = set_location + end + end + + local lat, lng = get_city_coordinates(city, config) + if not lat and not lng then utilities.answer_inline_query(self, inline_query) return end + if matches[1] == 'f' then + title, description, text, ttl = forecast:get_forecast(lat, lng, true) + else + title, description, text, ttl = forecast:get_forecast_hourly(lat, lng, true) + end + if not title and not description and not text and not ttl then utilities.answer_inline_query(self, inline_query) return end + + local text = text:gsub('\n', '\\n') + local results = '[{"type":"article","id":"28062013","title":"'..title..'","description":"'..description..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/weather/cloudy.jpg","thumb_width":80,"thumb_height":80,"input_message_content":{"message_text":"'..text..'", "parse_mode":"Markdown"}}]' + utilities.answer_inline_query(self, inline_query, results, ttl, is_personal) end function forecast:action(msg, config, matches) local user_id = msg.from.id - local city = get_location(user_id) if matches[2] then city = matches[2] @@ -184,22 +244,11 @@ function forecast:action(msg, config, matches) end end - local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') - local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng') + local lat, lng = get_city_coordinates(city, config) if not lat and not lng then - print('Koordinaten nicht eingespeichert, frage Google...') - coords = utilities.get_coords(city, config) - lat = coords.lat - lng = coords.lon - end - - if not lat and not lng then - utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) + utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) return end - - redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat) - redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng) if matches[1] == '/forecasth' or matches[1] == '/fh' then text = forecast:get_forecast_hourly(lat, lng) @@ -212,4 +261,4 @@ function forecast:action(msg, config, matches) utilities.send_reply(self, msg, text, true) end -return forecast +return forecast \ No newline at end of file diff --git a/miku/plugins/gImages.lua b/miku/plugins/gImages.lua index 3929f53..e083d6f 100644 --- a/miku/plugins/gImages.lua +++ b/miku/plugins/gImages.lua @@ -131,7 +131,7 @@ end function gImages:cache_result(results, text) local cache = {} for v in pairs(results) do - table.insert(cache, results[v].link) + cache[v] = results[v].link end for n, link in pairs(cache) do redis:hset('telegram:cache:gImages:'..link, 'mime', results[n].mime) @@ -234,4 +234,4 @@ function gImages:action(msg, config, matches) end end -return gImages +return gImages \ No newline at end of file diff --git a/miku/plugins/gImages_nsfw.lua b/miku/plugins/gImages_nsfw.lua index c32e411..62d2a3f 100644 --- a/miku/plugins/gImages_nsfw.lua +++ b/miku/plugins/gImages_nsfw.lua @@ -131,7 +131,7 @@ end function gImages_nsfw:cache_result(results, text) local cache = {} for v in pairs(results) do - table.insert(cache, results[v].link) + cache[v] = results[v].link end for n, link in pairs(cache) do redis:hset('telegram:cache:gImages_nsfw:'..link, 'mime', results[n].mime) diff --git a/miku/plugins/gMaps.lua b/miku/plugins/gMaps.lua index 659be17..adb0f4b 100644 --- a/miku/plugins/gMaps.lua +++ b/miku/plugins/gMaps.lua @@ -4,6 +4,9 @@ gMaps.command = 'loc ' function gMaps:init(config) gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Ll][Oo][Cc]', true).table + gMaps.inline_triggers = { + "^[Ll][Oo][Cc] (.+)" + } gMaps.doc = [[* ]]..config.cmd_pat..[[loc* __: Sendet Ort via Google Maps]] end @@ -16,25 +19,32 @@ function gMaps:get_staticmap(area, lat, lon) return file end +function gMaps:inline_callback(inline_query, config, matches) + local place = matches[1] + local coords = utilities.get_coords(place, config) + if type(coords) == 'string' then utilities.answer_inline_query(self, inline_query) return end + + local results = '[{"type":"venue","id":"10","latitude":'..coords.lat..',"longitude":'..coords.lon..',"title":"Ort","address":"'..coords.addr..'"}]' + + utilities.answer_inline_query(self, inline_query, results, 10000) +end + function gMaps:action(msg, config) - local input = utilities.input(msg.text) - if not input then - if msg.reply_to_message and msg.reply_to_message.text then - input = msg.reply_to_message.text - else - utilities.send_message(self, msg.chat.id, gMaps.doc, true, msg.message_id, true) - return - end - end - utilities.send_typing(self, msg.chat.id, 'find_location') - local coords = utilities.get_coords(input, config) - if type(coords) == 'string' then - utilities.send_reply(self, msg, coords) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, gMaps.doc, true) + return + end + + utilities.send_typing(self, msg.chat.id, 'find_location') + local coords = utilities.get_coords(input, config) + if type(coords) == 'string' then + utilities.send_reply(self, msg, coords) + return + end utilities.send_location(self, msg.chat.id, coords.lat, coords.lon, msg.message_id) utilities.send_photo(self, msg.chat.id, gMaps:get_staticmap(input, coords.lat, coords.lon), nil, msg.message_id) end -return gMaps +return gMaps \ No newline at end of file diff --git a/miku/plugins/gSearch.lua b/miku/plugins/gSearch.lua index b5fd5d0..e265945 100644 --- a/miku/plugins/gSearch.lua +++ b/miku/plugins/gSearch.lua @@ -51,14 +51,10 @@ function gSearch:stringlinks(results, stats) end function gSearch:action(msg, config) - local input = utilities.input(msg.text) + local input = utilities.input_from_msg(msg) if not input then - if msg.reply_to_message and msg.reply_to_message.text then - input = msg.reply_to_message.text - else - utilities.send_message(self, msg.chat.id, gSearch.doc, true, msg.message_id, true) - return - end + utilities.send_reply(self, msg, gImages.doc, true) + return end local results, stats = gSearch:googlethat(input, onfig) @@ -76,4 +72,4 @@ function gSearch:action(msg, config) end -return gSearch +return gSearch \ No newline at end of file diff --git a/miku/plugins/getfile.lua b/miku/plugins/getfile.lua index a82aa28..09e5c52 100644 --- a/miku/plugins/getfile.lua +++ b/miku/plugins/getfile.lua @@ -1,6 +1,5 @@ -- YOU NEED THE FOLLOWING FOLDERS: photo, document, video, voice -- PLEASE ADJUST YOUR PATH BELOW --- Save your bot api key in redis set telegram:credentials! local media_download = {} @@ -34,7 +33,7 @@ function media_download:download_to_file_permanently(url, file_name) return true end -function media_download:pre_process(msg, self) +function media_download:pre_process(msg, self, config) if msg.photo then local lv = #msg.photo -- find biggest photo, always the last value file_id = msg.photo[lv].file_id @@ -89,7 +88,7 @@ function media_download:pre_process(msg, self) end -- Construct what we want - local download_url = 'https://api.telegram.org/file/bot'..cred_data.bot_api_key..'/'..request.result.file_path + local download_url = 'https://api.telegram.org/file/bot'..config.bot_api_key..'/'..request.result.file_path local ok = media_download:download_to_file_permanently(download_url, file_path) if not ok then print('Download fehlgeschlagen!') diff --git a/miku/plugins/giphy.lua b/miku/plugins/giphy.lua index 3177cf3..29bf42d 100644 --- a/miku/plugins/giphy.lua +++ b/miku/plugins/giphy.lua @@ -30,12 +30,14 @@ function giphy:inline_callback(inline_query, config, matches) else data = giphy:get_gifs(matches[2]) end - if not data then return end - if not data[1] then return end + if not data then utilities.answer_inline_query(self, inline_query) return end + if not data[1] then utilities.answer_inline_query(self, inline_query) return end local results = '[' - + local id = 450 + for n in pairs(data) do - results = results..'{"type":"mpeg4_gif","id":"'..math.random(100000000000000000)..'","mpeg4_url":"'..data[n].images.original.mp4..'","thumb_url":"'..data[n].images.fixed_height.url..'","mpeg4_width":'..data[n].images.original.width..',"mp4_height":'..data[n].images.original.height..'}' + results = results..'{"type":"mpeg4_gif","id":"'..id..'","mpeg4_url":"'..data[n].images.original.mp4..'","thumb_url":"'..data[n].images.fixed_height.url..'","mpeg4_width":'..data[n].images.original.width..',"mp4_height":'..data[n].images.original.height..'}' + id = id+1 if n < #data then results = results..',' end diff --git a/miku/plugins/googl.lua b/miku/plugins/googl.lua index f56a499..b7f9e82 100644 --- a/miku/plugins/googl.lua +++ b/miku/plugins/googl.lua @@ -25,9 +25,9 @@ end function googl:inline_callback(inline_query, config, matches) local shorturl = matches[1] local text, longUrl = googl:send_googl_info(shorturl) - if not longUrl then return end + if not longUrl then utilities.answer_inline_query(self, inline_query) return end - local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"Verlängerte URL","description":"'..longUrl..'","url":"'..longUrl..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..text..'"}}]' + local results = '[{"type":"article","id":"9","title":"Verlängerte URL","description":"'..longUrl..'","url":"'..longUrl..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":165,"thumb_height":150,"hide_url":true,"input_message_content":{"message_text":"'..text..'"}}]' utilities.answer_inline_query(self, inline_query, results, 1) end diff --git a/miku/plugins/gps.lua b/miku/plugins/gps.lua index c6aa79a..c829cb8 100644 --- a/miku/plugins/gps.lua +++ b/miku/plugins/gps.lua @@ -5,6 +5,13 @@ gps.command = 'gps ,' function gps:init(config) gps.triggers = { "^/[Gg][Pp][Ss] ([^,]*)[,%s]([^,]*)$", + "google.de/maps/@([^,]*)[,%s]([^,]*)", + "google.com/maps/@([^,]*)[,%s]([^,]*)", + "google.de/maps/place/@([^,]*)[,%s]([^,]*)", + "google.com/maps/place/@([^,]*)[,%s]([^,]*)" + } + gps.inline_triggers = { + "^[Gg][Pp][Ss] ([^,]*)[,%s]([^,]*)$", "google.de/maps/@([^,]*)[,%s]([^,]*)", "google.com/maps/@([^,]*)[,%s]([^,]*)", "google.de/maps/place/@([^,]*)[,%s]([^,]*)", @@ -14,6 +21,15 @@ function gps:init(config) ]]..config.cmd_pat..[[gps* __,__: Sendet Karte mit diesen Koordinaten]] end +function gps:inline_callback(inline_query, config, matches) + local lat = matches[1] + local lon = matches[2] + + local results = '[{"type":"location","id":"8","latitude":'..lat..',"longitude":'..lon..',"title":"Standort"}]' + + utilities.answer_inline_query(self, inline_query, results, 10000) +end + function gps:action(msg, config, matches) utilities.send_typing(self, msg.chat.id, 'upload_photo') local lat = matches[1] @@ -32,4 +48,4 @@ function gps:action(msg, config, matches) utilities.send_location(self, msg.chat.id, lat, lon, msg.message_id) end -return gps +return gps \ No newline at end of file diff --git a/miku/plugins/help.lua b/miku/plugins/help.lua index 7f140c5..197e113 100644 --- a/miku/plugins/help.lua +++ b/miku/plugins/help.lua @@ -5,48 +5,81 @@ local help = {} local help_text function help:init(config) - help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Hh][Ii][Ll][Ff][Ee]', true):t('[Hh][Ee][Ll][Pp]', true).table + help.triggers = { + "^/[Hh][Ii][Ll][Ff][Ee] (.+)", + "^/[Hh][Ee][Ll][Pp] (.+)", + "^/(hilfe)_(.+)", + "^/[Hh][Ii][Ll][Ff][Ee]$" + } + help.inline_triggers = { + "^[Hh][Ii][Ll][Ff][Ee] (.+)", + "^[Hh][Ee][Ll][Pp] (.+)" + } end -function help:action(msg, config) +function help:inline_callback(inline_query, config, matches) + local query = matches[1] + + for n=1, #self.plugins do + local plugin = self.plugins[n] + if plugin.command and utilities.get_word(plugin.command, 1) == query and plugin.doc then + local doc = plugin.doc + local doc = doc:gsub('"', '\\"') + local doc = doc:gsub('\\n', '\\\n') + local chosen_plugin = utilities.get_word(plugin.command, 1) + local results = '[{"type":"article","id":"9","title":"Hilfe für '..chosen_plugin..'","description":"Hilfe für das Plugin \\"'..chosen_plugin..'\\" wird gepostet.","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/help/hilfe.jpg","input_message_content":{"message_text":"'..doc..'","parse_mode":"Markdown"}}]' + utilities.answer_inline_query(self, inline_query, results, 600, nil, nil, 'Hilfe anzeigen', 'hilfe_'..chosen_plugin) + end + end + utilities.answer_inline_query(self, inline_query) +end + +function help:action(msg, config, matches) + if matches[2] then + input = matches[2] + elseif matches[1] ~= '/hilfe' then + input = matches[1] + else + input = nil + end + + + -- Attempts to send the help message via PM. + -- If msg is from a group, it tells the group whether the PM was successful. + if not input then local commandlist = {} - help_text = '*Verfügbare Befehle:*\n• '..config.cmd_pat - - for _,plugin in ipairs(self.plugins) do - if plugin.command then - - table.insert(commandlist, plugin.command) - end + local help_text = '*Verfügbare Befehle:*\n• '..config.cmd_pat + for n=1, #self.plugins do + local plugin = self.plugins[n] + if plugin.command then + commandlist[#commandlist+1] = plugin.command + end end - table.insert(commandlist, 'hilfe [Befehl]') + commandlist[#commandlist+1] = 'hilfe [Befehl]' table.sort(commandlist) - help_text = help_text .. table.concat(commandlist, '\n• '..config.cmd_pat) .. '\nParameter: [optional]' + local help_text = help_text .. table.concat(commandlist, '\n• '..config.cmd_pat) .. '\nParameter: [optional]' + local help_text = help_text:gsub('%[', '\\[') - help_text = help_text:gsub('%[', '\\[') - local input = utilities.input(msg.text_lower) - - -- Attempts to send the help message via PM. - -- If msg is from a group, it tells the group whether the PM was successful. - if not input then - local res = utilities.send_message(self, msg.from.id, help_text, true, nil, true) - if not res then - utilities.send_reply(self, msg, 'Bitte schreibe mir zuerst [privat](http://telegram.me/' .. self.info.username .. '?start=help) für eine Hilfe.', true) - elseif msg.chat.type ~= 'private' then - utilities.send_reply(self, msg, 'Ich habe dir die Hilfe per PN gesendet!.') - end - return + local res = utilities.send_message(self, msg.from.id, help_text, true, nil, true) + if not res then + utilities.send_reply(self, msg, 'Bitte schreibe mir zuerst [privat](http://telegram.me/' .. self.info.username .. '?start=help) für eine Hilfe.', true) + elseif msg.chat.type ~= 'private' then + utilities.send_reply(self, msg, 'Ich habe dir die Hilfe privat gesendet!.') end + return + end - for _,plugin in ipairs(self.plugins) do - if plugin.command and utilities.get_word(plugin.command, 1) == input and plugin.doc then - local output = '*Hilfe für* _' .. utilities.get_word(plugin.command, 1) .. '_ *:*' .. plugin.doc - utilities.send_message(self, msg.chat.id, output, true, nil, true) - return - end + for n=1, #self.plugins do + local plugin = self.plugins[n] + if plugin.command and utilities.get_word(plugin.command, 1) == input and plugin.doc then + local output = '*Hilfe für* _' .. utilities.get_word(plugin.command, 1) .. '_ *:*' .. plugin.doc + utilities.send_message(self, msg.chat.id, output, true, nil, true) + return end + end - utilities.send_reply(self, msg, 'Für diesen Befehl gibt es keine Hilfe.') + utilities.send_reply(self, msg, 'Für diesen Befehl gibt es keine Hilfe.') end return help \ No newline at end of file diff --git a/miku/plugins/id.lua b/miku/plugins/id.lua index b57c0a9..e723b3f 100644 --- a/miku/plugins/id.lua +++ b/miku/plugins/id.lua @@ -3,11 +3,16 @@ local id = {} id.command = 'id' function id:init(config) - id.triggers = { + id.triggers = { "^/id$", "^/ids? (chat)$" - } - id.doc = [[```Zeige dir deine ID und die IDs aller Gruppenmitglieder an.``]] + } + + id.inline_triggers = { + "^id$" + } + + id.doc = [[```Zeige dir deine ID und die IDs aller Gruppenmitglieder an.``]] end function id:get_member_count(self, msg, chat_id) @@ -41,7 +46,15 @@ function id:get_user(user_id, chat_id) return user_info end -function id:action(msg) +function id:inline_callback(inline_query, config, matches) + local id = tostring(inline_query.from.id) + local name = utilities.build_name(inline_query.from.first_name, inline_query.from.last_name) + + local results = '[{"type":"article","id":"30","title":"Deine Telegram-ID ist:","description":"'..id..'","input_message_content":{"message_text":"'..name..': '..id..'","parse_mode":"HTML"}}]' + utilities.answer_inline_query(self, inline_query, results, 10000, true) +end + +function id:action(msg, config, matches) if matches[1] == "/id" then if msg.reply_to_message then @@ -86,7 +99,7 @@ function id:action(msg) for i = 1, #users do local user_id = users[i] local user_info = id:get_user(user_id, chat_id) - table.insert(users_info, user_info) + users_info[#users_info+1] = user_info end -- get all administrators and the creator @@ -94,7 +107,7 @@ function id:action(msg) local admins = {} for num in pairs(administrators.result) do if administrators.result[num].status ~= 'creator' then - table.insert(admins, tostring(administrators.result[num].user.id)) + admins[#admins+1] = tostring(administrators.result[num].user.id) else creator_id = administrators.result[num].user.id end diff --git a/miku/plugins/images.lua b/miku/plugins/images.lua index 70a83b8..4862e41 100644 --- a/miku/plugins/images.lua +++ b/miku/plugins/images.lua @@ -5,7 +5,7 @@ images.triggers = { "(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Jj][Pp][Ee]?[Gg])$" } -function images:action(msg) +function images:action(msg, config, matches) local url = matches[1] local file, last_modified, nocache = get_cached_file(url, nil, msg.chat.id, 'upload_photo', self) local result = utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id) @@ -17,4 +17,4 @@ function images:action(msg) cache_file(result, url, last_modified) end -return images +return images \ No newline at end of file diff --git a/miku/plugins/imdb.lua b/miku/plugins/imdb.lua index 9df39ce..06a233a 100644 --- a/miku/plugins/imdb.lua +++ b/miku/plugins/imdb.lua @@ -3,51 +3,99 @@ local imdb = {} imdb.command = 'imdb ' function imdb:init(config) - imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('[Ii][Mm][Dd][Bb]', true).table + imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table + imdb.inline_triggers = { + "^imdb (.+)" + } imdb.doc = [[* ]]..config.cmd_pat..[[imdb* __ Sucht einen _Film_ bei IMDb]] end -function imdb:action(msg, config) - - local input = utilities.input(msg.text) - if not input then - if msg.reply_to_message and msg.reply_to_message.text then - input = msg.reply_to_message.text - else - utilities.send_message(self, msg.chat.id, imdb.doc, true, msg.message_id, true) - return - end - end - - local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input) - - local jstr, res = http.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end - - local jdat = json.decode(jstr) - - if jdat.Response ~= 'True' then - utilities.send_reply(self, msg, config.errors.results) - return - end - - local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')* von '..jdat.Director..'\n' - output = output .. string.gsub(jdat.imdbRating, '%.', ',') ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n' - output = output .. '_' .. jdat.Plot .. '_\n' - output = output .. '[IMDb-Seite besuchen](http://imdb.com/title/' .. jdat.imdbID .. ')' - - utilities.send_message(self, msg.chat.id, output, true, nil, true) - - if jdat.Poster ~= "N/A" then - local file = download_to_file(jdat.Poster) - utilities.send_photo(self, msg.chat.id, file) - end +local BASE_URL = 'https://www.omdbapi.com' +function imdb:get_imdb_info(id) + local url = BASE_URL..'/?i='..id + local res, code = https.request(url) + if code ~= 200 then return end + local movie_info = json.decode(res) + return movie_info end -return imdb +function imdb:inline_callback(inline_query, config, matches) + local query = matches[1] + local url = BASE_URL..'/?s='..URL.escape(query) + local res, code = https.request(url) + if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end + local data = json.decode(res) + if data.Response ~= "True" then utilities.answer_inline_query(self, inline_query) return end + + local results = '[' + local id = 500 + for num in pairs(data.Search) do + if num > 5 then + break; + end + local imdb_id = data.Search[num].imdbID + local movie_info = imdb:get_imdb_info(imdb_id) + local title = movie_info.Title + local year = movie_info.Year + local text = ''..movie_info.Title.. ' ('..movie_info.Year..') 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'..movie_info.Plot..'' + 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 = ''..jdat.Title.. ' ('..jdat.Year..') von '..jdat.Director..'\n' + output = output..string.gsub(jdat.imdbRating, '%.', ',')..'/10 | '..jdat.Runtime..' | '.. jdat.Genre..'\n' + output = output..'' .. jdat.Plot .. '' + + 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 \ No newline at end of file diff --git a/miku/plugins/media.lua b/miku/plugins/media.lua index 67e26fe..29b900f 100644 --- a/miku/plugins/media.lua +++ b/miku/plugins/media.lua @@ -1,7 +1,5 @@ local media = {} -mimetype = (loadfile "./miku/mimetype.lua")() - media.triggers = { "(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(gif))$", "(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(mp4))$", @@ -23,10 +21,10 @@ media.triggers = { "(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(webp))$" } -function media:action(msg) +function media:action(msg, config, matches) local url = matches[1] local ext = matches[2] - local mime_type = mimetype.get_content_type_no_sub(ext) + local mime_type = mime.get_content_type_no_sub(ext) local receiver = msg.chat.id if mime_type == 'audio' then @@ -41,19 +39,14 @@ function media:action(msg) if not file then return end if ext == 'gif' then - print('Sende GIF') result = utilities.send_document(self, receiver, file, nil, msg.message_id) elseif ext == 'ogg' then - print('Sende OGG') result = utilities.send_voice(self, receiver, file, nil, msg.message_id) elseif mime_type == 'audio' then - print('Sende Audio') result = utilities.send_audio(self, receiver, file, nil, msg.message_id) elseif mime_type == 'video' then - print('Sende Video') result = utilities.send_video(self, receiver, file, nil, msg.message_id) else - print('Sende Datei') result = utilities.send_document(self, receiver, file, nil, msg.message_id) end diff --git a/miku/plugins/patterns.lua b/miku/plugins/patterns.lua index 68a5208..7269c7f 100644 --- a/miku/plugins/patterns.lua +++ b/miku/plugins/patterns.lua @@ -1,8 +1,11 @@ local patterns = {} -patterns.triggers = { - '^/?s/.-/.-$' -} +function patterns:init(config) + patterns.command = 's//' + patterns.triggers = { + config.cmd_pat .. '?s/.-/.-$' + } +end function patterns:action(msg) if not msg.reply_to_message then return true end diff --git a/miku/plugins/plugins.lua b/miku/plugins/plugins.lua index 240426c..f6ce2b2 100644 --- a/miku/plugins/plugins.lua +++ b/miku/plugins/plugins.lua @@ -5,12 +5,12 @@ local bot = require('miku.bot') function plugin_manager:init(config) plugin_manager.triggers = { "^/plugins$", + "^/plugins? (enable) ([%w_%.%-]+) (chat) (%d+)$", + "^/plugins? (enable) ([%w_%.%-]+) (chat)$", + "^/plugins? (disable) ([%w_%.%-]+) (chat) (%d+)$", + "^/plugins? (disable) ([%w_%.%-]+) (chat)$", "^/plugins? (enable) ([%w_%.%-]+)$", "^/plugins? (disable) ([%w_%.%-]+)$", - "^/plugins? (enable) ([%w_%.%-]+) (chat) (%d+)", - "^/plugins? (enable) ([%w_%.%-]+) (chat)", - "^/plugins? (disable) ([%w_%.%-]+) (chat) (%d+)", - "^/plugins? (disable) ([%w_%.%-]+) (chat)", "^/plugins? (reload)$", "^/(reload)$" } diff --git a/miku/plugins/post_photo.lua b/miku/plugins/post_photo.lua new file mode 100644 index 0000000..38eb757 --- /dev/null +++ b/miku/plugins/post_photo.lua @@ -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 \ No newline at end of file diff --git a/miku/plugins/preview.lua b/miku/plugins/preview.lua index a09bbc9..345d09a 100644 --- a/miku/plugins/preview.lua +++ b/miku/plugins/preview.lua @@ -4,41 +4,73 @@ preview.command = 'preview ' function preview:init(config) preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table - preview.doc = [[``` -]]..config.cmd_pat..[[preview -Returns a full-message, "unlinked" preview. -```]] + preview.inline_triggers = { + "^pr (https?://[%w-_%.%?%.:/%+=&%~%%#]+)$" + } + preview.doc = [[* +]]..config.cmd_pat..[[preview* __ +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 = ''..title..''..description_in_text..'\n— '..only_name + + local results = '[{"type":"article","id":"77","title":"'..title..'","description":"'..description..'","url":"'..preview_url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/generic/internet.jpg","thumb_width":150,"thumb_height":150,"hide_url":true,"reply_markup":{"inline_keyboard":[[{"text":"Webseite aufrufen","url":"'..preview_url..'"}]]},"input_message_content":{"message_text":"'..message_text..'","parse_mode":"HTML","disable_web_page_preview":true}}]' + utilities.answer_inline_query(self, inline_query, results, 3600, true) end function preview:action(msg) + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, preview.doc, true) + return + end - local input = utilities.input(msg.text) + input = utilities.get_word(input, 1) + if not input:match('^https?://.+') then + input = 'http://' .. input + end - if not input then - utilities.send_message(self, msg.chat.id, preview.doc, true, nil, true) - return - end + local res = http.request(input) + if not res then + utilities.send_reply(self, msg, 'Bitte gebe einen validen Link an.') + return + end - input = utilities.get_word(input, 1) - if not input:match('^https?://.+') then - input = 'http://' .. input - end - - local res = http.request(input) - if not res then - utilities.send_reply(self, msg, 'Please provide a valid link.') - return - end - - if res:len() == 0 then - utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.') - return - end - - -- Invisible zero-width, non-joiner. - local output = '[​](' .. input .. ')' - utilities.send_message(self, msg.chat.id, output, false, nil, true) + if res:len() == 0 then + utilities.send_reply(self, msg, 'Sorry, dieser Link lässt uns keine Vorschau erstellen.') + return + end + -- Invisible zero-width, non-joiner. + local output = '' .. utilities.char.zwnj .. '' + utilities.send_message(self, msg.chat.id, output, false, nil, 'HTML') end -return preview +return preview \ No newline at end of file diff --git a/miku/plugins/qr.lua b/miku/plugins/qr.lua index 2c607d4..7959ba6 100644 --- a/miku/plugins/qr.lua +++ b/miku/plugins/qr.lua @@ -71,11 +71,13 @@ end function qr:inline_callback(inline_query, config, matches) local text = matches[1] - if string.len(text) > 200 then return end + if string.len(text) > 200 then utilities.answer_inline_query(self, inline_query) return end local image_url = qr:qr(text, nil, nil, 'jpg') - if not image_url then return end + if not image_url then utilities.answer_inline_query(self, inline_query) return end + + local id = 600 - local results = '[{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"},' + local results = '[{"type":"photo","id":"'..id..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"},' local i = 0 while i < 29 do @@ -83,7 +85,8 @@ function qr:inline_callback(inline_query, config, matches) local color = math.random(255) local bgcolor = math.random(255) local image_url = qr:qr(text, color, bgcolor, 'jpg') - results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"}' + id = id+1 + results = results..'{"type":"photo","id":"'..id..'","photo_url":"'..image_url..'","thumb_url":"'..image_url..'","photo_width":600,"photo_height":600,"caption":"'..text..'"}' if i < 29 then results = results..',' end @@ -110,4 +113,4 @@ function qr:action(msg, config, matches) utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id) end -return qr +return qr \ No newline at end of file diff --git a/miku/plugins/remind.lua b/miku/plugins/remind.lua index 01575f5..adf967c 100644 --- a/miku/plugins/remind.lua +++ b/miku/plugins/remind.lua @@ -7,88 +7,79 @@ function remind:init(config) remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table remind.doc = [[* - ]]..config.cmd_pat..[[remind* __ __: Erinnert dich in X Minuten an die Nachricht]] +]]..config.cmd_pat..[[remind* __ __ +Erinnert dich in der angegeben Länge in Minuten an eine Nachricht. +Die maximale Länge einer Erinnerung beträgt %s Buchstaben, die maximale Zeit beträgt %s Minuten, die maximale Anzahl an Erinnerung für eine Gruppe ist %s und für private Chats %s.]] + remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private) end -function remind:action(msg) - -- Ensure there are arguments. If not, send doc. +function remind:action(msg, config) local input = utilities.input(msg.text) if not input then - utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true) + utilities.send_reply(self, msg, remind.doc, true) return end - -- Ensure first arg is a number. If not, send doc. - local duration = utilities.get_word(input, 1) - if not tonumber(duration) then - utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true) + local duration = tonumber(utilities.get_word(input, 1)) + if not duration then + utilities.send_reply(self, msg, remind.doc, true) return end - -- Duration must be between one minute and one day (approximately). - duration = tonumber(duration) if duration < 1 then duration = 1 - elseif duration > 1440 then - duration = 1440 + elseif duration > config.remind.max_duration then + duration = config.remind.max_duration end - - -- Ensure there is a second arg. local message = utilities.input(input) if not message then - utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true) + utilities.send_reply(self, msg, remind.doc, true) + return + end + + if #message > config.remind.max_length then + utilities.send_reply(self, msg, 'Die maximale Länge einer Erinnerung ist ' .. config.remind.max_length .. '.') return end - -- Make a database entry for the group/user if one does not exist. - self.database.reminders[msg.chat.id_str] = self.database.reminders[msg.chat.id_str] or {} - -- Limit group reminders to 10 and private reminders to 50. - if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 9 then - utilities.send_reply(self, msg, 'Diese Gruppe hat schon zehn Erinnerungen!') - return - elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then - utilities.send_reply(msg, 'Du hast schon 50 Erinnerungen!') - return + local chat_id_str = tostring(msg.chat.id) + local output + self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {} + if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then + output = 'Sorry, du kannst keine Erinnerungen mehr hinzufügen.' + elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then + output = 'Sorry, diese Gruppe kann keine Erinnerungen mehr hinzufügen.' + else + -- Put together the reminder with the expiration, message, and message to reply to. + local timestamp = os.time() + duration * 60 + local reminder = { + time = timestamp, + message = message + } + table.insert(self.database.reminders[chat_id_str], reminder) + local human_readable_time = convert_timestamp(timestamp, '%H:%M:%S') + output = 'Ich werde dich um *'..human_readable_time..' Uhr* erinnern.' end - - -- Put together the reminder with the expiration, message, and message to reply to. - local timestamp = os.time() + duration * 60 - local reminder = { - time = timestamp, - message = message - } - table.insert(self.database.reminders[msg.chat.id_str], reminder) - local human_readable_time = convert_timestamp(timestamp, '%H:%M:%S') - local output = 'Ich werde dich um *'..human_readable_time..' Uhr* erinnern.' utilities.send_reply(self, msg, output, true) end -function remind:cron() +function remind:cron(config) local time = os.time() -- Iterate over the group entries in the reminders database. for chat_id, group in pairs(self.database.reminders) do - local new_group = {} -- Iterate over each reminder. - for _, reminder in ipairs(group) do + for k, reminder in pairs(group) do -- If the reminder is past-due, send it and nullify it. -- Otherwise, add it to the replacement table. if time > reminder.time then local output = '*ERINNERUNG:*\n"' .. utilities.md_escape(reminder.message) .. '"' local res = utilities.send_message(self, chat_id, output, true, nil, true) - -- If the message fails to send, save it for later. - if not res then - table.insert(new_group, reminder) + -- If the message fails to send, save it for later (if enabled in config). + if res or not config.remind.persist then + group[k] = nil end - else - table.insert(new_group, reminder) end end - -- Nullify the original table and replace it with the new one. - self.database.reminders[chat_id] = new_group - -- Nullify the table if it is empty. - if #new_group == 0 then - self.database.reminders[chat_id] = nil - end end end diff --git a/miku/plugins/respond.lua b/miku/plugins/respond.lua index 8368d7d..8b5117d 100644 --- a/miku/plugins/respond.lua +++ b/miku/plugins/respond.lua @@ -40,14 +40,14 @@ function respond:inline_callback(inline_query, config, matches) elseif string.match(text, "[Nn][Bb][Cc]") or string.match(text, "[Ii][Dd][Cc]") or string.match(text, "[Kk][Aa]") or string.match(text, "[Ii][Dd][Kk]") then face = '¯\\\\\\_(ツ)_/¯' end - results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..face..'","input_message_content":{"message_text":"'..face..'"}}]' + results = '[{"type":"article","id":"8","title":"'..face..'","input_message_content":{"message_text":"'..face..'"}}]' utilities.answer_inline_query(self, inline_query, results, 9999) end function respond:action(msg, config, matches) local user_name = get_name(msg) local receiver = msg.chat.id - local GDRIVE_URL = 'https://de2319bd4b4b51a5ef2939a7638c1d35646f49f8.googledrive.com/host/0B_mfIlDgPiyqU25vUHZqZE9IUXc' + local BASE_URL = 'https://anditest.perseus.uberspace.de/plugins/respond' if user_name == "DefenderX" then user_name = "Deffu" end if string.match(msg.text, "[Ff][Gg][Tt].? [Ss][Ww][Ii][Ff][Tt]") then @@ -78,11 +78,11 @@ function respond:action(msg, config, matches) utilities.send_message(self, receiver, '🐸🐸🐸') return elseif string.match(msg.text, "[Ii][Nn][Ll][Oo][Vv][Ee]") then - local file = download_to_file(GDRIVE_URL..'/inlove.gif') + local file = download_to_file(BASE_URL..'/inlove.gif') utilities.send_document(self, receiver, file) return elseif string.match(msg.text, "[Ww][Aa][Tt]") then - local WAT_URL = GDRIVE_URL..'/wat' + local WAT_URL = BASE_URL..'/wat' local wats = { "/wat1.jpg", "/wat2.jpg", diff --git a/miku/plugins/rss.lua b/miku/plugins/rss.lua index 3ca0434..2b9a77c 100644 --- a/miku/plugins/rss.lua +++ b/miku/plugins/rss.lua @@ -13,7 +13,7 @@ function rss:init(config) "^/rss (sub) (https?://[%w-_%.%?%.:/%+=&%~]+)$", "^/rss (del) (%d+) @(.*)$", "^/rss (del) (%d+)$", - "^/rss (del)", + "^/rss (del)$", "^/rss (sync)$" } rss.doc = [[* diff --git a/miku/plugins/service_migrate_to_supergroup.lua b/miku/plugins/service_migrate_to_supergroup.lua new file mode 100644 index 0000000..510f49d --- /dev/null +++ b/miku/plugins/service_migrate_to_supergroup.lua @@ -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 \ No newline at end of file diff --git a/miku/plugins/stats.lua b/miku/plugins/stats.lua index 869ad76..7e4016c 100644 --- a/miku/plugins/stats.lua +++ b/miku/plugins/stats.lua @@ -70,11 +70,12 @@ function stats:chat_stats(chat_id) local text = '' for k,user in pairs(users_info) do - text = text..user.name..': '..comma_value(user.msgs)..'\n' - text = string.gsub(text, "%_", " ") -- Bot API doesn't use underscores anymore! Yippie! + local msg_num = user.msgs + local percent = tostring(round(msg_num / all_msgs * 100, 1)) + text = text..user.name..': '..comma_value(msg_num)..' ('..percent:gsub('%.', ',')..'%)\n' end if text:isempty() then return 'Keine Stats für diesen Chat verfügbar!'end - local text = utilities.md_escape(text)..'\n*TOTAL*: '..comma_value(all_msgs) + local text = text..'\nTOTAL: '..comma_value(all_msgs) return text end @@ -129,7 +130,7 @@ function stats:action(msg, config, matches) return else local chat_id = msg.chat.id - utilities.send_reply(self, msg, stats:chat_stats(chat_id), true) + utilities.send_reply(self, msg, stats:chat_stats(chat_id), 'HTML') return end end @@ -139,7 +140,7 @@ function stats:action(msg, config, matches) utilities.send_reply(self, msg, config.errors.sudo) return else - utilities.send_reply(self, msg, stats:chat_stats(matches[3]), true) + utilities.send_reply(self, msg, stats:chat_stats(matches[3]), 'HTML') return end end diff --git a/miku/plugins/tagesschau.lua b/miku/plugins/tagesschau.lua index f6da7ab..915dac6 100644 --- a/miku/plugins/tagesschau.lua +++ b/miku/plugins/tagesschau.lua @@ -42,7 +42,7 @@ function tagesschau:inline_callback(inline_query, config, matches) local article = matches[1] local full_url = 'http://www.tagesschau.de/'..article..'.html' local text, img_url, headline, shorttext = tagesschau:get_tagesschau_article(article) - if text == 'HTTP-Fehler' or text == 'Artikel nicht gefunden!' then return end + if text == 'HTTP-Fehler' or text == 'Artikel nicht gefunden!' then utilities.answer_inline_query(self, inline_query) return end if text:match('"') then text = text:gsub('"', '\\"') @@ -55,7 +55,7 @@ function tagesschau:inline_callback(inline_query, config, matches) end local text = text:gsub('\n', '\\n') - local results = '[{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..headline..'","description":"'..shorttext..'","url":"'..full_url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/tagesschau/tagesschau.jpg","thumb_width":150,"thumb_height":150,"hide_url":true,"reply_markup":{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"'..full_url..'"}]]},"input_message_content":{"message_text":"'..text..'","parse_mode":"HTML"}}]' + local results = '[{"type":"article","id":"11","title":"'..headline..'","description":"'..shorttext..'","url":"'..full_url..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/tagesschau/tagesschau.jpg","thumb_width":150,"thumb_height":150,"hide_url":true,"reply_markup":{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"'..full_url..'"}]]},"input_message_content":{"message_text":"'..text..'","parse_mode":"HTML"}}]' utilities.answer_inline_query(self, inline_query, results, 7200) end diff --git a/miku/plugins/time.lua b/miku/plugins/time.lua index 779e2cf..c2b7f26 100644 --- a/miku/plugins/time.lua +++ b/miku/plugins/time.lua @@ -4,6 +4,10 @@ time.command = 'time ' function time:init(config) time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table + time.inline_triggers = { + "^time (.+)", + "^time" + } time.doc = [[* ]]..config.cmd_pat..[[time*: Aktuelle Zeit in Deutschland *]]..config.cmd_pat..[[time* __: Gibt Zeit an diesem Ort aus]] @@ -45,8 +49,49 @@ function time:localize(output) return output end +function time:get_time(coords) + local now = os.time() + local utc = os.time(os.date("!*t", now)) + + local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '×tamp='..utc..'&language=de' + local jstr, res = https.request(url) + if res ~= 200 then return nil end + + local jdat = json.decode(jstr) + local place = string.gsub(jdat.timeZoneId, '_', ' ' ) + local place = time:localize(place) + local timezoneid = '*'..place..'*' + local timestamp = now + jdat.rawOffset + jdat.dstOffset + local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600 + if utcoff == math.abs(utcoff) then + utcoff = '+'.. utilities.pretty_float(utcoff) + else + utcoff = utilities.pretty_float(utcoff) + end + -- "%A, %d. %B %Y, %H:%M:%S Uhr" + local time_there = time:localize(os.date('!%A, %d. %B %Y, %H:%M:%S Uhr',timestamp)) + local output = timezoneid..':\n'..time_there + + return output..'\n_'..jdat.timeZoneName .. ' (UTC' .. utcoff .. ')_', place, time_there +end + +function time:inline_callback(inline_query, config, matches) + if matches[1] == 'time' then + local desc_time = os.date("%A, %d. %B %Y, %H:%M:%S Uhr") + local cur_time = time:localize(os.date("%A, %d. %B %Y, *%H:%M:%S Uhr*")) + results = '[{"type":"article","id":"12","title":"Europa/Berlin","description":"'..desc_time..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/time/clock.jpg","input_message_content":{"message_text":"'..cur_time..'","parse_mode":"Markdown"}}]' + else + local coords = utilities.get_coords(matches[1], config) + if type(coords) == 'string' then utilities.answer_inline_query(self, inline_query) return end + local output, place, desc_time = time:get_time(coords) + if not output then utilities.answer_inline_query(self, inline_query) return end + results = '[{"type":"article","id":"13","title":"'..place..'","description":"'..desc_time..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/time/clock.jpg","input_message_content":{"message_text":"'..output..'","parse_mode":"Markdown"}}]' + end + utilities.answer_inline_query(self, inline_query, results, 1) +end + function time:action(msg, config) - local input = utilities.input(msg.text) + local input = utilities.input_from_msg(msg) if not input then local output = os.date("%A, %d. %B %Y, *%H:%M:%S Uhr*") utilities.send_reply(self, msg, time:localize(output), true) @@ -58,33 +103,10 @@ function time:action(msg, config) utilities.send_reply(self, msg, coords) return end - - local now = os.time() - local utc = os.time(os.date("!*t", now)) - - local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '×tamp='..utc..'&language=de' - local jstr, res = https.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end - - local jdat = json.decode(jstr) - local timezoneid = '*'..string.gsub(jdat.timeZoneId, '_', ' ' )..'*' - local timestamp = now + jdat.rawOffset + jdat.dstOffset - local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600 - if utcoff == math.abs(utcoff) then - utcoff = '+'.. utilities.pretty_float(utcoff) - else - utcoff = utilities.pretty_float(utcoff) - end - -- "%A, %d. %B %Y, %H:%M:%S Uhr" - local output = timezoneid..':\n'..os.date('!%A, %d. %B %Y, %H:%M:%S Uhr',timestamp) - local output = time:localize(output) - - local output = output..'\n_'..jdat.timeZoneName .. ' (UTC' .. utcoff .. ')_' + local output = time:get_time(coords) + if not output then utilities.send_reply(self, msg, config.errors.connection) return end utilities.send_reply(self, msg, output, true) end -return time +return time \ No newline at end of file diff --git a/miku/plugins/twitter.lua b/miku/plugins/twitter.lua index 19b1987..2fc62d4 100644 --- a/miku/plugins/twitter.lua +++ b/miku/plugins/twitter.lua @@ -2,20 +2,20 @@ local twitter = {} function twitter:init(config) if not cred_data.tw_consumer_key then - print('Missing config value: tw_consumer_key.') - print('twitter.lua will not be enabled.') + print('Fehlender Key: tw_consumer_key.') + print('twitter.lua wird nicht aktiviert.') return elseif not cred_data.tw_consumer_secret then - print('Missing config value: tw_consumer_secret.') - print('twitter.lua will not be enabled.') + print('Fehlender Key: tw_consumer_secret.') + print('twitter.lua wird nicht aktiviert.') return elseif not cred_data.tw_access_token then - print('Missing config value: tw_access_token.') - print('twitter.lua will not be enabled.') + print('Fehlender Key: tw_access_token.') + print('twitter.lua wird nicht aktiviert.') return elseif not cred_data.tw_access_token_secret then - print('Missing config value: tw_access_token_secret.') - print('twitter.lua will not be enabled.') + print('Fehlender Key: tw_access_token_secret.') + print('twitter.lua wird nicht aktiviert.') return end @@ -104,14 +104,14 @@ function twitter:action(msg, config, matches) if v.video_info then if not v.video_info.variants[3] then local vid = v.video_info.variants[1].url - table.insert(videos, vid) + videos[#videos+1] = vid else local vid = v.video_info.variants[3].url - table.insert(videos, vid) + videos[#videos+1] = vid end end text = text:gsub(url, "") - table.insert(images, pic) + images[#images+1] = pic end end diff --git a/miku/plugins/twitter_send.lua b/miku/plugins/twitter_send.lua index 750ae2a..d641003 100644 --- a/miku/plugins/twitter_send.lua +++ b/miku/plugins/twitter_send.lua @@ -14,12 +14,14 @@ function twitter_send:init(config) end twitter_send.triggers = { - "^/tw (auth) (%d+)", + "^/tw (auth) (%d+)$", "^/tw (unauth)$", "^/tw (verify)$", - "^/tw (.+)", - "^/(twwhitelist add) (%d+)", - "^/(twwhitelist del) (%d+)" + "^/tw (.+)$", + "^/(twwhitelist add) (%d+)$", + "^/(twwhitelist del) (%d+)$", + "^/(twwhitelist add)$", + "^/(twwhitelist del)$" } twitter_send.doc = [[* ]]..config.cmd_pat..[[tw* __: Sendet einen Tweet an den Account, der im Chat angemeldet ist @@ -91,16 +93,18 @@ function twitter_send:get_twitter_access_token(hash, oauth_verifier, oauth_token redis:hset(hash, 'oauth_token', values.oauth_token) redis:hset(hash, 'oauth_token_secret', values.oauth_token_secret) - return 'Erfolgreich eingeloggt als "@'..values.screen_name..'" (User-ID: '..values.user_id..')' + local screen_name = values.screen_name + + return 'Erfolgreich eingeloggt als @'..screen_name..' (User-ID: '..values.user_id..')' end function twitter_send:reset_twitter_auth(hash, frominvalid) redis:hdel(hash, 'oauth_token') redis:hdel(hash, 'oauth_token_secret') if frominvalid then - return '*Authentifizierung nicht erfolgreich, wird zurückgesetzt...*' + return 'Authentifizierung nicht erfolgreich, wird zurückgesetzt...' else - return '*Erfolgreich abgemeldet!* Entziehe den Zugriff endgültig in deinen [Twitter-Einstellungen](https://twitter.com/settings/applications)!' + return 'Erfolgreich abgemeldet! Entziehe den Zugriff endgültig in deinen Twitter-Einstellungen!' end end @@ -217,7 +221,7 @@ function twitter_send:send_tweet(tweet, oauth_token, oauth_token_secret, hash) local screen_name = data.user.screen_name local status_id = data.id_str - return '*Tweet #'..statusnumber..' gesendet!* [Auf Twitter ansehen](https://twitter.com/statuses/'..status_id..')' + return 'Tweet #'..statusnumber..' gesendet!' end function twitter_send:add_to_twitter_whitelist(user_id) @@ -245,22 +249,36 @@ function twitter_send:del_from_twitter_whitelist(user_id) end function twitter_send:action(msg, config, matches) - if matches[1] == "twwhitelist add" and matches[2] then + if matches[1] == "twwhitelist add" then if msg.from.id ~= config.admin then utilities.send_reply(self, msg, config.errors.sudo) return else - utilities.send_reply(self, msg, twitter_send:add_to_twitter_whitelist(matches[2]), true) + local user_id = matches[2] + if not user_id then + if not msg.reply_to_message then + return + end + user_id = msg.reply_to_message.from.id + end + utilities.send_reply(self, msg, twitter_send:add_to_twitter_whitelist(user_id), true) return end end - if matches[1] == "twwhitelist del" and matches[2] then + if matches[1] == "twwhitelist del" then if msg.from.id ~= config.admin then utilities.send_reply(self, msg, config.errors.sudo) return else - utilities.send_reply(self, msg, twitter_send:del_from_twitter_whitelist(matches[2]), true) + local user_id = matches[2] + if not user_id then + if not msg.reply_to_message then + return + end + user_id = msg.reply_to_message.from.id + end + utilities.send_reply(self, msg, twitter_send:del_from_twitter_whitelist(user_id), true) return end end @@ -302,7 +320,7 @@ function twitter_send:action(msg, config, matches) end end if string.len(matches[2]) > 7 then utilities.send_reply(self, msg, 'Invalide PIN!') return end - utilities.send_reply(self, msg, twitter_send:get_twitter_access_token(hash, matches[2], oauth_token, oauth_token_secret)) + utilities.send_reply(self, msg, twitter_send:get_twitter_access_token(hash, matches[2], oauth_token, oauth_token_secret), 'HTML') local message_id = redis:hget(hash, 'login_msg') utilities.edit_message(self, msg.chat.id, message_id, '*Anmeldung abgeschlossen!*', true, true) redis:hdel(hash, 'login_msg') @@ -316,7 +334,7 @@ function twitter_send:action(msg, config, matches) return end end - utilities.send_reply(self, msg, twitter_send:reset_twitter_auth(hash), true) + utilities.send_reply(self, msg, twitter_send:reset_twitter_auth(hash), 'HTML') return end @@ -334,11 +352,11 @@ function twitter_send:action(msg, config, matches) utilities.send_reply(self, msg, '*Du darfst keine Tweets senden.* Entweder wurdest du noch gar nicht freigeschaltet oder ausgeschlossen.', true) return else - utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true) + utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), 'HTML') return end else - utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true) + utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), 'HTML') return end end diff --git a/miku/plugins/twitter_user.lua b/miku/plugins/twitter_user.lua index 336f473..f53783f 100644 --- a/miku/plugins/twitter_user.lua +++ b/miku/plugins/twitter_user.lua @@ -1,4 +1,4 @@ -local twitter_user = {} +local twitter_user = {} require "./miku/encoding" @@ -58,7 +58,7 @@ function twitter_user:resolve_url(url) end end -function twitter_user:action(msg) +function twitter_user:action(msg, config, matches) local twitter_url = "https://api.twitter.com/1.1/users/show/"..matches[1]..".json" local response_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url) local response = json.decode(response_body) @@ -111,4 +111,4 @@ function twitter_user:action(msg) end end -return twitter_user +return twitter_user \ No newline at end of file diff --git a/miku/plugins/venue.lua b/miku/plugins/venue.lua index ce75c59..83339bc 100644 --- a/miku/plugins/venue.lua +++ b/miku/plugins/venue.lua @@ -7,7 +7,7 @@ venue.triggers = { local apikey = cred_data.google_apikey function venue:pre_process(msg, self) - if not msg.venue then return end -- Ignore + if not msg.venue then return msg end -- Ignore local lat = msg.venue.location.latitude local lng = msg.venue.location.longitude @@ -24,4 +24,4 @@ end function venue:action(msg) end -return venue +return venue \ No newline at end of file diff --git a/miku/plugins/weather.lua b/miku/plugins/weather.lua index d2dd48b..e809270 100644 --- a/miku/plugins/weather.lua +++ b/miku/plugins/weather.lua @@ -17,6 +17,10 @@ function weather:init(config) "^/w$", "^/w (.*)$" } + weather.inline_triggers = { + "^w (.+)$", + "^w$" + } weather.doc = [[* ]]..config.cmd_pat..[[wetter*: Wetter für deinen Wohnort _(/location set [Ort])_ *]]..config.cmd_pat..[[wetter* __: Wetter für diesen Ort @@ -42,10 +46,43 @@ function get_city_name(lat, lng) return city end -function weather:get_weather(lat, lng) +function get_city_coordinates(city, config) + local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') + local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng') + if not lat and not lng then + print('Koordinaten nicht eingespeichert, frage Google...') + coords = utilities.get_coords(city, config) + lat = coords.lat + lng = coords.lon + end + + if not lat and not lng then + return nil + end + + redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat) + redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng) + return lat, lng +end + +function weather:get_weather(lat, lng, is_inline) print('Finde Wetter in '..lat..', '..lng) - local text = redis:get('telegram:cache:weather:'..lat..','..lng) - if text then print('...aus dem Cache') return text end + local hash = 'telegram:cache:weather:'..lat..','..lng + + local text = redis:hget(hash, 'text') + if text then + print('...aus dem Cache') + if is_inline then + local ttl = redis:ttl(hash) + local city = redis:hget(hash, 'city') + local temperature = redis:hget(hash, 'temperature') + local weather_icon = redis:hget(hash, 'weather_icon') + local condition = redis:hget(hash, 'condition') + return city, condition..' bei '..temperature..' °C', weather_icon, text, ttl + else + return text + end + end local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=minutely,hourly,daily,alerts,flags' @@ -55,10 +92,10 @@ function weather:get_weather(lat, lng) method = "GET", sink = ltn12.sink.table(response_body) } - local ok, response_code, response_headers, response_status_line = https.request(request_constructor) + local ok, response_code, response_headers = https.request(request_constructor) if not ok then return nil end local data = json.decode(table.concat(response_body)) - local ttl = string.sub(response_headers["cache-control"], 9) + local ttl = tonumber(string.sub(response_headers["cache-control"], 9)) local weather = data.currently @@ -66,26 +103,28 @@ function weather:get_weather(lat, lng) local temperature = string.gsub(round(weather.temperature, 1), "%.", ",") local feelslike = string.gsub(round(weather.apparentTemperature, 1), "%.", ",") local temp = '*Wetter in '..city..':*\n'..temperature..' °C' - local conditions = ' | '..weather.summary - if weather.icon == 'clear-day' then + local weather_summary = weather.summary + local conditions = ' | '..weather_summary + local weather_icon = weather.icon + if weather_icon == 'clear-day' then conditions = conditions..' ☀️' - elseif weather.icon == 'clear-night' then + elseif weather_icon == 'clear-night' then conditions = conditions..' 🌙' - elseif weather.icon == 'rain' then + elseif weather_icon == 'rain' then conditions = conditions..' ☔️' - elseif weather.icon == 'snow' then + elseif weather_icon == 'snow' then conditions = conditions..' ❄️' - elseif weather.icon == 'sleet' then + elseif weather_icon == 'sleet' then conditions = conditions..' 🌨' - elseif weather.icon == 'wind' then + elseif weather_icon == 'wind' then conditions = conditions..' 💨' elseif weather.icon == 'fog' then conditions = conditions..' 🌫' - elseif weather.icon == 'cloudy' then + elseif weather_icon == 'cloudy' then conditions = conditions..' ☁️☁️' - elseif weather.icon == 'partly-cloudy-day' then + elseif weather_icon == 'partly-cloudy-day' then conditions = conditions..' 🌤' - elseif weather.icon == 'partly-cloudy-night' then + elseif weather_icon == 'partly-cloudy-night' then conditions = conditions..' 🌙☁️' else conditions = conditions..'' @@ -98,8 +137,56 @@ function weather:get_weather(lat, lng) text = text..'\n(gefühlt: '..feelslike..' °C)' end - cache_data('weather', lat..','..lng, text, tonumber(ttl), 'key') - return text + print('Caching data...') + redis:hset(hash, 'city', city) + redis:hset(hash, 'temperature', temperature) + redis:hset(hash, 'weather_icon', weather_icon) + redis:hset(hash, 'condition', weather_summary) + redis:hset(hash, 'text', text) + redis:expire(hash, ttl) + + if is_inline then + return city, weather_summary..' bei '..temperature..' °C', weather_icon, text, ttl + else + return text + end +end + +function weather:inline_callback(inline_query, config, matches) + local user_id = inline_query.from.id + if matches[1] ~= 'w' then + city = matches[1] + is_personal = false + else + local set_location = get_location(user_id) + is_personal = true + if not set_location then + city = 'Berlin, Deutschland' + else + city = set_location + end + end + local lat, lng = get_city_coordinates(city, config) + if not lat and not lng then utilities.answer_inline_query(self, inline_query) return end + + local title, description, icon, text, ttl = weather:get_weather(lat, lng, true) + if not title and not description and not icon and not text and not ttl then utilities.answer_inline_query(self, inline_query) return end + + local text = text:gsub('\n', '\\n') + local thumb_url = 'https://anditest.perseus.uberspace.de/inlineQuerys/weather/' + if icon == 'clear-day' or icon == 'partly-cloudy-day' then + thumb_url = thumb_url..'day.jpg' + elseif icon == 'clear-night' or icon == 'partly-cloudy-night' then + thumb_url = thumb_url..'night.jpg' + elseif icon == 'rain' then + thumb_url = thumb_url..'rain.jpg' + elseif icon == 'snow' then + thumb_url = thumb_url..'snow.jpg' + else + thumb_url = thumb_url..'cloudy.jpg' + end + local results = '[{"type":"article","id":"19122006","title":"'..title..'","description":"'..description..'","thumb_url":"'..thumb_url..'","thumb_width":80,"thumb_height":80,"input_message_content":{"message_text":"'..text..'", "parse_mode":"Markdown"}}]' + utilities.answer_inline_query(self, inline_query, results, ttl, is_personal) end function weather:action(msg, config, matches) @@ -116,22 +203,11 @@ function weather:action(msg, config, matches) end end - local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') - local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng') + local lat, lng = get_city_coordinates(city, config) if not lat and not lng then - print('Koordinaten nicht eingespeichert, frage Google...') - coords = utilities.get_coords(city, config) - lat = coords.lat - lng = coords.lon - end - - if not lat and not lng then - utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) + utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) return end - - redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat) - redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng) local text = weather:get_weather(lat, lng) if not text then @@ -140,4 +216,4 @@ function weather:action(msg, config, matches) utilities.send_reply(self, msg, text, true) end -return weather +return weather \ No newline at end of file diff --git a/miku/plugins/wikipedia.lua b/miku/plugins/wikipedia.lua index 0174999..4f13a34 100644 --- a/miku/plugins/wikipedia.lua +++ b/miku/plugins/wikipedia.lua @@ -57,7 +57,7 @@ function wikipedia:getWikiServer(lang) end --[[ --- return decoded json table from Wikipedia +-- return decoded JSON table from Wikipedia --]] function wikipedia:loadPage(text, lang, intro, plain, is_search) local request, sink = {}, {} @@ -107,7 +107,7 @@ function wikipedia:loadPage(text, lang, intro, plain, is_search) end -- extract intro passage in wiki page -function wikipedia:wikintro(text, lang) +function wikipedia:wikintro(text, lang, is_inline) local text = decodetext(text) local result = self:loadPage(text, lang, true, true) @@ -124,13 +124,28 @@ function wikipedia:wikintro(text, lang) local lang = lang or "de" local title = page.title local title_enc = URL.escape(title) - return '*'..title.."*:\n"..utilities.md_escape(page.extract), '{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"https://'..lang..'.wikipedia.org/wiki/'..title_enc..'"}]]}' + if is_inline then + local result = ''..title..':\n'..page.extract + local result = result:gsub('\n', '\\n') + local result = result:gsub('"', '\\"') + return title, result, '{"inline_keyboard":[[{"text":"Wikipedia aufrufen","url":"https://'..lang..'.wikipedia.org/wiki/'..title_enc..'"}]]}' + else + return '*'..title..'*:\n'..utilities.md_escape(page.extract), '{"inline_keyboard":[[{"text":"Artikel aufrufen","url":"https://'..lang..'.wikipedia.org/wiki/'..title_enc..'"}]]}' + end else - local text = text.." nicht gefunden" - return text + if is_inline then + return nil + else + local text = text.." nicht gefunden" + return text + end end else - return "Ein Fehler ist aufgetreten." + if is_inline then + return nil + else + return "Ein Fehler ist aufgetreten." + end end end @@ -165,17 +180,20 @@ function wikipedia:inline_callback(inline_query, config, matches) lang = 'de' query = matches[1] end - local url = 'https://'..lang..'.wikipedia.org/w/api.php?action=query&list=search&srsearch='..URL.escape(query)..'&format=json&prop=extracts&srprop=snippet' - local res, code = https.request(url) - if code ~= 200 then return end + + local search_url = 'https://'..lang..'.wikipedia.org/w/api.php?action=query&list=search&srsearch='..URL.escape(query)..'&format=json&prop=extracts&srprop=snippet&&srlimit=5' + local res, code = https.request(search_url) + if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end local data = json.decode(res).query - if data.searchinfo.totalhits == 0 then return end local results = '[' + local id = 700 for num in pairs(data.search) do - local title = data.search[num].title - results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","title":"'..title..'","description":"'..wikipedia:snip_snippet(data.search[num].snippet)..'","url":"https://'..lang..'.wikipedia.org/wiki/'..URL.escape(title)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/wiki/logo.jpg","thumb_width":95,"thumb_height":86,"input_message_content":{"message_text":"https://'..lang..'.wikipedia.org/wiki/'..URL.escape(title)..'","disable_web_page_preview":true}}' + local title, result, keyboard = wikipedia:wikintro(data.search[num].title, lang, true) + if not title or not result or not keyboard then utilities.answer_inline_query(self, inline_query) return end + results = results..'{"type":"article","id":"'..id..'","title":"'..title..'","description":"'..wikipedia:snip_snippet(data.search[num].snippet)..'","url":"https://'..lang..'.wikipedia.org/wiki/'..URL.escape(title)..'","hide_url":true,"thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/wiki/logo.jpg","thumb_width":95,"thumb_height":86,"reply_markup":'..keyboard..',"input_message_content":{"message_text":"'..result..'","parse_mode":"HTML"}}' + id = id+1 if num < #data.search then results = results..',' end @@ -216,4 +234,4 @@ function wikipedia:action(msg, config, matches) utilities.send_reply(self, msg, result, true, keyboard) end -return wikipedia +return wikipedia \ No newline at end of file diff --git a/miku/plugins/youtube.lua b/miku/plugins/youtube.lua index 4e38169..1f1c8dd 100644 --- a/miku/plugins/youtube.lua +++ b/miku/plugins/youtube.lua @@ -147,10 +147,10 @@ function youtube:inline_callback(inline_query, config, matches) local query = matches[1] local url = BASE_URL..'/search?part=snippet&key='..apikey..'&maxResults=10&type=video&q='..URL.escape(query)..'&fields=items(id(videoId),snippet(publishedAt,title,thumbnails,channelTitle))' local res,code = https.request(url) - if code ~= 200 then return end + if code ~= 200 then utilities.answer_inline_query(self, inline_query) return end local data = json.decode(res) - if not data.items[1] then return end + if not data.items[1] then utilities.answer_inline_query(self, inline_query) return end local video_ids = "" -- We get all videoIds from search... @@ -170,6 +170,7 @@ function youtube:inline_callback(inline_query, config, matches) if not video_results.items[1] then return end local results = '[' + local id = 800 for num in pairs(video_results.items) do local video_url = 'https://www.youtube.com/watch?v='..video_results.items[num].id local thumb_url = get_yt_thumbnail(video_results.items[num]) @@ -195,7 +196,8 @@ function youtube:inline_callback(inline_query, config, matches) local uploader = video_results.items[num].snippet.channelTitle local description = uploader..', '..viewCount..' Views, '..readable_dur..likeCount..dislikeCount..commentCount - results = results..'{"type":"video","id":"'..math.random(100000000000000000)..'","video_url":"'..video_url..'","mime_type":"text/html","thumb_url":"'..thumb_url..'","title":"'..video_title..'","description":"'..description..'","video_duration":'..video_duration..',"input_message_content":{"message_text":"'..video_url..'"}}' + results = results..'{"type":"video","id":"'..id..'","video_url":"'..video_url..'","mime_type":"text/html","thumb_url":"'..thumb_url..'","title":"'..video_title..'","description":"'..description..'","video_duration":'..video_duration..',"input_message_content":{"message_text":"'..video_url..'"}}' + id = id+1 if num < #video_results.items then results = results..',' end @@ -207,6 +209,10 @@ end function youtube:action(msg, config, matches) local yt_code = matches[1] local data = get_yt_data(yt_code) + if not data then + utilities.send_reply(self, msg, config.errors.results) + return + end send_youtube_data(data, msg, self) return end diff --git a/miku/plugins/youtube_dl.lua b/miku/plugins/youtube_dl.lua index 973d237..091990c 100644 --- a/miku/plugins/youtube_dl.lua +++ b/miku/plugins/youtube_dl.lua @@ -4,34 +4,86 @@ local youtube_dl = {} function youtube_dl:init(config) youtube_dl.triggers = { - "^/(mp4) (https?://[%w-_%.%?%.:/%+=&]+)$", - "^/(mp3) (https?://[%w-_%.%?%.:/%+=&]+)$" + "^/(mp4) https?://w?w?w?%.?youtu.be/([A-Za-z0-9-_-]+)", + "^/(mp4) https?://w?w?w?m?%.?youtube.com/embed/([A-Za-z0-9-_-]+)", + "^/(mp4) https?://w?w?w?m?%.?youtube.com/watch%?v=([A-Za-z0-9-_-]+)", + "^/(mp3) https?://w?w?w?m?%.?youtu.be/([A-Za-z0-9-_-]+)", + "^/(mp3) https?://w?w?w?m?%.?youtube.com/embed/([A-Za-z0-9-_-]+)", + "^/(mp3) https?://w?w?w?m?%.?youtube.com/watch%?v=([A-Za-z0-9-_-]+)" } youtube_dl.doc = [[* -]]..config.cmd_pat..[[mp3* __: Lädt Audio von [untersützten Seiten](https://rg3.github.io/youtube-dl/supportedsites.html) -*]]..config.cmd_pat..[[mp4* __: Lädt Video von [untersützten Seiten](https://rg3.github.io/youtube-dl/supportedsites.html) +]]..config.cmd_pat..[[mp3* __: Lädt Audio von YouTube +*]]..config.cmd_pat..[[mp4* __: Lädt Video von YouTube ]] end youtube_dl.command = 'mp3 , /mp4 ' -function youtube_dl:convert_video(link) - local output = io.popen('youtube-dl -f mp4 --max-filesize 49m -o "/tmp/%(title)s.%(ext)s" '..link):read('*all') - print(output) - if string.match(output, '.* File is larger .*') then - return 'TOOBIG' +function youtube_dl:get_availabe_formats(id, hash) + local ytdl_json = io.popen('youtube-dl -j https://www.youtube.com/watch/?v='..id):read('*all') + if not ytdl_json then return end + local data = json.decode(ytdl_json) + if not data then return nil end + + local available_formats = {} + redis:hset(hash, 'duration', data.duration) + + -- Building table with infos + for n=1, #data.formats do + local vid_format = data.formats[n].format + local format_num = vid_format:match('^(%d+) ') + local valid_nums = {['17'] = true, ['36'] = true, ['43'] = true, ['18'] = true, ['22'] = true} + if not vid_format:match('DASH') and valid_nums[format_num] then -- We don't want DASH videos! + local format_info = {} + format_info.format = format_num + local hash = hash..':'..format_num + if format_num == '17' then + format_info.pretty_format = '144p' + elseif format_num == '36' then + format_info.pretty_format = '180p' + elseif format_num == '43' then + format_info.pretty_format = '360p WebM' + elseif format_num == '18' then + format_info.pretty_format = '360p MP4' + elseif format_num == '22' then + format_info.pretty_format = '720p' + end + format_info.ext = data.formats[n].ext + local url = data.formats[n].url + local headers = get_http_header(url) + local full_url = headers.location + + if not full_url then return end + local headers = get_http_header(full_url) -- first was for 302, this get's use the size + if headers.location then -- There are some videos where there is a "chain" of 302... repeat this, until we get the LAST url! + repeat + headers = get_http_header(headers.location) + until not headers.location + end + + + format_info.url = full_url + local size = tonumber(headers["content-length"]) + format_info.size = size + format_info.pretty_size = string.gsub(tostring(round(size / 1048576, 2)), '%.', ',')..' MB' -- 1048576 = 1024*1024 + available_formats[#available_formats+1] = format_info + redis:hset(hash, 'ext', format_info.ext) + redis:hset(hash, 'format', format_info.pretty_format) + redis:hset(hash, 'url', full_url) + redis:hset(hash, 'size', size) + redis:hset(hash, 'height', data.formats[n].height) + redis:hset(hash, 'width', data.formats[n].width) + redis:hset(hash, 'pretty_size', format_info.pretty_size) + redis:expire(hash, 7889400) + end end - local video = string.match(output, '%[download%] Destination: /tmp/(.*).mp4') - if not video then - video = string.match(output, '%[download%] /tmp/(.*).mp4 has already been downloaded') - end - return '/tmp/'..video..'.mp4' + + return available_formats end -function youtube_dl:convert_audio(link) - local output = io.popen('youtube-dl --max-filesize 49m -o "/tmp/%(title)s.%(ext)s" --extract-audio --audio-format mp3 '..link):read('*all') - print(output) +function youtube_dl:convert_audio(id) + local output = io.popen('youtube-dl --max-filesize 49m -o "/tmp/%(title)s.%(ext)s" --extract-audio --audio-format mp3 https://www.youtube.com/watch/?v='..id):read('*all') if string.match(output, '.* File is larger .*') then return 'TOOBIG' end @@ -39,25 +91,105 @@ function youtube_dl:convert_audio(link) return '/tmp/'..audio..'.mp3' end -function youtube_dl:action(msg, config) - local link = matches[2] +function youtube_dl:callback(callback, msg, self, config, input) + utilities.answer_callback_query(self, callback, 'Informationen werden verarbeitet...') + local video_id = input:match('(.+)@') + local vid_format = input:match('@(%d+)') + local hash = 'telegram:cache:youtube_dl:mp4:'..video_id + local format_hash = hash..':'..vid_format + if not redis:exists(format_hash) then + youtube_dl:get_availabe_formats(video_id, hash) + end + + local keyboard = redis:hget(hash, 'keyboard') + local duration = redis:hget(hash, 'duration') + local format_info = redis:hgetall(format_hash) + + local full_url = format_info.url + + local width = format_info.width + local height = format_info.height + local ext = format_info.ext + local pretty_size = format_info.pretty_size + local size = tonumber(format_info.size) + local format = format_info.format + local file = format_info.file_id + + if size > 52420000 then + utilities.edit_message(self, msg.chat.id, msg.message_id, 'Direktlink zum Video ('..format..', '..pretty_size..')', nil, 'HTML', keyboard) + return + end + + utilities.edit_message(self, msg.chat.id, msg.message_id, 'Video wird hochgeladen', 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, 'Direktlink zum Video ('..format..', '..pretty_size..')', nil, 'HTML', keyboard) + if not result then return end + local file_id = result.result.video.file_id + redis:hset(format_hash, 'file_id', file_id) +end + +function youtube_dl:action(msg, config, matches) + if msg.chat.type ~= 'private' then + utilities.send_reply(self, msg, 'Dieses Plugin kann nur im Privatchat benutzt werden') + return + end + local id = matches[2] if matches[1] == 'mp4' then - utilities.send_typing(self, msg.chat.id, 'upload_video') - local file = youtube_dl:convert_video(link) - if file == 'TOOBIG' then - utilities.send_reply(self, msg, 'Das Video überschreitet die Grenze von 50 MB!') - return + local hash = 'telegram:cache:youtube_dl:mp4:'..id + local first_msg = utilities.send_reply(self, msg, 'Verfügbare Videoformate werden ausgelesen...', 'HTML') + local callback_keyboard = redis:hget(hash, 'keyboard') + if not callback_keyboard then + utilities.send_typing(self, msg.chat.id, 'typing') + local available_formats = youtube_dl:get_availabe_formats(id, hash) + if not available_formats then + utilities.edit_message(self, msg.chat.id, first_msg.result.message_id, config.errors.results) + return + end + + local callback_buttons = {} + for n=1, #available_formats do + local video = available_formats[n] + local format = video.format + local size = video.size + local pretty_size = video.pretty_size + if size > 52420000 then + pretty_format = video.pretty_format..' ('..pretty_size..', nur Link)' + else + pretty_format = video.pretty_format..' ('..pretty_size..')' + end + local button = '{"text":"'..pretty_format..'","callback_data":"@'..self.info.username..' youtube_dl:'..id..'@'..format..'"}' + callback_buttons[#callback_buttons+1] = button + end + + local keyboard = '{"inline_keyboard":[' + for button in pairs(callback_buttons) do + keyboard = keyboard..'['..callback_buttons[button]..']' + if button < #callback_buttons then + keyboard = keyboard..',' + end + end + + callback_keyboard = keyboard..']}' + redis:hset(hash, 'keyboard', callback_keyboard) + redis:expire(hash, 7889400) end - utilities.send_video(self, msg.chat.id, file, nil, msg.message_id) - return + utilities.edit_message(self, msg.chat.id, first_msg.result.message_id, 'Wähle die gewünschte Auflösung.', nil, nil, callback_keyboard) + return end if matches[1] == 'mp3' then + local first_msg = utilities.send_reply(self, msg, 'Audio wird heruntergeladen...', 'HTML') utilities.send_typing(self, msg.chat.id, 'upload_audio') - local file = youtube_dl:convert_audio(link) + local file = youtube_dl:convert_audio(id) if file == 'TOOBIG' then - utilities.send_reply(self, msg, 'Die MP3 überschreitet die Grenze von 50 MB!') + utilities.edit_message(self, msg.chat.id, first_msg.result.message_id, 'Die MP3 überschreitet die Grenze von 50 MB!', nil, 'HTML') return end utilities.send_audio(self, msg.chat.id, file, msg.message_id) @@ -65,4 +197,4 @@ function youtube_dl:action(msg, config) end end -return youtube_dl +return youtube_dl \ No newline at end of file diff --git a/miku/utilities.lua b/miku/utilities.lua index 9dd5225..eef2d34 100644 --- a/miku/utilities.lua +++ b/miku/utilities.lua @@ -10,12 +10,11 @@ socket = require('socket') URL = require('socket.url') json = require('dkjson') pcall(json.use_lpeg) -serpent = require("serpent") -bindings = require('miku.bindings') -redis = (loadfile "./miku/redis.lua")() -mimetype = (loadfile "./miku/mimetype.lua")() -OAuth = require "OAuth" -helpers = require "OAuth.helpers" +serpent = require('serpent') +redis = (loadfile './miku/redis.lua')() +mime = (loadfile './miku/mimetype.lua')() +OAuth = require 'OAuth' +helpers = require 'OAuth.helpers' http.timeout = 5 https.timeout = 5 @@ -23,52 +22,42 @@ https.timeout = 5 -- For the sake of ease to new contributors and familiarity to old contributors, -- we'll provide a couple of aliases to real bindings here. function utilities:send_message(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown, reply_markup) - if use_markdown == true then - use_markdown = 'Markdown' - elseif not use_markdown then - use_markdown = nil + local parse_mode + if type(use_markdown) == 'string' then + parse_mode = use_markdown + elseif use_markdown == true then + parse_mode = 'Markdown' end return bindings.request(self, 'sendMessage', { chat_id = chat_id, text = text, disable_web_page_preview = disable_web_page_preview, reply_to_message_id = reply_to_message_id, - parse_mode = use_markdown, + parse_mode = parse_mode, reply_markup = reply_markup } ) end -- https://core.telegram.org/bots/api#editmessagetext function utilities:edit_message(chat_id, message_id, text, disable_web_page_preview, use_markdown, reply_markup) - if use_markdown == true then - use_markdown = 'Markdown' - elseif not use_markdown then - use_markdown = nil + local parse_mode + if type(use_markdown) == 'string' then + parse_mode = use_markdown + elseif use_markdown == true then + parse_mode = 'Markdown' end return bindings.request(self, 'editMessageText', { chat_id = chat_id, message_id = message_id, text = text, disable_web_page_preview = disable_web_page_preview, - parse_mode = use_markdown, + parse_mode = parse_mode, reply_markup = reply_markup } ) end function utilities:send_reply(old_msg, text, use_markdown, reply_markup) - if use_markdown == true then - use_markdown = 'Markdown' - elseif not use_markdown then - use_markdown = nil - end - return bindings.request(self, 'sendMessage', { - chat_id = old_msg.chat.id, - text = text, - disable_web_page_preview = true, - reply_to_message_id = old_msg.message_id, - parse_mode = use_markdown, - reply_markup = reply_markup - } ) + return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown, reply_markup) end -- NOTE: Telegram currently only allows file uploads up to 50 MB @@ -225,23 +214,14 @@ end -- get the indexed word in a string function utilities.get_word(s, i) - s = s or '' - i = i or 1 - local t = {} - for w in s:gmatch('%g+') do - table.insert(t, w) - end - return t[i] or false -end - - -- Like get_word(), but better. - -- Returns the actual index. -function utilities.index(s) - local t = {} - for w in s:gmatch('%g+') do - table.insert(t, w) - end - return t + s = s or '' + i = i or 1 + local n = 0 + for w in s:gmatch('%g+') do + n = n + 1 + if n == i then return w end + end + return false end -- Returns the string after the first space. @@ -252,6 +232,10 @@ function utilities.input(s) return s:sub(s:find(' ')+1) end +function utilities.input_from_msg(msg) + return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false +end + -- Calculates the length of the given string as UTF-8 characters function utilities.utf8_len(s) local chars = 0 @@ -341,19 +325,19 @@ function vardump(value) print(serpent.block(value, {comment=false})) end - -- Loads a json file as a table. + -- Loads a JSON file as a table. function utilities.load_data(filename) local f = io.open(filename) - if not f then + if f then + local s = f:read('*all') + f:close() + return json.decode(s) + else return {} end - local s = f:read('*all') - f:close() - local data = json.decode(s) - return data end - -- Saves a table to a json file. + -- Saves a table to a JSON file. function utilities.save_data(filename, data) local s = json.encode(data) local f = io.open(filename, 'w') @@ -363,24 +347,22 @@ end -- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua. function utilities.get_coords(input, config) + local url = 'https://maps.googleapis.com/maps/api/geocode/json?address='..URL.escape(input)..'&language=de' + local jstr, res = https.request(url) + if res ~= 200 then + return config.errors.connection + end - local url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) - - local jstr, res = https.request(url) - if res ~= 200 then - return config.errors.connection - end - - local jdat = json.decode(jstr) - if jdat.status == 'ZERO_RESULTS' then - return config.errors.results - end - - return { - lat = jdat.results[1].geometry.location.lat, - lon = jdat.results[1].geometry.location.lng - } + local jdat = json.decode(jstr) + if jdat.status == 'ZERO_RESULTS' then + return config.errors.results + end + return { + lat = jdat.results[1].geometry.location.lat, + lon = jdat.results[1].geometry.location.lng, + addr = jdat.results[1].formatted_address + } end -- Get the number of values in a key/value table. @@ -415,91 +397,15 @@ function utilities:resolve_username(input) end end - -- Simpler than above function; only returns an ID. - -- Returns nil if no ID is available. -function utilities:id_from_username(input) - input = input:gsub('^@', '') - for _, user in pairs(self.database.users) do - if user.username and user.username:lower() == input:lower() then - return user.id - end - end -end - - -- Simpler than below function; only returns an ID. - -- Returns nil if no ID is available. -function utilities:id_from_message(msg) - if msg.reply_to_message then - return msg.reply_to_message.from.id - else - local input = utilities.input(msg.text) - if input then - if tonumber(input) then - return tonumber(input) - elseif input:match('^@') then - return utilities.id_from_username(self, input) - end - end - end -end - -function utilities:user_from_message(msg, no_extra) - local input = utilities.input(msg.text_lower) - local target = {} - if msg.reply_to_message then - for k,v in pairs(self.database.users[msg.reply_to_message.from.id_str]) do - target[k] = v - end - elseif input and tonumber(input) then - target.id = tonumber(input) - if self.database.users[input] then - for k,v in pairs(self.database.users[input]) do - target[k] = v - end - end - elseif input and input:match('^@') then - local uname = input:gsub('^@', '') - for _,v in pairs(self.database.users) do - if v.username and uname == v.username:lower() then - for key, val in pairs(v) do - target[key] = val - end - end - end - if not target.id then - target.err = 'Sorry, I don\'t recognize that username.' - end - else - target.err = 'Please specify a user via reply, ID, or username.' - end - - if not no_extra then - if target.id then - target.id_str = tostring(target.id) - end - if not target.first_name then - target.first_name = 'User' - end - target.name = utilities.build_name(target.first_name, target.last_name) - end - - return target - -end - function utilities:handle_exception(err, message, config) - - if not err then err = '' end - - local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. self.info.username .. ': ' .. err .. '\n' .. message .. '\n' - - if config.log_chat then - output = '```' .. output .. '```' - utilities.send_message(self, config.log_chat, output, true, nil, true) - else - print(output) - end - + if not err then err = '' end + local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. self.info.username .. ': ' .. err .. '\n' .. message .. '\n' + if config.log_chat then + output = '```' .. output .. '```' + utilities.send_message(self, config.log_chat, output, true, nil, true) + else + print(output) + end end -- MOVED TO DOWNLOAD_TO_FILE @@ -507,15 +413,17 @@ function utilities.download_file(url, filename) return download_to_file(url, filename) end -function utilities.markdown_escape(text) - text = text:gsub('_', '\\_') - text = text:gsub('%[', '\\[') - text = text:gsub('%*', '\\*') - text = text:gsub('`', '\\`') - return text +function utilities.md_escape(text) + return text:gsub('_', '\\_') + :gsub('%[', '\\['):gsub('%]', '\\]') + :gsub('%*', '\\*'):gsub('`', '\\`') end -utilities.md_escape = utilities.markdown_escape +utilities.markdown_escape = utilities.md_escape + +function utilities.html_escape(text) + return text:gsub('&', '&'):gsub('<', '<'):gsub('>', '>') +end utilities.triggers_meta = {} utilities.triggers_meta.__index = utilities.triggers_meta @@ -591,7 +499,8 @@ utilities.char = { arabic = '[\216-\219][\128-\191]', rtl_override = '‮', rtl_mark = '‏', - em_dash = '—' + em_dash = '—', + utf_8 = '[%z\1-\127\194-\244][\128-\191]', } -- taken from http://stackoverflow.com/a/11130774/3163199 @@ -610,7 +519,7 @@ function plugins_names() for k, v in pairs(scandir("miku/plugins")) do -- Ends with .lua if (v:match(".lua$")) then - table.insert(files, v) + files[#files+1] = v end end return files @@ -723,6 +632,7 @@ function post_petition(url, arguments, headers) if type(arguments) == "table" then source = helpers.url_encode_arguments(arguments) end + if not headers then request_constructor.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF8" request_constructor.headers["X-Accept"] = "application/json" @@ -800,8 +710,8 @@ function get_location(user_id) end end -function cache_data(plugin, query, data, timeout, typ) - -- How to: cache_data(pluginname, query_name, data_to_cache, expire_in_seconds) +function cache_data(plugin, query, data, timeout, typ, hash_field) + -- How to: cache_data(pluginname, query_name, data_to_cache, expire_in_seconds, type, hash_field (if hash)) local hash = 'telegram:cache:'..plugin..':'..query if timeout then print('Caching "'..query..'" from plugin '..plugin..' (expires in '..timeout..' seconds)') @@ -819,7 +729,7 @@ function cache_data(plugin, query, data, timeout, typ) redis:sadd(hash, str) end else - redis:hmset(hash, data) + redis:hset(hash, hash_field, data) end if timeout then redis:expire(hash, timeout) @@ -841,6 +751,8 @@ function cache_file(result, url, last_modified) elseif result.result.photo then local lv = #result.result.photo file_id = result.result.photo[lv].file_id + elseif result.result.sticker then + file_id = result.result.sticker.file_id end print('Caching File...') redis:hset(hash..':'..url, 'file_id', file_id) @@ -935,7 +847,7 @@ function get_cached_file(url, file_name, receiver, chat_action, self) return nil end end - + if header["last-modified"] then last_modified = header["last-modified"] elseif header["Last-Modified"] then