tab -> 4 spaces

This commit is contained in:
topkecleon 2016-08-13 22:46:18 -04:00
parent 6fbd718af0
commit 148d4b0dc5
67 changed files with 4168 additions and 4167 deletions

View File

@ -6,4 +6,5 @@ insert_final_newline = true
[*.lua]
charset = utf-8
indent_style = tab
indent_style = space
indent_size = 4

View File

@ -292,26 +292,26 @@ Additionally, any method can be called as a key in the `bindings` table (for exa
```
bindings.request(
self,
'sendMessage',
{
chat_id = 987654321,
text = 'Quick brown fox.',
reply_to_message_id = 54321,
disable_web_page_preview = false,
parse_method = 'Markdown'
}
self,
'sendMessage',
{
chat_id = 987654321,
text = 'Quick brown fox.',
reply_to_message_id = 54321,
disable_web_page_preview = false,
parse_method = 'Markdown'
}
)
bindings.sendMessage(
self,
{
chat_id = 987654321,
text = 'Quick brown fox.',
reply_to_message_id = 54321,
disable_web_page_preview = false,
parse_method = 'Markdown'
}
self,
{
chat_id = 987654321,
text = 'Quick brown fox.',
reply_to_message_id = 54321,
disable_web_page_preview = false,
parse_method = 'Markdown'
}
)
```
@ -342,20 +342,20 @@ Alone, the database will have this structure:
```
{
users = {
["55994550"] = {
id = 55994550,
first_name = "Drew",
username = "topkecleon"
}
},
userdata = {
["55994550"] = {
nickname = "Worst coder ever",
lastfm = "topkecleon"
}
},
version = "3.11"
users = {
["55994550"] = {
id = 55994550,
first_name = "Drew",
username = "topkecleon"
}
},
userdata = {
["55994550"] = {
nickname = "Worst coder ever",
lastfm = "topkecleon"
}
},
version = "3.11"
}
```

View File

@ -1,159 +1,159 @@
-- For details on configuration values, see README.md#configuration.
return {
-- Your authorization token from the botfather.
bot_api_key = nil,
-- Your Telegram ID.
admin = nil,
-- Two-letter language code.
lang = 'en',
-- The channel, group, or user to send error reports to.
-- If this is not set, errors will be printed to the console.
log_chat = nil,
-- The port used to communicate with tg for administration.lua.
-- If you change this, make sure you also modify launch-tg.sh.
cli_port = 4567,
-- The symbol that starts a command. Usually noted as '/' in documentation.
cmd_pat = '/',
-- If drua is used, should a user be blocked when he's blacklisted?
drua_block_on_blacklist = false,
-- The filename of the database. If left nil, defaults to $username.db.
database_name = nil,
-- The block of text returned by /start and /about..
about_text = [[
-- Your authorization token from the botfather.
bot_api_key = nil,
-- Your Telegram ID.
admin = nil,
-- Two-letter language code.
lang = 'en',
-- The channel, group, or user to send error reports to.
-- If this is not set, errors will be printed to the console.
log_chat = nil,
-- The port used to communicate with tg for administration.lua.
-- If you change this, make sure you also modify launch-tg.sh.
cli_port = 4567,
-- The symbol that starts a command. Usually noted as '/' in documentation.
cmd_pat = '/',
-- If drua is used, should a user be blocked when he's blacklisted?
drua_block_on_blacklist = false,
-- The filename of the database. If left nil, defaults to $username.db.
database_name = nil,
-- The block of text returned by /start and /about..
about_text = [[
I am otouto, the plugin-wielding, multipurpose Telegram bot.
Send /help to get started.
]],
]],
errors = { -- Generic error messages.
generic = 'An unexpected error occurred.',
connection = 'Connection error.',
results = 'No results found.',
argument = 'Invalid argument.',
syntax = 'Invalid syntax.'
},
errors = { -- Generic error messages.
generic = 'An unexpected error occurred.',
connection = 'Connection error.',
results = 'No results found.',
argument = 'Invalid argument.',
syntax = 'Invalid syntax.'
},
-- https://datamarket.azure.com/dataset/bing/search
bing_api_key = nil,
-- http://console.developers.google.com
google_api_key = nil,
-- https://cse.google.com/cse
google_cse_key = nil,
-- http://openweathermap.org/appid
owm_api_key = nil,
-- http://last.fm/api
lastfm_api_key = nil,
-- http://api.biblia.com
biblia_api_key = nil,
-- http://thecatapi.com/docs.html
thecatapi_key = nil,
-- http://api.nasa.gov
nasa_api_key = nil,
-- http://tech.yandex.com/keys/get
yandex_key = nil,
-- Interval (in minutes) for hackernews.lua to update.
hackernews_interval = 60,
-- Whether hackernews.lua should update at load/reload.
hackernews_onstart = false,
-- Whether luarun should use serpent instead of dkjson for serialization.
luarun_serpent = false,
-- https://datamarket.azure.com/dataset/bing/search
bing_api_key = nil,
-- http://console.developers.google.com
google_api_key = nil,
-- https://cse.google.com/cse
google_cse_key = nil,
-- http://openweathermap.org/appid
owm_api_key = nil,
-- http://last.fm/api
lastfm_api_key = nil,
-- http://api.biblia.com
biblia_api_key = nil,
-- http://thecatapi.com/docs.html
thecatapi_key = nil,
-- http://api.nasa.gov
nasa_api_key = nil,
-- http://tech.yandex.com/keys/get
yandex_key = nil,
-- Interval (in minutes) for hackernews.lua to update.
hackernews_interval = 60,
-- Whether hackernews.lua should update at load/reload.
hackernews_onstart = false,
-- Whether luarun should use serpent instead of dkjson for serialization.
luarun_serpent = false,
remind = {
persist = true,
max_length = 1000,
max_duration = 526000,
max_reminders_group = 10,
max_reminders_private = 50
},
remind = {
persist = true,
max_length = 1000,
max_duration = 526000,
max_reminders_group = 10,
max_reminders_private = 50
},
chatter = {
cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=',
connection = 'I don\'t feel like talking right now.',
response = 'I don\'t know what to say to that.'
},
chatter = {
cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=',
connection = 'I don\'t feel like talking right now.',
response = 'I don\'t know what to say to that.'
},
greetings = {
["Hello, #NAME."] = {
"hello",
"hey",
"hi",
"good morning",
"good day",
"good afternoon",
"good evening"
},
["Goodbye, #NAME."] = {
"good%-?bye",
"bye",
"later",
"see ya",
"good night"
},
["Welcome back, #NAME."] = {
"i'm home",
"i'm back"
},
["You're welcome, #NAME."] = {
"thanks",
"thank you"
}
},
greetings = {
["Hello, #NAME."] = {
"hello",
"hey",
"hi",
"good morning",
"good day",
"good afternoon",
"good evening"
},
["Goodbye, #NAME."] = {
"good%-?bye",
"bye",
"later",
"see ya",
"good night"
},
["Welcome back, #NAME."] = {
"i'm home",
"i'm back"
},
["You're welcome, #NAME."] = {
"thanks",
"thank you"
}
},
reactions = {
['shrug'] = '¯\\_(ツ)_/¯',
['lenny'] = '( ͡° ͜ʖ ͡°)',
['flip'] = '(╯°□°)╯︵ ┻━┻',
['look'] = 'ಠ_ಠ',
['shots'] = 'SHOTS FIRED',
['facepalm'] = '(-‸ლ)'
},
reactions = {
['shrug'] = '¯\\_(ツ)_/¯',
['lenny'] = '( ͡° ͜ʖ ͡°)',
['flip'] = '(╯°□°)╯︵ ┻━┻',
['look'] = 'ಠ_ಠ',
['shots'] = 'SHOTS FIRED',
['facepalm'] = '(-‸ლ)'
},
administration = {
-- Whether moderators can set a group's message of the day.
moderator_setmotd = false,
-- Default antiflood values.
antiflood = {
text = 5,
voice = 5,
audio = 5,
contact = 5,
photo = 10,
video = 10,
location = 10,
document = 10,
sticker = 20
}
},
administration = {
-- Whether moderators can set a group's message of the day.
moderator_setmotd = false,
-- Default antiflood values.
antiflood = {
text = 5,
voice = 5,
audio = 5,
contact = 5,
photo = 10,
video = 10,
location = 10,
document = 10,
sticker = 20
}
},
plugins = { -- To enable a plugin, add its name to the list.
'about',
'blacklist',
'calc',
'cats',
'commit',
'control',
'currency',
'dice',
'echo',
'eightball',
'gMaps',
'hackernews',
'imdb',
'nick',
'ping',
'pun',
'reddit',
'shout',
'slap',
'time',
'urbandictionary',
'whoami',
'wikipedia',
'xkcd',
-- Put new plugins above this line.
'help',
'greetings'
}
plugins = { -- To enable a plugin, add its name to the list.
'about',
'blacklist',
'calc',
'cats',
'commit',
'control',
'currency',
'dice',
'echo',
'eightball',
'gMaps',
'hackernews',
'imdb',
'nick',
'ping',
'pun',
'reddit',
'shout',
'slap',
'time',
'urbandictionary',
'whoami',
'wikipedia',
'xkcd',
-- Put new plugins above this line.
'help',
'greetings'
}
}

View File

@ -1,7 +1,7 @@
#!/bin/sh
while true; do
lua main.lua
echo 'otouto has stopped. ^C to exit.'
sleep 5s
lua main.lua
echo 'otouto has stopped. ^C to exit.'
sleep 5s
done

View File

@ -1,10 +1,10 @@
--[[
bindings.lua (rev. 2016/05/28)
otouto's bindings for the Telegram bot API.
https://core.telegram.org/bots/api
Copyright 2016 topkecleon. Published under the AGPLv3.
bindings.lua (rev. 2016/05/28)
otouto's bindings for the Telegram bot API.
https://core.telegram.org/bots/api
Copyright 2016 topkecleon. Published under the AGPLv3.
See the "Bindings" section of README.md for usage information.
See the "Bindings" section of README.md for usage information.
]]--
local bindings = {}
@ -22,56 +22,56 @@ local MP_ENCODE = require('multipart-post').encode
-- response with failure. Returns false and false with a connection error.
-- To mimic old/normal behavior, it errs if used with an invalid method.
function bindings:request(method, parameters, file)
parameters = parameters or {}
for k,v in pairs(parameters) do
parameters[k] = tostring(v)
end
if file and next(file) ~= nil then
local file_type, file_name = next(file)
local file_file = io.open(file_name, 'r')
local file_data = {
filename = file_name,
data = file_file:read('*a')
}
file_file:close()
parameters[file_type] = file_data
end
if next(parameters) == nil then
parameters = {''}
end
local response = {}
local body, boundary = MP_ENCODE(parameters)
local success, code = HTTPS.request{
url = self.BASE_URL .. method,
method = 'POST',
headers = {
["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
["Content-Length"] = #body,
},
source = ltn12.source.string(body),
sink = ltn12.sink.table(response)
}
local data = table.concat(response)
if not success or success == 1 then
print(method .. ': Connection error. [' .. code .. ']')
return false, false
else
local result = JSON.decode(data)
if not result then
return false, false
elseif result.ok then
return result
else
assert(result.description ~= 'Method not found', method .. ': Method not found.')
return false, result
end
end
parameters = parameters or {}
for k,v in pairs(parameters) do
parameters[k] = tostring(v)
end
if file and next(file) ~= nil then
local file_type, file_name = next(file)
local file_file = io.open(file_name, 'r')
local file_data = {
filename = file_name,
data = file_file:read('*a')
}
file_file:close()
parameters[file_type] = file_data
end
if next(parameters) == nil then
parameters = {''}
end
local response = {}
local body, boundary = MP_ENCODE(parameters)
local success, code = HTTPS.request{
url = self.BASE_URL .. method,
method = 'POST',
headers = {
["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
["Content-Length"] = #body,
},
source = ltn12.source.string(body),
sink = ltn12.sink.table(response)
}
local data = table.concat(response)
if not success or success == 1 then
print(method .. ': Connection error. [' .. code .. ']')
return false, false
else
local result = JSON.decode(data)
if not result then
return false, false
elseif result.ok then
return result
else
assert(result.description ~= 'Method not found', method .. ': Method not found.')
return false, result
end
end
end
function bindings.gen(_, key)
return function(self, params, file)
return bindings.request(self, key, params, file)
end
return function(self, params, file)
return bindings.request(self, key, params, file)
end
end
setmetatable(bindings, { __index = bindings.gen })

View File

@ -7,190 +7,190 @@ bot.version = '3.13'
-- Function to be run on start and reload.
function bot:init(config)
bindings = require('otouto.bindings')
utilities = require('otouto.utilities')
bindings = require('otouto.bindings')
utilities = require('otouto.utilities')
assert(
config.bot_api_key,
'You did not set your bot token in the config!'
)
self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/'
assert(
config.bot_api_key,
'You did not set your bot token in the config!'
)
self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/'
-- Fetch bot information. Try until it succeeds.
repeat
print('Fetching bot information...')
self.info = bindings.getMe(self)
until self.info
self.info = self.info.result
-- Fetch bot information. Try until it succeeds.
repeat
print('Fetching bot information...')
self.info = bindings.getMe(self)
until self.info
self.info = self.info.result
-- Load the "database"! ;)
self.database_name = config.database_name or self.info.username .. '.db'
if not self.database then
self.database = utilities.load_data(self.database_name)
end
-- Load the "database"! ;)
self.database_name = config.database_name or self.info.username .. '.db'
if not self.database then
self.database = utilities.load_data(self.database_name)
end
-- Migration code 1.12 -> 1.13
-- Back to administration global ban list; copy over current blacklist.
if self.database.version ~= '3.13' then
if self.database.administration then
self.database.administration.globalbans = self.database.administration.globalbans or self.database.blacklist or {}
utilities.save_data(self.database_name, self.database)
self.database = utilities.load_data(self.database_name)
end
end
-- End migration code.
-- Migration code 1.12 -> 1.13
-- Back to administration global ban list; copy over current blacklist.
if self.database.version ~= '3.13' then
if self.database.administration then
self.database.administration.globalbans = self.database.administration.globalbans or self.database.blacklist or {}
utilities.save_data(self.database_name, self.database)
self.database = utilities.load_data(self.database_name)
end
end
-- End migration code.
-- Table to cache user info (usernames, IDs, etc).
self.database.users = self.database.users or {}
-- Table to store userdata (nicknames, lastfm usernames, etc).
self.database.userdata = self.database.userdata or {}
-- Table to store the IDs of blacklisted users.
self.database.blacklist = self.database.blacklist or {}
-- Save the bot's version in the database to make migration simpler.
self.database.version = bot.version
-- Add updated bot info to the user info cache.
self.database.users[tostring(self.info.id)] = self.info
-- Table to cache user info (usernames, IDs, etc).
self.database.users = self.database.users or {}
-- Table to store userdata (nicknames, lastfm usernames, etc).
self.database.userdata = self.database.userdata or {}
-- Table to store the IDs of blacklisted users.
self.database.blacklist = self.database.blacklist or {}
-- Save the bot's version in the database to make migration simpler.
self.database.version = bot.version
-- Add updated bot info to the user info cache.
self.database.users[tostring(self.info.id)] = self.info
-- All plugins go into self.plugins. Plugins which accept forwarded messages
-- and messages from blacklisted users also go into self.panoptic_plugins.
self.plugins = {}
self.panoptic_plugins = {}
for _, pname in ipairs(config.plugins) do
local plugin = require('otouto.plugins.'..pname)
table.insert(self.plugins, plugin)
if plugin.init then plugin.init(self, config) end
if plugin.panoptic then table.insert(self.panoptic_plugins, plugin) end
if plugin.doc then plugin.doc = '```\n'..plugin.doc..'\n```' end
if not plugin.triggers then plugin.triggers = {} end
end
-- All plugins go into self.plugins. Plugins which accept forwarded messages
-- and messages from blacklisted users also go into self.panoptic_plugins.
self.plugins = {}
self.panoptic_plugins = {}
for _, pname in ipairs(config.plugins) do
local plugin = require('otouto.plugins.'..pname)
table.insert(self.plugins, plugin)
if plugin.init then plugin.init(self, config) end
if plugin.panoptic then table.insert(self.panoptic_plugins, plugin) end
if plugin.doc then plugin.doc = '```\n'..plugin.doc..'\n```' end
if not plugin.triggers then plugin.triggers = {} end
end
print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')')
print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')')
-- Set loop variables.
self.last_update = self.last_update or 0 -- Update offset.
self.last_cron = self.last_cron or os.date('%M') -- Last cron job.
self.last_database_save = self.last_database_save or os.date('%H') -- Last db save.
self.is_started = true
-- Set loop variables.
self.last_update = self.last_update or 0 -- Update offset.
self.last_cron = self.last_cron or os.date('%M') -- Last cron job.
self.last_database_save = self.last_database_save or os.date('%H') -- Last db save.
self.is_started = true
end
-- Function to be run on each new message.
function bot:on_msg_receive(msg, config)
-- Do not process old messages.
if msg.date < os.time() - 5 then return end
-- Do not process old messages.
if msg.date < os.time() - 5 then return end
-- plugint is the array of plugins we'll check the message against.
-- If the message is forwarded or from a blacklisted user, the bot will only
-- check against panoptic plugins.
local plugint = self.plugins
local from_id_str = tostring(msg.from.id)
-- plugint is the array of plugins we'll check the message against.
-- If the message is forwarded or from a blacklisted user, the bot will only
-- check against panoptic plugins.
local plugint = self.plugins
local from_id_str = tostring(msg.from.id)
-- Cache user info for those involved.
self.database.users[from_id_str] = msg.from
if msg.reply_to_message then
self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from
elseif msg.forward_from then
-- Forwards only go to panoptic plugins.
plugint = self.panoptic_plugins
self.database.users[tostring(msg.forward_from.id)] = msg.forward_from
elseif msg.new_chat_member then
self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member
elseif msg.left_chat_member then
self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member
end
-- Cache user info for those involved.
self.database.users[from_id_str] = msg.from
if msg.reply_to_message then
self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from
elseif msg.forward_from then
-- Forwards only go to panoptic plugins.
plugint = self.panoptic_plugins
self.database.users[tostring(msg.forward_from.id)] = msg.forward_from
elseif msg.new_chat_member then
self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member
elseif msg.left_chat_member then
self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member
end
-- Messages from blacklisted users only go to panoptic plugins.
if self.database.blacklist[from_id_str] then
plugint = self.panoptic_plugins
end
-- Messages from blacklisted users only go to panoptic plugins.
if self.database.blacklist[from_id_str] then
plugint = self.panoptic_plugins
end
-- If no text, use captions.
msg.text = msg.text or msg.caption or ''
msg.text_lower = msg.text:lower()
if msg.reply_to_message then
msg.reply_to_message.text = msg.reply_to_message.text or msg.reply_to_message.caption or ''
end
-- If no text, use captions.
msg.text = msg.text or msg.caption or ''
msg.text_lower = msg.text:lower()
if msg.reply_to_message then
msg.reply_to_message.text = msg.reply_to_message.text or msg.reply_to_message.caption or ''
end
-- Support deep linking.
if msg.text:match('^'..config.cmd_pat..'start .+') then
msg.text = config.cmd_pat .. utilities.input(msg.text)
msg.text_lower = msg.text:lower()
end
-- Support deep linking.
if msg.text:match('^'..config.cmd_pat..'start .+') then
msg.text = config.cmd_pat .. utilities.input(msg.text)
msg.text_lower = msg.text:lower()
end
-- If the message is forwarded or comes from a blacklisted yser,
-- If the message is forwarded or comes from a blacklisted yser,
-- Do the thing.
for _, plugin in ipairs(plugint) do
for _, trigger in ipairs(plugin.triggers) do
if string.match(msg.text_lower, trigger) then
local success, result = pcall(function()
return plugin.action(self, msg, config)
end)
if not success then
-- If the plugin has an error message, send it. If it does
-- not, use the generic one specified in config. If it's set
-- to false, do nothing.
if plugin.error then
utilities.send_reply(self, msg, plugin.error)
elseif plugin.error == nil then
utilities.send_reply(self, msg, config.errors.generic)
end
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
msg = nil
return
-- Continue if the return value is true.
elseif result ~= true then
msg = nil
return
end
end
end
end
msg = nil
-- Do the thing.
for _, plugin in ipairs(plugint) do
for _, trigger in ipairs(plugin.triggers) do
if string.match(msg.text_lower, trigger) then
local success, result = pcall(function()
return plugin.action(self, msg, config)
end)
if not success then
-- If the plugin has an error message, send it. If it does
-- not, use the generic one specified in config. If it's set
-- to false, do nothing.
if plugin.error then
utilities.send_reply(self, msg, plugin.error)
elseif plugin.error == nil then
utilities.send_reply(self, msg, config.errors.generic)
end
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
msg = nil
return
-- Continue if the return value is true.
elseif result ~= true then
msg = nil
return
end
end
end
end
msg = nil
end
-- main
function bot:run(config)
bot.init(self, config)
while self.is_started do
-- Update loop.
local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update + 1 } )
if res then
-- Iterate over every new message.
for _,v in ipairs(res.result) do
self.last_update = v.update_id
if v.message then
bot.on_msg_receive(self, v.message, config)
end
end
else
print('Connection error while fetching updates.')
end
bot.init(self, config)
while self.is_started do
-- Update loop.
local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update + 1 } )
if res then
-- Iterate over every new message.
for _,v in ipairs(res.result) do
self.last_update = v.update_id
if v.message then
bot.on_msg_receive(self, v.message, config)
end
end
else
print('Connection error while fetching updates.')
end
-- Run cron jobs every minute.
if self.last_cron ~= os.date('%M') then
self.last_cron = os.date('%M')
for i,v in ipairs(self.plugins) do
if v.cron then -- Call each plugin's cron function, if it has one.
local result, err = pcall(function() v.cron(self, config) end)
if not result then
utilities.handle_exception(self, err, 'CRON: ' .. i, config)
end
end
end
end
-- Run cron jobs every minute.
if self.last_cron ~= os.date('%M') then
self.last_cron = os.date('%M')
for i,v in ipairs(self.plugins) do
if v.cron then -- Call each plugin's cron function, if it has one.
local result, err = pcall(function() v.cron(self, config) end)
if not result then
utilities.handle_exception(self, err, 'CRON: ' .. i, config)
end
end
end
end
-- Save the "database" every hour.
if self.last_database_save ~= os.date('%H') then
self.last_database_save = os.date('%H')
utilities.save_data(self.database_name, self.database)
end
end
-- Save the database before exiting.
utilities.save_data(self.database_name, self.database)
print('Halted.')
-- Save the "database" every hour.
if self.last_database_save ~= os.date('%H') then
self.last_database_save = os.date('%H')
utilities.save_data(self.database_name, self.database)
end
end
-- Save the database before exiting.
utilities.save_data(self.database_name, self.database)
print('Halted.')
end
return bot

