otouto 3.13

good lord
This commit is contained in:
topkecleon
2016-08-13 22:26:44 -04:00
parent e19d2e1e84
commit 43a6b53c90
52 changed files with 1315 additions and 1617 deletions

View File

@ -1,36 +1,19 @@
local about = {}
local bot = require('otouto.bot')
local utilities = require('otouto.utilities')
local about = {}
about.command = 'about'
about.doc = 'Returns information about the bot.'
about.triggers = {
''
}
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
end
function about:action(msg, config)
-- Filthy hack, but here is where we'll stop forwarded messages from hitting
-- other plugins.
if msg.forward_from then return end
local output = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.'
if
(msg.new_chat_member and msg.new_chat_member.id == self.info.id)
or msg.text_lower:match('^'..config.cmd_pat..'about$')
or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower()..'$')
or msg.text_lower:match('^'..config.cmd_pat..'start$')
or msg.text_lower:match('^'..config.cmd_pat..'start@'..self.info.username:lower()..'$')
then
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
return true
utilities.send_message(self, msg.chat.id, about.text, true, nil, true)
end
return about

View File

@ -9,8 +9,6 @@
It requires tg (http://github.com/vysheng/tg) with supergroup support.
For more documentation, read the the manual (otou.to/rtfm).
Remember to load this before blacklist.lua.
Important notices about updates will be here!
1.11 - Removed /kickme and /broadcast. Users should leave manually, and
@ -20,6 +18,8 @@
necessary.
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.
]]
local JSON = require('dkjson')
@ -36,11 +36,12 @@ function administration:init(config)
admins = {},
groups = {},
activity = {},
autokick_timer = os.date('%d')
autokick_timer = os.date('%d'),
globalbans = {}
}
end
self.admin_temp = {
administration.temp = {
help = {},
flood = {}
}
@ -49,13 +50,25 @@ function administration:init(config)
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]'
-- 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 {
@ -106,18 +119,6 @@ function administration.init_flags(cmd_pat) return {
}
} end
administration.antiflood = {
text = 5,
voice = 5,
audio = 5,
contact = 5,
photo = 10,
video = 10,
location = 10,
document = 10,
sticker = 20
}
administration.ranks = {
[0] = 'Banned',
[1] = 'Users',
@ -159,8 +160,8 @@ function administration:get_rank(user_id_str, chat_id_str, config)
end
end
-- Return 0 if the user_id_str is blacklisted (and antihammer is not enabled).
if self.database.blacklist[user_id_str] then
-- 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
@ -172,8 +173,9 @@ 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(msg.reply_to_message.from) do
for k,v in pairs(d) do
target[k] = v
end
target.name = utilities.build_name(target.first_name, target.last_name)
@ -184,7 +186,7 @@ function administration:get_targets(msg, config)
local input = utilities.input(msg.text)
if input then
local t = {}
for _, user in ipairs(utilities.index(input)) do
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
@ -195,10 +197,11 @@ function administration:get_targets(msg, config)
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 = tonumber(user),
id_str = user,
name = 'Unknown ('..user..')',
id = id,
id_str = tostring(id),
name = 'Unknown ('..id..')',
rank = administration.get_rank(self, user, msg.chat.id, config)
}
table.insert(t, target)
@ -227,7 +230,7 @@ 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.markdown_escape(name)
name = utilities.md_escape(name)
local output = '' .. name .. ' `[' .. id .. ']`\n'
return output
end
@ -356,36 +359,36 @@ function administration.init_command(self_, config_)
if not group.antiflood then
group.antiflood = JSON.decode(JSON.encode(administration.antiflood))
end
if not self.admin_temp.flood[chat_id_str] then
self.admin_temp.flood[chat_id_str] = {}
if not administration.temp.flood[chat_id_str] then
administration.temp.flood[chat_id_str] = {}
end
if not self.admin_temp.flood[chat_id_str][from_id_str] then
self.admin_temp.flood[chat_id_str][from_id_str] = 0
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.
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.sticker
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
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.photo
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
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.document
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
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.audio
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
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.contact
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
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.video
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
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.location
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
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.voice
administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.voice
else
self.admin_temp.flood[chat_id_str][from_id_str] = self.admin_temp.flood[chat_id_str][from_id_str] + group.antiflood.text
administration.temp.flood[chat_id_str][from_id_str] = administration.temp.flood[chat_id_str][from_id_str] + group.antiflood.text
end
if self.admin_temp.flood[chat_id_str][from_id_str] > 99 then
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)
self.admin_temp.flood[chat_id_str][from_id_str] = nil
administration.temp.flood[chat_id_str][from_id_str] = nil
end
end
@ -586,7 +589,7 @@ function administration.init_command(self_, config_)
else
local output = '*Commands for ' .. administration.ranks[rank] .. ':*\n'
for i = 1, rank do
for _, val in ipairs(self.admin_temp.help[i]) do
for _, val in ipairs(administration.temp.help[i]) do
output = output .. '' .. config.cmd_pat .. val .. '\n'
end
end
@ -685,7 +688,7 @@ function administration.init_command(self_, config_)
},
{ -- /motd
triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('motd').table,
triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('motd'):t('qotd').table,
command = 'motd',
privilege = 1,
@ -821,7 +824,7 @@ function administration.init_command(self_, config_)
triggers = utilities.triggers(self_.info.username, config_.cmd_pat):t('setmotd', true):t('setqotd', true).table,
command = 'setmotd <motd>',
privilege = 2,
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.',
@ -977,8 +980,7 @@ function administration.init_command(self_, config_)
local output = ''
local input = utilities.input(msg.text)
if input then
local index = utilities.index(input)
for _, i in ipairs(index) do
for i in input:gmatch('%g+') do
local n = tonumber(i)
if n and administration.flags[n] then
if group.flags[n] == true then
@ -1069,7 +1071,10 @@ function administration.init_command(self_, config_)
group.bans[target.id_str] = nil
end
if group.grouptype == 'supergroup' then
drua.channel_set_admin(msg.chat.id, target.id, 2)
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
@ -1138,7 +1143,10 @@ function administration.init_command(self_, config_)
utilities.send_reply(self, msg, target.name .. ' is the new governor.')
end
if group.grouptype == 'supergroup' then
drua.channel_set_admin(msg.chat.id, target.id, 2)
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
@ -1195,7 +1203,7 @@ function administration.init_command(self_, config_)
for _, target in ipairs(targets) do
if target.err then
output = output .. target.err .. '\n'
elseif self.database.blacklist[target.id_str] then
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'
@ -1211,7 +1219,7 @@ function administration.init_command(self_, config_)
end
end
end
self.database.blacklist[target.id_str] = true
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
@ -1243,10 +1251,10 @@ function administration.init_command(self_, config_)
for _, target in ipairs(targets) do
if target.err then
output = output .. target.err .. '\n'
elseif not self.database.blacklist[target.id_str] then
elseif not self.database.administration.globalbans[target.id_str] then
output = output .. target.name .. ' is not globally banned.\n'
else
self.database.blacklist[target.id_str] = nil
self.database.administration.globalbans[target.id_str] = nil
output = output .. target.name .. ' has been globally unbanned.\n'
end
end
@ -1333,7 +1341,7 @@ function administration.init_command(self_, config_)
action = function(self, msg, group, config)
if msg.chat.id == msg.from.id then
utilities.send_message(self, msg.chat.id, 'No.')
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
@ -1344,8 +1352,7 @@ function administration.init_command(self_, config_)
end
local input = utilities.input(msg.text)
if input then
local index = utilities.index(input)
for _, i in ipairs(index) do
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
@ -1442,11 +1449,11 @@ function administration.init_command(self_, config_)
-- Generate help messages and ahelp keywords.
self_.database.administration.help = {}
for i,_ in ipairs(administration.ranks) do
self_.admin_temp.help[i] = {}
administration.temp.help[i] = {}
end
for _,v in ipairs(administration.commands) do
if v.command then
table.insert(self_.admin_temp.help[v.privilege], v.command)
table.insert(administration.temp.help[v.privilege], v.command)
if v.doc then
v.keyword = utilities.get_word(v.command, 1)
end
@ -1475,7 +1482,7 @@ function administration:action(msg, config)
end
function administration:cron()
self.admin_temp.flood = {}
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

View File

@ -10,79 +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):t('apodhd', true):t('apodtext', true).table
apod.doc = config.cmd_pat .. [[apod [query]
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.
If the query is a date, in the format YYYY-MM-DD, the APOD of that day is returned.
Examples:
]] .. config.cmd_pat .. [[apodhd [query]
Returns the image in HD, if available.
]] .. config.cmd_pat .. [[apodtext [query]
Returns the explanation of the APOD.
Source: nasa.gov]]
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')
end
function apod:action(msg, config)
if not config.nasa_api_key then
config.nasa_api_key = 'DEMO_KEY'
end
local input = utilities.input(msg.text)
local date = '*'
local disable_page_preview = false
local url = 'https://api.nasa.gov/planetary/apod?api_key=' .. config.nasa_api_key
local url = apod.base_url
local date = os.date('%F')
if input then
if input:match('(%d+)%-(%d+)%-(%d+)$') then
if input:match('^(%d+)%-(%d+)%-(%d+)$') then
url = url .. '&date=' .. URL.escape(input)
date = date .. input
else
utilities.send_message(self, msg.chat.id, apod.doc, true, msg.message_id, true)
return
date = input
end
else
date = date .. os.date("%F")
end
date = date .. '*\n'
local jstr, res = HTTPS.request(url)
if res ~= 200 then
local jstr, code = HTTPS.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.error then
local data = JSON.decode(jstr)
if data.error then
utilities.send_reply(self, msg, config.errors.results)
return
end
local img_url = jdat.url
if string.match(msg.text, '^'..config.cmd_pat..'apodhd*') then
img_url = jdat.hdurl or jdat.url
end
local output = date .. '[' .. jdat.title .. '](' .. img_url .. ')'
if string.match(msg.text, '^'..config.cmd_pat..'apodtext*') then
output = output .. '\n' .. jdat.explanation
disable_page_preview = true
end
if jdat.copyright then
output = output .. '\nCopyright: ' .. jdat.copyright
end
utilities.send_message(self, msg.chat.id, output, disable_page_preview, nil, true)
local output = string.format(
'<b>%s (</b><a href="%s">%s</a><b>)</b>\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

View File

@ -5,11 +5,9 @@ local URL = require('socket.url')
local utilities = require('otouto.utilities')
function bible:init(config)
if not config.biblia_api_key then
print('Missing config value: biblia_api_key.')
print('bible.lua will not be enabled.')
return
end
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 <reference>
@ -21,9 +19,9 @@ bible.command = 'bible <reference>'
function bible:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_message(self, msg.chat.id, bible.doc, true, msg.message_id, true)
utilities.send_reply(self, msg, bible.doc, true)
return
end

View File

@ -1,5 +1,4 @@
-- Credit to Juan (tg:JuanPotato; gh:JuanPotato) for this plugin.
-- Or rather, the seven lines that actually mean anything.
local bing = {}
@ -15,54 +14,65 @@ bing.command = 'bing <query>'
bing.search_url = 'https://api.datamarket.azure.com/Data.ashx/Bing/Search/Web?Query=\'%s\'&$format=json'
function bing:init(config)
if not config.bing_api_key then
print('Missing config value: bing_api_key.')
print('bing.lua will not be enabled.')
return
end
bing.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bing', true):t('g', true):t('google', true).table
bing.doc = config.cmd_pat .. [[bing <query>
Returns the top web search results from Bing.
Aliases: ]] .. config.cmd_pat .. 'g, ' .. config.cmd_pat .. 'google'
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 <query>
Returns the top web results from Bing.
Aliases: /g, /google
]]
bing.doc = bing.doc:gsub('/', config.cmd_pat)
end
function bing:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text ~= '' then
input = msg.reply_to_message.text
else
utilities.send_reply(self, msg, bing.doc, true)
return
end
utilities.send_reply(self, msg, bing.doc, true)
return
end
local url = bing.search_url:format(URL.escape(input))
local resbody = {}
local _,b,_ = https.request{
local _, code = https.request{
url = url,
headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. config.bing_api_key) },
headers = bing.headers,
sink = ltn12.sink.table(resbody),
}
if b ~= 200 then
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 dat = JSON.decode(table.concat(resbody))
local limit = 4
if msg.chat.type == 'private' then
limit = 8
end
if limit > #dat.d.results then
limit = #dat.d.results
end
local reslist = {}
for i = 1, limit do
local result = dat.d.results[i]
local s = '• [' .. result.Title:gsub('%]', '\\]') .. '](' .. result.Url:gsub('%)', '\\)') .. ')'
table.insert(reslist, s)
table.insert(reslist, string.format(
'• <a href="%s">%s</a>',
utilities.html_escape(data.d.results[i].Url),
utilities.html_escape(data.d.results[i].Title)
))
end
local output = '*Search results for* _' .. utilities.md_escape(input) .. '_ *:*\n' .. table.concat(reslist, '\n')
utilities.send_message(self, msg.chat.id, output, true, nil, true)
local output = string.format(
'<b>Search results for</b> <i>%s</i><b>:</b>\n%s',
utilities.html_escape(input),
table.concat(reslist, '\n')
)
utilities.send_message(self, msg.chat.id, output, true, nil, 'html')
end
return bing

