Alles miku

Erste Anpassungen für Mikudayobot
This commit is contained in:
2016-07-17 13:22:27 +02:00
parent d3c2e99165
commit b7ed1dbc80
173 changed files with 7350 additions and 5016 deletions

View File

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

View File

@@ -1,198 +0,0 @@
local bot = {}
-- Requires are moved to init to allow for reloads.
local bindings -- Load Telegram bindings.
local utilities -- Load miscellaneous and cross-plugin functions.
local redis = (loadfile "./otouto/redis.lua")()
bot.version = '2.0'
function bot:init(config) -- The function run when the bot is started or reloaded.
bindings = require('otouto.bindings')
utilities = require('otouto.utilities')
redis = (loadfile "./otouto/redis.lua")()
cred_data = load_cred()
assert(
config.bot_api_key and config.bot_api_key ~= '',
'You did not set your bot token in the config!'
)
self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/'
-- Fetch bot information. Try until it succeeds.
repeat
print('Fetching bot information...')
self.info = bindings.getMe(self)
until self.info
self.info = self.info.result
-- Load the "database"! ;)
if not self.database then
self.database = utilities.load_data(self.info.username..'.db')
end
self.database.users = self.database.users or {} -- Table to cache userdata.
self.database.users[tostring(self.info.id)] = self.info
self.plugins = {} -- Load plugins.
for _,v in ipairs(config.plugins) do
local p = require('otouto.plugins.'..v)
table.insert(self.plugins, p)
if p.init then p.init(self, config) end
end
print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')')
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.
end
function bot:on_msg_receive(msg, config) -- The fn run whenever a message is received.
-- Cache user info for those involved.
utilities.create_user_entry(self, msg.from)
if msg.forward_from and msg.forward_from.id ~= msg.from.id then
utilities.create_user_entry(self, msg.forward_from)
elseif msg.reply_to_message and msg.reply_to_message.from.id ~= msg.from.id then
utilities.create_user_entry(self, msg.reply_to_message.from)
end
if msg.date < os.time() - 5 then return end -- Do not process old messages.
msg = utilities.enrich_message(msg)
if msg.text:match('^'..config.cmd_pat..'start .+') then
msg.text = config.cmd_pat .. utilities.input(msg.text)
msg.text_lower = msg.text:lower()
end
for _, plugin in ipairs(self.plugins) do
for _, trigger in pairs(plugin.triggers) do
if string.match(msg.text_lower, trigger) then
local success, result = pcall(function()
-- trying to port matches to otouto
for k, pattern in pairs(plugin.triggers) do
matches = match_pattern(pattern, msg.text)
if matches then
break;
end
end
return plugin.action(self, msg, config, matches)
end)
if not success then
-- If the plugin has an error message, send it. If it does
-- not, use the generic one specified in config. If it's set
-- to false, do nothing.
if plugin.error then
utilities.send_reply(self, msg, plugin.error)
elseif plugin.error == nil then
utilities.send_reply(self, msg, config.errors.generic, true)
end
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
return
end
-- If the action returns a table, make that table the new msg.
if type(result) == 'table' then
msg = result
-- If the action returns true, continue.
elseif result ~= true then
return
end
end
end
end
end
function bot:run(config)
bot.init(self, config) -- Actually start the script.
while self.is_started do -- Start a loop while the bot should be running.
local res = bindings.getUpdates(self, { timeout=20, offset = self.last_update+1 } )
if res then
for _,v in ipairs(res.result) do -- Go through every new message.
self.last_update = v.update_id
if v.message then
bot.on_msg_receive(self, v.message, config)
end
end
else
print('Connection error while fetching updates.')
end
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 result, err = pcall(function() v.cron(self, config) end)
if not result then
utilities.handle_exception(self, err, 'CRON: ' .. i, config)
end
end
end
end
end
-- Save the database before exiting.
utilities.save_data(self.info.username..'.db', self.database)
print('Halted.')
end
function load_cred()
if redis:exists("telegram:credentials") == false then
-- If credentials hash doesnt exists
print ("Created new credentials hash: telegram:credentials")
create_cred()
end
return redis:hgetall("telegram:credentials")
end
-- create credentials hash with redis
function create_cred()
cred = {
bitly_access_token = "",
cloudinary_apikey = "",
cloudinary_api_secret = "",
cloudinary_public_id = "",
derpibooru_apikey = "",
fb_access_token = "",
flickr_apikey = "",
ftp_site = "",
ftp_username = "",
ftp_password = "",
gender_apikey = "",
golem_apikey = "",
google_apikey = "",
google_cse_id = "",
gitlab_private_token = "",
gitlab_project_id = "",
instagram_access_token = "",
lyricsnmusic_apikey = "",
mal_username = "",
mal_pw = "",
neutrino_userid = "",
neutrino_apikey = "",
owm_apikey = "",
page2images_restkey = "",
soundcloud_client_id = "",
tw_consumer_key = "",
tw_consumer_secret = "",
tw_access_token = "",
tw_access_token_secret = "",
x_mashape_key = "",
yandex_translate_apikey = "",
yandex_rich_content_apikey = "",
yourls_site_url = "",
yourls_signature_token = ""
}
redis:hmset("telegram:credentials", cred)
print ('saved credentials into reds hash telegram:credentials')
end
return bot

View File

@@ -1,100 +0,0 @@
-- Thanks to https://github.com/catwell/lua-toolbox/blob/master/mime.types
do
local mimetype = {}
-- TODO: Add more?
local types = {
["text/html"] = "html",
["text/css"] = "css",
["text/xml"] = "xml",
["image/gif"] = "gif",
["image/jpeg"] = "jpg",
["application/x-javascript"] = "js",
["application/atom+xml"] = "atom",
["application/rss+xml"] = "rss",
["text/mathml"] = "mml",
["text/plain"] = "txt",
["text/vnd.sun.j2me.app-descriptor"] = "jad",
["text/vnd.wap.wml"] = "wml",
["text/x-component"] = "htc",
["image/png"] = "png",
["image/tiff"] = "tiff",
["image/vnd.wap.wbmp"] = "wbmp",
["image/x-icon"] = "ico",
["image/x-jng"] = "jng",
["image/x-ms-bmp"] = "bmp",
["image/svg+xml"] = "svg",
["application/java-archive"] = "jar",
["application/mac-binhex40"] = "hqx",
["application/msword"] = "doc",
["application/pdf"] = "pdf",
["application/postscript"] = "ps",
["application/rtf"] = "rtf",
["application/vnd.android.package-archive"] = "apk",
["application/vnd.ms-excel"] = "xls",
["application/vnd.ms-powerpoint"] = "ppt",
["application/vnd.wap.wmlc"] = "wmlc",
["application/vnd.google-earth.kml+xml"] = "kml",
["application/vnd.google-earth.kmz"] = "kmz",
["application/x-7z-compressed"] = "7z",
["application/x-cocoa"] = "cco",
["application/x-java-archive-diff"] = "jardiff",
["application/x-java-jnlp-file"] = "jnlp",
["application/x-makeself"] = "run",
["application/x-perl"] = "pl",
["application/x-pilot"] = "prc",
["application/x-rar-compressed"] = "rar",
["application/x-redhat-package-manager"] = "rpm",
["application/x-sea"] = "sea",
["application/x-shockwave-flash"] = "swf",
["application/x-stuffit"] = "sit",
["application/x-tcl"] = "tcl",
["application/x-x509-ca-cert"] = "crt",
["application/x-xpinstall"] = "xpi",
["application/xhtml+xml"] = "xhtml",
["application/zip"] = "zip",
["application/octet-stream"] = "bin",
["audio/midi"] = "mid",
["audio/mpeg"] = "mp3",
["audio/ogg"] = "ogg",
["audio/x-m4a"] = "m4a",
["audio/x-realaudio"] = "ra",
["video/3gpp"] = "3gpp",
["video/mp4"] = "mp4",
["video/mpeg"] = "mpeg",
["video/quicktime"] = "mov",
["video/x-flv"] = "flv",
["video/x-m4v"] = "m4v",
["video/x-mng"] = "mng",
["video/x-ms-asf"] = "asf",
["video/x-ms-wmv"] = "wmv",
["video/x-msvideo"] = "avi"
}
-- Returns the common file extension from a content-type
function mimetype.get_mime_extension(content_type)
return types[content_type]
end
-- Returns the mimetype and subtype
function mimetype.get_content_type(extension)
for k,v in pairs(types) do
if v == extension then
return k
end
end
end
-- Returns the mimetype without the subtype
function mimetype.get_content_type_no_sub(extension)
for k,v in pairs(types) do
if v == extension then
-- Before /
return k:match('([%w-]+)/')
end
end
end
return mimetype
end

View File

@@ -1,40 +0,0 @@
local ninegag = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
ninegag.command = '9gag'
function ninegag:init(config)
ninegag.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('9gag', true):t('9fag', true).table
ninegag.doc = [[*
]]..config.cmd_pat..[[9gag*: Gibt ein zufälliges Bild von den momentan populärsten 9GAG-Posts aus]]
end
function ninegag:get_9GAG()
local url = "http://api-9gag.herokuapp.com/"
local b,c = HTTP.request(url)
if c ~= 200 then return nil end
local gag = JSON.decode(b)
-- random max json table size
local i = math.random(#gag) local link_image = gag[i].src
local title = gag[i].title
return link_image, title, post_url
end
function ninegag:action(msg, config)
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local url, title = ninegag:get_9GAG()
if not url then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local file = download_to_file(url)
utilities.send_photo(self, msg.chat.id, file, title)
end
return ninegag

View File

@@ -1,33 +0,0 @@
local about = {}
local bot = require('otouto.bot')
local utilities = require('otouto.utilities')
about.command = 'about'
about.doc = '`Sendet Informationen über den Bot.`'
about.triggers = {
''
}
function about:action(msg, config)
-- Filthy hack, but here is where we'll stop forwarded messages from hitting
-- other plugins.
if msg.forward_from then return end
local output = config.about_text .. '\nBrawlbot v2, basierend auf Otouto v'..bot.version..' von topkecleon.'
if (msg.new_chat_participant and msg.new_chat_participant.id == self.info.id)
or msg.text_lower:match('^'..config.cmd_pat..'about')
or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower())
or msg.text_lower:match('^'..config.cmd_pat..'start') then
utilities.send_message(self, msg.chat.id, output, true)
return
end
return true
end
return about

View File

@@ -1,49 +0,0 @@
local adfly = {}
local utilities = require('otouto.utilities')
local HTTPS = require('ssl.https')
local redis = (loadfile "./otouto/redis.lua")()
function adfly:init(config)
adfly.triggers = {
'adf.ly/([A-Za-z0-9-_-]+)'
}
adfly.doc = [[*adf.ly-Link*: Postet vollen Link]]
end
function adfly:expand_adfly_link(adfly_code)
local BASE_URL = 'https://andibi.tk/dl/adfly.php'
local url = BASE_URL..'/?url=http://adf.ly/'..adfly_code
local res,code = HTTPS.request(url)
if code ~= 200 then return nil end
if res == 'Fehler: Keine Adf.ly-URL gefunden!' then return 'NOTFOUND' end
cache_data('adfly', adfly_code, res, 31536000, 'key')
return res
end
function adfly:action(msg)
local input = msg.text
if not input:match('adf.ly/([A-Za-z0-9-_-]+)') then
return
end
local adfly_code = input:match('adf.ly/([A-Za-z0-9-_-]+)')
local hash = 'telegram:cache:adfly:'..adfly_code
if redis:exists(hash) == false then
local expanded_url = adfly:expand_adfly_link(adfly_code)
if not expanded_url then
utilities.send_reply(self, msg, config.errors.connection)
return
end
if expanded_url == 'NOTFOUND' then
utilities.send_reply(self, msg, 'Fehler: Keine Adf.ly-URL gefunden!')
return
end
utilities.send_reply(self, msg, expanded_url)
else
local data = redis:get(hash)
utilities.send_reply(self, msg, data)
end
end
return adfly

File diff suppressed because it is too large Load Diff

View File

@@ -1,87 +0,0 @@
-- Credit to Heitor (tg:Wololo666; gh:heitorPB) for this plugin.
local apod = {}
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local URL = require('socket.url')
local utilities = require('otouto.utilities')
apod.command = 'apod [date]'
function apod:init(config)
apod.triggers = utilities.triggers(self.info.username, config.cmd_pat)
:t('apod', true):t('apodhd', true):t('apodtext', true).table
apod.doc = [[```
]]..config.cmd_pat..[[apod [query]
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.
]]..config.cmd_pat..[[apodhd [query]
Returns the image in HD, if available.
]]..config.cmd_pat..[[apodtext [query]
Returns the explanation of the APOD.
Source: nasa.gov
```]]
end
function apod:action(msg, config)
if not config.nasa_api_key then
config.nasa_api_key = 'DEMO_KEY'
end
local input = utilities.input(msg.text)
local date = '*'
local disable_page_preview = false
local url = 'https://api.nasa.gov/planetary/apod?api_key=' .. config.nasa_api_key
if input then
if input:match('(%d+)%-(%d+)%-(%d+)$') then
url = url .. '&date=' .. URL.escape(input)
date = date .. input
else
utilities.send_message(self, msg.chat.id, apod.doc, true, msg.message_id, true)
return
end
else
date = date .. os.date("%F")
end
date = date .. '*\n'
local jstr, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.error then
utilities.send_reply(self, msg, config.errors.results)
return
end
local img_url = jdat.url
if string.match(msg.text, '^'..config.cmd_pat..'apodhd*') then
img_url = jdat.hdurl or jdat.url
end
local output = date .. '[' .. jdat.title .. '](' .. img_url .. ')'
if string.match(msg.text, '^'..config.cmd_pat..'apodtext*') then
output = output .. '\n' .. jdat.explanation
disable_page_preview = true
end
if jdat.copyright then
output = output .. '\nCopyright: ' .. jdat.copyright
end
utilities.send_message(self, msg.chat.id, output, disable_page_preview, nil, true)
end
return apod

View File

@@ -1,117 +0,0 @@
local app_store = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
app_store.triggers = {
"itunes.apple.com/(.*)/app/(.*)/id(%d+)",
"^!itunes (%d+)$",
"itunes.apple.com/app/id(%d+)"
}
local BASE_URL = 'https://itunes.apple.com/lookup'
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)T"
local year, month, day = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function app_store:get_appstore_data()
local url = BASE_URL..'/?id='..appid..'&country=de'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res).results[1]
if data == nil then return 'NOTFOUND' end
if data.wrapperType ~= 'software' then return nil end
return data
end
function app_store:send_appstore_data(data)
-- Header
local name = data.trackName
local author = data.sellerName
local price = data.formattedPrice
local version = data.version
-- Body
local description = string.sub(unescape(data.description), 1, 150) .. '...'
local min_ios_ver = data.minimumOsVersion
local size = string.gsub(round(data.fileSizeBytes / 1000000, 2), "%.", ",") -- wtf Apple, it's 1024, not 1000!
local release = makeOurDate(data.releaseDate)
if data.isGameCenterEnabled then
game_center = '\nUnterstützt Game Center'
else
game_center = ''
end
local category_count = tablelength(data.genres)
if category_count == 1 then
category = '\nKategorie: '..data.genres[1]
else
local category_loop = '\nKategorien: '
for v in pairs(data.genres) do
if v < category_count then
category_loop = category_loop..data.genres[v]..', '
else
category_loop = category_loop..data.genres[v]
end
end
category = category_loop
end
-- Footer
if data.averageUserRating and data.userRatingCount then
avg_rating = 'Bewertung: '..string.gsub(data.averageUserRating, "%.", ",")..' Sterne '
ratings = 'von '..comma_value(data.userRatingCount)..' Bewertungen'
else
avg_rating = ""
ratings = ""
end
local header = '*'..name..'* v'..version..' von *'..author..'* ('..price..'):'
local body = '\n'..description..'\n_Benötigt mind. iOS '..min_ios_ver..'_\nGröße: '..size..' MB\nErstveröffentlicht am '..release..game_center..category
local footer = '\n'..avg_rating..ratings
local text = header..body..footer
-- Picture
if data.screenshotUrls[1] and data.ipadScreenshotUrls[1] then
image_url = data.screenshotUrls[1]
elseif data.screenshotUrls[1] and not data.ipadScreenshotUrls[1] then
image_url = data.screenshotUrls[1]
elseif not data.screenshotUrls[1] and data.ipadScreenshotUrls[1] then
image_url = data.ipadScreenshotUrls[1]
else
image_url = nil
end
return text, image_url
end
function app_store:action(msg, config, matches)
if not matches[3] then
appid = matches[1]
else
appid = matches[3]
end
local data = app_store:get_appstore_data()
if data == nil then print('Das Appstore-Plugin unterstützt nur Apps!') end
if data == 'HTTP-FEHLER' or data == 'NOTFOUND' then
utilities.send_reply(self, msg, '*App nicht gefunden!*', true)
return
else
local output, image_url = app_store:send_appstore_data(data)
utilities.send_reply(self, msg, output, true)
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
end
end
return app_store

View File

@@ -1,35 +0,0 @@
local bandersnatch = {}
local utilities = require('otouto.utilities')
bandersnatch.command = 'bandersnatch'
function bandersnatch:init(config)
bandersnatch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bandersnatch'):t('bc').table
bandersnatch.doc = [[```
Shun the frumious Bandersnatch.
Alias: ]]..config.cmd_pat..[[bc
```]]
end
local fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" }
local firstnames = { "Bumblebee", "Bandersnatch", "Broccoli", "Rinkydink", "Bombadil", "Boilerdang", "Bandicoot", "Fragglerock", "Muffintop", "Congleton", "Blubberdick", "Buffalo", "Benadryl", "Butterfree", "Burberry", "Whippersnatch", "Buttermilk", "Beezlebub", "Budapest", "Boilerdang", "Blubberwhale", "Bumberstump", "Bulbasaur", "Cogglesnatch", "Liverswort", "Bodybuild", "Johnnycash", "Bendydick", "Burgerking", "Bonaparte", "Bunsenburner", "Billiardball", "Bukkake", "Baseballmitt", "Blubberbutt", "Baseballbat", "Rumblesack", "Barister", "Danglerack", "Rinkydink", "Bombadil", "Honkytonk", "Billyray", "Bumbleshack", "Snorkeldink", "Anglerfish", "Beetlejuice", "Bedlington", "Bandicoot", "Boobytrap", "Blenderdick", "Bentobox", "Anallube", "Pallettown", "Wimbledon", "Buttercup", "Blasphemy", "Snorkeldink", "Brandenburg", "Barbituate", "Snozzlebert", "Tiddleywomp", "Bouillabaisse", "Wellington", "Benetton", "Bendandsnap", "Timothy", "Brewery", "Bentobox", "Brandybuck", "Benjamin", "Buckminster", "Bourgeoisie", "Bakery", "Oscarbait", "Buckyball", "Bourgeoisie", "Burlington", "Buckingham", "Barnoldswick" }
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" }
function bandersnatch:action(msg)
local output
if math.random(10) == 10 then
output = fullnames[math.random(#fullnames)]
else
output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)]
end
utilities.send_message(self, msg.chat.id, '_'..output..'_', true, nil, true)
end
return bandersnatch

View File