View File

@ -1,13 +1,13 @@
--[[
drua-tg
Based on JuanPotato's lua-tg (https://github.com/juanpotato/lua-tg),
modified to work more naturally from an API bot.
drua-tg
Based on JuanPotato's lua-tg (https://github.com/juanpotato/lua-tg),
modified to work more naturally from an API bot.
Usage:
drua = require('drua-tg')
drua.IP = 'localhost' -- 'localhost' is default
drua.PORT = 4567 -- 4567 is default
drua.message(chat_id, text)
Usage:
drua = require('drua-tg')
drua.IP = 'localhost' -- 'localhost' is default
drua.PORT = 4567 -- 4567 is default
drua.message(chat_id, text)
The MIT License (MIT)
@ -35,150 +35,150 @@ SOFTWARE.
local SOCKET = require('socket')
local comtab = {
add = { 'chat_add_user %s %s', 'channel_invite %s %s' },
kick = { 'chat_del_user %s %s', 'channel_kick %s %s' },
rename = { 'rename_chat %s "%s"', 'rename_channel %s "%s"' },
link = { 'export_chat_link %s', 'export_channel_link %s' },
photo_set = { 'chat_set_photo %s %s', 'channel_set_photo %s %s' },
photo_get = { [0] = 'load_user_photo %s', 'load_chat_photo %s', 'load_channel_photo %s' },
info = { [0] = 'user_info %s', 'chat_info %s', 'channel_info %s' }
add = { 'chat_add_user %s %s', 'channel_invite %s %s' },
kick = { 'chat_del_user %s %s', 'channel_kick %s %s' },
rename = { 'rename_chat %s "%s"', 'rename_channel %s "%s"' },
link = { 'export_chat_link %s', 'export_channel_link %s' },
photo_set = { 'chat_set_photo %s %s', 'channel_set_photo %s %s' },
photo_get = { [0] = 'load_user_photo %s', 'load_chat_photo %s', 'load_channel_photo %s' },
info = { [0] = 'user_info %s', 'chat_info %s', 'channel_info %s' }
}
local format_target = function(target)
target = tonumber(target)
if target < -1000000000000 then
target = 'channel#' .. math.abs(target) - 1000000000000
return target, 2
elseif target < 0 then
target = 'chat#' .. math.abs(target)
return target, 1
else
target = 'user#' .. target
return target, 0
end
target = tonumber(target)
if target < -1000000000000 then
target = 'channel#' .. math.abs(target) - 1000000000000
return target, 2
elseif target < 0 then
target = 'chat#' .. math.abs(target)
return target, 1
else
target = 'user#' .. target
return target, 0
end
end
local escape = function(text)
text = text:gsub('\\', '\\\\')
text = text:gsub('\n', '\\n')
text = text:gsub('\t', '\\t')
text = text:gsub('"', '\\"')
return text
text = text:gsub('\\', '\\\\')
text = text:gsub('\n', '\\n')
text = text:gsub('\t', '\\t')
text = text:gsub('"', '\\"')
return text
end
local drua = {
IP = 'localhost',
PORT = 4567
IP = 'localhost',
PORT = 4567
}
drua.send = function(command, do_receive)
local s = SOCKET.connect(drua.IP, drua.PORT)
assert(s, '\nUnable to connect to tg session.')
s:send(command..'\n')
local output
if do_receive then
output = string.match(s:receive('*l'), 'ANSWER (%d+)')
output = s:receive(tonumber(output)):gsub('\n$', '')
end
s:close()
return output
local s = SOCKET.connect(drua.IP, drua.PORT)
assert(s, '\nUnable to connect to tg session.')
s:send(command..'\n')
local output
if do_receive then
output = string.match(s:receive('*l'), 'ANSWER (%d+)')
output = s:receive(tonumber(output)):gsub('\n$', '')
end
s:close()
return output
end
drua.message = function(target, text)
target = format_target(target)
text = escape(text)
local command = 'msg %s "%s"'
command = command:format(target, text)
return drua.send(command)
target = format_target(target)
text = escape(text)
local command = 'msg %s "%s"'
command = command:format(target, text)
return drua.send(command)
end
drua.send_photo = function(target, photo)
target = format_target(target)
local command = 'send_photo %s %s'
command = command:format(target, photo)
return drua.send(command)
target = format_target(target)
local command = 'send_photo %s %s'
command = command:format(target, photo)
return drua.send(command)
end
drua.add_user = function(chat, target)
local a
chat, a = format_target(chat)
target = format_target(target)
local command = comtab.add[a]:format(chat, target)
return drua.send(command)
local a
chat, a = format_target(chat)
target = format_target(target)
local command = comtab.add[a]:format(chat, target)
return drua.send(command)
end
drua.kick_user = function(chat, target)
-- Get the group info so tg will recognize the target.
drua.get_info(chat)
local a
chat, a = format_target(chat)
target = format_target(target)
local command = comtab.kick[a]:format(chat, target)
return drua.send(command)
-- Get the group info so tg will recognize the target.
drua.get_info(chat)
local a
chat, a = format_target(chat)
target = format_target(target)
local command = comtab.kick[a]:format(chat, target)
return drua.send(command)
end
drua.rename_chat = function(chat, name)
local a
chat, a = format_target(chat)
local command = comtab.rename[a]:format(chat, name)
return drua.send(command)
local a
chat, a = format_target(chat)
local command = comtab.rename[a]:format(chat, name)
return drua.send(command)
end
drua.export_link = function(chat)
local a
chat, a = format_target(chat)
local command = comtab.link[a]:format(chat)
return drua.send(command, true)
local a
chat, a = format_target(chat)
local command = comtab.link[a]:format(chat)
return drua.send(command, true)
end
drua.get_photo = function(chat)
local a
chat, a = format_target(chat)
local command = comtab.photo_get[a]:format(chat)
local output = drua.send(command, true)
if output:match('FAIL') then
return false
else
return output:match('Saved to (.+)')
end
local a
chat, a = format_target(chat)
local command = comtab.photo_get[a]:format(chat)
local output = drua.send(command, true)
if output:match('FAIL') then
return false
else
return output:match('Saved to (.+)')
end
end
drua.set_photo = function(chat, photo)
local a
chat, a = format_target(chat)
local command = comtab.photo_set[a]:format(chat, photo)
return drua.send(command)
local a
chat, a = format_target(chat)
local command = comtab.photo_set[a]:format(chat, photo)
return drua.send(command)
end
drua.get_info = function(target)
local a
target, a = format_target(target)
local command = comtab.info[a]:format(target)
return drua.send(command, true)
local a
target, a = format_target(target)
local command = comtab.info[a]:format(target)
return drua.send(command, true)
end
drua.channel_set_admin = function(chat, user, rank)
chat = format_target(chat)
user = format_target(user)
local command = 'channel_set_admin %s %s %s'
command = command:format(chat, user, rank)
return drua.send(command)
chat = format_target(chat)
user = format_target(user)
local command = 'channel_set_admin %s %s %s'
command = command:format(chat, user, rank)
return drua.send(command)
end
drua.channel_set_about = function(chat, text)
chat = format_target(chat)
text = escape(text)
local command = 'channel_set_about %s "%s"'
command = command:format(chat, text)
return drua.send(command)
chat = format_target(chat)
text = escape(text)
local command = 'channel_set_about %s "%s"'
command = command:format(chat, text)
return drua.send(command)
end
drua.block = function(user)
return drua.send('block_user user#' .. user)
return drua.send('block_user user#' .. user)
end
drua.unblock = function(user)
return drua.send('unblock_user user#' .. user)
return drua.send('unblock_user user#' .. user)
end
return drua

View File

@ -7,13 +7,13 @@ about.command = 'about'
about.doc = 'Returns information about the bot.'
function about:init(config)
about.text = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.'
about.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('about'):t('start').table
about.text = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.'
about.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('about'):t('start').table
end
function about:action(msg, config)
utilities.send_message(self, msg.chat.id, about.text, true, nil, true)
utilities.send_message(self, msg.chat.id, about.text, true, nil, true)
end
return about

File diff suppressed because it is too large Load Diff

View File

@ -10,47 +10,47 @@ local utilities = require('otouto.utilities')
apod.command = 'apod [date]'
function apod:init(config)
apod.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('apod', true).table
apod.doc = [[
apod.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('apod', true).table
apod.doc = [[
/apod [YYYY-MM-DD]
Returns the Astronomy Picture of the Day.
Source: nasa.gov
]]
apod.doc = apod.doc:gsub('/', config.cmd_pat)
apod.base_url = 'https://api.nasa.gov/planetary/apod?api_key=' .. (config.nasa_api_key or 'DEMO_KEY')
]]
apod.doc = apod.doc:gsub('/', config.cmd_pat)
apod.base_url = 'https://api.nasa.gov/planetary/apod?api_key=' .. (config.nasa_api_key or 'DEMO_KEY')
end
function apod:action(msg, config)
local input = utilities.input(msg.text)
local url = apod.base_url
local date = os.date('%F')
if input then
if input:match('^(%d+)%-(%d+)%-(%d+)$') then
url = url .. '&date=' .. URL.escape(input)
date = input
end
end
local input = utilities.input(msg.text)
local url = apod.base_url
local date = os.date('%F')
if input then
if input:match('^(%d+)%-(%d+)%-(%d+)$') then
url = url .. '&date=' .. URL.escape(input)
date = input
end
end
local jstr, code = HTTPS.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jstr, code = HTTPS.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
if data.error then
utilities.send_reply(self, msg, config.errors.results)
return
end
local data = JSON.decode(jstr)
if data.error then
utilities.send_reply(self, msg, config.errors.results)
return
end
local output = string.format(
'<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')
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,8 +5,8 @@ local utilities = require('otouto.utilities')
bandersnatch.command = 'bandersnatch'
function bandersnatch:init(config)
bandersnatch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bandersnatch'):t('bc').table
bandersnatch.doc = 'Shun the frumious Bandersnatch. \nAlias: ' .. config.cmd_pat .. 'bc'
bandersnatch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bandersnatch'):t('bc').table
bandersnatch.doc = 'Shun the frumious Bandersnatch. \nAlias: ' .. config.cmd_pat .. 'bc'
end
local fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" }
@ -17,15 +17,15 @@ local lastnames = { "Coddleswort", "Crumplesack", "Curdlesnoot", "Calldispatch",
function bandersnatch:action(msg)
local output
local output
if math.random(10) == 10 then
output = fullnames[math.random(#fullnames)]
else
output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)]
end
if math.random(10) == 10 then
output = fullnames[math.random(#fullnames)]
else
output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)]
end
utilities.send_message(self, msg.chat.id, '_'..output..'_', true, nil, true)
utilities.send_message(self, msg.chat.id, '_'..output..'_', true, nil, true)
end

View File

@ -5,12 +5,12 @@ local URL = require('socket.url')
local utilities = require('otouto.utilities')
function bible:init(config)
assert(config.biblia_api_key,
'bible.lua requires a Biblia API key from http://api.biblia.com.'
)
assert(config.biblia_api_key,
'bible.lua requires a Biblia API key from http://api.biblia.com.'
)
bible.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bible', true):t('b', true).table
bible.doc = config.cmd_pat .. [[bible <reference>
bible.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bible', true):t('b', true).table
bible.doc = config.cmd_pat .. [[bible <reference>
Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com.
Alias: ]] .. config.cmd_pat .. 'b'
end
@ -19,30 +19,30 @@ bible.command = 'bible <reference>'
function bible:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, bible.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, bible.doc, true)
return
end
local url = 'http://api.biblia.com/v1/bible/content/ASV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input)
local url = 'http://api.biblia.com/v1/bible/content/ASV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input)
local output, res = HTTP.request(url)
local output, res = HTTP.request(url)
if not output or res ~= 200 or output:len() == 0 then
url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input)
output, res = HTTP.request(url)
end
if not output or res ~= 200 or output:len() == 0 then
url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input)
output, res = HTTP.request(url)
end
if not output or res ~= 200 or output:len() == 0 then
output = config.errors.results
end
if not output or res ~= 200 or output:len() == 0 then
output = config.errors.results
end
if output:len() > 4000 then
output = 'The text is too long to post here. Try being more specific.'
end
if output:len() > 4000 then
output = 'The text is too long to post here. Try being more specific.'
end
utilities.send_reply(self, msg, output)
utilities.send_reply(self, msg, output)
end

View File

@ -14,65 +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)
assert(config.bing_api_key,
'bing.lua requires a Bing API key from http://datamarket.azure.com/dataset/bing/search.'
)
assert(config.bing_api_key,
'bing.lua requires a Bing API key from http://datamarket.azure.com/dataset/bing/search.'
)
bing.headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. config.bing_api_key) }
bing.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('bing', true):t('g', true):t('google', true).table
bing.doc = [[
bing.headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. config.bing_api_key) }
bing.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('bing', true):t('g', true):t('google', true).table
bing.doc = [[
/bing <query>
Returns the top web results from Bing.
Aliases: /g, /google
]]
bing.doc = bing.doc:gsub('/', config.cmd_pat)
]]
bing.doc = bing.doc:gsub('/', config.cmd_pat)
end
function bing:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, bing.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, bing.doc, true)
return
end
local url = bing.search_url:format(URL.escape(input))
local resbody = {}
local _, code = https.request{
url = url,
headers = bing.headers,
sink = ltn12.sink.table(resbody),
}
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local url = bing.search_url:format(URL.escape(input))
local resbody = {}
local _, code = https.request{
url = url,
headers = bing.headers,
sink = ltn12.sink.table(resbody),
}
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(table.concat(resbody))
-- Four results in a group, eight in private.
local limit = msg.chat.type == 'private' and 8 or 4
-- No more results than provided.
limit = limit > #data.d.results and #data.d.results or limit
if limit == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local data = JSON.decode(table.concat(resbody))
-- Four results in a group, eight in private.
local limit = msg.chat.type == 'private' and 8 or 4
-- No more results than provided.
limit = limit > #data.d.results and #data.d.results or limit
if limit == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local reslist = {}
for i = 1, limit do
table.insert(reslist, string.format(
'• <a href="%s">%s</a>',
utilities.html_escape(data.d.results[i].Url),
utilities.html_escape(data.d.results[i].Title)
))
end
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')
local reslist = {}
for i = 1, limit do
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 = 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

@ -3,92 +3,92 @@ local utilities = require('otouto.utilities')
local blacklist = {}
function blacklist:init(config)
blacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('blacklist', true):t('unblacklist', true).table
blacklist.error = false
blacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('blacklist', true):t('unblacklist', true).table
blacklist.error = false
end
function blacklist:action(msg, config)
if msg.from.id ~= config.admin then return true end
local targets = {}
if msg.reply_to_message then
table.insert(targets, {
id = msg.reply_to_message.from.id,
id_str = tostring(msg.reply_to_message.from.id),
name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name)
})
else
local input = utilities.input(msg.text)
if input then
for user in input:gmatch('%g+') do
if self.database.users[user] then
table.insert(targets, {
id = self.database.users[user].id,
id_str = tostring(self.database.users[user].id),
name = utilities.build_name(self.database.users[user].first_name, self.database.users[user].last_name)
})
elseif tonumber(user) then
local t = {
id_str = user,
id = tonumber(user)
}
if tonumber(user) < 0 then
t.name = 'Group (' .. user .. ')'
else
t.name = 'Unknown (' .. user .. ')'
end
table.insert(targets, t)
elseif user:match('^@') then
local u = utilities.resolve_username(self, user)
if u then
table.insert(targets, {
id = u.id,
id_str = tostring(u.id),
name = utilities.build_name(u.first_name, u.last_name)
})
else
table.insert(targets, { err = 'Sorry, I do not recognize that username ('..user..').' })
end
else
table.insert(targets, { err = 'Invalid username or ID ('..user..').' })
end
end
else
utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID, or a group or groups via ID.')
return
end
end
local output = ''
if msg.text:match('^'..config.cmd_pat..'blacklist') then
for _, target in ipairs(targets) do
if target.err then
output = output .. target.err .. '\n'
elseif self.database.blacklist[target.id_str] then
output = output .. target.name .. ' is already blacklisted.\n'
else
self.database.blacklist[target.id_str] = true
output = output .. target.name .. ' is now blacklisted.\n'
if config.drua_block_on_blacklist and target.id > 0 then
require('otouto.drua-tg').block(target.id)
end
end
end
elseif msg.text:match('^'..config.cmd_pat..'unblacklist') then
for _, target in ipairs(targets) do
if target.err then
output = output .. target.err .. '\n'
elseif not self.database.blacklist[target.id_str] then
output = output .. target.name .. ' is not blacklisted.\n'
else
self.database.blacklist[target.id_str] = nil
output = output .. target.name .. ' is no longer blacklisted.\n'
if config.drua_block_on_blacklist and target.id > 0 then
require('otouto.drua-tg').unblock(target.id)
end
end
end
end
utilities.send_reply(self, msg, output)
if msg.from.id ~= config.admin then return true end
local targets = {}
if msg.reply_to_message then
table.insert(targets, {
id = msg.reply_to_message.from.id,
id_str = tostring(msg.reply_to_message.from.id),
name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name)
})
else
local input = utilities.input(msg.text)
if input then
for user in input:gmatch('%g+') do
if self.database.users[user] then
table.insert(targets, {
id = self.database.users[user].id,
id_str = tostring(self.database.users[user].id),
name = utilities.build_name(self.database.users[user].first_name, self.database.users[user].last_name)
})
elseif tonumber(user) then
local t = {
id_str = user,
id = tonumber(user)
}
if tonumber(user) < 0 then
t.name = 'Group (' .. user .. ')'
else
t.name = 'Unknown (' .. user .. ')'
end
table.insert(targets, t)
elseif user:match('^@') then
local u = utilities.resolve_username(self, user)
if u then
table.insert(targets, {
id = u.id,
id_str = tostring(u.id),
name = utilities.build_name(u.first_name, u.last_name)
})
else
table.insert(targets, { err = 'Sorry, I do not recognize that username ('..user..').' })
end
else
table.insert(targets, { err = 'Invalid username or ID ('..user..').' })
end
end
else
utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID, or a group or groups via ID.')
return
end
end
local output = ''
if msg.text:match('^'..config.cmd_pat..'blacklist') then
for _, target in ipairs(targets) do
if target.err then
output = output .. target.err .. '\n'
elseif self.database.blacklist[target.id_str] then
output = output .. target.name .. ' is already blacklisted.\n'
else
self.database.blacklist[target.id_str] = true
output = output .. target.name .. ' is now blacklisted.\n'
if config.drua_block_on_blacklist and target.id > 0 then
require('otouto.drua-tg').block(target.id)
end
end
end
elseif msg.text:match('^'..config.cmd_pat..'unblacklist') then
for _, target in ipairs(targets) do
if target.err then
output = output .. target.err .. '\n'
elseif not self.database.blacklist[target.id_str] then
output = output .. target.name .. ' is not blacklisted.\n'
else
self.database.blacklist[target.id_str] = nil
output = output .. target.name .. ' is no longer blacklisted.\n'
if config.drua_block_on_blacklist and target.id > 0 then
require('otouto.drua-tg').unblock(target.id)
end
end
end
end
utilities.send_reply(self, msg, output)
end
return blacklist

View File

@ -7,22 +7,22 @@ local utilities = require('otouto.utilities')
calc.command = 'calc <expression>'
function calc:init(config)
calc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('calc', true).table
calc.doc = config.cmd_pat .. [[calc <expression>
calc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('calc', true).table
calc.doc = config.cmd_pat .. [[calc <expression>
Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org.]]
end
function calc:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, calc.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, calc.doc, true)
return
end
local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input)
local output = HTTPS.request(url)
output = output and '`'..output..'`' or config.errors.connection
utilities.send_reply(self, msg, output, true)
local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input)
local output = HTTPS.request(url)
output = output and '`'..output..'`' or config.errors.connection
utilities.send_reply(self, msg, output, true)
end
return calc

View File