View File

@ -1,39 +1,15 @@
-- This plugin will allow the admin to blacklist users who will be unable to
-- use the bot. This plugin should be at the top of your plugin list in config.
local utilities = require('otouto.utilities')
local blacklist = {}
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
function blacklist:init()
if not self.database.blacklist then
self.database.blacklist = {}
end
function blacklist:init(config)
blacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('blacklist', true):t('unblacklist', true).table
blacklist.error = false
end
blacklist.triggers = {
''
}
blacklist.error = false
function blacklist:action(msg, config)
if self.database.blacklist[tostring(msg.from.id)] then
return
elseif self.database.blacklist[tostring(msg.chat.id)] then
bindings.leaveChat(self, { chat_id = msg.chat.id })
return
end
if not (
msg.from.id == config.admin
and (
msg.text:match('^'..config.cmd_pat..'blacklist')
or msg.text:match('^'..config.cmd_pat..'unblacklist')
)
) then
return true
end
if msg.from.id ~= config.admin then return true end
local targets = {}
if msg.reply_to_message then
table.insert(targets, {
@ -44,7 +20,7 @@ function blacklist:action(msg, config)
else
local input = utilities.input(msg.text)
if input then
for _, user in ipairs(utilities.index(input)) do
for user in input:gmatch('%g+') do
if self.database.users[user] then
table.insert(targets, {
id = self.database.users[user].id,

View File

@ -13,29 +13,16 @@ Returns solutions to mathematical expressions and conversions between common uni
end
function calc:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, calc.doc, true, msg.message_id, true)
return
end
end
local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input)
local output = HTTPS.request(url)
if not output then
utilities.send_reply(self, msg, config.errors.connection)
utilities.send_reply(self, msg, calc.doc, true)
return
end
output = '`' .. output .. '`'
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, 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

View File

@ -0,0 +1,28 @@
-- Based on a plugin by matthewhesketh.
local JSON = require('dkjson')
local HTTP = require('socket.http')
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'
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)
end
return catfact

View File

@ -1,80 +0,0 @@
-- Put this absolutely at the end, even after greetings.lua.
local chatter = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
function chatter:init(config)
if not config.simsimi_key then
print('Missing config value: simsimi_key.')
print('chatter.lua will not be enabled.')
return
end
chatter.triggers = {
''
}
end
chatter.base_url = 'http://%sapi.simsimi.com/request.p?key=%s&lc=%s&ft=1.0&text=%s'
function chatter:action(msg, config)
if msg.text == '' then return true end
if (
not (
msg.text_lower:match('^'..self.info.first_name:lower()..',')
or msg.text_lower:match('^@'..self.info.username:lower()..',')
or msg.from.id == msg.chat.id
--Uncomment the following line for Al Gore-like conversation.
--or (msg.reply_to_message and msg.reply_to_message.from.id == self.info.id)
)
or msg.text:match('^'..config.cmd_pat)
or msg.text == ''
) then
return true
end
bindings.sendChatAction(self, { action = 'typing' } )
local input = msg.text_lower:gsub(self.info.first_name, 'simsimi')
input = input:gsub('@'..self.info.username, 'simsimi')
local sandbox = config.simsimi_trial and 'sandbox.' or ''
local url = chatter.base_url:format(sandbox, config.simsimi_key, config.lang, URL.escape(input))
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_message(self, msg.chat.id, config.errors.chatter_connection)
return
end
local jdat = JSON.decode(jstr)
if not jdat.response or jdat.response:match('^I HAVE NO RESPONSE.') then
utilities.send_message(self, msg.chat.id, config.errors.chatter_response)
return
end
local output = jdat.response
-- Clean up the response here.
output = utilities.trim(output)
-- Simsimi will often refer to itself. Replace "simsimi" with the bot name.
output = output:gsub('%aimi?%aimi?', self.info.first_name)
-- Self-explanatory.
output = output:gsub('USER', msg.from.first_name)
-- Capitalize the first letter.
output = output:gsub('^%l', string.upper)
-- Add a period if there is no punctuation.
output = output:gsub('%P$', '%1.')
utilities.send_message(self, msg.chat.id, output)
end
return chatter

View File

@ -0,0 +1,28 @@
-- Based on a plugin by matthewhesketh.
local JSON = require('dkjson')
local HTTP = require('socket.http')
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'
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)
end
return chuck

