From da14baa05b5e42a4865bf0d58c0c4cb354d768fb Mon Sep 17 00:00:00 2001 From: Andreas Bielawski Date: Sat, 18 Jun 2016 12:51:13 +0200 Subject: [PATCH] =?UTF-8?q?-=20Portiere=20Channels=20-=20Portiere=20Plugin?= =?UTF-8?q?s=20-=20Stelle=20Plugin-Ladeprozess=20auf=20Redis=20um=20-=20Pl?= =?UTF-8?q?ugin-Name=20wird=20in=20die=20Plugins-Tabelle=20eingef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- otouto/bot.lua | 217 +++++++++++++++++++++++++++------ otouto/plugins/about.lua | 7 +- otouto/plugins/banhammer.lua | 4 +- otouto/plugins/channels.lua | 90 ++++++++++++++ otouto/plugins/control.lua | 4 +- otouto/plugins/plugins.lua | 228 +++++++++++++++++++++++++++++++++++ otouto/utilities.lua | 34 +++++- 7 files changed, 537 insertions(+), 47 deletions(-) create mode 100644 otouto/plugins/channels.lua create mode 100644 otouto/plugins/plugins.lua diff --git a/otouto/bot.lua b/otouto/bot.lua index 25a79c9..0cfae0e 100644 --- a/otouto/bot.lua +++ b/otouto/bot.lua @@ -16,7 +16,7 @@ function bot:init(config) -- The function run when the bot is started or reloade assert( config.bot_api_key and config.bot_api_key ~= '', - 'You did not set your bot token in the config!' + 'You did not set your bot token in the config!' ) self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' @@ -35,14 +35,18 @@ function bot:init(config) -- The function run when the bot is started or reloade self.database.users = self.database.users or {} -- Table to cache userdata. self.database.users[tostring(self.info.id)] = self.info + plugins = {} self.plugins = {} -- Load plugins. - for _,v in ipairs(config.plugins) do + enabled_plugins = load_plugins() + for k,v in pairs(enabled_plugins) do local p = require('otouto.plugins.'..v) + plugins[k] = p + print('loading plugin',v) table.insert(self.plugins, p) + self.plugins[k].name = v if p.init then p.init(self, config) end end - - print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')') + print('Bot started successfully as:\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, @@ -76,41 +80,8 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec msg.text_lower = string.gsub(msg.text, '@'..string.lower(config.bot_user_name), "") msg = pre_process_msg(self, msg, config) - for _, plugin in ipairs(self.plugins) do - for _, trigger in pairs(plugin.triggers) do - if string.match(msg.text_lower, trigger) then - local success, result = pcall(function() - -- trying to port matches to otouto - for k, pattern in pairs(plugin.triggers) do - matches = match_pattern(pattern, msg.text) - if matches then - break; - end - end - 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) - end - utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) - return - end - -- If the action returns a table, make that table the new msg. - if type(result) == 'table' then - msg = result - -- If the action returns true, continue. - elseif result ~= true then - return - end - end - end - end + + match_plugins(self, msg, config) end @@ -162,6 +133,174 @@ function pre_process_msg(self, msg, config) return new_msg end +function bakk_match_plugins(self, msg, config) + -- Go over patterns. If one matches is enough. + for k, pattern in pairs(plugin.triggers) do + local matches = match_pattern(pattern, msg.text) + -- local matches = match_pattern(pattern, msg.text, true) + if matches then + print("msg matches: ", pattern) + + if is_plugin_disabled_on_chat(plugin_name, msg) then + return nil + end + -- Function exists + --[[ if plugin.run then + if not plugin.notyping then send_typing(receiver, ok_cb, true) end + if not warns_user_not_allowed(plugin, msg) then + local result = plugin.run(msg, matches) + if result then + send_large_msg(receiver, result) + end + end + end ]]-- + -- One pattern matches + return + end + end +end + +function match_plugins(self, msg, config) + for _, plugin in ipairs(self.plugins) do + for _, trigger in pairs(plugin.triggers) do + 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 otouto + for k, pattern in pairs(plugin.triggers) do + matches = match_pattern(pattern, msg.text) + if matches then + break; + end + end + 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) + end + utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) + return + end + -- If the action returns a table, make that table the new msg. + if type(result) == 'table' then + msg = result + -- If the action returns true, continue. + elseif result ~= true then + return + end + end + end + end +end + +function is_plugin_disabled_on_chat(plugin_name, msg) + local hash = get_redis_hash(msg, 'disabled_plugins') + local disabled = redis:hget(hash, plugin_name) + + -- Plugin is disabled + if disabled == 'true' then + print('Plugin '..plugin_name..' ist in diesem Chat deaktiviert') + return true + else + return false + end +end + +function load_plugins() + enabled_plugins = redis:smembers('telegram:enabled_plugins') + if not enabled_plugins[1] then + create_plugin_set() + end + return enabled_plugins +end + +-- create plugin set if it doesn't exist +function create_plugin_set() + enabled_plugins = { + 'control', + 'blacklist', + 'about', + 'ping', + 'whoami', + 'nick', + 'echo', + 'imgblacklist', + 'gImages', + 'gSearch', + 'wikipedia', + 'hackernews', + 'imdb', + 'calc', + 'urbandictionary', + 'time', + 'reddit', + 'xkcd', + 'slap', + 'commit', + 'pun', + 'currency', + 'shout', + 'set', + 'get', + 'patterns', + '9gag', + 'shell', + 'adfly', + 'twitter', + 'rss', + 'remind', + 'youtube', + 'youtube_search', + 'youtube_channel', + 'youtube_playlist', + 'tagesschau_eil', + 'twitter_send', + 'respond', + 'roll', + 'quotes', + 'pasteee', + 'images', + 'media', + 'location_manager', + 'creds', + 'weather', + 'forecast', + 'expand', + 'facebook', + 'github', + 'bitly', + 'app_store', + 'bitly_create', + 'br', + 'heise', + 'tagesschau', + 'wiimmfi', + 'wikia', + 'afk', + 'stats', + 'btc', + 'cats', + 'cleverbot', + 'imgur', + 'banhammer', + 'channels', + 'plugins', + 'help', + 'greetings' + } + print ('enabling a few plugins - saving to redis set telegram:enabled_plugins') + for _,plugin in pairs(enabled_plugins) do + redis:sadd("telegram:enabled_plugins", plugin) + end +end + function load_cred() if redis:exists("telegram:credentials") == false then -- If credentials hash doesnt exists diff --git a/otouto/plugins/about.lua b/otouto/plugins/about.lua index 6912b6b..bf5fa23 100644 --- a/otouto/plugins/about.lua +++ b/otouto/plugins/about.lua @@ -7,16 +7,17 @@ about.command = 'about' about.doc = '`Sendet Informationen über den Bot.`' about.triggers = { - '' + '/about' } function about:action(msg, config) -- Filthy hack, but here is where we'll stop forwarded messages from hitting -- other plugins. - if msg.forward_from then return end + -- disabled to restore old behaviour + -- if msg.forward_from then return end - local output = config.about_text .. '\nBrawlbot v2, basierend auf Otouto v'..bot.version..' von topkecleon.' + local output = config.about_text .. '\nBrawlbot v2, basierend auf Otouto von topkecleon.' if (msg.new_chat_participant and msg.new_chat_participant.id == self.info.id) or msg.text_lower:match('^'..config.cmd_pat..'about') diff --git a/otouto/plugins/banhammer.lua b/otouto/plugins/banhammer.lua index f62c388..b43e17d 100644 --- a/otouto/plugins/banhammer.lua +++ b/otouto/plugins/banhammer.lua @@ -151,8 +151,8 @@ function banhammer:pre_process(msg, self, config) msg.entities = '' end - else - print('Whitelist not enabled or is sudo') + -- else + -- print('Whitelist not enabled or is sudo') end return msg diff --git a/otouto/plugins/channels.lua b/otouto/plugins/channels.lua new file mode 100644 index 0000000..c149184 --- /dev/null +++ b/otouto/plugins/channels.lua @@ -0,0 +1,90 @@ +local channels = {} + +local bindings = require('otouto.bindings') +local utilities = require('otouto.utilities') +local redis = (loadfile "./otouto/redis.lua")() + +channels.command = 'channel ' + +function channels:init(config) + channels.triggers = { + "^/channel? (enable)", + "^/channel? (disable)" + } + channels.doc = [[* +]]..config.cmd_pat..[[channel* __/__: Aktiviert/deaktiviert den Bot im Chat]] +end + +-- Checks if bot was disabled on specific chat +function channels:is_channel_disabled(msg) + local hash = 'chat:'..msg.chat.id..':disabled' + local disabled = redis:get(hash) + + if not disabled or disabled == "false" then + return false + end + + return disabled +end + +function channels:enable_channel(msg) + local hash = 'chat:'..msg.chat.id..':disabled' + local disabled = redis:get(hash) + if disabled then + print('Setting redis variable '..hash..' to false') + redis:set(hash, false) + return 'Channel aktiviert' + else + return 'Channel ist nicht deaktiviert!' + end +end + +function channels:disable_channel(msg) + local hash = 'chat:'..msg.chat.id..':disabled' + local disabled = redis:get(hash) + if disabled ~= "true" then + print('Setting redis variable '..hash..' to true') + redis:set(hash, true) + return 'Channel deaktiviert' + else + return 'Channel ist bereits deaktiviert!' + end +end + +function channels:pre_process(msg, self, config) + -- If is sudo can reeanble the channel + if is_sudo(msg, config) then + if msg.text == "/channel enable" then + channels:enable_channel(msg) + end + end + + if channels:is_channel_disabled(msg) then + print('Channel wurde deaktiviert') + msg.text = '' + msg.text_lower = '' + msg.entities = '' + end + + return msg +end + +function channels:action(msg, config) + if msg.from.id ~= config.admin then + utilities.send_reply(self, msg, config.errors.sudo) + return + end + + -- Enable a channel + if matches[1] == 'enable' then + utilities.send_reply(self, msg, channels:enable_channel(msg)) + return + end + -- Disable a channel + if matches[1] == 'disable' then + utilities.send_reply(self, msg, channels:disable_channel(msg)) + return + end +end + +return channels \ No newline at end of file diff --git a/otouto/plugins/control.lua b/otouto/plugins/control.lua index 5ebcd16..391c1fa 100644 --- a/otouto/plugins/control.lua +++ b/otouto/plugins/control.lua @@ -8,7 +8,7 @@ local cmd_pat -- Prevents the command from being uncallable. function control:init(config) cmd_pat = config.cmd_pat control.triggers = utilities.triggers(self.info.username, cmd_pat, - {'^'..cmd_pat..'script'}):t('reload', true):t('halt').table + {'^'..cmd_pat..'script'}):t('restart', true):t('halt').table end function control:action(msg, config) @@ -19,7 +19,7 @@ function control:action(msg, config) if msg.date < os.time() - 1 then return end - if msg.text_lower:match('^'..cmd_pat..'reload') then + if msg.text_lower:match('^'..cmd_pat..'restart') then for pac, _ in pairs(package.loaded) do if pac:match('^otouto%.plugins%.') then package.loaded[pac] = nil diff --git a/otouto/plugins/plugins.lua b/otouto/plugins/plugins.lua new file mode 100644 index 0000000..b6dff1b --- /dev/null +++ b/otouto/plugins/plugins.lua @@ -0,0 +1,228 @@ +local plugin_manager = {} + +local bindings = require('otouto.bindings') +local utilities = require('otouto.utilities') +local redis = (loadfile "./otouto/redis.lua")() + +function plugin_manager:init(config) + plugin_manager.triggers = { + "^/plugins$", + "^/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)$" + } + plugin_manager.doc = [[* +]]..config.cmd_pat..[[plugins*: Listet alle Plugins auf +*]]..config.cmd_pat..[[plugins* _enable/disable_ __: Aktiviert/deaktiviert Plugin +*]]..config.cmd_pat..[[plugins* _enable/disable_ __ chat: Aktiviert/deaktiviert Plugin im aktuellen Chat +*]]..config.cmd_pat..[[plugins* _enable/disable_ __ __: Aktiviert/deaktiviert Plugin in diesem Chat +*]]..config.cmd_pat..[[reload*: Lädt Plugins neu]] +end + +plugin_manager.command = 'plugins ' + +-- Returns the key (index) in the config.enabled_plugins table +function plugin_manager:plugin_enabled(name, chat) + for k,v in pairs(enabled_plugins) do + if name == v then + return k + end + end + -- If not found + return false +end + +-- Returns true if file exists in plugins folder +function plugin_manager:plugin_exists(name) + for k,v in pairs(plugins_names()) do + if name..'.lua' == v then + return true + end + end + return false +end + +function plugin_manager:list_plugins() + local text = '' + for k, v in pairs(plugins_names()) do + -- ✔ enabled, ❌ disabled + local status = '❌' + -- Check if is enabled + for k2, v2 in pairs(enabled_plugins) do + if v == v2..'.lua' then + status = '✔' + end + end + if not only_enabled or status == '✔' then + -- get the name + v = string.match (v, "(.*)%.lua") + text = text..v..' '..status..'\n' + end + end + return text +end + +function plugin_manager:reload_plugins(self, config, plugin_name, status) + self.plugins = {} + load_plugins() + for _,v in ipairs(enabled_plugins) do + local p = require('otouto.plugins.'..v) + print('loading plugin',v) + table.insert(self.plugins, p) + if p.init then p.init(self, config) end + end + if plugin_name then + return 'Plugin '..plugin_name..' wurde '..status + else + return 'Plugins neu geladen' + end +end + +function plugin_manager:enable_plugin(self, config, plugin_name) + print('checking if '..plugin_name..' exists') + -- Check if plugin is enabled + if plugin_manager:plugin_enabled(plugin_name) then + return 'Plugin '..plugin_name..' ist schon aktiviert' + end + -- Checks if plugin exists + if plugin_manager:plugin_exists(plugin_name) then + -- Add to redis set + redis:sadd('telegram:enabled_plugins', plugin_name) + print(plugin_name..' saved to redis set telegram:enabled_plugins') + -- Reload the plugins + return plugin_manager:reload_plugins(self, config, plugin_name, 'aktiviert') + else + return 'Plugin '..plugin_name..' existiert nicht' + end +end + +function plugin_manager:disable_plugin(self, config, name, chat) + -- Check if plugins exists + if not plugin_manager:plugin_exists(name) then + return 'Plugin '..name..' existiert nicht' + end + local k = plugin_manager:plugin_enabled(name) + -- Check if plugin is enabled + if not k then + return 'Plugin '..name..' ist nicht aktiviert' + end + -- Disable and reload + redis:srem('telegram:enabled_plugins', name) + print(name..' saved to redis set telegram:enabled_plugins') + return plugin_manager:reload_plugins(self, config, name, 'deaktiviert') +end + +function plugin_manager:disable_plugin_on_chat(msg, plugin) + if not plugin_manager:plugin_exists(plugin) then + return "Plugin existiert nicht!" + end + + if not msg.chat then + hash = 'chat:'..msg..':disabled_plugins' + else + hash = get_redis_hash(msg, 'disabled_plugins') + end + local disabled = redis:hget(hash, plugin) + + if disabled ~= 'true' then + print('Setting '..plugin..' in redis hash '..hash..' to true') + redis:hset(hash, plugin, true) + return 'Plugin '..plugin..' für diesen Chat deaktiviert.' + else + return 'Plugin '..plugin..' wurde für diesen Chat bereits deaktiviert.' + end +end + +function plugin_manager:reenable_plugin_on_chat(msg, plugin) + if not plugin_manager:plugin_exists(plugin) then + return "Plugin existiert nicht!" + end + + if not msg.chat then + hash = 'chat:'..msg..':disabled_plugins' + else + hash = get_redis_hash(msg, 'disabled_plugins') + end + local disabled = redis:hget(hash, plugin) + + if disabled == nil then return 'Es gibt keine deaktivierten Plugins für disen Chat.' end + + if disabled == 'true' then + print('Setting '..plugin..' in redis hash '..hash..' to false') + redis:hset(hash, plugin, false) + return 'Plugin '..plugin..' wurde für diesen Chat reaktiviert.' + else + return 'Plugin '..plugin..' ist nicht deaktiviert.' + end +end + +function plugin_manager:action(msg, config) + if msg.from.id ~= config.admin then + utilities.send_reply(self, msg, config.errors.sudo) + return + end + + -- Show the available plugins + if matches[1] == '/plugins' then + utilities.send_reply(self, msg, plugin_manager:list_plugins()) + return + end + + -- Reenable a plugin for this chat + if matches[1] == 'enable' and matches[3] == 'chat' then + local plugin = matches[2] + if matches[4] then + local id = matches[4] + print("enable "..plugin..' on chat#id'..id) + utilities.send_reply(self, msg, plugin_manager:reenable_plugin_on_chat(id, plugin)) + return + else + print("enable "..plugin..' on this chat') + utilities.send_reply(self, msg, plugin_manager:reenable_plugin_on_chat(msg, plugin)) + return + end + end + + -- Enable a plugin + if matches[1] == 'enable' then + local plugin_name = matches[2] + print("enable: "..matches[2]) + utilities.send_reply(self, msg, plugin_manager:enable_plugin(self, config, plugin_name)) + return + end + + -- Disable a plugin on a chat + if matches[1] == 'disable' and matches[3] == 'chat' then + local plugin = matches[2] + if matches[4] then + local id = matches[4] + print("disable "..plugin..' on chat#id'..id) + utilities.send_reply(self, msg, plugin_manager:disable_plugin_on_chat(id, plugin)) + return + else + print("disable "..plugin..' on this chat') + utilities.send_reply(self, msg, plugin_manager:disable_plugin_on_chat(msg, plugin)) + return + end + end + + -- Disable a plugin + if matches[1] == 'disable' then + print("disable: "..matches[2]) + utilities.send_reply(self, msg, plugin_manager:disable_plugin(self, config, matches[2])) + return + end + + -- Reload all the plugins! + if matches[1] == 'reload' then + utilities.send_reply(self, msg, plugin_manager:reload_plugins(self, config)) + return + end +end + +return plugin_manager \ No newline at end of file diff --git a/otouto/utilities.lua b/otouto/utilities.lua index 98ae417..4785794 100644 --- a/otouto/utilities.lua +++ b/otouto/utilities.lua @@ -589,8 +589,40 @@ utilities.char = { em_dash = '—' } +-- taken from http://stackoverflow.com/a/11130774/3163199 +function scandir(directory) + local i, t, popen = 0, {}, io.popen + for filename in popen('ls -a "'..directory..'"'):lines() do + i = i + 1 + t[i] = filename + end + return t +end + +-- Returns at table of lua files inside plugins +function plugins_names() + local files = {} + for k, v in pairs(scandir("otouto/plugins")) do + -- Ends with .lua + if (v:match(".lua$")) then + table.insert(files, v) + end + end + return files +end + +-- Function name explains what it does. +function file_exists(name) + local f = io.open(name,"r") + if f ~= nil then + io.close(f) + return true + else + return false + end +end + -- Returns a table with matches or nil ---function match_pattern(pattern, text, lower_case) function match_pattern(pattern, text) if text then local matches = { string.match(text, pattern) }