@ -7,22 +7,22 @@ local utilities = require('otouto.utilities')
local catfact = {}
function catfact:init(config)
catfact.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('catfact', true).table
catfact.command = 'catfact'
catfact.doc = 'Returns a cat fact.'
catfact.url = 'http://catfacts-api.appspot.com/api/facts'
catfact.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('catfact', true).table
catfact.command = 'catfact'
catfact.doc = 'Returns a cat fact.'
catfact.url = 'http://catfacts-api.appspot.com/api/facts'
end
function catfact:action(msg, config)
local jstr, code = HTTP.request(catfact.url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
local output = '*Cat Fact*\n_' .. data.facts[1] .. '_'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
local jstr, code = HTTP.request(catfact.url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
local output = '*Cat Fact*\n_' .. data.facts[1] .. '_'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return catfact

View File

@ -4,12 +4,12 @@ local HTTP = require('socket.http')
local utilities = require('otouto.utilities')
function cats:init(config)
if not config.thecatapi_key then
print('Missing config value: thecatapi_key.')
print('cats.lua will be enabled, but there are more features with a key.')
end
if not config.thecatapi_key then
print('Missing config value: thecatapi_key.')
print('cats.lua will be enabled, but there are more features with a key.')
end
cats.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cat').table
cats.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cat').table
end
cats.command = 'cat'
@ -17,21 +17,21 @@ cats.doc = 'Returns a cat!'
function cats:action(msg, config)
local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg'
if config.thecatapi_key then
url = url .. '&api_key=' .. config.thecatapi_key
end
local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg'
if config.thecatapi_key then
url = url .. '&api_key=' .. config.thecatapi_key
end
local str, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local str, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
str = str:match('<img src="(.-)">')
local output = '[Cat!]('..str..')'
str = str:match('<img src="(.-)">')
local output = '[Cat!]('..str..')'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end

View File

@ -4,9 +4,9 @@ local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
function channel:init(config)
channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table
channel.command = 'ch <channel> \\n <message>'
channel.doc = config.cmd_pat .. [[ch <channel>
channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table
channel.command = 'ch <channel> \\n <message>'
channel.doc = config.cmd_pat .. [[ch <channel>
<message>
Sends a message to a channel. Channel may be specified via ID or username. Messages are markdown-enabled. Users may only send messages to channels for which they are the owner or an administrator.
@ -20,41 +20,41 @@ The following markdown syntax is supported:
end
function channel:action(msg, config)
-- An exercise in using zero early returns. :)
local input = utilities.input(msg.text)
local output
if input then
local chat_id = utilities.get_word(input, 1)
local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } )
if admin_list then
local is_admin = false
for _, admin in ipairs(admin_list.result) do
if admin.user.id == msg.from.id then
is_admin = true
end
end
if is_admin then
local text = input:match('\n(.+)')
if text then
local success, result = utilities.send_message(self, chat_id, text, true, nil, true)
if success then
output = 'Your message has been sent!'
else
output = 'Sorry, I was unable to send your message.\n`' .. result.description .. '`'
end
else
output = 'Please enter a message to be sent. Markdown is supported.'
end
else
output = 'Sorry, you do not appear to be an administrator for that channel.'
end
else
output = 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`'
end
else
output = channel.doc
end
utilities.send_reply(self, msg, output, true)
-- An exercise in using zero early returns. :)
local input = utilities.input(msg.text)
local output
if input then
local chat_id = utilities.get_word(input, 1)
local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } )
if admin_list then
local is_admin = false
for _, admin in ipairs(admin_list.result) do
if admin.user.id == msg.from.id then
is_admin = true
end
end
if is_admin then
local text = input:match('\n(.+)')
if text then
local success, result = utilities.send_message(self, chat_id, text, true, nil, true)
if success then
output = 'Your message has been sent!'
else
output = 'Sorry, I was unable to send your message.\n`' .. result.description .. '`'
end
else
output = 'Please enter a message to be sent. Markdown is supported.'
end
else
output = 'Sorry, you do not appear to be an administrator for that channel.'
end
else
output = 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`'
end
else
output = channel.doc
end
utilities.send_reply(self, msg, output, true)
end
return channel

View File

@ -7,22 +7,22 @@ local utilities = require('otouto.utilities')
local chuck = {}
function chuck:init(config)
chuck.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('chuck', true):t('cn', true):t('chucknorris', true).table
chuck.command = 'chuck'
chuck.doc = 'Returns a fact about Chuck Norris.'
chuck.url = 'http://api.icndb.com/jokes/random'
chuck.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('chuck', true):t('cn', true):t('chucknorris', true).table
chuck.command = 'chuck'
chuck.doc = 'Returns a fact about Chuck Norris.'
chuck.url = 'http://api.icndb.com/jokes/random'
end
function chuck:action(msg, config)
local jstr, code = HTTP.request(chuck.url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
local output = '*Chuck Norris Fact*\n_' .. data.value.joke .. '_'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
local jstr, code = HTTP.request(chuck.url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
local output = '*Chuck Norris Fact*\n_' .. data.value.joke .. '_'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return chuck

View File

@ -7,30 +7,30 @@ local bindings = require('otouto.bindings')
local cleverbot = {}
function cleverbot:init(config)
cleverbot.name = '^' .. self.info.first_name:lower() .. ', '
cleverbot.username = '^@' .. self.info.username:lower() .. ', '
cleverbot.triggers = {
'^' .. self.info.first_name:lower() .. ', ',
'^@' .. self.info.username:lower() .. ', '
}
cleverbot.url = config.chatter.cleverbot_api
cleverbot.error = false
cleverbot.name = '^' .. self.info.first_name:lower() .. ', '
cleverbot.username = '^@' .. self.info.username:lower() .. ', '
cleverbot.triggers = {
'^' .. self.info.first_name:lower() .. ', ',
'^@' .. self.info.username:lower() .. ', '
}
cleverbot.url = config.chatter.cleverbot_api
cleverbot.error = false
end
function cleverbot:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' })
local input = msg.text_lower:gsub(cleverbot.name, ''):gsub(cleverbot.name, '')
local jstr, code = HTTPS.request(cleverbot.url .. URL.escape(input))
if code ~= 200 then
utilities.send_message(self, msg.chat.id, config.chatter.connection)
return
end
local data = JSON.decode(jstr)
if not data.clever then
utilities.send_message(self, msg.chat.id, config.chatter.response)
return
end
utilities.send_message(self, msg.chat.id, data.clever)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' })
local input = msg.text_lower:gsub(cleverbot.name, ''):gsub(cleverbot.name, '')
local jstr, code = HTTPS.request(cleverbot.url .. URL.escape(input))
if code ~= 200 then
utilities.send_message(self, msg.chat.id, config.chatter.connection)
return
end
local data = JSON.decode(jstr)
if not data.clever then
utilities.send_message(self, msg.chat.id, config.chatter.response)
return
end
utilities.send_message(self, msg.chat.id, data.clever)
end
return cleverbot

View File

@ -8,19 +8,19 @@ commit.command = 'commit'
commit.doc = 'Returns a commit message from whatthecommit.com.'
function commit:init(config)
commit.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('commit').table
commit.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('commit').table
end
function commit:action(msg)
bindings.request(
self,
'sendMessage',
{
chat_id = msg.chat.id,
text = '```\n' .. (http.request('http://whatthecommit.com/index.txt')) .. '\n```',
parse_mode = 'Markdown'
}
)
bindings.request(
self,
'sendMessage',
{
chat_id = msg.chat.id,
text = '```\n' .. (http.request('http://whatthecommit.com/index.txt')) .. '\n```',
parse_mode = 'Markdown'
}
)
end
return commit

View File

@ -6,52 +6,52 @@ local utilities = require('otouto.utilities')
local cmd_pat -- Prevents the command from being uncallable.
function control:init(config)
cmd_pat = config.cmd_pat
control.triggers = utilities.triggers(self.info.username, cmd_pat,
{'^'..cmd_pat..'script'}):t('reload', true):t('halt').table
cmd_pat = config.cmd_pat
control.triggers = utilities.triggers(self.info.username, cmd_pat,
{'^'..cmd_pat..'script'}):t('reload', true):t('halt').table
end
function control:action(msg, config)
if msg.from.id ~= config.admin then
return
end
if msg.from.id ~= config.admin then
return
end
if msg.date < os.time() - 2 then return end
if msg.date < os.time() - 2 then return end
if msg.text_lower:match('^'..cmd_pat..'reload') then
for pac, _ in pairs(package.loaded) do
if pac:match('^otouto%.plugins%.') then
package.loaded[pac] = nil
end
end
package.loaded['otouto.bindings'] = nil
package.loaded['otouto.utilities'] = nil
package.loaded['otouto.drua-tg'] = nil
package.loaded['config'] = nil
if not msg.text_lower:match('%-config') then
for k, v in pairs(require('config')) do
config[k] = v
end
end
bot.init(self, config)
utilities.send_reply(self, msg, 'Bot reloaded!')
elseif msg.text_lower:match('^'..cmd_pat..'halt') then
self.is_started = false
utilities.send_reply(self, msg, 'Stopping bot!')
elseif msg.text_lower:match('^'..cmd_pat..'script') then
local input = msg.text_lower:match('^'..cmd_pat..'script\n(.+)')
if not input then
utilities.send_reply(self, msg, 'usage: ```\n'..cmd_pat..'script\n'..cmd_pat..'command <arg>\n...\n```', true)
return
end
input = input .. '\n'
for command in input:gmatch('(.-)\n') do
command = utilities.trim(command)
msg.text = command
bot.on_msg_receive(self, msg, config)
end
end
if msg.text_lower:match('^'..cmd_pat..'reload') then
for pac, _ in pairs(package.loaded) do
if pac:match('^otouto%.plugins%.') then
package.loaded[pac] = nil
end
end
package.loaded['otouto.bindings'] = nil
package.loaded['otouto.utilities'] = nil
package.loaded['otouto.drua-tg'] = nil
package.loaded['config'] = nil
if not msg.text_lower:match('%-config') then
for k, v in pairs(require('config')) do
config[k] = v
end
end
bot.init(self, config)
utilities.send_reply(self, msg, 'Bot reloaded!')
elseif msg.text_lower:match('^'..cmd_pat..'halt') then
self.is_started = false
utilities.send_reply(self, msg, 'Stopping bot!')
elseif msg.text_lower:match('^'..cmd_pat..'script') then
local input = msg.text_lower:match('^'..cmd_pat..'script\n(.+)')
if not input then
utilities.send_reply(self, msg, 'usage: ```\n'..cmd_pat..'script\n'..cmd_pat..'command <arg>\n...\n```', true)
return
end
input = input .. '\n'
for command in input:gmatch('(.-)\n') do
command = utilities.trim(command)
msg.text = command
bot.on_msg_receive(self, msg, config)
end
end
end

View File

@ -6,8 +6,8 @@ local utilities = require('otouto.utilities')
currency.command = 'cash [amount] <from> to <to>'
function currency:init(config)
currency.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cash', true).table
currency.doc = config.cmd_pat .. [[cash [amount] <from> to <to>
currency.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cash', true).table
currency.doc = config.cmd_pat .. [[cash [amount] <from> to <to>
Example: ]] .. config.cmd_pat .. [[cash 5 USD to EUR
Returns exchange rates for various currencies.
Source: Google Finance.]]
@ -15,44 +15,44 @@ end
function currency:action(msg, config)
local input = msg.text:upper()
if not input:match('%a%a%a TO %a%a%a') then
utilities.send_message(self, msg.chat.id, currency.doc, true, msg.message_id, true)
return
end
local input = msg.text:upper()
if not input:match('%a%a%a TO %a%a%a') then
utilities.send_message(self, msg.chat.id, currency.doc, true, msg.message_id, true)
return
end
local from = input:match('(%a%a%a) TO')
local to = input:match('TO (%a%a%a)')
local amount = utilities.get_word(input, 2)
amount = tonumber(amount) or 1
local result = 1
local from = input:match('(%a%a%a) TO')
local to = input:match('TO (%a%a%a)')
local amount = utilities.get_word(input, 2)
amount = tonumber(amount) or 1
local result = 1
local url = 'https://www.google.com/finance/converter'
local url = 'https://www.google.com/finance/converter'
if from ~= to then
if from ~= to then
url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount
local str, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount
local str, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
str = str:match('<span class=bld>(.*) %u+</span>')
if not str then
utilities.send_reply(self, msg, config.errors.results)
return
end
str = str:match('<span class=bld>(.*) %u+</span>')
if not str then
utilities.send_reply(self, msg, config.errors.results)
return
end
result = string.format('%.2f', str)
result = string.format('%.2f', str)
end
end
local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n\n'
output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance`'
output = '```\n' .. output .. '\n```'
local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n\n'
output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance`'
output = '```\n' .. output .. '\n```'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end

View File

@ -5,49 +5,49 @@ local utilities = require('otouto.utilities')
dice.command = 'roll <nDr>'
function dice:init(config)
dice.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('roll', true).table
dice.doc = config.cmd_pat .. [[roll <nDr>
dice.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('roll', true).table
dice.doc = config.cmd_pat .. [[roll <nDr>
Returns a set of dice rolls, where n is the number of rolls and r is the range. If only a range is given, returns only one roll.]]
end
function dice:action(msg)
local input = utilities.input(msg.text_lower)
if not input then
utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true)
return
end
local input = utilities.input(msg.text_lower)
if not input then
utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true)
return
end
local count, range
if input:match('^[%d]+d[%d]+$') then
count, range = input:match('([%d]+)d([%d]+)')
elseif input:match('^d?[%d]+$') then
count = 1
range = input:match('^d?([%d]+)$')
else
utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true)
return
end
local count, range
if input:match('^[%d]+d[%d]+$') then
count, range = input:match('([%d]+)d([%d]+)')
elseif input:match('^d?[%d]+$') then
count = 1
range = input:match('^d?([%d]+)$')
else
utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true)
return
end
count = tonumber(count)
range = tonumber(range)
count = tonumber(count)
range = tonumber(range)
if range < 2 then
utilities.send_reply(self, msg, 'The minimum range is 2.')
return
end
if range > 1000 or count > 1000 then
utilities.send_reply(self, msg, 'The maximum range and count are 1000.')
return
end
if range < 2 then
utilities.send_reply(self, msg, 'The minimum range is 2.')
return
end
if range > 1000 or count > 1000 then
utilities.send_reply(self, msg, 'The maximum range and count are 1000.')
return
end
local output = '*' .. count .. 'd' .. range .. '*\n`'
for _ = 1, count do
output = output .. math.random(range) .. '\t'
end
output = output .. '`'
local output = '*' .. count .. 'd' .. range .. '*\n`'
for _ = 1, count do
output = output .. math.random(range) .. '\t'
end
output = output .. '`'
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
end

View File

@ -8,8 +8,8 @@ local utilities = require('otouto.utilities')
dilbert.command = 'dilbert [date]'
function dilbert:init(config)
dilbert.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('dilbert', true).table
dilbert.doc = config.cmd_pat .. [[dilbert [YYYY-MM-DD]
dilbert.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('dilbert', true).table
dilbert.doc = config.cmd_pat .. [[dilbert [YYYY-MM-DD]
Returns the latest Dilbert strip or that of the provided date.
Dates before the first strip will return the first strip. Dates after the last trip will return the last strip.
Source: dilbert.com]]
@ -17,32 +17,32 @@ end
function dilbert:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' } )
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' } )
local input = utilities.input(msg.text)
if not input then input = os.date('%F') end
if not input:match('^%d%d%d%d%-%d%d%-%d%d$') then input = os.date('%F') end
local input = utilities.input(msg.text)
if not input then input = os.date('%F') end
if not input:match('^%d%d%d%d%-%d%d%-%d%d$') then input = os.date('%F') end
local url = 'http://dilbert.com/strip/' .. URL.escape(input)
local str, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local url = 'http://dilbert.com/strip/' .. URL.escape(input)
local str, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local strip_filename = '/tmp/' .. input .. '.gif'
local strip_file = io.open(strip_filename)
if strip_file then
strip_file:close()
strip_file = strip_filename
else
local strip_url = str:match('<meta property="og:image" content="(.-)"/>')
strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif')
end
local strip_filename = '/tmp/' .. input .. '.gif'
local strip_file = io.open(strip_filename)
if strip_file then
strip_file:close()
strip_file = strip_filename
else
local strip_url = str:match('<meta property="og:image" content="(.-)"/>')
strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif')
end
local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>')
local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>')
bindings.sendPhoto(self, { chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file } )
bindings.sendPhoto(self, { chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file } )
end

View File

@ -5,25 +5,25 @@ local utilities = require('otouto.utilities')
echo.command = 'echo <text>'
function echo:init(config)
echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table
echo.doc = config.cmd_pat .. 'echo <text> \nRepeats a string of text.'
echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table
echo.doc = config.cmd_pat .. 'echo <text> \nRepeats a string of text.'
end
function echo:action(msg)
local input = utilities.input_from_msg(msg)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true)
else
local output
if msg.chat.type == 'supergroup' then
output = utilities.style.enquote('Echo', input)
else
output = utilities.md_escape(utilities.char.zwnj..input)
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
if not input then
utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true)
else
local output
if msg.chat.type == 'supergroup' then
output = utilities.style.enquote('Echo', input)
else
output = utilities.md_escape(utilities.char.zwnj..input)
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
end

View File

@ -6,52 +6,52 @@ eightball.command = '8ball'
eightball.doc = 'Returns an answer from a magic 8-ball!'
function eightball:init(config)
eightball.triggers = utilities.triggers(self.info.username, config.cmd_pat,
{'[Yy]/[Nn]%p*$'}):t('8ball', true).table
eightball.triggers = utilities.triggers(self.info.username, config.cmd_pat,
{'[Yy]/[Nn]%p*$'}):t('8ball', true).table
end
local ball_answers = {
"It is certain.",
"It is decidedly so.",
"Without a doubt.",
"Yes, definitely.",
"You may rely on it.",
"As I see it, yes.",
"Most likely.",
"Outlook: good.",
"Yes.",
"Signs point to yes.",
"Reply hazy try again.",
"Ask again later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Don't count on it.",
"My reply is no.",
"My sources say no.",
"Outlook: not so good.",
"Very doubtful.",
"There is a time and place for everything, but not now."
"It is certain.",
"It is decidedly so.",
"Without a doubt.",
"Yes, definitely.",
"You may rely on it.",
"As I see it, yes.",
"Most likely.",
"Outlook: good.",
"Yes.",
"Signs point to yes.",
"Reply hazy try again.",
"Ask again later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Don't count on it.",
"My reply is no.",
"My sources say no.",
"Outlook: not so good.",
"Very doubtful.",
"There is a time and place for everything, but not now."
}
local yesno_answers = {
'Absolutely.',
'In your dreams.',
'Yes.',
'No.'
'Absolutely.',
'In your dreams.',
'Yes.',
'No.'
}
function eightball:action(msg)
local output
local output
if msg.text_lower:match('y/n%p?$') then
output = yesno_answers[math.random(#yesno_answers)]
else
output = ball_answers[math.random(#ball_answers)]
end
if msg.text_lower:match('y/n%p?$') then
output = yesno_answers[math.random(#yesno_answers)]
else
output = ball_answers[math.random(#ball_answers)]
end
utilities.send_reply(self, msg, output)
utilities.send_reply(self, msg, output)
end

View File

@ -5,13 +5,13 @@ local fortune = {}
local utilities = require('otouto.utilities')
function fortune:init(config)
local s = io.popen('fortune'):read('*all')
assert(
not s:match('not found$'),
'fortune.lua requires the fortune program to be installed.'
)
local s = io.popen('fortune'):read('*all')
assert(
not s:match('not found$'),
'fortune.lua requires the fortune program to be installed.'
)
fortune.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('fortune').table
fortune.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('fortune').table
end
fortune.command = 'fortune'
@ -19,11 +19,11 @@ fortune.doc = 'Returns a UNIX fortune.'
function fortune:action(msg)
local fortunef = io.popen('fortune')
local output = fortunef:read('*all')
output = '```\n' .. output .. '\n```'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
fortunef:close()
local fortunef = io.popen('fortune')
local output = fortunef:read('*all')
output = '```\n' .. output .. '\n```'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
fortunef:close()
end

View File

@ -9,58 +9,58 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function gImages:init(config)
assert(config.google_api_key and config.google_cse_key,
'gImages.lua requires a Google API key from http://console.developers.google.com and a Google Custom Search Engine key from http://cse.google.com/cse.'
)
assert(config.google_api_key and config.google_cse_key,
'gImages.lua requires a Google API key from http://console.developers.google.com and a Google Custom Search Engine key from http://cse.google.com/cse.'
)
gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('image', true):t('i', true):t('insfw', true).table
gImages.doc = config.cmd_pat .. [[image <query>
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
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_from_msg(msg)
if not input then
utilities.send_reply(self, msg, gImages.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, gImages.doc, true)
return
end
local url = gImages.search_url
local url = gImages.search_url
if not string.match(msg.text, '^'..config.cmd_pat..'i[mage]*nsfw') then
url = url .. '&safe=high'
end
if not string.match(msg.text, '^'..config.cmd_pat..'i[mage]*nsfw') then
url = url .. '&safe=high'
end
url = url .. '&q=' .. URL.escape(input)
url = url .. '&q=' .. URL.escape(input)
local jstr, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jstr, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.searchInformation.totalResults == '0' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local jdat = JSON.decode(jstr)
if jdat.searchInformation.totalResults == '0' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local i = math.random(jdat.queries.request[1].count)
local img_url = jdat.items[i].link
local img_title = jdat.items[i].title
local output = '[' .. img_title .. '](' .. img_url .. ')'
local i = math.random(jdat.queries.request[1].count)
local img_url = jdat.items[i].link
local img_title = jdat.items[i].title
local output = '[' .. img_title .. '](' .. img_url .. ')'
if msg.text:match('nsfw') then
utilities.send_reply(self, '*NSFW*\n'..msg, output)
else
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end
if msg.text:match('nsfw') then
utilities.send_reply(self, '*NSFW*\n'..msg, output)
else
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end
end

View File

@ -6,34 +6,34 @@ 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 = [[
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: /loc
]]
gMaps.doc = gMaps.doc:gsub('/', config.cmd_pat)
]]
gMaps.doc = gMaps.doc:gsub('/', config.cmd_pat)
end
function gMaps:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, gMaps.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, gMaps.doc, true)
return
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
end
bindings.sendLocation(self, {
chat_id = msg.chat.id,
latitude = coords.lat,
longitude = coords.lon,
reply_to_message_id = msg.message_id
} )
bindings.sendLocation(self, {
chat_id = msg.chat.id,
latitude = coords.lat,
longitude = coords.lon,
reply_to_message_id = msg.message_id
} )
end
return gMaps

View File

@ -3,30 +3,30 @@ local utilities = require('otouto.utilities')
local greetings = {}
function greetings:init(config)
greetings.triggers = {}
for _, triggers in pairs(config.greetings) do
for i = 1, #triggers do
triggers[i] = '^' .. triggers[i] .. ',? ' .. self.info.first_name:lower() .. '%p*$'
table.insert(greetings.triggers, triggers[i])
end
end
greetings.triggers = {}
for _, triggers in pairs(config.greetings) do
for i = 1, #triggers do
triggers[i] = '^' .. triggers[i] .. ',? ' .. self.info.first_name:lower() .. '%p*$'
table.insert(greetings.triggers, triggers[i])
end
end
end
function greetings:action(msg, config)
local nick
if self.database.userdata[tostring(msg.from.id)] then
nick = self.database.userdata[tostring(msg.from.id)].nickname
end
nick = nick or utilities.build_name(msg.from.first_name, msg.from.last_name)
local nick
if self.database.userdata[tostring(msg.from.id)] then
nick = self.database.userdata[tostring(msg.from.id)].nickname
end
nick = nick or utilities.build_name(msg.from.first_name, msg.from.last_name)
for response, triggers in pairs(config.greetings) do
for _, trigger in pairs(triggers) do
if string.match(msg.text_lower, trigger) then
utilities.send_message(self, msg.chat.id, response:gsub('#NAME', nick))
return
end
end
end
for response, triggers in pairs(config.greetings) do
for _, trigger in pairs(triggers) do
if string.match(msg.text_lower, trigger) then
utilities.send_message(self, msg.chat.id, response:gsub('#NAME', nick))
return
end
end
end
end
return greetings

View File

@ -8,68 +8,68 @@ local hackernews = {}
hackernews.command = 'hackernews'
local function get_hackernews_results()
local results = {}
local jstr, code = HTTPS.request(hackernews.topstories_url)
if code ~= 200 then return end
local data = JSON.decode(jstr)
for i = 1, 8 do
local ijstr, icode = HTTPS.request(hackernews.res_url:format(data[i]))
if icode ~= 200 then return end
local idata = JSON.decode(ijstr)
local result
if idata.url then
result = string.format(
'\n• <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
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.
hackernews.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hackernews', true):t('hn', true).table
hackernews.doc = [[Returns four (if group) or eight (if private message) top stories from Hacker News.
Alias: ]] .. config.cmd_pat .. 'hn'
hackernews.topstories_url = 'https://hacker-news.firebaseio.com/v0/topstories.json'
hackernews.res_url = 'https://hacker-news.firebaseio.com/v0/item/%s.json'
hackernews.art_url = 'https://news.ycombinator.com/item?id=%s'
hackernews.last_update = 0
if config.hackernews_onstart == true then
hackernews.results = get_hackernews_results()
if hackernews.results then hackernews.last_update = os.time() / 60 end
end
hackernews.topstories_url = 'https://hacker-news.firebaseio.com/v0/topstories.json'
hackernews.res_url = 'https://hacker-news.firebaseio.com/v0/item/%s.json'
hackernews.art_url = 'https://news.ycombinator.com/item?id=%s'
hackernews.last_update = 0
if config.hackernews_onstart == true then
hackernews.results = get_hackernews_results()
if hackernews.results then hackernews.last_update = os.time() / 60 end
end
end
function hackernews:action(msg, config)
local now = os.time() / 60
if not hackernews.results or hackernews.last_update + config.hackernews_interval < now then
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' })
hackernews.results = get_hackernews_results()
if not hackernews.results then
utilities.send_reply(self, msg, config.errors.connection)
return
end
hackernews.last_update = now
end
-- Four results in a group, eight in private.
local res_count = msg.chat.id == msg.from.id and 8 or 4
local output = '<b>Top Stories from Hacker News:</b>'
for i = 1, res_count do
output = output .. hackernews.results[i]
end
utilities.send_message(self, msg.chat.id, output, true, nil, 'html')
local now = os.time() / 60
if not hackernews.results or hackernews.last_update + config.hackernews_interval < now then
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' })
hackernews.results = get_hackernews_results()
if not hackernews.results then
utilities.send_reply(self, msg, config.errors.connection)
return
end
hackernews.last_update = now
end
-- Four results in a group, eight in private.
local res_count = msg.chat.id == msg.from.id and 8 or 4
local output = '<b>Top Stories from Hacker News:</b>'
for i = 1, res_count do
output = output .. hackernews.results[i]
end
utilities.send_message(self, msg.chat.id, output, true, nil, 'html')
end
return hackernews

View File

@ -8,111 +8,111 @@ local utilities = require('otouto.utilities')
local HTTPS = require('ssl.https')
function hearthstone:init(config)
hearthstone.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hearthstone', true):t('hs').table
hearthstone.command = 'hearthstone <query>'
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
if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then
print('Downloading Hearthstone database...')
print('Downloading Hearthstone database...')
local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json')
if not jstr or res ~= 200 then
print('Error connecting to hearthstonejson.com.')
print('hearthstone.lua will not be enabled.')
hearthstone.command = nil
hearthstone.triggers = nil
return
end
self.database.hearthstone = JSON.decode(jstr)
self.database.hearthstone.expiration = os.time() + 600000
local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json')
if not jstr or res ~= 200 then
print('Error connecting to hearthstonejson.com.')
print('hearthstone.lua will not be enabled.')
hearthstone.command = nil
hearthstone.triggers = nil
return
end
self.database.hearthstone = JSON.decode(jstr)
self.database.hearthstone.expiration = os.time() + 600000
print('Download complete! It will be stored for a week.')
print('Download complete! It will be stored for a week.')
end
end
hearthstone.doc = config.cmd_pat .. [[hearthstone <query>
hearthstone.doc = config.cmd_pat .. [[hearthstone <query>
Returns Hearthstone card info.
Alias: ]] .. config.cmd_pat .. 'hs'
end
local function format_card(card)
local ctype = card.type
if card.race then
ctype = card.race
end
if card.rarity then
ctype = card.rarity .. ' ' .. ctype
end
if card.playerClass then
ctype = ctype .. ' (' .. card.playerClass .. ')'
elseif card.faction then
ctype = ctype .. ' (' .. card.faction .. ')'
end
local ctype = card.type
if card.race then
ctype = card.race
end
if card.rarity then
ctype = card.rarity .. ' ' .. ctype
end
if card.playerClass then
ctype = ctype .. ' (' .. card.playerClass .. ')'
elseif card.faction then
ctype = ctype .. ' (' .. card.faction .. ')'
end
local stats
if card.cost then
stats = card.cost .. 'c'
if card.attack then
stats = stats .. ' | ' .. card.attack .. 'a'
end
if card.health then
stats = stats .. ' | ' .. card.health .. 'h'
end
if card.durability then
stats = stats .. ' | ' .. card.durability .. 'd'
end
elseif card.health then
stats = card.health .. 'h'
end
local stats
if card.cost then
stats = card.cost .. 'c'
if card.attack then
stats = stats .. ' | ' .. card.attack .. 'a'
end
if card.health then
stats = stats .. ' | ' .. card.health .. 'h'
end
if card.durability then
stats = stats .. ' | ' .. card.durability .. 'd'
end
elseif card.health then
stats = card.health .. 'h'
end
-- unused?
local info
if card.text then
info = card.text:gsub('</?.->',''):gsub('%$','')
if card.flavor then
info = info .. '\n_' .. card.flavor .. '_'
end
elseif card.flavor then
info = card.flavor
else
info = nil
end
-- unused?
local info
if card.text then
info = card.text:gsub('</?.->',''):gsub('%$','')
if card.flavor then
info = info .. '\n_' .. card.flavor .. '_'
end
elseif card.flavor then
info = card.flavor
else
info = nil
end
local s = '*' .. card.name .. '*\n' .. ctype
if stats then
s = s .. '\n' .. stats
end
if info then
s = s .. '\n' .. info
end
local s = '*' .. card.name .. '*\n' .. ctype
if stats then
s = s .. '\n' .. stats
end
if info then
s = s .. '\n' .. info
end
return s
return s
end
function hearthstone:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, hearthstone.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, hearthstone.doc, true)
return
end
local output = ''
for _,v in pairs(self.database.hearthstone) do
if type(v) == 'table' and string.lower(v.name):match(input) then
output = output .. format_card(v) .. '\n\n'
end
end
local output = ''
for _,v in pairs(self.database.hearthstone) do
if type(v) == 'table' and string.lower(v.name):match(input) then
output = output .. format_card(v) .. '\n\n'
end
end
output = utilities.trim(output)
if output:len() == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
output = utilities.trim(output)
if output:len() == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
end

View File

@ -3,51 +3,51 @@ local utilities = require('otouto.utilities')
local help = {}
function help:init(config)
help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('help', true):t('h', true).table
help.command = 'help [command]'
help.doc = config.cmd_pat .. 'help [command] \nReturns usage information for a given command.'
help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('help', true):t('h', true).table
help.command = 'help [command]'
help.doc = config.cmd_pat .. 'help [command] \nReturns usage information for a given command.'
end
function help:action(msg, config)
local input = utilities.input(msg.text_lower)
if input then
if not help.help_word then
for _, plugin in ipairs(self.plugins) do
if plugin.command and plugin.doc and not plugin.help_word then
plugin.help_word = utilities.get_word(plugin.command, 1)
end
end
end
for _,plugin in ipairs(self.plugins) do
if plugin.help_word == input:gsub('^/', '') then
local output = '*Help for* _' .. plugin.help_word .. '_*:*\n' .. plugin.doc
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
end
utilities.send_reply(self, msg, 'Sorry, there is no help for that command.')
else
-- Generate the help message on first run.
if not help.text then
local commandlist = {}
for _, plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
end
end
table.sort(commandlist)
help.text = '*Available commands:*\n' .. config.cmd_pat .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nArguments: <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)
if not res then
utilities.send_reply(self, msg, 'Please [message me privately](http://telegram.me/' .. self.info.username .. '?start=help) for a list of commands.', true)
elseif msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.')
end
end
local input = utilities.input(msg.text_lower)
if input then
if not help.help_word then
for _, plugin in ipairs(self.plugins) do
if plugin.command and plugin.doc and not plugin.help_word then
plugin.help_word = utilities.get_word(plugin.command, 1)
end
end
end
for _,plugin in ipairs(self.plugins) do
if plugin.help_word == input:gsub('^/', '') then
local output = '*Help for* _' .. plugin.help_word .. '_*:*\n' .. plugin.doc
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
end
utilities.send_reply(self, msg, 'Sorry, there is no help for that command.')
else
-- Generate the help message on first run.
if not help.text then
local commandlist = {}
for _, plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
end
end
table.sort(commandlist)
help.text = '*Available commands:*\n' .. config.cmd_pat .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nArguments: <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)
if not res then
utilities.send_reply(self, msg, 'Please [message me privately](http://telegram.me/' .. self.info.username .. '?start=help) for a list of commands.', true)
elseif msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.')
end
end
end
return help

View File

@ -3,60 +3,60 @@ local utilities = require('otouto.utilities')
local id = {}
function id:init(config)
id.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('id', true).table
id.command = 'id <user>'
id.doc = config.cmd_pat .. [[id <user> ...
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
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')
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

@ -8,39 +8,39 @@ local utilities = require('otouto.utilities')
imdb.command = 'imdb <query>'
function imdb:init(config)
imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table
imdb.doc = config.cmd_pat .. 'imdb <query> \nReturns an IMDb entry.'
imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table
imdb.doc = config.cmd_pat .. 'imdb <query> \nReturns an IMDb entry.'
end
function imdb:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, imdb.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, imdb.doc, true)
return
end
local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input)
local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input)
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
local jdat = JSON.decode(jstr)
if jdat.Response ~= 'True' then
utilities.send_reply(self, msg, config.errors.results)
return
end
if jdat.Response ~= 'True' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')*\n'
output = output .. jdat.imdbRating ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n'
output = output .. '_' .. jdat.Plot .. '_\n'
output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')'
local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')*\n'
output = output .. jdat.imdbRating ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n'
output = output .. '_' .. jdat.Plot .. '_\n'
output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end

View File

@ -7,37 +7,37 @@ local utilities = require('otouto.utilities')
local isup = {}
function isup:init(config)
isup.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('websitedown', true):t('isitup', true):t('isup', true).table
isup.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('websitedown', true):t('isitup', true):t('isup', true).table
isup.doc = config.cmd_pat .. [[isup <url>
isup.doc = config.cmd_pat .. [[isup <url>
Returns the up or down status of a website.]]
isup.command = 'isup <url>'
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 input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, isup.doc)
return
end
local protocol = HTTP
local url_lower = input:lower()
if url_lower:match('^https') then
protocol = HTTPS
elseif not url_lower:match('^http') then
input = 'http://' .. input
end
local _, code = protocol.request(input)
code = tonumber(code)
local output
if not code or code > 399 then
output = 'This website is down or nonexistent.'
else
output = 'This website is up.'
end
utilities.send_reply(self, msg, output, true)
local protocol = HTTP
local url_lower = input:lower()
if url_lower:match('^https') then
protocol = HTTPS
elseif not url_lower:match('^http') then
input = 'http://' .. input
end
local _, code = protocol.request(input)
code = tonumber(code)
local output
if not code or code > 399 then
output = 'This website is down or nonexistent.'
else
output = 'This website is up.'
end
utilities.send_reply(self, msg, output, true)
end
return isup

View File

@ -9,12 +9,12 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function lastfm:init(config)
assert(config.lastfm_api_key,
'lastfm.lua requires a last.fm API key from http://last.fm/api.'
)
assert(config.lastfm_api_key,
'lastfm.lua requires a last.fm API key from http://last.fm/api.'
)
lastfm.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('fmset', true).table
lastfm.doc = config.cmd_pat .. [[np [username]
lastfm.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('fmset', true).table
lastfm.doc = config.cmd_pat .. [[np [username]
Returns what you are or were last listening to. If you specify a username, info will be returned for that username.
]] .. config.cmd_pat .. [[fmset <username>
@ -25,84 +25,84 @@ lastfm.command = 'lastfm'
function lastfm:action(msg, config)
local input = utilities.input(msg.text)
local from_id_str = tostring(msg.from.id)
self.database.userdata[from_id_str] = self.database.userdata[from_id_str] or {}
local input = utilities.input(msg.text)
local from_id_str = tostring(msg.from.id)
self.database.userdata[from_id_str] = self.database.userdata[from_id_str] or {}
if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then
utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
return
elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then
if not input then
utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
elseif input == '--' or input == utilities.char.em_dash then
self.database.userdata[from_id_str].lastfm = nil
utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.')
else
self.database.userdata[from_id_str].lastfm = input
utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".')
end
return
end
if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then
utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
return
elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then
if not input then
utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
elseif input == '--' or input == utilities.char.em_dash then
self.database.userdata[from_id_str].lastfm = nil
utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.')
else
self.database.userdata[from_id_str].lastfm = input
utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".')
end
return
end
local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user='
local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user='
local username
local alert = ''
if input then
username = input
elseif self.database.userdata[from_id_str].lastfm then
username = self.database.userdata[from_id_str].lastfm
elseif msg.from.username then
username = msg.from.username
alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use '..config.cmd_pat..'fmset <username>.'
self.database.userdata[from_id_str].lastfm = username
else
utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.')
return
end
local username
local alert = ''
if input then
username = input
elseif self.database.userdata[from_id_str].lastfm then
username = self.database.userdata[from_id_str].lastfm
elseif msg.from.username then
username = msg.from.username
alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use '..config.cmd_pat..'fmset <username>.'
self.database.userdata[from_id_str].lastfm = username
else
utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.')
return
end
url = url .. URL.escape(username)
url = url .. URL.escape(username)
local jstr, res
utilities.with_http_timeout(
1, function ()
jstr, res = HTTP.request(url)
end)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jstr, res
utilities.with_http_timeout(
1, function ()
jstr, res = HTTP.request(url)
end)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.error then
utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.')
return
end
local jdat = JSON.decode(jstr)
if jdat.error then
utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.')
return
end
jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then
utilities.send_reply(self, msg, 'No history for this user.' .. alert)
return
end
jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then
utilities.send_reply(self, msg, 'No history for this user.' .. alert)
return
end
local output = input or msg.from.first_name
output = '🎵 ' .. output
local output = input or msg.from.first_name
output = '🎵 ' .. output
if jdat['@attr'] and jdat['@attr'].nowplaying then
output = output .. ' is currently listening to:\n'
else
output = output .. ' last listened to:\n'
end
if jdat['@attr'] and jdat['@attr'].nowplaying then
output = output .. ' is currently listening to:\n'
else
output = output .. ' last listened to:\n'
end
local title = jdat.name or 'Unknown'
local artist = 'Unknown'
if jdat.artist then
artist = jdat.artist['#text']
end
local title = jdat.name or 'Unknown'
local artist = 'Unknown'
if jdat.artist then
artist = jdat.artist['#text']
end
output = output .. title .. ' - ' .. artist .. alert
utilities.send_message(self, msg.chat.id, output)
output = output .. title .. ' - ' .. artist .. alert
utilities.send_message(self, msg.chat.id, output)
end

View File

@ -5,59 +5,59 @@ local URL = require('socket.url')
local JSON, serpent
function luarun:init(config)
luarun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lua', true):t('return', true).table
if config.luarun_serpent then
serpent = require('serpent')
luarun.serialize = function(t)
return serpent.block(t, {comment=false})
end
else
JSON = require('dkjson')
luarun.serialize = function(t)
return JSON.encode(t, {indent=true})
end
end
luarun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lua', true):t('return', true).table
if config.luarun_serpent then
serpent = require('serpent')
luarun.serialize = function(t)
return serpent.block(t, {comment=false})
end
else
JSON = require('dkjson')
luarun.serialize = function(t)
return JSON.encode(t, {indent=true})
end
end
end
function luarun:action(msg, config)
if msg.from.id ~= config.admin then
return true
end
if msg.from.id ~= config.admin then
return true
end
local input = utilities.input(msg.text)
if not input then
utilities.send_reply(self, msg, 'Please enter a string to load.')
return
end
local input = utilities.input(msg.text)
if not input then
utilities.send_reply(self, msg, 'Please enter a string to load.')
return
end
if msg.text_lower:match('^'..config.cmd_pat..'return') then
input = 'return ' .. input
end
if msg.text_lower:match('^'..config.cmd_pat..'return') then
input = 'return ' .. input
end
local output = loadstring( [[
local bot = require('otouto.bot')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
local drua = require('otouto.drua-tg')
local JSON = require('dkjson')
local URL = require('socket.url')
local HTTP = require('socket.http')
local HTTPS = require('ssl.https')
return function (self, msg, config) ]] .. input .. [[ end
]] )()(self, msg, config)
if output == nil then
output = 'Done!'
else
if type(output) == 'table' then
local s = luarun.serialize(output)
if URL.escape(s):len() < 4000 then
output = s
end
end
output = '```\n' .. tostring(output) .. '\n```'
end
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
local output = loadstring( [[
local bot = require('otouto.bot')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
local drua = require('otouto.drua-tg')
local JSON = require('dkjson')
local URL = require('socket.url')
local HTTP = require('socket.http')
local HTTPS = require('ssl.https')
return function (self, msg, config) ]] .. input .. [[ end
]] )()(self, msg, config)
if output == nil then
output = 'Done!'
else
if type(output) == 'table' then
local s = luarun.serialize(output)
if URL.escape(s):len() < 4000 then
output = s
end
end
output = '```\n' .. tostring(output) .. '\n```'
end
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
end

View File

@ -3,65 +3,65 @@ local me = {}
local utilities = require('otouto.utilities')
function me:init(config)
me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table
me.command = 'me'
me.doc = 'Returns userdata stored by the bot.'
me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table
me.command = 'me'
me.doc = 'Returns userdata stored by the bot.'
end
function me:action(msg, config)
local user
if msg.from.id == config.admin then
if msg.reply_to_message then
user = msg.reply_to_message.from
else
local input = utilities.input(msg.text)
if input then
if tonumber(input) then
user = self.database.users[input]
if not user then
utilities.send_reply(self, msg, 'Unrecognized ID.')
return
end
elseif input:match('^@') then
user = utilities.resolve_username(self, input)
if not user then
utilities.send_reply(self, msg, 'Unrecognized username.')
return
end
else
utilities.send_reply(self, msg, 'Invalid username or ID.')
return
end
end
end
end
user = user or msg.from
local userdata = self.database.userdata[tostring(user.id)] or {}
local user
if msg.from.id == config.admin then
if msg.reply_to_message then
user = msg.reply_to_message.from
else
local input = utilities.input(msg.text)
if input then
if tonumber(input) then
user = self.database.users[input]
if not user then
utilities.send_reply(self, msg, 'Unrecognized ID.')
return
end
elseif input:match('^@') then
user = utilities.resolve_username(self, input)
if not user then
utilities.send_reply(self, msg, 'Unrecognized username.')
return
end
else
utilities.send_reply(self, msg, 'Invalid username or ID.')
return
end
end
end
end
user = user or msg.from
local userdata = self.database.userdata[tostring(user.id)] or {}
local data = {}
for k,v in pairs(userdata) do
table.insert(data, string.format(
'<b>%s</b> <code>%s</code>\n',
utilities.html_escape(k),
utilities.html_escape(v)
))
end
local data = {}
for k,v in pairs(userdata) do
table.insert(data, string.format(
'<b>%s</b> <code>%s</code>\n',
utilities.html_escape(k),
utilities.html_escape(v)
))
end
local output
if #data == 0 then
output = 'There is no data stored for this user.'
else
output = string.format(
'<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
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, 'html')
utilities.send_message(self, msg.chat.id, output, true, nil, 'html')
end

View File

@ -5,45 +5,45 @@ local utilities = require('otouto.utilities')
nick.command = 'nick <nickname>'
function nick:init(config)
nick.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('nick', true).table
nick.doc = config.cmd_pat .. [[nick <nickname>
nick.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('nick', true).table
nick.doc = config.cmd_pat .. [[nick <nickname>
Set your nickname. Use "]] .. config.cmd_pat .. 'nick --" to delete it.'
end
function nick:action(msg, config)
local id_str, name
local id_str, name
if msg.from.id == config.admin and msg.reply_to_message then
id_str = tostring(msg.reply_to_message.from.id)
name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name)
else
id_str = tostring(msg.from.id)
name = utilities.build_name(msg.from.first_name, msg.from.last_name)
end
if msg.from.id == config.admin and msg.reply_to_message then
id_str = tostring(msg.reply_to_message.from.id)
name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name)
else
id_str = tostring(msg.from.id)
name = utilities.build_name(msg.from.first_name, msg.from.last_name)
end
self.database.userdata[id_str] = self.database.userdata[id_str] or {}
self.database.userdata[id_str] = self.database.userdata[id_str] or {}
local output
local input = utilities.input(msg.text)
if not input then
if self.database.userdata[id_str].nickname then
output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".'
else
output = name .. ' currently has no nickname.'
end
elseif utilities.utf8_len(input) > 32 then
output = 'The character limit for nicknames is 32.'
elseif input == '--' or input == utilities.char.em_dash then
self.database.userdata[id_str].nickname = nil
output = name .. '\'s nickname has been deleted.'
else
input = input:gsub('\n', ' ')
self.database.userdata[id_str].nickname = input
output = name .. '\'s nickname has been set to "' .. input .. '".'
end
local output
local input = utilities.input(msg.text)
if not input then
if self.database.userdata[id_str].nickname then
output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".'
else
output = name .. ' currently has no nickname.'
end
elseif utilities.utf8_len(input) > 32 then
output = 'The character limit for nicknames is 32.'
elseif input == '--' or input == utilities.char.em_dash then
self.database.userdata[id_str].nickname = nil
output = name .. '\'s nickname has been deleted.'
else
input = input:gsub('\n', ' ')
self.database.userdata[id_str].nickname = input
output = name .. '\'s nickname has been set to "' .. input .. '".'
end
utilities.send_reply(self, msg, output)
utilities.send_reply(self, msg, output)
end

View File

@ -8,34 +8,34 @@ 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/.-/.-$' }
patterns.triggers = { config.cmd_pat .. '?s/.-/.-$' }
end
function patterns:action(msg)
if not msg.reply_to_message then return true end
local output = msg.reply_to_message.text
if msg.reply_to_message.from.id == self.info.id then
output = output:gsub('Did you mean:\n"', '')
output = output:gsub('"$', '')
end
local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$')
if not m2 then return true end
local res
res, output = pcall(
function()
return output:gsub(m1, m2)
end
)
if res == false then
utilities.send_reply(self, msg, 'Malformed pattern!')
else
output = utilities.trim(output:sub(1, 4000))
output = utilities.style.enquote('Did you mean', output)
utilities.send_reply(self, msg.reply_to_message, output, true)
end
if not msg.reply_to_message then return true end
local output = msg.reply_to_message.text
if msg.reply_to_message.from.id == self.info.id then
output = output:gsub('Did you mean:\n"', '')
output = output:gsub('"$', '')
end
local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$')
if not m2 then return true end
local res
res, output = pcall(
function()
return output:gsub(m1, m2)
end
)
if res == false then
utilities.send_reply(self, msg, 'Malformed pattern!')
else
output = utilities.trim(output:sub(1, 4000))
output = utilities.style.enquote('Did you mean', output)
utilities.send_reply(self, msg.reply_to_message, output, true)
end
end
return patterns

View File

@ -5,12 +5,12 @@ local ping = {}
local utilities = require('otouto.utilities')
function ping:init(config)
ping.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ping'):t('annyong').table
ping.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ping'):t('annyong').table
end
function ping:action(msg, config)
local output = msg.text_lower:match('^'..config.cmd_pat..'ping') and 'Pong!' or 'Annyong.'
utilities.send_message(self, msg.chat.id, output)
local output = msg.text_lower:match('^'..config.cmd_pat..'ping') and 'Pong!' or 'Annyong.'
utilities.send_message(self, msg.chat.id, output)
end
return ping

View File

@ -8,8 +8,8 @@ local utilities = require('otouto.utilities')
pokedex.command = 'pokedex <query>'
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>
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'
@ -17,54 +17,54 @@ end
function pokedex:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, pokedex.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, pokedex.doc, true)
return
end
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
local url = 'http://pokeapi.co'
local url = 'http://pokeapi.co'
local dex_url = url .. '/api/v1/pokemon/' .. input
local dex_jstr, res = HTTP.request(dex_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local dex_url = url .. '/api/v1/pokemon/' .. input
local dex_jstr, res = HTTP.request(dex_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local dex_jdat = JSON.decode(dex_jstr)
local dex_jdat = JSON.decode(dex_jstr)
if not dex_jdat.descriptions or not dex_jdat.descriptions[1] then
utilities.send_reply(self, msg, config.errors.results)
return
end
if not dex_jdat.descriptions or not dex_jdat.descriptions[1] then
utilities.send_reply(self, msg, config.errors.results)
return
end
local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri
local desc_jstr, _ = HTTP.request(desc_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri
local desc_jstr, _ = HTTP.request(desc_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local desc_jdat = JSON.decode(desc_jstr)
local desc_jdat = JSON.decode(desc_jstr)
local poke_type
for _,v in ipairs(dex_jdat.types) do
local type_name = v.name:gsub("^%l", string.upper)
if not poke_type then
poke_type = type_name
else
poke_type = poke_type .. ' / ' .. type_name
end
end
poke_type = poke_type .. ' type'
local poke_type
for _,v in ipairs(dex_jdat.types) do
local type_name = v.name:gsub("^%l", string.upper)
if not poke_type then
poke_type = type_name
else
poke_type = poke_type .. ' / ' .. type_name
end
end
poke_type = poke_type .. ' type'
local output = '*' .. dex_jdat.name .. '*\n#' .. dex_jdat.national_id .. ' | ' .. poke_type .. '\n_' .. desc_jdat.description:gsub('POKMON', 'Pokémon'):gsub('Pokmon', 'Pokémon') .. '_'
local output = '*' .. dex_jdat.name .. '*\n#' .. dex_jdat.national_id .. ' | ' .. poke_type .. '\n_' .. desc_jdat.description:gsub('POKMON', 'Pokémon'):gsub('Pokmon', 'Pokémon') .. '_'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end

View File

@ -3,110 +3,110 @@ local utilities = require('otouto.utilities')
local pgc = {}
function pgc:init(config)
pgc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('gocalc', true).table
pgc.doc = config.cmd_pat .. [[gocalc <required candy> <number of Pokémon> <number of candy>
pgc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('gocalc', true).table
pgc.doc = config.cmd_pat .. [[gocalc <required candy> <number of Pokémon> <number of candy>
Calculates the number of Pokémon that must be transferred before evolving, how many evolutions the user is able to perform, and how many Pokémon and candy will be left over.
All arguments must be positive numbers. Batch jobs may be performed by separating valid sets of arguments by lines.
Example (forty pidgeys and three hundred pidgey candies):
]] .. config.cmd_pat .. 'gocalc 12 40 300'
pgc.command = 'gocalc <required candy> <#pokemon> <#candy>'
pgc.command = 'gocalc <required candy> <#pokemon> <#candy>'
end
-- This function written by Juan Potato. MIT-licensed.
local pidgey_calc = function(candies_to_evolve, mons, candies)
local transferred = 0;
local evolved = 0;
local transferred = 0;
local evolved = 0;
while true do
if math.floor(candies / candies_to_evolve) == 0 or mons == 0 then
break
else
mons = mons - 1
candies = candies - candies_to_evolve + 1
evolved = evolved + 1
if mons == 0 then
break
end
end
end
while true do
if math.floor(candies / candies_to_evolve) == 0 or mons == 0 then
break
else
mons = mons - 1
candies = candies - candies_to_evolve + 1
evolved = evolved + 1
if mons == 0 then
break
end
end
end
while true do
if (candies + mons) < (candies_to_evolve + 1) or mons == 0 then
break
end
while candies < candies_to_evolve do
transferred = transferred + 1
mons = mons - 1
candies = candies + 1
end
mons = mons - 1
candies = candies - candies_to_evolve + 1
evolved = evolved + 1
end
while true do
if (candies + mons) < (candies_to_evolve + 1) or mons == 0 then
break
end
while candies < candies_to_evolve do
transferred = transferred + 1
mons = mons - 1
candies = candies + 1
end
mons = mons - 1
candies = candies - candies_to_evolve + 1
evolved = evolved + 1
end
return {
transfer = transferred,
evolve = evolved,
leftover_mons = mons,
leftover_candy = candies
}
return {
transfer = transferred,
evolve = evolved,
leftover_mons = mons,
leftover_candy = candies
}
end
local single_job = function(input)
local req_candy, mons, candies = input:match('^(%d+) (%d+) (%d+)$')
req_candy = tonumber(req_candy)
mons = tonumber(mons)
candies = tonumber(candies)
if not (req_candy and mons and candies) then
return { err = 'Invalid input: Three numbers expected.' }
elseif req_candy > 400 then
return { err = 'Invalid required candy: Maximum is 400.' }
elseif mons > 1000 then
return { err = 'Invalid number of Pokémon: Maximum is 1000.' }
elseif candies > 10000 then
return { err = 'Invalid number of candies: Maximum is 10000.' }
else
return pidgey_calc(req_candy, mons, candies)
end
local req_candy, mons, candies = input:match('^(%d+) (%d+) (%d+)$')
req_candy = tonumber(req_candy)
mons = tonumber(mons)
candies = tonumber(candies)
if not (req_candy and mons and candies) then
return { err = 'Invalid input: Three numbers expected.' }
elseif req_candy > 400 then
return { err = 'Invalid required candy: Maximum is 400.' }
elseif mons > 1000 then
return { err = 'Invalid number of Pokémon: Maximum is 1000.' }
elseif candies > 10000 then
return { err = 'Invalid number of candies: Maximum is 10000.' }
else
return pidgey_calc(req_candy, mons, candies)
end
end
function pgc:action(msg)
local input = utilities.input(msg.text)
if not input then
utilities.send_reply(self, msg, pgc.doc, true)
return
end
input = input .. '\n'
local output = ''
local total_evolutions = 0
for line in input:gmatch('(.-)\n') do
local info = single_job(line)
output = output .. '`' .. line .. '`\n'
if info.err then
output = output .. info.err .. '\n\n'
else
total_evolutions = total_evolutions + info.evolve
local s = '*Transfer:* %s. \n*Evolve:* %s (%s XP, %s minutes). \n*Leftover:* %s mons, %s candy.\n\n'
s = s:format(info.transfer, info.evolve, info.evolve..'k', info.evolve*0.5, info.leftover_mons, info.leftover_candy)
output = output .. s
end
end
local s = '*Total evolutions:* %s. \n*Recommendation:* %s'
local recommendation
local egg_count = math.floor(total_evolutions/60)
if egg_count < 1 then
recommendation = 'Wait until you have atleast sixty Pokémon to evolve before using a lucky egg.'
else
recommendation = string.format(
'Use %s lucky egg%s for %s evolutions.',
egg_count,
egg_count == 1 and '' or 's',
egg_count * 60
)
end
s = s:format(total_evolutions, recommendation)
output = output .. s
utilities.send_reply(self, msg, output, true)
local input = utilities.input(msg.text)
if not input then
utilities.send_reply(self, msg, pgc.doc, true)
return
end
input = input .. '\n'
local output = ''
local total_evolutions = 0
for line in input:gmatch('(.-)\n') do
local info = single_job(line)
output = output .. '`' .. line .. '`\n'
if info.err then
output = output .. info.err .. '\n\n'
else
total_evolutions = total_evolutions + info.evolve
local s = '*Transfer:* %s. \n*Evolve:* %s (%s XP, %s minutes). \n*Leftover:* %s mons, %s candy.\n\n'
s = s:format(info.transfer, info.evolve, info.evolve..'k', info.evolve*0.5, info.leftover_mons, info.leftover_candy)
output = output .. s
end
end
local s = '*Total evolutions:* %s. \n*Recommendation:* %s'
local recommendation
local egg_count = math.floor(total_evolutions/60)
if egg_count < 1 then
recommendation = 'Wait until you have atleast sixty Pokémon to evolve before using a lucky egg.'
else
recommendation = string.format(
'Use %s lucky egg%s for %s evolutions.',
egg_count,
egg_count == 1 and '' or 's',
egg_count * 60
)
end
s = s:format(total_evolutions, recommendation)
output = output .. s
utilities.send_reply(self, msg, output, true)
end
return pgc

View File

@ -21,7 +21,7 @@ Set your Pokémon Go team for statistical purposes. The team must be valid, and
db.membership = {}
end
for _, set in pairs(db.membership) do
setmetatable(set, utilities.set_meta)
setmetatable(set, utilities.set_meta)
end
end

View File

@ -6,37 +6,37 @@ local utilities = require('otouto.utilities')
preview.command = 'preview <link>'
function preview:init(config)
preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table
preview.doc = config.cmd_pat .. 'preview <link> \nReturns a full-message, "unlinked" preview.'
preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table
preview.doc = config.cmd_pat .. 'preview <link> \nReturns a full-message, "unlinked" preview.'
end
function preview:action(msg)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, preview.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, preview.doc, true)
return
end
input = utilities.get_word(input, 1)
if not input:match('^https?://.+') then
input = 'http://' .. input
end
input = utilities.get_word(input, 1)
if not input:match('^https?://.+') then
input = 'http://' .. input
end
local res = HTTP.request(input)
if not res then
utilities.send_reply(self, msg, 'Please provide a valid link.')
return
end
local res = HTTP.request(input)
if not res then
utilities.send_reply(self, msg, 'Please provide a valid link.')
return
end
if res:len() == 0 then
utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.')
return
end
if res:len() == 0 then
utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.')
return
end
-- Invisible zero-width, non-joiner.
local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>'
utilities.send_message(self, msg.chat.id, output, false, nil, 'html')
-- Invisible zero-width, non-joiner.
local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>'
utilities.send_message(self, msg.chat.id, output, false, nil, 'html')
end

View File

@ -6,138 +6,138 @@ pun.command = 'pun'
pun.doc = 'Returns a pun.'
function pun:init(config)
pun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pun').table
pun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pun').table
end
local puns = {
"The person who invented the door-knock won the No-bell prize.",
"I couldn't work out how to fasten my seatbelt. Then it clicked.",
"Never trust atoms; they make up everything.",
"Singing in the shower is all fun and games until you get shampoo in your mouth - Then it becomes a soap opera.",
"I can't believe I got fired from the calendar factory. All I did was take a day off.",
"To the guy who invented zero: Thanks for nothing!",
"Enough with the cripple jokes! I just can't stand them.",
"I've accidentally swallowed some Scrabble tiles. My next crap could spell disaster.",
"How does Moses make his tea? Hebrews it.",
"Did you hear about the guy who got hit in the head with a can of soda? He was lucky it was a soft drink.",
"When William joined the army he disliked the phrase 'fire at will'.",
"There was a sign on the lawn at a rehab center that said 'Keep off the Grass'.",
"I wondered why the baseball was getting bigger. Then it hit me.",
"I can hear music coming out of my printer. I think the paper's jamming again.",
"I have a few jokes about unemployed people, but none of them work",
"Want to hear a construction joke? I'm working on it",
"I always take a second pair of pants when I go golfing, in case I get a hole in one.",
"I couldn't remember how to throw a boomerang, but then it came back to me.",
"I've decided that my wifi will be my valentine. IDK, we just have this connection.",
"A prisoner's favorite punctuation mark is the period. It marks the end of his sentence.",
"I used to go fishing with Skrillex, but he kept dropping the bass.",
"Two antennae met on a roof and got married. The wedding was okay, but the reception was incredible.",
"A book just fell on my head. I've only got my shelf to blame.",
"I dropped my steak on the floor. Now it's ground beef.",
"I used to have a fear of hurdles, but I got over it.",
"The outcome of war does not prove who is right, but only who is left.",
"Darth Vader tries not to burn his food, but it always comes out a little on the dark side.",
"The store keeps calling me to buy more furniture, but all I wanted was a one night stand.",
"This girl said she recognized me from the vegetarian club, but I'd never met herbivore.",
"Police arrested two kids yesterday, one was drinking battery acid, the other was eating fireworks. They charged one and let the other one off...",
"No more Harry Potter jokes guys. I'm Sirius.",
"It was hard getting over my addiction to hokey pokey, but I've turned myself around.",
"It takes a lot of balls to golf the way I do.",
"Why did everyone want to hang out with the mushroom? Because he was a fungi.",
"How much does a hipster weigh? An instagram.",
"I used to be addicted to soap, but I'm clean now.",
"When life gives you melons, youre probably dyslexic.",
"What's with all the blind jokes? I just don't see the point.",
"If Apple made a car, would it have Windows?",
"Need an ark? I Noah guy.",
"The scarecrow won an award because he was outstanding in his field.",
"What's the difference between a man in a tux on a bicycle, and a man in a sweatsuit on a trycicle? A tire.",
"What do you do with a sick chemist? If you can't helium, and you can't curium, you'll just have to barium.",
"I'm reading a book about anti-gravity. It's impossible to put down.",
"Trying to write with a broken pencil is pointless.",
"When TVs go on vacation, they travel to remote islands.",
"I was going to tell a midget joke, but it's too short.",
"Jokes about German sausage are the wurst.",
"How do you organize a space party? You planet.",
"Sleeping comes so naturally to me, I could do it with my eyes closed.",
"I'm glad I know sign language; it's pretty handy.",
"Atheism is a non-prophet organization.",
"Velcro: What a rip-off!",
"If they made a Minecraft movie, it would be a blockbuster.",
"I don't trust people with graph paper. They're always plotting something",
"I had a friend who was addicted to brake fluid. He says he can stop anytime.",
"The form said I had Type A blood, but it was a Type O.",
"I went to to the shop to buy eight Sprites - I came home and realised I'd picked 7Up.",
"There was an explosion at a pie factory. 3.14 people died.",
"A man drove his car into a tree and found out how a Mercedes bends.",
"The experienced carpenter really nailed it, but the new guy screwed everything up.",
"I didn't like my beard at first, but then it grew on me.",
"Smaller babies may be delivered by stork, but the heavier ones need a crane.",
"What's the definition of a will? It's a dead giveaway.",
"I was going to look for my missing watch, but I could never find the time.",
"I hate elevators, and I often take steps to avoid them.",
"Did you hear about the guy whose whole left side was cut off? He's all right now.",
"It's not that the man did not know how to juggle, he just didn't have the balls to do it.",
"I used to be a loan shark, but I lost interest",
"I don't trust these stairs; they're always up to something.",
"My friend's bakery burned down last night. Now his business is toast.",
"Don't trust people that do acupuncture; they're back stabbers.",
"The man who survived mustard gas and pepper spray is now a seasoned veteran.",
"Police were called to a daycare where a three-year-old was resisting a rest.",
"When Peter Pan punches, they Neverland",
"The shoemaker did not deny his apprentice anything he needed. He gave him his awl.",
"I did a theatrical performance about puns. It was a play on words.",
"Show me a piano falling down a mineshaft and I'll show you A-flat minor.",
"Have you ever tried to eat a clock? It's very time consuming.",
"There was once a cross-eyed teacher who couldn't control his pupils.",
"A new type of broom came out and it is sweeping the nation.",
"I relish the fact that you've mustard the strength to ketchup to me.",
"I knew a woman who owned a taser. Man, was she stunning!",
"What did the grape say when it got stepped on? Nothing - but it let out a little whine.",
"It was an emotional wedding. Even the cake was in tiers.",
"When a clock is hungry it goes back four seconds.",
"The dead batteries were given out free of charge.",
"Why are there no knock-knock jokes about America? Because freedom rings.",
"When the cannibal showed up late to dinner, they gave him the cold shoulder.",
"I should have been sad when my flashlight died, but I was delighted.",
"Why don't tennis players ever get married? Love means nothing to them.",
"Pterodactyls can't be heard going to the bathroom because the P is silent.",
"Mermaids make calls on their shell phones.",
"What do you call an aardvark with three feet? A yaardvark.",
"Captain Kirk has three ears: A right ear, a left ear, and a final front ear.",
"How do celebrities stay cool? They have a lot of fans.",
"Without geometry, life is pointless.",
"Did you hear about the cow who tried to jump over a barbed-wire fence? It ended in udder destruction.",
"The truth may ring like a bell, but it is seldom ever tolled.",
"I used to work for the IRS, but my job was too taxing.",
"I used to be a programmer, but then I lost my drive.",
"Pediatricians are doctors with little patients.",
"I finally fired my masseuse today. She always rubbed me the wrong way.",
"I stayed up all night wondering where the sun went. Then it dawned on me.",
"What's the difference between a man and his dog? The man wears a suit; the dog just pants.",
"A psychic midget who escapes from prison is a small medium at large.",
"I've been to the dentist several times, so I know the drill.",
"The roundest knight at King Arthur's round table was Sir Cumference. He acquired his size from too much pi.",
"She was only a whiskey maker, but he loved her still.",
"Male deer have buck teeth.",
"Whiteboards are remarkable.",
"Visitors in Cuba are always Havana good time.",
"Why does electricity shock people? It doesn't know how to conduct itself.",
"Lancelot had a scary dream about his horse. It was a knight mare.",
"A tribe of cannibals captured a missionary and ate him. Afterward, they all had violent food poisoning. This just goes to show that you can't keep a good man down.",
"Heaven for gamblers is a paradise.",
"Old wheels aren't thrown away, they're just retired.",
"Horses are very stable animals.",
"Banks don't crash, they just lose their balance.",
"The career of a skier can go downhill very fast.",
"In democracy, it's your vote that counts. In feudalism, it's your count that votes.",
"A sea lion is nothing but an ionized seal.",
"The vegetables from my garden aren't that great. I guess you could say they're mediokra."
"The person who invented the door-knock won the No-bell prize.",
"I couldn't work out how to fasten my seatbelt. Then it clicked.",
"Never trust atoms; they make up everything.",
"Singing in the shower is all fun and games until you get shampoo in your mouth - Then it becomes a soap opera.",
"I can't believe I got fired from the calendar factory. All I did was take a day off.",
"To the guy who invented zero: Thanks for nothing!",
"Enough with the cripple jokes! I just can't stand them.",
"I've accidentally swallowed some Scrabble tiles. My next crap could spell disaster.",
"How does Moses make his tea? Hebrews it.",
"Did you hear about the guy who got hit in the head with a can of soda? He was lucky it was a soft drink.",
"When William joined the army he disliked the phrase 'fire at will'.",
"There was a sign on the lawn at a rehab center that said 'Keep off the Grass'.",
"I wondered why the baseball was getting bigger. Then it hit me.",
"I can hear music coming out of my printer. I think the paper's jamming again.",
"I have a few jokes about unemployed people, but none of them work",
"Want to hear a construction joke? I'm working on it",
"I always take a second pair of pants when I go golfing, in case I get a hole in one.",
"I couldn't remember how to throw a boomerang, but then it came back to me.",
"I've decided that my wifi will be my valentine. IDK, we just have this connection.",
"A prisoner's favorite punctuation mark is the period. It marks the end of his sentence.",
"I used to go fishing with Skrillex, but he kept dropping the bass.",
"Two antennae met on a roof and got married. The wedding was okay, but the reception was incredible.",
"A book just fell on my head. I've only got my shelf to blame.",
"I dropped my steak on the floor. Now it's ground beef.",
"I used to have a fear of hurdles, but I got over it.",
"The outcome of war does not prove who is right, but only who is left.",
"Darth Vader tries not to burn his food, but it always comes out a little on the dark side.",
"The store keeps calling me to buy more furniture, but all I wanted was a one night stand.",
"This girl said she recognized me from the vegetarian club, but I'd never met herbivore.",
"Police arrested two kids yesterday, one was drinking battery acid, the other was eating fireworks. They charged one and let the other one off...",
"No more Harry Potter jokes guys. I'm Sirius.",
"It was hard getting over my addiction to hokey pokey, but I've turned myself around.",
"It takes a lot of balls to golf the way I do.",
"Why did everyone want to hang out with the mushroom? Because he was a fungi.",
"How much does a hipster weigh? An instagram.",
"I used to be addicted to soap, but I'm clean now.",
"When life gives you melons, youre probably dyslexic.",
"What's with all the blind jokes? I just don't see the point.",
"If Apple made a car, would it have Windows?",
"Need an ark? I Noah guy.",
"The scarecrow won an award because he was outstanding in his field.",
"What's the difference between a man in a tux on a bicycle, and a man in a sweatsuit on a trycicle? A tire.",
"What do you do with a sick chemist? If you can't helium, and you can't curium, you'll just have to barium.",
"I'm reading a book about anti-gravity. It's impossible to put down.",
"Trying to write with a broken pencil is pointless.",
"When TVs go on vacation, they travel to remote islands.",
"I was going to tell a midget joke, but it's too short.",
"Jokes about German sausage are the wurst.",
"How do you organize a space party? You planet.",
"Sleeping comes so naturally to me, I could do it with my eyes closed.",
"I'm glad I know sign language; it's pretty handy.",
"Atheism is a non-prophet organization.",
"Velcro: What a rip-off!",
"If they made a Minecraft movie, it would be a blockbuster.",
"I don't trust people with graph paper. They're always plotting something",
"I had a friend who was addicted to brake fluid. He says he can stop anytime.",
"The form said I had Type A blood, but it was a Type O.",
"I went to to the shop to buy eight Sprites - I came home and realised I'd picked 7Up.",
"There was an explosion at a pie factory. 3.14 people died.",
"A man drove his car into a tree and found out how a Mercedes bends.",
"The experienced carpenter really nailed it, but the new guy screwed everything up.",
"I didn't like my beard at first, but then it grew on me.",
"Smaller babies may be delivered by stork, but the heavier ones need a crane.",
"What's the definition of a will? It's a dead giveaway.",
"I was going to look for my missing watch, but I could never find the time.",
"I hate elevators, and I often take steps to avoid them.",
"Did you hear about the guy whose whole left side was cut off? He's all right now.",
"It's not that the man did not know how to juggle, he just didn't have the balls to do it.",
"I used to be a loan shark, but I lost interest",
"I don't trust these stairs; they're always up to something.",
"My friend's bakery burned down last night. Now his business is toast.",
"Don't trust people that do acupuncture; they're back stabbers.",
"The man who survived mustard gas and pepper spray is now a seasoned veteran.",
"Police were called to a daycare where a three-year-old was resisting a rest.",
"When Peter Pan punches, they Neverland",
"The shoemaker did not deny his apprentice anything he needed. He gave him his awl.",
"I did a theatrical performance about puns. It was a play on words.",
"Show me a piano falling down a mineshaft and I'll show you A-flat minor.",
"Have you ever tried to eat a clock? It's very time consuming.",
"There was once a cross-eyed teacher who couldn't control his pupils.",
"A new type of broom came out and it is sweeping the nation.",
"I relish the fact that you've mustard the strength to ketchup to me.",
"I knew a woman who owned a taser. Man, was she stunning!",
"What did the grape say when it got stepped on? Nothing - but it let out a little whine.",
"It was an emotional wedding. Even the cake was in tiers.",
"When a clock is hungry it goes back four seconds.",
"The dead batteries were given out free of charge.",
"Why are there no knock-knock jokes about America? Because freedom rings.",
"When the cannibal showed up late to dinner, they gave him the cold shoulder.",
"I should have been sad when my flashlight died, but I was delighted.",
"Why don't tennis players ever get married? Love means nothing to them.",
"Pterodactyls can't be heard going to the bathroom because the P is silent.",
"Mermaids make calls on their shell phones.",
"What do you call an aardvark with three feet? A yaardvark.",
"Captain Kirk has three ears: A right ear, a left ear, and a final front ear.",
"How do celebrities stay cool? They have a lot of fans.",
"Without geometry, life is pointless.",
"Did you hear about the cow who tried to jump over a barbed-wire fence? It ended in udder destruction.",
"The truth may ring like a bell, but it is seldom ever tolled.",
"I used to work for the IRS, but my job was too taxing.",
"I used to be a programmer, but then I lost my drive.",
"Pediatricians are doctors with little patients.",
"I finally fired my masseuse today. She always rubbed me the wrong way.",
"I stayed up all night wondering where the sun went. Then it dawned on me.",
"What's the difference between a man and his dog? The man wears a suit; the dog just pants.",
"A psychic midget who escapes from prison is a small medium at large.",
"I've been to the dentist several times, so I know the drill.",
"The roundest knight at King Arthur's round table was Sir Cumference. He acquired his size from too much pi.",
"She was only a whiskey maker, but he loved her still.",
"Male deer have buck teeth.",
"Whiteboards are remarkable.",
"Visitors in Cuba are always Havana good time.",
"Why does electricity shock people? It doesn't know how to conduct itself.",
"Lancelot had a scary dream about his horse. It was a knight mare.",
"A tribe of cannibals captured a missionary and ate him. Afterward, they all had violent food poisoning. This just goes to show that you can't keep a good man down.",
"Heaven for gamblers is a paradise.",
"Old wheels aren't thrown away, they're just retired.",
"Horses are very stable animals.",
"Banks don't crash, they just lose their balance.",
"The career of a skier can go downhill very fast.",
"In democracy, it's your vote that counts. In feudalism, it's your count that votes.",
"A sea lion is nothing but an ionized seal.",
"The vegetables from my garden aren't that great. I guess you could say they're mediokra."
}
function pun:action(msg)
utilities.send_reply(self, msg, puns[math.random(#puns)])
utilities.send_reply(self, msg, puns[math.random(#puns)])
end

View File

@ -16,34 +16,34 @@ reactions.doc = 'Returns a list of "reaction" emoticon commands.'
local help
function reactions:init(config)
-- Generate a "help" message triggered by "/reactions".
help = 'Reactions:\n'
reactions.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('reactions').table
local username = self.info.username:lower()
for trigger,reaction in pairs(config.reactions) do
help = help .. '' .. config.cmd_pat .. trigger .. ': ' .. reaction .. '\n'
table.insert(reactions.triggers, '^'..config.cmd_pat..trigger)
table.insert(reactions.triggers, '^'..config.cmd_pat..trigger..'@'..username)
table.insert(reactions.triggers, config.cmd_pat..trigger..'$')
table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'$')
table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger)
table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger..'@'..username)
table.insert(reactions.triggers, config.cmd_pat..trigger..'\n')
table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'\n')
end
-- Generate a "help" message triggered by "/reactions".
help = 'Reactions:\n'
reactions.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('reactions').table
local username = self.info.username:lower()
for trigger,reaction in pairs(config.reactions) do
help = help .. '' .. config.cmd_pat .. trigger .. ': ' .. reaction .. '\n'
table.insert(reactions.triggers, '^'..config.cmd_pat..trigger)
table.insert(reactions.triggers, '^'..config.cmd_pat..trigger..'@'..username)
table.insert(reactions.triggers, config.cmd_pat..trigger..'$')
table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'$')
table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger)
table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger..'@'..username)
table.insert(reactions.triggers, config.cmd_pat..trigger..'\n')
table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'\n')
end
end
function reactions:action(msg, config)
if string.match(msg.text_lower, config.cmd_pat..'reactions') then
utilities.send_message(self, msg.chat.id, help)
return
end
for trigger,reaction in pairs(config.reactions) do
if string.match(msg.text_lower, config.cmd_pat..trigger) then
utilities.send_message(self, msg.chat.id, reaction)
return
end
end
if string.match(msg.text_lower, config.cmd_pat..'reactions') then
utilities.send_message(self, msg.chat.id, help)
return
end
for trigger,reaction in pairs(config.reactions) do
if string.match(msg.text_lower, config.cmd_pat..trigger) then
utilities.send_message(self, msg.chat.id, reaction)
return
end
end
end
return reactions

View File

@ -8,29 +8,29 @@ local utilities = require('otouto.utilities')
reddit.command = 'reddit [r/subreddit | query]'
function reddit:init(config)
reddit.triggers = utilities.triggers(self.info.username, config.cmd_pat, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table
reddit.doc = config.cmd_pat .. [[reddit [r/subreddit | query]
reddit.triggers = utilities.triggers(self.info.username, config.cmd_pat, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table
reddit.doc = config.cmd_pat .. [[reddit [r/subreddit | query]
Returns the top posts or results for a given subreddit or query. If no argument is given, returns the top posts from r/all. Querying specific subreddits is not supported.
Aliases: ]] .. config.cmd_pat .. 'r, /r/subreddit'
end
local format_results = function(posts)
local output = ''
for _,v in ipairs(posts) do
local post = v.data
local title = post.title:gsub('%[', '('):gsub('%]', ')'):gsub('&amp;', '&')
if title:len() > 256 then
title = title:sub(1, 253)
title = utilities.trim(title) .. '...'
end
local short_url = 'redd.it/' .. post.id
local s = '[' .. title .. '](' .. short_url .. ')'
if post.domain and not post.is_self and not post.over_18 then
s = '`[`[' .. post.domain .. '](' .. post.url:gsub('%)', '\\)') .. ')`]` ' .. s
end
output = output .. '' .. s .. '\n'
end
return output
local output = ''
for _,v in ipairs(posts) do
local post = v.data
local title = post.title:gsub('%[', '('):gsub('%]', ')'):gsub('&amp;', '&')
if title:len() > 256 then
title = title:sub(1, 253)
title = utilities.trim(title) .. '...'
end
local short_url = 'redd.it/' .. post.id
local s = '[' .. title .. '](' .. short_url .. ')'
if post.domain and not post.is_self and not post.over_18 then
s = '`[`[' .. post.domain .. '](' .. post.url:gsub('%)', '\\)') .. ')`]` ' .. s
end
output = output .. '' .. s .. '\n'
end
return output
end
reddit.subreddit_url = 'http://www.reddit.com/%s/.json?limit='
@ -38,46 +38,46 @@ reddit.search_url = 'http://www.reddit.com/search.json?q=%s&limit='
reddit.rall_url = 'http://www.reddit.com/.json?limit='
function reddit:action(msg, config)
-- Eight results in PM, four results elsewhere.
local limit = 4
if msg.chat.type == 'private' then
limit = 8
end
local text = msg.text_lower
if text:match('^/r/.') then
-- Normalize input so this hack works easily.
text = msg.text_lower:gsub('^/r/', config.cmd_pat..'r r/')
end
local input = utilities.input(text)
local source, url
if input then
if input:match('^r/.') then
input = utilities.get_word(input, 1)
url = reddit.subreddit_url:format(input) .. limit
source = '*/' .. utilities.md_escape(input) .. '*\n'
else
input = utilities.input(msg.text)
source = '*Results for* _' .. utilities.md_escape(input) .. '_ *:*\n'
input = URL.escape(input)
url = reddit.search_url:format(input) .. limit
end
else
url = reddit.rall_url .. limit
source = '*/r/all*\n'
end
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
else
local jdat = JSON.decode(jstr)
if #jdat.data.children == 0 then
utilities.send_reply(self, msg, config.errors.results)
else
local output = format_results(jdat.data.children)
output = source .. output
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
end
-- Eight results in PM, four results elsewhere.
local limit = 4
if msg.chat.type == 'private' then
limit = 8
end
local text = msg.text_lower
if text:match('^/r/.') then
-- Normalize input so this hack works easily.
text = msg.text_lower:gsub('^/r/', config.cmd_pat..'r r/')
end
local input = utilities.input(text)
local source, url
if input then
if input:match('^r/.') then
input = utilities.get_word(input, 1)
url = reddit.subreddit_url:format(input) .. limit
source = '*/' .. utilities.md_escape(input) .. '*\n'
else
input = utilities.input(msg.text)
source = '*Results for* _' .. utilities.md_escape(input) .. '_ *:*\n'
input = URL.escape(input)
url = reddit.search_url:format(input) .. limit
end
else
url = reddit.rall_url .. limit
source = '*/r/all*\n'
end
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
else
local jdat = JSON.decode(jstr)
if #jdat.data.children == 0 then
utilities.send_reply(self, msg, config.errors.results)
else
local output = format_results(jdat.data.children)
output = source .. output
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
end
end
return reddit

View File

@ -5,87 +5,87 @@ local utilities = require('otouto.utilities')
remind.command = 'remind <duration> <message>'
function remind:init(config)
self.database.reminders = self.database.reminders or {}
self.database.reminders = self.database.reminders or {}
remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table
remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table
config.remind = config.remind or {}
setmetatable(config.remind, { __index = function() return 1000 end })
config.remind = config.remind or {}
setmetatable(config.remind, { __index = function() return 1000 end })
remind.doc = config.cmd_pat .. [[remind <duration> <message>
remind.doc = config.cmd_pat .. [[remind <duration> <message>
Repeats a message after a duration of time, in minutes.
The maximum length of a reminder is %s characters. The maximum duration of a timer is %s minutes. The maximum number of reminders for a group is %s. The maximum number of reminders in private is %s.]]
remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private)
remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private)
end
function remind:action(msg, config)
local input = utilities.input(msg.text)
if not input then
utilities.send_reply(self, msg, remind.doc, true)
return
end
local input = utilities.input(msg.text)
if not input then
utilities.send_reply(self, msg, remind.doc, true)
return
end
local duration = tonumber(utilities.get_word(input, 1))
if not duration then
utilities.send_reply(self, msg, remind.doc, true)
return
end
local duration = tonumber(utilities.get_word(input, 1))
if not duration then
utilities.send_reply(self, msg, remind.doc, true)
return
end
if duration < 1 then
duration = 1
elseif duration > config.remind.max_duration then
duration = config.remind.max_duration
end
local message = utilities.input(input)
if not message then
utilities.send_reply(self, msg, remind.doc, true)
return
end
if duration < 1 then
duration = 1
elseif duration > config.remind.max_duration then
duration = config.remind.max_duration
end
local message = utilities.input(input)
if not message then
utilities.send_reply(self, msg, remind.doc, true)
return
end
if #message > config.remind.max_length then
utilities.send_reply(self, msg, 'The maximum length of reminders is ' .. config.remind.max_length .. '.')
return
end
if #message > config.remind.max_length then
utilities.send_reply(self, msg, 'The maximum length of reminders is ' .. config.remind.max_length .. '.')
return
end
local chat_id_str = tostring(msg.chat.id)
local output
self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {}
if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then
output = 'Sorry, you already have the maximum number of reminders.'
elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then
output = 'Sorry, this group already has the maximum number of reminders.'
else
table.insert(self.database.reminders[chat_id_str], {
time = os.time() + (duration * 60),
message = message
})
output = string.format(
'I will remind you in %s minute%s!',
duration,
duration == 1 and '' or 's'
)
end
utilities.send_reply(self, msg, output, true)
local chat_id_str = tostring(msg.chat.id)
local output
self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {}
if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then
output = 'Sorry, you already have the maximum number of reminders.'
elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then
output = 'Sorry, this group already has the maximum number of reminders.'
else
table.insert(self.database.reminders[chat_id_str], {
time = os.time() + (duration * 60),
message = message
})
output = string.format(
'I will remind you in %s minute%s!',
duration,
duration == 1 and '' or 's'
)
end
utilities.send_reply(self, msg, output, true)
end
function remind:cron(config)
local time = os.time()
-- Iterate over the group entries in the reminders database.
for chat_id, group in pairs(self.database.reminders) do
-- Iterate over each reminder.
for k, reminder in pairs(group) do
-- If the reminder is past-due, send it and nullify it.
-- Otherwise, add it to the replacement table.
if time > reminder.time then
local output = utilities.style.enquote('Reminder', reminder.message)
local res = utilities.send_message(self, chat_id, output, true, nil, true)
-- If the message fails to send, save it for later (if enabled in config).
if res or not config.remind.persist then
group[k] = nil
end
end
end
end
local time = os.time()
-- Iterate over the group entries in the reminders database.
for chat_id, group in pairs(self.database.reminders) do
-- Iterate over each reminder.
for k, reminder in pairs(group) do
-- If the reminder is past-due, send it and nullify it.
-- Otherwise, add it to the replacement table.
if time > reminder.time then
local output = utilities.style.enquote('Reminder', reminder.message)
local res = utilities.send_message(self, chat_id, output, true, nil, true)
-- If the message fails to send, save it for later (if enabled in config).
if res or not config.remind.persist then
group[k] = nil
end
end
end
end
end
return remind

View File

@ -5,30 +5,30 @@ local bindings = require('otouto.bindings')
local rms = {}
function rms:init(config)
rms.BASE_URL = 'https://rms.sexy/img/'
rms.LIST = {}
local s, r = https.request(rms.BASE_URL)
if r ~= 200 then
print('Error connecting to rms.sexy.\nrmspic.lua will not be enabled.')
return
end
for link in s:gmatch('<a href=".-%.%a%a%a">(.-)</a>') do
table.insert(rms.LIST, link)
end
rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table
rms.BASE_URL = 'https://rms.sexy/img/'
rms.LIST = {}
local s, r = https.request(rms.BASE_URL)
if r ~= 200 then
print('Error connecting to rms.sexy.\nrmspic.lua will not be enabled.')
return
end
for link in s:gmatch('<a href=".-%.%a%a%a">(.-)</a>') do
table.insert(rms.LIST, link)
end
rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table
end
function rms:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' })
local choice = rms.LIST[math.random(#rms.LIST)]
local filename = '/tmp/' .. choice
local image_file = io.open(filename)
if image_file then
image_file:close()
else
utilities.download_file(rms.BASE_URL .. choice, filename)
end
bindings.sendPhoto(self, { chat_id = msg.chat.id }, { photo = filename })
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' })
local choice = rms.LIST[math.random(#rms.LIST)]
local filename = '/tmp/' .. choice
local image_file = io.open(filename)
if image_file then
image_file:close()
else
utilities.download_file(rms.BASE_URL .. choice, filename)
end
bindings.sendPhoto(self, { chat_id = msg.chat.id }, { photo = filename })
end
return rms

View File

@ -3,9 +3,9 @@ local setandget = {}
local utilities = require('otouto.utilities')
function setandget:init(config)
self.database.setandget = self.database.setandget or {}
setandget.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('set', true):t('get', true).table
setandget.doc = config.cmd_pat .. [[set <name> <value>
self.database.setandget = self.database.setandget or {}
setandget.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('set', true):t('get', true).table
setandget.doc = config.cmd_pat .. [[set <name> <value>
Stores a value with the given name. Use "]] .. config.cmd_pat .. [[set <name> --" to delete the stored value.
]] .. config.cmd_pat .. [[get [name]
Returns the stored value or a list of stored values.]]
@ -15,56 +15,56 @@ setandget.command = 'set <name> <value>'
function setandget:action(msg, config)
local chat_id_str = tostring(msg.chat.id)
local input = utilities.input(msg.text)
self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {}
local chat_id_str = tostring(msg.chat.id)
local input = utilities.input(msg.text)
self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {}
if msg.text_lower:match('^'..config.cmd_pat..'set') then
if msg.text_lower:match('^'..config.cmd_pat..'set') then
if not input then
utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true)
return
end
if not input then
utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true)
return
end
local name = utilities.get_word(input:lower(), 1)
local value = utilities.input(input)
local name = utilities.get_word(input:lower(), 1)
local value = utilities.input(input)
if not name or not value then
utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true)
elseif value == '--' or value == '' then
self.database.setandget[chat_id_str][name] = nil
utilities.send_message(self, msg.chat.id, 'That value has been deleted.')
else
self.database.setandget[chat_id_str][name] = value
utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true)
end
if not name or not value then
utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true)
elseif value == '--' or value == '' then
self.database.setandget[chat_id_str][name] = nil
utilities.send_message(self, msg.chat.id, 'That value has been deleted.')
else
self.database.setandget[chat_id_str][name] = value
utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true)
end
elseif msg.text_lower:match('^'..config.cmd_pat..'get') then
elseif msg.text_lower:match('^'..config.cmd_pat..'get') then
if not input then
local output
if utilities.table_size(self.database.setandget[chat_id_str]) == 0 then
output = 'No values have been stored here.'
else
output = '*List of stored values:*\n'
for k,v in pairs(self.database.setandget[chat_id_str]) do
output = output .. '' .. k .. ': `' .. v .. '`\n'
end
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
if not input then
local output
if utilities.table_size(self.database.setandget[chat_id_str]) == 0 then
output = 'No values have been stored here.'
else
output = '*List of stored values:*\n'
for k,v in pairs(self.database.setandget[chat_id_str]) do
output = output .. '' .. k .. ': `' .. v .. '`\n'
end
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
local output
if self.database.setandget[chat_id_str][input:lower()] then
output = '`' .. self.database.setandget[chat_id_str][input:lower()] .. '`'
else
output = 'There is no value stored by that name.'
end
local output
if self.database.setandget[chat_id_str][input:lower()] then
output = '`' .. self.database.setandget[chat_id_str][input:lower()] .. '`'
else
output = 'There is no value stored by that name.'
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
end
end

View File

@ -3,32 +3,32 @@ local shell = {}
local utilities = require('otouto.utilities')
function shell:init(config)
shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('run', true).table
shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('run', true).table
end
function shell:action(msg, config)
if msg.from.id ~= config.admin then
return
end
if msg.from.id ~= config.admin then
return
end
local input = utilities.input(msg.text)
input = input:gsub('', '--')
local input = utilities.input(msg.text)
input = input:gsub('', '--')
if not input then
utilities.send_reply(self, msg, 'Please specify a command to run.')
return
end
if not input then
utilities.send_reply(self, msg, 'Please specify a command to run.')
return
end
local f = io.popen(input)
local output = f:read('*all')
f:close()
if output:len() == 0 then
output = 'Done!'
else
output = '```\n' .. output .. '\n```'
end
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
local f = io.popen(input)
local output = f:read('*all')
f:close()
if output:len() == 0 then
output = 'Done!'
else
output = '```\n' .. output .. '\n```'
end
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
end

View File

@ -6,45 +6,45 @@ 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
shout.doc = config.cmd_pat .. 'shout <text> \nShouts something. Input may be the replied-to message.'
shout.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('shout', true).table
shout.doc = config.cmd_pat .. 'shout <text> \nShouts something. Input may be the replied-to message.'
end
function shout:action(msg)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, shout.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, shout.doc, true)
return
end
input = utilities.trim(input)
input = input:upper()
input = utilities.trim(input)
input = input:upper()
local output = ''
local inc = 0
local ilen = 0
for match in input:gmatch(utf8) do
if ilen < 20 then
ilen = ilen + 1
output = output .. match .. ' '
end
end
ilen = 0
output = output .. '\n'
for match in input:sub(2):gmatch(utf8) do
if ilen < 19 then
local spacing = ''
for _ = 1, inc do
spacing = spacing .. ' '
end
inc = inc + 1
ilen = ilen + 1
output = output .. match .. ' ' .. spacing .. match .. '\n'
end
end
output = '```\n' .. utilities.trim(output) .. '\n```'
utilities.send_message(self, msg.chat.id, output, true, false, true)
local output = ''
local inc = 0
local ilen = 0
for match in input:gmatch(utf8) do
if ilen < 20 then
ilen = ilen + 1
output = output .. match .. ' '
end
end
ilen = 0
output = output .. '\n'
for match in input:sub(2):gmatch(utf8) do
if ilen < 19 then
local spacing = ''
for _ = 1, inc do
spacing = spacing .. ' '
end
inc = inc + 1
ilen = ilen + 1
output = output .. match .. ' ' .. spacing .. match .. '\n'
end
end
output = '```\n' .. utilities.trim(output) .. '\n```'
utilities.send_message(self, msg.chat.id, output, true, false, true)
end

View File

@ -5,160 +5,160 @@ local utilities = require('otouto.utilities')
slap.command = 'slap [target]'
function slap:init(config)
slap.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('slap', true).table
slap.doc = config.cmd_pat .. 'slap [target] \nSlap somebody.'
slap.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('slap', true).table
slap.doc = config.cmd_pat .. 'slap [target] \nSlap somebody.'
end
local slaps = {
'VICTIM was shot by VICTOR.',
'VICTIM was pricked to death.',
'VICTIM walked into a cactus while trying to escape VICTOR.',
'VICTIM drowned.',
'VICTIM drowned whilst trying to escape VICTOR.',
'VICTIM blew up.',
'VICTIM was blown up by VICTOR.',
'VICTIM hit the ground too hard.',
'VICTIM fell from a high place.',
'VICTIM fell off a ladder.',
'VICTIM fell into a patch of cacti.',
'VICTIM was doomed to fall by VICTOR.',
'VICTIM was blown from a high place by VICTOR.',
'VICTIM was squashed by a falling anvil.',
'VICTIM went up in flames.',
'VICTIM burned to death.',
'VICTIM was burnt to a crisp whilst fighting VICTOR.',
'VICTIM walked into a fire whilst fighting VICTOR.',
'VICTIM tried to swim in lava.',
'VICTIM tried to swim in lava while trying to escape VICTOR.',
'VICTIM was struck by lightning.',
'VICTIM was slain by VICTOR.',
'VICTIM got finished off by VICTOR.',
'VICTIM was killed by magic.',
'VICTIM was killed by VICTOR using magic.',
'VICTIM starved to death.',
'VICTIM suffocated in a wall.',
'VICTIM fell out of the world.',
'VICTIM was knocked into the void by VICTOR.',
'VICTIM withered away.',
'VICTIM was pummeled by VICTOR.',
'VICTIM was fragged by VICTOR.',
'VICTIM was desynchronized.',
'VICTIM was wasted.',
'VICTIM was busted.',
'VICTIM\'s bones are scraped clean by the desolate wind.',
'VICTIM has died of dysentery.',
'VICTIM fainted.',
'VICTIM is out of usable Pokemon! VICTIM whited out!',
'VICTIM is out of usable Pokemon! VICTIM blacked out!',
'VICTIM whited out!',
'VICTIM blacked out!',
'VICTIM says goodbye to this cruel world.',
'VICTIM got rekt.',
'VICTIM was sawn in half by VICTOR.',
'VICTIM died. I blame VICTOR.',
'VICTIM was axe-murdered by VICTOR.',
'VICTIM\'s melon was split by VICTOR.',
'VICTIM was sliced and diced by VICTOR.',
'VICTIM was split from crotch to sternum by VICTOR.',
'VICTIM\'s death put another notch in VICTOR\'s axe.',
'VICTIM died impossibly!',
'VICTIM died from VICTOR\'s mysterious tropical disease.',
'VICTIM escaped infection by dying.',
'VICTIM played hot-potato with a grenade.',
'VICTIM was knifed by VICTOR.',
'VICTIM fell on his sword.',
'VICTIM ate a grenade.',
'VICTIM practiced being VICTOR\'s clay pigeon.',
'VICTIM is what\'s for dinner!',
'VICTIM was terminated by VICTOR.',
'VICTIM was shot before being thrown out of a plane.',
'VICTIM was not invincible.',
'VICTIM has encountered an error.',
'VICTIM died and reincarnated as a goat.',
'VICTOR threw VICTIM off a building.',
'VICTIM is sleeping with the fishes.',
'VICTIM got a premature burial.',
'VICTOR replaced all of VICTIM\'s music with Nickelback.',
'VICTOR spammed VICTIM\'s email.',
'VICTOR made VICTIM a knuckle sandwich.',
'VICTOR slapped VICTIM with pure nothing.',
'VICTOR hit VICTIM with a small, interstellar spaceship.',
'VICTIM was quickscoped by VICTOR.',
'VICTOR put VICTIM in check-mate.',
'VICTOR RSA-encrypted VICTIM and deleted the private key.',
'VICTOR put VICTIM in the friendzone.',
'VICTOR slaps VICTIM with a DMCA takedown request!',
'VICTIM became a corpse blanket for VICTOR.',
'Death is when the monsters get you. Death comes for VICTIM.',
'Cowards die many times before their death. VICTIM never tasted death but once.',
'VICTIM died of hospital gangrene.',
'VICTIM got a house call from Doctor VICTOR.',
'VICTOR beheaded VICTIM.',
'VICTIM got stoned...by an angry mob.',
'VICTOR sued the pants off VICTIM.',
'VICTIM was impeached.',
'VICTIM was one-hit KO\'d by VICTOR.',
'VICTOR sent VICTIM to /dev/null.',
'VICTOR sent VICTIM down the memory hole.',
'VICTIM was a mistake.',
'"VICTIM was a mistake." - VICTOR',
'VICTOR checkmated VICTIM in two moves.'
'VICTIM was shot by VICTOR.',
'VICTIM was pricked to death.',
'VICTIM walked into a cactus while trying to escape VICTOR.',
'VICTIM drowned.',
'VICTIM drowned whilst trying to escape VICTOR.',
'VICTIM blew up.',
'VICTIM was blown up by VICTOR.',
'VICTIM hit the ground too hard.',
'VICTIM fell from a high place.',
'VICTIM fell off a ladder.',
'VICTIM fell into a patch of cacti.',
'VICTIM was doomed to fall by VICTOR.',
'VICTIM was blown from a high place by VICTOR.',
'VICTIM was squashed by a falling anvil.',
'VICTIM went up in flames.',
'VICTIM burned to death.',
'VICTIM was burnt to a crisp whilst fighting VICTOR.',
'VICTIM walked into a fire whilst fighting VICTOR.',
'VICTIM tried to swim in lava.',
'VICTIM tried to swim in lava while trying to escape VICTOR.',
'VICTIM was struck by lightning.',
'VICTIM was slain by VICTOR.',
'VICTIM got finished off by VICTOR.',
'VICTIM was killed by magic.',
'VICTIM was killed by VICTOR using magic.',
'VICTIM starved to death.',
'VICTIM suffocated in a wall.',
'VICTIM fell out of the world.',
'VICTIM was knocked into the void by VICTOR.',
'VICTIM withered away.',
'VICTIM was pummeled by VICTOR.',
'VICTIM was fragged by VICTOR.',
'VICTIM was desynchronized.',
'VICTIM was wasted.',
'VICTIM was busted.',
'VICTIM\'s bones are scraped clean by the desolate wind.',
'VICTIM has died of dysentery.',
'VICTIM fainted.',
'VICTIM is out of usable Pokemon! VICTIM whited out!',
'VICTIM is out of usable Pokemon! VICTIM blacked out!',
'VICTIM whited out!',
'VICTIM blacked out!',
'VICTIM says goodbye to this cruel world.',
'VICTIM got rekt.',
'VICTIM was sawn in half by VICTOR.',
'VICTIM died. I blame VICTOR.',
'VICTIM was axe-murdered by VICTOR.',
'VICTIM\'s melon was split by VICTOR.',
'VICTIM was sliced and diced by VICTOR.',
'VICTIM was split from crotch to sternum by VICTOR.',
'VICTIM\'s death put another notch in VICTOR\'s axe.',
'VICTIM died impossibly!',
'VICTIM died from VICTOR\'s mysterious tropical disease.',
'VICTIM escaped infection by dying.',
'VICTIM played hot-potato with a grenade.',
'VICTIM was knifed by VICTOR.',
'VICTIM fell on his sword.',
'VICTIM ate a grenade.',
'VICTIM practiced being VICTOR\'s clay pigeon.',
'VICTIM is what\'s for dinner!',
'VICTIM was terminated by VICTOR.',
'VICTIM was shot before being thrown out of a plane.',
'VICTIM was not invincible.',
'VICTIM has encountered an error.',
'VICTIM died and reincarnated as a goat.',
'VICTOR threw VICTIM off a building.',
'VICTIM is sleeping with the fishes.',
'VICTIM got a premature burial.',
'VICTOR replaced all of VICTIM\'s music with Nickelback.',
'VICTOR spammed VICTIM\'s email.',
'VICTOR made VICTIM a knuckle sandwich.',
'VICTOR slapped VICTIM with pure nothing.',
'VICTOR hit VICTIM with a small, interstellar spaceship.',
'VICTIM was quickscoped by VICTOR.',
'VICTOR put VICTIM in check-mate.',
'VICTOR RSA-encrypted VICTIM and deleted the private key.',
'VICTOR put VICTIM in the friendzone.',
'VICTOR slaps VICTIM with a DMCA takedown request!',
'VICTIM became a corpse blanket for VICTOR.',
'Death is when the monsters get you. Death comes for VICTIM.',
'Cowards die many times before their death. VICTIM never tasted death but once.',
'VICTIM died of hospital gangrene.',
'VICTIM got a house call from Doctor VICTOR.',
'VICTOR beheaded VICTIM.',
'VICTIM got stoned...by an angry mob.',
'VICTOR sued the pants off VICTIM.',
'VICTIM was impeached.',
'VICTIM was one-hit KO\'d by VICTOR.',
'VICTOR sent VICTIM to /dev/null.',
'VICTOR sent VICTIM down the memory hole.',
'VICTIM was a mistake.',
'"VICTIM was a mistake." - VICTOR',
'VICTOR checkmated VICTIM in two moves.'
}
-- optimize later
function slap:action(msg)
local input = utilities.input(msg.text)
local victor_id = msg.from.id
local victim_id
if msg.reply_to_message then
victim_id = msg.reply_to_message.from.id
else
if input then
if tonumber(input) then
victim_id = tonumber(input)
elseif input:match('^@') then
local t = utilities.resolve_username(self, input)
if t then
victim_id = t.id
end
end
end
end
-- IDs
if victim_id then
if victim_id == victor_id then
victor_id = self.info.id
end
else
if not input then
victor_id = self.info.id
victim_id = msg.from.id
end
end
-- Names
local victor_name, victim_name
if input and not victim_id then
victim_name = input
else
local victim_id_str = tostring(victim_id)
if self.database.userdata[victim_id_str] and self.database.userdata[victim_id_str].nickname then
victim_name = self.database.userdata[victim_id_str].nickname
elseif self.database.users[victim_id_str] then
victim_name = utilities.build_name(self.database.users[victim_id_str].first_name, self.database.users[victim_id_str].last_name)
else
victim_name = victim_id_str
end
end
local victor_id_str = tostring(victor_id)
if self.database.userdata[victor_id_str] and self.database.userdata[victor_id_str].nickname then
victor_name = self.database.userdata[victor_id_str].nickname
elseif self.database.users[victor_id_str] then
victor_name = utilities.build_name(self.database.users[victor_id_str].first_name, self.database.users[victor_id_str].last_name)
else
victor_name = self.info.first_name
end
local output = utilities.char.zwnj .. slaps[math.random(#slaps)]:gsub('VICTIM', victim_name):gsub('VICTOR', victor_name)
utilities.send_message(self, msg.chat.id, output)
local input = utilities.input(msg.text)
local victor_id = msg.from.id
local victim_id
if msg.reply_to_message then
victim_id = msg.reply_to_message.from.id
else
if input then
if tonumber(input) then
victim_id = tonumber(input)
elseif input:match('^@') then
local t = utilities.resolve_username(self, input)
if t then
victim_id = t.id
end
end
end
end
-- IDs
if victim_id then
if victim_id == victor_id then
victor_id = self.info.id
end
else
if not input then
victor_id = self.info.id
victim_id = msg.from.id
end
end
-- Names
local victor_name, victim_name
if input and not victim_id then
victim_name = input
else
local victim_id_str = tostring(victim_id)
if self.database.userdata[victim_id_str] and self.database.userdata[victim_id_str].nickname then
victim_name = self.database.userdata[victim_id_str].nickname
elseif self.database.users[victim_id_str] then
victim_name = utilities.build_name(self.database.users[victim_id_str].first_name, self.database.users[victim_id_str].last_name)
else
victim_name = victim_id_str
end
end
local victor_id_str = tostring(victor_id)
if self.database.userdata[victor_id_str] and self.database.userdata[victor_id_str].nickname then
victor_name = self.database.userdata[victor_id_str].nickname
elseif self.database.users[victor_id_str] then
victor_name = utilities.build_name(self.database.users[victor_id_str].first_name, self.database.users[victor_id_str].last_name)
else
victor_name = self.info.first_name
end
local output = utilities.char.zwnj .. slaps[math.random(#slaps)]:gsub('VICTIM', victim_name):gsub('VICTOR', victor_name)
utilities.send_message(self, msg.chat.id, output)
end
return slap

View File

@ -8,71 +8,71 @@ local utilities = require('otouto.utilities')
local starwars = {}
function starwars:init(config)
starwars.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('starwars', true):t('sw', true).table
starwars.doc = config.cmd_pat .. [[starwars <query>
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/'
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
['phantom menace'] = 4,
['attack of the clones'] = 5,
['revenge of the sith'] = 6,
['new hope'] = 1,
['empire strikes back'] = 2,
['return of the jedi'] = 3,
['force awakens'] = 7
}
local corrected_numbers = {
4,
5,
6,
1,
2,
3,
7
4,
5,
6,
1,
2,
3,
7
}
function starwars:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, starwars.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, starwars.doc, true)
return
end
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
local film
if tonumber(input) then
input = tonumber(input)
film = corrected_numbers[input] or input
else
for title, number in pairs(films_by_number) do
if string.match(input, title) then
film = number
break
end
end
end
local film
if tonumber(input) then
input = tonumber(input)
film = corrected_numbers[input] or input
else
for title, number in pairs(films_by_number) do
if string.match(input, title) then
film = number
break
end
end
end
if not film then
utilities.send_reply(self, msg, config.errors.results)
return
end
if not film then
utilities.send_reply(self, msg, config.errors.results)
return
end
local url = starwars.base_url .. film
local jstr, code = HTTP.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local url = starwars.base_url .. film
local jstr, code = HTTP.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local output = '*' .. JSON.decode(jstr).opening_crawl .. '*'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
local output = '*' .. JSON.decode(jstr).opening_crawl .. '*'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return starwars

View File

@ -8,52 +8,52 @@ 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
time.doc = config.cmd_pat .. [[time <location>
time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table
time.doc = config.cmd_pat .. [[time <location>
Returns the time, date, and timezone for the given location.]]
end
function time:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, time.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, time.doc, true)
return
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
local now = os.time()
local utc = os.time(os.date('!*t', now))
local url = time.base_url:format(coords.lat, coords.lon, utc)
local jstr, code = HTTPS.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local now = os.time()
local utc = os.time(os.date('!*t', now))
local url = time.base_url:format(coords.lat, coords.lon, utc)
local jstr, code = HTTPS.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
if data.status == 'ZERO_RESULTS' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local data = JSON.decode(jstr)
if data.status == 'ZERO_RESULTS' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local timestamp = now + data.rawOffset + data.dstOffset
local utcoff = (data.rawOffset + data.dstOffset) / 3600
if utcoff == math.abs(utcoff) then
utcoff = '+' .. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end
local output = string.format('```\n%s\n%s (UTC%s)\n```',
os.date('!%I:%M %p\n%A, %B %d, %Y', timestamp),
data.timeZoneName,
utcoff
)
utilities.send_reply(self, msg, output, true)
local timestamp = now + data.rawOffset + data.dstOffset
local utcoff = (data.rawOffset + data.dstOffset) / 3600
if utcoff == math.abs(utcoff) then
utcoff = '+' .. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end
local output = string.format('```\n%s\n%s (UTC%s)\n```',
os.date('!%I:%M %p\n%A, %B %d, %Y', timestamp),
data.timeZoneName,
utcoff
)
utilities.send_reply(self, msg, output, true)
end
return time

View File

@ -8,38 +8,38 @@ local utilities = require('otouto.utilities')
translate.command = 'translate [text]'
function translate:init(config)
assert(config.yandex_key,
'translate.lua requires a Yandex translate API key from http://tech.yandex.com/keys/get.'
)
assert(config.yandex_key,
'translate.lua requires a Yandex translate API key from http://tech.yandex.com/keys/get.'
)
translate.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('translate', true):t('tl', true).table
translate.doc = config.cmd_pat .. [[translate [text]
translate.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('translate', true):t('tl', true).table
translate.doc = config.cmd_pat .. [[translate [text]
Translates input or the replied-to message into the bot's language.]]
translate.base_url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=%s'
translate.base_url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=%s'
end
function translate:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, translate.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, translate.doc, true)
return
end
local url = translate.base_url:format(URL.escape(input))
local jstr, code = HTTPS.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local url = translate.base_url:format(URL.escape(input))
local jstr, code = HTTPS.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
if data.code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
if data.code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
utilities.send_reply(self, msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true)
utilities.send_reply(self, msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true)
end
return translate

View File

@ -9,42 +9,42 @@ 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 = [[
urbandictionary.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('urbandictionary', true):t('ud', true):t('urban', true).table
urbandictionary.doc = [[
/urbandictionary <query>
Returns a definition from Urban Dictionary.
Aliases: /ud, /urban
]]
urbandictionary.doc = urbandictionary.doc:gsub('/', config.cmd_pat)
]]
urbandictionary.doc = urbandictionary.doc:gsub('/', config.cmd_pat)
end
function urbandictionary:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, urbandictionary.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, urbandictionary.doc, true)
return
end
local url = urbandictionary.base_url .. URL.escape(input)
local jstr, code = HTTP.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local url = urbandictionary.base_url .. URL.escape(input)
local jstr, code = HTTP.request(url)
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
local output
if data.result_type == 'no_results' then
output = config.errors.results
else
output = string.format('*%s*\n\n%s\n\n_%s_',
data.list[1].word:gsub('*', '*\\**'),
utilities.trim(utilities.md_escape(data.list[1].definition)),
utilities.trim((data.list[1].example or '')):gsub('_', '_\\__')
)
end
utilities.send_reply(self, msg, output, true)
local data = JSON.decode(jstr)
local output
if data.result_type == 'no_results' then
output = config.errors.results
else
output = string.format('*%s*\n\n%s\n\n_%s_',
data.list[1].word:gsub('*', '*\\**'),
utilities.trim(utilities.md_escape(data.list[1].definition)),
utilities.trim((data.list[1].example or '')):gsub('_', '_\\__')
)
end
utilities.send_reply(self, msg, output, true)
end
return urbandictionary

View File

@ -6,12 +6,12 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function weather:init(config)
assert(config.owm_api_key,
'weather.lua requires an OpenWeatherMap API key from http://openweathermap.org/API.'
)
assert(config.owm_api_key,
'weather.lua requires an OpenWeatherMap API key from http://openweathermap.org/API.'
)
weather.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('weather', true).table
weather.doc = config.cmd_pat .. [[weather <location>
weather.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('weather', true).table
weather.doc = config.cmd_pat .. [[weather <location>
Returns the current weather conditions for a given location.]]
end
@ -19,37 +19,37 @@ weather.command = 'weather <location>'
function weather:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, weather.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, weather.doc, true)
return
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
local url = 'http://api.openweathermap.org/data/2.5/weather?APPID=' .. config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon
local url = 'http://api.openweathermap.org/data/2.5/weather?APPID=' .. config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.cod ~= 200 then
utilities.send_reply(self, msg, 'Error: City not found.')
return
end
local jdat = JSON.decode(jstr)
if jdat.cod ~= 200 then
utilities.send_reply(self, msg, 'Error: City not found.')
return
end
local celsius = string.format('%.2f', jdat.main.temp - 273.15)
local fahrenheit = string.format('%.2f', celsius * (9/5) + 32)
local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`'
local celsius = string.format('%.2f', jdat.main.temp - 273.15)
local fahrenheit = string.format('%.2f', celsius * (9/5) + 32)
local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`'
utilities.send_reply(self, msg, output, true)
utilities.send_reply(self, msg, output, true)
end

View File

@ -6,54 +6,54 @@ local bindings = require('otouto.bindings')
whoami.command = 'whoami'
function whoami:init(config)
whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who'):t('whoami').table
whoami.doc = [[
whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who'):t('whoami').table
whoami.doc = [[
Returns user and chat info for you or the replied-to message.
Alias: ]] .. config.cmd_pat .. 'who'
end
function whoami:action(msg)
-- Operate on the replied-to message, if it exists.
msg = msg.reply_to_message or msg
-- If it's a private conversation, bot is chat, unless bot is from.
local chat = msg.from.id == msg.chat.id and self.info or msg.chat
-- Names for the user and group, respectively. HTML-escaped.
local from_name = utilities.html_escape(
utilities.build_name(
msg.from.first_name,
msg.from.last_name
)
)
local chat_name = utilities.html_escape(
chat.title
or utilities.build_name(chat.first_name, chat.last_name)
)
-- "Normalize" a group ID so it's not arbitrarily modified by the bot API.
local chat_id = math.abs(chat.id)
if chat_id > 1000000000000 then chat_id = chat_id - 1000000000000 end
-- Do the thing.
local output = string.format(
'You are %s <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
})
-- 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

@ -8,83 +8,83 @@ local utilities = require('otouto.utilities')
wikipedia.command = 'wikipedia <query>'
function wikipedia:init(config)
wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true):t('w', true).table
wikipedia.doc = config.cmd_pat .. [[wikipedia <query>
wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true):t('w', true).table
wikipedia.doc = config.cmd_pat .. [[wikipedia <query>
Returns an article from Wikipedia.
Aliases: ]] .. config.cmd_pat .. 'w, ' .. config.cmd_pat .. 'wiki'
wikipedia.search_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch='
wikipedia.res_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles='
wikipedia.art_url = 'https://' .. config.lang .. '.wikipedia.org/wiki/'
wikipedia.search_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch='
wikipedia.res_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles='
wikipedia.art_url = 'https://' .. config.lang .. '.wikipedia.org/wiki/'
end
function wikipedia:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, wikipedia.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, wikipedia.doc, true)
return
end
local jstr, code = HTTPS.request(wikipedia.search_url .. URL.escape(input))
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jstr, code = HTTPS.request(wikipedia.search_url .. URL.escape(input))
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(jstr)
if data.query.searchinfo.totalhits == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local data = JSON.decode(jstr)
if data.query.searchinfo.totalhits == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local title
for _, v in ipairs(data.query.search) do
if not v.snippet:match('may refer to:') then
title = v.title
break
end
end
if not title then
utilities.send_reply(self, msg, config.errors.results)
return
end
local title
for _, v in ipairs(data.query.search) do
if not v.snippet:match('may refer to:') then
title = v.title
break
end
end
if not title then
utilities.send_reply(self, msg, config.errors.results)
return
end
local res_jstr, res_code = HTTPS.request(wikipedia.res_url .. URL.escape(title))
if res_code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local res_jstr, res_code = HTTPS.request(wikipedia.res_url .. URL.escape(title))
if res_code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local _, text = next(JSON.decode(res_jstr).query.pages)
if not text then
utilities.send_reply(self, msg, config.errors.results)
return
end
local _, text = next(JSON.decode(res_jstr).query.pages)
if not text then
utilities.send_reply(self, msg, config.errors.results)
return
end
text = text.extract
-- Remove crap and take only the first paragraph.
text = text:gsub('</?.->', ''):gsub('%[.+%]', '')
local l = text:find('\n')
if l then
text = text:sub(1, l-1)
end
local url = wikipedia.art_url .. URL.escape(title)
title = utilities.html_escape(title)
-- If the beginning of the article is the title, embolden that.
-- Otherwise, we'll add a title in bold.
local short_title = title:gsub('%(.+%)', '')
local combined_text, count = text:gsub('^'..short_title, '<b>'..short_title..'</b>')
local body
if count == 1 then
body = combined_text
else
body = '<b>' .. title .. '</b>\n' .. text
end
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')
text = text.extract
-- Remove crap and take only the first paragraph.
text = text:gsub('</?.->', ''):gsub('%[.+%]', '')
local l = text:find('\n')
if l then
text = text:sub(1, l-1)
end
local url = wikipedia.art_url .. URL.escape(title)
title = utilities.html_escape(title)
-- If the beginning of the article is the title, embolden that.
-- Otherwise, we'll add a title in bold.
local short_title = title:gsub('%(.+%)', '')
local combined_text, count = text:gsub('^'..short_title, '<b>'..short_title..'</b>')
local body
if count == 1 then
body = combined_text
else
body = '<b>' .. title .. '</b>\n' .. text
end
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

@ -9,44 +9,44 @@ xkcd.base_url = 'https://xkcd.com/info.0.json'
xkcd.strip_url = 'http://xkcd.com/%s/info.0.json'
function xkcd:init(config)
xkcd.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('xkcd', true).table
xkcd.doc = config.cmd_pat .. [[xkcd [i]
xkcd.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('xkcd', true).table
xkcd.doc = config.cmd_pat .. [[xkcd [i]
Returns the latest xkcd strip and its alt text. If a number is given, returns that number strip. If "r" is passed in place of a number, returns a random strip.]]
local jstr = HTTP.request(xkcd.base_url)
if jstr then
local data = JSON.decode(jstr)
if data then
xkcd.latest = data.num
end
end
xkcd.latest = xkcd.latest or 1700
local jstr = HTTP.request(xkcd.base_url)
if jstr then
local data = JSON.decode(jstr)
if data then
xkcd.latest = data.num
end
end
xkcd.latest = xkcd.latest or 1700
end
function xkcd:action(msg, config)
local input = utilities.get_word(msg.text, 2)
if input == 'r' then
input = math.random(xkcd.latest)
elseif tonumber(input) then
input = tonumber(input)
else
input = xkcd.latest
end
local url = xkcd.strip_url:format(input)
local jstr, code = HTTP.request(url)
if code == 404 then
utilities.send_reply(self, msg, config.errors.results)
elseif code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
else
local data = JSON.decode(jstr)
local output = string.format('*%s (*[%s](%s)*)*\n_%s_',
data.safe_title:gsub('*', '*\\**'),
data.num,
data.img,
data.alt:gsub('_', '_\\__')
)
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end
local input = utilities.get_word(msg.text, 2)
if input == 'r' then
input = math.random(xkcd.latest)
elseif tonumber(input) then
input = tonumber(input)
else
input = xkcd.latest
end
local url = xkcd.strip_url:format(input)
local jstr, code = HTTP.request(url)
if code == 404 then
utilities.send_reply(self, msg, config.errors.results)
elseif code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
else
local data = JSON.decode(jstr)
local output = string.format('*%s (*[%s](%s)*)*\n_%s_',
data.safe_title:gsub('*', '*\\**'),
data.num,
data.img,
data.alt:gsub('_', '_\\__')
)
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end
end
return xkcd

View File

@ -8,12 +8,12 @@ local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function youtube:init(config)
assert(config.google_api_key,
'youtube.lua requires a Google API key from http://console.developers.google.com.'
)
assert(config.google_api_key,
'youtube.lua requires a Google API key from http://console.developers.google.com.'
)
youtube.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('youtube', true):t('yt', true).table
youtube.doc = config.cmd_pat .. [[youtube <query>
youtube.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('youtube', true):t('yt', true).table
youtube.doc = config.cmd_pat .. [[youtube <query>
Returns the top result from YouTube.
Alias: ]] .. config.cmd_pat .. 'yt'
end
@ -22,32 +22,32 @@ youtube.command = 'youtube <query>'
function youtube:action(msg, config)
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, youtube.doc, true)
return
end
local input = utilities.input_from_msg(msg)
if not input then
utilities.send_reply(self, msg, youtube.doc, true)
return
end
local url = 'https://www.googleapis.com/youtube/v3/search?key=' .. config.google_api_key .. '&type=video&part=snippet&maxResults=4&q=' .. URL.escape(input)
local url = 'https://www.googleapis.com/youtube/v3/search?key=' .. config.google_api_key .. '&type=video&part=snippet&maxResults=4&q=' .. URL.escape(input)
local jstr, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jstr, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.pageInfo.totalResults == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local jdat = JSON.decode(jstr)
if jdat.pageInfo.totalResults == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local vid_url = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId
local vid_title = jdat.items[1].snippet.title
vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','')
local output = '[' .. vid_title .. '](' .. vid_url .. ')'
local vid_url = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId
local vid_title = jdat.items[1].snippet.title
vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','')
local output = '[' .. vid_title .. '](' .. vid_url .. ')'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end

View File

@ -15,47 +15,47 @@ local bindings = require('otouto.bindings')
-- Edit: To keep things working and allow for HTML messages, you can now pass a
-- string for use_markdown and that will be sent as the parse mode.
function utilities:send_message(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown)
local parse_mode
if type(use_markdown) == 'string' then
parse_mode = use_markdown
elseif use_markdown == true then
parse_mode = 'markdown'
end
return bindings.request(self, 'sendMessage', {
chat_id = chat_id,
text = text,
disable_web_page_preview = disable_web_page_preview,
reply_to_message_id = reply_to_message_id,
parse_mode = parse_mode
} )
local parse_mode
if type(use_markdown) == 'string' then
parse_mode = use_markdown
elseif use_markdown == true then
parse_mode = 'markdown'
end
return bindings.request(self, 'sendMessage', {
chat_id = chat_id,
text = text,
disable_web_page_preview = disable_web_page_preview,
reply_to_message_id = reply_to_message_id,
parse_mode = parse_mode
} )
end
function utilities:send_reply(old_msg, text, use_markdown)
return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown)
return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown)
end
-- get the indexed word in a string
function utilities.get_word(s, i)
s = s or ''
i = i or 1
local n = 0
for w in s:gmatch('%g+') do
n = n + 1
if n == i then return w end
end
return false
s = s or ''
i = i or 1
local n = 0
for w in s:gmatch('%g+') do
n = n + 1
if n == i then return w end
end
return false
end
-- Returns the string after the first space.
function utilities.input(s)
if not s:find(' ') then
return false
end
return s:sub(s:find(' ')+1)
if not s:find(' ') then
return false
end
return s:sub(s:find(' ')+1)
end
function utilities.input_from_msg(msg)
return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false
return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false
end
-- Calculates the length of the given string as UTF-8 characters
@ -72,180 +72,180 @@ end
-- Trims whitespace from a string.
function utilities.trim(str)
local s = str:gsub('^%s*(.-)%s*$', '%1')
return s
local s = str:gsub('^%s*(.-)%s*$', '%1')
return s
end
-- Loads a JSON file as a table.
function utilities.load_data(filename)
local f = io.open(filename)
if f then
local s = f:read('*all')
f:close()
return JSON.decode(s)
else
return {}
end
local f = io.open(filename)
if f then
local s = f:read('*all')
f:close()
return JSON.decode(s)
else
return {}
end
end
-- Saves a table to a JSON file.
function utilities.save_data(filename, data)
local s = JSON.encode(data)
local f = io.open(filename, 'w')
f:write(s)
f:close()
local s = JSON.encode(data)
local f = io.open(filename, 'w')
f:write(s)
f:close()
end
-- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua.
function utilities.get_coords(input, config)
local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input)
local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input)
local jstr, res = HTTP.request(url)
if res ~= 200 then
return config.errors.connection
end
local jstr, res = HTTP.request(url)
if res ~= 200 then
return config.errors.connection
end
local jdat = JSON.decode(jstr)
if jdat.status == 'ZERO_RESULTS' then
return config.errors.results
end
local jdat = JSON.decode(jstr)
if jdat.status == 'ZERO_RESULTS' then
return config.errors.results
end
return {
lat = jdat.results[1].geometry.location.lat,
lon = jdat.results[1].geometry.location.lng
}
return {
lat = jdat.results[1].geometry.location.lat,
lon = jdat.results[1].geometry.location.lng
}
end
-- Get the number of values in a key/value table.
function utilities.table_size(tab)
local i = 0
for _,_ in pairs(tab) do
i = i + 1
end
return i
local i = 0
for _,_ in pairs(tab) do
i = i + 1
end
return i
end
-- Just an easy way to get a user's full name.
-- Alternatively, abuse it to concat two strings like I do.
function utilities.build_name(first, last)
if last then
return first .. ' ' .. last
else
return first
end
if last then
return first .. ' ' .. last
else
return first
end
end
function utilities:resolve_username(input)
input = input:gsub('^@', '')
for _, user in pairs(self.database.users) do
if user.username and user.username:lower() == input:lower() then
local t = {}
for key, val in pairs(user) do
t[key] = val
end
return t
end
end
input = input:gsub('^@', '')
for _, user in pairs(self.database.users) do
if user.username and user.username:lower() == input:lower() then
local t = {}
for key, val in pairs(user) do
t[key] = val
end
return t
end
end
end
function utilities:handle_exception(err, message, config)
local output = string.format(
'\n[%s]\n%s: %s\n%s\n',
os.date('%F %T'),
self.info.username,
err or '',
message
)
if config.log_chat then
output = '```' .. output .. '```'
utilities.send_message(self, config.log_chat, output, true, nil, true)
else
print(output)
end
local output = string.format(
'\n[%s]\n%s: %s\n%s\n',
os.date('%F %T'),
self.info.username,
err or '',
message
)
if config.log_chat then
output = '```' .. output .. '```'
utilities.send_message(self, config.log_chat, output, true, nil, true)
else
print(output)
end
end
function utilities.download_file(url, filename)
if not filename then
filename = url:match('.+/(.-)$') or os.time()
filename = '/tmp/' .. filename
end
local body = {}
local doer = HTTP
local do_redir = true
if url:match('^https') then
doer = HTTPS
do_redir = false
end
local _, res = doer.request{
url = url,
sink = ltn12.sink.table(body),
redirect = do_redir
}
if res ~= 200 then return false end
local file = io.open(filename, 'w+')
file:write(table.concat(body))
file:close()
return filename
if not filename then
filename = url:match('.+/(.-)$') or os.time()
filename = '/tmp/' .. filename
end
local body = {}
local doer = HTTP
local do_redir = true
if url:match('^https') then
doer = HTTPS
do_redir = false
end
local _, res = doer.request{
url = url,
sink = ltn12.sink.table(body),
redirect = do_redir
}
if res ~= 200 then return false end
local file = io.open(filename, 'w+')
file:write(table.concat(body))
file:close()
return filename
end
function utilities.md_escape(text)
return text:gsub('_', '\\_')
:gsub('%[', '\\['):gsub('%]', '\\]')
:gsub('%*', '\\*'):gsub('`', '\\`')
return text:gsub('_', '\\_')
:gsub('%[', '\\['):gsub('%]', '\\]')
:gsub('%*', '\\*'):gsub('`', '\\`')
end
function utilities.html_escape(text)
return text:gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;')
return text:gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;')
end
utilities.triggers_meta = {}
utilities.triggers_meta.__index = utilities.triggers_meta
function utilities.triggers_meta:t(pattern, has_args)
local username = self.username:lower()
table.insert(self.table, '^'..self.cmd_pat..pattern..'$')
table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'$')
if has_args then
table.insert(self.table, '^'..self.cmd_pat..pattern..'%s+[^%s]*')
table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'%s+[^%s]*')
end
return self
local username = self.username:lower()
table.insert(self.table, '^'..self.cmd_pat..pattern..'$')
table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'$')
if has_args then
table.insert(self.table, '^'..self.cmd_pat..pattern..'%s+[^%s]*')
table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'%s+[^%s]*')
end
return self
end
function utilities.triggers(username, cmd_pat, trigger_table)
local self = setmetatable({}, utilities.triggers_meta)
self.username = username
self.cmd_pat = cmd_pat
self.table = trigger_table or {}
return self
local self = setmetatable({}, utilities.triggers_meta)
self.username = username
self.cmd_pat = cmd_pat
self.table = trigger_table or {}
return self
end
function utilities.with_http_timeout(timeout, fun)
local original = HTTP.TIMEOUT
HTTP.TIMEOUT = timeout
fun()
HTTP.TIMEOUT = original
local original = HTTP.TIMEOUT
HTTP.TIMEOUT = timeout
fun()
HTTP.TIMEOUT = original
end
function utilities.pretty_float(x)
if x % 1 == 0 then
return tostring(math.floor(x))
else
return tostring(x)
end
if x % 1 == 0 then
return tostring(math.floor(x))
else
return tostring(x)
end
end
-- This table will store unsavory characters that are not properly displayed,
-- or are just not fun to type.
utilities.char = {
zwnj = '',
arabic = '[\216-\219][\128-\191]',
rtl_override = '',
rtl_mark = '',
em_dash = '',
utf_8 = '[%z\1-\127\194-\244][\128-\191]',
zwnj = '',
arabic = '[\216-\219][\128-\191]',
rtl_override = '',
rtl_mark = '',
em_dash = '',
utf_8 = '[%z\1-\127\194-\244][\128-\191]',
}
utilities.set_meta = {}
@ -283,7 +283,7 @@ end
-- More to be added.
utilities.style = {}
utilities.style.enquote = function(title, body)
return '*' .. title:gsub('*', '\\*') .. ':*\n"' .. utilities.md_escape(body) .. '"'
return '*' .. title:gsub('*', '\\*') .. ':*\n"' .. utilities.md_escape(body) .. '"'
end
return utilities

View File

@ -4,8 +4,8 @@
# config.lua), delete state file after stop, wait five seconds, and restart.
while true; do
tg/bin/telegram-cli -P 4567 -E
[ -f ~/.telegram-cli/state ] && rm ~/.telegram-cli/state
echo 'tg has stopped. ^C to exit.'
sleep 5s
tg/bin/telegram-cli -P 4567 -E
[ -f ~/.telegram-cli/state ] && rm ~/.telegram-cli/state
echo 'tg has stopped. ^C to exit.'
sleep 5s
done