From 180cd6078aafc95756a98625190771fca3f43207 Mon Sep 17 00:00:00 2001 From: topkecleon Date: Sat, 20 Feb 2016 05:07:20 -0500 Subject: [PATCH] Added administration.lua. Adds self-hosted, single-realm administrative functions to otouto. Check out the README for a detailed description. Also added msg.from.name, which is msg.from.first_name + msg.from.last_name (if it exists), because I'm too lazy to build it each time. Fixed a crash-causing bug (ironically) in the handle_exception() function. --- .gitignore | 1 - README.md | 63 +++ bot.lua | 6 +- config.lua | 1 + launch.sh | 2 +- plugins/administration.lua | 1008 ++++++++++++++++++++++++++++++++++++ plugins/wikipedia.lua | 6 +- tg-install.sh | 12 + tg-launch.sh | 10 + utilities.lua | 2 + 10 files changed, 1105 insertions(+), 6 deletions(-) create mode 100644 plugins/administration.lua create mode 100755 tg-install.sh create mode 100755 tg-launch.sh diff --git a/.gitignore b/.gitignore index 7db5eb6..5d3b269 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -plugins/administration.lua plugins/CUSTOM.lua diff --git a/README.md b/README.md index f487e8d..8b0c987 100755 --- a/README.md +++ b/README.md @@ -60,6 +60,69 @@ Here is a list of most otouto plugins. * * * +## administration.lua +The administration plugin enables self-hosted, single-realm group administration, supporting both normal groups and supergroups. This works by sending TCP commands to an instance of tg running on the owner's account. + +To get started, run `./tg-install.sh`. Note that this script is written for Ubuntu/Debian. If you're running Arch (the only acceptable alternative), you'll have to do it yourself. If that is the case, note that otouto uses the "test" branch of tg, and the AUR package `telegram-cli-git` will not be sufficient, as it does not have support for supergroups yet. + +Once the installation is finished, enable `administration.lua` in your config file. You may have reason to change the default TCP port (4567); if that is the case, remember to change it in `tg-launch.sh` as well. Run `./tg-launch.sh` in a separate screen/tmux window. You'll have to enter your phone number and go through the login process the first time. The script is set to restart tg after two seconds, so you'll need to Ctrl+C after exiting. + +While tg is running, you may start/reload otouto with administration.lua enabled, and have access to a wide variety of administrative commands and automata. The administration "database" is stored in `administration.json`. To start using otouto to administrate a group (note that you must be the owner (or an administrator)), send `/gadd` to that group. For a list of commands, use `/ahelp`. Below I'll describe various functions now available to you. + +| Command | Function | Privilege | Internal? | +|---------|----------|-----------|-----------| +| /groups | Returns a list of administrated groups (except those flagged "unlisted". | 1 | N | +| /ahelp | Returns a list of administrative commands and their required privileges. | 1 | Y | +| /ops | Returns a list of moderators, governors, and administrators. | 1 | Y | +| /rules | Returns the rules of a group. | 1 | Y | +| /motd | Returns a group's "Message of the Day". | 1 | Y | +| /link | Returns the link for a group. | 1 | Y | +| /leave | Removes the user from the group. | 1 | Y | +| /kick | Removes the target from the group. | 2 | Y | +| /ban | Bans the target from the group. | 2 | Y | +| /unban | Unbans the target from the group. | 2 | Y | +| /setrules | Sets the rules for a group. | 3 | Y | +| /setmotd | Sets a group's "Message of the Day". | 3 | Y | +| /setlink | Sets a group's link. | 3 | Y | +| /flag | Returns a list of available flags and their settings, or toggles a flag. | 3 | Y | +| /mod | Promotes a user to a moderator. | 3 | Y | +| /demod | Demotes a moderator to a user. | 3 | Y | +| /gov | Promotes a user to a governor. | 4 | Y | +| /degov | Demotes a governor to a user. | 4 | Y | +| /hammer | Bans a user from all groups. | 4 | N | +| /unhammer | Removes a global ban. | 4 | N | +| /admin | Promotes a user to an administrator. | 5 | N | +| /deadmin | Demotes an administrator to a user. | 5 | N | +| /gadd | Adds a group to the administrative system. | 5 | N | +| /grem | Removes a group from the administrative system | 5 | Y | +| /broadcast | Broadcasts a message to all administrated groups. | 5 | N | + +Internal commands can only be run within an administrated group. + +###Description of Privileges + +| # | Title | Description | Scope | +|------|-------|-------------|-------| +| 0 | Banned | Cannot enter the group(s). | Either | +| 1 | User | Default rank. | Local | +| 2 | Moderator | Can kick/ban/unban users from a group. | Local | +| 3 | Governor | Can set rules/motd/link. Can promote/demote moderators. Can modify flags. | Local | +| 4 | Administrator | Can globally ban/unban users. Can promote/demote governors. | Global | +| 5 | Owner | Can add/remove groups. Can broadcast. Can promote/demote administrators. | Global | + +Obviously, each greater rank inherits the privileges of the lower, positive ranks. + +###Flags + +| # | Name | Description | +|---|------|-------------| +| 1 | unlisted | Removes a group from the /groups listing. | +| 2 | antisquig | Automatically removes users for posting Arabic script or RTL characters. | +| 3 | antisquig Strict | Automatically removes users whose names contain Arabic script or RTL characters. | +| 4 | antibot | Prevents bots from being added by non-moderators. | + +* * * + ##Liberbot Plugins Some plugins are only useful when the bot is used in a Liberbot group, like floodcontrol.lua and moderation.lua. diff --git a/bot.lua b/bot.lua index 577ef47..e4d43d2 100755 --- a/bot.lua +++ b/bot.lua @@ -54,12 +54,16 @@ on_msg_receive = function(msg) -- The fn run whenever a message is received. msg.chat.id_str = tostring(msg.chat.id) msg.from.id_str = tostring(msg.from.id) msg.text_lower = msg.text:lower() + msg.from.name = msg.from.first_name + if msg.from.last_name then + msg.from.name = msg.from.first_name .. ' ' .. msg.from.last_name + end local success, result = pcall(function() return v.action(msg) end) if not success then - sendReply(msg, 'An unexpected error occurred.') + sendReply(msg, 'Sorry, an unexpected error occurred.') handle_exception(result, msg.text) return end diff --git a/config.lua b/config.lua index 14d116c..8b097fb 100755 --- a/config.lua +++ b/config.lua @@ -12,6 +12,7 @@ return { time_offset = 0, lang = 'en', antisquig = false, + -- If you change this, make sure you also modify launch-tg.sh. cli_port = 4567, admin = 00000000, admin_name = 'John Smith', diff --git a/launch.sh b/launch.sh index f096845..29ce4d7 100755 --- a/launch.sh +++ b/launch.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh while true; do lua bot.lua diff --git a/plugins/administration.lua b/plugins/administration.lua new file mode 100644 index 0000000..c6d55b6 --- /dev/null +++ b/plugins/administration.lua @@ -0,0 +1,1008 @@ +admindata = load_data('administration.json') +if not admindata.global then + admindata.global = { + bans = {}, + admins = {} + } + save_data('administration.json', admindata) +end + +local sender = dofile('lua-tg/sender.lua') +tg = sender(localhost, config.cli_port) +local last_admin_cron = os.date('%M', os.time()) + +local flags = { + [1] = { + name = 'unlisted', + desc = 'Removes this group from the group listing.', + enabled = 'This group is no longer listed in /groups.', + disabled = 'This group is now listed in /groups.' + }, + [2] = { + name = 'antisquig', + desc = 'Automatically removes users who post Arabic script or RTL characters.', + enabled = 'Users will now be removed automatically for posting Arabic script and/or RTL characters.', + disabled = 'Users will no longer be removed automatically for posting Arabic script and/or RTL characters..', + kicked = 'You were kicked from GROUPNAME for posting Arabic script and/or RTL characters.' + }, + [3] = { + name = 'antisquig Strict', + desc = 'Automatically removes users whose names contain Arabic script or RTL characters.', + enabled = 'Users whose names contain Arabic script and/or RTL characters will now be removed automatically.', + disabled = 'Users whose names contain Arabic script and/or RTL characters will no longer be removed automatically.', + kicked = 'You were kicked from GROUPNAME for having a name which contains Arabic script and/or RTL characters.' + }, + [4] = { + name = 'antibot', + desc = 'Prevents the addition of bots by non-moderators. Only useful in non-supergroups.', + enabled = 'Non-moderators will no longer be able to add bots.', + disabled = 'Non-moderators will now be able to add bots.' + } +} + +local ranks = { + [0] = 'Banned', + [1] = 'Users', + [2] = 'Moderators', + [3] = 'Governors', + [4] = 'Administrators', + [5] = 'Owner' +} + +local get_rank = function(target, chat) + + target = tostring(target) + if chat then + chat = tostring(chat) + end + + if tonumber(target) == config.admin or tonumber(target) == bot.id then + return 5 + end + + if admindata.global.admins[target] then + return 4 + end + + if chat then + if admindata[chat].govs[target] then + return 3 + elseif admindata[chat].mods[target] then + return 2 + elseif admindata[chat].bans[target] then + return 0 + end + end + + if admindata.global.bans[target] then + return 0 + end + + return 1 + +end + +local get_target = function(msg) + + local target = {} + if msg.reply_to_message then + target.id = msg.reply_to_message.from.id + target.name = msg.reply_to_message.from.first_name + if msg.reply_to_message.from.last_name then + target.name = target.name .. ' ' .. msg.reply_to_message.from.last_name + end + else + target.name = 'User' + local input = get_word(msg.text, 2) + if not input then + target.err = 'Please provide a username or ID.' + else + target.id = resolve_username(input) + if target.id == nil then + target.err = 'Sorry, I do not recognize that username.' + elseif target.id == false then + target.err = 'Invalid ID or username.' + end + end + end + + if target.id then + target.id_str = tostring(target.id) + target.rank = get_rank(target.id, msg.chat.id) + end + + return target + +end + +local kick_user = function(target, chat) + + target = tonumber(target) + chat = tostring(chat) + + if admindata[chat].grouptype == 'group' then + tg:chat_del_user(tonumber(chat), target) + else + tg:channel_kick(chat, target) + end + +end + +local get_photo = function(chat) + + local filename = tg:load_chat_photo(chat) + if filename:find('FAIL') then + print('Error downloading photo for group ' .. chat .. '.') + return + end + filename = filename:gsub('Saved to ', '') + return filename + +end + +local commands = { + + { -- antisquig + triggers = { + '[\216-\219][\128-\191]', -- arabic + '‮' -- rtl + }, + + privilege = 0, + interior = true, + + action = function(msg) + if get_rank(msg.from.id, msg.chat.id) > 1 then + return true + end + if not admindata[msg.chat.id_str].flags[2] == true then + return true + end + kick_user(msg.from.id, msg.chat.id) + sendMessage(msg.from.id, flags[2].kicked:gsub('GROUPNAME', msg.chat.title)) + end + }, + + { -- generic + triggers = { + '' + }, + + privilege = 0, + interior = true, + + action = function(msg) + + local rank = get_rank(msg.from.id, msg.chat.id) + local group = admindata[msg.chat.id_str] + + -- banned + if rank == 0 then + kick_user(msg.from.id, msg.chat.id) + sendMessage(msg.from.id, 'Sorry, you are banned from ' .. msg.chat.title .. '.') + return + end + + if rank < 2 then + + -- antisquig Strict + if group.flags[3] == true then + if msg.from.name:match('[\216-\219][\128-\191]') then + kick_user(msg.from.id, msg.chat.id) + sendMessage(msg.from.id, flags[3].kicked:gsub('GROUPNAME', msg.chat.title)) + return + end + end + + -- antirtl + if group.flags[3] == true then + if msg.from.name:match('‮') then + kick_user(msg.from.id, msg.chat.id) + sendMessage(msg.from.id, flags[4].kicked:gsub('GROUPNAME', msg.chat.title)) + return + end + end + + end + + if msg.new_chat_participant then + + msg.new_chat_participant.name = msg.new_chat_participant.first_name + if msg.new_chat_participant.last_name then + msg.new_chat_participant.name = msg.new_chat_participant.first_name .. ' ' .. msg.new_chat_participant.last_name + end + + -- banned + if get_rank(msg.new_chat_participant.id, msg.chat.id) == 0 then + kick_user(msg.new_chat_participant.id, msg.chat.id) + sendMessage(msg.new_chat_participant.id, 'Sorry, you are banned from ' .. msg.chat.title .. '.') + return + end + + -- antisquig Strict + if group.flags[3] == true then + if msg.new_chat_participant.name:match('[\216-\219][\128-\191]') or msg.new_chat_participant.name:match('‮') then + kick_user(msg.new_chat_participant.id, msg.chat.id) + sendMessage(msg.new_chat_participant.id, flags[3].kicked:gsub('GROUPNAME', msg.chat.title)) + return + end + end + + -- antibot + if msg.new_chat_participant.username:match('bot$') then + if rank < 2 and group.flags[4] == true then + kick_user(msg.new_chat_participant.id, msg.chat.id) + return + end + else + local output + if group.link then + output = '*Welcome to* [' .. msg.chat.title .. '](' .. group.link .. ')*!*' + else + output = '*Welcome to* _' .. msg.chat.title .. '_*!*' + end + if group.motd then + output = output .. '\n\n*Message of the Day:*\n' .. group.motd + end + if group.rules then + output = output .. '\n\n*Rules:*\n' .. group.rules + end + if not sendMessage(msg.new_chat_participant.id, output, true, nil, true) then + sendMessage(msg.chat.id, output, true, nil, true) + end + return + end + + elseif msg.new_chat_title then + + if rank < 3 then + tg:rename_chat(msg.chat.id, group.name) + else + group.name = msg.new_chat_title + save_data('administration.json', admindata) + end + return + + elseif msg.new_chat_photo then + + if group.grouptype == 'group' then + if rank < 3 then + tg:chat_set_photo(msg.chat.id, group.photo) + else + group.photo = get_photo(msg.chat.id) + save_data('administration.json', admindata) + end + end + return + + elseif msg.delete_chat_photo then + + if group.grouptype == 'group' then + if rank < 3 then + tg:chat_set_photo(msg.chat.id, group.photo) + else + group.photo = nil + save_data('administration.json', admindata) + end + end + return + + end + + return true + + end + }, + + { -- groups + triggers = { + '^/groups[@'..bot.username..']*$', + '^/glist[@'..bot.username..']*$' + }, + + command = 'groups', + privilege = 1, + interior = false, + + action = function(msg) + local output = '' + for k,v in pairs(admindata) do + -- no "global" or unlisted groups + if tonumber(k) and not v.flags[1] then + if v.link then + output = output .. '• [' .. v.name .. '](' .. v.link .. ')\n' + else + output = output .. '• ' .. v.name .. '\n' + end + end + end + if output == '' then + output = 'There are currently no listed groups.' + else + output = '*Groups:*\n' .. output + end + sendMessage(msg.chat.id, output, true, nil, true) + end + }, + + { -- ahelp + triggers = { + '^/ahelp[@'..bot.username..']*$' + }, + + command = 'ahelp', + privilege = 1, + interior = true, + + action = function(msg) + sendMessage(msg.chat.id, help_text, true, nil, true) + end + }, + + { -- alist + triggers = { + '^/alist[@'..bot.username..']*$', + '^/ops[@'..bot.username..']*$', + '^/oplist[@'..bot.username..']*$' + }, + + command = 'ops', + privilege = 1, + interior = true, + + action = function(msg) + local modstring = '' + for k,v in pairs(admindata[msg.chat.id_str].mods) do + modstring = modstring .. '• ' .. v .. ' (' .. k .. ')\n' + end + if modstring ~= '' then + modstring = '*Moderators for* _' .. msg.chat.title .. '_ *:*\n' .. modstring + end + local govstring = '' + for k,v in pairs(admindata[msg.chat.id_str].govs) do + govstring = govstring .. '• ' .. v .. ' (' .. k .. ')\n' + end + if govstring ~= '' then + govstring = '*Governors for* _' .. msg.chat.title .. '_ *:*\n' .. govstring + end + local adminstring = '*Administrators:*\n• ' .. config.admin_name .. ' (' .. config.admin .. ')\n' + for k,v in pairs(admindata.global.admins) do + adminstring = adminstring .. '• ' .. v .. ' (' .. k .. ')\n' + end + local output = modstring .. govstring .. adminstring + sendMessage(msg.chat.id, output, true, nil, true) + end + + }, + + { -- rules + triggers = { + '^/rules[@'..bot.username..']*' + }, + + command = 'rules', + privilege = 1, + interior = true, + + action = function(msg) + local output = 'No rules have been set for ' .. msg.chat.title .. '.' + if admindata[msg.chat.id_str].rules then + output = '*Rules for* _' .. msg.chat.title .. '_ *:*\n' .. admindata[msg.chat.id_str].rules + end + sendMessage(msg.chat.id, output, true, nil, true) + end + }, + + { -- motd + triggers = { + '^/motd[@'..bot.username..']*', + '^/description[@'..bot.username..']*' + }, + + command = 'motd', + privilege = 1, + interior = true, + + action = function(msg) + local output = 'No MOTD has been set for ' .. msg.chat.title .. '.' + if admindata[msg.chat.id_str].motd then + output = '*MOTD for* _' .. msg.chat.title .. '_ *:*\n' .. admindata[msg.chat.id_str].motd + end + sendMessage(msg.chat.id, output, true, nil, true) + end + }, + + { -- link + triggers = { + '^/link[@'..bot.username..']*' + }, + + command = 'link', + privilege = 1, + interior = true, + + action = function(msg) + local output = 'No link has been set for ' .. msg.chat.title .. '.' + if admindata[msg.chat.id_str].link then + output = '[' .. msg.chat.title .. '](' .. admindata[msg.chat.id_str].link .. ')' + end + sendMessage(msg.chat.id, output, true, nil, true) + end + }, + + { -- kickme + triggers = { + '^/leave[@'..bot.username..']*', + '^/kickme[@'..bot.username..']*' + }, + + command = 'leave', + privilege = 1, + interior = true, + + action = function(msg) + if get_rank(msg.from.id) == 5 then + local output = 'I can\'t let you do that, ' .. msg.from.first_name .. '.' + sendMessage(msg.chat.id, output, true, nil, true) + elseif msg.chat.type == 'supergroup' then + local output = 'Leave this group manually or you will be unable to rejoin.' + sendMessage(msg.chat.id, output, true, nil, true) + else + kick_user(msg.from.id, msg.chat.id) + end + end + }, + + { -- kick + triggers = { + '^/kick[@'..bot.username..']*' + }, + + command = 'kick ', + privilege = 2, + interior = true, + + action = function(msg) + local target = get_target(msg) + if target.err then + sendReply(msg, target.err) + return + elseif target.rank > 1 then + sendReply(msg, target.name .. ' cannot be kicked: Too privileged.') + return + end + kick_user(target.id, msg.chat.id) + sendMessage(msg.chat.id, target.name .. ' has been kicked.') + end + }, + + { -- ban + triggers = { + '^/ban[@'..bot.username..']*' + }, + + command = 'ban ', + privilege = 2, + interior = true, + + action = function(msg) + local target = get_target(msg) + if target.err then + sendReply(msg, target.err) + return + end + if target.rank > 1 then + sendReply(msg, target.name .. ' cannot be banned: Too privileged.') + return + end + if admindata[msg.chat.id_str].bans[target.id_str] then + sendReply(msg, target.name .. ' is already banned.') + return + end + kick_user(target.id, msg.chat.id) + admindata[msg.chat.id_str].bans[target.id_str] = true + save_data('administration.json', admindata) + sendMessage(msg.chat.id, target.name .. ' has been banned.') + end + }, + + { -- unban + triggers = { + '^/unban[@'..bot.username..']*' + }, + + command = 'unban ', + privilege = 2, + interior = true, + + action = function(msg) + local target = get_target(msg) + if target.err then + sendReply(msg, target.err) + return + end + if not admindata[msg.chat.id_str].bans[target.id_str] then + if admindata.global.bans[target.id_str] then + sendReply(msg, target.name .. ' is banned globally.') + else + sendReply(msg, target.name .. ' is not banned.') + end + return + end + admindata[msg.chat.id_str].bans[target.id_str] = nil + save_data('administration.json', admindata) + sendMessage(msg.chat.id, target.name .. ' has been unbanned.') + end + }, + + { -- setrules + triggers = { + '^/setrules[@'..bot.username..']*' + }, + + command = 'setrules \\n \\[rule2] ...', + privilege = 3, + interior = true, + + action = function(msg) + local input = msg.text:match('^/setrules[@'..bot.username..']*(.+)') + if not input then + sendReply(msg, '/setrules [rule]\n\n[rule]\n...') + return + end + input = input:trim() .. '\n' + local output = '' + local i = 0 + for m in input:gmatch('(.-)\n') do + i = i + 1 + output = output .. '*' .. i .. '.* ' .. m:trim() .. '\n' + end + admindata[msg.chat.id_str].rules = output + save_data('administration.json', admindata) + output = '*Rules for* _' .. msg.chat.title .. '_ *:*\n' .. output + sendMessage(msg.chat.id, output, true, nil, true) + end + }, + + { -- setmotd + triggers = { + '^/setmotd[@'..bot.username..']*' + }, + + command = 'setmotd ', + privilege = 3, + interior = true, + + action = function(msg) + local input = msg.text:input() + if not input then + sendReply(msg, '/' .. command) + return + end + input = input:trim() + admindata[msg.chat.id_str].motd = input + save_data('administration.json', admindata) + local output = '*MOTD for* _' .. msg.chat.title .. '_ *:*\n' .. input + sendMessage(msg.chat.id, output, true, nil, true) + end + }, + + { -- setlink + triggers = { + '^/setlink[@'..bot.username..']*' + }, + + command = 'setlink ', + privilege = 3, + interior = true, + + action = function(msg) + local input = msg.text:input() + if not input then + sendReply(msg, '/' .. command) + return + end + admindata[msg.chat.id_str].link = input + save_data('administration.json', admindata) + local output = '[' .. msg.chat.title .. '](' .. input .. ')' + sendMessage(msg.chat.id, output, true, nil, true) + end + }, + + { -- flags + triggers = { + '^/flags?[@'..bot.username..']*' + }, + + command = 'flag ', + privilege = 3, + interior = true, + + action = function(msg) + local input = msg.text:input() + if input then + input = get_word(input, 1) + input = tonumber(input) + if not input or not flags[input] then input = false end + end + if not input then + local output = '*Flags for* _' .. msg.chat.title .. '_ *:*\n' + for i,v in ipairs(flags) do + local status = admindata[msg.chat.id_str].flags[i] or false + output = output .. '`[' .. i .. ']` *' .. v.name .. '* = `' .. tostring(status) .. '`\n• ' .. v.desc .. '\n' + end + sendMessage(msg.chat.id, output, true, nil, true) + return + end + local output + if admindata[msg.chat.id_str].flags[input] == true then + admindata[msg.chat.id_str].flags[input] = false + save_data('administration.json', admindata) + sendReply(msg, flags[input].disabled) + else + admindata[msg.chat.id_str].flags[input] = true + save_data('administration.json', admindata) + sendReply(msg, flags[input].enabled) + end + end + }, + + { -- mod + triggers = { + '^/mod[@'..bot.username..']*$' + }, + + command = 'mod ', + privilege = 3, + interior = true, + + action = function(msg) + if not msg.reply_to_message then + sendReply(msg, 'This command must be run via reply.') + return + end + local target = get_target(msg) + if target.rank > 1 then + sendReply(msg, target.name .. ' cannot be promoted: Already privileged.') + return + end + if admindata[msg.chat.id_str].grouptype == 'supergroup' then + tg:channel_set_admin(msg.chat.id, target, 1) + end + admindata[msg.chat.id_str].mods[target.id_str] = target.name + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' is now a moderator.') + end + }, + + { -- demod + triggers = { + '^/demod[@'..bot.username..']*' + }, + + command = 'demod ', + privilege = 3, + interior = true, + + action = function(msg) + local target = get_target(msg) + if target.err then + sendReply(msg, target.err) + return + end + if target.rank ~= 2 then + sendReply(msg, target.name .. ' is not a moderator.') + return + end + if admindata[msg.chat.id_str].grouptype == 'supergroup' then + tg:channel_set_admin(msg.chat.id, target, 0) + end + admindata[msg.chat.id_str].mods[target.id_str] = nil + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' is no longer a moderator.') + end + + }, + + { -- gov + triggers = { + '^/gov[@'..bot.username..']*$' + }, + + command = 'gov ', + privilege = 4, + interior = true, + + action = function(msg) + if not msg.reply_to_message then + sendReply(msg, 'This command must be run via reply.') + return + end + local target = get_target(msg) + if target.rank > 2 then + sendReply(msg, target.name .. ' cannot be promoted: Already privileged.') + return + elseif target.rank == 2 then + admindata[msg.chat.id_str].mods[target.id_str] = nil + end + if admindata[msg.chat.id_str].grouptype == 'supergroup' then + tg:channel_set_admin(msg.chat.id, target, 1) + end + admindata[msg.chat.id_str].govs[target.id_str] = target.name + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' is now a governor.') + end + }, + + { --degov + triggers = { + '^/degov[@'..bot.username..']*' + }, + + command = 'degov ', + privilege = 4, + interior = true, + + action = function(msg) + local target = get_target(msg) + if target.err then + sendReply(msg, target.err) + return + end + if target.rank ~= 3 then + sendReply(msg, target.name .. ' is not a governor.') + return + end + if admindata[msg.chat.id_str].grouptype == 'supergroup' then + tg:channel_set_admin(msg.chat.id, target, 0) + end + admindata[msg.chat.id_str].govs[target.id_str] = nil + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' is no longer a governor.') + end + }, + + { -- hammer + triggers = { + '^/hammer[@'..bot.username..']*', + '^/banall[@'..bot.username..']*' + }, + + command = 'hammer ', + privilege = 4, + interior = false, + + action = function(msg) + local target = get_target(msg) + if target.err then + sendReply(msg, target.err) + return + end + if target.rank > 3 then + sendReply(msg, target.name .. ' cannot be banned: Too privileged.') + return + end + if admindata.global.bans[target.id_str] then + sendReply(msg, target.name .. ' is already banned globally.') + return + end + for k,v in pairs(admindata) do + if tonumber(k) then + kick_user(target.id, k) + end + end + admindata.global.bans[target.id_str] = true + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' has been globally banned.') + end + }, + + { -- unhammer + triggers = { + '^/unhammer[@'..bot.username..']*', + '^/unbanall[@'..bot.username..']*' + }, + + command = 'unhammer ', + privilege = 4, + interior = false, + + action = function(msg) + local target = get_target(msg) + if target.err then + sendReply(msg, target.err) + return + end + if not admindata.global.bans[target.id_str] then + sendReply(msg, target.name .. ' is not banned globally.') + return + end + admindata.global.bans[target.id_str] = nil + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' has been globally unbanned.') + end + }, + + { -- admin + triggers = { + '^/admin[@'..bot.username..']*$' + }, + + command = 'admin ', + privilege = 5, + interior = false, + + action = function(msg) + if not msg.reply_to_message then + sendReply(msg, 'This command must be run via reply.') + return + end + local target = get_target(msg) + if target.rank > 3 then + sendReply(msg, target.name .. ' cannot be promoted: Already privileged.') + return + elseif target.rank == 2 then + admindata[msg.chat.id_str].mods[target.id_str] = nil + elseif target.rank == 3 then + admindata[msg.chat.id_str].govs[target.id_str] = nil + end + admindata.global.admins[target.id_str] = target.name + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' is now an administrator.') + end + }, + + { -- deadmin + triggers = { + '^/deadmin[@'..bot.username..']*' + }, + + command = 'deadmin ', + privilege = 5, + interior = false, + + action = function(msg) + local target = get_target(msg) + if target.rank ~= 4 then + sendReply(msg, target.name .. ' is not an administrator.') + return + end + admindata.global.admins[target.id_str] = nil + save_data('administration.json', admindata) + sendReply(msg, target.name .. ' is no longer an administrator.') + end + }, + + { -- gadd + triggers = { + '^/gadd[@'..bot.username..']*$' + }, + + command = 'gadd', + privilege = 5, + interior = false, + + action = function(msg) + if admindata[msg.chat.id_str] then + sendReply(msg, 'I am already administrating this group.') + return + end + admindata[msg.chat.id_str] = { + mods = {}, + govs = {}, + bans = {}, + flags = {}, + grouptype = msg.chat.type, + name = msg.chat.title, + founded = os.time() + } + if msg.chat.type == 'group' then + admindata[msg.chat.id_str].photo = get_photo(msg.chat.id) + admindata[msg.chat.id_str].link = tg:export_chat_link(msg.chat.id) + end + save_data('administration.json', admindata) + sendReply(msg, 'I am now administrating this group.') + end + }, + + { -- grem + triggers = { + '^/grem[@'..bot.username..']*', + '^/gremove[@'..bot.username..']*' + }, + + command = 'gremove \\[chat]', + privilege = 5, + interior = true, + + action = function(msg) + local input = msg.text:input() + if not input then + input = msg.chat.id_str + end + admindata[input] = nil + save_data('administration.json', admindata) + sendReply(msg, 'I am no longer administrating this group.') + end + }, + + { -- broadcast + triggers = { + '^/broadcast[@'..bot.username..']*' + }, + + command = 'broadcast ', + privilege = 5, + interior = false, + + action = function(msg) + local input = msg.text:input() + if not input then + sendReply(msg, 'Give me something to broadcast.') + return + end + input = '*Admin Broadcast:*\n' .. input + for k,v in pairs(admindata) do + if tonumber(k) then + sendMessage(k, input, true, nil, true) + end + end + end + } + +} + +local triggers = {} +for i,v in ipairs(commands) do + for key,val in pairs(v.triggers) do + table.insert(triggers, val) + end +end + +help_text = '' +for i = 1, 5 do + help_text = help_text .. '*' .. ranks[i] .. ':*\n' + for ind,val in pairs(commands) do + if val.privilege == i and val.command then + help_text = help_text .. '• /' .. val.command .. '\n' + end + end +end + +local action = function(msg) -- wee nesting + for i,v in ipairs(commands) do + for key,val in pairs(v.triggers) do + if msg.text_lower:match(val) then + if v.interior and not admindata[msg.chat.id_str] then + break + end + if msg.chat.type ~= 'private' and get_rank(msg.from.id, msg.chat.id) < v.privilege then + break + end + local res = v.action(msg) + if res ~= true then + return res + end + end + end + end + return true +end + +local cron = function() + if os.date('%M', os.time()) ~= last_admin_cron then + last_admin_cron = os.date('%M', os.time()) + tg = sender(localhost, config.cli_port) + end +end + +local command = 'groups' +local doc = '`Returns a list of administrated groups.\nUse /ahelp for more administrative commands.`' + +return { + action = action, + triggers = triggers, + cron = cron, + doc = doc, + command = command +} diff --git a/plugins/wikipedia.lua b/plugins/wikipedia.lua index a5e5954..b43e785 100755 --- a/plugins/wikipedia.lua +++ b/plugins/wikipedia.lua @@ -63,7 +63,7 @@ local action = function(msg) end text = text:gsub('', '') - local l = text:find('\n') + local l = text:find('

