From 9ebdbd9d3c36fbf174070f5fd6e6d622bf126769 Mon Sep 17 00:00:00 2001 From: topkecleon Date: Tue, 5 Jul 2016 03:29:11 -0400 Subject: [PATCH] otouto 3.11 "things occurred" Added some utilities (id_from_username, id_from_message), removed some utilities (latcyr, others?). Removed cycle-wasting "shortcuts" -- no more automatic id_str or name; text_lower remains. Moved userdata (nicknames, lastfm, etc) to a different tree in the database (automatic migration will occur). /me now returns userdata. Speaking of migration, database now stores the latest version run to make future automigration easy. Database now saves hourly rather than minutely. Changed readme and some plugins to reflect above changes. Removed broken rockspec (Brayden, feel free to re-add once it's working). Added option to automatically block people (via drua) when blacklisted. Fixed about.lua trigger problems. administration 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. --- README.md | 66 ++- config.lua | 2 + drua-tg.lua | 8 + otouto-dev-1.rockspec | 22 - otouto/bot.lua | 58 ++- otouto/plugins/about.lua | 9 +- otouto/plugins/administration.lua | 733 ++++++++++++++++-------------- otouto/plugins/blacklist.lua | 10 +- otouto/plugins/control.lua | 2 +- otouto/plugins/greetings.lua | 8 +- otouto/plugins/lastfm.lua | 12 +- otouto/plugins/me.lua | 27 +- otouto/plugins/nick.lua | 29 +- otouto/plugins/remind.lua | 9 +- otouto/plugins/setandget.lua | 15 +- otouto/plugins/slap.lua | 57 ++- otouto/plugins/weather.lua | 1 + otouto/plugins/whoami.lua | 7 +- otouto/utilities.lua | 125 ++--- 19 files changed, 666 insertions(+), 534 deletions(-) delete mode 100644 otouto-dev-1.rockspec diff --git a/README.md b/README.md index 3bdcce7..37fa9ed 100755 --- a/README.md +++ b/README.md @@ -13,8 +13,9 @@ otouto is free software; you are free to redistribute it and/or modify it under |:----------------------------------------------|:------------------------------| | [Setup](#setup) | [Plugins](#plugins) | | [Control plugins](#control-plugins) | [Bindings](#bindings) | -| [Group Administration](#group-administration) | [Output style](#output-style) | -| [List of plugins](#list-of-plugins) | [Contributors](#contributors) | +| [Group administration](#group-administration) | [Database](#database) | +| [List of plugins](#list-of-plugins) | [Output style](#output-style) | +| | [Contributors](#contributors) | * * * @@ -201,21 +202,22 @@ otouto uses a robust plugin system, similar to yagop's [Telegram-Bot](http://git Most plugins are intended for public use, but a few are for other purposes, like those for [use by the bot's owner](#control-plugins). See [here](#list-of-plugins) for a list of plugins. -A plugin can have five components, and two of them are required: +There are five standard plugin components. -| Component | Description | Required? | -|:------------------|:---------------------------------------------|:----------| -| `plugin:action` | Main function. Expects `msg` table as an argument. | Y | -| `plugin.triggers` | Table of triggers for the plugin. Uses Lua patterns. | Y | -| `plugin:init` | Optional function run when the plugin is loaded. | N | -| `plugin:cron` | Optional function to be called every minute. | N | -| `plugin.command` | Basic command and syntax. Listed in the help text. | N | -| `plugin.doc` | Usage for the plugin. Returned by "/help $command". | N | -| `plugin.error` | Plugin-specific error message; false for no message. | N | +| Component | Description | +|:-----------|:-----------------------------------------------------| +| `action` | Main function. Expects `msg` table as an argument. | +| `triggers` | Table of triggers for the plugin. Uses Lua patterns. | +| `init` | Optional function run when the plugin is loaded. | +| `cron` | Optional function to be called every minute. | +| `command` | Basic command and syntax. Listed in the help text. | +| `doc` | Usage for the plugin. Returned by "/help $command". | +| `error` | Plugin-specific error message; false for no message. | -The `bot:on_msg_receive` function adds a few variables to the `msg` table for your convenience. These are self-explanatory: `msg.from.id_str`, `msg.to.id_str`, `msg.chat.id_str`, `msg.text_lower`, `msg.from.name`. -Return values from `plugin:action` are optional, but they do effect the flow. If it returns a table, that table will become `msg`, and `on_msg_receive` will continue with that. If it returns `true`, it will continue with the current `msg`. +No component is required, but some depend on others. For example, `action` will never be run if there's no `triggers`, and `doc` will never be seen if there's no `command`. + +Return values from `action` are optional, but they do affect the flow. If it returns a table, that table will become `msg`, and `on_msg_receive` will continue with that. If it returns `true`, it will continue with the current `msg`. When an action or cron function fails, the exception is caught and passed to the `handle_exception` utilty and is either printed to the console or send to the chat/channel defined in `log_chat` in config.lua. @@ -266,7 +268,7 @@ utilities.send_message(self, 987654321, 'Quick brown fox.', false, 54321, true) Uploading a file for the `sendPhoto` method would look like this: ``` -bindings.sendPhoto(self, { chat_id = 987654321 }, { photo = 'rarepepe.jpg' } ) +bindings.sendPhoto(self, { chat_id = 987654321 }, { photo = 'dankmeme.jpg' } ) ``` and using `sendPhoto` with a file ID would look like this: @@ -279,6 +281,40 @@ Upon success, bindings will return the deserialized result from the API. Upon fa * * * +## Database +otouto doesn't use one. This isn't because of dedication to lightweightedness or some clever design choice. Interfacing with databases through Lua is never a simple, easy-to-learn process. As one of the goals of otouto is that it should be a bot which is easy to write plugins for, our approach to storing data is to treat our datastore like any ordinary Lua data structure. The "database" is a table accessible in the `database` value of the bot instance (usually `self.database`), and is saved as a JSON-encoded plaintext file each hour, or when the bot is told to halt. This way, keeping and interacting with persistent data is no different than interacting with a Lua table -- with one exception: Keys in tables used as associative arrays must not be numbers. If the index keys are too sparse, the JSON encoder/decoder will either change them to keys or throw an error. + +Alone, the database will have this structure: + +``` +{ + users = { + ["55994550"] = { + id = 55994550, + first_name = "Drew", + username = "topkecleon" + } + }, + userdata = { + ["55994550"] = { + nickname = "Worst coder ever", + lastfm = "topkecleon" + } + }, + version = "3.11" +} +``` + +`database.users` will store user information (usernames, IDs, etc) when the bot sees the user. Each table's key is the user's ID as a string. + +`database.userdata` is meant to store miscellanea from various plugins. + +`database.version` stores the last bot version that used it. This is to simplify migration to the next version of the bot an easy, automatic process. + +Data from other plugins is usually saved in a table with the same name of that plugin. For example, administration.lua stores data in `database.administration`. + +* * * + ## Output style otouto plugins should maintain a consistent visual style in their output. This provides a recognizable and comfortable user experience. diff --git a/config.lua b/config.lua index b2be581..500e419 100755 --- a/config.lua +++ b/config.lua @@ -20,6 +20,8 @@ Send /help to get started. ]], -- The symbol that starts a command. Usually noted as '/' in documentation. cmd_pat = '/', + -- If drua is used, should a user be blocked when he's blacklisted? (and vice-versa) + drua_block_on_blacklist = false, -- https://datamarket.azure.com/dataset/bing/search bing_api_key = '', 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-dev-1.rockspec b/otouto-dev-1.rockspec deleted file mode 100644 index 86acac1..0000000 --- a/otouto-dev-1.rockspec +++ /dev/null @@ -1,22 +0,0 @@ -package = 'otouto' -version = 'dev-1' - -source = { - url = 'git://github.com/topkecleon/otouto.git' -} - -description = { - summary = 'The plugin-wielding, multipurpose Telegram bot!', - detailed = 'A plugin-wielding, multipurpose bot for the Telegram API.', - homepage = 'http://otou.to', - maintainer = 'Drew ', - license = 'GPL-2' -} - -dependencies = { - 'lua >= 5.2', - 'LuaSocket ~> 3.0', - 'LuaSec ~> 0.6', - 'dkjson ~> 2.5', - 'LPeg ~> 1.0' -} diff --git a/otouto/bot.lua b/otouto/bot.lua index 4986ecf..ea83a76 100755 --- a/otouto/bot.lua +++ b/otouto/bot.lua @@ -4,7 +4,7 @@ local bot = {} local bindings -- Load Telegram bindings. local utilities -- Load miscellaneous and cross-plugin functions. -bot.version = '3.10' +bot.version = '3.11' function bot:init(config) -- The function run when the bot is started or reloaded. @@ -29,7 +29,28 @@ function bot:init(config) -- The function run when the bot is started or reloade self.database = utilities.load_data(self.info.username..'.db') end - self.database.users = self.database.users or {} -- Table to cache userdata. + -- MIGRATION CODE 3.10 -> 3.11 + if self.database.users and self.database.version ~= '3.11' 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[tostring(self.info.id)] = self.info self.plugins = {} -- Load plugins. @@ -43,31 +64,38 @@ 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 function bot:on_msg_receive(msg, config) -- The fn run whenever a message is received. - -- 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.text = msg.text or msg.caption or '' + msg.text_lower = msg.text:lower() + + -- 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() end for _, plugin in ipairs(self.plugins) do - for _, trigger in pairs(plugin.triggers) do + for _, trigger in pairs(plugin.triggers or {}) do if string.match(msg.text_lower, trigger) then local success, result = pcall(function() return plugin.action(self, msg, config) @@ -116,7 +144,6 @@ function bot:run(config) if self.last_cron ~= os.date('%M') then -- Run cron jobs every minute. self.last_cron = os.date('%M') - utilities.save_data(self.info.username..'.db', self.database) -- Save the database. for i,v in ipairs(self.plugins) do if v.cron then -- Call each plugin's cron function, if it has one. local result, err = pcall(function() v.cron(self, config) end) @@ -127,6 +154,11 @@ function bot:run(config) 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. diff --git a/otouto/plugins/about.lua b/otouto/plugins/about.lua index a9fcf57..87b00ee 100755 --- a/otouto/plugins/about.lua +++ b/otouto/plugins/about.lua @@ -19,10 +19,11 @@ function about:action(msg, config) local output = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by 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 100755 --- 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 5ebcd16..a9bbaaf 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..'reload') then for pac, _ in pairs(package.loaded) do diff --git a/otouto/plugins/greetings.lua b/otouto/plugins/greetings.lua index f254f5d..a2252c4 100755 --- 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 100755 --- 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 100755 --- 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 ed982c0..8e55c50 100644 --- a/otouto/plugins/remind.lua +++ b/otouto/plugins/remind.lua @@ -40,13 +40,14 @@ function remind:action(msg) utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true) return end + local chat_id_str = tostring(msg.chat.id) -- Make a database entry for the group/user if one does not exist. - self.database.reminders[msg.chat.id_str] = self.database.reminders[msg.chat.id_str] or {} + self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {} -- Limit group reminders to 10 and private reminders to 50. - if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 9 then + if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) > 9 then utilities.send_reply(self, msg, 'Sorry, this group already has ten reminders.') return - elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then + elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) > 49 then utilities.send_reply(msg, 'Sorry, you already have fifty reminders.') return end @@ -55,7 +56,7 @@ function remind:action(msg) time = os.time() + duration * 60, message = message } - table.insert(self.database.reminders[msg.chat.id_str], reminder) + table.insert(self.database.reminders[chat_id_str], reminder) local output = 'I will remind you in ' .. duration if duration == 1 then output = output .. ' minute!' diff --git a/otouto/plugins/setandget.lua b/otouto/plugins/setandget.lua index 11f4e82..913003c 100644 --- a/otouto/plugins/setandget.lua +++ b/otouto/plugins/setandget.lua @@ -17,8 +17,9 @@ setandget.command = 'set ' function setandget:action(msg, config) + local chat_id_str = tostring(msg.chat.id) local input = utilities.input(msg.text) - self.database.setandget[msg.chat.id_str] = self.database.setandget[msg.chat.id_str] or {} + self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {} if msg.text_lower:match('^'..config.cmd_pat..'set') then @@ -33,10 +34,10 @@ function setandget:action(msg, config) if not name or not value then utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) elseif value == '--' or value == '—' then - self.database.setandget[msg.chat.id_str][name] = nil + self.database.setandget[chat_id_str][name] = nil utilities.send_message(self, msg.chat.id, 'That value has been deleted.') else - self.database.setandget[msg.chat.id_str][name] = value + self.database.setandget[chat_id_str][name] = value utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true) end @@ -44,11 +45,11 @@ function setandget:action(msg, config) if not input then local output - if utilities.table_size(self.database.setandget[msg.chat.id_str]) == 0 then + if utilities.table_size(self.database.setandget[chat_id_str]) == 0 then output = 'No values have been stored here.' else output = '*List of stored values:*\n' - for k,v in pairs(self.database.setandget[msg.chat.id_str]) do + for k,v in pairs(self.database.setandget[chat_id_str]) do output = output .. '• ' .. k .. ': `' .. v .. '`\n' end end @@ -57,8 +58,8 @@ function setandget:action(msg, config) end local output - if self.database.setandget[msg.chat.id_str][input:lower()] then - output = '`' .. self.database.setandget[msg.chat.id_str][input:lower()] .. '`' + if self.database.setandget[chat_id_str][input:lower()] then + output = '`' .. self.database.setandget[chat_id_str][input:lower()] .. '`' else output = 'There is no value stored by that name.' end diff --git a/otouto/plugins/slap.lua b/otouto/plugins/slap.lua index 5128434..d115d12 100755 --- 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 d437c46..ce9256d 100755 --- a/otouto/plugins/weather.lua +++ b/otouto/plugins/weather.lua @@ -1,6 +1,7 @@ local weather = {} local HTTP = require('socket.http') +HTTP.TIMEOUT = 2 local JSON = require('dkjson') local utilities = require('otouto.utilities') diff --git a/otouto/plugins/whoami.lua b/otouto/plugins/whoami.lua index 52b8ae5..ce068ff 100755 --- 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 95fd387..4fa6fba 100755 --- a/otouto/utilities.lua +++ b/otouto/utilities.lua @@ -73,49 +73,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'] = 'у', - ['!'] = 'ǃ' -} - - -- 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) @@ -179,9 +142,41 @@ 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 @@ -309,37 +304,6 @@ function utilities.with_http_timeout(timeout, fun) HTTP.TIMEOUT = original end -function utilities.enrich_user(user) - user.id_str = tostring(user.id) - user.name = utilities.build_name(user.first_name, user.last_name) - return user -end - -function utilities.enrich_message(msg) - if not msg.text then msg.text = msg.caption or '' end - msg.text_lower = msg.text:lower() - msg.from = utilities.enrich_user(msg.from) - msg.chat.id_str = tostring(msg.chat.id) - if msg.reply_to_message then - if not msg.reply_to_message.text then - msg.reply_to_message.text = msg.reply_to_message.caption or '' - end - msg.reply_to_message.text_lower = msg.reply_to_message.text:lower() - msg.reply_to_message.from = utilities.enrich_user(msg.reply_to_message.from) - msg.reply_to_message.chat.id_str = tostring(msg.reply_to_message.chat.id) - end - if msg.forward_from then - msg.forward_from = utilities.enrich_user(msg.forward_from) - end - if msg.new_chat_participant then - msg.new_chat_participant = utilities.enrich_user(msg.new_chat_participant) - end - if msg.left_chat_participant then - msg.left_chat_participant = utilities.enrich_user(msg.left_chat_participant) - end - return msg -end - function utilities.pretty_float(x) if x % 1 == 0 then return tostring(math.floor(x)) @@ -348,21 +312,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 = {