@@ -1,53 +0,0 @@
local bible = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local utilities = require('otouto.utilities')
function bible:init(config)
if not config.biblia_api_key then
print('Missing config value: biblia_api_key.')
print('bible.lua will not be enabled.')
return
end
bible.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bible', true):t('b', true).table
bible.doc = [[```
]]..config.cmd_pat..[[bible <reference>
Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com.
Alias: ]]..config.cmd_pat..[[b
```]]
end
bible.command = 'bible <reference>'
function bible:action(msg, config)
local input = utilities.input(msg.text)
if not input then
utilities.send_message(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 output, res = HTTP.request(url)
if not output or res ~= 200 or output:len() == 0 then
url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input)
output, res = HTTP.request(url)
end
if not output or res ~= 200 or output:len() == 0 then
output = config.errors.results
end
if output:len() > 4000 then
output = 'The text is too long to post here. Try being more specific.'
end
utilities.send_reply(self, msg, output)
end
return bible

View File

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

View File

@@ -1,48 +0,0 @@
local bitly = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
function bitly:init(config)
if not cred_data.bitly_access_token then
print('Missing config value: bitly_access_token.')
print('bitly.lua will not be enabled.')
return
end
bitly.triggers = {
"bit.ly/([A-Za-z0-9-_-]+)",
"bitly.com/([A-Za-z0-9-_-]+)",
"j.mp/([A-Za-z0-9-_-]+)",
"andib.tk/([A-Za-z0-9-_-]+)"
}
end
local BASE_URL = 'https://api-ssl.bitly.com/v3/expand'
function bitly:expand_bitly_link (shorturl)
local access_token = cred_data.bitly_access_token
local url = BASE_URL..'?access_token='..access_token..'&shortUrl=https://bit.ly/'..shorturl
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res).data.expand[1]
cache_data('bitly', shorturl, data)
return data.long_url
end
function bitly:action(msg, config, matches)
local shorturl = matches[1]
local hash = 'telegram:cache:bitly:'..shorturl
if redis:exists(hash) == false then
utilities.send_reply(self, msg, bitly:expand_bitly_link(shorturl))
return
else
local data = redis:hgetall(hash)
utilities.send_reply(self, msg, data.long_url)
return
end
end
return bitly

View File

@@ -1,142 +0,0 @@
local bitly_create = {}
local http = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local OAuth = require "OAuth"
local redis = (loadfile "./otouto/redis.lua")()
function bitly_create:init(config)
if not cred_data.bitly_client_id then
print('Missing config value: bitly_client_id.')
print('bitly_create.lua will not be enabled.')
return
elseif not cred_data.bitly_client_secret then
print('Missing config value: bitly_client_secret.')
print('bitly_create.lua will not be enabled.')
return
elseif not cred_data.bitly_redirect_uri then
print('Missing config value: bitly_redirect_uri.')
print('bitly_create.lua will not be enabled.')
return
end
bitly_create.triggers = {
"^/short (auth) (.+)$",
"^/short (auth)$",
"^/short (unauth)$",
"^/short (me)$",
"^/short (j.mp) (https?://[%w-_%.%?%.:/%+=&]+)$",
"^/short (bit.ly) (https?://[%w-_%.%?%.:/%+=&]+)$",
"^/short (bitly.com) (https?://[%w-_%.%?%.:/%+=&]+)$",
"^/short (https?://[%w-_%.%?%.:/%+=&]+)$"
}
bitly_create.doc = [[*
]]..config.cmd_pat..[[short* _<Link>_: Kürzt einen Link mit der Standard Bitly-Adresse
*]]..config.cmd_pat..[[short* _<j.mp|bit.ly|bitly.com>_ _[Link]_: Kürzt einen Link mit der ausgewählten Kurz-URL
*]]..config.cmd_pat..[[short* _auth_: Loggt deinen Account ein und nutzt ihn für deine Links (empfohlen!)
*]]..config.cmd_pat..[[short* _me_: Gibt den eingeloggten Account aus
*]]..config.cmd_pat..[[short* _unauth_: Loggt deinen Account aus
]]
end
bitly_create.command = 'short <URL>'
local BASE_URL = 'https://api-ssl.bitly.com'
local client_id = cred_data.bitly_client_id
local client_secret = cred_data.bitly_client_secret
local redirect_uri = cred_data.bitly_redirect_uri
function bitly_create:get_bitly_access_token(hash, code)
local req = post_petition(BASE_URL..'/oauth/access_token', 'client_id='..client_id..'&client_secret='..client_secret..'&code='..code..'&redirect_uri='..redirect_uri)
if not req.access_token then return '*Fehler beim Einloggen!*' end
local access_token = req.access_token
local login_name = req.login
redis:hset(hash, 'bitly', access_token)
return 'Erfolgreich als `'..login_name..'` eingeloggt!'
end
function bitly_create:get_bitly_user_info(bitly_access_token)
local url = BASE_URL..'/v3/user/info?access_token='..bitly_access_token..'&format=json'
local res,code = https.request(url)
if code == 401 then return 'Login fehlgeschlagen!' end
if code ~= 200 then return 'HTTP-Fehler!' end
local data = json.decode(res).data
if data.full_name then
name = '*'..data.full_name..'* (`'..data.login..'`)'
else
name = '`'..data.login..'`'
end
local text = 'Eingeloggt als '..name
return text
end
function bitly_create:create_bitlink (long_url, domain, bitly_access_atoken)
local url = BASE_URL..'/v3/shorten?access_token='..bitly_access_token..'&domain='..domain..'&longUrl='..long_url..'&format=txt'
local text,code = https.request(url)
if code ~= 200 then return 'FEHLER: '..text end
return text
end
function bitly_create:action(msg, config, matches)
local hash = 'user:'..msg.from.id
bitly_access_token = redis:hget(hash, 'bitly')
if matches[1] == 'auth' and matches[2] then
utilities.send_reply(self, msg, bitly_create:get_bitly_access_token(hash, matches[2]), true)
return
end
if matches[1] == 'auth' then
utilities.send_reply(self, msg, 'Bitte logge dich ein und folge den Anweisungen:\n[Bei Bitly anmelden](https://bitly.com/oauth/authorize?client_id='..client_id..'&redirect_uri='..redirect_uri..')', true)
return
end
if matches[1] == 'unauth' and bitly_access_token then
redis:hdel(hash, 'bitly')
utilities.send_reply(self, msg, '*Erfolgreich ausgeloggt!* Du kannst den Zugriff [in deinen Kontoeinstellungen](https://bitly.com/a/settings/connected) endgültig entziehen.', true)
return
elseif matches[1] == 'unauth' and not bitly_access_token then
utilities.send_reply(self, msg, 'Wie willst du dich ausloggen, wenn du gar nicht eingeloggt bist?', true)
return
end
if matches[1] == 'me' and bitly_access_token then
local text = bitly_create:get_bitly_user_info(bitly_access_token)
if text then
utilities.send_reply(self, msg, text, true)
return
else
return
end
elseif matches[1] == 'me' and not bitly_access_token then
utilities.send_reply(self, msg, 'Du bist nicht eingeloggt! Logge dich ein mit\n/short auth', true)
return
end
if not bitly_access_token then
print('Not signed in, will use global bitly access_token')
bitly_access_token = cred_data.bitly_access_token
end
if matches[2] == nil then
long_url = url_encode(matches[1])
domain = 'bit.ly'
else
long_url = url_encode(matches[2])
domain = matches[1]
end
utilities.send_reply(self, msg, bitly_create:create_bitlink(long_url, domain, bitly_access_token))
return
end
return bitly_create

View File

@@ -1,45 +0,0 @@
-- This plugin will allow the admin to blacklist users who will be unable to
-- use the bot. This plugin should be at the top of your plugin list in config.
local blacklist = {}
local utilities = require('otouto.utilities')
function blacklist:init()
if not self.database.blacklist then
self.database.blacklist = {}
end
end
blacklist.triggers = {
''
}
function blacklist:action(msg, config)
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('^'..config.cmd_pat..'blacklist') then return true end
if msg.from.id ~= config.admin then return end
local target = utilities.user_from_message(self, msg)
if target.err then
utilities.send_reply(self, msg, target.err)
return
end
if tonumber(target.id) < 0 then
target.name = 'Group'
end
if self.database.blacklist[tostring(target.id)] then
self.database.blacklist[tostring(target.id)] = nil
utilities.send_reply(self, msg, target.name .. ' has been removed from the blacklist.')
else
self.database.blacklist[tostring(target.id)] = true
utilities.send_reply(self, msg, target.name .. ' has been added to the blacklist.')
end
end
return blacklist

View File

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

View File

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

View File

