diff --git a/.editorconfig b/.editorconfig index a7715af..90ffed2 100755 --- a/.editorconfig +++ b/.editorconfig @@ -6,4 +6,5 @@ insert_final_newline = true [*.lua] charset = utf-8 -indent_style = tab +indent_style = space +indent_size = 4 diff --git a/README.md b/README.md index c759bb6..337b3e0 100644 --- a/README.md +++ b/README.md @@ -292,26 +292,26 @@ Additionally, any method can be called as a key in the `bindings` table (for exa ``` bindings.request( - self, - 'sendMessage', - { - chat_id = 987654321, - text = 'Quick brown fox.', - reply_to_message_id = 54321, - disable_web_page_preview = false, - parse_method = 'Markdown' - } + self, + 'sendMessage', + { + chat_id = 987654321, + text = 'Quick brown fox.', + reply_to_message_id = 54321, + disable_web_page_preview = false, + parse_method = 'Markdown' + } ) bindings.sendMessage( - self, - { - chat_id = 987654321, - text = 'Quick brown fox.', - reply_to_message_id = 54321, - disable_web_page_preview = false, - parse_method = 'Markdown' - } + self, + { + chat_id = 987654321, + text = 'Quick brown fox.', + reply_to_message_id = 54321, + disable_web_page_preview = false, + parse_method = 'Markdown' + } ) ``` @@ -342,20 +342,20 @@ Alone, the database will have this structure: ``` { - users = { - ["55994550"] = { - id = 55994550, - first_name = "Drew", - username = "topkecleon" - } - }, - userdata = { - ["55994550"] = { - nickname = "Worst coder ever", - lastfm = "topkecleon" - } - }, - version = "3.11" + users = { + ["55994550"] = { + id = 55994550, + first_name = "Drew", + username = "topkecleon" + } + }, + userdata = { + ["55994550"] = { + nickname = "Worst coder ever", + lastfm = "topkecleon" + } + }, + version = "3.11" } ``` diff --git a/config.lua b/config.lua index f18b5c6..ee06ec1 100644 --- a/config.lua +++ b/config.lua @@ -1,159 +1,159 @@ -- For details on configuration values, see README.md#configuration. return { - -- Your authorization token from the botfather. - bot_api_key = nil, - -- Your Telegram ID. - admin = nil, - -- Two-letter language code. - lang = 'en', - -- The channel, group, or user to send error reports to. - -- If this is not set, errors will be printed to the console. - log_chat = nil, - -- The port used to communicate with tg for administration.lua. - -- If you change this, make sure you also modify launch-tg.sh. - cli_port = 4567, - -- The symbol that starts a command. Usually noted as '/' in documentation. - cmd_pat = '/', - -- If drua is used, should a user be blocked when he's blacklisted? - drua_block_on_blacklist = false, - -- The filename of the database. If left nil, defaults to $username.db. - database_name = nil, - -- The block of text returned by /start and /about.. - about_text = [[ + -- Your authorization token from the botfather. + bot_api_key = nil, + -- Your Telegram ID. + admin = nil, + -- Two-letter language code. + lang = 'en', + -- The channel, group, or user to send error reports to. + -- If this is not set, errors will be printed to the console. + log_chat = nil, + -- The port used to communicate with tg for administration.lua. + -- If you change this, make sure you also modify launch-tg.sh. + cli_port = 4567, + -- The symbol that starts a command. Usually noted as '/' in documentation. + cmd_pat = '/', + -- If drua is used, should a user be blocked when he's blacklisted? + drua_block_on_blacklist = false, + -- The filename of the database. If left nil, defaults to $username.db. + database_name = nil, + -- The block of text returned by /start and /about.. + about_text = [[ I am otouto, the plugin-wielding, multipurpose Telegram bot. Send /help to get started. - ]], + ]], - errors = { -- Generic error messages. - generic = 'An unexpected error occurred.', - connection = 'Connection error.', - results = 'No results found.', - argument = 'Invalid argument.', - syntax = 'Invalid syntax.' - }, + errors = { -- Generic error messages. + generic = 'An unexpected error occurred.', + connection = 'Connection error.', + results = 'No results found.', + argument = 'Invalid argument.', + syntax = 'Invalid syntax.' + }, - -- https://datamarket.azure.com/dataset/bing/search - bing_api_key = nil, - -- http://console.developers.google.com - google_api_key = nil, - -- https://cse.google.com/cse - google_cse_key = nil, - -- http://openweathermap.org/appid - owm_api_key = nil, - -- http://last.fm/api - lastfm_api_key = nil, - -- http://api.biblia.com - biblia_api_key = nil, - -- http://thecatapi.com/docs.html - thecatapi_key = nil, - -- http://api.nasa.gov - nasa_api_key = nil, - -- http://tech.yandex.com/keys/get - yandex_key = nil, - -- Interval (in minutes) for hackernews.lua to update. - hackernews_interval = 60, - -- Whether hackernews.lua should update at load/reload. - hackernews_onstart = false, - -- Whether luarun should use serpent instead of dkjson for serialization. - luarun_serpent = false, + -- https://datamarket.azure.com/dataset/bing/search + bing_api_key = nil, + -- http://console.developers.google.com + google_api_key = nil, + -- https://cse.google.com/cse + google_cse_key = nil, + -- http://openweathermap.org/appid + owm_api_key = nil, + -- http://last.fm/api + lastfm_api_key = nil, + -- http://api.biblia.com + biblia_api_key = nil, + -- http://thecatapi.com/docs.html + thecatapi_key = nil, + -- http://api.nasa.gov + nasa_api_key = nil, + -- http://tech.yandex.com/keys/get + yandex_key = nil, + -- Interval (in minutes) for hackernews.lua to update. + hackernews_interval = 60, + -- Whether hackernews.lua should update at load/reload. + hackernews_onstart = false, + -- Whether luarun should use serpent instead of dkjson for serialization. + luarun_serpent = false, - remind = { - persist = true, - max_length = 1000, - max_duration = 526000, - max_reminders_group = 10, - max_reminders_private = 50 - }, + remind = { + persist = true, + max_length = 1000, + max_duration = 526000, + max_reminders_group = 10, + max_reminders_private = 50 + }, - chatter = { - cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=', - connection = 'I don\'t feel like talking right now.', - response = 'I don\'t know what to say to that.' - }, + chatter = { + cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=', + connection = 'I don\'t feel like talking right now.', + response = 'I don\'t know what to say to that.' + }, - greetings = { - ["Hello, #NAME."] = { - "hello", - "hey", - "hi", - "good morning", - "good day", - "good afternoon", - "good evening" - }, - ["Goodbye, #NAME."] = { - "good%-?bye", - "bye", - "later", - "see ya", - "good night" - }, - ["Welcome back, #NAME."] = { - "i'm home", - "i'm back" - }, - ["You're welcome, #NAME."] = { - "thanks", - "thank you" - } - }, + greetings = { + ["Hello, #NAME."] = { + "hello", + "hey", + "hi", + "good morning", + "good day", + "good afternoon", + "good evening" + }, + ["Goodbye, #NAME."] = { + "good%-?bye", + "bye", + "later", + "see ya", + "good night" + }, + ["Welcome back, #NAME."] = { + "i'm home", + "i'm back" + }, + ["You're welcome, #NAME."] = { + "thanks", + "thank you" + } + }, - reactions = { - ['shrug'] = '¯\\_(ツ)_/¯', - ['lenny'] = '( ͡° ͜ʖ ͡°)', - ['flip'] = '(╯°□°)╯︵ ┻━┻', - ['look'] = 'ಠ_ಠ', - ['shots'] = 'SHOTS FIRED', - ['facepalm'] = '(-‸ლ)' - }, + reactions = { + ['shrug'] = '¯\\_(ツ)_/¯', + ['lenny'] = '( ͡° ͜ʖ ͡°)', + ['flip'] = '(╯°□°)╯︵ ┻━┻', + ['look'] = 'ಠ_ಠ', + ['shots'] = 'SHOTS FIRED', + ['facepalm'] = '(-‸ლ)' + }, - administration = { - -- Whether moderators can set a group's message of the day. - moderator_setmotd = false, - -- Default antiflood values. - antiflood = { - text = 5, - voice = 5, - audio = 5, - contact = 5, - photo = 10, - video = 10, - location = 10, - document = 10, - sticker = 20 - } - }, + administration = { + -- Whether moderators can set a group's message of the day. + moderator_setmotd = false, + -- Default antiflood values. + antiflood = { + text = 5, + voice = 5, + audio = 5, + contact = 5, + photo = 10, + video = 10, + location = 10, + document = 10, + sticker = 20 + } + }, - plugins = { -- To enable a plugin, add its name to the list. - 'about', - 'blacklist', - 'calc', - 'cats', - 'commit', - 'control', - 'currency', - 'dice', - 'echo', - 'eightball', - 'gMaps', - 'hackernews', - 'imdb', - 'nick', - 'ping', - 'pun', - 'reddit', - 'shout', - 'slap', - 'time', - 'urbandictionary', - 'whoami', - 'wikipedia', - 'xkcd', - -- Put new plugins above this line. - 'help', - 'greetings' - } + plugins = { -- To enable a plugin, add its name to the list. + 'about', + 'blacklist', + 'calc', + 'cats', + 'commit', + 'control', + 'currency', + 'dice', + 'echo', + 'eightball', + 'gMaps', + 'hackernews', + 'imdb', + 'nick', + 'ping', + 'pun', + 'reddit', + 'shout', + 'slap', + 'time', + 'urbandictionary', + 'whoami', + 'wikipedia', + 'xkcd', + -- Put new plugins above this line. + 'help', + 'greetings' + } } diff --git a/launch.sh b/launch.sh index be8b691..6f6f5f2 100755 --- a/launch.sh +++ b/launch.sh @@ -1,7 +1,7 @@ #!/bin/sh while true; do - lua main.lua - echo 'otouto has stopped. ^C to exit.' - sleep 5s + lua main.lua + echo 'otouto has stopped. ^C to exit.' + sleep 5s done diff --git a/otouto/bindings.lua b/otouto/bindings.lua index c85c43d..4ae3f00 100644 --- a/otouto/bindings.lua +++ b/otouto/bindings.lua @@ -1,10 +1,10 @@ --[[ - bindings.lua (rev. 2016/05/28) - otouto's bindings for the Telegram bot API. - https://core.telegram.org/bots/api - Copyright 2016 topkecleon. Published under the AGPLv3. + bindings.lua (rev. 2016/05/28) + otouto's bindings for the Telegram bot API. + https://core.telegram.org/bots/api + Copyright 2016 topkecleon. Published under the AGPLv3. - See the "Bindings" section of README.md for usage information. + See the "Bindings" section of README.md for usage information. ]]-- local bindings = {} @@ -22,56 +22,56 @@ local MP_ENCODE = require('multipart-post').encode -- response with failure. Returns false and false with a connection error. -- To mimic old/normal behavior, it errs if used with an invalid method. function bindings:request(method, parameters, file) - parameters = parameters or {} - for k,v in pairs(parameters) do - parameters[k] = tostring(v) - end - if file and next(file) ~= nil then - local file_type, file_name = next(file) - local file_file = io.open(file_name, 'r') - local file_data = { - filename = file_name, - data = file_file:read('*a') - } - file_file:close() - parameters[file_type] = file_data - end - if next(parameters) == nil then - parameters = {''} - end - local response = {} - local body, boundary = MP_ENCODE(parameters) - local success, code = HTTPS.request{ - url = self.BASE_URL .. method, - method = 'POST', - headers = { - ["Content-Type"] = "multipart/form-data; boundary=" .. boundary, - ["Content-Length"] = #body, - }, - source = ltn12.source.string(body), - sink = ltn12.sink.table(response) - } - local data = table.concat(response) - if not success or success == 1 then - print(method .. ': Connection error. [' .. code .. ']') - return false, false - else - local result = JSON.decode(data) - if not result then - return false, false - elseif result.ok then - return result - else - assert(result.description ~= 'Method not found', method .. ': Method not found.') - return false, result - end - end + parameters = parameters or {} + for k,v in pairs(parameters) do + parameters[k] = tostring(v) + end + if file and next(file) ~= nil then + local file_type, file_name = next(file) + local file_file = io.open(file_name, 'r') + local file_data = { + filename = file_name, + data = file_file:read('*a') + } + file_file:close() + parameters[file_type] = file_data + end + if next(parameters) == nil then + parameters = {''} + end + local response = {} + local body, boundary = MP_ENCODE(parameters) + local success, code = HTTPS.request{ + url = self.BASE_URL .. method, + method = 'POST', + headers = { + ["Content-Type"] = "multipart/form-data; boundary=" .. boundary, + ["Content-Length"] = #body, + }, + source = ltn12.source.string(body), + sink = ltn12.sink.table(response) + } + local data = table.concat(response) + if not success or success == 1 then + print(method .. ': Connection error. [' .. code .. ']') + return false, false + else + local result = JSON.decode(data) + if not result then + return false, false + elseif result.ok then + return result + else + assert(result.description ~= 'Method not found', method .. ': Method not found.') + return false, result + end + end end function bindings.gen(_, key) - return function(self, params, file) - return bindings.request(self, key, params, file) - end + return function(self, params, file) + return bindings.request(self, key, params, file) + end end setmetatable(bindings, { __index = bindings.gen }) diff --git a/otouto/bot.lua b/otouto/bot.lua index 1f851e6..b37649c 100644 --- a/otouto/bot.lua +++ b/otouto/bot.lua @@ -7,190 +7,190 @@ bot.version = '3.13' -- Function to be run on start and reload. function bot:init(config) - bindings = require('otouto.bindings') - utilities = require('otouto.utilities') + bindings = require('otouto.bindings') + utilities = require('otouto.utilities') - assert( - config.bot_api_key, - 'You did not set your bot token in the config!' - ) - self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' + assert( + config.bot_api_key, + 'You did not set your bot token in the config!' + ) + self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' - -- Fetch bot information. Try until it succeeds. - repeat - print('Fetching bot information...') - self.info = bindings.getMe(self) - until self.info - self.info = self.info.result + -- Fetch bot information. Try until it succeeds. + repeat + print('Fetching bot information...') + self.info = bindings.getMe(self) + until self.info + self.info = self.info.result - -- Load the "database"! ;) - self.database_name = config.database_name or self.info.username .. '.db' - if not self.database then - self.database = utilities.load_data(self.database_name) - end + -- Load the "database"! ;) + self.database_name = config.database_name or self.info.username .. '.db' + if not self.database then + self.database = utilities.load_data(self.database_name) + end - -- Migration code 1.12 -> 1.13 - -- Back to administration global ban list; copy over current blacklist. - if self.database.version ~= '3.13' then - if self.database.administration then - self.database.administration.globalbans = self.database.administration.globalbans or self.database.blacklist or {} - utilities.save_data(self.database_name, self.database) - self.database = utilities.load_data(self.database_name) - end - end - -- End migration code. + -- Migration code 1.12 -> 1.13 + -- Back to administration global ban list; copy over current blacklist. + if self.database.version ~= '3.13' then + if self.database.administration then + self.database.administration.globalbans = self.database.administration.globalbans or self.database.blacklist or {} + utilities.save_data(self.database_name, self.database) + self.database = utilities.load_data(self.database_name) + end + end + -- End migration code. - -- Table to cache user info (usernames, IDs, etc). - self.database.users = self.database.users or {} - -- Table to store userdata (nicknames, lastfm usernames, etc). - self.database.userdata = self.database.userdata or {} - -- Table to store the IDs of blacklisted users. - self.database.blacklist = self.database.blacklist or {} - -- Save the bot's version in the database to make migration simpler. - self.database.version = bot.version - -- Add updated bot info to the user info cache. - self.database.users[tostring(self.info.id)] = self.info + -- Table to cache user info (usernames, IDs, etc). + self.database.users = self.database.users or {} + -- Table to store userdata (nicknames, lastfm usernames, etc). + self.database.userdata = self.database.userdata or {} + -- Table to store the IDs of blacklisted users. + self.database.blacklist = self.database.blacklist or {} + -- Save the bot's version in the database to make migration simpler. + self.database.version = bot.version + -- Add updated bot info to the user info cache. + self.database.users[tostring(self.info.id)] = self.info - -- All plugins go into self.plugins. Plugins which accept forwarded messages - -- and messages from blacklisted users also go into self.panoptic_plugins. - self.plugins = {} - self.panoptic_plugins = {} - for _, pname in ipairs(config.plugins) do - local plugin = require('otouto.plugins.'..pname) - table.insert(self.plugins, plugin) - if plugin.init then plugin.init(self, config) end - if plugin.panoptic then table.insert(self.panoptic_plugins, plugin) end - if plugin.doc then plugin.doc = '```\n'..plugin.doc..'\n```' end - if not plugin.triggers then plugin.triggers = {} end - end + -- All plugins go into self.plugins. Plugins which accept forwarded messages + -- and messages from blacklisted users also go into self.panoptic_plugins. + self.plugins = {} + self.panoptic_plugins = {} + for _, pname in ipairs(config.plugins) do + local plugin = require('otouto.plugins.'..pname) + table.insert(self.plugins, plugin) + if plugin.init then plugin.init(self, config) end + if plugin.panoptic then table.insert(self.panoptic_plugins, plugin) end + if plugin.doc then plugin.doc = '```\n'..plugin.doc..'\n```' end + if not plugin.triggers then plugin.triggers = {} end + end - print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')') + print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')') - -- Set loop variables. - self.last_update = self.last_update or 0 -- Update offset. - self.last_cron = self.last_cron or os.date('%M') -- Last cron job. - self.last_database_save = self.last_database_save or os.date('%H') -- Last db save. - self.is_started = true + -- Set loop variables. + self.last_update = self.last_update or 0 -- Update offset. + self.last_cron = self.last_cron or os.date('%M') -- Last cron job. + self.last_database_save = self.last_database_save or os.date('%H') -- Last db save. + self.is_started = true end -- Function to be run on each new message. function bot:on_msg_receive(msg, config) - -- Do not process old messages. - if msg.date < os.time() - 5 then return end + -- Do not process old messages. + if msg.date < os.time() - 5 then return end - -- plugint is the array of plugins we'll check the message against. - -- If the message is forwarded or from a blacklisted user, the bot will only - -- check against panoptic plugins. - local plugint = self.plugins - local from_id_str = tostring(msg.from.id) + -- plugint is the array of plugins we'll check the message against. + -- If the message is forwarded or from a blacklisted user, the bot will only + -- check against panoptic plugins. + local plugint = self.plugins + local from_id_str = tostring(msg.from.id) - -- Cache user info for those involved. - self.database.users[from_id_str] = msg.from - if msg.reply_to_message then - self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from - elseif msg.forward_from then - -- Forwards only go to panoptic plugins. - plugint = self.panoptic_plugins - self.database.users[tostring(msg.forward_from.id)] = msg.forward_from - elseif msg.new_chat_member then - self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member - elseif msg.left_chat_member then - self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member - end + -- Cache user info for those involved. + self.database.users[from_id_str] = msg.from + if msg.reply_to_message then + self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from + elseif msg.forward_from then + -- Forwards only go to panoptic plugins. + plugint = self.panoptic_plugins + self.database.users[tostring(msg.forward_from.id)] = msg.forward_from + elseif msg.new_chat_member then + self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member + elseif msg.left_chat_member then + self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member + end - -- Messages from blacklisted users only go to panoptic plugins. - if self.database.blacklist[from_id_str] then - plugint = self.panoptic_plugins - end + -- Messages from blacklisted users only go to panoptic plugins. + if self.database.blacklist[from_id_str] then + plugint = self.panoptic_plugins + end - -- If no text, use captions. - msg.text = msg.text or msg.caption or '' - msg.text_lower = msg.text:lower() - if msg.reply_to_message then - msg.reply_to_message.text = msg.reply_to_message.text or msg.reply_to_message.caption or '' - end + -- If no text, use captions. + msg.text = msg.text or msg.caption or '' + msg.text_lower = msg.text:lower() + if msg.reply_to_message then + msg.reply_to_message.text = msg.reply_to_message.text or msg.reply_to_message.caption or '' + end - -- Support deep linking. - if msg.text:match('^'..config.cmd_pat..'start .+') then - msg.text = config.cmd_pat .. utilities.input(msg.text) - msg.text_lower = msg.text:lower() - end + -- Support deep linking. + if msg.text:match('^'..config.cmd_pat..'start .+') then + msg.text = config.cmd_pat .. utilities.input(msg.text) + msg.text_lower = msg.text:lower() + end - -- If the message is forwarded or comes from a blacklisted yser, + -- If the message is forwarded or comes from a blacklisted yser, - -- Do the thing. - for _, plugin in ipairs(plugint) do - for _, trigger in ipairs(plugin.triggers) do - if string.match(msg.text_lower, trigger) then - local success, result = pcall(function() - return plugin.action(self, msg, config) - end) - if not success then - -- If the plugin has an error message, send it. If it does - -- not, use the generic one specified in config. If it's set - -- to false, do nothing. - if plugin.error then - utilities.send_reply(self, msg, plugin.error) - elseif plugin.error == nil then - utilities.send_reply(self, msg, config.errors.generic) - end - utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) - msg = nil - return - -- Continue if the return value is true. - elseif result ~= true then - msg = nil - return - end - end - end - end - msg = nil + -- Do the thing. + for _, plugin in ipairs(plugint) do + for _, trigger in ipairs(plugin.triggers) do + if string.match(msg.text_lower, trigger) then + local success, result = pcall(function() + return plugin.action(self, msg, config) + end) + if not success then + -- If the plugin has an error message, send it. If it does + -- not, use the generic one specified in config. If it's set + -- to false, do nothing. + if plugin.error then + utilities.send_reply(self, msg, plugin.error) + elseif plugin.error == nil then + utilities.send_reply(self, msg, config.errors.generic) + end + utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) + msg = nil + return + -- Continue if the return value is true. + elseif result ~= true then + msg = nil + return + end + end + end + end + msg = nil end -- main function bot:run(config) - bot.init(self, config) - while self.is_started do - -- Update loop. - local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update + 1 } ) - if res then - -- Iterate over every new message. - for _,v in ipairs(res.result) do - self.last_update = v.update_id - if v.message then - bot.on_msg_receive(self, v.message, config) - end - end - else - print('Connection error while fetching updates.') - end + bot.init(self, config) + while self.is_started do + -- Update loop. + local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update + 1 } ) + if res then + -- Iterate over every new message. + for _,v in ipairs(res.result) do + self.last_update = v.update_id + if v.message then + bot.on_msg_receive(self, v.message, config) + end + end + else + print('Connection error while fetching updates.') + end - -- Run cron jobs every minute. - if self.last_cron ~= os.date('%M') then - self.last_cron = os.date('%M') - for i,v in ipairs(self.plugins) do - if v.cron then -- Call each plugin's cron function, if it has one. - local result, err = pcall(function() v.cron(self, config) end) - if not result then - utilities.handle_exception(self, err, 'CRON: ' .. i, config) - end - end - end - end + -- Run cron jobs every minute. + if self.last_cron ~= os.date('%M') then + self.last_cron = os.date('%M') + for i,v in ipairs(self.plugins) do + if v.cron then -- Call each plugin's cron function, if it has one. + local result, err = pcall(function() v.cron(self, config) end) + if not result then + utilities.handle_exception(self, err, 'CRON: ' .. i, config) + end + end + end + end - -- Save the "database" every hour. - if self.last_database_save ~= os.date('%H') then - self.last_database_save = os.date('%H') - utilities.save_data(self.database_name, self.database) - end - end - -- Save the database before exiting. - utilities.save_data(self.database_name, self.database) - print('Halted.') + -- Save the "database" every hour. + if self.last_database_save ~= os.date('%H') then + self.last_database_save = os.date('%H') + utilities.save_data(self.database_name, self.database) + end + end + -- Save the database before exiting. + utilities.save_data(self.database_name, self.database) + print('Halted.') end return bot diff --git a/otouto/drua-tg.lua b/otouto/drua-tg.lua index 3e83286..a9b5d0a 100644 --- a/otouto/drua-tg.lua +++ b/otouto/drua-tg.lua @@ -1,13 +1,13 @@ --[[ - drua-tg - Based on JuanPotato's lua-tg (https://github.com/juanpotato/lua-tg), - modified to work more naturally from an API bot. + drua-tg + Based on JuanPotato's lua-tg (https://github.com/juanpotato/lua-tg), + modified to work more naturally from an API bot. - Usage: - drua = require('drua-tg') - drua.IP = 'localhost' -- 'localhost' is default - drua.PORT = 4567 -- 4567 is default - drua.message(chat_id, text) + Usage: + drua = require('drua-tg') + drua.IP = 'localhost' -- 'localhost' is default + drua.PORT = 4567 -- 4567 is default + drua.message(chat_id, text) The MIT License (MIT) @@ -35,150 +35,150 @@ SOFTWARE. local SOCKET = require('socket') local comtab = { - add = { 'chat_add_user %s %s', 'channel_invite %s %s' }, - kick = { 'chat_del_user %s %s', 'channel_kick %s %s' }, - rename = { 'rename_chat %s "%s"', 'rename_channel %s "%s"' }, - link = { 'export_chat_link %s', 'export_channel_link %s' }, - photo_set = { 'chat_set_photo %s %s', 'channel_set_photo %s %s' }, - photo_get = { [0] = 'load_user_photo %s', 'load_chat_photo %s', 'load_channel_photo %s' }, - info = { [0] = 'user_info %s', 'chat_info %s', 'channel_info %s' } + add = { 'chat_add_user %s %s', 'channel_invite %s %s' }, + kick = { 'chat_del_user %s %s', 'channel_kick %s %s' }, + rename = { 'rename_chat %s "%s"', 'rename_channel %s "%s"' }, + link = { 'export_chat_link %s', 'export_channel_link %s' }, + photo_set = { 'chat_set_photo %s %s', 'channel_set_photo %s %s' }, + photo_get = { [0] = 'load_user_photo %s', 'load_chat_photo %s', 'load_channel_photo %s' }, + info = { [0] = 'user_info %s', 'chat_info %s', 'channel_info %s' } } local format_target = function(target) - target = tonumber(target) - if target < -1000000000000 then - target = 'channel#' .. math.abs(target) - 1000000000000 - return target, 2 - elseif target < 0 then - target = 'chat#' .. math.abs(target) - return target, 1 - else - target = 'user#' .. target - return target, 0 - end + target = tonumber(target) + if target < -1000000000000 then + target = 'channel#' .. math.abs(target) - 1000000000000 + return target, 2 + elseif target < 0 then + target = 'chat#' .. math.abs(target) + return target, 1 + else + target = 'user#' .. target + return target, 0 + end end local escape = function(text) - text = text:gsub('\\', '\\\\') - text = text:gsub('\n', '\\n') - text = text:gsub('\t', '\\t') - text = text:gsub('"', '\\"') - return text + text = text:gsub('\\', '\\\\') + text = text:gsub('\n', '\\n') + text = text:gsub('\t', '\\t') + text = text:gsub('"', '\\"') + return text end local drua = { - IP = 'localhost', - PORT = 4567 + IP = 'localhost', + PORT = 4567 } drua.send = function(command, do_receive) - local s = SOCKET.connect(drua.IP, drua.PORT) - assert(s, '\nUnable to connect to tg session.') - s:send(command..'\n') - local output - if do_receive then - output = string.match(s:receive('*l'), 'ANSWER (%d+)') - output = s:receive(tonumber(output)):gsub('\n$', '') - end - s:close() - return output + local s = SOCKET.connect(drua.IP, drua.PORT) + assert(s, '\nUnable to connect to tg session.') + s:send(command..'\n') + local output + if do_receive then + output = string.match(s:receive('*l'), 'ANSWER (%d+)') + output = s:receive(tonumber(output)):gsub('\n$', '') + end + s:close() + return output end drua.message = function(target, text) - target = format_target(target) - text = escape(text) - local command = 'msg %s "%s"' - command = command:format(target, text) - return drua.send(command) + target = format_target(target) + text = escape(text) + local command = 'msg %s "%s"' + command = command:format(target, text) + return drua.send(command) end drua.send_photo = function(target, photo) - target = format_target(target) - local command = 'send_photo %s %s' - command = command:format(target, photo) - return drua.send(command) + target = format_target(target) + local command = 'send_photo %s %s' + command = command:format(target, photo) + return drua.send(command) end drua.add_user = function(chat, target) - local a - chat, a = format_target(chat) - target = format_target(target) - local command = comtab.add[a]:format(chat, target) - return drua.send(command) + local a + chat, a = format_target(chat) + target = format_target(target) + local command = comtab.add[a]:format(chat, target) + return drua.send(command) end drua.kick_user = function(chat, target) - -- Get the group info so tg will recognize the target. - drua.get_info(chat) - local a - chat, a = format_target(chat) - target = format_target(target) - local command = comtab.kick[a]:format(chat, target) - return drua.send(command) + -- Get the group info so tg will recognize the target. + drua.get_info(chat) + local a + chat, a = format_target(chat) + target = format_target(target) + local command = comtab.kick[a]:format(chat, target) + return drua.send(command) end drua.rename_chat = function(chat, name) - local a - chat, a = format_target(chat) - local command = comtab.rename[a]:format(chat, name) - return drua.send(command) + local a + chat, a = format_target(chat) + local command = comtab.rename[a]:format(chat, name) + return drua.send(command) end drua.export_link = function(chat) - local a - chat, a = format_target(chat) - local command = comtab.link[a]:format(chat) - return drua.send(command, true) + local a + chat, a = format_target(chat) + local command = comtab.link[a]:format(chat) + return drua.send(command, true) end drua.get_photo = function(chat) - local a - chat, a = format_target(chat) - local command = comtab.photo_get[a]:format(chat) - local output = drua.send(command, true) - if output:match('FAIL') then - return false - else - return output:match('Saved to (.+)') - end + local a + chat, a = format_target(chat) + local command = comtab.photo_get[a]:format(chat) + local output = drua.send(command, true) + if output:match('FAIL') then + return false + else + return output:match('Saved to (.+)') + end end drua.set_photo = function(chat, photo) - local a - chat, a = format_target(chat) - local command = comtab.photo_set[a]:format(chat, photo) - return drua.send(command) + local a + chat, a = format_target(chat) + local command = comtab.photo_set[a]:format(chat, photo) + return drua.send(command) end drua.get_info = function(target) - local a - target, a = format_target(target) - local command = comtab.info[a]:format(target) - return drua.send(command, true) + local a + target, a = format_target(target) + local command = comtab.info[a]:format(target) + return drua.send(command, true) end drua.channel_set_admin = function(chat, user, rank) - chat = format_target(chat) - user = format_target(user) - local command = 'channel_set_admin %s %s %s' - command = command:format(chat, user, rank) - return drua.send(command) + chat = format_target(chat) + user = format_target(user) + local command = 'channel_set_admin %s %s %s' + command = command:format(chat, user, rank) + return drua.send(command) end drua.channel_set_about = function(chat, text) - chat = format_target(chat) - text = escape(text) - local command = 'channel_set_about %s "%s"' - command = command:format(chat, text) - return drua.send(command) + chat = format_target(chat) + text = escape(text) + local command = 'channel_set_about %s "%s"' + command = command:format(chat, text) + return drua.send(command) end drua.block = function(user) - return drua.send('block_user user#' .. user) + return drua.send('block_user user#' .. user) end drua.unblock = function(user) - return drua.send('unblock_user user#' .. user) + return drua.send('unblock_user user#' .. user) end return drua diff --git a/otouto/plugins/about.lua b/otouto/plugins/about.lua index 6088ace..3f453cb 100644 --- a/otouto/plugins/about.lua +++ b/otouto/plugins/about.lua @@ -7,13 +7,13 @@ about.command = 'about' about.doc = 'Returns information about the bot.' function about:init(config) - about.text = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.' - about.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('about'):t('start').table + about.text = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.' + about.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('about'):t('start').table end function about:action(msg, config) - utilities.send_message(self, msg.chat.id, about.text, true, nil, true) + utilities.send_message(self, msg.chat.id, about.text, true, nil, true) end return about diff --git a/otouto/plugins/administration.lua b/otouto/plugins/administration.lua index 092190c..82684ed 100644 --- a/otouto/plugins/administration.lua +++ b/otouto/plugins/administration.lua @@ -1,25 +1,25 @@ --[[ - administration.lua - Version 1.11 - Part of the otouto project. - © 2016 topkecleon - GNU General Public License, version 2 + administration.lua + Version 1.11 + Part of the otouto project. + © 2016 topkecleon + GNU General Public License, version 2 - This plugin provides self-hosted, single-realm group administration. - It requires tg (http://github.com/vysheng/tg) with supergroup support. - For more documentation, read the the manual (otou.to/rtfm). + This plugin provides self-hosted, single-realm group administration. + It requires tg (http://github.com/vysheng/tg) with supergroup support. + For more documentation, read the the manual (otou.to/rtfm). - Important notices about updates will be here! + Important notices about updates will be here! - 1.11 - Removed /kickme and /broadcast. Users should leave manually, and - announcements should be made via channel rather than spam. /setqotd now - handles forwarded messages correctly. /kick, /ban, /hammer, /mod, /admin - now support multiple arguments. Added get_targets function. No migration is - necessary. + 1.11 - Removed /kickme and /broadcast. Users should leave manually, and + announcements should be made via channel rather than spam. /setqotd now + handles forwarded messages correctly. /kick, /ban, /hammer, /mod, /admin + now support multiple arguments. Added get_targets function. No migration is + necessary. - 1.11.1 - Bugfixes. /hammer can now be used in PM. + 1.11.1 - Bugfixes. /hammer can now be used in PM. - 1.13 - Global banlist reinstated. Added default antiflood values to config. Bugfixes: Modding a user will no longer add him. Fixed kicks/bans in reply to join/leave notifications. + 1.13 - Global banlist reinstated. Added default antiflood values to config. Bugfixes: Modding a user will no longer add him. Fixed kicks/bans in reply to join/leave notifications. ]] local JSON = require('dkjson') @@ -30,1465 +30,1465 @@ local utilities = require('otouto.utilities') local administration = {} function administration:init(config) - -- Build the administration db if nonexistent. - if not self.database.administration then - self.database.administration = { - admins = {}, - groups = {}, - activity = {}, - autokick_timer = os.date('%d'), - globalbans = {} - } - end + -- Build the administration db if nonexistent. + if not self.database.administration then + self.database.administration = { + admins = {}, + groups = {}, + activity = {}, + autokick_timer = os.date('%d'), + globalbans = {} + } + end - administration.temp = { - help = {}, - flood = {} - } + administration.temp = { + help = {}, + flood = {} + } - drua.PORT = config.cli_port or 4567 + drua.PORT = config.cli_port or 4567 - administration.flags = administration.init_flags(config.cmd_pat) - administration.init_command(self, config) - administration.antiflood = config.administration.antiflood or { - text = 5, - voice = 5, - audio = 5, - contact = 5, - photo = 10, - video = 10, - location = 10, - document = 10, - sticker = 20 - } + administration.flags = administration.init_flags(config.cmd_pat) + administration.init_command(self, config) + administration.antiflood = config.administration.antiflood or { + text = 5, + voice = 5, + audio = 5, + contact = 5, + photo = 10, + video = 10, + location = 10, + document = 10, + sticker = 20 + } - administration.doc = 'Returns a list of administrated groups.\nUse '..config.cmd_pat..'ahelp for more administrative commands.' - administration.command = 'groups [query]' + administration.doc = 'Returns a list of administrated groups.\nUse '..config.cmd_pat..'ahelp for more administrative commands.' + administration.command = 'groups [query]' - -- In the worst case, don't send errors in reply to random messages. - administration.error = false - -- Accept forwarded messages and messages from blacklisted users. - administration.panoptic = true + -- In the worst case, don't send errors in reply to random messages. + administration.error = false + -- Accept forwarded messages and messages from blacklisted users. + administration.panoptic = true end function administration.init_flags(cmd_pat) return { - [1] = { - name = 'unlisted', - desc = 'Removes this group from the group listing.', - short = 'This group is unlisted.', - enabled = 'This group is no longer listed in '..cmd_pat..'groups.', - disabled = 'This group is now listed in '..cmd_pat..'groups.' - }, - [2] = { - name = 'antisquig', - desc = 'Automatically removes users who post Arabic script or RTL characters.', - short = 'This group does not allow 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 automatically kicked from GROUPNAME for posting Arabic script and/or RTL characters.' - }, - [3] = { - name = 'antisquig++', - desc = 'Automatically removes users whose names contain Arabic script or RTL characters.', - short = 'This group does not allow users whose names contain Arabic script or RTL characters.', - enabled = 'Users whose names contain Arabic script and/or RTL characters will now be removed automatically.', - disabled = 'Users whose names contain Arabic script and/or RTL characters will no longer be removed automatically.', - kicked = 'You were automatically 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.', - short = 'This group does not allow users to add bots.', - enabled = 'Non-moderators will no longer be able to add bots.', - disabled = 'Non-moderators will now be able to add bots.' - }, - [5] = { - name = 'antiflood', - desc = 'Prevents flooding by rate-limiting messages per user.', - short = 'This group automatically removes users who flood.', - enabled = 'Users will now be removed automatically for excessive messages. Use '..cmd_pat..'antiflood to configure limits.', - disabled = 'Users will no longer be removed automatically for excessive messages.', - kicked = 'You were automatically kicked from GROUPNAME for flooding.' - }, - [6] = { - name = 'antihammer', - desc = 'Allows globally banned users to enter this group. Note that users hammered in this group will also be banned locally.', - short = 'This group does not acknowledge global bans.', - enabled = 'This group will no longer remove users for being globally banned.', - disabled = 'This group will now remove users for being globally banned.' - } + [1] = { + name = 'unlisted', + desc = 'Removes this group from the group listing.', + short = 'This group is unlisted.', + enabled = 'This group is no longer listed in '..cmd_pat..'groups.', + disabled = 'This group is now listed in '..cmd_pat..'groups.' + }, + [2] = { + name = 'antisquig', + desc = 'Automatically removes users who post Arabic script or RTL characters.', + short = 'This group does not allow 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 automatically kicked from GROUPNAME for posting Arabic script and/or RTL characters.' + }, + [3] = { + name = 'antisquig++', + desc = 'Automatically removes users whose names contain Arabic script or RTL characters.', + short = 'This group does not allow users whose names contain Arabic script or RTL characters.', + enabled = 'Users whose names contain Arabic script and/or RTL characters will now be removed automatically.', + disabled = 'Users whose names contain Arabic script and/or RTL characters will no longer be removed automatically.', + kicked = 'You were automatically 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.', + short = 'This group does not allow users to add bots.', + enabled = 'Non-moderators will no longer be able to add bots.', + disabled = 'Non-moderators will now be able to add bots.' + }, + [5] = { + name = 'antiflood', + desc = 'Prevents flooding by rate-limiting messages per user.', + short = 'This group automatically removes users who flood.', + enabled = 'Users will now be removed automatically for excessive messages. Use '..cmd_pat..'antiflood to configure limits.', + disabled = 'Users will no longer be removed automatically for excessive messages.', + kicked = 'You were automatically kicked from GROUPNAME for flooding.' + }, + [6] = { + name = 'antihammer', + desc = 'Allows globally banned users to enter this group. Note that users hammered in this group will also be banned locally.', + short = 'This group does not acknowledge global bans.', + enabled = 'This group will no longer remove users for being globally banned.', + disabled = 'This group will now remove users for being globally banned.' + } } end administration.ranks = { - [0] = 'Banned', - [1] = 'Users', - [2] = 'Moderators', - [3] = 'Governors', - [4] = 'Administrators', - [5] = 'Owner' + [0] = 'Banned', + [1] = 'Users', + [2] = 'Moderators', + [3] = 'Governors', + [4] = 'Administrators', + [5] = 'Owner' } function administration:get_rank(user_id_str, chat_id_str, config) - user_id_str = tostring(user_id_str) - local user_id = tonumber(user_id_str) - chat_id_str = tostring(chat_id_str) + user_id_str = tostring(user_id_str) + local user_id = tonumber(user_id_str) + chat_id_str = tostring(chat_id_str) - -- Return 5 if the user_id_str is the bot or its owner. - if user_id == config.admin or user_id == self.info.id then - return 5 - end + -- Return 5 if the user_id_str is the bot or its owner. + if user_id == config.admin or user_id == self.info.id then + return 5 + end - -- Return 4 if the user_id_str is an administrator. - if self.database.administration.admins[user_id_str] then - return 4 - end + -- Return 4 if the user_id_str is an administrator. + if self.database.administration.admins[user_id_str] then + return 4 + end - if chat_id_str and self.database.administration.groups[chat_id_str] then - -- Return 3 if the user_id_str is the governor of the chat_id_str. - if self.database.administration.groups[chat_id_str].governor == user_id then - return 3 - -- Return 2 if the user_id_str is a moderator of the chat_id_str. - elseif self.database.administration.groups[chat_id_str].mods[user_id_str] then - return 2 - -- Return 0 if the user_id_str is banned from the chat_id_str. - elseif self.database.administration.groups[chat_id_str].bans[user_id_str] then - return 0 - -- Return 1 if antihammer is enabled. - elseif self.database.administration.groups[chat_id_str].flags[6] then - return 1 - end - end + if chat_id_str and self.database.administration.groups[chat_id_str] then + -- Return 3 if the user_id_str is the governor of the chat_id_str. + if self.database.administration.groups[chat_id_str].governor == user_id then + return 3 + -- Return 2 if the user_id_str is a moderator of the chat_id_str. + elseif self.database.administration.groups[chat_id_str].mods[user_id_str] then + return 2 + -- Return 0 if the user_id_str is banned from the chat_id_str. + elseif self.database.administration.groups[chat_id_str].bans[user_id_str] then + return 0 + -- Return 1 if antihammer is enabled. + elseif self.database.administration.groups[chat_id_str].flags[6] then + return 1 + end + end - -- Return 0 if the user_id_str is globally banned (and antihammer is not enabled). - if self.database.administration.globalbans[user_id_str] then - return 0 - end + -- Return 0 if the user_id_str is globally banned (and antihammer is not enabled). + if self.database.administration.globalbans[user_id_str] then + return 0 + end - -- Return 1 if the user_id_str is a regular user. - return 1 + -- Return 1 if the user_id_str is a regular user. + return 1 end -- Returns an array of "user" tables. function administration:get_targets(msg, config) - if msg.reply_to_message then - local d = msg.reply_to_message.new_chat_member or msg.reply_to_message.left_chat_member or msg.reply_to_message.from - local target = {} - for k,v in pairs(d) do - target[k] = v - end - target.name = utilities.build_name(target.first_name, target.last_name) - target.id_str = tostring(target.id) - target.rank = administration.get_rank(self, target.id, msg.chat.id, config) - return { target } - else - local input = utilities.input(msg.text) - if input then - local t = {} - for user in input:gmatch('%g+') do - if self.database.users[user] then - local target = {} - for k,v in pairs(self.database.users[user]) do - target[k] = v - end - target.name = utilities.build_name(target.first_name, target.last_name) - target.id_str = tostring(target.id) - target.rank = administration.get_rank(self, target.id, msg.chat.id, config) - table.insert(t, target) - elseif tonumber(user) then - local id = math.abs(tonumber(user)) - local target = { - id = id, - id_str = tostring(id), - name = 'Unknown ('..id..')', - rank = administration.get_rank(self, user, msg.chat.id, config) - } - table.insert(t, target) - elseif user:match('^@') then - local target = utilities.resolve_username(self, user) - if target then - target.rank = administration.get_rank(self, target.id, msg.chat.id, config) - target.id_str = tostring(target.id) - target.name = utilities.build_name(target.first_name, target.last_name) - table.insert(t, target) - else - table.insert(t, { err = 'Sorry, I do not recognize that username ('..user..').' }) - end - else - table.insert(t, { err = 'Invalid username or ID ('..user..').' }) - end - end - return t - else - return false - end - end + if msg.reply_to_message then + local d = msg.reply_to_message.new_chat_member or msg.reply_to_message.left_chat_member or msg.reply_to_message.from + local target = {} + for k,v in pairs(d) do + target[k] = v + end + target.name = utilities.build_name(target.first_name, target.last_name) + target.id_str = tostring(target.id) + target.rank = administration.get_rank(self, target.id, msg.chat.id, config) + return { target } + else + local input = utilities.input(msg.text) + if input then + local t = {} + for user in input:gmatch('%g+') do + if self.database.users[user] then + local target = {} + for k,v in pairs(self.database.users[user]) do + target[k] = v + end + target.name = utilities.build_name(target.first_name, target.last_name) + target.id_str = tostring(target.id) + target.rank = administration.get_rank(self, target.id, msg.chat.id, config) + table.insert(t, target) + elseif tonumber(user) then + local id = math.abs(tonumber(user)) + local target = { + id = id, + id_str = tostring(id), + name = 'Unknown ('..id..')', + rank = administration.get_rank(self, user, msg.chat.id, config) + } + table.insert(t, target) + elseif user:match('^@') then + local target = utilities.resolve_username(self, user) + if target then + target.rank = administration.get_rank(self, target.id, msg.chat.id, config) + target.id_str = tostring(target.id) + target.name = utilities.build_name(target.first_name, target.last_name) + table.insert(t, target) + else + table.insert(t, { err = 'Sorry, I do not recognize that username ('..user..').' }) + end + else + table.insert(t, { err = 'Invalid username or ID ('..user..').' }) + end + end + return t + else + return false + end + end end function administration:mod_format(id) - id = tostring(id) - local user = self.database.users[id] or { first_name = 'Unknown' } - local name = utilities.build_name(user.first_name, user.last_name) - name = utilities.md_escape(name) - local output = '• ' .. name .. ' `[' .. id .. ']`\n' - return output + id = tostring(id) + local user = self.database.users[id] or { first_name = 'Unknown' } + local name = utilities.build_name(user.first_name, user.last_name) + name = utilities.md_escape(name) + local output = '• ' .. name .. ' `[' .. id .. ']`\n' + return output end function administration:get_desc(chat_id, config) - local group = self.database.administration.groups[tostring(chat_id)] - local t = {} - if group.link then - table.insert(t, '*Welcome to* [' .. group.name .. '](' .. group.link .. ')*!*') - else - table.insert(t, '*Welcome to ' .. group.name .. '!*') - end - if group.motd then - table.insert(t, '*Message of the Day:*\n' .. group.motd) - end - if #group.rules > 0 then - local rulelist = '*Rules:*\n' - for i = 1, #group.rules do - rulelist = rulelist .. '*' .. i .. '.* ' .. group.rules[i] .. '\n' - end - table.insert(t, utilities.trim(rulelist)) - end - local flaglist = '' - for i = 1, #administration.flags do - if group.flags[i] then - flaglist = flaglist .. '• ' .. administration.flags[i].short .. '\n' - end - end - if flaglist ~= '' then - table.insert(t, '*Flags:*\n' .. utilities.trim(flaglist)) - end - if group.governor then - local gov = self.database.users[tostring(group.governor)] - local s - if gov then - s = utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' - else - s = 'Unknown `[' .. group.governor .. ']`' - end - table.insert(t, '*Governor:* ' .. s) - end - local modstring = '' - for k,_ in pairs(group.mods) do - modstring = modstring .. administration.mod_format(self, k) - end - if modstring ~= '' then - table.insert(t, '*Moderators:*\n' .. utilities.trim(modstring)) - end - table.insert(t, 'Run '..config.cmd_pat..'ahelp@' .. self.info.username .. ' for a list of commands.') - return table.concat(t, '\n\n') + local group = self.database.administration.groups[tostring(chat_id)] + local t = {} + if group.link then + table.insert(t, '*Welcome to* [' .. group.name .. '](' .. group.link .. ')*!*') + else + table.insert(t, '*Welcome to ' .. group.name .. '!*') + end + if group.motd then + table.insert(t, '*Message of the Day:*\n' .. group.motd) + end + if #group.rules > 0 then + local rulelist = '*Rules:*\n' + for i = 1, #group.rules do + rulelist = rulelist .. '*' .. i .. '.* ' .. group.rules[i] .. '\n' + end + table.insert(t, utilities.trim(rulelist)) + end + local flaglist = '' + for i = 1, #administration.flags do + if group.flags[i] then + flaglist = flaglist .. '• ' .. administration.flags[i].short .. '\n' + end + end + if flaglist ~= '' then + table.insert(t, '*Flags:*\n' .. utilities.trim(flaglist)) + end + if group.governor then + local gov = self.database.users[tostring(group.governor)] + local s + if gov then + s = utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' + else + s = 'Unknown `[' .. group.governor .. ']`' + end + table.insert(t, '*Governor:* ' .. s) + end + local modstring = '' + for k,_ in pairs(group.mods) do + modstring = modstring .. administration.mod_format(self, k) + end + if modstring ~= '' then + table.insert(t, '*Moderators:*\n' .. utilities.trim(modstring)) + end + table.insert(t, 'Run '..config.cmd_pat..'ahelp@' .. self.info.username .. ' for a list of commands.') + return table.concat(t, '\n\n') end function administration:update_desc(chat, config) - local group = self.database.administration.groups[tostring(chat)] - local desc = 'Welcome to ' .. group.name .. '!\n' - if group.motd then desc = desc .. group.motd .. '\n' end - if group.governor then - local gov = self.database.users[tostring(group.governor)] - desc = desc .. '\nGovernor: ' .. utilities.build_name(gov.first_name, gov.last_name) .. ' [' .. gov.id .. ']\n' - end - local s = '\n'..config.cmd_pat..'desc@' .. self.info.username .. ' for more information.' - desc = desc:sub(1, 250-s:len()) .. s - drua.channel_set_about(chat, desc) + local group = self.database.administration.groups[tostring(chat)] + local desc = 'Welcome to ' .. group.name .. '!\n' + if group.motd then desc = desc .. group.motd .. '\n' end + if group.governor then + local gov = self.database.users[tostring(group.governor)] + desc = desc .. '\nGovernor: ' .. utilities.build_name(gov.first_name, gov.last_name) .. ' [' .. gov.id .. ']\n' + end + local s = '\n'..config.cmd_pat..'desc@' .. self.info.username .. ' for more information.' + desc = desc:sub(1, 250-s:len()) .. s + drua.channel_set_about(chat, desc) end function administration:kick_user(chat, target, reason, config) - drua.kick_user(chat, target) - local victim = target - if self.database.users[tostring(target)] then - victim = utilities.build_name( - self.database.users[tostring(target)].first_name, - self.database.users[tostring(target)].last_name - ) .. ' [' .. victim .. ']' - end - local group = self.database.administration.groups[tostring(chat)].name - utilities.handle_exception(self, victim..' kicked from '..group, reason, config) + drua.kick_user(chat, target) + local victim = target + if self.database.users[tostring(target)] then + victim = utilities.build_name( + self.database.users[tostring(target)].first_name, + self.database.users[tostring(target)].last_name + ) .. ' [' .. victim .. ']' + end + local group = self.database.administration.groups[tostring(chat)].name + utilities.handle_exception(self, victim..' kicked from '..group, reason, config) end function administration.init_command(self_, config_) - administration.commands = { - - { -- generic, mostly autokicks - triggers = { '' }, - - privilege = 0, - interior = true, - - action = function(self, msg, group, config) - - local rank = administration.get_rank(self, msg.from.id, msg.chat.id, config) - local user = {} - local from_id_str = tostring(msg.from.id) - local chat_id_str = tostring(msg.chat.id) - - if rank < 2 then - local from_name = utilities.build_name(msg.from.first_name, msg.from.last_name) - - -- banned - if rank == 0 then - user.do_kick = true - user.dont_unban = true - user.reason = 'banned' - user.output = 'Sorry, you are banned from ' .. msg.chat.title .. '.' - elseif group.flags[2] and ( -- antisquig - msg.text:match(utilities.char.arabic) - or msg.text:match(utilities.char.rtl_override) - or msg.text:match(utilities.char.rtl_mark) - ) then - user.do_kick = true - user.reason = 'antisquig' - user.output = administration.flags[2].kicked:gsub('GROUPNAME', msg.chat.title) - elseif group.flags[3] and ( -- antisquig++ - from_name:match(utilities.char.arabic) - or from_name:match(utilities.char.rtl_override) - or from_name:match(utilities.char.rtl_mark) - ) then - user.do_kick = true - user.reason = 'antisquig++' - user.output = administration.flags[3].kicked:gsub('GROUPNAME', msg.chat.title) - end - - -- antiflood - if group.flags[5] then - if not group.antiflood then - group.antiflood = JSON.decode(JSON.encode(administration.antiflood)) - end - if not administration.temp.flood[chat_id_str] then - administration.temp.flood[chat_id_str] = {} - end - if not administration.temp.flood[chat_id_str][from_id_str] then - administration.temp.flood[chat_id_str][from_id_str] = 0 - end - if msg.sticker then -- Thanks Brazil for discarding switches. - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.sticker - elseif msg.photo then - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.photo - elseif msg.document then - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.document - elseif msg.audio then - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.audio - elseif msg.contact then - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.contact - elseif msg.video then - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.video - elseif msg.location then - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.location - elseif msg.voice then - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.voice - else - administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.text - end - if administration.temp.flood[chat_id_str][from_id_str] > 99 then - user.do_kick = true - user.reason = 'antiflood' - user.output = administration.flags[5].kicked:gsub('GROUPNAME', msg.chat.title) - administration.temp.flood[chat_id_str][from_id_str] = nil - end - end - - end - - local new_user = user - local new_rank = rank - - if msg.new_chat_member then - - -- I hate typing this out. - local noob = msg.new_chat_member - local noob_name = utilities.build_name(noob.first_name, noob.last_name) - - -- We'll make a new table for the new guy, unless he's also - -- the original guy. - if msg.new_chat_member.id ~= msg.from.id then - new_user = {} - new_rank = administration.get_rank(self,noob.id, msg.chat.id, config) - end - - if new_rank == 0 then - new_user.do_kick = true - new_user.dont_unban = true - new_user.reason = 'banned' - new_user.output = 'Sorry, you are banned from ' .. msg.chat.title .. '.' - elseif new_rank == 1 then - if group.flags[3] and ( -- antisquig++ - noob_name:match(utilities.char.arabic) - or noob_name:match(utilities.char.rtl_override) - or noob_name:match(utilities.char.rtl_mark) - ) then - new_user.do_kick = true - new_user.reason = 'antisquig++' - new_user.output = administration.flags[3].kicked:gsub('GROUPNAME', msg.chat.title) - elseif ( -- antibot - group.flags[4] - and noob.username - and noob.username:match('bot$') - and rank < 2 - ) then - new_user.do_kick = true - new_user.reason = 'antibot' - end - else - -- Make the new user a group admin if he's a mod or higher. - if msg.chat.type == 'supergroup' then - drua.channel_set_admin(msg.chat.id, msg.new_chat_member.id, 2) - end - end - - elseif msg.new_chat_title then - if rank < 3 then - drua.rename_chat(msg.chat.id, group.name) - else - group.name = msg.new_chat_title - if group.grouptype == 'supergroup' then - administration.update_desc(self, msg.chat.id, config) - end - end - elseif msg.new_chat_photo then - if group.grouptype == 'group' then - if rank < 3 then - drua.set_photo(msg.chat.id, group.photo) - else - group.photo = drua.get_photo(msg.chat.id) - end - else - group.photo = drua.get_photo(msg.chat.id) - end - elseif msg.delete_chat_photo then - if group.grouptype == 'group' then - if rank < 3 then - drua.set_photo(msg.chat.id, group.photo) - else - group.photo = nil - end - else - group.photo = nil - end - end - - if new_user ~= user and new_user.do_kick then - administration.kick_user(self, msg.chat.id, msg.new_chat_member.id, new_user.reason, config) - if new_user.output then - utilities.send_message(self, msg.new_chat_member.id, new_user.output) - end - if not new_user.dont_unban and msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = msg.from.id } ) - end - end - - if group.flags[5] and user.do_kick and not user.dont_unban then - if group.autokicks[from_id_str] then - group.autokicks[from_id_str] = group.autokicks[from_id_str] + 1 - else - group.autokicks[from_id_str] = 1 - end - if group.autokicks[from_id_str] >= group.autoban then - group.autokicks[from_id_str] = 0 - group.bans[from_id_str] = true - user.dont_unban = true - user.reason = 'antiflood autoban: ' .. user.reason - user.output = user.output .. '\nYou have been banned for being autokicked too many times.' - end - end - - if user.do_kick then - administration.kick_user(self, msg.chat.id, msg.from.id, user.reason, config) - if user.output then - utilities.send_message(self, msg.from.id, user.output) - end - if not user.dont_unban and msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = msg.from.id } ) - end - end - - if msg.new_chat_member and not new_user.do_kick then - local output = administration.get_desc(self, msg.chat.id, config) - utilities.send_message(self, msg.new_chat_member.id, output, true, nil, true) - end - - -- Last active time for group listing. - if msg.text:len() > 0 then - for i,v in pairs(self.database.administration.activity) do - if v == chat_id_str then - table.remove(self.database.administration.activity, i) - table.insert(self.database.administration.activity, 1, chat_id_str) - end - end - end - - return true - - end - }, - - { -- /groups - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('groups', true).table, - - command = 'groups \\[query]', - privilege = 1, - interior = false, - doc = 'Returns a list of groups matching the query, or a list of all administrated groups.', - - action = function(self, msg, _, config) - local input = utilities.input(msg.text) - local search_res = '' - local grouplist = '' - for _, chat_id_str in ipairs(self.database.administration.activity) do - local group = self.database.administration.groups[chat_id_str] - if (not group.flags[1]) and group.link then -- no unlisted or unlinked groups - grouplist = grouplist .. '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')\n' - if input and string.match(group.name:lower(), input:lower()) then - search_res = search_res .. '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')\n' - end - end - end - local output - if search_res ~= '' then - output = '*Groups matching* _' .. input .. '_ *:*\n' .. search_res - elseif grouplist ~= '' then - output = '*Groups:*\n' .. grouplist - else - output = 'There are currently no listed groups.' - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - }, - - { -- /ahelp - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ahelp', true).table, - - command = 'ahelp \\[command]', - privilege = 1, - interior = false, - doc = 'Returns a list of realm-related commands for your rank (in a private message), or command-specific help.', - - action = function(self, msg, group, config) - local rank = administration.get_rank(self, msg.from.id, msg.chat.id, config) - local input = utilities.get_word(msg.text_lower, 2) - if input then - input = input:gsub('^'..config.cmd_pat..'', '') - local doc - for _,action in ipairs(administration.commands) do - if action.keyword == input then - doc = ''..config.cmd_pat..'' .. action.command:gsub('\\','') .. '\n' .. action.doc - break - end - end - if doc then - local output = '*Help for* _' .. input .. '_ :\n```\n' .. doc .. '\n```' - utilities.send_message(self, msg.chat.id, output, true, nil, true) - else - local output = 'Sorry, there is no help for that command.\n'..config.cmd_pat..'ahelp@'..self.info.username - utilities.send_reply(self, msg, output) - end - else - local output = '*Commands for ' .. administration.ranks[rank] .. ':*\n' - for i = 1, rank do - for _, val in ipairs(administration.temp.help[i]) do - output = output .. '• ' .. config.cmd_pat .. val .. '\n' - end - end - output = output .. 'Arguments: \\[optional]' - if utilities.send_message(self, msg.from.id, output, true, nil, true) then - if msg.from.id ~= msg.chat.id then - utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') - end - else - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - end - end - }, - - { -- /ops - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ops'):t('oplist').table, - - command = 'ops', - privilege = 1, - interior = true, - doc = 'Returns a list of moderators and the governor for the group.', - - action = function(self, msg, group, config) - local modstring = '' - for k,_ in pairs(group.mods) do - modstring = modstring .. administration.mod_format(self, k) - end - if modstring ~= '' then - modstring = '*Moderators for ' .. msg.chat.title .. ':*\n' .. modstring - end - local govstring = '' - if group.governor then - local gov = self.database.users[tostring(group.governor)] - if gov then - govstring = '*Governor:* ' .. utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' - else - govstring = '*Governor:* Unknown `[' .. group.governor .. ']`' - end - end - local output = utilities.trim(modstring) ..'\n\n' .. utilities.trim(govstring) - if output == '\n\n' then - output = 'There are currently no moderators for this group.' - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - - }, - - { -- /desc - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('desc'):t('description').table, - - command = 'description', - privilege = 1, - interior = true, - doc = 'Returns a description of the group (in a private message), including its motd, rules, flags, governor, and moderators.', - - action = function(self, msg, group, config) - local output = administration.get_desc(self, msg.chat.id, config) - if utilities.send_message(self, msg.from.id, output, true, nil, true) then - if msg.from.id ~= msg.chat.id then - utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') - end - else - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - end - }, - - { -- /rules - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('rules?', true).table, - - command = 'rules \\[i]', - privilege = 1, - interior = true, - doc = 'Returns the group\'s list of rules, or a specific rule.', - - action = function(self, msg, group, config) - local output - local input = utilities.get_word(msg.text_lower, 2) - input = tonumber(input) - if #group.rules > 0 then - if input and group.rules[input] then - output = '*' .. input .. '.* ' .. group.rules[input] - else - output = '*Rules for ' .. msg.chat.title .. ':*\n' - for i,v in ipairs(group.rules) do - output = output .. '*' .. i .. '.* ' .. v .. '\n' - end - end - else - output = 'No rules have been set for ' .. msg.chat.title .. '.' - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - }, - - { -- /motd - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('motd'):t('qotd').table, - - command = 'motd', - privilege = 1, - interior = true, - doc = 'Returns the group\'s message of the day.', - - action = function(self, msg, group, config) - local output = 'No MOTD has been set for ' .. msg.chat.title .. '.' - if group.motd then - output = '*MOTD for ' .. msg.chat.title .. ':*\n' .. group.motd - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - }, - - { -- /link - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('link').table, - - command = 'link', - privilege = 1, - interior = true, - doc = 'Returns the group\'s link.', - - action = function(self, msg, group, config) - local output = 'No link has been set for ' .. msg.chat.title .. '.' - if group.link then - output = '[' .. msg.chat.title .. '](' .. group.link .. ')' - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - }, - - { -- /kick - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('kick', true).table, - - command = 'kick ', - privilege = 2, - interior = true, - doc = 'Removes a user from the group. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then - output = output .. target.name .. ' is too privileged to be kicked.\n' - else - administration.kick_user(self, msg.chat.id, target.id, 'kicked by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) - output = output .. target.name .. ' has been kicked.\n' - if msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) - end - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /ban - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ban', true).table, - - command = 'ban ', - privilege = 2, - interior = true, - doc = 'Bans a user from the group. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif group.bans[target.id_str] then - output = output .. target.name .. ' is already banned.\n' - elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then - output = output .. target.name .. ' is too privileged to be banned.\n' - else - administration.kick_user(self, msg.chat.id, target.id, 'banned by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) - output = output .. target.name .. ' has been banned.\n' - group.mods[target.id_str] = nil - group.bans[target.id_str] = true - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /unban - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('unban', true).table, - - command = 'unban ', - privilege = 2, - interior = true, - doc = 'Unbans a user from the group. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - else - if not group.bans[target.id_str] then - output = output .. target.name .. ' is not banned.\n' - else - output = output .. target.name .. ' has been unbanned.\n' - group.bans[target.id_str] = nil - end - if msg.chat.type == 'supergroup' then - bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) - end - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /setmotd - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setmotd', true):t('setqotd', true).table, - - command = 'setmotd ', - privilege = config_.administration.moderator_setmotd and 2 or 3, - interior = true, - doc = 'Sets the group\'s message of the day. Markdown is supported. Pass "--" to delete the message.', - - action = function(self, msg, group, config) - local input = utilities.input(msg.text) - local quoted = utilities.build_name(msg.from.first_name, msg.from.last_name) - if msg.reply_to_message and #msg.reply_to_message.text > 0 then - input = msg.reply_to_message.text - if msg.reply_to_message.forward_from then - quoted = utilities.build_name(msg.reply_to_message.forward_from.first_name, msg.reply_to_message.forward_from.last_name) - else - quoted = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) - end - end - if input then - if input == '--' or input == utilities.char.em_dash then - group.motd = nil - utilities.send_reply(self, msg, 'The MOTD has been cleared.') - else - if msg.text:match('^/setqotd') then - input = '_' .. utilities.md_escape(input) .. '_\n - ' .. utilities.md_escape(quoted) - end - group.motd = input - local output = '*MOTD for ' .. msg.chat.title .. ':*\n' .. input - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - if group.grouptype == 'supergroup' then - administration.update_desc(self, msg.chat.id, config) - end - else - utilities.send_reply(self, msg, 'Please specify the new message of the day.') - end - end - }, - - { -- /setrules - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setrules', true).table, - - command = 'setrules ', - privilege = 3, - interior = true, - doc = 'Sets the group\'s rules. Rules will be automatically numbered. Separate rules with a new line. Markdown is supported. Pass "--" to delete the rules.', - - action = function(self, msg, group, config) - local input = msg.text:match('^'..config.cmd_pat..'setrules[@'..self.info.username..']*(.+)') - if input == ' --' or input == ' ' .. utilities.char.em_dash then - group.rules = {} - utilities.send_reply(self, msg, 'The rules have been cleared.') - elseif input then - group.rules = {} - input = utilities.trim(input) .. '\n' - local output = '*Rules for ' .. msg.chat.title .. ':*\n' - local i = 1 - for l in input:gmatch('(.-)\n') do - output = output .. '*' .. i .. '.* ' .. l .. '\n' - i = i + 1 - table.insert(group.rules, utilities.trim(l)) - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - else - utilities.send_reply(self, msg, 'Please specify the new rules.') - end - end - }, - - { -- /changerule - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('changerule', true).table, - - command = 'changerule ', - privilege = 3, - interior = true, - doc = 'Changes a single rule. Pass "--" to delete the rule. If i is a number for which there is no rule, adds a rule by the next incremented number.', - - action = function(self, msg, group, config) - local input = utilities.input(msg.text) - local output = 'usage: `'..config.cmd_pat..'changerule `' - if input then - local rule_num = tonumber(input:match('^%d+')) - local new_rule = utilities.input(input) - if not rule_num then - output = 'Please specify which rule you want to change.' - elseif not new_rule then - output = 'Please specify the new rule.' - elseif new_rule == '--' or new_rule == utilities.char.em_dash then - if group.rules[rule_num] then - table.remove(group.rules, rule_num) - output = 'That rule has been deleted.' - else - output = 'There is no rule with that number.' - end - else - if not group.rules[rule_num] then - rule_num = #group.rules + 1 - end - group.rules[rule_num] = new_rule - output = '*' .. rule_num .. '*. ' .. new_rule - end - end - utilities.send_reply(self, msg, output, true) - end - }, - - { -- /setlink - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setlink', true).table, - - command = 'setlink ', - privilege = 3, - interior = true, - doc = 'Sets the group\'s join link. Pass "--" to regenerate the link.', - - action = function(self, msg, group, config) - local input = utilities.input(msg.text) - if input == '--' or input == utilities.char.em_dash then - group.link = drua.export_link(msg.chat.id) - utilities.send_reply(self, msg, 'The link has been regenerated.') - elseif input then - group.link = input - local output = '[' .. msg.chat.title .. '](' .. input .. ')' - utilities.send_message(self, msg.chat.id, output, true, nil, true) - else - utilities.send_reply(self, msg, 'Please specify the new link.') - end - end - }, - - { -- /alist - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('alist').table, - - command = 'alist', - privilege = 3, - interior = true, - doc = 'Returns a list of administrators. Owner is denoted with a star character.', - - action = function(self, msg, group, config) - local output = '*Administrators:*\n' - output = output .. administration.mod_format(self, config.admin):gsub('\n', ' ★\n') - for id,_ in pairs(self.database.administration.admins) do - output = output .. administration.mod_format(self, id) - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - }, - - { -- /flags - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('flags?', true).table, - - command = 'flag \\[i] ...', - privilege = 3, - interior = true, - doc = 'Returns a list of flags or toggles the specified flags.', - - action = function(self, msg, group, config) - local output = '' - local input = utilities.input(msg.text) - if input then - for i in input:gmatch('%g+') do - local n = tonumber(i) - if n and administration.flags[n] then - if group.flags[n] == true then - group.flags[n] = false - output = output .. administration.flags[n].disabled .. '\n' - else - group.flags[n] = true - output = output .. administration.flags[n].enabled .. '\n' - end - end - end - if output == '' then - input = false - end - end - if not input then - output = '*Flags for ' .. msg.chat.title .. ':*\n' - for i, flag in ipairs(administration.flags) do - local status = group.flags[i] or false - output = output .. '*' .. i .. '. ' .. flag.name .. '* `[' .. tostring(status) .. ']`\n• ' .. flag.desc .. '\n' - end - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - }, - - { -- /antiflood - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('antiflood', true).table, - - command = 'antiflood \\[ ]', - privilege = 3, - interior = true, - doc = 'Returns a list of antiflood values or sets one.', - - action = function(self, msg, group, config) - if not group.flags[5] then - utilities.send_message(self, msg.chat.id, 'antiflood is not enabled. Use `'..config.cmd_pat..'flag 5` to enable it.', true, nil, true) - else - if not group.antiflood then - group.antiflood = JSON.decode(JSON.encode(administration.antiflood)) - end - local input = utilities.input(msg.text_lower) - local output - if input then - local key, val = input:match('(%a+) (%d+)') - if not key or not val or not tonumber(val) then - output = 'Not a valid message type or number.' - elseif key == 'autoban' then - group.autoban = tonumber(val) - output = 'Users will now be autobanned after *' .. val .. '* autokicks.' - else - group.antiflood[key] = tonumber(val) - output = '*' .. key:gsub('^%l', string.upper) .. '* messages are now worth *' .. val .. '* points.' - end - else - output = 'usage: `'..config.cmd_pat..'antiflood `\nexample: `'..config.cmd_pat..'antiflood text 5`\nUse this command to configure the point values for each message type. When a user reaches 100 points, he is kicked. The points are reset each minute. The current values are:\n' - for k,v in pairs(group.antiflood) do - output = output .. '*'..k..':* `'..v..'`\n' - end - output = output .. 'Users will be banned automatically after *' .. group.autoban .. '* autokicks. Configure this with the *autoban* keyword.' - end - utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) - end - end - }, - - { -- /mod - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('mod', true).table, - - command = 'mod ', - privilege = 3, - interior = true, - doc = 'Promotes a user to a moderator. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - else - if target.rank > 1 then - output = output .. target.name .. ' is already a moderator or greater.\n' - else - output = output .. target.name .. ' is now a moderator.\n' - group.mods[target.id_str] = true - group.bans[target.id_str] = nil - end - if group.grouptype == 'supergroup' then - local chat_member = bindings.getChatMember(self, { chat_id = msg.chat.id, user_id = target.id }) - if chat_member and chat_member.result.status == 'member' then - drua.channel_set_admin(msg.chat.id, target.id, 2) - end - end - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /demod - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('demod', true).table, - - command = 'demod ', - privilege = 3, - interior = true, - doc = 'Demotes a moderator to a user. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - else - if not group.mods[target.id_str] then - output = output .. target.name .. ' is not a moderator.\n' - else - output = output .. target.name .. ' is no longer a moderator.\n' - group.mods[target.id_str] = nil - end - if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 0) - end - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /gov - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('gov', true).table, - - command = 'gov ', - privilege = 4, - interior = true, - doc = 'Promotes a user to the governor. The current governor will be replaced. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local target = targets[1] - if target.err then - utilities.send_reply(self, msg, target.err) - else - if group.governor == target.id then - utilities.send_reply(self, msg, target.name .. ' is already the governor.') - else - group.bans[target.id_str] = nil - group.mods[target.id_str] = nil - group.governor = target.id - utilities.send_reply(self, msg, target.name .. ' is the new governor.') - end - if group.grouptype == 'supergroup' then - local chat_member = bindings.getChatMember(self, { chat_id = msg.chat.id, user_id = target.id }) - if chat_member and chat_member.result.status == 'member' then - drua.channel_set_admin(msg.chat.id, target.id, 2) - end - administration.update_desc(self, msg.chat.id, config) - end - end - else - utilities.send_reply(self, msg, 'Please specify a user via reply, username, or ID.') - end - end - }, - - { -- /degov - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('degov', true).table, - - command = 'degov ', - privilege = 4, - interior = true, - doc = 'Demotes the governor to a user. The administrator will become the new governor. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local target = targets[1] - if target.err then - utilities.send_reply(self, msg, target.err) - else - if group.governor ~= target.id then - utilities.send_reply(self, msg, target.name .. ' is not the governor.') - else - group.governor = msg.from.id - utilities.send_reply(self, msg, target.name .. ' is no longer the governor.') - end - if group.grouptype == 'supergroup' then - drua.channel_set_admin(msg.chat.id, target.id, 0) - administration.update_desc(self, msg.chat.id, config) - end - end - else - utilities.send_reply(self, msg, 'Please specify a user via reply, username, or ID.') - end - end - }, - - { -- /hammer - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('hammer', true).table, - - command = 'hammer ', - privilege = 4, - interior = false, - doc = 'Bans a user from all groups. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif self.database.administration.globalbans[target.id_str] then - output = output .. target.name .. ' is already globally banned.\n' - elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then - output = output .. target.name .. ' is too privileged to be globally banned.\n' - else - if group then - administration.kick_user(self, msg.chat.id, target.id, 'hammered by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) - end - if #targets == 1 then - for k,v in pairs(self.database.administration.groups) do - if not v.flags[6] then - v.mods[target.id_str] = nil - drua.kick_user(k, target.id) - end - end - end - self.database.administration.globalbans[target.id_str] = true - if group and group.flags[6] == true then - group.mods[target.id_str] = nil - group.bans[target.id_str] = true - output = output .. target.name .. ' has been globally and locally banned.\n' - else - output = output .. target.name .. ' has been globally banned.\n' - end - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /unhammer - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('unhammer', true).table, - - command = 'unhammer ', - privilege = 4, - interior = false, - doc = 'Removes a global ban. The target may be specified via reply, username, or ID.', - - action = function(self, msg, group, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif not self.database.administration.globalbans[target.id_str] then - output = output .. target.name .. ' is not globally banned.\n' - else - self.database.administration.globalbans[target.id_str] = nil - output = output .. target.name .. ' has been globally unbanned.\n' - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /admin - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('admin', true).table, - - command = 'admin ', - privilege = 5, - interior = false, - doc = 'Promotes a user to an administrator. The target may be specified via reply, username, or ID.', - - action = function(self, msg, _, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif target.rank >= 4 then - output = output .. target.name .. ' is already an administrator or greater.\n' - else - for _, group in pairs(self.database.administration.groups) do - group.mods[target.id_str] = nil - end - self.database.administration.admins[target.id_str] = true - output = output .. target.name .. ' is now an administrator.\n' - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /deadmin - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('deadmin', true).table, - - command = 'deadmin ', - privilege = 5, - interior = false, - doc = 'Demotes an administrator to a user. The target may be specified via reply, username, or ID.', - - action = function(self, msg, _, config) - local targets = administration.get_targets(self, msg, config) - if targets then - local output = '' - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif target.rank ~= 4 then - output = output .. target.name .. ' is not an administrator.\n' - else - for chat_id, group in pairs(self.database.administration.groups) do - if group.grouptype == 'supergroup' then - drua.channel_set_admin(chat_id, target.id, 0) - end - end - self.database.administration.admins[target.id_str] = nil - output = output .. target.name .. ' is no longer an administrator.\n' - end - end - utilities.send_reply(self, msg, output) - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') - end - end - }, - - { -- /gadd - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('gadd', true).table, - - command = 'gadd \\[i] ...', - privilege = 5, - interior = false, - doc = 'Adds a group to the administration system. Pass numbers as arguments to enable those flags immediately.\nExample usage:\n\t/gadd 1 4 5\nThis would add a group and enable the unlisted flag, antibot, and antiflood.', - - action = function(self, msg, group, config) - if msg.chat.id == msg.from.id then - utilities.send_message(self, msg.chat.id, 'This is not a group.') - elseif group then - utilities.send_reply(self, msg, 'I am already administrating this group.') - else - local output = 'I am now administrating this group.' - local flags = {} - for i = 1, #administration.flags do - flags[i] = false - end - local input = utilities.input(msg.text) - if input then - for i in input:gmatch('%g+') do - local n = tonumber(i) - if n and administration.flags[n] and flags[n] ~= true then - flags[n] = true - output = output .. '\n' .. administration.flags[n].short - end - end - end - self.database.administration.groups[tostring(msg.chat.id)] = { - mods = {}, - governor = msg.from.id, - bans = {}, - flags = flags, - rules = {}, - grouptype = msg.chat.type, - name = msg.chat.title, - link = drua.export_link(msg.chat.id), - photo = drua.get_photo(msg.chat.id), - founded = os.time(), - autokicks = {}, - autoban = 3 - } - administration.update_desc(self, msg.chat.id, config) - table.insert(self.database.administration.activity, tostring(msg.chat.id)) - utilities.send_reply(self, msg, output) - drua.channel_set_admin(msg.chat.id, self.info.id, 2) - end - end - }, - - { -- /grem - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('grem', true):t('gremove', true).table, - - command = 'gremove \\[chat]', - privilege = 5, - interior = false, - doc = 'Removes a group from the administration system.', - - action = function(self, msg) - local input = utilities.input(msg.text) or tostring(msg.chat.id) - local output - if self.database.administration.groups[input] then - local chat_name = self.database.administration.groups[input].name - self.database.administration.groups[input] = nil - for i,v in ipairs(self.database.administration.activity) do - if v == input then - table.remove(self.database.administration.activity, i) - end - end - output = 'I am no longer administrating _' .. utilities.md_escape(chat_name) .. '_.' - else - if input == tostring(msg.chat.id) then - output = 'I do not administrate this group.' - else - output = 'I do not administrate that group.' - end - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - }, - - { -- /glist - triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('glist', false).table, - - command = 'glist', - privilege = 5, - interior = false, - doc = 'Returns a list (in a private message) of all administrated groups with their governors and links.', - - action = function(self, msg, group, config) - local output = '' - if utilities.table_size(self.database.administration.groups) > 0 then - for k,v in pairs(self.database.administration.groups) do - output = output .. '[' .. utilities.md_escape(v.name) .. '](' .. v.link .. ') `[' .. k .. ']`\n' - if v.governor then - local gov = self.database.users[tostring(v.governor)] - output = output .. '★ ' .. utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`\n' - end - end - else - output = 'There are no groups.' - end - if utilities.send_message(self, msg.from.id, output, true, nil, true) then - if msg.from.id ~= msg.chat.id then - utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') - end - end - end - } - - } - - administration.triggers = {''} - - -- Generate help messages and ahelp keywords. - self_.database.administration.help = {} - for i,_ in ipairs(administration.ranks) do - administration.temp.help[i] = {} - end - for _,v in ipairs(administration.commands) do - if v.command then - table.insert(administration.temp.help[v.privilege], v.command) - if v.doc then - v.keyword = utilities.get_word(v.command, 1) - end - end - end + administration.commands = { + + { -- generic, mostly autokicks + triggers = { '' }, + + privilege = 0, + interior = true, + + action = function(self, msg, group, config) + + local rank = administration.get_rank(self, msg.from.id, msg.chat.id, config) + local user = {} + local from_id_str = tostring(msg.from.id) + local chat_id_str = tostring(msg.chat.id) + + if rank < 2 then + local from_name = utilities.build_name(msg.from.first_name, msg.from.last_name) + + -- banned + if rank == 0 then + user.do_kick = true + user.dont_unban = true + user.reason = 'banned' + user.output = 'Sorry, you are banned from ' .. msg.chat.title .. '.' + elseif group.flags[2] and ( -- antisquig + msg.text:match(utilities.char.arabic) + or msg.text:match(utilities.char.rtl_override) + or msg.text:match(utilities.char.rtl_mark) + ) then + user.do_kick = true + user.reason = 'antisquig' + user.output = administration.flags[2].kicked:gsub('GROUPNAME', msg.chat.title) + elseif group.flags[3] and ( -- antisquig++ + from_name:match(utilities.char.arabic) + or from_name:match(utilities.char.rtl_override) + or from_name:match(utilities.char.rtl_mark) + ) then + user.do_kick = true + user.reason = 'antisquig++' + user.output = administration.flags[3].kicked:gsub('GROUPNAME', msg.chat.title) + end + + -- antiflood + if group.flags[5] then + if not group.antiflood then + group.antiflood = JSON.decode(JSON.encode(administration.antiflood)) + end + if not administration.temp.flood[chat_id_str] then + administration.temp.flood[chat_id_str] = {} + end + if not administration.temp.flood[chat_id_str][from_id_str] then + administration.temp.flood[chat_id_str][from_id_str] = 0 + end + if msg.sticker then -- Thanks Brazil for discarding switches. + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.sticker + elseif msg.photo then + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.photo + elseif msg.document then + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.document + elseif msg.audio then + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.audio + elseif msg.contact then + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.contact + elseif msg.video then + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.video + elseif msg.location then + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.location + elseif msg.voice then + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.voice + else + administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.text + end + if administration.temp.flood[chat_id_str][from_id_str] > 99 then + user.do_kick = true + user.reason = 'antiflood' + user.output = administration.flags[5].kicked:gsub('GROUPNAME', msg.chat.title) + administration.temp.flood[chat_id_str][from_id_str] = nil + end + end + + end + + local new_user = user + local new_rank = rank + + if msg.new_chat_member then + + -- I hate typing this out. + local noob = msg.new_chat_member + local noob_name = utilities.build_name(noob.first_name, noob.last_name) + + -- We'll make a new table for the new guy, unless he's also + -- the original guy. + if msg.new_chat_member.id ~= msg.from.id then + new_user = {} + new_rank = administration.get_rank(self,noob.id, msg.chat.id, config) + end + + if new_rank == 0 then + new_user.do_kick = true + new_user.dont_unban = true + new_user.reason = 'banned' + new_user.output = 'Sorry, you are banned from ' .. msg.chat.title .. '.' + elseif new_rank == 1 then + if group.flags[3] and ( -- antisquig++ + noob_name:match(utilities.char.arabic) + or noob_name:match(utilities.char.rtl_override) + or noob_name:match(utilities.char.rtl_mark) + ) then + new_user.do_kick = true + new_user.reason = 'antisquig++' + new_user.output = administration.flags[3].kicked:gsub('GROUPNAME', msg.chat.title) + elseif ( -- antibot + group.flags[4] + and noob.username + and noob.username:match('bot$') + and rank < 2 + ) then + new_user.do_kick = true + new_user.reason = 'antibot' + end + else + -- Make the new user a group admin if he's a mod or higher. + if msg.chat.type == 'supergroup' then + drua.channel_set_admin(msg.chat.id, msg.new_chat_member.id, 2) + end + end + + elseif msg.new_chat_title then + if rank < 3 then + drua.rename_chat(msg.chat.id, group.name) + else + group.name = msg.new_chat_title + if group.grouptype == 'supergroup' then + administration.update_desc(self, msg.chat.id, config) + end + end + elseif msg.new_chat_photo then + if group.grouptype == 'group' then + if rank < 3 then + drua.set_photo(msg.chat.id, group.photo) + else + group.photo = drua.get_photo(msg.chat.id) + end + else + group.photo = drua.get_photo(msg.chat.id) + end + elseif msg.delete_chat_photo then + if group.grouptype == 'group' then + if rank < 3 then + drua.set_photo(msg.chat.id, group.photo) + else + group.photo = nil + end + else + group.photo = nil + end + end + + if new_user ~= user and new_user.do_kick then + administration.kick_user(self, msg.chat.id, msg.new_chat_member.id, new_user.reason, config) + if new_user.output then + utilities.send_message(self, msg.new_chat_member.id, new_user.output) + end + if not new_user.dont_unban and msg.chat.type == 'supergroup' then + bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = msg.from.id } ) + end + end + + if group.flags[5] and user.do_kick and not user.dont_unban then + if group.autokicks[from_id_str] then + group.autokicks[from_id_str] = group.autokicks[from_id_str] + 1 + else + group.autokicks[from_id_str] = 1 + end + if group.autokicks[from_id_str] >= group.autoban then + group.autokicks[from_id_str] = 0 + group.bans[from_id_str] = true + user.dont_unban = true + user.reason = 'antiflood autoban: ' .. user.reason + user.output = user.output .. '\nYou have been banned for being autokicked too many times.' + end + end + + if user.do_kick then + administration.kick_user(self, msg.chat.id, msg.from.id, user.reason, config) + if user.output then + utilities.send_message(self, msg.from.id, user.output) + end + if not user.dont_unban and msg.chat.type == 'supergroup' then + bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = msg.from.id } ) + end + end + + if msg.new_chat_member and not new_user.do_kick then + local output = administration.get_desc(self, msg.chat.id, config) + utilities.send_message(self, msg.new_chat_member.id, output, true, nil, true) + end + + -- Last active time for group listing. + if msg.text:len() > 0 then + for i,v in pairs(self.database.administration.activity) do + if v == chat_id_str then + table.remove(self.database.administration.activity, i) + table.insert(self.database.administration.activity, 1, chat_id_str) + end + end + end + + return true + + end + }, + + { -- /groups + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('groups', true).table, + + command = 'groups \\[query]', + privilege = 1, + interior = false, + doc = 'Returns a list of groups matching the query, or a list of all administrated groups.', + + action = function(self, msg, _, config) + local input = utilities.input(msg.text) + local search_res = '' + local grouplist = '' + for _, chat_id_str in ipairs(self.database.administration.activity) do + local group = self.database.administration.groups[chat_id_str] + if (not group.flags[1]) and group.link then -- no unlisted or unlinked groups + grouplist = grouplist .. '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')\n' + if input and string.match(group.name:lower(), input:lower()) then + search_res = search_res .. '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')\n' + end + end + end + local output + if search_res ~= '' then + output = '*Groups matching* _' .. input .. '_ *:*\n' .. search_res + elseif grouplist ~= '' then + output = '*Groups:*\n' .. grouplist + else + output = 'There are currently no listed groups.' + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + }, + + { -- /ahelp + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ahelp', true).table, + + command = 'ahelp \\[command]', + privilege = 1, + interior = false, + doc = 'Returns a list of realm-related commands for your rank (in a private message), or command-specific help.', + + action = function(self, msg, group, config) + local rank = administration.get_rank(self, msg.from.id, msg.chat.id, config) + local input = utilities.get_word(msg.text_lower, 2) + if input then + input = input:gsub('^'..config.cmd_pat..'', '') + local doc + for _,action in ipairs(administration.commands) do + if action.keyword == input then + doc = ''..config.cmd_pat..'' .. action.command:gsub('\\','') .. '\n' .. action.doc + break + end + end + if doc then + local output = '*Help for* _' .. input .. '_ :\n```\n' .. doc .. '\n```' + utilities.send_message(self, msg.chat.id, output, true, nil, true) + else + local output = 'Sorry, there is no help for that command.\n'..config.cmd_pat..'ahelp@'..self.info.username + utilities.send_reply(self, msg, output) + end + else + local output = '*Commands for ' .. administration.ranks[rank] .. ':*\n' + for i = 1, rank do + for _, val in ipairs(administration.temp.help[i]) do + output = output .. '• ' .. config.cmd_pat .. val .. '\n' + end + end + output = output .. 'Arguments: \\[optional]' + if utilities.send_message(self, msg.from.id, output, true, nil, true) then + if msg.from.id ~= msg.chat.id then + utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') + end + else + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + end + end + }, + + { -- /ops + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ops'):t('oplist').table, + + command = 'ops', + privilege = 1, + interior = true, + doc = 'Returns a list of moderators and the governor for the group.', + + action = function(self, msg, group, config) + local modstring = '' + for k,_ in pairs(group.mods) do + modstring = modstring .. administration.mod_format(self, k) + end + if modstring ~= '' then + modstring = '*Moderators for ' .. msg.chat.title .. ':*\n' .. modstring + end + local govstring = '' + if group.governor then + local gov = self.database.users[tostring(group.governor)] + if gov then + govstring = '*Governor:* ' .. utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`' + else + govstring = '*Governor:* Unknown `[' .. group.governor .. ']`' + end + end + local output = utilities.trim(modstring) ..'\n\n' .. utilities.trim(govstring) + if output == '\n\n' then + output = 'There are currently no moderators for this group.' + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + + }, + + { -- /desc + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('desc'):t('description').table, + + command = 'description', + privilege = 1, + interior = true, + doc = 'Returns a description of the group (in a private message), including its motd, rules, flags, governor, and moderators.', + + action = function(self, msg, group, config) + local output = administration.get_desc(self, msg.chat.id, config) + if utilities.send_message(self, msg.from.id, output, true, nil, true) then + if msg.from.id ~= msg.chat.id then + utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') + end + else + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + end + }, + + { -- /rules + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('rules?', true).table, + + command = 'rules \\[i]', + privilege = 1, + interior = true, + doc = 'Returns the group\'s list of rules, or a specific rule.', + + action = function(self, msg, group, config) + local output + local input = utilities.get_word(msg.text_lower, 2) + input = tonumber(input) + if #group.rules > 0 then + if input and group.rules[input] then + output = '*' .. input .. '.* ' .. group.rules[input] + else + output = '*Rules for ' .. msg.chat.title .. ':*\n' + for i,v in ipairs(group.rules) do + output = output .. '*' .. i .. '.* ' .. v .. '\n' + end + end + else + output = 'No rules have been set for ' .. msg.chat.title .. '.' + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + }, + + { -- /motd + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('motd'):t('qotd').table, + + command = 'motd', + privilege = 1, + interior = true, + doc = 'Returns the group\'s message of the day.', + + action = function(self, msg, group, config) + local output = 'No MOTD has been set for ' .. msg.chat.title .. '.' + if group.motd then + output = '*MOTD for ' .. msg.chat.title .. ':*\n' .. group.motd + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + }, + + { -- /link + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('link').table, + + command = 'link', + privilege = 1, + interior = true, + doc = 'Returns the group\'s link.', + + action = function(self, msg, group, config) + local output = 'No link has been set for ' .. msg.chat.title .. '.' + if group.link then + output = '[' .. msg.chat.title .. '](' .. group.link .. ')' + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + }, + + { -- /kick + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('kick', true).table, + + command = 'kick ', + privilege = 2, + interior = true, + doc = 'Removes a user from the group. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then + output = output .. target.name .. ' is too privileged to be kicked.\n' + else + administration.kick_user(self, msg.chat.id, target.id, 'kicked by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) + output = output .. target.name .. ' has been kicked.\n' + if msg.chat.type == 'supergroup' then + bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) + end + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /ban + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('ban', true).table, + + command = 'ban ', + privilege = 2, + interior = true, + doc = 'Bans a user from the group. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif group.bans[target.id_str] then + output = output .. target.name .. ' is already banned.\n' + elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then + output = output .. target.name .. ' is too privileged to be banned.\n' + else + administration.kick_user(self, msg.chat.id, target.id, 'banned by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) + output = output .. target.name .. ' has been banned.\n' + group.mods[target.id_str] = nil + group.bans[target.id_str] = true + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /unban + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('unban', true).table, + + command = 'unban ', + privilege = 2, + interior = true, + doc = 'Unbans a user from the group. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + else + if not group.bans[target.id_str] then + output = output .. target.name .. ' is not banned.\n' + else + output = output .. target.name .. ' has been unbanned.\n' + group.bans[target.id_str] = nil + end + if msg.chat.type == 'supergroup' then + bindings.unbanChatMember(self, { chat_id = msg.chat.id, user_id = target.id } ) + end + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /setmotd + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setmotd', true):t('setqotd', true).table, + + command = 'setmotd ', + privilege = config_.administration.moderator_setmotd and 2 or 3, + interior = true, + doc = 'Sets the group\'s message of the day. Markdown is supported. Pass "--" to delete the message.', + + action = function(self, msg, group, config) + local input = utilities.input(msg.text) + local quoted = utilities.build_name(msg.from.first_name, msg.from.last_name) + if msg.reply_to_message and #msg.reply_to_message.text > 0 then + input = msg.reply_to_message.text + if msg.reply_to_message.forward_from then + quoted = utilities.build_name(msg.reply_to_message.forward_from.first_name, msg.reply_to_message.forward_from.last_name) + else + quoted = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) + end + end + if input then + if input == '--' or input == utilities.char.em_dash then + group.motd = nil + utilities.send_reply(self, msg, 'The MOTD has been cleared.') + else + if msg.text:match('^/setqotd') then + input = '_' .. utilities.md_escape(input) .. '_\n - ' .. utilities.md_escape(quoted) + end + group.motd = input + local output = '*MOTD for ' .. msg.chat.title .. ':*\n' .. input + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + if group.grouptype == 'supergroup' then + administration.update_desc(self, msg.chat.id, config) + end + else + utilities.send_reply(self, msg, 'Please specify the new message of the day.') + end + end + }, + + { -- /setrules + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setrules', true).table, + + command = 'setrules ', + privilege = 3, + interior = true, + doc = 'Sets the group\'s rules. Rules will be automatically numbered. Separate rules with a new line. Markdown is supported. Pass "--" to delete the rules.', + + action = function(self, msg, group, config) + local input = msg.text:match('^'..config.cmd_pat..'setrules[@'..self.info.username..']*(.+)') + if input == ' --' or input == ' ' .. utilities.char.em_dash then + group.rules = {} + utilities.send_reply(self, msg, 'The rules have been cleared.') + elseif input then + group.rules = {} + input = utilities.trim(input) .. '\n' + local output = '*Rules for ' .. msg.chat.title .. ':*\n' + local i = 1 + for l in input:gmatch('(.-)\n') do + output = output .. '*' .. i .. '.* ' .. l .. '\n' + i = i + 1 + table.insert(group.rules, utilities.trim(l)) + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + else + utilities.send_reply(self, msg, 'Please specify the new rules.') + end + end + }, + + { -- /changerule + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('changerule', true).table, + + command = 'changerule ', + privilege = 3, + interior = true, + doc = 'Changes a single rule. Pass "--" to delete the rule. If i is a number for which there is no rule, adds a rule by the next incremented number.', + + action = function(self, msg, group, config) + local input = utilities.input(msg.text) + local output = 'usage: `'..config.cmd_pat..'changerule `' + if input then + local rule_num = tonumber(input:match('^%d+')) + local new_rule = utilities.input(input) + if not rule_num then + output = 'Please specify which rule you want to change.' + elseif not new_rule then + output = 'Please specify the new rule.' + elseif new_rule == '--' or new_rule == utilities.char.em_dash then + if group.rules[rule_num] then + table.remove(group.rules, rule_num) + output = 'That rule has been deleted.' + else + output = 'There is no rule with that number.' + end + else + if not group.rules[rule_num] then + rule_num = #group.rules + 1 + end + group.rules[rule_num] = new_rule + output = '*' .. rule_num .. '*. ' .. new_rule + end + end + utilities.send_reply(self, msg, output, true) + end + }, + + { -- /setlink + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setlink', true).table, + + command = 'setlink ', + privilege = 3, + interior = true, + doc = 'Sets the group\'s join link. Pass "--" to regenerate the link.', + + action = function(self, msg, group, config) + local input = utilities.input(msg.text) + if input == '--' or input == utilities.char.em_dash then + group.link = drua.export_link(msg.chat.id) + utilities.send_reply(self, msg, 'The link has been regenerated.') + elseif input then + group.link = input + local output = '[' .. msg.chat.title .. '](' .. input .. ')' + utilities.send_message(self, msg.chat.id, output, true, nil, true) + else + utilities.send_reply(self, msg, 'Please specify the new link.') + end + end + }, + + { -- /alist + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('alist').table, + + command = 'alist', + privilege = 3, + interior = true, + doc = 'Returns a list of administrators. Owner is denoted with a star character.', + + action = function(self, msg, group, config) + local output = '*Administrators:*\n' + output = output .. administration.mod_format(self, config.admin):gsub('\n', ' ★\n') + for id,_ in pairs(self.database.administration.admins) do + output = output .. administration.mod_format(self, id) + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + }, + + { -- /flags + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('flags?', true).table, + + command = 'flag \\[i] ...', + privilege = 3, + interior = true, + doc = 'Returns a list of flags or toggles the specified flags.', + + action = function(self, msg, group, config) + local output = '' + local input = utilities.input(msg.text) + if input then + for i in input:gmatch('%g+') do + local n = tonumber(i) + if n and administration.flags[n] then + if group.flags[n] == true then + group.flags[n] = false + output = output .. administration.flags[n].disabled .. '\n' + else + group.flags[n] = true + output = output .. administration.flags[n].enabled .. '\n' + end + end + end + if output == '' then + input = false + end + end + if not input then + output = '*Flags for ' .. msg.chat.title .. ':*\n' + for i, flag in ipairs(administration.flags) do + local status = group.flags[i] or false + output = output .. '*' .. i .. '. ' .. flag.name .. '* `[' .. tostring(status) .. ']`\n• ' .. flag.desc .. '\n' + end + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + }, + + { -- /antiflood + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('antiflood', true).table, + + command = 'antiflood \\[ ]', + privilege = 3, + interior = true, + doc = 'Returns a list of antiflood values or sets one.', + + action = function(self, msg, group, config) + if not group.flags[5] then + utilities.send_message(self, msg.chat.id, 'antiflood is not enabled. Use `'..config.cmd_pat..'flag 5` to enable it.', true, nil, true) + else + if not group.antiflood then + group.antiflood = JSON.decode(JSON.encode(administration.antiflood)) + end + local input = utilities.input(msg.text_lower) + local output + if input then + local key, val = input:match('(%a+) (%d+)') + if not key or not val or not tonumber(val) then + output = 'Not a valid message type or number.' + elseif key == 'autoban' then + group.autoban = tonumber(val) + output = 'Users will now be autobanned after *' .. val .. '* autokicks.' + else + group.antiflood[key] = tonumber(val) + output = '*' .. key:gsub('^%l', string.upper) .. '* messages are now worth *' .. val .. '* points.' + end + else + output = 'usage: `'..config.cmd_pat..'antiflood `\nexample: `'..config.cmd_pat..'antiflood text 5`\nUse this command to configure the point values for each message type. When a user reaches 100 points, he is kicked. The points are reset each minute. The current values are:\n' + for k,v in pairs(group.antiflood) do + output = output .. '*'..k..':* `'..v..'`\n' + end + output = output .. 'Users will be banned automatically after *' .. group.autoban .. '* autokicks. Configure this with the *autoban* keyword.' + end + utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) + end + end + }, + + { -- /mod + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('mod', true).table, + + command = 'mod ', + privilege = 3, + interior = true, + doc = 'Promotes a user to a moderator. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + else + if target.rank > 1 then + output = output .. target.name .. ' is already a moderator or greater.\n' + else + output = output .. target.name .. ' is now a moderator.\n' + group.mods[target.id_str] = true + group.bans[target.id_str] = nil + end + if group.grouptype == 'supergroup' then + local chat_member = bindings.getChatMember(self, { chat_id = msg.chat.id, user_id = target.id }) + if chat_member and chat_member.result.status == 'member' then + drua.channel_set_admin(msg.chat.id, target.id, 2) + end + end + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /demod + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('demod', true).table, + + command = 'demod ', + privilege = 3, + interior = true, + doc = 'Demotes a moderator to a user. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + else + if not group.mods[target.id_str] then + output = output .. target.name .. ' is not a moderator.\n' + else + output = output .. target.name .. ' is no longer a moderator.\n' + group.mods[target.id_str] = nil + end + if group.grouptype == 'supergroup' then + drua.channel_set_admin(msg.chat.id, target.id, 0) + end + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /gov + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('gov', true).table, + + command = 'gov ', + privilege = 4, + interior = true, + doc = 'Promotes a user to the governor. The current governor will be replaced. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local target = targets[1] + if target.err then + utilities.send_reply(self, msg, target.err) + else + if group.governor == target.id then + utilities.send_reply(self, msg, target.name .. ' is already the governor.') + else + group.bans[target.id_str] = nil + group.mods[target.id_str] = nil + group.governor = target.id + utilities.send_reply(self, msg, target.name .. ' is the new governor.') + end + if group.grouptype == 'supergroup' then + local chat_member = bindings.getChatMember(self, { chat_id = msg.chat.id, user_id = target.id }) + if chat_member and chat_member.result.status == 'member' then + drua.channel_set_admin(msg.chat.id, target.id, 2) + end + administration.update_desc(self, msg.chat.id, config) + end + end + else + utilities.send_reply(self, msg, 'Please specify a user via reply, username, or ID.') + end + end + }, + + { -- /degov + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('degov', true).table, + + command = 'degov ', + privilege = 4, + interior = true, + doc = 'Demotes the governor to a user. The administrator will become the new governor. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local target = targets[1] + if target.err then + utilities.send_reply(self, msg, target.err) + else + if group.governor ~= target.id then + utilities.send_reply(self, msg, target.name .. ' is not the governor.') + else + group.governor = msg.from.id + utilities.send_reply(self, msg, target.name .. ' is no longer the governor.') + end + if group.grouptype == 'supergroup' then + drua.channel_set_admin(msg.chat.id, target.id, 0) + administration.update_desc(self, msg.chat.id, config) + end + end + else + utilities.send_reply(self, msg, 'Please specify a user via reply, username, or ID.') + end + end + }, + + { -- /hammer + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('hammer', true).table, + + command = 'hammer ', + privilege = 4, + interior = false, + doc = 'Bans a user from all groups. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif self.database.administration.globalbans[target.id_str] then + output = output .. target.name .. ' is already globally banned.\n' + elseif target.rank >= administration.get_rank(self, msg.from.id, msg.chat.id, config) then + output = output .. target.name .. ' is too privileged to be globally banned.\n' + else + if group then + administration.kick_user(self, msg.chat.id, target.id, 'hammered by ' .. utilities.build_name(msg.from.first_name, msg.from.last_name), config) + end + if #targets == 1 then + for k,v in pairs(self.database.administration.groups) do + if not v.flags[6] then + v.mods[target.id_str] = nil + drua.kick_user(k, target.id) + end + end + end + self.database.administration.globalbans[target.id_str] = true + if group and group.flags[6] == true then + group.mods[target.id_str] = nil + group.bans[target.id_str] = true + output = output .. target.name .. ' has been globally and locally banned.\n' + else + output = output .. target.name .. ' has been globally banned.\n' + end + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /unhammer + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('unhammer', true).table, + + command = 'unhammer ', + privilege = 4, + interior = false, + doc = 'Removes a global ban. The target may be specified via reply, username, or ID.', + + action = function(self, msg, group, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif not self.database.administration.globalbans[target.id_str] then + output = output .. target.name .. ' is not globally banned.\n' + else + self.database.administration.globalbans[target.id_str] = nil + output = output .. target.name .. ' has been globally unbanned.\n' + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /admin + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('admin', true).table, + + command = 'admin ', + privilege = 5, + interior = false, + doc = 'Promotes a user to an administrator. The target may be specified via reply, username, or ID.', + + action = function(self, msg, _, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif target.rank >= 4 then + output = output .. target.name .. ' is already an administrator or greater.\n' + else + for _, group in pairs(self.database.administration.groups) do + group.mods[target.id_str] = nil + end + self.database.administration.admins[target.id_str] = true + output = output .. target.name .. ' is now an administrator.\n' + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /deadmin + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('deadmin', true).table, + + command = 'deadmin ', + privilege = 5, + interior = false, + doc = 'Demotes an administrator to a user. The target may be specified via reply, username, or ID.', + + action = function(self, msg, _, config) + local targets = administration.get_targets(self, msg, config) + if targets then + local output = '' + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif target.rank ~= 4 then + output = output .. target.name .. ' is not an administrator.\n' + else + for chat_id, group in pairs(self.database.administration.groups) do + if group.grouptype == 'supergroup' then + drua.channel_set_admin(chat_id, target.id, 0) + end + end + self.database.administration.admins[target.id_str] = nil + output = output .. target.name .. ' is no longer an administrator.\n' + end + end + utilities.send_reply(self, msg, output) + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID.') + end + end + }, + + { -- /gadd + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('gadd', true).table, + + command = 'gadd \\[i] ...', + privilege = 5, + interior = false, + doc = 'Adds a group to the administration system. Pass numbers as arguments to enable those flags immediately.\nExample usage:\n\t/gadd 1 4 5\nThis would add a group and enable the unlisted flag, antibot, and antiflood.', + + action = function(self, msg, group, config) + if msg.chat.id == msg.from.id then + utilities.send_message(self, msg.chat.id, 'This is not a group.') + elseif group then + utilities.send_reply(self, msg, 'I am already administrating this group.') + else + local output = 'I am now administrating this group.' + local flags = {} + for i = 1, #administration.flags do + flags[i] = false + end + local input = utilities.input(msg.text) + if input then + for i in input:gmatch('%g+') do + local n = tonumber(i) + if n and administration.flags[n] and flags[n] ~= true then + flags[n] = true + output = output .. '\n' .. administration.flags[n].short + end + end + end + self.database.administration.groups[tostring(msg.chat.id)] = { + mods = {}, + governor = msg.from.id, + bans = {}, + flags = flags, + rules = {}, + grouptype = msg.chat.type, + name = msg.chat.title, + link = drua.export_link(msg.chat.id), + photo = drua.get_photo(msg.chat.id), + founded = os.time(), + autokicks = {}, + autoban = 3 + } + administration.update_desc(self, msg.chat.id, config) + table.insert(self.database.administration.activity, tostring(msg.chat.id)) + utilities.send_reply(self, msg, output) + drua.channel_set_admin(msg.chat.id, self.info.id, 2) + end + end + }, + + { -- /grem + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('grem', true):t('gremove', true).table, + + command = 'gremove \\[chat]', + privilege = 5, + interior = false, + doc = 'Removes a group from the administration system.', + + action = function(self, msg) + local input = utilities.input(msg.text) or tostring(msg.chat.id) + local output + if self.database.administration.groups[input] then + local chat_name = self.database.administration.groups[input].name + self.database.administration.groups[input] = nil + for i,v in ipairs(self.database.administration.activity) do + if v == input then + table.remove(self.database.administration.activity, i) + end + end + output = 'I am no longer administrating _' .. utilities.md_escape(chat_name) .. '_.' + else + if input == tostring(msg.chat.id) then + output = 'I do not administrate this group.' + else + output = 'I do not administrate that group.' + end + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + }, + + { -- /glist + triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('glist', false).table, + + command = 'glist', + privilege = 5, + interior = false, + doc = 'Returns a list (in a private message) of all administrated groups with their governors and links.', + + action = function(self, msg, group, config) + local output = '' + if utilities.table_size(self.database.administration.groups) > 0 then + for k,v in pairs(self.database.administration.groups) do + output = output .. '[' .. utilities.md_escape(v.name) .. '](' .. v.link .. ') `[' .. k .. ']`\n' + if v.governor then + local gov = self.database.users[tostring(v.governor)] + output = output .. '★ ' .. utilities.md_escape(utilities.build_name(gov.first_name, gov.last_name)) .. ' `[' .. gov.id .. ']`\n' + end + end + else + output = 'There are no groups.' + end + if utilities.send_message(self, msg.from.id, output, true, nil, true) then + if msg.from.id ~= msg.chat.id then + utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') + end + end + end + } + + } + + administration.triggers = {''} + + -- Generate help messages and ahelp keywords. + self_.database.administration.help = {} + for i,_ in ipairs(administration.ranks) do + administration.temp.help[i] = {} + end + for _,v in ipairs(administration.commands) do + if v.command then + table.insert(administration.temp.help[v.privilege], v.command) + if v.doc then + v.keyword = utilities.get_word(v.command, 1) + end + end + end end function administration:action(msg, config) - for _,command in ipairs(administration.commands) do - for _,trigger in pairs(command.triggers) do - if msg.text_lower:match(trigger) then - if - (command.interior and not self.database.administration.groups[tostring(msg.chat.id)]) - or administration.get_rank(self, msg.from.id, msg.chat.id, config) < command.privilege - then - break - end - local res = command.action(self, msg, self.database.administration.groups[tostring(msg.chat.id)], config) - if res ~= true then - return res - end - end - end - end - return true + for _,command in ipairs(administration.commands) do + for _,trigger in pairs(command.triggers) do + if msg.text_lower:match(trigger) then + if + (command.interior and not self.database.administration.groups[tostring(msg.chat.id)]) + or administration.get_rank(self, msg.from.id, msg.chat.id, config) < command.privilege + then + break + end + local res = command.action(self, msg, self.database.administration.groups[tostring(msg.chat.id)], config) + if res ~= true then + return res + end + end + end + end + return true end function administration:cron() - administration.temp.flood = {} - if os.date('%d') ~= self.database.administration.autokick_timer then - self.database.administration.autokick_timer = os.date('%d') - for _,v in pairs(self.database.administration.groups) do - v.autokicks = {} - end - end + administration.temp.flood = {} + if os.date('%d') ~= self.database.administration.autokick_timer then + self.database.administration.autokick_timer = os.date('%d') + for _,v in pairs(self.database.administration.groups) do + v.autokicks = {} + end + end end return administration diff --git a/otouto/plugins/apod.lua b/otouto/plugins/apod.lua index 7350ffc..e806812 100644 --- a/otouto/plugins/apod.lua +++ b/otouto/plugins/apod.lua @@ -10,47 +10,47 @@ local utilities = require('otouto.utilities') apod.command = 'apod [date]' function apod:init(config) - apod.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('apod', true).table - apod.doc = [[ + apod.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('apod', true).table + apod.doc = [[ /apod [YYYY-MM-DD] Returns the Astronomy Picture of the Day. Source: nasa.gov - ]] - apod.doc = apod.doc:gsub('/', config.cmd_pat) - apod.base_url = 'https://api.nasa.gov/planetary/apod?api_key=' .. (config.nasa_api_key or 'DEMO_KEY') + ]] + apod.doc = apod.doc:gsub('/', config.cmd_pat) + apod.base_url = 'https://api.nasa.gov/planetary/apod?api_key=' .. (config.nasa_api_key or 'DEMO_KEY') end function apod:action(msg, config) - local input = utilities.input(msg.text) - local url = apod.base_url - local date = os.date('%F') - if input then - if input:match('^(%d+)%-(%d+)%-(%d+)$') then - url = url .. '&date=' .. URL.escape(input) - date = input - end - end + local input = utilities.input(msg.text) + local url = apod.base_url + local date = os.date('%F') + if input then + if input:match('^(%d+)%-(%d+)%-(%d+)$') then + url = url .. '&date=' .. URL.escape(input) + date = input + end + end - local jstr, code = HTTPS.request(url) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local jstr, code = HTTPS.request(url) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local data = JSON.decode(jstr) - if data.error then - utilities.send_reply(self, msg, config.errors.results) - return - end + local data = JSON.decode(jstr) + if data.error then + utilities.send_reply(self, msg, config.errors.results) + return + end - local output = string.format( - '%s (%s)\n%s', - utilities.html_escape(data.title), - utilities.html_escape(data.hdurl or data.url), - date, - utilities.html_escape(data.explanation) - ) - utilities.send_message(self, msg.chat.id, output, false, nil, 'html') + local output = string.format( + '%s (%s)\n%s', + utilities.html_escape(data.title), + utilities.html_escape(data.hdurl or data.url), + date, + utilities.html_escape(data.explanation) + ) + utilities.send_message(self, msg.chat.id, output, false, nil, 'html') end return apod diff --git a/otouto/plugins/bandersnatch.lua b/otouto/plugins/bandersnatch.lua index bda4c93..46de247 100644 --- a/otouto/plugins/bandersnatch.lua +++ b/otouto/plugins/bandersnatch.lua @@ -5,8 +5,8 @@ local utilities = require('otouto.utilities') bandersnatch.command = 'bandersnatch' function bandersnatch:init(config) - bandersnatch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bandersnatch'):t('bc').table - bandersnatch.doc = 'Shun the frumious Bandersnatch. \nAlias: ' .. config.cmd_pat .. 'bc' + bandersnatch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bandersnatch'):t('bc').table + bandersnatch.doc = 'Shun the frumious Bandersnatch. \nAlias: ' .. config.cmd_pat .. 'bc' end local fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" } @@ -17,15 +17,15 @@ local lastnames = { "Coddleswort", "Crumplesack", "Curdlesnoot", "Calldispatch", function bandersnatch:action(msg) - local output + local output - if math.random(10) == 10 then - output = fullnames[math.random(#fullnames)] - else - output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)] - end + if math.random(10) == 10 then + output = fullnames[math.random(#fullnames)] + else + output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)] + end - utilities.send_message(self, msg.chat.id, '_'..output..'_', true, nil, true) + utilities.send_message(self, msg.chat.id, '_'..output..'_', true, nil, true) end diff --git a/otouto/plugins/bible.lua b/otouto/plugins/bible.lua index 2d85b43..69f8efa 100644 --- a/otouto/plugins/bible.lua +++ b/otouto/plugins/bible.lua @@ -5,12 +5,12 @@ local URL = require('socket.url') local utilities = require('otouto.utilities') function bible:init(config) - assert(config.biblia_api_key, - 'bible.lua requires a Biblia API key from http://api.biblia.com.' - ) + assert(config.biblia_api_key, + 'bible.lua requires a Biblia API key from http://api.biblia.com.' + ) - bible.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bible', true):t('b', true).table - bible.doc = config.cmd_pat .. [[bible + bible.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bible', true):t('b', true).table + bible.doc = config.cmd_pat .. [[bible Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com. Alias: ]] .. config.cmd_pat .. 'b' end @@ -19,30 +19,30 @@ bible.command = 'bible ' function bible:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, bible.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, bible.doc, true) + return + end - local url = 'http://api.biblia.com/v1/bible/content/ASV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) + local url = 'http://api.biblia.com/v1/bible/content/ASV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) - local output, res = HTTP.request(url) + local output, res = HTTP.request(url) - if not output or res ~= 200 or output:len() == 0 then - url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) - output, res = HTTP.request(url) - end + if not output or res ~= 200 or output:len() == 0 then + url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) + output, res = HTTP.request(url) + end - if not output or res ~= 200 or output:len() == 0 then - output = config.errors.results - end + if not output or res ~= 200 or output:len() == 0 then + output = config.errors.results + end - if output:len() > 4000 then - output = 'The text is too long to post here. Try being more specific.' - end + if output:len() > 4000 then + output = 'The text is too long to post here. Try being more specific.' + end - utilities.send_reply(self, msg, output) + utilities.send_reply(self, msg, output) end diff --git a/otouto/plugins/bing.lua b/otouto/plugins/bing.lua index c71e5ef..627ff3a 100644 --- a/otouto/plugins/bing.lua +++ b/otouto/plugins/bing.lua @@ -14,65 +14,65 @@ bing.command = 'bing ' bing.search_url = 'https://api.datamarket.azure.com/Data.ashx/Bing/Search/Web?Query=\'%s\'&$format=json' function bing:init(config) - assert(config.bing_api_key, - 'bing.lua requires a Bing API key from http://datamarket.azure.com/dataset/bing/search.' - ) + assert(config.bing_api_key, + 'bing.lua requires a Bing API key from http://datamarket.azure.com/dataset/bing/search.' + ) - bing.headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. config.bing_api_key) } - bing.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('bing', true):t('g', true):t('google', true).table - bing.doc = [[ + bing.headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. config.bing_api_key) } + bing.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('bing', true):t('g', true):t('google', true).table + bing.doc = [[ /bing Returns the top web results from Bing. Aliases: /g, /google - ]] - bing.doc = bing.doc:gsub('/', config.cmd_pat) + ]] + bing.doc = bing.doc:gsub('/', config.cmd_pat) end function bing:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, bing.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, bing.doc, true) + return + end - local url = bing.search_url:format(URL.escape(input)) - local resbody = {} - local _, code = https.request{ - url = url, - headers = bing.headers, - sink = ltn12.sink.table(resbody), - } - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local url = bing.search_url:format(URL.escape(input)) + local resbody = {} + local _, code = https.request{ + url = url, + headers = bing.headers, + sink = ltn12.sink.table(resbody), + } + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local data = JSON.decode(table.concat(resbody)) - -- Four results in a group, eight in private. - local limit = msg.chat.type == 'private' and 8 or 4 - -- No more results than provided. - limit = limit > #data.d.results and #data.d.results or limit - if limit == 0 then - utilities.send_reply(self, msg, config.errors.results) - return - end + local data = JSON.decode(table.concat(resbody)) + -- Four results in a group, eight in private. + local limit = msg.chat.type == 'private' and 8 or 4 + -- No more results than provided. + limit = limit > #data.d.results and #data.d.results or limit + if limit == 0 then + utilities.send_reply(self, msg, config.errors.results) + return + end - local reslist = {} - for i = 1, limit do - table.insert(reslist, string.format( - '• %s', - utilities.html_escape(data.d.results[i].Url), - utilities.html_escape(data.d.results[i].Title) - )) - end - local output = string.format( - 'Search results for %s:\n%s', - utilities.html_escape(input), - table.concat(reslist, '\n') - ) - utilities.send_message(self, msg.chat.id, output, true, nil, 'html') + local reslist = {} + for i = 1, limit do + table.insert(reslist, string.format( + '• %s', + utilities.html_escape(data.d.results[i].Url), + utilities.html_escape(data.d.results[i].Title) + )) + end + local output = string.format( + 'Search results for %s:\n%s', + utilities.html_escape(input), + table.concat(reslist, '\n') + ) + utilities.send_message(self, msg.chat.id, output, true, nil, 'html') end return bing diff --git a/otouto/plugins/blacklist.lua b/otouto/plugins/blacklist.lua index 52e2926..95a405e 100644 --- a/otouto/plugins/blacklist.lua +++ b/otouto/plugins/blacklist.lua @@ -3,92 +3,92 @@ local utilities = require('otouto.utilities') local blacklist = {} function blacklist:init(config) - blacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('blacklist', true):t('unblacklist', true).table - blacklist.error = false + blacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('blacklist', true):t('unblacklist', true).table + blacklist.error = false end function blacklist:action(msg, config) - if msg.from.id ~= config.admin then return true end - local targets = {} - if msg.reply_to_message then - table.insert(targets, { - id = msg.reply_to_message.from.id, - id_str = tostring(msg.reply_to_message.from.id), - name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) - }) - else - local input = utilities.input(msg.text) - if input then - for user in input:gmatch('%g+') do - if self.database.users[user] then - table.insert(targets, { - id = self.database.users[user].id, - id_str = tostring(self.database.users[user].id), - name = utilities.build_name(self.database.users[user].first_name, self.database.users[user].last_name) - }) - elseif tonumber(user) then - local t = { - id_str = user, - id = tonumber(user) - } - if tonumber(user) < 0 then - t.name = 'Group (' .. user .. ')' - else - t.name = 'Unknown (' .. user .. ')' - end - table.insert(targets, t) - elseif user:match('^@') then - local u = utilities.resolve_username(self, user) - if u then - table.insert(targets, { - id = u.id, - id_str = tostring(u.id), - name = utilities.build_name(u.first_name, u.last_name) - }) - else - table.insert(targets, { err = 'Sorry, I do not recognize that username ('..user..').' }) - end - else - table.insert(targets, { err = 'Invalid username or ID ('..user..').' }) - end - end - else - utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID, or a group or groups via ID.') - return - end - end - local output = '' - if msg.text:match('^'..config.cmd_pat..'blacklist') then - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif self.database.blacklist[target.id_str] then - output = output .. target.name .. ' is already blacklisted.\n' - else - self.database.blacklist[target.id_str] = true - output = output .. target.name .. ' is now blacklisted.\n' - if config.drua_block_on_blacklist and target.id > 0 then - require('otouto.drua-tg').block(target.id) - end - end - end - elseif msg.text:match('^'..config.cmd_pat..'unblacklist') then - for _, target in ipairs(targets) do - if target.err then - output = output .. target.err .. '\n' - elseif not self.database.blacklist[target.id_str] then - output = output .. target.name .. ' is not blacklisted.\n' - else - self.database.blacklist[target.id_str] = nil - output = output .. target.name .. ' is no longer blacklisted.\n' - if config.drua_block_on_blacklist and target.id > 0 then - require('otouto.drua-tg').unblock(target.id) - end - end - end - end - utilities.send_reply(self, msg, output) + if msg.from.id ~= config.admin then return true end + local targets = {} + if msg.reply_to_message then + table.insert(targets, { + id = msg.reply_to_message.from.id, + id_str = tostring(msg.reply_to_message.from.id), + name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) + }) + else + local input = utilities.input(msg.text) + if input then + for user in input:gmatch('%g+') do + if self.database.users[user] then + table.insert(targets, { + id = self.database.users[user].id, + id_str = tostring(self.database.users[user].id), + name = utilities.build_name(self.database.users[user].first_name, self.database.users[user].last_name) + }) + elseif tonumber(user) then + local t = { + id_str = user, + id = tonumber(user) + } + if tonumber(user) < 0 then + t.name = 'Group (' .. user .. ')' + else + t.name = 'Unknown (' .. user .. ')' + end + table.insert(targets, t) + elseif user:match('^@') then + local u = utilities.resolve_username(self, user) + if u then + table.insert(targets, { + id = u.id, + id_str = tostring(u.id), + name = utilities.build_name(u.first_name, u.last_name) + }) + else + table.insert(targets, { err = 'Sorry, I do not recognize that username ('..user..').' }) + end + else + table.insert(targets, { err = 'Invalid username or ID ('..user..').' }) + end + end + else + utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID, or a group or groups via ID.') + return + end + end + local output = '' + if msg.text:match('^'..config.cmd_pat..'blacklist') then + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif self.database.blacklist[target.id_str] then + output = output .. target.name .. ' is already blacklisted.\n' + else + self.database.blacklist[target.id_str] = true + output = output .. target.name .. ' is now blacklisted.\n' + if config.drua_block_on_blacklist and target.id > 0 then + require('otouto.drua-tg').block(target.id) + end + end + end + elseif msg.text:match('^'..config.cmd_pat..'unblacklist') then + for _, target in ipairs(targets) do + if target.err then + output = output .. target.err .. '\n' + elseif not self.database.blacklist[target.id_str] then + output = output .. target.name .. ' is not blacklisted.\n' + else + self.database.blacklist[target.id_str] = nil + output = output .. target.name .. ' is no longer blacklisted.\n' + if config.drua_block_on_blacklist and target.id > 0 then + require('otouto.drua-tg').unblock(target.id) + end + end + end + end + utilities.send_reply(self, msg, output) end return blacklist diff --git a/otouto/plugins/calc.lua b/otouto/plugins/calc.lua index 30e2ecd..b0bb305 100644 --- a/otouto/plugins/calc.lua +++ b/otouto/plugins/calc.lua @@ -7,22 +7,22 @@ local utilities = require('otouto.utilities') calc.command = 'calc ' function calc:init(config) - calc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('calc', true).table - calc.doc = config.cmd_pat .. [[calc + calc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('calc', true).table + calc.doc = config.cmd_pat .. [[calc Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org.]] end function calc:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, calc.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, calc.doc, true) + return + end - local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input) - local output = HTTPS.request(url) - output = output and '`'..output..'`' or config.errors.connection - utilities.send_reply(self, msg, output, true) + local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input) + local output = HTTPS.request(url) + output = output and '`'..output..'`' or config.errors.connection + utilities.send_reply(self, msg, output, true) end return calc diff --git a/otouto/plugins/catfact.lua b/otouto/plugins/catfact.lua index 25a9bdb..e05e5af 100644 --- a/otouto/plugins/catfact.lua +++ b/otouto/plugins/catfact.lua @@ -7,22 +7,22 @@ local utilities = require('otouto.utilities') local catfact = {} function catfact:init(config) - catfact.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('catfact', true).table - catfact.command = 'catfact' - catfact.doc = 'Returns a cat fact.' - catfact.url = 'http://catfacts-api.appspot.com/api/facts' + catfact.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('catfact', true).table + catfact.command = 'catfact' + catfact.doc = 'Returns a cat fact.' + catfact.url = 'http://catfacts-api.appspot.com/api/facts' end function catfact:action(msg, config) - local jstr, code = HTTP.request(catfact.url) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end - local data = JSON.decode(jstr) - local output = '*Cat Fact*\n_' .. data.facts[1] .. '_' - utilities.send_message(self, msg.chat.id, output, true, nil, true) + local jstr, code = HTTP.request(catfact.url) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end + local data = JSON.decode(jstr) + local output = '*Cat Fact*\n_' .. data.facts[1] .. '_' + utilities.send_message(self, msg.chat.id, output, true, nil, true) end return catfact diff --git a/otouto/plugins/cats.lua b/otouto/plugins/cats.lua index 7e99bb5..c6374b7 100644 --- a/otouto/plugins/cats.lua +++ b/otouto/plugins/cats.lua @@ -4,12 +4,12 @@ local HTTP = require('socket.http') local utilities = require('otouto.utilities') function cats:init(config) - if not config.thecatapi_key then - print('Missing config value: thecatapi_key.') - print('cats.lua will be enabled, but there are more features with a key.') - end + if not config.thecatapi_key then + print('Missing config value: thecatapi_key.') + print('cats.lua will be enabled, but there are more features with a key.') + end - cats.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cat').table + cats.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cat').table end cats.command = 'cat' @@ -17,21 +17,21 @@ cats.doc = 'Returns a cat!' function cats:action(msg, config) - local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg' - if config.thecatapi_key then - url = url .. '&api_key=' .. config.thecatapi_key - end + local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg' + if config.thecatapi_key then + url = url .. '&api_key=' .. config.thecatapi_key + end - local str, res = HTTP.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local str, res = HTTP.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - str = str:match('') - local output = '[Cat!]('..str..')' + str = str:match('') + local output = '[Cat!]('..str..')' - utilities.send_message(self, msg.chat.id, output, false, nil, true) + utilities.send_message(self, msg.chat.id, output, false, nil, true) end diff --git a/otouto/plugins/channel.lua b/otouto/plugins/channel.lua index 4be994e..3ad84af 100644 --- a/otouto/plugins/channel.lua +++ b/otouto/plugins/channel.lua @@ -4,9 +4,9 @@ local bindings = require('otouto.bindings') local utilities = require('otouto.utilities') function channel:init(config) - channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table - channel.command = 'ch \\n ' - channel.doc = config.cmd_pat .. [[ch + channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table + channel.command = 'ch \\n ' + channel.doc = config.cmd_pat .. [[ch Sends a message to a channel. Channel may be specified via ID or username. Messages are markdown-enabled. Users may only send messages to channels for which they are the owner or an administrator. @@ -20,41 +20,41 @@ The following markdown syntax is supported: end function channel:action(msg, config) - -- An exercise in using zero early returns. :) - local input = utilities.input(msg.text) - local output - if input then - local chat_id = utilities.get_word(input, 1) - local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } ) - if admin_list then - local is_admin = false - for _, admin in ipairs(admin_list.result) do - if admin.user.id == msg.from.id then - is_admin = true - end - end - if is_admin then - local text = input:match('\n(.+)') - if text then - local success, result = utilities.send_message(self, chat_id, text, true, nil, true) - if success then - output = 'Your message has been sent!' - else - output = 'Sorry, I was unable to send your message.\n`' .. result.description .. '`' - end - else - output = 'Please enter a message to be sent. Markdown is supported.' - end - else - output = 'Sorry, you do not appear to be an administrator for that channel.' - end - else - output = 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`' - end - else - output = channel.doc - end - utilities.send_reply(self, msg, output, true) + -- An exercise in using zero early returns. :) + local input = utilities.input(msg.text) + local output + if input then + local chat_id = utilities.get_word(input, 1) + local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } ) + if admin_list then + local is_admin = false + for _, admin in ipairs(admin_list.result) do + if admin.user.id == msg.from.id then + is_admin = true + end + end + if is_admin then + local text = input:match('\n(.+)') + if text then + local success, result = utilities.send_message(self, chat_id, text, true, nil, true) + if success then + output = 'Your message has been sent!' + else + output = 'Sorry, I was unable to send your message.\n`' .. result.description .. '`' + end + else + output = 'Please enter a message to be sent. Markdown is supported.' + end + else + output = 'Sorry, you do not appear to be an administrator for that channel.' + end + else + output = 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`' + end + else + output = channel.doc + end + utilities.send_reply(self, msg, output, true) end return channel diff --git a/otouto/plugins/chuckfact.lua b/otouto/plugins/chuckfact.lua index 8cfc2d6..2c287bb 100644 --- a/otouto/plugins/chuckfact.lua +++ b/otouto/plugins/chuckfact.lua @@ -7,22 +7,22 @@ local utilities = require('otouto.utilities') local chuck = {} function chuck:init(config) - chuck.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('chuck', true):t('cn', true):t('chucknorris', true).table - chuck.command = 'chuck' - chuck.doc = 'Returns a fact about Chuck Norris.' - chuck.url = 'http://api.icndb.com/jokes/random' + chuck.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('chuck', true):t('cn', true):t('chucknorris', true).table + chuck.command = 'chuck' + chuck.doc = 'Returns a fact about Chuck Norris.' + chuck.url = 'http://api.icndb.com/jokes/random' end function chuck:action(msg, config) - local jstr, code = HTTP.request(chuck.url) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end - local data = JSON.decode(jstr) - local output = '*Chuck Norris Fact*\n_' .. data.value.joke .. '_' - utilities.send_message(self, msg.chat.id, output, true, nil, true) + local jstr, code = HTTP.request(chuck.url) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end + local data = JSON.decode(jstr) + local output = '*Chuck Norris Fact*\n_' .. data.value.joke .. '_' + utilities.send_message(self, msg.chat.id, output, true, nil, true) end return chuck diff --git a/otouto/plugins/cleverbot.lua b/otouto/plugins/cleverbot.lua index d859af9..548f9f0 100644 --- a/otouto/plugins/cleverbot.lua +++ b/otouto/plugins/cleverbot.lua @@ -7,30 +7,30 @@ local bindings = require('otouto.bindings') local cleverbot = {} function cleverbot:init(config) - cleverbot.name = '^' .. self.info.first_name:lower() .. ', ' - cleverbot.username = '^@' .. self.info.username:lower() .. ', ' - cleverbot.triggers = { - '^' .. self.info.first_name:lower() .. ', ', - '^@' .. self.info.username:lower() .. ', ' - } - cleverbot.url = config.chatter.cleverbot_api - cleverbot.error = false + cleverbot.name = '^' .. self.info.first_name:lower() .. ', ' + cleverbot.username = '^@' .. self.info.username:lower() .. ', ' + cleverbot.triggers = { + '^' .. self.info.first_name:lower() .. ', ', + '^@' .. self.info.username:lower() .. ', ' + } + cleverbot.url = config.chatter.cleverbot_api + cleverbot.error = false end function cleverbot:action(msg, config) - bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) - local input = msg.text_lower:gsub(cleverbot.name, ''):gsub(cleverbot.name, '') - local jstr, code = HTTPS.request(cleverbot.url .. URL.escape(input)) - if code ~= 200 then - utilities.send_message(self, msg.chat.id, config.chatter.connection) - return - end - local data = JSON.decode(jstr) - if not data.clever then - utilities.send_message(self, msg.chat.id, config.chatter.response) - return - end - utilities.send_message(self, msg.chat.id, data.clever) + bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) + local input = msg.text_lower:gsub(cleverbot.name, ''):gsub(cleverbot.name, '') + local jstr, code = HTTPS.request(cleverbot.url .. URL.escape(input)) + if code ~= 200 then + utilities.send_message(self, msg.chat.id, config.chatter.connection) + return + end + local data = JSON.decode(jstr) + if not data.clever then + utilities.send_message(self, msg.chat.id, config.chatter.response) + return + end + utilities.send_message(self, msg.chat.id, data.clever) end return cleverbot diff --git a/otouto/plugins/commit.lua b/otouto/plugins/commit.lua index 5087669..7ed5184 100644 --- a/otouto/plugins/commit.lua +++ b/otouto/plugins/commit.lua @@ -8,19 +8,19 @@ commit.command = 'commit' commit.doc = 'Returns a commit message from whatthecommit.com.' function commit:init(config) - commit.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('commit').table + commit.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('commit').table end function commit:action(msg) - bindings.request( - self, - 'sendMessage', - { - chat_id = msg.chat.id, - text = '```\n' .. (http.request('http://whatthecommit.com/index.txt')) .. '\n```', - parse_mode = 'Markdown' - } - ) + bindings.request( + self, + 'sendMessage', + { + chat_id = msg.chat.id, + text = '```\n' .. (http.request('http://whatthecommit.com/index.txt')) .. '\n```', + parse_mode = 'Markdown' + } + ) end return commit diff --git a/otouto/plugins/control.lua b/otouto/plugins/control.lua index cc4f1e1..b37f876 100644 --- a/otouto/plugins/control.lua +++ b/otouto/plugins/control.lua @@ -6,52 +6,52 @@ local utilities = require('otouto.utilities') local cmd_pat -- Prevents the command from being uncallable. function control:init(config) - cmd_pat = config.cmd_pat - control.triggers = utilities.triggers(self.info.username, cmd_pat, - {'^'..cmd_pat..'script'}):t('reload', true):t('halt').table + cmd_pat = config.cmd_pat + control.triggers = utilities.triggers(self.info.username, cmd_pat, + {'^'..cmd_pat..'script'}):t('reload', true):t('halt').table end function control:action(msg, config) - if msg.from.id ~= config.admin then - return - end + if msg.from.id ~= config.admin then + return + end - if msg.date < os.time() - 2 then return end + if msg.date < os.time() - 2 then return end - if msg.text_lower:match('^'..cmd_pat..'reload') then - for pac, _ in pairs(package.loaded) do - if pac:match('^otouto%.plugins%.') then - package.loaded[pac] = nil - end - end - package.loaded['otouto.bindings'] = nil - package.loaded['otouto.utilities'] = nil - package.loaded['otouto.drua-tg'] = nil - package.loaded['config'] = nil - if not msg.text_lower:match('%-config') then - for k, v in pairs(require('config')) do - config[k] = v - end - end - bot.init(self, config) - utilities.send_reply(self, msg, 'Bot reloaded!') - elseif msg.text_lower:match('^'..cmd_pat..'halt') then - self.is_started = false - utilities.send_reply(self, msg, 'Stopping bot!') - elseif msg.text_lower:match('^'..cmd_pat..'script') then - local input = msg.text_lower:match('^'..cmd_pat..'script\n(.+)') - if not input then - utilities.send_reply(self, msg, 'usage: ```\n'..cmd_pat..'script\n'..cmd_pat..'command \n...\n```', true) - return - end - input = input .. '\n' - for command in input:gmatch('(.-)\n') do - command = utilities.trim(command) - msg.text = command - bot.on_msg_receive(self, msg, config) - end - end + if msg.text_lower:match('^'..cmd_pat..'reload') then + for pac, _ in pairs(package.loaded) do + if pac:match('^otouto%.plugins%.') then + package.loaded[pac] = nil + end + end + package.loaded['otouto.bindings'] = nil + package.loaded['otouto.utilities'] = nil + package.loaded['otouto.drua-tg'] = nil + package.loaded['config'] = nil + if not msg.text_lower:match('%-config') then + for k, v in pairs(require('config')) do + config[k] = v + end + end + bot.init(self, config) + utilities.send_reply(self, msg, 'Bot reloaded!') + elseif msg.text_lower:match('^'..cmd_pat..'halt') then + self.is_started = false + utilities.send_reply(self, msg, 'Stopping bot!') + elseif msg.text_lower:match('^'..cmd_pat..'script') then + local input = msg.text_lower:match('^'..cmd_pat..'script\n(.+)') + if not input then + utilities.send_reply(self, msg, 'usage: ```\n'..cmd_pat..'script\n'..cmd_pat..'command \n...\n```', true) + return + end + input = input .. '\n' + for command in input:gmatch('(.-)\n') do + command = utilities.trim(command) + msg.text = command + bot.on_msg_receive(self, msg, config) + end + end end diff --git a/otouto/plugins/currency.lua b/otouto/plugins/currency.lua index 163d041..d156b24 100644 --- a/otouto/plugins/currency.lua +++ b/otouto/plugins/currency.lua @@ -6,8 +6,8 @@ local utilities = require('otouto.utilities') currency.command = 'cash [amount] to ' function currency:init(config) - currency.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cash', true).table - currency.doc = config.cmd_pat .. [[cash [amount] to + currency.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cash', true).table + currency.doc = config.cmd_pat .. [[cash [amount] to Example: ]] .. config.cmd_pat .. [[cash 5 USD to EUR Returns exchange rates for various currencies. Source: Google Finance.]] @@ -15,44 +15,44 @@ end function currency:action(msg, config) - local input = msg.text:upper() - if not input:match('%a%a%a TO %a%a%a') then - utilities.send_message(self, msg.chat.id, currency.doc, true, msg.message_id, true) - return - end + local input = msg.text:upper() + if not input:match('%a%a%a TO %a%a%a') then + utilities.send_message(self, msg.chat.id, currency.doc, true, msg.message_id, true) + return + end - local from = input:match('(%a%a%a) TO') - local to = input:match('TO (%a%a%a)') - local amount = utilities.get_word(input, 2) - amount = tonumber(amount) or 1 - local result = 1 + local from = input:match('(%a%a%a) TO') + local to = input:match('TO (%a%a%a)') + local amount = utilities.get_word(input, 2) + amount = tonumber(amount) or 1 + local result = 1 - local url = 'https://www.google.com/finance/converter' + local url = 'https://www.google.com/finance/converter' - if from ~= to then + if from ~= to then - url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount - local str, res = HTTPS.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount + local str, res = HTTPS.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - str = str:match('(.*) %u+') - if not str then - utilities.send_reply(self, msg, config.errors.results) - return - end + str = str:match('(.*) %u+') + if not str then + utilities.send_reply(self, msg, config.errors.results) + return + end - result = string.format('%.2f', str) + result = string.format('%.2f', str) - end + end - local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n\n' - output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance`' - output = '```\n' .. output .. '\n```' + local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n\n' + output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance`' + output = '```\n' .. output .. '\n```' - utilities.send_message(self, msg.chat.id, output, true, nil, true) + utilities.send_message(self, msg.chat.id, output, true, nil, true) end diff --git a/otouto/plugins/dice.lua b/otouto/plugins/dice.lua index cdd4d29..07868fa 100644 --- a/otouto/plugins/dice.lua +++ b/otouto/plugins/dice.lua @@ -5,49 +5,49 @@ local utilities = require('otouto.utilities') dice.command = 'roll ' function dice:init(config) - dice.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('roll', true).table - dice.doc = config.cmd_pat .. [[roll + dice.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('roll', true).table + dice.doc = config.cmd_pat .. [[roll Returns a set of dice rolls, where n is the number of rolls and r is the range. If only a range is given, returns only one roll.]] end function dice:action(msg) - local input = utilities.input(msg.text_lower) - if not input then - utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true) - return - end + local input = utilities.input(msg.text_lower) + if not input then + utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true) + return + end - local count, range - if input:match('^[%d]+d[%d]+$') then - count, range = input:match('([%d]+)d([%d]+)') - elseif input:match('^d?[%d]+$') then - count = 1 - range = input:match('^d?([%d]+)$') - else - utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true) - return - end + local count, range + if input:match('^[%d]+d[%d]+$') then + count, range = input:match('([%d]+)d([%d]+)') + elseif input:match('^d?[%d]+$') then + count = 1 + range = input:match('^d?([%d]+)$') + else + utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true) + return + end - count = tonumber(count) - range = tonumber(range) + count = tonumber(count) + range = tonumber(range) - if range < 2 then - utilities.send_reply(self, msg, 'The minimum range is 2.') - return - end - if range > 1000 or count > 1000 then - utilities.send_reply(self, msg, 'The maximum range and count are 1000.') - return - end + if range < 2 then + utilities.send_reply(self, msg, 'The minimum range is 2.') + return + end + if range > 1000 or count > 1000 then + utilities.send_reply(self, msg, 'The maximum range and count are 1000.') + return + end - local output = '*' .. count .. 'd' .. range .. '*\n`' - for _ = 1, count do - output = output .. math.random(range) .. '\t' - end - output = output .. '`' + local output = '*' .. count .. 'd' .. range .. '*\n`' + for _ = 1, count do + output = output .. math.random(range) .. '\t' + end + output = output .. '`' - utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) + utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) end diff --git a/otouto/plugins/dilbert.lua b/otouto/plugins/dilbert.lua index ff197d5..2075471 100644 --- a/otouto/plugins/dilbert.lua +++ b/otouto/plugins/dilbert.lua @@ -8,8 +8,8 @@ local utilities = require('otouto.utilities') dilbert.command = 'dilbert [date]' function dilbert:init(config) - dilbert.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('dilbert', true).table - dilbert.doc = config.cmd_pat .. [[dilbert [YYYY-MM-DD] + dilbert.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('dilbert', true).table + dilbert.doc = config.cmd_pat .. [[dilbert [YYYY-MM-DD] Returns the latest Dilbert strip or that of the provided date. Dates before the first strip will return the first strip. Dates after the last trip will return the last strip. Source: dilbert.com]] @@ -17,32 +17,32 @@ end function dilbert:action(msg, config) - bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' } ) + bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' } ) - local input = utilities.input(msg.text) - if not input then input = os.date('%F') end - if not input:match('^%d%d%d%d%-%d%d%-%d%d$') then input = os.date('%F') end + local input = utilities.input(msg.text) + if not input then input = os.date('%F') end + if not input:match('^%d%d%d%d%-%d%d%-%d%d$') then input = os.date('%F') end - local url = 'http://dilbert.com/strip/' .. URL.escape(input) - local str, res = HTTP.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local url = 'http://dilbert.com/strip/' .. URL.escape(input) + local str, res = HTTP.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local strip_filename = '/tmp/' .. input .. '.gif' - local strip_file = io.open(strip_filename) - if strip_file then - strip_file:close() - strip_file = strip_filename - else - local strip_url = str:match('') - strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif') - end + local strip_filename = '/tmp/' .. input .. '.gif' + local strip_file = io.open(strip_filename) + if strip_file then + strip_file:close() + strip_file = strip_filename + else + local strip_url = str:match('') + strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif') + end - local strip_title = str:match('') + local strip_title = str:match('') - bindings.sendPhoto(self, { chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file } ) + bindings.sendPhoto(self, { chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file } ) end diff --git a/otouto/plugins/echo.lua b/otouto/plugins/echo.lua index 5cc1ad6..d0fabf9 100644 --- a/otouto/plugins/echo.lua +++ b/otouto/plugins/echo.lua @@ -5,25 +5,25 @@ local utilities = require('otouto.utilities') echo.command = 'echo ' function echo:init(config) - echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table - echo.doc = config.cmd_pat .. 'echo \nRepeats a string of text.' + echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table + echo.doc = config.cmd_pat .. 'echo \nRepeats a string of text.' end function echo:action(msg) - local input = utilities.input_from_msg(msg) + local input = utilities.input_from_msg(msg) - if not input then - utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true) - else - local output - if msg.chat.type == 'supergroup' then - output = utilities.style.enquote('Echo', input) - else - output = utilities.md_escape(utilities.char.zwnj..input) - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end + if not input then + utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true) + else + local output + if msg.chat.type == 'supergroup' then + output = utilities.style.enquote('Echo', input) + else + output = utilities.md_escape(utilities.char.zwnj..input) + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end end diff --git a/otouto/plugins/eightball.lua b/otouto/plugins/eightball.lua index 2e01cbd..2dd6dc2 100644 --- a/otouto/plugins/eightball.lua +++ b/otouto/plugins/eightball.lua @@ -6,52 +6,52 @@ eightball.command = '8ball' eightball.doc = 'Returns an answer from a magic 8-ball!' function eightball:init(config) - eightball.triggers = utilities.triggers(self.info.username, config.cmd_pat, - {'[Yy]/[Nn]%p*$'}):t('8ball', true).table + eightball.triggers = utilities.triggers(self.info.username, config.cmd_pat, + {'[Yy]/[Nn]%p*$'}):t('8ball', true).table end local ball_answers = { - "It is certain.", - "It is decidedly so.", - "Without a doubt.", - "Yes, definitely.", - "You may rely on it.", - "As I see it, yes.", - "Most likely.", - "Outlook: good.", - "Yes.", - "Signs point to yes.", - "Reply hazy try again.", - "Ask again later.", - "Better not tell you now.", - "Cannot predict now.", - "Concentrate and ask again.", - "Don't count on it.", - "My reply is no.", - "My sources say no.", - "Outlook: not so good.", - "Very doubtful.", - "There is a time and place for everything, but not now." + "It is certain.", + "It is decidedly so.", + "Without a doubt.", + "Yes, definitely.", + "You may rely on it.", + "As I see it, yes.", + "Most likely.", + "Outlook: good.", + "Yes.", + "Signs point to yes.", + "Reply hazy try again.", + "Ask again later.", + "Better not tell you now.", + "Cannot predict now.", + "Concentrate and ask again.", + "Don't count on it.", + "My reply is no.", + "My sources say no.", + "Outlook: not so good.", + "Very doubtful.", + "There is a time and place for everything, but not now." } local yesno_answers = { - 'Absolutely.', - 'In your dreams.', - 'Yes.', - 'No.' + 'Absolutely.', + 'In your dreams.', + 'Yes.', + 'No.' } function eightball:action(msg) - local output + local output - if msg.text_lower:match('y/n%p?$') then - output = yesno_answers[math.random(#yesno_answers)] - else - output = ball_answers[math.random(#ball_answers)] - end + if msg.text_lower:match('y/n%p?$') then + output = yesno_answers[math.random(#yesno_answers)] + else + output = ball_answers[math.random(#ball_answers)] + end - utilities.send_reply(self, msg, output) + utilities.send_reply(self, msg, output) end diff --git a/otouto/plugins/fortune.lua b/otouto/plugins/fortune.lua index 5d7f0d7..f415f34 100644 --- a/otouto/plugins/fortune.lua +++ b/otouto/plugins/fortune.lua @@ -5,13 +5,13 @@ local fortune = {} local utilities = require('otouto.utilities') function fortune:init(config) - local s = io.popen('fortune'):read('*all') - assert( - not s:match('not found$'), - 'fortune.lua requires the fortune program to be installed.' - ) + local s = io.popen('fortune'):read('*all') + assert( + not s:match('not found$'), + 'fortune.lua requires the fortune program to be installed.' + ) - fortune.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('fortune').table + fortune.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('fortune').table end fortune.command = 'fortune' @@ -19,11 +19,11 @@ fortune.doc = 'Returns a UNIX fortune.' function fortune:action(msg) - local fortunef = io.popen('fortune') - local output = fortunef:read('*all') - output = '```\n' .. output .. '\n```' - utilities.send_message(self, msg.chat.id, output, true, nil, true) - fortunef:close() + local fortunef = io.popen('fortune') + local output = fortunef:read('*all') + output = '```\n' .. output .. '\n```' + utilities.send_message(self, msg.chat.id, output, true, nil, true) + fortunef:close() end diff --git a/otouto/plugins/gImages.lua b/otouto/plugins/gImages.lua index 94eadff..add99cb 100644 --- a/otouto/plugins/gImages.lua +++ b/otouto/plugins/gImages.lua @@ -9,58 +9,58 @@ local JSON = require('dkjson') local utilities = require('otouto.utilities') function gImages:init(config) - assert(config.google_api_key and config.google_cse_key, - 'gImages.lua requires a Google API key from http://console.developers.google.com and a Google Custom Search Engine key from http://cse.google.com/cse.' - ) + assert(config.google_api_key and config.google_cse_key, + 'gImages.lua requires a Google API key from http://console.developers.google.com and a Google Custom Search Engine key from http://cse.google.com/cse.' + ) - gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('image', true):t('i', true):t('insfw', true).table - gImages.doc = config.cmd_pat .. [[image + gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('image', true):t('i', true):t('insfw', true).table + gImages.doc = config.cmd_pat .. [[image Returns a randomized top result from Google Images. Safe search is enabled by default; use "]] .. config.cmd_pat .. [[insfw" to disable it. NSFW results will not display an image preview. Alias: ]] .. config.cmd_pat .. 'i' - gImages.search_url = 'https://www.googleapis.com/customsearch/v1?&searchType=image&imgSize=xlarge&alt=json&num=8&start=1&key=' .. config.google_api_key .. '&cx=' .. config.google_cse_key + gImages.search_url = 'https://www.googleapis.com/customsearch/v1?&searchType=image&imgSize=xlarge&alt=json&num=8&start=1&key=' .. config.google_api_key .. '&cx=' .. config.google_cse_key end gImages.command = 'image ' function gImages:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, gImages.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, gImages.doc, true) + return + end - local url = gImages.search_url + local url = gImages.search_url - if not string.match(msg.text, '^'..config.cmd_pat..'i[mage]*nsfw') then - url = url .. '&safe=high' - end + if not string.match(msg.text, '^'..config.cmd_pat..'i[mage]*nsfw') then + url = url .. '&safe=high' + end - url = url .. '&q=' .. URL.escape(input) + url = url .. '&q=' .. URL.escape(input) - local jstr, res = HTTPS.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local jstr, res = HTTPS.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local jdat = JSON.decode(jstr) - if jdat.searchInformation.totalResults == '0' then - utilities.send_reply(self, msg, config.errors.results) - return - end + local jdat = JSON.decode(jstr) + if jdat.searchInformation.totalResults == '0' then + utilities.send_reply(self, msg, config.errors.results) + return + end - local i = math.random(jdat.queries.request[1].count) - local img_url = jdat.items[i].link - local img_title = jdat.items[i].title - local output = '[' .. img_title .. '](' .. img_url .. ')' + local i = math.random(jdat.queries.request[1].count) + local img_url = jdat.items[i].link + local img_title = jdat.items[i].title + local output = '[' .. img_title .. '](' .. img_url .. ')' - if msg.text:match('nsfw') then - utilities.send_reply(self, '*NSFW*\n'..msg, output) - else - utilities.send_message(self, msg.chat.id, output, false, nil, true) - end + if msg.text:match('nsfw') then + utilities.send_reply(self, '*NSFW*\n'..msg, output) + else + utilities.send_message(self, msg.chat.id, output, false, nil, true) + end end diff --git a/otouto/plugins/gMaps.lua b/otouto/plugins/gMaps.lua index 5838715..ea0baac 100644 --- a/otouto/plugins/gMaps.lua +++ b/otouto/plugins/gMaps.lua @@ -6,34 +6,34 @@ local utilities = require('otouto.utilities') gMaps.command = 'location ' function gMaps:init(config) - gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('location', true):t('loc', true).table - gMaps.doc = [[ + gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('location', true):t('loc', true).table + gMaps.doc = [[ /location Returns a location from Google Maps. Alias: /loc - ]] - gMaps.doc = gMaps.doc:gsub('/', config.cmd_pat) + ]] + gMaps.doc = gMaps.doc:gsub('/', config.cmd_pat) end function gMaps:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, gMaps.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, gMaps.doc, true) + return + end - local coords = utilities.get_coords(input, config) - if type(coords) == 'string' then - utilities.send_reply(self, msg, coords) - end + local coords = utilities.get_coords(input, config) + if type(coords) == 'string' then + utilities.send_reply(self, msg, coords) + end - bindings.sendLocation(self, { - chat_id = msg.chat.id, - latitude = coords.lat, - longitude = coords.lon, - reply_to_message_id = msg.message_id - } ) + bindings.sendLocation(self, { + chat_id = msg.chat.id, + latitude = coords.lat, + longitude = coords.lon, + reply_to_message_id = msg.message_id + } ) end return gMaps diff --git a/otouto/plugins/greetings.lua b/otouto/plugins/greetings.lua index 5375e66..53a8910 100644 --- a/otouto/plugins/greetings.lua +++ b/otouto/plugins/greetings.lua @@ -3,30 +3,30 @@ local utilities = require('otouto.utilities') local greetings = {} function greetings:init(config) - greetings.triggers = {} - for _, triggers in pairs(config.greetings) do - for i = 1, #triggers do - triggers[i] = '^' .. triggers[i] .. ',? ' .. self.info.first_name:lower() .. '%p*$' - table.insert(greetings.triggers, triggers[i]) - end - end + greetings.triggers = {} + for _, triggers in pairs(config.greetings) do + for i = 1, #triggers do + triggers[i] = '^' .. triggers[i] .. ',? ' .. self.info.first_name:lower() .. '%p*$' + table.insert(greetings.triggers, triggers[i]) + end + end end function greetings:action(msg, config) - local nick - if self.database.userdata[tostring(msg.from.id)] then - nick = self.database.userdata[tostring(msg.from.id)].nickname - end - nick = nick or utilities.build_name(msg.from.first_name, msg.from.last_name) + local nick + if self.database.userdata[tostring(msg.from.id)] then + nick = self.database.userdata[tostring(msg.from.id)].nickname + end + nick = nick or utilities.build_name(msg.from.first_name, msg.from.last_name) - for response, triggers in pairs(config.greetings) do - for _, trigger in pairs(triggers) do - if string.match(msg.text_lower, trigger) then - utilities.send_message(self, msg.chat.id, response:gsub('#NAME', nick)) - return - end - end - end + for response, triggers in pairs(config.greetings) do + for _, trigger in pairs(triggers) do + if string.match(msg.text_lower, trigger) then + utilities.send_message(self, msg.chat.id, response:gsub('#NAME', nick)) + return + end + end + end end return greetings diff --git a/otouto/plugins/hackernews.lua b/otouto/plugins/hackernews.lua index 47e8dc2..512154a 100644 --- a/otouto/plugins/hackernews.lua +++ b/otouto/plugins/hackernews.lua @@ -8,68 +8,68 @@ local hackernews = {} hackernews.command = 'hackernews' local function get_hackernews_results() - local results = {} - local jstr, code = HTTPS.request(hackernews.topstories_url) - if code ~= 200 then return end - local data = JSON.decode(jstr) - for i = 1, 8 do - local ijstr, icode = HTTPS.request(hackernews.res_url:format(data[i])) - if icode ~= 200 then return end - local idata = JSON.decode(ijstr) - local result - if idata.url then - result = string.format( - '\n• [%s] %s', - utilities.html_escape(hackernews.art_url:format(idata.id)), - idata.id, - utilities.html_escape(idata.url), - utilities.html_escape(idata.title) - ) - else - result = string.format( - '\n• [%s] %s', - utilities.html_escape(hackernews.art_url:format(idata.id)), - idata.id, - utilities.html_escape(idata.title) - ) - end - table.insert(results, result) - end - return results + local results = {} + local jstr, code = HTTPS.request(hackernews.topstories_url) + if code ~= 200 then return end + local data = JSON.decode(jstr) + for i = 1, 8 do + local ijstr, icode = HTTPS.request(hackernews.res_url:format(data[i])) + if icode ~= 200 then return end + local idata = JSON.decode(ijstr) + local result + if idata.url then + result = string.format( + '\n• [%s] %s', + utilities.html_escape(hackernews.art_url:format(idata.id)), + idata.id, + utilities.html_escape(idata.url), + utilities.html_escape(idata.title) + ) + else + result = string.format( + '\n• [%s] %s', + utilities.html_escape(hackernews.art_url:format(idata.id)), + idata.id, + utilities.html_escape(idata.title) + ) + end + table.insert(results, result) + end + return results end function hackernews:init(config) - hackernews.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hackernews', true):t('hn', true).table - hackernews.doc = [[Returns four (if group) or eight (if private message) top stories from Hacker News. + hackernews.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hackernews', true):t('hn', true).table + hackernews.doc = [[Returns four (if group) or eight (if private message) top stories from Hacker News. Alias: ]] .. config.cmd_pat .. 'hn' - hackernews.topstories_url = 'https://hacker-news.firebaseio.com/v0/topstories.json' - hackernews.res_url = 'https://hacker-news.firebaseio.com/v0/item/%s.json' - hackernews.art_url = 'https://news.ycombinator.com/item?id=%s' - hackernews.last_update = 0 - if config.hackernews_onstart == true then - hackernews.results = get_hackernews_results() - if hackernews.results then hackernews.last_update = os.time() / 60 end - end + hackernews.topstories_url = 'https://hacker-news.firebaseio.com/v0/topstories.json' + hackernews.res_url = 'https://hacker-news.firebaseio.com/v0/item/%s.json' + hackernews.art_url = 'https://news.ycombinator.com/item?id=%s' + hackernews.last_update = 0 + if config.hackernews_onstart == true then + hackernews.results = get_hackernews_results() + if hackernews.results then hackernews.last_update = os.time() / 60 end + end end function hackernews:action(msg, config) - local now = os.time() / 60 - if not hackernews.results or hackernews.last_update + config.hackernews_interval < now then - bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) - hackernews.results = get_hackernews_results() - if not hackernews.results then - utilities.send_reply(self, msg, config.errors.connection) - return - end - hackernews.last_update = now - end - -- Four results in a group, eight in private. - local res_count = msg.chat.id == msg.from.id and 8 or 4 - local output = 'Top Stories from Hacker News:' - for i = 1, res_count do - output = output .. hackernews.results[i] - end - utilities.send_message(self, msg.chat.id, output, true, nil, 'html') + local now = os.time() / 60 + if not hackernews.results or hackernews.last_update + config.hackernews_interval < now then + bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) + hackernews.results = get_hackernews_results() + if not hackernews.results then + utilities.send_reply(self, msg, config.errors.connection) + return + end + hackernews.last_update = now + end + -- Four results in a group, eight in private. + local res_count = msg.chat.id == msg.from.id and 8 or 4 + local output = 'Top Stories from Hacker News:' + for i = 1, res_count do + output = output .. hackernews.results[i] + end + utilities.send_message(self, msg.chat.id, output, true, nil, 'html') end return hackernews diff --git a/otouto/plugins/hearthstone.lua b/otouto/plugins/hearthstone.lua index 05cbca8..2cee7a6 100644 --- a/otouto/plugins/hearthstone.lua +++ b/otouto/plugins/hearthstone.lua @@ -8,111 +8,111 @@ local utilities = require('otouto.utilities') local HTTPS = require('ssl.https') function hearthstone:init(config) - hearthstone.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hearthstone', true):t('hs').table - hearthstone.command = 'hearthstone ' + hearthstone.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hearthstone', true):t('hs').table + hearthstone.command = 'hearthstone ' - if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then + if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then - print('Downloading Hearthstone database...') + print('Downloading Hearthstone database...') - local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json') - if not jstr or res ~= 200 then - print('Error connecting to hearthstonejson.com.') - print('hearthstone.lua will not be enabled.') - hearthstone.command = nil - hearthstone.triggers = nil - return - end - self.database.hearthstone = JSON.decode(jstr) - self.database.hearthstone.expiration = os.time() + 600000 + local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json') + if not jstr or res ~= 200 then + print('Error connecting to hearthstonejson.com.') + print('hearthstone.lua will not be enabled.') + hearthstone.command = nil + hearthstone.triggers = nil + return + end + self.database.hearthstone = JSON.decode(jstr) + self.database.hearthstone.expiration = os.time() + 600000 - print('Download complete! It will be stored for a week.') + print('Download complete! It will be stored for a week.') - end + end - hearthstone.doc = config.cmd_pat .. [[hearthstone + hearthstone.doc = config.cmd_pat .. [[hearthstone Returns Hearthstone card info. Alias: ]] .. config.cmd_pat .. 'hs' end local function format_card(card) - local ctype = card.type - if card.race then - ctype = card.race - end - if card.rarity then - ctype = card.rarity .. ' ' .. ctype - end - if card.playerClass then - ctype = ctype .. ' (' .. card.playerClass .. ')' - elseif card.faction then - ctype = ctype .. ' (' .. card.faction .. ')' - end + local ctype = card.type + if card.race then + ctype = card.race + end + if card.rarity then + ctype = card.rarity .. ' ' .. ctype + end + if card.playerClass then + ctype = ctype .. ' (' .. card.playerClass .. ')' + elseif card.faction then + ctype = ctype .. ' (' .. card.faction .. ')' + end - local stats - if card.cost then - stats = card.cost .. 'c' - if card.attack then - stats = stats .. ' | ' .. card.attack .. 'a' - end - if card.health then - stats = stats .. ' | ' .. card.health .. 'h' - end - if card.durability then - stats = stats .. ' | ' .. card.durability .. 'd' - end - elseif card.health then - stats = card.health .. 'h' - end + local stats + if card.cost then + stats = card.cost .. 'c' + if card.attack then + stats = stats .. ' | ' .. card.attack .. 'a' + end + if card.health then + stats = stats .. ' | ' .. card.health .. 'h' + end + if card.durability then + stats = stats .. ' | ' .. card.durability .. 'd' + end + elseif card.health then + stats = card.health .. 'h' + end - -- unused? - local info - if card.text then - info = card.text:gsub('',''):gsub('%$','') - if card.flavor then - info = info .. '\n_' .. card.flavor .. '_' - end - elseif card.flavor then - info = card.flavor - else - info = nil - end + -- unused? + local info + if card.text then + info = card.text:gsub('',''):gsub('%$','') + if card.flavor then + info = info .. '\n_' .. card.flavor .. '_' + end + elseif card.flavor then + info = card.flavor + else + info = nil + end - local s = '*' .. card.name .. '*\n' .. ctype - if stats then - s = s .. '\n' .. stats - end - if info then - s = s .. '\n' .. info - end + local s = '*' .. card.name .. '*\n' .. ctype + if stats then + s = s .. '\n' .. stats + end + if info then + s = s .. '\n' .. info + end - return s + return s end function hearthstone:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, hearthstone.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, hearthstone.doc, true) + return + end - local output = '' - for _,v in pairs(self.database.hearthstone) do - if type(v) == 'table' and string.lower(v.name):match(input) then - output = output .. format_card(v) .. '\n\n' - end - end + local output = '' + for _,v in pairs(self.database.hearthstone) do + if type(v) == 'table' and string.lower(v.name):match(input) then + output = output .. format_card(v) .. '\n\n' + end + end - output = utilities.trim(output) - if output:len() == 0 then - utilities.send_reply(self, msg, config.errors.results) - return - end + output = utilities.trim(output) + if output:len() == 0 then + utilities.send_reply(self, msg, config.errors.results) + return + end - utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) + utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) end diff --git a/otouto/plugins/help.lua b/otouto/plugins/help.lua index 8b2ed71..f8c8124 100644 --- a/otouto/plugins/help.lua +++ b/otouto/plugins/help.lua @@ -3,51 +3,51 @@ local utilities = require('otouto.utilities') local help = {} function help:init(config) - help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('help', true):t('h', true).table - help.command = 'help [command]' - help.doc = config.cmd_pat .. 'help [command] \nReturns usage information for a given command.' + help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('help', true):t('h', true).table + help.command = 'help [command]' + help.doc = config.cmd_pat .. 'help [command] \nReturns usage information for a given command.' end function help:action(msg, config) - local input = utilities.input(msg.text_lower) - if input then - if not help.help_word then - for _, plugin in ipairs(self.plugins) do - if plugin.command and plugin.doc and not plugin.help_word then - plugin.help_word = utilities.get_word(plugin.command, 1) - end - end - end - for _,plugin in ipairs(self.plugins) do - if plugin.help_word == input:gsub('^/', '') then - local output = '*Help for* _' .. plugin.help_word .. '_*:*\n' .. plugin.doc - utilities.send_message(self, msg.chat.id, output, true, nil, true) - return - end - end - utilities.send_reply(self, msg, 'Sorry, there is no help for that command.') - else - -- Generate the help message on first run. - if not help.text then - local commandlist = {} - for _, plugin in ipairs(self.plugins) do - if plugin.command then - table.insert(commandlist, plugin.command) - end - end - table.sort(commandlist) - help.text = '*Available commands:*\n• ' .. config.cmd_pat .. table.concat(commandlist, '\n• '..config.cmd_pat) .. '\nArguments: [optional]' - help.text = help.text:gsub('%[', '\\[') - end - -- Attempt to send the help message via PM. - -- If msg is from a group, tell the group whether the PM was successful. - local res = utilities.send_message(self, msg.from.id, help.text, true, nil, true) - if not res then - utilities.send_reply(self, msg, 'Please [message me privately](http://telegram.me/' .. self.info.username .. '?start=help) for a list of commands.', true) - elseif msg.chat.type ~= 'private' then - utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') - end - end + local input = utilities.input(msg.text_lower) + if input then + if not help.help_word then + for _, plugin in ipairs(self.plugins) do + if plugin.command and plugin.doc and not plugin.help_word then + plugin.help_word = utilities.get_word(plugin.command, 1) + end + end + end + for _,plugin in ipairs(self.plugins) do + if plugin.help_word == input:gsub('^/', '') then + local output = '*Help for* _' .. plugin.help_word .. '_*:*\n' .. plugin.doc + utilities.send_message(self, msg.chat.id, output, true, nil, true) + return + end + end + utilities.send_reply(self, msg, 'Sorry, there is no help for that command.') + else + -- Generate the help message on first run. + if not help.text then + local commandlist = {} + for _, plugin in ipairs(self.plugins) do + if plugin.command then + table.insert(commandlist, plugin.command) + end + end + table.sort(commandlist) + help.text = '*Available commands:*\n• ' .. config.cmd_pat .. table.concat(commandlist, '\n• '..config.cmd_pat) .. '\nArguments: [optional]' + help.text = help.text:gsub('%[', '\\[') + end + -- Attempt to send the help message via PM. + -- If msg is from a group, tell the group whether the PM was successful. + local res = utilities.send_message(self, msg.from.id, help.text, true, nil, true) + if not res then + utilities.send_reply(self, msg, 'Please [message me privately](http://telegram.me/' .. self.info.username .. '?start=help) for a list of commands.', true) + elseif msg.chat.type ~= 'private' then + utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') + end + end end return help diff --git a/otouto/plugins/id.lua b/otouto/plugins/id.lua index d077573..fd7568a 100644 --- a/otouto/plugins/id.lua +++ b/otouto/plugins/id.lua @@ -3,60 +3,60 @@ local utilities = require('otouto.utilities') local id = {} function id:init(config) - id.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('id', true).table - id.command = 'id ' - id.doc = config.cmd_pat .. [[id ... + id.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('id', true).table + id.command = 'id ' + id.doc = config.cmd_pat .. [[id ... Returns the name, ID, and username (if applicable) for the given users. Arguments must be usernames and/or IDs. Input is also accepted via reply. If no input is given, returns info for the user. - ]] + ]] end function id.format(t) - if t.username then - return string.format( - '@%s, AKA %s [%s].\n', - t.username, - utilities.build_name(t.first_name, t.last_name), - t.id - ) - else - return string.format( - '%s [%s].\n', - utilities.build_name(t.first_name, t.last_name), - t.id - ) - end + if t.username then + return string.format( + '@%s, AKA %s [%s].\n', + t.username, + utilities.build_name(t.first_name, t.last_name), + t.id + ) + else + return string.format( + '%s [%s].\n', + utilities.build_name(t.first_name, t.last_name), + t.id + ) + end end function id:action(msg) - local output - local input = utilities.input(msg.text) - if msg.reply_to_message then - output = id.format(msg.reply_to_message.from) - elseif input then - output = '' - for user in input:gmatch('%g+') do - if tonumber(user) then - if self.database.users[user] then - output = output .. id.format(self.database.users[user]) - else - output = output .. 'I don\'t recognize that ID (' .. user .. ').\n' - end - elseif user:match('^@') then - local t = utilities.resolve_username(self, user) - if t then - output = output .. id.format(t) - else - output = output .. 'I don\'t recognize that username (' .. user .. ').\n' - end - else - output = output .. 'Invalid username or ID (' .. user .. ').\n' - end - end - else - output = id.format(msg.from) - end - utilities.send_reply(self, msg, output, 'html') + local output + local input = utilities.input(msg.text) + if msg.reply_to_message then + output = id.format(msg.reply_to_message.from) + elseif input then + output = '' + for user in input:gmatch('%g+') do + if tonumber(user) then + if self.database.users[user] then + output = output .. id.format(self.database.users[user]) + else + output = output .. 'I don\'t recognize that ID (' .. user .. ').\n' + end + elseif user:match('^@') then + local t = utilities.resolve_username(self, user) + if t then + output = output .. id.format(t) + else + output = output .. 'I don\'t recognize that username (' .. user .. ').\n' + end + else + output = output .. 'Invalid username or ID (' .. user .. ').\n' + end + end + else + output = id.format(msg.from) + end + utilities.send_reply(self, msg, output, 'html') end return id diff --git a/otouto/plugins/imdb.lua b/otouto/plugins/imdb.lua index 1f91930..e748a35 100644 --- a/otouto/plugins/imdb.lua +++ b/otouto/plugins/imdb.lua @@ -8,39 +8,39 @@ local utilities = require('otouto.utilities') imdb.command = 'imdb ' function imdb:init(config) - imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table - imdb.doc = config.cmd_pat .. 'imdb \nReturns an IMDb entry.' + imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table + imdb.doc = config.cmd_pat .. 'imdb \nReturns an IMDb entry.' end function imdb:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, imdb.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, imdb.doc, true) + return + end - local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input) + local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input) - local jstr, res = HTTP.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local jstr, res = HTTP.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local jdat = JSON.decode(jstr) + local jdat = JSON.decode(jstr) - if jdat.Response ~= 'True' then - utilities.send_reply(self, msg, config.errors.results) - return - end + if jdat.Response ~= 'True' then + utilities.send_reply(self, msg, config.errors.results) + return + end - local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')*\n' - output = output .. jdat.imdbRating ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n' - output = output .. '_' .. jdat.Plot .. '_\n' - output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')' + local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')*\n' + output = output .. jdat.imdbRating ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n' + output = output .. '_' .. jdat.Plot .. '_\n' + output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')' - utilities.send_message(self, msg.chat.id, output, true, nil, true) + utilities.send_message(self, msg.chat.id, output, true, nil, true) end diff --git a/otouto/plugins/isup.lua b/otouto/plugins/isup.lua index bdda1ae..b0f37d6 100644 --- a/otouto/plugins/isup.lua +++ b/otouto/plugins/isup.lua @@ -7,37 +7,37 @@ local utilities = require('otouto.utilities') local isup = {} function isup:init(config) - isup.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('websitedown', true):t('isitup', true):t('isup', true).table + isup.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('websitedown', true):t('isitup', true):t('isup', true).table - isup.doc = config.cmd_pat .. [[isup + isup.doc = config.cmd_pat .. [[isup Returns the up or down status of a website.]] - isup.command = 'isup ' + isup.command = 'isup ' end function isup:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, isup.doc) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, isup.doc) + return + end - local protocol = HTTP - local url_lower = input:lower() - if url_lower:match('^https') then - protocol = HTTPS - elseif not url_lower:match('^http') then - input = 'http://' .. input - end - local _, code = protocol.request(input) - code = tonumber(code) - local output - if not code or code > 399 then - output = 'This website is down or nonexistent.' - else - output = 'This website is up.' - end - utilities.send_reply(self, msg, output, true) + local protocol = HTTP + local url_lower = input:lower() + if url_lower:match('^https') then + protocol = HTTPS + elseif not url_lower:match('^http') then + input = 'http://' .. input + end + local _, code = protocol.request(input) + code = tonumber(code) + local output + if not code or code > 399 then + output = 'This website is down or nonexistent.' + else + output = 'This website is up.' + end + utilities.send_reply(self, msg, output, true) end return isup diff --git a/otouto/plugins/lastfm.lua b/otouto/plugins/lastfm.lua index 76b47ed..2c14b2d 100644 --- a/otouto/plugins/lastfm.lua +++ b/otouto/plugins/lastfm.lua @@ -9,12 +9,12 @@ local JSON = require('dkjson') local utilities = require('otouto.utilities') function lastfm:init(config) - assert(config.lastfm_api_key, - 'lastfm.lua requires a last.fm API key from http://last.fm/api.' - ) + assert(config.lastfm_api_key, + 'lastfm.lua requires a last.fm API key from http://last.fm/api.' + ) - lastfm.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('fmset', true).table - lastfm.doc = config.cmd_pat .. [[np [username] + lastfm.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('fmset', true).table + lastfm.doc = config.cmd_pat .. [[np [username] Returns what you are or were last listening to. If you specify a username, info will be returned for that username. ]] .. config.cmd_pat .. [[fmset @@ -25,84 +25,84 @@ lastfm.command = 'lastfm' function lastfm:action(msg, config) - local input = utilities.input(msg.text) - local from_id_str = tostring(msg.from.id) - self.database.userdata[from_id_str] = self.database.userdata[from_id_str] or {} + local input = utilities.input(msg.text) + local from_id_str = tostring(msg.from.id) + self.database.userdata[from_id_str] = self.database.userdata[from_id_str] or {} - if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then - utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) - return - elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then - if not input then - utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) - elseif input == '--' or input == utilities.char.em_dash then - self.database.userdata[from_id_str].lastfm = nil - utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.') - else - self.database.userdata[from_id_str].lastfm = input - utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".') - end - return - end + if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then + utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) + return + elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then + if not input then + utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) + elseif input == '--' or input == utilities.char.em_dash then + self.database.userdata[from_id_str].lastfm = nil + utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.') + else + self.database.userdata[from_id_str].lastfm = input + utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".') + end + return + end - local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user=' + local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user=' - local username - local alert = '' - if input then - username = input - elseif self.database.userdata[from_id_str].lastfm then - username = self.database.userdata[from_id_str].lastfm - elseif msg.from.username then - username = msg.from.username - alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use '..config.cmd_pat..'fmset .' - self.database.userdata[from_id_str].lastfm = username - else - utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') - return - end + local username + local alert = '' + if input then + username = input + elseif self.database.userdata[from_id_str].lastfm then + username = self.database.userdata[from_id_str].lastfm + elseif msg.from.username then + username = msg.from.username + alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use '..config.cmd_pat..'fmset .' + self.database.userdata[from_id_str].lastfm = username + else + utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') + return + end - url = url .. URL.escape(username) + url = url .. URL.escape(username) - local jstr, res - utilities.with_http_timeout( - 1, function () - jstr, res = HTTP.request(url) - end) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local jstr, res + utilities.with_http_timeout( + 1, function () + jstr, res = HTTP.request(url) + end) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local jdat = JSON.decode(jstr) - if jdat.error then - utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') - return - end + local jdat = JSON.decode(jstr) + if jdat.error then + utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') + return + end - jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track - if not jdat then - utilities.send_reply(self, msg, 'No history for this user.' .. alert) - return - end + jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track + if not jdat then + utilities.send_reply(self, msg, 'No history for this user.' .. alert) + return + end - local output = input or msg.from.first_name - output = '🎵 ' .. output + local output = input or msg.from.first_name + output = '🎵 ' .. output - if jdat['@attr'] and jdat['@attr'].nowplaying then - output = output .. ' is currently listening to:\n' - else - output = output .. ' last listened to:\n' - end + if jdat['@attr'] and jdat['@attr'].nowplaying then + output = output .. ' is currently listening to:\n' + else + output = output .. ' last listened to:\n' + end - local title = jdat.name or 'Unknown' - local artist = 'Unknown' - if jdat.artist then - artist = jdat.artist['#text'] - end + local title = jdat.name or 'Unknown' + local artist = 'Unknown' + if jdat.artist then + artist = jdat.artist['#text'] + end - output = output .. title .. ' - ' .. artist .. alert - utilities.send_message(self, msg.chat.id, output) + output = output .. title .. ' - ' .. artist .. alert + utilities.send_message(self, msg.chat.id, output) end diff --git a/otouto/plugins/luarun.lua b/otouto/plugins/luarun.lua index dfa4b96..add9222 100644 --- a/otouto/plugins/luarun.lua +++ b/otouto/plugins/luarun.lua @@ -5,59 +5,59 @@ local URL = require('socket.url') local JSON, serpent function luarun:init(config) - luarun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lua', true):t('return', true).table - if config.luarun_serpent then - serpent = require('serpent') - luarun.serialize = function(t) - return serpent.block(t, {comment=false}) - end - else - JSON = require('dkjson') - luarun.serialize = function(t) - return JSON.encode(t, {indent=true}) - end - end + luarun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lua', true):t('return', true).table + if config.luarun_serpent then + serpent = require('serpent') + luarun.serialize = function(t) + return serpent.block(t, {comment=false}) + end + else + JSON = require('dkjson') + luarun.serialize = function(t) + return JSON.encode(t, {indent=true}) + end + end end function luarun:action(msg, config) - if msg.from.id ~= config.admin then - return true - end + if msg.from.id ~= config.admin then + return true + end - local input = utilities.input(msg.text) - if not input then - utilities.send_reply(self, msg, 'Please enter a string to load.') - return - end + local input = utilities.input(msg.text) + if not input then + utilities.send_reply(self, msg, 'Please enter a string to load.') + return + end - if msg.text_lower:match('^'..config.cmd_pat..'return') then - input = 'return ' .. input - end + if msg.text_lower:match('^'..config.cmd_pat..'return') then + input = 'return ' .. input + end - local output = loadstring( [[ - local bot = require('otouto.bot') - local bindings = require('otouto.bindings') - local utilities = require('otouto.utilities') - local drua = require('otouto.drua-tg') - local JSON = require('dkjson') - local URL = require('socket.url') - local HTTP = require('socket.http') - local HTTPS = require('ssl.https') - return function (self, msg, config) ]] .. input .. [[ end - ]] )()(self, msg, config) - if output == nil then - output = 'Done!' - else - if type(output) == 'table' then - local s = luarun.serialize(output) - if URL.escape(s):len() < 4000 then - output = s - end - end - output = '```\n' .. tostring(output) .. '\n```' - end - utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) + local output = loadstring( [[ + local bot = require('otouto.bot') + local bindings = require('otouto.bindings') + local utilities = require('otouto.utilities') + local drua = require('otouto.drua-tg') + local JSON = require('dkjson') + local URL = require('socket.url') + local HTTP = require('socket.http') + local HTTPS = require('ssl.https') + return function (self, msg, config) ]] .. input .. [[ end + ]] )()(self, msg, config) + if output == nil then + output = 'Done!' + else + if type(output) == 'table' then + local s = luarun.serialize(output) + if URL.escape(s):len() < 4000 then + output = s + end + end + output = '```\n' .. tostring(output) .. '\n```' + end + utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) end diff --git a/otouto/plugins/me.lua b/otouto/plugins/me.lua index 67afd38..668ecb4 100644 --- a/otouto/plugins/me.lua +++ b/otouto/plugins/me.lua @@ -3,65 +3,65 @@ local me = {} local utilities = require('otouto.utilities') function me:init(config) - me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table - me.command = 'me' - me.doc = 'Returns userdata stored by the bot.' + me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table + me.command = 'me' + me.doc = 'Returns userdata stored by the bot.' end function me:action(msg, config) - local user - if msg.from.id == config.admin then - if msg.reply_to_message then - user = msg.reply_to_message.from - else - local input = utilities.input(msg.text) - if input then - if tonumber(input) then - user = self.database.users[input] - if not user then - utilities.send_reply(self, msg, 'Unrecognized ID.') - return - end - elseif input:match('^@') then - user = utilities.resolve_username(self, input) - if not user then - utilities.send_reply(self, msg, 'Unrecognized username.') - return - end - else - utilities.send_reply(self, msg, 'Invalid username or ID.') - return - end - end - end - end - user = user or msg.from - local userdata = self.database.userdata[tostring(user.id)] or {} + local user + if msg.from.id == config.admin then + if msg.reply_to_message then + user = msg.reply_to_message.from + else + local input = utilities.input(msg.text) + if input then + if tonumber(input) then + user = self.database.users[input] + if not user then + utilities.send_reply(self, msg, 'Unrecognized ID.') + return + end + elseif input:match('^@') then + user = utilities.resolve_username(self, input) + if not user then + utilities.send_reply(self, msg, 'Unrecognized username.') + return + end + else + utilities.send_reply(self, msg, 'Invalid username or ID.') + return + end + end + end + end + user = user or msg.from + local userdata = self.database.userdata[tostring(user.id)] or {} - local data = {} - for k,v in pairs(userdata) do - table.insert(data, string.format( - '%s %s\n', - utilities.html_escape(k), - utilities.html_escape(v) - )) - end + local data = {} + for k,v in pairs(userdata) do + table.insert(data, string.format( + '%s %s\n', + utilities.html_escape(k), + utilities.html_escape(v) + )) + end - local output - if #data == 0 then - output = 'There is no data stored for this user.' - else - output = string.format( - '%s [%s]:\n', - utilities.html_escape(utilities.build_name( - user.first_name, - user.last_name - )), - user.id - ) .. table.concat(data) - end + local output + if #data == 0 then + output = 'There is no data stored for this user.' + else + output = string.format( + '%s [%s]:\n', + utilities.html_escape(utilities.build_name( + user.first_name, + user.last_name + )), + user.id + ) .. table.concat(data) + end - utilities.send_message(self, msg.chat.id, output, true, nil, 'html') + utilities.send_message(self, msg.chat.id, output, true, nil, 'html') end diff --git a/otouto/plugins/nick.lua b/otouto/plugins/nick.lua index 9812aa7..69e3e53 100644 --- a/otouto/plugins/nick.lua +++ b/otouto/plugins/nick.lua @@ -5,45 +5,45 @@ local utilities = require('otouto.utilities') nick.command = 'nick ' function nick:init(config) - nick.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('nick', true).table - nick.doc = config.cmd_pat .. [[nick + nick.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('nick', true).table + nick.doc = config.cmd_pat .. [[nick Set your nickname. Use "]] .. config.cmd_pat .. 'nick --" to delete it.' end function nick:action(msg, config) - local id_str, name + local id_str, name - if msg.from.id == config.admin and msg.reply_to_message then - id_str = tostring(msg.reply_to_message.from.id) - name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) - else - id_str = tostring(msg.from.id) - name = utilities.build_name(msg.from.first_name, msg.from.last_name) - end + if msg.from.id == config.admin and msg.reply_to_message then + id_str = tostring(msg.reply_to_message.from.id) + name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) + else + id_str = tostring(msg.from.id) + name = utilities.build_name(msg.from.first_name, msg.from.last_name) + end - self.database.userdata[id_str] = self.database.userdata[id_str] or {} + self.database.userdata[id_str] = self.database.userdata[id_str] or {} - local output - local input = utilities.input(msg.text) - if not input then - if self.database.userdata[id_str].nickname then - output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".' - else - output = name .. ' currently has no nickname.' - end - elseif utilities.utf8_len(input) > 32 then - output = 'The character limit for nicknames is 32.' - elseif input == '--' or input == utilities.char.em_dash then - self.database.userdata[id_str].nickname = nil - output = name .. '\'s nickname has been deleted.' - else - input = input:gsub('\n', ' ') - self.database.userdata[id_str].nickname = input - output = name .. '\'s nickname has been set to "' .. input .. '".' - end + local output + local input = utilities.input(msg.text) + if not input then + if self.database.userdata[id_str].nickname then + output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".' + else + output = name .. ' currently has no nickname.' + end + elseif utilities.utf8_len(input) > 32 then + output = 'The character limit for nicknames is 32.' + elseif input == '--' or input == utilities.char.em_dash then + self.database.userdata[id_str].nickname = nil + output = name .. '\'s nickname has been deleted.' + else + input = input:gsub('\n', ' ') + self.database.userdata[id_str].nickname = input + output = name .. '\'s nickname has been set to "' .. input .. '".' + end - utilities.send_reply(self, msg, output) + utilities.send_reply(self, msg, output) end diff --git a/otouto/plugins/patterns.lua b/otouto/plugins/patterns.lua index 7480cf2..d23b8e9 100644 --- a/otouto/plugins/patterns.lua +++ b/otouto/plugins/patterns.lua @@ -8,34 +8,34 @@ patterns.doc = [[ s// Replace all matches for the given pattern. Uses Lua patterns. - ]] + ]] function patterns:init(config) - patterns.triggers = { config.cmd_pat .. '?s/.-/.-$' } + patterns.triggers = { config.cmd_pat .. '?s/.-/.-$' } end function patterns:action(msg) - if not msg.reply_to_message then return true end - local output = msg.reply_to_message.text - if msg.reply_to_message.from.id == self.info.id then - output = output:gsub('Did you mean:\n"', '') - output = output:gsub('"$', '') - end - local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$') - if not m2 then return true end - local res - res, output = pcall( - function() - return output:gsub(m1, m2) - end - ) - if res == false then - utilities.send_reply(self, msg, 'Malformed pattern!') - else - output = utilities.trim(output:sub(1, 4000)) - output = utilities.style.enquote('Did you mean', output) - utilities.send_reply(self, msg.reply_to_message, output, true) - end + if not msg.reply_to_message then return true end + local output = msg.reply_to_message.text + if msg.reply_to_message.from.id == self.info.id then + output = output:gsub('Did you mean:\n"', '') + output = output:gsub('"$', '') + end + local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$') + if not m2 then return true end + local res + res, output = pcall( + function() + return output:gsub(m1, m2) + end + ) + if res == false then + utilities.send_reply(self, msg, 'Malformed pattern!') + else + output = utilities.trim(output:sub(1, 4000)) + output = utilities.style.enquote('Did you mean', output) + utilities.send_reply(self, msg.reply_to_message, output, true) + end end return patterns diff --git a/otouto/plugins/ping.lua b/otouto/plugins/ping.lua index c368c33..26913e6 100644 --- a/otouto/plugins/ping.lua +++ b/otouto/plugins/ping.lua @@ -5,12 +5,12 @@ local ping = {} local utilities = require('otouto.utilities') function ping:init(config) - ping.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ping'):t('annyong').table + ping.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ping'):t('annyong').table end function ping:action(msg, config) - local output = msg.text_lower:match('^'..config.cmd_pat..'ping') and 'Pong!' or 'Annyong.' - utilities.send_message(self, msg.chat.id, output) + local output = msg.text_lower:match('^'..config.cmd_pat..'ping') and 'Pong!' or 'Annyong.' + utilities.send_message(self, msg.chat.id, output) end return ping diff --git a/otouto/plugins/pokedex.lua b/otouto/plugins/pokedex.lua index 8942e60..8e08927 100644 --- a/otouto/plugins/pokedex.lua +++ b/otouto/plugins/pokedex.lua @@ -8,8 +8,8 @@ local utilities = require('otouto.utilities') pokedex.command = 'pokedex ' function pokedex:init(config) - pokedex.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pokedex', true):t('dex', true).table - pokedex.doc = config.cmd_pat .. [[pokedex + pokedex.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pokedex', true):t('dex', true).table + pokedex.doc = config.cmd_pat .. [[pokedex Returns a Pokedex entry from pokeapi.co. Queries must be a number of the name of a Pokémon. Alias: ]] .. config.cmd_pat .. 'dex' @@ -17,54 +17,54 @@ end function pokedex:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, pokedex.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, pokedex.doc, true) + return + end - bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) + bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) - local url = 'http://pokeapi.co' + local url = 'http://pokeapi.co' - local dex_url = url .. '/api/v1/pokemon/' .. input - local dex_jstr, res = HTTP.request(dex_url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local dex_url = url .. '/api/v1/pokemon/' .. input + local dex_jstr, res = HTTP.request(dex_url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local dex_jdat = JSON.decode(dex_jstr) + local dex_jdat = JSON.decode(dex_jstr) - if not dex_jdat.descriptions or not dex_jdat.descriptions[1] then - utilities.send_reply(self, msg, config.errors.results) - return - end + if not dex_jdat.descriptions or not dex_jdat.descriptions[1] then + utilities.send_reply(self, msg, config.errors.results) + return + end - local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri - local desc_jstr, _ = HTTP.request(desc_url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri + local desc_jstr, _ = HTTP.request(desc_url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local desc_jdat = JSON.decode(desc_jstr) + local desc_jdat = JSON.decode(desc_jstr) - local poke_type - for _,v in ipairs(dex_jdat.types) do - local type_name = v.name:gsub("^%l", string.upper) - if not poke_type then - poke_type = type_name - else - poke_type = poke_type .. ' / ' .. type_name - end - end - poke_type = poke_type .. ' type' + local poke_type + for _,v in ipairs(dex_jdat.types) do + local type_name = v.name:gsub("^%l", string.upper) + if not poke_type then + poke_type = type_name + else + poke_type = poke_type .. ' / ' .. type_name + end + end + poke_type = poke_type .. ' type' - local output = '*' .. dex_jdat.name .. '*\n#' .. dex_jdat.national_id .. ' | ' .. poke_type .. '\n_' .. desc_jdat.description:gsub('POKMON', 'Pokémon'):gsub('Pokmon', 'Pokémon') .. '_' + local output = '*' .. dex_jdat.name .. '*\n#' .. dex_jdat.national_id .. ' | ' .. poke_type .. '\n_' .. desc_jdat.description:gsub('POKMON', 'Pokémon'):gsub('Pokmon', 'Pokémon') .. '_' - utilities.send_message(self, msg.chat.id, output, true, nil, true) + utilities.send_message(self, msg.chat.id, output, true, nil, true) end diff --git a/otouto/plugins/pokego-calculator.lua b/otouto/plugins/pokego-calculator.lua index 1690edd..4ba4866 100644 --- a/otouto/plugins/pokego-calculator.lua +++ b/otouto/plugins/pokego-calculator.lua @@ -3,110 +3,110 @@ local utilities = require('otouto.utilities') local pgc = {} function pgc:init(config) - pgc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('gocalc', true).table - pgc.doc = config.cmd_pat .. [[gocalc + pgc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('gocalc', true).table + pgc.doc = config.cmd_pat .. [[gocalc Calculates the number of Pokémon that must be transferred before evolving, how many evolutions the user is able to perform, and how many Pokémon and candy will be left over. All arguments must be positive numbers. Batch jobs may be performed by separating valid sets of arguments by lines. Example (forty pidgeys and three hundred pidgey candies): ]] .. config.cmd_pat .. 'gocalc 12 40 300' - pgc.command = 'gocalc <#pokemon> <#candy>' + pgc.command = 'gocalc <#pokemon> <#candy>' end -- This function written by Juan Potato. MIT-licensed. local pidgey_calc = function(candies_to_evolve, mons, candies) - local transferred = 0; - local evolved = 0; + local transferred = 0; + local evolved = 0; - while true do - if math.floor(candies / candies_to_evolve) == 0 or mons == 0 then - break - else - mons = mons - 1 - candies = candies - candies_to_evolve + 1 - evolved = evolved + 1 - if mons == 0 then - break - end - end - end + while true do + if math.floor(candies / candies_to_evolve) == 0 or mons == 0 then + break + else + mons = mons - 1 + candies = candies - candies_to_evolve + 1 + evolved = evolved + 1 + if mons == 0 then + break + end + end + end - while true do - if (candies + mons) < (candies_to_evolve + 1) or mons == 0 then - break - end - while candies < candies_to_evolve do - transferred = transferred + 1 - mons = mons - 1 - candies = candies + 1 - end - mons = mons - 1 - candies = candies - candies_to_evolve + 1 - evolved = evolved + 1 - end + while true do + if (candies + mons) < (candies_to_evolve + 1) or mons == 0 then + break + end + while candies < candies_to_evolve do + transferred = transferred + 1 + mons = mons - 1 + candies = candies + 1 + end + mons = mons - 1 + candies = candies - candies_to_evolve + 1 + evolved = evolved + 1 + end - return { - transfer = transferred, - evolve = evolved, - leftover_mons = mons, - leftover_candy = candies - } + return { + transfer = transferred, + evolve = evolved, + leftover_mons = mons, + leftover_candy = candies + } end local single_job = function(input) - local req_candy, mons, candies = input:match('^(%d+) (%d+) (%d+)$') - req_candy = tonumber(req_candy) - mons = tonumber(mons) - candies = tonumber(candies) - if not (req_candy and mons and candies) then - return { err = 'Invalid input: Three numbers expected.' } - elseif req_candy > 400 then - return { err = 'Invalid required candy: Maximum is 400.' } - elseif mons > 1000 then - return { err = 'Invalid number of Pokémon: Maximum is 1000.' } - elseif candies > 10000 then - return { err = 'Invalid number of candies: Maximum is 10000.' } - else - return pidgey_calc(req_candy, mons, candies) - end + local req_candy, mons, candies = input:match('^(%d+) (%d+) (%d+)$') + req_candy = tonumber(req_candy) + mons = tonumber(mons) + candies = tonumber(candies) + if not (req_candy and mons and candies) then + return { err = 'Invalid input: Three numbers expected.' } + elseif req_candy > 400 then + return { err = 'Invalid required candy: Maximum is 400.' } + elseif mons > 1000 then + return { err = 'Invalid number of Pokémon: Maximum is 1000.' } + elseif candies > 10000 then + return { err = 'Invalid number of candies: Maximum is 10000.' } + else + return pidgey_calc(req_candy, mons, candies) + end end function pgc:action(msg) - local input = utilities.input(msg.text) - if not input then - utilities.send_reply(self, msg, pgc.doc, true) - return - end - input = input .. '\n' - local output = '' - local total_evolutions = 0 - for line in input:gmatch('(.-)\n') do - local info = single_job(line) - output = output .. '`' .. line .. '`\n' - if info.err then - output = output .. info.err .. '\n\n' - else - total_evolutions = total_evolutions + info.evolve - local s = '*Transfer:* %s. \n*Evolve:* %s (%s XP, %s minutes). \n*Leftover:* %s mons, %s candy.\n\n' - s = s:format(info.transfer, info.evolve, info.evolve..'k', info.evolve*0.5, info.leftover_mons, info.leftover_candy) - output = output .. s - end - end - local s = '*Total evolutions:* %s. \n*Recommendation:* %s' - local recommendation - local egg_count = math.floor(total_evolutions/60) - if egg_count < 1 then - recommendation = 'Wait until you have atleast sixty Pokémon to evolve before using a lucky egg.' - else - recommendation = string.format( - 'Use %s lucky egg%s for %s evolutions.', - egg_count, - egg_count == 1 and '' or 's', - egg_count * 60 - ) - end - s = s:format(total_evolutions, recommendation) - output = output .. s - utilities.send_reply(self, msg, output, true) + local input = utilities.input(msg.text) + if not input then + utilities.send_reply(self, msg, pgc.doc, true) + return + end + input = input .. '\n' + local output = '' + local total_evolutions = 0 + for line in input:gmatch('(.-)\n') do + local info = single_job(line) + output = output .. '`' .. line .. '`\n' + if info.err then + output = output .. info.err .. '\n\n' + else + total_evolutions = total_evolutions + info.evolve + local s = '*Transfer:* %s. \n*Evolve:* %s (%s XP, %s minutes). \n*Leftover:* %s mons, %s candy.\n\n' + s = s:format(info.transfer, info.evolve, info.evolve..'k', info.evolve*0.5, info.leftover_mons, info.leftover_candy) + output = output .. s + end + end + local s = '*Total evolutions:* %s. \n*Recommendation:* %s' + local recommendation + local egg_count = math.floor(total_evolutions/60) + if egg_count < 1 then + recommendation = 'Wait until you have atleast sixty Pokémon to evolve before using a lucky egg.' + else + recommendation = string.format( + 'Use %s lucky egg%s for %s evolutions.', + egg_count, + egg_count == 1 and '' or 's', + egg_count * 60 + ) + end + s = s:format(total_evolutions, recommendation) + output = output .. s + utilities.send_reply(self, msg, output, true) end return pgc diff --git a/otouto/plugins/pokemon-go.lua b/otouto/plugins/pokemon-go.lua index 51cc909..79b4489 100644 --- a/otouto/plugins/pokemon-go.lua +++ b/otouto/plugins/pokemon-go.lua @@ -21,7 +21,7 @@ Set your Pokémon Go team for statistical purposes. The team must be valid, and db.membership = {} end for _, set in pairs(db.membership) do - setmetatable(set, utilities.set_meta) + setmetatable(set, utilities.set_meta) end end diff --git a/otouto/plugins/preview.lua b/otouto/plugins/preview.lua index 802a0f0..2b1a350 100644 --- a/otouto/plugins/preview.lua +++ b/otouto/plugins/preview.lua @@ -6,37 +6,37 @@ local utilities = require('otouto.utilities') preview.command = 'preview ' function preview:init(config) - preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table - preview.doc = config.cmd_pat .. 'preview \nReturns a full-message, "unlinked" preview.' + preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table + preview.doc = config.cmd_pat .. 'preview \nReturns a full-message, "unlinked" preview.' end function preview:action(msg) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, preview.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, preview.doc, true) + return + end - input = utilities.get_word(input, 1) - if not input:match('^https?://.+') then - input = 'http://' .. input - end + input = utilities.get_word(input, 1) + if not input:match('^https?://.+') then + input = 'http://' .. input + end - local res = HTTP.request(input) - if not res then - utilities.send_reply(self, msg, 'Please provide a valid link.') - return - end + local res = HTTP.request(input) + if not res then + utilities.send_reply(self, msg, 'Please provide a valid link.') + return + end - if res:len() == 0 then - utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.') - return - end + if res:len() == 0 then + utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.') + return + end - -- Invisible zero-width, non-joiner. - local output = '' .. utilities.char.zwnj .. '' - utilities.send_message(self, msg.chat.id, output, false, nil, 'html') + -- Invisible zero-width, non-joiner. + local output = '' .. utilities.char.zwnj .. '' + utilities.send_message(self, msg.chat.id, output, false, nil, 'html') end diff --git a/otouto/plugins/pun.lua b/otouto/plugins/pun.lua index 5063096..702ca9a 100644 --- a/otouto/plugins/pun.lua +++ b/otouto/plugins/pun.lua @@ -6,138 +6,138 @@ pun.command = 'pun' pun.doc = 'Returns a pun.' function pun:init(config) - pun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pun').table + pun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pun').table end local puns = { - "The person who invented the door-knock won the No-bell prize.", - "I couldn't work out how to fasten my seatbelt. Then it clicked.", - "Never trust atoms; they make up everything.", - "Singing in the shower is all fun and games until you get shampoo in your mouth - Then it becomes a soap opera.", - "I can't believe I got fired from the calendar factory. All I did was take a day off.", - "To the guy who invented zero: Thanks for nothing!", - "Enough with the cripple jokes! I just can't stand them.", - "I've accidentally swallowed some Scrabble tiles. My next crap could spell disaster.", - "How does Moses make his tea? Hebrews it.", - "Did you hear about the guy who got hit in the head with a can of soda? He was lucky it was a soft drink.", - "When William joined the army he disliked the phrase 'fire at will'.", - "There was a sign on the lawn at a rehab center that said 'Keep off the Grass'.", - "I wondered why the baseball was getting bigger. Then it hit me.", - "I can hear music coming out of my printer. I think the paper's jamming again.", - "I have a few jokes about unemployed people, but none of them work", - "Want to hear a construction joke? I'm working on it", - "I always take a second pair of pants when I go golfing, in case I get a hole in one.", - "I couldn't remember how to throw a boomerang, but then it came back to me.", - "I've decided that my wifi will be my valentine. IDK, we just have this connection.", - "A prisoner's favorite punctuation mark is the period. It marks the end of his sentence.", - "I used to go fishing with Skrillex, but he kept dropping the bass.", - "Two antennae met on a roof and got married. The wedding was okay, but the reception was incredible.", - "A book just fell on my head. I've only got my shelf to blame.", - "I dropped my steak on the floor. Now it's ground beef.", - "I used to have a fear of hurdles, but I got over it.", - "The outcome of war does not prove who is right, but only who is left.", - "Darth Vader tries not to burn his food, but it always comes out a little on the dark side.", - "The store keeps calling me to buy more furniture, but all I wanted was a one night stand.", - "This girl said she recognized me from the vegetarian club, but I'd never met herbivore.", - "Police arrested two kids yesterday, one was drinking battery acid, the other was eating fireworks. They charged one and let the other one off...", - "No more Harry Potter jokes guys. I'm Sirius.", - "It was hard getting over my addiction to hokey pokey, but I've turned myself around.", - "It takes a lot of balls to golf the way I do.", - "Why did everyone want to hang out with the mushroom? Because he was a fungi.", - "How much does a hipster weigh? An instagram.", - "I used to be addicted to soap, but I'm clean now.", - "When life gives you melons, you’re probably dyslexic.", - "What's with all the blind jokes? I just don't see the point.", - "If Apple made a car, would it have Windows?", - "Need an ark? I Noah guy.", - "The scarecrow won an award because he was outstanding in his field.", - "What's the difference between a man in a tux on a bicycle, and a man in a sweatsuit on a trycicle? A tire.", - "What do you do with a sick chemist? If you can't helium, and you can't curium, you'll just have to barium.", - "I'm reading a book about anti-gravity. It's impossible to put down.", - "Trying to write with a broken pencil is pointless.", - "When TVs go on vacation, they travel to remote islands.", - "I was going to tell a midget joke, but it's too short.", - "Jokes about German sausage are the wurst.", - "How do you organize a space party? You planet.", - "Sleeping comes so naturally to me, I could do it with my eyes closed.", - "I'm glad I know sign language; it's pretty handy.", - "Atheism is a non-prophet organization.", - "Velcro: What a rip-off!", - "If they made a Minecraft movie, it would be a blockbuster.", - "I don't trust people with graph paper. They're always plotting something", - "I had a friend who was addicted to brake fluid. He says he can stop anytime.", - "The form said I had Type A blood, but it was a Type O.", - "I went to to the shop to buy eight Sprites - I came home and realised I'd picked 7Up.", - "There was an explosion at a pie factory. 3.14 people died.", - "A man drove his car into a tree and found out how a Mercedes bends.", - "The experienced carpenter really nailed it, but the new guy screwed everything up.", - "I didn't like my beard at first, but then it grew on me.", - "Smaller babies may be delivered by stork, but the heavier ones need a crane.", - "What's the definition of a will? It's a dead giveaway.", - "I was going to look for my missing watch, but I could never find the time.", - "I hate elevators, and I often take steps to avoid them.", - "Did you hear about the guy whose whole left side was cut off? He's all right now.", - "It's not that the man did not know how to juggle, he just didn't have the balls to do it.", - "I used to be a loan shark, but I lost interest", - "I don't trust these stairs; they're always up to something.", - "My friend's bakery burned down last night. Now his business is toast.", - "Don't trust people that do acupuncture; they're back stabbers.", - "The man who survived mustard gas and pepper spray is now a seasoned veteran.", - "Police were called to a daycare where a three-year-old was resisting a rest.", - "When Peter Pan punches, they Neverland", - "The shoemaker did not deny his apprentice anything he needed. He gave him his awl.", - "I did a theatrical performance about puns. It was a play on words.", - "Show me a piano falling down a mineshaft and I'll show you A-flat minor.", - "Have you ever tried to eat a clock? It's very time consuming.", - "There was once a cross-eyed teacher who couldn't control his pupils.", - "A new type of broom came out and it is sweeping the nation.", - "I relish the fact that you've mustard the strength to ketchup to me.", - "I knew a woman who owned a taser. Man, was she stunning!", - "What did the grape say when it got stepped on? Nothing - but it let out a little whine.", - "It was an emotional wedding. Even the cake was in tiers.", - "When a clock is hungry it goes back four seconds.", - "The dead batteries were given out free of charge.", - "Why are there no knock-knock jokes about America? Because freedom rings.", - "When the cannibal showed up late to dinner, they gave him the cold shoulder.", - "I should have been sad when my flashlight died, but I was delighted.", - "Why don't tennis players ever get married? Love means nothing to them.", - "Pterodactyls can't be heard going to the bathroom because the P is silent.", - "Mermaids make calls on their shell phones.", - "What do you call an aardvark with three feet? A yaardvark.", - "Captain Kirk has three ears: A right ear, a left ear, and a final front ear.", - "How do celebrities stay cool? They have a lot of fans.", - "Without geometry, life is pointless.", - "Did you hear about the cow who tried to jump over a barbed-wire fence? It ended in udder destruction.", - "The truth may ring like a bell, but it is seldom ever tolled.", - "I used to work for the IRS, but my job was too taxing.", - "I used to be a programmer, but then I lost my drive.", - "Pediatricians are doctors with little patients.", - "I finally fired my masseuse today. She always rubbed me the wrong way.", - "I stayed up all night wondering where the sun went. Then it dawned on me.", - "What's the difference between a man and his dog? The man wears a suit; the dog just pants.", - "A psychic midget who escapes from prison is a small medium at large.", - "I've been to the dentist several times, so I know the drill.", - "The roundest knight at King Arthur's round table was Sir Cumference. He acquired his size from too much pi.", - "She was only a whiskey maker, but he loved her still.", - "Male deer have buck teeth.", - "Whiteboards are remarkable.", - "Visitors in Cuba are always Havana good time.", - "Why does electricity shock people? It doesn't know how to conduct itself.", - "Lancelot had a scary dream about his horse. It was a knight mare.", - "A tribe of cannibals captured a missionary and ate him. Afterward, they all had violent food poisoning. This just goes to show that you can't keep a good man down.", - "Heaven for gamblers is a paradise.", - "Old wheels aren't thrown away, they're just retired.", - "Horses are very stable animals.", - "Banks don't crash, they just lose their balance.", - "The career of a skier can go downhill very fast.", - "In democracy, it's your vote that counts. In feudalism, it's your count that votes.", - "A sea lion is nothing but an ionized seal.", - "The vegetables from my garden aren't that great. I guess you could say they're mediokra." + "The person who invented the door-knock won the No-bell prize.", + "I couldn't work out how to fasten my seatbelt. Then it clicked.", + "Never trust atoms; they make up everything.", + "Singing in the shower is all fun and games until you get shampoo in your mouth - Then it becomes a soap opera.", + "I can't believe I got fired from the calendar factory. All I did was take a day off.", + "To the guy who invented zero: Thanks for nothing!", + "Enough with the cripple jokes! I just can't stand them.", + "I've accidentally swallowed some Scrabble tiles. My next crap could spell disaster.", + "How does Moses make his tea? Hebrews it.", + "Did you hear about the guy who got hit in the head with a can of soda? He was lucky it was a soft drink.", + "When William joined the army he disliked the phrase 'fire at will'.", + "There was a sign on the lawn at a rehab center that said 'Keep off the Grass'.", + "I wondered why the baseball was getting bigger. Then it hit me.", + "I can hear music coming out of my printer. I think the paper's jamming again.", + "I have a few jokes about unemployed people, but none of them work", + "Want to hear a construction joke? I'm working on it", + "I always take a second pair of pants when I go golfing, in case I get a hole in one.", + "I couldn't remember how to throw a boomerang, but then it came back to me.", + "I've decided that my wifi will be my valentine. IDK, we just have this connection.", + "A prisoner's favorite punctuation mark is the period. It marks the end of his sentence.", + "I used to go fishing with Skrillex, but he kept dropping the bass.", + "Two antennae met on a roof and got married. The wedding was okay, but the reception was incredible.", + "A book just fell on my head. I've only got my shelf to blame.", + "I dropped my steak on the floor. Now it's ground beef.", + "I used to have a fear of hurdles, but I got over it.", + "The outcome of war does not prove who is right, but only who is left.", + "Darth Vader tries not to burn his food, but it always comes out a little on the dark side.", + "The store keeps calling me to buy more furniture, but all I wanted was a one night stand.", + "This girl said she recognized me from the vegetarian club, but I'd never met herbivore.", + "Police arrested two kids yesterday, one was drinking battery acid, the other was eating fireworks. They charged one and let the other one off...", + "No more Harry Potter jokes guys. I'm Sirius.", + "It was hard getting over my addiction to hokey pokey, but I've turned myself around.", + "It takes a lot of balls to golf the way I do.", + "Why did everyone want to hang out with the mushroom? Because he was a fungi.", + "How much does a hipster weigh? An instagram.", + "I used to be addicted to soap, but I'm clean now.", + "When life gives you melons, you’re probably dyslexic.", + "What's with all the blind jokes? I just don't see the point.", + "If Apple made a car, would it have Windows?", + "Need an ark? I Noah guy.", + "The scarecrow won an award because he was outstanding in his field.", + "What's the difference between a man in a tux on a bicycle, and a man in a sweatsuit on a trycicle? A tire.", + "What do you do with a sick chemist? If you can't helium, and you can't curium, you'll just have to barium.", + "I'm reading a book about anti-gravity. It's impossible to put down.", + "Trying to write with a broken pencil is pointless.", + "When TVs go on vacation, they travel to remote islands.", + "I was going to tell a midget joke, but it's too short.", + "Jokes about German sausage are the wurst.", + "How do you organize a space party? You planet.", + "Sleeping comes so naturally to me, I could do it with my eyes closed.", + "I'm glad I know sign language; it's pretty handy.", + "Atheism is a non-prophet organization.", + "Velcro: What a rip-off!", + "If they made a Minecraft movie, it would be a blockbuster.", + "I don't trust people with graph paper. They're always plotting something", + "I had a friend who was addicted to brake fluid. He says he can stop anytime.", + "The form said I had Type A blood, but it was a Type O.", + "I went to to the shop to buy eight Sprites - I came home and realised I'd picked 7Up.", + "There was an explosion at a pie factory. 3.14 people died.", + "A man drove his car into a tree and found out how a Mercedes bends.", + "The experienced carpenter really nailed it, but the new guy screwed everything up.", + "I didn't like my beard at first, but then it grew on me.", + "Smaller babies may be delivered by stork, but the heavier ones need a crane.", + "What's the definition of a will? It's a dead giveaway.", + "I was going to look for my missing watch, but I could never find the time.", + "I hate elevators, and I often take steps to avoid them.", + "Did you hear about the guy whose whole left side was cut off? He's all right now.", + "It's not that the man did not know how to juggle, he just didn't have the balls to do it.", + "I used to be a loan shark, but I lost interest", + "I don't trust these stairs; they're always up to something.", + "My friend's bakery burned down last night. Now his business is toast.", + "Don't trust people that do acupuncture; they're back stabbers.", + "The man who survived mustard gas and pepper spray is now a seasoned veteran.", + "Police were called to a daycare where a three-year-old was resisting a rest.", + "When Peter Pan punches, they Neverland", + "The shoemaker did not deny his apprentice anything he needed. He gave him his awl.", + "I did a theatrical performance about puns. It was a play on words.", + "Show me a piano falling down a mineshaft and I'll show you A-flat minor.", + "Have you ever tried to eat a clock? It's very time consuming.", + "There was once a cross-eyed teacher who couldn't control his pupils.", + "A new type of broom came out and it is sweeping the nation.", + "I relish the fact that you've mustard the strength to ketchup to me.", + "I knew a woman who owned a taser. Man, was she stunning!", + "What did the grape say when it got stepped on? Nothing - but it let out a little whine.", + "It was an emotional wedding. Even the cake was in tiers.", + "When a clock is hungry it goes back four seconds.", + "The dead batteries were given out free of charge.", + "Why are there no knock-knock jokes about America? Because freedom rings.", + "When the cannibal showed up late to dinner, they gave him the cold shoulder.", + "I should have been sad when my flashlight died, but I was delighted.", + "Why don't tennis players ever get married? Love means nothing to them.", + "Pterodactyls can't be heard going to the bathroom because the P is silent.", + "Mermaids make calls on their shell phones.", + "What do you call an aardvark with three feet? A yaardvark.", + "Captain Kirk has three ears: A right ear, a left ear, and a final front ear.", + "How do celebrities stay cool? They have a lot of fans.", + "Without geometry, life is pointless.", + "Did you hear about the cow who tried to jump over a barbed-wire fence? It ended in udder destruction.", + "The truth may ring like a bell, but it is seldom ever tolled.", + "I used to work for the IRS, but my job was too taxing.", + "I used to be a programmer, but then I lost my drive.", + "Pediatricians are doctors with little patients.", + "I finally fired my masseuse today. She always rubbed me the wrong way.", + "I stayed up all night wondering where the sun went. Then it dawned on me.", + "What's the difference between a man and his dog? The man wears a suit; the dog just pants.", + "A psychic midget who escapes from prison is a small medium at large.", + "I've been to the dentist several times, so I know the drill.", + "The roundest knight at King Arthur's round table was Sir Cumference. He acquired his size from too much pi.", + "She was only a whiskey maker, but he loved her still.", + "Male deer have buck teeth.", + "Whiteboards are remarkable.", + "Visitors in Cuba are always Havana good time.", + "Why does electricity shock people? It doesn't know how to conduct itself.", + "Lancelot had a scary dream about his horse. It was a knight mare.", + "A tribe of cannibals captured a missionary and ate him. Afterward, they all had violent food poisoning. This just goes to show that you can't keep a good man down.", + "Heaven for gamblers is a paradise.", + "Old wheels aren't thrown away, they're just retired.", + "Horses are very stable animals.", + "Banks don't crash, they just lose their balance.", + "The career of a skier can go downhill very fast.", + "In democracy, it's your vote that counts. In feudalism, it's your count that votes.", + "A sea lion is nothing but an ionized seal.", + "The vegetables from my garden aren't that great. I guess you could say they're mediokra." } function pun:action(msg) - utilities.send_reply(self, msg, puns[math.random(#puns)]) + utilities.send_reply(self, msg, puns[math.random(#puns)]) end diff --git a/otouto/plugins/reactions.lua b/otouto/plugins/reactions.lua index d814f93..6ea39f7 100644 --- a/otouto/plugins/reactions.lua +++ b/otouto/plugins/reactions.lua @@ -16,34 +16,34 @@ reactions.doc = 'Returns a list of "reaction" emoticon commands.' local help function reactions:init(config) - -- Generate a "help" message triggered by "/reactions". - help = 'Reactions:\n' - reactions.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('reactions').table - local username = self.info.username:lower() - for trigger,reaction in pairs(config.reactions) do - help = help .. '• ' .. config.cmd_pat .. trigger .. ': ' .. reaction .. '\n' - table.insert(reactions.triggers, '^'..config.cmd_pat..trigger) - table.insert(reactions.triggers, '^'..config.cmd_pat..trigger..'@'..username) - table.insert(reactions.triggers, config.cmd_pat..trigger..'$') - table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'$') - table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger) - table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger..'@'..username) - table.insert(reactions.triggers, config.cmd_pat..trigger..'\n') - table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'\n') - end + -- Generate a "help" message triggered by "/reactions". + help = 'Reactions:\n' + reactions.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('reactions').table + local username = self.info.username:lower() + for trigger,reaction in pairs(config.reactions) do + help = help .. '• ' .. config.cmd_pat .. trigger .. ': ' .. reaction .. '\n' + table.insert(reactions.triggers, '^'..config.cmd_pat..trigger) + table.insert(reactions.triggers, '^'..config.cmd_pat..trigger..'@'..username) + table.insert(reactions.triggers, config.cmd_pat..trigger..'$') + table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'$') + table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger) + table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger..'@'..username) + table.insert(reactions.triggers, config.cmd_pat..trigger..'\n') + table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'\n') + end end function reactions:action(msg, config) - if string.match(msg.text_lower, config.cmd_pat..'reactions') then - utilities.send_message(self, msg.chat.id, help) - return - end - for trigger,reaction in pairs(config.reactions) do - if string.match(msg.text_lower, config.cmd_pat..trigger) then - utilities.send_message(self, msg.chat.id, reaction) - return - end - end + if string.match(msg.text_lower, config.cmd_pat..'reactions') then + utilities.send_message(self, msg.chat.id, help) + return + end + for trigger,reaction in pairs(config.reactions) do + if string.match(msg.text_lower, config.cmd_pat..trigger) then + utilities.send_message(self, msg.chat.id, reaction) + return + end + end end return reactions diff --git a/otouto/plugins/reddit.lua b/otouto/plugins/reddit.lua index 8bdcb05..66db1db 100644 --- a/otouto/plugins/reddit.lua +++ b/otouto/plugins/reddit.lua @@ -8,29 +8,29 @@ local utilities = require('otouto.utilities') reddit.command = 'reddit [r/subreddit | query]' function reddit:init(config) - reddit.triggers = utilities.triggers(self.info.username, config.cmd_pat, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table - reddit.doc = config.cmd_pat .. [[reddit [r/subreddit | query] + reddit.triggers = utilities.triggers(self.info.username, config.cmd_pat, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table + reddit.doc = config.cmd_pat .. [[reddit [r/subreddit | query] Returns the top posts or results for a given subreddit or query. If no argument is given, returns the top posts from r/all. Querying specific subreddits is not supported. Aliases: ]] .. config.cmd_pat .. 'r, /r/subreddit' end local format_results = function(posts) - local output = '' - for _,v in ipairs(posts) do - local post = v.data - local title = post.title:gsub('%[', '('):gsub('%]', ')'):gsub('&', '&') - if title:len() > 256 then - title = title:sub(1, 253) - title = utilities.trim(title) .. '...' - end - local short_url = 'redd.it/' .. post.id - local s = '[' .. title .. '](' .. short_url .. ')' - if post.domain and not post.is_self and not post.over_18 then - s = '`[`[' .. post.domain .. '](' .. post.url:gsub('%)', '\\)') .. ')`]` ' .. s - end - output = output .. '• ' .. s .. '\n' - end - return output + local output = '' + for _,v in ipairs(posts) do + local post = v.data + local title = post.title:gsub('%[', '('):gsub('%]', ')'):gsub('&', '&') + if title:len() > 256 then + title = title:sub(1, 253) + title = utilities.trim(title) .. '...' + end + local short_url = 'redd.it/' .. post.id + local s = '[' .. title .. '](' .. short_url .. ')' + if post.domain and not post.is_self and not post.over_18 then + s = '`[`[' .. post.domain .. '](' .. post.url:gsub('%)', '\\)') .. ')`]` ' .. s + end + output = output .. '• ' .. s .. '\n' + end + return output end reddit.subreddit_url = 'http://www.reddit.com/%s/.json?limit=' @@ -38,46 +38,46 @@ reddit.search_url = 'http://www.reddit.com/search.json?q=%s&limit=' reddit.rall_url = 'http://www.reddit.com/.json?limit=' function reddit:action(msg, config) - -- Eight results in PM, four results elsewhere. - local limit = 4 - if msg.chat.type == 'private' then - limit = 8 - end - local text = msg.text_lower - if text:match('^/r/.') then - -- Normalize input so this hack works easily. - text = msg.text_lower:gsub('^/r/', config.cmd_pat..'r r/') - end - local input = utilities.input(text) - local source, url - if input then - if input:match('^r/.') then - input = utilities.get_word(input, 1) - url = reddit.subreddit_url:format(input) .. limit - source = '*/' .. utilities.md_escape(input) .. '*\n' - else - input = utilities.input(msg.text) - source = '*Results for* _' .. utilities.md_escape(input) .. '_ *:*\n' - input = URL.escape(input) - url = reddit.search_url:format(input) .. limit - end - else - url = reddit.rall_url .. limit - source = '*/r/all*\n' - end - local jstr, res = HTTP.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - else - local jdat = JSON.decode(jstr) - if #jdat.data.children == 0 then - utilities.send_reply(self, msg, config.errors.results) - else - local output = format_results(jdat.data.children) - output = source .. output - utilities.send_message(self, msg.chat.id, output, true, nil, true) - end - end + -- Eight results in PM, four results elsewhere. + local limit = 4 + if msg.chat.type == 'private' then + limit = 8 + end + local text = msg.text_lower + if text:match('^/r/.') then + -- Normalize input so this hack works easily. + text = msg.text_lower:gsub('^/r/', config.cmd_pat..'r r/') + end + local input = utilities.input(text) + local source, url + if input then + if input:match('^r/.') then + input = utilities.get_word(input, 1) + url = reddit.subreddit_url:format(input) .. limit + source = '*/' .. utilities.md_escape(input) .. '*\n' + else + input = utilities.input(msg.text) + source = '*Results for* _' .. utilities.md_escape(input) .. '_ *:*\n' + input = URL.escape(input) + url = reddit.search_url:format(input) .. limit + end + else + url = reddit.rall_url .. limit + source = '*/r/all*\n' + end + local jstr, res = HTTP.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + else + local jdat = JSON.decode(jstr) + if #jdat.data.children == 0 then + utilities.send_reply(self, msg, config.errors.results) + else + local output = format_results(jdat.data.children) + output = source .. output + utilities.send_message(self, msg.chat.id, output, true, nil, true) + end + end end return reddit diff --git a/otouto/plugins/remind.lua b/otouto/plugins/remind.lua index a9fd7d9..888ef9c 100644 --- a/otouto/plugins/remind.lua +++ b/otouto/plugins/remind.lua @@ -5,87 +5,87 @@ local utilities = require('otouto.utilities') remind.command = 'remind ' function remind:init(config) - self.database.reminders = self.database.reminders or {} + self.database.reminders = self.database.reminders or {} - remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table + remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table - config.remind = config.remind or {} - setmetatable(config.remind, { __index = function() return 1000 end }) + config.remind = config.remind or {} + setmetatable(config.remind, { __index = function() return 1000 end }) - remind.doc = config.cmd_pat .. [[remind + remind.doc = config.cmd_pat .. [[remind Repeats a message after a duration of time, in minutes. The maximum length of a reminder is %s characters. The maximum duration of a timer is %s minutes. The maximum number of reminders for a group is %s. The maximum number of reminders in private is %s.]] - remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private) + remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private) end function remind:action(msg, config) - local input = utilities.input(msg.text) - if not input then - utilities.send_reply(self, msg, remind.doc, true) - return - end + local input = utilities.input(msg.text) + if not input then + utilities.send_reply(self, msg, remind.doc, true) + return + end - local duration = tonumber(utilities.get_word(input, 1)) - if not duration then - utilities.send_reply(self, msg, remind.doc, true) - return - end + local duration = tonumber(utilities.get_word(input, 1)) + if not duration then + utilities.send_reply(self, msg, remind.doc, true) + return + end - if duration < 1 then - duration = 1 - elseif duration > config.remind.max_duration then - duration = config.remind.max_duration - end - local message = utilities.input(input) - if not message then - utilities.send_reply(self, msg, remind.doc, true) - return - end + if duration < 1 then + duration = 1 + elseif duration > config.remind.max_duration then + duration = config.remind.max_duration + end + local message = utilities.input(input) + if not message then + utilities.send_reply(self, msg, remind.doc, true) + return + end - if #message > config.remind.max_length then - utilities.send_reply(self, msg, 'The maximum length of reminders is ' .. config.remind.max_length .. '.') - return - end + if #message > config.remind.max_length then + utilities.send_reply(self, msg, 'The maximum length of reminders is ' .. config.remind.max_length .. '.') + return + end - local chat_id_str = tostring(msg.chat.id) - local output - self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {} - if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then - output = 'Sorry, you already have the maximum number of reminders.' - elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then - output = 'Sorry, this group already has the maximum number of reminders.' - else - table.insert(self.database.reminders[chat_id_str], { - time = os.time() + (duration * 60), - message = message - }) - output = string.format( - 'I will remind you in %s minute%s!', - duration, - duration == 1 and '' or 's' - ) - end - utilities.send_reply(self, msg, output, true) + local chat_id_str = tostring(msg.chat.id) + local output + self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {} + if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then + output = 'Sorry, you already have the maximum number of reminders.' + elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then + output = 'Sorry, this group already has the maximum number of reminders.' + else + table.insert(self.database.reminders[chat_id_str], { + time = os.time() + (duration * 60), + message = message + }) + output = string.format( + 'I will remind you in %s minute%s!', + duration, + duration == 1 and '' or 's' + ) + end + utilities.send_reply(self, msg, output, true) end function remind:cron(config) - local time = os.time() - -- Iterate over the group entries in the reminders database. - for chat_id, group in pairs(self.database.reminders) do - -- Iterate over each reminder. - for k, reminder in pairs(group) do - -- If the reminder is past-due, send it and nullify it. - -- Otherwise, add it to the replacement table. - if time > reminder.time then - local output = utilities.style.enquote('Reminder', reminder.message) - local res = utilities.send_message(self, chat_id, output, true, nil, true) - -- If the message fails to send, save it for later (if enabled in config). - if res or not config.remind.persist then - group[k] = nil - end - end - end - end + local time = os.time() + -- Iterate over the group entries in the reminders database. + for chat_id, group in pairs(self.database.reminders) do + -- Iterate over each reminder. + for k, reminder in pairs(group) do + -- If the reminder is past-due, send it and nullify it. + -- Otherwise, add it to the replacement table. + if time > reminder.time then + local output = utilities.style.enquote('Reminder', reminder.message) + local res = utilities.send_message(self, chat_id, output, true, nil, true) + -- If the message fails to send, save it for later (if enabled in config). + if res or not config.remind.persist then + group[k] = nil + end + end + end + end end return remind diff --git a/otouto/plugins/rmspic.lua b/otouto/plugins/rmspic.lua index 40cf66f..b52d6e5 100644 --- a/otouto/plugins/rmspic.lua +++ b/otouto/plugins/rmspic.lua @@ -5,30 +5,30 @@ local bindings = require('otouto.bindings') local rms = {} function rms:init(config) - rms.BASE_URL = 'https://rms.sexy/img/' - rms.LIST = {} - local s, r = https.request(rms.BASE_URL) - if r ~= 200 then - print('Error connecting to rms.sexy.\nrmspic.lua will not be enabled.') - return - end - for link in s:gmatch('(.-)') do - table.insert(rms.LIST, link) - end - rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table + rms.BASE_URL = 'https://rms.sexy/img/' + rms.LIST = {} + local s, r = https.request(rms.BASE_URL) + if r ~= 200 then + print('Error connecting to rms.sexy.\nrmspic.lua will not be enabled.') + return + end + for link in s:gmatch('(.-)') do + table.insert(rms.LIST, link) + end + rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table end function rms:action(msg, config) - bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' }) - local choice = rms.LIST[math.random(#rms.LIST)] - local filename = '/tmp/' .. choice - local image_file = io.open(filename) - if image_file then - image_file:close() - else - utilities.download_file(rms.BASE_URL .. choice, filename) - end - bindings.sendPhoto(self, { chat_id = msg.chat.id }, { photo = filename }) + bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' }) + local choice = rms.LIST[math.random(#rms.LIST)] + local filename = '/tmp/' .. choice + local image_file = io.open(filename) + if image_file then + image_file:close() + else + utilities.download_file(rms.BASE_URL .. choice, filename) + end + bindings.sendPhoto(self, { chat_id = msg.chat.id }, { photo = filename }) end return rms diff --git a/otouto/plugins/setandget.lua b/otouto/plugins/setandget.lua index 122b942..6c5dd08 100644 --- a/otouto/plugins/setandget.lua +++ b/otouto/plugins/setandget.lua @@ -3,9 +3,9 @@ local setandget = {} local utilities = require('otouto.utilities') function setandget:init(config) - self.database.setandget = self.database.setandget or {} - setandget.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('set', true):t('get', true).table - setandget.doc = config.cmd_pat .. [[set + self.database.setandget = self.database.setandget or {} + setandget.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('set', true):t('get', true).table + setandget.doc = config.cmd_pat .. [[set Stores a value with the given name. Use "]] .. config.cmd_pat .. [[set --" to delete the stored value. ]] .. config.cmd_pat .. [[get [name] Returns the stored value or a list of stored values.]] @@ -15,56 +15,56 @@ setandget.command = 'set ' function setandget:action(msg, config) - local chat_id_str = tostring(msg.chat.id) - local input = utilities.input(msg.text) - self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {} + local chat_id_str = tostring(msg.chat.id) + local input = utilities.input(msg.text) + self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {} - if msg.text_lower:match('^'..config.cmd_pat..'set') then + if msg.text_lower:match('^'..config.cmd_pat..'set') then - if not input then - utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) - return - end + if not input then + utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) + return + end - local name = utilities.get_word(input:lower(), 1) - local value = utilities.input(input) + local name = utilities.get_word(input:lower(), 1) + local value = utilities.input(input) - if not name or not value then - utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) - elseif value == '--' or value == '—' then - self.database.setandget[chat_id_str][name] = nil - utilities.send_message(self, msg.chat.id, 'That value has been deleted.') - else - self.database.setandget[chat_id_str][name] = value - utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true) - end + if not name or not value then + utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) + elseif value == '--' or value == '—' then + self.database.setandget[chat_id_str][name] = nil + utilities.send_message(self, msg.chat.id, 'That value has been deleted.') + else + self.database.setandget[chat_id_str][name] = value + utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true) + end - elseif msg.text_lower:match('^'..config.cmd_pat..'get') then + elseif msg.text_lower:match('^'..config.cmd_pat..'get') then - if not input then - local output - if utilities.table_size(self.database.setandget[chat_id_str]) == 0 then - output = 'No values have been stored here.' - else - output = '*List of stored values:*\n' - for k,v in pairs(self.database.setandget[chat_id_str]) do - output = output .. '• ' .. k .. ': `' .. v .. '`\n' - end - end - utilities.send_message(self, msg.chat.id, output, true, nil, true) - return - end + if not input then + local output + if utilities.table_size(self.database.setandget[chat_id_str]) == 0 then + output = 'No values have been stored here.' + else + output = '*List of stored values:*\n' + for k,v in pairs(self.database.setandget[chat_id_str]) do + output = output .. '• ' .. k .. ': `' .. v .. '`\n' + end + end + utilities.send_message(self, msg.chat.id, output, true, nil, true) + return + end - local output - if self.database.setandget[chat_id_str][input:lower()] then - output = '`' .. self.database.setandget[chat_id_str][input:lower()] .. '`' - else - output = 'There is no value stored by that name.' - end + local output + if self.database.setandget[chat_id_str][input:lower()] then + output = '`' .. self.database.setandget[chat_id_str][input:lower()] .. '`' + else + output = 'There is no value stored by that name.' + end - utilities.send_message(self, msg.chat.id, output, true, nil, true) + utilities.send_message(self, msg.chat.id, output, true, nil, true) - end + end end diff --git a/otouto/plugins/shell.lua b/otouto/plugins/shell.lua index 66a42b9..fdb88ed 100644 --- a/otouto/plugins/shell.lua +++ b/otouto/plugins/shell.lua @@ -3,32 +3,32 @@ local shell = {} local utilities = require('otouto.utilities') function shell:init(config) - shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('run', true).table + shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('run', true).table end function shell:action(msg, config) - if msg.from.id ~= config.admin then - return - end + if msg.from.id ~= config.admin then + return + end - local input = utilities.input(msg.text) - input = input:gsub('—', '--') + local input = utilities.input(msg.text) + input = input:gsub('—', '--') - if not input then - utilities.send_reply(self, msg, 'Please specify a command to run.') - return - end + if not input then + utilities.send_reply(self, msg, 'Please specify a command to run.') + return + end - local f = io.popen(input) - local output = f:read('*all') - f:close() - if output:len() == 0 then - output = 'Done!' - else - output = '```\n' .. output .. '\n```' - end - utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) + local f = io.popen(input) + local output = f:read('*all') + f:close() + if output:len() == 0 then + output = 'Done!' + else + output = '```\n' .. output .. '\n```' + end + utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) end diff --git a/otouto/plugins/shout.lua b/otouto/plugins/shout.lua index 1ea7bf0..c4b9b6d 100644 --- a/otouto/plugins/shout.lua +++ b/otouto/plugins/shout.lua @@ -6,45 +6,45 @@ shout.command = 'shout ' local utf8 = '('..utilities.char.utf_8..'*)' function shout:init(config) - shout.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('shout', true).table - shout.doc = config.cmd_pat .. 'shout \nShouts something. Input may be the replied-to message.' + shout.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('shout', true).table + shout.doc = config.cmd_pat .. 'shout \nShouts something. Input may be the replied-to message.' end function shout:action(msg) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, shout.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, shout.doc, true) + return + end - input = utilities.trim(input) - input = input:upper() + input = utilities.trim(input) + input = input:upper() - local output = '' - local inc = 0 - local ilen = 0 - for match in input:gmatch(utf8) do - if ilen < 20 then - ilen = ilen + 1 - output = output .. match .. ' ' - end - end - ilen = 0 - output = output .. '\n' - for match in input:sub(2):gmatch(utf8) do - if ilen < 19 then - local spacing = '' - for _ = 1, inc do - spacing = spacing .. ' ' - end - inc = inc + 1 - ilen = ilen + 1 - output = output .. match .. ' ' .. spacing .. match .. '\n' - end - end - output = '```\n' .. utilities.trim(output) .. '\n```' - utilities.send_message(self, msg.chat.id, output, true, false, true) + local output = '' + local inc = 0 + local ilen = 0 + for match in input:gmatch(utf8) do + if ilen < 20 then + ilen = ilen + 1 + output = output .. match .. ' ' + end + end + ilen = 0 + output = output .. '\n' + for match in input:sub(2):gmatch(utf8) do + if ilen < 19 then + local spacing = '' + for _ = 1, inc do + spacing = spacing .. ' ' + end + inc = inc + 1 + ilen = ilen + 1 + output = output .. match .. ' ' .. spacing .. match .. '\n' + end + end + output = '```\n' .. utilities.trim(output) .. '\n```' + utilities.send_message(self, msg.chat.id, output, true, false, true) end diff --git a/otouto/plugins/slap.lua b/otouto/plugins/slap.lua index b3cbf7c..8919a17 100644 --- a/otouto/plugins/slap.lua +++ b/otouto/plugins/slap.lua @@ -5,160 +5,160 @@ local utilities = require('otouto.utilities') slap.command = 'slap [target]' function slap:init(config) - slap.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('slap', true).table - slap.doc = config.cmd_pat .. 'slap [target] \nSlap somebody.' + slap.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('slap', true).table + slap.doc = config.cmd_pat .. 'slap [target] \nSlap somebody.' end local slaps = { - 'VICTIM was shot by VICTOR.', - 'VICTIM was pricked to death.', - 'VICTIM walked into a cactus while trying to escape VICTOR.', - 'VICTIM drowned.', - 'VICTIM drowned whilst trying to escape VICTOR.', - 'VICTIM blew up.', - 'VICTIM was blown up by VICTOR.', - 'VICTIM hit the ground too hard.', - 'VICTIM fell from a high place.', - 'VICTIM fell off a ladder.', - 'VICTIM fell into a patch of cacti.', - 'VICTIM was doomed to fall by VICTOR.', - 'VICTIM was blown from a high place by VICTOR.', - 'VICTIM was squashed by a falling anvil.', - 'VICTIM went up in flames.', - 'VICTIM burned to death.', - 'VICTIM was burnt to a crisp whilst fighting VICTOR.', - 'VICTIM walked into a fire whilst fighting VICTOR.', - 'VICTIM tried to swim in lava.', - 'VICTIM tried to swim in lava while trying to escape VICTOR.', - 'VICTIM was struck by lightning.', - 'VICTIM was slain by VICTOR.', - 'VICTIM got finished off by VICTOR.', - 'VICTIM was killed by magic.', - 'VICTIM was killed by VICTOR using magic.', - 'VICTIM starved to death.', - 'VICTIM suffocated in a wall.', - 'VICTIM fell out of the world.', - 'VICTIM was knocked into the void by VICTOR.', - 'VICTIM withered away.', - 'VICTIM was pummeled by VICTOR.', - 'VICTIM was fragged by VICTOR.', - 'VICTIM was desynchronized.', - 'VICTIM was wasted.', - 'VICTIM was busted.', - 'VICTIM\'s bones are scraped clean by the desolate wind.', - 'VICTIM has died of dysentery.', - 'VICTIM fainted.', - 'VICTIM is out of usable Pokemon! VICTIM whited out!', - 'VICTIM is out of usable Pokemon! VICTIM blacked out!', - 'VICTIM whited out!', - 'VICTIM blacked out!', - 'VICTIM says goodbye to this cruel world.', - 'VICTIM got rekt.', - 'VICTIM was sawn in half by VICTOR.', - 'VICTIM died. I blame VICTOR.', - 'VICTIM was axe-murdered by VICTOR.', - 'VICTIM\'s melon was split by VICTOR.', - 'VICTIM was sliced and diced by VICTOR.', - 'VICTIM was split from crotch to sternum by VICTOR.', - 'VICTIM\'s death put another notch in VICTOR\'s axe.', - 'VICTIM died impossibly!', - 'VICTIM died from VICTOR\'s mysterious tropical disease.', - 'VICTIM escaped infection by dying.', - 'VICTIM played hot-potato with a grenade.', - 'VICTIM was knifed by VICTOR.', - 'VICTIM fell on his sword.', - 'VICTIM ate a grenade.', - 'VICTIM practiced being VICTOR\'s clay pigeon.', - 'VICTIM is what\'s for dinner!', - 'VICTIM was terminated by VICTOR.', - 'VICTIM was shot before being thrown out of a plane.', - 'VICTIM was not invincible.', - 'VICTIM has encountered an error.', - 'VICTIM died and reincarnated as a goat.', - 'VICTOR threw VICTIM off a building.', - 'VICTIM is sleeping with the fishes.', - 'VICTIM got a premature burial.', - 'VICTOR replaced all of VICTIM\'s music with Nickelback.', - 'VICTOR spammed VICTIM\'s email.', - 'VICTOR made VICTIM a knuckle sandwich.', - 'VICTOR slapped VICTIM with pure nothing.', - 'VICTOR hit VICTIM with a small, interstellar spaceship.', - 'VICTIM was quickscoped by VICTOR.', - 'VICTOR put VICTIM in check-mate.', - 'VICTOR RSA-encrypted VICTIM and deleted the private key.', - 'VICTOR put VICTIM in the friendzone.', - 'VICTOR slaps VICTIM with a DMCA takedown request!', - 'VICTIM became a corpse blanket for VICTOR.', - 'Death is when the monsters get you. Death comes for VICTIM.', - 'Cowards die many times before their death. VICTIM never tasted death but once.', - 'VICTIM died of hospital gangrene.', - 'VICTIM got a house call from Doctor VICTOR.', - 'VICTOR beheaded VICTIM.', - 'VICTIM got stoned...by an angry mob.', - 'VICTOR sued the pants off VICTIM.', - 'VICTIM was impeached.', - 'VICTIM was one-hit KO\'d by VICTOR.', - 'VICTOR sent VICTIM to /dev/null.', - 'VICTOR sent VICTIM down the memory hole.', - 'VICTIM was a mistake.', - '"VICTIM was a mistake." - VICTOR', - 'VICTOR checkmated VICTIM in two moves.' + 'VICTIM was shot by VICTOR.', + 'VICTIM was pricked to death.', + 'VICTIM walked into a cactus while trying to escape VICTOR.', + 'VICTIM drowned.', + 'VICTIM drowned whilst trying to escape VICTOR.', + 'VICTIM blew up.', + 'VICTIM was blown up by VICTOR.', + 'VICTIM hit the ground too hard.', + 'VICTIM fell from a high place.', + 'VICTIM fell off a ladder.', + 'VICTIM fell into a patch of cacti.', + 'VICTIM was doomed to fall by VICTOR.', + 'VICTIM was blown from a high place by VICTOR.', + 'VICTIM was squashed by a falling anvil.', + 'VICTIM went up in flames.', + 'VICTIM burned to death.', + 'VICTIM was burnt to a crisp whilst fighting VICTOR.', + 'VICTIM walked into a fire whilst fighting VICTOR.', + 'VICTIM tried to swim in lava.', + 'VICTIM tried to swim in lava while trying to escape VICTOR.', + 'VICTIM was struck by lightning.', + 'VICTIM was slain by VICTOR.', + 'VICTIM got finished off by VICTOR.', + 'VICTIM was killed by magic.', + 'VICTIM was killed by VICTOR using magic.', + 'VICTIM starved to death.', + 'VICTIM suffocated in a wall.', + 'VICTIM fell out of the world.', + 'VICTIM was knocked into the void by VICTOR.', + 'VICTIM withered away.', + 'VICTIM was pummeled by VICTOR.', + 'VICTIM was fragged by VICTOR.', + 'VICTIM was desynchronized.', + 'VICTIM was wasted.', + 'VICTIM was busted.', + 'VICTIM\'s bones are scraped clean by the desolate wind.', + 'VICTIM has died of dysentery.', + 'VICTIM fainted.', + 'VICTIM is out of usable Pokemon! VICTIM whited out!', + 'VICTIM is out of usable Pokemon! VICTIM blacked out!', + 'VICTIM whited out!', + 'VICTIM blacked out!', + 'VICTIM says goodbye to this cruel world.', + 'VICTIM got rekt.', + 'VICTIM was sawn in half by VICTOR.', + 'VICTIM died. I blame VICTOR.', + 'VICTIM was axe-murdered by VICTOR.', + 'VICTIM\'s melon was split by VICTOR.', + 'VICTIM was sliced and diced by VICTOR.', + 'VICTIM was split from crotch to sternum by VICTOR.', + 'VICTIM\'s death put another notch in VICTOR\'s axe.', + 'VICTIM died impossibly!', + 'VICTIM died from VICTOR\'s mysterious tropical disease.', + 'VICTIM escaped infection by dying.', + 'VICTIM played hot-potato with a grenade.', + 'VICTIM was knifed by VICTOR.', + 'VICTIM fell on his sword.', + 'VICTIM ate a grenade.', + 'VICTIM practiced being VICTOR\'s clay pigeon.', + 'VICTIM is what\'s for dinner!', + 'VICTIM was terminated by VICTOR.', + 'VICTIM was shot before being thrown out of a plane.', + 'VICTIM was not invincible.', + 'VICTIM has encountered an error.', + 'VICTIM died and reincarnated as a goat.', + 'VICTOR threw VICTIM off a building.', + 'VICTIM is sleeping with the fishes.', + 'VICTIM got a premature burial.', + 'VICTOR replaced all of VICTIM\'s music with Nickelback.', + 'VICTOR spammed VICTIM\'s email.', + 'VICTOR made VICTIM a knuckle sandwich.', + 'VICTOR slapped VICTIM with pure nothing.', + 'VICTOR hit VICTIM with a small, interstellar spaceship.', + 'VICTIM was quickscoped by VICTOR.', + 'VICTOR put VICTIM in check-mate.', + 'VICTOR RSA-encrypted VICTIM and deleted the private key.', + 'VICTOR put VICTIM in the friendzone.', + 'VICTOR slaps VICTIM with a DMCA takedown request!', + 'VICTIM became a corpse blanket for VICTOR.', + 'Death is when the monsters get you. Death comes for VICTIM.', + 'Cowards die many times before their death. VICTIM never tasted death but once.', + 'VICTIM died of hospital gangrene.', + 'VICTIM got a house call from Doctor VICTOR.', + 'VICTOR beheaded VICTIM.', + 'VICTIM got stoned...by an angry mob.', + 'VICTOR sued the pants off VICTIM.', + 'VICTIM was impeached.', + 'VICTIM was one-hit KO\'d by VICTOR.', + 'VICTOR sent VICTIM to /dev/null.', + 'VICTOR sent VICTIM down the memory hole.', + 'VICTIM was a mistake.', + '"VICTIM was a mistake." - VICTOR', + 'VICTOR checkmated VICTIM in two moves.' } -- optimize later function slap:action(msg) - local input = utilities.input(msg.text) - local victor_id = msg.from.id - local victim_id - if msg.reply_to_message then - victim_id = msg.reply_to_message.from.id - else - if input then - if tonumber(input) then - victim_id = tonumber(input) - elseif input:match('^@') then - local t = utilities.resolve_username(self, input) - if t then - victim_id = t.id - end - end - end - end - -- IDs - if victim_id then - if victim_id == victor_id then - victor_id = self.info.id - end - else - if not input then - victor_id = self.info.id - victim_id = msg.from.id - end - end - -- Names - local victor_name, victim_name - if input and not victim_id then - victim_name = input - else - local victim_id_str = tostring(victim_id) - if self.database.userdata[victim_id_str] and self.database.userdata[victim_id_str].nickname then - victim_name = self.database.userdata[victim_id_str].nickname - elseif self.database.users[victim_id_str] then - victim_name = utilities.build_name(self.database.users[victim_id_str].first_name, self.database.users[victim_id_str].last_name) - else - victim_name = victim_id_str - end - end - local victor_id_str = tostring(victor_id) - if self.database.userdata[victor_id_str] and self.database.userdata[victor_id_str].nickname then - victor_name = self.database.userdata[victor_id_str].nickname - elseif self.database.users[victor_id_str] then - victor_name = utilities.build_name(self.database.users[victor_id_str].first_name, self.database.users[victor_id_str].last_name) - else - victor_name = self.info.first_name - end - local output = utilities.char.zwnj .. slaps[math.random(#slaps)]:gsub('VICTIM', victim_name):gsub('VICTOR', victor_name) - utilities.send_message(self, msg.chat.id, output) + local input = utilities.input(msg.text) + local victor_id = msg.from.id + local victim_id + if msg.reply_to_message then + victim_id = msg.reply_to_message.from.id + else + if input then + if tonumber(input) then + victim_id = tonumber(input) + elseif input:match('^@') then + local t = utilities.resolve_username(self, input) + if t then + victim_id = t.id + end + end + end + end + -- IDs + if victim_id then + if victim_id == victor_id then + victor_id = self.info.id + end + else + if not input then + victor_id = self.info.id + victim_id = msg.from.id + end + end + -- Names + local victor_name, victim_name + if input and not victim_id then + victim_name = input + else + local victim_id_str = tostring(victim_id) + if self.database.userdata[victim_id_str] and self.database.userdata[victim_id_str].nickname then + victim_name = self.database.userdata[victim_id_str].nickname + elseif self.database.users[victim_id_str] then + victim_name = utilities.build_name(self.database.users[victim_id_str].first_name, self.database.users[victim_id_str].last_name) + else + victim_name = victim_id_str + end + end + local victor_id_str = tostring(victor_id) + if self.database.userdata[victor_id_str] and self.database.userdata[victor_id_str].nickname then + victor_name = self.database.userdata[victor_id_str].nickname + elseif self.database.users[victor_id_str] then + victor_name = utilities.build_name(self.database.users[victor_id_str].first_name, self.database.users[victor_id_str].last_name) + else + victor_name = self.info.first_name + end + local output = utilities.char.zwnj .. slaps[math.random(#slaps)]:gsub('VICTIM', victim_name):gsub('VICTOR', victor_name) + utilities.send_message(self, msg.chat.id, output) end return slap diff --git a/otouto/plugins/starwars-crawl.lua b/otouto/plugins/starwars-crawl.lua index ed4594c..500be3e 100644 --- a/otouto/plugins/starwars-crawl.lua +++ b/otouto/plugins/starwars-crawl.lua @@ -8,71 +8,71 @@ local utilities = require('otouto.utilities') local starwars = {} function starwars:init(config) - starwars.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('starwars', true):t('sw', true).table - starwars.doc = config.cmd_pat .. [[starwars + starwars.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('starwars', true):t('sw', true).table + starwars.doc = config.cmd_pat .. [[starwars Returns the opening crawl from the specified Star Wars film. Alias: ]] .. config.cmd_pat .. 'sw' - starwars.command = 'starwars ' - starwars.base_url = 'http://swapi.co/api/films/' + starwars.command = 'starwars ' + starwars.base_url = 'http://swapi.co/api/films/' end local films_by_number = { - ['phantom menace'] = 4, - ['attack of the clones'] = 5, - ['revenge of the sith'] = 6, - ['new hope'] = 1, - ['empire strikes back'] = 2, - ['return of the jedi'] = 3, - ['force awakens'] = 7 + ['phantom menace'] = 4, + ['attack of the clones'] = 5, + ['revenge of the sith'] = 6, + ['new hope'] = 1, + ['empire strikes back'] = 2, + ['return of the jedi'] = 3, + ['force awakens'] = 7 } local corrected_numbers = { - 4, - 5, - 6, - 1, - 2, - 3, - 7 + 4, + 5, + 6, + 1, + 2, + 3, + 7 } function starwars:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, starwars.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, starwars.doc, true) + return + end - bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) + bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) - local film - if tonumber(input) then - input = tonumber(input) - film = corrected_numbers[input] or input - else - for title, number in pairs(films_by_number) do - if string.match(input, title) then - film = number - break - end - end - end + local film + if tonumber(input) then + input = tonumber(input) + film = corrected_numbers[input] or input + else + for title, number in pairs(films_by_number) do + if string.match(input, title) then + film = number + break + end + end + end - if not film then - utilities.send_reply(self, msg, config.errors.results) - return - end + if not film then + utilities.send_reply(self, msg, config.errors.results) + return + end - local url = starwars.base_url .. film - local jstr, code = HTTP.request(url) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local url = starwars.base_url .. film + local jstr, code = HTTP.request(url) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local output = '*' .. JSON.decode(jstr).opening_crawl .. '*' - utilities.send_message(self, msg.chat.id, output, true, nil, true) + local output = '*' .. JSON.decode(jstr).opening_crawl .. '*' + utilities.send_message(self, msg.chat.id, output, true, nil, true) end return starwars diff --git a/otouto/plugins/time.lua b/otouto/plugins/time.lua index e028428..81af8a1 100644 --- a/otouto/plugins/time.lua +++ b/otouto/plugins/time.lua @@ -8,52 +8,52 @@ time.command = 'time ' time.base_url = 'https://maps.googleapis.com/maps/api/timezone/json?location=%s,%s×tamp=%s' function time:init(config) - time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table - time.doc = config.cmd_pat .. [[time + time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table + time.doc = config.cmd_pat .. [[time Returns the time, date, and timezone for the given location.]] end function time:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, time.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, time.doc, true) + return + end - local coords = utilities.get_coords(input, config) - if type(coords) == 'string' then - utilities.send_reply(self, msg, coords) - return - end + local coords = utilities.get_coords(input, config) + if type(coords) == 'string' then + utilities.send_reply(self, msg, coords) + return + end - local now = os.time() - local utc = os.time(os.date('!*t', now)) - local url = time.base_url:format(coords.lat, coords.lon, utc) - local jstr, code = HTTPS.request(url) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local now = os.time() + local utc = os.time(os.date('!*t', now)) + local url = time.base_url:format(coords.lat, coords.lon, utc) + local jstr, code = HTTPS.request(url) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local data = JSON.decode(jstr) - if data.status == 'ZERO_RESULTS' then - utilities.send_reply(self, msg, config.errors.results) - return - end + local data = JSON.decode(jstr) + if data.status == 'ZERO_RESULTS' then + utilities.send_reply(self, msg, config.errors.results) + return + end - local timestamp = now + data.rawOffset + data.dstOffset - local utcoff = (data.rawOffset + data.dstOffset) / 3600 - if utcoff == math.abs(utcoff) then - utcoff = '+' .. utilities.pretty_float(utcoff) - else - utcoff = utilities.pretty_float(utcoff) - end - local output = string.format('```\n%s\n%s (UTC%s)\n```', - os.date('!%I:%M %p\n%A, %B %d, %Y', timestamp), - data.timeZoneName, - utcoff - ) - utilities.send_reply(self, msg, output, true) + local timestamp = now + data.rawOffset + data.dstOffset + local utcoff = (data.rawOffset + data.dstOffset) / 3600 + if utcoff == math.abs(utcoff) then + utcoff = '+' .. utilities.pretty_float(utcoff) + else + utcoff = utilities.pretty_float(utcoff) + end + local output = string.format('```\n%s\n%s (UTC%s)\n```', + os.date('!%I:%M %p\n%A, %B %d, %Y', timestamp), + data.timeZoneName, + utcoff + ) + utilities.send_reply(self, msg, output, true) end return time diff --git a/otouto/plugins/translate.lua b/otouto/plugins/translate.lua index 5576d03..b427aef 100644 --- a/otouto/plugins/translate.lua +++ b/otouto/plugins/translate.lua @@ -8,38 +8,38 @@ local utilities = require('otouto.utilities') translate.command = 'translate [text]' function translate:init(config) - assert(config.yandex_key, - 'translate.lua requires a Yandex translate API key from http://tech.yandex.com/keys/get.' - ) + assert(config.yandex_key, + 'translate.lua requires a Yandex translate API key from http://tech.yandex.com/keys/get.' + ) - translate.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('translate', true):t('tl', true).table - translate.doc = config.cmd_pat .. [[translate [text] + translate.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('translate', true):t('tl', true).table + translate.doc = config.cmd_pat .. [[translate [text] Translates input or the replied-to message into the bot's language.]] - translate.base_url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=%s' + translate.base_url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=%s' end function translate:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, translate.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, translate.doc, true) + return + end - local url = translate.base_url:format(URL.escape(input)) - local jstr, code = HTTPS.request(url) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local url = translate.base_url:format(URL.escape(input)) + local jstr, code = HTTPS.request(url) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local data = JSON.decode(jstr) - if data.code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local data = JSON.decode(jstr) + if data.code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - utilities.send_reply(self, msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true) + utilities.send_reply(self, msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true) end return translate diff --git a/otouto/plugins/urbandictionary.lua b/otouto/plugins/urbandictionary.lua index 9a2cfcf..2dacaad 100644 --- a/otouto/plugins/urbandictionary.lua +++ b/otouto/plugins/urbandictionary.lua @@ -9,42 +9,42 @@ urbandictionary.command = 'urbandictionary ' urbandictionary.base_url = 'http://api.urbandictionary.com/v0/define?term=' function urbandictionary:init(config) - urbandictionary.triggers = utilities.triggers(self.info.username, config.cmd_pat) - :t('urbandictionary', true):t('ud', true):t('urban', true).table - urbandictionary.doc = [[ + urbandictionary.triggers = utilities.triggers(self.info.username, config.cmd_pat) + :t('urbandictionary', true):t('ud', true):t('urban', true).table + urbandictionary.doc = [[ /urbandictionary Returns a definition from Urban Dictionary. Aliases: /ud, /urban - ]] - urbandictionary.doc = urbandictionary.doc:gsub('/', config.cmd_pat) + ]] + urbandictionary.doc = urbandictionary.doc:gsub('/', config.cmd_pat) end function urbandictionary:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, urbandictionary.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, urbandictionary.doc, true) + return + end - local url = urbandictionary.base_url .. URL.escape(input) - local jstr, code = HTTP.request(url) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local url = urbandictionary.base_url .. URL.escape(input) + local jstr, code = HTTP.request(url) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local data = JSON.decode(jstr) - local output - if data.result_type == 'no_results' then - output = config.errors.results - else - output = string.format('*%s*\n\n%s\n\n_%s_', - data.list[1].word:gsub('*', '*\\**'), - utilities.trim(utilities.md_escape(data.list[1].definition)), - utilities.trim((data.list[1].example or '')):gsub('_', '_\\__') - ) - end - utilities.send_reply(self, msg, output, true) + local data = JSON.decode(jstr) + local output + if data.result_type == 'no_results' then + output = config.errors.results + else + output = string.format('*%s*\n\n%s\n\n_%s_', + data.list[1].word:gsub('*', '*\\**'), + utilities.trim(utilities.md_escape(data.list[1].definition)), + utilities.trim((data.list[1].example or '')):gsub('_', '_\\__') + ) + end + utilities.send_reply(self, msg, output, true) end return urbandictionary diff --git a/otouto/plugins/weather.lua b/otouto/plugins/weather.lua index 60b0862..7747ba3 100644 --- a/otouto/plugins/weather.lua +++ b/otouto/plugins/weather.lua @@ -6,12 +6,12 @@ local JSON = require('dkjson') local utilities = require('otouto.utilities') function weather:init(config) - assert(config.owm_api_key, - 'weather.lua requires an OpenWeatherMap API key from http://openweathermap.org/API.' - ) + assert(config.owm_api_key, + 'weather.lua requires an OpenWeatherMap API key from http://openweathermap.org/API.' + ) - weather.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('weather', true).table - weather.doc = config.cmd_pat .. [[weather + weather.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('weather', true).table + weather.doc = config.cmd_pat .. [[weather Returns the current weather conditions for a given location.]] end @@ -19,37 +19,37 @@ weather.command = 'weather ' function weather:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, weather.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, weather.doc, true) + return + end - local coords = utilities.get_coords(input, config) - if type(coords) == 'string' then - utilities.send_reply(self, msg, coords) - return - end + local coords = utilities.get_coords(input, config) + if type(coords) == 'string' then + utilities.send_reply(self, msg, coords) + return + end - local url = 'http://api.openweathermap.org/data/2.5/weather?APPID=' .. config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon + local url = 'http://api.openweathermap.org/data/2.5/weather?APPID=' .. config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon - local jstr, res = HTTP.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local jstr, res = HTTP.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local jdat = JSON.decode(jstr) - if jdat.cod ~= 200 then - utilities.send_reply(self, msg, 'Error: City not found.') - return - end + local jdat = JSON.decode(jstr) + if jdat.cod ~= 200 then + utilities.send_reply(self, msg, 'Error: City not found.') + return + end - local celsius = string.format('%.2f', jdat.main.temp - 273.15) - local fahrenheit = string.format('%.2f', celsius * (9/5) + 32) - local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`' + local celsius = string.format('%.2f', jdat.main.temp - 273.15) + local fahrenheit = string.format('%.2f', celsius * (9/5) + 32) + local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`' - utilities.send_reply(self, msg, output, true) + utilities.send_reply(self, msg, output, true) end diff --git a/otouto/plugins/whoami.lua b/otouto/plugins/whoami.lua index 946cdf4..9059159 100644 --- a/otouto/plugins/whoami.lua +++ b/otouto/plugins/whoami.lua @@ -6,54 +6,54 @@ local bindings = require('otouto.bindings') whoami.command = 'whoami' function whoami:init(config) - whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who'):t('whoami').table - whoami.doc = [[ + whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who'):t('whoami').table + whoami.doc = [[ Returns user and chat info for you or the replied-to message. Alias: ]] .. config.cmd_pat .. 'who' end function whoami:action(msg) - -- Operate on the replied-to message, if it exists. - msg = msg.reply_to_message or msg - -- If it's a private conversation, bot is chat, unless bot is from. - local chat = msg.from.id == msg.chat.id and self.info or msg.chat - -- Names for the user and group, respectively. HTML-escaped. - local from_name = utilities.html_escape( - utilities.build_name( - msg.from.first_name, - msg.from.last_name - ) - ) - local chat_name = utilities.html_escape( - chat.title - or utilities.build_name(chat.first_name, chat.last_name) - ) - -- "Normalize" a group ID so it's not arbitrarily modified by the bot API. - local chat_id = math.abs(chat.id) - if chat_id > 1000000000000 then chat_id = chat_id - 1000000000000 end - -- Do the thing. - local output = string.format( - 'You are %s [%s], and you are messaging %s [%s].', - msg.from.username and string.format( - '@%s, also known as %s', - msg.from.username, - from_name - ) or '' .. from_name .. '', - msg.from.id, - msg.chat.username and string.format( - '@%s, also known as %s', - chat.username, - chat_name - ) or '' .. chat_name .. '', - chat_id - ) - bindings.sendMessage(self, { - chat_id = msg.chat.id, - reply_to_message_id = msg.message_id, - disable_web_page_preview = true, - parse_mode = 'HTML', - text = output - }) + -- Operate on the replied-to message, if it exists. + msg = msg.reply_to_message or msg + -- If it's a private conversation, bot is chat, unless bot is from. + local chat = msg.from.id == msg.chat.id and self.info or msg.chat + -- Names for the user and group, respectively. HTML-escaped. + local from_name = utilities.html_escape( + utilities.build_name( + msg.from.first_name, + msg.from.last_name + ) + ) + local chat_name = utilities.html_escape( + chat.title + or utilities.build_name(chat.first_name, chat.last_name) + ) + -- "Normalize" a group ID so it's not arbitrarily modified by the bot API. + local chat_id = math.abs(chat.id) + if chat_id > 1000000000000 then chat_id = chat_id - 1000000000000 end + -- Do the thing. + local output = string.format( + 'You are %s [%s], and you are messaging %s [%s].', + msg.from.username and string.format( + '@%s, also known as %s', + msg.from.username, + from_name + ) or '' .. from_name .. '', + msg.from.id, + msg.chat.username and string.format( + '@%s, also known as %s', + chat.username, + chat_name + ) or '' .. chat_name .. '', + chat_id + ) + bindings.sendMessage(self, { + chat_id = msg.chat.id, + reply_to_message_id = msg.message_id, + disable_web_page_preview = true, + parse_mode = 'HTML', + text = output + }) end return whoami diff --git a/otouto/plugins/wikipedia.lua b/otouto/plugins/wikipedia.lua index 54336f5..0b81144 100644 --- a/otouto/plugins/wikipedia.lua +++ b/otouto/plugins/wikipedia.lua @@ -8,83 +8,83 @@ local utilities = require('otouto.utilities') wikipedia.command = 'wikipedia ' function wikipedia:init(config) - wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true):t('w', true).table - wikipedia.doc = config.cmd_pat .. [[wikipedia + wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true):t('w', true).table + wikipedia.doc = config.cmd_pat .. [[wikipedia Returns an article from Wikipedia. Aliases: ]] .. config.cmd_pat .. 'w, ' .. config.cmd_pat .. 'wiki' - wikipedia.search_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch=' - wikipedia.res_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles=' - wikipedia.art_url = 'https://' .. config.lang .. '.wikipedia.org/wiki/' + wikipedia.search_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch=' + wikipedia.res_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles=' + wikipedia.art_url = 'https://' .. config.lang .. '.wikipedia.org/wiki/' end function wikipedia:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, wikipedia.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, wikipedia.doc, true) + return + end - local jstr, code = HTTPS.request(wikipedia.search_url .. URL.escape(input)) - if code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local jstr, code = HTTPS.request(wikipedia.search_url .. URL.escape(input)) + if code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local data = JSON.decode(jstr) - if data.query.searchinfo.totalhits == 0 then - utilities.send_reply(self, msg, config.errors.results) - return - end + local data = JSON.decode(jstr) + if data.query.searchinfo.totalhits == 0 then + utilities.send_reply(self, msg, config.errors.results) + return + end - local title - for _, v in ipairs(data.query.search) do - if not v.snippet:match('may refer to:') then - title = v.title - break - end - end - if not title then - utilities.send_reply(self, msg, config.errors.results) - return - end + local title + for _, v in ipairs(data.query.search) do + if not v.snippet:match('may refer to:') then + title = v.title + break + end + end + if not title then + utilities.send_reply(self, msg, config.errors.results) + return + end - local res_jstr, res_code = HTTPS.request(wikipedia.res_url .. URL.escape(title)) - if res_code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local res_jstr, res_code = HTTPS.request(wikipedia.res_url .. URL.escape(title)) + if res_code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local _, text = next(JSON.decode(res_jstr).query.pages) - if not text then - utilities.send_reply(self, msg, config.errors.results) - return - end + local _, text = next(JSON.decode(res_jstr).query.pages) + if not text then + utilities.send_reply(self, msg, config.errors.results) + return + end - text = text.extract - -- Remove crap and take only the first paragraph. - text = text:gsub('', ''):gsub('%[.+%]', '') - local l = text:find('\n') - if l then - text = text:sub(1, l-1) - end - local url = wikipedia.art_url .. URL.escape(title) - title = utilities.html_escape(title) - -- If the beginning of the article is the title, embolden that. - -- Otherwise, we'll add a title in bold. - local short_title = title:gsub('%(.+%)', '') - local combined_text, count = text:gsub('^'..short_title, ''..short_title..'') - local body - if count == 1 then - body = combined_text - else - body = '' .. title .. '\n' .. text - end - local output = string.format( - '%s\nRead more.', - body, - utilities.html_escape(url) - ) - utilities.send_message(self, msg.chat.id, output, true, nil, 'html') + text = text.extract + -- Remove crap and take only the first paragraph. + text = text:gsub('', ''):gsub('%[.+%]', '') + local l = text:find('\n') + if l then + text = text:sub(1, l-1) + end + local url = wikipedia.art_url .. URL.escape(title) + title = utilities.html_escape(title) + -- If the beginning of the article is the title, embolden that. + -- Otherwise, we'll add a title in bold. + local short_title = title:gsub('%(.+%)', '') + local combined_text, count = text:gsub('^'..short_title, ''..short_title..'') + local body + if count == 1 then + body = combined_text + else + body = '' .. title .. '\n' .. text + end + local output = string.format( + '%s\nRead more.', + body, + utilities.html_escape(url) + ) + utilities.send_message(self, msg.chat.id, output, true, nil, 'html') end return wikipedia diff --git a/otouto/plugins/xkcd.lua b/otouto/plugins/xkcd.lua index a2484a2..ce0a262 100644 --- a/otouto/plugins/xkcd.lua +++ b/otouto/plugins/xkcd.lua @@ -9,44 +9,44 @@ xkcd.base_url = 'https://xkcd.com/info.0.json' xkcd.strip_url = 'http://xkcd.com/%s/info.0.json' function xkcd:init(config) - xkcd.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('xkcd', true).table - xkcd.doc = config.cmd_pat .. [[xkcd [i] + xkcd.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('xkcd', true).table + xkcd.doc = config.cmd_pat .. [[xkcd [i] Returns the latest xkcd strip and its alt text. If a number is given, returns that number strip. If "r" is passed in place of a number, returns a random strip.]] - local jstr = HTTP.request(xkcd.base_url) - if jstr then - local data = JSON.decode(jstr) - if data then - xkcd.latest = data.num - end - end - xkcd.latest = xkcd.latest or 1700 + local jstr = HTTP.request(xkcd.base_url) + if jstr then + local data = JSON.decode(jstr) + if data then + xkcd.latest = data.num + end + end + xkcd.latest = xkcd.latest or 1700 end function xkcd:action(msg, config) - local input = utilities.get_word(msg.text, 2) - if input == 'r' then - input = math.random(xkcd.latest) - elseif tonumber(input) then - input = tonumber(input) - else - input = xkcd.latest - end - local url = xkcd.strip_url:format(input) - local jstr, code = HTTP.request(url) - if code == 404 then - utilities.send_reply(self, msg, config.errors.results) - elseif code ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - else - local data = JSON.decode(jstr) - local output = string.format('*%s (*[%s](%s)*)*\n_%s_', - data.safe_title:gsub('*', '*\\**'), - data.num, - data.img, - data.alt:gsub('_', '_\\__') - ) - utilities.send_message(self, msg.chat.id, output, false, nil, true) - end + local input = utilities.get_word(msg.text, 2) + if input == 'r' then + input = math.random(xkcd.latest) + elseif tonumber(input) then + input = tonumber(input) + else + input = xkcd.latest + end + local url = xkcd.strip_url:format(input) + local jstr, code = HTTP.request(url) + if code == 404 then + utilities.send_reply(self, msg, config.errors.results) + elseif code ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + else + local data = JSON.decode(jstr) + local output = string.format('*%s (*[%s](%s)*)*\n_%s_', + data.safe_title:gsub('*', '*\\**'), + data.num, + data.img, + data.alt:gsub('_', '_\\__') + ) + utilities.send_message(self, msg.chat.id, output, false, nil, true) + end end return xkcd diff --git a/otouto/plugins/youtube.lua b/otouto/plugins/youtube.lua index 2de16ae..c86ea3d 100644 --- a/otouto/plugins/youtube.lua +++ b/otouto/plugins/youtube.lua @@ -8,12 +8,12 @@ local JSON = require('dkjson') local utilities = require('otouto.utilities') function youtube:init(config) - assert(config.google_api_key, - 'youtube.lua requires a Google API key from http://console.developers.google.com.' - ) + assert(config.google_api_key, + 'youtube.lua requires a Google API key from http://console.developers.google.com.' + ) - youtube.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('youtube', true):t('yt', true).table - youtube.doc = config.cmd_pat .. [[youtube + youtube.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('youtube', true):t('yt', true).table + youtube.doc = config.cmd_pat .. [[youtube Returns the top result from YouTube. Alias: ]] .. config.cmd_pat .. 'yt' end @@ -22,32 +22,32 @@ youtube.command = 'youtube ' function youtube:action(msg, config) - local input = utilities.input_from_msg(msg) - if not input then - utilities.send_reply(self, msg, youtube.doc, true) - return - end + local input = utilities.input_from_msg(msg) + if not input then + utilities.send_reply(self, msg, youtube.doc, true) + return + end - local url = 'https://www.googleapis.com/youtube/v3/search?key=' .. config.google_api_key .. '&type=video&part=snippet&maxResults=4&q=' .. URL.escape(input) + local url = 'https://www.googleapis.com/youtube/v3/search?key=' .. config.google_api_key .. '&type=video&part=snippet&maxResults=4&q=' .. URL.escape(input) - local jstr, res = HTTPS.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end + local jstr, res = HTTPS.request(url) + if res ~= 200 then + utilities.send_reply(self, msg, config.errors.connection) + return + end - local jdat = JSON.decode(jstr) - if jdat.pageInfo.totalResults == 0 then - utilities.send_reply(self, msg, config.errors.results) - return - end + local jdat = JSON.decode(jstr) + if jdat.pageInfo.totalResults == 0 then + utilities.send_reply(self, msg, config.errors.results) + return + end - local vid_url = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId - local vid_title = jdat.items[1].snippet.title - vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','') - local output = '[' .. vid_title .. '](' .. vid_url .. ')' + local vid_url = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId + local vid_title = jdat.items[1].snippet.title + vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','') + local output = '[' .. vid_title .. '](' .. vid_url .. ')' - utilities.send_message(self, msg.chat.id, output, false, nil, true) + utilities.send_message(self, msg.chat.id, output, false, nil, true) end diff --git a/otouto/utilities.lua b/otouto/utilities.lua index 22f5204..86ba0a1 100644 --- a/otouto/utilities.lua +++ b/otouto/utilities.lua @@ -15,47 +15,47 @@ local bindings = require('otouto.bindings') -- Edit: To keep things working and allow for HTML messages, you can now pass a -- string for use_markdown and that will be sent as the parse mode. function utilities:send_message(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown) - local parse_mode - if type(use_markdown) == 'string' then - parse_mode = use_markdown - elseif use_markdown == true then - parse_mode = 'markdown' - end - return bindings.request(self, 'sendMessage', { - chat_id = chat_id, - text = text, - disable_web_page_preview = disable_web_page_preview, - reply_to_message_id = reply_to_message_id, - parse_mode = parse_mode - } ) + local parse_mode + if type(use_markdown) == 'string' then + parse_mode = use_markdown + elseif use_markdown == true then + parse_mode = 'markdown' + end + return bindings.request(self, 'sendMessage', { + chat_id = chat_id, + text = text, + disable_web_page_preview = disable_web_page_preview, + reply_to_message_id = reply_to_message_id, + parse_mode = parse_mode + } ) end function utilities:send_reply(old_msg, text, use_markdown) - return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown) + return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown) end -- get the indexed word in a string function utilities.get_word(s, i) - s = s or '' - i = i or 1 - local n = 0 - for w in s:gmatch('%g+') do - n = n + 1 - if n == i then return w end - end - return false + s = s or '' + i = i or 1 + local n = 0 + for w in s:gmatch('%g+') do + n = n + 1 + if n == i then return w end + end + return false end -- Returns the string after the first space. function utilities.input(s) - if not s:find(' ') then - return false - end - return s:sub(s:find(' ')+1) + if not s:find(' ') then + return false + end + return s:sub(s:find(' ')+1) end function utilities.input_from_msg(msg) - return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false + return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false end -- Calculates the length of the given string as UTF-8 characters @@ -72,180 +72,180 @@ end -- Trims whitespace from a string. function utilities.trim(str) - local s = str:gsub('^%s*(.-)%s*$', '%1') - return s + local s = str:gsub('^%s*(.-)%s*$', '%1') + return s end -- Loads a JSON file as a table. function utilities.load_data(filename) - local f = io.open(filename) - if f then - local s = f:read('*all') - f:close() - return JSON.decode(s) - else - return {} - end + local f = io.open(filename) + if f then + local s = f:read('*all') + f:close() + return JSON.decode(s) + else + return {} + end end -- Saves a table to a JSON file. function utilities.save_data(filename, data) - local s = JSON.encode(data) - local f = io.open(filename, 'w') - f:write(s) - f:close() + local s = JSON.encode(data) + local f = io.open(filename, 'w') + f:write(s) + f:close() end -- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua. function utilities.get_coords(input, config) - local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) + local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) - local jstr, res = HTTP.request(url) - if res ~= 200 then - return config.errors.connection - end + local jstr, res = HTTP.request(url) + if res ~= 200 then + return config.errors.connection + end - local jdat = JSON.decode(jstr) - if jdat.status == 'ZERO_RESULTS' then - return config.errors.results - end + local jdat = JSON.decode(jstr) + if jdat.status == 'ZERO_RESULTS' then + return config.errors.results + end - return { - lat = jdat.results[1].geometry.location.lat, - lon = jdat.results[1].geometry.location.lng - } + return { + lat = jdat.results[1].geometry.location.lat, + lon = jdat.results[1].geometry.location.lng + } end -- Get the number of values in a key/value table. function utilities.table_size(tab) - local i = 0 - for _,_ in pairs(tab) do - i = i + 1 - end - return i + local i = 0 + for _,_ in pairs(tab) do + i = i + 1 + end + return i end -- Just an easy way to get a user's full name. -- Alternatively, abuse it to concat two strings like I do. function utilities.build_name(first, last) - if last then - return first .. ' ' .. last - else - return first - end + if last then + return first .. ' ' .. last + else + return first + end end function utilities:resolve_username(input) - input = input:gsub('^@', '') - for _, user in pairs(self.database.users) do - if user.username and user.username:lower() == input:lower() then - local t = {} - for key, val in pairs(user) do - t[key] = val - end - return t - end - end + input = input:gsub('^@', '') + for _, user in pairs(self.database.users) do + if user.username and user.username:lower() == input:lower() then + local t = {} + for key, val in pairs(user) do + t[key] = val + end + return t + end + end end function utilities:handle_exception(err, message, config) - local output = string.format( - '\n[%s]\n%s: %s\n%s\n', - os.date('%F %T'), - self.info.username, - err or '', - message - ) - if config.log_chat then - output = '```' .. output .. '```' - utilities.send_message(self, config.log_chat, output, true, nil, true) - else - print(output) - end + local output = string.format( + '\n[%s]\n%s: %s\n%s\n', + os.date('%F %T'), + self.info.username, + err or '', + message + ) + if config.log_chat then + output = '```' .. output .. '```' + utilities.send_message(self, config.log_chat, output, true, nil, true) + else + print(output) + end end function utilities.download_file(url, filename) - if not filename then - filename = url:match('.+/(.-)$') or os.time() - filename = '/tmp/' .. filename - end - local body = {} - local doer = HTTP - local do_redir = true - if url:match('^https') then - doer = HTTPS - do_redir = false - end - local _, res = doer.request{ - url = url, - sink = ltn12.sink.table(body), - redirect = do_redir - } - if res ~= 200 then return false end - local file = io.open(filename, 'w+') - file:write(table.concat(body)) - file:close() - return filename + if not filename then + filename = url:match('.+/(.-)$') or os.time() + filename = '/tmp/' .. filename + end + local body = {} + local doer = HTTP + local do_redir = true + if url:match('^https') then + doer = HTTPS + do_redir = false + end + local _, res = doer.request{ + url = url, + sink = ltn12.sink.table(body), + redirect = do_redir + } + if res ~= 200 then return false end + local file = io.open(filename, 'w+') + file:write(table.concat(body)) + file:close() + return filename end function utilities.md_escape(text) - return text:gsub('_', '\\_') - :gsub('%[', '\\['):gsub('%]', '\\]') - :gsub('%*', '\\*'):gsub('`', '\\`') + return text:gsub('_', '\\_') + :gsub('%[', '\\['):gsub('%]', '\\]') + :gsub('%*', '\\*'):gsub('`', '\\`') end function utilities.html_escape(text) - return text:gsub('&', '&'):gsub('<', '<'):gsub('>', '>') + return text:gsub('&', '&'):gsub('<', '<'):gsub('>', '>') end utilities.triggers_meta = {} utilities.triggers_meta.__index = utilities.triggers_meta function utilities.triggers_meta:t(pattern, has_args) - local username = self.username:lower() - table.insert(self.table, '^'..self.cmd_pat..pattern..'$') - table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'$') - if has_args then - table.insert(self.table, '^'..self.cmd_pat..pattern..'%s+[^%s]*') - table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'%s+[^%s]*') - end - return self + local username = self.username:lower() + table.insert(self.table, '^'..self.cmd_pat..pattern..'$') + table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'$') + if has_args then + table.insert(self.table, '^'..self.cmd_pat..pattern..'%s+[^%s]*') + table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'%s+[^%s]*') + end + return self end function utilities.triggers(username, cmd_pat, trigger_table) - local self = setmetatable({}, utilities.triggers_meta) - self.username = username - self.cmd_pat = cmd_pat - self.table = trigger_table or {} - return self + local self = setmetatable({}, utilities.triggers_meta) + self.username = username + self.cmd_pat = cmd_pat + self.table = trigger_table or {} + return self end function utilities.with_http_timeout(timeout, fun) - local original = HTTP.TIMEOUT - HTTP.TIMEOUT = timeout - fun() - HTTP.TIMEOUT = original + local original = HTTP.TIMEOUT + HTTP.TIMEOUT = timeout + fun() + HTTP.TIMEOUT = original end function utilities.pretty_float(x) - if x % 1 == 0 then - return tostring(math.floor(x)) - else - return tostring(x) - end + if x % 1 == 0 then + return tostring(math.floor(x)) + else + return tostring(x) + end end -- This table will store unsavory characters that are not properly displayed, -- or are just not fun to type. utilities.char = { - zwnj = '‌', - arabic = '[\216-\219][\128-\191]', - rtl_override = '‮', - rtl_mark = '‏', - em_dash = '—', - utf_8 = '[%z\1-\127\194-\244][\128-\191]', + zwnj = '‌', + arabic = '[\216-\219][\128-\191]', + rtl_override = '‮', + rtl_mark = '‏', + em_dash = '—', + utf_8 = '[%z\1-\127\194-\244][\128-\191]', } utilities.set_meta = {} @@ -283,7 +283,7 @@ end -- More to be added. utilities.style = {} utilities.style.enquote = function(title, body) - return '*' .. title:gsub('*', '\\*') .. ':*\n"' .. utilities.md_escape(body) .. '"' + return '*' .. title:gsub('*', '\\*') .. ':*\n"' .. utilities.md_escape(body) .. '"' end return utilities diff --git a/tg-launch.sh b/tg-launch.sh index 570e0ba..62d2513 100755 --- a/tg-launch.sh +++ b/tg-launch.sh @@ -4,8 +4,8 @@ # config.lua), delete state file after stop, wait five seconds, and restart. while true; do - tg/bin/telegram-cli -P 4567 -E - [ -f ~/.telegram-cli/state ] && rm ~/.telegram-cli/state - echo 'tg has stopped. ^C to exit.' - sleep 5s + tg/bin/telegram-cli -P 4567 -E + [ -f ~/.telegram-cli/state ] && rm ~/.telegram-cli/state + echo 'tg has stopped. ^C to exit.' + sleep 5s done