View File

@ -0,0 +1,36 @@
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
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
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)
end
return cleverbot

View File

@ -1,8 +1,8 @@
-- Commits from https://github.com/ngerakines/commitment.
local commit = {}
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local http = require('socket.http')
commit.command = 'commit'
commit.doc = 'Returns a commit message from whatthecommit.com.'
@ -11,420 +11,16 @@ function commit:init(config)
commit.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('commit').table
end
local commits = {
"One does not simply merge into master",
"Merging the merge",
"Another bug bites the dust",
"de-misunderestimating",
"Some shit.",
"add actual words",
"I CAN HAZ COMMENTZ.",
"giggle.",
"Whatever.",
"Finished fondling.",
"FONDLED THE CODE",
"this is how we generate our shit.",
"unh",
"It works!",
"unionfind is no longer being molested.",
"Well, it's doing something.",
"I'M PUSHING.",
"Whee.",
"Whee, good night.",
"It'd be nice if type errors caused the compiler to issue a type error",
"Fucking templates.",
"I hate this fucking language.",
"marks",
"that coulda been bad",
"hoo boy",
"It was the best of times, it was the worst of times",
"Fucking egotistical bastard. adds expandtab to vimrc",
"if you're not using et, fuck off",
"WHO THE FUCK CAME UP WITH MAKE?",
"This is a basic implementation that works.",
"By works, I meant 'doesnt work'. Works now..",
"Last time I said it works? I was kidding. Try this.",
"Just stop reading these for a while, ok..",
"Give me a break, it's 2am. But it works now.",
"Make that it works in 90% of the cases. 3:30.",
"Ok, 5am, it works. For real.",
"FOR REAL.",
"I don't know what these changes are supposed to accomplish but somebody told me to make them.",
"I don't get paid enough for this shit.",
"fix some fucking errors",
"first blush",
"So my boss wanted this button ...",
"uhhhhhh",
"forgot we're not using a smart language",
"include shit",
"To those I leave behind, good luck!",
"things occurred",
"i dunno, maybe this works",
"8==========D",
"No changes made",
"whooooooooooooooooooooooooooo",
"clarify further the brokenness of C++. why the fuck are we using C++?",
".",
"Friday 5pm",
"changes",
"A fix I believe, not like I tested or anything",
"Useful text",
"pgsql is being a pain",
"pgsql is more strict, increase the hackiness up to 11",
"c&p fail",
"syntax",
"fix",
"just shoot me",
"arrrggghhhhh fixed!",
"someone fails and it isn't me",
"totally more readable",
"better grepping",
"fix",
"fix bug, for realz",
"fix /sigh",
"Does this work",
"MOAR BIFURCATION",
"bifurcation",
"REALLY FUCKING FIXED",
"FIX",
"better ignores",
"More ignore",
"more ignores",
"more ignores",
"more ignores",
"more ignores",
"more ignores",
"more ignored words",
"more fixes",
"really ignore ignored worsd",
"fixes",
"/sigh",
"fix",
"fail",
"pointless limitation",
"omg what have I done?",
"added super-widget 2.0.",
"tagging release w.t.f.",
"I can't believe it took so long to fix this.",
"I must have been drunk.",
"This is why the cat shouldn't sit on my keyboard.",
"This is why git rebase is a horrible horrible thing.",
"ajax-loader hotness, oh yeah",
"small is a real HTML tag, who knew.",
"WTF is this.",
"Do things better, faster, stronger",
"Use a real JS construct, WTF knows why this works in chromium.",
"Added a banner to the default admin page. Please have mercy on me =(",
"needs more cow bell",
"Switched off unit test X because the build had to go out now and there was no time to fix it properly.",
"Updated",
"I must sleep... it's working... in just three hours...",
"I was wrong...",
"Completed with no bugs...",
"Fixed a little bug...",
"Fixed a bug in NoteLineCount... not seriously...",
"woa!! this one was really HARD!",
"Made it to compile...",
"changed things...",
"touched...",
"i think i fixed a bug...",
"perfect...",
"Moved something to somewhere... goodnight...",
"oops, forgot to add the file",
"Corrected mistakes",
"oops",
"oops!",
"put code that worked where the code that didn't used to be",
"Nothing to see here, move along",
"I am even stupider than I thought",
"I don't know what the hell I was thinking.",
"fixed errors in the previous commit",
"Committed some changes",
"Some bugs fixed",
"Minor updates",
"Added missing file in previous commit",
"bug fix",
"typo",
"bara bra grejjor",
"Continued development...",
"Does anyone read this? I'll be at the coffee shop accross the street.",
"That's just how I roll",
"work in progress",
"minor changes",
"some brief changes",
"assorted changes",
"lots and lots of changes",
"another big bag of changes",
"lots of changes after a lot of time",
"LOTS of changes. period",
"Test commit. Please ignore",
"I'm just a grunt. Don't blame me for this awful PoS.",
"I did it for the lulz!",
"I'll explain this when I'm sober .. or revert it",
"Obligatory placeholder commit message",
"A long time ago, in a galaxy far far away...",
"Fixed the build.",
"various changes",
"One more time, but with feeling.",
"Handled a particular error.",
"Fixed unnecessary bug.",
"Removed code.",
"Added translation.",
"Updated build targets.",
"Refactored configuration.",
"Locating the required gigapixels to render...",
"Spinning up the hamster...",
"Shovelling coal into the server...",
"Programming the flux capacitor",
"The last time I tried this the monkey didn't survive. Let's hope it works better this time.",
"I should have had a V8 this morning.",
"640K ought to be enough for anybody",
"pay no attention to the man behind the curtain",
"a few bits tried to escape, but we caught them",
"Who has two thumbs and remembers the rudiments of his linear algebra courses? Apparently, this guy.",
"workaround for ant being a pile of fail",
"Don't push this commit",
"rats",
"squash me",
"fixed mistaken bug",
"Final commit, ready for tagging",
"-m \'So I hear you like commits ...\'",
"epic",
"need another beer",
"Well the book was obviously wrong.",
"lolwhat?",
"Another commit to keep my CAN streak going.",
"I cannot believe that it took this long to write a test for this.",
"TDD: 1, Me: 0",
"Yes, I was being sarcastic.",
"Apparently works-for-me is a crappy excuse.",
"tl;dr",
"I would rather be playing SC2.",
"Crap. Tonight is raid night and I am already late.",
"I know what I am doing. Trust me.",
"You should have trusted me.",
"Is there an award for this?",
"Is there an achievement for this?",
"I'm totally adding this to epic win. +300",
"This really should not take 19 minutes to build.",
"fixed the israeli-palestinian conflict",
"SHIT ===> GOLD",
"Committing in accordance with the prophecy.",
"It compiles! Ship it!",
"LOL!",
"Reticulating splines...",
"SEXY RUSSIAN CODES WAITING FOR YOU TO CALL",
"s/import/include/",
"extra debug for stuff module",
"debug line test",
"debugo",
"remove debug<br/>all good",
"debug suff",
"more debug... who overwrote!",
"these confounded tests drive me nuts",
"For great justice.",
"QuickFix.",
"oops - thought I got that one.",
"removed echo and die statements, lolz.",
"somebody keeps erasing my changes.",
"doh.",
"pam anderson is going to love me.",
"added security.",
"arrgghh... damn this thing for not working.",
"jobs... steve jobs",
"and a comma",
"this is my quickfix branch and i will use to do my quickfixes",
"Fix my stupidness",
"and so the crazy refactoring process sees the sunlight after some months in the dark!",
"gave up and used tables.",
"[Insert your commit message here. Be sure to make it descriptive.]",
"Removed test case since code didn't pass QA",
"removed tests since i can't make them green",
"stuff",
"more stuff",
"Become a programmer, they said. It'll be fun, they said.",
"Same as last commit with changes",
"foo",
"just checking if git is working properly...",
"fixed some minor stuff, might need some additional work.",
"just trolling the repo",
"All your codebase are belong to us.",
"Somebody set up us the bomb.",
"should work I guess...",
"To be honest, I do not quite remember everything I changed here today. But it is all good, I tell ya.",
"well crap.",
"herpderp (redux)",
"herpderp",
"Derp",
"derpherp",
"Herping the derp",
"sometimes you just herp the derp so hard it herpderps",
"Derp. Fix missing constant post rename",
"Herping the fucking derp right here and now.",
"Derp, asset redirection in dev mode",
"mergederp",
"Derp search/replace fuckup",
"Herpy dooves.",
"Derpy hooves",
"derp, helper method rename",
"Herping the derp derp (silly scoping error)",
"Herp derp I left the debug in there and forgot to reset errors.",
"Reset error count between rows. herpderp",
"hey, what's that over there?!",
"hey, look over there!",
"It worked for me...",
"Does not work.",
"Either Hot Shit or Total Bollocks",
"Arrrrgggg",
"Dont mess with Voodoo",
"I expected something different.",
"Todo!!!",
"This is supposed to crash",
"No changes after this point.",
"I know, I know, this is not how Im supposed to do it, but I can't think of something better.",
"Dont even try to refactor it.",
"(c) Microsoft 1988",
"Please no changes this time.",
"Why The Fuck?",
"We should delete this crap before shipping.",
"Shit code!",
"ALL SORTS OF THINGS",
"Herpderp, shoulda check if it does really compile.",
"I CAN HAZ PYTHON, I CAN HAZ INDENTS",
"Major fixup.",
"less french words",
"breathe, =, breathe",
"IEize",
"this doesn't really make things faster, but I tried",
"this should fix it",
"forgot to save that file",
"Glue. Match sticks. Paper. Build script!",
"Argh! About to give up :(",
"Blaming regex.",
"oops",
"it's friday",
"yo recipes",
"Not sure why",
"lol digg",
"grrrr",
"For real, this time.",
"Feed. You. Stuff. No time.",
"I don't give a damn 'bout my reputation",
"DEAL WITH IT",
"commit",
"tunning",
"I really should've committed this when I finished it...",
"It's getting hard to keep up with the crap I've trashed",
"I honestly wish I could remember what was going on here...",
"I must enjoy torturing myself",
"For the sake of my sanity, just ignore this...",
"That last commit message about silly mistakes pales in comparision to this one",
"My bad",
"Still can't get this right...",
"Nitpicking about alphabetizing methods, minor OCD thing",
"Committing fixes in the dark, seriously, who killed my power!?",
"You can't see it, but I'm making a very angry face right now",
"Fix the fixes",
"It's secret!",
"Commit committed....",
"No time to commit.. My people need me!",
"Something fixed",
"I'm hungry",
"asdfasdfasdfasdfasdfasdfadsf",
"hmmm",
"formatted all",
"Replace all whitespaces with tabs.",
"s/ / /g",
"I'm too foo for this bar",
"Things went wrong...",
"??! what the ...",
"This solves it.",
"Working on tests (haha)",
"fixed conflicts (LOL merge -s ours; push -f)",
"last minute fixes.",
"fuckup.",
"Revert \"fuckup\".",
"should work now.",
"final commit.",
"done. going to bed now.",
"buenas those-things.",
"Your commit is writing checks your merge can't cash.",
"This branch is so dirty, even your mom can't clean it.",
"wip",
"Revert \"just testing, remember to revert\"",
"bla",
"harharhar",
"restored deleted entities just to be sure",
"added some filthy stuff",
"bugger",
"lol",
"oopsie B|",
"Copy pasta fail. still had a instead of a",
"Now added delete for real",
"grmbl",
"move your body every every body",
"Trying to fake a conflict",
"And a commit that I don't know the reason of...",
"ffs",
"that's all folks",
"Fucking submodule bull shit",
"apparently i did something…",
"bump to 0.0.3-dev:wq",
"pep8 - cause I fell like doing a barrel roll",
"pep8 fixer",
"it is hump day _^_",
"happy monday _ bleh _",
"after of this commit remember do a git reset hard",
"someday I gonna kill someone for this shit...",
"magic, have no clue but it works",
"I am sorry",
"dirty hack, have a better idea ?",
"Code was clean until manager requested to fuck it up",
" - Temporary commit.",
":(:(",
"...",
"GIT :/",
"stopped caring 10 commits ago",
"Testing in progress ;)",
"Fixed Bug",
"Fixed errors",
"Push poorly written test can down the road another ten years",
"commented out failing tests",
"I'm human",
"TODO: write meaningful commit message",
"Pig",
"SOAP is a piece of shit",
"did everything",
"project lead is allergic to changes...",
"making this thing actually usable.",
"I was told to leave it alone, but I have this thing called OCD, you see",
"Whatever will be, will be 8{",
"It's 2015; why are we using ColdFusion?!",
"#GrammarNazi",
"Future self, please forgive me and don't hit me with the baseball bat again!",
"Hide those navs, boi!",
"Who knows...",
"Who knows WTF?!",
"I should get a raise for this.",
"Done, to whoever merges this, good luck.",
"Not one conflict, today was a good day.",
"First Blood",
"Fixed the fuck out of #526!",
"I'm too old for this shit!",
"One little whitespace gets its very own commit! Oh, life is so erratic!",
"please dont let this be the problem",
"good: no crash. bad: nothing happens",
"trying",
"trying harder",
"i tried",
"fml"
}
function commit:action(msg)
local output = '`'..commits[math.random(#commits)]..'`'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
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

View File

@ -11,14 +11,14 @@ end
function echo:action(msg)
local input = utilities.input(msg.text)
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 = '*Echo:*\n"' .. utilities.md_escape(input) .. '"'
output = utilities.style.enquote('Echo', input)
else
output = utilities.md_escape(utilities.char.zwnj..input)
end

View File

@ -6,11 +6,10 @@ local utilities = require('otouto.utilities')
function fortune:init(config)
local s = io.popen('fortune'):read('*all')
if s:match('not found$') then
print('fortune is not installed on this computer.')
print('fortune.lua will not be enabled.')
return
end
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
end

View File

@ -9,37 +9,28 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function gImages:init(config)
if not config.google_api_key then
print('Missing config value: google_api_key.')
print('gImages.lua will not be enabled.')
return
elseif not config.google_cse_key then
print('Missing config value: google_cse_key.')
print('gImages.lua will not be enabled.')
return
end
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 <query>
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
end
gImages.command = 'image <query>'
function gImages:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gImages.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, gImages.doc, true)
return
end
local 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
local url = gImages.search_url
if not string.match(msg.text, '^'..config.cmd_pat..'i[mage]*nsfw') then
url = url .. '&safe=high'

View File

@ -6,28 +6,26 @@ local utilities = require('otouto.utilities')
gMaps.command = 'location <query>'
function gMaps:init(config)
gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('location', true):t('loc', true).table
gMaps.doc = config.cmd_pat .. [[location <query>
gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('location', true):t('loc', true).table
gMaps.doc = [[
/location <query>
Returns a location from Google Maps.
Alias: ]] .. config.cmd_pat .. 'loc'
Alias: /loc
]]
gMaps.doc = gMaps.doc:gsub('/', config.cmd_pat)
end
function gMaps:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gMaps.doc, true, msg.message_id, true)
return
end
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)
return
end
bindings.sendLocation(self, {
@ -36,7 +34,6 @@ function gMaps:action(msg, config)
longitude = coords.lon,
reply_to_message_id = msg.message_id
} )
end
return gMaps

