diff --git a/README.md b/README.md index a7f3ed4..bb04575 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Brawlbot v2 ist freie Software; du darfst in modifizieren und weiterverbreiten, |:----------------------------------------------|:------------------------------| | [Setup](#setup) | [Plugins](#plugins) | | [Bot steuern](#bot-steuern) | [Bindings](#bindings) | +| | [Datenbank](#datenbank) * * * # Für User @@ -147,4 +148,38 @@ and using `sendPhoto` with a file ID would look like this: bindings.sendPhoto(self, { chat_id = 987654321, photo = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789' } ) ``` -Upon success, bindings will return the deserialized result from the API. Upon failure, it will return false and the result. In the case of a connection error, it will return two false values. If an invalid method name is given, bindings will throw an exception. This is to mimic the behavior of more conventional bindings as well as to prevent "silent errors". \ No newline at end of file +Upon success, bindings will return the deserialized result from the API. Upon failure, it will return false and the result. In the case of a connection error, it will return two false values. If an invalid method name is given, bindings will throw an exception. This is to mimic the behavior of more conventional bindings as well as to prevent "silent errors". + +* * * + +## Datenbank +Brawlbot benutzt eine interne Datenbank, wie Otouto sie benutzt und Redis. Die "Datenbank" ist eine Tabelle, auf die über die Variable `database` zugegriffen werden kann (normalerweise `self.database`) und die als JSON-encodierte Plaintext-Datei jede Stunde gespeichert wird oder wenn der Bot gestoppt wird (über `/halt`). + +Das ist die Datenbank-Struktur: + +``` +{ + users = { + ["55994550"] = { + id = 55994550, + first_name = "Drew", + username = "topkecleon" + } + }, + userdata = { + ["55994550"] = { + nickname = "Best coder ever", + lastfm = "topkecleon" + } + }, + version = "2.1" +} +``` + +`database.users` speichert User-Informationen, wie Usernamen, IDs, etc., wenn der Bot den User sieht. Jeder Tabellen-Key ist die User-ID als String. + +`database.userdata` speichert Daten von verschiedenen Plugins, hierzu wird aber für Brawlbot-Plugins Redis verwendet. + +`database.version` speichert die Bot-Version. + +* * * \ No newline at end of file diff --git a/config.lua b/config.lua new file mode 100755 index 0000000..4cd82c2 --- /dev/null +++ b/config.lua @@ -0,0 +1,35 @@ +return { + + -- Your authorization token from the botfather. + bot_api_key = '235106290:AAGKZwTJBE1J6MvorodwG1E_0Y86DaIRa-o', + -- Your Telegram ID. + admin = 36623702, + -- Two-letter language code. + lang = 'de', + -- The channel, group, or user to send error reports to. + -- If this is not set, errors will be printed to the console. + log_chat = nil, + -- The port used to communicate with tg for administration.lua. + -- If you change this, make sure you also modify launch-tg.sh. + cli_port = 4567, + -- The block of text returned by /start. + about_text = [[ +*Willkommen beim Brawlbot!* +Sende /hilfe, um zu starten + ]], + -- The symbol that starts a command. Usually noted as '/' in documentation. + cmd_pat = '/', + + errors = { -- Generic error messages used in various plugins. + generic = 'Ein unbekannter Fehler ist aufgetreten, bitte [melde diesen Bug](https://github.com/Brawl345/Brawlbot-v2/issues).', + connection = 'Verbindungsfehler.', + quotaexceeded = 'API-Quota aufgebraucht.', + results = 'Keine Ergebnisse gefunden.', + sudo = 'Du bist kein Superuser. Dieser Vorfall wird gemeldet!', + argument = 'Invalides Argument.', + syntax = 'Invalide Syntax.', + chatter_connection = 'Ich möchte gerade nicht reden', + chatter_response = 'Ich weiß nicht, was ich darauf antworten soll.' + } + +} diff --git a/drua-tg.lua b/drua-tg.lua index da4055b..c0233b0 100644 --- a/drua-tg.lua +++ b/drua-tg.lua @@ -151,4 +151,12 @@ drua.channel_set_about = function(chat, text) return drua.send(command) end +drua.block = function(user) + return drua.send('block_user user#' .. user) +end + +drua.unblock = function(user) + return drua.send('unblock_user user#' .. user) +end + return drua diff --git a/otouto/bot.lua b/otouto/bot.lua index c5f673b..095b730 100644 --- a/otouto/bot.lua +++ b/otouto/bot.lua @@ -5,7 +5,7 @@ local bindings -- Load Telegram bindings. local utilities -- Load miscellaneous and cross-plugin functions. local redis = (loadfile "./otouto/redis.lua")() -bot.version = '2.0' +bot.version = '2.1' function bot:init(config) -- The function run when the bot is started or reloaded. @@ -31,7 +31,29 @@ function bot:init(config) -- The function run when the bot is started or reloade if not self.database then self.database = utilities.load_data(self.info.username..'.db') end + + -- MIGRATION CODE 2.0 -> 2.1 + if self.database.users and self.database.version ~= '2.1' then + self.database.userdata = {} + for id, user in pairs(self.database.users) do + self.database.userdata[id] = {} + self.database.userdata[id].nickname = user.nickname + self.database.userdata[id].lastfm = user.lastfm + user.nickname = nil + user.lastfm = nil + user.id_str = nil + user.name = nil + end + end + -- END MIGRATION CODE + -- Table to cache user info (usernames, IDs, etc). + self.database.users = self.database.users or {} + -- Table to store userdata (nicknames, lastfm usernames, etc). + self.database.userdata = self.database.userdata or {} + -- Save the bot's version in the database to make migration simpler. + self.database.version = bot.version + -- Add updated bot info to the user info cache. self.database.users = self.database.users or {} -- Table to cache userdata. self.database.users[tostring(self.info.id)] = self.info @@ -49,6 +71,7 @@ function bot:init(config) -- The function run when the bot is started or reloade self.last_update = self.last_update or 0 -- Set loop variables: Update offset, self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job, + self.last_database_save = self.last_database_save or os.date('%H') -- the time of the last database save, self.is_started = true -- and whether or not the bot should be running. end @@ -57,18 +80,24 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec -- remove comment to enable debugging -- vardump(msg) -- Cache user info for those involved. - utilities.create_user_entry(self, msg.from) - if msg.forward_from and msg.forward_from.id ~= msg.from.id then - utilities.create_user_entry(self, msg.forward_from) - elseif msg.reply_to_message and msg.reply_to_message.from.id ~= msg.from.id then - utilities.create_user_entry(self, msg.reply_to_message.from) - end - + if msg.date < os.time() - 5 then return end -- Do not process old messages. - msg = utilities.enrich_message(msg) - + -- Cache user info for those involved. + self.database.users[tostring(msg.from.id)] = msg.from + if msg.reply_to_message then + self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from + elseif msg.forward_from then + self.database.users[tostring(msg.forward_from.id)] = msg.forward_from + elseif msg.new_chat_member then + self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member + elseif msg.left_chat_member then + self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member + end + msg = utilities.enrich_message(msg) + + -- Support deep linking. if msg.text:match('^'..config.cmd_pat..'start .+') then msg.text = config.cmd_pat .. utilities.input(msg.text) msg.text_lower = msg.text:lower() @@ -104,6 +133,7 @@ function bot:on_callback_receive(callback, msg, config) -- whenever a new callba print('Callback Query "'..param..'" für Plugin "'..called_plugin..'" ausgelöst von '..callback.from.first_name..' ('..callback.from.id..')') msg = utilities.enrich_message(msg) + for _, plugin in ipairs(self.plugins) do if plugin.name == called_plugin then if is_plugin_disabled_on_chat(plugin.name, msg) then return end @@ -143,7 +173,10 @@ function bot:run(config) end end end - + if self.last_database_save ~= os.date('%H') then + utilities.save_data(self.info.username..'.db', self.database) -- Save the database. + self.last_database_save = os.date('%H') + end end -- Save the database before exiting. @@ -163,7 +196,7 @@ function pre_process_msg(self, msg, config) end function match_plugins(self, msg, config, plugin) - for _, trigger in pairs(plugin.triggers) do + for _, trigger in pairs(plugin.triggers or {}) 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 @@ -347,4 +380,4 @@ function create_cred() print ('saved credentials into reds hash telegram:credentials') end -return bot +return bot \ No newline at end of file diff --git a/otouto/plugins/about.lua b/otouto/plugins/about.lua index f460197..cc9f974 100644 --- a/otouto/plugins/about.lua +++ b/otouto/plugins/about.lua @@ -18,13 +18,14 @@ function about:action(msg, config) -- disabled to restore old behaviour -- if msg.forward_from then return end - local output = config.about_text .. '\nBrawlbot v2.0, basierend auf Otouto von topkecleon.' + local output = config.about_text .. '\nBrawlbot v'..bot.version..', 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') - or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower()) - or msg.text_lower:match('^'..config.cmd_pat..'start') + (msg.new_chat_member and msg.new_chat_member.id == self.info.id) + or msg.text_lower:match('^'..config.cmd_pat..'about$') + or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower()..'$') + or msg.text_lower:match('^'..config.cmd_pat..'start$') + or msg.text_lower:match('^'..config.cmd_pat..'start@'..self.info.username:lower()..'$') then utilities.send_message(self, msg.chat.id, output, true, nil, true) return diff --git a/otouto/plugins/administration.lua b/otouto/plugins/administration.lua index 244a211..88dccfb 100644 --- a/otouto/plugins/administration.lua +++ b/otouto/plugins/administration.lua @@ -1,6 +1,6 @@ --[[ administration.lua - Version 1.10.5 + Version 1.11 Part of the otouto project. © 2016 topkecleon GNU General Public License, version 2 @@ -13,24 +13,11 @@ Important notices about updates will be here! - 1.10 - Added /ahelp $command support. No migration required. All actions - have been reworked to be more elegant. Style has been slightly changed (no - more weak-looking, italic group names). Added some (but not many) comments. - - 1.10.1 - Bug fixes and minor improvements. :^) - - 1.10.2 - Fixed bug in antibot. Further, ranks 2+ will be automatically made - group admins when they join a group. - - 1.10.3 - /gadd now supports arguments to enable flags immediately, eg: - "/gadd 1 4 5" will add a grouo with the unlisted, antibot, and antiflood - flags. - - 1.10.4 - Kick notifications now include user IDs. Errors are silenced. - /flags can now be used with multiple arguments, similar to /gadd. - - 1.10.5 - /groups now supports searching for groups. /setqotd can set a - quoted MOTD. + 1.11 - Removed /kickme and /broadcast. Users should leave manually, and + announcements should be made via channel rather than spam. /setqotd now + handles forwarded messages correctly. /kick, /ban, /hammer, /mod, /admin + now support multiple arguments. Added get_targets function. No migration is + necessary. ]]-- @@ -63,6 +50,7 @@ function administration:init(config) administration.init_command(self, config) administration.doc = '`Returns a list of administrated groups.\nUse '..config.cmd_pat..'ahelp for more administrative commands.`' + administration.command = 'groups [query]' -- In the worst case, don't send errors in reply to random messages. administration.error = false @@ -138,53 +126,100 @@ administration.ranks = { [5] = 'Owner' } -function administration:get_rank(target, chat, config) +function administration:get_rank(user_id_str, chat_id_str, config) - target = tostring(target) - chat = tostring(chat) + user_id_str = tostring(user_id_str) + local user_id = tonumber(user_id_str) + chat_id_str = tostring(chat_id_str) - -- Return 5 if the target is the bot or its owner. - if tonumber(target) == config.admin or tonumber(target) == self.info.id then + -- Return 5 if the user_id_str is the bot or its owner. + if user_id == config.admin or user_id == self.info.id then return 5 end - -- Return 4 if the target is an administrator. - if self.database.administration.admins[target] then + -- Return 4 if the user_id_str is an administrator. + if self.database.administration.admins[user_id_str] then return 4 end - if chat and self.database.administration.groups[chat] then - -- Return 3 if the target is the governor of the chat. - if self.database.administration.groups[chat].governor == tonumber(target) then + if chat_id_str and self.database.administration.groups[chat_id_str] then + -- Return 3 if the user_id_str is the governor of the chat_id_str. + if self.database.administration.groups[chat_id_str].governor == user_id then return 3 - -- Return 2 if the target is a moderator of the chat. - elseif self.database.administration.groups[chat].mods[target] then + -- Return 2 if the user_id_str is a moderator of the chat_id_str. + elseif self.database.administration.groups[chat_id_str].mods[user_id_str] then return 2 - -- Return 0 if the target is banned from the chat. - elseif self.database.administration.groups[chat].bans[target] then + -- Return 0 if the user_id_str is banned from the chat_id_str. + elseif self.database.administration.groups[chat_id_str].bans[user_id_str] then return 0 -- Return 1 if antihammer is enabled. - elseif self.database.administration.groups[chat].flags[6] then + elseif self.database.administration.groups[chat_id_str].flags[6] then return 1 end end - -- Return 0 if the target is blacklisted (and antihammer is not enabled). - if self.database.blacklist[target] then + -- Return 0 if the user_id_str is blacklisted (and antihammer is not enabled). + if self.database.blacklist[user_id_str] then return 0 end - -- Return 1 if the target is a regular user. + -- Return 1 if the user_id_str is a regular user. return 1 end -function administration:get_target(msg, config) - local target = utilities.user_from_message(self, msg) - if target.id then - target.rank = administration.get_rank(self, target.id_str, msg.chat.id, config) +-- Returns an array of "user" tables. +function administration:get_targets(msg, config) + if msg.reply_to_message then + local target = {} + for k,v in pairs(msg.reply_to_message.from) do + target[k] = v + end + target.name = utilities.build_name(target.first_name, target.last_name) + target.id_str = tostring(target.id) + target.rank = administration.get_rank(self, target.id, msg.chat.id, config) + return { target } + else + local input = utilities.input(msg.text) + if input then + local t = {} + for _, user in ipairs(utilities.index(input)) do + if self.database.users[user] then + local target = {} + for k,v in pairs(self.database.users[user]) do + target[k] = v + end + target.name = utilities.build_name(target.first_name, target.last_name) + target.id_str = tostring(target.id) + target.rank = administration.get_rank(self, target.id, msg.chat.id, config) + table.insert(t, target) + elseif tonumber(user) then + local target = { + id = tonumber(user), + id_str = user, + name = 'Unknown ('..user..')', + rank = administration.get_rank(self, user, msg.chat.id, config) + } + table.insert(t, target) + elseif user:match('^@') then + local target = utilities.resolve_username(self, user) + if target then + target.rank = administration.get_rank(self, target.id, msg.chat.id, config) + target.id_str = tostring(target.id) + target.name = utilities.build_name(target.first_name, target.last_name) + table.insert(t, target) + else + table.insert(t, { err = 'Sorry, I do not recognize that username ('..user..').' }) + end + else + table.insert(t, { err = 'Invalid username or ID ('..user..').' }) + end + end + return t + else + return false + end end - return target end function administration:mod_format(id) @@ -226,7 +261,12 @@ function administration:get_desc(chat_id, config) end if group.governor then local gov = self.database.users[tostring(group.governor)] - local s = utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' + local s + if gov then + s = utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' + else + s = 'Unknown `[' .. group.governor .. ']`' + end table.insert(t, '*Governor:* ' .. s) end local modstring = '' @@ -267,7 +307,7 @@ function administration:kick_user(chat, target, reason, config) utilities.handle_exception(self, victim..' kicked from '..group, reason, config) end -function administration.init_command(self_, config) +function administration.init_command(self_, config_) administration.commands = { { -- generic, mostly autokicks @@ -280,8 +320,11 @@ function administration.init_command(self_, config) local rank = administration.get_rank(self, msg.from.id, msg.chat.id, config) local user = {} + local from_id_str = tostring(msg.from.id) + local chat_id_str = tostring(msg.chat.id) if rank < 2 then + local from_name = utilities.build_name(msg.from.first_name, msg.from.last_name) -- banned if rank == 0 then @@ -298,9 +341,9 @@ function administration.init_command(self_, config) user.reason = 'antisquig' user.output = administration.flags[2].kicked:gsub('GROUPNAME', msg.chat.title) elseif group.flags[3] and ( -- antisquig++ - msg.from.name:match(utilities.char.arabic) - or msg.from.name:match(utilities.char.rtl_override) - or msg.from.name:match(utilities.char.rtl_mark) + from_name:match(utilities.char.arabic) + or from_name:match(utilities.char.rtl_override) + or from_name:match(utilities.char.rtl_mark) ) then user.do_kick = true user.reason = 'antisquig++' @@ -312,36 +355,36 @@ function administration.init_command(self_, config) if not group.antiflood then group.antiflood = JSON.decode(JSON.encode(administration.antiflood)) end - if not self.admin_temp.flood[msg.chat.id_str] then - self.admin_temp.flood[msg.chat.id_str] = {} + if not self.admin_temp.flood[chat_id_str] then + self.admin_temp.flood[chat_id_str] = {} end - if not self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = 0 + if not self.admin_temp.flood[chat_id_str][from_id_str] then + self.admin_temp.flood[chat_id_str][from_id_str] = 0 end if msg.sticker then -- Thanks Brazil for discarding switches. - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.sticker + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.sticker elseif msg.photo then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.photo + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.photo elseif msg.document then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.document + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.document elseif msg.audio then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.audio + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.audio elseif msg.contact then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.contact + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.contact elseif msg.video then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.video + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.video elseif msg.location then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.location + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.location elseif msg.voice then - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.voice + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.voice else - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] + group.antiflood.text + self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.text end - if self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] > 99 then + if self.admin_temp.flood[chat_id_str][from_id_str] > 99 then user.do_kick = true user.reason = 'antiflood' user.output = administration.flags[5].kicked:gsub('GROUPNAME', msg.chat.title) - self.admin_temp.flood[msg.chat.id_str][msg.from.id_str] = nil + self.admin_temp.flood[chat_id_str][from_id_str] = nil end end @@ -350,14 +393,15 @@ function administration.init_command(self_, config) local new_user = user local new_rank = rank - if msg.new_chat_participant then + if msg.new_chat_member then -- I hate typing this out. - local noob = msg.new_chat_participant + local noob = msg.new_chat_member + local noob_name = utilities.build_name(noob.first_name, noob.last_name) -- We'll make a new table for the new guy, unless he's also -- the original guy. - if msg.new_chat_participant.id ~= msg.from.id then + if msg.new_chat_member.id ~= msg.from.id then new_user = {} new_rank = administration.get_rank(self,noob.id, msg.chat.id, config) end @@ -369,9 +413,9 @@ function administration.init_command(self_, config) new_user.output = 'Sorry, you are banned from ' .. msg.chat.title .. '.' elseif new_rank == 1 then if group.flags[3] and ( -- antisquig++ - noob.name:match(utilities.char.arabic) - or noob.name:match(utilities.char.rtl_override) - or noob.name:match(utilities.char.rtl_mark) + noob_name:match(utilities.char.arabic) + or noob_name:match(utilities.char.rtl_override) + or noob_name:match(utilities.char.rtl_mark) ) then new_user.do_kick = true new_user.reason = 'antisquig++' @@ -388,7 +432,7 @@ function administration.init_command(self_, config) else -- Make the new user a group admin if he's a mod or higher. if msg.chat.type == 'supergroup' then - drua.channel_set_admin(msg.chat.id, msg.new_chat_participant.id, 2) + drua.channel_set_admin(msg.chat.id, msg.new_chat_member.id, 2) end end @@ -424,9 +468,9 @@ function administration.init_command(self_, config) end if new_user ~= user and new_user.do_kick then - administration.kick_user(self, msg.chat.id, msg.new_chat_participant.id, new_user.reason, config) + administration.kick_user(self, msg.chat.id, msg.new_chat_member.id, new_user.reason, config) if new_user.output then - utilities.send_message(self, msg.new_chat_participant.id, new_user.output) + utilities.send_message(self, msg.new_chat_member.id, new_user.output) end if not new_user.dont_unban and msg.chat.type == 'supergroup' then bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = msg.from.id } ) @@ -434,14 +478,14 @@ function administration.init_command(self_, config) end if group.flags[5] and user.do_kick and not user.dont_unban then - if group.autokicks[msg.from.id_str] then - group.autokicks[msg.from.id_str] = group.autokicks[msg.from.id_str] + 1 + if group.autokicks[from_id_str] then + group.autokicks[from_id_str] = group.autokicks[from_id_str] + 1 else - group.autokicks[msg.from.id_str] = 1 + group.autokicks[from_id_str] = 1 end - if group.autokicks[msg.from.id_str] >= group.autoban then - group.autokicks[msg.from.id_str] = 0 - group.bans[msg.from.id_str] = true + if group.autokicks[from_id_str] >= group.autoban then + group.autokicks[from_id_str] = 0 + group.bans[from_id_str] = true user.dont_unban = true user.reason = 'antiflood autoban: ' .. user.reason user.output = user.output .. '\nYou have been banned for being autokicked too many times.' @@ -458,17 +502,17 @@ function administration.init_command(self_, config) end end - if msg.new_chat_participant and not new_user.do_kick then + if msg.new_chat_member and not new_user.do_kick then local output = administration.get_desc(self, msg.chat.id, config) - utilities.send_message(self, msg.new_chat_participant.id, output, true, nil, true) + utilities.send_message(self, msg.new_chat_member.id, output, true, nil, true) end -- Last active time for group listing. if msg.text:len() > 0 then for i,v in pairs(self.database.administration.activity) do - if v == msg.chat.id_str then + if v == chat_id_str then table.remove(self.database.administration.activity, i) - table.insert(self.database.administration.activity, 1, msg.chat.id_str) + table.insert(self.database.administration.activity, 1, chat_id_str) end end end @@ -479,7 +523,7 @@ function administration.init_command(self_, config) }, { -- /groups - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('groups', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('groups', true).table, command = 'groups \\[query]', privilege = 1, @@ -490,8 +534,8 @@ function administration.init_command(self_, config) local input = utilities.input(msg.text) local search_res = '' local grouplist = '' - for i,v in ipairs(self.database.administration.activity) do - local group = self.database.administration.groups[v] + for _, chat_id_str in ipairs(self.database.administration.activity) do + local group = self.database.administration.groups[chat_id_str] if (not group.flags[1]) and group.link then -- no unlisted or unlinked groups grouplist = grouplist .. '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')\n' if input and string.match(group.name:lower(), input:lower()) then @@ -512,7 +556,7 @@ function administration.init_command(self_, config) }, { -- /ahelp - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('ahelp', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ahelp', true).table, command = 'ahelp \\[command]', privilege = 1, @@ -558,7 +602,7 @@ function administration.init_command(self_, config) }, { -- /ops - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('ops'):t('oplist').table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ops'):t('oplist').table, command = 'ops', privilege = 1, @@ -576,7 +620,11 @@ function administration.init_command(self_, config) local govstring = '' if group.governor then local gov = self.database.users[tostring(group.governor)] - govstring = '*Governor:* ' .. utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' + if gov then + govstring = '*Governor:* ' .. utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' + else + govstring = '*Governor:* Unknown `[' .. group.governor .. ']`' + end end local output = utilities.trim(modstring) ..'\n\n' .. utilities.trim(govstring) if output == '\n\n' then @@ -588,7 +636,7 @@ function administration.init_command(self_, config) }, { -- /desc - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('desc'):t('description').table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('desc'):t('description').table, command = 'description', privilege = 1, @@ -608,7 +656,7 @@ function administration.init_command(self_, config) }, { -- /rules - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('rules?', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('rules?', true).table, command = 'rules \\[i]', privilege = 1, @@ -636,7 +684,7 @@ function administration.init_command(self_, config) }, { -- /motd - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('motd').table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('motd').table, command = 'motd', privilege = 1, @@ -653,7 +701,7 @@ function administration.init_command(self_, config) }, { -- /link - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('link').table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('link').table, command = 'link', privilege = 1, @@ -669,29 +717,8 @@ function administration.init_command(self_, config) end }, - { -- /kickme - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('leave'):t('kickme').table, - - command = 'kickme', - privilege = 1, - interior = true, - doc = 'Removes the user from the group.', - - action = function(self, msg, group, config) - if administration.get_rank(self, msg.from.id, nil, config) == 5 then - utilities.send_reply(self, msg, 'I can\'t let you do that, '..msg.from.name..'.') - else - administration.kick_user(self, msg.chat.id, msg.from.id, 'kickme', config) - utilities.send_message(self, msg.chat.id, 'Goodbye, ' .. msg.from.name .. '!', true) - if msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = msg.from.id } ) - end - end - end - }, - { -- /kick - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('kick', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('kick', true).table, command = 'kick ', privilege = 2, @@ -699,23 +726,31 @@ function administration.init_command(self_, config) doc = 'Removes a user from the group. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - elseif target.rank > 1 then - utilities.send_reply(self, msg, target.name .. ' is too privileged to be kicked.') - else - administration.kick_user(self, msg.chat.id, target.id, 'kicked by ' .. msg.from.name, config) - utilities.send_message(self, msg.chat.id, target.name .. ' has been kicked.') - if msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then + output = output .. target.name .. ' is too privileged to be kicked.\n' + else + administration.kick_user(self, msg.chat.id, target.id, 'kicked by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) + output = output .. target.name .. ' has been kicked.\n' + if msg.chat.type == 'supergroup' then + bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) + end + end end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /ban - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('ban', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ban', true).table, command = 'ban ', privilege = 2, @@ -723,23 +758,32 @@ function administration.init_command(self_, config) doc = 'Bans a user from the group. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - elseif target.rank > 1 then - utilities.send_reply(self, msg, target.name .. ' is too privileged to be banned.') - elseif group.bans[target.id_str] then - utilities.send_reply(self, msg, target.name .. ' is already banned.') + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif group.bans[target.id_str] then + output = output .. target.name .. ' is already banned.\n' + elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then + output = output .. target.name .. ' is too privileged to be banned.\n' + else + administration.kick_user(self, msg.chat.id, target.id, 'banned by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) + output = output .. target.name .. ' has been banned.\n' + group.mods[target.id_str] = nil + group.bans[target.id_str] = true + end + end + utilities.send_reply(self, msg, output) else - administration.kick_user(self, msg.chat.id, target.id, 'banned by '..msg.from.name, config) - utilities.send_reply(self, msg, target.name .. ' has been banned.') - group.bans[target.id_str] = true + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /unban - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('unban', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('unban', true).table, command = 'unban ', privilege = 2, @@ -747,25 +791,33 @@ function administration.init_command(self_, config) doc = 'Unbans a user from the group. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + else + if not group.bans[target.id_str] then + output = output .. target.name .. ' is not banned.\n' + else + output = output .. target.name .. ' has been unbanned.\n' + group.bans[target.id_str] = nil + end + if msg.chat.type == 'supergroup' then + bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) + end + end + end + utilities.send_reply(self, msg, output) else - if not group.bans[target.id_str] then - utilities.send_reply(self, msg, target.name .. ' is not banned.') - else - group.bans[target.id_str] = nil - utilities.send_reply(self, msg, target.name .. ' has been unbanned.') - end - if msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) - end + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /setmotd - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('setmotd', true):t('setqotd', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setmotd', true):t('setqotd', true).table, command = 'setmotd ', privilege = 2, @@ -774,10 +826,14 @@ function administration.init_command(self_, config) action = function(self, msg, group, config) local input = utilities.input(msg.text) - local quoted = msg.from.name + local quoted = utilities.build_name(msg.from.first_name, msg.from.last_name) if msg.reply_to_message and #msg.reply_to_message.text > 0 then input = msg.reply_to_message.text - quoted = msg.reply_to_message.from.name + if msg.reply_to_message.forward_from then + quoted = utilities.build_name(msg.reply_to_message.forward_from.first_name, msg.reply_to_message.forward_from.last_name) + else + quoted = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) + end end if input then if input == '--' or input == utilities.char.em_dash then @@ -801,7 +857,7 @@ function administration.init_command(self_, config) }, { -- /setrules - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('setrules', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setrules', true).table, command = 'setrules ', privilege = 3, @@ -831,7 +887,7 @@ function administration.init_command(self_, config) }, { -- /changerule - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('changerule', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('changerule', true).table, command = 'changerule ', privilege = 3, @@ -868,7 +924,7 @@ function administration.init_command(self_, config) }, { -- /setlink - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('setlink', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setlink', true).table, command = 'setlink ', privilege = 3, @@ -891,14 +947,14 @@ function administration.init_command(self_, config) }, { -- /alist - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('alist').table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('alist').table, command = 'alist', privilege = 3, interior = true, doc = 'Returns a list of administrators. Owner is denoted with a star character.', - action = function(self, msg, _, config) + action = function(self, msg, group, config) local output = '*Administrators:*\n' output = output .. administration.mod_format(self, config.admin):gsub('\n', ' ★\n') for id,_ in pairs(self.database.administration.admins) do @@ -909,7 +965,7 @@ function administration.init_command(self_, config) }, { -- /flags - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('flags?', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('flags?', true).table, command = 'flag \\[i] ...', privilege = 3, @@ -922,7 +978,7 @@ function administration.init_command(self_, config) if input then local index = utilities.index(input) for _, i in ipairs(index) do - n = tonumber(i) + local n = tonumber(i) if n and administration.flags[n] then if group.flags[n] == true then group.flags[n] = false @@ -949,7 +1005,7 @@ function administration.init_command(self_, config) }, { -- /antiflood - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('antiflood', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('antiflood', true).table, command = 'antiflood \\[ ]', privilege = 3, @@ -989,7 +1045,7 @@ function administration.init_command(self_, config) }, { -- /mod - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('mod', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('mod', true).table, command = 'mod ', privilege = 3, @@ -997,26 +1053,34 @@ function administration.init_command(self_, config) doc = 'Promotes a user to a moderator. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + else + if target.rank > 1 then + output = output .. target.name .. ' is already a moderator or greater.\n' + else + output = output .. target.name .. ' is now a moderator.\n' + group.mods[target.id_str] = true + group.bans[target.id_str] = nil + end + if group.grouptype == 'supergroup' then + drua.channel_set_admin(msg.chat.id, target.id, 2) + end + end + end + utilities.send_reply(self, msg, output) else - if target.rank > 1 then - utilities.send_reply(self, msg, target.name .. ' is already a moderator or greater.') - else - group.bans[target.id_str] = nil - group.mods[target.id_str] = true - utilities.send_reply(self, msg, target.name .. ' is now a moderator.') - end - if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 2) - end + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /demod - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('demod', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('demod', true).table, command = 'demod ', privilege = 3, @@ -1024,25 +1088,33 @@ function administration.init_command(self_, config) doc = 'Demotes a moderator to a user. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + else + if not group.mods[target.id_str] then + output = output .. target.name .. ' is not a moderator.\n' + else + output = output .. target.name .. ' is no longer a moderator.\n' + group.mods[target.id_str] = nil + end + if group.grouptype == 'supergroup' then + drua.channel_set_admin(msg.chat.id, target.id, 0) + end + end + end + utilities.send_reply(self, msg, output) else - if not group.mods[target.id_str] then - utilities.send_reply(self, msg, target.name .. ' is not a moderator.') - else - group.mods[target.id_str] = nil - utilities.send_reply(self, msg, target.name .. ' is no longer a moderator.') - end - if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 0) - end + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /gov - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('gov', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('gov', true).table, command = 'gov ', privilege = 4, @@ -1050,28 +1122,33 @@ function administration.init_command(self_, config) doc = 'Promotes a user to the governor. The current governor will be replaced. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - else - if group.governor == target.id then - utilities.send_reply(self, msg, target.name .. ' is already the governor.') + local targets = administration.get_targets(self, msg, config) + if targets then + local target = targets[1] + if target.err then + utilities.send_reply(self, msg, target.err) else - group.bans[target.id_str] = nil - group.mods[target.id_str] = nil - group.governor = target.id - utilities.send_reply(self, msg, target.name .. ' is the new governor.') - end - if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 2) - administration.update_desc(self, msg.chat.id, config) + if group.governor == target.id then + utilities.send_reply(self, msg, target.name .. ' is already the governor.') + else + group.bans[target.id_str] = nil + group.mods[target.id_str] = nil + group.governor = target.id + utilities.send_reply(self, msg, target.name .. ' is the new governor.') + end + if group.grouptype == 'supergroup' then + drua.channel_set_admin(msg.chat.id, target.id, 2) + administration.update_desc(self, msg.chat.id, config) + end end + else + utilities.send_reply(self, msg, 'Please specify a user via reply, username, or ID.') end end }, { -- /degov - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('degov', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('degov', true).table, command = 'degov ', privilege = 4, @@ -1079,26 +1156,31 @@ function administration.init_command(self_, config) doc = 'Demotes the governor to a user. The administrator will become the new governor. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - else - if group.governor ~= target.id then - utilities.send_reply(self, msg, target.name .. ' is not the governor.') + local targets = administration.get_targets(self, msg, config) + if targets then + local target = targets[1] + if target.err then + utilities.send_reply(self, msg, target.err) else - group.governor = msg.from.id - utilities.send_reply(self, msg, target.name .. ' is no longer the governor.') - end - if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 0) - administration.update_desc(self, msg.chat.id, config) + if group.governor ~= target.id then + utilities.send_reply(self, msg, target.name .. ' is not the governor.') + else + group.governor = msg.from.id + utilities.send_reply(self, msg, target.name .. ' is no longer the governor.') + end + if group.grouptype == 'supergroup' then + drua.channel_set_admin(msg.chat.id, target.id, 0) + administration.update_desc(self, msg.chat.id, config) + end end + else + utilities.send_reply(self, msg, 'Please specify a user via reply, username, or ID.') end end }, { -- /hammer - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('hammer', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('hammer', true).table, command = 'hammer ', privilege = 4, @@ -1106,35 +1188,45 @@ function administration.init_command(self_, config) doc = 'Bans a user from all groups. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - elseif target.rank > 3 then - utilities.send_reply(self, msg, target.name .. ' is too privileged to be globally banned.') - elseif self.database.blacklist[target.id_str] then - utilities.send_reply(self, msg, target.name .. ' is already globally banned.') - else - administration.kick_user(self, msg.chat.id, target.id, 'hammered by '..msg.from.name, config) - self.database.blacklist[target.id_str] = true - for k,v in pairs(self.database.administration.groups) do - if not v.flags[6] then - v.mods[target.id_str] = nil - drua.kick_user(k, target.id) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif self.database.blacklist[target.id_str] then + output = output .. target.name .. ' is already globally banned.\n' + elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then + output = output .. target.name .. ' is too privileged to be globally banned.\n' + else + administration.kick_user(self, msg.chat.id, target.id, 'hammered by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) + if #targets == 1 then + for k,v in pairs(self.database.administration.groups) do + if not v.flags[6] then + v.mods[target.id_str] = nil + drua.kick_user(k, target.id) + end + end + end + self.database.blacklist[target.id_str] = true + if group.flags[6] == true then + group.mods[target.id_str] = nil + group.bans[target.id_str] = true + output = output .. target.name .. ' has been globally and locally banned.\n' + else + output = output .. target.name .. ' has been globally banned.\n' + end end end - local output = target.name .. ' has been globally banned.' - if group.flags[6] == true then - group.mods[target.id_str] = nil - group.bans[target.id_str] = true - output = target.name .. ' has been globally and locally banned.' - end utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /unhammer - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('unhammer', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('unhammer', true).table, command = 'unhammer ', privilege = 4, @@ -1142,78 +1234,94 @@ function administration.init_command(self_, config) doc = 'Removes a global ban. The target may be specified via reply, username, or ID.', action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - elseif not self.database.blacklist[target.id_str] then - utilities.send_reply(self, msg, target.name .. ' is not globally banned.') + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif not self.database.blacklist[target.id_str] then + output = output .. target.name .. ' is not globally banned.\n' + else + self.database.blacklist[target.id_str] = nil + output = output .. target.name .. ' has been globally unbanned.\n' + end + end + utilities.send_reply(self, msg, output) else - self.database.blacklist[target.id_str] = nil - utilities.send_reply(self, msg, target.name .. ' has been globally unbanned.') - end - if msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /admin - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('admin', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('admin', true).table, command = 'admin ', privilege = 5, interior = false, doc = 'Promotes a user to an administrator. The target may be specified via reply, username, or ID.', - action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - elseif target.rank >= 4 then - utilities.send_reply(self, msg, target.name .. ' is already an administrator or greater.') - else - for _,g in pairs(self.database.administration.groups) do - g.mods[target.id_str] = nil + action = function(self, msg, _, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif target.rank >= 4 then + output = output .. target.name .. ' is already an administrator or greater.\n' + else + for _, group in pairs(self.database.administration.groups) do + group.mods[target.id_str] = nil + end + self.database.administration.admins[target.id_str] = true + output = output .. target.name .. ' is now an administrator.\n' + end end - self.database.administration.admins[target.id_str] = true - utilities.send_reply(self, msg, target.name .. ' is now an administrator.') - end - if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 2) + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /deadmin - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('deadmin', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('deadmin', true).table, command = 'deadmin ', privilege = 5, interior = false, doc = 'Demotes an administrator to a user. The target may be specified via reply, username, or ID.', - action = function(self, msg, group, config) - local target = administration.get_target(self, msg, config) - if target.err then - utilities.send_reply(self, msg, target.err) - else - for chat_id, group in pairs(self.database.administration.groups) do - if group.grouptype == 'supergroup' then - drua.channel_set_admin(chat_id, target.id, 0) + action = function(self, msg, _, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif target.rank ~= 4 then + output = output .. target.name .. ' is not an administrator.\n' + else + for chat_id, group in pairs(self.database.administration.groups) do + if group.grouptype == 'supergroup' then + drua.channel_set_admin(chat_id, target.id, 0) + end + end + self.database.administration.admins[target.id_str] = nil + output = output .. target.name .. ' is no longer an administrator.\n' end end - if target.rank ~= 4 then - utilities.send_reply(self, msg, target.name .. ' is not an administrator.') - else - self.database.administration.admins[target.id_str] = nil - utilities.send_reply(self, msg, target.name .. ' is no longer an administrator.') - end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') end end }, { -- /gadd - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('gadd', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('gadd', true).table, command = 'gadd \\[i] ...', privilege = 5, @@ -1223,7 +1331,7 @@ function administration.init_command(self_, config) action = function(self, msg, group, config) if msg.chat.id == msg.from.id then utilities.send_message(self, msg.chat.id, 'No.') - elseif self.database.administration.groups[msg.chat.id_str] then + elseif group then utilities.send_reply(self, msg, 'I am already administrating this group.') else local output = 'I am now administrating this group.' @@ -1235,14 +1343,14 @@ function administration.init_command(self_, config) if input then local index = utilities.index(input) for _, i in ipairs(index) do - n = tonumber(i) + local n = tonumber(i) if n and administration.flags[n] and flags[n] ~= true then flags[n] = true output = output .. '\n' .. administration.flags[n].short end end end - self.database.administration.groups[msg.chat.id_str] = { + self.database.administration.groups[tostring(msg.chat.id)] = { mods = {}, governor = msg.from.id, bans = {}, @@ -1257,7 +1365,7 @@ function administration.init_command(self_, config) autoban = 3 } administration.update_desc(self, msg.chat.id, config) - table.insert(self.database.administration.activity, msg.chat.id_str) + table.insert(self.database.administration.activity, tostring(msg.chat.id)) utilities.send_reply(self, msg, output) drua.channel_set_admin(msg.chat.id, self.info.id, 2) end @@ -1265,15 +1373,15 @@ function administration.init_command(self_, config) }, { -- /grem - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('grem', true):t('gremove', true).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('grem', true):t('gremove', true).table, command = 'gremove \\[chat]', privilege = 5, interior = false, doc = 'Removes a group from the administration system.', - action = function(self, msg, group, config) - local input = utilities.input(msg.text) or msg.chat.id_str + action = function(self, msg) + local input = utilities.input(msg.text) or tostring(msg.chat.id) local output if self.database.administration.groups[input] then local chat_name = self.database.administration.groups[input].name @@ -1285,7 +1393,7 @@ function administration.init_command(self_, config) end output = 'I am no longer administrating _' .. utilities.md_escape(chat_name) .. '_.' else - if input == msg.chat.id_str then + if input == tostring(msg.chat.id) then output = 'I do not administrate this group.' else output = 'I do not administrate that group.' @@ -1296,7 +1404,7 @@ function administration.init_command(self_, config) }, { -- /glist - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('glist', false).table, + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('glist', false).table, command = 'glist', privilege = 5, @@ -1322,42 +1430,13 @@ function administration.init_command(self_, config) end end end - }, - - { -- /broadcast - triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('broadcast', true).table, - - command = 'broadcast ', - privilege = 5, - interior = false, - doc = 'Broadcasts a message to all administrated groups.', - - action = function(self, msg, group, config) - local input = utilities.input(msg.text) - if not input then - utilities.send_reply(self, msg, 'Give me something to broadcast.') - else - input = '*Admin Broadcast:*\n' .. input - for id,_ in pairs(self.database.administration.groups) do - utilities.send_message(self, id, input, true, nil, true) - end - end - end } } - -- These could be merged, but load time doesn't matter. + administration.triggers = {''} - -- Generate trigger table. - administration.triggers = {} - for _, command in ipairs(administration.commands) do - for _, trigger in ipairs(command.triggers) do - table.insert(administration.triggers, trigger) - end - end - - -- Generate help messages. + -- Generate help messages and ahelp keywords. self_.database.administration.help = {} for i,_ in ipairs(administration.ranks) do self_.admin_temp.help[i] = {} @@ -1365,13 +1444,9 @@ function administration.init_command(self_, config) for _,v in ipairs(administration.commands) do if v.command then table.insert(self_.admin_temp.help[v.privilege], v.command) - end - end - - -- Generate ahelp keywords. - for _,v in ipairs(administration.commands) do - if v.command and v.doc then - v.keyword = utilities.get_word(v.command, 1) + if v.doc then + v.keyword = utilities.get_word(v.command, 1) + end end end end @@ -1380,13 +1455,13 @@ function administration:action(msg, config) for _,command in ipairs(administration.commands) do for _,trigger in pairs(command.triggers) do if msg.text_lower:match(trigger) then - if command.interior and not self.database.administration.groups[msg.chat.id_str] then + if + (command.interior and not self.database.administration.groups[tostring(msg.chat.id)]) + or administration.get_rank(self, msg.from.id, msg.chat.id, config) < command.privilege + then break end - if administration.get_rank(self, msg.from.id, msg.chat.id, config) < command.privilege then - break - end - local res = command.action(self, msg, self.database.administration.groups[msg.chat.id_str], config) + local res = command.action(self, msg, self.database.administration.groups[tostring(msg.chat.id)], config) if res ~= true then return res end @@ -1406,6 +1481,4 @@ function administration:cron() end end -administration.command = 'groups' - return administration diff --git a/otouto/plugins/blacklist.lua b/otouto/plugins/blacklist.lua index ac83e74..29a852e 100644 --- a/otouto/plugins/blacklist.lua +++ b/otouto/plugins/blacklist.lua @@ -17,8 +17,8 @@ blacklist.triggers = { function blacklist:action(msg, config) - if self.database.blacklist[msg.from.id_str] then return end - if self.database.blacklist[msg.chat.id_str] then return end + if self.database.blacklist[tostring(msg.from.id)] then return end + if self.database.blacklist[tostring(msg.chat.id)] then return end if not msg.text:match('^'..config.cmd_pat..'blacklist') then return true end if msg.from.id ~= config.admin then return end @@ -35,9 +35,15 @@ function blacklist:action(msg, config) if self.database.blacklist[tostring(target.id)] then self.database.blacklist[tostring(target.id)] = nil utilities.send_reply(self, msg, target.name .. ' has been removed from the blacklist.') + if config.drua_block_on_blacklist then + require('drua-tg').unblock(target.id) + end else self.database.blacklist[tostring(target.id)] = true utilities.send_reply(self, msg, target.name .. ' has been added to the blacklist.') + if config.drua_block_on_blacklist then + require('drua-tg').block(target.id) + end end end diff --git a/otouto/plugins/control.lua b/otouto/plugins/control.lua index 2423f74..ae0bd50 100644 --- a/otouto/plugins/control.lua +++ b/otouto/plugins/control.lua @@ -17,7 +17,7 @@ function control:action(msg, config) return end - if msg.date < os.time() - 1 then return end + if msg.date < os.time() - 2 then return end if msg.text_lower:match('^'..cmd_pat..'restart') then for pac, _ in pairs(package.loaded) do diff --git a/otouto/plugins/greetings.lua b/otouto/plugins/greetings.lua index f254f5d..a2252c4 100644 --- a/otouto/plugins/greetings.lua +++ b/otouto/plugins/greetings.lua @@ -41,12 +41,16 @@ end function greetings:action(msg, config) - local nick = self.database.users[msg.from.id_str].nickname or msg.from.first_name + local nick = utilities.build_name(msg.from.first_name, msg.from.last_name) + if self.database.userdata[tostring(msg.from.id)] then + nick = self.database.userdata[tostring(msg.from.id)].nickname or nick + end for trigger,responses in pairs(config.greetings) do for _,response in pairs(responses) do if msg.text_lower:match(response..',? '..self.info.first_name:lower()) then - utilities.send_message(self, msg.chat.id, utilities.latcyr(trigger:gsub('#NAME', nick))) + local output = utilities.char.zwnj .. trigger:gsub('#NAME', nick) + utilities.send_message(self, msg.chat.id, output) return end end diff --git a/otouto/plugins/lastfm.lua b/otouto/plugins/lastfm.lua index 316b3e0..0cc831f 100644 --- a/otouto/plugins/lastfm.lua +++ b/otouto/plugins/lastfm.lua @@ -30,6 +30,8 @@ lastfm.command = 'lastfm' function lastfm:action(msg, config) local input = utilities.input(msg.text) + local from_id_str = tostring(msg.from.id) + self.database.userdata[from_id_str] = self.database.userdata[from_id_str] or {} if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) @@ -38,10 +40,10 @@ function lastfm:action(msg, config) if not input then utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) elseif input == '--' or input == utilities.char.em_dash then - self.database.users[msg.from.id_str].lastfm = nil + self.database.userdata[from_id_str].lastfm = nil utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.') else - self.database.users[msg.from.id_str].lastfm = input + self.database.userdata[from_id_str].lastfm = input utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".') end return @@ -53,12 +55,12 @@ function lastfm:action(msg, config) local alert = '' if input then username = input - elseif self.database.users[msg.from.id_str].lastfm then - username = self.database.users[msg.from.id_str].lastfm + elseif self.database.userdata[from_id_str].lastfm then + username = self.database.userdata[from_id_str].lastfm elseif msg.from.username then username = msg.from.username alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use '..config.cmd_pat..'fmset .' - self.database.users[msg.from.id_str].lastfm = username + self.database.userdata[from_id_str].lastfm = username else utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') return diff --git a/otouto/plugins/me.lua b/otouto/plugins/me.lua index f11769c..771ba80 100644 --- a/otouto/plugins/me.lua +++ b/otouto/plugins/me.lua @@ -4,24 +4,37 @@ local utilities = require('otouto.utilities') function me:init(config) me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table + me.command = 'me' + me.doc = '`Returns userdata stored by the bot.`' end function me:action(msg, config) - local target = self.database.users[msg.from.id_str] + local userdata = self.database.userdata[tostring(msg.from.id)] or {} - if msg.from.id == config.admin and (msg.reply_to_message or utilities.input(msg.text)) then - target = utilities.user_from_message(self, msg, true) - if target.err then - utilities.send_reply(self, msg, target.err) - return + if msg.from.id == config.admin then + if msg.reply_to_message then + userdata = self.database.userdata[tostring(msg.reply_to_message.from.id)] + else + local input = utilities.input(msg.text) + if input then + local user_id = utilities.id_from_username(self, input) + if user_id then + userdata = self.database.userdata[tostring(user_id)] or {} + end + end end end local output = '' - for k,v in pairs(target) do + for k,v in pairs(userdata) do output = output .. '*' .. k .. ':* `' .. tostring(v) .. '`\n' end + + if output == '' then + output = 'There is no data stored for this user.' + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) end diff --git a/otouto/plugins/nick.lua b/otouto/plugins/nick.lua index 50a60b8..1819c5c 100644 --- a/otouto/plugins/nick.lua +++ b/otouto/plugins/nick.lua @@ -14,34 +14,35 @@ end function nick:action(msg, config) - local target = msg.from + local id_str, name if msg.from.id == config.admin and msg.reply_to_message then - target = msg.reply_to_message.from - target.id_str = tostring(target.id) - target.name = target.first_name - if target.last_name then - target.name = target.first_name .. ' ' .. target.last_name - end + id_str = tostring(msg.reply_to_message.from.id) + name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) + else + id_str = tostring(msg.from.id) + name = utilities.build_name(msg.from.first_name, msg.from.last_name) end + self.database.userdata[id_str] = self.database.userdata[id_str] or {} + local output local input = utilities.input(msg.text) if not input then - if self.database.users[target.id_str].nickname then - output = target.name .. '\'s nickname is "' .. self.database.users[target.id_str].nickname .. '".' + if self.database.userdata[id_str].nickname then + output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".' else - output = target.name .. ' currently has no nickname.' + output = name .. ' currently has no nickname.' end elseif utilities.utf8_len(input) > 32 then output = 'The character limit for nicknames is 32.' elseif input == '--' or input == utilities.char.em_dash then - self.database.users[target.id_str].nickname = nil - output = target.name .. '\'s nickname has been deleted.' + self.database.userdata[id_str].nickname = nil + output = name .. '\'s nickname has been deleted.' else input = input:gsub('\n', ' ') - self.database.users[target.id_str].nickname = input - output = target.name .. '\'s nickname has been set to "' .. input .. '".' + self.database.userdata[id_str].nickname = input + output = name .. '\'s nickname has been set to "' .. input .. '".' end utilities.send_reply(self, msg, output) diff --git a/otouto/plugins/remind.lua b/otouto/plugins/remind.lua index 9509f65..013d255 100644 --- a/otouto/plugins/remind.lua +++ b/otouto/plugins/remind.lua @@ -92,4 +92,4 @@ function remind:cron() end end -return remind +return remind \ No newline at end of file diff --git a/otouto/plugins/slap.lua b/otouto/plugins/slap.lua index 5128434..d115d12 100644 --- a/otouto/plugins/slap.lua +++ b/otouto/plugins/slap.lua @@ -61,7 +61,7 @@ local slaps = { 'VICTIM died. I blame VICTOR.', 'VICTIM was axe-murdered by VICTOR.', 'VICTIM\'s melon was split by VICTOR.', - 'VICTIM was slice and diced by VICTOR.', + 'VICTIM was sliced and diced by VICTOR.', 'VICTIM was split from crotch to sternum by VICTOR.', 'VICTIM\'s death put another notch in VICTOR\'s axe.', 'VICTIM died impossibly!', @@ -102,29 +102,52 @@ local slaps = { 'VICTIM was impeached.', 'VICTIM was one-hit KO\'d by VICTOR.', 'VICTOR sent VICTIM to /dev/null.', - 'VICTOR sent VICTIM down the memory hole.' + 'VICTOR sent VICTIM down the memory hole.', + 'VICTIM was a mistake.', + '"VICTIM was a mistake." - VICTOR', + 'VICTOR checkmated VICTIM in two moves.' } + -- optimize later function slap:action(msg) - - local victor = self.database.users[msg.from.id_str] - local victim = utilities.user_from_message(self, msg, true) local input = utilities.input(msg.text) - - local victim_name = victim.nickname or victim.first_name or input - local victor_name = victor.nickname or victor.first_name - if not victim_name or victim_name == victor_name then - victim_name = victor_name + local victor_id = msg.from.id + local victim_id = utilities.id_from_message(self, msg) + -- IDs + if victim_id then + if victim_id == victor_id then + victor_id = self.info.id + end + else + if not input then + victor_id = self.info.id + victim_id = msg.from.id + end + end + -- Names + local victor_name, victim_name + if input and not victim_id then + victim_name = input + else + local victim_id_str = tostring(victim_id) + if self.database.userdata[victim_id_str] and self.database.userdata[victim_id_str].nickname then + victim_name = self.database.userdata[victim_id_str].nickname + elseif self.database.users[victim_id_str] then + victim_name = utilities.build_name(self.database.users[victim_id_str].first_name, self.database.users[victim_id_str].last_name) + else + victim_name = victim_id_str + end + end + local victor_id_str = tostring(victor_id) + if self.database.userdata[victor_id_str] and self.database.userdata[victor_id_str].nickname then + victor_name = self.database.userdata[victor_id_str].nickname + elseif self.database.users[victor_id_str] then + victor_name = utilities.build_name(self.database.users[victor_id_str].first_name, self.database.users[victor_id_str].last_name) + else victor_name = self.info.first_name end - - local output = slaps[math.random(#slaps)] - output = output:gsub('VICTIM', victim_name) - output = output:gsub('VICTOR', victor_name) - output = utilities.char.zwnj .. output - + local output = utilities.char.zwnj .. slaps[math.random(#slaps)]:gsub('VICTIM', victim_name):gsub('VICTOR', victor_name) utilities.send_message(self, msg.chat.id, output) - end return slap diff --git a/otouto/plugins/weather.lua b/otouto/plugins/weather.lua index a0f42f5..6a9d936 100644 --- a/otouto/plugins/weather.lua +++ b/otouto/plugins/weather.lua @@ -2,6 +2,7 @@ local weather = {} local HTTPS = require('ssl.https') local URL = require('socket.url') +local HTTP = require('socket.http') local JSON = require('dkjson') local utilities = require('otouto.utilities') local bindings = require('otouto.bindings') diff --git a/otouto/plugins/whoami.lua b/otouto/plugins/whoami.lua index 52b8ae5..ce068ff 100644 --- a/otouto/plugins/whoami.lua +++ b/otouto/plugins/whoami.lua @@ -16,9 +16,10 @@ function whoami:action(msg) if msg.reply_to_message then msg = msg.reply_to_message - msg.from.name = utilities.build_name(msg.from.first_name, msg.from.last_name) end + local from_name = utilities.build_name(msg.from.first_name, msg.from.last_name) + local chat_id = math.abs(msg.chat.id) if chat_id > 1000000000000 then chat_id = chat_id - 1000000000000 @@ -26,10 +27,10 @@ function whoami:action(msg) local user = 'You are @%s, also known as *%s* `[%s]`' if msg.from.username then - user = user:format(utilities.markdown_escape(msg.from.username), msg.from.name, msg.from.id) + user = user:format(utilities.markdown_escape(msg.from.username), from_name, msg.from.id) else user = 'You are *%s* `[%s]`,' - user = user:format(msg.from.name, msg.from.id) + user = user:format(from_name, msg.from.id) end local group = '@%s, also known as *%s* `[%s]`.' diff --git a/otouto/utilities.lua b/otouto/utilities.lua index 13e7b42..484a895 100644 --- a/otouto/utilities.lua +++ b/otouto/utilities.lua @@ -216,41 +216,12 @@ function utilities.utf8_len(s) return chars end - -- I swear, I copied this from PIL, not yago! :) -function utilities.trim(str) -- Trims whitespace from a string. +-- Trims whitespace from a string. +function utilities.trim(str) local s = str:gsub('^%s*(.-)%s*$', '%1') return s end -local lc_list = { --- Latin = 'Cyrillic' - ['A'] = 'А', - ['B'] = 'В', - ['C'] = 'С', - ['E'] = 'Е', - ['I'] = 'І', - ['J'] = 'Ј', - ['K'] = 'К', - ['M'] = 'М', - ['H'] = 'Н', - ['O'] = 'О', - ['P'] = 'Р', - ['S'] = 'Ѕ', - ['T'] = 'Т', - ['X'] = 'Х', - ['Y'] = 'Ү', - ['a'] = 'а', - ['c'] = 'с', - ['e'] = 'е', - ['i'] = 'і', - ['j'] = 'ј', - ['o'] = 'о', - ['s'] = 'ѕ', - ['x'] = 'х', - ['y'] = 'у', - ['!'] = 'ǃ' -} - -- Retruns true if the string is empty function string:isempty() return self == nil or self == '' @@ -322,14 +293,6 @@ function vardump(value) print(serpent.block(value, {comment=false})) end - -- Replaces letters with corresponding Cyrillic characters. -function utilities.latcyr(str) - for k,v in pairs(lc_list) do - str = str:gsub(k, v) - end - return str -end - -- Loads a JSON file as a table. function utilities.load_data(filename) local f = io.open(filename) @@ -393,15 +356,46 @@ end function utilities:resolve_username(input) input = input:gsub('^@', '') - for _,v in pairs(self.database.users) do - if v.username and v.username:lower() == input:lower() then - return v + for _, user in pairs(self.database.users) do + if user.username and user.username:lower() == input:lower() then + local t = {} + for key, val in pairs(user) do + t[key] = val + end + return t + end + end +end + + -- Simpler than above function; only returns an ID. + -- Returns nil if no ID is available. +function utilities:id_from_username(input) + input = input:gsub('^@', '') + for _, user in pairs(self.database.users) do + if user.username and user.username:lower() == input:lower() then + return user.id + end + end +end + + -- Simpler than below function; only returns an ID. + -- Returns nil if no ID is available. +function utilities:id_from_message(msg) + if msg.reply_to_message then + return msg.reply_to_message.from.id + else + local input = utilities.input(msg.text) + if input then + if tonumber(input) then + return tonumber(input) + elseif input:match('^@') then + return utilities.id_from_username(self, input) + end end end end function utilities:user_from_message(msg, no_extra) - local input = utilities.input(msg.text_lower) local target = {} if msg.reply_to_message then @@ -543,21 +537,6 @@ function utilities.pretty_float(x) end end -function utilities:create_user_entry(user) - local id = tostring(user.id) - -- Clear things that may no longer exist, or create a user entry. - if self.database.users[id] then - self.database.users[id].username = nil - self.database.users[id].last_name = nil - else - self.database.users[id] = {} - end - -- Add all the user info to the entry. - for k,v in pairs(user) do - self.database.users[id][k] = v - end -end - -- This table will store unsavory characters that are not properly displayed, -- or are just not fun to type. utilities.char = {