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) |
## 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`:
@ -26,7 +26,6 @@ Clone the repository and set the following values in `config.lua`:
Optionally:
- `time_offset` as the difference, in seconds, of your system clock to UTC.
- `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.

View File

@ -2,17 +2,13 @@
-- Bindings for the Telegram bot API.
-- https://core.telegram.org/bots/api
assert(HTTPS)
assert(JSON)
assert(URL)
local bindings = {}
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
error('You did not set your bot token in config.lua!')
end
sendRequest = function(url)
function bindings.sendRequest(url)
local dat, res = HTTPS.request(url)
@ -28,28 +24,28 @@ sendRequest = function(url)
end
getMe = function()
function bindings:getMe()
local url = BASE_URL .. '/getMe'
return sendRequest(url)
local url = self.BASE_URL .. '/getMe'
return bindings.sendRequest(url)
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
url = url .. '&offset=' .. offset
end
return sendRequest(url)
return bindings.sendRequest(url)
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
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'
end
return sendRequest(url)
return bindings.sendRequest(url)
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
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
local url = BASE_URL .. '/sendChatAction?chat_id=' .. chat_id .. '&action=' .. action
return sendRequest(url)
local url = self.BASE_URL .. '/sendChatAction?chat_id=' .. chat_id .. '&action=' .. action
return bindings.sendRequest(url)
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 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
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'
end
return sendRequest(url)
return bindings.sendRequest(url)
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 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
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'
end
return sendRequest(url)
return bindings.sendRequest(url)
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
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'
end
return sendRequest(url)
return bindings.sendRequest(url)
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
url = url .. '&disable_notification=true'
end
return sendRequest(url)
return bindings.sendRequest(url)
end
kickChatMember = function(chat_id, user_id)
local url = BASE_URL .. '/kickChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id
return sendRequest(url)
function bindings:kickChatMember(chat_id, user_id)
local url = self.BASE_URL .. '/kickChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id
return bindings.sendRequest(url)
end
unbanChatMember = function(chat_id, user_id)
local url = BASE_URL .. '/unbanChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id
return sendRequest(url)
function bindings:unbanChatMember(chat_id, user_id)
local url = self.BASE_URL .. '/unbanChatMember?chat_id=' .. chat_id .. '&user_id=' .. user_id
return bindings.sendRequest(url)
end
-- 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
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'
end
return sendRequest(url)
return bindings.sendRequest(url)
end
curlRequest = function(curl_command)
function bindings.curlRequest(curl_command)
-- Use at your own risk. Will not check for success.
io.popen(curl_command)
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 .. '"'
@ -216,13 +212,13 @@ sendPhoto = function(chat_id, photo, caption, reply_to_message_id, disable_notif
curl_command = curl_command .. ' -F "disable_notification=true"'
end
return curlRequest(curl_command)
return bindings.curlRequest(curl_command)
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 .. '"'
@ -234,13 +230,13 @@ sendDocument = function(chat_id, document, reply_to_message_id, disable_notifica
curl_command = curl_command .. ' -F "disable_notification=true"'
end
return curlRequest(curl_command)
return bindings.curlRequest(curl_command)
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 .. '"'
@ -252,13 +248,13 @@ sendSticker = function(chat_id, sticker, reply_to_message_id, disable_notificati
curl_command = curl_command .. ' -F "disable_notification=true"'
end
return curlRequest(curl_command)
return bindings.curlRequest(curl_command)
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 .. '"'
@ -282,13 +278,13 @@ sendAudio = function(chat_id, audio, reply_to_message_id, duration, performer, t
curl_command = curl_command .. ' -F "disable_notification=true"'
end
return curlRequest(curl_command)
return bindings.curlRequest(curl_command)
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 .. '"'
@ -308,13 +304,13 @@ sendVideo = function(chat_id, video, reply_to_message_id, duration, caption, dis
curl_command = curl_command .. ' -F "disable_notification=true"'
end
return curlRequest(curl_command)
return bindings.curlRequest(curl_command)
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 .. '"'
@ -330,6 +326,8 @@ sendVoice = function(chat_id, voice, reply_to_message_id, duration, disable_noti
curl_command = curl_command .. ' -F "disable_notification=true"'
end
return curlRequest(curl_command)
return bindings.curlRequest(curl_command)
end
return bindings

114
bot.lua
View File

@ -1,77 +1,85 @@
HTTP = require('socket.http')
HTTPS = require('ssl.https')
URL = require('socket.url')
JSON = require('cjson')
local bot = {}
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.
dofile('bindings.lua') -- Load Telegram bindings.
dofile('utilities.lua') -- Load miscellaneous and cross-plugin functions.
function bot:init() -- The function run when the bot is started or reloaded.
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.
repeat bot = getMe() until bot
bot = bot.result
repeat self.info = bindings.getMe(self) until self.info
self.info = self.info.result
-- Load the "database"! ;)
if not database then
database = load_data(bot.username..'.db')
if not self.database then
self.database = utilities.load_data(self.info.username..'.db')
end
plugins = {} -- Load plugins.
for i,v in ipairs(config.plugins) do
local p = dofile('plugins/'..v)
table.insert(plugins, p)
self.plugins = {} -- Load plugins.
for _,v in ipairs(self.config.plugins) do
local p = require('plugins.'..v)
table.insert(self.plugins, p)
if p.init then p.init(self) 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. :)
math.randomseed(os.time())
math.random()
last_update = last_update or 0 -- Set loop variables: Update offset,
last_cron = 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.
database.users = database.users or {} -- Table to cache userdata.
database.users[tostring(bot.id)] = bot
self.last_update = self.last_update or 0 -- Set loop variables: Update offset,
self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job,
self.is_started = true -- and whether or not the bot should be running.
self.database.users = self.database.users or {} -- Table to cache userdata.
self.database.users[tostring(self.info.id)] = self.info
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.
if not database.users[tostring(msg.from.id)] then
database.users[tostring(msg.from.id)] = {}
if not self.database.users[tostring(msg.from.id)] then
self.database.users[tostring(msg.from.id)] = {}
end
-- Clear things that no longer exist.
database.users[tostring(msg.from.id)].username = nil
database.users[tostring(msg.from.id)].last_name = nil
self.database.users[tostring(msg.from.id)].username = nil
self.database.users[tostring(msg.from.id)].last_name = nil
-- Wee.
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
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
msg.text = '/' .. msg.text:input()
msg.text = '/' .. utilities.input(msg.text)
msg.text_lower = msg.text:lower()
end
for i,v in ipairs(plugins) do
for k,w in pairs(v.triggers) do
for _,v in ipairs(self.plugins) do
for _,w in pairs(v.triggers) do
if string.match(msg.text:lower(), w) then
local success, result = pcall(function()
return v.action(msg)
return v.action(self, msg)
end)
if not success then
sendReply(msg, 'Sorry, an unexpected error occurred.')
handle_exception(result, msg.from.id .. ': ' .. msg.text)
bindings.sendReply(self, msg, 'Sorry, an unexpected error occurred.')
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text)
return
end
-- If the action returns a table, make that table msg.
@ -87,28 +95,31 @@ on_msg_receive = function(msg) -- The fn run whenever a message is received.
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
local res = bindings.getUpdates(self, self.last_update+1) -- Get the latest updates!
if res then
for i,v in ipairs(res.result) do -- Go through every new message.
last_update = v.update_id
on_msg_receive(v.message)
for _,v in ipairs(res.result) do -- Go through every new message.
self.last_update = v.update_id
bot.on_msg_receive(self, v.message)
end
else
print(config.errors.connection)
print(self.config.errors.connection)
end
end
if last_cron ~= os.date('%M') then -- Run cron jobs every minute.
last_cron = os.date('%M')
save_data(bot.username..'.db', database) -- Save the database.
for i,v in ipairs(plugins) do
if self.last_cron ~= os.date('%M') then -- Run cron jobs every minute.
self.last_cron = os.date('%M')
utilities.save_data(self.info.username..'.db', self.database) -- Save the database.
for i,v in ipairs(self.plugins) do
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
handle_exception(err, 'CRON: ' .. i)
utilities.handle_exception(self, err, 'CRON: ' .. i)
end
end
end
@ -117,5 +128,8 @@ while is_started do -- Start a loop while the bot should be running.
end
-- Save the database before exiting.
save_data(bot.username..'.db', database)
utilities.save_data(self.info.username..'.db', self.database)
print('Halted.')
end
return bot

View File

@ -4,8 +4,6 @@ return {
bot_api_key = '',
-- Your Telegram ID.
admin = 00000000,
-- Differences, in seconds, between your time and UTC.
time_offset = 0,
-- Two-letter language code.
lang = 'en',
-- 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.
'control.lua',
'blacklist.lua',
'about.lua',
'ping.lua',
'whoami.lua',
'nick.lua',
'echo.lua',
'gSearch.lua',
'gMaps.lua',
'wikipedia.lua',
'hackernews.lua',
'imdb.lua',
'calc.lua',
'urbandictionary.lua',
'time.lua',
'eightball.lua',
'dice.lua',
'reddit.lua',
'xkcd.lua',
'slap.lua',
'commit.lua',
'pun.lua',
'currency.lua',
'cats.lua',
'shout.lua',
'patterns.lua',
'control',
'blacklist',
'about',
'ping',
'whoami',
'nick',
'echo',
'gSearch',
'gMaps',
'wikipedia',
'hackernews',
'imdb',
'calc',
'urbandictionary',
'time',
'eightball',
'dice',
'reddit',
'xkcd',
'slap',
'commit',
'pun',
'currency',
'cats',
'shout',
'patterns',
-- Put new plugins above this line.
'help.lua',
'greetings.lua'
'help',
'greetings'
}
}

View File

@ -1,7 +1,7 @@
#!/bin/sh
while true; do
lua bot.lua
lua main.lua
echo 'otouto has stopped. ^C to exit.'
sleep 5s
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 doc = '`Returns information about the bot.`'
local about = {}
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
-- other plugins.
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)
or msg.text_lower:match('^/about[@'..bot.username..']*')
if (msg.new_chat_participant and msg.new_chat_participant.id == self.info.id)
or msg.text_lower:match('^/about')
or msg.text_lower:match('^/about@'..self.info.username:lower())
or msg.text_lower:match('^/start') then
sendMessage(msg.chat.id, output, true)
bindings.sendMessage(self, msg.chat.id, output, true)
return
end
@ -24,9 +30,4 @@ local action = function(msg)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return about

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,13 +1,17 @@
local command = 'bandersnatch'
local doc = [[```
local bandersnatch = {}
local bindings = require('bindings')
local utilities = require('utilities')
bandersnatch.command = 'bandersnatch'
bandersnatch.doc = [[```
Shun the frumious Bandersnatch.
Alias: /bc
```]]
local triggers = {
'^/bandersnatch[@'..bot.username..']*',
'^/bc[@'..bot.username..']*'
}
function bandersnatch:init()
bandersnatch.triggers = utilities.triggers(self.info.username):t('bandersnatch'):t('bc').table
end
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 action = function(msg)
function bandersnatch:action(msg)
local message
local output
if math.random(10) == 10 then
output = fullnames[math.random(#fullnames)]
@ -25,15 +29,8 @@ local action = function(msg)
output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)]
end
output = '_' .. output .. '_'
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendMessage(self, msg.chat.id, '_'..output..'_', true, nil, true)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return bandersnatch