View File

@ -1,79 +0,0 @@
local gSearch = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
gSearch.command = 'google <query>'
function gSearch:init(config)
gSearch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('g', true):t('google', true):t('gnsfw', true).table
gSearch.doc = config.cmd_pat .. [[google <query>
Returns four (if group) or eight (if private message) results from Google. Safe search is enabled by default, use "]] .. config.cmd_pat .. [[gnsfw" to disable it.
Alias: ]] .. config.cmd_pat .. 'g'
end
function gSearch:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gSearch.doc, true, msg.message_id, true)
return
end
end
local url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0'
if msg.from.id == msg.chat.id then
url = url .. '&rsz=8'
else
url = url .. '&rsz=4'
end
if not string.match(msg.text, '^'..config.cmd_pat..'g[oogle]*nsfw') then
url = url .. '&safe=active'
end
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 jdat = JSON.decode(jstr)
if not jdat.responseData then
utilities.send_reply(self, msg, config.errors.connection)
return
end
if not jdat.responseData.results[1] then
utilities.send_reply(self, msg, config.errors.results)
return
end
local output = '*Google results for* _' .. input .. '_ *:*\n'
for i,_ in ipairs(jdat.responseData.results) do
local title = jdat.responseData.results[i].titleNoFormatting:gsub('%[.+%]', ''):gsub('&amp;', '&')
--[[
if title:len() > 48 then
title = title:sub(1, 45) .. '...'
end
]]--
local u = jdat.responseData.results[i].unescapedUrl
if u:find('%)') then
output = output .. '' .. title .. '\n' .. u:gsub('_', '\\_') .. '\n'
else
output = output .. '• [' .. title .. '](' .. u .. ')\n'
end
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return gSearch