') if l then text = text:sub(1, l-1) end @@ -78,15 +78,15 @@ local action = function(msg) output = output .. '[Read more.](' .. url .. ')' end - sendMessage(msg.chat.id, output, true, nil, true) -- --[[ Comment the previous block and uncomment this one for full-message, -- "unlinked" link previews. -- Invisible zero-width, non-joiner. local output = '[​](' .. jdat.responseData.results[1].url .. ')' - sendMessage(msg.chat.id, output, false, nil, true) ]]-- + sendMessage(msg.chat.id, output, true, nil, true) + end return { diff --git a/tg-install.sh b/tg-install.sh new file mode 100755 index 0000000..98c5043 --- /dev/null +++ b/tg-install.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# Will download lua-tg and will download and build tg's "test" branch. +# Written for Ubuntu/Debian. If you're running Arch (the only acceptable +# alternative), figure it out yourself. + +sudo apt-get install libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev libjansson-dev libpython-dev make +git clone http://github.com/topkecleon/lua-tg +git clone http://github.com/vysheng/tg --recursive -b test +cd tg +./configure +make diff --git a/tg-launch.sh b/tg-launch.sh new file mode 100755 index 0000000..063f505 --- /dev/null +++ b/tg-launch.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# Launch tg listening on the default port (change this if you've changed it in +# config.lua), delete state file after stop, wait two seconds, and restart. + +while true; do + tg/bin/telegram-cli -P 4567 -E + rm ~/.telegram-cli/state + sleep 2s +done diff --git a/utilities.lua b/utilities.lua index e915b08..b694e5d 100755 --- a/utilities.lua +++ b/utilities.lua @@ -148,6 +148,8 @@ end handle_exception = function(err, message) + if not err then err = '' end + local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. bot.username .. ': ' .. err .. '\n' .. message .. '\n' if config.log_chat then