diff --git a/README.md b/README.md index 80b3b8a..e9f484e 100755 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ While tg is running, you may start/reload otouto with administration.lua enabled | /setmotd | Sets a group's "Message of the Day". | 3 | Y | | /setlink | Sets a group's link. | 3 | Y | | /flag | Returns a list of available flags and their settings, or toggles a flag. | 3 | Y | +| /antiflood | Configures antiflood (flag 5) settings. | 3 | Y | | /mod | Promotes a user to a moderator. | 3 | Y | | /demod | Demotes a moderator to a user. | 3 | Y | | /gov | Promotes a user to a governor. | 4 | Y | @@ -141,6 +142,23 @@ Obviously, each greater rank inherits the privileges of the lower, positive rank | 2 | antisquig | Automatically removes users for posting Arabic script or RTL characters. | | 3 | antisquig Strict | Automatically removes users whose names contain Arabic script or RTL characters. | | 4 | antibot | Prevents bots from being added by non-moderators. | +| 5 | antiflood | Prevents flooding by rate-limiting messages per user. | + +#### antiflood +antiflood (flag 5) provides a system of automatic flood protection by removing users who post too much. It is entirely configurable by a group's governor, an administrator, or the bot owner. For each message to a particular group, a user is awarded a certain number of "points". The number of points is different for each message type. When the user reaches 100 points, he is removed. Points are reset each minute. In this way, if a user posts twenty messages within one minute, he is removed. + +**Default antiflood values:** + +| Type | Points | +| text | 5 | +| contact | 5 | +| audio | 5 | +| voice | 5 | +| photo | 10 | +| document | 10 | +| location | 10 | +| video | 10 | +| sticker | 20 | * * * diff --git a/plugins/administration.lua b/plugins/administration.lua index 1832cc5..fe5a401 100644 --- a/plugins/administration.lua +++ b/plugins/administration.lua @@ -30,6 +30,11 @@ if not database.administration then } end +admin_temp = { + help = {}, + flood = {} +} + -- Migration code: Remove this in v1.7. -- Group data is now stored in a "groups" array. if not database.administration.groups then @@ -82,7 +87,7 @@ local flags = { kicked = 'You were automatically kicked from GROUPNAME for posting Arabic script and/or RTL characters.' }, [3] = { - name = 'antisquig Strict', + name = 'antisquig++', desc = 'Automatically removes users whose names contain Arabic script or RTL characters.', short = 'This group does not allow users whose names contain Arabic script or RTL characters.', enabled = 'Users whose names contain Arabic script and/or RTL characters will now be removed automatically.', @@ -95,9 +100,29 @@ local flags = { short = 'This group does not allow users to add bots.', enabled = 'Non-moderators will no longer be able to add bots.', disabled = 'Non-moderators will now be able to add bots.' + }, + [5] = { + name = 'antiflood', + desc = 'Prevents flooding by rate-limiting messages per user.', + short = 'This group automatically removes users who flood.', + enabled = 'Users will now be removed automatically for excessive messages. Use /antiflood to configure limits.', + disabled = 'Users will no longer be removed automatically for excessive messages.', + kicked = 'You were automatically kicked from GROUPNAME for flooding.' } } +local antiflood = { + text = 5, + voice = 5, + audio = 5, + contact = 5, + photo = 10, + video = 10, + location = 10, + document = 10, + sticker = 20 +} + local ranks = { [0] = 'Banned', [1] = 'Users', @@ -182,7 +207,7 @@ local get_desc = function(chat_id) local flaglist = '' for i = 1, #flags do if group.flags[i] then - output = output .. '• ' .. flags[i].short .. '\n' + flaglist = flaglist .. '• ' .. flags[i].short .. '\n' end end if flaglist ~= '' then @@ -260,6 +285,45 @@ local commands = { end end + -- antiflood + if group.flags[5] == true then + if not group.antiflood then + group.antiflood = JSON.decode(JSON.encode(antiflood)) + end + if not admin_temp.flood[msg.chat.id_str] then + admin_temp.flood[msg.chat.id_str] = {} + end + if not admin_temp.flood[msg.chat.id_str][msg.from.id_str] then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = 0 + end + if msg.sticker then -- Thanks Brazil for discarding switches. + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.sticker + elseif msg.photo then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.photo + elseif msg.document then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.document + elseif msg.audio then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.audio + elseif msg.contact then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.contact + elseif msg.video then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.video + elseif msg.location then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.location + elseif msg.voice then + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.voice + else + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.text + end + if admin_temp.flood[msg.chat.id_str][msg.from.id_str] > 99 then + drua.kick_user(msg.chat.id, msg.from.id) + local output = flags[5].kicked:gsub('GROUPNAME', msg.chat.title) + sendMessage(msg.from.id, output) + admin_temp.flood[msg.chat.id_str][msg.from.id_str] = nil + return + end + end + end if msg.new_chat_participant then @@ -350,7 +414,8 @@ local commands = { { -- groups triggers = { - '^/groups[@'..bot.username..']*$' + '^/groups$', + '^/groups@'..bot.username }, command = 'groups', @@ -380,7 +445,8 @@ local commands = { { -- ahelp triggers = { - '^/ahelp[@'..bot.username..']*$' + '^/ahelp$', + '^/ahelp@'..bot.username }, command = 'ahelp', @@ -391,7 +457,7 @@ local commands = { local rank = get_rank(msg.from.id, msg.chat.id) local output = '*Commands for ' .. ranks[rank] .. ':*\n' for i = 1, rank do - for ind, val in ipairs(database.administration.help[i]) do + for ind, val in ipairs(admin_temp.help[i]) do output = output .. '• /' .. val .. '\n' end end @@ -405,9 +471,10 @@ local commands = { { -- alist triggers = { - '^/alist[@'..bot.username..']*$', - '^/ops[@'..bot.username..']*$', - '^/oplist[@'..bot.username..']*$' + '^/ops$', + '^/ops@'..bot.username, + '^/oplist$', + '^/oplist@'..bot.username }, command = 'ops', @@ -440,8 +507,8 @@ local commands = { { -- desc triggers = { - '^/desc[@'..bot.username..']*$', - '^/description[@'..bot.username..']*$' + '^/desc[ription]*$', + '^/desc[ription]*@'..bot.username }, command = 'description', @@ -460,7 +527,8 @@ local commands = { { -- rules triggers = { - '^/rules[@'..bot.username..']*$' + '^/rules$', + '^/rules@'..bot.username }, command = 'rules', @@ -481,7 +549,8 @@ local commands = { { -- motd triggers = { - '^/motd[@'..bot.username..']*' + '^/motd$', + '^/motd@'..bot.username }, command = 'motd', @@ -499,7 +568,8 @@ local commands = { { -- link triggers = { - '^/link[@'..bot.username..']*' + '^/link$', + '^/link@'..bot.username }, command = 'link', @@ -598,7 +668,7 @@ local commands = { '^/changerule@' .. bot.username }, - command = 'changerule ', + command = 'changerule ', privilege = 3, interior = true, @@ -650,16 +720,16 @@ local commands = { '^/setrules[@'..bot.username..']*' }, - command = 'setrules\\n\\n\\[rule2]\\n...', + command = 'setrules ', privilege = 3, interior = true, action = function(msg, group) - local input = msg.text:match('^/setrules[@'..bot.username..']* (.+)') + local input = msg.text:match('^/setrules[@'..bot.username..']*(.+)') if not input then - sendMessage(msg.chat.id, '```\n/setrules\n\n[rule2]\n...\n```', true, msg.message_id, true) + sendMessage(msg.chat.id, '```\n/setrules [rule]\n\n[rule]\n...\n```', true, msg.message_id, true) return - elseif input == '--' or input == '—' then + elseif input == ' --' or input == ' —' then group.rules = {} sendReply(msg, 'The rules have been cleared.') return @@ -689,8 +759,12 @@ local commands = { action = function(msg, group) local input = msg.text:input() if not input then - sendReply(msg, 'Please specify the new message of the day.') - return + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, 'Please specify the new message of the day.') + return + end elseif input == '--' or input == '—' then group.motd = nil sendReply(msg, 'The MOTD has been cleared.') @@ -764,6 +838,44 @@ local commands = { end }, + { -- antiflood + triggers = { + '^/antiflood', + '^/antiflood@'..bot.username + }, + + command = 'antiflood ', + privilege = 3, + interior = true, + + action = function(msg, group) + if not group.flags[5] then + sendMessage(msg.chat.id, 'antiflood is not enabled. Use `/flag 5` to enable it.', true, nil, true) + return + end + if not group.antiflood then + group.antiflood = JSON.decode(JSON.encode(antiflood)) + end + local input = msg.text_lower:input() + local output + if input then + local key, val = input:match('(%a+) (%d+)') + if not group.antiflood[key] or not tonumber(val) then + output = 'Not a valid message type or number.' + else + group.antiflood[key] = val + output = 'A *' .. key .. '* message is now worth *' .. val .. '* points.' + end + else + output = 'usage: `/antiflood `\nexample: `/antiflood text 5`\nUse this command to configure the point values for each message type. When a user reaches 100 points, he is kicked. The points are reset each minute. The current values are:\n' + for k,v in pairs(group.antiflood) do + output = output .. '*'..k..':* `'..v..'`\n' + end + end + sendMessage(msg.chat.id, output, true, msg.message_id, true) + end + }, + { -- mod triggers = { '^/mod', @@ -792,7 +904,7 @@ local commands = { return end if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 1) + drua.channel_set_admin(msg.chat.id, target.id, 2) end group.mods[target.id_str] = true sendReply(msg, target.name .. ' is now a moderator.') @@ -827,7 +939,7 @@ local commands = { group.mods[target.id_str] = nil end if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 1) + drua.channel_set_admin(msg.chat.id, target.id, 2) end group.govs[target.id_str] = true sendReply(msg, target.name .. ' is now a governor.') @@ -993,11 +1105,11 @@ end database.administration.help = {} for i,v in ipairs(ranks) do - database.administration.help[i] = {} + admin_temp.help[i] = {} end for i,v in ipairs(commands) do if v.command then - table.insert(database.administration.help[v.privilege], v.command) + table.insert(admin_temp.help[v.privilege], v.command) end end @@ -1021,12 +1133,17 @@ local action = function(msg) return true end +local cron = function() + admin_temp.flood = {} +end + local command = 'groups' local doc = '`Returns a list of administrated groups.\nUse /ahelp for more administrative commands.`' return { action = action, triggers = triggers, + cron = cron, doc = doc, command = command } diff --git a/plugins/bible.lua b/plugins/bible.lua index 297e766..03aca67 100755 --- a/plugins/bible.lua +++ b/plugins/bible.lua @@ -29,12 +29,12 @@ local action = function(msg) local message, res = HTTP.request(url) - if message:len() == 0 then + if not message or res ~= 200 or message:len() == 0 then url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) message, res = HTTP.request(url) end - if res ~= 200 then + if not message or res ~= 200 or message:len() == 0 then message = config.errors.results end diff --git a/plugins/hearthstone.lua b/plugins/hearthstone.lua index 8d18b9a..8370702 100755 --- a/plugins/hearthstone.lua +++ b/plugins/hearthstone.lua @@ -4,7 +4,9 @@ if not database.hearthstone or os.time() > database.hearthstone.expiration then print('Downloading Hearthstone database...') - database.hearthstone = {} + database.hearthstone = { + expiration = os.time() + 600000 + } local jstr, res = HTTPS.request('http://hearthstonejson.com/json/AllSets.json') if res ~= 200 then @@ -20,8 +22,6 @@ if not database.hearthstone or os.time() > database.hearthstone.expiration then end end - database.hearthstone.expiration = os.time() + 600000 - print('Download complete! It will be stored for a week.') end diff --git a/utilities.lua b/utilities.lua index 1151b8a..c7433cb 100755 --- a/utilities.lua +++ b/utilities.lua @@ -19,6 +19,16 @@ get_word = function(s, i) return t[i] or false +end + + -- Like get_word(), but better. + -- Returns the actual index. +function string:index() + local t = {} + for w in s:gmatch('%g+') do + table.insert(t, w) + end + return t end -- Returns the string after the first space.