View File

@ -1,63 +1,32 @@
-- Put this on the bottom of your plugin list, after help.lua.
-- If you want to configure your own greetings, copy the following table
-- (without the "config.") to your config.lua file.
local utilities = require('otouto.utilities')
local greetings = {}
local utilities = require('otouto.utilities')
function greetings:init(config)
config.greetings = config.greetings or {
['Hello, #NAME.'] = {
'hello',
'hey',
'sup',
'hi',
'good morning',
'good day',
'good afternoon',
'good evening'
},
['Goodbye, #NAME.'] = {
'bye',
'later',
'see ya',
'good night'
},
['Welcome back, #NAME.'] = {
'i\'m home',
'i\'m back'
},
['You\'re welcome, #NAME.'] = {
'thanks',
'thank you'
}
}
greetings.triggers = {
self.info.first_name:lower() .. '%p*$'
}
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 = 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 or nick
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 trigger,responses in pairs(config.greetings) do
for _,response in pairs(responses) do
if msg.text_lower:match(response..',? '..self.info.first_name:lower()) then
local output = utilities.char.zwnj .. trigger:gsub('#NAME', nick)
utilities.send_message(self, msg.chat.id, output)
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
return true
end
return greetings

View File

@ -1,63 +1,75 @@
local hackernews = {}
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
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• <code>[</code><a href="%s">%s</a><code>]</code> <a href="%s">%s</a>',
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• <code>[</code><a href="%s">%s</a><code>]</code> %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.
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
end
function hackernews:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
local jstr, res = HTTPS.request('https://hacker-news.firebaseio.com/v0/topstories.json')
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
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
local jdat = JSON.decode(jstr)
local res_count = 4
if msg.chat.id == msg.from.id then
res_count = 8
end
local output = '*Hacker News:*\n'
-- Four results in a group, eight in private.
local res_count = msg.chat.id == msg.from.id and 8 or 4
local output = '<b>Top Stories from Hacker News:</b>'
for i = 1, res_count do
local res_url = 'https://hacker-news.firebaseio.com/v0/item/' .. jdat[i] .. '.json'
jstr, res = HTTPS.request(res_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local res_jdat = JSON.decode(jstr)
local title = res_jdat.title:gsub('%[.+%]', ''):gsub('%(.+%)', ''):gsub('&amp;', '&')
if title:len() > 48 then
title = title:sub(1, 45) .. '...'
end
local url = res_jdat.url
if not url then
utilities.send_reply(self, msg, config.errors.connection)
return
end
if url:find('%(') then
output = output .. '' .. title .. '\n' .. url:gsub('_', '\\_') .. '\n'
else
output = output .. '• [' .. title .. '](' .. url .. ')\n'
end
output = output .. hackernews.results[i]
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
utilities.send_message(self, msg.chat.id, output, true, nil, 'html')
end
return hackernews

View File

@ -5,45 +5,36 @@ local hearthstone = {}
--local HTTPS = require('ssl.https')
local JSON = require('dkjson')
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 <query>'
if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then
print('Downloading Hearthstone database...')
-- This stuff doesn't play well with lua-sec. Disable it for now; hack in curl.
--local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json')
--if res ~= 200 then
-- print('Error connecting to hearthstonejson.com.')
-- print('hearthstone.lua will not be enabled.')
-- return
--end
--local jdat = JSON.decode(jstr)
local s = io.popen('curl -s https://api.hearthstonejson.com/v1/latest/enUS/cards.json'):read('*all')
local d = JSON.decode(s)
if not d then
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 = d
self.database.hearthstone = JSON.decode(jstr)
self.database.hearthstone.expiration = os.time() + 600000
print('Download complete! It will be stored for a week.')
end
hearthstone.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hearthstone', true):t('hs').table
hearthstone.doc = config.cmd_pat .. [[hearthstone <query>
Returns Hearthstone card info.
Alias: ]] .. config.cmd_pat .. 'hs'
end
hearthstone.command = 'hearthstone <query>'
local function format_card(card)
local ctype = card.type
@ -102,9 +93,9 @@ end
function hearthstone:action(msg, config)
local input = utilities.input(msg.text_lower)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_message(self, msg.chat.id, hearthstone.doc, true, msg.message_id, true)
utilities.send_reply(self, msg, hearthstone.doc, true)
return
end

View File

@ -1,46 +1,47 @@
-- This plugin should go at the end of your plugin list in
-- config.lua, but not after greetings.lua.
local utilities = require('otouto.utilities')
local help = {}
local utilities = require('otouto.utilities')
local help_text
function help:init(config)
local commandlist = {}
help_text = '*Available commands:*\n'..config.cmd_pat
for _,plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
if plugin.doc then
plugin.help_word = utilities.get_word(plugin.command, 1)
end
end
end
table.insert(commandlist, 'help [command]')
table.sort(commandlist)
help_text = help_text .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nArguments: <required> [optional]'
help_text = help_text:gsub('%[', '\\[')
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)
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
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: <required> [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)
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

