diff --git a/.gitignore b/.gitignore index 46109bb..89411f1 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,11 @@ luac.out *.hex +composer.lock +*.madeline +*.madeline.lock +data/config.lua + # ========================= # Operating System Files # ========================= diff --git a/.gitmodules b/.gitmodules index f033e96..edd35df 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "tg"] - path = tg - url = https://github.com/LucentW/tg.git +[submodule "Madeline_lua_shim"] + path = Madeline_lua_shim + url = https://github.com/giuseppeM99/Madeline_lua_shim diff --git a/Madeline_lua_shim b/Madeline_lua_shim new file mode 160000 index 0000000..f089f1a --- /dev/null +++ b/Madeline_lua_shim @@ -0,0 +1 @@ +Subproject commit f089f1ae45d5e0fc009342e2fb93c69d1f1dba17 diff --git a/bot/bot.lua b/bot/bot.lua index 9dd7785..cf55f7c 100644 --- a/bot/bot.lua +++ b/bot/bot.lua @@ -1,15 +1,29 @@ -package.path = package.path .. ';.luarocks/share/lua/5.2/?.lua' -..';.luarocks/share/lua/5.2/?/init.lua' -package.cpath = package.cpath .. ';.luarocks/lib/lua/5.2/?.so' +local _VERSION_NUM = _VERSION:match('(%d%.%d)$') + +package.path = package.path .. ';.luarocks/share/lua/'.._VERSION_NUM..'/?.lua' +..';.luarocks/share/lua/'.._VERSION_NUM..'/?/init.lua' +package.cpath = package.cpath .. ';.luarocks/lib/lua/'.._VERSION_NUM..'/?.so' require("./bot/utils") require("./bot/emoji") -VERSION = '0.2' +VERSION = '0.3' + +-- Let's leave space for backwards-compatible new levels +LOGLEVEL_DEBUG = 0 +LOGLEVEL_INFO = 10 +LOGLEVEL_WARN = 20 +LOGLEVEL_ERROR = 30 +if (not loglevel) then loglevel = LOGLEVEL_INFO end +function log(level, message) + if (level >= loglevel) then print(message) end +end --- This function is called when tg receive a msg +-- This function is called when tg receives a msg +-- Returns false if the message was ignored, true otherwise function on_msg_receive (msg) if not started then + log(LOGLEVEL_DEBUG, "not started") return end @@ -23,7 +37,7 @@ function on_msg_receive (msg) -- vardump(msg) msg = pre_process_service_msg(msg) - + if msg_valid(msg) then msg = pre_process_msg(msg) if msg then @@ -38,8 +52,13 @@ function on_msg_receive (msg) -- as read for everybody. -- mark_read(receiver, ok_cb, false) - end + if not (not whitelistmod or (whitelistmod and is_momod(msg))) then + log(LOGLEVEL_INFO, 'Message ignored -- '..chat_id..' has modonly wl enabled') + return false end + + match_plugins(msg) + return true end function ok_cb(extra, success, result) @@ -51,53 +70,56 @@ function on_binlog_replay_end() -- See plugins/isup.lua as an example for cron - _config = load_config() + -- You can optionally pass a pre-existing configuration. Used in tests. + if (not _config) then + _config = load_config() + end -- load plugins plugins = {} - load_plugins() + return load_plugins() end function msg_valid(msg) -- Don't process outgoing messages if msg.out then - print('\27[36mNot valid: msg from us\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: msg from us\27[39m') return false end -- Before bot was started if msg.date < now then - print('\27[36mNot valid: old msg\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: old msg\27[39m') return false end if msg.unread == 0 then - print('\27[36mNot valid: read\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: read\27[39m') return false end if not msg.to.id then - print('\27[36mNot valid: To id not provided\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: To id not provided\27[39m') return false end if not msg.from.id then - print('\27[36mNot valid: From id not provided\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: From id not provided\27[39m') return false end if msg.from.id == our_id then - print('\27[36mNot valid: Msg from our id\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: Msg from our id\27[39m') return false end if msg.to.type == 'encr_chat' then - print('\27[36mNot valid: Encrypted chat\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: Encrypted chat\27[39m') return false end if msg.from.id == 777000 then - print('\27[36mNot valid: Telegram message\27[39m') + log(LOGLEVEL_INFO, '\27[36mNot valid: Telegram message\27[39m') return false end @@ -126,7 +148,7 @@ end function pre_process_msg(msg) for name,plugin in pairs(plugins) do if plugin.pre_process and msg then - print('Preprocess', name) + log(LOGLEVEL_INFO, 'Preprocess ' .. name) msg = plugin.pre_process(msg) end end @@ -150,10 +172,10 @@ local function is_plugin_disabled_on_chat(plugin_name, receiver) for disabled_plugin,disabled in pairs(disabled_chats[receiver]) do if disabled_plugin == plugin_name and disabled then if plugins[disabled_plugin].hidden then - print('Plugin '..disabled_plugin..' is disabled on this chat') + log(LOGLEVEL_INFO, 'Plugin '..disabled_plugin..' is disabled on this chat') else local warning = 'Plugin '..disabled_plugin..' is disabled on this chat' - print(warning) + log(LOGLEVEL_INFO, warning) send_msg(receiver, warning, ok_cb, false) end return true @@ -180,14 +202,14 @@ function match_plugin(plugin, plugin_name, msg) for k, pattern in pairs(plugin.patterns) do local matches = match_pattern(pattern, msg.text) if matches then - print("msg matches: ", pattern) + log(LOGLEVEL_INFO, "msg matches: " .. pattern) if not is_sudo(msg) then if is_plugin_disabled_on_chat(plugin_name, receiver) then - return nil + goto continue end if plugin.nsfw and is_nsfw_disabled_on_chat(receiver) then - return nil + goto continue end end @@ -202,8 +224,9 @@ function match_plugin(plugin, plugin_name, msg) end end -- One patterns matches - return + goto continue end + ::continue:: end end @@ -215,7 +238,7 @@ end -- Save the content of _config to config.lua function save_config( ) serialize_to_file(_config, './data/config.lua') - print ('saved config into ./data/config.lua') + log(LOGLEVEL_INFO, 'saved config into ./data/config.lua') end -- Returns the config from config.lua file. @@ -224,14 +247,14 @@ function load_config( ) local f = io.open('./data/config.lua', "r") -- If config.lua doesn't exist if not f then - print ("Created new config file: data/config.lua") + log(LOGLEVEL_INFO, "Created new config file: data/config.lua") create_config() else f:close() end local config = loadfile ("./data/config.lua")() for v,user in pairs(config.sudo_users) do - print("Allowed user: " .. user) + log(LOGLEVEL_INFO, "Allowed user: " .. user) end return config end @@ -288,8 +311,8 @@ function create_config( ) moderation = {data = 'data/moderation.json'} } serialize_to_file(config, './data/config.lua') - print ('Saved clean configuration into ./data/config.lua') - print ('Make sure to edit sudo_users and add your ID.') + log(LOGLEVEL_INFO, 'Saved clean configuration into ./data/config.lua') + log(LOGLEVEL_INFO, 'Make sure to edit sudo_users and add your ID.') end function on_our_id (id) @@ -316,21 +339,25 @@ function on_get_difference_end () end -- Enable plugins in config.json +-- Returns true if all the plugins were loaded correctly, false otherwise function load_plugins() + local success = true for k, v in pairs(_config.enabled_plugins) do - print("Loading plugin", v) + log(LOGLEVEL_INFO, "Loading plugin " .. v) local ok, err = pcall(function() - local t = loadfile("plugins/"..v..'.lua')() + local t = assert(loadfile("plugins/"..v..'.lua'))() plugins[v] = t end) if not ok then - print('\27[31mError loading plugin '..v..'\27[39m') - print('\27[31m'..err..'\27[39m') + success = false + log(LOGLEVEL_WARN, '\27[31mError loading plugin '..v..'\27[39m') + log(LOGLEVEL_WARN, '\27[31m'..err..'\27[39m') end - end + + return success end -- custom add @@ -371,6 +398,7 @@ function cron_plugins() postpone (cron_plugins, false, 5*60.0) end + -- Start and load values our_id = 0 now = os.time() diff --git a/bot/utils.lua b/bot/utils.lua index 30a1973..7f2ffcc 100644 --- a/bot/utils.lua +++ b/bot/utils.lua @@ -13,7 +13,7 @@ JSON = (loadfile "./libs/dkjson.lua")() http.TIMEOUT = 10 -- insert snoop group ID -local LOG_ID = 0 +local LOG_ID = "chat#id0" function get_receiver(msg) if msg.to.type == 'user' then @@ -47,6 +47,20 @@ function is_chan_msg( msg ) return false end +function user_print_name(user) + local text = '' + if user.first_name then + text = user.first_name..' ' + end + if user.last_name then + text = text..user.last_name + end + if user.title then + text = user.title + end + return text or user.print_name:gsub('_', ' ') +end + function string.random(length) local str = ""; for i = 1, length do @@ -65,7 +79,7 @@ end -- DEPRECATED function string.trim(s) - print("string.trim(s) is DEPRECATED use string:trim() instead") + log(LOGLEVEL_WARN, "string.trim(s) is DEPRECATED use string:trim() instead") return s:gsub("^%s*(.-)%s*$", "%1") end @@ -104,7 +118,7 @@ end -- Saves file to /tmp/. If file_name isn't provided, -- will get the text after the last "/" for filename -- and content-type for extension -function download_to_file(url, file_name) +function my_download_to_file(url, file_name) print("url to download: "..url) local respbody = {} @@ -133,7 +147,7 @@ function download_to_file(url, file_name) file_name = file_name or get_http_file_name(url, headers) local file_path = "/tmp/"..file_name - print("Saved to: "..file_path) + log(LOGLEVEL_INFO, "Saved to: "..file_path) file = io.open(file_path, "w+") file:write(table.concat(respbody)) @@ -143,7 +157,7 @@ function download_to_file(url, file_name) end function vardump(value) - print(serpent.block(value, {comment=false})) + log(LOGLEVEL_INFO, serpent.block(value, {comment=false})) end -- taken from http://stackoverflow.com/a/11130774/3163199 @@ -166,104 +180,102 @@ end -- User has superuser privileges function is_sudo(msg) - local var = false -- Check users id in config for v,user in pairs(_config.sudo_users) do if user == msg.from.id then - var = true + return true end end - return var + return false end -- user has admins privileges function is_admin(msg) - local var = false local data = load_data(_config.moderation.data) local user = msg.from.id local admins = 'admins' if data[tostring(admins)] then if data[tostring(admins)][tostring(user)] then - var = true + return true end end for v,user in pairs(_config.sudo_users) do if user == msg.from.id then - var = true + return true end end - return var + return false end -- user has blocklist adding privileges function is_blocklistadm(msg) - local var = false local data = load_data(_config.moderation.data) local user = msg.from.id local blocklist = 'blocklist' if data[tostring(blocklist)] then if data[tostring(blocklist)][tostring(user)] then - var = true + return true end end for v,user in pairs(_config.sudo_users) do if user == msg.from.id then - var = true + return true end end - return var + return false end -- user has moderator privileges function is_momod(msg) - local var = false local data = load_data(_config.moderation.data) local user = msg.from.id if data[tostring(msg.to.id)] then if data[tostring(msg.to.id)]['moderators'] then if data[tostring(msg.to.id)]['moderators'][tostring(user)] then - var = true + return true end end end if data['admins'] then if data['admins'][tostring(user)] then - var = true + return true end end for v,user in pairs(_config.sudo_users) do if user == msg.from.id then - var = true + return true end end - return var + if user == our_id then + return true + end + return false end -- check whether user is mod, admin or sudo function is_mod(user_id, chat_id) - local var = false local data = load_data(_config.moderation.data) if data[tostring(chat_id)] then if data[tostring(chat_id)]['moderators'] then if data[tostring(chat_id)]['moderators'][tostring(user_id)] then - var = true + return true end end end if data['admins'] then if data['admins'][tostring(user_id)] then - var = true + return true end end for v,user in pairs(_config.sudo_users) do if user == user_id then - var = true + return true end end - if user == our_id then - var = true + if user_id == our_id then + return true end - return var + return false end -- Returns the name of the sender @@ -328,7 +340,7 @@ end -- DEPRECATED!!!!! function string.starts(String, Start) - print("string.starts(String, Start) is DEPRECATED use string:starts(text) instead") + log(LOGLEVEL_INFO, "string.starts(String, Start) is DEPRECATED use string:starts(text) instead") return Start == string.sub(String,1,string.len(Start)) end @@ -346,7 +358,7 @@ function _send_photo(receiver, file_path, cb_function, cb_extra) cb_extra = cb_extra } -- Call to remove with optional callback - send_photo(receiver, file_path, cb_function, cb_extra) + send_photo(receiver, file_path, rmtmp_cb, cb_extra) end -- Download the image and send to receiver, it will be deleted. @@ -355,14 +367,13 @@ function send_photo_from_url(receiver, url, cb_function, cb_extra) -- If callback not provided cb_function = cb_function or ok_cb cb_extra = cb_extra or false - - local file_path = download_to_file(url, false) - if not file_path then -- Error + local inputMedia = {_ = "inputMediaPhotoExternal", url = url , caption = ""} + local res = fixfp(messages.sendMedia({peer = receiver, media = inputMedia})) + if not res or res == {} or res.error then -- Error local text = 'Error downloading the image' send_msg(receiver, text, cb_function, cb_extra) else - print("File path: "..file_path) - _send_photo(receiver, file_path, cb_function, cb_extra) + cb_function(cb_extra, true, res) end end @@ -371,13 +382,10 @@ function send_photo_from_url_callback(cb_extra, success, result) local receiver = cb_extra.receiver local url = cb_extra.url - local file_path = download_to_file(url, false) - if not file_path then -- Error + local file_path = my_download_to_file(url, false) + if not res or res == {} or res.error then -- Error local text = 'Error downloading the image' - send_msg(receiver, text, ok_cb, false) - else - print("File path: "..file_path) - _send_photo(receiver, file_path, ok_cb, false) + send_msg(receiver, text, cb_function, cb_extra) end end @@ -398,13 +406,6 @@ function send_photos_from_url_callback(cb_extra, success, result) -- cb_extra is a table containing receiver, urls and remove_path local receiver = cb_extra.receiver local urls = cb_extra.urls - local remove_path = cb_extra.remove_path - - -- The previously image to remove - if remove_path ~= nil then - os.remove(remove_path) - print("Deleted: "..remove_path) - end -- Nil or empty, exit case (no more urls) if urls == nil or #urls == 0 then @@ -414,15 +415,11 @@ function send_photos_from_url_callback(cb_extra, success, result) -- Take the head and remove from urls table local head = table.remove(urls, 1) - local file_path = download_to_file(head, false) - local cb_extra = { - receiver = receiver, - urls = urls, - remove_path = file_path - } + local inputMedia = {_ = "inputMediaPhotoExternal", url = head , caption = ""} + local res = fixfp(messages.sendMedia({peer = receiver, media = inputMedia})) -- Send first and postpone the others as callback - send_photo(receiver, file_path, send_photos_from_url_callback, cb_extra) + send_photos_from_url_callback(cb_extra, true, res) end -- Callback to remove a file @@ -433,7 +430,7 @@ function rmtmp_cb(cb_extra, success, result) if file_path ~= nil then os.remove(file_path) - print("Deleted: "..file_path) + log(LOGLEVEL_INFO, "Deleted: "..file_path) end -- Finally call the callback cb_function(cb_extra, success, result) @@ -454,9 +451,13 @@ end -- Download the image and send to receiver, it will be deleted. -- cb_function and cb_extra are optionals callback function send_document_from_url(receiver, url, cb_function, cb_extra) - local file_path = download_to_file(url, false) - print("File path: "..file_path) - _send_document(receiver, file_path, cb_function, cb_extra) + local inputMedia = {_ = "inputMediaDocumentExternal", url = url , caption = ""} + local res = fixfp(messages.sendMedia({peer = receiver, media = inputMedia})) + if not res or res == {} or res.error then + cb_function(cb_extra, false, res) + else + cb_function(cb_extra, true, res) + end end -- Parameters in ?a=1&b=2 style @@ -522,7 +523,7 @@ function send_order_msg_callback(cb_extra, success, result) local file_path = cb_extra.file_path if file_path ~= nil then os.remove(file_path) - print("Deleted: " .. file_path) + log(LOGLEVEL_INFO, "Deleted: " .. file_path) end if type(msgs) == 'string' then send_large_msg(destination, msgs) @@ -569,7 +570,7 @@ end -- Log to group function snoop_msg(text) local cb_extra = { - destination = "chat#id"..LOG_ID, + destination = LOG_ID, text = text } send_large_msg_callback(cb_extra, true) @@ -588,6 +589,10 @@ function send_large_msg_callback(cb_extra, success, result) end local text_len + if type(text) == "number" then + text = tostring(text) + end + if type(text) ~= "boolean" then text_len = string.len(text) or 0 else @@ -616,18 +621,16 @@ end -- Returns a table with matches or nil function match_pattern(pattern, text, lower_case) - if text then - local matches = {} - if lower_case then - matches = { string.match(text:lower(), pattern) } - else - matches = { string.match(text, pattern) } - end - if next(matches) then - return matches - end + if not text then return nil end + local matches = {} + if lower_case then + matches = { string.match(text:lower(), pattern) } + else + matches = { string.match(text, pattern) } + end + if next(matches) then + return matches end - -- nil end -- Function to read data from files @@ -638,9 +641,9 @@ function load_from_file(file, default_data) -- Create a new empty table default_data = default_data or {} serialize_to_file(default_data, file) - print ('Created file', file) + log(LOGLEVEL_INFO, 'Created file ' .. file) else - print ('Data loaded from file', file) + log(LOGLEVEL_INFO, 'Data loaded from file ' .. file) f:close() end return loadfile (file)() @@ -682,5 +685,13 @@ function backward_msg_format (msg) user.peer_id = longid user.type = user.peer_type end + if msg.action and msg.action.users then + for _, user in ipairs(msg.action.users) do + local longid = user.id + user.id = user.peer_id + user.peer_id = longid + user.type = user.peer_type + end + end return msg end diff --git a/launch.sh b/launch.sh index f40f2ce..77be257 100755 --- a/launch.sh +++ b/launch.sh @@ -1,35 +1,66 @@ #!/usr/bin/env bash THIS_DIR=$(cd $(dirname $0); pwd) +LUA_DIR="$THIS_DIR/.lua" cd $THIS_DIR update() { git pull git submodule update --init --recursive - install_rocks + cd Madeline_lua_shim + composer update + cd .. +} + +install_lua() { + if [ ! -f $LUA_DIR]; then + mkdir $LUA_DIR + fi + curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz + tar zxf lua-5.3.4.tar.gz + cd lua-5.3.4 + sed -i 's/CFLAGS= -O2/CFLAGS= -fPIC -O2/g' src/Makefile + sed -i "s:/usr/local:$LUA_DIR:g" Makefile + make linux + make install + cd .. + rm -rf lua-5.3.4* +} + +install_php_lua() { + git clone https://github.com/giuseppem99/php-lua + cd php-lua + phpize + ./configure --with-lua=$LUA_DIR + make + cp modules/lua.so .. + cd .. + rm -rf php-lua } # Will install luarocks on THIS_DIR/.luarocks install_luarocks() { - git clone https://github.com/keplerproject/luarocks.git - cd luarocks - git checkout tags/v2.2.1 # Current stable + if [ ! -f .luarocks/bin/luarocks ]; then + git clone https://github.com/keplerproject/luarocks.git + cd luarocks + git checkout tags/v2.4.2 # Current stable - PREFIX="$THIS_DIR/.luarocks" + PREFIX="$THIS_DIR/.luarocks" - ./configure --prefix=$PREFIX --sysconfdir=$PREFIX/luarocks --force-config + ./configure --prefix=$PREFIX --sysconfdir=$PREFIX/luarocks --with-lua=$LUA_DIR --force-config - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi + RET=$?; if [ $RET -ne 0 ]; + then echo "Error. Exiting."; exit $RET; + fi - make build && make install - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting.";exit $RET; - fi + make build && make install + RET=$?; if [ $RET -ne 0 ]; + then echo "Error. Exiting.";exit $RET; + fi - cd .. - rm -rf luarocks + cd .. + rm -rf luarocks + fi } install_rocks() { @@ -38,11 +69,15 @@ install_rocks() { then echo "Error. Exiting."; exit $RET; fi - ./.luarocks/bin/luarocks install oauth + ./.luarocks/bin/luarocks install luasec RET=$?; if [ $RET -ne 0 ]; then echo "Error. Exiting."; exit $RET; fi + git clone https://github.com/ignacio/LuaOAuth oauth + cp -a oauth/src/* .luarocks/share/lua/5.3/ + rm -rf oauth + ./.luarocks/bin/luarocks install redis-lua RET=$?; if [ $RET -ne 0 ]; then echo "Error. Exiting."; exit $RET; @@ -107,42 +142,57 @@ install_rocks() { install() { git pull git submodule update --init --recursive - cd tg && ./configure && make + cd Madeline_lua_shim && composer update + cd .. + install_lua + install_php_lua + install_luarocks + install_rocks +} - RET=$?; if [ $RET -ne 0 ]; then - echo "Trying without Python..."; - ./configure --disable-python && make - RET=$? +botlogin() { + if [ ! -f ./Madeline_lua_shim/vendor/autoload.php ]; then + echo "MadelineProto not found, installing..." + install fi + cd Madeline_lua_shim + php botlogin.php + cp bot.madeline ../bot.madeline + cd .. +} - if [ $RET -ne 0 ]; then - echo "Error. Exiting."; exit $RET; +login() { + if [ ! -f ./Madeline_lua_shim/vendor/autoload.php ]; then + echo "MadelineProto not found, installing..." + install fi + cd Madeline_lua_shim + php userlogin.php + cp bot.madeline ../bot.madeline cd .. - install_luarocks - install_rocks } if [ "$1" = "install" ]; then install elif [ "$1" = "update" ]; then update +elif [ "$1" = "login" ]; then + login +elif [ "$1" = "botlogin" ]; then + botlogin else - if [ ! -f ./tg/telegram.h ]; then - echo "tg not found" + if [ ! -f ./Madeline_lua_shim/vendor/autoload.php ]; then + echo "MadelineProto not found" echo "Run $0 install" exit 1 fi - if [ ! -f ./tg/bin/telegram-cli ]; then - echo "tg binary not found" - echo "Run $0 install" + if [ ! -e "bot.madeline" ]; then + echo "Login file not found" + echo "Run $0 login or $0 botlogin" exit 1 fi - if [ ! -e "bot_mode" ]; then - ./tg/bin/telegram-cli -k ./tg/tg-server.pub -s ./bot/bot.lua -l 1 -E - else - ./tg/bin/telegram-cli -k ./tg/tg-server.pub -s ./bot/bot.lua -l 1 -E -b - fi + php -d extension='./lua.so' madeline.php + fi diff --git a/launchd.sh b/launchd.sh deleted file mode 100755 index 9cc43bf..0000000 --- a/launchd.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env bash - -THIS_DIR=$(cd $(dirname $0); pwd) -cd $THIS_DIR - -update() { - git pull - git submodule update --init --recursive - install_rocks -} - -# Will install luarocks on THIS_DIR/.luarocks -install_luarocks() { - git clone https://github.com/keplerproject/luarocks.git - cd luarocks - git checkout tags/v2.2.1 # Current stable - - PREFIX="$THIS_DIR/.luarocks" - - ./configure --prefix=$PREFIX --sysconfdir=$PREFIX/luarocks --force-config - - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - make build && make install - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting.";exit $RET; - fi - - cd .. - rm -rf luarocks -} - -install_rocks() { - ./.luarocks/bin/luarocks install luasocket - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - ./.luarocks/bin/luarocks install oauth - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - ./.luarocks/bin/luarocks install redis-lua - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - ./.luarocks/bin/luarocks install lua-cjson - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - ./.luarocks/bin/luarocks install fakeredis - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - ./.luarocks/bin/luarocks install xml - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - ./.luarocks/bin/luarocks install feedparser - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi - - ./.luarocks/bin/luarocks install serpent - RET=$?; if [ $RET -ne 0 ]; - then echo "Error. Exiting."; exit $RET; - fi -} - -install() { - git pull - git submodule update --init --recursive - cd tg && ./configure && make - - RET=$?; if [ $RET -ne 0 ]; then - echo "Trying without Python..."; - ./configure --disable-python && make - RET=$? - fi - - if [ $RET -ne 0 ]; then - echo "Error. Exiting."; exit $RET; - fi - cd .. - install_luarocks - install_rocks -} - -if [ "$1" = "install" ]; then - install -elif [ "$1" = "update" ]; then - update -else - if [ ! -f ./tg/telegram.h ]; then - echo "tg not found" - echo "Run $0 install" - exit 1 - fi - - if [ ! -f ./tg/bin/telegram-cli ]; then - echo "tg binary not found" - echo "Run $0 install" - exit 1 - fi - - if [ ! -e "bot_mode" ]; then - gdb --args ./tg/bin/telegram-cli -k ./tg/tg-server.pub -s ./bot/bot.lua -l 1 -E - else - gdb --args ./tg/bin/telegram-cli -k ./tg/tg-server.pub -s ./bot/bot.lua -l 1 -E -b - fi -fi diff --git a/launchf.sh b/launchf.sh index bb8f32e..320dac3 100755 --- a/launchf.sh +++ b/launchf.sh @@ -2,7 +2,6 @@ while : do - rm ~/.telegram-cli/state ./launch.sh sleep 5 done diff --git a/libs/redis.lua b/libs/redis.lua index eca0185..1769e70 100644 --- a/libs/redis.lua +++ b/libs/redis.lua @@ -5,10 +5,15 @@ local params = { host = '127.0.0.1', port = 6379, db = 0 - --- for multiple instances on the same machine - --- change db = 0 with a number from 0 to 15 + -- For multiple instances on the same machine + -- change db = 0 with a number from 0 to 15. + -- Note that db #15 is used for tests. } +if IS_TEST_ENVIRONMENT then + params.db = 15 +end + -- Overwrite HGETALL Redis.commands.hgetall = Redis.command('hgetall', { response = function(reply, command, ...) @@ -26,6 +31,11 @@ local ok = pcall(function() end) if not ok then + if IS_TEST_ENVIRONMENT then + print("Couldn't connect to Redis, can't run tests without it.") + os.exit() + end + local fake_func = function() print('\27[31mCan\'t connect with Redis, install/configure it!\27[39m') end diff --git a/madeline.php b/madeline.php new file mode 100644 index 0000000..11fe44c --- /dev/null +++ b/madeline.php @@ -0,0 +1,53 @@ +#!/usr/bin/env php +. +*/ + +//See https://github.com/danog/MadelineProto/blob/master/lua/madeline.php + +require 'Madeline_lua_shim/vendor/autoload.php'; +$Lua = false; + +try { + $Lua = new \danog\MadelineProto\Lua('start.lua', \danog\MadelineProto\Serialization::deserialize('bot.madeline')); +} catch (\danog\MadelineProto\Exception $e) { + die($e->getMessage().PHP_EOL); +} + +function ser() +{ + global $Lua; + $Lua->MadelineProto->serialize('bot.madeline'); +} + +$Lua->registerCallback('serialize', 'ser'); + +if (!file_exists('download')) { + mkdir('download'); +} + +$Lua->madeline_update_callback(['_' => 'init']); +$offset = 0; +$lastSer = time(); +while (true) { + $updates = $Lua->MadelineProto->API->get_updates(['offset' => $offset, 'limit' => 50, 'timeout' => 0]); + foreach ($updates as $update) { + $offset = $update['update_id'] + 1; + $Lua->madeline_update_callback($update['update']); + echo PHP_EOL; + } + + $Lua->doCrons(); + if (time()-60 >= $lastSer) { + ser(); + $lastSer = time(); + } +} diff --git a/plugins/anti-flood.lua b/plugins/anti-flood.lua index 34a566e..cbe7fa6 100644 --- a/plugins/anti-flood.lua +++ b/plugins/anti-flood.lua @@ -21,219 +21,193 @@ local function kick_user(user_id, chat_id) end end, {chat=chat, user=user}) end + end + if matches[1] == 'delexcept' then + if msg.reply_id then + get_message(msg.reply_id, delexcept_reply, get_receiver(msg)) + return nil + end + end + if matches[1] == 'enable' then + redis:set(hash, 1) + return str2emoji(':information_source:')..' Anti-flood enabled on chat' + end + if matches[1] == 'disable' then + redis:del(hash) + return str2emoji(':information_source:')..' Anti-flood disabled on chat' + end + if matches[1] == 'status' then + local hash_enable = 'anti-flood:enabled:'..msg.to.id + local enabled = redis:get(hash_enable) + + if enabled then + local hash_maxmsg = 'anti-flood:maxmsg:'..msg.to.id + local hash_timeframe = 'anti-flood:timeframe:'..msg.to.id - local function addexcept_reply(extra, success, result) - local hash = 'anti-flood:exception:'..result.to.peer_id..':'..result.from.peer_id - redis:set(hash, true) - send_large_msg(extra, str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is now exempt from antiflood checks.') + -- Max number of messages per TIME_CHECK seconds + local NUM_MSG_MAX = tonumber(redis:get(hash_maxmsg) or 5) + local TIME_CHECK = tonumber(redis:get(hash_timeframe) or 5) + + return str2emoji(':information_source:')..' Anti-flood current parameters:\n'..str2emoji(":no_entry_sign:")..' Kick set at '..NUM_MSG_MAX..' messages over '..TIME_CHECK..' seconds.' + else + return str2emoji(':information_source:')..' Anti-flood is disabled on this chat.\n'..str2emoji(":point_right:")..' Enable it with !antiflood enable.' + end + end + if matches[2] then + local hash_maxmsg = 'anti-flood:maxmsg:'..msg.to.id + local hash_timeframe = 'anti-flood:timeframe:'..msg.to.id + + local parameter_mt = tonumber(matches[2]) + if matches[1] == 'maxmsg' then + if parameter_mt > 4 then + redis:set(hash_maxmsg, parameter_mt) + return str2emoji(':information_source:')..' Now the number of messages needed to trip the antiflood is '..matches[2]..'.' + end + return str2emoji(":exclamation:")..' The limit should be higher than 4.' + end + if matches[1] == 'timeframe' then + if parameter_mt > 4 then + redis:set(hash_timeframe, parameter_mt) + return 'Now the timeframe in which the antiflood will take its samples is '..matches[2].. ' seconds.' + end + return str2emoji(":exclamation:")..' The time frame should be higher than 4.' end - local function delexcept_reply(extra, success, result) - local hash = 'anti-flood:exception:'..result.to.peer_id..':'..result.from.peer_id - local reply + hash = 'anti-flood:exception:'..chat..':'..matches[2] + if matches[1] == 'addexcept' then + redis:set(hash, 1) + return str2emoji(':information_source:')..' User ID '..matches[2]..' is now exempt from antiflood checks.' + end + if matches[1] == 'delexcept' then if redis:get(hash) then redis:del(hash) - reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is now subject to antiflood checks.' + return str2emoji(':information_source:')..' User ID '..matches[2]..' is now subject to antiflood checks.' else - reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is not exempt from antiflood checks.' + return str2emoji(':information_source:')..' User ID '..matches[2]..' is not exempt from antiflood checks.' end - send_large_msg(extra, reply) end + end +end - local function run (msg, matches) - if not is_chat_msg(msg) then - return str2emoji(":exclamation:")..' Anti-flood works only on groups' - else - if is_momod(msg) then - local chat = msg.to.id - local hash = 'anti-flood:enabled:'..chat - if matches[1] == 'addexcept' then - if msg.reply_id then - get_message(msg.reply_id, addexcept_reply, get_receiver(msg)) - return nil - end - end - if matches[1] == 'delexcept' then - if msg.reply_id then - get_message(msg.reply_id, delexcept_reply, get_receiver(msg)) - return nil - end - end - if matches[1] == 'enable' then - redis:set(hash, true) - return str2emoji(':information_source:')..' Anti-flood enabled on chat' - end - if matches[1] == 'disable' then - redis:del(hash) - return str2emoji(':information_source:')..' Anti-flood disabled on chat' - end - if matches[1] == 'status' then - local hash_enable = 'anti-flood:enabled:'..msg.to.id - local enabled = redis:get(hash_enable) - - if enabled then - local hash_maxmsg = 'anti-flood:maxmsg:'..msg.to.id - local hash_timeframe = 'anti-flood:timeframe:'..msg.to.id - - -- Max number of messages per TIME_CHECK seconds - local NUM_MSG_MAX = tonumber(redis:get(hash_maxmsg) or 5) - local TIME_CHECK = tonumber(redis:get(hash_timeframe) or 5) - - return str2emoji(':information_source:')..' Anti-flood current parameters:\n'..str2emoji(":no_entry_sign:")..' Kick set at '..NUM_MSG_MAX..' messages over '..TIME_CHECK..' seconds.' - else - return str2emoji(':information_source:')..' Anti-flood is disabled on this chat.\n'..str2emoji(":point_right:")..' Enable it with !antiflood enable.' - end - end - if matches[2] then - local hash_maxmsg = 'anti-flood:maxmsg:'..msg.to.id - local hash_timeframe = 'anti-flood:timeframe:'..msg.to.id - - local parameter_mt = tonumber(matches[2]) - if matches[1] == 'maxmsg' then - if parameter_mt > 4 then - redis:set(hash_maxmsg, parameter_mt) - return str2emoji(':information_source:')..' Now the number of messages needed to trip the antiflood is '..matches[2]..'.' - end - return str2emoji(":exclamation:")..' The limit should be higher than 4.' - end - if matches[1] == 'timeframe' then - if parameter_mt > 4 then - redis:set(hash_timeframe, parameter_mt) - return 'Now the timeframe in which the antiflood will take its samples is '..matches[2].. ' seconds.' - end - return str2emoji(":exclamation:")..' The time frame should be higher than 4.' - end - - hash = 'anti-flood:exception:'..chat..':'..matches[2] - if matches[1] == 'addexcept' then - redis:set(hash, true) - return str2emoji(':information_source:')..' User ID '..matches[2]..' is now exempt from antiflood checks.' - end - if matches[1] == 'delexcept' then - if redis:get(hash) then - redis:del(hash) - return str2emoji(':information_source:')..' User ID '..matches[2]..' is now subject to antiflood checks.' - else - return str2emoji(':information_source:')..' User ID '..matches[2]..' is not exempt from antiflood checks.' - end - end - end - else - return str2emoji(":no_entry_sign:")..' You are not a moderator on this channel' - end - end - end +local function pre_process (msg) + -- Ignore service msg + if msg.service then + log(LOGLEVEL_INFO, 'Service message') + return msg + end - local function pre_process (msg) - -- Ignore service msg - if msg.service then - print('Service message') - return msg - end + local hash_enable = 'anti-flood:enabled:'..msg.to.id + local enabled = redis:get(hash_enable) + + if not enabled then return msg end + log(LOGLEVEL_INFO, 'anti-flood enabled') + + -- Check flood + if msg.from.type ~= 'user' then return msg end + local hash_maxmsg = 'anti-flood:maxmsg:'..msg.to.id + local hash_timeframe = 'anti-flood:timeframe:'..msg.to.id + + -- Max number of messages per TIME_CHECK seconds + local NUM_MSG_MAX = tonumber(redis:get(hash_maxmsg) or 5) + local TIME_CHECK = tonumber(redis:get(hash_timeframe) or 5) + + -- Increase the number of messages from the user on the chat + local hash = 'anti-flood:'..msg.from.id..':'..msg.to.id..':msg-num' + local hash_warned = 'anti-flood:'..msg.from.id..':'..msg.to.id..':msg-num' + local msgs = tonumber(redis:get(hash) or 0) + local warned = redis:get(hash_warned) or false + + msgs = msgs + 1 + redis:setex(hash, TIME_CHECK, msgs) + if msgs <= NUM_MSG_MAX then return msg end + + local receiver = get_receiver(msg) + local user = msg.from.id + local text = str2emoji(":exclamation:")..' User ' + if msg.from.username ~= nil then + text = text..' @'..msg.from.username..' ['..user..'] is flooding' + else + text = text..string.gsub(msg.from.print_name, '_', ' ')..' ['..user..'] is flooding' + end + local chat = msg.to.id + local hash_exception = 'anti-flood:exception:'..msg.to.id..':'..msg.from.id + + if not is_chat_msg(msg) then + log(LOGLEVEL_INFO, "Flood in not a chat group!") + return msg + elseif user == tostring(our_id) then + log(LOGLEVEL_INFO, 'I won\'t kick myself') + return msg + elseif is_momod(msg) then + log(LOGLEVEL_INFO, 'I won\'t kick a mod/admin/sudo!') + return msg + elseif redis:get(hash_exception) then + log(LOGLEVEL_INFO, 'User is exempt from antiflood checks!') + return msg + end - local hash_enable = 'anti-flood:enabled:'..msg.to.id - local enabled = redis:get(hash_enable) - - if enabled then - print('anti-flood enabled') - -- Check flood - if msg.from.type == 'user' then - local hash_maxmsg = 'anti-flood:maxmsg:'..msg.to.id - local hash_timeframe = 'anti-flood:timeframe:'..msg.to.id - - -- Max number of messages per TIME_CHECK seconds - local NUM_MSG_MAX = tonumber(redis:get(hash_maxmsg) or 5) - local TIME_CHECK = tonumber(redis:get(hash_timeframe) or 5) - - -- Increase the number of messages from the user on the chat - local hash = 'anti-flood:'..msg.from.id..':'..msg.to.id..':msg-num' - local hash_warned = 'anti-flood:'..msg.from.id..':'..msg.to.id..':msg-num' - local msgs = tonumber(redis:get(hash) or 0) - local warned = redis:get(hash_warned) or false - - if msgs > NUM_MSG_MAX and not warned then - local receiver = get_receiver(msg) - local user = msg.from.id - local text = str2emoji(":exclamation:")..' User ' - if msg.from.username ~= nil then - text = text..' @'..msg.from.username..' ['..user..'] is flooding' - else - text = text..string.gsub(msg.from.print_name, '_', ' ')..' ['..user..'] is flooding' - end - local chat = msg.to.id - local hash_exception = 'anti-flood:exception:'..msg.to.id..':'..msg.from.id - - if not is_chat_msg(msg) then - print("Flood in not a chat group!") - msg = nil - elseif user == tostring(our_id) then - print('I won\'t kick myself') - msg = nil - elseif is_momod(msg) then - print('I won\'t kick a mod/admin/sudo!') - msg = nil - elseif redis:get(hash_exception) then - print('User is exempt from antiflood checks!') - msg = nil - else - local real_text - if msg.media ~= nil then - if msg.media.caption ~= nil then - real_text = msg.media.caption - else - real_text = "[media with no caption]" - end - else - if msg.text ~= nil then - real_text = msg.text - end - end - - if msg.from.username ~= nil then - snoop_msg('User @'..msg.from.username..' ['..msg.from.id..'] has been found flooding.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) - else - snoop_msg('User '..string.gsub(msg.from.print_name, '_', ' ')..' ['..msg.from.id..'] has been found flooding.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) - end - send_msg(receiver, text, ok_cb, nil) - redis:set(hash_warned, true) - if not is_chan_msg(msg) then - kick_user(user, chat) - else - kick_chan_user(user, chat) - end - msg = nil - end - end - redis:setex(hash, TIME_CHECK, msgs+1) - end - end - return msg + local real_text + if msg.media ~= nil then + if msg.media.caption ~= nil then + real_text = msg.media.caption + else + real_text = "[media with no caption]" + end + else + if msg.text ~= nil then + real_text = msg.text end + end - return { - description = 'Plugin to kick flooders from group.', - usage = { - moderator = { - "!antiflood enable/disable : Enable or disable flood checking", - "!antiflood addexcept/delexcept : Add user to antiflood exceptions", - "!antiflood status : Get current antiflood parameters", - "!antiflood maxmsg : Set number of messages/time needed to trip the antiflood", - "!antiflood timeframe : Set the antiflood's sample time frame", - "#addexcept (by reply) : Add user to antiflood exceptions", - "#delexcept (by reply) : Delete user from antiflood exceptions" - }, - }, - patterns = { - '^!antiflood (enable)$', - '^!antiflood (disable)$', - '^!antiflood (addexcept) (%d+)$', - '^!antiflood (delexcept) (%d+)$', - '^!antiflood (maxmsg) (%d+)$', - '^!antiflood (timeframe) (%d+)$', - '^!antiflood (status)$', - '^#(addexcept)$', - '^#(delexcept)$' - }, - run = run, - pre_process = pre_process - } + -- Cooldown: avoid sending more than one message in TIME_CHECK seconds + if not warned then + if msg.from.username ~= nil then + snoop_msg('User @'..msg.from.username..' ['..msg.from.id..'] has been found flooding.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) + else + snoop_msg('User '..string.gsub(msg.from.print_name, '_', ' ')..' ['..msg.from.id..'] has been found flooding.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) + end + send_msg(receiver, text, ok_cb, nil) + end + redis:setex(hash_warned, TIME_CHECK, 1) + + if not is_chan_msg(msg) then + kick_user(user, chat) + else + kick_chan_user(user, chat) + end + return nil +end + +-- Public function, used in test suite +function is_antiflood_enabled(msg) + -- ugly cast to boolean + return not not redis:get('anti-flood:enabled:'..msg.to.id) +end + +return { + description = 'Plugin to kick flooders from group.', + usage = { + moderator = { + "!antiflood enable/disable : Enable or disable flood checking", + "!antiflood addexcept/delexcept : Add user to antiflood exceptions", + "!antiflood status : Get current antiflood parameters", + "!antiflood maxmsg : Set number of messages/time needed to trip the antiflood", + "!antiflood timeframe : Set the antiflood's sample time frame", + "#addexcept (by reply) : Add user to antiflood exceptions", + "#delexcept (by reply) : Delete user from antiflood exceptions" + }, + }, + patterns = { + '^!antiflood (enable)$', + '^!antiflood (disable)$', + '^!antiflood (addexcept) (%d+)$', + '^!antiflood (delexcept) (%d+)$', + '^!antiflood (maxmsg) (%d+)$', + '^!antiflood (timeframe) (%d+)$', + '^!antiflood (status)$', + '^#(addexcept)$', + '^#(delexcept)$' + }, + run = run, + pre_process = pre_process +} diff --git a/plugins/antispam.lua b/plugins/antispam.lua index 21eca3a..66944aa 100644 --- a/plugins/antispam.lua +++ b/plugins/antispam.lua @@ -37,192 +37,192 @@ local function kick_user(user_id, chat_id) end, {chat=chat, user=user}) end - local function kick_chan_user(user_id, chat_id) - local chat = 'channel#id'..chat_id - local user = 'user#id'..user_id - channel_kick(chat, user, function (data, success, result) - if not success then - local text = str2emoji(":exclamation:")..' I can\'t kick '..data.user..' but should be kicked' - snoop_msg('I am unable to kick user '..user_id..' from group '..chat_id..'.') - send_msg(data.chat, text, ok_cb, nil) - end - end, {chat=chat, user=user}) +local function kick_chan_user(user_id, chat_id) + local chat = 'channel#id'..chat_id + local user = 'user#id'..user_id + channel_kick(chat, user, function (data, success, result) + if not success then + local text = str2emoji(":exclamation:")..' I can\'t kick '..data.user..' but should be kicked' + snoop_msg('I am unable to kick user '..user_id..' from group '..chat_id..'.') + send_msg(data.chat, text, ok_cb, nil) end + end, {chat=chat, user=user}) +end - local function addexcept_reply(extra, success, result) - local hash = 'anti-spam:exception:'..result.to.peer_id..':'..result.from.peer_id - redis:set(hash, true) - send_large_msg(extra, str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is now exempt from antispam checks.') - end +local function addexcept_reply(extra, success, result) + local hash = 'anti-spam:exception:'..result.to.peer_id..':'..result.from.peer_id + redis:set(hash, true) + send_large_msg(extra, str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is now exempt from antispam checks.') +end - local function delexcept_reply(extra, success, result) - local hash = 'anti-spam:exception:'..result.to.peer_id..':'..result.from.peer_id - local reply - if redis:get(hash) then - redis:del(hash) - reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is now subject to antispam checks.' - else - reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is not exempt from antispam checks.' - end - send_large_msg(extra, reply) - end +local function delexcept_reply(extra, success, result) + local hash = 'anti-spam:exception:'..result.to.peer_id..':'..result.from.peer_id + local reply + if redis:get(hash) then + redis:del(hash) + reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is now subject to antispam checks.' + else + reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' is not exempt from antispam checks.' + end + send_large_msg(extra, reply) +end - local function run (msg, matches) - if matches[1] ~= nil then - if not is_chat_msg(msg) then - return str2emoji(":exclamation:")..' Anti-spam works only on groups' - else - if is_momod(msg) then - local chat = msg.to.id - local hash = 'anti-spam:enabled:'..chat - if matches[1] == 'addspamexcept' then - if msg.reply_id then - get_message(msg.reply_id, addexcept_reply, get_receiver(msg)) - return nil - end - end - if matches[1] == 'delspamexcept' then - if msg.reply_id then - get_message(msg.reply_id, delexcept_reply, get_receiver(msg)) - return nil - end - end - if matches[1] == 'enable' then - if matches[2] == 'fwd' then - redis:set(hash..':fwd', true) - return str2emoji(':information_source:')..' Kick on forward enabled on chat' - end - redis:set(hash, true) - return str2emoji(':information_source:')..' Anti-spam enabled on chat' - end - if matches[1] == 'disable' then - if matches[2] == 'fwd' then - redis:del(hash..':fwd') - return str2emoji(':information_source:')..' Kick on forward disabled on chat' - end +local function run (msg, matches) + if matches[1] ~= nil then + if not is_chat_msg(msg) then + return str2emoji(":exclamation:")..' Anti-spam works only on groups' + else + if is_momod(msg) then + local chat = msg.to.id + local hash = 'anti-spam:enabled:'..chat + if matches[1] == 'addspamexcept' then + if msg.reply_id then + get_message(msg.reply_id, addexcept_reply, get_receiver(msg)) + return nil + end + end + if matches[1] == 'delspamexcept' then + if msg.reply_id then + get_message(msg.reply_id, delexcept_reply, get_receiver(msg)) + return nil + end + end + if matches[1] == 'enable' then + if matches[2] == 'fwd' then + redis:set(hash..':fwd', true) + return str2emoji(':information_source:')..' Kick on forward enabled on chat' + end + redis:set(hash, true) + return str2emoji(':information_source:')..' Anti-spam enabled on chat' + end + if matches[1] == 'disable' then + if matches[2] == 'fwd' then + redis:del(hash..':fwd') + return str2emoji(':information_source:')..' Kick on forward disabled on chat' + end + redis:del(hash) + return str2emoji(':information_source:')..' Anti-spam disabled on chat' + end + if matches[2] then + hash = 'anti-spam:exception:'..chat..':'..matches[2] + if matches[1] == 'addexcept' then + redis:set(hash, true) + return str2emoji(':information_source:')..' User ID '..matches[2]..' is now exempt from antispam checks.' + end + if matches[1] == 'delexcept' then + if redis:get(hash) then redis:del(hash) - return str2emoji(':information_source:')..' Anti-spam disabled on chat' - end - if matches[2] then - hash = 'anti-spam:exception:'..chat..':'..matches[2] - if matches[1] == 'addexcept' then - redis:set(hash, true) - return str2emoji(':information_source:')..' User ID '..matches[2]..' is now exempt from antispam checks.' - end - if matches[1] == 'delexcept' then - if redis:get(hash) then - redis:del(hash) - return str2emoji(':information_source:')..' User ID '..matches[2]..' is now subject to antispam checks.' - else - return str2emoji(':information_source:')..' User ID '..matches[2]..' is not exempt from antispam checks.' - end - end + return str2emoji(':information_source:')..' User ID '..matches[2]..' is now subject to antispam checks.' + else + return str2emoji(':information_source:')..' User ID '..matches[2]..' is not exempt from antispam checks.' end - else - return str2emoji(":no_entry_sign:")..' You are not a moderator on this channel' end end + else + return str2emoji(":no_entry_sign:")..' You are not a moderator on this channel' end - - return nil end + end - local function pre_process(msg) - -- Ignore service msg - if msg.service then - print('Service message') - return msg - end + return nil +end - local hash_enable = 'anti-spam:enabled:'..msg.to.id - local enabled = redis:get(hash_enable) +local function pre_process(msg) + -- Ignore service msg + if msg.service then + print('Service message') + return msg + end - if enabled then - print('Anti-spam enabled') - local real_text + local hash_enable = 'anti-spam:enabled:'..msg.to.id + local enabled = redis:get(hash_enable) - if msg.media ~= nil then - if msg.media.caption ~= nil then - real_text = msg.media.caption - else - real_text = "[media with no caption]" - end - else - if msg.text ~= nil then - real_text = msg.text - end - end + if enabled then + print('Anti-spam enabled') + local real_text - local is_rly_spam = is_spam(real_text) + if msg.media ~= nil then + if msg.media.caption ~= nil then + real_text = msg.media.caption + else + real_text = "[media with no caption]" + end + else + if msg.text ~= nil then + real_text = msg.text + end + end - local hash_enable_fwd = hash_enable..':fwd' - local enabled_fwd = redis:get(hash_enable_fwd) - if enabled_fwd then - is_rly_spam = is_rly_spam or is_chan_fwd(msg) - end + local is_rly_spam = is_spam(real_text) - if msg.from.type == 'user' and is_rly_spam then - local receiver = get_receiver(msg) - local user = msg.from.id - local text = str2emoji(":exclamation:")..' User ' - if msg.from.username ~= nil then - text = text..' @'..msg.from.username..' ['..user..'] is spamming' - else - text = text..string.gsub(msg.from.print_name, '_', ' ')..' ['..user..'] is spamming' - end - local chat = msg.to.id - local hash_exception = 'anti-spam:exception:'..msg.to.id..':'..msg.from.id - - if not is_chat_msg(msg) then - print("Spam not in a chat group!") - elseif user == tostring(our_id) then - print('I won\'t kick myself') - elseif is_momod(msg) then - print('I won\'t kick a mod/admin/sudo!') - elseif redis:get(hash_exception) then - print('User is exempt from antispam checks!') - else - send_msg(receiver, text, ok_cb, nil) - if msg.from.username ~= nil then - snoop_msg('User @'..msg.from.username..' ['..msg.from.id..'] has been found spamming.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) - else - snoop_msg('User '..string.gsub(msg.from.print_name, '_', ' ')..' ['..msg.from.id..'] has been found spamming.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) - end - if not is_chan_msg(msg) then - kick_user(user, chat) - else - delete_msg(msg.id, ok_cb, nil) - kick_chan_user(user, chat) - end - return nil - end + local hash_enable_fwd = hash_enable..':fwd' + local enabled_fwd = redis:get(hash_enable_fwd) + if enabled_fwd then + is_rly_spam = is_rly_spam or is_chan_fwd(msg) + end + + if msg.from.type == 'user' and is_rly_spam then + local receiver = get_receiver(msg) + local user = msg.from.id + local text = str2emoji(":exclamation:")..' User ' + if msg.from.username ~= nil then + text = text..' @'..msg.from.username..' ['..user..'] is spamming' + else + text = text..string.gsub(msg.from.print_name, '_', ' ')..' ['..user..'] is spamming' + end + local chat = msg.to.id + local hash_exception = 'anti-spam:exception:'..msg.to.id..':'..msg.from.id + + if not is_chat_msg(msg) then + print("Spam not in a chat group!") + elseif user == tostring(our_id) then + print('I won\'t kick myself') + elseif is_momod(msg) then + print('I won\'t kick a mod/admin/sudo!') + elseif redis:get(hash_exception) then + print('User is exempt from antispam checks!') + else + send_msg(receiver, text, ok_cb, nil) + if msg.from.username ~= nil then + snoop_msg('User @'..msg.from.username..' ['..msg.from.id..'] has been found spamming.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) + else + snoop_msg('User '..string.gsub(msg.from.print_name, '_', ' ')..' ['..msg.from.id..'] has been found spamming.\nGroup: '..msg.to.print_name..' ['..msg.to.id..']\nText: '..real_text) end + if not is_chan_msg(msg) then + kick_user(user, chat) + else + delete_msg(msg.id, ok_cb, nil) + kick_chan_user(user, chat) + end + return nil end - - return msg end + end - return { - description = 'Plugin to kick spammers from group.', - usage = { - moderator = { - "!antispam / : Enable or disable spam checking", - "!antispam / fwd : Enable or disable kicking who forwards from channels", - "!antispam / : Add user to antispam exceptions", - "#addspamexcept (by reply) : Add user to antispam exceptions", - "#delspamexcept (by reply) : Delete user from antispam exceptions" - }, - }, - patterns = { - '^!antispam (enable) (fwd)$', - '^!antispam (enable)$', - '^!antispam (disable) (fwd)$', - '^!antispam (disable)$', - '^!antispam (addexcept) (%d+)$', - '^!antispam (delexcept) (%d+)$', - '^#(addspamexcept)$', - '^#(delspamexcept)$' - }, - run = run, - pre_process = pre_process - } \ No newline at end of file + return msg +end + +return { + description = 'Plugin to kick spammers from group.', + usage = { + moderator = { + "!antispam / : Enable or disable spam checking", + "!antispam / fwd : Enable or disable kicking who forwards from channels", + "!antispam / : Add user to antispam exceptions", + "#addspamexcept (by reply) : Add user to antispam exceptions", + "#delspamexcept (by reply) : Delete user from antispam exceptions" + }, + }, + patterns = { + '^!antispam (enable) (fwd)$', + '^!antispam (enable)$', + '^!antispam (disable) (fwd)$', + '^!antispam (disable)$', + '^!antispam (addexcept) (%d+)$', + '^!antispam (delexcept) (%d+)$', + '^#(addspamexcept)$', + '^#(delspamexcept)$' + }, + run = run, + pre_process = pre_process +} diff --git a/plugins/banhammer.lua b/plugins/banhammer.lua index fc3ef7a..3e921fa 100644 --- a/plugins/banhammer.lua +++ b/plugins/banhammer.lua @@ -182,9 +182,11 @@ end local function is_super_banned2(user_id, chat_id) local hash = 'superbanned:'..user_id local hashexc = 'superbanexc:'..chat_id + local hashuserexc = 'superbanexc:' .. chat_id .. ':' .. user_id local superbanned = redis:get(hash) local superbanexc = redis:get(hashexc) - return superbanned and not superbanexc + local superbanuserexc = redis:get(hashuserexc) + return superbanned and not superbanexc and not superbanuserexc end local function is_blocklisted(user_id, chat_id) @@ -195,19 +197,59 @@ local function is_blocklisted(user_id, chat_id) return blocklisted and is_chat_blocklist_ok end +local function check_ban_all(chat, users, is_chan) + for _, user in ipairs(users) do + local user_id = user.id + print('Checking invited user '..user_id) + local superbanned = is_super_banned2(user_id, chat) + local banned = is_banned(user_id, chat) + local blocklisted = is_blocklisted(user_id, chat) + if superbanned or banned or blocklisted then + print('User is banned!') + if not is_chan_msg(msg) then + kick_user(user_id, chat, "chat#id" .. chat) + else + kick_chan_user(user_id, chat, "channel#id" .. chat) + end + end + end +end + +local function check_unban_all(chat, users, is_chan) + local txt = "" + for _, user in ipairs(users) do + local user_id = user.id + if is_banned(user_id, chat) then + redis:del("banned:"..chat..":"..user_id) + txt = txt .. "User " .. user_print_name(user) .. " [" .. user_id .."] unbanned because added by a mod\n" + end + + if is_super_banned2(user_id, chat) then + redis:set("superbanexc:" .. chat .. ":" .. user_id) + txt = txt .. "User " .. user_print_name(user) .. " [" .. user_id .."] added to superban whitelist because added by a mod\n" + end + end + + if txt then + local chat_id = is_chan and "channe#id" .. chat or "chat#id" .. chat + send_large_msg(chat_id, txt) + end +end + local function pre_process(msg) -- SERVICE MESSAGE if msg.action and msg.action.type then local action = msg.action.type -- Check if banned user joins chat - if action == 'chat_add_user' or action == 'chat_add_user_link' then - local user_id - if msg.action.link_issuer then - user_id = msg.from.id + if action == 'chat_add_user' then + if is_mod(msg.from.id, msg.to.id) then + check_unban_all(msg.to.id, msg.action.users, is_chan_msg(msg)) else - user_id = msg.action.user.id + check_ban_all(msg.to.id, msg.action.users, is_chan_msg(msg)) end + elseif action == 'chat_add_user_link' then + local user_id = msg.from.id print('Checking invited user '..user_id) local superbanned = is_super_banned2(user_id, msg.to.id) local banned = is_banned(user_id, msg.to.id) @@ -282,7 +324,7 @@ local function resolved_username(cb_extra, success, result) local is_chan = cb_extra.is_chan local text = str2emoji(':exclamation:')..' User @'..member..' does not exist.' - if success == 1 then + if success then if result.username then member = result.username end member_id = result.peer_id if get_cmd == 'kick' then @@ -352,6 +394,15 @@ local function resolved_username(cb_extra, success, result) return nil end end + elseif get_cmd == 'superban whitelist' then + local hash = 'superbanexc:' .. chat_id .. ':' .. member_id + if redis:get(hash) then + redis:del(hash) + return send_large_msg(receiver, str2emoji(':information_source:')..' User '..user_print_name(result)..' ['..member_id..'] is no more whitelisted for the superban.') + else + redis:set(hash, true) + return send_large_msg(receiver, str2emoji(':information_source:')..' User '..user_print_name(result)..' ['..member_id..'] is now whitelisted for the superban.') + end elseif get_cmd == 'blocklist delete' then local hash = 'blocklist:' .. member_id if redis:get(hash) then @@ -479,12 +530,29 @@ local function run(msg, matches) return str2emoji(':information_source:')..' Superbans are now enforced on group '..string.gsub(msg.to.print_name, '_', ' ')..' ['..msg.to.id..'].' end end - + + if matches[2] == 'whitelist' then + local hash = 'superbanexc:' .. msg.to.id .. ':' + if string.match(matches[3], '^%d+$') then + hash = hash .. matches[3] + if redis:get(hash) then + redis:del(hash) + return str2emoji(':information_source:')..' User '..user_print_name(msg.from)..' ['..msg.from.id..'] is no more whitelisted for the superban.' + else + redis:set(hash, true) + return str2emoji(':information_source:')..' User '..user_print_name(msg.from)..' ['..msg.from.id..'] is now whitelisted for the superban.' + end + else + local member = string.gsub(matches[3], '@', '') + resolve_username(member, resolved_username, {get_cmd=get_cmd, receiver=receiver, chat_id=chat_id, member=member, is_chan=is_chan_msg(msg)}) + end + end + --Only admin can superban if not is_admin(msg) then return nil end ---Superban via reply +--Superban via reply if msg.reply_id then get_message(msg.reply_id, superban_by_reply, {is_chan=is_chan_msg(msg),receiver=get_receiver(msg)}) return nil @@ -542,7 +610,7 @@ local function run(msg, matches) resolve_username(member, resolved_username, {get_cmd=get_cmd, receiver=receiver, chat_id=chat_id, member=member, is_chan=is_chan_msg(msg)}) end end - + --Enable/Disable Blocklist in chat if matches[2] == 'enable' then local hash = 'blocklistok:'..msg.to.id @@ -635,7 +703,7 @@ local function run(msg, matches) redis:del(hash) return str2emoji(':information_source:')..' Chat '..msg.to.print_name..' ['..msg.to.id..'] removed from whitelist' end - + --Enable/Disable modonly if matches[2] == 'modonly' and matches[3] == 'enable' and is_momod(msg) then local hash = 'whitelist:modonly:'..msg.to.id @@ -671,6 +739,7 @@ return { '!ban delete : Unban user', '!ban delete : Unban user', '!superban / : Enable or disable global bans on the current group', + '!superban whitelist / : Toggle user from local superban whitelist', '!kick : Kick user from chat group by id', '!kick : Kick user from chat group by username', '#ban (by reply) : Kick user from chat and kicks it if joins chat again', @@ -711,6 +780,7 @@ return { '^!(superban) (disable)$', '^!(superban) (user) (.*)$', '^!(superban) (delete) (.*)$', + '^!(superban) (whitelist) (.*)$', '^!(blocklist) (user) (.*)$', '^!(blocklist) (delete) (.*)$', '^!(blocklist) (enable)$', diff --git a/plugins/groupmanager.lua b/plugins/groupmanager.lua index 9c85b08..0376b23 100644 --- a/plugins/groupmanager.lua +++ b/plugins/groupmanager.lua @@ -213,6 +213,20 @@ do return text end + local function ban_all(chat, users, is_chan, bots_only) + if is_chan then + for _, user in ipairs(users) do + if not bots_only or bots_only and is_bot(user) then + channel_kick(chat, "user#id" .. user.id, ok_cb, nil) + end + end + else + if not bots_only or bots_only and is_bot(user) then + chat_del_user(chat, "user#id" .. user.id, ok_cb, nil) + end + end + end + function run(msg, matches) --vardump(msg) if matches[1] == 'creategroup' and matches[2] then @@ -334,23 +348,27 @@ do if not is_chan_msg(msg) then chat = 'chat#id'..msg.to.id if group_member_lock == 'yes' then - chat_del_user(chat, user, ok_cb, true) + --chat_del_user(chat, user, ok_cb, true) + ban_all(chat, msg.action.users, false, false) end if group_bots_lock == 'yes' then - if is_bot(userobj) then + --[[if is_bot(userobj) then chat_del_user(chat, user, ok_cb, true) - end + end--]] + ban_all(chat, msg.action.users, false, true) end return nil else chat = 'channel#id'..msg.to.id if group_member_lock == 'yes' then - channel_kick(chat, user, ok_cb, true) + --channel_kick(chat, user, ok_cb, true) + ban_all(chat, msg.action.users, true, false) end if group_bots_lock == 'yes' then - if is_bot(userobj) then + --[[if is_bot(userobj) then channel_kick(chat, user, ok_cb, true) - end + end--]] + ban_all(chat, msg.action.users, true, true) end return nil end diff --git a/plugins/httpcat.lua b/plugins/httpcat.lua index f327856..ba038c4 100644 --- a/plugins/httpcat.lua +++ b/plugins/httpcat.lua @@ -23,4 +23,4 @@ return{ usage = { "!httpcat : gives an image from http.cat" } -} \ No newline at end of file +} diff --git a/plugins/media.lua b/plugins/media.lua index 9428382..3719c82 100644 --- a/plugins/media.lua +++ b/plugins/media.lua @@ -5,7 +5,7 @@ do local url = matches[1] local ext = matches[2] - local file = download_to_file(url) + local file = my_download_to_file(url) local cb_extra = {file_path=file} local mime_type = mimetype.get_content_type_no_sub(ext) diff --git a/plugins/moderation.lua b/plugins/moderation.lua index 8f46f50..af8f7e8 100644 --- a/plugins/moderation.lua +++ b/plugins/moderation.lua @@ -216,7 +216,7 @@ do return send_large_msg(receiver, 'Blocklist admin '..member_username..' has been demoted.') end - + local function syncmods(cb_extra, success, result) local receiver = cb_extra @@ -227,14 +227,14 @@ do return send_large_msg(receiver, 'Group is not added.') end data[group]['moderators'] = {} - + for _,cur_user in pairs(result) do if cur_user.peer_id ~= our_id then data[group]['moderators'][tostring(cur_user.peer_id)] = cur_user.username end end save_data(_config.moderation.data, data) - + send_large_msg(receiver, "Moderators synced successfully.") end @@ -489,8 +489,12 @@ do end return blocklistadm_list(msg) end - if matches[1] == 'chat_add_user' and msg.action.user.id == our_id then - return automodadd(msg) + if matches[1] == 'chat_add_user' then + for _, user in ipairs(msg.action.users) do + if user.id == our_id then + return automodadd(msg) + end + end end if matches[1] == 'chat_created' and msg.from.id == 0 then return automodadd(msg) diff --git a/plugins/sudo.lua b/plugins/sudo.lua index c07cacd..986b3e3 100644 --- a/plugins/sudo.lua +++ b/plugins/sudo.lua @@ -1,3 +1,7 @@ +local function reply(cb_extra, success, result) + send_large_msg(cb_extra, serpent.block(result, {comment=false})) +end + function run_sh(msg) name = get_name(msg) text = '' @@ -81,12 +85,28 @@ function run(msg, matches) get_dialog_list(on_getting_dialogs, get_receiver(msg)) return end + + if matches[1] == "reload" then + if matches[2] == "config" then + _config = load_config() + elseif matches[2] == "bot" then + loadBot() + return "Bot reloaded" + end + end + + if matches[1] == "debug" then + if msg.reply_id then + get_message(msg.reply_id, reply, receiver) + end + return serpent.block(msg, {comment = false}) + end end return { description = "shows cpuinfo", usage = "!cpu", hide = true, - patterns = {"^!cpu", "^!sh","^Get dialogs$"}, + patterns = {"^!cpu", "^!sh", "^Get dialogs$", "^!(reload) (config)$", "^!(reload) (bot)$", "^!(debug)$"}, run = run } diff --git a/plugins/tweet.lua b/plugins/tweet.lua index 2622106..fb10229 100644 --- a/plugins/tweet.lua +++ b/plugins/tweet.lua @@ -36,7 +36,7 @@ local function send_generics_from_url_callback(cb_extra, success, result) -- Take the head and remove from urls table local head = table.remove(urls, 1) - local file_path = download_to_file(head, false) + local file_path = my_download_to_file(head, false) local cb_extra = { receiver = receiver, urls = urls, diff --git a/plugins/warn.lua b/plugins/warn.lua new file mode 100644 index 0000000..c067976 --- /dev/null +++ b/plugins/warn.lua @@ -0,0 +1,330 @@ +local MAX_WARN = 3 +local ACTION_WARN = 'ban' + +local function ban_user(chat_id, user_id) + local chat = 'chat#id'..chat_id + local user = 'user#id'..user_id + local hash = 'banned:'..chat_id..':'..user_id + redis:set(hash, true) + chat_del_user(chat, user, function (data, success, result) + if not success then + local text = str2emoji(":exclamation:")..' I can\'t kick '..data.user..' but should be kicked' + snoop_msg('I am unable to kick user '..user_id..' from group '..chat_id..'.') + send_msg(data.chat, text, ok_cb, nil) + end + end, {chat=chat, user=user}) +end + +local function kick_user(chat_id, user_id) + local chat = 'chat#id'..chat_id + local user = 'user#id'..user_id + print(chat, user) + chat_del_user(chat, user, function (data, success, result) + if not success then + local text = str2emoji(":exclamation:")..' I can\'t kick '..data.user..' but should be kicked' + snoop_msg('I am unable to kick user '..user_id..' from group '..chat_id..'.') + send_msg(data.chat, text, ok_cb, nil) + end + end, {chat=chat, user=user}) +end + +local function ban_chan_user(chat_id, user_id) + local chat = 'channel#id'..chat_id + local user = 'user#id'..user_id + local hash = 'banned:'..chat_id..':'..user_id + redis:set(hash, true) + channel_kick(chat, user, function (data, success, result) + if not success then + local text = str2emoji(":exclamation:")..' I can\'t ban '..data.user..' but should be banned' + snoop_msg('I am unable to ban user '..user_id..' from supergroup '..chat_id..'.') + send_msg(data.chat, text, ok_cb, nil) + end + end, {chat=chat, user=user}) +end + +local function kick_chan_user(chat_id, user_id) + local chat = 'channel#id'..chat_id + local user = 'user#id'..user_id + print(chat, user) + channel_kick(chat, user, function (data, success, result) + if not success then + local text = str2emoji(":exclamation:")..' I can\'t kick '..data.user..' but should be kicked' + snoop_msg('I am unable to kick user '..user_id..' from supergroup '..chat_id..'.') + send_msg(data.chat, text, ok_cb, nil) + end + end, {chat=chat, user=user}) + channel_unblock(chat, user, function (data, success, result) + if not success then + local text = str2emoji(":exclamation:")..' I can\'t unban '..data.user + snoop_msg('I am unable to unban user '..user_id..' from supergroup '..chat_id..'.') + send_msg(data.chat, text, ok_cb, nil) + end + end, {chat=chat, user=user}) +end + +local function warn_reply(extra, success, result) + if not success then return end + if is_mod(result.from.peer_id, result.to.peer_id) then + send_large_msg(extra, str2emoji(':no_entry_sign:')..' I won\'t warn myself, admins or mods.') + return + end + local hash = 'warn:'..result.to.peer_id..':'..result.from.peer_id + local hashmax = 'maxwarn:'..result.to.peer_id + local counter = tonumber(redis:get(hash) or 0)+1 + local locmax_warn = tonumber(redis:get(hashmax)) or MAX_WARN + local action = redis:get('actionwarn:'..result.to.peer_id) or ACTION_WARN + redis:set(hash, counter) + if counter >= tonumber(locmax_warn) then + redis:del(hash) + send_large_msg(extra, str2emoji(':exclamation:')..' User ID '..result.from.peer_id..' has been warned '..counter..' times. Banned.') + if is_chan_msg(result) then + if action == 'ban' then + return ban_chan_user(result.to.peer_id, result.from.peer_id) + elseif action == 'kick' then + return kick_chan_user(result.to.peer_id, result.from.peer_id) + end + else + if action == 'ban' then + return ban_user(result.to.peer_id, result.from.peer_id) + elseif action == 'kick' then + return kick_user(result.to.peer_id, result.from.peer_id) + end + end + end + send_large_msg(extra, str2emoji(':information_source:')..' User ID '..result.from.peer_id..' now has '..counter..' warn(s).') +end + +local function rstwarn_reply(extra, success, result) + if not success then return end + local hash = 'warn:'..result.to.peer_id..':'..result.from.peer_id + local reply + if redis:get(hash) then + redis:del(hash) + reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' has no more warns.' + else + reply = str2emoji(':information_source:')..' User ID '..result.from.peer_id..' had no warns.' + end + send_large_msg(extra, reply) +end + +local function status_reply(extra, success, result) + if not success then return end + local hash = 'warn:'..result.to.peer_id..':'..result.from.peer_id + local counter = tonumber(redis:get(hash) or 0) + send_large_msg(extra, str2emoji(':information_source:')..' User ID '..result.from.peer_id..' has currently '..counter..' warn(s).') +end + +local function run (msg, matches) + if not matches[1] then + return + end + if not is_chat_msg(msg) then + return str2emoji(":exclamation:")..' Warn works only on groups' + end + + --works only for mods + if is_momod(msg) then + local chat = msg.to.id + + if matches[2] then + --Set maximum warns + if matches[1] == 'setwarn' then + if tonumber(matches[2]) > 1 then + local hashmax = 'maxwarn:'..chat + redis:set(hashmax, matches[2]) + return str2emoji(':information_source:')..' Maximum warns now set at '..matches[2]..' warn(s).' + else + return str2emoji(':no_entry_sign:')..' Instead of setting warns at 1, you should use #ban or !ban user.' + end + end + + --Set the penalty + if matches[1] == 'setwarnaction' then + local hash = 'actionwarn:' .. chat + if matches[2] == 'kick' then + redis:set(hash, 'kick') + return str2emoji(':information_source:') .. ' The penalty is now kick' + elseif matches[2] == 'ban' then + redis:set(hash, 'ban') + return str2emoji(':information_source:') .. ' The penalty is now ban' + end + end + + --Warn status + if matches[2] == 'info' then + local u = false + --Warn status user + if matches[3] then + if not matches[3]:match("^%d+$") then + u = matches[3] or "" + resolve_username(matches[3], function(extra, success, result) + if not success then + matches[3] = false + else + if result.peer_type == "user" then + matches[3] = result.peer_id + else + matches[3] = false + end + end + end, false) + if not matches[3] then + return str2emoji(':exclamation:') .. " User " .. u .. "not found." + end + end + local hash = 'warn:'..chat..':'..matches[3] + local counter = redis:get(hash) or 0 + if u then + return str2emoji(':information_source:')..' User '..u..' ['..matches[3]..'] has currently '..counter..' warn(s).' + else + return str2emoji(':information_source:')..' User ID '..matches[3]..' has currently '..counter..' warn(s).' + end + end + + --Warn status chat + local hashmax = 'maxwarn:'..chat + local locmax_warn = redis:get(hashmax) or MAX_WARN + return str2emoji(':information_source:')..' Warn current parameters:\n'..str2emoji(":no_entry_sign:")..' Ban set at '..locmax_warn..' warn(s).' + end + + --Get the id from a username + local u = false + if not matches[2]:match("^%d+$") then + u = matches[2] or "" + resolve_username(matches[2], function(extra, success, result) + if not success then + matches[2] = false + else + if result.peer_type == "user" then + matches[2] = result.peer_id + else + matches[2] = false + end + end + end, false) + if not matches[2] then + return str2emoji(':exclamation:') .. " User " .. u .. "not found." + end + end + + --Warining script + local hash = 'warn:'..chat..':'..matches[2] + if matches[1] == 'warn' then + if is_mod(matches[2], msg.to.id) then + return str2emoji(':no_entry_sign:')..' I won\'t warn myself, admins or mods.' + end + local hashmax = 'maxwarn:'..chat + local counter = math.ceil(tonumber(redis:get(hash)) or 0)+1 + local locmax_warn = redis:get(hashmax) or MAX_WARN + local action = redis:get('actionwarn:'..chat) or ACTION_WARN + redis:set(hash, counter) + if counter >= tonumber(locmax_warn) then + redis:del(hash) --Reset waring for the user + if is_chan_msg(msg) then + if action == 'ban' then + ban_chan_user(msg.to.id, matches[2]) + elseif action == 'kick' then + kick_chan_user(msg.to.id, matches[2]) + end + else + if action == 'ban' then + ban_user(msg.to.id, matches[2]) + elseif action == 'kick' then + kick_user(msg.to.id, matches[2]) + end + end + if u then + return str2emoji(':exclamation:')..' User '..u..' ['..matches[2]..'] has been warned '..counter..' times. Banned.' + else + return str2emoji(':exclamation:')..' User ID '..matches[2]..' has been warned '..counter..' times. Banned.' + end + end + if u then + return str2emoji(':information_source:')..' User ' .. u .. ' ['..matches[2]..'] now has '..counter..' warn(s).' + else + return str2emoji(':information_source:')..' User ID '..matches[2]..' now has '..counter..' warn(s).' + end + end + + --Reset warning + if matches[1] == 'resetwarn' then + local reply + if redis:get(hash) then + redis:del(hash) + if u then + reply = str2emoji(':information_source:')..' User '..u..' ['..matches[2]..'] has no more warns.' + else + reply = str2emoji(':information_source:')..' User ID '..matches[2]..' has no more warns.' + end + else + if u then + + else + reply = str2emoji(':information_source:')..' User '..u..' ['..matches[2]..'] had no warns.' + end + end + return reply + end + end + + --Reply commands + if matches[1] == 'warn' then + if msg.reply_id then + get_message(msg.reply_id, warn_reply, get_receiver(msg)) + return nil + end + end + + if matches[1] == 'resetwarn' then + if msg.reply_id then + get_message(msg.reply_id, rstwarn_reply, get_receiver(msg)) + return nil + end + end + + if matches[1] == 'warninfo' then + if msg.reply_id then + get_message(msg.reply_id, status_reply, get_receiver(msg)) + return nil + end + end + else + return str2emoji(":no_entry_sign:")..' You are not a moderator on this channel' + end + + return nil +end + +return { + description = 'Plugin to keep a warning list on the group.', + usage = { + moderator = { + "!setwarn : Set how many warns are needed to trip the automatic ban", + "!warn : Adds a warn point to user_id", + "!warn info : Returns the plugin's settings", + "!warn info : Returns how many warns user_id got", + "!resetwarn : Reset user_id's warn points to 0", + "!setwarnaction