diff --git a/locales/en_GB.po b/locales/en_GB.po index e03b20bc0..b0c43938e 100644 --- a/locales/en_GB.po +++ b/locales/en_GB.po @@ -45,8 +45,10 @@ msgid "Please input a text" msgstr "Please input some text" #: lua/groupbutler/api_errors.lua:26 -msgid "This message is too long. Max lenght allowed by Telegram: 4000 characters" -msgstr "This message is too long. Max length allowed by Telegram is 4000 characters" +msgid "" +"This message is too long. Max lenght allowed by Telegram: 4000 characters" +msgstr "" +"This message is too long. Max length allowed by Telegram is 4000 characters" #: lua/groupbutler/api_errors.lua:27 msgid "One of the inline buttons you are trying to set is missing the URL" @@ -54,7 +56,9 @@ msgstr "" #: lua/groupbutler/api_errors.lua:28 #, lua-format -msgid "Inline link formatted incorrectly. Check the text between brackets -> \\[]()\n" +msgid "" +"Inline link formatted incorrectly. Check the text between brackets -> \\[]" +"()\n" "%s" msgstr "" @@ -63,44 +67,63 @@ msgid "More info [here](https://telegram.me/GB_tutorials/12)" msgstr "" #: lua/groupbutler/api_errors.lua:29 -msgid "This text breaks the markdown.\n" -"More info about a proper use of markdown [here](https://telegram.me/GB_tutorials/10) and [here](https://telegram.me/GB_tutorials/12)." +msgid "" +"This text breaks the markdown.\n" +"More info about a proper use of markdown [here](https://telegram.me/" +"GB_tutorials/10) and [here](https://telegram.me/GB_tutorials/12)." msgstr "" #: lua/groupbutler/api_errors.lua:31 -msgid "One of the URLs that should be placed in an inline button seems to be invalid (not an URL). Please check it" +msgid "" +"One of the URLs that should be placed in an inline button seems to be " +"invalid (not an URL). Please check it" msgstr "" #: lua/groupbutler/api_errors.lua:32 msgid "One of the inline buttons you are trying to set doesn't have a name" msgstr "" -#: lua/groupbutler/main.lua:128 +#: lua/groupbutler/main.lua:151 #, lua-format -msgid "Hello everyone!\n" -"My name is %s, and I'm a bot made to help administrators in their hard work.\n" -"Unfortunately I can't work in normal groups. If you need me, please ask the creator to convert this group to a supergroup and then add me again.\n" +msgid "" +"Hello everyone!\n" +"My name is %s, and I'm a bot made to help administrators in their hard " +"work.\n" +"Unfortunately I can't work in normal groups. If you need me, please ask the " +"creator to convert this group to a supergroup and then add me again.\n" msgstr "" -#: lua/groupbutler/main.lua:196 +#: lua/groupbutler/main.lua:219 msgid "🐞 Sorry, a *bug* occurred" msgstr "" +#: lua/groupbutler/message.lua:80 +msgid "Reply to a user or mention them" +msgstr "" + +#: lua/groupbutler/message.lua:83 +msgid "" +"I've never seen this user before.\n" +"This command works by reply, username, user ID or text mention.\n" +"If you're using it by username and want to teach me who the user is, forward " +"me one of their messages" +msgstr "" + #: lua/groupbutler/plugins/antispam.lua:43 #: lua/groupbutler/plugins/onmessage.lua:162 -#: lua/groupbutler/plugins/warn.lua:113 +#: lua/groupbutler/plugins/warn.lua:114 msgid "banned" msgstr "" #: lua/groupbutler/plugins/antispam.lua:44 #: lua/groupbutler/plugins/onmessage.lua:159 -#: lua/groupbutler/plugins/warn.lua:117 +#: lua/groupbutler/plugins/warn.lua:118 msgid "kicked" msgstr "" #: lua/groupbutler/plugins/antispam.lua:45 #: lua/groupbutler/plugins/onmessage.lua:165 -#: lua/groupbutler/plugins/warn.lua:121 +#: lua/groupbutler/plugins/warn.lua:122 msgid "muted" msgstr "" @@ -182,14 +205,15 @@ msgid "Allow/forbid forwarded messages from channels" msgstr "" #: lua/groupbutler/plugins/antispam.lua:204 -msgid "Set how many times the bot should warn users before kicking/banning them" +msgid "" +"Set how many times the bot should warn users before kicking/banning them" msgstr "" #: lua/groupbutler/plugins/antispam.lua:206 -#: lua/groupbutler/plugins/defaultpermissions.lua:66 -#: lua/groupbutler/plugins/floodmanager.lua:24 +#: lua/groupbutler/plugins/defaultpermissions.lua:68 +#: lua/groupbutler/plugins/floodmanager.lua:26 #: lua/groupbutler/plugins/logchannel.lua:37 -#: lua/groupbutler/plugins/menu.lua:42 +#: lua/groupbutler/plugins/menu.lua:44 #: lua/groupbutler/plugins/private_settings.lua:25 msgid "Description not available" msgstr "" @@ -207,20 +231,19 @@ msgid "Mute 👁" msgstr "" #: lua/groupbutler/plugins/antispam.lua:292 -#: lua/groupbutler/plugins/floodmanager.lua:144 #: lua/groupbutler/plugins/logchannel.lua:125 -#: lua/groupbutler/plugins/mediasettings.lua:115 -#: lua/groupbutler/plugins/menu.lua:258 msgid "You're no longer an admin" msgstr "" #: lua/groupbutler/plugins/antispam.lua:294 -msgid "*Anti-spam settings*\n" +msgid "" +"*Anti-spam settings*\n" "Choose which kind of message you want to forbid\n" "• ✅ = *Allowed*\n" "• ❌ = *Not allowed*\n" "• 🗑 = *Delete*\n" -"When set on `delete`, the bot doesn't warn users until they are about to be kicked/banned/muted (at the second-to-last warning)" +"When set on `delete`, the bot doesn't warn users until they are about to be " +"kicked/banned/muted (at the second-to-last warning)" msgstr "" #: lua/groupbutler/plugins/antispam.lua:362 @@ -229,7 +252,8 @@ msgstr "" #: lua/groupbutler/plugins/antispam.lua:365 #, lua-format -msgid "*Whitelist cleaned*\n" +msgid "" +"*Whitelist cleaned*\n" "%d links have been removed" msgstr "" @@ -248,17 +272,21 @@ msgstr "" #: lua/groupbutler/plugins/antispam.lua:378 #: lua/groupbutler/plugins/antispam.lua:409 #, lua-format -msgid "\n" +msgid "" +"\n" "%d links were already in the list" msgstr "" #: lua/groupbutler/plugins/antispam.lua:390 -msgid "_The whitelist is empty_.\n" +msgid "" +"_The whitelist is empty_.\n" "Use `/wl [links]` to add some links to the whitelist" msgstr "" #: lua/groupbutler/plugins/antispam.lua:392 -msgid "Whitelisted links:\n\n" +msgid "" +"Whitelisted links:\n" +"\n" msgstr "" #: lua/groupbutler/plugins/antispam.lua:407 @@ -272,7 +300,8 @@ msgstr "" #: lua/groupbutler/plugins/antispam.lua:426 #, lua-format -msgid "*Whitelisted channels:*\n" +msgid "" +"*Whitelisted channels:*\n" "%s" msgstr "" @@ -283,7 +312,9 @@ msgstr "" #: lua/groupbutler/plugins/backup.lua:100 #, lua-format -msgid "I'm sorry, this command has been used for the last time less then 3 hours ago by %s (ask them for the file).\n" +msgid "" +"I'm sorry, this command has been used for the last time less then 3 hours " +"ago by %s (ask them for the file).\n" "Wait [%s] to use it again\n" msgstr "" @@ -292,7 +323,8 @@ msgid "*Sent in private*" msgstr "" #: lua/groupbutler/plugins/backup.lua:120 -msgid "Invalid input. Please reply to the backup file (/snap command to get it)" +msgid "" +"Invalid input. Please reply to the backup file (/snap command to get it)" msgstr "" #: lua/groupbutler/plugins/backup.lua:125 @@ -301,7 +333,8 @@ msgstr "" #: lua/groupbutler/plugins/backup.lua:130 #, lua-format -msgid "This is not a valid backup file.\n" +msgid "" +"This is not a valid backup file.\n" "Reason: invalid name (%s)" msgstr "" @@ -316,94 +349,100 @@ msgid "Chat IDs don't match (%s and %s)" msgstr "" #: lua/groupbutler/plugins/backup.lua:172 -msgid "Import was successful.\n\n" +msgid "" +"Import was successful.\n" +"\n" "Important:\n" -"- #extra commands which are associated with a media must be set again if the bot you are using now is different from the bot that originated the backup.\n" +"- #extra commands which are associated with a media must be set again if the " +"bot you are using now is different from the bot that originated the backup.\n" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:89 -msgid "Use -/+ to edit the value, then select a timeframe to temporary ban the user" +#: lua/groupbutler/plugins/banhammer.lua:90 +msgid "" +"Use -/+ to edit the value, then select a timeframe to temporary ban the user" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:96 +#: lua/groupbutler/plugins/banhammer.lua:106 #, lua-format msgid "%s kicked %s!" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:105 -#: lua/groupbutler/plugins/banhammer.lua:118 +#: lua/groupbutler/plugins/banhammer.lua:120 +#: lua/groupbutler/plugins/banhammer.lua:139 #, lua-format msgid "%s banned %s!" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:112 +#: lua/groupbutler/plugins/banhammer.lua:125 msgid "_Use this command in reply to a forwarded message_" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:126 +#: lua/groupbutler/plugins/banhammer.lua:144 msgid "_An admin can't be unbanned_" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:135 +#: lua/groupbutler/plugins/banhammer.lua:148 msgid "This user is not banned!" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:140 +#: lua/groupbutler/plugins/banhammer.lua:162 #, lua-format msgid "%s unbanned by %s!" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:152 -#: lua/groupbutler/plugins/defaultpermissions.lua:127 +#: lua/groupbutler/plugins/banhammer.lua:174 +#: lua/groupbutler/plugins/defaultpermissions.lua:135 msgid "Sorry, you don't have permission to restrict members" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:156 -msgid "Tap on the -/+ buttons to change this value. Then select a timeframe to execute the ban" +#: lua/groupbutler/plugins/banhammer.lua:180 +msgid "" +"Tap on the -/+ buttons to change this value. Then select a timeframe to " +"execute the ban" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:165 +#: lua/groupbutler/plugins/banhammer.lua:196 msgid "You can't set a lower value" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:173 +#: lua/groupbutler/plugins/banhammer.lua:204 msgid "Stop!!!" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:189 +#: lua/groupbutler/plugins/banhammer.lua:221 msgid "hours" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:193 +#: lua/groupbutler/plugins/banhammer.lua:225 msgid "days" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:197 +#: lua/groupbutler/plugins/banhammer.lua:229 msgid "minutes" msgstr "" -#: lua/groupbutler/plugins/banhammer.lua:203 +#: lua/groupbutler/plugins/banhammer.lua:237 #, lua-format msgid "User banned for %d %s" msgstr "" -#: lua/groupbutler/plugins/configure.lua:19 +#: lua/groupbutler/plugins/configure.lua:20 msgid "🛠 Menu" msgstr "" -#: lua/groupbutler/plugins/configure.lua:20 +#: lua/groupbutler/plugins/configure.lua:21 msgid "⚡️ Antiflood" msgstr "" -#: lua/groupbutler/plugins/configure.lua:21 +#: lua/groupbutler/plugins/configure.lua:22 msgid "🌈 Media" msgstr "" -#: lua/groupbutler/plugins/configure.lua:22 +#: lua/groupbutler/plugins/configure.lua:23 msgid "🚫 Antispam" msgstr "" -#: lua/groupbutler/plugins/configure.lua:23 +#: lua/groupbutler/plugins/configure.lua:24 msgid "📥 Log channel" msgstr "" @@ -411,76 +450,75 @@ msgstr "" msgid "⛔️ Default permissions" msgstr "" -#: lua/groupbutler/plugins/configure.lua:45 -#: lua/groupbutler/plugins/configure.lua:68 +#: lua/groupbutler/plugins/configure.lua:33 msgid "Change the settings of your group" msgstr "" -#: lua/groupbutler/plugins/configure.lua:58 +#: lua/groupbutler/plugins/configure.lua:66 msgid "_I've sent you the keyboard via private message_" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:20 -#: lua/groupbutler/plugins/floodmanager.lua:36 +#: lua/groupbutler/plugins/floodmanager.lua:38 msgid "✅ | ON" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:22 -#: lua/groupbutler/plugins/floodmanager.lua:38 +#: lua/groupbutler/plugins/floodmanager.lua:40 msgid "❌ | OFF" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:29 -#: lua/groupbutler/plugins/menu.lua:160 lua/groupbutler/plugins/menu.lua:224 +#: lua/groupbutler/plugins/menu.lua:162 lua/groupbutler/plugins/menu.lua:226 msgid "👞 kick" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:31 -#: lua/groupbutler/plugins/menu.lua:162 +#: lua/groupbutler/plugins/menu.lua:164 msgid "🔨 ban" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:33 -#: lua/groupbutler/plugins/floodmanager.lua:49 -#: lua/groupbutler/plugins/menu.lua:164 lua/groupbutler/plugins/menu.lua:228 +#: lua/groupbutler/plugins/floodmanager.lua:51 +#: lua/groupbutler/plugins/menu.lua:166 lua/groupbutler/plugins/menu.lua:230 msgid "👁 mute" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:38 -#: lua/groupbutler/plugins/floodmanager.lua:67 +#: lua/groupbutler/plugins/floodmanager.lua:69 msgid "Texts" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:39 -#: lua/groupbutler/plugins/floodmanager.lua:68 +#: lua/groupbutler/plugins/floodmanager.lua:70 msgid "Forwards" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:40 #: lua/groupbutler/plugins/dashboard.lua:167 -#: lua/groupbutler/plugins/floodmanager.lua:69 -#: lua/groupbutler/plugins/mediasettings.lua:42 +#: lua/groupbutler/plugins/floodmanager.lua:71 +#: lua/groupbutler/plugins/mediasettings.lua:44 msgid "Stickers" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:41 #: lua/groupbutler/plugins/dashboard.lua:159 -#: lua/groupbutler/plugins/floodmanager.lua:70 -#: lua/groupbutler/plugins/mediasettings.lua:33 +#: lua/groupbutler/plugins/floodmanager.lua:72 +#: lua/groupbutler/plugins/mediasettings.lua:35 msgid "Images" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:42 #: lua/groupbutler/plugins/dashboard.lua:160 -#: lua/groupbutler/plugins/floodmanager.lua:71 -#: lua/groupbutler/plugins/mediasettings.lua:34 +#: lua/groupbutler/plugins/floodmanager.lua:73 +#: lua/groupbutler/plugins/mediasettings.lua:36 msgid "GIFs" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:43 #: lua/groupbutler/plugins/dashboard.lua:161 -#: lua/groupbutler/plugins/floodmanager.lua:72 -#: lua/groupbutler/plugins/mediasettings.lua:35 +#: lua/groupbutler/plugins/floodmanager.lua:74 +#: lua/groupbutler/plugins/mediasettings.lua:37 msgid "Videos" msgstr "" @@ -501,7 +539,8 @@ msgstr "" #: lua/groupbutler/plugins/dashboard.lua:60 #, lua-format -msgid "- *Ignored media*:\n" +msgid "" +"- *Ignored media*:\n" "%s" msgstr "" @@ -514,7 +553,7 @@ msgid "Admins" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:71 -#: lua/groupbutler/plugins/menu.lua:181 lua/groupbutler/utilities.lua:592 +#: lua/groupbutler/plugins/menu.lua:183 lua/groupbutler/utilities.lua:474 msgid "Rules" msgstr "" @@ -544,7 +583,9 @@ msgid "🚫 This group does not exist" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:126 -msgid "🚷 You are not a member of the chat. You can't see the settings of a private group." +msgid "" +"🚷 You are not a member of the chat. You can't see the settings of a private " +"group." msgstr "" #: lua/groupbutler/plugins/dashboard.lua:133 @@ -572,7 +613,7 @@ msgid "ℹ️ Group ► Flood" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:162 -#: lua/groupbutler/plugins/mediasettings.lua:37 +#: lua/groupbutler/plugins/mediasettings.lua:39 msgid "Documents" msgstr "" @@ -581,85 +622,100 @@ msgid "Voice Messages" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:165 -#: lua/groupbutler/plugins/mediasettings.lua:40 +#: lua/groupbutler/plugins/mediasettings.lua:42 msgid "Links" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:166 -#: lua/groupbutler/plugins/mediasettings.lua:41 +#: lua/groupbutler/plugins/mediasettings.lua:43 msgid "Music" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:168 -#: lua/groupbutler/plugins/mediasettings.lua:43 +#: lua/groupbutler/plugins/mediasettings.lua:45 msgid "Contacts" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:169 -#: lua/groupbutler/plugins/mediasettings.lua:44 +#: lua/groupbutler/plugins/mediasettings.lua:46 msgid "Games" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:170 -#: lua/groupbutler/plugins/mediasettings.lua:45 +#: lua/groupbutler/plugins/mediasettings.lua:47 msgid "Locations" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:171 -#: lua/groupbutler/plugins/mediasettings.lua:46 +#: lua/groupbutler/plugins/mediasettings.lua:48 msgid "Venues" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:173 -msgid "*Current media settings*:\n\n" +msgid "" +"*Current media settings*:\n" +"\n" msgstr "" #: lua/groupbutler/plugins/dashboard.lua:185 msgid "ℹ️ Group ► Media" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:62 -msgid "Permission to send messages. If disabled, the user won't be able to send any kind of message" +#: lua/groupbutler/plugins/defaultpermissions.lua:64 +msgid "" +"Permission to send messages. If disabled, the user won't be able to send any " +"kind of message" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:63 -msgid "Permission to send media (audios, documents, photos, videos, video notes and voice notes). Implies the permission to send messages" +#: lua/groupbutler/plugins/defaultpermissions.lua:65 +msgid "" +"Permission to send media (audios, documents, photos, videos, video notes and " +"voice notes). Implies the permission to send messages" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:64 -msgid "Permission to send other types of messages (GIFs, games, stickers and use inline bots). Implies the permission to send medias" +#: lua/groupbutler/plugins/defaultpermissions.lua:66 +msgid "" +"Permission to send other types of messages (GIFs, games, stickers and use " +"inline bots). Implies the permission to send medias" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:65 -msgid "When disabled, user's messages with a link won't show the web page preview" +#: lua/groupbutler/plugins/defaultpermissions.lua:67 +msgid "" +"When disabled, user's messages with a link won't show the web page preview" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:74 +#: lua/groupbutler/plugins/defaultpermissions.lua:76 msgid "Send messages" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:75 +#: lua/groupbutler/plugins/defaultpermissions.lua:77 msgid "Send media" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:76 +#: lua/groupbutler/plugins/defaultpermissions.lua:78 msgid "Send other types of media" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:77 +#: lua/groupbutler/plugins/defaultpermissions.lua:79 msgid "Show web page preview" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:129 -msgid "*Default permissions*\n" -"From this menu you can change the default permissions that will be granted when a new member join.\n" -"_Only the administrators with the permission to restrict a member can access this menu._\n" -"Tap on the name of a permission for a description of what kind of messages it will influence.\n" +#: lua/groupbutler/plugins/defaultpermissions.lua:139 +msgid "" +"*Default permissions*\n" +"From this menu you can change the default permissions that will be granted " +"when a new member join.\n" +"_Only the administrators with the permission to restrict a member can access " +"this menu._\n" +"Tap on the name of a permission for a description of what kind of messages " +"it will influence.\n" msgstr "" -#: lua/groupbutler/plugins/defaultpermissions.lua:151 +#: lua/groupbutler/plugins/defaultpermissions.lua:161 #, lua-format -msgid "Setting saved, but I can't edit the buttons because you are too fast! Wait other %d seconds" +msgid "" +"Setting saved, but I can't edit the buttons because you are too fast! Wait " +"other %d seconds" msgstr "" #: lua/groupbutler/plugins/extra.lua:81 @@ -679,7 +735,8 @@ msgstr "" #: lua/groupbutler/plugins/extra.lua:119 #, lua-format -msgid "\n" +msgid "" +"\n" "Commands not found: `%s`" msgstr "" @@ -688,57 +745,68 @@ msgstr "" msgid "_Please_ [start me](%s) _so I can send you the answer_" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:18 +#: lua/groupbutler/plugins/floodmanager.lua:20 msgid "⚖ Current sensitivity. Tap on the + or the - to change it" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:20 -msgid "Choose which media must be ignored by the antiflood (the bot won't consider them).\n" +#: lua/groupbutler/plugins/floodmanager.lua:22 +msgid "" +"Choose which media must be ignored by the antiflood (the bot won't consider " +"them).\n" "✅: ignored\n" "❌: not ignored" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:45 +#: lua/groupbutler/plugins/floodmanager.lua:47 msgid "👞️ kick" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:47 +#: lua/groupbutler/plugins/floodmanager.lua:49 msgid "🔨 ️ban" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:106 +#: lua/groupbutler/plugins/floodmanager.lua:108 msgid "Flooders will be banned" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:109 +#: lua/groupbutler/plugins/floodmanager.lua:111 msgid "Flooders will be kicked" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:112 +#: lua/groupbutler/plugins/floodmanager.lua:114 msgid "Flooders will be muted" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:121 -#: lua/groupbutler/plugins/floodmanager.lua:128 +#: lua/groupbutler/plugins/floodmanager.lua:123 +#: lua/groupbutler/plugins/floodmanager.lua:130 #, lua-format msgid "%d is not a valid value!\n" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:146 -msgid "You can manage the antiflood settings from here.\n\n" -"It is also possible to choose which type of messages the antiflood will ignore (✅)" +#: lua/groupbutler/plugins/floodmanager.lua:157 +#: lua/groupbutler/plugins/mediasettings.lua:127 +#: lua/groupbutler/plugins/menu.lua:271 +msgid "Sorry, you don't have permission to change settings" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:153 -msgid "Antiflood settings" +#: lua/groupbutler/plugins/floodmanager.lua:161 +msgid "" +"You can manage the antiflood settings from here.\n" +"\n" +"It is also possible to choose which type of messages the antiflood will " +"ignore (✅)" msgstr "" #: lua/groupbutler/plugins/floodmanager.lua:168 +msgid "Antiflood settings" +msgstr "" + +#: lua/groupbutler/plugins/floodmanager.lua:177 #, lua-format msgid "❎ [%s] will be ignored by the anti-flood" msgstr "" -#: lua/groupbutler/plugins/floodmanager.lua:171 +#: lua/groupbutler/plugins/floodmanager.lua:180 #, lua-format msgid "🚫 [%s] won't be ignored by the anti-flood" msgstr "" @@ -749,33 +817,54 @@ msgstr "" #: lua/groupbutler/plugins/help.lua:23 #, lua-format -msgid "Hello %s 👋🏼, nice to meet you!\n" -"I'm Group Butler, the first administration bot using the official Bot API.\n\n" +msgid "" +"Hello %s 👋🏼, nice to meet you!\n" +"I'm Group Butler, the first administration bot using the official Bot API.\n" +"\n" "*I can do a lot of cool stuffs*, here's a short list:\n" "• I can *kick or ban* users\n" "• You can use me to set the group rules\n" "• I have a flexible *anti-flood* system\n" -"• I can *welcome new users* with a customizable message, or if you want with a gif or a sticker\n" -"• I can *warn* users, and ban them when they reach the maximum number of warnings\n" +"• I can *welcome new users* with a customizable message, or if you want with " +"a gif or a sticker\n" +"• I can *warn* users, and ban them when they reach the maximum number of " +"warnings\n" "• I can also warn, kick or ban users when they post a specific media\n" -"…and more, below you can find the \"all commands\" button to get the whole list!\n\n" -"I work better if you add me to the group administrators (otherwise I won't be able to kick or ban)!" +"…and more, below you can find the \"all commands\" button to get the whole " +"list!\n" +"\n" +"I work better if you add me to the group administrators (otherwise I won't " +"be able to kick or ban)!" msgstr "" #: lua/groupbutler/plugins/help.lua:36 -msgid "This bot works only in supergroups.\n\n" -"To work properly, [it needs to be admin in your group](https://telegram.me/GroupButler_ch/104), so it can kick or ban people if needed.\n" -"Only the group owner can promote it :)\n\n" -"You can use `/, ! or #` to trigger a command.\n\n" -"Group Butler saves the adminlist of a group in its databse to avoid to send too many requests to Telegram.\n" -"This list is updated every 5 hours, so there could be some differences between who the bot thinks are the admins and who the admins actually are, if during the 5 hours timeframe some users have been promoted/demoted.\n" -"It's possible to force the bot to update its adminlist with `/cache`.\n\n" -"Remember: you have to use commands *in the group*, unless they are specifically designed for private chats (see \"private\" tab)." +msgid "" +"This bot works only in supergroups.\n" +"\n" +"To work properly, [it needs to be admin in your group](https://telegram.me/" +"GroupButler_ch/104), so it can kick or ban people if needed.\n" +"Only the group owner can promote it :)\n" +"\n" +"You can use `/, ! or #` to trigger a command.\n" +"\n" +"Group Butler saves the adminlist of a group in its databse to avoid to send " +"too many requests to Telegram.\n" +"This list is updated every 5 hours, so there could be some differences " +"between who the bot thinks are the admins and who the admins actually are, " +"if during the 5 hours timeframe some users have been promoted/demoted.\n" +"It's possible to force the bot to update its adminlist with `/cache`.\n" +"\n" +"Remember: you have to use commands *in the group*, unless they are " +"specifically designed for private chats (see \"private\" tab)." msgstr "" #: lua/groupbutler/plugins/help.lua:48 -msgid "*Commands that work in private*:\n\n" -"• `/mysettings`: show a keyboard that allows you to change your personal settings, such as choosing if receive the rules in private when you join a group or if receive reports made with the `@admin` command\n" +msgid "" +"*Commands that work in private*:\n" +"\n" +"• `/mysettings`: show a keyboard that allows you to change your personal " +"settings, such as choosing if receive the rules in private when you join a " +"group or if receive reports made with the `@admin` command\n" "• `/echo [text]` : the bot will send the text back, formatted with markdown\n" "• `/about` : show some useful informations about the bot\n" "• `/groups` : show the list of the discussion groups\n" @@ -785,175 +874,315 @@ msgid "*Commands that work in private*:\n\n" msgstr "" #: lua/groupbutler/plugins/help.lua:57 -msgid "*Commands available for every user in a group*:\n\n" +msgid "" +"*Commands available for every user in a group*:\n" +"\n" "• `/dashboard`: see all the informations about the group\n" "• `/rules`: show the group rules\n" "• `/adminlist`: show the administrators of the group\n" "• `/help`: receive the help message\n" "• `!kickme`: the bot will kick you\n" -"*Note*: `/dashboard`, `/adminlist` and `/staff` always reply in private. If the bot is unable to reach a user, it will ask in the group to that user to be started, but just if _silent mode_ is off.\n" -"With `/rules`, the bot always answer in the group for admins, but with normal users the message is sent in the group or in private according to the group settings.\n\n" -"• `@admin` (by reply): report a message to the admins of the group (the bot will forward it in prvate). This ability could be turned off from the group settings. A description of the report can be added.\n" -"Admins need to give their consense to receive reports from users, with `/mysettings` command" +"*Note*: `/dashboard`, `/adminlist` and `/staff` always reply in private. If " +"the bot is unable to reach a user, it will ask in the group to that user to " +"be started, but just if _silent mode_ is off.\n" +"With `/rules`, the bot always answer in the group for admins, but with " +"normal users the message is sent in the group or in private according to the " +"group settings.\n" +"\n" +"• `@admin` (by reply): report a message to the admins of the group (the bot " +"will forward it in prvate). This ability could be turned off from the group " +"settings. A description of the report can be added.\n" +"Admins need to give their consense to receive reports from users, with `/" +"mysettings` command" msgstr "" #: lua/groupbutler/plugins/help.lua:69 -msgid "*Admins: info about the group*\n\n" -"• `/setrules [group rules]`: set the new regulation for the group (the old will be overwritten).\n" -"• `/setrules -`: delete the current rules.\n\n" -"*Note*: the markdown is supported. If the text sent breaks the markdown, the bot will notify that something is wrong.\n" -"For a correct use of the markdown, check [this post](https://telegram.me/GroupButler_ch/46) in the channel\n\n" -"• `/setlink [link|-]`: set the group link, so it can be re-called by other admins, or unset it.\n" -"If you are going to use it in a public supergroup, you do not need to append the group link. Just send `/setlink`\n" +msgid "" +"*Admins: info about the group*\n" +"\n" +"• `/setrules [group rules]`: set the new regulation for the group (the old " +"will be overwritten).\n" +"• `/setrules -`: delete the current rules.\n" +"\n" +"*Note*: the markdown is supported. If the text sent breaks the markdown, the " +"bot will notify that something is wrong.\n" +"For a correct use of the markdown, check [this post](https://telegram.me/" +"GroupButler_ch/46) in the channel\n" +"\n" +"• `/setlink [link|-]`: set the group link, so it can be re-called by other " +"admins, or unset it.\n" +"If you are going to use it in a public supergroup, you do not need to append " +"the group link. Just send `/setlink`\n" "• `/link`: get the group link, if already set.\n" -"• `/msglink`: get the link to a message. Works only in public supergroups\n\n" -"*Note*: the bot can recognize valid group links. If a link is not valid, you won't receive a reply." +"• `/msglink`: get the link to a message. Works only in public supergroups\n" +"\n" +"*Note*: the bot can recognize valid group links. If a link is not valid, you " +"won't receive a reply." msgstr "" #: lua/groupbutler/plugins/help.lua:83 -msgid "*Banhammer powers*\n" -"A set of commands that let admins kick and ban people from a group, and get some information about a user.\n" -"Kicked people can join back, banned people can't. Banned users are added to the group's blacklist. It's possible to blacklist users even if they are not part of the group.\n" -"Only the administrators who have the permission to restrict users can use these commands, but `/status` can be used by all the admins.\n\n" +msgid "" +"*Banhammer powers*\n" +"A set of commands that let admins kick and ban people from a group, and get " +"some information about a user.\n" +"Kicked people can join back, banned people can't. Banned users are added to " +"the group's blacklist. It's possible to blacklist users even if they are not " +"part of the group.\n" +"Only the administrators who have the permission to restrict users can use " +"these commands, but `/status` can be used by all the admins.\n" +"\n" "• `/kick [by reply|username|id|text mention]`: kick a user from the group.\n" "• `/ban [by reply|username|id|text mention]`: ban a user from the group.\n" -"• `/tempban [by reply|username|id|text mention]`: ban a user for a specific amount of time. Use the returned keyboard to ban the user.\n" -"Pass a value on a new line to use it as starting value. When a ban expires, the user won't be added back. Check the Telegram's restricted users list for pending unbans.\n" +"• `/tempban [by reply|username|id|text mention]`: ban a user for a specific " +"amount of time. Use the returned keyboard to ban the user.\n" +"Pass a value on a new line to use it as starting value. When a ban expires, " +"the user won't be added back. Check the Telegram's restricted users list for " +"pending unbans.\n" "• `/fwdban [by reply]`: ban the original sender of a forwarded message.\n" -"• `/unban [by reply|username|id|text mention]`: unban the user from the group.\n" -"• `/user [by reply|username|id|text mention]`: shows how many times the user has been banned *in all the groups*, and the warns received.\n" -"• `/status [username|id]`: show the current status of the user `(member|restricted|kicked/left the chat|banned|admin/creator|never seen)`.\n" -"Will also show the permissions the user *doesn't* have.\n\n" +"• `/unban [by reply|username|id|text mention]`: unban the user from the " +"group.\n" +"• `/user [by reply|username|id|text mention]`: shows how many times the user " +"has been banned *in all the groups*, and the warns received.\n" +"• `/status [username|id]`: show the current status of the user `(member|" +"restricted|kicked/left the chat|banned|admin/creator|never seen)`.\n" +"Will also show the permissions the user *doesn't* have.\n" +"\n" "*Antiflood*\n" -"The \"antiflood\" is a system that auto-removes people that send many consecutive messages in a group.\n" -"If on, the antiflood system will kick/ban flooders.\n\n" -"• `/config` command, then `antiflood` button: manage the flood settings in private, with an inline keyboard. You can change the sensitivity, the action (kick/ban) to perform, and even set some exceptions." +"The \"antiflood\" is a system that auto-removes people that send many " +"consecutive messages in a group.\n" +"If on, the antiflood system will kick/ban flooders.\n" +"\n" +"• `/config` command, then `antiflood` button: manage the flood settings in " +"private, with an inline keyboard. You can change the sensitivity, the action " +"(kick/ban) to perform, and even set some exceptions." msgstr "" #: lua/groupbutler/plugins/help.lua:103 -msgid "*Reports settings*\n" -"`@admin` is an useful command to let users report some messages to the group admins.\n" -"A reported message will be forwarded to the available admins.\n\n" -"• `/config` command, then `menu` button: here you can find an option, \"Report\". If turned on, users will be able to use the `@admin` command.\n" -"Only admins who accepted to receive reports (with `/mysettings` command) will be notified\n" -"• `/mysettings` (in private): from here, you can choose if receive reports or not\n\n" -"*Note*: admins can't use the `@admin` command, and users can't report admins with it." +msgid "" +"*Reports settings*\n" +"`@admin` is an useful command to let users report some messages to the group " +"admins.\n" +"A reported message will be forwarded to the available admins.\n" +"\n" +"• `/config` command, then `menu` button: here you can find an option, " +"\"Report\". If turned on, users will be able to use the `@admin` command.\n" +"Only admins who accepted to receive reports (with `/mysettings` command) " +"will be notified\n" +"• `/mysettings` (in private): from here, you can choose if receive reports " +"or not\n" +"\n" +"*Note*: admins can't use the `@admin` command, and users can't report admins " +"with it." msgstr "" #: lua/groupbutler/plugins/help.lua:112 -msgid "*Welcome/goodbye settings*\n\n" -"• `/config`, then `menu` tab: receive in private the menu keyboard. You will find an option to enable/disable welcome/goodbye messages.\n" -"*Note*: goodbye messages don't work in large groups. This is a Telegram limitation that can't be avoided.\n\n" +msgid "" +"*Welcome/goodbye settings*\n" +"\n" +"• `/config`, then `menu` tab: receive in private the menu keyboard. You will " +"find an option to enable/disable welcome/goodbye messages.\n" +"*Note*: goodbye messages don't work in large groups. This is a Telegram " +"limitation that can't be avoided.\n" +"\n" "*Custom welcome message*:\n" "• `/welcome Welcome $name, enjoy the group!`\n" -"Write after `/welcome` your welcome message. `/goodbye` works in the same way.\n\n" -"You can use some placeholders to include the name/username/id of the new member of the group\n" +"Write after `/welcome` your welcome message. `/goodbye` works in the same " +"way.\n" +"\n" +"You can use some placeholders to include the name/username/id of the new " +"member of the group\n" "Placeholders:\n" "`$username`: _will be replaced with the username_\n" "`$name`: _will be replaced with the name_\n" "`$id`: _will be replaced with the id_\n" "`$title`: _will be replaced with the group title_\n" "`$surname`: _will be replaced by the user's last name_\n" -"`$rules`: _will be replaced by a link to the rules of the group. Please read_ [here](https://telegram.me/GroupButler_beta/26) _how to use it, or you will get an error for sure_\n" -"*Note*: `$name`, `$surname`, and `$title` may not work properly within markdown markup.\n\n" +"`$rules`: _will be replaced by a link to the rules of the group. Please " +"read_ [here](https://telegram.me/GroupButler_beta/26) _how to use it, or you " +"will get an error for sure_\n" +"*Note*: `$name`, `$surname`, and `$title` may not work properly within " +"markdown markup.\n" +"\n" "*GIF/sticker as welcome message*\n" -"You can use a particular gif/sticker as welcome message. To set it, reply to the gif/sticker you want to set as welcome message with `/welcome`. Same goes for `/goodbye`" +"You can use a particular gif/sticker as welcome message. To set it, reply to " +"the gif/sticker you want to set as welcome message with `/welcome`. Same " +"goes for `/goodbye`" msgstr "" #: lua/groupbutler/plugins/help.lua:133 -msgid "*Whitelist settings*\n\n" -"As you may know, the bot can warn/kick/ban who sends a telegram.me link (antispam settings) or any other link (media settings).\n" +msgid "" +"*Whitelist settings*\n" +"\n" +"As you may know, the bot can warn/kick/ban who sends a telegram.me link " +"(antispam settings) or any other link (media settings).\n" "The whitelist is a list of links that will be ignored by the bot.\n" -"If users send a whitelisted link, they won't be warned or kicked.\n\n" -"`/whitelist [link(s)]` or `/wl [link(s)]`: add one or more links to the whitelist.\n" -"`/unwhitelist [link(s)]` or `/unwl [link(s)]`: remove one or more links from the whitelist.\n" +"If users send a whitelisted link, they won't be warned or kicked.\n" +"\n" +"`/whitelist [link(s)]` or `/wl [link(s)]`: add one or more links to the " +"whitelist.\n" +"`/unwhitelist [link(s)]` or `/unwl [link(s)]`: remove one or more links from " +"the whitelist.\n" "`/whitelist` or `/wl`: get the whitelist.\n" -"`/whitelistl -` or `/wl -`: empty the whitelist.\n\n" -"When the group link is saved with `/setlink`, it gets automatically added to the whitelist.\n\n" +"`/whitelistl -` or `/wl -`: empty the whitelist.\n" +"\n" +"When the group link is saved with `/setlink`, it gets automatically added to " +"the whitelist.\n" +"\n" "*Why links are saved without* _https://_ *and* _www_*?*\n" -"The bot auto-removes _https://, http:// and www_ from every link to reduce the possibility of having the same link saved twice." +"The bot auto-removes _https://, http:// and www_ from every link to reduce " +"the possibility of having the same link saved twice." msgstr "" #: lua/groupbutler/plugins/help.lua:148 -msgid "*Extra commands*\n" -"#extra commands are a smart way to save your own custom commands.\n\n" -"• `/extra [#trigger] [reply]`: set a reply to be sent when someone writes the trigger.\n" -"_Example_ : with \"`/extra #hello Good morning!`\", the bot will reply \"Good morning!\" each time someone writes #hello.\n" -"You can reply to a media (_photo, file, vocal, video, gif, audio_) with `/extra #yourtrigger` to save the #extra and receive that media each time you use # command\n" +msgid "" +"*Extra commands*\n" +"#extra commands are a smart way to save your own custom commands.\n" +"\n" +"• `/extra [#trigger] [reply]`: set a reply to be sent when someone writes " +"the trigger.\n" +"_Example_ : with \"`/extra #hello Good morning!`\", the bot will reply " +"\"Good morning!\" each time someone writes #hello.\n" +"You can reply to a media (_photo, file, vocal, video, gif, audio_) with `/" +"extra #yourtrigger` to save the #extra and receive that media each time you " +"use # command\n" "• `/extra list`: get the list of your custom commands.\n" -"• `/extra del [#trigger]`: delete the trigger and its message.\n\n" -"*Note:* the markdown is supported. If the text sent breaks the markdown, the bot will notify that something is wrong.\n" -"For a correct use of the markdown, check [this post](https://telegram.me/GroupButler_ch/46) in the channel.\n" -"Now supports placeholders. Check the \"welcome\" tab for the list of the available placeholders" +"• `/extra del [#trigger]`: delete the trigger and its message.\n" +"\n" +"*Note:* the markdown is supported. If the text sent breaks the markdown, the " +"bot will notify that something is wrong.\n" +"For a correct use of the markdown, check [this post](https://telegram.me/" +"GroupButler_ch/46) in the channel.\n" +"Now supports placeholders. Check the \"welcome\" tab for the list of the " +"available placeholders" msgstr "" #: lua/groupbutler/plugins/help.lua:160 -msgid "*Warns*\n" -"Warn are made to keep the count of the admonitions received by a user. Once users have been warned for the defined number of times, they are kicked/banned by the bot.\n" +msgid "" +"*Warns*\n" +"Warn are made to keep the count of the admonitions received by a user. Once " +"users have been warned for the defined number of times, they are kicked/" +"banned by the bot.\n" "There are two different type of warns:\n" "- _normal warns_, given by an admin with the `/warn` command\n" -"- _automatic warns_ (read: media warns and spam warns), given by the bot when someone sends a media that is not allowed in the chat, or spams other channels or telegram.me links.\n\n" +"- _automatic warns_ (read: media warns and spam warns), given by the bot " +"when someone sends a media that is not allowed in the chat, or spams other " +"channels or telegram.me links.\n" +"\n" "• `/warn [by reply]`: warn a user\n" -"• `/sw`: you can place a `/sw` (_\"silent warn\"_) everywhere you want in your message. The bot will silently count the warn, but won't answer in the group unless the user reached the max. number of warnings.\n" -"• `/nowarns [by reply]`: reset the warns received by a user (both normal and automatic warns).\n" +"• `/sw`: you can place a `/sw` (_\"silent warn\"_) everywhere you want in " +"your message. The bot will silently count the warn, but won't answer in the " +"group unless the user reached the max. number of warnings.\n" +"• `/nowarns [by reply]`: reset the warns received by a user (both normal and " +"automatic warns).\n" "• `/warnmax [number]`: set the max number of the warns before the kick/ban.\n" -"• `/warnmax media [number]`: set the max number of the warns before kick/ban when an unallowed media is sent.\n\n" -"How to see how many warns a user has received (or to reset them): `/user` command.\n" -"How to change the max. number of warnings allowed: `/config` command, then `menu` button.\n" -"How to change the max. number of warnings allowed for medias: `/config` command, then `media` button.\n" -"How to change the max. number of warnings allowed for spam: `/config` command, then `antispam` button." +"• `/warnmax media [number]`: set the max number of the warns before kick/ban " +"when an unallowed media is sent.\n" +"\n" +"How to see how many warns a user has received (or to reset them): `/user` " +"command.\n" +"How to change the max. number of warnings allowed: `/config` command, then " +"`menu` button.\n" +"How to change the max. number of warnings allowed for medias: `/config` " +"command, then `media` button.\n" +"How to change the max. number of warnings allowed for spam: `/config` " +"command, then `antispam` button." msgstr "" #: lua/groupbutler/plugins/help.lua:176 -msgid "*Pinning messages*\n" +msgid "" +"*Pinning messages*\n" "The \"48 hours limit\" to edit your own messages doesn't apply to bots.\n" -"This command was born from the necessity of editing the pinned message without sending it again, maybe just to change few things.\n" -"So with `/pin` you can generate a message to pin, and edit it how many times you want.\n\n" -"• `/pin [text]`: the bot will send you back the text you used as argument, with markdown. You can pin the message and use `/pin [text]` again to edit it\n" -"• `/pin`: the bot will find the latest message generate by `/pin`, if it still exists\n" -"• `/newpin [text]`: forces the bot to send another message that will be saved as new target for `/pin`\n\n" +"This command was born from the necessity of editing the pinned message " +"without sending it again, maybe just to change few things.\n" +"So with `/pin` you can generate a message to pin, and edit it how many times " +"you want.\n" +"\n" +"• `/pin [text]`: the bot will send you back the text you used as argument, " +"with markdown. You can pin the message and use `/pin [text]` again to edit " +"it\n" +"• `/pin`: the bot will find the latest message generate by `/pin`, if it " +"still exists\n" +"• `/newpin [text]`: forces the bot to send another message that will be " +"saved as new target for `/pin`\n" +"\n" "*Note*: `/pin` supports markdown, but only `$rules` and `$title` placeholders" msgstr "" #: lua/groupbutler/plugins/help.lua:186 -msgid "*Group language*\n" -"• `/lang`: change the bot language (works on groups and private chats)\n\n" -"*Note*: the translators are volunteers, so neither the correctness nor completeness of localizations can be guaranteed.\n\n" -"You can help improve translations on our [Crowdin Project](https://crowdin.com/project/group-butler).\n\n" -"*Special characters*\n\n" -"• `/config` command, then `menu` button: you will receive in private the menu keyboard.\n" -"Here you will find two particular options: _Arab and RTL_.\n\n" -"*Arab*: when Arab is not allowed (🚫), people who write Arab characters will be kicked from the group.\n" -"*Rtl*: stands for 'Right To Left' character, is the cause of weird service messages written in the opposite direction.\n" -"When Rtl is not allowed (🚫), people who write Rtl characters (or have it in their names) will be kicked." +msgid "" +"*Group language*\n" +"• `/lang`: change the bot language (works on groups and private chats)\n" +"\n" +"*Note*: the translators are volunteers, so neither the correctness nor " +"completeness of localizations can be guaranteed.\n" +"\n" +"You can help improve translations on our [Crowdin Project](https://crowdin." +"com/project/group-butler).\n" +"\n" +"*Special characters*\n" +"\n" +"• `/config` command, then `menu` button: you will receive in private the " +"menu keyboard.\n" +"Here you will find two particular options: _Arab and RTL_.\n" +"\n" +"*Arab*: when Arab is not allowed (🚫), people who write Arab characters will " +"be kicked from the group.\n" +"*Rtl*: stands for 'Right To Left' character, is the cause of weird service " +"messages written in the opposite direction.\n" +"When Rtl is not allowed (🚫), people who write Rtl characters (or have it in " +"their names) will be kicked." msgstr "" #: lua/groupbutler/plugins/help.lua:201 -msgid "*General group settings*\n\n" -"`/config` or `/settings`: manage the group settings in private from an inline keyboard.\n" -"The inline keyboard has six sub-menus:\n\n" +msgid "" +"*General group settings*\n" +"\n" +"`/config` or `/settings`: manage the group settings in private from an " +"inline keyboard.\n" +"The inline keyboard has six sub-menus:\n" +"\n" "*Menu*: manage the most important group settings\n" -"*Antiflood*: turn on or off the antiflood, set its sensitivity and choose some media to ignore, if you want\n" -"*Media*: choose which media to forbid in your group, and set the number of times that a user will be warned before being kicked/banned\n" -"*Antispam*: choose which kind of message you want to forbid (e.g. telegram.me links, forwarded messages from channels)\n" -"*Log channel*: choose which updates should be logged\n\n" +"*Antiflood*: turn on or off the antiflood, set its sensitivity and choose " +"some media to ignore, if you want\n" +"*Media*: choose which media to forbid in your group, and set the number of " +"times that a user will be warned before being kicked/banned\n" +"*Antispam*: choose which kind of message you want to forbid (e.g. telegram." +"me links, forwarded messages from channels)\n" +"*Log channel*: choose which updates should be logged\n" +"\n" "*Bonus commands*:\n" -"`/reportflood [number of messages]/[timeframe]`: set how many times users can use the @admin command within a certain timeframe.\n" -"`/leave`: the bot will leave the group without deleting its data. Use this command only if you are going to add the bot to the group again\n" -"`/snap`: generate a backup file that can be restored with `/import` (send the file in the group and reply to it). `/snap` can be used once every three days" +"`/reportflood [number of messages]/[timeframe]`: set how many times users " +"can use the @admin command within a certain timeframe.\n" +"`/leave`: the bot will leave the group without deleting its data. Use this " +"command only if you are going to add the bot to the group again\n" +"`/snap`: generate a backup file that can be restored with `/import` (send " +"the file in the group and reply to it). `/snap` can be used once every three " +"days" msgstr "" #: lua/groupbutler/plugins/help.lua:216 -msgid "*Log channel informations*\n\n" -"A log channel is a _(private)_ channel where the bot will record all the important events that will happen in your group.\n" -"If you want to use this feature, you need to pair your group with a channel with the commands described below.\n" -"All the events, by default, are *not logged*. Admins can choose which events to log from the `/config` menu -> `log channel` button.\n\n" -"To pair a channel with a group, the *channel creator* must [add the bot to the channel administrators](telegram.me/gb_tutorials/4) (otherwise it won't be able to post), and send in the channel this command:\n" +msgid "" +"*Log channel informations*\n" +"\n" +"A log channel is a _(private)_ channel where the bot will record all the " +"important events that will happen in your group.\n" +"If you want to use this feature, you need to pair your group with a channel " +"with the commands described below.\n" +"All the events, by default, are *not logged*. Admins can choose which events " +"to log from the `/config` menu -> `log channel` button.\n" +"\n" +"To pair a channel with a group, the *channel creator* must [add the bot to " +"the channel administrators](telegram.me/gb_tutorials/4) (otherwise it won't " +"be able to post), and send in the channel this command:\n" "`/setlog`\n" -"Then, an admin of the group must forward in the group the message (\"`/setlog`\") sent in the channel. *Done*!\n" -"(you can find a video-tutorial [here](https://telegram.me/GB_tutorials/8))\n\n" +"Then, an admin of the group must forward in the group the message (\"`/" +"setlog`\") sent in the channel. *Done*!\n" +"(you can find a video-tutorial [here](https://telegram.me/GB_tutorials/8))\n" +"\n" "A channel can be used as log by different groups.\n" -"To change your log channel, simply repeat this process with another channel.\n\n" +"To change your log channel, simply repeat this process with another " +"channel.\n" +"\n" "`/unsetlog`: remove your current log channel\n" "`/logchannel`: get some informations about your log channel, if paired" msgstr "" @@ -1069,7 +1298,9 @@ msgid "❗️ Already there" msgstr "" #: lua/groupbutler/plugins/links.lua:34 -msgid "*No link* for this group. Ask the owner to save it with `/setlink [group link]`" +msgid "" +"*No link* for this group. Ask the owner to save it with `/setlink [group " +"link]`" msgstr "" #: lua/groupbutler/plugins/links.lua:45 @@ -1078,12 +1309,15 @@ msgstr "" #: lua/groupbutler/plugins/links.lua:51 lua/groupbutler/plugins/links.lua:68 #, lua-format -msgid "The link has been set.\n" +msgid "" +"The link has been set.\n" "*Here's the link*: %s" msgstr "" #: lua/groupbutler/plugins/links.lua:54 -msgid "This is not a *public supergroup*, so you need to write the link near `/setlink`" +msgid "" +"This is not a *public supergroup*, so you need to write the link near `/" +"setlink`" msgstr "" #: lua/groupbutler/plugins/links.lua:57 @@ -1092,7 +1326,8 @@ msgstr "" #: lua/groupbutler/plugins/links.lua:66 #, lua-format -msgid "The link has been updated.\n" +msgid "" +"The link has been updated.\n" "*Here's the new link*: %s" msgstr "" @@ -1117,7 +1352,9 @@ msgid "Forbidden media will be logged in the channel" msgstr "" #: lua/groupbutler/plugins/logchannel.lua:28 -msgid "Spam links/forwards from channels will be logged in the channel, only if forbidden" +msgid "" +"Spam links/forwards from channels will be logged in the channel, only if " +"forbidden" msgstr "" #: lua/groupbutler/plugins/logchannel.lua:29 @@ -1160,7 +1397,7 @@ msgstr "" msgid "Kick" msgstr "" -#: lua/groupbutler/plugins/logchannel.lua:61 lua/groupbutler/utilities.lua:957 +#: lua/groupbutler/plugins/logchannel.lua:61 lua/groupbutler/utilities.lua:776 msgid "Unban" msgstr "" @@ -1229,9 +1466,11 @@ msgid "User unbanned!" msgstr "" #: lua/groupbutler/plugins/logchannel.lua:136 -msgid "*Select the events the will be logged in the channel*\n" +msgid "" +"*Select the events the will be logged in the channel*\n" "✅ = will be logged\n" -"☑️ = won't be logged\n\n" +"☑️ = won't be logged\n" +"\n" "Tap on an option to get further information" msgstr "" @@ -1283,246 +1522,270 @@ msgid "*Log channel removed*" msgstr "" #: lua/groupbutler/plugins/logchannel.lua:216 -msgid "_This group has a log channel saved, but I'm not a member there, so I can't post/retrieve its info_" +msgid "" +"_This group has a log channel saved, but I'm not a member there, so I can't " +"post/retrieve its info_" msgstr "" #: lua/groupbutler/plugins/logchannel.lua:223 #, lua-format -msgid "This group has a log channel\n" +msgid "" +"This group has a log channel\n" "Channel: %s" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:36 +#: lua/groupbutler/plugins/mediasettings.lua:38 msgid "Video messages" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:39 +#: lua/groupbutler/plugins/mediasettings.lua:41 msgid "Vocal messages" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:65 +#: lua/groupbutler/plugins/mediasettings.lua:67 #, lua-format msgid "Warnings | %d | kick" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:67 +#: lua/groupbutler/plugins/mediasettings.lua:69 #, lua-format msgid "Warnings | %d | mute" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:69 +#: lua/groupbutler/plugins/mediasettings.lua:71 #, lua-format msgid "Warnings | %d | ban" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:94 +#: lua/groupbutler/plugins/mediasettings.lua:96 msgid "❌ warning" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:97 +#: lua/groupbutler/plugins/mediasettings.lua:99 msgid "🗑 delete" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:103 +#: lua/groupbutler/plugins/mediasettings.lua:105 msgid "✅ allowed" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:117 -msgid "\n" -"Tap on an option on the right to *change the setting*\n" -"You can use the last lines to change how many warnings the bot should give before kicking/banning/muting someone.\n" -"The number is not related the the normal `/warn` command.\n\n" -"Possible statuses: ✅ allowed, ❌ warning, 🗑 delete.\n" -"When a media is set to delete, the bot will give a warning *only* when this is the users last warning\n" +#: lua/groupbutler/plugins/mediasettings.lua:116 +msgid "⚠️ Tap on the right column" msgstr "" #: lua/groupbutler/plugins/mediasettings.lua:131 -msgid "⚠️ Tap on the right column" +msgid "" +"Tap on an option on the right to *change the setting*\n" +"You can use the last lines to change how many warnings the bot should give " +"before kicking/banning/muting someone.\n" +"The number is not related the the normal `/warn` command.\n" +"\n" +"Possible statuses: ✅ allowed, ❌ warning, 🗑 delete.\n" +"When a media is set to delete, the bot will give a warning *only* when this " +"is the users last warning" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:140 +#: lua/groupbutler/plugins/mediasettings.lua:149 msgid "⚙ The new value is too low ( < 1)" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:147 +#: lua/groupbutler/plugins/mediasettings.lua:156 msgid "⚙ The new value is too high ( > 12)" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:161 +#: lua/groupbutler/plugins/mediasettings.lua:171 msgid "👞 New status is kick" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:164 +#: lua/groupbutler/plugins/mediasettings.lua:174 msgid "👁 New status is mute" msgstr "" -#: lua/groupbutler/plugins/mediasettings.lua:167 +#: lua/groupbutler/plugins/mediasettings.lua:177 msgid "🔨 New status is ban" msgstr "" #. TRANSLATORS: these strings should be shorter than 200 characters -#: lua/groupbutler/plugins/menu.lua:24 -msgid "When enabled, users will be able to report messages with the @admin command" -msgstr "" - -#: lua/groupbutler/plugins/menu.lua:25 -msgid "Enable or disable the goodbye message. Can't be sent in large groups" -msgstr "" - #: lua/groupbutler/plugins/menu.lua:26 -msgid "Enable or disable the welcome message" +msgid "" +"When enabled, users will be able to report messages with the @admin command" msgstr "" #: lua/groupbutler/plugins/menu.lua:27 -msgid "When enabled, every time a new welcome message is sent, the previously sent welcome message is removed" +msgid "Enable or disable the goodbye message. Can't be sent in large groups" msgstr "" #: lua/groupbutler/plugins/menu.lua:28 -msgid "When enabled, the bot doesn't answer in the group to /dashboard, /config and /help commands (it will just answer in private)" +msgid "Enable or disable the welcome message" msgstr "" #: lua/groupbutler/plugins/menu.lua:29 -msgid "Enable and disable the anti-flood system (more info in the /help message)" +msgid "" +"When enabled, every time a new welcome message is sent, the previously sent " +"welcome message is removed" msgstr "" #: lua/groupbutler/plugins/menu.lua:30 -msgid "If the welcome message is enabled, it will include an inline button that will send to the user the rules in private" +msgid "" +"When enabled, the bot doesn't answer in the group to /dashboard, /config " +"and /help commands (it will just answer in private)" msgstr "" #: lua/groupbutler/plugins/menu.lua:31 -msgid "When someone uses /rules\n" +msgid "" +"Enable and disable the anti-flood system (more info in the /help message)" +msgstr "" + +#: lua/groupbutler/plugins/menu.lua:32 +msgid "" +"If the welcome message is enabled, it will include an inline button that " +"will send to the user the rules in private" +msgstr "" + +#: lua/groupbutler/plugins/menu.lua:33 +msgid "" +"When someone uses /rules\n" "👥: the bot will answer in the group (always, with admins)\n" "👤: the bot will answer in private" msgstr "" -#: lua/groupbutler/plugins/menu.lua:34 -msgid "When someone uses an #extra\n" +#: lua/groupbutler/plugins/menu.lua:36 +msgid "" +"When someone uses an #extra\n" "👥: the bot will answer in the group (always, with admins)\n" "👤: the bot will answer in private" msgstr "" -#: lua/groupbutler/plugins/menu.lua:37 -msgid "Select what the bot should do when someone sends a message with arab characters" +#: lua/groupbutler/plugins/menu.lua:39 +msgid "" +"Select what the bot should do when someone sends a message with arab " +"characters" msgstr "" -#: lua/groupbutler/plugins/menu.lua:38 +#: lua/groupbutler/plugins/menu.lua:40 msgid "Bots will be banned when added by normal users" msgstr "" -#: lua/groupbutler/plugins/menu.lua:39 -msgid "Select what the bot should do when someone sends a message with the RTL character, or has it in their name" +#: lua/groupbutler/plugins/menu.lua:41 +msgid "" +"Select what the bot should do when someone sends a message with the RTL " +"character, or has it in their name" msgstr "" -#: lua/groupbutler/plugins/menu.lua:40 -msgid "Change how many times a user has to be warned before being kicked/banned" +#: lua/groupbutler/plugins/menu.lua:42 +msgid "" +"Change how many times a user has to be warned before being kicked/banned" msgstr "" -#: lua/groupbutler/plugins/menu.lua:41 -msgid "Change the action to perform when a user reaches the max. number of warnings" +#: lua/groupbutler/plugins/menu.lua:43 +msgid "" +"Change the action to perform when a user reaches the max. number of warnings" msgstr "" -#: lua/groupbutler/plugins/menu.lua:55 +#: lua/groupbutler/plugins/menu.lua:57 msgid "The new value is too high ( > 12)" msgstr "" -#: lua/groupbutler/plugins/menu.lua:62 +#: lua/groupbutler/plugins/menu.lua:64 msgid "The new value is too low ( < 1)" msgstr "" -#: lua/groupbutler/plugins/menu.lua:73 +#: lua/groupbutler/plugins/menu.lua:75 msgid "New action on max number of warns received: ban" msgstr "" -#: lua/groupbutler/plugins/menu.lua:76 +#: lua/groupbutler/plugins/menu.lua:78 msgid "New action on max number of warns received: mute" msgstr "" -#: lua/groupbutler/plugins/menu.lua:79 +#: lua/groupbutler/plugins/menu.lua:81 msgid "New action on max number of warns received: kick" msgstr "" -#: lua/groupbutler/plugins/menu.lua:88 +#: lua/groupbutler/plugins/menu.lua:90 msgid "Action -> kick" msgstr "" -#: lua/groupbutler/plugins/menu.lua:89 +#: lua/groupbutler/plugins/menu.lua:91 msgid "Action -> ban" msgstr "" -#: lua/groupbutler/plugins/menu.lua:90 +#: lua/groupbutler/plugins/menu.lua:92 msgid "Action -> mute" msgstr "" -#: lua/groupbutler/plugins/menu.lua:91 +#: lua/groupbutler/plugins/menu.lua:93 msgid "Allowed ✅" msgstr "" -#: lua/groupbutler/plugins/menu.lua:166 +#: lua/groupbutler/plugins/menu.lua:168 msgid "✅" msgstr "" -#: lua/groupbutler/plugins/menu.lua:176 +#: lua/groupbutler/plugins/menu.lua:178 msgid "Welcome" msgstr "" -#: lua/groupbutler/plugins/menu.lua:177 +#: lua/groupbutler/plugins/menu.lua:179 msgid "Goodbye" msgstr "" -#: lua/groupbutler/plugins/menu.lua:178 lua/groupbutler/utilities.lua:588 +#: lua/groupbutler/plugins/menu.lua:180 lua/groupbutler/utilities.lua:470 msgid "Extra" msgstr "" -#: lua/groupbutler/plugins/menu.lua:179 lua/groupbutler/utilities.lua:589 +#: lua/groupbutler/plugins/menu.lua:181 lua/groupbutler/utilities.lua:471 msgid "Anti-flood" msgstr "" -#: lua/groupbutler/plugins/menu.lua:180 lua/groupbutler/utilities.lua:591 +#: lua/groupbutler/plugins/menu.lua:182 lua/groupbutler/utilities.lua:473 msgid "Silent mode" msgstr "" -#: lua/groupbutler/plugins/menu.lua:182 lua/groupbutler/utilities.lua:593 +#: lua/groupbutler/plugins/menu.lua:184 lua/groupbutler/utilities.lua:475 msgid "Arab" msgstr "" -#: lua/groupbutler/plugins/menu.lua:183 lua/groupbutler/utilities.lua:594 +#: lua/groupbutler/plugins/menu.lua:185 lua/groupbutler/utilities.lua:476 msgid "RTL" msgstr "" -#: lua/groupbutler/plugins/menu.lua:184 lua/groupbutler/utilities.lua:590 +#: lua/groupbutler/plugins/menu.lua:186 lua/groupbutler/utilities.lua:472 msgid "Ban bots" msgstr "" -#: lua/groupbutler/plugins/menu.lua:185 lua/groupbutler/utilities.lua:595 +#: lua/groupbutler/plugins/menu.lua:187 lua/groupbutler/utilities.lua:477 msgid "Reports" msgstr "" -#: lua/groupbutler/plugins/menu.lua:186 lua/groupbutler/utilities.lua:596 +#: lua/groupbutler/plugins/menu.lua:188 lua/groupbutler/utilities.lua:478 msgid "Delete last welcome message" msgstr "" -#: lua/groupbutler/plugins/menu.lua:187 +#: lua/groupbutler/plugins/menu.lua:189 msgid "Welcome + rules button" msgstr "" -#: lua/groupbutler/plugins/menu.lua:188 lua/groupbutler/utilities.lua:598 +#: lua/groupbutler/plugins/menu.lua:190 lua/groupbutler/utilities.lua:480 msgid "Clean Service Messages" msgstr "" -#: lua/groupbutler/plugins/menu.lua:226 +#: lua/groupbutler/plugins/menu.lua:228 msgid "🔨️ ban" msgstr "" -#: lua/groupbutler/plugins/menu.lua:232 +#: lua/groupbutler/plugins/menu.lua:234 msgid "Warns: " msgstr "" -#: lua/groupbutler/plugins/menu.lua:237 +#: lua/groupbutler/plugins/menu.lua:239 msgid "Action:" msgstr "" -#: lua/groupbutler/plugins/menu.lua:260 -msgid "Manage the settings of the group. Click on the left column to get a small hint" +#: lua/groupbutler/plugins/menu.lua:275 +msgid "" +"Manage the settings of the group. Click on the left column to get a small " +"hint" msgstr "" #: lua/groupbutler/plugins/onmessage.lua:117 @@ -1542,19 +1805,22 @@ msgstr "" #: lua/groupbutler/plugins/onmessage.lua:170 #, lua-format -msgid "%s %s: media sent not allowed!\n" +msgid "" +"%s %s: media sent not allowed!\n" "❗️ %d/%d" msgstr "" #: lua/groupbutler/plugins/onmessage.lua:180 #, lua-format -msgid "%s, this type of media is not allowed in this chat.\n" +msgid "" +"%s, this type of media is not allowed in this chat.\n" "(%d/%d)" msgstr "" #: lua/groupbutler/plugins/onmessage.lua:185 #, lua-format -msgid "%s, this type of media is not allowed in this chat.\n" +msgid "" +"%s, this type of media is not allowed in this chat.\n" "The next time you will be banned/kicked/muted\n" msgstr "" @@ -1597,7 +1863,8 @@ msgid "Last message generated by /pin ^" msgstr "" #: lua/groupbutler/plugins/pin.lua:86 -msgid "The old message generated with /pin does not exist anymore." +msgid "" +"The old message generated with /pin does not exist anymore." msgstr "" #: lua/groupbutler/plugins/private.lua:17 @@ -1606,9 +1873,13 @@ msgstr "" #: lua/groupbutler/plugins/private.lua:25 #, lua-format -msgid "This bot is based on [otouto](https://github.com/topkecleon/otouto) (AKA @mokubot, channel: @otouto), a multipurpose Lua bot.\n" -"Group Butler wouldn't exist without it.\n\n" -"You can contact the owners of this bot using the /groups command.\n\n" +msgid "" +"This bot is based on [otouto](https://github.com/topkecleon/otouto) (AKA " +"@mokubot, channel: @otouto), a multipurpose Lua bot.\n" +"Group Butler wouldn't exist without it.\n" +"\n" +"You can contact the owners of this bot using the /groups command.\n" +"\n" "Bot version: %s\n" "*Some useful links:*" msgstr "" @@ -1644,11 +1915,15 @@ msgid "🔙 back" msgstr "" #: lua/groupbutler/plugins/private_settings.lua:23 -msgid "When you join a group moderated by this bot, you will receive the group rules in private" +msgid "" +"When you join a group moderated by this bot, you will receive the group " +"rules in private" msgstr "" #: lua/groupbutler/plugins/private_settings.lua:24 -msgid "If enabled, you will receive all the messages reported with the @admin command in the groups you are moderating" +msgid "" +"If enabled, you will receive all the messages reported with the @admin " +"command in the groups you are moderating" msgstr "" #: lua/groupbutler/plugins/private_settings.lua:35 @@ -1678,31 +1953,36 @@ msgstr "" #: lua/groupbutler/plugins/report.lua:33 #, lua-format -msgid "\n" +msgid "" +"\n" "• Reported message sent by: %s (%d)" msgstr "" #: lua/groupbutler/plugins/report.lua:37 #, lua-format -msgid "\n" +msgid "" +"\n" "• Group: %s" msgstr "" #: lua/groupbutler/plugins/report.lua:39 #, lua-format -msgid "\n" +msgid "" +"\n" "• Group: %s" msgstr "" #: lua/groupbutler/plugins/report.lua:43 #, lua-format -msgid "\n" +msgid "" +"\n" "• Go to the message" msgstr "" #: lua/groupbutler/plugins/report.lua:47 #, lua-format -msgid "\n" +msgid "" +"\n" "• Description: %s" msgstr "" @@ -1722,13 +2002,16 @@ msgstr "" #: lua/groupbutler/plugins/report.lua:116 #, lua-format -msgid "*New parameters saved*.\n" +msgid "" +"*New parameters saved*.\n" "Users will be able to use @admin %d times/%d minutes" msgstr "" #: lua/groupbutler/plugins/report.lua:137 #, lua-format -msgid "_Please, do not abuse this command. It can be used %d times every %d minutes_.\n" +msgid "" +"_Please, do not abuse this command. It can be used %d times every %d " +"minutes_.\n" "Wait other %d minutes, %d seconds." msgstr "" @@ -1738,7 +2021,8 @@ msgid "_Reported to %d admin(s)_" msgstr "" #: lua/groupbutler/plugins/report.lua:165 -msgid "You closed this issue and deleted all the other reports sent to the admins" +msgid "" +"You closed this issue and deleted all the other reports sent to the admins" msgstr "" #: lua/groupbutler/plugins/report.lua:173 @@ -1759,7 +2043,9 @@ msgid "%s has/will address this report" msgstr "" #: lua/groupbutler/plugins/report.lua:204 -msgid "This button will delete all the reports sent to the other admins. Tap it again to confirm" +msgid "" +"This button will delete all the reports sent to the other admins. Tap it " +"again to confirm" msgstr "" #: lua/groupbutler/plugins/report.lua:212 @@ -1771,7 +2057,9 @@ msgid "🚫 Unknown or non-existent group" msgstr "" #: lua/groupbutler/plugins/rules.lua:45 -msgid "🚷 You are not a member of this chat. You can't read the rules of a private group." +msgid "" +"🚷 You are not a member of this chat. You can't read the rules of a private " +"group." msgstr "" #: lua/groupbutler/plugins/rules.lua:75 @@ -1797,12 +2085,16 @@ msgstr "" #: lua/groupbutler/plugins/service.lua:66 #, lua-format -msgid "Hello everyone!\n" -"My name is %s, and I'm a bot made to help administrators in their hard work.\n" +msgid "" +"Hello everyone!\n" +"My name is %s, and I'm a bot made to help administrators in their hard " +"work.\n" msgstr "" #: lua/groupbutler/plugins/service.lua:70 -msgid "Yay! This group has been upgraded. You are great! Now I can work properly :)\n" +msgid "" +"Yay! This group has been upgraded. You are great! Now I can work " +"properly :)\n" msgstr "" #: lua/groupbutler/plugins/setlang.lua:33 @@ -1820,149 +2112,143 @@ msgid "English language is *set*" msgstr "" #: lua/groupbutler/plugins/setlang.lua:59 -msgid ".\n" -"Please note that translators are volunteers, and this localization _may be incomplete_. You can help improve translations on our [Crowdin Project](https://crowdin.com/project/group-butler).\n" +msgid "" +".\n" +"Please note that translators are volunteers, and this localization _may be " +"incomplete_. You can help improve translations on our [Crowdin Project]" +"(https://crowdin.com/project/group-butler).\n" msgstr "" -#: lua/groupbutler/plugins/users.lua:17 +#: lua/groupbutler/plugins/users.lua:23 msgid "can't change the chat title/description/icon" msgstr "" -#: lua/groupbutler/plugins/users.lua:18 +#: lua/groupbutler/plugins/users.lua:24 msgid "can't send messages" msgstr "" -#: lua/groupbutler/plugins/users.lua:19 +#: lua/groupbutler/plugins/users.lua:25 msgid "can't delete messages" msgstr "" -#: lua/groupbutler/plugins/users.lua:20 +#: lua/groupbutler/plugins/users.lua:26 msgid "can't invite users/generate a link" msgstr "" -#: lua/groupbutler/plugins/users.lua:21 +#: lua/groupbutler/plugins/users.lua:27 msgid "can't restrict members" msgstr "" -#: lua/groupbutler/plugins/users.lua:22 +#: lua/groupbutler/plugins/users.lua:28 msgid "can't pin messages" msgstr "" -#: lua/groupbutler/plugins/users.lua:23 +#: lua/groupbutler/plugins/users.lua:29 msgid "can't promote new admins" msgstr "" -#: lua/groupbutler/plugins/users.lua:24 +#: lua/groupbutler/plugins/users.lua:30 msgid "can't send photos/videos/documents/audios/voice messages/video messages" msgstr "" -#: lua/groupbutler/plugins/users.lua:25 +#: lua/groupbutler/plugins/users.lua:31 msgid "can't send stickers/GIFs/games/use inline bots" msgstr "" -#: lua/groupbutler/plugins/users.lua:26 +#: lua/groupbutler/plugins/users.lua:32 msgid "can't show link previews" msgstr "" -#: lua/groupbutler/plugins/users.lua:32 +#: lua/groupbutler/plugins/users.lua:38 msgid "🔄️ Refresh cache" msgstr "" -#: lua/groupbutler/plugins/users.lua:60 +#: lua/groupbutler/plugins/users.lua:46 msgid "Remove warnings" msgstr "" -#: lua/groupbutler/plugins/users.lua:70 +#: lua/groupbutler/plugins/users.lua:56 #, lua-format -msgid "*User ID*: `%d`\n" +msgid "" +"*User ID*: `%d`\n" "`Warnings`: *%d*\n" "`Media warnings`: *%d*\n" "`Spam warnings`: *%d*\n" msgstr "" -#: lua/groupbutler/plugins/users.lua:90 +#: lua/groupbutler/plugins/users.lua:76 #, lua-format msgid "Your ID is `%d`" +#: lua/groupbutler/plugins/users.lua:109 msgstr "" -#: lua/groupbutler/plugins/users.lua:124 -msgid "That user has nothing to do with this chat" -msgstr "" - -#: lua/groupbutler/plugins/users.lua:131 #, lua-format msgid "%s is banned from this group" msgstr "" -#: lua/groupbutler/plugins/users.lua:132 +#: lua/groupbutler/plugins/users.lua:110 #, lua-format msgid "%s left the group or has been kicked and unbanned" msgstr "" -#: lua/groupbutler/plugins/users.lua:133 +#: lua/groupbutler/plugins/users.lua:111 #, lua-format msgid "%s is an admin" msgstr "" -#: lua/groupbutler/plugins/users.lua:134 +#: lua/groupbutler/plugins/users.lua:112 #, lua-format msgid "%s is the group creator" msgstr "" -#: lua/groupbutler/plugins/users.lua:135 +#: lua/groupbutler/plugins/users.lua:113 #, lua-format msgid "%s has nothing to do with this chat" msgstr "" -#: lua/groupbutler/plugins/users.lua:136 +#: lua/groupbutler/plugins/users.lua:114 #, lua-format msgid "%s is a chat member" msgstr "" -#: lua/groupbutler/plugins/users.lua:137 +#: lua/groupbutler/plugins/users.lua:115 #, lua-format msgid "%s is a restricted" msgstr "" -#: lua/groupbutler/plugins/users.lua:148 +#: lua/groupbutler/plugins/users.lua:127 #, lua-format -msgid "\n" +msgid "" +"\n" "Restrictions: %s" msgstr "" -#: lua/groupbutler/plugins/users.lua:159 +#: lua/groupbutler/plugins/users.lua:138 msgid "Reply to a user or mention them by username or numerical ID" msgstr "" -#: lua/groupbutler/plugins/users.lua:183 lua/groupbutler/plugins/users.lua:244 +#: lua/groupbutler/plugins/users.lua:156 #, lua-format -msgid "📌 Status: `CACHED`\n" -"⌛ ️Remaining: `%s`\n" -"👥 Admins cached: `%d`" +msgid "👥 Admins cached: %d" msgstr "" -#: lua/groupbutler/plugins/users.lua:192 +#: lua/groupbutler/plugins/users.lua:164 #, lua-format msgid "Message N° %d" msgstr "" -#: lua/groupbutler/plugins/users.lua:214 lua/groupbutler/plugins/warn.lua:167 +#: lua/groupbutler/plugins/users.lua:186 lua/groupbutler/plugins/warn.lua:170 msgid "You are not allowed to use this button" msgstr "" -#: lua/groupbutler/plugins/users.lua:223 +#: lua/groupbutler/plugins/users.lua:194 #, lua-format -msgid "The number of warnings received by this user has been reset, by %s" -msgstr "" - -#: lua/groupbutler/plugins/users.lua:237 -#, lua-format -msgid "The adminlist has just been updated. You must wait 10 minutes from the last refresh (wait %d seconds)" +msgid "" +"The number of warnings received by this user has been reset, by %s" msgstr "" -#: lua/groupbutler/plugins/users.lua:246 -#, lua-format -msgid "✅ Updated. Next update in %s" +#: lua/groupbutler/plugins/users.lua:205 +msgid "✅ The admin list will be updated soon" msgstr "" #: lua/groupbutler/plugins/warn.lua:18 @@ -1979,7 +2265,8 @@ msgstr "" #: lua/groupbutler/plugins/warn.lua:55 #, lua-format -msgid "*Old* value was %d\n" +msgid "" +"*Old* value was %d\n" "*New* max is %d" msgstr "" @@ -1992,376 +2279,386 @@ msgid "No" msgstr "" #: lua/groupbutler/plugins/warn.lua:68 -msgid "Do you want to continue and reset *all* the warnings received by *all* the users of the group?" +msgid "" +"Do you want to continue and reset *all* the warnings received by *all* the " +"users of the group?" msgstr "" -#: lua/groupbutler/plugins/warn.lua:86 +#: lua/groupbutler/plugins/warn.lua:89 #, lua-format msgid "Done! %s has been forgiven." msgstr "" -#: lua/groupbutler/plugins/warn.lua:111 +#: lua/groupbutler/plugins/warn.lua:112 #, lua-format msgid "%s %s: reached the max number of warnings (%d/%d)" msgstr "" -#: lua/groupbutler/plugins/warn.lua:144 +#: lua/groupbutler/plugins/warn.lua:145 #, lua-format msgid "%s has been warned (%d/%d)" msgstr "" -#: lua/groupbutler/plugins/warn.lua:175 +#: lua/groupbutler/plugins/warn.lua:181 msgid "The number of warnings received by this user is already zero" msgstr "" -#: lua/groupbutler/plugins/warn.lua:179 +#: lua/groupbutler/plugins/warn.lua:185 #, lua-format msgid "Warn removed! (%d/%d)" msgstr "" -#: lua/groupbutler/plugins/warn.lua:182 +#: lua/groupbutler/plugins/warn.lua:188 #, lua-format -msgid "\n" +msgid "" +"\n" "(Admin: %s)" msgstr "" -#: lua/groupbutler/plugins/warn.lua:191 +#: lua/groupbutler/plugins/warn.lua:198 #, lua-format msgid "Done. All the warnings of this group have been erased by %s" msgstr "" -#: lua/groupbutler/plugins/warn.lua:193 +#: lua/groupbutler/plugins/warn.lua:200 msgid "_Action aborted_" msgstr "" -#: lua/groupbutler/plugins/welcome.lua:83 +#: lua/groupbutler/plugins/welcome.lua:71 msgid "Read the rules" msgstr "" -#: lua/groupbutler/plugins/welcome.lua:106 +#: lua/groupbutler/plugins/welcome.lua:94 msgid "Hi $name!" msgstr "" -#: lua/groupbutler/plugins/welcome.lua:155 +#: lua/groupbutler/plugins/welcome.lua:146 msgid "Welcome and...?" msgstr "" -#: lua/groupbutler/plugins/welcome.lua:179 +#: lua/groupbutler/plugins/welcome.lua:170 #, lua-format msgid "A form of media has been set as the welcome message: `%s`" msgstr "" -#: lua/groupbutler/plugins/welcome.lua:181 +#: lua/groupbutler/plugins/welcome.lua:172 msgid "Reply to a `sticker` or a `gif` to set them as the *welcome message*" msgstr "" -#: lua/groupbutler/plugins/welcome.lua:200 +#: lua/groupbutler/plugins/welcome.lua:191 msgid "*Custom welcome message saved!*" msgstr "" -#: lua/groupbutler/utilities.lua:515 +#: lua/groupbutler/utilities.lua:397 msgid "-*empty*-" msgstr "" -#: lua/groupbutler/utilities.lua:556 +#: lua/groupbutler/utilities.lua:438 #, lua-format -msgid "👤 Creator\n" -"└ %s\n\n" +msgid "" +"👤 Creator\n" +"└ %s\n" +"\n" "👥 Admins (%d)\n" "%s" msgstr "" -#: lua/groupbutler/utilities.lua:566 +#: lua/groupbutler/utilities.lua:448 msgid "No commands set" msgstr "" -#: lua/groupbutler/utilities.lua:569 +#: lua/groupbutler/utilities.lua:451 msgid "List of custom commands:\n" msgstr "" -#: lua/groupbutler/utilities.lua:581 -msgid "Current settings for *the group*:\n\n" +#: lua/groupbutler/utilities.lua:463 +msgid "" +"Current settings for *the group*:\n" +"\n" msgstr "" -#: lua/groupbutler/utilities.lua:582 +#: lua/groupbutler/utilities.lua:464 #, lua-format msgid "*Language*: %s\n" msgstr "" -#: lua/groupbutler/utilities.lua:586 +#: lua/groupbutler/utilities.lua:468 msgid "Welcome message" msgstr "" -#: lua/groupbutler/utilities.lua:587 +#: lua/groupbutler/utilities.lua:469 msgid "Goodbye message" msgstr "" -#: lua/groupbutler/utilities.lua:597 +#: lua/groupbutler/utilities.lua:479 msgid "Welcome button" msgstr "" -#: lua/groupbutler/utilities.lua:599 +#: lua/groupbutler/utilities.lua:481 msgid "Unknown" msgstr "" -#: lua/groupbutler/utilities.lua:634 +#: lua/groupbutler/utilities.lua:516 msgid "*Welcome type*: `GIF / sticker`\n" msgstr "" -#: lua/groupbutler/utilities.lua:636 +#: lua/groupbutler/utilities.lua:518 msgid "*Welcome type*: `custom message`\n" msgstr "" -#: lua/groupbutler/utilities.lua:638 +#: lua/groupbutler/utilities.lua:520 msgid "*Welcome type*: `default message`\n" msgstr "" -#: lua/groupbutler/utilities.lua:647 +#: lua/groupbutler/utilities.lua:529 #, lua-format msgid "Warns (`standard`): *%s*\n" msgstr "" -#: lua/groupbutler/utilities.lua:648 +#: lua/groupbutler/utilities.lua:530 #, lua-format -msgid "Warns (`media`): *%s*\n\n" +msgid "" +"Warns (`media`): *%s*\n" +"\n" msgstr "" -#: lua/groupbutler/utilities.lua:649 +#: lua/groupbutler/utilities.lua:531 msgid "✅ = _enabled / allowed_\n" msgstr "" -#: lua/groupbutler/utilities.lua:650 +#: lua/groupbutler/utilities.lua:532 msgid "🚫 = _disabled / not allowed_\n" msgstr "" -#: lua/groupbutler/utilities.lua:651 +#: lua/groupbutler/utilities.lua:533 msgid "👥 = _sent in group (always for admins)_\n" msgstr "" -#: lua/groupbutler/utilities.lua:652 +#: lua/groupbutler/utilities.lua:534 msgid "👤 = _sent in private_" msgstr "" -#: lua/groupbutler/utilities.lua:662 +#: lua/groupbutler/utilities.lua:544 msgid "@admin command disabled" msgstr "" -#: lua/groupbutler/utilities.lua:663 +#: lua/groupbutler/utilities.lua:545 msgid "Welcome message won't be displayed from now" msgstr "" -#: lua/groupbutler/utilities.lua:664 +#: lua/groupbutler/utilities.lua:546 msgid "Goodbye message won't be displayed from now" msgstr "" -#: lua/groupbutler/utilities.lua:665 +#: lua/groupbutler/utilities.lua:547 msgid "#extra commands are now available only for administrators" msgstr "" -#: lua/groupbutler/utilities.lua:666 +#: lua/groupbutler/utilities.lua:548 msgid "Anti-flood is now off" msgstr "" -#: lua/groupbutler/utilities.lua:667 +#: lua/groupbutler/utilities.lua:549 msgid "/rules will reply in private (for users)" msgstr "" -#: lua/groupbutler/utilities.lua:668 +#: lua/groupbutler/utilities.lua:550 msgid "Silent mode is now off" msgstr "" -#: lua/groupbutler/utilities.lua:669 +#: lua/groupbutler/utilities.lua:551 msgid "Links preview disabled" msgstr "" -#: lua/groupbutler/utilities.lua:670 +#: lua/groupbutler/utilities.lua:552 msgid "Welcome message without a button for the rules" msgstr "" -#: lua/groupbutler/utilities.lua:673 +#: lua/groupbutler/utilities.lua:555 msgid "@admin command enabled" msgstr "" -#: lua/groupbutler/utilities.lua:674 +#: lua/groupbutler/utilities.lua:556 msgid "Welcome message will be displayed" msgstr "" -#: lua/groupbutler/utilities.lua:675 +#: lua/groupbutler/utilities.lua:557 msgid "Goodbye message will be displayed" msgstr "" -#: lua/groupbutler/utilities.lua:676 +#: lua/groupbutler/utilities.lua:558 msgid "#extra commands are now available for all" msgstr "" -#: lua/groupbutler/utilities.lua:677 +#: lua/groupbutler/utilities.lua:559 msgid "Anti-flood is now on" msgstr "" -#: lua/groupbutler/utilities.lua:678 +#: lua/groupbutler/utilities.lua:560 msgid "/rules will reply in the group (with everyone)" msgstr "" -#: lua/groupbutler/utilities.lua:679 +#: lua/groupbutler/utilities.lua:561 msgid "Silent mode is now on" msgstr "" -#: lua/groupbutler/utilities.lua:680 +#: lua/groupbutler/utilities.lua:562 msgid "Links preview enabled" msgstr "" -#: lua/groupbutler/utilities.lua:681 +#: lua/groupbutler/utilities.lua:563 msgid "The welcome message will have a button for the rules" msgstr "" -#: lua/groupbutler/utilities.lua:694 -msgid "This setting is enabled, but the goodbye message won't be displayed in large groups, because I can't see service messages about left members" +#: lua/groupbutler/utilities.lua:576 +msgid "" +"This setting is enabled, but the goodbye message won't be displayed in large " +"groups, because I can't see service messages about left members" msgstr "" -#: lua/groupbutler/utilities.lua:707 +#: lua/groupbutler/utilities.lua:589 msgid "Start me" msgstr "" -#: lua/groupbutler/utilities.lua:709 +#: lua/groupbutler/utilities.lua:591 msgid "_Please message me first so I can message you_" msgstr "" -#: lua/groupbutler/utilities.lua:778 lua/groupbutler/utilities.lua:779 -msgid "Someone" -msgstr "" - -#: lua/groupbutler/utilities.lua:787 -msgid "Reply to a user or mention them" -msgstr "" - -#: lua/groupbutler/utilities.lua:813 -msgid "I've never seen this user before.\n" -"This command works by reply, username, user ID or text mention.\n" -"If you're using it by username and want to teach me who the user is, forward me one of their messages" -msgstr "" - -#: lua/groupbutler/utilities.lua:832 +#: lua/groupbutler/utilities.lua:651 #, lua-format msgid "Chat: %s [#chat%d]" msgstr "" -#: lua/groupbutler/utilities.lua:841 lua/groupbutler/utilities.lua:849 +#: lua/groupbutler/utilities.lua:660 lua/groupbutler/utilities.lua:668 #, lua-format -msgid "#%s (%d/%d), %s\n" +msgid "" +"#%s (%d/%d), %s\n" "• %s\n" "• User: %s" msgstr "" -#: lua/groupbutler/utilities.lua:855 +#: lua/groupbutler/utilities.lua:674 #, lua-format -msgid "#%s\n" +msgid "" +"#%s\n" "• %s\n" "• User: %s" msgstr "" -#: lua/groupbutler/utilities.lua:858 lua/groupbutler/utilities.lua:868 -#: lua/groupbutler/utilities.lua:871 lua/groupbutler/utilities.lua:874 +#: lua/groupbutler/utilities.lua:677 lua/groupbutler/utilities.lua:687 +#: lua/groupbutler/utilities.lua:690 lua/groupbutler/utilities.lua:693 #, lua-format -msgid "%s\n" +msgid "" +"%s\n" "• %s\n" "• By: %s" msgstr "" -#: lua/groupbutler/utilities.lua:861 +#: lua/groupbutler/utilities.lua:680 msgid "Get the new photo" msgstr "" -#: lua/groupbutler/utilities.lua:878 +#: lua/groupbutler/utilities.lua:697 #, lua-format -msgid "%s\n" +msgid "" +"%s\n" "• %s\n" "• By: %s\n" "• Reported to %d admin(s)" msgstr "" -#: lua/groupbutler/utilities.lua:882 +#: lua/groupbutler/utilities.lua:701 #, lua-format -msgid "#%s\n" +msgid "" +"#%s\n" "• %s\n" "• User: %s [#id%d]" msgstr "" -#: lua/groupbutler/utilities.lua:887 +#: lua/groupbutler/utilities.lua:706 #, lua-format -msgid "%s\n" +msgid "" +"%s\n" "• %s\n" "• User: %s" msgstr "" -#: lua/groupbutler/utilities.lua:889 +#: lua/groupbutler/utilities.lua:708 #, lua-format -msgid "\n" +msgid "" +"\n" "• Added by: %s [#id%d]" msgstr "" -#: lua/groupbutler/utilities.lua:902 +#: lua/groupbutler/utilities.lua:721 #, lua-format -msgid "#%s\n" +msgid "" +"#%s\n" "• Admin: %s [#id%d]\n" "• %s\n" "• User: %s [#id%d]\n" "• Count: %d/%d" msgstr "" -#: lua/groupbutler/utilities.lua:910 lua/groupbutler/utilities.lua:940 +#: lua/groupbutler/utilities.lua:729 lua/groupbutler/utilities.lua:759 #, lua-format -msgid "#%s\n" +msgid "" +"#%s\n" "• Admin: %s [#id%s]\n" "• %s\n" "• User: %s [#id%s]" msgstr "" -#: lua/groupbutler/utilities.lua:914 +#: lua/groupbutler/utilities.lua:733 #, lua-format -msgid "#%s\n" +msgid "" +"#%s\n" "• Admin: %s [#id%s]\n" "• %s\n" msgstr "" -#: lua/groupbutler/utilities.lua:917 +#: lua/groupbutler/utilities.lua:736 #, lua-format msgid "• Users involved: %d" msgstr "" -#: lua/groupbutler/utilities.lua:919 +#: lua/groupbutler/utilities.lua:738 #, lua-format msgid "• User: %s [#id%d]" msgstr "" -#: lua/groupbutler/utilities.lua:931 +#: lua/groupbutler/utilities.lua:750 #, lua-format -msgid "#%s\n" +msgid "" +"#%s\n" "• Admin: %s [#id%s]\n" "• %s\n" "• User: %s [#id%s]\n" "• Duration: %d days, %d hours" msgstr "" -#: lua/groupbutler/utilities.lua:944 +#: lua/groupbutler/utilities.lua:763 #, lua-format -msgid "#%s\n" +msgid "" +"#%s\n" "• %s\n" "• By: %s" msgstr "" -#: lua/groupbutler/utilities.lua:965 +#: lua/groupbutler/utilities.lua:784 #, lua-format -msgid "\n" +msgid "" +"\n" "• Action: #%s" msgstr "" -#: lua/groupbutler/utilities.lua:968 +#: lua/groupbutler/utilities.lua:787 #, lua-format -msgid "\n" +msgid "" +"\n" "• Reason: %s" msgstr "" -#: lua/groupbutler/utilities.lua:974 +#: lua/groupbutler/utilities.lua:793 msgid "Go to the message" msgstr "" - diff --git a/lua/groupbutler/chat.lua b/lua/groupbutler/chat.lua index e1e246811..57d5b5d2d 100644 --- a/lua/groupbutler/chat.lua +++ b/lua/groupbutler/chat.lua @@ -1,3 +1,5 @@ +local log = require("groupbutler.logging") + local Chat = {} local function p(self) @@ -5,14 +7,45 @@ local function p(self) end function Chat:new(obj, private) + assert(obj.id, "Chat: Missing obj.id") + assert(private.api, "Chat: Missing private.api") assert(private.db, "Chat: Missing private.db") setmetatable(obj, { - __index = self, + __index = function(s, index) + if self[index] then + return self[index] + end + return s:getProperty(index) + end, __private = private, }) return obj end +function Chat:getProperty(index) + local property = rawget(self, index) + if property == nil then + property = p(self).db:getChatProperty(self, index) + if property == nil then + local ok = p(self).api:getChat(self.id) + if not ok then + log.warn("Chat: Failed to get {property} for {id}", { + property = index, + id = self.id, + }) + return nil + end + for k,v in pairs(ok) do + self[k] = v + end + self:cache() + property = rawget(self, index) + end + self[index] = property + end + return property +end + function Chat:cache() p(self).db:cacheChat(self) end diff --git a/lua/groupbutler/chatmember.lua b/lua/groupbutler/chatmember.lua new file mode 100644 index 000000000..40b158228 --- /dev/null +++ b/lua/groupbutler/chatmember.lua @@ -0,0 +1,107 @@ +local log = require("groupbutler.logging") +local User = require("groupbutler.user") + +local ChatMember = {} + +local function p(self) + return getmetatable(self).__private +end + +function ChatMember:new(obj, private) + assert(obj.chat, "ChatMember: Missing obj.chat") + assert(obj.user, "ChatMember: Missing obj.user") + assert(private.db, "ChatMember: Missing private.db") + assert(private.api, "ChatMember: Missing private.api") + setmetatable(obj, { + __index = function(s, index) + if self[index] then + return self[index] + end + return s:getProperty(index) + end, + __private = private, + }) + return obj +end + +function ChatMember:getProperty(index) + local property = rawget(self, index) + if property == nil then + property = p(self).db:getChatMemberProperty(self, index) + if property == nil then + local ok = p(self).api:getChatMember(self.chat.id, self.user.id) + if not ok then + log.warn("ChatMember: Failed to get {property} for {chat_id}, {user_id}", { + property = index, + chat_id = self.chat.id, + user_id = self.user.id, + }) + return nil + end + for k,v in pairs(ok) do + self[k] = v + if k == "user" then + User:new(self.user, p(self)) + end + end + self:cache() + property = rawget(self, index) + end + self[index] = property + end + return property +end + +function ChatMember:cache() + p(self).db:cacheChatMember(self) +end + +function ChatMember:isAdmin() + if self.chat.type == "private" then -- This should never happen but... + return false + end + return self.status == "creator" or self.status == "administrator" +end + +function ChatMember:can(permission) + if self.chat.type == "private" then -- This should never happen but... + return false + end + if self.status == "creator" + or (self.status == "administrator" and self[permission]) then + return true + end + return false +end + +function ChatMember:ban(until_date) + local ok, err = p(self).api:kickChatMember(self.chat.id, self.user.id, until_date) + if not ok then + return nil, p(self).api_err:trans(err) + end + return ok +end + +function ChatMember:kick() + local ok, err = p(self).api:kickChatMember(self.chat.id, self.user.id) + if not ok then + return nil, p(self).api_err:trans(err) + end + p(self).api:unbanChatMember(self.chat.id, self.user.id) + return ok +end + +function ChatMember:mute(until_date) + local ok, err = p(self).api:restrictChatMember({ + chat_id = self.chat.id, + user_id = self.user.id, + until_date = until_date, + can_send_messages = false, + }) + if not ok then + return nil, p(self).api_err:trans(err) + end + return ok +end + +return ChatMember diff --git a/lua/groupbutler/main.lua b/lua/groupbutler/main.lua index 885ce4580..435dd9495 100644 --- a/lua/groupbutler/main.lua +++ b/lua/groupbutler/main.lua @@ -7,6 +7,7 @@ local plugins = require "groupbutler.plugins" local Message = require "groupbutler.message" local User = require "groupbutler.user" local Chat = require "groupbutler.chat" +local ChatMember = require "groupbutler.chatmember" local storage = require "groupbutler.storage" local locale = require "groupbutler.languages" local api_err = require "groupbutler.api_errors" @@ -41,21 +42,43 @@ function _M:new(update_obj) end local function inject_message_methods(message, update) + if message.from then + message.from = { + user = message.from, + chat = message.chat, + } + end Message:new(message, update) - if message.from then -- Sender is empty for messages sent to channels - User:new(message.from, update):cache() + if message.from then + if message.from.user then -- Sender is empty for messages sent to channels + User:new(message.from.user, update):cache() + end + if message.from.chat then + Chat:new(message.chat, update)--:cache() + end + if message.from.user and message.from.chat then + ChatMember:new(message.from, update)--:cache() + end end if message.forward_from then User:new(message.forward_from, update):cache() end - if message.chat then - Chat:new(message.chat, update) + if message.new_chat_members then + for k,v in pairs(message.new_chat_members) do + message.new_chat_members[k] = { + user = v, + chat = message.chat, + } + User:new(message.new_chat_members[k].user, update) + Chat:new(message.new_chat_members[k].chat, update) + ChatMember:new(message.new_chat_members[k], update):cache() + end end end local function add_message_methods(object, update) local message_objects = { - "message", "edited_message", "channel_post", -- Possible messages inside updates + "message", --[["edited_message",]] "channel_post", -- Possible messages inside updates "reply_to_message", "pinned_message", -- Possible messages inside messages } for _, message in pairs(message_objects) do @@ -75,9 +98,9 @@ local function collect_stats(self) local red = self.red local u = self.u local now = os.time(os.date("*t")) - if msg.chat.type ~= 'private' and msg.chat.type ~= 'inline' and msg.from then - red:hset('chat:'..msg.chat.id..':userlast', msg.from.id, now) --last message for each user - red:hset('bot:chats:latsmsg', msg.chat.id, now) --last message in the group + if msg.from.chat.type ~= 'private' and msg.from.chat.type ~= 'inline' and msg.from.user then + red:hset('chat:'..msg.from.chat.id..':userlast', msg.from.user.id, now) --last message for each user + red:hset('bot:chats:latsmsg', msg.from.chat.id, now) --last message in the group end u:metric_incr("messages_processed_count") u:metric_set("message_timestamp_distance_sec", now - msg.date) @@ -121,21 +144,21 @@ local function on_msg_receive(self, callback) -- The fn run whenever a message i end -- Set chat language - i18n:setLanguage(red:get('lang:'..msg.chat.id)) + i18n:setLanguage(red:get('lang:'..msg.from.chat.id)) -- Do not process messages from normal groups - if msg.chat.type == 'group' then - api:sendMessage(msg.chat.id, i18n([[Hello everyone! + if msg.from.chat.type == 'group' then + api:sendMessage(msg.from.chat.id, i18n([[Hello everyone! My name is %s, and I'm a bot made to help administrators in their hard work. Unfortunately I can't work in normal groups. If you need me, please ask the creator to convert this group to a supergroup and then add me again. ]]):format(bot.first_name)) - api:leaveChat(msg.chat.id) + api:leaveChat(msg.from.chat.id) if config.bot_settings.stream_commands then log.info('Bot was added to a normal group {by_name} [{from_id}] -> [{chat_id}]', { - by_name=msg.from.first_name, - from_id=msg.from.id, - chat_id=msg.chat.id, + by_name=msg.from.user.first_name, + from_id=msg.from.user.id, + chat_id=msg.from.chat.id, }) end return true @@ -167,11 +190,11 @@ Unfortunately I can't work in normal groups. If you need me, please ask the crea if blocks then -- init agroup if the bot wasn't aware to be in - if msg.chat.id < 0 - and msg.chat.type ~= 'inline' - and red:exists('chat:'..msg.chat.id..':settings') == 0 + if msg.from.chat.id < 0 + and msg.from.chat.type ~= 'inline' + and red:exists('chat:'..msg.from.chat.id..':settings') == 0 and not msg.service then - u:initGroup(msg.chat.id) + u:initGroup(msg.from.chat) end -- print some info in the terminal @@ -179,9 +202,9 @@ Unfortunately I can't work in normal groups. If you need me, please ask the crea log.info('{trigger} {from_name} [{from_id}] -> [{chat_id}]', { trigger=trigger, - from_name=msg.from.first_name, - from_id=msg.from.id, - chat_id=msg.chat.id, + from_name=msg.from.user.first_name, + from_id=msg.from.user.id, + chat_id=msg.from.chat.id, }) end @@ -230,7 +253,7 @@ function _M:process() function_key = 'onEditedMessage' end - self.message = self.message or self.edited_message + self.message = self.message or self.edited_message -- TODO: undo this local service_messages = { "left_chat_member", "new_chat_member", "new_chat_photo", "delete_chat_photo", "group_chat_created", @@ -291,7 +314,11 @@ function _M:process() self.message.message_id = self.message.message.message_id self.message.chat = self.message.message.chat else --when the inline keyboard is sent via the inline mode - self.message.chat = {type = 'inline', id = self.message.from.id, title = self.message.from.first_name} + self.message.chat = { + type = 'inline', + id = self.message.from.user.id, + title = self.message.from.user.first_name + } self.message.message_id = self.message.inline_message_id end self.message.date = os.time() diff --git a/lua/groupbutler/message.lua b/lua/groupbutler/message.lua index ebfad84b7..666df2ff5 100644 --- a/lua/groupbutler/message.lua +++ b/lua/groupbutler/message.lua @@ -1,26 +1,21 @@ -local message = {} +local User = require("groupbutler.user") +local ChatMember = require("groupbutler.chatmember") -function message:new(message_obj, update_obj) - message_obj.api = update_obj.api - message_obj.u = update_obj.u - setmetatable(message_obj, {__index = self}) - return message_obj -end +local Message = {} +local message = Message -local function is_from_admin(self) - if self.chat.type == "private" -- This should never happen but... - or not (self.chat.id < 0 or self.target_id) - or not self.from then - return false - end - return self.u:is_admin(self.target_id or self.chat.id, self.from.id) +local function p(self) + return getmetatable(self).__private end -function message:is_from_admin() - if self._cached_is_from_admin == nil then - self._cached_is_from_admin = is_from_admin(self) - end - return self._cached_is_from_admin +function Message:new(obj, private) + assert(private.api, "Message: Missing private.api") + assert(private.i18n, "Message: Missing private.i18n") + setmetatable(obj, { + __index = self, + __private = private, + }) + return obj end local function msg_type(self) @@ -75,9 +70,57 @@ function message:get_file_id() end function message:send_reply(text, parse_mode, disable_web_page_preview, disable_notification, reply_markup) - local api = self.api - return api:send_message(self.chat.id, text, parse_mode, disable_web_page_preview, disable_notification, + return p(self).api:sendMessage(self.chat.id, text, parse_mode, disable_web_page_preview, disable_notification, self.message_id, reply_markup) end -return message +function Message:getTargetMember(blocks) -- TODO: extract username/id from self.text or move blocks{} into self + if not self.reply_to_message + and (not blocks or not blocks[2]) then + return false, p(self).i18n("Reply to a user or mention them") + end + + local user_not_found = p(self).i18n([[I've never seen this user before. +This command works by reply, username, user ID or text mention. +If you're using it by username and want to teach me who the user is, forward me one of their messages]]) + + if self.reply_to_message then + if self.reply_to_message.new_chat_member then + return ChatMember:new({ + user = self.reply_to_message.new_chat_member, + chat = self.from.chat, + }, p(self)) + end + return self.reply_to_message.from + end + + if blocks[2]:byte(1) == string.byte("@") then + local user = User:new({username = blocks[2]}, p(self)) + if not user then + return false, user_not_found + end + return ChatMember:new({ + user = user, + chat = self.from.chat, + }, p(self)) + end + + if self.mention_id then + return ChatMember:new({ + user = User:new({id=self.mention_id}, p(self)), + chat = self.from.chat, + }, p(self)) + end + + local id = blocks[2]:match("%d+") + if id then + return ChatMember:new({ + user = User:new({id=id}, p(self)), + chat = self.from.chat, + }, p(self)) + end + + return false, user_not_found +end + +return Message diff --git a/lua/groupbutler/plugins/admin.lua b/lua/groupbutler/plugins/admin.lua index 88ef88ad1..da23f4162 100644 --- a/lua/groupbutler/plugins/admin.lua +++ b/lua/groupbutler/plugins/admin.lua @@ -1,6 +1,8 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" local json = require "cjson" +local User = require("groupbutler.user") +local Chat = require("groupbutler.chat") local _M = {} @@ -90,7 +92,7 @@ end local function get_chat_id(msg) if msg.text:find('$chat') then - return msg.chat.id + return msg.from.chat.id elseif msg.text:find('-%d+') then return msg.text:match('(-%d+)') elseif msg.reply then @@ -108,7 +110,7 @@ function _M:onTextMessage(blocks) local bot = self.bot local u = self.u local red = self.red - if not u:is_superadmin(msg.from.id) then return end + if not u:is_superadmin(msg.from.user.id) then return end for i=1, #triggers2 do blocks = match_pattern(self, triggers2[i], msg.text) @@ -118,7 +120,7 @@ function _M:onTextMessage(blocks) if not blocks or not next(blocks) then return true end --leave this plugin and continue to match the others if blocks[1] == 'admin' then - api:sendMessage(msg.from.id, json.encode(triggers2)) + api:sendMessage(msg.from.user.id, json.encode(triggers2)) end -- if blocks[1] == 'init' then -- local n_plugins = bot.init(true) @@ -131,11 +133,11 @@ function _M:onTextMessage(blocks) local cmd = io.popen('sudo tar -cpf '..bot.first_name:gsub(' ', '_')..'.tar *') cmd:read('*all') cmd:close() - api:sendDocument(msg.from.id, './'..bot.first_name:gsub(' ', '_')..'.tar') + api:sendDocument(msg.from.user.id, './'..bot.first_name:gsub(' ', '_')..'.tar') end if blocks[1] == 'save' then local res = red:save() - api:sendMessage(msg.chat.id, 'res: '..tostring(res)) + api:sendMessage(msg.from.chat.id, 'res: '..tostring(res)) end if blocks[1] == 'stats' then local text = '#stats `['..u:get_date()..']`:\n' @@ -166,11 +168,11 @@ function _M:onTextMessage(blocks) -- end -- text = text..'- *ops/sec*: `'..dbinfo.stats.instantaneous_ops_per_sec..'`\n' - api:sendMessage(msg.chat.id, text, "Markdown") + api:sendMessage(msg.from.chat.id, text, "Markdown") end -- if blocks[1] == 'lua' then -- local output = load_lua(blocks[2], msg) - -- api:sendMessage(msg.chat.id, output, "Markdown") + -- api:sendMessage(msg.from.chat.id, output, "Markdown") -- end if blocks[1] == 'run' then --read the output @@ -211,45 +213,45 @@ function _M:onTextMessage(blocks) msg:send_reply(text) end if blocks[1] == 'blocked' then - api:sendMessage(msg.chat.id, json.encode(red:smembers('bot:blocked'))) + api:sendMessage(msg.from.chat.id, json.encode(red:smembers('bot:blocked'))) end if blocks[1] == 'leave' then local text if not blocks[2] then - if msg.chat.type == 'private' then + if msg.from.chat.type == 'private' then text = 'ID missing' else - text = bot_leave(self, msg.chat.id) + text = bot_leave(self, msg.from.chat.id) end else text = bot_leave(self, blocks[2]) end - api:sendMessage(msg.from.id, text) + api:sendMessage(msg.from.user.id, text) end if blocks[1] == 'api errors' then local t = red:array_to_hash(red:hgetall('bot:errors')) - api:sendMessage(msg.chat.id, json.encode(t)) + api:sendMessage(msg.from.chat.id, json.encode(t)) end -- if blocks[1] == 'rediscli' then -- local redis_f = blocks[2]:gsub(' ', '(\'', 1) -- redis_f = redis_f:gsub(' ', '\',\'') -- redis_f = 'return red:'..redis_f..'\')' - -- redis_f = redis_f:gsub('$chat', msg.chat.id) - -- redis_f = redis_f:gsub('$from', msg.from.id) + -- redis_f = redis_f:gsub('$chat', msg.from.chat.id) + -- redis_f = redis_f:gsub('$from', msg.from.user.id) -- local output = load_lua(redis_f) -- msg:send_reply(output, "Markdown") -- end if blocks[1] == 'sendfile' then local path = blocks[2] - api:sendDocument(msg.from.id, path) + api:sendDocument(msg.from.user.id, path) end if blocks[1] == 'res' then - local username = blocks[2] - local user_id = { - normal = u:resolve_user(username), - lower = u:resolve_user(username:lower()) - } - api:sendMessage(msg.chat.id, json.encode(user_id)) + local user = User:new({username=blocks[2]}, self) + if user then + msg:send_reply(user:getLink(), "html") + else + msg:send_reply("User not found") + end end if blocks[1] == 'tban' then if blocks[2] == 'flush' then @@ -257,14 +259,14 @@ function _M:onTextMessage(blocks) msg:send_reply('Flushed!') end if blocks[2] == 'get' then - api:sendMessage(msg.chat.id, json.encode(red:array_to_hash(red:hgetall('tempbanned')))) + api:sendMessage(msg.from.chat.id, json.encode(red:array_to_hash(red:hgetall('tempbanned')))) end end if blocks[1] == 'remban' then - local user_id = u:resolve_user(blocks[2]) + local user = User:new({username = blocks[2]}, self) local text - if user_id then - red:del('ban:'..user_id) + if user then + red:del('ban:'..user.id) text = 'Done' else text = 'Username not stored' @@ -274,7 +276,7 @@ function _M:onTextMessage(blocks) if blocks[1] == 'rawinfo' then local chat_id = blocks[2] if blocks[2] == '$chat' then - chat_id = msg.chat.id + chat_id = msg.from.chat.id end local text = ''..chat_id for set, _ in pairs(config.chat_settings) do @@ -286,12 +288,12 @@ function _M:onTextMessage(blocks) text = text..'' - api:sendMessage(msg.chat.id, text, 'html') + api:sendMessage(msg.from.chat.id, text, 'html') end if blocks[1] == 'rawinfo2' then local chat_id if blocks[2] == '$chat' then - chat_id = msg.chat.id + chat_id = msg.from.chat.id else chat_id = blocks[2] end @@ -308,18 +310,18 @@ function _M:onTextMessage(blocks) text = text.."\n\n"..config.chat_sets[i]..'(set)>\n'..section end text = text..'' - local ok, err = api:sendMessage(msg.chat.id, text, 'html') + local ok, err = api:sendMessage(msg.from.chat.id, text, 'html') if not ok and err.description:match("message is too long") then local file_path = '/tmp/'..chat_id..'.txt' local file = io.open(file_path, "w") file:write(text) file:close() - api:sendDocument(msg.chat.id, file_path) + api:sendDocument(msg.from.chat.id, file_path) end end if blocks[1] == 'initgroup' then - u:initGroup(blocks[2]) - api:sendMessage(msg.chat.id, 'Done') + u:initGroup(Chat:new({id=blocks[2]}, self)) + api:sendMessage(msg.from.chat.id, 'Done') end if blocks[1] == 'remgroup' then local full = false @@ -329,7 +331,7 @@ function _M:onTextMessage(blocks) chat_id = blocks[3] end u:remGroup(chat_id) - api:sendMessage(msg.chat.id, 'Removed (heavy: '..tostring(full)..')') + api:sendMessage(msg.from.chat.id, 'Removed (heavy: '..tostring(full)..')') end if blocks[1] == 'cache' then local chat_id = get_chat_id(msg) @@ -338,18 +340,18 @@ function _M:onTextMessage(blocks) local permissions = red:smembers("cache:chat:"..chat_id..":"..members[i]..":permissions") members[members[i]] = permissions end - api:sendMessage(msg.chat.id, chat_id..' ➤ '..tostring(#members)..'\n'..json.encode(members)) + api:sendMessage(msg.from.chat.id, chat_id..' ➤ '..tostring(#members)..'\n'..json.encode(members)) end if blocks[1] == 'initcache' then local chat_id, text chat_id = get_chat_id(msg) - local res, code = u:cache_adminlist(chat_id) + local res, code = u:cache_adminlist(Chat:new({id=chat_id}, self)) if res then text = 'Cached ➤ '..code..' admins stored' else text = 'Failed: '..tostring(code) end - api:sendMessage(msg.chat.id, text) + api:sendMessage(msg.from.chat.id, text) end if blocks[1] == 'active' then local days = tonumber(blocks[2]) or 7 @@ -362,11 +364,11 @@ function _M:onTextMessage(blocks) n = n + 1 end end - api:sendMessage(msg.chat.id, 'Active groups in the last '..days..' days: '..n) + api:sendMessage(msg.from.chat.id, 'Active groups in the last '..days..' days: '..n) end if blocks[1] == 'getid' then if msg.reply.forward_from then - api:sendMessage(msg.chat.id, '`'..msg.reply.forward_from.id..'`', true) + api:sendMessage(msg.from.chat.id, '`'..msg.reply.forward_from.id..'`', true) end end if blocks[1] == 'permission' then @@ -375,7 +377,7 @@ function _M:onTextMessage(blocks) api:sendMessage(msg, "Can't find a chat_id") else local res = api:getChatMember(chat_id, bot.id) - api:sendMessage(msg.chat.id, ('%s\n%s'):format(chat_id, json.encode(res))) + api:sendMessage(msg.from.chat.id, ('%s\n%s'):format(chat_id, json.encode(res))) end end end diff --git a/lua/groupbutler/plugins/antispam.lua b/lua/groupbutler/plugins/antispam.lua index 2499a187c..2611c9241 100644 --- a/lua/groupbutler/plugins/antispam.lua +++ b/lua/groupbutler/plugins/antispam.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -55,54 +57,54 @@ function _M:on_message() local red = self.red local i18n = self.i18n - if not msg.inline and msg.spam and msg.chat.id < 0 and not msg.cb and not msg:is_from_admin() then - local status = red:hget('chat:'..msg.chat.id..':antispam', msg.spam) + if not msg.inline and msg.spam and msg.from.chat.id < 0 and not msg.cb and not msg.from:isAdmin() then + local status = red:hget('chat:'..msg.from.chat.id..':antispam', msg.spam) if status ~= null and status ~= 'alwd' then local whitelisted if msg.spam == 'links' then - whitelisted = is_whitelisted(self, msg.chat.id, msg.text:lower()) + whitelisted = is_whitelisted(self, msg.from.chat.id, msg.text:lower()) --[[elseif msg.forward_from_chat then if msg.forward_from_chat.type == 'channel' then - whitelisted = is_whitelisted_channel(msg.chat.id, msg.forward_from_chat.id) + whitelisted = is_whitelisted_channel(msg.from.chat.id, msg.forward_from_chat.id) end]] end if not whitelisted then local hammer_text = nil - local name = u:getname_final(msg.from) - local warns_received, max_allowed = getAntispamWarns(self, msg.chat.id, msg.from.id) --also increases the warns counter + local name = msg.from.user:getLink() + local warns_received, max_allowed = getAntispamWarns(self, msg.from.chat.id, msg.from.user.id) --also increases the warns counter if warns_received >= max_allowed then if status == 'del' then - api:deleteMessage(msg.chat.id, msg.message_id) + api:deleteMessage(msg.from.chat.id, msg.message_id) end - local action = red:hget('chat:'..msg.chat.id..':antispam', 'action') + local action = red:hget('chat:'..msg.from.chat.id..':antispam', 'action') if action == null then action = config.chat_settings['antispam']['action'] end local res if action == 'ban' then - res = u:banUser(msg.chat.id, msg.from.id) + res = msg.from:ban() elseif action == 'kick' then - res = u:kickUser(msg.chat.id, msg.from.id) + res = msg.from:kick() elseif action == 'mute' then - res = u:muteUser(msg.chat.id, msg.from.id) + res = msg.from:mute() end if res then - red:hdel('chat:'..msg.chat.id..':spamwarns', msg.from.id) --remove spam warns - api:sendMessage(msg.chat.id, + red:hdel('chat:'..msg.from.chat.id..':spamwarns', msg.from.user.id) --remove spam warns + api:sendMessage(msg.from.chat.id, i18n('%s %s for spam! (%d/%d)'):format(name, humanizations(self)[action], warns_received, max_allowed), 'html' ) end else if status == 'del' and warns_received == max_allowed - 1 then - api:deleteMessage(msg.chat.id, msg.message_id) + api:deleteMessage(msg.from.chat.id, msg.message_id) msg:send_reply(i18n('%s, spam is not allowed here. The next time you will be restricted'):format(name), 'html') elseif status == 'del' then --just delete - api:deleteMessage(msg.chat.id, msg.message_id) + api:deleteMessage(msg.from.chat.id, msg.message_id) elseif status ~= 'del' then msg:send_reply(i18n("%s, this kind of message is not allowed in this chat (%d/%d)") :format(name, warns_received, max_allowed), 'html') @@ -279,44 +281,48 @@ end function _M:onCallbackQuery(blocks) local api = self.api local msg = self.message - local u = self.u local i18n = self.i18n if blocks[1] == 'alert' then local text = get_alert_text(self, blocks[2]) api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) - else + return + end - local chat_id = msg.target_id - if not u:is_allowed('config', chat_id, msg.from) then - api:answerCallbackQuery(msg.cb_id, i18n("You're no longer an admin")) - else - local antispam_first = i18n([[*Anti-spam settings* + local member = ChatMember:new({ + chat = Chat:new({id=msg.target_id}, self), + user = msg.from.user, + }, self) + + if not member:can("can_change_info") then + api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to change settings")) + return + end + + local antispam_first = i18n([[*Anti-spam settings* Choose which kind of message you want to forbid • ✅ = *Allowed* • ❌ = *Not allowed* • 🗑 = *Delete* When set on `delete`, the bot doesn't warn users until they are about to be kicked/banned/muted (at the second-to-last warning)]]) -- luacheck: ignore 631 - local keyboard, text - - if blocks[1] == 'toggle' then - if blocks[2] == 'forwards' or blocks[2] == 'links' then - text = toggleAntispamSetting(self, chat_id, blocks[2]) - else - if blocks[2] == 'raise' or blocks[2] == 'dim' then - text = changeWarnsNumber(self, chat_id, blocks[2]) - elseif blocks[2] == 'action' then - text = changeAction(self, chat_id, blocks[2]) - end - end - end + local keyboard, text - keyboard = doKeyboard_antispam(self, chat_id) - api:editMessageText(msg.chat.id, msg.message_id, nil, antispam_first, "Markdown", nil, keyboard) - if text then api:answerCallbackQuery(msg.cb_id, text) end + if blocks[1] == "toggle" then + if blocks[2] == "forwards" or blocks[2] == "links" then + text = toggleAntispamSetting(self, member.chat.id, blocks[2]) + elseif blocks[2] == "raise" or blocks[2] == "dim" then + text = changeWarnsNumber(self, member.chat.id, blocks[2]) + elseif blocks[2] == "action" then + text = changeAction(self, member.chat.id, blocks[2]) end end + + keyboard = doKeyboard_antispam(self, member.chat.id) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, antispam_first, "Markdown", nil, keyboard) + if text then + api:answerCallbackQuery(msg.cb_id, text) + end end local function edit_channels_whitelist(self, chat_id, list, action) @@ -348,65 +354,36 @@ end function _M:onTextMessage(blocks) local msg = self.message - local u = self.u local red = self.red local i18n = self.i18n - if u:is_allowed('texts', msg.chat.id, msg.from) then - if (blocks[1] == 'wl' or blocks[1] == 'whitelist') and blocks[2] then - if blocks[2] == '-' then - local set = ('chat:%d:whitelist'):format(msg.chat.id) - local n = red:scard(set) or 0 - local text - if n == 0 then - text = i18n("_The whitelist was already empty_") - else - red:del(set) - text = i18n("*Whitelist cleaned*\n%d links have been removed"):format(n) - end - msg:send_reply(text, "Markdown") - else - local text - if msg.entities then - local links = urls_table(msg.entities, msg.text) - if not next(links) then - text = i18n("_I can't find any url in this message_") - else - local new = red:sadd(('chat:%d:whitelist'):format(msg.chat.id), unpack(links)) - text = i18n("%d link(s) will be whitelisted"):format(#links - (#links - new)) - if new ~= #links then - text = text..i18n("\n%d links were already in the list"):format(#links - new) - end - end - else - text = i18n("_I can't find any url in this message_") - end - msg:send_reply(text, "Markdown") - end - end - if (blocks[1] == 'wl' or blocks[1] == 'whitelist') and not blocks[2] then - local links = red:smembers(('chat:%d:whitelist'):format(msg.chat.id)) - if not next(links) then - msg:send_reply(i18n("_The whitelist is empty_.\nUse `/wl [links]` to add some links to the whitelist"),"Markdown") + if not msg.from:isAdmin() then + return + end + + if (blocks[1] == 'wl' or blocks[1] == 'whitelist') and blocks[2] then + if blocks[2] == '-' then + local set = ('chat:%d:whitelist'):format(msg.from.chat.id) + local n = red:scard(set) or 0 + local text + if n == 0 then + text = i18n("_The whitelist was already empty_") else - local text = i18n("Whitelisted links:\n\n") - for i=1, #links do - text = text..'• '..links[i]..'\n' - end - msg:send_reply(text) + red:del(set) + text = i18n("*Whitelist cleaned*\n%d links have been removed"):format(n) end - end - if blocks[1] == 'unwl' or blocks[1] == 'unwhitelist' then + msg:send_reply(text, "Markdown") + else local text if msg.entities then local links = urls_table(msg.entities, msg.text) if not next(links) then text = i18n("_I can't find any url in this message_") else - local removed = red:srem(('chat:%d:whitelist'):format(msg.chat.id), unpack(links)) - text = i18n("%d link(s) removed from the whitelist"):format(removed) - if removed ~= #links then - text = text..i18n("\n%d links were already in the list"):format(#links - removed) + local new = red:sadd(('chat:%d:whitelist'):format(msg.from.chat.id), unpack(links)) + text = i18n("%d link(s) will be whitelisted"):format(#links - (#links - new)) + if new ~= #links then + text = text..i18n("\n%d links were already in the list"):format(#links - new) end end else @@ -414,52 +391,91 @@ function _M:onTextMessage(blocks) end msg:send_reply(text, "Markdown") end - if blocks[1] == 'funwl' then --force the unwhitelist of a link - red:srem(('chat:%d:whitelist'):format(msg.chat.id), blocks[2]) - msg:send_reply('Done') - end - if blocks[1] == 'wlchan' and not blocks[2] then - local channels = red:smembers(('chat:%d:chanwhitelist'):format(msg.chat.id)) - if not next(channels) then - msg:send_reply(i18n("_Whitelist of channels empty_"), "Markdown") - else - msg:send_reply(i18n("*Whitelisted channels:*\n%s"):format(table.concat(channels, '\n')), "Markdown") + return + end + + if (blocks[1] == 'wl' or blocks[1] == 'whitelist') and not blocks[2] then + local links = red:smembers(('chat:%d:whitelist'):format(msg.from.chat.id)) + if not next(links) then + msg:send_reply(i18n("_The whitelist is empty_.\nUse `/wl [links]` to add some links to the whitelist"),"Markdown") + else + local text = i18n("Whitelisted links:\n\n") + for i=1, #links do + text = text..'• '..links[i]..'\n' end + msg:send_reply(text) end - if blocks[1] == 'wlchan' and blocks[2] then - local for_entered, channels = edit_channels_whitelist(self, msg.chat.id, blocks[2], 'add') + return + end - if not for_entered then - msg:send_reply(i18n("_I can't find a channel ID in your message_"), "Markdown") + if blocks[1] == 'unwl' or blocks[1] == 'unwhitelist' then + local text + if msg.entities then + local links = urls_table(msg.entities, msg.text) + if not next(links) then + text = i18n("_I can't find any url in this message_") else - local text = '' - if next(channels.valid) then - text = text..("*Channels whitelisted*: `%s`\n"):format(table.concat(channels.valid, ', ')) - end - if next(channels.not_valid) then - text = text..("*Channels already whitelisted*: `%s`\n"):format(table.concat(channels.not_valid, ', ')) + local removed = red:srem(('chat:%d:whitelist'):format(msg.from.chat.id), unpack(links)) + text = i18n("%d link(s) removed from the whitelist"):format(removed) + if removed ~= #links then + text = text..i18n("\n%d links were already in the list"):format(#links - removed) end - - msg:send_reply(text, "Markdown") end + else + text = i18n("_I can't find any url in this message_") end - if blocks[1] == 'unwlchan' then - local for_entered, channels = edit_channels_whitelist(self, msg.chat.id, blocks[2], 'rem') + msg:send_reply(text, "Markdown") + return + end - if not for_entered then - msg:send_reply(i18n("_I can't find a channel ID in your message_"), "Markdown") - else - local text = '' - if next(channels.valid) then - text = text..("*Channels unwhitelisted*: `%s`\n"):format(table.concat(channels.valid, ', ')) - end - if next(channels.not_valid) then - text = text..("*Channels not whitelisted*: `%s`\n"):format(table.concat(channels.not_valid, ', ')) - end + if blocks[1] == 'funwl' then --force the unwhitelist of a link + red:srem(('chat:%d:whitelist'):format(msg.from.chat.id), blocks[2]) + msg:send_reply('Done') + return + end + + if blocks[1] == 'wlchan' and not blocks[2] then + local channels = red:smembers(('chat:%d:chanwhitelist'):format(msg.from.chat.id)) + if not next(channels) then + msg:send_reply(i18n("_Whitelist of channels empty_"), "Markdown") + else + msg:send_reply(i18n("*Whitelisted channels:*\n%s"):format(table.concat(channels, '\n')), "Markdown") + end + return + end - msg:send_reply(text, "Markdown") + if blocks[1] == 'wlchan' and blocks[2] then + local for_entered, channels = edit_channels_whitelist(self, msg.from.chat.id, blocks[2], 'add') + if not for_entered then + msg:send_reply(i18n("_I can't find a channel ID in your message_"), "Markdown") + else + local text = '' + if next(channels.valid) then + text = text..("*Channels whitelisted*: `%s`\n"):format(table.concat(channels.valid, ', ')) end + if next(channels.not_valid) then + text = text..("*Channels already whitelisted*: `%s`\n"):format(table.concat(channels.not_valid, ', ')) + end + msg:send_reply(text, "Markdown") + end + return + end + + if blocks[1] == 'unwlchan' then + local for_entered, channels = edit_channels_whitelist(self, msg.from.chat.id, blocks[2], 'rem') + if not for_entered then + msg:send_reply(i18n("_I can't find a channel ID in your message_"), "Markdown") + else + local text = '' + if next(channels.valid) then + text = text..("*Channels unwhitelisted*: `%s`\n"):format(table.concat(channels.valid, ', ')) + end + if next(channels.not_valid) then + text = text..("*Channels not whitelisted*: `%s`\n"):format(table.concat(channels.not_valid, ', ')) + end + msg:send_reply(text, "Markdown") end + return end end diff --git a/lua/groupbutler/plugins/backup.lua b/lua/groupbutler/plugins/backup.lua index f7f2a0972..01576cbf1 100644 --- a/lua/groupbutler/plugins/backup.lua +++ b/lua/groupbutler/plugins/backup.lua @@ -89,10 +89,10 @@ function _M:onTextMessage(blocks) local i18n = self.i18n local u = self.u - if not msg:is_from_admin() then return end + if not msg.from:isAdmin() then return end if blocks[1] == 'snap' then - local key = 'chat:'..msg.chat.id..':lastsnap' + local key = 'chat:'..msg.from.chat.id..':lastsnap' local last_user = red:get(key) if last_user ~= null then -- A snapshot has been done recently local ttl = red:ttl(key) @@ -104,12 +104,12 @@ Wait [%s] to use it again return end - local file_path = gen_backup(self, msg.chat.id) - local ok = api:sendDocument(msg.from.id, {path = file_path}, ('#snap\n%s'):format(msg.chat.title)) + local file_path = gen_backup(self, msg.from.chat.id) + local ok = api:sendDocument(msg.from.user.id, {path = file_path}, ('#snap\n%s'):format(msg.from.chat.title)) if not ok then return end - local name = u:getname_final(msg.from) + local name = msg.from.user:getLink() red:setex(key, 10800, name) --3 hours msg:send_reply(i18n('*Sent in private*'), "Markdown") return @@ -118,36 +118,36 @@ Wait [%s] to use it again local text if not msg.reply then text = i18n('Invalid input. Please reply to the backup file (/snap command to get it)') - api:sendMessage(msg.chat.id, text) + api:sendMessage(msg.from.chat.id, text) return end if not msg.reply.document then text = i18n('Invalid input. Please reply to a document') - api:sendMessage(msg.chat.id, text) + api:sendMessage(msg.from.chat.id, text) return end - if msg.reply.document.file_name ~= 'snap'..msg.chat.id..'.gbb' then + if msg.reply.document.file_name ~= 'snap'..msg.from.chat.id..'.gbb' then text = i18n('This is not a valid backup file.\nReason: invalid name (%s)') :format(tostring(msg.reply_to_message.document.file_name)) - api:sendMessage(msg.chat.id, text) + api:sendMessage(msg.from.chat.id, text) return end local res = api:getFile(msg.reply.document.file_id) local download_link = u:telegram_file_link(res) - local file_path, code = u:download_to_file(download_link, '/tmp/'..msg.chat.id..'.json') + local file_path, code = u:download_to_file(download_link, '/tmp/'..msg.from.chat.id..'.json') if not file_path then text = i18n('Download of the file failed with code %s'):format(tostring(code)) - api:sendMessage(msg.chat.id, text) + api:sendMessage(msg.from.chat.id, text) return end local data = load_data(file_path) for chat_id, group_data in pairs(data) do chat_id = tonumber(chat_id) - if tonumber(chat_id) ~= msg.chat.id then - text = i18n('Chat IDs don\'t match (%s and %s)'):format(tostring(chat_id), tostring(msg.chat.id)) - api:sendMessage(msg.chat.id, text) + if tonumber(chat_id) ~= msg.from.chat.id then + text = i18n('Chat IDs don\'t match (%s and %s)'):format(tostring(chat_id), tostring(msg.from.chat.id)) + api:sendMessage(msg.from.chat.id, text) return end --restoring sets @@ -169,7 +169,7 @@ Wait [%s] to use it again end end end - api:sendMessage(msg.chat.id, i18n([[Import was successful. + api:sendMessage(msg.from.chat.id, i18n([[Import was successful. Important: - #extra commands which are associated with a media must be set again if the bot you are using now is different from the bot that originated the backup. diff --git a/lua/groupbutler/plugins/banhammer.lua b/lua/groupbutler/plugins/banhammer.lua index afdacf836..224af23f7 100644 --- a/lua/groupbutler/plugins/banhammer.lua +++ b/lua/groupbutler/plugins/banhammer.lua @@ -1,4 +1,6 @@ local config = require "groupbutler.config" +local ChatMember = require("groupbutler.chatmember") +local User = require("groupbutler.user") local _M = {} @@ -57,21 +59,21 @@ function _M:onTextMessage(blocks) local api_err = self.api_err local u = self.u - if msg.chat.type == "private" - or not u:can(msg.chat.id, msg.from.id, "can_restrict_members") then + if msg.from.chat.type == "private" + or not msg.from:can("can_restrict_members") then return end - - local user_id, error_translation_key = u:get_user_id(msg, blocks) - - if not user_id and blocks[1] ~= "kickme" and blocks[1] ~= "fwdban" then - msg:send_reply(error_translation_key, "Markdown") - return + local admin = msg.from + local target + do + local err + target, err = msg:getTargetMember(blocks) + if not target and blocks[1] ~= "fwdban" then + msg:send_reply(err, "Markdown") + return + end end - if tonumber(user_id) == bot.id then return end - - local chat_id = msg.chat.id - local admin, kicked = u:getnames_complete(msg, blocks) + if tonumber(target.user.id) == bot.id then return end --print(get_motivation(msg)) @@ -81,63 +83,87 @@ function _M:onTextMessage(blocks) if tonumber(time_value) > 100 then time_value = 100 end - local key = ('chat:%s:%s:tbanvalue'):format(msg.chat.id, user_id) + local key = ('chat:%s:%s:tbanvalue'):format(msg.from.chat.id, target.user.id) red:setex(key, 3600, time_value) end - - local markup = markup_tempban(self, msg.chat.id, user_id) + local markup = markup_tempban(self, msg.from.chat.id, target.user.id) msg:send_reply(i18n("Use -/+ to edit the value, then select a timeframe to temporary ban the user"), "Markdown", nil, nil, markup) end + + local text if blocks[1] == 'kick' then - local ok, err = u:kickUser(chat_id, user_id) - if ok then - u:logEvent('kick', msg, {motivation = get_motivation(msg), admin = admin, user = kicked, user_id = user_id}) - api:sendMessage(msg.chat.id, i18n("%s kicked %s!"):format(admin, kicked), 'html', true) - else + local ok, err = target:kick() + if not ok then msg:send_reply(err, "Markdown") + return end + u:logEvent("kick", msg, { + motivation = get_motivation(msg), + admin = admin.user, + user = target.user, + user_id = target.user.id + }) + text = i18n("%s kicked %s!"):format(admin.user:getLink(), target.user:getLink()) + api:sendMessage(msg.from.chat.id, text, "html", true) end + if blocks[1] == 'ban' then - local ok, err = u:banUser(chat_id, user_id) - if ok then - u:logEvent('ban', msg, {motivation = get_motivation(msg), admin = admin, user = kicked, user_id = user_id}) - api:sendMessage(msg.chat.id, i18n("%s banned %s!"):format(admin, kicked), 'html', true) - else + local ok, err = target:ban() + if not ok then msg:send_reply(err, "Markdown") end + u:logEvent("ban", msg, { + motivation = get_motivation(msg), + admin = admin.user, + user = target.user, + user_id = target.user.id + }) + text = i18n("%s banned %s!"):format(admin.user:getLink(), target.user:getLink()) + api:sendMessage(msg.from.chat.id, text, "html", true) end + if blocks[1] == 'fwdban' then if not msg.reply or not msg.reply.forward_from then msg:send_reply(i18n("_Use this command in reply to a forwarded message_"), "Markdown") - else - user_id = msg.reply.forward_from.id - local ok, err = u:banUser(chat_id, user_id) - if ok then - u:logEvent('ban', msg, {motivation = get_motivation(msg), admin = admin, user = kicked, user_id = user_id}) - api:sendMessage(msg.chat.id, i18n("%s banned %s!"):format(admin, u:getname_final(msg.reply.forward_from)), 'html') - else - msg:send_reply(err, "Markdown") - end + return end + target = msg.reply.forward_from + local ok, err = target:ban() + if not ok then + msg:send_reply(err, "Markdown") + end + u:logEvent("ban", msg, { + motivation = get_motivation(msg), + admin = admin.user, + user = target.user, + user_id = target.user.id + }) + text = i18n("%s banned %s!"):format(admin.user:getLink(), target.user:getLink()) + api:sendMessage(msg.from.chat.id, text, "html", true) end + if blocks[1] == 'unban' then - if u:is_admin(chat_id, user_id) then + if target:isAdmin() then msg:send_reply(i18n("_An admin can't be unbanned_"), "Markdown") return end - local ok, err = api:getChatMember(chat_id, user_id) - if not ok then - msg:send_reply(api_err:trans(err), 'html') + if target.status ~= "kicked" then + msg:send_reply(i18n("This user is not banned!")) return end - if ok.status ~= 'kicked' then - msg:send_reply(i18n("This user is not banned!")) + local ok, err = api:unbanChatMember(target.chat.id, target.user.id) + if not ok then + msg:send_reply(api_err:trans(err), "Markdown") return end - api:unbanChatMember(chat_id, user_id) - u:logEvent('unban', msg, {motivation = get_motivation(msg), admin = admin, user = kicked, user_id = user_id}) - msg:send_reply(i18n("%s unbanned by %s!"):format(kicked, admin), 'html') + u:logEvent("unban", msg, { + motivation = get_motivation(msg), + admin = admin, + user = target.user, + user_id = target.user.id + }) + msg:send_reply(i18n("%s unbanned by %s!"):format(target.user:getLink(), admin.user:getLink()), 'html') end end @@ -146,67 +172,75 @@ function _M:onCallbackQuery(matches) local msg = self.message local red = self.red local i18n = self.i18n - local u = self.u - if not u:can(msg.chat.id, msg.from.id, 'can_restrict_members') then + if msg.from.chat.type == "private" + or not msg.from:can("can_restrict_members") then api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to restrict members"), true) - else - if matches[1] == 'nil' then - api:answerCallbackQuery(msg.cb_id, - i18n("Tap on the -/+ buttons to change this value. Then select a timeframe to execute the ban"), true) - elseif matches[1] == 'val' then - local user_id = matches[3] - local key = ('chat:%d:%s:tbanvalue'):format(msg.chat.id, user_id) - local current_value, new_value - current_value = tonumber(red:get(key)) or 3 - if matches[2] == 'm' then - new_value = current_value - 1 - if new_value < 1 then - api:answerCallbackQuery(msg.cb_id, i18n("You can't set a lower value")) - return --don't proceed - else - red:setex(key, 3600, new_value) - end - elseif matches[2] == 'p' then - new_value = current_value + 1 - if new_value > 100 then - api:answerCallbackQuery(msg.cb_id, i18n("Stop!!!"), true) - return --don't proceed - else - red:setex(key, 3600, new_value) - end - end + return + end - local markup = markup_tempban(self, msg.chat.id, user_id, new_value) - api:editMessageReplyMarkup(msg.chat.id, msg.message_id, nil, markup) - elseif matches[1] == 'ban' then - local user_id = matches[3] - local key = ('chat:%d:%s:tbanvalue'):format(msg.chat.id, user_id) - local time_value = tonumber(red:get(key)) or 3 - local timeframe_string, until_date - if matches[2] == 'h' then - time_value = time_value <= 24 and time_value or 24 - timeframe_string = i18n('hours') - until_date = msg.date + (time_value * 3600) - elseif matches[2] == 'd' then - time_value = time_value <= 30 and time_value or 30 - timeframe_string = i18n('days') - until_date = msg.date + (time_value * 3600 * 24) - elseif matches[2] == 'm' then - time_value = time_value <= 60 and time_value or 60 - timeframe_string = i18n('minutes') - until_date = msg.date + (time_value * 60) - end + if matches[1] == "nil" then + api:answerCallbackQuery(msg.cb_id, + i18n("Tap on the -/+ buttons to change this value. Then select a timeframe to execute the ban"), true) + return + end - local ok, err = u:banUser(msg.chat.id, user_id, until_date) - if ok then - local text = i18n("User banned for %d %s"):format(time_value, timeframe_string) - api:editMessageText(msg.chat.id, msg.message_id, nil, text) - red:del(key) + local target = ChatMember:new({ + user = User:new({id=matches[3]}, self), + chat = msg.from.chat, + }) + + if matches[1] == "val" then + local key = ("chat:%d:%s:tbanvalue"):format(msg.from.chat.id, target.user.id) + local current_value, new_value + current_value = tonumber(red:get(key)) or 3 + if matches[2] == "m" then + new_value = current_value - 1 + if new_value < 1 then + api:answerCallbackQuery(msg.cb_id, i18n("You can't set a lower value")) + return --don't proceed + else + red:setex(key, 3600, new_value) + end + elseif matches[2] == "p" then + new_value = current_value + 1 + if new_value > 100 then + api:answerCallbackQuery(msg.cb_id, i18n("Stop!!!"), true) + return --don't proceed else - api:editMessageText(msg.chat.id, msg.message_id, nil, err) + red:setex(key, 3600, new_value) end end + local markup = markup_tempban(self, msg.from.chat.id, target.user.id, new_value) + api:editMessageReplyMarkup(msg.from.chat.id, msg.message_id, nil, markup) + return + end + + if matches[1] == 'ban' then + local key = ('chat:%d:%s:tbanvalue'):format(msg.from.chat.id, target.user.id) + local time_value = tonumber(red:get(key)) or 3 + local timeframe_string, until_date + if matches[2] == 'h' then + time_value = time_value <= 24 and time_value or 24 + timeframe_string = i18n('hours') + until_date = msg.date + (time_value * 3600) + elseif matches[2] == 'd' then + time_value = time_value <= 30 and time_value or 30 + timeframe_string = i18n('days') + until_date = msg.date + (time_value * 3600 * 24) + elseif matches[2] == 'm' then + time_value = time_value <= 60 and time_value or 60 + timeframe_string = i18n('minutes') + until_date = msg.date + (time_value * 60) + end + local ok, err = target:ban(until_date) + if not ok then + api:editMessageText(msg.from.chat.id, msg.message_id, nil, err) + return + end + local text = i18n("User banned for %d %s"):format(time_value, timeframe_string) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, text) + red:del(key) end end diff --git a/lua/groupbutler/plugins/configure.lua b/lua/groupbutler/plugins/configure.lua index 359c6c2e6..e154b8969 100644 --- a/lua/groupbutler/plugins/configure.lua +++ b/lua/groupbutler/plugins/configure.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local api_u = require "telegram-bot-api.utilities" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -12,42 +14,48 @@ function _M:new(update_obj) return plugin_obj end -local function doKeyboardConfig(self, chat_id, user_id) - local u = self.u +local function doKeyboardConfig(self, member) local i18n = self.i18n local reply_markup = api_u.InlineKeyboardMarkup:new() - reply_markup:row({text = i18n("🛠 Menu"), callback_data = 'config:menu:'..chat_id}) - reply_markup:row({text = i18n("⚡️ Antiflood"), callback_data = 'config:antiflood:'..chat_id}) - reply_markup:row({text = i18n("🌈 Media"), callback_data = 'config:media:'..chat_id}) - reply_markup:row({text = i18n("🚫 Antispam"), callback_data = 'config:antispam:'..chat_id}) - reply_markup:row({text = i18n("📥 Log channel"), callback_data = 'config:logchannel:'..chat_id}) - - if u:can(chat_id, user_id, "can_restrict_members") then - reply_markup:row({text = i18n("⛔️ Default permissions"), callback_data = 'config:defpermissions:'..chat_id}) + reply_markup:row({text = i18n("🛠 Menu"), callback_data = "config:menu:"..member.chat.id}) + reply_markup:row({text = i18n("⚡️ Antiflood"), callback_data = "config:antiflood:"..member.chat.id}) + reply_markup:row({text = i18n("🌈 Media"), callback_data = "config:media:"..member.chat.id}) + reply_markup:row({text = i18n("🚫 Antispam"), callback_data = "config:antispam:"..member.chat.id}) + reply_markup:row({text = i18n("📥 Log channel"), callback_data = "config:logchannel:"..member.chat.id}) + if member:can("can_restrict_members") then + reply_markup:row({text = i18n("⛔️ Default permissions"), callback_data = "config:defpermissions:"..member.chat.id}) -- luacheck: ignore 631 end return reply_markup end +local function messageConstructor(self, member) + local i18n = self.i18n + local text = i18n("Change the settings of your group") + if member.chat.title then + text = ("%s\n"):format(member.chat.title:escape_html())..text + end + return { + chat_id = member.user.id, + text = text, + parse_mode = "html", + reply_markup = doKeyboardConfig(self, member) + } +end + function _M:onTextMessage() local api = self.api local msg = self.message local u = self.u local i18n = self.i18n - if msg.chat.type ~= "supergroup" - or not msg:is_from_admin() then + if msg.from.chat.type ~= "supergroup" + or not msg.from:isAdmin() then return end - msg.chat:cache() - local res = api:sendMessage({ - chat_id = msg.from.id, - text = ("%s\n"):format(msg.chat.title:escape_html())..i18n("Change the settings of your group"), - parse_mode = "html", - reply_markup = doKeyboardConfig(self, msg.chat.id, msg.from.id), - }) + local res = api:sendMessage(messageConstructor(self, msg.from)) - if u:is_silentmode_on(msg.chat.id) then -- send the response in the group only if the silent mode is off + if u:is_silentmode_on(msg.from.chat.id) then -- send the response in the group only if the silent mode is off return end @@ -55,29 +63,21 @@ function _M:onTextMessage() u:sendStartMe(msg) return end - api:sendMessage(msg.chat.id, i18n("_I've sent you the keyboard via private message_"), "Markdown") + api:sendMessage(msg.from.chat.id, i18n("_I've sent you the keyboard via private message_"), "Markdown") end function _M:onCallbackQuery() local api = self.api local msg = self.message - local i18n = self.i18n - local db = self.db - local chat_id = msg.target_id - local text = i18n("Change the settings of your group") - local chat_title = db:getChatTitle({id=chat_id}) - if chat_title then - text = ("%s\n"):format(chat_title:escape_html())..text - end + local member = ChatMember:new({ + chat = Chat:new({id=msg.target_id}, self), + user = msg.from.user, + }, self) - api:editMessageText({ - chat_id = msg.chat.id, - message_id = msg.message_id, - text = text, - parse_mode = "html", - reply_markup = doKeyboardConfig(self, chat_id, msg.from.id) - }) + local body = messageConstructor(self, member) + body.message_id = msg.message_id + api:editMessageText(body) end _M.triggers = { diff --git a/lua/groupbutler/plugins/dashboard.lua b/lua/groupbutler/plugins/dashboard.lua index 1ac36d870..0ef5e37a8 100644 --- a/lua/groupbutler/plugins/dashboard.lua +++ b/lua/groupbutler/plugins/dashboard.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -85,21 +87,21 @@ function _M:onTextMessage() local msg = self.message local u = self.u local i18n = self.i18n - if msg.chat.type ~= 'private' then - local chat_id = msg.chat.id + if msg.from.chat.type ~= 'private' then + local chat_id = msg.from.chat.id local reply_markup = doKeyboard_dashboard(self, chat_id) local ok = api:send_message{ - chat_id = msg.from.id, + chat_id = msg.from.user.id, text = i18n("Navigate this message to see *all the info* about this group!"), parse_mode = "Markdown", reply_markup = reply_markup } - if not u:is_silentmode_on(msg.chat.id) then --send the responde in the group only if the silent mode is off + if not u:is_silentmode_on(msg.from.chat.id) then --send the responde in the group only if the silent mode is off if not ok then u:sendStartMe(msg) return end - api:sendMessage(msg.chat.id, i18n("_I've sent you the group dashboard via private message_"), "Markdown") + api:sendMessage(msg.from.chat.id, i18n("_I've sent you the group dashboard via private message_"), "Markdown") end end end @@ -110,35 +112,35 @@ function _M:onCallbackQuery(blocks) local u = self.u local red = self.red local i18n = self.i18n - local chat_id = msg.target_id + local request = blocks[2] local text, notification local parse_mode = "Markdown" - local res = api:getChat(chat_id) - if not res then - api:answerCallbackQuery(msg.cb_id, i18n("🚫 This group does not exist")) - return - end - -- Private chats don't have a username - local private = not res.username - res = api:getChatMember(chat_id, msg.from.id) - if not res or (res.status == 'left' or res.status == 'kicked') and private then - api:editMessageText(msg.from.id, msg.message_id, nil, i18n("🚷 You are not a member of the chat. " .. + + local chat = Chat:new({id=msg.target_id}, self) + local member = ChatMember:new({ + chat = chat, + user = msg.from.user, + }, self) + + if member.status == 'left' + or member.status == 'kicked' then + api:editMessageText(msg.from.user.id, msg.message_id, nil, i18n("🚷 You are not a member of the chat. " .. "You can't see the settings of a private group.")) return end - local reply_markup = doKeyboard_dashboard(self, chat_id) + local reply_markup = doKeyboard_dashboard(self, chat.id) if request == 'settings' then - text = u:getSettings(chat_id) + text = u:getSettings(chat.id) notification = i18n("ℹ️ Group ► Settings") end if request == 'rules' then - text = u:getRules(chat_id) + text = u:getRules(chat.id) notification = i18n("ℹ️ Group ► Rules") end if request == 'adminlist' then parse_mode = 'html' - local adminlist = u:getAdminlist(chat_id) + local adminlist = u:getAdminlist(chat) if adminlist then text = adminlist else @@ -147,11 +149,11 @@ function _M:onCallbackQuery(blocks) notification = i18n("ℹ️ Group ► Admin list") end if request == 'extra' then - text = u:getExtraList(chat_id) + text = u:getExtraList(chat.id) notification = i18n("ℹ️ Group ► Extra") end if request == 'flood' then - text = getFloodSettings_text(self, chat_id) + text = getFloodSettings_text(self, chat.id) notification = i18n("ℹ️ Group ► Flood") end if request == 'media' then @@ -172,7 +174,7 @@ function _M:onCallbackQuery(blocks) } text = i18n("*Current media settings*:\n\n") for media, default_status in pairs(config.chat_settings['media']) do - local status = red:hget('chat:'..chat_id..':media', media) + local status = red:hget('chat:'..chat.id..':media', media) if status == null then status = default_status end if status == 'ok' then status = '✅' @@ -184,7 +186,7 @@ function _M:onCallbackQuery(blocks) end notification = i18n("ℹ️ Group ► Media") end - api:edit_message_text(msg.from.id, msg.message_id, nil, text, parse_mode, true, reply_markup) + api:edit_message_text(msg.from.user.id, msg.message_id, nil, text, parse_mode, true, reply_markup) api:answerCallbackQuery(msg.cb_id, notification) end diff --git a/lua/groupbutler/plugins/defaultpermissions.lua b/lua/groupbutler/plugins/defaultpermissions.lua index 001983a71..d10db306e 100644 --- a/lua/groupbutler/plugins/defaultpermissions.lua +++ b/lua/groupbutler/plugins/defaultpermissions.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -116,44 +118,52 @@ end function _M:onCallbackQuery(blocks) local api = self.api local msg = self.message - local u = self.u local i18n = self.i18n + if blocks[1] == 'alert' then local text = get_alert_text(self, blocks[2]) api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) - else - local chat_id = msg.target_id - if not u:can(chat_id, msg.from.id, 'can_restrict_members') then - api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to restrict members")) - else - local msg_text = i18n([[*Default permissions* + return + end + + local member = ChatMember:new({ + chat = Chat:new({id=msg.target_id}, self), + user = msg.from.user, + }, self) + + if not member:can("can_restrict_members") then + api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to restrict members")) + return + end + + local msg_text = i18n([[*Default permissions* From this menu you can change the default permissions that will be granted when a new member join. _Only the administrators with the permission to restrict a member can access this menu._ Tap on the name of a permission for a description of what kind of messages it will influence. ]]) - local reply_markup, popup_text, show_alert - - if blocks[1] == 'toggle' then - popup_text = toggle_permissions_setting(self, chat_id, blocks[2]) - end - - reply_markup = doKeyboard_permissions(self, chat_id) - local ok, err - if blocks[2] then - --if the user tapped on a keybord button, just edit the markup and not the whole message - ok, err = api:editMessageReplyMarkup(msg.chat.id, msg.message_id, nil, reply_markup) - else - ok, err = api:editMessageText(msg.chat.id, msg.message_id, nil, msg_text, "Markdown", nil, reply_markup) - end - - if not ok and err.retry_after then - popup_text = i18n("Setting saved, but I can't edit the buttons because you are too fast! Wait other %d seconds") - :format(err.retry_after) - show_alert = true - end - if popup_text then api:answerCallbackQuery(msg.cb_id, popup_text, show_alert) end - end + local reply_markup, popup_text, show_alert + + if blocks[1] == 'toggle' then + popup_text = toggle_permissions_setting(self, member.chat.id, blocks[2]) + end + + reply_markup = doKeyboard_permissions(self, member.chat.id) + local ok, err + if blocks[2] then + --if the user tapped on a keybord button, just edit the markup and not the whole message + ok, err = api:editMessageReplyMarkup(msg.from.chat.id, msg.message_id, nil, reply_markup) + else + ok, err = api:editMessageText(msg.from.chat.id, msg.message_id, nil, msg_text, "Markdown", nil, reply_markup) + end + + if not ok and err.retry_after then + popup_text = i18n("Setting saved, but I can't edit the buttons because you are too fast! Wait other %d seconds") + :format(err.retry_after) + show_alert = true + end + if popup_text then + api:answerCallbackQuery(msg.cb_id, popup_text, show_alert) end end diff --git a/lua/groupbutler/plugins/extra.lua b/lua/groupbutler/plugins/extra.lua index 07b4bf6b7..9fa9a96d1 100644 --- a/lua/groupbutler/plugins/extra.lua +++ b/lua/groupbutler/plugins/extra.lua @@ -57,12 +57,12 @@ function _M:onTextMessage(blocks) local red = self.red local i18n = self.i18n local api_err = self.api_err - if msg.chat.type == 'private' and not(blocks[1] == 'start') then return end + if msg.from.chat.type == 'private' and not(blocks[1] == 'start') then return end if blocks[1] == 'extra' then if not blocks[2] then return end if not blocks[3] and not msg.reply then return end - if not msg:is_from_admin() then return end + if not msg.from:isAdmin() then return end if msg.reply and not blocks[3] then local file_id = msg.reply:get_file_id() @@ -77,34 +77,34 @@ function _M:onTextMessage(blocks) to_save = '###file_id!'..v..'###:'..file_id end end - red:hset('chat:'..msg.chat.id..':extra', blocks[2], to_save) + red:hset('chat:'..msg.from.chat.id..':extra', blocks[2], to_save) msg:send_reply(i18n("This media has been saved as a response to %s"):format(blocks[2])) else - local hash = 'chat:'..msg.chat.id..':extra' + local hash = 'chat:'..msg.from.chat.id..':extra' local new_extra = blocks[3] local reply_markup, test_text = u:reply_markup_from_text(u:replaceholders(new_extra, msg)) test_text = test_text:gsub('\n', '') local ok, err = msg:send_reply(test_text, "Markdown", reply_markup) if not ok then - api:sendMessage(msg.chat.id, api_err:trans(err), "Markdown") + api:sendMessage(msg.from.chat.id, api_err:trans(err), "Markdown") return end red:hset(hash, blocks[2]:lower(), new_extra) local msg_id = ok.message_id - api:editMessageText(msg.chat.id, msg_id, nil, i18n("Command '%s' saved!"):format(blocks[2])) + api:editMessageText(msg.from.chat.id, msg_id, nil, i18n("Command '%s' saved!"):format(blocks[2])) end elseif blocks[1] == 'extra list' then - local text = u:getExtraList(msg.chat.id) - if not is_locked(self, msg.chat.id) and not msg:is_from_admin() then - api:sendMessage(msg.from.id, text) + local text = u:getExtraList(msg.from.chat.id) + if not is_locked(self, msg.from.chat.id) and not msg.from:isAdmin() then + api:sendMessage(msg.from.user.id, text) else msg:send_reply(text) end elseif blocks[1] == 'extra del' then - if not msg:is_from_admin() then return end + if not msg.from:isAdmin() then return end local deleted, not_found, found = {}, {} - local hash = 'chat:'..msg.chat.id..':extra' + local hash = 'chat:'..msg.from.chat.id..':extra' for extra in blocks[2]:gmatch('(#[%w_]+)') do found = red:hdel(hash, extra) if found == 1 then @@ -120,7 +120,7 @@ function _M:onTextMessage(blocks) end msg:send_reply(text, "Markdown") else - local chat_id = blocks[1] == 'start' and tonumber(blocks[2]) or msg.chat.id + local chat_id = blocks[1] == 'start' and tonumber(blocks[2]) or msg.from.chat.id local extra = blocks[1] == 'start' and '#'..blocks[3] or blocks[1] --print(chat_id, extra) local hash = 'chat:'..chat_id..':extra' @@ -134,15 +134,15 @@ function _M:onTextMessage(blocks) local link_preview = text:find('telegra%.ph/') == nil local _, err - if msg.chat.id > 0 - or(is_locked(self, msg.chat.id) and not msg:is_from_admin()) then -- send it in private + if msg.from.chat.id > 0 + or(is_locked(self, msg.from.chat.id) and not msg.from:isAdmin()) then -- send it in private if not file_id then local reply_markup, clean_text = u:reply_markup_from_text(u:replaceholders(text, msg.reply or msg)) - _, err = api:sendMessage(msg.from.id, clean_text, "Markdown", link_preview, nil, nil, reply_markup) + _, err = api:sendMessage(msg.from.user.id, clean_text, "Markdown", link_preview, nil, nil, reply_markup) elseif special_method then - _, err = sendMedia(self, msg.from.id, file_id, special_method) -- photo, voices, video need their method to be sent by file_id + _, err = sendMedia(self, msg.from.user.id, file_id, special_method) -- photo, voices, video need their method to be sent by file_id else - _, err = api:sendDocument(msg.from.id, file_id) + _, err = api:sendDocument(msg.from.user.id, file_id) end else local msg_to_reply @@ -153,19 +153,19 @@ function _M:onTextMessage(blocks) end if file_id then if special_method then - sendMedia(self, msg.chat.id, file_id, special_method, msg_to_reply) -- photo, voices, video need their method to be sent by file_id + sendMedia(self, msg.from.chat.id, file_id, special_method, msg_to_reply) -- photo, voices, video need their method to be sent by file_id else - api:sendDocument(msg.chat.id, file_id, nil, nil, msg_to_reply) + api:sendDocument(msg.from.chat.id, file_id, nil, nil, msg_to_reply) end else local reply_markup, clean_text = u:reply_markup_from_text(u:replaceholders(text, msg.reply or msg)) - api:sendMessage(msg.chat.id, clean_text, "Markdown", link_preview, nil, msg_to_reply, reply_markup) -- if the admin replies to a user, the bot will reply to the user too + api:sendMessage(msg.from.chat.id, clean_text, "Markdown", link_preview, nil, msg_to_reply, reply_markup) -- if the admin replies to a user, the bot will reply to the user too end end - if err and err.error_code == 403 and msg.chat.id < 0 and not u:is_silentmode_on(msg.chat.id) then -- if the user haven't started the bot and silent mode is off + if err and err.error_code == 403 and msg.from.chat.id < 0 and not u:is_silentmode_on(msg.from.chat.id) then -- if the user haven't started the bot and silent mode is off msg:send_reply(i18n("_Please_ [start me](%s) _so I can send you the answer_") - :format(u:deeplink_constructor(msg.chat.id, extra:sub(2, -1))), "Markdown") + :format(u:deeplink_constructor(msg.from.chat.id, extra:sub(2, -1))), "Markdown") end end end diff --git a/lua/groupbutler/plugins/floodmanager.lua b/lua/groupbutler/plugins/floodmanager.lua index 839bb2a4f..e6aa94c53 100644 --- a/lua/groupbutler/plugins/floodmanager.lua +++ b/lua/groupbutler/plugins/floodmanager.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -139,60 +141,66 @@ function _M:onCallbackQuery(blocks) local u = self.u local red = self.red local i18n = self.i18n - local chat_id = msg.target_id - if chat_id and not u:is_allowed('config', chat_id, msg.from) then - api:answerCallbackQuery(msg.cb_id, i18n("You're no longer an admin")) - else - local header = i18n([[You can manage the antiflood settings from here. -It is also possible to choose which type of messages the antiflood will ignore (✅)]]) + if blocks[1] == "alert" then + local text = get_button_description(self, blocks[2]) + api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) + return + end - local text + local member = ChatMember:new({ + chat = Chat:new({id=msg.target_id}, self), + user = msg.from.user, + }, self) - if blocks[1] == 'config' then - text = i18n("Antiflood settings") - end + if not member:can("can_change_info") then + api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to change settings")) + return + end - if blocks[1] == 'alert' then - text = get_button_description(self, blocks[2]) - api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) - return - end + local header = i18n([[You can manage the antiflood settings from here. - if blocks[1] == 'exc' then - local media = blocks[2] - local hash = 'chat:'..chat_id..':floodexceptions' - local status = red:hget(hash, media) - if status == 'no' then - red:hset(hash, media, 'yes') - text = i18n("❎ [%s] will be ignored by the anti-flood"):format(media) - else - red:hset(hash, media, 'no') - text = i18n("🚫 [%s] won't be ignored by the anti-flood"):format(media) - end - end +It is also possible to choose which type of messages the antiflood will ignore (✅)]]) - local action - if blocks[1] == 'action' or blocks[1] == 'dim' or blocks[1] == 'raise' then - if blocks[1] == 'action' then - action = red:hget('chat:'..chat_id..':flood', 'ActionFlood') - if action == null then action = config.chat_settings.flood.ActionFlood end - elseif blocks[1] == 'dim' then - action = -1 - elseif blocks[1] == 'raise' then - action = 1 - end - text = changeFloodSettings(self, chat_id, action) + local text + + if blocks[1] == "config" then + text = i18n("Antiflood settings") + end + + if blocks[1] == "exc" then + local media = blocks[2] + local hash = "chat:"..member.chat.id..":floodexceptions" + local status = red:hget(hash, media) + if status == "no" then + red:hset(hash, media, "yes") + text = i18n("❎ [%s] will be ignored by the anti-flood"):format(media) + else + red:hset(hash, media, "no") + text = i18n("🚫 [%s] won't be ignored by the anti-flood"):format(media) end + end - if blocks[1] == 'status' then - text = u:changeSettingStatus(chat_id, 'Flood') + local action + if blocks[1] == "action" or blocks[1] == "dim" or blocks[1] == "raise" then + if blocks[1] == "action" then + action = red:hget("chat:"..member.chat.id..":flood", "ActionFlood") + if action == null then action = config.chat_settings.flood.ActionFlood end + elseif blocks[1] == "dim" then + action = -1 + elseif blocks[1] == "raise" then + action = 1 end + text = changeFloodSettings(self, member.chat.id, action) + end - local keyboard = do_keyboard_flood(self, chat_id) - api:editMessageText(msg.chat.id, msg.message_id, nil, header, "Markdown", nil, keyboard) - api:answerCallbackQuery(msg.cb_id, text) + if blocks[1] == "status" then + text = u:changeSettingStatus(member.chat.id, "Flood") end + + local keyboard = do_keyboard_flood(self, member.chat.id) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, header, "Markdown", nil, keyboard) + api:answerCallbackQuery(msg.cb_id, text) end _M.triggers = { diff --git a/lua/groupbutler/plugins/help.lua b/lua/groupbutler/plugins/help.lua index 361efa534..e7429d37e 100644 --- a/lua/groupbutler/plugins/help.lua +++ b/lua/groupbutler/plugins/help.lua @@ -322,21 +322,23 @@ function _M:onTextMessage(blocks) local red = self.red local i18n = self.i18n if blocks[1] == 'start' then - if msg.chat.type == 'private' then - local message = get_helped_string(self, 'start'):format(msg.from.first_name:escape()) + if msg.from.chat.type == 'private' then + local message = get_helped_string(self, 'start'):format(msg.from.user.first_name:escape()) local keyboard = do_keyboard_private(self) - api:sendMessage(msg.from.id, message, "Markdown", nil, nil, nil, keyboard) + api:sendMessage(msg.from.user.id, message, "Markdown", nil, nil, nil, keyboard) end end if blocks[1] == 'help' then local text = get_helped_string(self, blocks[2] or 'main_menu') if blocks[2] then - api:sendMessage(msg.from.id, text, "Markdown") + api:sendMessage(msg.from.user.id, text, "Markdown") else local keyboard = do_keyboard(self, 'main') - local res = api:sendMessage(msg.from.id, text, "Markdown", nil, nil, nil, keyboard) - if not res and msg.chat.type ~= 'private' and red:hget('chat:'..msg.chat.id..':settings', 'Silent') ~= 'on' then - api:sendMessage(msg.chat.id, + local res = api:sendMessage(msg.from.user.id, text, "Markdown", nil, nil, nil, keyboard) + if not res + and msg.from.chat.type ~= 'private' + and red:hget('chat:'..msg.from.chat.id..':settings', 'Silent') ~= 'on' then + api:sendMessage(msg.from.chat.id, i18n('[Start me](%s) _to get the list of commands_'):format(u:deeplink_constructor('', 'help')), "Markdown") end end @@ -380,7 +382,7 @@ function _M:onCallbackQuery(blocks) query[blocks[1]]() local keyboard = do_keyboard(self, keyboard_type) - local ok, err = api:editMessageText(msg.chat.id, msg.message_id, nil, text, "Markdown", nil, keyboard) + local ok, err = api:editMessageText(msg.from.chat.id, msg.message_id, nil, text, "Markdown", nil, keyboard) if not ok and err and err.error_code == 111 then api:answerCallbackQuery(msg.cb_id, i18n("❗️ Already there")) end diff --git a/lua/groupbutler/plugins/links.lua b/lua/groupbutler/plugins/links.lua index 0e4f8e464..c53c87c1e 100644 --- a/lua/groupbutler/plugins/links.lua +++ b/lua/groupbutler/plugins/links.lua @@ -14,26 +14,28 @@ end function _M:onTextMessage(blocks) local msg = self.message - local u = self.u local red = self.red local i18n = self.i18n - if msg.chat.type == 'private' then return end - if not u:is_allowed('texts', msg.chat.id, msg.from) then return end - local hash = 'chat:'..msg.chat.id..':links' + if msg.from.chat.type == "private" + or not msg.from:isAdmin() then + return + end + + local hash = 'chat:'..msg.from.chat.id..':links' local text if blocks[1] == 'link' then - if msg.chat.username then - red:sadd('chat:'..msg.chat.id..':whitelist', 'telegram.me/'..msg.chat.username) - local title = msg.chat.title:escape_hard('link') - msg:send_reply(string.format('[%s](telegram.me/%s)', title, msg.chat.username), "Markdown") + if msg.from.chat.username then + red:sadd('chat:'..msg.from.chat.id..':whitelist', 'telegram.me/'..msg.from.chat.username) + local title = msg.from.chat.title:escape_hard('link') + msg:send_reply(string.format('[%s](telegram.me/%s)', title, msg.from.chat.username), "Markdown") else local link = red:hget(hash, 'link') if link == null then text = i18n("*No link* for this group. Ask the owner to save it with `/setlink [group link]`") else - local title = msg.chat.title:escape_hard('link') + local title = msg.from.chat.title:escape_hard('link') text = string.format('[%s](%s)', title, link) end msg:send_reply(text, "Markdown") @@ -45,9 +47,9 @@ function _M:onTextMessage(blocks) text = i18n("Link *unset*") else local link - if msg.chat.username then - link = 'https://telegram.me/'..msg.chat.username - local substitution = '['..msg.chat.title:escape_hard('link')..']('..link..')' + if msg.from.chat.username then + link = 'https://telegram.me/'..msg.from.chat.username + local substitution = '['..msg.from.chat.title:escape_hard('link')..']('..link..')' text = i18n("The link has been set.\n*Here's the link*: %s"):format(substitution) else if not blocks[2] then @@ -57,10 +59,10 @@ function _M:onTextMessage(blocks) text = i18n("This link is *not valid!*") else link = 'https://telegram.me/joinchat/'..blocks[2] - red:sadd('chat:'..msg.chat.id..':whitelist', link:gsub('https://', '')) --save the link in the whitelist + red:sadd('chat:'..msg.from.chat.id..':whitelist', link:gsub('https://', '')) --save the link in the whitelist local succ = red:hset(hash, 'link', link) - local title = msg.chat.title:escape_hard('link') + local title = msg.from.chat.title:escape_hard('link') local substitution = '['..title..']('..link..')' if not succ then text = i18n("The link has been updated.\n*Here's the new link*: %s"):format(substitution) diff --git a/lua/groupbutler/plugins/logchannel.lua b/lua/groupbutler/plugins/logchannel.lua index dcf39a767..03d565460 100644 --- a/lua/groupbutler/plugins/logchannel.lua +++ b/lua/groupbutler/plugins/logchannel.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -101,51 +103,60 @@ end function _M:onCallbackQuery(blocks) local api = self.api local msg = self.message - local u = self.u local i18n = self.i18n + if blocks[1] == 'alert' then + local text = get_alert_text(self, blocks[2]) + api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) + return + end + + local chat = Chat:new({id=msg.target_id}, self) + if blocks[1] == 'logcb' then - local chat_id = msg.target_id - if not msg:is_from_admin() then + if not msg.from:isAdmin() then api:answerCallbackQuery(msg.cb_id, i18n("You are not admin of this group"), true) - else - if blocks[2] == 'unban' or blocks[2] == 'untempban' then - local user_id = blocks[3] - api:unban_chat_member(chat_id, user_id) - api:answerCallbackQuery(msg.cb_id, i18n("User unbanned!"), true) - end + return end - else - if blocks[1] == 'alert' then - local text = get_alert_text(self, blocks[2]) - api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) - else - local chat_id = msg.target_id - if not u:is_allowed('config', chat_id, msg.from) then - api:answerCallbackQuery(msg.cb_id, i18n("You're no longer an admin")) - else - local text + if blocks[2] == 'unban' or blocks[2] == 'untempban' then + local user_id = blocks[3] + api:unbanChatMember(chat.id, user_id) + api:answerCallbackQuery(msg.cb_id, i18n("User unbanned!"), true) + end + return + end - if blocks[1] == 'toggle' then - toggle_event(self, chat_id, blocks[2]) - text = '👌🏼' - end + local member = ChatMember:new({ + chat = chat, + user = msg.from.user, + }, self) + + if not member:can("can_change_info") then + api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to change settings")) + return + end + + local text - local reply_markup = doKeyboard_logchannel(self, chat_id) - if blocks[1] == 'config' then - local logchannel_first = i18n([[*Select the events the will be logged in the channel* + if blocks[1] == 'toggle' then + toggle_event(self, chat.id, blocks[2]) + text = '👌🏼' + end + + local reply_markup = doKeyboard_logchannel(self, chat.id) + if blocks[1] == 'config' then + local logchannel_first = i18n([[*Select the events the will be logged in the channel* ✅ = will be logged ☑️ = won't be logged Tap on an option to get further information]]) - api:edit_message_text(msg.chat.id, msg.message_id, nil, logchannel_first, "Markdown", true, reply_markup) - else - api:editMessageReplyMarkup(msg.chat.id, msg.message_id, nil, reply_markup) - end + api:editMessageText(msg.from.chat.id, msg.message_id, nil, logchannel_first, "Markdown", true, reply_markup) + else + api:editMessageReplyMarkup(msg.from.chat.id, msg.message_id, nil, reply_markup) + end - if text then api:answerCallbackQuery(msg.cb_id, text) end - end - end + if text then + api:answerCallbackQuery(msg.cb_id, text) end end @@ -155,14 +166,14 @@ function _M:onTextMessage(blocks) local red = self.red local i18n = self.i18n - if msg.chat.type ~= 'private' then - if not msg:is_from_admin() then return end + if msg.from.chat.type ~= 'private' then + if not msg.from:isAdmin() then return end if blocks[1] == 'setlog' then if msg.forward_from_chat then if msg.forward_from_chat.type == 'channel' then if not msg.forward_from_chat.username then - local ok, err = api:getChatMember(msg.forward_from_chat.id, msg.from.id) + local ok, err = api:getChatMember(msg.forward_from_chat.id, msg.from.user.id) if not ok then if err.error_code == 429 then msg:send_reply(i18n('_Too many requests. Retry later_'), "Markdown") @@ -172,18 +183,18 @@ function _M:onTextMessage(blocks) else if ok.status == 'creator' then local text - local old_log = red:hget('bot:chatlogs', msg.chat.id) + local old_log = red:hget('bot:chatlogs', msg.from.chat.id) if old_log == tostring(msg.forward_from_chat.id) then text = i18n('_Already using this channel_') else - red:hset('bot:chatlogs', msg.chat.id, msg.forward_from_chat.id) + red:hset('bot:chatlogs', msg.from.chat.id, msg.forward_from_chat.id) text = i18n('*Log channel added!*') if old_log then api:sendMessage(old_log, - i18n("%s changed its log channel"):format(msg.chat.title:escape_html()), 'html') + i18n("%s changed its log channel"):format(msg.from.chat.title:escape_html()), 'html') end api:sendMessage(msg.forward_from_chat.id, - i18n("Logs of %s will be posted here"):format(msg.chat.title:escape_html()), 'html') + i18n("Logs of %s will be posted here"):format(msg.from.chat.title:escape_html()), 'html') end msg:send_reply(text, "Markdown") else @@ -198,15 +209,15 @@ function _M:onTextMessage(blocks) msg:send_reply(i18n("You have to *forward* the message from the channel"), "Markdown") end elseif blocks[1] == 'unsetlog' then - local log_channel = red:hget('bot:chatlogs', msg.chat.id) + local log_channel = red:hget('bot:chatlogs', msg.from.chat.id) if log_channel == null then msg:send_reply(i18n("_This groups is not using a log channel_"), "Markdown") else - red:hdel('bot:chatlogs', msg.chat.id) + red:hdel('bot:chatlogs', msg.from.chat.id) msg:send_reply(i18n("*Log channel removed*"), "Markdown") end elseif blocks[1] == 'logchannel' then - local log_channel = red:hget('bot:chatlogs', msg.chat.id) + local log_channel = red:hget('bot:chatlogs', msg.from.chat.id) if log_channel == null then msg:send_reply(i18n("_This groups is not using a log channel_"), "Markdown") else @@ -226,7 +237,7 @@ function _M:onTextMessage(blocks) end end elseif blocks[1] == 'photo' then - api:sendPhoto(msg.chat.id, blocks[2]) + api:sendPhoto(msg.from.chat.id, blocks[2]) end end @@ -245,7 +256,7 @@ _M.triggers = { --callbacks from the configuration keyboard '^###cb:logchannel:(toggle):([%w_]+):(-?%d+)$', - '^###cb:logchannel:(alert):([%w_]+):([%w_]+)$', + '^###cb:logchannel:(alert):([%w_]+)$', '^###cb:(config):logchannel:(-?%d+)$' } } diff --git a/lua/groupbutler/plugins/mediasettings.lua b/lua/groupbutler/plugins/mediasettings.lua index db0f5041c..127eeecab 100644 --- a/lua/groupbutler/plugins/mediasettings.lua +++ b/lua/groupbutler/plugins/mediasettings.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -109,73 +111,81 @@ function _M:onCallbackQuery(blocks) local msg = self.message local red = self.red local i18n = self.i18n - local u = self.u - local chat_id = msg.target_id - if chat_id and not u:is_allowed('config', chat_id, msg.from) then - api:answerCallbackQuery(msg.cb_id, i18n("You're no longer an admin")) - else - local media_first = i18n([[ -Tap on an option on the right to *change the setting* + + if blocks[1] == "mediallert" then + api:answerCallbackQuery(msg.cb_id, i18n("⚠️ Tap on the right column"), false, + config.bot_settings.cache_time.alert_help) + return + end + + local member = ChatMember:new({ + chat = Chat:new({id=msg.target_id}, self), + user = msg.from.user, + }, self) + + if not member:can("can_change_info") then + api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to change settings")) + return + end + + local media_first = i18n([[Tap on an option on the right to *change the setting* You can use the last lines to change how many warnings the bot should give before kicking/banning/muting someone. The number is not related the the normal `/warn` command. Possible statuses: ✅ allowed, ❌ warning, 🗑 delete. -When a media is set to delete, the bot will give a warning *only* when this is the users last warning -]]) - - if blocks[1] == 'config' then - local keyboard = doKeyboard_media(self, chat_id) - api:editMessageText(msg.chat.id, msg.message_id, nil, media_first, "Markdown", nil, keyboard) - else - if blocks[1] == 'mediallert' then - api:answerCallbackQuery(msg.cb_id, i18n("⚠️ Tap on the right column"), false, - config.bot_settings.cache_time.alert_help) - return - end - local cb_text - if blocks[1] == 'mediawarn' then - local current = tonumber(red:hget('chat:'..chat_id..':warnsettings', 'mediamax')) or 2 - if blocks[2] == 'dim' then - if current < 2 then - cb_text = i18n("⚙ The new value is too low ( < 1)") - else - local new = red:hincrby('chat:'..chat_id..':warnsettings', 'mediamax', -1) - cb_text = string.format('⚙ %d → %d', current, new) - end - elseif blocks[2] == 'raise' then - if current > 11 then - cb_text = i18n("⚙ The new value is too high ( > 12)") - else - local new = red:hincrby('chat:'..chat_id..':warnsettings', 'mediamax', 1) - cb_text = string.format('⚙ %d → %d', current, new) - end - end - end - if blocks[1] == 'mediatype' then - local hash = 'chat:'..chat_id..':warnsettings' - local current = red:hget(hash, 'mediatype') - if current == null then current = config.chat_settings['warnsettings']['mediatype'] end - - if current == 'ban' then - red:hset(hash, 'mediatype', 'kick') - cb_text = i18n("👞 New status is kick") - elseif current == 'kick' then - red:hset(hash, 'mediatype', 'mute') - cb_text = i18n("👁 New status is mute") - elseif current == 'mute' then - red:hset(hash, 'mediatype', 'ban') - cb_text = i18n("🔨 New status is ban") - end +When a media is set to delete, the bot will give a warning *only* when this is the users last warning]]) + + if blocks[1] == "config" then + local keyboard = doKeyboard_media(self, member.chat.id) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, media_first, "Markdown", nil, keyboard) + return + end + + local cb_text + if blocks[1] == "mediawarn" then + local current = tonumber(red:hget("chat:"..member.chat.id..":warnsettings", "mediamax")) or 2 + if blocks[2] == "dim" then + if current < 2 then + cb_text = i18n("⚙ The new value is too low ( < 1)") + else + local new = red:hincrby("chat:"..member.chat.id..":warnsettings", "mediamax", -1) + cb_text = string.format("⚙ %d → %d", current, new) end - if blocks[1] == 'media' then - local media = blocks[2] - cb_text = change_media_status(self, chat_id, media) + elseif blocks[2] == "raise" then + if current > 11 then + cb_text = i18n("⚙ The new value is too high ( > 12)") + else + local new = red:hincrby("chat:"..member.chat.id..":warnsettings", "mediamax", 1) + cb_text = string.format("⚙ %d → %d", current, new) end - local keyboard = doKeyboard_media(self, chat_id) - api:editMessageText(msg.chat.id, msg.message_id, nil, media_first, "Markdown", nil, keyboard) - api:answerCallbackQuery(msg.cb_id, cb_text) end end + + if blocks[1] == "mediatype" then + local hash = "chat:"..member.chat.id..":warnsettings" + local current = red:hget(hash, "mediatype") + if current == null then current = config.chat_settings["warnsettings"]["mediatype"] end + + if current == "ban" then + red:hset(hash, "mediatype", "kick") + cb_text = i18n("👞 New status is kick") + elseif current == "kick" then + red:hset(hash, "mediatype", "mute") + cb_text = i18n("👁 New status is mute") + elseif current == "mute" then + red:hset(hash, "mediatype", "ban") + cb_text = i18n("🔨 New status is ban") + end + end + + if blocks[1] == "media" then + local media = blocks[2] + cb_text = change_media_status(self, member.chat.id, media) + end + + local keyboard = doKeyboard_media(self, member.chat.id) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, media_first, "Markdown", nil, keyboard) + api:answerCallbackQuery(msg.cb_id, cb_text) end _M.triggers = { @@ -183,7 +193,7 @@ _M.triggers = { '^###cb:(media):([%a_]+):(-?%d+)', '^###cb:(mediatype):(-?%d+)', '^###cb:(mediawarn):(%a+):(-?%d+)', - '^###cb:(mediallert):([%w_]+)$', + '^###cb:(mediallert)$', '^###cb:(config):media:(-?%d+)$' } diff --git a/lua/groupbutler/plugins/menu.lua b/lua/groupbutler/plugins/menu.lua index ff8d1f192..78f5870e7 100644 --- a/lua/groupbutler/plugins/menu.lua +++ b/lua/groupbutler/plugins/menu.lua @@ -1,5 +1,7 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local Chat = require("groupbutler.chat") +local ChatMember = require("groupbutler.chatmember") local _M = {} @@ -253,49 +255,55 @@ function _M:onCallbackQuery(blocks) local msg = self.message local u = self.u local i18n = self.i18n - local chat_id = msg.target_id - if chat_id and not u:is_allowed('config', chat_id, msg.from) then - api:answerCallbackQuery(msg.cb_id, i18n("You're no longer an admin")) - else - local menu_first = i18n("Manage the settings of the group. Click on the left column to get a small hint") - local keyboard, text, show_alert + if blocks[2] == "alert" then + local text = get_button_description(self, blocks[3]) + api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) + return + end - if blocks[1] == 'config' then - keyboard = doKeyboard_menu(self, chat_id) - api:editMessageText(msg.chat.id, msg.message_id, nil, menu_first, "Markdown", nil, keyboard) - else - if blocks[2] == 'alert' then - text = get_button_description(self, blocks[3]) - api:answerCallbackQuery(msg.cb_id, text, true, config.bot_settings.cache_time.alert_help) - return - end - if blocks[2] == 'DimWarn' or blocks[2] == 'RaiseWarn' or blocks[2] == 'ActionWarn' then - if blocks[2] == 'DimWarn' then - text = changeWarnSettings(self, chat_id, -1) - elseif blocks[2] == 'RaiseWarn' then - text = changeWarnSettings(self, chat_id, 1) - elseif blocks[2] == 'ActionWarn' then - text = changeWarnSettings(self, chat_id, 'status') - end - elseif blocks[2] == 'Rtl' or blocks[2] == 'Arab' then - text = changeCharSettings(self, chat_id, blocks[2]) - else - text, show_alert = u:changeSettingStatus(chat_id, blocks[2]) - end - keyboard = doKeyboard_menu(self, chat_id) - api:editMessageText(msg.chat.id, msg.message_id, nil, menu_first, "Markdown", nil, keyboard) - if text then - --workaround to avoid to send an error to users who are using an old inline keyboard - api:answerCallbackQuery(msg.cb_id, '⚙ '..text, show_alert) - end - end + local member = ChatMember:new({ + chat = Chat:new({id=msg.target_id}, self), + user = msg.from.user, + }, self) + + if not member:can("can_change_info") then + api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to change settings")) + return + end + + local menu_first = i18n("Manage the settings of the group. Click on the left column to get a small hint") + + local keyboard, text, show_alert + + if blocks[1] == "config" then + keyboard = doKeyboard_menu(self, member.chat.id) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, menu_first, "Markdown", nil, keyboard) + return + end + + if blocks[2] == "DimWarn" then + text = changeWarnSettings(self, member.chat.id, -1) + elseif blocks[2] == "RaiseWarn" then + text = changeWarnSettings(self, member.chat.id, 1) + elseif blocks[2] == "ActionWarn" then + text = changeWarnSettings(self, member.chat.id, "status") + elseif blocks[2] == "Rtl" or blocks[2] == "Arab" then + text = changeCharSettings(self, member.chat.id, blocks[2]) + else + text, show_alert = u:changeSettingStatus(member.chat.id, blocks[2]) + end + keyboard = doKeyboard_menu(self, member.chat.id) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, menu_first, "Markdown", nil, keyboard) + if text then + --workaround to avoid to send an error to users who are using an old inline keyboard + api:answerCallbackQuery(msg.cb_id, "⚙ "..text, show_alert) end end _M.triggers = { onCallbackQuery = { - '^###cb:(menu):(alert):settings:([%w_]+):([%w_]+)$', + '^###cb:(menu):(alert):settings:([%w_]+)$', '^###cb:(menu):(.*):', '^###cb:(config):menu:(-?%d+)$' } diff --git a/lua/groupbutler/plugins/onmessage.lua b/lua/groupbutler/plugins/onmessage.lua index 5cbc7b089..b1819dc7e 100644 --- a/lua/groupbutler/plugins/onmessage.lua +++ b/lua/groupbutler/plugins/onmessage.lua @@ -38,7 +38,7 @@ local function is_flooding_funct(self, msg) if msg.media_group_id then -- albums should count as one message - local media_group_id_key = 'mediagroupidkey:'..msg.chat.id + local media_group_id_key = 'mediagroupidkey:'..msg.from.chat.id if msg.media_group_id == red:get(media_group_id_key) then -- msg.media_group_id is a str -- photo/video is from an already processed sent album return false @@ -49,11 +49,11 @@ local function is_flooding_funct(self, msg) end end - local spamkey = 'spam:'..msg.chat.id..':'..msg.from.id + local spamkey = 'spam:'..msg.from.chat.id..':'..msg.from.user.id local msgs = tonumber(red:get(spamkey)) or 1 - local max_msgs = tonumber(red:hget('chat:'..msg.chat.id..':flood', 'MaxFlood')) or 5 + local max_msgs = tonumber(red:hget('chat:'..msg.from.chat.id..':flood', 'MaxFlood')) or 5 if msg.cb then max_msgs = 15 end local max_time = 5 @@ -91,23 +91,23 @@ function _M:on_message() local msg_type = msg:type() if msg.forward_from or msg.forward_from_chat then msg_type = 'forward' end - if not is_ignored(self, msg.chat.id, msg_type) and not msg.edited then + if not is_ignored(self, msg.from.chat.id, msg_type) and not msg.edited then local is_flooding, msgs_sent, msgs_max = is_flooding_funct(self, msg) if is_flooding then - local status = red:hget('chat:'..msg.chat.id..':settings', 'Flood') + local status = red:hget('chat:'..msg.from.chat.id..':settings', 'Flood') if status == null then status = config.chat_settings['settings']['Flood'] end - if status == 'on' and not msg.cb and not msg:is_from_admin() then --if the status is on, and the user is not an admin, and the message is not a callback, then: - local action = red:hget('chat:'..msg.chat.id..':flood', 'ActionFlood') - local name = u:getname_final(msg.from) + if status == 'on' and not msg.cb and not msg.from:isAdmin() then --if the status is on, and the user is not an admin, and the message is not a callback, then: + local action = red:hget('chat:'..msg.from.chat.id..':flood', 'ActionFlood') + local name = msg.from.user:getLink() local ok, message --try to kick or ban if action == 'ban' then - ok = u:banUser(msg.chat.id, msg.from.id) + ok = msg.from:ban() elseif action == 'kick' then - ok = u:kickUser(msg.chat.id, msg.from.id) + ok = msg.from:kick() elseif action == 'mute' then - ok = u:muteUser(msg.chat.id, msg.from.id) + ok = msg.from:mute() end --if kicked/banned, send a message if ok then @@ -120,7 +120,7 @@ function _M:on_message() elseif action == 'mute' then message = i18n("%s muted for flood!"):format(name) end - api:sendMessage(msg.chat.id, message, 'html') + api:sendMessage(msg.from.chat.id, message, 'html') u:logEvent('flood', msg, {hammered = log_hammered}) end end @@ -134,45 +134,45 @@ function _M:on_message() end if msg_type ~= "text" and not msg.cb and not msg.edited then - local hash = 'chat:'..msg.chat.id..':media' + local hash = 'chat:'..msg.from.chat.id..':media' local media_status = (red:hget(hash, msg_type)) if media_status == null then media_status = config.chat_settings.media[msg_type] end if media_status == 'notok' then local whitelisted if msg_type == 'link' then - whitelisted = is_whitelisted(self, msg.chat.id, msg.text) + whitelisted = is_whitelisted(self, msg.from.chat.id, msg.text) end - if not whitelisted and not msg:is_from_admin() then -- Postpone admin check to avoid hitting API limits + if not whitelisted and not msg.from:isAdmin() then -- Postpone admin check to avoid hitting API limits local status - local name = u:getname_final(msg.from) - local max_reached_var, n, max = max_reached(self, msg.chat.id, msg.from.id) + local name = msg.from.user:getLink() + local max_reached_var, n, max = max_reached(self, msg.from.chat.id, msg.from.user.id) if max_reached_var then --max num reached. Kick/ban the user - status = red:hget('chat:'..msg.chat.id..':warnsettings', 'mediatype') + status = red:hget('chat:'..msg.from.chat.id..':warnsettings', 'mediatype') if status == null then status = config.chat_settings['warnsettings']['mediatype'] end --try to kick/ban local ok, punishment if status == 'kick' then - ok = u:kickUser(msg.chat.id, msg.from.id) + ok = msg.from:kick() punishment = i18n('kicked') elseif status == 'ban' then - ok = u:banUser(msg.chat.id, msg.from.id) + ok = msg.from:ban() punishment = i18n('banned') elseif status == 'mute' then - ok = u:muteUser(msg.chat.id, msg.from.id) + ok = msg.from:mute() punishment = i18n('muted') end if ok then --kick worked - red:hdel('chat:'..msg.chat.id..':mediawarn', msg.from.id) --remove media warns + red:hdel('chat:'..msg.from.chat.id..':mediawarn', msg.from.user.id) --remove media warns local message = i18n('%s %s: media sent not allowed!\n❗️ %d/%d'):format(name, punishment, n, max) - api:sendMessage(msg.chat.id, message, 'html') + api:sendMessage(msg.from.chat.id, message, 'html') end if media_status == 'del' then --do not forget to delete the message - api:deleteMessage(msg.chat.id, msg.message_id) + api:deleteMessage(msg.from.chat.id, msg.message_id) end else --max num not reached -> warn or delete if media_status ~= 'del' then @@ -180,14 +180,14 @@ function _M:on_message() i18n('%s, this type of media is not allowed in this chat.\n(%d/%d)'):format(name, n, max) msg:send_reply(message, 'html') elseif media_status == 'del' and n + 1 >= max then - api:deleteMessage(msg.chat.id, msg.message_id) + api:deleteMessage(msg.from.chat.id, msg.message_id) local message = i18n([[%s, this type of media is not allowed in this chat. The next time you will be banned/kicked/muted ]]):format(name) - api:sendMessage(msg.chat.id, message, 'html') + api:sendMessage(msg.from.chat.id, message, 'html') elseif media_status == 'del' then - api:deleteMessage(msg.chat.id, msg.message_id) + api:deleteMessage(msg.from.chat.id, msg.message_id) end end u:logEvent('mediawarn', msg, {warns = n, warnmax = max, media = msg_type, hammered = status}) @@ -195,60 +195,60 @@ function _M:on_message() end end - local rtl_status = red:hget('chat:'..msg.chat.id..':char', 'Rtl') + local rtl_status = red:hget('chat:'..msg.from.chat.id..':char', 'Rtl') if rtl_status == null then rtl_status = config.chat_settings.char.Rtl end if rtl_status ~= 'allowed' then local rtl = '‮' local last_name = 'x' - if msg.from.last_name then last_name = msg.from.last_name end - local check = msg.text:find(rtl..'+') or msg.from.first_name:find(rtl..'+') or last_name:find(rtl..'+') + if msg.from.user.last_name then last_name = msg.from.user.last_name end + local check = msg.text:find(rtl..'+') or msg.from.user.first_name:find(rtl..'+') or last_name:find(rtl..'+') if check ~= nil then local ok, message if rtl_status == 'kick' then - ok = u:kickUser(msg.chat.id, msg.from.id) + ok = msg.from:kick() message = i18n("%s kicked: RTL character in names/messages are not allowed!") elseif rtl_status == 'ban' then - ok = u:banUser(msg.chat.id, msg.from.id) + ok = msg.from:ban() message = i18n("%s banned: RTL character in names/messages are not allowed!") elseif rtl_status == 'mute' then - ok = u:muteUser(msg.chat.id, msg.from.id) + ok = msg.from:mute() message = i18n("%s muted: RTL character in names/messages are not allowed!") end if ok then - local name = u:getname_final(msg.from) - api:sendMessage(msg.chat.id, message:format(name), 'html') + local name = msg.from.user:getLink() + api:sendMessage(msg.from.chat.id, message:format(name), 'html') return false end end end if msg.text and msg.text:find('([\216-\219][\128-\191])') then - local arab_status = red:hget('chat:'..msg.chat.id..':char', 'Arab') + local arab_status = red:hget('chat:'..msg.from.chat.id..':char', 'Arab') if arab_status == null then arab_status = config.chat_settings.char.Arab end if arab_status ~= 'allowed' then local ok, message if arab_status == 'kick' then - ok = u:kickUser(msg.chat.id, msg.from.id) + ok = msg.from:kick() message = i18n("%s kicked: arab/persian message detected!") elseif arab_status == 'ban' then - ok = u:banUser(msg.chat.id, msg.from.id) + ok = msg.from:ban() message = i18n("%s banned: arab/persian message detected!") elseif arab_status == 'mute' then - ok = u:muteUser(msg.chat.id, msg.from.id) + ok = msg.from:mute() message = i18n("%s muted: arab/persian message detected!") end if ok then - local name = u:getname_final(msg.from) - api:sendMessage(msg.chat.id, message:format(name), 'html') + local name = msg.from.user:getLink() + api:sendMessage(msg.from.chat.id, message:format(name), 'html') return false end end end end - if u:is_blocked_global(msg.from.id) then --ignore blocked users + if u:is_blocked_global(msg.from.user.id) then --ignore blocked users return false -- if a user is blocked, don't go through plugins end diff --git a/lua/groupbutler/plugins/pin.lua b/lua/groupbutler/plugins/pin.lua index 8f6160064..b72d6e1e6 100644 --- a/lua/groupbutler/plugins/pin.lua +++ b/lua/groupbutler/plugins/pin.lua @@ -25,7 +25,7 @@ local function new_pin(self, msg, pin_text) local api_err = self.api_err local reply_markup, text = get_reply_markup(self, msg, pin_text) local ok, err = self.api:send_message{ - chat_id = msg.chat.id, + chat_id = msg.from.chat.id, text = text, parse_mode = "Markdown", disable_web_page_preview = true, @@ -37,20 +37,20 @@ local function new_pin(self, msg, pin_text) return end - pin_message(self, msg.chat.id, ok.message_id) + pin_message(self, msg.from.chat.id, ok.message_id) return end local function edit_pin(self, msg, pin_text) local api_err = self.api_err - local pin_id = self.red:get("chat:"..msg.chat.id..":pin") + local pin_id = self.red:get("chat:"..msg.from.chat.id..":pin") if pin_id == null then new_pin(self, msg, pin_text) return end local reply_markup, text = get_reply_markup(self, msg, pin_text) local ok, err = self.api:edit_message_text{ - chat_id = msg.chat.id, + chat_id = msg.from.chat.id, message_id = pin_id, text = text, parse_mode = "Markdown", @@ -65,26 +65,26 @@ local function edit_pin(self, msg, pin_text) msg:send_reply(api_err:trans(err), "Markdown") return end - pin_message(self, msg.chat.id, ok.message_id) + pin_message(self, msg.from.chat.id, ok.message_id) return end local function last_pin(self, msg) local i18n = self.i18n - local pin_id = self.red:get("chat:"..msg.chat.id..":pin") + local pin_id = self.red:get("chat:"..msg.from.chat.id..":pin") if pin_id == null then msg:send_reply(i18n("I couldn't find any message generated by /pin."), "html") return end local ok, err = self.api:send_message{ - chat_id = msg.chat.id, + chat_id = msg.from.chat.id, text = i18n("Last message generated by /pin ^"), parse_mode = "html", reply_to_message_id = pin_id, } if not ok and err.description:lower():match("reply message not found") then msg:send_reply(i18n("The old message generated with /pin does not exist anymore."), "html") - self.red:del("chat:"..msg.chat.id..":pin") + self.red:del("chat:"..msg.from.chat.id..":pin") return end end @@ -92,8 +92,8 @@ end function _M:onTextMessage(blocks) local msg = self.message - if msg.chat.type == "private" - or not msg:is_from_admin() then + if msg.from.chat.type == "private" + or not msg.from:isAdmin() then return end diff --git a/lua/groupbutler/plugins/private.lua b/lua/groupbutler/plugins/private.lua index e77a60146..8d527383e 100644 --- a/lua/groupbutler/plugins/private.lua +++ b/lua/groupbutler/plugins/private.lua @@ -55,24 +55,24 @@ function _M:onTextMessage(blocks) local i18n = self.i18n local api_err = self.api_err - if msg.chat.type ~= 'private' then return end + if msg.from.chat.type ~= 'private' then return end if blocks[1] == 'ping' then - api:sendMessage(msg.from.id, i18n("Pong!"), "Markdown") + api:sendMessage(msg.from.user.id, i18n("Pong!"), "Markdown") end if blocks[1] == 'echo' then - local ok, err = api:sendMessage(msg.chat.id, blocks[2], "Markdown") + local ok, err = api:sendMessage(msg.from.chat.id, blocks[2], "Markdown") if not ok then - api:sendMessage(msg.chat.id, api_err:trans(err), "Markdown") + api:sendMessage(msg.from.chat.id, api_err:trans(err), "Markdown") end end if blocks[1] == 'about' then local keyboard = do_keyboard_credits(self) - api:sendMessage(msg.chat.id, strings(self).about, "Markdown", true, nil, nil, keyboard) + api:sendMessage(msg.from.chat.id, strings(self).about, "Markdown", true, nil, nil, keyboard) end if blocks[1] == 'group' then if config.help_group and config.help_group ~= '' then - api:sendMessage(msg.chat.id, + api:sendMessage(msg.from.chat.id, i18n('You can find the list of our support groups in [this channel](%s)'):format(config.help_group), "Markdown") end end @@ -85,12 +85,12 @@ function _M:onCallbackQuery(blocks) if blocks[1] == 'about' then local keyboard = do_keyboard_credits(self) - api:editMessageText(msg.chat.id, msg.message_id, nil, strings(self).about, "Markdown", true, keyboard) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, strings(self).about, "Markdown", true, keyboard) end if blocks[1] == 'group' then if config.help_group and config.help_group ~= '' then local markup = {inline_keyboard={{{text = i18n('🔙 back'), callback_data = 'fromhelp:about'}}}} - api:editMessageText(msg.chat.id, msg.message_id, nil, + api:editMessageText(msg.from.chat.id, msg.message_id, nil, i18n("You can find the list of our support groups in [this channel](%s)"):format(config.help_group), "Markdown", nil, markup) end diff --git a/lua/groupbutler/plugins/private_settings.lua b/lua/groupbutler/plugins/private_settings.lua index 7d509595b..50b5f3fd2 100644 --- a/lua/groupbutler/plugins/private_settings.lua +++ b/lua/groupbutler/plugins/private_settings.lua @@ -54,10 +54,10 @@ function _M:onTextMessage() local api = self.api local msg = self.message local i18n = self.i18n - if msg.chat.type == 'private' then - local reply_markup = doKeyboard_privsett(self, msg.from.id) + if msg.from.chat.type == 'private' then + local reply_markup = doKeyboard_privsett(self, msg.from.user.id) api:send_message{ - chat_id = msg.from.id, + chat_id = msg.from.user.id, text = i18n("Change your private settings"), reply_markup = reply_markup } @@ -72,10 +72,10 @@ function _M:onCallbackQuery(blocks) api:answerCallbackQuery(msg.cb_id, get_button_description(self, blocks[2]), true) return end - self.db:toggle_user_setting(msg.from.id, blocks[2]) - local reply_markup = doKeyboard_privsett(self, msg.from.id) + self.db:toggle_user_setting(msg.from.user.id, blocks[2]) + local reply_markup = doKeyboard_privsett(self, msg.from.user.id) api:edit_message_reply_markup{ - chat_id = msg.from.id, + chat_id = msg.from.user.id, message_id = msg.message_id, reply_markup = reply_markup } diff --git a/lua/groupbutler/plugins/report.lua b/lua/groupbutler/plugins/report.lua index b21a57f34..cfeced62e 100644 --- a/lua/groupbutler/plugins/report.lua +++ b/lua/groupbutler/plugins/report.lua @@ -1,5 +1,6 @@ local config = require "groupbutler.config" local null = require "groupbutler.null" +local api_u = require("telegram-bot-api.utilities") local _M = {} @@ -22,26 +23,26 @@ end local function report(self, msg, description) local api = self.api local red = self.red + local db = self.db local i18n = self.i18n - local u = self.u local text = i18n( - '• Message reported by: %s (%d)'):format(u:getname_final(msg.from), msg.from.id) - local chat_link = red:hget('chat:'..msg.chat.id..':links', 'link') + '• Message reported by: %s (%d)'):format(msg.from.user:getLink(), msg.from.user.id) + local chat_link = red:hget('chat:'..msg.from.chat.id..':links', 'link') if msg.reply.forward_from or msg.reply.forward_from_chat or msg.reply.sticker then text = text..i18n( '\n• Reported message sent by: %s (%d)' - ):format(u:getname_final(msg.reply.from), msg.reply.from.id) + ):format(msg.reply.from.user:getLink(), msg.reply.from.user.id) end if chat_link == null then - text = text..i18n('\n• Group: %s'):format(msg.chat.title:escape_html()) + text = text..i18n('\n• Group: %s'):format(msg.from.chat.title:escape_html()) else - text = text..i18n('\n• Group: %s'):format(chat_link, msg.chat.title:escape_html()) + text = text..i18n('\n• Group: %s'):format(chat_link, msg.from.chat.title:escape_html()) end - if msg.chat.username then + if msg.from.chat.username then text = text..i18n( '\n• Go to the message' - ):format('telegram.me/'..msg.chat.username..'/'..msg.message_id) + ):format('telegram.me/'..msg.from.chat.username..'/'..msg.message_id) end if description then text = text..i18n('\n• Description: %s'):format(description:escape_html()) @@ -49,20 +50,18 @@ local function report(self, msg, description) local n = 0 - local admins_list = u:get_cached_admins_list(msg.chat.id) + local admins_list = db:getChatAdministratorsList(msg.from.chat) if not admins_list then return false end - local desc_msg - local markup = {inline_keyboard={{{text = i18n("Address this report")}}}} - local callback_data = ("report:%d:"):format(msg.chat.id) - local hash = 'chat:'..msg.chat.id..':report:'..msg.message_id --stores the user_id and the msg_id of the report messages sent to the admins + local callback_data = ("report:%d:"):format(msg.from.chat.id) + local hash = 'chat:'..msg.from.chat.id..':report:'..msg.message_id --stores the user_id and the msg_id of the report messages sent to the admins for i=1, #admins_list do - local receive_reports = red:hget('user:'..admins_list[i]..':settings', 'reports') - if receive_reports ~= null and receive_reports == 'on' then - local res_fwd = api:forwardMessage(admins_list[i], msg.chat.id, msg.reply.message_id) + if db:get_user_setting(admins_list[i], "reports") then + local res_fwd = api:forwardMessage(admins_list[i], msg.from.chat.id, msg.reply.message_id) if res_fwd then - markup.inline_keyboard[1][1].callback_data = callback_data..(msg.message_id) - desc_msg = api:sendMessage(admins_list[i], text, 'html', true, nil, res_fwd.message_id, markup) + local reply_markup = api_u.InlineKeyboardMarkup:new() + :row({text = i18n("Address this report"), callback_data = callback_data..msg.message_id}) + local desc_msg = api:sendMessage(admins_list[i], text, 'html', true, nil, res_fwd.message_id, reply_markup) if desc_msg then red:hset(hash, admins_list[i], desc_msg.message_id) --save the msg_id of the msg sent to the admin n = n + 1 @@ -100,8 +99,9 @@ function _M:onTextMessage(blocks) local i18n = self.i18n local u = self.u - if msg.chat.id < 0 then - if #blocks > 1 and u:is_allowed('config', msg.chat.id, msg.from) then + if msg.from.chat.id < 0 then + if #blocks > 1 + and msg.from:isAdmin() then local times_allowed, duration = tonumber(blocks[2]), tonumber(blocks[3]) local text if times_allowed < 1 or times_allowed > 1000 then @@ -109,7 +109,7 @@ function _M:onTextMessage(blocks) elseif duration < 1 or duration > 10080 then text = i18n("_Invalid value:_ time (`input: %d`)"):format(duration) else - local hash = 'chat:'..msg.chat.id..':report' + local hash = 'chat:'..msg.from.chat.id..':report' red:hset(hash, 'times_allowed', times_allowed) red:hset(hash, 'duration', (duration * 60)) text = i18n( @@ -118,21 +118,21 @@ function _M:onTextMessage(blocks) end msg:send_reply(text, "Markdown") else - if not msg.reply or msg:is_from_admin() then + if not msg.reply or msg.from:isAdmin() then return end - local status = red:hget('chat:'..msg.chat.id..':settings', 'Reports') + local status = red:hget('chat:'..msg.from.chat.id..':settings', 'Reports') if status == null then status = config.chat_settings['settings']['Reports'] end if status == 'off' then return end local text - if user_is_abusing(self, msg.chat.id, msg.from.id) then - local hash = 'chat:'..msg.chat.id..':report' + if user_is_abusing(self, msg.from.chat.id, msg.from.user.id) then + local hash = 'chat:'..msg.from.chat.id..':report' local duration = tonumber(red:hget(hash, 'duration')) or config.bot_settings.report.duration local times_allowed = tonumber(red:hget(hash, 'times_allowed')) or config.bot_settings.report.times_allowed - local ttl = red:ttl(hash..':'..msg.from.id) + local ttl = red:ttl(hash..':'..msg.from.user.id) local minutes, seconds = seconds2minutes(ttl) text = i18n([[_Please, do not abuse this command. It can be used %d times every %d minutes_. Wait other %d minutes, %d seconds.]]):format(times_allowed, (duration / 60), minutes, seconds) @@ -177,7 +177,7 @@ function _M:onCallbackQuery(blocks) if addressed_by == null then --no one addressed the issue yet - local name = msg.from.first_name:sub(1, 120) + local name = msg.from.user.first_name:sub(1, 120) local chats_reached = red:hgetall(hash) if next(chats_reached) then local markup = {inline_keyboard={ @@ -188,7 +188,7 @@ function _M:onCallbackQuery(blocks) api:editMessageReplyMarkup(user_id, message_id, markup) end table.insert(markup.inline_keyboard, close_issue_line) - api:editMessageReplyMarkup(msg.from.id, msg.message_id, markup) + api:editMessageReplyMarkup(msg.from.user.id, msg.message_id, markup) end red:setex(hash..':addressed', 3600*24*2, name) api:answerCallbackQuery(msg.cb_id, "✅") @@ -196,7 +196,7 @@ function _M:onCallbackQuery(blocks) api:answerCallbackQuery(msg.cb_id, i18n("%s has/will address this report"):format(addressed_by), true, 48 * 3600) end elseif blocks[1] == 'close' then - local key = hash .. (':close:%d'):format(msg.from.id) + local key = hash .. (':close:%d'):format(msg.from.user.id) local second_tap = red:get(key) if second_tap == null then red:setex(key, 3600*24, 'x') @@ -205,12 +205,12 @@ function _M:onCallbackQuery(blocks) else local chats_reached = red:hgetall(hash) for user_id, message_id in pairs(chats_reached) do - if tonumber(user_id) ~= msg.from.id then + if tonumber(user_id) ~= msg.from.user.id then api:deleteMessages(user_id, { [1] = message_id, [2] = (tonumber(message_id) - 1) }) end end local markup = {inline_keyboard={{{text = i18n("(issue closed by you)"), callback_data = "issueclosed"}}}} - api:editMessageReplyMarkup(msg.from.id, msg.message_id, nil, markup) + api:editMessageReplyMarkup(msg.from.user.id, msg.message_id, nil, markup) end end end diff --git a/lua/groupbutler/plugins/rules.lua b/lua/groupbutler/plugins/rules.lua index bfb80d3be..7ee5ead1c 100644 --- a/lua/groupbutler/plugins/rules.lua +++ b/lua/groupbutler/plugins/rules.lua @@ -28,21 +28,21 @@ function _M:onTextMessage(blocks) local api_err = self.api_err local u = self.u - if msg.chat.type == 'private' then + if msg.from.chat.type == 'private' then if blocks[1] == 'start' then - msg.chat.id = tonumber(blocks[2]) + msg.from.chat.id = tonumber(blocks[2]) - local res = api:getChat(msg.chat.id) + local res = api:getChat(msg.from.chat.id) if not res then - api:sendMessage(msg.from.id, i18n("🚫 Unknown or non-existent group")) + api:sendMessage(msg.from.user.id, i18n("🚫 Unknown or non-existent group")) return end -- Private chats have no username local private = not res.username - res = api:getChatMember(msg.chat.id, msg.from.id) + res = api:getChatMember(msg.from.chat.id, msg.from.user.id) if not res or (res.status == 'left' or res.status == 'kicked') and private then - api:sendMessage(msg.from.id, i18n("🚷 You are not a member of this chat. " .. + api:sendMessage(msg.from.user.id, i18n("🚷 You are not a member of this chat. " .. "You can't read the rules of a private group.")) return end @@ -51,22 +51,22 @@ function _M:onTextMessage(blocks) end end - local hash = 'chat:'..msg.chat.id..':info' + local hash = 'chat:'..msg.from.chat.id..':info' if blocks[1] == 'rules' or blocks[1] == 'start' then - local rules = u:getRules(msg.chat.id) + local rules = u:getRules(msg.from.chat.id) local reply_markup reply_markup, rules = u:reply_markup_from_text(rules) local link_preview = rules:find('telegra%.ph/') == nil - if msg.chat.type == 'private' or (not send_in_group(self, msg.chat.id) and not msg:is_from_admin()) then - api:sendMessage(msg.from.id, rules, "Markdown", link_preview, nil, nil, reply_markup) + if msg.from.chat.type == 'private' or (not send_in_group(self, msg.from.chat.id) and not msg.from:isAdmin()) then + api:sendMessage(msg.from.user.id, rules, "Markdown", link_preview, nil, nil, reply_markup) else msg:send_reply(rules, "Markdown", link_preview, nil, nil, reply_markup) end end - if not u:is_allowed('texts', msg.chat.id, msg.from) then return end + if not msg.from:isAdmin() then return end if blocks[1] == 'setrules' then local rules = blocks[2] @@ -87,11 +87,11 @@ function _M:onTextMessage(blocks) --set the new rules local ok, err = msg:send_reply(test_text, "Markdown", nil, nil, reply_markup) if not ok then - api:sendMessage(msg.chat.id, api_err:trans(err), "Markdown") + api:sendMessage(msg.from.chat.id, api_err:trans(err), "Markdown") else red:hset(hash, 'rules', rules) local id = ok.message_id - api:editMessageText(msg.chat.id, id, nil, i18n("New rules *saved successfully*!"), "Markdown") + api:editMessageText(msg.from.chat.id, id, nil, i18n("New rules *saved successfully*!"), "Markdown") end end end diff --git a/lua/groupbutler/plugins/service.lua b/lua/groupbutler/plugins/service.lua index 486163f9a..fd226cd7a 100644 --- a/lua/groupbutler/plugins/service.lua +++ b/lua/groupbutler/plugins/service.lua @@ -23,43 +23,36 @@ function _M:onTextMessage(blocks) if not msg.service then return end if blocks[1] == "new_chat_member" then - red:sadd(string.format("chat:%d:members", msg.chat.id), msg.new_chat_member.id) + red:sadd(string.format("chat:%d:members", msg.from.chat.id), msg.new_chat_member.id) end if blocks[1] == "left_chat_member" then - red:srem(string.format("chat:%d:members", msg.chat.id), msg.left_chat_member.id) + red:srem(string.format("chat:%d:members", msg.from.chat.id), msg.left_chat_member.id) end if blocks[1] == 'new_chat_member' or blocks[1] == 'left_chat_member' then - local status = red:hget(('chat:%d:settings'):format(msg.chat.id), 'Clean_service_msg') + local status = red:hget(('chat:%d:settings'):format(msg.from.chat.id), 'Clean_service_msg') if status == null then status = config.chat_settings.settings.Clean_service_msg end if status == 'on' then - api:deleteMessage(msg.chat.id, msg.message_id) + api:deleteMessage(msg.from.chat.id, msg.message_id) end return true end if blocks[1] == 'new_chat_member:bot' or blocks[1] == 'migrate_from_chat_id' then - -- set the language - --[[locale.language = red:get(string.format('lang:%d', msg.from.id)) or config.lang - if not config.available_languages[locale.language] then - locale.language = 'en' - end]] - if u:is_blocked_global(msg.from.id) then - api:sendMessage(msg.chat.id, i18n("_You (user ID: %d) are in the blocked list_"):format(msg.from.id), "Markdown") - api:leaveChat(msg.chat.id) + if u:is_blocked_global(msg.from.user.id) then + api:sendMessage(msg.from.chat.id, i18n("_You (user ID: %d) are in the blocked list_"):format(msg.from.user.id), + "Markdown") + api:leaveChat(msg.from.chat.id) return end - if config.bot_settings.admin_mode and not u:is_superadmin(msg.from.id) then - api:sendMessage(msg.chat.id, i18n("_Admin mode is on: only the bot admin can add me to a new group_"), "Markdown") - api:leaveChat(msg.chat.id) + if config.bot_settings.admin_mode and not u:is_superadmin(msg.from.user.id) then + api:sendMessage(msg.from.chat.id, i18n("_Admin mode is on: only the bot admin can add me to a new group_"), + "Markdown") + api:leaveChat(msg.from.chat.id) return end - -- save language - --[[if locale.language then - red:set(string.format('lang:%d', msg.chat.id), locale.language) - end]] - u:initGroup(msg.chat.id) + u:initGroup(msg.from.chat) -- send manuals local text if blocks[1] == 'new_chat_member:bot' then @@ -69,23 +62,9 @@ function _M:onTextMessage(blocks) else text = i18n("Yay! This group has been upgraded. You are great! Now I can work properly :)\n") end - --[[if not u:is_admin(msg.chat.id, bot.id) then - if u:is_owner(msg.chat.id, msg.from.id) then - text = text .. i18n("Hmm… apparently I'm not an administrator. " - .. "I can be more useful if you make me an admin. " - .. "See [here](https://telegram.me/GroupButler_ch/104) how to do it.\n") - else - text = text .. i18n("Hmm… apparently I'm not an administrator. " - .. "I can be more useful if I'm an admin. Ask a creator to make me an admin. " - .. "If they don't know how, there is a good [guide](https://telegram.me/GroupButler_ch/104).\n") - end - end - text = text .. i18n("I can do a lot of cool things. To discover about them, " - -- TODO: old link, update it - .. "watch this [video-tutorial](https://youtu.be/uqNumbcUyzs).") ]] - api:sendMessage(msg.chat.id, text, "Markdown") + api:sendMessage(msg.from.chat.id, text, "Markdown") elseif blocks[1] == 'left_chat_member:bot' then - u:remGroup(msg.chat.id) + u:remGroup(msg.from.chat.id) end u:logEvent(blocks[1], msg) diff --git a/lua/groupbutler/plugins/setlang.lua b/lua/groupbutler/plugins/setlang.lua index 285f7b051..ee102c0ff 100644 --- a/lua/groupbutler/plugins/setlang.lua +++ b/lua/groupbutler/plugins/setlang.lua @@ -25,12 +25,12 @@ end function _M:onTextMessage() local api = self.api local msg = self.message - local u = self.u local i18n = self.i18n - if msg.chat.type == 'private' or (msg.chat.id < 0 and u:is_allowed('config', msg.chat.id, msg.from)) then + if msg.from.chat.type == "private" + or msg.from:can("can_change_info") then local keyboard = doKeyboard_lang() - api:sendMessage(msg.chat.id, i18n("*List of available languages*:"), "Markdown", nil, nil, nil, keyboard) + api:sendMessage(msg.from.chat.id, i18n("*List of available languages*:"), "Markdown", nil, nil, nil, keyboard) end end @@ -40,27 +40,30 @@ function _M:onCallbackQuery(blocks) local red = self.red local i18n = self.i18n - if msg.chat.type ~= 'private' and not msg:is_from_admin() then - api:answerCallbackQuery(msg.cb_id, i18n("You are not an admin")) - else - if blocks[1] == 'selectlang' then - local keyboard = doKeyboard_lang() - api:editMessageText(msg.chat.id, msg.message_id, nil, i18n("*List of available languages*:"), "Markdown", nil, - keyboard) - else - i18n:setLanguage(blocks[1]) - red:set('lang:'..msg.chat.id, i18n:getLanguage()) - if (blocks[1] == 'ar_SA' or blocks[1] == 'fa_IR') and msg.chat.type ~= 'private' then - red:hset('chat:'..msg.chat.id..':char', 'Arab', 'allowed') - red:hset('chat:'..msg.chat.id..':char', 'Rtl', 'allowed') - end - -- TRANSLATORS: replace 'English' with the name of your language - api:editMessageText(msg.chat.id, msg.message_id, nil, i18n("English language is *set*") .. -i18n([[. + if msg.from.chat.type ~= "private" + and not msg.from:isAdmin() then + api:answerCallbackQuery(msg.cb_id, i18n("Sorry, you don't have permission to change settings")) + return + end + + if blocks[1] == "selectlang" then + local keyboard = doKeyboard_lang() + api:editMessageText(msg.from.chat.id, msg.message_id, nil, i18n("*List of available languages*:"), "Markdown", nil, + keyboard) + return + end + + i18n:setLanguage(blocks[1]) + red:set("lang:"..msg.from.chat.id, i18n:getLanguage()) + if msg.from.chat.type ~= "private" + and (blocks[1] == "ar_SA" or blocks[1] == "fa_IR") then + red:hset("chat:"..msg.from.chat.id..":char", "Arab", "allowed") + red:hset("chat:"..msg.from.chat.id..":char", "Rtl", "allowed") + end + -- TRANSLATORS: replace 'English' with the name of your language + api:editMessageText(msg.from.chat.id, msg.message_id, nil, i18n("English language is *set*")..i18n([[. Please note that translators are volunteers, and this localization _may be incomplete_. You can help improve translations on our [Crowdin Project](https://crowdin.com/project/group-butler). ]]), "Markdown") - end - end end _M.triggers = { diff --git a/lua/groupbutler/plugins/users.lua b/lua/groupbutler/plugins/users.lua index 0ebf008b7..989520347 100644 --- a/lua/groupbutler/plugins/users.lua +++ b/lua/groupbutler/plugins/users.lua @@ -1,4 +1,6 @@ local config = require "groupbutler.config" +local User = require("groupbutler.user") +local Chat = require("groupbutler.chat") local _M = {} @@ -11,6 +13,11 @@ function _M:new(update_obj) return plugin_obj end +local function set_default(t, d) + local mt = {__index = function() return d end} + setmetatable(t, mt) +end + local function permissions(self) local i18n = self.i18n return { @@ -33,26 +40,6 @@ local function do_keyboard_cache(self, chat_id) return keyboard end -local function get_time_remaining(seconds) - local final = '' - local hours = math.floor(seconds/3600) - seconds = seconds - (hours*60*60) - local min = math.floor(seconds/60) - seconds = seconds - (min*60) - - if hours and hours > 0 then - final = final..hours..'h ' - end - if min and min > 0 then - final = final..min..'m ' - end - if seconds and seconds > 0 then - final = final..seconds..'s' - end - - return final -end - local function do_keyboard_userinfo(self, user_id) local i18n = self.i18n local keyboard = { @@ -81,52 +68,44 @@ end function _M:onTextMessage(blocks) local api = self.api local msg = self.message - local red = self.red + local db = self.db local i18n = self.i18n local u = self.u if blocks[1] == 'id' then --in private: send user id - if msg.chat.id > 0 and msg.chat.type == 'private' then - api:sendMessage(msg.chat.id, string.format(i18n('Your ID is `%d`'), msg.from.id), "Markdown") + if msg.from.chat.id > 0 and msg.from.chat.type == 'private' then + api:sendMessage(msg.from.chat.id, string.format(i18n('Your ID is `%d`'), msg.from.user.id), "Markdown") end end - if msg.chat.type == 'private' then return end + if msg.from.chat.type == 'private' then return end if blocks[1] == 'id' then --in groups: send chat ID - if msg.chat.id < 0 and msg:is_from_admin() then - api:sendMessage(msg.chat.id, string.format('`%d`', msg.chat.id), "Markdown") + if msg.from.chat.id < 0 and msg.from:isAdmin() then + api:sendMessage(msg.from.chat.id, string.format('`%d`', msg.from.chat.id), "Markdown") end end if blocks[1] == 'adminlist' then - local adminlist = u:getAdminlist(msg.chat.id) - if not msg:is_from_admin() then - api:sendMessage(msg.from.id, adminlist, 'html', true) + local adminlist = u:getAdminlist(msg.from.chat) + if not msg.from:isAdmin() then + api:sendMessage(msg.from.user.id, adminlist, 'html', true) else msg:send_reply(adminlist, 'html', true) end end if blocks[1] == 'status' then - if (not blocks[2] and not msg.reply) or not msg:is_from_admin() then + if (not blocks[2] and not msg.reply) or not msg.from:isAdmin() then return end - local user_id, error_tr_id = u:get_user_id(msg, blocks) - if not user_id then - msg:send_reply(error_tr_id, "Markdown") - return - end - local res = api:getChatMember(msg.chat.id, user_id) - - if not res then - msg:send_reply(i18n("That user has nothing to do with this chat")) + local member, err = msg:getTargetMember(blocks) + if not member then + msg:send_reply(err, "Markdown") return end - local status = res.status - local name = u:getname_final(res.user) local statuses = { kicked = i18n("%s is banned from this group"), left = i18n("%s left the group or has been kicked and unbanned"), @@ -135,24 +114,25 @@ function _M:onTextMessage(blocks) unknown = i18n("%s has nothing to do with this chat"), member = i18n("%s is a chat member"), restricted = i18n("%s is a restricted") - } + } set_default(statuses, statuses.unknown) + local denied_permissions = {} for permission, str in pairs(permissions(self)) do - if res[permission] ~= nil and res[permission] == false then + if member[permission] ~= nil and member[permission] == false then table.insert(denied_permissions, str) end end - local text = statuses[status]:format(name) + local text = statuses[member.status]:format(member.user:getLink()) if next(denied_permissions) then text = text..i18n('\nRestrictions: %s'):format(table.concat(denied_permissions, ', ')) end msg:send_reply(text, 'html') end - if blocks[1] == 'user' then - if not msg:is_from_admin() then return end + if blocks[1] == 'user' then + if not msg.from:isAdmin() then return end if not msg.reply and (not blocks[2] or (not blocks[2]:match('@[%w_]+$') and not blocks[2]:match('%d+$') and not msg.mention_id)) then @@ -160,92 +140,70 @@ function _M:onTextMessage(blocks) return end - ------------------ get user_id -------------------------- - local user_id, err = u:get_user_id(msg, blocks) - - if not user_id then + local member, err = msg:getTargetMember(blocks) + if not member then msg:send_reply(err, "Markdown") return end - ----------------------------------------------------------------------------- - - local keyboard = do_keyboard_userinfo(self, user_id) - local text = get_userinfo(self, user_id, msg.chat.id) + local keyboard = do_keyboard_userinfo(self, member.user.id) - api:sendMessage(msg.chat.id, text, "Markdown", nil, nil, nil, keyboard) + local text = get_userinfo(self, member.user.id, msg.from.chat.id) + api:sendMessage(msg.from.chat.id, text, "Markdown", nil, nil, nil, keyboard) end + if blocks[1] == 'cache' then - if not msg:is_from_admin() then return end - local hash = 'cache:chat:'..msg.chat.id..':admins' - local seconds = red:ttl(hash) - local cached_admins = red:scard(hash) - local text = i18n("📌 Status: `CACHED`\n⌛ ️Remaining: `%s`\n👥 Admins cached: `%d`") - :format(get_time_remaining(tonumber(seconds)), cached_admins) - local keyboard = do_keyboard_cache(self, msg.chat.id) - api:sendMessage(msg.chat.id, text, "Markdown", nil, nil, nil, keyboard) + if not msg.from:isAdmin() then return end + local text = i18n("👥 Admins cached: %d"):format(db:getChatAdministratorsCount(msg.from.chat)) + local keyboard = do_keyboard_cache(self, msg.from.chat.id) + api:sendMessage(msg.from.chat.id, text, "html", nil, nil, nil, keyboard) end - if blocks[1] == 'msglink' then - if not msg.reply or not msg.chat.username then return end + if blocks[1] == 'msglink' then + if not msg.reply or not msg.from.chat.username then return end local text = string.format('[%s](https://telegram.me/%s/%d)', - i18n("Message N° %d"):format(msg.reply.message_id), msg.chat.username, msg.reply.message_id) - if not u:is_silentmode_on(msg.chat.id) or msg:is_from_admin() then + i18n("Message N° %d"):format(msg.reply.message_id), msg.from.chat.username, msg.reply.message_id) + if not u:is_silentmode_on(msg.from.chat.id) or msg.from:isAdmin() then msg.reply:send_reply(text, "Markdown") else - api:sendMessage(msg.from.id, text, "Markdown") + api:sendMessage(msg.from.user.id, text, "Markdown") end end - if blocks[1] == 'leave' and msg:is_from_admin() then - -- u:remGroup(msg.chat.id) - api:leaveChat(msg.chat.id) + + if blocks[1] == 'leave' and msg.from:isAdmin() then + -- u:remGroup(msg.from.chat.id) + api:leaveChat(msg.from.chat.id) end end function _M:onCallbackQuery(blocks) local api = self.api local msg = self.message - local red = self.red local db = self.db local i18n = self.i18n local u = self.u - if not msg:is_from_admin() then + if not msg.from:isAdmin() then api:answerCallbackQuery(msg.cb_id, i18n("You are not allowed to use this button")) return end if blocks[1] == 'remwarns' then - db:forgetUserWarns(msg.chat.id, blocks[2]) - - local name = u:getname_final(msg.from) - local res = api:getChatMember(msg.chat.id, blocks[2]) - local text = i18n("The number of warnings received by this user has been reset, by %s"):format(name) - api:editMessageText(msg.chat.id, msg.message_id, nil, text:format(name), 'html') - u:logEvent('nowarn', msg, { - admin = name, - user = u:getname_final(res.user), + db:forgetUserWarns(msg.from.chat.id, blocks[2]) + local admin = msg.from.user + local target = User:new({id = blocks[2]}, self) + local text = i18n("The number of warnings received by this user has been reset, by %s"):format(admin:getLink()) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, text, "html") + u:logEvent("nowarn", msg, { + admin = admin, + user = target, user_id = blocks[2] }) end - if blocks[1] == 'recache' and msg:is_from_admin() then - local missing_sec = tonumber(red:ttl('cache:chat:'..msg.target_id..':admins') or 0) - local wait = 600 - if config.bot_settings.cache_time.adminlist - missing_sec < wait then - local seconds_to_wait = wait - (config.bot_settings.cache_time.adminlist - missing_sec) - api:answerCallbackQuery(msg.cb_id,i18n( - "The adminlist has just been updated. You must wait 10 minutes from the last refresh (wait %d seconds)" - ):format(seconds_to_wait), true) - else - red:del('cache:chat:'..msg.target_id..':admins') - u:cache_adminlist(msg.target_id) - local cached_admins = red:smembers('cache:chat:'..msg.target_id..':admins') - local time = get_time_remaining(config.bot_settings.cache_time.adminlist) - local text = i18n("📌 Status: `CACHED`\n⌛ ️Remaining: `%s`\n👥 Admins cached: `%d`") - :format(time, #cached_admins) - api:answerCallbackQuery(msg.cb_id, i18n("✅ Updated. Next update in %s"):format(time)) - api:editMessageText(msg.chat.id, msg.message_id, nil, text, "Markdown", nil, do_keyboard_cache(self, msg.target_id)) - end + + if blocks[1] == 'recache' and msg.from:isAdmin() then + u:cache_adminlist(Chat:new({id=msg.target_id}, self)) + api:answerCallbackQuery(msg.cb_id, i18n("✅ The admin list will be updated soon")) end end diff --git a/lua/groupbutler/plugins/warn.lua b/lua/groupbutler/plugins/warn.lua index bf92c02f0..5c60a5352 100644 --- a/lua/groupbutler/plugins/warn.lua +++ b/lua/groupbutler/plugins/warn.lua @@ -29,14 +29,14 @@ function _M:onTextMessage(blocks) local i18n = self.i18n local u = self.u - if msg.chat.type == 'private' - or (msg.chat.type ~= 'private' and not u:is_allowed('hammer', msg.chat.id, msg.from)) then + if msg.from.chat.type == "private" + or not msg.from:isAdmin() then return end if blocks[1] == 'warnmax' then local new, default, text, key - local hash = 'chat:'..msg.chat.id..':warnsettings' + local hash = 'chat:'..msg.from.chat.id..':warnsettings' if blocks[2] == 'media' then new = blocks[3] default = 2 @@ -64,7 +64,7 @@ function _M:onTextMessage(blocks) {{{text = i18n('Yes'), callback_data = 'cleanwarns:yes'}, {text = i18n('No'), callback_data = 'cleanwarns:no'}}} } - api:sendMessage(msg.chat.id, + api:sendMessage(msg.from.chat.id, i18n('Do you want to continue and reset *all* the warnings received by *all* the users of the group?'), "Markdown", nil, nil, nil, reply_markup) @@ -72,85 +72,88 @@ function _M:onTextMessage(blocks) end --do not reply when... - local user_id, err_msg = u:get_user_id(msg, blocks) - if not user_id then - msg:send_reply(err_msg, "Markdown") - return + local admin = msg.from + local target + do + local err + target, err = msg:getTargetMember(blocks) + if not target then + msg:send_reply(err, "Markdown") + return + end end - if tonumber(user_id) == bot.id then return end + if tonumber(target.user.id) == bot.id then return end if blocks[1] == 'nowarn' then - db:forgetUserWarns(msg.chat.id, msg.reply.from.id) - local admin = u:getname_final(msg.from) - local user = u:getname_final(msg.reply.from) - local text = i18n("Done! %s has been forgiven."):format(user) + db:forgetUserWarns(msg.from.chat.id, msg.reply.from.user.id) + local text = i18n("Done! %s has been forgiven."):format(target.user:getLink()) msg:send_reply(text, 'html') u:logEvent('nowarn', msg, { - admin = admin, - user = user, - user_id = msg.reply.from.id + admin = admin.user, + user = target.user, + user_id = target.user.id }) + return end - if u:is_admin(msg.chat.id, user_id) then return end + if target:isAdmin() then return end if blocks[1] == 'warn' or blocks[1] == 'sw' then - -- Get the user that was targeted, again, but get the name this time - local admin_name, target_name = u:getnames_complete(msg) - - local hash = 'chat:'..msg.chat.id..':warns' - local num = tonumber(red:hincrby(hash, user_id, 1)) --add one warn - local nmax = tonumber(red:hget('chat:'..msg.chat.id..':warnsettings', 'max')) or 3 --get the max num of warnings + local hash = 'chat:'..msg.from.chat.id..':warns' + local num = tonumber(red:hincrby(hash, target.user.id, 1)) --add one warn + local nmax = tonumber(red:hget('chat:'..msg.from.chat.id..':warnsettings', 'max')) or 3 --get the max num of warnings local text, res, err, hammer_log if num >= nmax then - local type = red:hget('chat:'..msg.chat.id..':warnsettings', 'type') + local type = red:hget('chat:'..msg.from.chat.id..':warnsettings', 'type') if type == null then type = 'kick' end --try to kick/ban text = i18n("%s %s: reached the max number of warnings (%d/%d)") if type == 'ban' then hammer_log = i18n('banned') - text = text:format(target_name, hammer_log, num, nmax) - res, err = u:banUser(msg.chat.id, user_id) + text = text:format(target.user:getLink(), hammer_log, num, nmax) + res, err = target:ban() elseif type == 'kick' then --kick hammer_log = i18n('kicked') - text = text:format(target_name, hammer_log, num, nmax) - res, err = u:kickUser(msg.chat.id, user_id) + text = text:format(target.user:getLink(), hammer_log, num, nmax) + res, err = target:kick() elseif type == 'mute' then --kick hammer_log = i18n('muted') - text = text:format(target_name, hammer_log, num, nmax) - res, err = u:muteUser(msg.chat.id, user_id) + text = text:format(target.user:getLink(), hammer_log, num, nmax) + res, err = target:mute() end --if kick/ban fails, send the motivation if not res then - if num > nmax then red:hset(hash, user_id, nmax) end --avoid to have a number of warnings bigger than the max + if num > nmax then red:hset(hash, target.user.id, nmax) end --avoid to have a number of warnings bigger than the max text = err else - db:forgetUserWarns(msg.chat.id, user_id) + db:forgetUserWarns(msg.from.chat.id, target.user.id) end --if the user reached the max num of warns, kick and send message msg:send_reply(text, 'html') u:logEvent('warn', msg, { motivation = blocks[2], - admin = admin_name, - user = target_name, - user_id = user_id, + admin = admin.user, + user = target.user, + user_id = target.user.id, hammered = hammer_log, warns = num, warnmax = nmax }) else - text = i18n("%s has been warned (%d/%d)"):format(target_name, num, nmax) - local keyboard = doKeyboard_warn(self, user_id) - if blocks[1] ~= 'sw' then api:sendMessage(msg.chat.id, text, 'html', true, nil, nil, keyboard) end + if blocks[1] ~= 'sw' then + text = i18n("%s has been warned (%d/%d)"):format(target.user:getLink(), num, nmax) + local keyboard = doKeyboard_warn(self, target.user.id) + api:sendMessage(msg.from.chat.id, text, 'html', true, nil, nil, keyboard) + end u:logEvent('warn', msg, { motivation = blocks[2], warns = num, warnmax = nmax, - admin = admin_name, - user = target_name, - user_id = user_id + admin = admin.user, + user = target.user, + user_id = target.user.id }) end end @@ -161,36 +164,40 @@ function _M:onCallbackQuery(blocks) local msg = self.message local red = self.red local i18n = self.i18n - local u = self.u - if not u:is_allowed('hammer', msg.chat.id, msg.from) then - api:answerCallbackQuery(msg.cb_id, i18n("You are not allowed to use this button")) return + if msg.from.chat.type == "private" + or not msg.from:isAdmin() then + api:answerCallbackQuery(msg.cb_id, i18n("You are not allowed to use this button")) + return end + local admin = msg.from.user + if blocks[1] == 'removewarn' then local user_id = blocks[2] - local num = tonumber(red:hincrby('chat:'..msg.chat.id..':warns', user_id, -1)) --add one warn + local num = tonumber(red:hincrby('chat:'..msg.from.chat.id..':warns', user_id, -1)) --add one warn local text, nmax if num < 0 then text = i18n("The number of warnings received by this user is already zero") - red:hincrby('chat:'..msg.chat.id..':warns', user_id, 1) --restore the previouvs number + red:hincrby('chat:'..msg.from.chat.id..':warns', user_id, 1) --restore the previouvs number else - nmax = tonumber(red:hget('chat:'..msg.chat.id..':warnsettings', 'max')) or 3 --get the max num of warnings + nmax = tonumber(red:hget('chat:'..msg.from.chat.id..':warnsettings', 'max')) or 3 --get the max num of warnings text = i18n("Warn removed! (%d/%d)"):format(num, nmax) end - text = text .. i18n("\n(Admin: %s)"):format(u:getname_final(msg.from)) - api:editMessageText(msg.chat.id, msg.message_id, nil, text, 'html') + text = text .. i18n("\n(Admin: %s)"):format(admin:getLink()) + api:editMessageText(msg.from.chat.id, msg.message_id, nil, text, 'html') end + if blocks[1] == 'cleanwarns' then if blocks[2] == 'yes' then - red:del('chat:'..msg.chat.id..':warns') - red:del('chat:'..msg.chat.id..':mediawarn') - red:del('chat:'..msg.chat.id..':spamwarns') - api:editMessageText(msg.chat.id, msg.message_id, nil, - i18n('Done. All the warnings of this group have been erased by %s'):format(u:getname_final(msg.from)), 'html') + red:del('chat:'..msg.from.chat.id..':warns') + red:del('chat:'..msg.from.chat.id..':mediawarn') + red:del('chat:'..msg.from.chat.id..':spamwarns') + api:editMessageText(msg.from.chat.id, msg.message_id, nil, + i18n('Done. All the warnings of this group have been erased by %s'):format(admin:getLink()), 'html') else - api:editMessageText(msg.chat.id, msg.message_id, nil, i18n('_Action aborted_'), "Markdown") + api:editMessageText(msg.from.chat.id, msg.message_id, nil, i18n('_Action aborted_'), "Markdown") end end end diff --git a/lua/groupbutler/plugins/welcome.lua b/lua/groupbutler/plugins/welcome.lua index b9dfa8089..c089fa9e1 100644 --- a/lua/groupbutler/plugins/welcome.lua +++ b/lua/groupbutler/plugins/welcome.lua @@ -15,51 +15,39 @@ end local function ban_bots(self, msg) local db = self.db - local u = self.u - -- ignore if added by an admin or new member joined by link or the setting is disabled - if msg.from.id == msg.new_chat_member.id - or msg:is_from_admin() - or not db:get_chat_setting(msg.chat.id, 'Antibot') then + if msg.from.user.id == msg.new_chat_member.id + or msg.from:isAdmin() + or not db:get_chat_setting(msg.from.chat.id, 'Antibot') then return end - - local users = msg.new_chat_members + local members = msg.new_chat_members local n = 0 --bots banned - for i = 1, #users do - if users[i].is_bot == true then - u:banUser(msg.chat.id, users[i].id) + for i = 1, #members do + if members[i].user.is_bot then + members[i]:ban() n = n + 1 end end - if n == #users then + if n == #members then --if all the new members added are bots then don't send a welcome message return true end end --- local permissions = --- {'can_send_messages', 'can_send_media_messages', 'can_send_other_messages', 'can_add_web_page_previews'} - -local function apply_default_permissions(self, chat_id, users) +local function apply_default_permissions(self, msg) local api = self.api local red = self.red + local members = msg.new_chat_members - local hash = ('chat:%d:defpermissions'):format(chat_id) + local hash = ("chat:%d:defpermissions"):format(msg.from.chat.id) local def_permissions = red:array_to_hash(red:hgetall(hash)) if next(def_permissions) then - --for i=1, #permissions do - --if not def_permissions[permissions[i]] then - --def_permissions[permissions[i]] = config.chat_settings.defpermissions[permissions[i]] - --end - --end - - for i=1, #users do - local res = api:getChatMember(chat_id, users[i].id) - if res.status ~= 'restricted' then - def_permissions.chat_id = chat_id - def_permissions.user_id = users[i].id + for i=1, #members do + if members[i].status == "member" then + def_permissions.chat_id = msg.from.chat.id + def_permissions.user_id = members[i].user.id api:restrictChatMember(def_permissions) end end @@ -76,11 +64,11 @@ local function get_reply_markup(self, msg, text) reply_markup, new_text = u:reply_markup_from_text(u:replaceholders(text, msg)) end - if db:get_chat_setting(msg.chat.id, "Welbut") then + if db:get_chat_setting(msg.from.chat.id, "Welbut") then if not reply_markup then reply_markup = api_u.InlineKeyboardMarkup:new() end - reply_markup:row({text = i18n("Read the rules"), url = u:deeplink_constructor(msg.chat.id, "rules")}) + reply_markup:row({text = i18n("Read the rules"), url = u:deeplink_constructor(msg.from.chat.id, "rules")}) end return reply_markup, new_text @@ -93,11 +81,11 @@ local function send_welcome(self, msg) local u = self.u local db = self.db - if not db:get_chat_setting(msg.chat.id, 'Welcome') then + if not db:get_chat_setting(msg.from.chat.id, 'Welcome') then return end - local hash = 'chat:'..msg.chat.id..':welcome' + local hash = 'chat:'..msg.from.chat.id..':welcome' local welcome_type = red:hget(hash, "type") local content = red:hget(hash, 'content') if welcome_type == "no" or content == "no" -- TODO: database migration no -> null @@ -111,7 +99,7 @@ local function send_welcome(self, msg) or welcome_type == "text" then local reply_markup, text = get_reply_markup(self, msg, content) local link_preview = text:find('telegra%.ph/') == nil - ok, err = api:sendMessage(msg.chat.id, text, "Markdown", link_preview, nil, nil, reply_markup) + ok, err = api:sendMessage(msg.from.chat.id, text, "Markdown", link_preview, nil, nil, reply_markup) end if welcome_type == "media" then local caption = red:hget(hash, "caption") @@ -119,20 +107,20 @@ local function send_welcome(self, msg) caption = nil end local reply_markup, text = get_reply_markup(self, msg, caption) - ok, err = api:sendDocument(msg.chat.id, content, text, nil, nil, reply_markup) + ok, err = api:sendDocument(msg.from.chat.id, content, text, nil, nil, reply_markup) end if not ok and err.description:match("have no rights to send a message") then - u:remGroup(msg.chat.id) - api:leaveChat(msg.chat.id) + u:remGroup(msg.from.chat.id) + api:leaveChat(msg.from.chat.id) return end - if ok and db:get_chat_setting(msg.chat.id, "Weldelchain") then - local key = ('chat:%d:lastwelcome'):format(msg.chat.id) -- get the id of the last sent welcome message + if ok and db:get_chat_setting(msg.from.chat.id, "Weldelchain") then + local key = ('chat:%d:lastwelcome'):format(msg.from.chat.id) -- get the id of the last sent welcome message local message_id = red:get(key) if message_id ~= null then - api:deleteMessage(msg.chat.id, message_id) + api:deleteMessage(msg.from.chat.id, message_id) end red:setex(key, 259200, ok.message_id) --set the new message id to delete end @@ -148,7 +136,10 @@ function _M:onTextMessage(blocks) local u = self.u if blocks[1] == 'welcome' then - if msg.chat.type == 'private' or not u:is_allowed('texts', msg.chat.id, msg.from) then return end + if msg.from.chat.type == "private" + or not msg.from:can("can_change_info") then + return + end local input = blocks[2] if not input and not msg.reply then @@ -156,7 +147,7 @@ function _M:onTextMessage(blocks) return end - local hash = 'chat:'..msg.chat.id..':welcome' + local hash = 'chat:'..msg.from.chat.id..':welcome' if not input and msg.reply then local replied_to = msg.reply:type() @@ -175,7 +166,7 @@ function _M:onTextMessage(blocks) red:hdel(hash, 'caption') --remove the caption key if the new media doesn't have a caption end -- turn on the welcome message in the group settings - db:set_chat_setting(msg.chat.id, "Welcome", true) + db:set_chat_setting(msg.from.chat.id, "Welcome", true) msg:send_reply(i18n("A form of media has been set as the welcome message: `%s`"):format(replied_to), "Markdown") else msg:send_reply(i18n("Reply to a `sticker` or a `gif` to set them as the *welcome message*"), "Markdown") @@ -183,7 +174,7 @@ function _M:onTextMessage(blocks) else local reply_markup, new_text = u:reply_markup_from_text(input) - new_text = new_text:gsub('$rules', u:deeplink_constructor(msg.chat.id, 'rules')) + new_text = new_text:gsub('$rules', u:deeplink_constructor(msg.from.chat.id, 'rules')) red:hset(hash, 'type', 'custom') red:hset(hash, 'content', input) @@ -192,31 +183,32 @@ function _M:onTextMessage(blocks) if not ok then red:hset(hash, 'type', 'no') --if wrong markdown, remove 'custom' again red:hset(hash, 'content', 'no') - api:sendMessage(msg.chat.id, api_err:trans(err), "Markdown") + api:sendMessage(msg.from.chat.id, api_err:trans(err), "Markdown") else -- turn on the welcome message in the group settings - db:set_chat_setting(msg.chat.id, "Welcome", true) + db:set_chat_setting(msg.from.chat.id, "Welcome", true) local id = ok.message_id - api:editMessageText(msg.chat.id, id, nil, i18n("*Custom welcome message saved!*"), "Markdown") + api:editMessageText(msg.from.chat.id, id, nil, i18n("*Custom welcome message saved!*"), "Markdown") end end end + if blocks[1] == 'new_chat_member' then if not msg.service then return end local extra - if msg.from.id ~= msg.new_chat_member.id then extra = msg.from end + if msg.from.user.id ~= msg.new_chat_member.id then extra = msg.from.user end u:logEvent(blocks[1], msg, extra) local stop = ban_bots(self, msg) if stop then return end - apply_default_permissions(self, msg.chat.id, msg.new_chat_members) + apply_default_permissions(self, msg) send_welcome(self, msg) if db:get_user_setting(msg.new_chat_member.id, 'rules_on_join') then - local rules = red:hget('chat:'..msg.chat.id..':info', 'rules') + local rules = red:hget('chat:'..msg.from.chat.id..':info', 'rules') if rules ~= null then api:sendMessage(msg.new_chat_member.id, rules, "Markdown") end diff --git a/lua/groupbutler/storage.lua b/lua/groupbutler/storage.lua index 8e85ccc16..da99d144f 100644 --- a/lua/groupbutler/storage.lua +++ b/lua/groupbutler/storage.lua @@ -14,6 +14,11 @@ local PostgresStorage = {} local MixedStorage = {} +local function set_default(t, d) + local mt = {__index = function() return d end} + setmetatable(t, mt) +end + local function enum(t) local new_t = {} for k,v in pairs(t) do @@ -30,6 +35,15 @@ local chat_type = enum({ channel = 3, }) +local chat_member_status = enum({ + creator = 0, + administrator = 1, + member = 2, + restricted = 3, + left = 4, + kicked = 5, +}) + local function string_toboolean(v) if v == false or v == "false" @@ -148,14 +162,69 @@ function RedisStorage:toggle_user_setting(user_id, setting) self:set_user_setting(user_id, setting, not self:get_user_setting(user_id, setting)) end -function RedisStorage:cache_user(user) - if user.username then - self.redis:hset("bot:usernames", "@"..user.username:lower(), user.id) +function RedisStorage:cacheUser(user) + if rawget(user, "username") then + self.redis:hset("bot:usernames", "@"..rawget(user, "username"):lower(), user.id) + end +end + +function RedisStorage:getUserId(username) + if username:byte(1) ~= string.byte("@") then + username = "@"..username + end + return tonumber(self.redis:hget("bot:usernames", username:lower())) +end + +function RedisStorage:getUserProperty(user, property) -- luacheck: ignore +end + +function RedisStorage:getChatProperty(chat, property) + if property == "title" then + local title = self.redis:get("chat:"..chat.id..":title") + if title == null then + return + end + return title + end +end + +function RedisStorage:getChatAdministratorsCount(chat) + return self.redis:scard("cache:chat:"..chat.id..":admins") +end + +function RedisStorage:getChatAdministratorsList(chat) + local admins = self.redis:smembers("cache:chat:"..chat.id..":admins") or {} + local owner = self.redis:get("cache:chat:"..chat.id..":owner") + if owner then + table.insert(admins, owner) end + return admins end -function RedisStorage:get_user_id(username) - return tonumber(self.redis:hget("bot:usernames", username)) +local is_admin_permission = { + can_be_edited = true, + can_change_info = true, + can_delete_messages = true, + can_invite_users = true, + can_restrict_members = true, + can_promote_members = true, + can_pin_messages = true, +} set_default(is_admin_permission, false) + +function RedisStorage:getChatMemberProperty(member, property) + if is_admin_permission[property] then + local set = ("cache:chat:%s:%s:permissions"):format(member.chat.id, member.user.id) + return self.redis:sismember(set, property) == 1 + end + if property == "status" then + if tonumber(self.redis:get("cache:chat:"..member.chat.id..":owner")) == member.user.id then + return "creator" + end + if self.redis:sismember("cache:chat:"..member.chat.id..":admins", member.user.id) == 1 then + return "administrator" + end + return nil + end end function RedisStorage:cacheChat(chat) @@ -170,12 +239,36 @@ function RedisStorage:cacheChat(chat) end end -function RedisStorage:getChatTitle(chat) - local title = self.redis:get("chat:"..chat.id..":title") - if title == null then - return +local admins_permissions = { + can_be_edited = false, + can_change_info = false, + can_delete_messages = false, + can_invite_users = false, + can_restrict_members = false, + can_promote_members = false, + can_pin_messages = false, +} + +function RedisStorage:cacheAdmins(chat, list) + local set = 'cache:chat:'..chat.id..':admins' + local cache_time = config.bot_settings.cache_time.adminlist + self.redis:del(set) + for _, admin in pairs(list) do + if admin.status == 'creator' then + self.redis:set('cache:chat:'..chat.id..':owner', admin.user.id) + else + local set_permissions = "cache:chat:"..chat.id..":"..admin.user.id..":permissions" + self.redis:del(set_permissions) + for k, v in pairs(admin) do + if v and admins_permissions[k] then + self.redis:sadd(set_permissions, k) + end + end + self.redis:expire(set_permissions, cache_time) + end + self.redis:sadd(set, admin.user.id) end - return title + self.redis:expire(set, cache_time) end function RedisStorage:deleteChat(chat) @@ -194,9 +287,7 @@ function RedisStorage:deleteChat(chat) self.redis:del('chat:'..chat.id..':'..set) end - local owner_id = self.redis:get("cache:chat:"..chat.id..":owner") local keys = { - "cache:chat:"..chat.id..":"..owner_id..":permissions", "cache:chat:"..chat.id..":admins", "cache:chat:"..chat.id..":owner", "chat:"..chat.id..":title", @@ -205,6 +296,11 @@ function RedisStorage:deleteChat(chat) "chat:"..chat.id..":pin", "lang:"..chat.id, } + local owner_id = self.redis:get("cache:chat:"..chat.id..":owner") + if owner_id + and owner_id ~= null then + table.insert(keys, "cache:chat:"..chat.id..":"..owner_id..":permissions") + end for _,k in pairs(keys) do self.redis:del(k) end @@ -219,6 +315,9 @@ function RedisStorage:deleteChat(chat) end end +function RedisStorage:cacheChatMember(member) -- luacheck: ignore 212 +end + function RedisStorage:set_keepalive() self.redis:set_keepalive() end @@ -228,17 +327,17 @@ function RedisStorage:get_reused_times() end local function is_user_property_optional(k) - if k == "last_name" + if k == "is_bot" + or k == "last_name" or k == "username" or k == "language_code" then return true end end -function PostgresStorage:cache_user(user) +function PostgresStorage:cacheUser(user) local row = { id = user.id, - is_bot = user.is_bot, first_name = self.pg:escape_literal(user.first_name) } for k, _ in pairs(user) do @@ -247,11 +346,11 @@ function PostgresStorage:cache_user(user) end end local username = "" - if user.username then + if rawget(user, "username") then username = 'UPDATE "user" SET username = NULL WHERE lower(username) = lower({username});\n' end - local insert = 'INSERT INTO "user" (id, is_bot, first_name' - local values = ") VALUES ({id}, {is_bot}, {first_name}" + local insert = 'INSERT INTO "user" (id, first_name' + local values = ") VALUES ({id}, {first_name}" local on_conflict = " ON CONFLICT (id) DO UPDATE SET first_name = {first_name}" for k, _ in pairs(row) do if is_user_property_optional(k) then @@ -269,7 +368,7 @@ function PostgresStorage:cache_user(user) return true end -function PostgresStorage:get_user_id(username) +function PostgresStorage:getUserId(username) if username:byte(1) == string.byte("@") then username = username:sub(2) end @@ -282,6 +381,49 @@ function PostgresStorage:get_user_id(username) return ok[1].id end +function PostgresStorage:getUserProperty(user, property) + local query = interpolate('SELECT {property} FROM "user" WHERE id = {id}', { + id = user.id, + property = property, + }) + local ok = self.pg:query(query) + if not ok or not ok[1] or not ok[1][property] then + return nil + end + return ok[1][property] +end + +function PostgresStorage:getChatProperty(chat, property) + local query = interpolate('SELECT {property} FROM "chat" WHERE id = {id}', { + id = chat.id, + property = property, + }) + local ok = self.pg:query(query) + if not ok or not ok[1] or not ok[1][property] then + return nil + end + if property == "type" then + return chat_type[ok[1][property]] + end + return ok[1][property] +end + +function PostgresStorage:getChatMemberProperty(member, property) + local query = interpolate('SELECT {property} FROM "chat_user" WHERE chat_id = {chat_id} AND user_id = {user_id}', { + chat_id = member.chat.id, + user_id = member.user.id, + property = property, + }) + local ok = self.pg:query(query) + if not ok or not ok[1] or not ok[1][property] then + return nil + end + if property == "status" then + return chat_member_status[ok[1][property]] + end + return ok[1][property] +end + local function is_chat_property_optional(k) if k == "username" or k == "invite_link" then @@ -321,18 +463,155 @@ function PostgresStorage:cacheChat(chat) return true end -function PostgresStorage:getChatTitle(chat) - local query = interpolate('SELECT title FROM "chat" WHERE id = {id}', chat) +function PostgresStorage:getChatAdministratorsCount(chat) + local row = { + chat_id = chat.id, + administrator = chat_member_status["administrator"], + } + local query = interpolate( + 'SELECT count(*) FROM "chat_user" WHERE chat_id = {chat_id} AND status = {administrator}', row) local ok = self.pg:query(query) - if not ok or not ok[1] or not ok[1].title then - return false + if not ok or not ok[1] or not ok[1].count then + return 0 end - return ok[1].title + return ok[1].count +end + +function PostgresStorage:getChatAdministratorsList(chat) + local row = { + chat_id = chat.id, + creator = chat_member_status["creator"], + administrator = chat_member_status["administrator"], + } + local query = interpolate('SELECT user_id FROM "chat_user" WHERE chat_id = {chat_id}'.. + 'AND (status = {creator} OR status = {administrator})', row) + local ok = self.pg:query(query) + if not ok or not ok[1] or not ok[1].user_id then + return nil + end + local retval = {} + for _,v in pairs(ok) do + table.insert(retval, v.user_id) + end + return retval end function PostgresStorage:deleteChat(chat) local query = interpolate('DELETE FROM "chat" WHERE id = {id}', chat) self.pg:query(query) + return true +end + +local function isChatMemberPropertyOptional(status, k) + if status == "administrator" then + if k == "can_be_edited" + or k == "can_change_info" + or k == "can_delete_messages" + or k == "can_invite_users" + or k == "can_restrict_members" + or k == "can_promote_members" + or k == "can_pin_messages" then + -- or k == "can_post_messages" -- Channels only + -- or k == "can_edit_messages" then -- Channels only + return true + end + end + if status == "restricted" then + if k == "until_date" + or k == "can_send_messages" + or k == "can_send_media_messages" + or k == "can_send_other_messages" + or k == "can_add_web_page_previews" then + return true + end + end +end + +function PostgresStorage:cacheChatMember(member) + if member.chat.type ~= "supergroup" then -- don't cache private chats, channels, etc. + return + end + do + local ok = self:cacheChat(member.chat) + if not ok then + return false + end + end + do + local ok = self:cacheUser(member.user) + if not ok then + return false + end + end + if not rawget(member, "status") then + log.warn("Tried to cache member without status {chat_id}, {user_id}", { + chat_id = member.chat.id, + user_id = member.user.id, + }) + return false + end + local row = { + chat_id = member.chat.id, + user_id = member.user.id, + status = chat_member_status[member.status], + } + local insert = 'INSERT INTO "chat_user" (chat_id, user_id, status' + local values = ") VALUES ({chat_id}, {user_id}, {status}" + local on_conflict = ") ON CONFLICT (chat_id, user_id) DO UPDATE SET status = {status}" + for k, v in pairs(member) do + if isChatMemberPropertyOptional(member.status, k) then + row[k] = v + insert = insert..", "..k + values = values..", {"..k.."}" + on_conflict = on_conflict..", "..k.." = {"..k.."}" + end + end + local query = interpolate(insert..values..on_conflict, row) + local ok, err = self.pg:query(query) + if not ok then + log.err("Query {query} failed: {err}", {query=query, err=err}) + end + return true +end + +function PostgresStorage:wipeAdmins(chat) + local row = { + chat_id = chat.id, + member = chat_member_status["member"], + administrator = chat_member_status["administrator"], + } + local set = 'UPDATE "chat_user" SET status = {member}' + local where = ' WHERE chat_id = {chat_id} AND status = {administrator}' + for k, v in pairs(admins_permissions) do + row[k] = v + set = set..", "..k.." = {"..k.."}" + end + local query = interpolate(set..where, row) + local ok, err = self.pg:query(query) + if not ok then + log.err("Query {query} failed: {err}", {query=query, err=err}) + return false + end + return true +end + +function PostgresStorage:cacheAdmins(chat, list) + do + local ok = self:wipeAdmins(chat) + if not ok then + return false + end + end + for _, admin in pairs(list) do + admin.chat = chat + do + local ok = self:cacheChatMember(admin) + if not ok then + return false + end + end + end + return true end function PostgresStorage:set_keepalive() @@ -343,21 +622,45 @@ function PostgresStorage:get_reused_times() -- luacheck: ignore 212 return "Unknown" end -function MixedStorage:cache_user(user) - local res, ok = pcall(function() return self.postgres_storage:cache_user(user) end) +function MixedStorage:cacheUser(user) + local res, ok = pcall(function() return self.postgres_storage:cacheUser(user) end) if not res or not ok then - self.redis_storage:cache_user(user) + self.redis_storage:cacheUser(user) end end -function MixedStorage:get_user_id(username) - local ok, id = pcall(function() return self.postgres_storage:get_user_id(username) end) +function MixedStorage:getUserId(username) + local ok, id = pcall(function() return self.postgres_storage:getUserId(username) end) if not ok or not id then - return self.redis_storage:get_user_id(username) + return self.redis_storage:getUserId(username) end return id end +function MixedStorage:getUserProperty(user, property) + local ok, retval = pcall(function() return self.postgres_storage:getUserProperty(user, property) end) + if not ok or not retval then + return self.redis_storage:getUserProperty(user, property) + end + return retval +end + +function MixedStorage:getChatProperty(chat, property) + local ok, retval = pcall(function() return self.postgres_storage:getChatProperty(chat, property) end) + if not ok or not retval then + return self.redis_storage:getChatProperty(chat, property) + end + return retval +end + +function MixedStorage:getChatMemberProperty(member, property) + local ok, retval = pcall(function() return self.postgres_storage:getChatMemberProperty(member, property) end) + if not ok or not retval then + return self.redis_storage:getChatMemberProperty(member, property) + end + return retval +end + function MixedStorage:cacheChat(chat) local res, ok = pcall(function() return self.postgres_storage:cacheChat(chat) end) if not res or not ok then @@ -365,10 +668,18 @@ function MixedStorage:cacheChat(chat) end end -function MixedStorage:getChatTitle(chat) - local ok, title = pcall(function() return self.postgres_storage:getChatTitle(chat) end) +function MixedStorage:getChatAdministratorsCount(chat) + local ok, title = pcall(function() return self.postgres_storage:getChatAdministratorsCount(chat) end) if not ok or not title then - return self.redis_storage:getChatTitle(chat) + return self.redis_storage:getChatAdministratorsCount(chat) + end + return title +end + +function MixedStorage:getChatAdministratorsList(chat) + local ok, title = pcall(function() return self.postgres_storage:getChatAdministratorsList(chat) end) + if not ok or not title then + return self.redis_storage:getChatAdministratorsList(chat) end return title end @@ -378,6 +689,17 @@ function MixedStorage:deleteChat(chat) self.redis_storage:deleteChat(chat) end +function MixedStorage:cacheChatMember(member) + pcall(function() return self.postgres_storage:cacheChatMember(member) end) +end + +function MixedStorage:cacheAdmins(chat, list) + local res, ok = pcall(function() return self.postgres_storage:cacheAdmins(chat, list) end) + if not res or not ok then + self.redis_storage:cacheAdmins(chat, list) + end +end + function MixedStorage:set_keepalive() pcall(function() return self.postgres_storage:set_keepalive() end) self.redis_storage:set_keepalive() diff --git a/lua/groupbutler/user.lua b/lua/groupbutler/user.lua index e9d43c968..142cd8483 100644 --- a/lua/groupbutler/user.lua +++ b/lua/groupbutler/user.lua @@ -1,3 +1,5 @@ +local log = require("groupbutler.logging") + local User = {} local function p(self) @@ -5,16 +7,94 @@ local function p(self) end function User:new(obj, private) + assert(obj.id or obj.username, "User: Missing obj.id or obj.username") + assert(private.api, "User: Missing private.api") assert(private.db, "User: Missing private.db") setmetatable(obj, { - __index = self, + __index = function(s, index) + if self[index] then + return self[index] + end + return s:getProperty(index) + end, __private = private, + __tostring = self.__tostring, }) + if not obj:checkId() then + return nil, "Username not found" + end return obj end +function User:checkId() + local username = rawget(self, "username") + if username + and username:byte(1) == string.byte("@") then + self.username = username:sub(2) + end + local id = rawget(self, "id") + if not id then + id = p(self).db:getUserId(self.username) + self.id = id + if not id then + return false -- No cached id for this username + end + local user = p(self).api:getChat(id) + if not user -- Api call failed + or not user.username then -- User removed their username + return true -- Assuming it's the same user + end + if self.username ~= user.username then -- Got a different user than expected + User:new(user, p(self)):cache() -- Update cache with the different user so this doesn't happen again + return false + end + end + return true +end + +function User:getProperty(index) + local property = rawget(self, index) + if property == nil then + property = p(self).db:getUserProperty(self, index) + if property == nil then + local ok = p(self).api:getChat(self.id) + if not ok then + log.warn("User: Failed to get {property} for {id}", { + property = index, + id = self.id, + }) + return nil + end + for k,v in pairs(ok) do + self[k] = v + end + self:cache() + property = rawget(self, index) + end + self[index] = property + end + return property +end + +function User:__tostring() + if self.first_name then + if self.last_name then + return self.first_name.." "..self.last_name + end + return self.first_name + end + if self.username then + return self.username + end + return self.id +end + function User:cache() - p(self).db:cache_user(self) + p(self).db:cacheUser(self) +end + +function User:getLink() + return ('%s'):format(self.id, tostring(self):escape_html()) end return User diff --git a/lua/groupbutler/utilities.lua b/lua/groupbutler/utilities.lua index afb886e08..63da5d3c6 100644 --- a/lua/groupbutler/utilities.lua +++ b/lua/groupbutler/utilities.lua @@ -2,6 +2,8 @@ local config = require "groupbutler.config" local api_u = require "telegram-bot-api.utilities" local log = require "groupbutler.logging" local null = require "groupbutler.null" +local User = require("groupbutler.user") +local ChatMember = require("groupbutler.chatmember") local http, HTTPS, ltn12, time_hires, sleep if ngx then http = require "resty.http" @@ -41,41 +43,6 @@ local function set_default(t, d) setmetatable(t, mt) end -function _M:banUser(chat_id, user_id, until_date) - local api = p(self).api - local api_err = p(self).api_err - local ok, err = api:kickChatMember(chat_id, user_id, until_date) --try to kick. "code" is already specific - if not ok then --if the user has been kicked, then... - return nil, api_err:trans(err) - end - return ok --return res and not the text -end - -function _M:kickUser(chat_id, user_id) - local api = p(self).api - local api_err = p(self).api_err - local ok, err = api:kickChatMember(chat_id, user_id) --try to kick - if not ok then --if the user has been kicked, then unban... - return nil, api_err:trans(err) - end - api:unbanChatMember(chat_id, user_id) - return ok -end - -function _M:muteUser(chat_id, user_id) - local api = p(self).api - local api_err = p(self).api_err - local ok, err = api:restrictChatMember{ - chat_id = chat_id, - user_id = user_id, - can_send_messages = false - } - if not ok then - return nil, api_err:trans(err) - end - return ok -end - -- Strings -- Escape markdown for Telegram. This function makes non-clickable usernames, @@ -127,12 +94,12 @@ end -- otherwise it processes all available placeholders. function _M:replaceholders(str, msg, ...) if msg.new_chat_member then - msg.from = msg.new_chat_member + msg.from.user = msg.new_chat_member elseif msg.left_chat_member then - msg.from = msg.left_chat_member + msg.from.user = msg.left_chat_member end - msg.chat.title = msg.chat.title and msg.chat.title or '-' + msg.from.chat.title = msg.from.chat.title and msg.from.chat.title or '-' local tail_arguments = {...} -- check that the second argument is a boolean and true @@ -141,24 +108,24 @@ function _M:replaceholders(str, msg, ...) local replace_map if non_escapable then replace_map = { - name = msg.from.first_name, - surname = msg.from.last_name and msg.from.last_name or '', - username = msg.from.username and '@'..msg.from.username or '-', - id = msg.from.id, - title = msg.chat.title, - rules = self:deeplink_constructor(msg.chat.id, "rules"), + name = msg.from.user.first_name, + surname = msg.from.user.last_name and msg.from.user.last_name or '', + username = msg.from.user.username and '@'..msg.from.user.username or '-', + id = msg.from.user.id, + title = msg.from.chat.title, + rules = self:deeplink_constructor(msg.from.chat.id, "rules"), } -- remove flag about escaping table.remove(tail_arguments, 1) else replace_map = { - name = msg.from.first_name:escape(), - surname = msg.from.last_name and msg.from.last_name:escape() or '', - username = msg.from.username and '@'..msg.from.username:escape() or '-', - userorname = msg.from.username and '@'..msg.from.username:escape() or msg.from.first_name:escape(), - id = msg.from.id, - title = msg.chat.title:escape(), - rules = self:deeplink_constructor(msg.chat.id, "rules"), + name = msg.from.user.first_name:escape(), + surname = msg.from.user.last_name and msg.from.user.last_name:escape() or '', + username = msg.from.user.username and '@'..msg.from.user.username:escape() or '-', + userorname = msg.from.user.username and '@'..msg.from.user.username:escape() or msg.from.user.first_name:escape(), + id = msg.from.user.id, + title = msg.from.chat.title:escape(), + rules = self:deeplink_constructor(msg.from.chat.id, "rules"), } end @@ -170,23 +137,6 @@ function _M:replaceholders(str, msg, ...) return str:gsub('$(%w+)', substitutions) end -function _M:is_allowed(_, chat_id, user_obj) -- action is not used anymore - return self:is_admin(chat_id, user_obj.id) -end - -function _M:can(chat_id, user_id, permission) - local red = p(self).red - if tonumber(red:get('cache:chat:'..chat_id..':owner')) == user_id then - return true - end - local set = ("cache:chat:%s:%s:permissions"):format(chat_id, user_id) - local set_admins = 'cache:chat:'..chat_id..':admins' - if red:exists(set_admins) == 0 then - self:cache_adminlist(chat_id) - end - return red:sismember(set, permission) == 1 -end - function _M:is_superadmin(user_id) -- luacheck: ignore 212 for i=1, #config.superadmins do if tonumber(user_id) == config.superadmins[i] then @@ -196,74 +146,14 @@ function _M:is_superadmin(user_id) -- luacheck: ignore 212 return false end --- Returns the admin status of the user. The first argument can be the message, --- then the function checks the rights of the sender in the incoming chat. -function _M:is_admin(chat_id, user_id) - local red = p(self).red - if type(chat_id) == 'table' then - local msg = chat_id - chat_id = msg.chat.id - user_id = msg.from.id - end - - local set = 'cache:chat:'..chat_id..':admins' - if red:exists(set) == 0 then - self:cache_adminlist(chat_id) - end - return red:sismember(set, user_id) ~= 0 -end - -function _M:is_owner(chat_id, user_id) - local red = p(self).red - if type(chat_id) == 'table' then - local msg = chat_id - chat_id = msg.chat.id - user_id = msg.from.id - end - - local hash = 'cache:chat:'..chat_id..':owner' - local owner_id - local res = true - repeat - owner_id = red:get(hash) - if owner_id == null then - res = self:cache_adminlist(chat_id) - end - until owner_id ~= null or not res - - if owner_id then - if tonumber(owner_id) == tonumber(user_id) then - return true - end - end - - return false -end - -local adminspermissions = { - can_change_info = true, - can_delete_messages = true, - can_invite_users = true, - can_restrict_members = true, - canpin_messages = true, - canpromote_member = true -} - -local function set_creatorpermissions(self, chat_id, user_id) - local red = p(self).red - local set = ("cache:chat:%s:%s:permissions"):format(chat_id, user_id) - for k, _ in pairs(adminspermissions) do - red:sadd(set, k) - end -end - -function _M:cache_adminlist(chat_id) +function _M:cache_adminlist(chat) local api = p(self).api local red = p(self).red + local db = p(self).db local global_lock = "bot:getadmin_lock" - local chat_lock = "cache:chat:"..chat_id..":getadmin_lock" - local set = 'cache:chat:'..chat_id..':admins' + local chat_lock = "cache:chat:"..chat.id..":getadmin_lock" + local set = 'cache:chat:'..chat.id..':admins' if red:exists(global_lock) == 1 or red:exists(chat_lock) == 1 then @@ -271,15 +161,12 @@ function _M:cache_adminlist(chat_id) and (red:exists(global_lock) == 1 or red:exists(chat_lock) == 1) do sleep(0.1) end - if red:exists(set) == 1 then - return true, 0 -- Another concurrent request has just updated the adminlist - end end - red:setex(chat_lock, 5, "") - log.info('Saving the adminlist for: {chat_id}', {chat_id=chat_id}) + red:setex(global_lock, 5, "") + log.info('Saving the adminlist for: {chat_id}', {chat_id=chat.id}) self:metric_incr("api_getchatadministrators_count") - local ok, err = api:getChatAdministrators(chat_id) + local ok, err = api:getChatAdministrators(chat.id) if not ok then if err.retry_after then red:setex(global_lock, err.retry_after, "") @@ -289,45 +176,12 @@ function _M:cache_adminlist(chat_id) self:metric_incr("api_getchatadministrators_error_count") return false, err end - local cache_time = config.bot_settings.cache_time.adminlist - local setpermissions - red:del(set) - for _, admin in pairs(ok) do - if admin.status == 'creator' then - red:set('cache:chat:'..chat_id..':owner', admin.user.id) - set_creatorpermissions(self, chat_id, admin.user.id) - else - setpermissions = "cache:chat:"..chat_id..":"..admin.user.id..":permissions" - red:del(setpermissions) - for k, v in pairs(admin) do - if v and adminspermissions[k] then red:sadd(setpermissions, k) end - end - red:expire(setpermissions, cache_time) - end - - red:sadd(set, admin.user.id) - self:demote(chat_id, admin.user.id) - end - red:expire(set, cache_time) + db:cacheAdmins(chat, ok) return true, #ok or 0 end -function _M:get_cached_admins_list(chat_id, second_try) - local red = p(self).red - local hash = 'cache:chat:'..chat_id..':admins' - local list = red:smembers(hash) - if not list or not next(list) then - self:cache_adminlist(chat_id) - if not second_try then - return self:get_cached_admins_list(chat_id, true) - end - return false - end - return list -end - function _M:is_blocked_global(id) local red = p(self).red return red:sismember('bot:blocked', id) ~= 0 @@ -397,35 +251,6 @@ function _M:get_date(timestamp) -- luacheck: ignore 212 return os.date('%d/%m/%y', timestamp) end --- Resolves username. Returns ID of user if it was early stored in date base. --- Argument username must begin with symbol @ (commercial 'at') -function _M:resolve_user(username) - local api = p(self).api - assert(username:byte(1) == string.byte('@')) - username = username:lower() - - local stored_id = p(self).db:get_user_id(username) - if not stored_id then return false end - - local user_obj = api:getChat(stored_id) - if not user_obj then - return stored_id - end - if not user_obj.username then - return stored_id - end - - -- Users could change their username - if username ~= '@' .. user_obj.username:lower() then - p(self).db:cache_user(user_obj) - -- And return false because this user not the same that asked - return false - end - - assert(stored_id == user_obj.id) - return user_obj.id -end - function _M:reply_markup_from_text(text) -- luacheck: ignore 212 local clean_text = text local n = 0 @@ -452,53 +277,6 @@ function _M:demote(chat_id, user_id) return removed == 1 end -function _M:migrate_chat_info(old, new, on_request) - local api = p(self).api - local red = p(self).red - if not old or not new then - return false - end - - for hash_name, _ in pairs(config.chat_settings) do - local old_t = red:hgetall('chat:'..old..':'..hash_name) - if next(old_t) then - for key, val in pairs(old_t) do - red:hset('chat:'..new..':'..hash_name, key, val) - end - end - end - - for _, hash_name in pairs(config.chat_hashes) do - local old_t = red:hgetall('chat:'..old..':'..hash_name) - if next(old_t) then - for key, val in pairs(old_t) do - red:hset('chat:'..new..':'..hash_name, key, val) - end - end - end - - for i=1, #config.chat_sets do - local old_t = red:smembers('chat:'..old..':'..config.chat_sets[i]) - if next(old_t) then - red:sadd('chat:'..new..':'..config.chat_sets[i], unpack(old_t)) - end - end - - if on_request then - api:send_message(new, "Should be done") - end -end - --- Return user mention for output a text -function _M:getname_final(user) - return self:getname_link(user) or ''..user.first_name:escape_html()..'' -end - --- Return link to user profile or false, if they don't have login -function _M:getname_link(user) -- luacheck: ignore 212 - return ('%s'):format('tg://user?id='..user.id, user.first_name:escape_html()) -end - function _M:bash(str) -- luacheck: ignore 212 local cmd = io.popen(str) local result = cmd:read('*all') @@ -512,8 +290,7 @@ function _M:telegram_file_link(res) -- luacheck: ignore 212 end function _M:is_silentmode_on(chat_id) - local red = p(self).red - return red:hget("chat:"..chat_id..":settings", "Silent") == "on" + return p(self).db:get_chat_setting(chat_id, "Silent") end function _M:getRules(chat_id) @@ -527,42 +304,39 @@ function _M:getRules(chat_id) return rules end -function _M:getAdminlist(chat_id) - local api = p(self).api +function _M:getAdminlist(chat) local i18n = p(self).i18n - local list, code = self:get_cached_admins_list(chat_id) + local db = p(self).db + local list = db:getChatAdministratorsList(chat) if not list then - return false, code + return false end - local creator = '' - local adminlist = '' + local creator = "" + local adminlist = "" local count = 1 for _, user_id in pairs(list) do - local s = ' ├ ' - -- TODO: Cache admin names nad usernames - local admin = api:getChatMember(chat_id, user_id) - if admin and admin.status == 'administrator' then - local name = admin.user.first_name - if admin.user.username then - name = ('%s'):format(admin.user.username, name:escape_html()) - else - name = name:escape_html() + local s = " ├ " + local admin = ChatMember:new({ + chat = chat, + user = User:new({id=user_id}, p(self)), + }, p(self)) + if admin.status == "administrator" then + if count + 1 == #list then + s = " └ " end - if count + 1 == #list then s = ' └ ' end - adminlist = adminlist..s..name..'\n' + adminlist = adminlist..s..admin.user:getLink().."\n" count = count + 1 - elseif admin and admin.status == 'creator' then - creator = admin.user.first_name - if admin.user.username then - creator = ('%s'):format(admin.user.username, creator:escape_html()) - else - creator = creator:escape_html() - end + end + if admin.status == "creator" then + creator = admin.user:getLink() end end - if adminlist == '' then adminlist = '-' end - if creator == '' then creator = '-' end - + if adminlist == "" then + adminlist = "-" + end + if creator == "" then + creator = "-" + end return i18n("👤 Creator\n└ %s\n\n👥 Admins (%d)\n%s"):format(creator, #list - 1, adminlist) end @@ -716,27 +490,26 @@ function _M:sendStartMe(msg) local keyboard = { inline_keyboard = {{{text = i18n("Start me"), url = 'https://telegram.me/'..bot.username}}} } - api:sendMessage(msg.chat.id, i18n("please message me first so I can message you_"), 'Markdown', nil, nil, nil, + api:sendMessage(msg.from.chat.id, i18n("_Please message me first so I can message you_"), 'Markdown', nil, nil, nil, keyboard) end -function _M:initGroup(chat_id) +function _M:initGroup(chat) local red = p(self).red - local db = p(self).db for set, setting in pairs(config.chat_settings) do - local hash = 'chat:'..chat_id..':'..set + local hash = 'chat:'..chat.id..':'..set for field, value in pairs(setting) do red:hset(hash, field, value) end end - self:cache_adminlist(chat_id) --init admin cache + self:cache_adminlist(chat) --init admin cache --save group id - red:sadd('bot:groupsid', chat_id) + red:sadd('bot:groupsid', chat.id) --remove the group id from the list of dead groups - red:srem('bot:groupsid:removed', chat_id) - db:cacheChat({id=chat_id, type="supergroup"}) + red:srem('bot:groupsid:removed', chat.id) + chat:cache() end local function empty_modlist(self, chat_id) @@ -758,89 +531,23 @@ function _M:remGroup(chat_id) db:deleteChat({id=chat_id}) end -function _M:getnames_complete(msg) - local api = p(self).api - local i18n = p(self).i18n - local admin, kicked - - admin = self:getname_link(msg.from) - - if msg.reply then - kicked = self:getname_link(msg.reply.from) - elseif msg.text:match(config.cmd..'%w%w%w%w?%w?%s(@[%w_]+)%s?') then - local username = msg.text:match('%s(@[%w_]+)') - kicked = username - elseif msg.mention_id then - for _, entity in pairs(msg.entities) do - if entity.user then - kicked = self:getname_link(entity.user) - end - end - elseif msg.text:match(config.cmd..'%w%w%w%w?%w?%s(%d+)') then - local id = msg.text:match(config.cmd..'%w%w%w%w?%w?%s(%d+)') - local res = api:getChatMember(msg.chat.id, id) - if res then - kicked = self:getname_final(res.user) - end - end - - -- TODO: Actually fix this - if not kicked then kicked = i18n("Someone") end - if not admin then admin = i18n("Someone") end - return admin, kicked -end - -function _M:get_user_id(msg, blocks) - local i18n = p(self).i18n - --if no user id: returns false and the msg id of the translation for the problem - if not msg.reply and not blocks[2] then - return false, i18n("Reply to a user or mention them") - end - - if msg.reply then - if msg.reply.new_chat_member then - msg.reply.from = msg.reply.new_chat_member - end - return msg.reply.from.id - end - - if blocks[2]:byte(1) == string.byte("@") then - local id = self:resolve_user(blocks[2]) - if id then - return id - end - end - - if msg.mention_id then - return msg.mention_id - end - - local id = blocks[2]:match("%d+") - if id then - return id - end - - return false, i18n([[I've never seen this user before. -This command works by reply, username, user ID or text mention. -If you're using it by username and want to teach me who the user is, forward me one of their messages]]) -end - function _M:logEvent(event, msg, extra) local api = p(self).api local bot = p(self).bot local red = p(self).red local i18n = p(self).i18n - local log_id = red:hget('bot:chatlogs', msg.chat.id) + local log_id = red:hget('bot:chatlogs', msg.from.chat.id) -- self:dump(extra) if not log_id or log_id == null then return end - local is_loggable = red:hget('chat:'..msg.chat.id..':tolog', event) + local is_loggable = red:hget('chat:'..msg.from.chat.id..':tolog', event) if not is_loggable == 'yes' then return end local text, reply_markup - local chat_info = i18n("Chat: %s [#chat%d]"):format(msg.chat.title:escape_html(), msg.chat.id * -1) - local member = ("%s [@%s] [#id%d]"):format(msg.from.first_name:escape_html(), msg.from.username or '-', msg.from.id) + local chat_info = i18n("Chat: %s [#chat%d]"):format(msg.from.chat.title:escape_html(), msg.from.chat.id * -1) + local member = ("%s [@%s] [#id%d]"):format(msg.from.user.first_name:escape_html(), msg.from.user.username or '-', + msg.from.user.id) local log_event = { mediawarn = function() @@ -895,8 +602,8 @@ function _M:logEvent(event, msg, extra) local member2 = ("%s [@%s] [#id%d]"):format(msg.new_chat_member.first_name:escape_html(), msg.new_chat_member.username or '-', msg.new_chat_member.id) text = i18n('%s\n• %s\n• User: %s'):format('#NEW_MEMBER', chat_info, member2) - if extra then --extra == msg.from - text = text..i18n("\n• Added by: %s [#id%d]"):format(self:getname_final(extra), extra.id) + if extra then --extra == msg.from.user + text = text..i18n("\n• Added by: %s [#id%d]"):format(extra:getLink(), extra.id) end end, -- events that requires user + admin @@ -910,7 +617,9 @@ function _M:logEvent(event, msg, extra) --motivation: motivation text = i18n( '#%s\n• Admin: %s [#id%d]\n• %s\n• User: %s [#id%d]\n• Count: %d/%d' - ):format(event:upper(), extra.admin, msg.from.id, chat_info, extra.user, extra.user_id, extra.warns, extra.warnmax) + ):format(event:upper(), tostring(extra.admin), msg.from.user.id, chat_info, tostring(extra.user), extra.user_id, + extra.warns, extra.warnmax + ) end, nowarn = function() --WARNS REMOVED @@ -918,11 +627,11 @@ function _M:logEvent(event, msg, extra) --user name formatted: user --user id: user_id text = i18n("#%s\n• Admin: %s [#id%s]\n• %s\n• User: %s [#id%s]"):format( - 'WARNS_RESET', extra.admin, msg.from.id, chat_info, extra.user, tostring(extra.user_id)) + 'WARNS_RESET', extra.admin, msg.from.user.id, chat_info, extra.user, extra.user_id) end, block = function() -- or unblock text = i18n('#%s\n• Admin: %s [#id%s]\n• %s\n' - ):format(event:upper(), self:getname_final(msg.from), msg.from.id, chat_info) + ):format(event:upper(), msg.from.user, msg.from.user.id, chat_info) if extra.n then text = text..i18n('• Users involved: %d'):format(extra.n) elseif extra.user then @@ -939,7 +648,8 @@ function _M:logEvent(event, msg, extra) --motivation: motivation text = i18n( '#%s\n• Admin: %s [#id%s]\n• %s\n• User: %s [#id%s]\n• Duration: %d days, %d hours' - ):format(event:upper(), extra.admin, msg.from.id, chat_info, extra.user, tostring(extra.user_id), extra.d, extra.h) + ):format(event:upper(), tostring(extra.admin), msg.from.user.id, chat_info, tostring(extra.user), + extra.user_id, extra.d, extra.h) end, ban = function() -- or kick or unban --BAN OR KICK OR UNBAN @@ -948,7 +658,7 @@ function _M:logEvent(event, msg, extra) --user id: user_id --motivation: motivation text = i18n('#%s\n• Admin: %s [#id%s]\n• %s\n• User: %s [#id%s]'):format( - event:upper(), extra.admin, msg.from.id, chat_info, extra.user, tostring(extra.user_id)) + event:upper(), tostring(extra.admin), msg.from.user.id, chat_info, tostring(extra.user), extra.user_id) end, } set_default(log_event, function() text = i18n('#%s\n• %s\n• By: %s'):format(event:upper(), chat_info, member) @@ -965,23 +675,24 @@ function _M:logEvent(event, msg, extra) reply_markup = { inline_keyboard = {{{ text = i18n("Unban"), - callback_data = ("logcb:un%s:%d:%d"):format(event, extra.user_id, msg.chat.id) + callback_data = ("logcb:un%s:%d:%d"):format(event, extra.user_id, msg.from.chat.id) }}} } end if extra then - if extra.hammered then + if rawget(extra, "hammered") then text = text..i18n("\n• Action: #%s"):format(extra.hammered:upper()) end - if extra.motivation then + if rawget(extra, "motivation") then text = text..i18n('\n• Reason: %s'):format(extra.motivation:escape_html()) end end - if msg.chat.username then - text = text.. - ('\n• %s'):format(msg.chat.username, msg.message_id, i18n('Go to the message')) + if msg.from.chat.username then + text = text..('\n• %s'):format( + msg.from.chat.username, msg.message_id, i18n('Go to the message') + ) end local ok, err = api:send_message{ @@ -992,7 +703,7 @@ function _M:logEvent(event, msg, extra) reply_markup = reply_markup } if not ok and err.description:match("chat not found") then - red:hdel('bot:chatlogs', msg.chat.id) + red:hdel('bot:chatlogs', msg.from.chat.id) end end diff --git a/schema/2.sql b/schema/2.sql index 00633aca7..595802422 100644 --- a/schema/2.sql +++ b/schema/2.sql @@ -21,53 +21,6 @@ CREATE TABLE "chat" ( CONSTRAINT chat_pkey PRIMARY KEY (id) ); --- CREATE TABLE "chat_user" ( --- chat_id bigint NOT NULL REFERENCES "chat"(id), --- user_id integer NOT NULL REFERENCES "user"(id), --- status smallint NOT NULL, - --- created_at timestamptz DEFAULT now() NOT NULL, --- updated_at timestamptz DEFAULT now() NOT NULL, - --- CONSTRAINT chat_user_pkey PRIMARY KEY (chat_id, user_id) --- ); - --- CREATE TABLE "chat_user_admin" ( --- chat_id bigint NOT NULL REFERENCES "chat"(id), --- user_id integer NOT NULL REFERENCES "user"(id), - --- can_be_edited boolean DEFAULT false NOT NULL, --- can_change_info boolean DEFAULT false NOT NULL, --- can_delete_messages boolean DEFAULT false NOT NULL, --- can_invite_users boolean DEFAULT false NOT NULL, --- can_restrict_members boolean DEFAULT false NOT NULL, --- can_pin_messages boolean DEFAULT false NOT NULL, --- can_promote_members boolean DEFAULT false NOT NULL, --- can_post_messages boolean DEFAULT false NOT NULL, -- Channels only --- can_edit_messages boolean DEFAULT false NOT NULL, -- Channels only - --- created_at timestamptz DEFAULT now() NOT NULL, --- updated_at timestamptz DEFAULT now() NOT NULL, - --- CONSTRAINT chat_user_admin_pkey PRIMARY KEY (chat_id, user_id) --- ); - --- CREATE TABLE "chat_user_restricted" ( --- chat_id bigint NOT NULL REFERENCES "chat"(id), --- user_id integer NOT NULL REFERENCES "user"(id), - --- until_date timestamptz, -- Date when restrictions will be lifted for this user, if ever --- can_send_messages boolean DEFAULT true NOT NULL, --- can_send_media_messages boolean DEFAULT true NOT NULL, -- implies can_send_messages --- can_send_other_messages boolean DEFAULT true NOT NULL, -- implies can_send_media_messages --- can_add_web_page_previews boolean DEFAULT true NOT NULL -- implies can_send_media_messages - --- created_at timestamptz DEFAULT now() NOT NULL, --- updated_at timestamptz DEFAULT now() NOT NULL, - --- CONSTRAINT chat_user_restricted_pkey PRIMARY KEY (chat_id, user_id) --- ); - -- -- Triggers -- @@ -76,20 +29,3 @@ CREATE TRIGGER set_updated_at BEFORE UPDATE ON "chat" FOR EACH ROW EXECUTE PROCEDURE trigger_set_updated_at(); - --- CREATE TRIGGER set_updated_at --- BEFORE UPDATE ON "chat_user" --- FOR EACH ROW --- EXECUTE PROCEDURE trigger_set_updated_at(); - --- --- Foreign Keys --- - --- ALTER TABLE chat_user --- ADD CONSTRAINT chat_user_chat_id_fkey FOREIGN KEY (chat_id) --- REFERENCES "chat"(id) ON UPDATE CASCADE ON DELETE CASCADE; - --- ALTER TABLE chat_user --- ADD CONSTRAINT chat_user_user_id_fkey FOREIGN KEY (user_id) --- REFERENCES "user"(id) ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/schema/3.sql b/schema/3.sql new file mode 100644 index 000000000..3af2c55ce --- /dev/null +++ b/schema/3.sql @@ -0,0 +1,47 @@ +-- +-- Tables +-- + +CREATE TABLE "chat_user" ( + chat_id bigint NOT NULL REFERENCES "chat"(id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, + status smallint NOT NULL, + + -- status == administrator only + can_be_edited boolean DEFAULT false NOT NULL, + can_change_info boolean DEFAULT false NOT NULL, + can_delete_messages boolean DEFAULT false NOT NULL, + can_invite_users boolean DEFAULT false NOT NULL, + can_restrict_members boolean DEFAULT false NOT NULL, + can_pin_messages boolean DEFAULT false NOT NULL, + can_promote_members boolean DEFAULT false NOT NULL, + -- can_post_messages boolean DEFAULT false NOT NULL, -- Channels only + -- can_edit_messages boolean DEFAULT false NOT NULL, -- Channels only + + -- status == restricted only + until_date timestamptz, + can_send_messages boolean DEFAULT true NOT NULL, + can_send_media_messages boolean DEFAULT true NOT NULL, -- implies can_send_messages + can_send_other_messages boolean DEFAULT true NOT NULL, -- implies can_send_media_messages + can_add_web_page_previews boolean DEFAULT true NOT NULL, -- implies can_send_media_messages + + created_at timestamptz DEFAULT now() NOT NULL, + updated_at timestamptz DEFAULT now() NOT NULL, + + CONSTRAINT chat_user_pkey PRIMARY KEY (chat_id, user_id) +); + +-- +-- Triggers +-- + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON "chat_user" + FOR EACH ROW + EXECUTE PROCEDURE trigger_set_updated_at(); + +-- +-- Updates +-- + +ALTER TABLE "user" ALTER COLUMN is_bot DROP NOT NULL;