62
otouto/plugins/id.lua Normal file
View File

@ -0,0 +1,62 @@
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 <user>'
id.doc = config.cmd_pat .. [[id <user> ...
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 <b>%s</b> <code>[%s]</code>.\n',
t.username,
utilities.build_name(t.first_name, t.last_name),
t.id
)
else
return string.format(
'<b>%s</b> <code>[%s]</code>.\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')
end
return id

View File

@ -14,14 +14,10 @@ end
function imdb:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, imdb.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, imdb.doc, true)
return
end
local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input)

43
otouto/plugins/isup.lua Normal file
View File

@ -0,0 +1,43 @@
-- Based on a plugin by matthewhesketh.
local HTTP = require('socket.http')
local HTTPS = require('ssl.https')
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.doc = config.cmd_pat .. [[isup <url>
Returns the up or down status of a website.]]
isup.command = 'isup <url>'
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 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

View File

@ -9,11 +9,9 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function lastfm:init(config)
if not config.lastfm_api_key then
print('Missing config value: lastfm_api_key.')
print('lastfm.lua will not be enabled.')
return
end
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]

View File

@ -2,10 +2,21 @@ local luarun = {}
local utilities = require('otouto.utilities')
local URL = require('socket.url')
local JSON = require('dkjson')
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
end
function luarun:action(msg, config)
@ -28,6 +39,7 @@ function luarun:action(msg, config)
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')
@ -38,7 +50,7 @@ function luarun:action(msg, config)
output = 'Done!'
else
if type(output) == 'table' then
local s = JSON.encode(output, {indent=true})
local s = luarun.serialize(output)
if URL.escape(s):len() < 4000 then
output = s
end

View File

@ -9,33 +9,59 @@ function me:init(config)
end
function me:action(msg, config)
local userdata = self.database.userdata[tostring(msg.from.id)] or {}
local user
if msg.from.id == config.admin then
if msg.reply_to_message then
userdata = self.database.userdata[tostring(msg.reply_to_message.from.id)]
user = msg.reply_to_message.from
else
local input = utilities.input(msg.text)
if input then
local user_id = utilities.id_from_username(self, input)
if user_id then
userdata = self.database.userdata[tostring(user_id)] or {}
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 output = ''
local data = {}
for k,v in pairs(userdata) do
output = output .. '*' .. k .. ':* `' .. tostring(v) .. '`\n'
table.insert(data, string.format(
'<b>%s</b> <code>%s</code>\n',
utilities.html_escape(k),
utilities.html_escape(v)
))
end
if output == '' then
local output
if #data == 0 then
output = 'There is no data stored for this user.'
else
output = string.format(
'<b>%s</b> <code>[%s]</code><b>:</b>\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, true)
utilities.send_message(self, msg.chat.id, output, true, nil, 'html')
end

View File

@ -1,10 +1,18 @@
local patterns = {}
local utilities = require('otouto.utilities')
patterns.triggers = {
'^/?s/.-/.-$'
}
local patterns = {}
patterns.command = 's/<pattern>/<substitution>'
patterns.help_word = 'sed'
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/.-/.-$' }
end
function patterns:action(msg)
if not msg.reply_to_message then return true end
@ -24,8 +32,8 @@ function patterns:action(msg)
if res == false then
utilities.send_reply(self, msg, 'Malformed pattern!')
else
output = output:sub(1, 4000)
output = '*Did you mean:*\n"' .. utilities.md_escape(utilities.trim(output)) .. '"'
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

View File

@ -11,23 +11,20 @@ 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 <query>
Returns a Pokedex entry from pokeapi.co.
Queries must be a number of the name of a Pokémon.
Alias: ]] .. config.cmd_pat .. 'dex'
end
function pokedex:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
local input = utilities.input(msg.text_lower)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, pokedex.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, pokedex.doc, true)
return
end
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
local url = 'http://pokeapi.co'
local dex_url = url .. '/api/v1/pokemon/' .. input
@ -39,6 +36,11 @@ function pokedex:action(msg, config)
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
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

View File

@ -97,8 +97,12 @@ function pgc:action(msg)
if egg_count < 1 then
recommendation = 'Wait until you have atleast sixty Pokémon to evolve before using a lucky egg.'
else
recommendation = 'Use %s lucky egg(s) for %s evolutions.'
recommendation = recommendation:format(egg_count, egg_count*60)
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

View File

@ -8,7 +8,8 @@ pokemon_go.command = 'pokego <team>'
function pokemon_go:init(config)
pokemon_go.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('pokego', true):t('pokégo', true)
:t('pokemongo', true):t('pokémongo', true).table
:t('pokemongo', true):t('pokémongo', true)
:t('pogo', true):t('mongo', true).table
pokemon_go.doc = config.cmd_pat .. [[pokego <team>
Set your Pokémon Go team for statistical purposes. The team must be valid, and can be referred to by name or color (or the first letter of either). Giving no team name will show statistics.]]
local db = self.database.pokemon_go

View File

@ -12,10 +12,9 @@ end
function preview:action(msg)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_message(self, msg.chat.id, preview.doc, true, nil, true)
utilities.send_reply(self, msg, preview.doc, true)
return
end
@ -36,8 +35,8 @@ function preview:action(msg)
end
-- Invisible zero-width, non-joiner.
local output = '[](' .. input .. ')'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>'
utilities.send_message(self, msg.chat.id, output, false, nil, 'html')
end