View File

@ -1,54 +1,54 @@
if not config.biblia_api_key then
local bible = {}
local HTTP = require('socket.http')
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
local command = 'bible <reference>'
local doc = [[```
bible.triggers = utilities.triggers(self.info.username):t('bible', true):t('b', true).table
end
bible.command = 'bible <reference>'
bible.doc = [[```
/bible <reference>
Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com.
Alias: /b
```]]
local triggers = {
'^/bible*[@'..bot.username..']*',
'^/b[@'..bot.username..']* ',
'^/b[@'..bot.username..']*$'
}
function bible:action(msg)
local action = function(msg)
local input = msg.text:input()
local input = utilities.input(msg.text)
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
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)
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)
end
if not output or res ~= 200 or output:len() == 0 then
output = config.errors.results
output = self.config.errors.results
end
if output:len() > 4000 then
output = 'The text is too long to post here. Try being more specific.'
end
sendMessage(msg.chat.id, output, true, msg.message_id, true)
bindings.sendReply(self, msg, output)
end
return {
action = action,
triggers = triggers,
command = command,
doc = doc
}
return bible

View File

@ -1,24 +1,31 @@
-- 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.
if not database.blacklist then
database.blacklist = {}
local blacklist = {}
local bindings = require('bindings')
local utilities = require('utilities')
function blacklist:init()
if not self.database.blacklist then
self.database.blacklist = {}
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 database.blacklist[msg.chat.id_str] then return end
if self.database.blacklist[msg.from.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 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
sendReply(msg, target.err)
bindings.sendReply(self, msg, target.err)
return
end
@ -26,17 +33,14 @@ local triggers = {
target.name = 'Group'
end
if database.blacklist[tostring(target.id)] then
database.blacklist[tostring(target.id)] = nil
sendReply(msg, target.name .. ' has been removed from the blacklist.')
if self.database.blacklist[tostring(target.id)] then
self.database.blacklist[tostring(target.id)] = nil
bindings.sendReply(self, msg, target.name .. ' has been removed from the blacklist.')
else
database.blacklist[tostring(target.id)] = true
sendReply(msg, target.name .. ' has been added to the blacklist.')
self.database.blacklist[tostring(target.id)] = true
bindings.sendReply(self, msg, target.name .. ' has been added to the blacklist.')
end
end
return {
action = action,
triggers = triggers
}
return blacklist

View File

@ -1,21 +1,28 @@
local command = 'calc <expression>'
local doc = [[```
local calc = {}
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>
Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org.
```]]
local triggers = {
'^/calc[@'..bot.username..']*'
}
function calc:init()
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 msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
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
end
end
@ -24,19 +31,14 @@ local action = function(msg)
local output = HTTPS.request(url)
if not output then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
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
return {
action = action,
triggers = triggers,
command = command,
doc = doc
}
return calc

View File

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

View File

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

View File

@ -1,11 +1,16 @@
-- Commits from https://github.com/ngerakines/commitment.
local command = 'commit'
local doc = '`Returns a commit message from whatthecommit.com.`'
local commit = {}
local triggers = {
'^/commit[@'..bot.username..']*'
}
local bindings = require('bindings')
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 = {
"One does not simply merge into master",
@ -416,16 +421,11 @@ local commits = {
"fml"
}
local action = function(msg)
function commit:action(msg)
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return commit

View File

@ -1,28 +1,38 @@
local triggers = {
'^/reload[@'..bot.username..']*',
'^/halt[@'..bot.username..']*'
}
local control = {}
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
end
if msg.date < os.time() - 1 then return end
if msg.text:match('^/reload') then
bot_init()
sendReply(msg, 'Bot reloaded!')
elseif msg.text:match('^/halt') then
is_started = false
sendReply(msg, 'Stopping bot!')
if msg.text:match('^'..utilities.INVOCATION_PATTERN..'reload') then
for pac, _ in pairs(package.loaded) do
if pac:match('^plugins%.') then
package.loaded[pac] = nil
end
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
return {
action = action,
triggers = triggers
}
return control

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,29 @@
-- Liberbot-compliant floodcontrol.
-- 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
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 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.
end
@ -29,25 +36,21 @@ local action = function(msg)
input.duration = 600
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.'
handle_exception('floodcontrol.lua', output)
utilities.handle_exception(self, 'floodcontrol.lua', output)
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
floodcontrol[k] = nil
self.floodcontrol[k] = nil
end
end
end
return {
action = action,
triggers = triggers,
cron = cron
}
return floodcontrol

View File

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

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 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 = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
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 config.google_cse_key then
elseif not self.config.google_cse_key then
print('Missing config value: google_cse_key.')
print('gImages.lua will not be enabled.')
return
end
local command = 'image <query>'
local doc = [[```
gImages.triggers = utilities.triggers(self.info.username):t('image', true):t('i', true):t('insfw', true).table
end
gImages.command = 'image <query>'
gImages.doc = [[```
/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.
Alias: /i
```]]
local triggers = {
'^/image[@'..bot.username..']*',
'^/i[@'..bot.username..']* ',
'^/i[@'..bot.username..']*$',
'^/insfw[@'..bot.username..']*'
}
function gImages:action(msg)
local action = function(msg)
local input = msg.text:input()
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
sendMessage(msg.chat.id, doc, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, gImages.doc, true, msg.message_id, true)
return
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
url = url .. '&safe=high'
@ -47,13 +52,13 @@ local action = function(msg)
local jstr, res = HTTPS.request(url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.searchInformation.totalResults == '0' then
sendReply(msg, config.errors.results)
bindings.sendReply(self, msg, self.config.errors.results)
return
end
@ -64,17 +69,11 @@ local action = function(msg)
if msg.text:match('nsfw') then
output = '*NSFW*\n' .. output
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendReply(self, '*NSFW*\n'..msg, output)
else
sendMessage(msg.chat.id, output, false, nil, true)
bindings.sendMessage(self, msg.chat.id, output, false, nil, true)
end
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return gImages

View File

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

View File

@ -1,25 +1,30 @@
local command = 'google <query>'
local doc = [[```
local gSearch = {}
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>
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
```]]
local triggers = {
'^/g[@'..bot.username..']*$',
'^/g[@'..bot.username..']* ',
'^/google[@'..bot.username..']*',
'^/gnsfw[@'..bot.username..']*'
}
function gSearch:init()
gSearch.triggers = utilities.triggers(self.info.username):t('g', true):t('google', true):t('gnsfw', true).table
end
local action = function(msg)
function gSearch:action(msg)
local input = msg.text:input()
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
sendMessage(msg.chat.id, doc, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, gSearch.doc, true, msg.message_id, true)
return
end
end
@ -40,22 +45,22 @@ local action = function(msg)
local jstr, res = HTTPS.request(url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if not jdat.responseData then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
if not jdat.responseData.results[1] then
sendReply(msg, config.errors.results)
bindings.sendReply(self, msg, self.config.errors.results)
return
end
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;', '&')
--[[
if title:len() > 48 then
@ -70,13 +75,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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return gSearch

View File

@ -2,8 +2,14 @@
-- If you want to configure your own greetings, copy the following table
-- (without the "config.") to your config.lua file.
if not config.greetings then
config.greetings = {
local greetings = {}
local bindings = require('bindings')
local utilities = require('utilities')
function greetings:init()
if not self.config.greetings then
self.config.greetings = {
['Hello, #NAME.'] = {
'hello',
'hey',
@ -31,18 +37,19 @@ if not config.greetings then
}
end
local triggers = {
bot.first_name .. '%p*$'
greetings.triggers = {
self.info.first_name:lower() .. '%p*$'
}
end
local action = function(msg)
function greetings:action(msg)
local nick = database.users[msg.from.id_str].nickname or msg.from.first_name
local nick = self.database.users[msg.from.id_str].nickname or msg.from.first_name
for k,v in pairs(config.greetings) do
for key,val in pairs(v) do
if msg.text_lower:match(val..',? '..bot.first_name) then
sendMessage(msg.chat.id, latcyr(k:gsub('#NAME', nick)))
for trigger,responses in pairs(self.config.greetings) do
for _,response in pairs(responses) do
if msg.text_lower:match(response..',? '..self.info.first_name:lower()) then
bindings.sendMessage(self, msg.chat.id, utilities.latcyr(trigger:gsub('#NAME', nick)))
return
end
end
@ -52,7 +59,4 @@ local action = function(msg)
end
return {
action = action,
triggers = triggers
}
return greetings

View File

@ -1,21 +1,27 @@
local command = 'hackernews'
local doc = [[```
local hackernews = {}
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.
Alias: /hn
```]]
local triggers = {
'^/hackernews[@'..bot.username..']*',
'^/hn[@'..bot.username..']*'
}
function hackernews:init()
hackernews.triggers = utilities.triggers(self.info.username):t('hackernews', true):t('hn', true).table
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')
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
@ -31,7 +37,7 @@ local action = function(msg)
local res_url = 'https://hacker-news.firebaseio.com/v0/item/' .. jdat[i] .. '.json'
jstr, res = HTTPS.request(res_url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local res_jdat = JSON.decode(jstr)
@ -41,7 +47,7 @@ local action = function(msg)
end
local url = res_jdat.url
if not url then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
if url:find('%(') then
@ -52,13 +58,8 @@ local action = function(msg)
end
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return hackernews

View File

@ -1,6 +1,14 @@
-- Plugin for the Hearthstone database provided by hearthstonejson.com.
if not database.hearthstone or os.time() > database.hearthstone.expiration then
local hearthstone = {}
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
function hearthstone:init()
if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then
print('Downloading Hearthstone database...')
@ -22,27 +30,24 @@ if not database.hearthstone or os.time() > database.hearthstone.expiration then
return
end
database.hearthstone = d
database.hearthstone.expiration = os.time() + 600000
self.database.hearthstone = d
self.database.hearthstone.expiration = os.time() + 600000
print('Download complete! It will be stored for a week.')
end
local command = 'hearthstone <query>'
local doc = [[```
hearthstone.triggers = utilities.triggers(self.info.username):t('hearthstone', true):t('hs').table
end
hearthstone.command = 'hearthstone <query>'
hearthstone.doc = [[```
/hearthstone <query>
Returns Hearthstone card info.
Alias: /hs
```]]
local triggers = {
'^/hearthstone[@'..bot.username..']*',
'^/hs[@'..bot.username..']*$',
'^/hs[@'..bot.username..']* '
}
local format_card = function(card)
local function format_card(card)
local ctype = card.type
if card.race then
@ -73,6 +78,7 @@ local format_card = function(card)
stats = card.health .. 'h'
end
-- unused?
local info = ''
if card.text then
info = card.text:gsub('</?.->',''):gsub('%$','')
@ -97,34 +103,29 @@ local format_card = function(card)
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
sendMessage(msg.chat.id, doc, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, hearthstone.doc, true, msg.message_id, true)
return
end
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
output = output .. format_card(v) .. '\n\n'
end
end
output = output:trim()
output = utilities.trim(output)
if output:len() == 0 then
sendReply(msg, config.errors.results)
bindings.sendReply(self, msg, self.config.errors.results)
return
end
sendMessage(msg.chat.id, output, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return hearthstone

View File

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

View File

@ -1,21 +1,29 @@
local command = 'imdb <query>'
local doc = [[```
local imdb = {}
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>
Returns an IMDb entry.
```]]
local triggers = {
'^/imdb[@'..bot.username..']*'
}
function imdb:init()
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 msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
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
end
end
@ -24,14 +32,14 @@ local action = function(msg)
local jstr, res = HTTP.request(url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.Response ~= 'True' then
sendReply(msg, config.errors.results)
bindings.sendReply(self, msg, self.config.errors.results)
return
end
@ -40,13 +48,8 @@ local action = function(msg)
output = output .. '_' .. jdat.Plot .. '_\n'
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return imdb

View File

@ -1,14 +1,23 @@
if not config.lastfm_api_key then
local lastfm = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local bindings = require('bindings')
local utilities = require('utilities')
function lastfm:init()
if not self.config.lastfm_api_key then
print('Missing config value: lastfm_api_key.')
print('lastfm.lua will not be enabled.')
return
end
local HTTP = require('socket.http')
HTTP.TIMEOUT = 1
lastfm.triggers = utilities.triggers(self.info.username):t('lastfm', true):t('np', true):t('fmset', true).table
end
local command = 'lastfm'
local doc = [[```
bindings.command = 'lastfm'
bindings.doc = [[```
/np [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.
```]]
local triggers = {
'^/lastfm[@'..bot.username..']*',
'^/np[@'..bot.username..']*',
'^/fmset[@'..bot.username..']*'
}
function lastfm:action(msg)
local action = function(msg)
local input = msg.text:input()
local input = utilities.input(msg.text)
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
elseif string.match(msg.text, '^/fmset') 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
database.users[msg.from.id_str].lastfm = nil
sendReply(msg, 'Your last.fm username has been forgotten.')
self.database.users[msg.from.id_str].lastfm = nil
bindings.sendReply(self, msg, 'Your last.fm username has been forgotten.')
else
database.users[msg.from.id_str].lastfm = input
sendReply(msg, 'Your last.fm username has been set to "' .. input .. '".')
self.database.users[msg.from.id_str].lastfm = input
bindings.sendReply(self, msg, 'Your last.fm username has been set to "' .. input .. '".')
end
return
end
local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user='
local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. self.config.lastfm_api_key .. '&user='
local username
local alert = ''
if input then
username = input
elseif database.users[msg.from.id_str].lastfm then
username = database.users[msg.from.id_str].lastfm
elseif self.database.users[msg.from.id_str].lastfm then
username = self.database.users[msg.from.id_str].lastfm
elseif msg.from.username then
username = msg.from.username
alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use /fmset <username>.'
database.users[msg.from.id_str].lastfm = username
self.database.users[msg.from.id_str].lastfm = username
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
end
url = url .. URL.escape(username)
local jstr, res
utilities.with_http_timeout(
1, function ()
jstr, res = HTTP.request(url)
end)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
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
end
local jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then
sendReply(msg, 'No history for this user.' .. alert)
bindings.sendReply(self, msg, 'No history for this user.' .. alert)
return
end
@ -95,13 +102,8 @@ local action = function(msg)
end
output = output .. title .. ' - ' .. artist .. alert
sendMessage(msg.chat.id, output)
bindings.sendMessage(self, msg.chat.id, output)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return lastfm

View File

@ -1,9 +1,21 @@
if not database.librefm then
database.librefm = {}
local 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
local command = 'librefm'
local doc = [[```
librefm.triggers = utilities.triggers(self.info.username):t('librefm', true):t('lnp', true):t('lfmset', true)
end
librefm.command = 'librefm'
librefm.doc = [[```
/lnp [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.
```]]
local triggers = {
'^/librefm[@'..bot.username..']*',
'^/lnp[@'..bot.username..']*',
'^/lfmset[@'..bot.username..']*'
}
function librefm:action(msg)
local action = function(msg)
local input = msg.text:input()
local input = utilities.input(msg.text)
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
elseif string.match(msg.text, '^/lfmset') 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
database.librefm[msg.from.id_str] = nil
sendReply(msg, 'Your libre.fm username has been forgotten.')
self.database.librefm[msg.from.id_str] = nil
bindings.sendReply(self, msg, 'Your libre.fm username has been forgotten.')
else
database.librefm[msg.from.id_str] = input
sendReply(msg, 'Your libre.fm username has been set to "' .. input .. '".')
self.database.librefm[msg.from.id_str] = input
bindings.sendReply(self, msg, 'Your libre.fm username has been set to "' .. input .. '".')
end
return
end
@ -43,34 +49,34 @@ local action = function(msg)
local alert = ''
if input then
username = input
elseif database.librefm[msg.from.id_str] then
username = database.librefm[msg.from.id_str]
elseif self.database.librefm[msg.from.id_str] then
username = self.database.librefm[msg.from.id_str]
elseif msg.from.username then
username = msg.from.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
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
end
url = url .. URL.escape(username)
jstr, res = HTTPS.request(url)
local jstr, res = HTTPS.request(url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
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
end
local jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then
sendReply(msg, 'No history for this user.' .. alert)
bindings.sendReply(self, msg, 'No history for this user.' .. alert)
return
end
@ -90,13 +96,8 @@ local action = function(msg)
end
output = output .. title .. ' - ' .. artist .. alert
sendMessage(msg.chat.id, output)
bindings.sendMessage(self, msg.chat.id, output)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return librefm

View File

@ -1,20 +1,25 @@
local triggers = {
'^/lua[@'..bot.username..']*'
}
local luarun = {}
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
end
local input = msg.text:input()
local input = utilities.input(msg.text)
if not input then
sendReply(msg, 'Please enter a string to load.')
bindings.sendReply(self, msg, 'Please enter a string to load.')
return
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
output = 'Done!'
elseif type(output) == 'table' then
@ -22,12 +27,9 @@ local action = function(msg)
else
output = '```\n' .. tostring(output) .. '\n```'
end
sendMessage(msg.chat.id, output, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end
return {
action = action,
triggers = triggers
}
return luarun

View File

@ -1,16 +1,20 @@
local triggers = {
'^/me',
'^/me@'..bot.username
}
local me = {}
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
target = user_from_message(msg)
function me:action(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
sendReply(msg, target.err)
bindings.sendReply(self, msg, target.err)
return
end
end
@ -19,11 +23,8 @@ local action = function(msg)
for k,v in pairs(target) do
output = output .. '*' .. k .. ':* `' .. tostring(v) .. '`\n'
end
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end
return {
triggers = triggers,
action = action
}
return me

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

View File

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

View File

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

View File

@ -1,23 +1,29 @@
local command = 'pokedex <query>'
local doc = [[```
local pokedex = {}
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>
Returns a Pokedex entry from pokeapi.co.
Alias: /dex
```]]
local triggers = {
'^/pokedex[@'..bot.username..']*',
'^/dex[@'..bot.username..']*'
}
function pokedex:init()
pokedex.triggers = utilities.triggers(self.info.username):t('pokedex', true):t('dex', true).table
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 msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
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
end
end
@ -27,23 +33,23 @@ local action = function(msg)
local dex_url = url .. '/api/v1/pokemon/' .. input
local dex_jstr, res = HTTP.request(dex_url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local dex_jdat = JSON.decode(dex_jstr)
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
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local desc_jdat = JSON.decode(desc_jstr)
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)
if not poke_type then
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') .. '_'
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return pokedex

View File

@ -1,47 +1,48 @@
local command = 'preview <link>'
local doc = [[```
local preview = {}
local HTTP = require('socket.http')
local bindings = require('bindings')
local utilities = require('utilities')
preview.command = 'preview <link>'
preview.doc = [[```
/preview <link>
Returns a full-message, "unlinked" preview.
```]]
local triggers = {
'^/preview'
}
function preview:init()
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
sendMessage(msg.chat.id, doc, true, nil, true)
bindings.sendMessage(self, msg.chat.id, preview.doc, true, nil, true)
return
end
input = get_word(input, 1)
input = utilities.get_word(input, 1)
if not input:match('^https?://.+') then
input = 'http://' .. input
end
local res = HTTP.request(input)
if not res then
sendReply(msg, 'Please provide a valid link.')
bindings.sendReply(self, msg, 'Please provide a valid link.')
return
end
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
end
-- Invisible zero-width, non-joiner.
local output = '[](' .. input .. ')'
sendMessage(msg.chat.id, output, false, nil, true)
bindings.sendMessage(self, msg.chat.id, output, false, nil, true)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return preview

View File

@ -1,9 +1,14 @@
local command = 'pun'
local doc = '`Returns a pun.`'
local pun = {}
local triggers = {
'^/pun[@'..bot.username..']*'
}
local bindings = require('bindings')
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 = {
"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."
}
local action = function(msg)
function pun:action(msg)
sendReply(msg, puns[math.random(#puns)])
bindings.sendReply(self, msg, puns[math.random(#puns)])
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return pun

View File

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

View File

@ -1,26 +1,31 @@
local command = 'reddit [r/subreddit | query]'
local doc = [[```
local reddit = {}
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]
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]
```]]
local triggers = {
'^/reddit[@'..bot.username..']*',
'^/r[@'..bot.username..']*$',
'^/r[@'..bot.username..']* ',
'^/r/'
}
function reddit:init()
reddit.triggers = utilities.triggers(self.info.username, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table
end
local action = function(msg)
function reddit:action(msg)
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
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
input = msg.text_lower:input()
input = utilities.input(msg.text_lower)
end
local url
@ -45,18 +50,18 @@ local action = function(msg)
local jstr, res = HTTP.request(url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if #jdat.data.children == 0 then
sendReply(msg, config.errors.results)
bindings.sendReply(self, msg, self.config.errors.results)
return
end
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;', '&')
if title:len() > 48 then
title = title:sub(1,45) .. '...'
@ -73,13 +78,8 @@ local action = function(msg)
output = source .. output
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return reddit

View File

@ -1,26 +1,31 @@
database.reminders = database.reminders or {}
local remind = {}
local command = 'remind <duration> <message>'
local doc = [[```
local bindings = require('bindings')
local utilities = require('utilities')
remind.command = 'remind <duration> <message>'
remind.doc = [[```
/remind <duration> <message>
Repeats a message after a duration of time, in minutes.
```]]
local triggers = {
'^/remind'
}
function remind:init()
self.database.reminders = self.database.reminders or {}
local action = function(msg)
remind.triggers = utilities.triggers(self.info.username):t('remind', true).table
end
function remind:action(msg)
-- Ensure there are arguments. If not, send doc.
local input = msg.text:input()
local input = utilities.input(msg.text)
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
end
-- 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
sendMessage(msg.chat.id, doc, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, remind.doc, true, msg.message_id, true)
return
end
-- Duration must be between one minute and one year (approximately).
@ -31,19 +36,19 @@ local action = function(msg)
duration = 526000
end
-- Ensure there is a second arg.
local message = input:input()
local message = utilities.input(input)
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
end
-- 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.
if msg.chat.type ~= 'private' and table_size(database.reminders[msg.chat.id_str]) > 9 then
sendReply(msg, 'Sorry, this group already has ten reminders.')
if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 9 then
bindings.sendReply(self, msg, 'Sorry, this group already has ten reminders.')
return
elseif msg.chat.type == 'private' and table_size(database.reminders[msg.chat.id_str]) > 49 then
sendReply(msg, 'Sorry, you already have fifty reminders.')
elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then
bindings.sendReply(msg, 'Sorry, you already have fifty reminders.')
return
end
-- 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,
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
if duration == 1 then
output = output .. ' minute!'
else
output = output .. ' minutes!'
end
sendReply(msg, output)
bindings.sendReply(self, msg, output)
end
local cron = function()
function remind:cron()
local time = os.time()
-- 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 = {}
-- 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.
-- Otherwise, add it to the replacement table.
if time > reminder.time then
local output = '*Reminder:*\n"' .. markdown_escape(reminder.message) .. '"'
local res = sendMessage(chat_id, output, true, nil, true)
local output = '*Reminder:*\n"' .. utilities.md_escape(reminder.message) .. '"'
local res = bindings.sendMessage(self, chat_id, output, true, nil, true)
-- If the message fails to send, save it for later.
if res then
reminder = nil
else
if not res then
table.insert(new_group, reminder)
end
else
@ -84,19 +87,12 @@ local cron = function()
end
end
-- Nullify the original table and replace it with the new one.
group = nil
database.reminders[chat_id] = new_group
self.database.reminders[chat_id] = new_group
-- Nullify the table if it is empty.
if #new_group == 0 then
database.reminders[chat_id] = nil
self.database.reminders[chat_id] = nil
end
end
end
return {
action = action,
triggers = triggers,
cron = cron,
command = command,
doc = doc
}
return remind

View File

@ -1,75 +1,74 @@
database.setandget = database.setandget or {}
local setandget = {}
local command = 'set <name> <value>'
local doc = [[```
local bindings = require('bindings')
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>
Stores a value with the given name. Use "/set <name> --" to delete the stored value.
/get [name]
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()
database.setandget[msg.chat.id_str] = database.setandget[msg.chat.id_str] or {}
local input = utilities.input(msg.text)
self.database.setandget[msg.chat.id_str] = self.database.setandget[msg.chat.id_str] or {}
if msg.text_lower:match('^/set') 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
end
local name = get_word(input:lower(), 1)
local value = input:input()
local name = utilities.get_word(input:lower(), 1)
local value = utilities.input(input)
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
database.setandget[msg.chat.id_str][name] = nil
sendMessage(msg.chat.id, 'That value has been deleted.')
self.database.setandget[msg.chat.id_str][name] = nil
bindings.sendMessage(self, msg.chat.id, 'That value has been deleted.')
else
database.setandget[msg.chat.id_str][name] = value
sendMessage(msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true)
self.database.setandget[msg.chat.id_str][name] = value
bindings.sendMessage(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true)
end
elseif msg.text_lower:match('^/get') then
if not input then
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.'
else
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'
end
end
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
return
end
local output
if database.setandget[msg.chat.id_str][input:lower()] then
output = '`' .. database.setandget[msg.chat.id_str][input:lower()] .. '`'
if self.database.setandget[msg.chat.id_str][input:lower()] then
output = '`' .. self.database.setandget[msg.chat.id_str][input:lower()] .. '`'
else
output = 'There is no value stored by that name.'
end
sendMessage(msg.chat.id, output, true, nil, true)
bindings.sendMessage(self, msg.chat.id, output, true, nil, true)
end
end
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return setandget

View File

@ -1,18 +1,23 @@
local triggers = {
'^/run[@'..bot.username..']*'
}
local shell = {}
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
end
local input = msg.text:input()
local input = utilities.input(msg.text)
input = input:gsub('', '--')
if not input then
sendReply(msg, 'Please specify a command to run.')
bindings.sendReply(self, msg, 'Please specify a command to run.')
return
end
@ -22,11 +27,8 @@ local action = function(msg)
else
output = '```\n' .. output .. '\n```'
end
sendMessage(msg.chat.id, output, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, output, true, msg.message_id, true)
end
return {
action = action,
triggers = triggers
}
return shell

View File

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

View File

@ -1,12 +1,17 @@
local command = 'slap [target]'
local doc = [[```
local slap = {}
local bindings = require('bindings')
local utilities = require('utilities')
slap.command = 'slap [target]'
slap.doc = [[```
/slap [target]
Slap somebody.
```]]
local triggers = {
'^/slap[@'..bot.username..']*'
}
function slap:init()
slap.triggers = utilities.triggers(self.info.username):t('slap', true).table
end
local slaps = {
'VICTIM was shot by VICTOR.',
@ -92,40 +97,35 @@ local slaps = {
'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 database.users[tostring(msg.reply_to_message.from.id)].nickname then
victim = database.users[tostring(msg.reply_to_message.from.id)].nickname
if self.database.users[tostring(msg.reply_to_message.from.id)].nickname then
victim = self.database.users[tostring(msg.reply_to_message.from.id)].nickname
else
victim = msg.reply_to_message.from.first_name
end
end
local victor = msg.from.first_name
if database.users[msg.from.id_str].nickname then
victor = database.users[msg.from.id_str].nickname
if self.database.users[msg.from.id_str].nickname then
victor = self.database.users[msg.from.id_str].nickname
end
if not victim then
victim = victor
victor = bot.first_name
victor = self.info.first_name
end
local message = slaps[math.random(#slaps)]
message = message:gsub('VICTIM', victim)
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return slap

View File

@ -1,55 +1,63 @@
local command = 'time <location>'
local doc = [[```
local time = {}
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>
Returns the time, date, and timezone for the given location.
```]]
local triggers = {
'^/time[@'..bot.username..']*'
}
function time:init()
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 msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
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
end
end
local coords = get_coords(input)
local coords = utilities.get_coords(self, input)
if type(coords) == 'string' then
sendReply(msg, coords)
bindings.sendReply(self, msg, coords)
return
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)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
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
if utcoff == math.abs(utcoff) then
utcoff = '+' .. utcoff
utcoff = '+'.. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end
local output = '`' .. os.date('%I:%M %p\n', timestamp) .. os.date('%A, %B %d, %Y\n', timestamp) .. jdat.timeZoneName .. ' (UTC' .. utcoff .. ')' .. '`'
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return time

View File

@ -1,50 +1,52 @@
local command = 'translate [text]'
local doc = [[```
local translate = {}
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]
Translates input or the replied-to message into the bot's language.
```]]
local triggers = {
'^/translate[@'..bot.username..']*',
'^/tl[@'..bot.username..']*'
}
function translate:init()
translate.triggers = utilities.triggers(self.info.username):t('translate', true):t('tl', true).table
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 msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
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
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)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(str)
if jdat.code ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return translate

View File

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

View File

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

View File

@ -1,18 +1,23 @@
local command = 'whoami'
local doc = [[```
local whoami = {}
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.
Alias: /who
```]]
local triggers = {
'^/who[ami]*[@'..bot.username..']*$'
}
function whoami:init()
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
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
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]`'
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
user = 'You are *%s* `[%s]`,'
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]`.'
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
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
group = '*%s* `[%s]`.'
group = group:format(msg.chat.title, chat_id)
@ -40,13 +45,8 @@ local action = function(msg)
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return whoami

View File

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

View File

@ -1,20 +1,29 @@
local command = 'xkcd [query]'
local doc = [[```
local xkcd = {}
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]
Returns an xkcd strip and its alt text. If there is no query, it will be randomized.
```]]
local triggers = {
'^/xkcd[@'..bot.username..']*'
}
function xkcd:init()
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')
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
@ -23,14 +32,14 @@ local action = function(msg)
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 jstr, res = HTTPS.request(url)
jstr, res = HTTPS.request(url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if #jdat.responseData.results == 0 then
sendReply(msg, config.errors.results)
bindings.sendReply(self, msg, self.config.errors.results)
return
end
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'
end
local jstr, res = HTTP.request(res_url)
jstr, res = HTTP.request(res_url)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return xkcd

View File

@ -1,47 +1,53 @@
-- Thanks to @TiagoDanin for writing the original plugin.
if not config.google_api_key then
local youtube = {}
local HTTPS = require('ssl.https')
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
local command = 'youtube <query>'
local doc = [[```
youtube.triggers = utilities.triggers(self.info.username):t('youtube', true):t('yt', true).table
end
youtube.command = 'youtube <query>'
youtube.doc = [[```
/youtube <query>
Returns the top result from YouTube.
Alias: /yt
```]]
local triggers = {
'^/youtube[@'..bot.username..']*',
'^/yt[@'..bot.username..']*$',
'^/yt[@'..bot.username..']* '
}
function youtube:action(msg)
local action = function(msg)
local input = msg.text:input()
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
sendMessage(msg.chat.id, doc, true, msg.message_id, true)
bindings.sendMessage(self, msg.chat.id, youtube.doc, true, msg.message_id, true)
return
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)
if res ~= 200 then
sendReply(msg, config.errors.connection)
bindings.sendReply(self, msg, self.config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.pageInfo.totalResults == 0 then
sendReply(msg, config.errors.results)
bindings.sendReply(self, msg, self.config.errors.results)
return
end
@ -50,13 +56,8 @@ local action = function(msg)
vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','')
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
return {
action = action,
triggers = triggers,
doc = doc,
command = command
}
return youtube

View File

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