otouto 3.14
All help messages and many other things moved from markdown to html. Eventually, I'd like only things made from user input to use markdown. cats.lua, rmspic.lua, and dilbert.lua moved to sendPhoto with URL. xkcd.lua and apod.lua not moved to retain formatting. Probably a lot of other stuff that I forget about. I should commit more often.
This commit is contained in:
parent
bc6727275c
commit
9f760114bd
@ -67,7 +67,7 @@ Send /help to get started.
|
||||
max_reminders_private = 50
|
||||
},
|
||||
|
||||
chatter = {
|
||||
cleverbot = {
|
||||
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.'
|
||||
@ -113,6 +113,9 @@ Send /help to get started.
|
||||
-- Conversation, group, or channel for kick/ban notifications.
|
||||
-- Defaults to config.log_chat if left empty.
|
||||
log_chat = nil,
|
||||
-- Default autoban setting.
|
||||
-- A user is banned after being autokicked this many times in a day.
|
||||
autoban = 3,
|
||||
-- Default antiflood values.
|
||||
antiflood = {
|
||||
text = 5,
|
||||
|
@ -5,19 +5,7 @@
|
||||
See the "Bindings" section of README.md for usage information.
|
||||
|
||||
Copyright 2016 topkecleon <drew@otou.to>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
This code is licensed under the GNU AGPLv3. See /LICENSE for details.
|
||||
]]--
|
||||
|
||||
local bindings = {}
|
||||
@ -25,7 +13,7 @@ local bindings = {}
|
||||
local https = require('ssl.https')
|
||||
local json = require('dkjson')
|
||||
local ltn12 = require('ltn12')
|
||||
local mp_encode = require('multipart-post').encode
|
||||
local mp = require('multipart-post')
|
||||
|
||||
function bindings.init(token)
|
||||
bindings.BASE_URL = 'https://api.telegram.org/bot' .. token .. '/'
|
||||
@ -58,7 +46,7 @@ function bindings.request(method, parameters, file)
|
||||
parameters = {''}
|
||||
end
|
||||
local response = {}
|
||||
local body, boundary = mp_encode(parameters)
|
||||
local body, boundary = mp.encode(parameters)
|
||||
local success, code = https.request{
|
||||
url = bindings.BASE_URL .. method,
|
||||
method = 'POST',
|
||||
@ -79,8 +67,9 @@ function bindings.request(method, parameters, file)
|
||||
return false, false
|
||||
elseif result.ok then
|
||||
return result
|
||||
elseif result.description == 'Method not found' then
|
||||
error(method .. ': Method not found.')
|
||||
else
|
||||
assert(result.description ~= 'Method not found', method .. ': Method not found.')
|
||||
return false, result
|
||||
end
|
||||
end
|
||||
|
@ -3,26 +3,14 @@
|
||||
The heart and sole of otouto, ie the init and main loop.
|
||||
|
||||
Copyright 2016 topkecleon <drew@otou.to>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
This code is licensed under the GNU AGPLv3. See /LICENSE for details.
|
||||
]]--
|
||||
|
||||
local bot = {}
|
||||
local bindings -- Bot API bindings.
|
||||
local utilities -- Miscellaneous and shared plugins.
|
||||
|
||||
bot.version = '3.13'
|
||||
bot.version = '3.14'
|
||||
|
||||
-- Function to be run on start and reload.
|
||||
function bot:init(config)
|
||||
@ -45,14 +33,11 @@ function bot:init(config)
|
||||
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
|
||||
-- Migration code 1.13 -> 1.14
|
||||
-- "database.reminders" -> "database.remind"
|
||||
if self.database.version ~= '3.14' then
|
||||
self.database.remind = self.database.reminders
|
||||
self.database.reminders = nil
|
||||
end
|
||||
-- End migration code.
|
||||
|
||||
@ -76,7 +61,9 @@ function bot:init(config)
|
||||
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 plugin.doc then
|
||||
plugin.doc = '<pre>'..utilities.html_escape(plugin.doc)..'</pre>'
|
||||
end
|
||||
if not plugin.triggers then plugin.triggers = {} end
|
||||
end
|
||||
|
||||
@ -129,13 +116,11 @@ function bot:on_msg_receive(msg, config)
|
||||
end
|
||||
|
||||
-- Support deep linking.
|
||||
if msg.text:match('^'..config.cmd_pat..'start .+') then
|
||||
if msg.text:match('^/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,
|
||||
|
||||
-- Do the thing.
|
||||
for _, plugin in ipairs(plugint) do
|
||||
for _, trigger in ipairs(plugin.triggers) do
|
||||
@ -153,18 +138,14 @@ function bot:on_msg_receive(msg, config)
|
||||
utilities.send_reply(msg, config.errors.generic)
|
||||
end
|
||||
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config.log_chat)
|
||||
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
|
||||
|
@ -5,19 +5,7 @@
|
||||
For more documentation, read the the manual (otou.to/rtfm).
|
||||
|
||||
Copyright 2016 topkecleon <drew@otou.to>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
This code is licensed under the GNU AGPLv3. See /LICENSE for details.
|
||||
]]--
|
||||
|
||||
--[[
|
||||
@ -36,6 +24,10 @@
|
||||
target's autokick counter. Added configuration for default flag settings.
|
||||
|
||||
1.13.2 - /desc can now be used with a query.
|
||||
|
||||
1.13.3 - Removed activity array. Group listings sorted alphabetically.
|
||||
Removed unneeded "founded" and "grouptype" variables from group data. Added
|
||||
default autoban setting to config.
|
||||
]]--
|
||||
|
||||
local drua = require('otouto.drua-tg')
|
||||
@ -50,7 +42,6 @@ function administration:init(config)
|
||||
self.database.administration = {
|
||||
admins = {},
|
||||
groups = {},
|
||||
activity = {},
|
||||
autokick_timer = os.date('%d'),
|
||||
globalbans = {}
|
||||
}
|
||||
@ -66,22 +57,8 @@ function administration:init(config)
|
||||
administration.flags = administration.init_flags(config.cmd_pat)
|
||||
administration.init_command(self, config)
|
||||
|
||||
-- Migration 3.13 -> 3.13.1
|
||||
for _, group in pairs(self.database.administration.groups) do
|
||||
for i = 7, 9 do
|
||||
if group.flags[i] == nil then
|
||||
group.flags[i] = config.administration.flags[i]
|
||||
end
|
||||
end
|
||||
group.antiflood = group.antiflood or {}
|
||||
for k, v in pairs(config.administration.antiflood) do
|
||||
group.antiflood[k] = group.antiflood[k] or config.administration.antiflood[k]
|
||||
end
|
||||
end
|
||||
-- End migration
|
||||
|
||||
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.'
|
||||
|
||||
-- In the worst case, don't send errors in reply to random messages.
|
||||
administration.error = false
|
||||
@ -385,16 +362,15 @@ function administration.init_command(self_, config_)
|
||||
local from_id_str = tostring(msg.from.id)
|
||||
local chat_id_str = tostring(msg.chat.id)
|
||||
|
||||
if rank < 2 then
|
||||
if rank == 0 then -- banned
|
||||
user.do_kick = true
|
||||
user.dont_unban = true
|
||||
user.reason = 'banned'
|
||||
user.output = 'Sorry, you are banned from ' .. msg.chat.title .. '.'
|
||||
elseif rank == 1 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
|
||||
if 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)
|
||||
@ -526,12 +502,12 @@ function administration.init_command(self_, config_)
|
||||
drua.rename_chat(msg.chat.id, group.name)
|
||||
else
|
||||
group.name = msg.new_chat_title
|
||||
if group.grouptype == 'supergroup' then
|
||||
if msg.chat.type == 'supergroup' then
|
||||
administration.update_desc(self, msg.chat.id, config)
|
||||
end
|
||||
end
|
||||
elseif msg.new_chat_photo then
|
||||
if group.grouptype == 'group' then
|
||||
if msg.chat.type == 'group' then
|
||||
if rank < (group.flags[9] and 2 or 3) then
|
||||
drua.set_photo(msg.chat.id, group.photo)
|
||||
else
|
||||
@ -541,7 +517,7 @@ function administration.init_command(self_, config_)
|
||||
group.photo = drua.get_photo(msg.chat.id)
|
||||
end
|
||||
elseif msg.delete_chat_photo then
|
||||
if group.grouptype == 'group' then
|
||||
if msg.chat.type == 'group' then
|
||||
if rank < (group.flags[9] and 2 or 3) then
|
||||
drua.set_photo(msg.chat.id, group.photo)
|
||||
else
|
||||
@ -592,16 +568,6 @@ function administration.init_command(self_, config_)
|
||||
utilities.send_message(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
|
||||
@ -613,26 +579,28 @@ function administration.init_command(self_, config_)
|
||||
command = 'groups \\[query]',
|
||||
privilege = 1,
|
||||
interior = false,
|
||||
doc = 'Returns a list of groups matching the query, or a list of all administrated groups.',
|
||||
doc = 'Returns a list of groups matching a 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]
|
||||
local group_list = {}
|
||||
local result_list = {}
|
||||
for _, group in pairs(self.database.administration.groups) do
|
||||
if (not group.flags[1]) and group.link then -- no unlisted or unlinked groups
|
||||
grouplist = grouplist .. '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')\n'
|
||||
local line = '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')'
|
||||
table.insert(group_list, line)
|
||||
if input and string.match(group.name:lower(), input:lower()) then
|
||||
search_res = search_res .. '• [' .. utilities.md_escape(group.name) .. '](' .. group.link .. ')\n'
|
||||
table.insert(result_list, line)
|
||||
end
|
||||
end
|
||||
end
|
||||
local output
|
||||
if search_res ~= '' then
|
||||
output = '*Groups matching* _' .. input .. '_ *:*\n' .. search_res
|
||||
elseif grouplist ~= '' then
|
||||
output = '*Groups:*\n' .. grouplist
|
||||
if #result_list > 0 then
|
||||
table.sort(result_list)
|
||||
output = '*Groups matching* _' .. input:gsub('_', '_\\__') .. '_*:*\n' .. table.concat(result_list, '\n')
|
||||
elseif #group_list > 0 then
|
||||
table.sort(group_list)
|
||||
output = '*Groups:*\n' .. table.concat(group_list, '\n')
|
||||
else
|
||||
output = 'There are currently no listed groups.'
|
||||
end
|
||||
@ -764,13 +732,17 @@ function administration.init_command(self_, config_)
|
||||
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)
|
||||
local output = ''
|
||||
local input = utilities.input(msg.text)
|
||||
if #group.rules > 0 then
|
||||
if input and group.rules[input] then
|
||||
output = '*' .. input .. '.* ' .. group.rules[input]
|
||||
else
|
||||
if input then
|
||||
for i in input:gmatch('%g+') do
|
||||
if group.rules[tonumber(i)] then
|
||||
output = output .. '*' .. i .. '.* ' .. group.rules[tonumber(i)] .. '\n'
|
||||
end
|
||||
end
|
||||
end
|
||||
if output == '' or not input then
|
||||
output = '*Rules for ' .. msg.chat.title .. ':*\n'
|
||||
for i,v in ipairs(group.rules) do
|
||||
output = output .. '*' .. i .. '.* ' .. v .. '\n'
|
||||
@ -958,7 +930,7 @@ function administration.init_command(self_, config_)
|
||||
local output = '*MOTD for ' .. msg.chat.title .. ':*\n' .. input
|
||||
utilities.send_message(msg.chat.id, output, true, nil, true)
|
||||
end
|
||||
if group.grouptype == 'supergroup' then
|
||||
if msg.chat.type == 'supergroup' then
|
||||
administration.update_desc(self, msg.chat.id, config)
|
||||
end
|
||||
else
|
||||
@ -1195,7 +1167,7 @@ Use this command to configure the point values for each message type. When a use
|
||||
group.mods[target.id_str] = true
|
||||
group.bans[target.id_str] = nil
|
||||
end
|
||||
if group.grouptype == 'supergroup' then
|
||||
if msg.chat.type == 'supergroup' then
|
||||
local chat_member = bindings.getChatMember{ 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, s)
|
||||
@ -1234,7 +1206,7 @@ Use this command to configure the point values for each message type. When a use
|
||||
output = output .. target.name .. ' is no longer a moderator.\n'
|
||||
group.mods[target.id_str] = nil
|
||||
end
|
||||
if group.grouptype == 'supergroup' then
|
||||
if msg.chat.type == 'supergroup' then
|
||||
drua.channel_set_admin(msg.chat.id, target.id, 0, s)
|
||||
end
|
||||
end
|
||||
@ -1270,7 +1242,7 @@ Use this command to configure the point values for each message type. When a use
|
||||
group.governor = target.id
|
||||
utilities.send_reply(msg, target.name .. ' is the new governor.')
|
||||
end
|
||||
if group.grouptype == 'supergroup' then
|
||||
if msg.chat.type == 'supergroup' then
|
||||
local chat_member = bindings.getChatMember{ 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)
|
||||
@ -1305,7 +1277,7 @@ Use this command to configure the point values for each message type. When a use
|
||||
group.governor = msg.from.id
|
||||
utilities.send_reply(msg, target.name .. ' is no longer the governor.')
|
||||
end
|
||||
if group.grouptype == 'supergroup' then
|
||||
if msg.chat.type == 'supergroup' then
|
||||
drua.channel_set_admin(msg.chat.id, target.id, 0)
|
||||
administration.update_desc(self, msg.chat.id, config)
|
||||
end
|
||||
@ -1447,7 +1419,7 @@ Use this command to configure the point values for each message type. When a use
|
||||
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
|
||||
if tonumber(chat_id) < -1000000000000 then
|
||||
drua.channel_set_admin(chat_id, target.id, 0, s)
|
||||
end
|
||||
end
|
||||
@ -1482,43 +1454,41 @@ This would add a group and enable the unlisted flag, antibot, and antiflood.
|
||||
elseif group then
|
||||
utilities.send_reply(msg, 'I am already administrating this group.')
|
||||
else
|
||||
local output = 'I am now administrating this group.'
|
||||
local flags = {}
|
||||
self.database.administration.groups[tostring(msg.chat.id)] = {
|
||||
name = msg.chat.title,
|
||||
photo = drua.get_photo(msg.chat.id),
|
||||
link = drua.export_link(msg.chat.id),
|
||||
governor = msg.from.id,
|
||||
mods = {},
|
||||
bans = {},
|
||||
autoban = config.administration.autoban,
|
||||
autokicks = {},
|
||||
antiflood = {},
|
||||
flags = {},
|
||||
rules = {},
|
||||
tempbans = {}
|
||||
}
|
||||
group = self.database.administration.groups[tostring(msg.chat.id)]
|
||||
for k, v in pairs(config.administration.antiflood) do
|
||||
group.antiflood[k] = config.administration.antiflood[k]
|
||||
end
|
||||
for i = 1, #administration.flags do
|
||||
flags[i] = config.administration.flags[i]
|
||||
group.flags[i] = config.administration.flags[i]
|
||||
end
|
||||
local input = utilities.input(msg.text)
|
||||
local output = 'I am now administrating this group.'
|
||||
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
|
||||
if n and administration.flags[n] and group.flags[n] ~= true then
|
||||
group.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,
|
||||
antiflood = {}
|
||||
}
|
||||
for k, v in pairs(config.administration.antiflood) do
|
||||
self.database.administration.groups[tostring(msg.chat.id)].antiflood[k] = config.administration.antiflood[k]
|
||||
end
|
||||
administration.update_desc(self, msg.chat.id, config)
|
||||
table.insert(self.database.administration.activity, tostring(msg.chat.id))
|
||||
utilities.send_reply(msg, output)
|
||||
drua.channel_set_admin(msg.chat.id, self.info.id, 2)
|
||||
utilities.send_reply(msg, output)
|
||||
end
|
||||
end
|
||||
},
|
||||
@ -1537,11 +1507,6 @@ This would add a group and enable the unlisted flag, antibot, and antiflood.
|
||||
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
|
||||
|
@ -11,12 +11,9 @@ 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 [YYYY-MM-DD]
|
||||
apod.doc = config.cmd_pat .. [[apod [YYYY-MM-DD]
|
||||
Returns the Astronomy Picture of the Day.
|
||||
Source: nasa.gov
|
||||
]]
|
||||
apod.doc = apod.doc:gsub('/', config.cmd_pat)
|
||||
Source: nasa.gov]]
|
||||
apod.base_url = 'https://api.nasa.gov/planetary/apod?api_key=' .. (config.nasa_api_key or 'DEMO_KEY')
|
||||
end
|
||||
|
||||
|
@ -21,7 +21,7 @@ function bible:action(msg, config)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, bible.doc, true)
|
||||
utilities.send_reply(msg, bible.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -21,11 +21,9 @@ function bing:init(config)
|
||||
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 <query>
|
||||
bing.doc = [[/bing <query>
|
||||
Returns the top web results from Bing.
|
||||
Aliases: /g, /google
|
||||
]]
|
||||
Aliases: /g, /google]]
|
||||
bing.doc = bing.doc:gsub('/', config.cmd_pat)
|
||||
|
||||
end
|
||||
@ -33,7 +31,7 @@ end
|
||||
function bing:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, bing.doc, true)
|
||||
utilities.send_reply(msg, bing.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -15,7 +15,7 @@ end
|
||||
function calc:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, calc.doc, true)
|
||||
utilities.send_reply(msg, calc.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -2,6 +2,7 @@ local cats = {}
|
||||
|
||||
local HTTP = require('socket.http')
|
||||
local utilities = require('otouto.utilities')
|
||||
local bindings = require('otouto.bindings')
|
||||
|
||||
function cats:init(config)
|
||||
if not config.thecatapi_key then
|
||||
@ -29,9 +30,7 @@ function cats:action(msg, config)
|
||||
end
|
||||
|
||||
str = str:match('<img src="(.-)">')
|
||||
local output = '[Cat!]('..str..')'
|
||||
|
||||
utilities.send_message(msg.chat.id, output, false, nil, true)
|
||||
bindings.sendPhoto{chat_id = msg.chat.id, photo = str}
|
||||
|
||||
end
|
||||
|
||||
|
@ -16,45 +16,55 @@ The following markdown syntax is supported:
|
||||
_italic text_
|
||||
[text](URL)
|
||||
`inline fixed-width code`
|
||||
```pre-formatted fixed-width code block```]]
|
||||
```pre-formatted fixed-width code block```]]
|
||||
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{ 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(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
|
||||
if not input then
|
||||
utilities.send_reply(msg, channel.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
local chat_id = utilities.get_word(input, 1)
|
||||
local chat, t = bindings.getChat{chat_id = chat_id}
|
||||
if not chat then
|
||||
utilities.send_reply(msg, 'Sorry, I was unable to retrieve information for that channel.\n`' .. t.description .. '`', true)
|
||||
return
|
||||
elseif chat.result.type ~= 'channel' then
|
||||
utilities.send_reply(msg, 'Sorry, that group does not appear to be a channel.')
|
||||
return
|
||||
end
|
||||
|
||||
local admin_list, t = bindings.getChatAdministrators{ chat_id = chat_id }
|
||||
if not admin_list then
|
||||
utilities.send_reply(msg, 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`', true)
|
||||
return
|
||||
end
|
||||
|
||||
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 not is_admin then
|
||||
utilities.send_reply(msg, 'Sorry, you do not appear to be an administrator for that channel.')
|
||||
return
|
||||
end
|
||||
|
||||
local text = input:match('\n(.+)')
|
||||
if not text then
|
||||
utilities.send_reply(msg, 'Please enter a message to be sent on a new line. Markdown is supported.')
|
||||
return
|
||||
end
|
||||
|
||||
local success, result = utilities.send_message(chat_id, text, true, nil, true)
|
||||
if success then
|
||||
utilities.send_reply(msg, 'Your message has been sent!')
|
||||
else
|
||||
utilities.send_reply(msg, 'Sorry, I was unable to send your message.\n`' .. result.description .. '`', true)
|
||||
end
|
||||
utilities.send_reply(msg, output, true)
|
||||
end
|
||||
|
||||
return channel
|
||||
|
@ -13,7 +13,7 @@ function cleverbot:init(config)
|
||||
'^' .. self.info.first_name:lower() .. ', ',
|
||||
'^@' .. self.info.username:lower() .. ', '
|
||||
}
|
||||
cleverbot.url = config.chatter.cleverbot_api
|
||||
cleverbot.url = config.cleverbot.cleverbot_api
|
||||
cleverbot.error = false
|
||||
end
|
||||
|
||||
@ -22,12 +22,12 @@ function cleverbot:action(msg, config)
|
||||
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(msg.chat.id, config.chatter.connection)
|
||||
utilities.send_message(msg.chat.id, config.cleverbot.connection)
|
||||
return
|
||||
end
|
||||
local data = JSON.decode(jstr)
|
||||
if not data.clever then
|
||||
utilities.send_message(msg.chat.id, config.chatter.response)
|
||||
utilities.send_message(msg.chat.id, config.cleverbot.response)
|
||||
return
|
||||
end
|
||||
utilities.send_message(msg.chat.id, data.clever)
|
||||
|
@ -12,9 +12,10 @@ function commit:init(config)
|
||||
end
|
||||
|
||||
function commit:action(msg)
|
||||
local output = http.request('http://whatthecommit.com/index.txt') or 'Minor text fixes'
|
||||
bindings.sendMessage{
|
||||
chat_id = msg.chat.id,
|
||||
text = '```\n' .. (http.request('http://whatthecommit.com/index.txt')) .. '\n```',
|
||||
text = '```\n' .. output .. '\n```',
|
||||
parse_mode = 'Markdown'
|
||||
}
|
||||
end
|
||||
|
@ -17,7 +17,7 @@ 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(msg.chat.id, currency.doc, true, msg.message_id, true)
|
||||
utilities.send_message(msg.chat.id, currency.doc, true, msg.message_id, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -14,7 +14,7 @@ function dice:action(msg)
|
||||
|
||||
local input = utilities.input(msg.text_lower)
|
||||
if not input then
|
||||
utilities.send_message(msg.chat.id, dice.doc, true, msg.message_id, true)
|
||||
utilities.send_message(msg.chat.id, dice.doc, true, msg.message_id, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
@ -25,7 +25,7 @@ function dice:action(msg)
|
||||
count = 1
|
||||
range = input:match('^d?([%d]+)$')
|
||||
else
|
||||
utilities.send_message(msg.chat.id, dice.doc, true, msg.message_id, true)
|
||||
utilities.send_message(msg.chat.id, dice.doc, true, msg.message_id, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -30,19 +30,10 @@ function dilbert:action(msg, config)
|
||||
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('<meta property="og:image" content="(.-)"/>')
|
||||
strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif')
|
||||
end
|
||||
|
||||
local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>')
|
||||
|
||||
bindings.sendPhoto({ chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file })
|
||||
local strip_url = str:match('<meta property="og:image" content="(.-)"/>')
|
||||
bindings.sendPhoto{chat_id = msg.chat.id, photo = strip_url, caption = strip_title}
|
||||
|
||||
end
|
||||
|
||||
|
@ -10,22 +10,18 @@ function echo:init(config)
|
||||
end
|
||||
|
||||
function echo:action(msg)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
|
||||
if not input then
|
||||
utilities.send_message(msg.chat.id, echo.doc, true, msg.message_id, true)
|
||||
utilities.send_message(msg.chat.id, echo.doc, true, msg.message_id, 'html')
|
||||
else
|
||||
local output
|
||||
if msg.chat.type == 'supergroup' then
|
||||
output = utilities.style.enquote('Echo', input)
|
||||
output = '<b>Echo:</b>\n"' .. utilities.html_escape(input) .. '"'
|
||||
else
|
||||
output = utilities.md_escape(utilities.char.zwnj..input)
|
||||
output = utilities.html_escape(utilities.char.zwnj..input)
|
||||
end
|
||||
utilities.send_message(msg.chat.id, output, true, nil, true)
|
||||
utilities.send_message(msg.chat.id, output, true, nil, 'html')
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
return echo
|
||||
|
@ -10,7 +10,6 @@ function fortune:init(config)
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -11,6 +11,7 @@ local HTTPS = require('ssl.https')
|
||||
local URL = require('socket.url')
|
||||
local JSON = require('dkjson')
|
||||
local utilities = require('otouto.utilities')
|
||||
local bindings = require('otouto.bindings')
|
||||
|
||||
function gImages:init(config)
|
||||
assert(config.google_api_key and config.google_cse_key,
|
||||
@ -32,7 +33,7 @@ function gImages:action(msg, config)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, gImages.doc, true)
|
||||
utilities.send_reply(msg, gImages.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
@ -59,13 +60,12 @@ function gImages:action(msg, config)
|
||||
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_lower:match(gImages.nsfw_trigger) then
|
||||
local output = '[' .. img_title .. '](' .. img_url .. ')'
|
||||
utilities.send_message(msg.chat.id, '*NSFW*\n'..output, true, msg.message_id, true)
|
||||
else
|
||||
utilities.send_message(msg.chat.id, output, false, msg.message_id, true)
|
||||
bindings.sendPhoto{chat_id = msg.chat.id, photo = img_url, caption = img_title}
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -19,7 +19,7 @@ end
|
||||
function gMaps:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, gMaps.doc, true)
|
||||
utilities.send_reply(msg, gMaps.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -95,7 +95,7 @@ function hearthstone:action(msg, config)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, hearthstone.doc, true)
|
||||
utilities.send_reply(msg, hearthstone.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -20,8 +20,8 @@ function help:action(msg, config)
|
||||
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(msg.chat.id, output, true, nil, true)
|
||||
local output = '<b>Help for</b> <i>' .. plugin.help_word .. '</i><b>:</b>\n' .. plugin.doc
|
||||
utilities.send_message(msg.chat.id, output, true, nil, 'html')
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -36,14 +36,14 @@ function help:action(msg, config)
|
||||
end
|
||||
end
|
||||
table.sort(commandlist)
|
||||
help.text = '*Available commands:*\n• ' .. config.cmd_pat .. table.concat(commandlist, '\n• '..config.cmd_pat) .. '\nArguments: <required> [optional]'
|
||||
help.text = help.text:gsub('%[', '\\[')
|
||||
local comlist = '\n• ' .. config.cmd_pat .. table.concat(commandlist, '\n• ' .. config.cmd_pat) .. '\nArguments: <required> [optional]'
|
||||
help.text = '<b>Available commands:</b>' .. utilities.html_escape(comlist)
|
||||
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(msg.from.id, help.text, true, nil, true)
|
||||
local res = utilities.send_message(msg.from.id, help.text, true, nil, 'html')
|
||||
if not res then
|
||||
utilities.send_reply(msg, 'Please [message me privately](http://telegram.me/' .. self.info.username .. '?start=help) for a list of commands.', true)
|
||||
utilities.send_reply(msg, 'Please <a href="http://telegram.me/' .. self.info.username .. '?start=help">message me privately</a> for a list of commands.', 'html')
|
||||
elseif msg.chat.type ~= 'private' then
|
||||
utilities.send_reply(msg, 'I have sent you the requested information in a private message.')
|
||||
end
|
||||
|
@ -16,7 +16,7 @@ function imdb:action(msg, config)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, imdb.doc, true)
|
||||
utilities.send_reply(msg, imdb.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -18,7 +18,7 @@ end
|
||||
function isup:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, isup.doc, true)
|
||||
utilities.send_reply(msg, isup.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -9,19 +9,25 @@ local JSON = require('dkjson')
|
||||
local utilities = require('otouto.utilities')
|
||||
|
||||
function lastfm:init(config)
|
||||
assert(config.lastfm_api_key,
|
||||
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.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('npfull', 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 .. [[npfull [username]
|
||||
Works like ]] .. config.cmd_pat .. [[np, but returns more info, differently formatted and including album art, if available.
|
||||
|
||||
]] .. config.cmd_pat .. [[fmset <username>
|
||||
Sets your last.fm username. Otherwise, ]] .. config.cmd_pat .. [[np will use your Telegram username. Use "]] .. config.cmd_pat .. [[fmset --" to delete it.]]
|
||||
end
|
||||
|
||||
lastfm.command = 'lastfm'
|
||||
lastfm.command = 'lastfm'
|
||||
|
||||
lastfm.base_url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user='
|
||||
end
|
||||
|
||||
function lastfm:action(msg, config)
|
||||
|
||||
@ -29,12 +35,12 @@ function lastfm:action(msg, config)
|
||||
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(msg.chat.id, lastfm.doc, true, msg.message_id, true)
|
||||
if string.match(msg.text_lower, '^'..config.cmd_pat..'lastfm') then
|
||||
utilities.send_message(msg.chat.id, lastfm.doc, true, msg.message_id, 'html')
|
||||
return
|
||||
elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then
|
||||
elseif string.match(msg.text_lower, '^'..config.cmd_pat..'fmset') then
|
||||
if not input then
|
||||
utilities.send_message(msg.chat.id, lastfm.doc, true, msg.message_id, true)
|
||||
utilities.send_message(msg.chat.id, lastfm.doc, true, msg.message_id, 'html')
|
||||
elseif input == '--' or input == utilities.char.em_dash then
|
||||
self.database.userdata[from_id_str].lastfm = nil
|
||||
utilities.send_reply(msg, 'Your last.fm username has been forgotten.')
|
||||
@ -45,8 +51,6 @@ function lastfm:action(msg, config)
|
||||
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 username
|
||||
local alert = ''
|
||||
if input then
|
||||
@ -62,13 +66,11 @@ function lastfm:action(msg, config)
|
||||
return
|
||||
end
|
||||
|
||||
url = url .. URL.escape(username)
|
||||
local orig = HTTP.TIMEOUT
|
||||
HTTP.TIMEOUT = 1
|
||||
local jstr, res = HTTP.request(lastfm.base_url .. URL.escape(username))
|
||||
HTTP.TIMEOUT = orig
|
||||
|
||||
local jstr, res
|
||||
utilities.with_http_timeout(
|
||||
1, function ()
|
||||
jstr, res = HTTP.request(url)
|
||||
end)
|
||||
if res ~= 200 then
|
||||
utilities.send_reply(msg, config.errors.connection)
|
||||
return
|
||||
@ -80,29 +82,51 @@ function lastfm:action(msg, config)
|
||||
return
|
||||
end
|
||||
|
||||
jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
|
||||
if not jdat then
|
||||
local track = jdat.recenttracks.track[1] or jdat.recenttracks.track
|
||||
if not track then
|
||||
utilities.send_reply(msg, 'No history for this user.' .. alert)
|
||||
return
|
||||
end
|
||||
|
||||
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'
|
||||
local output = utilities.html_escape(input or msg.from.first_name)
|
||||
if track['@attr'] and track['@attr'].nowplaying then
|
||||
output = output .. ' is currently listening to:'
|
||||
else
|
||||
output = output .. ' last listened to:\n'
|
||||
output = output .. ' last listened to:'
|
||||
end
|
||||
|
||||
local title = jdat.name or 'Unknown'
|
||||
local artist = 'Unknown'
|
||||
if jdat.artist then
|
||||
artist = jdat.artist['#text']
|
||||
if msg.text_lower:match('^' .. config.cmd_pat .. 'npfull') then
|
||||
|
||||
output = '<b>' .. utilities.html_escape(output) .. '</b>'
|
||||
if track.name and #track.name > 0 then
|
||||
output = output .. '\n🎵 ' .. utilities.html_escape(track.name)
|
||||
else
|
||||
output = output .. '\n🎵 Unknown'
|
||||
end
|
||||
if track.artist and track.artist['#text'] and #track.artist['#text'] > 0 then
|
||||
output = output .. '\n👤 ' .. utilities.html_escape(track.artist['#text'])
|
||||
end
|
||||
if track.album and track.album['#text'] and #track.album['#text'] > 0 then
|
||||
output = output .. '\n💿 ' .. utilities.html_escape(track.album['#text'])
|
||||
end
|
||||
-- album art
|
||||
if track.image and track.image[3] and #track.image[3]['#text'] > 0 then
|
||||
output = '<a href="' .. utilities.html_escape(track.image[3]['#text']) .. '">' .. utilities.char.zwnj .. '</a>' .. output
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
output = output .. '\n'
|
||||
if track.artist and track.artist['#text'] and #track.artist['#text'] > 0 then
|
||||
output = output .. utilities.html_escape(track.artist['#text']) .. ' - '
|
||||
end
|
||||
output = output .. utilities.html_escape((track.name or 'Unknown'))
|
||||
|
||||
end
|
||||
|
||||
output = output .. title .. ' - ' .. artist .. alert
|
||||
utilities.send_message(msg.chat.id, output)
|
||||
output = output .. alert
|
||||
|
||||
utilities.send_message(msg.chat.id, output, nil, nil, 'html')
|
||||
|
||||
end
|
||||
|
||||
|
@ -8,7 +8,7 @@ patterns.doc = [[
|
||||
s/<pattern>/<substitution>
|
||||
Replace all matches for the given pattern.
|
||||
Uses Lua patterns.
|
||||
]]
|
||||
]]
|
||||
|
||||
function patterns:init(config)
|
||||
patterns.triggers = { config.cmd_pat .. '?s/.-/.-$' }
|
||||
@ -18,8 +18,7 @@ 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('"$', '')
|
||||
output = output:match('^Did you mean:\n"(.+)"$') or output
|
||||
end
|
||||
local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$')
|
||||
if not m2 then return true end
|
||||
@ -33,8 +32,8 @@ function patterns:action(msg)
|
||||
utilities.send_reply(msg, 'Malformed pattern!')
|
||||
else
|
||||
output = utilities.trim(output:sub(1, 4000))
|
||||
output = utilities.style.enquote('Did you mean', output)
|
||||
utilities.send_reply(msg.reply_to_message, output, true)
|
||||
output = '<b>Did you mean:</b>\n"' .. utilities.html_escape(output) .. '"'
|
||||
utilities.send_reply(msg.reply_to_message, output, 'html')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -19,7 +19,7 @@ function pokedex:action(msg, config)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, pokedex.doc, true)
|
||||
utilities.send_reply(msg, pokedex.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -73,7 +73,7 @@ end
|
||||
function pgc:action(msg)
|
||||
local input = utilities.input(msg.text)
|
||||
if not input then
|
||||
utilities.send_reply(msg, pgc.doc, true)
|
||||
utilities.send_reply(msg, pgc.doc, 'html')
|
||||
return
|
||||
end
|
||||
input = input .. '\n'
|
||||
|
@ -14,7 +14,7 @@ function preview:action(msg)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, preview.doc, true)
|
||||
utilities.send_reply(msg, preview.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -132,7 +132,8 @@ local puns = {
|
||||
"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 vegetables from my garden aren't that great. I guess you could say they're mediokra.",
|
||||
"Did you hear about the restaurant on the International Space Station? It lacks atmosphere, but the food is out of this world."
|
||||
}
|
||||
|
||||
function pun:action(msg)
|
||||
|
@ -1,9 +1,9 @@
|
||||
local reddit = {}
|
||||
|
||||
local HTTP = require('socket.http')
|
||||
local URL = require('socket.url')
|
||||
local JSON = require('dkjson')
|
||||
local utilities = require('otouto.utilities')
|
||||
local HTTPS = require('ssl.https')
|
||||
|
||||
reddit.command = 'reddit [r/subreddit | query]'
|
||||
|
||||
@ -65,7 +65,7 @@ function reddit:action(msg, config)
|
||||
url = reddit.rall_url .. limit
|
||||
source = '*/r/all*\n'
|
||||
end
|
||||
local jstr, res = HTTP.request(url)
|
||||
local jstr, res = HTTPS.request(url)
|
||||
if res ~= 200 then
|
||||
utilities.send_reply(msg, config.errors.connection)
|
||||
else
|
||||
|
@ -5,26 +5,31 @@ local utilities = require('otouto.utilities')
|
||||
remind.command = 'remind <duration> <message>'
|
||||
|
||||
function remind:init(config)
|
||||
self.database.reminders = self.database.reminders or {}
|
||||
self.database.remind = self.database.remind or {}
|
||||
|
||||
remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table
|
||||
|
||||
remind.doc = config.cmd_pat .. [[remind <duration> <message>
|
||||
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(msg, remind.doc, true)
|
||||
utilities.send_reply(msg, remind.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
local duration = tonumber(utilities.get_word(input, 1))
|
||||
if not duration then
|
||||
utilities.send_reply(msg, remind.doc, true)
|
||||
utilities.send_reply(msg, remind.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
@ -40,7 +45,7 @@ function remind:action(msg, config)
|
||||
elseif utilities.input(input) then
|
||||
message = utilities.input(input)
|
||||
else
|
||||
utilities.send_reply(msg, remind.doc, true)
|
||||
utilities.send_reply(msg, remind.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
@ -51,13 +56,13 @@ function remind:action(msg, config)
|
||||
|
||||
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
|
||||
self.database.remind[chat_id_str] = self.database.remind[chat_id_str] or {}
|
||||
if msg.chat.type == 'private' and utilities.table_size(self.database.remind[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
|
||||
elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.remind[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], {
|
||||
table.insert(self.database.remind[chat_id_str], {
|
||||
time = os.time() + (duration * 60),
|
||||
message = message
|
||||
})
|
||||
@ -73,14 +78,14 @@ 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
|
||||
for chat_id, group in pairs(self.database.remind) 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(chat_id, output, true, nil, true)
|
||||
local output = '<b>Reminder:</b>\n"' .. utilities.html_escape(reminder.message) .. '"'
|
||||
local res = utilities.send_message(chat_id, output, true, nil, 'html')
|
||||
-- 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
|
||||
|
@ -5,30 +5,22 @@ local bindings = require('otouto.bindings')
|
||||
local rms = {}
|
||||
|
||||
function rms:init(config)
|
||||
rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table
|
||||
rms.command = 'rms'
|
||||
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
|
||||
rms.triggers = {}
|
||||
end
|
||||
for link in s:gmatch('<a href=".-%.%a%a%a">(.-)</a>') do
|
||||
table.insert(rms.LIST, link)
|
||||
table.insert(rms.LIST, rms.BASE_URL .. link)
|
||||
end
|
||||
rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table
|
||||
end
|
||||
|
||||
function rms:action(msg, config)
|
||||
bindings.sendChatAction{ 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({ chat_id = msg.chat.id }, { photo = filename })
|
||||
bindings.sendPhoto{chat_id = msg.chat.id, photo = rms.LIST[math.random(#rms.LIST)]}
|
||||
end
|
||||
|
||||
return rms
|
||||
|
@ -22,7 +22,7 @@ function setandget:action(msg, config)
|
||||
if msg.text_lower:match('^'..config.cmd_pat..'set') then
|
||||
|
||||
if not input then
|
||||
utilities.send_message(msg.chat.id, setandget.doc, true, nil, true)
|
||||
utilities.send_message(msg.chat.id, setandget.doc, true, nil, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
@ -30,7 +30,7 @@ function setandget:action(msg, config)
|
||||
local value = utilities.input(input)
|
||||
|
||||
if not name or not value then
|
||||
utilities.send_message(msg.chat.id, setandget.doc, true, nil, true)
|
||||
utilities.send_message(msg.chat.id, setandget.doc, true, nil, 'html')
|
||||
elseif value == '--' or value == '—' then
|
||||
self.database.setandget[chat_id_str][name] = nil
|
||||
utilities.send_message(msg.chat.id, 'That value has been deleted.')
|
||||
|
@ -14,7 +14,7 @@ function shout:action(msg)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, shout.doc, true)
|
||||
utilities.send_reply(msg, shout.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -40,7 +40,7 @@ local corrected_numbers = {
|
||||
function starwars:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, starwars.doc, true)
|
||||
utilities.send_reply(msg, starwars.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -16,7 +16,7 @@ end
|
||||
function time:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, time.doc, true)
|
||||
utilities.send_reply(msg, time.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -22,7 +22,7 @@ end
|
||||
function translate:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, translate.doc, true)
|
||||
utilities.send_reply(msg, translate.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
@ -39,7 +39,9 @@ function translate:action(msg, config)
|
||||
return
|
||||
end
|
||||
|
||||
utilities.send_reply(msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true)
|
||||
local output = '<b>Translation:</b>\n"' .. utilities.html_escape(data.text[1]) .. '"'
|
||||
|
||||
utilities.send_reply(msg.reply_to_message or msg, output, 'html')
|
||||
end
|
||||
|
||||
return translate
|
||||
|
@ -11,18 +11,16 @@ 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 <query>
|
||||
urbandictionary.doc = [[/urbandictionary <query>
|
||||
Returns a definition from Urban Dictionary.
|
||||
Aliases: /ud, /urban
|
||||
]]
|
||||
Aliases: /ud, /urban]]
|
||||
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(msg, urbandictionary.doc, true)
|
||||
utilities.send_reply(msg, urbandictionary.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
@ -38,13 +36,13 @@ function urbandictionary:action(msg, config)
|
||||
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('_', '_\\__')
|
||||
output = string.format('<b>%s</b>\n\n%s\n\n<i>%s</i>',
|
||||
utilities.html_escape(data.list[1].word),
|
||||
utilities.trim(utilities.html_escape(data.list[1].definition)),
|
||||
utilities.trim(utilities.html_escape(data.list[1].example or ''))
|
||||
)
|
||||
end
|
||||
utilities.send_reply(msg, output, true)
|
||||
utilities.send_reply(msg, output, 'html')
|
||||
end
|
||||
|
||||
return urbandictionary
|
||||
|
44
otouto/plugins/wait.lua
Normal file
44
otouto/plugins/wait.lua
Normal file
@ -0,0 +1,44 @@
|
||||
local utilities = require('otouto.utilities')
|
||||
local bot = require('otouto.bot')
|
||||
|
||||
local wait = {}
|
||||
|
||||
function wait:init(config)
|
||||
self.database.wait = self.database.wait or {}
|
||||
wait.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wait', true).table
|
||||
wait.command = '/wait <duration> <command> [args]'
|
||||
wait.doc = config.cmd_pat .. [[wait <duration> <command> [args]
|
||||
Postpone a command for a given duration, in minutes.
|
||||
Max duration is 10000.]]
|
||||
end
|
||||
|
||||
-- ex: /wait 15 /calc 5 * 10
|
||||
function wait:action(msg, config)
|
||||
local duration = utilities.get_word(msg.text, 2)
|
||||
duration = tonumber(duration)
|
||||
local input = msg.text
|
||||
repeat
|
||||
input = input:gsub('^' .. config.cmd_pat .. '[Ww][Aa][Ii][Tt] %g+ ', '')
|
||||
until not input:match('^' .. config.cmd_pat .. '[Ww][Aa][Ii][Tt] %g+ ')
|
||||
if not input or not duration or duration > 10000 then
|
||||
utilities.send_reply(msg, wait.doc, 'html')
|
||||
return
|
||||
end
|
||||
msg.date = msg.date + ( duration * 60 )
|
||||
msg.text = input
|
||||
table.insert(self.database.wait, msg)
|
||||
utilities.send_reply(msg, 'Queued.')
|
||||
end
|
||||
|
||||
function wait:cron(config)
|
||||
local now = os.time() + 1
|
||||
for k, msg in pairs(self.database.wait) do
|
||||
if msg.date < now then
|
||||
msg.date = os.time()
|
||||
bot.on_msg_receive(self, msg, config)
|
||||
self.database.wait[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return wait
|
@ -21,7 +21,7 @@ function weather:action(msg, config)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, weather.doc, true)
|
||||
utilities.send_reply(msg, weather.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -20,7 +20,7 @@ end
|
||||
function wikipedia:action(msg, config)
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, wikipedia.doc, true)
|
||||
utilities.send_reply(msg, wikipedia.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -24,7 +24,7 @@ function youtube:action(msg, config)
|
||||
|
||||
local input = utilities.input_from_msg(msg)
|
||||
if not input then
|
||||
utilities.send_reply(msg, youtube.doc, true)
|
||||
utilities.send_reply(msg, youtube.doc, 'html')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -3,23 +3,9 @@
|
||||
Functions shared among otouto plugins.
|
||||
|
||||
Copyright 2016 topkecleon <drew@otou.to>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
This code is licensed under the GNU AGPLv3. See /LICENSE for details.
|
||||
]]--
|
||||
|
||||
local utilities = {}
|
||||
|
||||
local HTTP = require('socket.http')
|
||||
local ltn12 = require('ltn12')
|
||||
local HTTPS = require('ssl.https')
|
||||
@ -30,6 +16,8 @@ local bindings = require('otouto.bindings')
|
||||
-- If no built-in utf8 is available, load the library.
|
||||
local utf8 = utf8 or require('lua-utf8')
|
||||
|
||||
local utilities = {}
|
||||
|
||||
-- For the sake of ease to new contributors and familiarity to old contributors,
|
||||
-- we'll provide a couple of aliases to real bindings here.
|
||||
-- Edit: To keep things working and allow for HTML messages, you can now pass a
|
||||
@ -159,7 +147,7 @@ end
|
||||
-- Get the number of values in a key/value table.
|
||||
function utilities.table_size(tab)
|
||||
local i = 0
|
||||
for _,_ in pairs(tab) do
|
||||
for _ in pairs(tab) do
|
||||
i = i + 1
|
||||
end
|
||||
return i
|
||||
@ -260,13 +248,6 @@ function utilities.triggers(username, cmd_pat, trigger_table)
|
||||
return self
|
||||
end
|
||||
|
||||
function utilities.with_http_timeout(timeout, fun)
|
||||
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))
|
||||
@ -317,13 +298,6 @@ function utilities.set_meta:__len()
|
||||
return self.__count
|
||||
end
|
||||
|
||||
-- Styling functions to keep things consistent and easily changeable across plugins.
|
||||
-- More to be added.
|
||||
utilities.style = {}
|
||||
utilities.style.enquote = function(title, body)
|
||||
return '*' .. title:gsub('*', '\\*') .. ':*\n"' .. utilities.md_escape(body) .. '"'
|
||||
end
|
||||
|
||||
-- Converts a gross string back into proper UTF-8.
|
||||
-- Useful for fixing improper encoding caused by bad JSON escaping.
|
||||
function utilities.fix_utf8(str)
|
||||
|
Reference in New Issue
Block a user