View File

@ -13,16 +13,6 @@ local utilities = require('otouto.utilities')
reactions.command = 'reactions'
reactions.doc = 'Returns a list of "reaction" emoticon commands.'
local mapping = {
['shrug'] = '¯\\_(ツ)_/¯',
['lenny'] = '( ͡° ͜ʖ ͡°)',
['flip'] = '(╯°□°)╯︵ ┻━┻',
['homo'] = ' o',
['look'] = 'ಠ_ಠ',
['shots?'] = 'SHOTS FIRED',
['facepalm'] = '(-‸ლ)'
}
local help
function reactions:init(config)
@ -30,8 +20,8 @@ function reactions:init(config)
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(mapping) do
help = help .. '' .. config.cmd_pat .. trigger:gsub('.%?', '') .. ': ' .. reaction .. '\n'
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..'$')
@ -48,7 +38,7 @@ function reactions:action(msg, config)
utilities.send_message(self, msg.chat.id, help)
return
end
for trigger,reaction in pairs(mapping) do
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

View File

@ -8,87 +8,83 @@ function remind:init(config)
self.database.reminders = self.database.reminders or {}
remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table
remind.doc = config.cmd_pat .. 'remind <duration> <message> \nRepeats a message after a duration of time, in minutes.'
config.remind = config.remind or {}
setmetatable(config.remind, { __index = function() return 1000 end })
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)
end
function remind:action(msg)
-- Ensure there are arguments. If not, send doc.
function remind:action(msg, config)
local input = utilities.input(msg.text)
if not input then
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
utilities.send_reply(self, msg, remind.doc, true)
return
end
-- Ensure first arg is a number. If not, send doc.
local duration = utilities.get_word(input, 1)
if not tonumber(duration) then
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
local duration = tonumber(utilities.get_word(input, 1))
if not duration then
utilities.send_reply(self, msg, remind.doc, true)
return
end
-- Duration must be between one minute and one year (approximately).
duration = tonumber(duration)
if duration < 1 then
duration = 1
elseif duration > 526000 then
duration = 526000
elseif duration > config.remind.max_duration then
duration = config.remind.max_duration
end
-- Ensure there is a second arg.
local message = utilities.input(input)
if not message then
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
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
local chat_id_str = tostring(msg.chat.id)
-- Make a database entry for the group/user if one does not exist.
local output
self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {}
-- Limit group reminders to 10 and private reminders to 50.
if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) > 9 then
utilities.send_reply(self, msg, 'Sorry, this group already has ten reminders.')
return
elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) > 49 then
utilities.send_reply(msg, 'Sorry, you already have fifty reminders.')
return
end
-- Put together the reminder with the expiration, message, and message to reply to.
local reminder = {
time = os.time() + duration * 60,
message = message
}
table.insert(self.database.reminders[chat_id_str], reminder)
local output = 'I will remind you in ' .. duration
if duration == 1 then
output = output .. ' minute!'
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
output = output .. ' minutes!'
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)
utilities.send_reply(self, msg, output, true)
end
function remind:cron()
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
local new_group = {}
-- Iterate over each reminder.
for _, reminder in ipairs(group) do
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 = '*Reminder:*\n"' .. utilities.md_escape(reminder.message) .. '"'
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 not res then
table.insert(new_group, reminder)
-- 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
else
table.insert(new_group, reminder)
end
end
-- Nullify the original table and replace it with the new one.
self.database.reminders[chat_id] = new_group
-- Nullify the table if it is empty.
if #new_group == 0 then
self.database.reminders[chat_id] = nil
end
end
end

View File

@ -20,7 +20,9 @@ function shell:action(msg, config)
return
end
local output = io.popen(input):read('*all')
local f = io.popen(input)
local output = f:read('*all')
f:close()
if output:len() == 0 then
output = 'Done!'
else

View File

@ -3,6 +3,7 @@ local shout = {}
local utilities = require('otouto.utilities')
shout.command = 'shout <text>'
local utf8 = '('..utilities.char.utf_8..'*)'
function shout:init(config)
shout.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('shout', true).table
@ -11,22 +12,19 @@ end
function shout:action(msg)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and #msg.reply_to_message.text > 0 then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, shout.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, shout.doc, true)
return
end
input = utilities.trim(input)
input = input:upper()
local output = ''
local inc = 0
local ilen = 0
for match in input:gmatch(utilities.char.utf_8) do
for match in input:gmatch(utf8) do
if ilen < 20 then
ilen = ilen + 1
output = output .. match .. ' '
@ -34,7 +32,7 @@ function shout:action(msg)
end
ilen = 0
output = output .. '\n'
for match in input:sub(2):gmatch(utilities.char.utf_8) do
for match in input:sub(2):gmatch(utf8) do
if ilen < 19 then
local spacing = ''
for _ = 1, inc do

View File

@ -109,7 +109,21 @@ local slaps = {
function slap:action(msg)
local input = utilities.input(msg.text)
local victor_id = msg.from.id
local victim_id = utilities.id_from_message(self, msg)
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

View File

@ -0,0 +1,78 @@
-- Based on a plugin by matthewhesketh.
local HTTP = require('socket.http')
local JSON = require('dkjson')
local bindings = require('otouto.bindings')
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 <query>
Returns the opening crawl from the specified Star Wars film.
Alias: ]] .. config.cmd_pat .. 'sw'
starwars.command = 'starwars <query>'
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
}
local corrected_numbers = {
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
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
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 output = '*' .. JSON.decode(jstr).opening_crawl .. '*'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return starwars

View File

@ -5,6 +5,7 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
time.command = 'time <location>'
time.base_url = 'https://maps.googleapis.com/maps/api/timezone/json?location=%s,%s&timestamp=%s'
function time:init(config)
time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table
@ -13,15 +14,10 @@ Returns the time, date, and timezone for the given location.]]
end
function time:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, time.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, time.doc, true)
return
end
local coords = utilities.get_coords(input, config)
@ -31,30 +27,33 @@ function time:action(msg, config)
end
local now = os.time()
local utc = os.time(os.date("!*t", now))
local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..utc
local jstr, res = HTTPS.request(url)
if res ~= 200 then
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 jdat = JSON.decode(jstr)
local data = JSON.decode(jstr)
if data.status == 'ZERO_RESULTS' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local timestamp = now + jdat.rawOffset + jdat.dstOffset
local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600
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)
utcoff = '+' .. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end
local output = os.date('!%I:%M %p\n', timestamp) .. os.date('!%A, %B %d, %Y\n', timestamp) .. jdat.timeZoneName .. ' (UTC' .. utcoff .. ')'
output = '```\n' .. output .. '\n```'
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

View File

@ -8,42 +8,38 @@ local utilities = require('otouto.utilities')
translate.command = 'translate [text]'
function translate:init(config)
translate.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('translate', true):t('tl', true).table
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]
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'
end
function translate:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, translate.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, translate.doc, true)
return
end
local url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=' .. URL.escape(input)
local str, res = HTTPS.request(url)
if res ~= 200 then
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 jdat = JSON.decode(str)
if jdat.code ~= 200 then
local data = JSON.decode(jstr)
if data.code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local output = jdat.text[1]
output = '*Translation:*\n"' .. utilities.md_escape(output) .. '"'
utilities.send_reply(self, msg.reply_to_message or msg, output, true)
utilities.send_reply(self, msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true)
end
return translate

View File

