Merge pull request #48 from bb010g/less-magic

[RFC] Less global magic
This commit is contained in:
Drew 2016-05-06 14:40:38 -04:00
commit d00403eb2e
60 changed files with 2326 additions and 2551 deletions

View File

@ -17,7 +17,7 @@ otouto is an independently-developed Telegram API bot written in Lua. Originally
| [Contributors](#contributors) | | [Contributors](#contributors) |
## Setup ## Setup
You _must_ have Lua (5.2+), lua-socket, lua-sec, and lua-cjson installed. To upload files, you must have curl installed. To use fortune.lua, you must have fortune installed. You _must_ have Lua (5.2+), lua-socket, lua-sec, and dkjson installed. It is recommended you install these with LuaRocks. To upload files, you must have curl installed. To use fortune.lua, you must have fortune installed.
Clone the repository and set the following values in `config.lua`: Clone the repository and set the following values in `config.lua`:
@ -26,7 +26,6 @@ Clone the repository and set the following values in `config.lua`:
Optionally: Optionally:
- `time_offset` as the difference, in seconds, of your system clock to UTC.
- `lang` as the two-letter code representing your language. - `lang` as the two-letter code representing your language.
Some plugins are not enabled by default. If you wish to enable them, add them to the `plugins` array. Some plugins are not enabled by default. If you wish to enable them, add them to the `plugins` array.

View File

@ -2,17 +2,13 @@
-- Bindings for the Telegram bot API. -- Bindings for the Telegram bot API.
-- https://core.telegram.org/bots/api -- https://core.telegram.org/bots/api
assert(HTTPS) local bindings = {}
assert(JSON)
assert(URL)
local BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local URL = require('socket.url')
if config.bot_api_key == '' then function bindings.sendRequest(url)
error('You did not set your bot token in config.lua!')
end
sendRequest = function(url)
local dat, res = HTTPS.request(url) local dat, res = HTTPS.request(url)
@ -28,28 +24,28 @@ sendRequest = function(url)
end end
getMe = function() function bindings:getMe()
local url = BASE_URL .. '/getMe' local url = self.BASE_URL .. '/getMe'
return sendRequest(url) return bindings.sendRequest(url)
end end
getUpdates = function(offset) function bindings:getUpdates(offset)
local url = BASE_URL .. '/getUpdates?timeout=20' local url = self.BASE_URL .. '/getUpdates?timeout=20'
if offset then if offset then
url = url .. '&offset=' .. offset url = url .. '&offset=' .. offset
end end
return sendRequest(url) return bindings.sendRequest(url)
end end
sendMessage = function(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown, disable_notification) function bindings:sendMessage(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown, disable_notification)
local url = BASE_URL .. '/sendMessage?chat_id=' .. chat_id .. '&text=' .. URL.escape(text) local url = self.BASE_URL .. '/sendMessage?chat_id=' .. chat_id .. '&text=' .. URL.escape(text)
if disable_web_page_preview == true then if disable_web_page_preview == true then
url = url .. '&disable_web_page_preview=true' url = url .. '&disable_web_page_preview=true'
@ -67,30 +63,30 @@ sendMessage = function(chat_id, text, disable_web_page_preview, reply_to_message
url = url .. '&disable_notification=true' url = url .. '&disable_notification=true'
end end
return sendRequest(url) return bindings.sendRequest(url)
end end
sendReply = function(msg, text) function bindings:sendReply(msg, text, use_markdown, disable_notification)
return sendMessage(msg.chat.id, text, true, msg.message_id) return bindings.sendMessage(self, msg.chat.id, text, true, msg.message_id, use_markdown, disable_notification)
end end
sendChatAction = function(chat_id, action) function bindings:sendChatAction(chat_id, action)
-- Support actions are typing, upload_photo, record_video, upload_video, record_audio, upload_audio, upload_document, find_location -- Support actions are typing, upload_photo, record_video, upload_video, record_audio, upload_audio, upload_document, find_location
local url = BASE_URL .. '/sendChatAction?chat_id=' .. chat_id .. '&action=' .. action local url = self.BASE_URL .. '/sendChatAction?chat_id=' .. chat_id .. '&action=' .. action
return sendRequest(url) return bindings.sendRequest(url)
end end
sendLocation = function(chat_id, latitude, longitude, reply_to_message_id, disable_notification) function bindings:sendLocation(chat_id, latitude, longitude, reply_to_message_id, disable_notification)
if latitude == 0 then latitude = 0.001 end if latitude == 0 then latitude = 0.001 end
if longitude == 0 then longitude = 0.001 end if longitude == 0 then longitude = 0.001 end
local url = BASE_URL .. '/sendLocation?chat_id=' .. chat_id .. '&latitude=' .. latitude .. '&longitude=' .. longitude local url = self.BASE_URL .. '/sendLocation?chat_id=' .. chat_id .. '&latitude=' .. latitude .. '&longitude=' .. longitude
if reply_to_message_id then if reply_to_message_id then
url = url .. '&reply_to_message_id=' .. reply_to_message_id url = url .. '&reply_to_message_id=' .. reply_to_message_id
@ -100,16 +96,16 @@ sendLocation = function(chat_id, latitude, longitude, reply_to_message_id, disab
url = url .. '&disable_notification=true' url = url .. '&disable_notification=true'
end end
return sendRequest(url) return bindings.sendRequest(url)
end end
sendVenue = function(chat_id, latitude, longitude, title, address, foursquare_id, reply_to_message_id, disable_notification) function bindings:sendVenue(chat_id, latitude, longitude, title, address, foursquare_id, reply_to_message_id, disable_notification)
if latitude == 0 then latitude = 0.001 end if latitude == 0 then latitude = 0.001 end
if longitude == 0 then longitude = 0.001 end if longitude == 0 then longitude = 0.001 end
local url = BASE_URL .. '/sendVenue?chat_id=' .. chat_id .. '&latitude=' .. latitude .. '&longitude=' .. longitude .. '&title=' .. title .. '&address=' .. address local url = self.BASE_URL .. '/sendVenue?chat_id=' .. chat_id .. '&latitude=' .. latitude .. '&longitude=' .. longitude .. '&title=' .. title .. '&address=' .. address
if foursquare_id then if foursquare_id then
url = url .. '&foursquare_id=' .. foursquare_id url = url .. '&foursquare_id=' .. foursquare_id
@ -123,13 +119,13 @@ sendVenue = function(chat_id, latitude, longitude, title, address, foursquare_id
url = url .. '&disable_notification=true' url = url .. '&disable_notification=true'
end end
return sendRequest(url) return bindings.sendRequest(url)
end end
sendContact = function(chat_id, phone_number, first_name, last_name, reply_to_message_id, disable_notification) function bindings.sendContact(chat_id, phone_number, first_name, last_name, reply_to_message_id, disable_notification)
local url = BASE_URL .. '/sendContact?chat_id=' .. chat_id .. '&phone_number=' .. phone_number .. '&first_name=' .. first_name local url = self.BASE_URL .. '/sendContact?chat_id=' .. chat_id .. '&phone_number=' .. phone_number .. '&first_name=' .. first_name
if last_name then if last_name then
url = url .. '&last_name=' .. last_name url = url .. '&last_name=' .. last_name
@ -143,37 +139,37 @@ sendContact = function(chat_id, phone_number, first_name, last_name, reply_to_me
url = url .. '&disable_notification=true' url = url .. '&disable_notification=true'
end end
return sendRequest(url) return bindings.sendRequest(url)
end end
forwardMessage = function(chat_id, from_chat_id, message_id, disable_notification) function bindings:forwardMessage(chat_id, from_chat_id, message_id, disable_notification)
local url = BASE_URL .. '/forwardMessage?chat_id=' .. chat_id .. '&from_chat_id=' .. from_chat_id .. '&message_id=' .. message_id local url = self.BASE_URL .. '/forwardMessage?chat_id=' .. chat_id .. '&from_chat_id=' .. from_chat_id .. '&message_id=' .. message_id
if disable_notification then if disable_notification then
url = url .. '&disable_notification=true' url = url .. '&disable_notification=true'
end end
return sendRequest(url) return bindings.sendRequest(url)
end end
kickChatMember = function(chat_id, user_id) function bindings:kickChatMember(chat_id, user_id)
local url = BASE_URL .. '/kickChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id local url = self.BASE_URL .. '/kickChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id
return sendRequest(url) return bindings.sendRequest(url)
end end
unbanChatMember = function(chat_id, user_id) function bindings:unbanChatMember(chat_id, user_id)
local url = BASE_URL .. '/unbanChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id local url = self.BASE_URL .. '/unbanChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id
return sendRequest(url) return bindings.sendRequest(url)
end end
-- TODO: More of this. -- TODO: More of this.
sendPhotoID = function(chat_id, file_id, caption, reply_to_message_id, disable_notification) function bindings:sendPhotoID(chat_id, file_id, caption, reply_to_message_id, disable_notification)
local url = BASE_URL .. '/sendPhoto?chat_id=' .. chat_id .. '&photo=' .. file_id local url = self.BASE_URL .. '/sendPhoto?chat_id=' .. chat_id .. '&photo=' .. file_id
if caption then if caption then
url = url .. '&caption=' .. URL.escape(caption) url = url .. '&caption=' .. URL.escape(caption)
@ -187,20 +183,20 @@ sendPhotoID = function(chat_id, file_id, caption, reply_to_message_id, disable_n
url = url .. '&disable_notification=true' url = url .. '&disable_notification=true'
end end
return sendRequest(url) return bindings.sendRequest(url)
end end
curlRequest = function(curl_command) function bindings.curlRequest(curl_command)
-- Use at your own risk. Will not check for success. -- Use at your own risk. Will not check for success.
io.popen(curl_command) io.popen(curl_command)
end end
sendPhoto = function(chat_id, photo, caption, reply_to_message_id, disable_notification) function bindings:sendPhoto(chat_id, photo, caption, reply_to_message_id, disable_notification)
local url = BASE_URL .. '/sendPhoto' local url = self.BASE_URL .. '/sendPhoto'
local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "photo=@' .. photo .. '"' local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "photo=@' .. photo .. '"'
@ -216,13 +212,13 @@ sendPhoto = function(chat_id, photo, caption, reply_to_message_id, disable_notif
curl_command = curl_command .. ' -F "disable_notification=true"' curl_command = curl_command .. ' -F "disable_notification=true"'
end end
return curlRequest(curl_command) return bindings.curlRequest(curl_command)
end end
sendDocument = function(chat_id, document, reply_to_message_id, disable_notification) function bindings:sendDocument(chat_id, document, reply_to_message_id, disable_notification)
local url = BASE_URL .. '/sendDocument' local url = self.BASE_URL .. '/sendDocument'
local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "document=@' .. document .. '"' local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "document=@' .. document .. '"'
@ -234,13 +230,13 @@ sendDocument = function(chat_id, document, reply_to_message_id, disable_notifica
curl_command = curl_command .. ' -F "disable_notification=true"' curl_command = curl_command .. ' -F "disable_notification=true"'
end end
return curlRequest(curl_command) return bindings.curlRequest(curl_command)
end end
sendSticker = function(chat_id, sticker, reply_to_message_id, disable_notification) function bindings:sendSticker(chat_id, sticker, reply_to_message_id, disable_notification)
local url = BASE_URL .. '/sendSticker' local url = self.BASE_URL .. '/sendSticker'
local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "sticker=@' .. sticker .. '"' local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "sticker=@' .. sticker .. '"'
@ -252,13 +248,13 @@ sendSticker = function(chat_id, sticker, reply_to_message_id, disable_notificati
curl_command = curl_command .. ' -F "disable_notification=true"' curl_command = curl_command .. ' -F "disable_notification=true"'
end end
return curlRequest(curl_command) return bindings.curlRequest(curl_command)
end end
sendAudio = function(chat_id, audio, reply_to_message_id, duration, performer, title, disable_notification) function bindings:sendAudio(chat_id, audio, reply_to_message_id, duration, performer, title, disable_notification)
local url = BASE_URL .. '/sendAudio' local url = self.BASE_URL .. '/sendAudio'
local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "audio=@' .. audio .. '"' local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "audio=@' .. audio .. '"'
@ -282,13 +278,13 @@ sendAudio = function(chat_id, audio, reply_to_message_id, duration, performer, t
curl_command = curl_command .. ' -F "disable_notification=true"' curl_command = curl_command .. ' -F "disable_notification=true"'
end end
return curlRequest(curl_command) return bindings.curlRequest(curl_command)
end end
sendVideo = function(chat_id, video, reply_to_message_id, duration, caption, disable_notification) function bindings:sendVideo(chat_id, video, reply_to_message_id, duration, caption, disable_notification)
local url = BASE_URL .. '/sendVideo' local url = self.BASE_URL .. '/sendVideo'
local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "video=@' .. video .. '"' local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "video=@' .. video .. '"'
@ -308,13 +304,13 @@ sendVideo = function(chat_id, video, reply_to_message_id, duration, caption, dis
curl_command = curl_command .. ' -F "disable_notification=true"' curl_command = curl_command .. ' -F "disable_notification=true"'
end end
return curlRequest(curl_command) return bindings.curlRequest(curl_command)
end end
sendVoice = function(chat_id, voice, reply_to_message_id, duration, disable_notification) function bindings:sendVoice(chat_id, voice, reply_to_message_id, duration, disable_notification)
local url = BASE_URL .. '/sendVoice' local url = self.BASE_URL .. '/sendVoice'
local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "voice=@' .. voice .. '"' local curl_command = 'curl -s "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "voice=@' .. voice .. '"'
@ -330,6 +326,8 @@ sendVoice = function(chat_id, voice, reply_to_message_id, duration, disable_noti
curl_command = curl_command .. ' -F "disable_notification=true"' curl_command = curl_command .. ' -F "disable_notification=true"'
end end
return curlRequest(curl_command) return bindings.curlRequest(curl_command)
end end
return bindings

128
bot.lua
View File

@ -1,77 +1,85 @@
HTTP = require('socket.http') local bot = {}
HTTPS = require('ssl.https')
URL = require('socket.url')
JSON = require('cjson')
version = '3.6' -- Requires are moved to init to allow for reloads.
local bindings -- Load Telegram bindings.
local utilities -- Load miscellaneous and cross-plugin functions.
bot_init = function() -- The function run when the bot is started or reloaded. bot.version = '3.7'
config = dofile('config.lua') -- Load configuration file. function bot:init() -- The function run when the bot is started or reloaded.
dofile('bindings.lua') -- Load Telegram bindings.
dofile('utilities.lua') -- Load miscellaneous and cross-plugin functions. bindings = require('bindings')
utilities = require('utilities')
self.config = require('config') -- Load configuration file.
self.BASE_URL = 'https://api.telegram.org/bot' .. self.config.bot_api_key
if self.config.bot_api_key == '' then
error('You did not set your bot token in config.lua!')
end
-- Fetch bot information. Try until it succeeds. -- Fetch bot information. Try until it succeeds.
repeat bot = getMe() until bot repeat self.info = bindings.getMe(self) until self.info
bot = bot.result self.info = self.info.result
-- Load the "database"! ;) -- Load the "database"! ;)
if not database then if not self.database then
database = load_data(bot.username..'.db') self.database = utilities.load_data(self.info.username..'.db')
end end
plugins = {} -- Load plugins. self.plugins = {} -- Load plugins.
for i,v in ipairs(config.plugins) do for _,v in ipairs(self.config.plugins) do
local p = dofile('plugins/'..v) local p = require('plugins.'..v)
table.insert(plugins, p) table.insert(self.plugins, p)
if p.init then p.init(self) end
end end
print('@' .. bot.username .. ', AKA ' .. bot.first_name ..' ('..bot.id..')') print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')')
-- Generate a random seed and "pop" the first random number. :) -- Generate a random seed and "pop" the first random number. :)
math.randomseed(os.time()) math.randomseed(os.time())
math.random() math.random()
last_update = last_update or 0 -- Set loop variables: Update offset, self.last_update = self.last_update or 0 -- Set loop variables: Update offset,
last_cron = last_cron or os.date('%M') -- the time of the last cron job, self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job,
is_started = true -- and whether or not the bot should be running. self.is_started = true -- and whether or not the bot should be running.
database.users = database.users or {} -- Table to cache userdata. self.database.users = self.database.users or {} -- Table to cache userdata.
database.users[tostring(bot.id)] = bot self.database.users[tostring(self.info.id)] = self.info
end end
on_msg_receive = function(msg) -- The fn run whenever a message is received. function bot:on_msg_receive(msg) -- The fn run whenever a message is received.
-- Create a user entry if it does not exist. -- Create a user entry if it does not exist.
if not database.users[tostring(msg.from.id)] then if not self.database.users[tostring(msg.from.id)] then
database.users[tostring(msg.from.id)] = {} self.database.users[tostring(msg.from.id)] = {}
end end
-- Clear things that no longer exist. -- Clear things that no longer exist.
database.users[tostring(msg.from.id)].username = nil self.database.users[tostring(msg.from.id)].username = nil
database.users[tostring(msg.from.id)].last_name = nil self.database.users[tostring(msg.from.id)].last_name = nil
-- Wee. -- Wee.
for k,v in pairs(msg.from) do for k,v in pairs(msg.from) do
database.users[tostring(msg.from.id)][k] = v self.database.users[tostring(msg.from.id)][k] = v
end end
if msg.date < os.time() - 5 then return end -- Do not process old messages. if msg.date < os.time() - 5 then return end -- Do not process old messages.
msg = enrich_message(msg) msg = utilities.enrich_message(msg)
if msg.text:match('^/start .+') then if msg.text:match('^/start .+') then
msg.text = '/' .. msg.text:input() msg.text = '/' .. utilities.input(msg.text)
msg.text_lower = msg.text:lower() msg.text_lower = msg.text:lower()
end end
for i,v in ipairs(plugins) do for _,v in ipairs(self.plugins) do
for k,w in pairs(v.triggers) do for _,w in pairs(v.triggers) do
if string.match(msg.text:lower(), w) then if string.match(msg.text:lower(), w) then
local success, result = pcall(function() local success, result = pcall(function()
return v.action(msg) return v.action(self, msg)
end) end)
if not success then if not success then
sendReply(msg, 'Sorry, an unexpected error occurred.') bindings.sendReply(self, msg, 'Sorry, an unexpected error occurred.')
handle_exception(result, msg.from.id .. ': ' .. msg.text) utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text)
return return
end end
-- If the action returns a table, make that table msg. -- If the action returns a table, make that table msg.
@ -87,35 +95,41 @@ on_msg_receive = function(msg) -- The fn run whenever a message is received.
end end
bot_init() -- Actually start the script. Run the bot_init function. function bot:run()
bot.init(self) -- Actually start the script. Run the bot_init function.
while is_started do -- Start a loop while the bot should be running. while self.is_started do -- Start a loop while the bot should be running.
local res = getUpdates(last_update+1) -- Get the latest updates! do
if res then local res = bindings.getUpdates(self, self.last_update+1) -- Get the latest updates!
for i,v in ipairs(res.result) do -- Go through every new message. if res then
last_update = v.update_id for _,v in ipairs(res.result) do -- Go through every new message.
on_msg_receive(v.message) self.last_update = v.update_id
bot.on_msg_receive(self, v.message)
end
else
print(self.config.errors.connection)
end
end end
else
print(config.errors.connection)
end
if last_cron ~= os.date('%M') then -- Run cron jobs every minute. if self.last_cron ~= os.date('%M') then -- Run cron jobs every minute.
last_cron = os.date('%M') self.last_cron = os.date('%M')
save_data(bot.username..'.db', database) -- Save the database. utilities.save_data(self.info.username..'.db', self.database) -- Save the database.
for i,v in ipairs(plugins) do for i,v in ipairs(self.plugins) do
if v.cron then -- Call each plugin's cron function, if it has one. if v.cron then -- Call each plugin's cron function, if it has one.
local res, err = pcall(function() v.cron() end) local res, err = pcall(function() v.cron(self) end)
if not res then if not res then
handle_exception(err, 'CRON: ' .. i) utilities.handle_exception(self, err, 'CRON: ' .. i)
end
end end
end end
end end
end end
-- Save the database before exiting.
utilities.save_data(self.info.username..'.db', self.database)
print('Halted.')
end end
-- Save the database before exiting. return bot
save_data(bot.username..'.db', database)
print('Halted.')

View File

@ -4,8 +4,6 @@ return {
bot_api_key = '', bot_api_key = '',
-- Your Telegram ID. -- Your Telegram ID.
admin = 00000000, admin = 00000000,
-- Differences, in seconds, between your time and UTC.
time_offset = 0,
-- Two-letter language code. -- Two-letter language code.
lang = 'en', lang = 'en',
-- The channel, group, or user to send error reports to. -- The channel, group, or user to send error reports to.
@ -51,35 +49,35 @@ Send /help to get started.
}, },
plugins = { -- To enable a plugin, add its name to the list. plugins = { -- To enable a plugin, add its name to the list.
'control.lua', 'control',
'blacklist.lua', 'blacklist',
'about.lua', 'about',
'ping.lua', 'ping',
'whoami.lua', 'whoami',
'nick.lua', 'nick',
'echo.lua', 'echo',
'gSearch.lua', 'gSearch',
'gMaps.lua', 'gMaps',
'wikipedia.lua', 'wikipedia',
'hackernews.lua', 'hackernews',
'imdb.lua', 'imdb',
'calc.lua', 'calc',
'urbandictionary.lua', 'urbandictionary',
'time.lua', 'time',
'eightball.lua', 'eightball',
'dice.lua', 'dice',
'reddit.lua', 'reddit',
'xkcd.lua', 'xkcd',
'slap.lua', 'slap',
'commit.lua', 'commit',
'pun.lua', 'pun',
'currency.lua', 'currency',
'cats.lua', 'cats',
'shout.lua', 'shout',
'patterns.lua', 'patterns',
-- Put new plugins above this line. -- Put new plugins above this line.
'help.lua', 'help',
'greetings.lua' 'greetings'
} }
} }

View File

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

5
main.lua Normal file
View File

@ -0,0 +1,5 @@
local bot = require('bot')
local instance = {}
return bot.run(instance)

22
otouto-dev-1.rockspec Normal file
View File

@ -0,0 +1,22 @@
package = 'otouto'
version = 'dev-1'
source = {
url = 'git://github.com/topkecleon/otouto.git'
}
description = {
summary = 'The plugin-wielding, multipurpose Telegram bot!',
detailed = 'A plugin-wielding, multipurpose bot for the Telegram API.',
homepage = 'http://otou.to',
maintainer = 'Drew <drew@otou.to>',
license = 'GPL-2'
}
dependencies = {
'lua >= 5.2',
'LuaSocket ~> 3.0',
'LuaSec ~> 0.6',
'dkjson ~> 2.5',
'LPeg ~> 1.0'
}

View File

@ -1,22 +1,28 @@
local command = 'about' local about = {}
local doc = '`Returns information about the bot.`'
local triggers = { local bot = require('bot')
local bindings = require('bindings')
about.command = 'about'
about.doc = '`Returns information about the bot.`'
about.triggers = {
'' ''
} }
local action = function(msg) function about:action(msg)
-- Filthy hack, but here is where we'll stop forwarded messages from hitting -- Filthy hack, but here is where we'll stop forwarded messages from hitting
-- other plugins. -- other plugins.
if msg.forward_from then return end if msg.forward_from then return end
local output = config.about_text .. '\nBased on @otouto v'..version..' by topkecleon.' local output = self.config.about_text .. '\nBased on @otouto v'..bot.version..' by topkecleon.'
if (msg.new_chat_participant and msg.new_chat_participant.id == bot.id) if (msg.new_chat_participant and msg.new_chat_participant.id == self.info.id)
or msg.text_lower:match('^/about[@'..bot.username..']*') or msg.text_lower:match('^/about')
or msg.text_lower:match('^/about@'..self.info.username:lower())
or msg.text_lower:match('^/start') then or msg.text_lower:match('^/start') then
sendMessage(msg.chat.id, output, true) bindings.sendMessage(self, msg.chat.id, output, true)
return return
end end
@ -24,9 +30,4 @@ local action = function(msg)
end end
return { return about
action = action,
triggers = triggers,
doc = doc,
command = command
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,13 @@
local command = 'apod [date]' local apod = {}
local doc = [[```
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local URL = require('socket.url')
local bindings = require('bindings')
local utilities = require('utilities')
apod.command = 'apod [date]'
apod.doc = [[```
/apod [query] /apod [query]
Returns the Astronomy Picture of the Day. Returns the Astronomy Picture of the Day.
If the query is a date, in the format YYYY-MM-DD, the APOD of that day is returned. If the query is a date, in the format YYYY-MM-DD, the APOD of that day is returned.
@ -10,31 +18,29 @@ Returns the explanation of the APOD.
Source: nasa.gov Source: nasa.gov
```]] ```]]
local triggers = { function apod:init()
'^/apod[@'..bot.username..']*', apod.triggers = utilities.triggers(self.info.username)
'^/apodhd[@'..bot.username..']*', :t('apod', true):t('apodhd', true):t('apodtext', true).table
'^/apodtext[@'..bot.username..']*' end
}
local action = function(msg) function apod:action(msg)
if not config.nasa_api_key then if not self.config.nasa_api_key then
config.nasa_api_key = 'DEMO_KEY' self.config.nasa_api_key = 'DEMO_KEY'
end end
local input = msg.text:input() local input = utilities.input(msg.text)
local caption = ''
local date = '*' local date = '*'
local disable_page_preview = false local disable_page_preview = false
local url = 'https://api.nasa.gov/planetary/apod?api_key=' .. config.nasa_api_key local url = 'https://api.nasa.gov/planetary/apod?api_key=' .. self.config.nasa_api_key
if input then if input then
if input:match('(%d+)%-(%d+)%-(%d+)$') then if input:match('(%d+)%-(%d+)%-(%d+)$') then
url = url .. '&date=' .. URL.escape(input) url = url .. '&date=' .. URL.escape(input)
date = date .. input date = date .. input
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, apod.doc, true, msg.message_id, true)
return return
end end
else else
@ -45,14 +51,14 @@ local action = function(msg)
local jstr, res = HTTPS.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.error then if jdat.error then
sendReply(msg, config.errors.results) bindings.sendReply(msg, self.config.errors.results)
return return
end end
@ -62,7 +68,7 @@ local action = function(msg)
img_url = jdat.hdurl or jdat.url img_url = jdat.hdurl or jdat.url
end end
output = date .. '[' .. jdat.title .. '](' .. img_url .. ')' local output = date .. '[' .. jdat.title .. '](' .. img_url .. ')'
if string.match(msg.text, '^/apodtext*') then if string.match(msg.text, '^/apodtext*') then
output = output .. '\n' .. jdat.explanation output = output .. '\n' .. jdat.explanation
@ -73,13 +79,8 @@ local action = function(msg)
output = output .. '\nCopyright: ' .. jdat.copyright output = output .. '\nCopyright: ' .. jdat.copyright
end end
sendMessage(msg.chat.id, output, disable_page_preview, nil, true) bindings.sendMessage(self, msg.chat.id, output, disable_page_preview, nil, true)
end end
return { return apod
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,13 +1,17 @@
local command = 'bandersnatch' local bandersnatch = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
bandersnatch.command = 'bandersnatch'
bandersnatch.doc = [[```
Shun the frumious Bandersnatch. Shun the frumious Bandersnatch.
Alias: /bc Alias: /bc
```]] ```]]
local triggers = { function bandersnatch:init()
'^/bandersnatch[@'..bot.username..']*', bandersnatch.triggers = utilities.triggers(self.info.username):t('bandersnatch'):t('bc').table
'^/bc[@'..bot.username..']*' end
}
local fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" } local fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" }
@ -15,9 +19,9 @@ local firstnames = { "Bumblebee", "Bandersnatch", "Broccoli", "Rinkydink", "Bomb
local lastnames = { "Coddleswort", "Crumplesack", "Curdlesnoot", "Calldispatch", "Humperdinck", "Rivendell", "Cuttlefish", "Lingerie", "Vegemite", "Ampersand", "Cumberbund", "Candycrush", "Clombyclomp", "Cragglethatch", "Nottinghill", "Cabbagepatch", "Camouflage", "Creamsicle", "Curdlemilk", "Upperclass", "Frumblesnatch", "Crumplehorn", "Talisman", "Candlestick", "Chesterfield", "Bumbersplat", "Scratchnsniff", "Snugglesnatch", "Charizard", "Carrotstick", "Cumbercooch", "Crackerjack", "Crucifix", "Cuckatoo", "Cockletit", "Collywog", "Capncrunch", "Covergirl", "Cumbersnatch", "Countryside", "Coggleswort", "Splishnsplash", "Copperwire", "Animorph", "Curdledmilk", "Cheddarcheese", "Cottagecheese", "Crumplehorn", "Snickersbar", "Banglesnatch", "Stinkyrash", "Cameltoe", "Chickenbroth", "Concubine", "Candygram", "Moldyspore", "Chuckecheese", "Cankersore", "Crimpysnitch", "Wafflesmack", "Chowderpants", "Toodlesnoot", "Clavichord", "Cuckooclock", "Oxfordshire", "Cumbersome", "Chickenstrips", "Battleship", "Commonwealth", "Cunningsnatch", "Custardbath", "Kryptonite", "Curdlesnoot", "Cummerbund", "Coochyrash", "Crackerdong", "Crackerdong", "Curdledong", "Crackersprout", "Crumplebutt", "Colonist", "Coochierash", "Thundersnatch" } local lastnames = { "Coddleswort", "Crumplesack", "Curdlesnoot", "Calldispatch", "Humperdinck", "Rivendell", "Cuttlefish", "Lingerie", "Vegemite", "Ampersand", "Cumberbund", "Candycrush", "Clombyclomp", "Cragglethatch", "Nottinghill", "Cabbagepatch", "Camouflage", "Creamsicle", "Curdlemilk", "Upperclass", "Frumblesnatch", "Crumplehorn", "Talisman", "Candlestick", "Chesterfield", "Bumbersplat", "Scratchnsniff", "Snugglesnatch", "Charizard", "Carrotstick", "Cumbercooch", "Crackerjack", "Crucifix", "Cuckatoo", "Cockletit", "Collywog", "Capncrunch", "Covergirl", "Cumbersnatch", "Countryside", "Coggleswort", "Splishnsplash", "Copperwire", "Animorph", "Curdledmilk", "Cheddarcheese", "Cottagecheese", "Crumplehorn", "Snickersbar", "Banglesnatch", "Stinkyrash", "Cameltoe", "Chickenbroth", "Concubine", "Candygram", "Moldyspore", "Chuckecheese", "Cankersore", "Crimpysnitch", "Wafflesmack", "Chowderpants", "Toodlesnoot", "Clavichord", "Cuckooclock", "Oxfordshire", "Cumbersome", "Chickenstrips", "Battleship", "Commonwealth", "Cunningsnatch", "Custardbath", "Kryptonite", "Curdlesnoot", "Cummerbund", "Coochyrash", "Crackerdong", "Crackerdong", "Curdledong", "Crackersprout", "Crumplebutt", "Colonist", "Coochierash", "Thundersnatch" }
local action = function(msg) function bandersnatch:action(msg)
local message local output
if math.random(10) == 10 then if math.random(10) == 10 then
output = fullnames[math.random(#fullnames)] output = fullnames[math.random(#fullnames)]
@ -25,15 +29,8 @@ local action = function(msg)
output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)] output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)]
end end
output = '_' .. output .. '_' bindings.sendMessage(self, msg.chat.id, '_'..output..'_', true, nil, true)
sendMessage(msg.chat.id, output, true, nil, true)
end end
return { return bandersnatch
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,54 +1,54 @@
if not config.biblia_api_key then local bible = {}
print('Missing config value: biblia_api_key.')
print('bible.lua will not be enabled.') local HTTP = require('socket.http')
return local URL = require('socket.url')
local bindings = require('bindings')
local utilities = require('utilities')
function bible:init()
if not self.config.biblia_api_key then
print('Missing config value: biblia_api_key.')
print('bible.lua will not be enabled.')
return
end
bible.triggers = utilities.triggers(self.info.username):t('bible', true):t('b', true).table
end end
local command = 'bible <reference>' bible.command = 'bible <reference>'
local doc = [[``` bible.doc = [[```
/bible <reference> /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. 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: /b Alias: /b
```]] ```]]
local triggers = { function bible:action(msg)
'^/bible*[@'..bot.username..']*',
'^/b[@'..bot.username..']* ',
'^/b[@'..bot.username..']*$'
}
local action = function(msg) local input = utilities.input(msg.text)
local input = msg.text:input()
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, bible.doc, true, msg.message_id, true)
return return
end 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=' .. self.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 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) url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. self.config.biblia_api_key .. '&passage=' .. URL.escape(input)
output, res = HTTP.request(url) output, res = HTTP.request(url)
end end
if not output or res ~= 200 or output:len() == 0 then if not output or res ~= 200 or output:len() == 0 then
output = config.errors.results output = self.config.errors.results
end end
if output:len() > 4000 then if output:len() > 4000 then
output = 'The text is too long to post here. Try being more specific.' output = 'The text is too long to post here. Try being more specific.'
end end
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendReply(self, msg, output)
end end
return { return bible
action = action,
triggers = triggers,
command = command,
doc = doc
}

View File

@ -1,24 +1,31 @@
-- This plugin will allow the admin to blacklist users who will be unable to -- This plugin will allow the admin to blacklist users who will be unable to
-- use the bot. This plugin should be at the top of your plugin list in config. -- use the bot. This plugin should be at the top of your plugin list in config.
if not database.blacklist then local blacklist = {}
database.blacklist = {}
local bindings = require('bindings')
local utilities = require('utilities')
function blacklist:init()
if not self.database.blacklist then
self.database.blacklist = {}
end
end end
local triggers = { blacklist.triggers = {
'' ''
} }
local action = function(msg) function blacklist:action(msg)
if database.blacklist[msg.from.id_str] then return end if self.database.blacklist[msg.from.id_str] then return end
if database.blacklist[msg.chat.id_str] then return end if self.database.blacklist[msg.chat.id_str] then return end
if not msg.text:match('^/blacklist') then return true end if not msg.text:match('^/blacklist') then return true end
if msg.from.id ~= config.admin then return end if msg.from.id ~= self.config.admin then return end
local target = user_from_message(msg) local target = utilities.user_from_message(self, msg)
if target.err then if target.err then
sendReply(msg, target.err) bindings.sendReply(self, msg, target.err)
return return
end end
@ -26,17 +33,14 @@ local triggers = {
target.name = 'Group' target.name = 'Group'
end end
if database.blacklist[tostring(target.id)] then if self.database.blacklist[tostring(target.id)] then
database.blacklist[tostring(target.id)] = nil self.database.blacklist[tostring(target.id)] = nil
sendReply(msg, target.name .. ' has been removed from the blacklist.') bindings.sendReply(self, msg, target.name .. ' has been removed from the blacklist.')
else else
database.blacklist[tostring(target.id)] = true self.database.blacklist[tostring(target.id)] = true
sendReply(msg, target.name .. ' has been added to the blacklist.') bindings.sendReply(self, msg, target.name .. ' has been added to the blacklist.')
end end
end end
return { return blacklist
action = action,
triggers = triggers
}

View File

@ -1,21 +1,28 @@
local command = 'calc <expression>' local calc = {}
local doc = [[```
local URL = require('socket.url')
local HTTPS = require('ssl.https')
local bindings = require('bindings')
local utilities = require('utilities')
calc.command = 'calc <expression>'
calc.doc = [[```
/calc <expression> /calc <expression>
Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org. Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org.
```]] ```]]
local triggers = { function calc:init()
'^/calc[@'..bot.username..']*' calc.triggers = utilities.triggers(self.info.username):t('calc', true).table
} end
local action = function(msg) function calc:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, calc.doc, true, msg.message_id, true)
return return
end end
end end
@ -24,19 +31,14 @@ local action = function(msg)
local output = HTTPS.request(url) local output = HTTPS.request(url)
if not output then if not output then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
output = '`' .. output .. '`' output = '`' .. output .. '`'
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end end
return { return calc
action = action,
triggers = triggers,
command = command,
doc = doc
}

View File

@ -1,38 +1,39 @@
if not config.thecatapi_key then local cats = {}
print('Missing config value: thecatapi_key.')
print('cats.lua will be enabled, but there are more features with a key.') local HTTP = require('socket.http')
local bindings = require('bindings')
local utilities = require('utilities')
function cats:init()
if not self.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):t('cat').table
end end
local command = 'cat' cats.command = 'cat'
local doc = '`Returns a cat!`' cats.doc = '`Returns a cat!`'
local triggers = { function cats:action(msg)
'^/cat[@'..bot.username..']*$'
}
local action = function(msg)
local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg' local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg'
if config.thecatapi_key then if self.config.thecatapi_key then
url = url .. '&api_key=' .. config.thecatapi_key url = url .. '&api_key=' .. self.config.thecatapi_key
end end
local str, res = HTTP.request(url) local str, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(msg, self.config.errors.connection)
return return
end end
str = str:match('<img src="(.-)">') str = str:match('<img src="(.-)">')
local output = '[Cat!]('..str..')' local output = '[Cat!]('..str..')'
sendMessage(msg.chat.id, output, false, nil, true) bindings.sendMessage(self, msg.chat.id, output, false, nil, true)
end end
return { return cats
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,24 +1,33 @@
-- Put this absolutely at the end, even after greetings.lua. -- Put this absolutely at the end, even after greetings.lua.
if not config.simsimi_key then local chatter = {}
print('Missing config value: simsimi_key.')
print('chatter.lua will not be enabled.') local HTTP = require('socket.http')
return local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
function chatter:init()
if not self.config.simsimi_key then
print('Missing config value: simsimi_key.')
print('chatter.lua will not be enabled.')
return
end
chatter.triggers = {
'',
'^' .. self.info.first_name .. ',',
'^@' .. self.info.username .. ','
}
end end
local triggers = { function chatter:action(msg)
'',
'^' .. bot.first_name .. ',',
'^@' .. bot.username .. ','
}
local action = function(msg)
if msg.text == '' then return end if msg.text == '' then return end
-- This is awkward, but if you have a better way, please share. -- This is awkward, but if you have a better way, please share.
if msg.text_lower:match('^' .. bot.first_name .. ',') then if msg.text_lower:match('^' .. self.info.first_name .. ',')
elseif msg.text_lower:match('^@' .. bot.username .. ',') then or msg.text_lower:match('^@' .. self.info.username .. ',') then
elseif msg.text:match('^/') then elseif msg.text:match('^/') then
return true return true
-- Uncomment the following line for Al Gore-like reply chatter. -- Uncomment the following line for Al Gore-like reply chatter.
@ -28,40 +37,41 @@ local action = function(msg)
return true return true
end end
sendChatAction(msg.chat.id, 'typing') bindings.sendChatAction(self, msg.chat.id, 'typing')
local input = msg.text_lower local input = msg.text_lower
input = input:gsub(bot.first_name, 'simsimi') input = input:gsub(self.info.first_name, 'simsimi')
input = input:gsub('@'..bot.username, 'simsimi') input = input:gsub('@'..self.info.username, 'simsimi')
if config.simsimi_trial then local sandbox
if self.config.simsimi_trial then
sandbox = 'sandbox.' sandbox = 'sandbox.'
else else
sandbox = '' -- NO Sandbox sandbox = '' -- NO Sandbox
end end
local url = 'http://' ..sandbox.. 'api.simsimi.com/request.p?key=' ..config.simsimi_key.. '&lc=' ..config.lang.. '&ft=1.0&text=' .. URL.escape(input) local url = 'http://' ..sandbox.. 'api.simsimi.com/request.p?key=' ..self.config.simsimi_key.. '&lc=' ..self.config.lang.. '&ft=1.0&text=' .. URL.escape(input)
local jstr, res = HTTP.request(url) local jstr, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
sendMessage(msg.chat.id, config.errors.chatter_connection) bindings.sendMessage(self, msg.chat.id, self.config.errors.chatter_connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if not jdat.response then if not jdat.response then
sendMessage(msg.chat.id, config.errors.chatter_response) bindings.sendMessage(self, msg.chat.id, self.config.errors.chatter_response)
return return
end end
local output = jdat.response local output = jdat.response
if output:match('^I HAVE NO RESPONSE.') then if output:match('^I HAVE NO RESPONSE.') then
output = config.errors.chatter_response output = self.config.errors.chatter_response
end end
-- Let's clean up the response a little. Capitalization & punctuation. -- Let's clean up the response a little. Capitalization & punctuation.
local filter = { local filter = {
['%aimi?%aimi?'] = bot.first_name, ['%aimi?%aimi?'] = self.info.first_name,
['^%s*(.-)%s*$'] = '%1', ['^%s*(.-)%s*$'] = '%1',
['^%l'] = string.upper, ['^%l'] = string.upper,
['USER'] = msg.from.first_name ['USER'] = msg.from.first_name
@ -75,11 +85,8 @@ local action = function(msg)
output = output .. '.' output = output .. '.'
end end
sendMessage(msg.chat.id, output) bindings.sendMessage(self, msg.chat.id, output)
end end
return { return chatter
action = action,
triggers = triggers
}

View File

@ -1,11 +1,16 @@
-- Commits from https://github.com/ngerakines/commitment. -- Commits from https://github.com/ngerakines/commitment.
local command = 'commit' local commit = {}
local doc = '`Returns a commit message from whatthecommit.com.`'
local triggers = { local bindings = require('bindings')
'^/commit[@'..bot.username..']*' local utilities = require('utilities')
}
commit.command = 'commit'
commit.doc = '`Returns a commit message from whatthecommit.com.`'
function commit:init()
commit.triggers = utilities.triggers(self.info.username):t('commit').table
end
local commits = { local commits = {
"One does not simply merge into master", "One does not simply merge into master",
@ -416,16 +421,11 @@ local commits = {
"fml" "fml"
} }
local action = function(msg) function commit:action(msg)
local output = '`'..commits[math.random(#commits)]..'`' local output = '`'..commits[math.random(#commits)]..'`'
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return commit
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,28 +1,38 @@
local triggers = { local control = {}
'^/reload[@'..bot.username..']*',
'^/halt[@'..bot.username..']*'
}
local action = function(msg) local bot = require('bot')
local bindings = require('bindings')
local utilities = require('utilities')
if msg.from.id ~= config.admin then function control:init()
control.triggers = utilities.triggers(self.info.username):t('reload'):t('halt').table
end
function control:action(msg)
if msg.from.id ~= self.config.admin then
return return
end end
if msg.date < os.time() - 1 then return end if msg.date < os.time() - 1 then return end
if msg.text:match('^/reload') then if msg.text:match('^'..utilities.INVOCATION_PATTERN..'reload') then
bot_init() for pac, _ in pairs(package.loaded) do
sendReply(msg, 'Bot reloaded!') if pac:match('^plugins%.') then
elseif msg.text:match('^/halt') then package.loaded[pac] = nil
is_started = false end
sendReply(msg, 'Stopping bot!') package.loaded['bindings'] = nil
package.loaded['utilities'] = nil
package.loaded['config'] = nil
end
bot.init(self)
bindings.sendReply(self, msg, 'Bot reloaded!')
elseif msg.text:match('^'..utilities.INVOCATION_PATTERN..'halt') then
self.is_started = false
bindings.sendReply(self, msg, 'Stopping bot!')
end end
end end
return { return control
action = action,
triggers = triggers
}

View File

@ -1,26 +1,32 @@
local command = 'cash [amount] <from> to <to>' local currency = {}
local doc = [[```
local HTTPS = require('ssl.https')
local bindings = require('bindings')
local utilities = require('utilities')
currency.command = 'cash [amount] <from> to <to>'
currency.doc = [[```
/cash [amount] <from> to <to> /cash [amount] <from> to <to>
Example: /cash 5 USD to EUR Example: /cash 5 USD to EUR
Returns exchange rates for various currencies. Returns exchange rates for various currencies.
Source: Google Finance. Source: Google Finance.
```]] ```]]
local triggers = { function currency:init()
'^/cash[@'..bot.username..']*' currency.triggers = utilities.triggers(self.info.username):t('cash', true).table
} end
local action = function(msg) function currency:action(msg)
local input = msg.text:upper() local input = msg.text:upper()
if not input:match('%a%a%a TO %a%a%a') then if not input:match('%a%a%a TO %a%a%a') then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, currency.doc, true, msg.message_id, true)
return return
end end
local from = input:match('(%a%a%a) TO') local from = input:match('(%a%a%a) TO')
local to = input:match('TO (%a%a%a)') local to = input:match('TO (%a%a%a)')
local amount = get_word(input, 2) local amount = utilities.get_word(input, 2)
amount = tonumber(amount) or 1 amount = tonumber(amount) or 1
local result = 1 local result = 1
@ -28,16 +34,16 @@ local action = function(msg)
if from ~= to then if from ~= to then
local url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount
local str, res = HTTPS.request(url) local str, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
str = str:match('<span class=bld>(.*) %u+</span>') str = str:match('<span class=bld>(.*) %u+</span>')
if not str then if not str then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
@ -45,17 +51,12 @@ local action = function(msg)
end end
local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n' local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n\n'
output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance' output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance`'
output = '```\n' .. output .. '\n```' output = '```\n' .. output .. '\n```'
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return currency
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,18 +1,23 @@
local command = 'roll <nDr>' local dice = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
dice.command = 'roll <nDr>'
dice.doc = [[```
/roll <nDr> /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. 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.
```]] ```]]
local triggers = { function dice:init()
'^/roll[@'..bot.username..']*' dice.triggers = utilities.triggers(self.info.username):t('roll', true).table
} end
local action = function(msg) function dice:action(msg)
local input = msg.text_lower:input() local input = utilities.input(msg.text_lower)
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, dice.doc, true, msg.message_id, true)
return return
end end
@ -23,7 +28,7 @@ local action = function(msg)
count = 1 count = 1
range = input:match('^d?([%d]+)$') range = input:match('^d?([%d]+)$')
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, dice.doc, true, msg.message_id, true)
return return
end end
@ -31,27 +36,22 @@ local action = function(msg)
range = tonumber(range) range = tonumber(range)
if range < 2 then if range < 2 then
sendReply(msg, 'The minimum range is 2.') bindings.sendReply(self, msg, 'The minimum range is 2.')
return return
end end
if range > 1000 or count > 1000 then if range > 1000 or count > 1000 then
sendReply(msg, 'The maximum range and count are 1000.') bindings.sendReply(self, msg, 'The maximum range and count are 1000.')
return return
end end
local output = '*' .. count .. 'd' .. range .. '*\n`' local output = '*' .. count .. 'd' .. range .. '*\n`'
for i = 1, count do for _ = 1, count do
output = output .. math.random(range) .. '\t' output = output .. math.random(range) .. '\t'
end end
output = output .. '`' output = output .. '`'
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end end
return { return dice
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,46 +1,51 @@
dilbert = dilbert or {} local dilbert = {}
local command = 'dilbert [date]' local HTTP = require('socket.http')
local doc = [[``` local URL = require('socket.url')
local bindings = require('bindings')
local utilities = require('utilities')
dilbert.command = 'dilbert [date]'
dilbert.doc = [[```
/dilbert [YYYY-MM-DD] /dilbert [YYYY-MM-DD]
Returns the latest Dilbert strip or that of the provided date. 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. Dates before the first strip will return the first strip. Dates after the last trip will return the last strip.
Source: dilbert.com Source: dilbert.com
```]] ```]]
local triggers = { function dilbert:init()
'^/dilbert[@'..bot.username..']*' dilbert.triggers = utilities.triggers(self.info.username):t('dilbert', true).table
} end
local action = function(msg) function dilbert:action(msg)
sendChatAction(msg.chat.id, 'upload_photo') bindings.sendChatAction(self, msg.chat.id, 'upload_photo')
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then input = os.date('%F') end 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 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 url = 'http://dilbert.com/strip/' .. URL.escape(input)
local str, res = HTTP.request(url) local str, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
if not dilbert[input] then 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="(.-)"/>') local strip_url = str:match('<meta property="og:image" content="(.-)"/>')
dilbert[input] = download_file(strip_url, '/tmp/' .. input .. '.gif') strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif')
end end
local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>') local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>')
sendPhoto(msg.chat.id, dilbert[input], strip_title) bindings.sendPhoto(self, msg.chat.id, strip_file, strip_title)
end end
return { return dilbert
command = command,
doc = doc,
triggers = triggers,
action = action
}

View File

@ -1,36 +1,35 @@
local command = 'echo <text>' local echo = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
echo.command = 'echo <text>'
echo.doc = [[```
/echo <text> /echo <text>
Repeats a string of text. Repeats a string of text.
```]] ```]]
local triggers = { function echo:init()
'^/echo[@'..bot.username..']*' echo.triggers = utilities.triggers(self.info.username):t('echo', true).table
} end
local action = function(msg) function echo:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, echo.doc, true, msg.message_id, true)
else else
input = markdown_escape(input)
local output local output
if msg.chat.type == 'supergroup' then if msg.chat.type == 'supergroup' then
output = '*Echo:*\n"' .. input .. '"' output = '*Echo:*\n"' .. utilities.md_escape(input) .. '"'
else else
output = latcyr(input) output = utilities.md_escape(utilities.latcyr(input))
end end
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true)
end end
end end
return { return echo
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,10 +1,14 @@
local command = '8ball' local eightball = {}
local doc = '`Returns an answer from a magic 8-ball!`'
local triggers = { local bindings = require('bindings')
'^/8ball', local utilities = require('utilities')
'y/n%p?$'
} eightball.command = '8ball'
eightball.doc = '`Returns an answer from a magic 8-ball!`'
function eightball:init()
eightball.triggers = utilities.triggers(self.info.username, {'[Yy]/[Nn]%p*$'}):t('8ball', true).table
end
local ball_answers = { local ball_answers = {
"It is certain.", "It is certain.",
@ -37,7 +41,7 @@ local yesno_answers = {
'No.' 'No.'
} }
local action = function(msg) function eightball:action(msg)
local output local output
@ -47,13 +51,8 @@ local action = function(msg)
output = ball_answers[math.random(#ball_answers)] output = ball_answers[math.random(#ball_answers)]
end end
sendReply(msg, output) bindings.sendReply(self, msg, output)
end end
return { return eightball
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,22 +1,29 @@
-- Liberbot-compliant floodcontrol. -- Liberbot-compliant floodcontrol.
-- Put this after moderation.lua or blacklist.lua. -- Put this after moderation.lua or blacklist.lua.
floodcontrol = floodcontrol or {} local floodcontrol = {}
local triggers = { local JSON = require('dkjson')
local utilities = require('utilities')
function floodcontrol:init()
self.floodcontrol = self.floodcontrol or {}
end
floodcontrol.triggers = {
'' ''
} }
local action = function(msg) function floodcontrol:action(msg)
if floodcontrol[-msg.chat.id] then if self.floodcontrol[-msg.chat.id] then
return return
end end
local input = msg.text_lower:match('^/floodcontrol[@'..bot.username..']* (.+)') local input = msg.text_lower:match('^/floodcontrol (.+)') or msg.text_lower:match('^/floodcontrol@'..self.info.username..' (.+)')
if not input then return true end if not input then return true end
if msg.from.id ~= 100547061 and msg.from.id ~= config.admin then if msg.from.id ~= 100547061 and msg.from.id ~= self.config.admin then
return -- Only run for Liberbot or the admin. return -- Only run for Liberbot or the admin.
end end
@ -29,25 +36,21 @@ local action = function(msg)
input.duration = 600 input.duration = 600
end end
floodcontrol[input.groupid] = os.time() + input.duration self.floodcontrol[input.groupid] = os.time() + input.duration
local output = input.groupid .. ' silenced for ' .. input.duration .. ' seconds.' local output = input.groupid .. ' silenced for ' .. input.duration .. ' seconds.'
handle_exception('floodcontrol.lua', output) utilities.handle_exception(self, 'floodcontrol.lua', output)
end end
local cron = function() function floodcontrol:cron()
for k,v in pairs(floodcontrol) do for k,v in pairs(self.floodcontrol) do
if os.time() > v then if os.time() > v then
floodcontrol[k] = nil self.floodcontrol[k] = nil
end end
end end
end end
return { return floodcontrol
action = action,
triggers = triggers,
cron = cron
}

View File

@ -1,29 +1,31 @@
-- Requires that the "fortune" program is installed on your computer. -- Requires that the "fortune" program is installed on your computer.
local s = io.popen('fortune'):read('*all') local fortune = {}
if s:match('not found$') then
print('fortune is not installed on this computer.') local bindings = require('bindings')
print('fortune.lua will not be enabled.') local utilities = require('utilities')
return
function fortune:init()
local s = io.popen('fortune'):read('*all')
if s:match('not found$') then
print('fortune is not installed on this computer.')
print('fortune.lua will not be enabled.')
return
end
fortune.triggers = utilities.triggers(self.info.username):t('fortune').table
end end
local command = 'fortune' fortune.command = 'fortune'
local doc = '`Returns a UNIX fortune.`' fortune.doc = '`Returns a UNIX fortune.`'
local triggers = { function fortune:action(msg)
'^/fortune[@'..bot.username..']*'
}
local action = function(msg) local fortunef = io.popen('fortune')
local output = fortunef:read('*all')
local output = io.popen('fortune'):read('*all') bindings.sendMessage(self, msg.chat.id, output)
sendMessage(msg.chat.id, output) fortunef:close()
end end
return { return fortune
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,43 +1,48 @@
-- You need a Google API key and a Google Custom Search Engine set up to use this, in config.google_api_key and config.google_cse_key, respectively. -- You need a Google API key and a Google Custom Search Engine set up to use this, in config.google_api_key and config.google_cse_key, respectively.
-- You must also sign up for the CSE in the Google Developer Concsole, and enable image results. -- You must also sign up for the CSE in the Google Developer Console, and enable image results.
if not config.google_api_key then local gImages = {}
print('Missing config value: google_api_key.')
print('gImages.lua will not be enabled.') local HTTPS = require('ssl.https')
return local URL = require('socket.url')
elseif not config.google_cse_key then local JSON = require('dkjson')
print('Missing config value: google_cse_key.') local bindings = require('bindings')
print('gImages.lua will not be enabled.') local utilities = require('utilities')
return
function gImages:init()
if not self.config.google_api_key then
print('Missing config value: google_api_key.')
print('gImages.lua will not be enabled.')
return
elseif not self.config.google_cse_key then
print('Missing config value: google_cse_key.')
print('gImages.lua will not be enabled.')
return
end
gImages.triggers = utilities.triggers(self.info.username):t('image', true):t('i', true):t('insfw', true).table
end end
local command = 'image <query>' gImages.command = 'image <query>'
local doc = [[``` gImages.doc = [[```
/image <query> /image <query>
Returns a randomized top result from Google Images. Safe search is enabled by default; use "/insfw" to disable it. NSFW results will not display an image preview. Returns a randomized top result from Google Images. Safe search is enabled by default; use "/insfw" to disable it. NSFW results will not display an image preview.
Alias: /i Alias: /i
```]] ```]]
local triggers = { function gImages:action(msg)
'^/image[@'..bot.username..']*',
'^/i[@'..bot.username..']* ',
'^/i[@'..bot.username..']*$',
'^/insfw[@'..bot.username..']*'
}
local action = function(msg) local input = utilities.input(msg.text)
local input = msg.text:input()
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, gImages.doc, true, msg.message_id, true)
return return
end end
end end
local url = 'https://www.googleapis.com/customsearch/v1?&searchType=image&imgSize=xlarge&alt=json&num=8&start=1&key=' .. config.google_api_key .. '&cx=' .. config.google_cse_key local url = 'https://www.googleapis.com/customsearch/v1?&searchType=image&imgSize=xlarge&alt=json&num=8&start=1&key=' .. self.config.google_api_key .. '&cx=' .. self.config.google_cse_key
if not string.match(msg.text, '^/i[mage]*nsfw') then if not string.match(msg.text, '^/i[mage]*nsfw') then
url = url .. '&safe=high' url = url .. '&safe=high'
@ -47,13 +52,13 @@ local action = function(msg)
local jstr, res = HTTPS.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.searchInformation.totalResults == '0' then if jdat.searchInformation.totalResults == '0' then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
@ -64,17 +69,11 @@ local action = function(msg)
if msg.text:match('nsfw') then if msg.text:match('nsfw') then
output = '*NSFW*\n' .. output bindings.sendReply(self, '*NSFW*\n'..msg, output)
sendMessage(msg.chat.id, output, true, nil, true)
else else
sendMessage(msg.chat.id, output, false, nil, true) bindings.sendMessage(self, msg.chat.id, output, false, nil, true)
end end
end end
return { return gImages
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,41 +1,39 @@
local command = 'location <query>' local gMaps = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
gMaps.command = 'location <query>'
gMaps.doc = [[```
/location <query> /location <query>
Returns a location from Google Maps. Returns a location from Google Maps.
Alias: /loc Alias: /loc
```]] ```]]
local triggers = { function gMaps:init()
'^/location[@'..bot.username..']*', gMaps.triggers = utilities.triggers(self.info.username):t('location', true):t('loc', true).table
'^/loc[@'..bot.username..']* ', end
'^/loc[@'..bot.username..']*$'
}
local action = function(msg) function gMaps:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, gMaps.doc, true, msg.message_id, true)
return return
end end
end end
local coords = get_coords(input) local coords = utilities.get_coords(self, input)
if type(coords) == 'string' then if type(coords) == 'string' then
sendReply(msg, coords) bindings.sendReply(self, msg, coords)
return return
end end
sendLocation(msg.chat.id, coords.lat, coords.lon, msg.message_id) bindings.sendLocation(self, msg.chat.id, coords.lat, coords.lon, msg.message_id)
end end
return { return gMaps
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,25 +1,30 @@
local command = 'google <query>' local gSearch = {}
local doc = [[```
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
gSearch.command = 'google <query>'
gSearch.doc = [[```
/google <query> /google <query>
Returns four (if group) or eight (if private message) results from Google. Safe search is enabled by default, use "/gnsfw" to disable it. Returns four (if group) or eight (if private message) results from Google. Safe search is enabled by default, use "/gnsfw" to disable it.
Alias: /g Alias: /g
```]] ```]]
local triggers = { function gSearch:init()
'^/g[@'..bot.username..']*$', gSearch.triggers = utilities.triggers(self.info.username):t('g', true):t('google', true):t('gnsfw', true).table
'^/g[@'..bot.username..']* ', end
'^/google[@'..bot.username..']*',
'^/gnsfw[@'..bot.username..']*'
}
local action = function(msg) function gSearch:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, gSearch.doc, true, msg.message_id, true)
return return
end end
end end
@ -40,22 +45,22 @@ local action = function(msg)
local jstr, res = HTTPS.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if not jdat.responseData then if not jdat.responseData then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
if not jdat.responseData.results[1] then if not jdat.responseData.results[1] then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
local output = '*Google results for* _' .. input .. '_ *:*\n' local output = '*Google results for* _' .. input .. '_ *:*\n'
for i,v in ipairs(jdat.responseData.results) do for i,_ in ipairs(jdat.responseData.results) do
local title = jdat.responseData.results[i].titleNoFormatting:gsub('%[.+%]', ''):gsub('&amp;', '&') local title = jdat.responseData.results[i].titleNoFormatting:gsub('%[.+%]', ''):gsub('&amp;', '&')
--[[ --[[
if title:len() > 48 then if title:len() > 48 then
@ -70,13 +75,8 @@ local action = function(msg)
end end
end end
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return gSearch
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -2,47 +2,54 @@
-- If you want to configure your own greetings, copy the following table -- If you want to configure your own greetings, copy the following table
-- (without the "config.") to your config.lua file. -- (without the "config.") to your config.lua file.
if not config.greetings then local greetings = {}
config.greetings = {
['Hello, #NAME.'] = { local bindings = require('bindings')
'hello', local utilities = require('utilities')
'hey',
'sup', function greetings:init()
'hi', if not self.config.greetings then
'good morning', self.config.greetings = {
'good day', ['Hello, #NAME.'] = {
'good afternoon', 'hello',
'good evening' 'hey',
}, 'sup',
['Goodbye, #NAME.'] = { 'hi',
'bye', 'good morning',
'later', 'good day',
'see ya', 'good afternoon',
'good night' 'good evening'
}, },
['Welcome back, #NAME.'] = { ['Goodbye, #NAME.'] = {
'i\'m home', 'bye',
'i\'m back' 'later',
}, 'see ya',
['You\'re welcome, #NAME.'] = { 'good night'
'thanks', },
'thank you' ['Welcome back, #NAME.'] = {
'i\'m home',
'i\'m back'
},
['You\'re welcome, #NAME.'] = {
'thanks',
'thank you'
}
} }
end
greetings.triggers = {
self.info.first_name:lower() .. '%p*$'
} }
end end
local triggers = { function greetings:action(msg)
bot.first_name .. '%p*$'
}
local action = function(msg) local nick = self.database.users[msg.from.id_str].nickname or msg.from.first_name
local nick = database.users[msg.from.id_str].nickname or msg.from.first_name for trigger,responses in pairs(self.config.greetings) do
for _,response in pairs(responses) do
for k,v in pairs(config.greetings) do if msg.text_lower:match(response..',? '..self.info.first_name:lower()) then
for key,val in pairs(v) do bindings.sendMessage(self, msg.chat.id, utilities.latcyr(trigger:gsub('#NAME', nick)))
if msg.text_lower:match(val..',? '..bot.first_name) then
sendMessage(msg.chat.id, latcyr(k:gsub('#NAME', nick)))
return return
end end
end end
@ -52,7 +59,4 @@ local action = function(msg)
end end
return { return greetings
action = action,
triggers = triggers
}

View File

@ -1,21 +1,27 @@
local command = 'hackernews' local hackernews = {}
local doc = [[```
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
hackernews.command = 'hackernews'
hackernews.doc = [[```
Returns four (if group) or eight (if private message) top stories from Hacker News. Returns four (if group) or eight (if private message) top stories from Hacker News.
Alias: /hn Alias: /hn
```]] ```]]
local triggers = { function hackernews:init()
'^/hackernews[@'..bot.username..']*', hackernews.triggers = utilities.triggers(self.info.username):t('hackernews', true):t('hn', true).table
'^/hn[@'..bot.username..']*' end
}
local action = function(msg) function hackernews:action(msg)
sendChatAction(msg.chat.id, 'typing') bindings.sendChatAction(self, msg.chat.id, 'typing')
local jstr, res = HTTPS.request('https://hacker-news.firebaseio.com/v0/topstories.json') local jstr, res = HTTPS.request('https://hacker-news.firebaseio.com/v0/topstories.json')
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
@ -31,7 +37,7 @@ local action = function(msg)
local res_url = 'https://hacker-news.firebaseio.com/v0/item/' .. jdat[i] .. '.json' local res_url = 'https://hacker-news.firebaseio.com/v0/item/' .. jdat[i] .. '.json'
jstr, res = HTTPS.request(res_url) jstr, res = HTTPS.request(res_url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local res_jdat = JSON.decode(jstr) local res_jdat = JSON.decode(jstr)
@ -41,7 +47,7 @@ local action = function(msg)
end end
local url = res_jdat.url local url = res_jdat.url
if not url then if not url then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
if url:find('%(') then if url:find('%(') then
@ -52,13 +58,8 @@ local action = function(msg)
end end
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return hackernews
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,48 +1,53 @@
-- Plugin for the Hearthstone database provided by hearthstonejson.com. -- Plugin for the Hearthstone database provided by hearthstonejson.com.
if not database.hearthstone or os.time() > database.hearthstone.expiration then local hearthstone = {}
print('Downloading Hearthstone database...') local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
-- This stuff doesn't play well with lua-sec. Disable it for now; hack in curl. function hearthstone:init()
--local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json') if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then
--if res ~= 200 then
--print('Error connecting to hearthstonejson.com.')
--print('hearthstone.lua will not be enabled.')
--return
--end
--local jdat = JSON.decode(jstr)
local s = io.popen('curl -s https://api.hearthstonejson.com/v1/latest/enUS/cards.json'):read('*all') print('Downloading Hearthstone database...')
local d = JSON.decode(s)
-- This stuff doesn't play well with lua-sec. Disable it for now; hack in curl.
--local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json')
--if res ~= 200 then
-- print('Error connecting to hearthstonejson.com.')
-- print('hearthstone.lua will not be enabled.')
-- return
--end
--local jdat = JSON.decode(jstr)
local s = io.popen('curl -s https://api.hearthstonejson.com/v1/latest/enUS/cards.json'):read('*all')
local d = JSON.decode(s)
if not d then
print('Error connecting to hearthstonejson.com.')
print('hearthstone.lua will not be enabled.')
return
end
self.database.hearthstone = d
self.database.hearthstone.expiration = os.time() + 600000
print('Download complete! It will be stored for a week.')
if not d then
print('Error connecting to hearthstonejson.com.')
print('hearthstone.lua will not be enabled.')
return
end end
database.hearthstone = d hearthstone.triggers = utilities.triggers(self.info.username):t('hearthstone', true):t('hs').table
database.hearthstone.expiration = os.time() + 600000
print('Download complete! It will be stored for a week.')
end end
local command = 'hearthstone <query>' hearthstone.command = 'hearthstone <query>'
local doc = [[``` hearthstone.doc = [[```
/hearthstone <query> /hearthstone <query>
Returns Hearthstone card info. Returns Hearthstone card info.
Alias: /hs Alias: /hs
```]] ```]]
local triggers = { local function format_card(card)
'^/hearthstone[@'..bot.username..']*',
'^/hs[@'..bot.username..']*$',
'^/hs[@'..bot.username..']* '
}
local format_card = function(card)
local ctype = card.type local ctype = card.type
if card.race then if card.race then
@ -73,6 +78,7 @@ local format_card = function(card)
stats = card.health .. 'h' stats = card.health .. 'h'
end end
-- unused?
local info = '' local info = ''
if card.text then if card.text then
info = card.text:gsub('</?.->',''):gsub('%$','') info = card.text:gsub('</?.->',''):gsub('%$','')
@ -97,34 +103,29 @@ local format_card = function(card)
end end
local action = function(msg) function hearthstone:action(msg)
local input = msg.text_lower:input() local input = utilities.input(msg.text_lower)
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, hearthstone.doc, true, msg.message_id, true)
return return
end end
local output = '' local output = ''
for k,v in pairs(database.hearthstone) do for _,v in pairs(self.database.hearthstone) do
if type(v) == 'table' and string.lower(v.name):match(input) then if type(v) == 'table' and string.lower(v.name):match(input) then
output = output .. format_card(v) .. '\n\n' output = output .. format_card(v) .. '\n\n'
end end
end end
output = output:trim() output = utilities.trim(output)
if output:len() == 0 then if output:len() == 0 then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end end
return { return hearthstone
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,54 +1,62 @@
-- This plugin should go at the end of your plugin list in -- This plugin should go at the end of your plugin list in
-- config.lua, but not after greetings.lua. -- config.lua, but not after greetings.lua.
local commandlist = {} local help = {}
for i,v in ipairs(plugins) do
if v.command then local bindings = require('bindings')
table.insert(commandlist, v.command) local utilities = require('utilities')
local help_text
function help:init()
local commandlist = {}
help_text = '*Available commands:*\n• /'
for _,plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
--help_text = help_text .. '\n• /' .. plugin.command:gsub('%[', '\\[')
end
end end
table.insert(commandlist, 'help [command]')
table.sort(commandlist)
help_text = help_text .. table.concat(commandlist, '\n• /') .. '\nArguments: <required> [optional]'
help_text = help_text:gsub('%[', '\\[')
help.triggers = utilities.triggers(self.info.username):t('help', true):t('h', true).table
end end
table.insert(commandlist, 'help [command]') function help:action(msg)
table.sort(commandlist)
local help_text = '*Available commands:*\n• /' .. table.concat(commandlist,'\n• /') .. '\nArguments: <required> [optional]' local input = utilities.input(msg.text_lower)
help_text = help_text:gsub('%[', '\\[')
local triggers = {
'^/help[@'..bot.username..']*',
'^/h[@'..bot.username..']*$'
}
local action = function(msg)
local input = msg.text_lower:input()
-- Attempts to send the help message via PM. -- Attempts to send the help message via PM.
-- If msg is from a group, it tells the group whether the PM was successful. -- If msg is from a group, it tells the group whether the PM was successful.
if not input then if not input then
local res = sendMessage(msg.from.id, help_text, true, nil, true) local res = bindings.sendMessage(self, msg.from.id, help_text, true, nil, true)
if not res then if not res then
sendReply(msg, 'Please message me privately for a list of commands.') bindings.sendReply(self, msg, 'Please message me privately for a list of commands.')
elseif msg.chat.type ~= 'private' then elseif msg.chat.type ~= 'private' then
sendReply(msg, 'I have sent you the requested information in a private message.') bindings.sendReply(self, msg, 'I have sent you the requested information in a private message.')
end end
return return
end end
for i,v in ipairs(plugins) do for _,plugin in ipairs(self.plugins) do
if v.command and get_word(v.command, 1) == input and v.doc then if plugin.command and utilities.get_word(plugin.command, 1) == input and plugin.doc then
local output = '*Help for* _' .. get_word(v.command, 1) .. '_ *:*\n' .. v.doc local output = '*Help for* _' .. utilities.get_word(plugin.command, 1) .. '_ *:*\n' .. plugin.doc
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
return return
end end
end end
sendReply(msg, 'Sorry, there is no help for that command.') bindings.sendReply(self, msg, 'Sorry, there is no help for that command.')
end end
return { return help
action = action,
triggers = triggers
}

View File

@ -1,21 +1,29 @@
local command = 'imdb <query>' local imdb = {}
local doc = [[```
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
imdb.command = 'imdb <query>'
imdb.doc = [[```
/imdb <query> /imdb <query>
Returns an IMDb entry. Returns an IMDb entry.
```]] ```]]
local triggers = { function imdb:init()
'^/imdb[@'..bot.username..']*' imdb.triggers = utilities.triggers(self.info.username):t('imdb', true).table
} end
local action = function(msg) function imdb:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, imdb.doc, true, msg.message_id, true)
return return
end end
end end
@ -24,14 +32,14 @@ local action = function(msg)
local jstr, res = HTTP.request(url) local jstr, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.Response ~= 'True' then if jdat.Response ~= 'True' then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
@ -40,13 +48,8 @@ local action = function(msg)
output = output .. '_' .. jdat.Plot .. '_\n' output = output .. '_' .. jdat.Plot .. '_\n'
output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')' output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')'
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return imdb
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,14 +1,23 @@
if not config.lastfm_api_key then local lastfm = {}
print('Missing config value: lastfm_api_key.')
print('lastfm.lua will not be enabled.')
return
end
local HTTP = require('socket.http') local HTTP = require('socket.http')
HTTP.TIMEOUT = 1 local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
local command = 'lastfm' function lastfm:init()
local doc = [[``` if not self.config.lastfm_api_key then
print('Missing config value: lastfm_api_key.')
print('lastfm.lua will not be enabled.')
return
end
lastfm.triggers = utilities.triggers(self.info.username):t('lastfm', true):t('np', true):t('fmset', true).table
end
bindings.command = 'lastfm'
bindings.doc = [[```
/np [username] /np [username]
Returns what you are or were last listening to. If you specify a username, info will be returned for that username. Returns what you are or were last listening to. If you specify a username, info will be returned for that username.
@ -16,66 +25,64 @@ Returns what you are or were last listening to. If you specify a username, info
Sets your last.fm username. Otherwise, /np will use your Telegram username. Use "/fmset --" to delete it. Sets your last.fm username. Otherwise, /np will use your Telegram username. Use "/fmset --" to delete it.
```]] ```]]
local triggers = { function lastfm:action(msg)
'^/lastfm[@'..bot.username..']*',
'^/np[@'..bot.username..']*',
'^/fmset[@'..bot.username..']*'
}
local action = function(msg) local input = utilities.input(msg.text)
local input = msg.text:input()
if string.match(msg.text, '^/lastfm') then if string.match(msg.text, '^/lastfm') then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
return return
elseif string.match(msg.text, '^/fmset') then elseif string.match(msg.text, '^/fmset') then
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
elseif input == '--' or input == '' then elseif input == '--' or input == '' then
database.users[msg.from.id_str].lastfm = nil self.database.users[msg.from.id_str].lastfm = nil
sendReply(msg, 'Your last.fm username has been forgotten.') bindings.sendReply(self, msg, 'Your last.fm username has been forgotten.')
else else
database.users[msg.from.id_str].lastfm = input self.database.users[msg.from.id_str].lastfm = input
sendReply(msg, 'Your last.fm username has been set to "' .. input .. '".') bindings.sendReply(self, msg, 'Your last.fm username has been set to "' .. input .. '".')
end end
return return
end 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=' .. self.config.lastfm_api_key .. '&user='
local username local username
local alert = '' local alert = ''
if input then if input then
username = input username = input
elseif database.users[msg.from.id_str].lastfm then elseif self.database.users[msg.from.id_str].lastfm then
username = database.users[msg.from.id_str].lastfm username = self.database.users[msg.from.id_str].lastfm
elseif msg.from.username then elseif msg.from.username then
username = msg.from.username username = msg.from.username
alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use /fmset <username>.' alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use /fmset <username>.'
database.users[msg.from.id_str].lastfm = username self.database.users[msg.from.id_str].lastfm = username
else else
sendReply(msg, 'Please specify your last.fm username or set it with /fmset.') bindings.sendReply(self, msg, 'Please specify your last.fm username or set it with /fmset.')
return return
end end
url = url .. URL.escape(username) url = url .. URL.escape(username)
jstr, res = HTTP.request(url) local jstr, res
utilities.with_http_timeout(
1, function ()
jstr, res = HTTP.request(url)
end)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.error then if jdat.error then
sendReply(msg, 'Please specify your last.fm username or set it with /fmset.') bindings.sendReply(self, msg, 'Please specify your last.fm username or set it with /fmset.')
return return
end end
local jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then if not jdat then
sendReply(msg, 'No history for this user.' .. alert) bindings.sendReply(self, msg, 'No history for this user.' .. alert)
return return
end end
@ -95,13 +102,8 @@ local action = function(msg)
end end
output = output .. title .. ' - ' .. artist .. alert output = output .. title .. ' - ' .. artist .. alert
sendMessage(msg.chat.id, output) bindings.sendMessage(self, msg.chat.id, output)
end end
return { return lastfm
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,9 +1,21 @@
if not database.librefm then local librefm = {}
database.librefm = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
function librefm:init()
if not self.database.librefm then
self.database.librefm = {}
end
librefm.triggers = utilities.triggers(self.info.username):t('librefm', true):t('lnp', true):t('lfmset', true)
end end
local command = 'librefm' librefm.command = 'librefm'
local doc = [[``` librefm.doc = [[```
/lnp [username] /lnp [username]
Returns what you are or were last listening to. If you specify a username, info will be returned for that username. Returns what you are or were last listening to. If you specify a username, info will be returned for that username.
@ -11,28 +23,22 @@ Returns what you are or were last listening to. If you specify a username, info
Sets your libre.fm username. Otherwise, /np will use your Telegram username. Use "/fmset -" to delete it. Sets your libre.fm username. Otherwise, /np will use your Telegram username. Use "/fmset -" to delete it.
```]] ```]]
local triggers = { function librefm:action(msg)
'^/librefm[@'..bot.username..']*',
'^/lnp[@'..bot.username..']*',
'^/lfmset[@'..bot.username..']*'
}
local action = function(msg) local input = utilities.input(msg.text)
local input = msg.text:input()
if string.match(msg.text, '^/librefm') then if string.match(msg.text, '^/librefm') then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, librefm.doc, true, msg.message_id, true)
return return
elseif string.match(msg.text, '^/lfmset') then elseif string.match(msg.text, '^/lfmset') then
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, librefm.doc, true, msg.message_id, true)
elseif input == '-' then elseif input == '-' then
database.librefm[msg.from.id_str] = nil self.database.librefm[msg.from.id_str] = nil
sendReply(msg, 'Your libre.fm username has been forgotten.') bindings.sendReply(self, msg, 'Your libre.fm username has been forgotten.')
else else
database.librefm[msg.from.id_str] = input self.database.librefm[msg.from.id_str] = input
sendReply(msg, 'Your libre.fm username has been set to "' .. input .. '".') bindings.sendReply(self, msg, 'Your libre.fm username has been set to "' .. input .. '".')
end end
return return
end end
@ -43,34 +49,34 @@ local action = function(msg)
local alert = '' local alert = ''
if input then if input then
username = input username = input
elseif database.librefm[msg.from.id_str] then elseif self.database.librefm[msg.from.id_str] then
username = database.librefm[msg.from.id_str] username = self.database.librefm[msg.from.id_str]
elseif msg.from.username then elseif msg.from.username then
username = msg.from.username username = msg.from.username
alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use /lfmset <username>.' alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use /lfmset <username>.'
database.librefm[msg.from.id_str] = username self.database.librefm[msg.from.id_str] = username
else else
sendReply(msg, 'Please specify your libre.fm username or set it with /lfmset.') bindings.sendReply(self, msg, 'Please specify your libre.fm username or set it with /lfmset.')
return return
end end
url = url .. URL.escape(username) url = url .. URL.escape(username)
jstr, res = HTTPS.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.error then if jdat.error then
sendReply(msg, 'Please specify your libre.fm username or set it with /lfmset.') bindings.sendReply(self, msg, 'Please specify your libre.fm username or set it with /lfmset.')
return return
end end
local jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then if not jdat then
sendReply(msg, 'No history for this user.' .. alert) bindings.sendReply(self, msg, 'No history for this user.' .. alert)
return return
end end
@ -90,13 +96,8 @@ local action = function(msg)
end end
output = output .. title .. ' - ' .. artist .. alert output = output .. title .. ' - ' .. artist .. alert
sendMessage(msg.chat.id, output) bindings.sendMessage(self, msg.chat.id, output)
end end
return { return librefm
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,20 +1,25 @@
local triggers = { local luarun = {}
'^/lua[@'..bot.username..']*'
}
local action = function(msg) local bindings = require('bindings')
local utilities = require('utilities')
if msg.from.id ~= config.admin then function luarun:init()
luarun.triggers = utilities.triggers(self.info.username):t('lua', true).table
end
function luarun:action(msg)
if msg.from.id ~= self.config.admin then
return return
end end
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
sendReply(msg, 'Please enter a string to load.') bindings.sendReply(self, msg, 'Please enter a string to load.')
return return
end end
local output = loadstring(input)() local output = loadstring('local bindings = require(\'bindings\'); local utilities = require(\'utilities\'); return function (self, msg) '..input..' end')()(self, msg)
if output == nil then if output == nil then
output = 'Done!' output = 'Done!'
elseif type(output) == 'table' then elseif type(output) == 'table' then
@ -22,12 +27,9 @@ local action = function(msg)
else else
output = '```\n' .. tostring(output) .. '\n```' output = '```\n' .. tostring(output) .. '\n```'
end end
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end end
return { return luarun
action = action,
triggers = triggers
}

View File

@ -1,16 +1,20 @@
local triggers = { local me = {}
'^/me',
'^/me@'..bot.username
}
local action = function(msg) local bindings = require('bindings')
local utilities = require('utilities')
local target = database.users[msg.from.id_str] function me:init()
me.triggers = utilities.triggers(self.info.username):t('me', true).table
end
if msg.from.id == config.admin and (msg.reply_to_message or msg.text:input()) then function me:action(msg)
target = user_from_message(msg)
local target = self.database.users[msg.from.id_str]
if msg.from.id == self.config.admin and (msg.reply_to_message or utilities.input(msg.text)) then
target = utilities.user_from_message(self, msg)
if target.err then if target.err then
sendReply(msg, target.err) bindings.sendReply(self, msg, target.err)
return return
end end
end end
@ -19,11 +23,8 @@ local action = function(msg)
for k,v in pairs(target) do for k,v in pairs(target) do
output = output .. '*' .. k .. ':* `' .. tostring(v) .. '`\n' output = output .. '*' .. k .. ':* `' .. tostring(v) .. '`\n'
end end
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return me
triggers = triggers,
action = action
}

View File

@ -1,299 +0,0 @@
-- Moderation for Liberbot groups.
-- The bot must be made an admin.
-- Put this near the top, after blacklist.
-- If you want to enable antisquig, put that at the top, before blacklist.
if not database.moderation then
database.moderation = {}
end
local antisquig = {}
local commands = {
['^/modhelp[@'..bot.username..']*$'] = function(msg)
if not database.moderation[msg.chat.id_str] then return end
local output = [[
*Users:*
/modlist - List the moderators and administrators of this group.
*Moderators:*
/modkick - Kick a user from this group.
/modban - Ban a user from this group.
*Administrators:*
/modadd - Add this group to the moderation system.
/modrem - Remove this group from the moderation system.
/modprom - Promote a user to a moderator.
/moddem - Demote a moderator to a user.
/modcast - Send a broadcast to every moderated group.
]]
output = output:gsub('\t', '')
sendMessage(msg.chat.id, output, true, nil, true)
end,
['^/modlist[@'..bot.username..']*$'] = function(msg)
if not database.moderation[msg.chat.id_str] then return end
local output = ''
for k,v in pairs(database.moderation[msg.chat.id_str]) do
output = output .. '' .. v .. ' (' .. k .. ')\n'
end
if output ~= '' then
output = '*Moderators for* _' .. msg.chat.title .. '_ *:*\n' .. output
end
output = output .. '*Administrators for* _' .. config.moderation.realm_name .. '_ *:*\n'
for k,v in pairs(config.moderation.admins) do
output = output .. '' .. v .. ' (' .. k .. ')\n'
end
sendMessage(msg.chat.id, output, true, nil, true)
end,
['^/modcast[@'..bot.username..']*'] = function(msg)
local output = msg.text:input()
if not output then
return 'You must include a message.'
end
if msg.chat.id ~= config.moderation.admin_group then
return 'This command must be run in the administration group.'
end
if not config.moderation.admins[msg.from.id_str] then
return config.moderation.errors.not_admin
end
output = '*Admin Broadcast:*\n' .. output
for k,v in pairs(database.moderation) do
sendMessage(k, output, true, nil, true)
end
return 'Your broadcast has been sent.'
end,
['^/modadd[@'..bot.username..']*$'] = function(msg)
if not config.moderation.admins[msg.from.id_str] then
return config.moderation.errors.not_admin
end
if database.moderation[msg.chat.id_str] then
return 'I am already moderating this group.'
end
database.moderation[msg.chat.id_str] = {}
return 'I am now moderating this group.'
end,
['^/modrem[@'..bot.username..']*$'] = function(msg)
if not config.moderation.admins[msg.from.id_str] then
return config.moderation.errors.not_admin
end
if not database.moderation[msg.chat.id_str] then
return config.moderation.errors.moderation
end
database.moderation[msg.chat.id_str] = nil
return 'I am no longer moderating this group.'
end,
['^/modprom[@'..bot.username..']*$'] = function(msg)
if not database.moderation[msg.chat.id_str] then return end
if not config.moderation.admins[msg.from.id_str] then
return config.moderation.errors.not_admin
end
if not msg.reply_to_message then
return 'Promotions must be done via reply.'
end
local modid = tostring(msg.reply_to_message.from.id)
local modname = msg.reply_to_message.from.first_name
if config.moderation.admins[modid] then
return modname .. ' is already an administrator.'
end
if database.moderation[msg.chat.id_str][modid] then
return modname .. ' is already a moderator.'
end
database.moderation[msg.chat.id_str][modid] = modname
return modname .. ' is now a moderator.'
end,
['^/moddem[@'..bot.username..']*'] = function(msg)
if not database.moderation[msg.chat.id_str] then return end
if not config.moderation.admins[msg.from.id_str] then
return config.moderation.errors.not_admin
end
local modid = msg.text:input()
if not modid then
if msg.reply_to_message then
modid = tostring(msg.reply_to_message.from.id)
else
return 'Demotions must be done via reply or specification of a moderator\'s ID.'
end
end
if config.moderation.admins[modid] then
return config.moderation.admins[modid] .. ' is an administrator.'
end
if not database.moderation[msg.chat.id_str][modid] then
return 'User is not a moderator.'
end
local modname = database.moderation[msg.chat.id_str][modid]
database.moderation[msg.chat.id_str][modid] = nil
return modname .. ' is no longer a moderator.'
end,
['/modkick[@'..bot.username..']*'] = function(msg)
if not database.moderation[msg.chat.id_str] then return end
if not database.moderation[msg.chat.id_str][msg.from.id_str] then
if not config.moderation.admins[msg.from.id_str] then
return config.moderation.errors.not_mod
end
end
local userid = msg.text:input()
local usernm = userid
if msg.reply_to_message then
userid = tostring(msg.reply_to_message.from.id)
usernm = msg.reply_to_message.from.first_name
end
if not userid then
return 'Kicks must be done via reply or specification of a user/bot\'s ID or username.'
end
if database.moderation[msg.chat.id_str][userid] or config.moderation.admins[userid] then
return 'You cannot kick a moderator.'
end
sendMessage(config.moderation.admin_group, '/kick ' .. userid .. ' from ' .. math.abs(msg.chat.id))
sendMessage(config.moderation.admin_group, usernm .. ' kicked from ' .. msg.chat.title .. ' by ' .. msg.from.first_name .. '.')
end,
['^/modban[@'..bot.username..']*'] = function(msg)
if not database.moderation[msg.chat.id_str] then return end
if not database.moderation[msg.chat.id_str][msg.from.id_str] then
if not config.moderation.admins[msg.from.id_str] then
return config.moderation.errors.not_mod
end
end
local userid = msg.text:input()
local usernm = userid
if msg.reply_to_message then
userid = tostring(msg.reply_to_message.from.id)
usernm = msg.reply_to_message.from.first_name
end
if not userid then
return 'Kicks must be done via reply or specification of a user/bot\'s ID or username.'
end
if database.moderation[msg.chat.id_str][userid] or config.moderation.admins[userid] then
return 'You cannot ban a moderator.'
end
sendMessage(config.moderation.admin_group, '/ban ' .. userid .. ' from ' .. math.abs(msg.chat.id))
sendMessage(config.moderation.admin_group, usernm .. ' banned from ' .. msg.chat.title .. ' by ' .. msg.from.first_name .. '.')
end
}
if config.moderation.antisquig then
commands['[\216-\219][\128-\191]'] = function(msg)
if not database.moderation[msg.chat.id_str] then return true end
if config.moderation.admins[msg.from.id_str] then return true end
if database.moderation[msg.chat.id_str][msg.from.id_str] then return true end
if antisquig[msg.from.id] == true then
return
end
antisquig[msg.from.id] = true
sendReply(msg, config.moderation.errors.antisquig)
sendMessage(config.moderation.admin_group, '/kick ' .. msg.from.id .. ' from ' .. math.abs(msg.chat.id))
sendMessage(config.moderation.admin_group, 'ANTISQUIG: ' .. msg.from.first_name .. ' kicked from ' .. msg.chat.title .. '.')
end
end
local triggers = {}
for k,v in pairs(commands) do
table.insert(triggers, k)
end
local action = function(msg)
for k,v in pairs(commands) do
if string.match(msg.text_lower, k) then
local output = v(msg)
if output == true then
return true
elseif output then
sendReply(msg, output)
end
return
end
end
return true
end
-- When a user is kicked for squiggles, his ID is added to this table.
-- That user will not be kicked again as long as his ID is in the table.
-- The table is emptied every five seconds.
-- Thus the bot will not spam the group or admin group when a user posts more than one infringing messages.
local cron = function()
antisquig = {}
end
return {
action = action,
triggers = triggers,
cron = cron
}

View File

@ -1,18 +1,23 @@
local command = 'nick <nickname>' local nick = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
nick.command = 'nick <nickname>'
nick.doc = [[```
/nick <nickname> /nick <nickname>
Set your nickname. Use "/nick --" to delete it. Set your nickname. Use "/nick --" to delete it.
```]] ```]]
local triggers = { function nick:init()
'^/nick[@'..bot.username..']*' nick.triggers = utilities.triggers(self.info.username):t('nick', true).table
} end
local action = function(msg) function nick:action(msg)
local target = msg.from local target = msg.from
if msg.from.id == config.admin and msg.reply_to_message then if msg.from.id == self.config.admin and msg.reply_to_message then
target = msg.reply_to_message.from target = msg.reply_to_message.from
target.id_str = tostring(target.id) target.id_str = tostring(target.id)
target.name = target.first_name target.name = target.first_name
@ -22,30 +27,25 @@ local action = function(msg)
end end
local output local output
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if database.users[target.id_str].nickname then if self.database.users[target.id_str].nickname then
output = target.name .. '\'s nickname is "' .. database.users[target.id_str].nickname .. '".' output = target.name .. '\'s nickname is "' .. self.database.users[target.id_str].nickname .. '".'
else else
output = target.name .. ' currently has no nickname.' output = target.name .. ' currently has no nickname.'
end end
elseif string.len(input) > 32 then elseif string.len(input) > 32 then
output = 'The character limit for nicknames is 32.' output = 'The character limit for nicknames is 32.'
elseif input == '--' or input == '' then elseif input == '--' or input == '' then
database.users[target.id_str].nickname = nil self.database.users[target.id_str].nickname = nil
output = target.name .. '\'s nickname has been deleted.' output = target.name .. '\'s nickname has been deleted.'
else else
database.users[target.id_str].nickname = input self.database.users[target.id_str].nickname = input
output = target.name .. '\'s nickname has been set to "' .. input .. '".' output = target.name .. '\'s nickname has been set to "' .. input .. '".'
end end
sendReply(msg, output) bindings.sendReply(self, msg, output)
end end
return { return nick
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,11 +1,15 @@
-- Shout-out to Kenny, as I didn't want to write this until -- Shout-out to Kenny, as I didn't want to write this until
-- he upset himself over the very thought of me doing so. -- he upset himself over the very thought of me doing so.
local triggers = { local patterns = {}
local bindings = require('bindings')
patterns.triggers = {
'^/?s/.-/.-/?$' '^/?s/.-/.-/?$'
} }
local action = function(msg) function patterns:action(msg)
if not msg.reply_to_message then return end if not msg.reply_to_message then return end
local output = msg.reply_to_message.text or '' local output = msg.reply_to_message.text or ''
@ -13,11 +17,8 @@ local action = function(msg)
if not m2 then return true end if not m2 then return true end
output = output:gsub(m1, m2) output = output:gsub(m1, m2)
output = 'Did you mean:\n"' .. output:sub(1, 4000) .. '"' output = 'Did you mean:\n"' .. output:sub(1, 4000) .. '"'
sendReply(msg.reply_to_message, output) bindings.sendReply(self, msg.reply_to_message, output)
end end
return { return patterns
triggers = triggers,
action = action
}

View File

@ -1,16 +1,17 @@
-- Actually the simplest plugin ever! -- Actually the simplest plugin ever!
local triggers = { local ping = {}
'^/ping[@'..bot.username..']*',
'^/annyong[@'..bot.username..']*'
}
local action = function(msg) local utilities = require('utilities')
local output = msg.text_lower:match('^/ping') and 'Pong!' or 'Annyong.' local bindings = require('bindings')
sendMessage(msg.chat.id, output)
function ping:init()
ping.triggers = utilities.triggers(self.info.username):t('ping'):t('annyong').table
end end
return { function ping:action(msg)
action = action, local output = msg.text_lower:match('^/ping') and 'Pong!' or 'Annyong.'
triggers = triggers bindings.sendMessage(self, msg.chat.id, output)
} end
return ping

View File

@ -1,23 +1,29 @@
local command = 'pokedex <query>' local pokedex = {}
local doc = [[```
local HTTP = require('socket.http')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
pokedex.command = 'pokedex <query>'
pokedex.doc = [[```
/pokedex <query> /pokedex <query>
Returns a Pokedex entry from pokeapi.co. Returns a Pokedex entry from pokeapi.co.
Alias: /dex Alias: /dex
```]] ```]]
local triggers = { function pokedex:init()
'^/pokedex[@'..bot.username..']*', pokedex.triggers = utilities.triggers(self.info.username):t('pokedex', true):t('dex', true).table
'^/dex[@'..bot.username..']*' end
}
local action = function(msg) function pokedex:action(msg)
local input = msg.text_lower:input() local input = utilities.input(msg.text_lower)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, pokedex.doc, true, msg.message_id, true)
return return
end end
end end
@ -27,23 +33,23 @@ local action = function(msg)
local dex_url = url .. '/api/v1/pokemon/' .. input local dex_url = url .. '/api/v1/pokemon/' .. input
local dex_jstr, res = HTTP.request(dex_url) local dex_jstr, res = HTTP.request(dex_url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local dex_jdat = JSON.decode(dex_jstr) local dex_jdat = JSON.decode(dex_jstr)
local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri
local desc_jstr, res = HTTP.request(desc_url) local desc_jstr, _ = HTTP.request(desc_url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local desc_jdat = JSON.decode(desc_jstr) local desc_jdat = JSON.decode(desc_jstr)
local poke_type local poke_type
for i,v in ipairs(dex_jdat.types) do for _,v in ipairs(dex_jdat.types) do
local type_name = v.name:gsub("^%l", string.upper) local type_name = v.name:gsub("^%l", string.upper)
if not poke_type then if not poke_type then
poke_type = type_name poke_type = type_name
@ -56,13 +62,8 @@ local action = function(msg)
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') .. '_'
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return pokedex
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,47 +1,48 @@
local command = 'preview <link>' local preview = {}
local doc = [[```
local HTTP = require('socket.http')
local bindings = require('bindings')
local utilities = require('utilities')
preview.command = 'preview <link>'
preview.doc = [[```
/preview <link> /preview <link>
Returns a full-message, "unlinked" preview. Returns a full-message, "unlinked" preview.
```]] ```]]
local triggers = { function preview:init()
'^/preview' preview.triggers = utilities.triggers(self.info.username):t('preview', true).table
} end
local action = function(msg) function preview:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
sendMessage(msg.chat.id, doc, true, nil, true) bindings.sendMessage(self, msg.chat.id, preview.doc, true, nil, true)
return return
end end
input = get_word(input, 1) input = utilities.get_word(input, 1)
if not input:match('^https?://.+') then if not input:match('^https?://.+') then
input = 'http://' .. input input = 'http://' .. input
end end
local res = HTTP.request(input) local res = HTTP.request(input)
if not res then if not res then
sendReply(msg, 'Please provide a valid link.') bindings.sendReply(self, msg, 'Please provide a valid link.')
return return
end end
if res:len() == 0 then if res:len() == 0 then
sendReply(msg, 'Sorry, the link you provided is not letting us make a preview.') bindings.sendReply(self, msg, 'Sorry, the link you provided is not letting us make a preview.')
return return
end end
-- Invisible zero-width, non-joiner. -- Invisible zero-width, non-joiner.
local output = '[](' .. input .. ')' local output = '[](' .. input .. ')'
sendMessage(msg.chat.id, output, false, nil, true) bindings.sendMessage(self, msg.chat.id, output, false, nil, true)
end end
return { return preview
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,9 +1,14 @@
local command = 'pun' local pun = {}
local doc = '`Returns a pun.`'
local triggers = { local bindings = require('bindings')
'^/pun[@'..bot.username..']*' local utilities = require('utilities')
}
pun.command = 'pun'
pun.doc = '`Returns a pun.`'
function pun:init()
pun.triggers = utilities.triggers(self.info.username):t('pun').table
end
local puns = { local puns = {
"The person who invented the door-knock won the No-bell prize.", "The person who invented the door-knock won the No-bell prize.",
@ -129,15 +134,10 @@ local puns = {
"In democracy, it's your vote that counts. In feudalism, it's your count that votes." "In democracy, it's your vote that counts. In feudalism, it's your count that votes."
} }
local action = function(msg) function pun:action(msg)
sendReply(msg, puns[math.random(#puns)]) bindings.sendReply(self, msg, puns[math.random(#puns)])
end end
return { return pun
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -3,38 +3,47 @@
-- You must never restructure. You must never disable this plugin. -- You must never restructure. You must never disable this plugin.
-- ~ Drew, creator, a year later. -- ~ Drew, creator, a year later.
local command = 'reactions' local reactions = {}
local doc = '`Returns a list of "reaction" emoticon commands.`'
local triggers = { local bindings = require('bindings')
['¯\\_(ツ)_/¯'] = '/shrug', local utilities = require('utilities')
['( ͡° ͜ʖ ͡°)'] = '/lenny',
['(╯°□°)╯︵ ┻━┻'] = '/flip', reactions.command = 'reactions'
[' o'] = '/homo', reactions.doc = '`Returns a list of "reaction" emoticon commands.`'
['ಠ_ಠ'] = '/look',
['SHOTS FIRED'] = '/shots?' local mapping = {
['shrug'] = '¯\\_(ツ)_/¯',
['lenny'] = '( ͡° ͜ʖ ͡°)',
['flip'] = '(╯°□°)╯︵ ┻━┻',
['homo'] = ' o',
['look'] = 'ಠ_ಠ',
['shots?'] = 'SHOTS FIRED'
} }
-- Generate a "help" message triggered by "/reactions". local help
local help = 'Reactions:\n'
for k,v in pairs(triggers) do
help = help .. '' .. v:gsub('%a%?', '') .. ': ' .. k .. '\n'
v = v .. '[@'..bot.username..']*'
end
triggers[help] = '^/reactions$'
local action = function(msg) function reactions:init()
for k,v in pairs(triggers) do -- Generate a "help" message triggered by "/reactions".
if string.match(msg.text_lower, v) then help = 'Reactions:\n'
sendMessage(msg.chat.id, k) reactions.triggers = utilities.triggers(self.info.username):t('reactions').table
for trigger,reaction in pairs(mapping) do
help = help .. '' .. utilities.INVOCATION_PATTERN..trigger .. trigger:gsub('.%?', '') .. ': ' .. reaction .. '\n'
table.insert(reactions.triggers, utilities.INVOCATION_PATTERN..trigger)
table.insert(reactions.triggers, utilities.INVOCATION_PATTERN..trigger..'@'..self.info.username:lower())
end
end
function reactions:action(msg)
if string.match(msg.text_lower, utilities.INVOCATION_PATTERN..'reactions') then
bindings.sendMessage(self, msg.chat.id, help)
return
end
for trigger,reaction in pairs(mapping) do
if string.match(msg.text_lower, utilities.INVOCATION_PATTERN..trigger) then
bindings.sendMessage(self, msg.chat.id, reaction)
return return
end end
end end
end end
return { return reactions
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,26 +1,31 @@
local command = 'reddit [r/subreddit | query]' local reddit = {}
local doc = [[```
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
reddit.command = 'reddit [r/subreddit | query]'
reddit.doc = [[```
/reddit [r/subreddit | query] /reddit [r/subreddit | query]
Returns the four (if group) or eight (if private message) top posts for the given subreddit or query, or from the frontpage. Returns the four (if group) or eight (if private message) top posts for the given subreddit or query, or from the frontpage.
Aliases: /r, /r/[subreddit] Aliases: /r, /r/[subreddit]
```]] ```]]
local triggers = { function reddit:init()
'^/reddit[@'..bot.username..']*', reddit.triggers = utilities.triggers(self.info.username, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table
'^/r[@'..bot.username..']*$', end
'^/r[@'..bot.username..']* ',
'^/r/'
}
local action = function(msg) function reddit:action(msg)
msg.text_lower = msg.text_lower:gsub('/r/', '/r r/') msg.text_lower = msg.text_lower:gsub('/r/', '/r r/')
local input = msg.text_lower:input() local input
if msg.text_lower:match('^/r/') then if msg.text_lower:match('^/r/') then
msg.text_lower = msg.text_lower:gsub('/r/', '/r r/') msg.text_lower = msg.text_lower:gsub('/r/', '/r r/')
input = get_word(msg.text_lower, 1) input = utilities.get_word(msg.text_lower, 1)
else else
input = msg.text_lower:input() input = utilities.input(msg.text_lower)
end end
local url local url
@ -45,18 +50,18 @@ local action = function(msg)
local jstr, res = HTTP.request(url) local jstr, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if #jdat.data.children == 0 then if #jdat.data.children == 0 then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
local output = '' local output = ''
for i,v in ipairs(jdat.data.children) do for _,v in ipairs(jdat.data.children) do
local title = v.data.title:gsub('%[', '('):gsub('%]', ')'):gsub('&amp;', '&') local title = v.data.title:gsub('%[', '('):gsub('%]', ')'):gsub('&amp;', '&')
if title:len() > 48 then if title:len() > 48 then
title = title:sub(1,45) .. '...' title = title:sub(1,45) .. '...'
@ -73,13 +78,8 @@ local action = function(msg)
output = source .. output output = source .. output
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return reddit
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,26 +1,31 @@
database.reminders = database.reminders or {} local remind = {}
local command = 'remind <duration> <message>' local bindings = require('bindings')
local doc = [[``` local utilities = require('utilities')
/remind <duration> <message>
Repeats a message after a duration of time, in minutes.
```]]
local triggers = { remind.command = 'remind <duration> <message>'
'^/remind' remind.doc = [[```
} /remind <duration> <message>
Repeats a message after a duration of time, in minutes.
```]]
local action = function(msg) function remind:init()
self.database.reminders = self.database.reminders or {}
remind.triggers = utilities.triggers(self.info.username):t('remind', true).table
end
function remind:action(msg)
-- Ensure there are arguments. If not, send doc. -- Ensure there are arguments. If not, send doc.
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, remind.doc, true, msg.message_id, true)
return return
end end
-- Ensure first arg is a number. If not, send doc. -- Ensure first arg is a number. If not, send doc.
local duration = get_word(input, 1) local duration = utilities.get_word(input, 1)
if not tonumber(duration) then if not tonumber(duration) then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, remind.doc, true, msg.message_id, true)
return return
end end
-- Duration must be between one minute and one year (approximately). -- Duration must be between one minute and one year (approximately).
@ -31,19 +36,19 @@ local action = function(msg)
duration = 526000 duration = 526000
end end
-- Ensure there is a second arg. -- Ensure there is a second arg.
local message = input:input() local message = utilities.input(input)
if not message then if not message then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, remind.doc, true, msg.message_id, true)
return return
end end
-- Make a database entry for the group/user if one does not exist. -- Make a database entry for the group/user if one does not exist.
database.reminders[msg.chat.id_str] = database.reminders[msg.chat.id_str] or {} self.database.reminders[msg.chat.id_str] = self.database.reminders[msg.chat.id_str] or {}
-- Limit group reminders to 10 and private reminders to 50. -- Limit group reminders to 10 and private reminders to 50.
if msg.chat.type ~= 'private' and table_size(database.reminders[msg.chat.id_str]) > 9 then if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 9 then
sendReply(msg, 'Sorry, this group already has ten reminders.') bindings.sendReply(self, msg, 'Sorry, this group already has ten reminders.')
return return
elseif msg.chat.type == 'private' and table_size(database.reminders[msg.chat.id_str]) > 49 then elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then
sendReply(msg, 'Sorry, you already have fifty reminders.') bindings.sendReply(msg, 'Sorry, you already have fifty reminders.')
return return
end end
-- Put together the reminder with the expiration, message, and message to reply to. -- Put together the reminder with the expiration, message, and message to reply to.
@ -51,32 +56,30 @@ local action = function(msg)
time = os.time() + duration * 60, time = os.time() + duration * 60,
message = message message = message
} }
table.insert(database.reminders[msg.chat.id_str], reminder) table.insert(self.database.reminders[msg.chat.id_str], reminder)
local output = 'I will remind you in ' .. duration local output = 'I will remind you in ' .. duration
if duration == 1 then if duration == 1 then
output = output .. ' minute!' output = output .. ' minute!'
else else
output = output .. ' minutes!' output = output .. ' minutes!'
end end
sendReply(msg, output) bindings.sendReply(self, msg, output)
end end
local cron = function() function remind:cron()
local time = os.time() local time = os.time()
-- Iterate over the group entries in the reminders database. -- Iterate over the group entries in the reminders database.
for chat_id, group in pairs(database.reminders) do for chat_id, group in pairs(self.database.reminders) do
local new_group = {} local new_group = {}
-- Iterate over each reminder. -- Iterate over each reminder.
for i, reminder in ipairs(group) do for _, reminder in ipairs(group) do
-- If the reminder is past-due, send it and nullify it. -- If the reminder is past-due, send it and nullify it.
-- Otherwise, add it to the replacement table. -- Otherwise, add it to the replacement table.
if time > reminder.time then if time > reminder.time then
local output = '*Reminder:*\n"' .. markdown_escape(reminder.message) .. '"' local output = '*Reminder:*\n"' .. utilities.md_escape(reminder.message) .. '"'
local res = sendMessage(chat_id, output, true, nil, true) local res = bindings.sendMessage(self, chat_id, output, true, nil, true)
-- If the message fails to send, save it for later. -- If the message fails to send, save it for later.
if res then if not res then
reminder = nil
else
table.insert(new_group, reminder) table.insert(new_group, reminder)
end end
else else
@ -84,19 +87,12 @@ local cron = function()
end end
end end
-- Nullify the original table and replace it with the new one. -- Nullify the original table and replace it with the new one.
group = nil self.database.reminders[chat_id] = new_group
database.reminders[chat_id] = new_group
-- Nullify the table if it is empty. -- Nullify the table if it is empty.
if #new_group == 0 then if #new_group == 0 then
database.reminders[chat_id] = nil self.database.reminders[chat_id] = nil
end end
end end
end end
return { return remind
action = action,
triggers = triggers,
cron = cron,
command = command,
doc = doc
}

View File

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

View File

@ -1,18 +1,23 @@
local triggers = { local shell = {}
'^/run[@'..bot.username..']*'
}
local action = function(msg) local bindings = require('bindings')
local utilities = require('utilities')
if msg.from.id ~= config.admin then function shell:init()
shell.triggers = utilities.triggers(self.info.username):t('run', true).table
end
function shell:action(msg)
if msg.from.id ~= self.config.admin then
return return
end end
local input = msg.text:input() local input = utilities.input(msg.text)
input = input:gsub('', '--') input = input:gsub('', '--')
if not input then if not input then
sendReply(msg, 'Please specify a command to run.') bindings.sendReply(self, msg, 'Please specify a command to run.')
return return
end end
@ -22,11 +27,8 @@ local action = function(msg)
else else
output = '```\n' .. output .. '\n```' output = '```\n' .. output .. '\n```'
end end
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end end
return { return shell
action = action,
triggers = triggers
}

View File

@ -1,22 +1,27 @@
local command = 'shout <text>' local shout = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
shout.command = 'shout <text>'
shout.doc = [[```
/shout <text> /shout <text>
Shouts something. Shouts something.
```]] ```]]
local triggers = { function shout:init()
'^/shout[@'..bot.username..']*' shout.triggers = utilities.triggers(self.info.username):t('shout', true).table
} end
local action = function(msg) function shout:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, shout.doc, true, msg.message_id, true)
return return
end end
input = input:trim() input = utilities.trim(input)
if input:len() > 20 then if input:len() > 20 then
input = input:sub(1,20) input = input:sub(1,20)
@ -31,20 +36,15 @@ local action = function(msg)
output = output .. '\n' output = output .. '\n'
for match in input:sub(2):gmatch('([%z\1-\127\194-\244][\128-\191]*)') do for match in input:sub(2):gmatch('([%z\1-\127\194-\244][\128-\191]*)') do
local spacing = '' local spacing = ''
for i = 1, inc do for _ = 1, inc do
spacing = spacing .. ' ' spacing = spacing .. ' '
end end
inc = inc + 1 inc = inc + 1
output = output .. match .. ' ' .. spacing .. match .. '\n' output = output .. match .. ' ' .. spacing .. match .. '\n'
end end
output = '```\n' .. output:trim() .. '\n```' output = '```\n' .. utilities.trim(output) .. '\n```'
sendMessage(msg.chat.id, output, true, false, true) bindings.sendMessage(self, msg.chat.id, output, true, false, true)
end end
return { return shout
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,12 +1,17 @@
local command = 'slap [target]' local slap = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
slap.command = 'slap [target]'
slap.doc = [[```
/slap [target] /slap [target]
Slap somebody. Slap somebody.
```]] ```]]
local triggers = { function slap:init()
'^/slap[@'..bot.username..']*' slap.triggers = utilities.triggers(self.info.username):t('slap', true).table
} end
local slaps = { local slaps = {
'VICTIM was shot by VICTOR.', 'VICTIM was shot by VICTOR.',
@ -92,40 +97,35 @@ local slaps = {
'Cowards die many times before their death. VICTIM never tasted death but once.' 'Cowards die many times before their death. VICTIM never tasted death but once.'
} }
local action = function(msg) function slap:action(msg)
local victim = msg.text:input() local victim = utilities.input(msg.text)
if msg.reply_to_message then if msg.reply_to_message then
if database.users[tostring(msg.reply_to_message.from.id)].nickname then if self.database.users[tostring(msg.reply_to_message.from.id)].nickname then
victim = database.users[tostring(msg.reply_to_message.from.id)].nickname victim = self.database.users[tostring(msg.reply_to_message.from.id)].nickname
else else
victim = msg.reply_to_message.from.first_name victim = msg.reply_to_message.from.first_name
end end
end end
local victor = msg.from.first_name local victor = msg.from.first_name
if database.users[msg.from.id_str].nickname then if self.database.users[msg.from.id_str].nickname then
victor = database.users[msg.from.id_str].nickname victor = self.database.users[msg.from.id_str].nickname
end end
if not victim then if not victim then
victim = victor victim = victor
victor = bot.first_name victor = self.info.first_name
end end
local message = slaps[math.random(#slaps)] local message = slaps[math.random(#slaps)]
message = message:gsub('VICTIM', victim) message = message:gsub('VICTIM', victim)
message = message:gsub('VICTOR', victor) message = message:gsub('VICTOR', victor)
message = latcyr(message) message = utilities.latcyr(message)
sendMessage(msg.chat.id, message) bindings.sendMessage(self, msg.chat.id, message)
end end
return { return slap
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,55 +1,63 @@
local command = 'time <location>' local time = {}
local doc = [[```
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
time.command = 'time <location>'
time.doc = [[```
/time <location> /time <location>
Returns the time, date, and timezone for the given location. Returns the time, date, and timezone for the given location.
```]] ```]]
local triggers = { function time:init()
'^/time[@'..bot.username..']*' time.triggers = utilities.triggers(self.info.username):t('time', true).table
} end
local action = function(msg) function time:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, time.doc, true, msg.message_id, true)
return return
end end
end end
local coords = get_coords(input) local coords = utilities.get_coords(self, input)
if type(coords) == 'string' then if type(coords) == 'string' then
sendReply(msg, coords) bindings.sendReply(self, msg, coords)
return return
end end
local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..os.time() local now = os.time()
local utc = os.time(os.date("!*t", now))
local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..utc
local jstr, res = HTTPS.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
local timestamp = os.time() + jdat.rawOffset + jdat.dstOffset + config.time_offset local timestamp = now + jdat.rawOffset + jdat.dstOffset
local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600 local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600
if utcoff == math.abs(utcoff) then if utcoff == math.abs(utcoff) then
utcoff = '+' .. utcoff utcoff = '+'.. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end end
local output = '`' .. os.date('%I:%M %p\n', timestamp) .. os.date('%A, %B %d, %Y\n', timestamp) .. jdat.timeZoneName .. ' (UTC' .. utcoff .. ')' .. '`' local output = os.date('!%I:%M %p\n', timestamp) .. os.date('!%A, %B %d, %Y\n', timestamp) .. jdat.timeZoneName .. ' (UTC' .. utcoff .. ')'
output = '```\n' .. output .. '\n```'
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendReply(self, msg, output, true)
end end
return { return time
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,50 +1,52 @@
local command = 'translate [text]' local translate = {}
local doc = [[```
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
translate.command = 'translate [text]'
translate.doc = [[```
/translate [text] /translate [text]
Translates input or the replied-to message into the bot's language. Translates input or the replied-to message into the bot's language.
```]] ```]]
local triggers = { function translate:init()
'^/translate[@'..bot.username..']*', translate.triggers = utilities.triggers(self.info.username):t('translate', true):t('tl', true).table
'^/tl[@'..bot.username..']*' end
}
local action = function(msg) function translate:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, translate.doc, true, msg.message_id, true)
return return
end end
end end
local url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=' .. URL.escape(input) local url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. self.config.yandex_key .. '&lang=' .. self.config.lang .. '&text=' .. URL.escape(input)
local str, res = HTTPS.request(url) local str, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(str) local jdat = JSON.decode(str)
if jdat.code ~= 200 then if jdat.code ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local output = jdat.text[1] local output = jdat.text[1]
output = '*Translation:*\n"' .. markdown_escape(output) .. '"' output = '*Translation:*\n"' .. utilities.md_escape(output) .. '"'
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendReply(self, msg.reply_to_message or msg, output, true)
end end
return { return translate
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,25 +1,30 @@
local command = 'urbandictionary <query>' local urbandictionary = {}
local doc = [[```
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
urbandictionary.command = 'urbandictionary <query>'
urbandictionary.doc = [[```
/urbandictionary <query> /urbandictionary <query>
Returns a definition from Urban Dictionary. Returns a definition from Urban Dictionary.
Aliases: /ud, /urban Aliases: /ud, /urban
```]] ```]]
local triggers = { function urbandictionary:init()
'^/urbandictionary[@'..bot.username..']*', urbandictionary.triggers = utilities.triggers(self.info.username):t('urbandictionary', true):t('ud', true):t('urban', true).table
'^/ud[@'..bot.username..']*$', end
'^/ud[@'..bot.username..']* ',
'^/urban[@'..bot.username..']*'
}
local action = function(msg) function urbandictionary:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, urbandictionary.doc, true, msg.message_id, true)
return return
end end
end end
@ -28,30 +33,25 @@ local action = function(msg)
local jstr, res = HTTP.request(url) local jstr, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.result_type == "no_results" then if jdat.result_type == "no_results" then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
local output = '*' .. jdat.list[1].word .. '*\n\n' .. jdat.list[1].definition:trim() local output = '*' .. jdat.list[1].word .. '*\n\n' .. utilities.trim(jdat.list[1].definition)
if string.len(jdat.list[1].example) > 0 then if string.len(jdat.list[1].example) > 0 then
output = output .. '_\n\n' .. jdat.list[1].example:trim() .. '_' output = output .. '_\n\n' .. utilities.trim(jdat.list[1].example) .. '_'
end end
output = output:gsub('%[', ''):gsub('%]', '') output = output:gsub('%[', ''):gsub('%]', '')
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return urbandictionary
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,48 +1,55 @@
if not config.owm_api_key then local weather = {}
print('Missing config value: owm_api_key.')
print('weather.lua will not be enabled.') local HTTP = require('socket.http')
return local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
function weather:init()
if not self.config.owm_api_key then
print('Missing config value: owm_api_key.')
print('weather.lua will not be enabled.')
return
end
weather.triggers = utilities.triggers(self.info.username):t('weather', true).table
end end
local command = 'weather <location>' weather.command = 'weather <location>'
local doc = [[``` weather.doc = [[```
/weather <location> /weather <location>
Returns the current weather conditions for a given location. Returns the current weather conditions for a given location.
```]] ```]]
local triggers = { function weather:action(msg)
'^/weather[@'..bot.username..']*'
}
local action = function(msg) local input = utilities.input(msg.text)
local input = msg.text:input()
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, weather.doc, true, msg.message_id, true)
return return
end end
end end
local coords = get_coords(input) local coords = utilities.get_coords(self, input)
if type(coords) == 'string' then if type(coords) == 'string' then
sendReply(msg, coords) bindings.sendReply(self, msg, coords)
return return
end 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=' .. self.config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon
local jstr, res = HTTP.request(url) local jstr, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.cod ~= 200 then if jdat.cod ~= 200 then
sendReply(msg, 'Error: City not found.') bindings.sendReply(self, msg, 'Error: City not found.')
return return
end end
@ -50,13 +57,8 @@ local action = function(msg)
local fahrenheit = string.format('%.2f', celsius * (9/5) + 32) local fahrenheit = string.format('%.2f', celsius * (9/5) + 32)
local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`' local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`'
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendReply(self, msg, output, true)
end end
return { return weather
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,18 +1,23 @@
local command = 'whoami' local whoami = {}
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
whoami.command = 'whoami'
whoami.doc = [[```
Returns user and chat info for you or the replied-to message. Returns user and chat info for you or the replied-to message.
Alias: /who Alias: /who
```]] ```]]
local triggers = { function whoami:init()
'^/who[ami]*[@'..bot.username..']*$' whoami.triggers = utilities.triggers(self.info.username):t('who', true):t('whoami').table
} end
local action = function(msg) function whoami:action(msg)
if msg.reply_to_message then if msg.reply_to_message then
msg = msg.reply_to_message msg = msg.reply_to_message
msg.from.name = build_name(msg.from.first_name, msg.from.last_name) msg.from.name = utilities.build_name(msg.from.first_name, msg.from.last_name)
end end
local chat_id = math.abs(msg.chat.id) local chat_id = math.abs(msg.chat.id)
@ -22,7 +27,7 @@ local action = function(msg)
local user = 'You are @%s, also known as *%s* `[%s]`' local user = 'You are @%s, also known as *%s* `[%s]`'
if msg.from.username then if msg.from.username then
user = user:format(markdown_escape(msg.from.username), msg.from.name, msg.from.id) user = user:format(utilities.markdown_escape(msg.from.username), msg.from.name, msg.from.id)
else else
user = 'You are *%s* `[%s]`,' user = 'You are *%s* `[%s]`,'
user = user:format(msg.from.name, msg.from.id) user = user:format(msg.from.name, msg.from.id)
@ -30,9 +35,9 @@ local action = function(msg)
local group = '@%s, also known as *%s* `[%s]`.' local group = '@%s, also known as *%s* `[%s]`.'
if msg.chat.type == 'private' then if msg.chat.type == 'private' then
group = group:format(markdown_escape(bot.username), bot.first_name, bot.id) group = group:format(utilities.markdown_escape(self.info.username), self.info.first_name, self.info.id)
elseif msg.chat.username then elseif msg.chat.username then
group = group:format(markdown_escape(msg.chat.username), msg.chat.title, chat_id) group = group:format(utilities.markdown_escape(msg.chat.username), msg.chat.title, chat_id)
else else
group = '*%s* `[%s]`.' group = '*%s* `[%s]`.'
group = group:format(msg.chat.title, chat_id) group = group:format(msg.chat.title, chat_id)
@ -40,13 +45,8 @@ local action = function(msg)
local output = user .. ', and you are messaging ' .. group local output = user .. ', and you are messaging ' .. group
sendMessage(msg.chat.id, output, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end end
return { return whoami
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,25 +1,30 @@
local command = 'wikipedia <query>' local wikipedia = {}
local doc = [[```
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
wikipedia.command = 'wikipedia <query>'
wikipedia.doc = [[```
/wikipedia <query> /wikipedia <query>
Returns an article from Wikipedia. Returns an article from Wikipedia.
Aliases: /w, /wiki Aliases: /w, /wiki
```]] ```]]
local triggers = { function wikipedia:init()
'^/wikipedia[@'..bot.username..']*', wikipedia.triggers = utilities.triggers(self.info.username):t('wikipedia', true):t('wiki', true):t('w', true).table
'^/wiki[@'..bot.username..']*', end
'^/w[@'..bot.username..']*$',
'^/w[@'..bot.username..']* '
}
local action = function(msg) function wikipedia:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, wikipedia.doc, true, msg.message_id, true)
return return
end end
end end
@ -29,17 +34,17 @@ local action = function(msg)
local jstr, res = HTTPS.request(gurl .. URL.escape(input)) local jstr, res = HTTPS.request(gurl .. URL.escape(input))
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if not jdat.responseData then if not jdat.responseData then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
if not jdat.responseData.results[1] then if not jdat.responseData.results[1] then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
@ -49,18 +54,18 @@ local action = function(msg)
-- 'https://en.wikipedia.org/wiki/':len() == 30 -- 'https://en.wikipedia.org/wiki/':len() == 30
jstr, res = HTTPS.request(wurl .. url:sub(31)) jstr, res = HTTPS.request(wurl .. url:sub(31))
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.error.connection) bindings.sendReply(self, msg, self.config.error.connection)
return return
end end
local _
local text = JSON.decode(jstr).query.pages local text = JSON.decode(jstr).query.pages
for k,v in pairs(text) do _, text = next(text)
text = v.extract
break -- Seriously, there's probably a way more elegant solution.
end
if not text then if not text then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
else
text = text.extract
end end
text = text:gsub('</?.->', '') text = text:gsub('</?.->', '')
@ -78,13 +83,8 @@ local action = function(msg)
output = output .. '[Read more.](' .. url .. ')' output = output .. '[Read more.](' .. url .. ')'
end end
sendMessage(msg.chat.id, output, true, nil, true) bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end end
return { return wikipedia
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,20 +1,29 @@
local command = 'xkcd [query]' local xkcd = {}
local doc = [[```
local HTTP = require('socket.http')
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
xkcd.command = 'xkcd [query]'
xkcd.doc = [[```
/xkcd [query] /xkcd [query]
Returns an xkcd strip and its alt text. If there is no query, it will be randomized. Returns an xkcd strip and its alt text. If there is no query, it will be randomized.
```]] ```]]
local triggers = { function xkcd:init()
'^/xkcd[@'..bot.username..']*' xkcd.triggers = utilities.triggers(self.info.username):t('xkcd', true).table
} end
local action = function(msg) function xkcd:action(msg)
local input = msg.text:input() local input = utilities.input(msg.text)
local jstr, res = HTTP.request('http://xkcd.com/info.0.json') local jstr, res = HTTP.request('http://xkcd.com/info.0.json')
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
@ -23,14 +32,14 @@ local action = function(msg)
if input then if input then
local url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&safe=active&q=site%3axkcd%2ecom%20' .. URL.escape(input) local url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&safe=active&q=site%3axkcd%2ecom%20' .. URL.escape(input)
local jstr, res = HTTPS.request(url) jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if #jdat.responseData.results == 0 then if #jdat.responseData.results == 0 then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
res_url = jdat.responseData.results[1].url .. 'info.0.json' res_url = jdat.responseData.results[1].url .. 'info.0.json'
@ -38,22 +47,17 @@ local action = function(msg)
res_url = 'http://xkcd.com/' .. math.random(latest) .. '/info.0.json' res_url = 'http://xkcd.com/' .. math.random(latest) .. '/info.0.json'
end end
local jstr, res = HTTP.request(res_url) jstr, res = HTTP.request(res_url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
local output = '[' .. jdat.num .. '](' .. jdat.img .. ')\n' .. jdat.alt local output = '[' .. jdat.num .. '](' .. jdat.img .. ')\n' .. jdat.alt
sendMessage(msg.chat.id, output, false, nil, true) bindings.sendMessage(self, msg.chat.id, output, false, nil, true)
end end
return { return xkcd
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,47 +1,53 @@
-- Thanks to @TiagoDanin for writing the original plugin. -- Thanks to @TiagoDanin for writing the original plugin.
if not config.google_api_key then local youtube = {}
print('Missing config value: google_api_key.')
print('youtube.lua will not be enabled.') local HTTPS = require('ssl.https')
return local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
function youtube:init()
if not self.config.google_api_key then
print('Missing config value: google_api_key.')
print('youtube.lua will not be enabled.')
return
end
youtube.triggers = utilities.triggers(self.info.username):t('youtube', true):t('yt', true).table
end end
local command = 'youtube <query>' youtube.command = 'youtube <query>'
local doc = [[``` youtube.doc = [[```
/youtube <query> /youtube <query>
Returns the top result from YouTube. Returns the top result from YouTube.
Alias: /yt Alias: /yt
```]] ```]]
local triggers = { function youtube:action(msg)
'^/youtube[@'..bot.username..']*',
'^/yt[@'..bot.username..']*$',
'^/yt[@'..bot.username..']* '
}
local action = function(msg) local input = utilities.input(msg.text)
local input = msg.text:input()
if not input then if not input then
if msg.reply_to_message and msg.reply_to_message.text then if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text input = msg.reply_to_message.text
else else
sendMessage(msg.chat.id, doc, true, msg.message_id, true) bindings.sendMessage(self, msg.chat.id, youtube.doc, true, msg.message_id, true)
return return
end end
end 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=' .. self.config.google_api_key .. '&type=video&part=snippet&maxResults=4&q=' .. URL.escape(input)
local jstr, res = HTTPS.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
sendReply(msg, config.errors.connection) bindings.sendReply(self, msg, self.config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.pageInfo.totalResults == 0 then if jdat.pageInfo.totalResults == 0 then
sendReply(msg, config.errors.results) bindings.sendReply(self, msg, self.config.errors.results)
return return
end end
@ -50,13 +56,8 @@ local action = function(msg)
vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','') vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','')
local output = '[' .. vid_title .. '](' .. vid_url .. ')' local output = '[' .. vid_title .. '](' .. vid_url .. ')'
sendMessage(msg.chat.id, output, false, nil, true) bindings.sendMessage(self, msg.chat.id, output, false, nil, true)
end end
return { return youtube
action = action,
triggers = triggers,
doc = doc,
command = command
}

View File

@ -1,12 +1,17 @@
-- utilities.lua -- utilities.lua
-- Functions shared among plugins. -- Functions shared among plugins.
HTTP = HTTP or require('socket.http') local utilities = {}
HTTPS = HTTPS or require('ssl.https')
JSON = JSON or require('cjson') local HTTP = require('socket.http')
local ltn12 = require('ltn12')
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
-- get the indexed word in a string -- get the indexed word in a string
get_word = function(s, i) function utilities.get_word(s, i)
s = s or '' s = s or ''
i = i or 1 i = i or 1
@ -22,25 +27,25 @@ end
-- Like get_word(), but better. -- Like get_word(), but better.
-- Returns the actual index. -- Returns the actual index.
function string:index() function utilities.index(s)
local t = {} local t = {}
for w in self:gmatch('%g+') do for w in s:gmatch('%g+') do
table.insert(t, w) table.insert(t, w)
end end
return t return t
end end
-- Returns the string after the first space. -- Returns the string after the first space.
function string:input() function utilities.input(s)
if not self:find(' ') then if not s:find(' ') then
return false return false
end end
return self:sub(self:find(' ')+1) return s:sub(s:find(' ')+1)
end end
-- I swear, I copied this from PIL, not yago! :) -- I swear, I copied this from PIL, not yago! :)
function string:trim() -- Trims whitespace from a string. function utilities.trim(str) -- Trims whitespace from a string.
local s = self:gsub('^%s*(.-)%s*$', '%1') local s = str:gsub('^%s*(.-)%s*$', '%1')
return s return s
end end
@ -74,7 +79,7 @@ local lc_list = {
} }
-- Replaces letters with corresponding Cyrillic characters. -- Replaces letters with corresponding Cyrillic characters.
latcyr = function(str) function utilities.latcyr(str)
for k,v in pairs(lc_list) do for k,v in pairs(lc_list) do
str = str:gsub(k, v) str = str:gsub(k, v)
end end
@ -82,7 +87,7 @@ latcyr = function(str)
end end
-- Loads a JSON file as a table. -- Loads a JSON file as a table.
load_data = function(filename) function utilities.load_data(filename)
local f = io.open(filename) local f = io.open(filename)
if not f then if not f then
@ -97,7 +102,7 @@ load_data = function(filename)
end end
-- Saves a table to a JSON file. -- Saves a table to a JSON file.
save_data = function(filename, data) function utilities.save_data(filename, data)
local s = JSON.encode(data) local s = JSON.encode(data)
local f = io.open(filename, 'w') local f = io.open(filename, 'w')
@ -107,18 +112,18 @@ save_data = function(filename, data)
end end
-- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua. -- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua.
get_coords = function(input) function utilities:get_coords(input)
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) local jstr, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
return config.errors.connection return self.config.errors.connection
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.status == 'ZERO_RESULTS' then if jdat.status == 'ZERO_RESULTS' then
return config.errors.results return self.config.errors.results
end end
return { return {
@ -129,10 +134,10 @@ get_coords = function(input)
end end
-- Get the number of values in a key/value table. -- Get the number of values in a key/value table.
table_size = function(tab) function utilities.table_size(tab)
local i = 0 local i = 0
for k,v in pairs(tab) do for _,_ in pairs(tab) do
i = i + 1 i = i + 1
end end
return i return i
@ -140,7 +145,7 @@ table_size = function(tab)
end end
-- Just an easy way to get a user's full name. -- Just an easy way to get a user's full name.
build_name = function(first, last) function utilities.build_name(first, last)
if last then if last then
return first .. ' ' .. last return first .. ' ' .. last
else else
@ -148,10 +153,10 @@ build_name = function(first, last)
end end
end end
resolve_username = function(input) function utilities:resolve_username(input)
input = input:gsub('^@', '') input = input:gsub('^@', '')
for k,v in pairs(database.users) do for _,v in pairs(self.database.users) do
if v.username and v.username:lower() == input:lower() then if v.username and v.username:lower() == input:lower() then
return v return v
end end
@ -159,22 +164,23 @@ resolve_username = function(input)
end end
user_from_message = function(msg) function utilities:user_from_message(msg)
local input = msg.text_lower:input() local input = utilities.input(msg.text_lower)
local target = {} local target = {}
if msg.reply_to_message then if msg.reply_to_message then
print('reply')
target = msg.reply_to_message.from target = msg.reply_to_message.from
elseif input and tonumber(input) then elseif input and tonumber(input) then
target.id = tonumber(input) target.id = tonumber(input)
if database.users[input] then if self.database.users[input] then
for k,v in pairs(database.users[input]) do for k,v in pairs(self.database.users[input]) do
target[k] = v target[k] = v
end end
end end
elseif input and input:match('^@') then elseif input and input:match('^@') then
local uname = input:gsub('^@', '') local uname = input:gsub('^@', '')
for k,v in pairs(database.users) do for _,v in pairs(self.database.users) do
if v.username and uname == v.username:lower() then if v.username and uname == v.username:lower() then
for key, val in pairs(v) do for key, val in pairs(v) do
target[key] = val target[key] = val
@ -194,21 +200,21 @@ user_from_message = function(msg)
if not target.first_name then target.first_name = 'User' end if not target.first_name then target.first_name = 'User' end
target.name = build_name(target.first_name, target.last_name) target.name = utilities.build_name(target.first_name, target.last_name)
return target return target
end end
handle_exception = function(err, message) function utilities:handle_exception(err, message)
if not err then err = '' end if not err then err = '' end
local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. bot.username .. ': ' .. err .. '\n' .. message .. '\n' local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. self.info.username .. ': ' .. err .. '\n' .. message .. '\n'
if config.log_chat then if self.config.log_chat then
output = '```' .. output .. '```' output = '```' .. output .. '```'
sendMessage(config.log_chat, output, true, nil, true) bindings.sendMessage(self, self.config.log_chat, output, true, nil, true)
else else
print(output) print(output)
end end
@ -217,7 +223,7 @@ end
-- Okay, this one I actually did copy from yagop. -- Okay, this one I actually did copy from yagop.
-- https://github.com/yagop/telegram-bot/blob/master/bot/utils.lua -- https://github.com/yagop/telegram-bot/blob/master/bot/utils.lua
download_file = function(url, filename) function utilities.download_file(url, filename)
local respbody = {} local respbody = {}
local options = { local options = {
@ -226,7 +232,7 @@ download_file = function(url, filename)
redirect = true redirect = true
} }
local response = nil local response
if url:match('^https') then if url:match('^https') then
options.redirect = false options.redirect = false
@ -251,7 +257,7 @@ download_file = function(url, filename)
end end
markdown_escape = function(text) function utilities.markdown_escape(text)
text = text:gsub('_', '\\_') text = text:gsub('_', '\\_')
text = text:gsub('%[', '\\[') text = text:gsub('%[', '\\[')
@ -262,43 +268,74 @@ markdown_escape = function(text)
end end
function string:md_escape() utilities.md_escape = utilities.markdown_escape
local text = self
text = text:gsub('_', '\\_') utilities.INVOCATION_PATTERN = '/'
text = text:gsub('%[', '\\[')
text = text:gsub('%]', '\\]') utilities.triggers_meta = {}
text = text:gsub('%*', '\\*') utilities.triggers_meta.__index = utilities.triggers_meta
text = text:gsub('`', '\\`') function utilities.triggers_meta:t(pattern, has_args)
return text local username = self.username:lower()
table.insert(self.table, '^'..utilities.INVOCATION_PATTERN..pattern..'$')
table.insert(self.table, '^'..utilities.INVOCATION_PATTERN..pattern..'@'..username..'$')
if has_args then
table.insert(self.table, '^'..utilities.INVOCATION_PATTERN..pattern..'%s+[^%s]*')
table.insert(self.table, '^'..utilities.INVOCATION_PATTERN..pattern..'@'..username..'%s+[^%s]*')
end
return self
end end
enrich_user = function(user) function utilities.triggers(username, trigger_table)
local self = setmetatable({}, utilities.triggers_meta)
self.username = username
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
end
function utilities.enrich_user(user)
user.id_str = tostring(user.id) user.id_str = tostring(user.id)
user.name = build_name(user.first_name, user.last_name) user.name = utilities.build_name(user.first_name, user.last_name)
return user return user
end end
enrich_message = function(msg) function utilities.enrich_message(msg)
if not msg.text then msg.text = msg.caption or '' end if not msg.text then msg.text = msg.caption or '' end
msg.text_lower = msg.text:lower() msg.text_lower = msg.text:lower()
msg.from = enrich_user(msg.from) msg.from = utilities.enrich_user(msg.from)
msg.chat.id_str = tostring(msg.chat.id) msg.chat.id_str = tostring(msg.chat.id)
if msg.reply_to_message then if msg.reply_to_message then
if not msg.reply_to_message.text then if not msg.reply_to_message.text then
msg.reply_to_message.text = msg.reply_to_message.caption or '' msg.reply_to_message.text = msg.reply_to_message.caption or ''
end end
msg.reply_to_message.text_lower = msg.reply_to_message.text:lower() msg.reply_to_message.text_lower = msg.reply_to_message.text:lower()
msg.reply_to_message.from = enrich_user(msg.reply_to_message.from) msg.reply_to_message.from = utilities.enrich_user(msg.reply_to_message.from)
msg.reply_to_message.chat.id_str = tostring(msg.reply_to_message.chat.id) msg.reply_to_message.chat.id_str = tostring(msg.reply_to_message.chat.id)
end end
if msg.forward_from then if msg.forward_from then
msg.forward_from = enrich_user(msg.forward_from) msg.forward_from = utilities.enrich_user(msg.forward_from)
end end
if msg.new_chat_participant then if msg.new_chat_participant then
msg.new_chat_participant = enrich_user(msg.new_chat_participant) msg.new_chat_participant = utilities.enrich_user(msg.new_chat_participant)
end end
if msg.left_chat_participant then if msg.left_chat_participant then
msg.left_chat_participant = enrich_user(msg.left_chat_participant) msg.left_chat_participant = utilities.enrich_user(msg.left_chat_participant)
end end
return msg return msg
end end
function utilities.pretty_float(x)
if x % 1 == 0 then
return tostring(math.floor(x))
else
return tostring(x)
end
end
return utilities