@@ -1,65 +0,0 @@
local channel = {}
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
--channel.command = 'ch <channel> \\n <message>'
channel.doc = [[```
/ch <channel>
<message>
Sends a message to a channel. Channel may be specified via ID or username. Messages are markdown-enabled. Users may only send messages to channels for which they are the owner or an administrator.
The following markdown syntax is supported:
*bold text*
_italic text_
[text](URL)
`inline fixed-width code`
```pre-formatted fixed-width code block```
Due to the frequent dysfunction and incompletion of the API method used to determine the administrators of a channel, this command may not work for the owners of some channels.
```]]
function channel:init(config)
channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table
end
function channel:action(msg, config)
-- An exercise in using zero early returns. :)
local input = utilities.input(msg.text)
local output
if input then
local chat_id = utilities.get_word(input, 1)
local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } )
if admin_list then
local is_admin = false
for _, admin in ipairs(admin_list.result) do
if admin.user.id == msg.from.id then
is_admin = true
end
end
if is_admin then
local text = input:match('\n(.+)')
if text then
local success, result = utilities.send_message(self, chat_id, text, true, nil, true)
if success then
output = 'Your message has been sent!'
else
output = 'Sorry, I was unable to send your message.\n`' .. result.description .. '`'
end
else
output = 'Please enter a message to be sent. Markdown is supported.'
end
else
output = 'Sorry, you do not appear to be an administrator for that channel.\nThere is currently a known bug in the getChatAdministrators method, where administrator lists will often not show a channel\'s owner.'
end
else
output = 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`'
end
else
output = channel.doc
end
utilities.send_reply(self, msg, output, true)
end
return channel

View File

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

View File

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

View File

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

View File

@@ -1,125 +0,0 @@
local creds_manager = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
function creds_manager:init(config)
creds_manager.triggers = {
"^(/creds)$",
"^(/creds add) ([^%s]+) (.+)$",
"^(/creds del) (.+)$",
"^(/creds rename) ([^%s]+) (.+)$"
}
creds_manager.doc = [[*
]]..config.cmd_pat..[[creds*: Zeigt alle Logindaten und API-Keys
*]]..config.cmd_pat..[[creds* _add_ _<Variable>_ _<Schlüssel>_: Speichert Schlüssel mit dieser Variable ein
*]]..config.cmd_pat..[[creds* _del_ _<Variable>_: Löscht Schlüssel mit dieser Variable
*]]..config.cmd_pat..[[creds* _rename_ _<Variable>_ _<Neue Variable>_: Benennt Variable um, behält Schlüssel bei
]]
end
creds_manager.command = 'creds'
local hash = "telegram:credentials"
-- See: http://www.lua.org/pil/19.3.html
function pairsByKeys (t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then
return nil
else
return a[i], t[a[i]]
end
end
return iter
end
function creds_manager:reload_creds()
cred_data = redis:hgetall(hash)
end
function creds_manager:list_creds()
creds_manager:reload_creds()
if redis:exists("telegram:credentials") == true then
local text = ""
for var, key in pairsByKeys(cred_data) do
text = text..var..' = '..key..'\n'
end
return text
else
create_cred()
return "Es wurden noch keine Logininformationen gespeichert, lege Tabelle an...\nSpeichere Keys mit /creds add [Variable] [Key] ein!"
end
end
function creds_manager:add_creds(var, key)
print('Saving credential for '..var..' to redis hash '..hash)
redis:hset(hash, var, key)
creds_manager:reload_creds()
return 'Gespeichert!'
end
function creds_manager:del_creds(var)
if redis:hexists(hash, var) == true then
print('Deleting credential for '..var..' from redis hash '..hash)
redis:hdel(hash, var)
creds_manager:reload_creds()
return 'Key von "'..var..'" erfolgreich gelöscht!'
else
return 'Du hast keine Logininformationen für diese Variable eingespeichert.'
end
end
function creds_manager:rename_creds(var, newvar)
if redis:hexists(hash, var) == true then
local key = redis:hget(hash, var)
if redis:hsetnx(hash, newvar, key) == true then
redis:hdel(hash, var)
creds_manager:reload_creds()
return '"'..var..'" erfolgreich zu "'..newvar..'" umbenannt.'
else
return "Variable konnte nicht umbenannt werden: Zielvariable existiert bereits."
end
else
return 'Die zu umbennende Variable existiert nicht.'
end
end
function creds_manager:action(msg, config, matches)
local receiver = msg.from.id
if receiver ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
if msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'Dieses Plugin solltest du nur [privat](http://telegram.me/' .. self.info.username .. '?start=creds) verwenden!', true)
return
end
if matches[1] == "/creds" then
utilities.send_reply(self, msg, creds_manager:list_creds())
return
elseif matches[1] == "/creds add" then
local var = string.lower(string.sub(matches[2], 1, 50))
local key = string.sub(matches[3], 1, 1000)
utilities.send_reply(self, msg, creds_manager:add_creds(var, key))
return
elseif matches[1] == "/creds del" then
local var = string.lower(matches[2])
utilities.send_reply(self, msg, creds_manager:del_creds(var))
return
elseif matches[1] == "/creds rename" then
local var = string.lower(string.sub(matches[2], 1, 50))
local newvar = string.lower(string.sub(matches[3], 1, 1000))
utilities.send_reply(self, msg, creds_manager:rename_creds(var, newvar))
return
end
end
return creds_manager

View File

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

View File

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

View File

@@ -1,51 +0,0 @@
local dilbert = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
dilbert.command = 'dilbert [date]'
function dilbert:init(config)
dilbert.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('dilbert', true).table
dilbert.doc = [[```
]]..config.cmd_pat..[[dilbert [YYYY-MM-DD]
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
```]]
end
function dilbert:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' } )
local input = utilities.input(msg.text)
if not input then input = os.date('%F') end
if not input:match('^%d%d%d%d%-%d%d%-%d%d$') then input = os.date('%F') end
local url = 'http://dilbert.com/strip/' .. URL.escape(input)
local str, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local strip_filename = '/tmp/' .. input .. '.gif'
local strip_file = io.open(strip_filename)
if strip_file then
strip_file:close()
strip_file = strip_filename
else
local strip_url = str:match('<meta property="og:image" content="(.-)"/>')
strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif')
end
local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>')
bindings.sendPhoto(self, { chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file } )
end
return dilbert

View File

@@ -1,34 +0,0 @@
local echo = {}
local utilities = require('otouto.utilities')
echo.command = 'echo <text>'
function echo:init(config)
echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table
echo.doc = [[```
]]..config.cmd_pat..[[echo <text>
Repeats a string of text.
```]]
end
function echo:action(msg)
local input = utilities.input(msg.text)
if not input then
utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true)
else
local output
if msg.chat.type == 'supergroup' then
output = '*Echo:*\n"' .. utilities.md_escape(input) .. '"'
else
output = utilities.md_escape(utilities.char.zwnj..input)
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
end
return echo

View File

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

View File

@@ -1,37 +0,0 @@
local expand = {}
local http = require('socket.http')
local utilities = require('otouto.utilities')
function expand:init(config)
expand.triggers = {
"^/expand (https?://[%w-_%.%?%.:/%+=&]+)$"
}
expand.doc = [[*
]]..config.cmd_pat..[[expand* _<Kurz-URL>_: Verlängert Kurz-URL (301er/302er)]]
end
expand.command = 'expand <Kurz-URL>'
function expand:action(msg, config, matches)
local response_body = {}
local request_constructor = {
url = matches[1],
method = "HEAD",
sink = ltn12.sink.table(response_body),
headers = {},
redirect = false
}
local ok, response_code, response_headers, response_status_line = http.request(request_constructor)
if ok and response_headers.location then
utilities.send_reply(self, msg, response_headers.location)
return
else
utilities.send_reply(self, msg, "Fehler beim Erweitern der URL.")
return
end
end
return expand

View File

@@ -1,180 +0,0 @@
local facebook = {}
local http = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
function facebook:init(config)
if not cred_data.fb_access_token then
print('Missing config value: fb_access_token.')
print('facebook.lua will not be enabled.')
return
end
facebook.triggers = {
"facebook.com/([A-Za-z0-9-._-]+)/(posts)/(%d+)",
"facebook.com/(permalink).php%?(story_fbid)=(%d+)&id=(%d+)",
"facebook.com/(photo).php%?fbid=(%d+)",
"facebook.com/([A-Za-z0-9-._-]+)/(photos)/a.(%d+[%d%.]*)/(%d+)",
"facebook.com/(video).php%?v=(%d+)",
"facebook.com/([A-Za-z0-9-._-]+)/(videos)/(%d+[%d%.]*)",
"facebook.com/([A-Za-z0-9-._-]+)"
}
end
local BASE_URL = 'https://graph.facebook.com/v2.5'
local fb_access_token = cred_data.fb_access_token
local makeOurDate = function(dateString)
local pattern = "(%d+)%/(%d+)%/(%d+)"
local month, day, year = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function facebook:get_fb_id(name)
local url = BASE_URL..'/'..name..'?access_token='..fb_access_token..'&locale=de_DE'
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
return data.id
end
function facebook:fb_post (id, story_id)
local url = BASE_URL..'/'..id..'_'..story_id..'?access_token='..fb_access_token..'&locale=de_DE&fields=from,name,story,message,link'
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
local from = data.from.name
local message = data.message
local name = data.name
if data.link then
link = '\n'..data.name..':\n'..data.link
else
link = ""
end
if data.story then
story = ' ('..data.story..')'
else
story = ""
end
local text = '*'..from..'*'..story..':\n'..message..'\n'..link
return text
end
function facebook:send_facebook_photo(photo_id, receiver)
local url = BASE_URL..'/'..photo_id..'?access_token='..fb_access_token..'&locale=de_DE&fields=images,from,name'
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
local from = '*'..data.from.name..'*'
if data.name then
text = from..' hat ein Bild gepostet:\n'..data.name
else
text = from..' hat ein Bild gepostet:'
end
local image_url = data.images[1].source
return text, image_url
end
function facebook:send_facebook_video(video_id)
local url = BASE_URL..'/'..video_id..'?access_token='..fb_access_token..'&locale=de_DE&fields=description,from,source,title'
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
local from = '*'..data.from.name..'*'
local description = data.description
local source = data.source
if data.title then
text = from..' hat ein Video gepostet:\n'..description..'\n['..data.title..']('..source..')'
else
text = from..' hat ein Video gepostet:\n'..description..'\n'..utilities.md_escape(source)
end
return text
end
function facebook:facebook_info(name)
local url = BASE_URL..'/'..name..'?access_token='..fb_access_token..'&locale=de_DE&fields=about,name,birthday,category,founded,general_info,is_verified'
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
local name = data.name
if data.is_verified then
name = name..''
end
local category = data.category
if data.about then
about = '\n'..data.about
else
about = ""
end
if data.general_info then
general_info = '\n'..data.general_info
else
general_info = ""
end
if data.birthday and data.founded then
birth = '\nGeburtstag: '..makeOurDate(data.birthday)
elseif data.birthday and not data.founded then
birth = '\nGeburtstag: '..makeOurDate(data.birthday)
elseif data.founded and not data.birthday then
birth = '\nGegründet: '..data.founded
else
birth = ""
end
local text = '*'..name..'* ('..category..')_'..about..'_'..general_info..birth
return text
end
function facebook:action(msg, config, matches)
if matches[1] == 'permalink' or matches[2] == 'posts' then
story_id = matches[3]
if not matches[4] then
id = facebook:get_fb_id(matches[1])
else
id = matches[4]
end
utilities.send_reply(self, msg, facebook:fb_post(id, story_id), true)
return
elseif matches[1] == 'photo' or matches[2] == 'photos' then
if not matches[4] then
photo_id = matches[2]
else
photo_id = matches[4]
end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local text, image_url = facebook:send_facebook_photo(photo_id, receiver)
local file = download_to_file(image_url)
utilities.send_reply(self, msg, text, true)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
return
elseif matches[1] == 'video' or matches[2] == 'videos' then
if not matches[3] then
video_id = matches[2]
else
video_id = matches[3]
end
local output = facebook:send_facebook_video(video_id)
utilities.send_reply(self, msg, output, true)
return
else
utilities.send_reply(self, msg, facebook:facebook_info(matches[1]), true)
return
end
end
return facebook

View File

@@ -1,222 +0,0 @@
local forecast = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
function forecast:init(config)
if not cred_data.forecastio_apikey then
print('Missing config value: forecastio_apikey.')
print('weather.lua will not be enabled.')
return
elseif not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('weather.lua will not be enabled.')
return
end
forecast.triggers = {
"^(/f)$",
"^(/f) (.*)$",
"^(/fh)$",
"^(/fh) (.*)$",
"^(/forecast)$",
"^(/forecast) (.*)$",
"^(/forecasth)$",
"^(/forecasth) (.*)$"
}
forecast.doc = [[*
]]..config.cmd_pat..[[f*: Wettervorhersage für deinen Wohnort _(/location set <Ort>)_
*]]..config.cmd_pat..[[f* _<Ort>_: Wettervorhersage für diesen Ort
*]]..config.cmd_pat..[[fh*: 24-Stunden-Wettervorhersage für deine Stadt _(/location set [Ort]_
*]]..config.cmd_pat..[[fh* _<Ort>_: 24-Stunden-Wettervorhersage für diesen Ort
]]
end
forecast.command = 'forecast'
local BASE_URL = "https://api.forecast.io/forecast"
local apikey = cred_data.forecastio_apikey
local google_apikey = cred_data.google_apikey
function get_city_name(lat, lng)
local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng)
if city then return city end
local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey
local res, code = HTTPS.request(url)
if code ~= 200 then return 'Unbekannte Stadt' end
local data = JSON.decode(res).results[1]
local city = data.formatted_address
print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"')
redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city)
return city
end
function get_condition_symbol(weather, n)
if weather.data[n].icon == 'clear-day' then
return '☀️'
elseif weather.data[n].icon == 'clear-night' then
return '🌙'
elseif weather.data[n].icon == 'rain' then
return '☔️'
elseif weather.data[n].icon == 'snow' then
return '❄️'
elseif weather.data[n].icon == 'sleet' then
return '🌨'
elseif weather.data[n].icon == 'wind' then
return '💨'
elseif weather.data[n].icon == 'fog' then
return '🌫'
elseif weather.data[n].icon == 'cloudy' then
return '☁️☁️'
elseif weather.data[n].icon == 'partly-cloudy-day' then
return '🌤'
elseif weather.data[n].icon == 'partly-cloudy-night' then
return '🌙☁️'
else
return ''
end
end
function get_temp(weather, n, hourly)
if hourly then
local temperature = string.gsub(round(weather.data[n].temperature, 1), "%.", ",")
local condition = weather.data[n].summary
return temperature..'°C | '..get_condition_symbol(weather, n)..' '..condition
else
local day = string.gsub(round(weather.data[n].temperatureMax, 1), "%.", ",")
local night = string.gsub(round(weather.data[n].temperatureMin, 1), "%.", ",")
local condition = weather.data[n].summary
return '☀️ '..day..'°C | 🌙 '..night..'°C | '..get_condition_symbol(weather, n)..' '..condition
end
end
function forecast:get_forecast(lat, lng)
print('Finde Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:forecast:'..lat..','..lng)
if text then print('...aus dem Cache..') return text end
local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,hourly,alerts,flags'
local response_body = {}
local request_constructor = {
url = url,
method = "GET",
sink = ltn12.sink.table(response_body)
}
local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor)
if not ok then return nil end
local data = JSON.decode(table.concat(response_body))
local ttl = string.sub(response_headers["cache-control"], 9)
local weather = data.daily
local city = get_city_name(lat, lng)
local header = '*Vorhersage für '..city..':*\n_'..weather.summary..'_\n'
local text = '*Heute:* '..get_temp(weather, 1)
local text = text..'\n*Morgen:* '..get_temp(weather, 2)
for day in pairs(weather.data) do
if day > 2 then
text = text..'\n*'..convert_timestamp(weather.data[day].time, '%a, %d.%m')..'*: '..get_temp(weather, day)
end
end
local text = string.gsub(text, "Mon", "Mo")
local text = string.gsub(text, "Tue", "Di")
local text = string.gsub(text, "Wed", "Mi")
local text = string.gsub(text, "Thu", "Do")
local text = string.gsub(text, "Fri", "Fr")
local text = string.gsub(text, "Sat", "Sa")
local text = string.gsub(text, "Sun", "So")
cache_data('forecast', lat..','..lng, header..text, tonumber(ttl), 'key')
return header..text
end
function forecast:get_forecast_hourly(lat, lng)
print('Finde stündliches Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:forecast:'..lat..','..lng..':hourly')
if text then print('...aus dem Cache..') return text end
local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,daily,alerts,flags'
local response_body = {}
local request_constructor = {
url = url,
method = "GET",
sink = ltn12.sink.table(response_body)
}
local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor)
if not ok then return nil end
local data = JSON.decode(table.concat(response_body))
local ttl = string.sub(response_headers["cache-control"], 9)
local weather = data.hourly
local city = get_city_name(lat, lng)
local header = '*24-Stunden-Vorhersage für '..city..':*\n_'..weather.summary..'_'
local text = ""
for hour in pairs(weather.data) do
if hour < 26 then
text = text..'\n*'..convert_timestamp(weather.data[hour].time, '%H:%M Uhr')..'* | '..get_temp(weather, hour, true)
end
end
cache_data('forecast', lat..','..lng..':hourly', header..text, tonumber(ttl), 'key')
return header..text
end
function forecast:action(msg, config, matches)
local user_id = msg.from.id
local city = get_location(user_id)
if matches[2] then
city = matches[2]
else
local set_location = get_location(user_id)
if not set_location then
city = 'Berlin, Deutschland'
else
city = set_location
end
end
local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat')
local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng')
if not lat and not lng then
print('Koordinaten nicht eingespeichert, frage Google...')
coords = utilities.get_coords(city, config)
lat = coords.lat
lng = coords.lon
end
if not lat and not lng then
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
return
end
redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat)
redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng)
if matches[1] == '/forecasth' or matches[1] == '/fh' then
text = forecast:get_forecast_hourly(lat, lng)
else
text = forecast:get_forecast(lat, lng)
end
if not text then
text = '*Konnte die Wettervorhersage für diese Stadt nicht bekommen.*'
end
utilities.send_reply(self, msg, text, true)
end
return forecast

View File

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

View File

@@ -1,74 +0,0 @@
-- 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 Console, and enable image results.
local gImages = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
function gImages:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('gImages.lua will not be enabled.')
return
elseif not cred_data.google_cse_id then
print('Missing config value: google_cse_id.')
print('gImages.lua will not be enabled.')
return
end
gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('img', true):t('i', true):t('insfw', true).table
gImages.doc = [[*
]]..config.cmd_pat..[[img* _<Suchbegriff>_
Sucht Bild mit Google und versendet es (SafeSearch aktiv)
Alias: *]]..config.cmd_pat..[[i*]]
end
gImages.command = 'img <Suchbegriff>'
function gImages:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gImages.doc, true, msg.message_id, true)
return
end
end
print ('Checking if search contains blacklisted word: '..input)
if is_blacklisted(input) then
utilities.send_reply(self, msg, 'Vergiss es! ._.')
return
end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local apikey = cred_data.google_apikey
local cseid = cred_data.google_cse_id
local BASE_URL = 'https://www.googleapis.com/customsearch/v1'
local url = BASE_URL..'/?searchType=image&alt=json&num=10&key='..apikey..'&cx='..cseid..'&safe=high'..'&q=' .. URL.escape(input)
local jstr, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.searchInformation.totalResults == '0' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local i = math.random(jdat.queries.request[1].count)
local img_url = jdat.items[i].link
local file = download_to_file(img_url)
utilities.send_photo(self, msg.chat.id, file, img_url)
end
return gImages

View File

@@ -1,44 +0,0 @@
local gMaps = {}
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
gMaps.command = 'location <query>'
function gMaps:init(config)
gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('location', true):t('loc', true).table
gMaps.doc = [[```
]]..config.cmd_pat..[[location <query>
Returns a location from Google Maps.
Alias: ]]..config.cmd_pat..[[loc
```]]
end
function gMaps:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gMaps.doc, true, msg.message_id, true)
return
end
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
bindings.sendLocation(self, {
chat_id = msg.chat.id,
latitude = coords.lat,
longitude = coords.lon,
reply_to_message_id = msg.message_id
} )
end
return gMaps

View File

@@ -1,77 +0,0 @@
local gSearch = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
gSearch.command = 'google <query>'
function gSearch:init(config)
gSearch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('g', true):t('google', true):t('gnsfw', true).table
gSearch.doc = [[```
]]..config.cmd_pat..[[google <Suchbegriff>
Sendet Suchergebnisse von Google
Alias: ]]..config.cmd_pat..[[g
```]]
end
function gSearch:googlethat(query, config)
local BASE_URL = 'https://www.googleapis.com/customsearch/v1'
local apikey = cred_data.google_apikey
local cseid = cred_data.google_cse_id
local number = 5 -- Set number of results
local api = BASE_URL.."/?key="..apikey.."&cx="..cseid.."&gl=de&num="..number.."&safe=medium&fields=searchInformation%28formattedSearchTime,formattedTotalResults%29,items%28title,link,displayLink%29&"
local parameters = "q=".. (URL.escape(query) or "")
-- Do the request
local res, code = HTTPS.request(api..parameters)
if code == 403 then
utilities.send_reply(self, msg, config.errors.quotaexceeded)
return
end
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local data = JSON.decode(res)
if data.searchInformation.formattedTotalResults == "0" then return nil end
local results={}
for key,result in ipairs(data.items) do
table.insert(results, {
result.title,
result.link,
result.displayLink
})
end
local stats = data.searchInformation.formattedTotalResults..' Ergebnisse, gefunden in '..data.searchInformation.formattedSearchTime..' Sekunden'
return results, stats
end
function gSearch:stringlinks(results, stats)
local stringresults=""
for key,val in ipairs(results) do
stringresults=stringresults.."["..val[1].."]("..val[2]..") - `"..val[3].."`\n"
end
return stringresults..stats
end
function gSearch:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gSearch.doc, true, msg.message_id, true)
return
end
end
local results, stats = gSearch:googlethat(input, config)
utilities.send_message(self, msg.chat.id, gSearch:stringlinks(results, stats), true, nil, true)
end
return gSearch

View File

@@ -1,59 +0,0 @@
local get = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
get.command = 'get <Variable>'
function get:init(config)
get.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('get', true).table
get.doc = [[*
]]..config.cmd_pat..[[get*: Gibt alle Variablen aus
*]]..config.cmd_pat..[[get* _<Variable>_: Gibt _Variable_ aus
Nutze `!set <Variable> <Wert>` zum Setzen von Variablen]]
end
function get:get_value(msg, var_name)
local hash = get_redis_hash(msg, 'variables')
if hash then
local value = redis:hget(hash, var_name)
if not value then
return'Nicht gefunden; benutze /get, um alle Variablen aufzulisten.'
else
return var_name..' = '..value
end
end
end
function get:list_variables(msg)
local hash = get_redis_hash(msg, 'variables')
print(hash)
if hash then
print('Getting variable from redis hash '..hash)
local names = redis:hkeys(hash)
local text = ''
for i=1, #names do
variables = get:get_value(msg, names[i])
text = text..variables.."\n"
end
if text == '' or text == nil then
return 'Keine Variablen vorhanden!'
else
return text
end
end
end
function get:action(msg)
local input = utilities.input(msg.text)
if input then
output = get:get_value(msg, input:match('(.+)'))
else
output = get:list_variables(msg)
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return get

View File

@@ -1,77 +0,0 @@
local github = {}
local http = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
function github:init(config)
github.triggers = {
"github.com/([A-Za-z0-9-_-.-._.]+)/([A-Za-z0-9-_-.-._.]+)/commit/([a-z0-9-]+)",
"github.com/([A-Za-z0-9-_-.-._.]+)/([A-Za-z0-9-_-.-._.]+)/?$"
}
end
local BASE_URL = 'https://api.github.com'
function github:get_gh_data(gh_code, gh_commit_sha)
if gh_commit_sha == nil then
url = BASE_URL..'/repos/'..gh_code
else
url = BASE_URL..'/repos/'..gh_code..'/git/commits/'..gh_commit_sha
end
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res)
return data
end
function github:send_github_data(data)
if not data.owner then return nil end
local name = '*'..data.name..'*'
local description = '_'..data.description..'_'
local owner = data.owner.login
local clone_url = data.clone_url
if data.language == nil or data.language == "" then
language = ''
else
language = '\nSprache: '..data.language
end
if data.open_issues_count == 0 then
issues = ''
else
issues = '\nOffene Bugreports: '..data.open_issues_count
end
if data.homepage == nil or data.homepage == "" then
homepage = ''
else
homepage = '\n[Homepage besuchen]('..data.homepage..')'
end
local text = name..' von '..owner..'\n'..description..'\n`git clone '..clone_url..'`'..language..issues..homepage
return text
end
function github:send_gh_commit_data(gh_code, gh_commit_sha, data)
if not data.committer then return nil end
local committer = data.committer.name
local message = data.message
local text = '`'..gh_code..'@'..gh_commit_sha..'` von *'..committer..'*:\n'..message
return text
end
function github:action(msg, config, matches)
local gh_code = matches[1]..'/'..matches[2]
local gh_commit_sha = matches[3]
local data = github:get_gh_data(gh_code, gh_commit_sha)
if not gh_commit_sha then
output = github:send_github_data(data)
else
output = github:send_gh_commit_data(gh_code, gh_commit_sha, data)
end
utilities.send_reply(self, msg, output, true)
end
return github

View File

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

View File

@@ -1,65 +0,0 @@
local hackernews = {}
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
hackernews.command = 'hackernews'
function hackernews:init(config)
hackernews.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hackernews', true):t('hn', true).table
hackernews.doc = [[```
Returns four (if group) or eight (if private message) top stories from Hacker News.
Alias: ]]..config.cmd_pat..[[hn
```]]
end
function hackernews:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
local jstr, res = HTTPS.request('https://hacker-news.firebaseio.com/v0/topstories.json')
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
local res_count = 4
if msg.chat.id == msg.from.id then
res_count = 8
end
local output = '*Hacker News:*\n'
for i = 1, res_count do
local res_url = 'https://hacker-news.firebaseio.com/v0/item/' .. jdat[i] .. '.json'
jstr, res = HTTPS.request(res_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local res_jdat = JSON.decode(jstr)
local title = res_jdat.title:gsub('%[.+%]', ''):gsub('%(.+%)', ''):gsub('&amp;', '&')
if title:len() > 48 then
title = title:sub(1, 45) .. '...'
end
local url = res_jdat.url
if not url then
utilities.send_reply(self, msg, config.errors.connection)
return
end
if url:find('%(') then
output = output .. '' .. title .. '\n' .. url:gsub('_', '\\_') .. '\n'
else
output = output .. '• [' .. title .. '](' .. url .. ')\n'
end
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return hackernews

View File

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

View File

@@ -1,61 +0,0 @@
-- This plugin should go at the end of your plugin list in
-- config.lua, but not after greetings.lua.
local help = {}
local utilities = require('otouto.utilities')
local help_text
function help:init(config)
local commandlist = {}
help_text = '*Verfügbare Befehle:*\n'..config.cmd_pat
for _,plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
--help_text = help_text .. '\n• '..config.cmd_pat .. plugin.command:gsub('%[', '\\[')
end
end
table.insert(commandlist, 'hilfe [Plugin]')
table.sort(commandlist)
help_text = help_text .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nParameter: <benötigt> [optional]'
help_text = help_text:gsub('%[', '\\[')
help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hilfe', true):t('help', true).table
end
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 = utilities.send_message(self, msg.from.id, help_text, true, nil, true)
if not res then
utilities.send_reply(self, msg, 'Bitte schreibe mir zuerst [privat](http://telegram.me/' .. self.info.username .. '?start=help) für eine Hilfe.', true)
elseif msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'Ich habe dir die Hilfe per PN gesendet!.')
end
return
end
for _,plugin in ipairs(self.plugins) do
if plugin.command and utilities.get_word(plugin.command, 1) == input and plugin.doc then
local output = '*Hilfe für* _' .. utilities.get_word(plugin.command, 1) .. '_ *:*' .. plugin.doc
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
end
utilities.send_reply(self, msg, 'Für diesen Befehl gibt es keine Hilfe.')
end
return help

View File

@@ -1,16 +0,0 @@
local images = {}
local utilities = require('otouto.utilities')
images.triggers = {
"(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Pp][Nn][Gg])$",
"(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Jj][Pp][Ee]?[Gg])$"
}
function images:action(msg)
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local url = matches[1]
local file = download_to_file(url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
return images

View File

@@ -1,59 +0,0 @@
local imdb = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
imdb.command = 'imdb <query>'
function imdb:init(config)
imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table
imdb.doc = [[*
]]..config.cmd_pat..[[imdb* _<Film>_
Sucht _Film_ bei IMDB]]
end
function imdb:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, imdb.doc, true, msg.message_id, true)
return
end
end
local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input)
local jstr, res = HTTP.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.Response ~= 'True' then
utilities.send_reply(self, msg, config.errors.results)
return
end
local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')* von '..jdat.Director..'\n'
output = output .. string.gsub(jdat.imdbRating, '%.', ',') ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n'
output = output .. '_' .. jdat.Plot .. '_\n'
output = output .. '[IMDB-Seite besuchen](http://imdb.com/title/' .. jdat.imdbID .. ')'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
if jdat.Poster ~= "N/A" then
local file = download_to_file(jdat.Poster)
utilities.send_photo(self, msg.chat.id, file)
end
end
return imdb

View File

@@ -1,76 +0,0 @@
local imgblacklist = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
imgblacklist.command = 'imgblacklist'
function imgblacklist:init(config)
imgblacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imgblacklist', true).table
imgblacklist.doc = [[*
]]..config.cmd_pat..[[imgblacklist* _show_: Zeige Blacklist
*]]..config.cmd_pat..[[imgblacklist* _add_ _<Wort>_: Fügt Wort der Blacklist hinzu
*]]..config.cmd_pat..[[imgblacklist* _remove_ _<Wort>_: Entfernt Wort von der Blacklist]]
end
function imgblacklist:show_blacklist()
if not _blacklist[1] then
return "Keine Wörter geblacklisted!\nBlackliste welche mit `/imgblacklist add [Wort]`"
else
local sort_alph = function( a,b ) return a < b end
table.sort( _blacklist, sort_alph )
local blacklist = "Folgende Wörter stehen auf der Blacklist:\n"
for v,word in pairs(_blacklist) do
blacklist = blacklist..'- '..word..'\n'
end
return blacklist
end
end
function imgblacklist:add_blacklist(word)
print('Blacklisting '..word..' - saving to redis set telegram:img_blacklist')
if redis:sismember("telegram:img_blacklist", word) == true then
return '"'..word..'" steht schon auf der Blacklist.'
else
redis:sadd("telegram:img_blacklist", word)
return '"'..word..'" blacklisted!'
end
end
function imgblacklist:remove_blacklist(word)
print('De-blacklisting '..word..' - removing from redis set telegram:img_blacklist')
if redis:sismember("telegram:img_blacklist", word) == true then
redis:srem("telegram:img_blacklist", word)
return '"'..word..'" erfolgreich von der Blacklist gelöscht!'
else
return '"'..word..'" steht nicht auf der Blacklist.'
end
end
function imgblacklist:action(msg)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local input = utilities.input(msg.text)
local input = string.lower(input)
_blacklist = redis:smembers("telegram:img_blacklist")
if input:match('(add) (.*)') then
local word = input:match('add (.*)')
output = imgblacklist:add_blacklist(word)
elseif input:match('(remove) (.*)') then
local word = input:match('remove (.*)')
output = imgblacklist:remove_blacklist(word)
elseif input:match('(show)') then
output = imgblacklist:show_blacklist()
else
utilities.send_message(self, msg.chat.id, imgblacklist.doc, true, msg.message_id, true)
return
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return imgblacklist

View File

@@ -1,111 +0,0 @@
-- TODO: Add support for librefm API.
-- Just kidding, nobody actually uses that.
local lastfm = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
function lastfm:init(config)
if not config.lastfm_api_key then
print('Missing config value: lastfm_api_key.')
print('lastfm.lua will not be enabled.')
return
end
lastfm.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('fmset', true).table
lastfm.doc = [[```
]]..config.cmd_pat..[[np [username]
Returns what you are or were last listening to. If you specify a username, info will be returned for that username.
]]..config.cmd_pat..[[fmset <username>
Sets your last.fm username. Otherwise, ]]..config.cmd_pat..[[np will use your Telegram username. Use "]]..config.cmd_pat..[[fmset --" to delete it.
```]]
end
lastfm.command = 'lastfm'
function lastfm:action(msg, config)
local input = utilities.input(msg.text)
if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then
utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
return
elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then
if not input then
utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
elseif input == '--' or input == utilities.char.em_dash then
self.database.users[msg.from.id_str].lastfm = nil
utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.')
else
self.database.users[msg.from.id_str].lastfm = input
utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".')
end
return
end
local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user='
local username
local alert = ''
if input then
username = input
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 '..config.cmd_pat..'fmset <username>.'
self.database.users[msg.from.id_str].lastfm = username
else
utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.')
return
end
url = url .. URL.escape(username)
local jstr, res
utilities.with_http_timeout(
1, function ()
jstr, res = HTTP.request(url)
end)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
if jdat.error then
utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.')
return
end
jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then
utilities.send_reply(self, msg, 'No history for this user.' .. alert)
return
end
local output = input or msg.from.first_name
output = '🎵 ' .. output
if jdat['@attr'] and jdat['@attr'].nowplaying then
output = output .. ' is currently listening to:\n'
else
output = output .. ' last listened to:\n'
end
local title = jdat.name or 'Unknown'
local artist = 'Unknown'
if jdat.artist then
artist = jdat.artist['#text']
end
output = output .. title .. ' - ' .. artist .. alert
utilities.send_message(self, msg.chat.id, output)
end
return lastfm

View File

@@ -1,69 +0,0 @@
local loc_manager = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
function loc_manager:init(config)
loc_manager.triggers = {
"^/location (set) (.*)$",
"^/location (del)$",
"^/location$"
}
loc_manager.doc = [[*
]]..config.cmd_pat..[[location*: Gibt deinen gesetzten Wohnort aus
*]]..config.cmd_pat..[[location* _set_ _<Ort>_: Setzt deinen Wohnort auf diesen Ort
*]]..config.cmd_pat..[[location* _del_: Löscht deinen angegebenen Wohnort
]]
end
loc_manager.command = 'location'
function loc_manager:set_location(user_id, location)
local hash = 'user:'..user_id
local set_location = get_location(user_id)
if set_location == location then
return 'Dieser Ort wurde bereits gesetzt.'
else
print('Setting location in redis hash '..hash..' to location')
redis:hset(hash, 'location', location)
return 'Dein Wohnort wurde auf *'..location..'* festgelegt.'
end
end
function loc_manager:del_location(user_id)
local hash = 'user:'..user_id
local set_location = get_location(user_id)
if not set_location then
return 'Du hast keinen Ort gesetzt'
else
print('Setting location in redis hash '..hash..' to false')
-- We set the location to false, because deleting the value blocks redis for a few milliseconds
redis:hset(hash, 'location', false)
return 'Dein Wohnort *'..set_location..'* wurde gelöscht!'
end
end
function loc_manager:action(msg, config, matches)
local user_id = msg.from.id
if matches[1] == 'set' then
utilities.send_reply(self, msg, loc_manager:set_location(user_id, matches[2]), true)
return
elseif matches[1] == 'del' then
utilities.send_reply(self, msg, loc_manager:del_location(user_id), true)
return
else
local set_location = get_location(user_id)
if not set_location then
utilities.send_reply(self, msg, '*Du hast keinen Ort gesetzt!*', true)
return
else
local coords = utilities.get_coords(set_location, config)
utilities.send_location(self, msg.chat.id, coords.lat, coords.lon, msg.message_id)
utilities.send_reply(self, msg, 'Gesetzter Wohnort: *'..set_location..'*', true)
return
end
end
end
return loc_manager

View File

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

View File

@@ -1,29 +0,0 @@
local me = {}
local utilities = require('otouto.utilities')
function me:init(config)
me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table
end
function me:action(msg, config)
local target = self.database.users[msg.from.id_str]
if msg.from.id == config.admin and (msg.reply_to_message or utilities.input(msg.text)) then
target = utilities.user_from_message(self, msg, true)
if target.err then
utilities.send_reply(self, msg, target.err)
return
end
end
local output = ''
for k,v in pairs(target) do
output = output .. '*' .. k .. ':* `' .. tostring(v) .. '`\n'
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return me

View File

@@ -1,67 +0,0 @@
local media = {}
local utilities = require('otouto.utilities')
local mimetype = (loadfile "./otouto/mimetype.lua")()
media.triggers = {
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(gif))$",
"^(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(mp4))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(pdf))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(ogg))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(zip))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(tar.gz))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(7z))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(mp3))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(rar))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(wmv))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(doc))$",
"^(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(avi))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(wav))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(apk))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(webm))$",
"^(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(ogv))$",
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(webp))$"
}
function media:action(msg)
local url = matches[1]
local ext = matches[2]
local receiver = msg.chat.id
utilities.send_typing(self, receiver, 'upload_document')
local file = download_to_file(url)
local mime_type = mimetype.get_content_type_no_sub(ext)
if ext == 'gif' then
print('send gif')
utilities.send_document(self, receiver, file, nil, msg.message_id)
return
elseif mime_type == 'text' then
print('send_document')
utilities.send_document(self, receiver, file, nil, msg.message_id)
return
elseif mime_type == 'image' then
print('send_photo')
utilities.send_photo(self, receiver, file, nil, msg.message_id)
return
elseif mime_type == 'audio' then
print('send_audio')
utilities.send_audio(self, receiver, file, nil, msg.message_id)
return
elseif mime_type == 'video' then
print('send_video')
utilities.send_video(self, receiver, file, nil, msg.message_id)
return
else
print('send_file')
utilities.send_document(self, receiver, file, nil, msg.message_id)
return
end
end
return media

View File

@@ -1,51 +0,0 @@
local nick = {}
local utilities = require('otouto.utilities')
nick.command = 'nick <nickname>'
function nick:init(config)
nick.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('nick', true).table
nick.doc = [[```
]]..config.cmd_pat..[[nick <nickname>
Set your nickname. Use "]]..config.cmd_pat..[[nick --" to delete it.
```]]
end
function nick:action(msg, config)
local target = msg.from
if msg.from.id == 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
if target.last_name then
target.name = target.first_name .. ' ' .. target.last_name
end
end
local output
local input = utilities.input(msg.text)
if not input then
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 utilities.utf8_len(input) > 32 then
output = 'The character limit for nicknames is 32.'
elseif input == '--' or input == utilities.char.em_dash then
self.database.users[target.id_str].nickname = nil
output = target.name .. '\'s nickname has been deleted.'
else
input = input:gsub('\n', ' ')
self.database.users[target.id_str].nickname = input
output = target.name .. '\'s nickname has been set to "' .. input .. '".'
end
utilities.send_reply(self, msg, output)
end
return nick

View File

@@ -1,45 +0,0 @@
local pasteee = {}
local bot = require('otouto.bot')
local utilities = require('otouto.utilities')
function pasteee:init(config)
if not cred_data.pasteee_key then
print('Missing config value: pasteee_key.')
print('pasteee.lua will not be enabled, listquotes won\'t be available.')
return
end
pasteee.triggers = {
"^/pasteee (.*)$"
}
pasteee.doc = [[*
]]..config.cmd_pat..[[pasteee* _<Text>_: Postet Text auf Paste.ee]]
end
pasteee.command = 'pasteee <Text>'
local key = cred_data.pasteee_key
function upload(text, noraw)
local url = "https://paste.ee/api"
local pet = post_petition(url, 'key='..key..'&paste='..text..'&format=json')
if pet.status ~= 'success' then return 'Ein Fehler ist aufgetreten: '..pet.error, true end
if noraw then
return pet.paste.link
else
return pet.paste.raw
end
end
function pasteee:action(msg, config, matches)
local text = matches[1]
local link, iserror = upload(text)
if iserror then
utilities.send_reply(self, msg, link)
return
end
utilities.send_reply(self, msg, '[Text auf Paste.ee ansehen]('..link..')', true)
end
return pasteee

View File

@@ -1,33 +0,0 @@
local patterns = {}
local utilities = require('otouto.utilities')
patterns.triggers = {
'^/?s/.-/.-$'
}
function patterns:action(msg)
if not msg.reply_to_message then return true end
local output = msg.reply_to_message.text
if msg.reply_to_message.from.id == self.info.id then
output = output:gsub('Did you mean:\n"', '')
output = output:gsub('"$', '')
end
local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$')
if not m2 then return true end
local res
res, output = pcall(
function()
return output:gsub(m1, m2)
end
)
if res == false then
utilities.send_reply(self, msg, 'Malformed pattern!')
else
output = output:sub(1, 4000)
output = 'Did you mean:\n"' .. output .. '"'
utilities.send_reply(self, msg.reply_to_message, output)
end
end
return patterns

View File

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

View File

@@ -1,71 +0,0 @@
local pokedex = {}
local HTTP = require('socket.http')
local JSON = require('dkjson')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
pokedex.command = 'pokedex <query>'
function pokedex:init(config)
pokedex.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pokedex', true):t('dex', true).table
pokedex.doc = [[```
]]..config.cmd_pat..[[pokedex <query>
Returns a Pokedex entry from pokeapi.co.
Alias: ]]..config.cmd_pat..[[dex
```]]
end
function pokedex:action(msg, config)
bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } )
local input = utilities.input(msg.text_lower)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, pokedex.doc, true, msg.message_id, true)
return
end
end
local url = 'http://pokeapi.co'
local dex_url = url .. '/api/v1/pokemon/' .. input
local dex_jstr, res = HTTP.request(dex_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local dex_jdat = JSON.decode(dex_jstr)
local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri
local desc_jstr, _ = HTTP.request(desc_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local desc_jdat = JSON.decode(desc_jstr)
local poke_type
for _,v in ipairs(dex_jdat.types) do
local type_name = v.name:gsub("^%l", string.upper)
if not poke_type then
poke_type = type_name
else
poke_type = poke_type .. ' / ' .. type_name
end
end
poke_type = poke_type .. ' type'
local output = '*' .. dex_jdat.name .. '*\n#' .. dex_jdat.national_id .. ' | ' .. poke_type .. '\n_' .. desc_jdat.description:gsub('POKMON', 'Pokémon'):gsub('Pokmon', 'Pokémon') .. '_'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return pokedex

View File

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

View File

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

View File

@@ -1,111 +0,0 @@
local quotes = {}
local bot = require('otouto.bot')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
require("./otouto/plugins/pasteee")
function quotes:init(config)
quotes.triggers = {
"^/(delquote) (.+)$",
"^/(addquote) (.+)$",
"^/(quote)$",
"^/(listquotes)$"
}
quotes.doc = [[*
]]..config.cmd_pat..[[addquote* _<Zitat>_: Fügt Zitat hinzu.
*]]..config.cmd_pat..[[delquote* _<Zitat>_: Löscht das Zitat (nur Superuser)
*]]..config.cmd_pat..[[quote*: Gibt zufälliges Zitat aus
*]]..config.cmd_pat..[[listquotes*: Listet alle Zitate auf
]]
end
quotes.command = 'quote'
function quotes:save_quote(msg)
if msg.text:sub(11):isempty() then
return "Benutzung: /addquote [Zitat]"
end
local quote = msg.text:sub(11)
local hash = get_redis_hash(msg, 'quotes')
print('Saving quote to redis set '..hash)
redis:sadd(hash, quote)
return '*Gespeichert!*'
end
function quotes:delete_quote(msg)
if msg.text:sub(11):isempty() then
return "Benutzung: /delquote [Zitat]"
end
local quote = msg.text:sub(11)
local hash = get_redis_hash(msg, 'quotes')
print('Deleting quote from redis set '..hash)
if redis:sismember(hash, quote) == true then
redis:srem(hash, quote)
return '*Zitat erfolgreich gelöscht!*'
else
return 'Dieses Zitat existiert nicht.'
end
end
function quotes:get_quote(msg)
local hash = get_redis_hash(msg, 'quotes')
if hash then
print('Getting quote from redis set '..hash)
local quotes_table = redis:smembers(hash)
if not quotes_table[1] then
return 'Es wurden noch keine Zitate gespeichert.\nSpeichere doch welche mit /addquote [Zitat]'
else
return quotes_table[math.random(1,#quotes_table)]
end
end
end
function quotes:list_quotes(msg)
local hash = get_redis_hash(msg, 'quotes')
if hash then
print('Getting quotes from redis set '..hash)
local quotes_table = redis:smembers(hash)
local text = ""
for num,quote in pairs(quotes_table) do
text = text..num..") "..quote..'\n'
end
if not text or text == "" then
return 'Es wurden noch keine Zitate gespeichert.\nSpeichere doch welche mit !addquote [Zitat]'
else
return upload(text)
end
end
end
function quotes:action(msg, config, matches)
if matches[1] == "quote" then
utilities.send_message(self, msg.chat.id, quotes:get_quote(msg), true)
return
elseif matches[1] == "addquote" and matches[2] then
utilities.send_reply(self, msg, quotes:save_quote(msg), true)
return
elseif matches[1] == "delquote" and matches[2] then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
utilities.send_reply(self, msg, quotes:delete_quote(msg), true)
return
elseif matches[1] == "listquotes" then
local link, iserror = quotes:list_quotes(msg)
if iserror then
utilities.send_reply(self, msg, link)
return
end
utilities.send_reply(self, msg, '[Lise aller Zitate auf Paste.ee ansehen]('..link..')', true)
return
end
utilities.send_reply(self, msg, quotes.doc, true)
end
return quotes

View File

@@ -1,52 +0,0 @@
-- Never change this plugin. It was not meant to be changed.
-- You may add reactions. You must never remove reactions.
-- You must never restructure. You must never disable this plugin.
-- - Drew, creator, a year later.
-- Nevermind, Brayden changed it.
-- - Drew, just now.
local reactions = {}
local utilities = require('otouto.utilities')
reactions.command = 'reactions'
reactions.doc = '`Returns a list of "reaction" emoticon commands.`'
local mapping = {
['shrug'] = '¯\\_(ツ)_/¯',
['lenny'] = '( ͡° ͜ʖ ͡°)',
['flip'] = '(╯°□°)╯︵ ┻━┻',
['homo'] = ' o',
['look'] = 'ಠ_ಠ',
['shots?'] = 'SHOTS FIRED',
['facepalm'] = '(-‸ლ)'
}
local help
function reactions:init(config)
-- Generate a "help" message triggered by "/reactions".
help = 'Reactions:\n'
reactions.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('reactions').table
for trigger,reaction in pairs(mapping) do
help = help .. '' .. config.cmd_pat .. trigger:gsub('.%?', '') .. ': ' .. reaction .. '\n'
table.insert(reactions.triggers, config.cmd_pat..trigger)
table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..self.info.username:lower())
end
end
function reactions:action(msg, config)
if string.match(msg.text_lower, config.cmd_pat..'reactions') then
utilities.send_message(self, msg.chat.id, help)
return
end
for trigger,reaction in pairs(mapping) do
if string.match(msg.text_lower, config.cmd_pat..trigger) then
utilities.send_message(self, msg.chat.id, reaction)
return
end
end
end
return reactions

View File

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

View File

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

View File

@@ -1,86 +0,0 @@
local respond = {}
local https = require('ssl.https')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
function respond:init(config)
respond.triggers = {
"([Ff][Gg][Tt].? [Ss][Ww][Ii][Ff][Tt])",
"([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Ss])",
"([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Rr])",
"([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee])",
"^[Bb][Oo][Tt]%??$",
"^/([Ll][Oo][Dd])$",
"^/([Ll][Ff])$",
"^/([Kk][Aa])$",
"^/([Ii][Dd][Kk])$",
"^/([Nn][Bb][Cc])$",
"^/([Ii][Dd][Cc])$",
"^%*([Ff][Rr][Oo][Ss][Cc][Hh])%*",
"^/([Ff][Rr][Oo][Ss][Cc][Hh])$",
"^%(([Ii][Nn][Ll][Oo][Vv][Ee])%)$",
"^/[Ww][Aa][Tt]$"
}
end
respond.command = 'lod, /lf, /nbc, /wat'
function respond:action(msg, config, matches)
local user_name = get_name(msg)
local receiver = msg.chat.id
local GDRIVE_URL = 'https://de2319bd4b4b51a5ef2939a7638c1d35646f49f8.googledrive.com/host/0B_mfIlDgPiyqU25vUHZqZE9IUXc'
if user_name == "DefenderX" then user_name = "Deffu" end
if string.match(msg.text, "[Ff][Gg][Tt].? [Ss][Ww][Ii][Ff][Tt]") then
utilities.send_message(self, receiver, 'Dünnes Eis, '..user_name..'!')
return
elseif string.match(msg.text, "([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Ss])") then
utilities.send_message(self, receiver, '*einziges')
return
elseif string.match(msg.text, "([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Rr])") then
utilities.send_message(self, receiver, '*einziger')
return
elseif string.match(msg.text, "([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee])") then
utilities.send_message(self, receiver, '*einzige')
return
elseif string.match(msg.text, "[Bb][Oo][Tt]%??") then
utilities.send_reply(self, msg, '*Ich bin da, '..user_name..'!*', true)
return
elseif string.match(msg.text, "[Ll][Oo][Dd]") then
utilities.send_message(self, receiver, 'ಠ_ಠ')
return
elseif string.match(msg.text, "[Ll][Ff]") then
utilities.send_message(self, receiver, '( ͡° ͜ʖ ͡°)')
return
elseif string.match(msg.text, "[Nn][Bb][Cc]") or string.match(msg.text, "[Ii][Dd][Cc]") or string.match(msg.text, "[Kk][Aa]") or string.match(msg.text, "[Ii][Dd][Kk]") then
utilities.send_message(self, receiver, [[¯\_(ツ)_/¯]])
return
elseif string.match(msg.text, "[Ff][Rr][Oo][Ss][Cc][Hh]") then
utilities.send_message(self, receiver, '🐸🐸🐸')
return
elseif string.match(msg.text, "[Ii][Nn][Ll][Oo][Vv][Ee]") then
local file = download_to_file(GDRIVE_URL..'/inlove.gif')
utilities.send_document(self, receiver, file)
return
elseif string.match(msg.text, "[Ww][Aa][Tt]") then
local WAT_URL = GDRIVE_URL..'/wat'
local wats = {
"/wat1.jpg",
"/wat2.jpg",
"/wat3.jpg",
"/wat4.jpg",
"/wat5.jpg",
"/wat6.jpg",
"/wat7.jpg",
"/wat8.jpg"
}
local random_wat = math.random(5)
local file = download_to_file(WAT_URL..wats[random_wat])
utilities.send_photo(self, receiver, file)
return
end
end
return respond

View File

@@ -1,31 +0,0 @@
local roll = {}
local utilities = require('otouto.utilities')
roll.command = 'roll'
function roll:init(config)
roll.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('roll', true).table
roll.doc = [[*
]]..config.cmd_pat..[[roll*: Werfe einen Würfel]]
end
local canroll = {
"1",
"2",
"3",
"4",
"5",
"6"
}
function roll:roll_dice()
local randomroll = math.random(6)
return canroll[randomroll]
end
function roll:action(msg)
utilities.send_reply(self, msg, 'Du hast eine *'..roll:roll_dice()..'* gewürfelt.', true)
end
return roll

View File

@@ -1,302 +0,0 @@
local rss = {}
local http = require('socket.http')
local https = require('ssl.https')
local url = require('socket.url')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local feedparser = require("feedparser")
rss.command = 'rss <sub/del>'
function rss:init(config)
rss.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rss', true).table
rss.doc = [[*
]]..config.cmd_pat..[[rss*: Feed-Abonnements anzeigen
*]]..config.cmd_pat..[[rss* _sub_ _<URL>_: Diesen Feed abonnieren
*]]..config.cmd_pat..[[rss* _del_ _<#>_: Diesen Feed deabonnieren
*]]..config.cmd_pat..[[rss* _sync_: Feeds syncen (nur Superuser)]]
end
function tail(n, k)
local u, r=''
for i=1,k do
n,r = math.floor(n/0x40), n%0x40
u = string.char(r+0x80) .. u
end
return u, n
end
function to_utf8(a)
local n, r, u = tonumber(a)
if n<0x80 then -- 1 byte
return string.char(n)
elseif n<0x800 then -- 2 byte
u, n = tail(n, 1)
return string.char(n+0xc0) .. u
elseif n<0x10000 then -- 3 byte
u, n = tail(n, 2)
return string.char(n+0xe0) .. u
elseif n<0x200000 then -- 4 byte
u, n = tail(n, 3)
return string.char(n+0xf0) .. u
elseif n<0x4000000 then -- 5 byte
u, n = tail(n, 4)
return string.char(n+0xf8) .. u
else -- 6 byte
u, n = tail(n, 5)
return string.char(n+0xfc) .. u
end
end
function unescape_for_rss(str)
str = string.gsub( str, '&lt;', '<' )
str = string.gsub( str, '&gt;', '>' )
str = string.gsub( str, '&quot;', '"' )
str = string.gsub( str, '&apos;', "'" )
str = string.gsub( str, "&Auml;", "Ä")
str = string.gsub( str, "&auml;", "ä")
str = string.gsub( str, "&Ouml;", "Ö")
str = string.gsub( str, "&ouml;", "ö")
str = string.gsub( str, "Uuml;", "Ü")
str = string.gsub( str, "&uuml;", "ü")
str = string.gsub( str, "&szlig;", "ß")
str = string.gsub(str, '&#(%d+);', to_utf8)
str = string.gsub( str, '&#x(%d+);', function(n) return string.char(tonumber(n,16)) end )
str = string.gsub( str, '&amp;', '&' ) -- Be sure to do this after all others
return str
end
function get_base_redis(id, option, extra)
local ex = ''
if option ~= nil then
ex = ex .. ':' .. option
if extra ~= nil then
ex = ex .. ':' .. extra
end
end
return 'rss:' .. id .. ex
end
function prot_url(url)
local url, h = string.gsub(url, "http://", "")
local url, hs = string.gsub(url, "https://", "")
local protocol = "http"
if hs == 1 then
protocol = "https"
end
return url, protocol
end
function get_rss(url, prot)
local res, code = nil, 0
if prot == "http" then
res, code = http.request(url)
elseif prot == "https" then
res, code = https.request(url)
end
if code ~= 200 then
return nil, "Fehler beim Erreichen von " .. url
end
local parsed = feedparser.parse(res)
if parsed == nil then
return nil, "Fehler beim Dekodieren des Feeds.\nBist du sicher, dass "..url.." ein Feed ist?"
end
return parsed, nil
end
function get_new_entries(last, nentries)
local entries = {}
for k,v in pairs(nentries) do
if v.id == last then
return entries
else
table.insert(entries, v)
end
end
return entries
end
function print_subs(id, chat_name)
local uhash = get_base_redis(id)
local subs = redis:smembers(uhash)
local text = '"'..chat_name..'" hat abonniert:\n---------\n'
for k,v in pairs(subs) do
text = text .. k .. ") " .. v .. '\n'
end
return text
end
function rss:subscribe(id, url)
local baseurl, protocol = prot_url(url)
local prothash = get_base_redis(baseurl, "protocol")
local lasthash = get_base_redis(baseurl, "last_entry")
local lhash = get_base_redis(baseurl, "subs")
local uhash = get_base_redis(id)
if redis:sismember(uhash, baseurl) then
return "Du hast `"..url.."` bereits abonniert."
end
local parsed, err = get_rss(url, protocol)
if err ~= nil then
return err
end
local last_entry = ""
if #parsed.entries > 0 then
last_entry = parsed.entries[1].id
end
local name = parsed.feed.title
redis:set(prothash, protocol)
redis:set(lasthash, last_entry)
redis:sadd(lhash, id)
redis:sadd(uhash, baseurl)
return "_"..name.."_ abonniert!"
end
function rss:unsubscribe(id, n)
if #n > 5 then
return "Du kannst nicht mehr als fünf Feeds abonnieren!"
end
n = tonumber(n)
local uhash = get_base_redis(id)
local subs = redis:smembers(uhash)
if n < 1 or n > #subs then
return "Abonnement-ID zu hoch!"
end
local sub = subs[n]
local lhash = get_base_redis(sub, "subs")
redis:srem(uhash, sub)
redis:srem(lhash, id)
local left = redis:smembers(lhash)
if #left < 1 then -- no one subscribed, remove it
local prothash = get_base_redis(sub, "protocol")
local lasthash = get_base_redis(sub, "last_entry")
redis:del(prothash)
redis:del(lasthash)
end
return "Du hast `"..sub.."` deabonniert."
end
function rss:print_subs(id, chat_name)
local uhash = get_base_redis(id)
local subs = redis:smembers(uhash)
if not subs[1] then
return 'Keine Feeds abonniert!'
end
local text = '*'..chat_name..'* hat abonniert:\n---------\n'
for k,v in pairs(subs) do
text = text .. k .. ") " .. v .. '\n'
end
return text
end
function rss:action(msg, config)
local input = utilities.input(msg.text)
local id = "user#id" .. msg.from.id
if msg.chat.type == 'channel' then
print('Kanäle werden momentan nicht unterstützt')
end
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
id = 'chat#id'..msg.chat.id
end
if not input then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
chat_name = msg.chat.title
else
chat_name = msg.chat.first_name
end
local output = rss:print_subs(id, chat_name)
utilities.send_reply(self, msg, output, true)
return
end
if input:match('(sub) (https?://[%w-_%.%?%.:/%+=&%~]+)$') then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local rss_url = input:match('(https?://[%w-_%.%?%.:/%+=&%~]+)$')
local output = rss:subscribe(id, rss_url)
utilities.send_reply(self, msg, output, true)
elseif input:match('(del) (%d+)$') then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local rss_url = input:match('(%d+)$')
local output = rss:unsubscribe(id, rss_url)
utilities.send_reply(self, msg, output, true)
elseif input:match('(sync)$') then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
rss:cron(self)
end
return
end
function rss:cron(self_plz)
if not self.BASE_URL then
self = self_plz
end
local keys = redis:keys(get_base_redis("*", "subs"))
for k,v in pairs(keys) do
local base = string.match(v, "rss:(.+):subs") -- Get the URL base
print('RSS: '..base)
local prot = redis:get(get_base_redis(base, "protocol"))
local last = redis:get(get_base_redis(base, "last_entry"))
local url = prot .. "://" .. base
local parsed, err = get_rss(url, prot)
if err ~= nil then
return
end
-- local feed_title = parsed.feed.title
local newentr = get_new_entries(last, parsed.entries)
local subscribers = {}
local text = '' -- Send one message per feed with the latest entries
for k2, v2 in pairs(newentr) do
local title = v2.title or 'Kein Titel'
local link = v2.link or v2.id or 'Kein Link'
if v2.content then
if string.len(v2.content) > 250 then
content = string.sub(unescape_for_rss(v2.content:gsub("%b<>", "")), 1, 250) .. '...'
else
content = unescape_for_rss(v2.content:gsub("%b<>", ""))
end
elseif v2.summary then
if string.len(v2.summary) > 250 then
content = string.sub(unescape_for_rss(v2.summary:gsub("%b<>", "")), 1, 250) .. '...'
else
content = unescape_for_rss(v2.summary:gsub("%b<>", ""))
end
else
content = ''
end
text = text..'\n*'..title..'*\n'..content..' [Weiterlesen]('..link..')\n'
end
if text ~= '' then
local newlast = newentr[1].id
redis:set(get_base_redis(base, "last_entry"), newlast)
for k2, receiver in pairs(redis:smembers(v)) do
local receiver = string.gsub(receiver, 'chat%#id', '')
local receiver = string.gsub(receiver, 'user%#id', '')
utilities.send_message(self, receiver, text, true, nil, true)
end
end
end
end
return rss

View File

@@ -1,55 +0,0 @@
local set = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
set.command = 'set <Variable> <Wert>'
function set:init(config)
set.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('set', true).table
set.doc = [[*
]]..config.cmd_pat..[[set* _<Variable>_ _<Wert>_: Speichert eine Variable mit einem Wert
*]]..config.cmd_pat..[[set* _<Variable>_ _nil_: Löscht Variable
Nutze `!get <Variable>` zum Abrufen]]
end
function set:save_value(msg, name, value)
local hash = get_redis_hash(msg, 'variables')
if hash then
print('Saving variable to redis hash '..hash)
redis:hset(hash, name, value)
return "Gespeichert: "..name.." = "..value
end
end
function set:delete_value(msg, name)
local hash = get_redis_hash(msg, 'variables')
if redis:hexists(hash, name) == true then
print('Deleting variable from redis hash '..hash)
redis:hdel(hash, name)
return 'Variable "'..name..'" erfolgreich gelöscht!'
else
return 'Du kannst keine Variable löschen, die nicht existiert .-.'
end
end
function set:action(msg)
local input = utilities.input(msg.text)
if not input:match('([^%s]+) (.+)') then
utilities.send_message(self, msg.chat.id, set.doc, true, msg.message_id, true)
return
end
local name = input:match('([^%s]+) ')
local value = input:match(' (.+)')
if value == "nil" then
output = set:delete_value(msg, name)
else
output = set:save_value(msg, name, value)
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return set

View File

@@ -1,33 +0,0 @@
local shell = {}
local utilities = require('otouto.utilities')
function shell:init(config)
shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('sh', true).table
end
function shell:action(msg, config)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
end
local input = utilities.input(msg.text)
input = input:gsub('', '--')
if not input then
utilities.send_reply(self, msg, 'Bitte gebe ein Kommando ein.')
return
end
local output = io.popen(input):read('*all')
if output:len() == 0 then
output = 'Ausgeführt.'
else
output = '```\n' .. output .. '\n```'
end
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
end
return shell

View File

@@ -1,52 +0,0 @@
local shout = {}
local utilities = require('otouto.utilities')
shout.command = 'shout <text>'
function shout:init(config)
shout.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('shout', true).table
shout.doc = [[```
]]..config.cmd_pat..[[shout <text>
Shouts something. Input may be the replied-to message.
```]]
end
function shout:action(msg)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and #msg.reply_to_message.text > 0 then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, shout.doc, true, msg.message_id, true)
return
end
end
input = utilities.trim(input)
if input:len() > 20 then
input = input:sub(1,20)
end
input = input:upper()
local output = ''
local inc = 0
for match in input:gmatch('([%z\1-\127\194-\244][\128-\191]*)') do
output = output .. match .. ' '
end
output = output .. '\n'
for match in input:sub(2):gmatch('([%z\1-\127\194-\244][\128-\191]*)') do
local spacing = ''
for _ = 1, inc do
spacing = spacing .. ' '
end
inc = inc + 1
output = output .. match .. ' ' .. spacing .. match .. '\n'
end
output = '```\n' .. utilities.trim(output) .. '\n```'
utilities.send_message(self, msg.chat.id, output, true, false, true)
end
return shout

View File

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

View File

@@ -1,112 +0,0 @@
local tagesschau_eil = {}
local http = require('socket.http')
local https = require('ssl.https')
local url = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
tagesschau_eil.command = 'eil <sub/del>'
function tagesschau_eil:init(config)
tagesschau_eil.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('eil', true).table
tagesschau_eil.doc = [[*
]]..config.cmd_pat..[[eil* _sub_: Eilmeldungen abonnieren
*]]..config.cmd_pat..[[eil* _del_: Eilmeldungen deabonnieren
*]]..config.cmd_pat..[[eil* _sync_: Nach neuen Eilmeldungen prüfen (nur Superuser)]]
end
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+)%:(%d+)%:(%d+)"
local year, month, day, hours, minutes, seconds = dateString:match(pattern)
return day..'.'..month..'.'..year..' um '..hours..':'..minutes..':'..seconds
end
local url = 'http://www.tagesschau.de/api'
local hash = 'telegram:tagesschau'
function tagesschau_eil:abonnieren(id)
if redis:sismember(hash..':subs', id) == false then
redis:sadd(hash..':subs', id)
return '*Eilmeldungen abonniert.*'
else
return 'Die Eilmeldungen wurden hier bereits abonniert.'
end
end
function tagesschau_eil:deabonnieren(id)
if redis:sismember(hash..':subs', id) == true then
redis:srem(hash..':subs', id)
return '*Eilmeldungen deabonniert.*'
else
return 'Die Eilmeldungen wurden hier noch nicht abonniert.'
end
end
function tagesschau_eil:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, tagesschau_eil.doc, true, msg.message_id, true)
return
end
end
local id = "user#id" .. msg.from.id
if msg.chat.type == 'channel' then
print('Kanäle werden momentan nicht unterstützt')
end
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
id = 'chat#id'..msg.chat.id
end
if input:match('(sub)$') then
local output = tagesschau_eil:abonnieren(id)
utilities.send_reply(self, msg, output, true)
elseif input:match('(del)$') then
local output = tagesschau_eil:deabonnieren(id)
utilities.send_reply(self, msg, output, true)
elseif input:match('(sync)$') then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
tagesschau_eil:cron(self)
end
return
end
function tagesschau_eil:cron(self_plz)
if not self.BASE_URL then
self = self_plz
end
-- print('EIL: Prüfe...')
local last_eil = redis:get(hash..':last_entry')
local res,code = http.request(url)
local data = json.decode(res)
if code ~= 200 then return end
if not data then return end
if data.breakingnews[1] then
if data.breakingnews[1].details ~= last_eil then
local title = '#EIL: *'..data.breakingnews[1].headline..'*'
local news = data.breakingnews[1].shorttext
local posted_at = makeOurDate(data.breakingnews[1].date)..' Uhr'
local post_url = string.gsub(data.breakingnews[1].details, '/api/', '/')
local post_url = string.gsub(post_url, '.json', '.html')
local eil = title..'\n_'..posted_at..'_\n'..news..'\n[Artikel aufrufen]('..post_url..')'
redis:set(hash..':last_entry', data.breakingnews[1].details)
for _,user in pairs(redis:smembers(hash..':subs')) do
local user = string.gsub(user, 'chat%#id', '')
local user = string.gsub(user, 'user%#id', '')
utilities.send_message(self, user, eil, true, nil, true)
end
end
end
end
return tagesschau_eil

View File

@@ -1,94 +0,0 @@
local time = {}
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
time.command = 'time <Ort>'
function time:init(config)
time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table
time.doc = [[*
]]..config.cmd_pat..[[time*: Aktuelle Zeit in Deutschland
*]]..config.cmd_pat..[[time* _<Ort>_: Gibt Zeit an diesem Ort aus]]
end
function time:localize(output)
-- Days
local output = string.gsub(output, "Monday", "Montag")
local output = string.gsub(output, "Tuesday", "Dienstag")
local output = string.gsub(output, "Wednesday", "Mittwoch")
local output = string.gsub(output, "Thursday", "Donnerstag")
local output = string.gsub(output, "Friday", "Freitag")
local output = string.gsub(output, "Saturday", "Samstag")
local output = string.gsub(output, "Sunday", "Sonntag")
-- Months
local output = string.gsub(output, "January", "Januar")
local output = string.gsub(output, "February", "Februar")
local output = string.gsub(output, "March", "März")
local output = string.gsub(output, "April", "April")
local output = string.gsub(output, "May", "Mai")
local output = string.gsub(output, "June", "Juni")
local output = string.gsub(output, "July", "Juli")
local output = string.gsub(output, "August", "August")
local output = string.gsub(output, "September", "September")
local output = string.gsub(output, "October", "Oktober")
local output = string.gsub(output, "November", "November")
local output = string.gsub(output, "December", "Dezember")
-- Timezones
local output = string.gsub(output, "Africa", "Afrika")
local output = string.gsub(output, "America", "Amerika")
local output = string.gsub(output, "Asia", "Asien")
local output = string.gsub(output, "Australia", "Australien")
local output = string.gsub(output, "Europe", "Europa")
local output = string.gsub(output, "Indian", "Indien")
local output = string.gsub(output, "Pacific", "Pazifik")
return output
end
function time:action(msg, config)
local input = utilities.input(msg.text)
if not input then
local output = os.date("%A, %d. %B %Y, *%H:%M:%S Uhr*")
utilities.send_reply(self, msg, time:localize(output), true)
return
end
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
local now = os.time()
local utc = os.time(os.date("!*t", now))
local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..utc..'&language=de'
local jstr, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
local timezoneid = '*'..string.gsub(jdat.timeZoneId, '_', ' ' )..'*'
local timestamp = now + jdat.rawOffset + jdat.dstOffset
local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600
if utcoff == math.abs(utcoff) then
utcoff = '+'.. utilities.pretty_float(utcoff)
else
utcoff = utilities.pretty_float(utcoff)
end
-- "%A, %d. %B %Y, %H:%M:%S Uhr"
local output = timezoneid..':\n'..os.date('!%A, %d. %B %Y, %H:%M:%S Uhr',timestamp)
local output = time:localize(output)
local output = output..'\n_'..jdat.timeZoneName .. ' (UTC' .. utcoff .. ')_'
utilities.send_reply(self, msg, output, true)
end
return time

View File

@@ -1,51 +0,0 @@
local translate = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
translate.command = 'translate [text]'
function translate:init(config)
translate.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('translate', true):t('tl', true).table
translate.doc = [[```
]]..config.cmd_pat..[[translate [text]
Translates input or the replied-to message into the bot's language.
```]]
end
function translate:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, 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 str, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(str)
if jdat.code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local output = jdat.text[1]
output = '*Translation:*\n"' .. utilities.md_escape(output) .. '"'
utilities.send_reply(self, msg.reply_to_message or msg, output, true)
end
return translate

View File

@@ -1,151 +0,0 @@
local twitter = {}
local utilities = require('otouto.utilities')
local HTTPS = require('ssl.https')
local JSON = require('dkjson')
local redis = (loadfile "./otouto/redis.lua")()
local OAuth = (require "OAuth")
local bindings = require('otouto.bindings')
function twitter:init(config)
if not cred_data.tw_consumer_key then
print('Missing config value: tw_consumer_key.')
print('twitter.lua will not be enabled.')
return
elseif not cred_data.tw_consumer_secret then
print('Missing config value: tw_consumer_secret.')
print('twitter.lua will not be enabled.')
return
elseif not cred_data.tw_access_token then
print('Missing config value: tw_access_token.')
print('twitter.lua will not be enabled.')
return
elseif not cred_data.tw_access_token_secret then
print('Missing config value: tw_access_token_secret.')
print('twitter.lua will not be enabled.')
return
end
twitter.triggers = {
'twitter.com/[^/]+/statuse?s?/([0-9]+)'
}
twitter.doc = [[*Twitter-Link*: Postet Tweet]]
end
local consumer_key = cred_data.tw_consumer_key
local consumer_secret = cred_data.tw_consumer_secret
local access_token = cred_data.tw_access_token
local access_token_secret = cred_data.tw_access_token_secret
local client = OAuth.new(consumer_key, consumer_secret, {
RequestToken = "https://api.twitter.com/oauth/request_token",
AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"},
AccessToken = "https://api.twitter.com/oauth/access_token"
}, {
OAuthToken = access_token,
OAuthTokenSecret = access_token_secret
})
function twitter:action(msg)
if not msg.text:match('twitter.com/[^/]+/statuse?s?/([0-9]+)') then
return
end
local id = msg.text:match('twitter.com/[^/]+/statuse?s?/([0-9]+)')
local twitter_url = "https://api.twitter.com/1.1/statuses/show/" .. id.. ".json"
local response_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url)
local response = JSON.decode(response_body)
local full_name = response.user.name
local user_name = response.user.screen_name
if response.user.verified then
verified = ''
else
verified = ''
end
-- MD: local header = 'Tweet von '..full_name..' ([@' ..user_name.. '](https://twitter.com/'..user_name..')'..verified..')\n'
local header = 'Tweet von '..full_name..' (@' ..user_name..verified..')\n'
local text = response.text
-- favorites & retweets
if response.retweet_count == 0 then
retweets = ""
else
retweets = response.retweet_count..'x retweeted'
end
if response.favorite_count == 0 then
favorites = ""
else
favorites = response.favorite_count..'x favorisiert'
end
if retweets == "" and favorites ~= "" then
footer = favorites
elseif retweets ~= "" and favorites == "" then
footer = retweets
elseif retweets ~= "" and favorites ~= "" then
footer = retweets..' - '..favorites
else
footer = ""
end
-- replace short URLs
if response.entities.urls then
for k, v in pairs(response.entities.urls) do
local short = v.url
local long = v.expanded_url
text = text:gsub(short, long)
end
end
-- remove images
local images = {}
local videos = {}
if response.entities.media and response.extended_entities.media then
for k, v in pairs(response.extended_entities.media) do
local url = v.url
local pic = v.media_url_https
if v.video_info then
if not v.video_info.variants[3] then
local vid = v.video_info.variants[1].url
table.insert(videos, vid)
else
local vid = v.video_info.variants[3].url
table.insert(videos, vid)
end
end
text = text:gsub(url, "")
table.insert(images, pic)
end
end
-- quoted tweet
if response.quoted_status then
local quoted_text = response.quoted_status.text
local quoted_name = response.quoted_status.user.name
local quoted_screen_name = response.quoted_status.user.screen_name
if response.quoted_status.user.verified then
quoted_verified = ''
else
quoted_verified = ''
end
-- MD: quote = 'Als Antwort auf '..quoted_name..' ([@' ..quoted_screen_name.. '](https://twitter.com/'..quoted_screen_name..')'..quoted_verified..'):\n'..quoted_text
quote = 'Als Antwort auf '..quoted_name..' (@' ..quoted_screen_name..quoted_verified..'):\n'..quoted_text
text = text..'\n\n'..quote..'\n'
end
-- send the parts
local text = unescape(text)
utilities.send_reply(self, msg, header .. "\n" .. text.."\n"..footer)
for k, v in pairs(images) do
local file = download_to_file(v)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
for k, v in pairs(videos) do
local file = download_to_file(v)
utilities.send_video(self, msg.chat.id, file, nil, msg.message_id)
end
end
return twitter

View File

@@ -1,349 +0,0 @@
local twitter_send = {}
local http = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local OAuth = require "OAuth"
local redis = (loadfile "./otouto/redis.lua")()
function twitter_send:init(config)
if not cred_data.tw_consumer_key then
print('Missing config value: tw_consumer_key.')
print('twitter_send.lua will not be enabled.')
return
elseif not cred_data.tw_consumer_secret then
print('Missing config value: tw_consumer_secret.')
print('twitter_send.lua will not be enabled.')
return
end
twitter_send.triggers = {
"^/tw (auth) (%d+)",
"^/tw (unauth)$",
"^/tw (verify)$",
"^/tw (.+)",
"^/(twwhitelist add) (%d+)",
"^/(twwhitelist del) (%d+)"
}
twitter_send.doc = [[*
]]..config.cmd_pat..[[tw* _<Text>_: Sendet einen Tweet an den Account, der im Chat angemeldet ist
*]]..config.cmd_pat..[[tw* _verify_: Gibt den angemeldeten User aus, inklusive Profilbild
*]]..config.cmd_pat..[[twwitelist* _add_ _<user>_: Schaltet User für die Tweet-Funktion frei
*]]..config.cmd_pat..[[twwitelist* _del_ _<user>_: Entfernt User von der Tweet-Whitelist
*]]..config.cmd_pat..[[tw* _auth_ _<PIN>_: Meldet mit dieser PIN an (Setup)
*]]..config.cmd_pat..[[tw* _unauth_: Meldet Twitter-Account ab
]]
end
twitter_send.command = 'tw <Tweet>'
local consumer_key = cred_data.tw_consumer_key
local consumer_secret = cred_data.tw_consumer_secret
function can_send_tweet(msg)
local hash = 'user:'..msg.from.id
local var = redis:hget(hash, 'can_send_tweet')
if var == "true" then
return true
else
return false
end
end
local client = OAuth.new(consumer_key, consumer_secret, {
RequestToken = "https://api.twitter.com/oauth/request_token",
AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"},
AccessToken = "https://api.twitter.com/oauth/access_token"
})
function twitter_send:do_twitter_authorization_flow(hash, is_chat)
local callback_url = "oob"
local values = client:RequestToken({ oauth_callback = callback_url })
local oauth_token = values.oauth_token
local oauth_token_secret = values.oauth_token_secret
-- save temporary oauth keys
redis:hset(hash, 'oauth_token', oauth_token)
redis:hset(hash, 'oauth_token_secret', oauth_token_secret)
local auth_url = client:BuildAuthorizationUrl({ oauth_callback = callback_url, force_login = true })
if is_chat then
return 'Bitte schließe den Vorgang ab, indem du unten auf den Link klickst und mir die angezeigte PIN per `/tw auth PIN` *im Chat von gerade* übergibst.\n[Bei Twitter anmelden]('..auth_url..')'
else
return 'Bitte schließe den Vorgang ab, indem du unten auf den Link klickst und mir die angezeigte PIN per `/tw auth PIN` übergibst.\n[Bei Twitter anmelden]('..auth_url..')'
end
end
function twitter_send:get_twitter_access_token(hash, oauth_verifier, oauth_token, oauth_token_secret)
local oauth_verifier = tostring(oauth_verifier) -- must be a string
-- now we'll use the tokens we got in the RequestToken call, plus our PIN
local client = OAuth.new(consumer_key, consumer_secret, {
RequestToken = "https://api.twitter.com/oauth/request_token",
AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"},
AccessToken = "https://api.twitter.com/oauth/access_token"
}, {
OAuthToken = oauth_token,
OAuthVerifier = oauth_verifier
})
client:SetTokenSecret(oauth_token_secret)
local values, err, headers, status, body = client:GetAccessToken()
if err then return 'Einloggen fehlgeschlagen!' end
-- save permanent oauth keys
redis:hset(hash, 'oauth_token', values.oauth_token)
redis:hset(hash, 'oauth_token_secret', values.oauth_token_secret)
return 'Erfolgreich eingeloggt als "@'..values.screen_name..'" (User-ID: '..values.user_id..')'
end
function twitter_send:reset_twitter_auth(hash, frominvalid)
redis:hdel(hash, 'oauth_token')
redis:hdel(hash, 'oauth_token_secret')
if frominvalid then
return '*Authentifizierung nicht erfolgreich, wird zurückgesetzt...*'
else
return '*Erfolgreich abgemeldet!* Entziehe den Zugriff endgültig in deinen [Twitter-Einstellungen](https://twitter.com/settings/applications)!'
end
end
function twitter_send:resolve_url(url)
local response_body = {}
local request_constructor = {
url = url,
method = "HEAD",
sink = ltn12.sink.table(response_body),
headers = {},
redirect = false
}
local ok, response_code, response_headers, response_status_line = http.request(request_constructor)
if ok and response_headers.location then
return response_headers.location
else
return url
end
end
function twitter_send:twitter_verify_credentials(oauth_token, oauth_token_secret)
local client = OAuth.new(consumer_key, consumer_secret, {
RequestToken = "https://api.twitter.com/oauth/request_token",
AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"},
AccessToken = "https://api.twitter.com/oauth/access_token"
}, {
OAuthToken = oauth_token,
OAuthTokenSecret = oauth_token_secret
})
local response_code, response_headers, response_status_line, response_body =
client:PerformRequest(
"GET", "https://api.twitter.com/1.1/account/verify_credentials.json", {
include_entities = false,
skip_status = true,
include_email = false
}
)
local response = json.decode(response_body)
if response_code == 401 then
return twitter_send:reset_twitter_auth(hash, true)
end
if response_code ~= 200 then
return 'HTTP-Fehler '..response_code..': '..data.errors[1].message
end
-- TODO: copied straight from the twitter_user plugin, maybe we can do it better?
local full_name = response.name
local user_name = response.screen_name
if response.verified then
user_name = user_name..''
end
if response.protected then
user_name = user_name..' 🔒'
end
local header = full_name.. " (@" ..user_name.. ")\n"
local description = unescape(response.description)
if response.location then
location = response.location
else
location = ''
end
if response.url and response.location ~= '' then
url = ' | '..twitter_send:resolve_url(response.url)..'\n'
elseif response.url and response.location == '' then
url = twitter_send:resolve_url(response.url)..'\n'
else
url = '\n'
end
local body = description..'\n'..location..url
local favorites = comma_value(response.favourites_count)
local follower = comma_value(response.followers_count)
local following = comma_value(response.friends_count)
local statuses = comma_value(response.statuses_count)
local footer = statuses..' Tweets, '..follower..' Follower, '..following..' folge ich, '..favorites..' Tweets favorisiert'
local text = 'Eingeloggter Account:\n'..header..body..footer
local pp_url = string.gsub(response.profile_image_url_https, "normal", "400x400")
return text, pp_url
end
function twitter_send:send_tweet(tweet, oauth_token, oauth_token_secret, hash)
local client = OAuth.new(consumer_key, consumer_secret, {
RequestToken = "https://api.twitter.com/oauth/request_token",
AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"},
AccessToken = "https://api.twitter.com/oauth/access_token"
}, {
OAuthToken = oauth_token,
OAuthTokenSecret = oauth_token_secret
})
local response_code, response_headers, response_status_line, response_body =
client:PerformRequest(
"POST", "https://api.twitter.com/1.1/statuses/update.json", {
status = tweet
}
)
local data = json.decode(response_body)
if response_code == 401 then
return twitter_send:reset_twitter_auth(hash, true)
end
if response_code ~= 200 then
return 'HTTP-Fehler '..response_code..': '..data.errors[1].message
end
local statusnumber = comma_value(data.user.statuses_count)
local screen_name = data.user.screen_name
local status_id = data.id_str
return '*Tweet #'..statusnumber..' gesendet!* [Auf Twitter ansehen](https://twitter.com/statuses/'..status_id..')'
end
function twitter_send:add_to_twitter_whitelist(user_id)
local hash = 'user:'..user_id
local whitelisted = redis:hget(hash, 'can_send_tweet')
if whitelisted ~= 'true' then
print('Setting can_send_tweet in redis hash '..hash..' to true')
redis:hset(hash, 'can_send_tweet', true)
return '*User '..user_id..' kann jetzt Tweets senden!*'
else
return '*User '..user_id..' kann schon Tweets senden.*'
end
end
function twitter_send:del_from_twitter_whitelist(user_id)
local hash = 'user:'..user_id
local whitelisted = redis:hget(hash, 'can_send_tweet')
if whitelisted == 'true' then
print('Setting can_send_tweet in redis hash '..hash..' to false')
redis:hset(hash, 'can_send_tweet', false)
return '*User '..user_id..' kann jetzt keine Tweets mehr senden!*'
else
return '*User '..user_id..' ist nicht whitelisted.*'
end
end
function twitter_send:action(msg, config, matches)
if matches[1] == "twwhitelist add" and matches[2] then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
else
utilities.send_reply(self, msg, twitter_send:add_to_twitter_whitelist(matches[2]), true)
return
end
end
if matches[1] == "twwhitelist del" and matches[2] then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
else
utilities.send_reply(self, msg, twitter_send:del_from_twitter_whitelist(matches[2]), true)
return
end
end
local hash = get_redis_hash(msg, 'twitter')
local oauth_token = redis:hget(hash, 'oauth_token')
local oauth_token_secret = redis:hget(hash, 'oauth_token_secret')
-- Thanks to the great doc at https://github.com/ignacio/LuaOAuth#a-more-involved-example
if not oauth_token and not oauth_token_secret then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
else
-- maybe we can edit the older message to update it to "logged in!"?
-- this should be interesting: https://core.telegram.org/bots/api#editmessagetext
local text = twitter_send:do_twitter_authorization_flow(hash, true)
local res = utilities.send_message(self, msg.from.id, text, true, nil, true)
if not res then
utilities.send_reply(self, msg, 'Bitte starte mich zuerst [privat](http://telegram.me/' .. self.info.username .. '?start).', true)
elseif msg.chat.type ~= 'private' then
utilities.send_message(self, msg.chat.id, '_Bitte warten, der Administrator meldet sich an..._', true, nil, true)
end
return
end
else
utilities.send_reply(self, msg, twitter_send:do_twitter_authorization_flow(hash), true)
return
end
end
if matches[1] == 'auth' and matches[2] then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
end
if string.len(matches[2]) > 7 then utilities.send_reply(self, msg, 'Invalide PIN!') return end
utilities.send_reply(self, msg, twitter_send:get_twitter_access_token(hash, matches[2], oauth_token, oauth_token_secret))
return
end
if matches[1] == 'unauth' then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
end
utilities.send_reply(self, msg, twitter_send:reset_twitter_auth(hash), true)
return
end
if matches[1] == 'verify' then
local text, pp_url = twitter_send:twitter_verify_credentials(oauth_token, oauth_token_secret)
local file = download_to_file(pp_url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
utilities.send_reply(self, msg, text)
return
end
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
if not can_send_tweet(msg) then
utilities.send_reply(self, msg, '*Du darfst keine Tweets senden.* Entweder wurdest du noch gar nicht freigeschaltet oder ausgeschlossen.', true)
return
else
utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true)
return
end
else
utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true)
return
end
end
return twitter_send

View File

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

View File

@@ -1,150 +0,0 @@
local weather = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
function weather:init(config)
if not cred_data.forecastio_apikey then
print('Missing config value: forecastio_apikey.')
print('weather.lua will not be enabled.')
return
elseif not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('weather.lua will not be enabled.')
return
end
weather.triggers = {
"^/wetter$",
"^/wetter (.*)$",
"^/w$",
"^/w (.*)$"
}
weather.doc = [[*
]]..config.cmd_pat..[[wetter*: Wetter für deinen Wohnort _(/location set [Ort])_
*]]..config.cmd_pat..[[wetter* _<Ort>_: Wetter für diesen Ort
]]
end
weather.command = 'wetter'
local BASE_URL = "https://api.forecast.io/forecast"
local apikey = cred_data.forecastio_apikey
local google_apikey = cred_data.google_apikey
function get_city_name(lat, lng)
local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng)
if city then return city end
local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey
local res, code = HTTPS.request(url)
if code ~= 200 then return 'Unbekannte Stadt' end
local data = JSON.decode(res).results[1]
local city = data.formatted_address
print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"')
redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city)
return city
end
function weather:get_weather(lat, lng)
print('Finde Wetter in '..lat..', '..lng)
local text = redis:get('telegram:cache:weather:'..lat..','..lng)
if text then print('...aus dem Cache') return text end
local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=minutely,hourly,daily,alerts,flags'
local response_body = {}
local request_constructor = {
url = url,
method = "GET",
sink = ltn12.sink.table(response_body)
}
local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor)
if not ok then return nil end
local data = JSON.decode(table.concat(response_body))
local ttl = string.sub(response_headers["cache-control"], 9)
local weather = data.currently
local city = get_city_name(lat, lng)
local temperature = string.gsub(round(weather.temperature, 1), "%.", ",")
local feelslike = string.gsub(round(weather.apparentTemperature, 1), "%.", ",")
local temp = '*Wetter in '..city..':*\n'..temperature..' °C'
local conditions = ' | '..weather.summary
if weather.icon == 'clear-day' then
conditions = conditions..' ☀️'
elseif weather.icon == 'clear-night' then
conditions = conditions..' 🌙'
elseif weather.icon == 'rain' then
conditions = conditions..' ☔️'
elseif weather.icon == 'snow' then
conditions = conditions..' ❄️'
elseif weather.icon == 'sleet' then
conditions = conditions..' 🌨'
elseif weather.icon == 'wind' then
conditions = conditions..' 💨'
elseif weather.icon == 'fog' then
conditions = conditions..' 🌫'
elseif weather.icon == 'cloudy' then
conditions = conditions..' ☁️☁️'
elseif weather.icon == 'partly-cloudy-day' then
conditions = conditions..' 🌤'
elseif weather.icon == 'partly-cloudy-night' then
conditions = conditions..' 🌙☁️'
else
conditions = conditions..''
end
local windspeed = ' | 💨 '..string.gsub(round(weather.windSpeed, 1), "%.", ",")..' m/s'
local text = temp..conditions..windspeed
if temperature ~= feelslike then
text = text..'\n(gefühlt: '..feelslike..' °C)'
end
cache_data('weather', lat..','..lng, text, tonumber(ttl), 'key')
return text
end
function weather:action(msg, config, matches)
local user_id = msg.from.id
if matches[1] ~= '/wetter' and matches[1] ~= '/w' then
city = matches[1]
else
local set_location = get_location(user_id)
if not set_location then
city = 'Berlin, Deutschland'
else
city = set_location
end
end
local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat')
local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng')
if not lat and not lng then
print('Koordinaten nicht eingespeichert, frage Google...')
coords = utilities.get_coords(city, config)
lat = coords.lat
lng = coords.lon
end
if not lat and not lng then
utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true)
return
end
redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat)
redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng)
local text = weather:get_weather(lat, lng)
if not text then
text = 'Konnte das Wetter von dieser Stadt nicht bekommen.'
end
utilities.send_reply(self, msg, text, true)
end
return weather

View File

@@ -1,51 +0,0 @@
local whoami = {}
local utilities = require('otouto.utilities')
whoami.command = 'whoami'
function whoami:init(config)
whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who', true):t('whoami').table
whoami.doc = [[```
Returns user and chat info for you or the replied-to message.
Alias: ]]..config.cmd_pat..[[who
```]]
end
function whoami:action(msg)
if msg.reply_to_message then
msg = msg.reply_to_message
msg.from.name = utilities.build_name(msg.from.first_name, msg.from.last_name)
end
local chat_id = math.abs(msg.chat.id)
if chat_id > 1000000000000 then
chat_id = chat_id - 1000000000000
end
local user = 'You are @%s, also known as *%s* `[%s]`'
if msg.from.username then
user = user:format(utilities.markdown_escape(msg.from.username), msg.from.name, msg.from.id)
else
user = 'You are *%s* `[%s]`,'
user = user:format(msg.from.name, msg.from.id)
end
local group = '@%s, also known as *%s* `[%s]`.'
if msg.chat.type == 'private' then
group = group:format(utilities.markdown_escape(self.info.username), self.info.first_name, self.info.id)
elseif msg.chat.username then
group = group:format(utilities.markdown_escape(msg.chat.username), msg.chat.title, chat_id)
else
group = '*%s* `[%s]`.'
group = group:format(msg.chat.title, chat_id)
end
local output = user .. ', and you are messaging ' .. group
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
end
return whoami

View File

@@ -1,112 +0,0 @@
local wikipedia = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
wikipedia.command = 'wiki <Begriff>'
function wikipedia:init(config)
wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true).table
wikipedia.doc = [[*
]]..config.cmd_pat..[[wiki* _<Begriff>_: Gibt Wikipedia-Artikel aus
Alias: ]]..config.cmd_pat..[[wikipedia]]
end
local get_title = function(search)
for _,v in ipairs(search) do
if not v.snippet:match('steht für') then
return v.title
end
end
return false
end
function wikipedia:action(msg, config)
-- Get the query. If it's not in the message, check the replied-to message.
-- If those don't exist, send the help text.
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, wikipedia.doc, true, msg.message_id, true)
return
end
end
-- This kinda sucks, but whatever.
input = input:gsub('#', ' sharp')
-- Disclaimer: These variables will be reused.
local jstr, res, jdat
-- All pretty standard from here.
local search_url = 'https://de.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch='
jstr, res = HTTPS.request(search_url .. URL.escape(input))
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
jdat = JSON.decode(jstr)
if jdat.query.searchinfo.totalhits == 0 then
utilities.send_reply(self, msg, config.errors.results)
return
end
local title = get_title(jdat.query.search)
if not title then
utilities.send_reply(self, msg, config.errors.results)
return
end
local res_url = 'https://de.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles='
jstr, res = HTTPS.request(res_url .. URL.escape(title))
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local _
local text = JSON.decode(jstr).query.pages
_, text = next(text)
if not text then
utilities.send_reply(self, msg, config.errors.results)
return
else
text = text.extract
end
-- Remove needless bits from the article, take only the first paragraph.
text = text:gsub('</?.->', '')
local l = text:find('\n')
if l then
text = text:sub(1, l-1)
end
-- This block can be annoying to read.
-- We use the initial title to make the url for later use. Then we remove
-- the extra bits that won't be in the article. We determine whether the
-- first part of the text is the title, and if so, we embolden that.
-- Otherwise, we prepend the text with a bold title. Then we append a "Read
-- More" link.
local url = 'https://de.wikipedia.org/wiki/' .. URL.escape(title)
title = title:gsub('%(.+%)', '')
local output
if string.match(text:sub(1, title:len()), title) then
output = '*' .. title .. '*' .. text:sub(title:len()+1)
else
output = '*' .. title:gsub('%(.+%)', '') .. '*\n' .. text:gsub('%[.+%]','')
end
output = output .. '\n[Wikipedia - '..title..'](' .. url:gsub('%)', '\\)') .. ')'
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end
return wikipedia

View File

@@ -1,58 +0,0 @@
local xkcd = {}
local HTTP = require('socket.http')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
xkcd.command = 'xkcd [i]'
function xkcd:init(config)
xkcd.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('xkcd', true).table
xkcd.doc = [[```
]]..config.cmd_pat..[[xkcd [i]
Returns the latest xkcd strip and its alt text. If a number is given, returns that number strip. If "r" is passed in place of a number, returns a random strip.
```]]
end
function xkcd:action(msg, config)
local jstr, res = HTTP.request('http://xkcd.com/info.0.json')
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local latest = JSON.decode(jstr).num
local strip_num = latest
local input = utilities.input(msg.text)
if input then
if input == '404' then
utilities.send_message(self, msg.chat.id, '*404*\nNot found.', false, nil, true)
return
elseif tonumber(input) then
if tonumber(input) > latest then
strip_num = latest
else
strip_num = input
end
elseif input == 'r' then
strip_num = math.random(latest)
end
end
local res_url = 'http://xkcd.com/' .. strip_num .. '/info.0.json'
jstr, res = HTTP.request(res_url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local jdat = JSON.decode(jstr)
local output = '*' .. jdat.safe_title .. ' (*[' .. jdat.num .. '](' .. jdat.img .. ')*)*\n_' .. jdat.alt:gsub('_', '\\_') .. '_'
utilities.send_message(self, msg.chat.id, output, false, nil, true)
end
return xkcd

View File

@@ -1,163 +0,0 @@
local youtube = {}
local utilities = require('otouto.utilities')
local https = require('ssl.https')
local JSON = require('dkjson')
local bindings = require('otouto.bindings')
function youtube:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('youtube.lua will not be enabled.')
return
end
youtube.triggers = {
'youtu.be/([A-Za-z0-9-_-]+)',
'youtube.com/watch%?v=([A-Za-z0-9-_-]+)'
}
youtube.doc = [[*YouTube-Link*: Postet Infos zu Video]]
end
local apikey = cred_data.google_apikey
local BASE_URL = 'https://www.googleapis.com/youtube/v3'
function table.contains(table, element)
for _, value in pairs(table) do
if value == element then
return true
end
end
return false
end
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)T"
local year, month, day = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function get_yt_data (yt_code)
local apikey = cred_data.google_apikey
local url = BASE_URL..'/videos?part=snippet,statistics,contentDetails&key='..apikey..'&id='..yt_code..'&fields=items(snippet(publishedAt,channelTitle,localized(title,description),thumbnails),statistics(viewCount,likeCount,dislikeCount,commentCount),contentDetails(duration,regionRestriction(blocked)))'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = JSON.decode(res).items[1]
return data
end
local function convertISO8601Time(duration)
local a = {}
for part in string.gmatch(duration, "%d+") do
table.insert(a, part)
end
if duration:find('M') and not (duration:find('H') or duration:find('S')) then
a = {0, a[1], 0}
end
if duration:find('H') and not duration:find('M') then
a = {a[1], 0, a[2]}
end
if duration:find('H') and not (duration:find('M') or duration:find('S')) then
a = {a[1], 0, 0}
end
duration = 0
if #a == 3 then
duration = duration + tonumber(a[1]) * 3600
duration = duration + tonumber(a[2]) * 60
duration = duration + tonumber(a[3])
end
if #a == 2 then
duration = duration + tonumber(a[1]) * 60
duration = duration + tonumber(a[2])
end
if #a == 1 then
duration = duration + tonumber(a[1])
end
return duration
end
function send_youtube_data(data, msg, self, link, sendpic)
local title = data.snippet.localized.title
-- local description = data.snippet.localized.description
local uploader = data.snippet.channelTitle
local upload_date = makeOurDate(data.snippet.publishedAt)
local viewCount = comma_value(data.statistics.viewCount)
if data.statistics.likeCount then
likeCount = ', '..comma_value(data.statistics.likeCount)..' Likes und '
dislikeCount = comma_value(data.statistics.dislikeCount)..' Dislikes'
else
likeCount = ''
dislikeCount = ''
end
if data.statistics.commentCount then
commentCount = ', '..comma_value(data.statistics.commentCount)..' Kommentare'
else
commentCount = ''
end
local totalseconds = convertISO8601Time(data.contentDetails.duration)
local duration = makeHumanTime(totalseconds)
if data.contentDetails.regionRestriction then
blocked = data.contentDetails.regionRestriction.blocked
blocked = table.contains(blocked, "DE")
else
blocked = false
end
text = '*'..title..'*\n_('..uploader..' am '..upload_date..', '..viewCount..'x angesehen, Länge: '..duration..likeCount..dislikeCount..commentCount..')_\n'
if link then
text = link..'\n'..text
end
if blocked then
text = text..'\n*ACHTUNG, Video ist in Deutschland gesperrt!*'
end
if sendpic then
if data.snippet.thumbnails.maxres then
image_url = data.snippet.thumbnails.maxres.url
elseif data.snippet.thumbnails.high then
image_url = data.snippet.thumbnails.high.url
elseif data.snippet.thumbnails.medium then
image_url = data.snippet.thumbnails.medium.url
elseif data.snippet.thumbnails.standard then
image_url = data.snippet.thumbnails.standard.url
else
image_url = data.snippet.thumbnails.default.url
end
-- need to change text, because Telegram captions can only be 200 characters long and don't support Markdown
local text = link..'\n'..title..'\n('..uploader..' am '..upload_date..', '..viewCount..'x angesehen, Länge: '..duration..')'
if blocked then
text = text..'\nACHTUNG, In Deutschland gesperrt!'
end
local file = download_to_file(image_url)
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id)
else
utilities.send_reply(self, msg, text, true)
end
end
function youtube:action(msg)
if not msg.text:match('youtu.be/([A-Za-z0-9-_-]+)') and not msg.text:match('youtube.com/watch%?v=([A-Za-z0-9-_-]+)') then
return
end
local yt_code = msg.text:match('youtu.be/([A-Za-z0-9-_-]+)')
if not yt_code then yt_code = msg.text:match('youtube.com/watch%?v=([A-Za-z0-9-_-]+)') end
local data = get_yt_data(yt_code)
send_youtube_data(data, msg, self)
return
end
return youtube

View File

@@ -1,67 +0,0 @@
local youtube_channel = {}
local utilities = require('otouto.utilities')
local https = require('ssl.https')
local JSON = require('dkjson')
function youtube_channel:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('youtube_channel.lua will not be enabled.')
return
end
youtube_channel.triggers = {
"youtube.com/user/([A-Za-z0-9-_-]+)",
"youtube.com/channel/([A-Za-z0-9-_-]+)"
}
youtube_channel.doc = [[*YouTube-Channel-Link*: Postet Infos zum Kanal]]
end
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)T"
local year, month, day = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function youtube_channel:get_yt_channel_data(channel_name)
local BASE_URL = 'https://www.googleapis.com/youtube/v3'
local apikey = cred_data.google_apikey
local url = BASE_URL..'/channels?part=snippet,statistics&key='..apikey..'&forUsername='..channel_name..'&fields=items%28snippet%28publishedAt,localized%28title,description%29%29,statistics%28viewCount,subscriberCount,videoCount%29%29'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = JSON.decode(res).items[1]
if data == nil then
local url = BASE_URL..'/channels?part=snippet,statistics&key='..apikey..'&id='..channel_name..'&fields=items%28snippet%28publishedAt,localized%28title,description%29%29,statistics%28viewCount,subscriberCount,videoCount%29%29'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
return JSON.decode(res).items[1]
end
return data
end
function youtube_channel:send_yt_channel_data(data)
local name = data.snippet.localized.title
local creation_date = makeOurDate(data.snippet.publishedAt)
local description = data.snippet.localized.description
local views = comma_value(data.statistics.viewCount)
local subscriber = comma_value(data.statistics.subscriberCount)
if subscriber == "0" then subscriber = "0 (ausgblendet?)" end
local videos = comma_value(data.statistics.videoCount)
local text = '*'..name..'*\n_Registriert am '..creation_date..', '..views..' Video-Aufrufe insgesamt, '..subscriber..' Abonnenten und '..videos..' Videos_\n'..description
return text
end
function youtube_channel:action(msg)
if not msg.text:match('youtube.com/user/([A-Za-z0-9-_-]+)') and not msg.text:match('youtube.com/channel/([A-Za-z0-9-_-]+)') then
return
end
local channel_name = msg.text:match('youtube.com/user/([A-Za-z0-9-_-]+)')
if not channel_name then channel_name = msg.text:match('youtube.com/channel/([A-Za-z0-9-_-]+)') end
local data = youtube_channel:get_yt_channel_data(channel_name)
local output = youtube_channel:send_yt_channel_data(data)
utilities.send_reply(self, msg, output, true)
end
return youtube_channel

View File

@@ -1,65 +0,0 @@
local youtube_playlist = {}
local utilities = require('otouto.utilities')
local https = require('ssl.https')
local JSON = require('dkjson')
function youtube_playlist:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('youtube_playlist.lua will not be enabled.')
return
end
youtube_playlist.triggers = {
"youtube.com/playlist%?list=([A-Za-z0-9-_-]+)"
}
youtube_playlist.doc = [[*YouTube-PlayList-Link*: Postet Infos zu PlayList]]
end
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)T"
local year, month, day = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function youtube_playlist:get_pl_data (pl_code)
local BASE_URL = 'https://www.googleapis.com/youtube/v3'
local apikey = cred_data.google_apikey
local url = BASE_URL..'/playlists?part=snippet,contentDetails&key='..apikey..'&id='..pl_code..'&fields=items(snippet(publishedAt,channelTitle,localized(title,description)),contentDetails(itemCount))'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = JSON.decode(res).items[1]
return data
end
function youtube_playlist:send_youtubepl_data(data)
local title = data.snippet.localized.title
if data.snippet.localized.description == '(null)' or data.snippet.localized.description == '' then
description = ''
else
description = '\n'..data.snippet.localized.description
end
local author = data.snippet.channelTitle
local creation_date = makeOurDate(data.snippet.publishedAt)
if data.contentDetails.itemCount == 1 then
itemCount = data.contentDetails.itemCount..' Video'
else
itemCount = comma_value(data.contentDetails.itemCount)..' Videos'
end
local text = '*'..title..'*'..description..'\n_Erstellt von '..author..' am '..creation_date..', '..itemCount..'_'
return text
end
function youtube_playlist:action(msg)
if not msg.text:match('youtube.com/playlist%?list=([A-Za-z0-9-_-]+)') then
return
end
local pl_code = msg.text:match('youtube.com/playlist%?list=([A-Za-z0-9-_-]+)')
local data = youtube_playlist:get_pl_data(pl_code)
local output = youtube_playlist:send_youtubepl_data(data)
utilities.send_reply(self, msg, output, true)
end
return youtube_playlist

View File

@@ -1,66 +0,0 @@
require("./otouto/plugins/youtube")
local yt_search = {}
local utilities = require('otouto.utilities')
local https = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
yt_search.command = 'yt <Suchbegriff>'
function yt_search:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('youtube_search.lua will not be enabled.')
return
end
yt_search.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('yt', true):t('youtube', true).table
yt_search.doc = [[*
]]..config.cmd_pat..[[yt* _<Suchbegriff>_: Sucht nach einem YouTube-Video]]
end
local BASE_URL = 'https://www.googleapis.com/youtube/v3'
function searchYoutubeVideo(text)
local apikey = cred_data.google_apikey
local data = httpsRequest('https://www.googleapis.com/youtube/v3/search?part=snippet&key='..apikey..'&maxResults=1&type=video&q=' .. URL.escape(text))
if not data then
print("HTTP-Fehler")
return nil
elseif not data.items[1] then
return "YouTube-Video nicht gefunden!"
end
local videoId = data.items[1].id.videoId
local videoURL = 'https://youtube.com/watch?v='..videoId
return videoURL, videoId
end
function httpsRequest(url)
local res,code = https.request(url)
if code ~= 200 then return nil end
return JSON.decode(res)
end
function yt_search:action(msg)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, yt_search.doc, true, msg.message_id, true)
return
end
end
local link, videoId = searchYoutubeVideo(input)
if link == "YouTube-Video nicht gefunden!" or nil then utilities.send_reply(self, msg, 'YouTube-Video nicht gefunden!') return end
local data = get_yt_data(videoId)
send_youtube_data(data, msg, self, link, true)
return
end
return yt_search

View File

@@ -1,57 +0,0 @@
local Redis = require 'redis'
local FakeRedis = require 'fakeredis'
local params = {
host = os.getenv('REDIS_HOST') or '127.0.0.1',
port = tonumber(os.getenv('REDIS_PORT') or 6379)
}
local database = os.getenv('REDIS_DB')
local password = os.getenv('REDIS_PASSWORD')
-- Overwrite HGETALL
Redis.commands.hgetall = Redis.command('hgetall', {
response = function(reply, command, ...)
local new_reply = { }
for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end
return new_reply
end
})
local redis = nil
-- Won't launch an error if fails
local ok = pcall(function()
redis = Redis.connect(params)
end)
if not ok then
local fake_func = function()
print('\27[31mCan\'t connect with Redis, install/configure it!\27[39m')
end
fake_func()
fake = FakeRedis.new()
print('\27[31mRedis addr: '..params.host..'\27[39m')
print('\27[31mRedis port: '..params.port..'\27[39m')
redis = setmetatable({fakeredis=true}, {
__index = function(a, b)
if b ~= 'data' and fake[b] then
fake_func(b)
end
return fake[b] or fake_func
end })
else
if password then
redis:auth(password)
end
if database then
redis:select(database)
end
end
return redis

View File

@@ -1,834 +0,0 @@
-- utilities.lua
-- Functions shared among plugins.
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 http = require('socket.http')
local https = require('ssl.https')
local serpent = require("serpent")
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
local mimetype = (loadfile "./otouto/mimetype.lua")()
-- For the sake of ease to new contributors and familiarity to old contributors,
-- we'll provide a couple of aliases to real bindings here.
function utilities:send_message(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown)
return bindings.request(self, 'sendMessage', {
chat_id = chat_id,
text = text,
disable_web_page_preview = disable_web_page_preview,
reply_to_message_id = reply_to_message_id,
parse_mode = use_markdown and 'Markdown' or nil
} )
end
function utilities:send_reply(old_msg, text, use_markdown)
return bindings.request(self, 'sendMessage', {
chat_id = old_msg.chat.id,
text = text,
disable_web_page_preview = true,
reply_to_message_id = old_msg.message_id,
parse_mode = use_markdown and 'Markdown' or nil
} )
end
-- NOTE: Telegram currently only allows file uploads up to 50 MB
-- https://core.telegram.org/bots/api#sendphoto
function utilities:send_photo(chat_id, file, text, reply_to_message_id)
local output = bindings.request(self, 'sendPhoto', {
chat_id = chat_id,
caption = text or nil,
reply_to_message_id = reply_to_message_id
}, {photo = file} )
os.remove(file)
print("Deleted: "..file)
return output
end
-- https://core.telegram.org/bots/api#sendaudio
function utilities:send_audio(chat_id, file, text, reply_to_message_id, duration, performer, title)
local output = bindings.request(self, 'sendAudio', {
chat_id = chat_id,
caption = text or nil,
duration = duration or nil,
performer = performer or nil,
title = title or nil,
reply_to_message_id = reply_to_message_id
}, {audio = file} )
os.remove(file)
print("Deleted: "..file)
return output
end
-- https://core.telegram.org/bots/api#senddocument
function utilities:send_document(chat_id, file, text, reply_to_message_id)
local output = bindings.request(self, 'sendDocument', {
chat_id = chat_id,
caption = text or nil,
reply_to_message_id = reply_to_message_id
}, {document = file} )
os.remove(file)
print("Deleted: "..file)
return output
end
-- https://core.telegram.org/bots/api#sendvideo
function utilities:send_video(chat_id, file, text, reply_to_message_id, duration, width, height)
local output = bindings.request(self, 'sendVideo', {
chat_id = chat_id,
caption = text or nil,
duration = duration or nil,
width = width or nil,
height = height or nil,
reply_to_message_id = reply_to_message_id
}, {video = file} )
os.remove(file)
print("Deleted: "..file)
return output
end
-- NOTE: Voice messages are .ogg files encoded with OPUS
-- https://core.telegram.org/bots/api#sendvoice
function utilities:send_voice(chat_id, file, text, reply_to_message_id, duration)
local output = bindings.request(self, 'sendVoice', {
chat_id = chat_id,
duration = duration or nil,
reply_to_message_id = reply_to_message_id
}, {voice = file} )
os.remove(file)
print("Deleted: "..file)
return output
end
-- https://core.telegram.org/bots/api#sendlocation
function utilities:send_location(chat_id, latitude, longitude, reply_to_message_id)
return bindings.request(self, 'sendLocation', {
chat_id = chat_id,
latitude = latitude,
longitude = longitude,
reply_to_message_id = reply_to_message_id
} )
end
-- NOTE: Venue is different from location: it shows information, such as the street adress or
-- title of the location with it.
-- https://core.telegram.org/bots/api#sendvenue
function utilities:send_venue(chat_id, latitude, longitude, reply_to_message_id, title, address)
return bindings.request(self, 'sendVenue', {
chat_id = chat_id,
latitude = latitude,
longitude = longitude,
title = title,
address = address,
reply_to_message_id = reply_to_message_id
} )
end
-- https://core.telegram.org/bots/api#sendchataction
function utilities:send_typing(chat_id, action)
return bindings.request(self, 'sendChatAction', {
chat_id = chat_id,
action = action
} )
end
-- get the indexed word in a string
function utilities.get_word(s, i)
s = s or ''
i = i or 1
local t = {}
for w in s:gmatch('%g+') do
table.insert(t, w)
end
return t[i] or false
end
-- Like get_word(), but better.
-- Returns the actual index.
function utilities.index(s)
local t = {}
for w in s:gmatch('%g+') do
table.insert(t, w)
end
return t
end
-- Returns the string after the first space.
function utilities.input(s)
if not s:find(' ') then
return false
end
return s:sub(s:find(' ')+1)
end
-- Calculates the length of the given string as UTF-8 characters
function utilities.utf8_len(s)
local chars = 0
for i = 1, string.len(s) do
local b = string.byte(s, i)
if b < 128 or b >= 192 then
chars = chars + 1
end
end
return chars
end
-- I swear, I copied this from PIL, not yago! :)
function utilities.trim(str) -- Trims whitespace from a string.
local s = str:gsub('^%s*(.-)%s*$', '%1')
return s
end
local lc_list = {
-- Latin = 'Cyrillic'
['A'] = 'А',
['B'] = 'В',
['C'] = 'С',
['E'] = 'Е',
['I'] = 'І',
['J'] = 'Ј',
['K'] = 'К',
['M'] = 'М',
['H'] = 'Н',
['O'] = 'О',
['P'] = 'Р',
['S'] = 'Ѕ',
['T'] = 'Т',
['X'] = 'Х',
['Y'] = 'Ү',
['a'] = 'а',
['c'] = 'с',
['e'] = 'е',
['i'] = 'і',
['j'] = 'ј',
['o'] = 'о',
['s'] = 'ѕ',
['x'] = 'х',
['y'] = 'у',
['!'] = 'ǃ'
}
-- Retruns true if the string is empty
function string:isempty()
return self == nil or self == ''
end
-- Retruns true if the string is blank
function string:isblank()
self = self:trim()
return self:isempty()
end
function get_name(msg)
local name = msg.from.first_name
if name == nil then
name = msg.from.id
end
return name
end
-- http://www.lua.org/manual/5.2/manual.html#pdf-io.popen
function run_command(str)
local cmd = io.popen(str)
local result = cmd:read('*all')
cmd:close()
return result
end
function convert_timestamp(timestamp, format)
local converted_date = run_command('date -d @'..timestamp..' +"'..format..'"')
local converted_date = string.gsub(converted_date, '%\n', '')
return converted_date
end
function string.starts(String, Start)
return Start == string.sub(String,1,string.len(Start))
end
function get_http_file_name(url, headers)
-- Eg: fooo.var
local file_name = url:match("[^%w]+([%.%w]+)$")
-- Any delimited aphanumeric on the url
file_name = file_name or url:match("[^%w]+(%w+)[^%w]+$")
-- Random name, hope content-type works
file_name = file_name or str:random(5)
local content_type = headers["content-type"]
local extension = nil
if content_type then
extension = mimetype.get_mime_extension(content_type)
end
if extension then
file_name = file_name.."."..extension
end
local disposition = headers["content-disposition"]
if disposition then
-- attachment; filename=CodeCogsEqn.png
file_name = disposition:match('filename=([^;]+)') or file_name
file_name = string.gsub(file_name, "\"", "")
end
return file_name
end
-- Saves file to $HOME/tmp/. If file_name isn't provided,
-- will get the text after the last "/" for filename
-- and content-type for extension
function download_to_file(url, file_name)
print("url to download: "..url)
local respbody = {}
local options = {
url = url,
sink = ltn12.sink.table(respbody),
redirect = true
}
-- nil, code, headers, status
local response = nil
if string.starts(url, 'https') then
options.redirect = false
response = {HTTPS.request(options)}
else
response = {HTTP.request(options)}
end
local code = response[2]
local headers = response[3]
local status = response[4]
if code ~= 200 then return nil end
file_name = file_name or get_http_file_name(url, headers)
local file_path = "/home/akamaru/Mikubot-V2/tmp/"..file_name
print("Saved to: "..file_path)
file = io.open(file_path, "w+")
file:write(table.concat(respbody))
file:close()
return file_path
end
function vardump(value)
print(serpent.block(value, {comment=false}))
end
-- Replaces letters with corresponding Cyrillic characters.
function utilities.latcyr(str)
for k,v in pairs(lc_list) do
str = str:gsub(k, v)
end
return str
end
-- Loads a JSON file as a table.
function utilities.load_data(filename)
local f = io.open(filename)
if not f then
return {}
end
local s = f:read('*all')
f:close()
local data = JSON.decode(s)
return data
end
-- Saves a table to a JSON file.
function utilities.save_data(filename, data)
local s = JSON.encode(data)
local f = io.open(filename, 'w')
f:write(s)
f:close()
end
-- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua.
function utilities.get_coords(input, config)
local url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input)
local jstr, res = HTTPS.request(url)
if res ~= 200 then
return config.errors.connection
end
local jdat = JSON.decode(jstr)
if jdat.status == 'ZERO_RESULTS' then
return config.errors.results
end
return {
lat = jdat.results[1].geometry.location.lat,
lon = jdat.results[1].geometry.location.lng
}
end
-- Get the number of values in a key/value table.
function utilities.table_size(tab)
local i = 0
for _,_ in pairs(tab) do
i = i + 1
end
return i
end
-- Just an easy way to get a user's full name.
-- Alternatively, abuse it to concat two strings like I do.
function utilities.build_name(first, last)
if last then
return first .. ' ' .. last
else
return first
end
end
function utilities:resolve_username(input)
input = input:gsub('^@', '')
for _,v in pairs(self.database.users) do
if v.username and v.username:lower() == input:lower() then
return v
end
end
end
function utilities:user_from_message(msg, no_extra)
local input = utilities.input(msg.text_lower)
local target = {}
if msg.reply_to_message then
for k,v in pairs(self.database.users[msg.reply_to_message.from.id_str]) do
target[k] = v
end
elseif input and tonumber(input) then
target.id = tonumber(input)
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 _,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
end
end
end
if not target.id then
target.err = 'Sorry, I don\'t recognize that username.'
end
else
target.err = 'Please specify a user via reply, ID, or username.'
end
if not no_extra then
if target.id then
target.id_str = tostring(target.id)
end
if not target.first_name then
target.first_name = 'User'
end
target.name = utilities.build_name(target.first_name, target.last_name)
end
return target
end
function utilities:handle_exception(err, message, config)
if not err then err = '' end
local output = '\n[' .. os.date('%F %T', os.time()) .. ']\n' .. self.info.username .. ': ' .. err .. '\n' .. message .. '\n'
if config.log_chat then
output = '```' .. output .. '```'
utilities.send_message(self, config.log_chat, output, true, nil, true)
else
print(output)
end
end
function utilities.download_file(url, filename)
if not filename then
filename = url:match('.+/(.-)$') or os.time()
filename = '/tmp/' .. filename
end
local body = {}
local doer = HTTP
local do_redir = true
if url:match('^https') then
doer = HTTPS
do_redir = false
end
local _, res = doer.request{
url = url,
sink = ltn12.sink.table(body),
redirect = do_redir
}
if res ~= 200 then return false end
local file = io.open(filename, 'w+')
file:write(table.concat(body))
file:close()
return filename
end
function utilities.markdown_escape(text)
text = text:gsub('_', '\\_')
text = text:gsub('%[', '\\[')
text = text:gsub('%]', '\\]')
text = text:gsub('%*', '\\*')
text = text:gsub('`', '\\`')
return text
end
utilities.md_escape = utilities.markdown_escape
utilities.triggers_meta = {}
utilities.triggers_meta.__index = utilities.triggers_meta
function utilities.triggers_meta:t(pattern, has_args)
local username = self.username:lower()
table.insert(self.table, '^'..self.cmd_pat..pattern..'$')
table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'$')
if has_args then
table.insert(self.table, '^'..self.cmd_pat..pattern..'%s+[^%s]*')
table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'%s+[^%s]*')
end
return self
end
function utilities.triggers(username, cmd_pat, trigger_table)
local self = setmetatable({}, utilities.triggers_meta)
self.username = username
self.cmd_pat = cmd_pat
self.table = trigger_table or {}
return self
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 = utilities.build_name(user.first_name, user.last_name)
return user
end
function utilities.enrich_message(msg)
if not msg.text then msg.text = msg.caption or '' end
msg.text_lower = msg.text:lower()
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 = 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 = utilities.enrich_user(msg.forward_from)
end
if msg.new_chat_participant then
msg.new_chat_participant = utilities.enrich_user(msg.new_chat_participant)
end
if msg.left_chat_participant then
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
function utilities:create_user_entry(user)
local id = tostring(user.id)
-- Clear things that may no longer exist, or create a user entry.
if self.database.users[id] then
self.database.users[id].username = nil
self.database.users[id].last_name = nil
else
self.database.users[id] = {}
end
-- Add all the user info to the entry.
for k,v in pairs(user) do
self.database.users[id][k] = v
end
end
-- This table will store unsavory characters that are not properly displayed,
-- or are just not fun to type.
utilities.char = {
zwnj = '',
arabic = '[\216-\219][\128-\191]',
rtl_override = '',
rtl_mark = '',
em_dash = ''
}
-- Returns a table with matches or nil
--function match_pattern(pattern, text, lower_case)
function match_pattern(pattern, text)
if text then
local matches = { string.match(text, pattern) }
if next(matches) then
return matches
end
end
-- nil
end
function post_petition(url, arguments, headers)
local url, h = string.gsub(url, "http://", "")
local url, hs = string.gsub(url, "https://", "")
local post_prot = "http"
if hs == 1 then
post_prot = "https"
end
local response_body = {}
local request_constructor = {
url = post_prot..'://'..url,
method = "POST",
sink = ltn12.sink.table(response_body),
headers = headers or {},
redirect = false
}
local source = arguments
if type(arguments) == "table" then
local source = helpers.url_encode_arguments(arguments)
end
if not headers then
request_constructor.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF8"
request_constructor.headers["X-Accept"] = "application/json"
request_constructor.headers["Accept"] = "application/json"
end
request_constructor.headers["Content-Length"] = tostring(#source)
request_constructor.source = ltn12.source.string(source)
if post_prot == "http" then
ok, response_code, response_headers, response_status_line = http.request(request_constructor)
else
ok, response_code, response_headers, response_status_line = https.request(request_constructor)
end
if not ok then
return nil
end
response_body = JSON.decode(table.concat(response_body))
return response_body, response_headers
end
function get_redis_hash(msg, var)
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
return 'chat:'..msg.chat.id..':'..var
end
if msg.chat.type == 'private' then
return 'user:'..msg.from.id..':'..var
end
end
-- remove whitespace
function all_trim(s)
return s:match( "^%s*(.-)%s*$" )
end
function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
function round(num, idp)
if idp and idp>0 then
local mult = 10^idp
return math.floor(num * mult + 0.5) / mult
end
return math.floor(num + 0.5)
end
function comma_value(amount)
local formatted = amount
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1.%2')
if (k==0) then
break
end
end
return formatted
end
function string.ends(str, fin)
return fin=='' or string.sub(str,-string.len(fin)) == fin
end
function get_location(user_id)
local hash = 'user:'..user_id
local set_location = redis:hget(hash, 'location')
if set_location == 'false' or set_location == nil then
return false
else
return set_location
end
end
function cache_data(plugin, query, data, timeout, typ)
-- How to: cache_data(pluginname, query_name, data_to_cache, expire_in_seconds)
local hash = 'telegram:cache:'..plugin..':'..query
if timeout then
print('Caching "'..query..'" from plugin '..plugin..' (expires in '..timeout..' seconds)')
else
print('Caching "'..query..'" from plugin '..plugin..' (expires never)')
end
if typ == 'key' then
redis:set(hash, data)
elseif typ == 'set' then
-- make sure that you convert your data into a table:
-- {"foo", "bar", "baz"} instead of
-- {"bar" = "foo", "foo" = "bar", "bar" = "baz"}
-- because other formats are not supported by redis (or I haven't found a way to store them)
for _,str in pairs(data) do
redis:sadd(hash, str)
end
else
redis:hmset(hash, data)
end
if timeout then
redis:expire(hash, timeout)
end
end
--[[
Ordered table iterator, allow to iterate on the natural order of the keys of a
table.
-- http://lua-users.org/wiki/SortedIteration
]]
function __genOrderedIndex( t )
local orderedIndex = {}
for key in pairs(t) do
table.insert( orderedIndex, key )
end
table.sort( orderedIndex )
return orderedIndex
end
function orderedNext(t, state)
-- Equivalent of the next function, but returns the keys in the alphabetic
-- order. We use a temporary ordered key table that is stored in the
-- table being iterated.
key = nil
--print("orderedNext: state = "..tostring(state) )
if state == nil then
-- the first time, generate the index
t.__orderedIndex = __genOrderedIndex( t )
key = t.__orderedIndex[1]
else
-- fetch the next value
for i = 1,table.getn(t.__orderedIndex) do
if t.__orderedIndex[i] == state then
key = t.__orderedIndex[i+1]
end
end
end
if key then
return key, t[key]
end
-- no more value to return, cleanup
t.__orderedIndex = nil
return
end
function orderedPairs(t)
-- Equivalent of the pairs() function on tables. Allows to iterate
-- in order
return orderedNext, t, nil
end
-- converts total amount of seconds (e.g. 65 seconds) to human redable time (e.g. 1:05 minutes)
function makeHumanTime(totalseconds)
local seconds = totalseconds % 60
local minutes = math.floor(totalseconds / 60)
local minutes = minutes % 60
local hours = math.floor(totalseconds / 3600)
if minutes == 00 and hours == 00 then
return seconds..' Sekunden'
elseif hours == 00 and minutes ~= 00 then
return string.format("%02d:%02d", minutes, seconds)..' Minuten'
elseif hours ~= 00 then
return string.format("%02d:%02d:%02d", hours, minutes, seconds)..' Stunden'
end
end
function is_blacklisted(msg)
_blacklist = redis:smembers("telegram:img_blacklist")
local var = false
for v,word in pairs(_blacklist) do
if string.find(string.lower(msg), string.lower(word)) then
print("Wort steht auf der Blacklist!")
var = true
break
end
end
return var
end
function unescape(str)
str = string.gsub( str, '&lt;', '<' )
str = string.gsub( str, '&gt;', '>' )
str = string.gsub( str, '&quot;', '"' )
str = string.gsub( str, '&apos;', "'" )
str = string.gsub( str, "&Auml;", "Ä")
str = string.gsub( str, "&auml;", "ä")
str = string.gsub( str, "&Ouml;", "Ö")
str = string.gsub( str, "&ouml;", "ö")
str = string.gsub( str, "Uuml;", "Ü")
str = string.gsub( str, "&uuml;", "ü")
str = string.gsub( str, "&szlig;", "ß")
str = string.gsub( str, '&#(%d+);', function(n) return string.char(n) end )
str = string.gsub( str, '&#x(%d+);', function(n) return string.char(tonumber(n,16)) end )
str = string.gsub( str, '&amp;', '&' ) -- Be sure to do this after all others
return str
end
function url_encode(str)
if (str) then
str = string.gsub (str, "\n", "\r\n")
str = string.gsub (str, "([^%w %-%_%.%~])",
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
end
return str
end
return utilities