@ -6,50 +6,45 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
urbandictionary.command = 'urbandictionary <query>'
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 = config.cmd_pat .. [[urbandictionary <query>
urbandictionary.doc = [[
/urbandictionary <query>
Returns a definition from Urban Dictionary.
Aliases: ]] .. config.cmd_pat .. 'ud, ' .. config.cmd_pat .. 'urban'
Aliases: /ud, /urban
]]
urbandictionary.doc = urbandictionary.doc:gsub('/', config.cmd_pat)
end
function urbandictionary:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, urbandictionary.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, urbandictionary.doc, true)
return
end
local url = 'http://api.urbandictionary.com/v0/define?term=' .. URL.escape(input)
local jstr, res = HTTP.request(url)
if res ~= 200 then
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 jdat = JSON.decode(jstr)
if jdat.result_type == "no_results" then
utilities.send_reply(self, msg, config.errors.results)
return
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
local output = '*' .. jdat.list[1].word .. '*\n\n' .. utilities.trim(jdat.list[1].definition)
if string.len(jdat.list[1].example) > 0 then
output = output .. '_\n\n' .. utilities.trim(jdat.list[1].example) .. '_'
end
output = output:gsub('%[', ''):gsub('%]', '')
utilities.send_message(self, msg.chat.id, output, true, nil, true)
utilities.send_reply(self, msg, output, true)
end
return urbandictionary

View File

@ -6,11 +6,9 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function weather:init(config)
if not config.owm_api_key then
print('Missing config value: owm_api_key.')
print('weather.lua will not be enabled.')
return
end
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 <location>
@ -21,14 +19,10 @@ weather.command = 'weather <location>'
function weather:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, weather.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, weather.doc, true)
return
end
local coords = utilities.get_coords(input, config)

View File

@ -1,51 +1,59 @@
local whoami = {}
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
whoami.command = 'whoami'
function whoami:init(config)
whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who', true):t('whoami').table
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)
if msg.reply_to_message then
msg = msg.reply_to_message
end
local from_name = utilities.build_name(msg.from.first_name, msg.from.last_name)
local chat_id = math.abs(msg.chat.id)
if chat_id > 1000000000000 then
chat_id = chat_id - 1000000000000
end
local user = 'You are @%s, also known as *%s* `[%s]`'
if msg.from.username then
user = user:format(utilities.markdown_escape(msg.from.username), from_name, msg.from.id)
else
user = 'You are *%s* `[%s]`,'
user = user:format(from_name, msg.from.id)
end
local group = '@%s, also known as *%s* `[%s]`.'
if msg.chat.type == 'private' then
group = group:format(utilities.markdown_escape(self.info.username), self.info.first_name, self.info.id)
elseif msg.chat.username then
group = group:format(utilities.markdown_escape(msg.chat.username), msg.chat.title, chat_id)
else
group = '*%s* `[%s]`.'
group = group:format(msg.chat.title, chat_id)
end
local output = user .. ', and you are messaging ' .. group
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
-- 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 <code>[%s]</code>, and you are messaging %s <code>[%s]</code>.',
msg.from.username and string.format(
'@%s, also known as <b>%s</b>',
msg.from.username,
from_name
) or '<b>' .. from_name .. '</b>',
msg.from.id,
msg.chat.username and string.format(
'@%s, also known as <b>%s</b>',
chat.username,
chat_name
) or '<b>' .. chat_name .. '</b>',
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

View File

@ -12,101 +12,79 @@ function wikipedia:init(config)
wikipedia.doc = config.cmd_pat .. [[wikipedia <query>
Returns an article from Wikipedia.
Aliases: ]] .. config.cmd_pat .. 'w, ' .. config.cmd_pat .. 'wiki'
end
local get_title = function(search)
for _,v in ipairs(search) do
if not v.snippet:match('may refer to:') then
return v.title
end
end
return false
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)
-- Get the query. If it's not in the message, check the replied-to message.
-- If those don't exist, send the help text.
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, wikipedia.doc, true, msg.message_id, true)
return
end
utilities.send_reply(self, msg, wikipedia.doc, true)
return
end
-- This kinda sucks, but whatever.
input = input:gsub('#', ' sharp')
-- Disclaimer: These variables will be reused.
local jstr, res, jdat
-- All pretty standard from here.
local search_url = 'https://en.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch='
jstr, res = HTTPS.request(search_url .. URL.escape(input))
if res ~= 200 then
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
jdat = JSON.decode(jstr)
if jdat.query.searchinfo.totalhits == 0 then
local data = JSON.decode(jstr)
if data.query.searchinfo.totalhits == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local title = get_title(jdat.query.search)
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_url = 'https://en.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles='
jstr, res = HTTPS.request(res_url .. URL.escape(title))
if res ~= 200 then
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 _
local text = JSON.decode(jstr).query.pages
_, text = next(text)
local _, text = next(JSON.decode(res_jstr).query.pages)
if not text then
utilities.send_reply(self, msg, config.errors.results)
return
else
text = text.extract
end
-- Remove needless bits from the article, take only the first paragraph.
text = text:gsub('</?.->', '')
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
-- This block can be annoying to read.
-- We use the initial title to make the url for later use. Then we remove
-- the extra bits that won't be in the article. We determine whether the
-- first part of the text is the title, and if so, we embolden that.
-- Otherwise, we prepend the text with a bold title. Then we append a "Read
-- More" link.
local url = 'https://en.wikipedia.org/wiki/' .. URL.escape(title)
title = title:gsub('%(.+%)', '')
local output
if string.match(text:sub(1, title:len()), title) then
output = '*' .. title .. '*' .. text:sub(title:len()+1)
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, '<b>'..short_title..'</b>')
local body
if count == 1 then
body = combined_text
else
output = '*' .. title:gsub('%(.+%)', '') .. '*\n' .. text:gsub('%[.+%]','')
body = '<b>' .. title .. '</b>\n' .. text
end
output = output .. '\n[Read more.](' .. url:gsub('%)', '\\)') .. ')'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
local output = string.format(
'%s\n<a href="%s">Read more.</a>',
body,
utilities.html_escape(url)
)
utilities.send_message(self, msg.chat.id, output, true, nil, 'html')
end
return wikipedia

View File

@ -5,52 +5,48 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
xkcd.command = 'xkcd [i]'
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]
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
end
function xkcd:action(msg, config)
local jstr, res = HTTP.request('http://xkcd.com/info.0.json')
if res ~= 200 then
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)
return
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 latest = JSON.decode(jstr).num
local strip_num = latest
local input = utilities.input(msg.text)
if input then
if input == '404' then
utilities.send_message(self, msg.chat.id, '*404*\nNot found.', false, nil, true)
return
elseif tonumber(input) then
if tonumber(input) > latest then
strip_num = latest
else
strip_num = input
end
elseif input == 'r' then
strip_num = math.random(latest)
end
end
local res_url = 'http://xkcd.com/' .. strip_num .. '/info.0.json'
jstr, res = HTTP.request(res_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
local output = '*' .. jdat.safe_title .. ' (*[' .. jdat.num .. '](' .. jdat.img .. ')*)*\n_' .. jdat.alt:gsub('_', '\\_') .. '_'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end
return xkcd

View File

@ -8,11 +8,9 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function youtube:init(config)
if not config.google_api_key then
print('Missing config value: google_api_key.')
print('youtube.lua will not be enabled.')
return
end
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 <query>
@ -24,14 +22,10 @@ youtube.command = 'youtube <query>'
function youtube:action(msg, config)
local input = utilities.input(msg.text)
local input = utilities.input_from_msg(msg)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, youtube.doc, true, msg.message_id, true)
return
end
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)