diff --git a/README.md b/README.md index 0c621be..3ab4cdc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # otouto -The popular Telegram assistant bot, now on the bot API. +A plugin-wielding Telegram bot using the new API. +The latest version runs on @mokubot. v1 runs on @otouto. diff --git a/bindings.lua b/bindings.lua new file mode 100644 index 0000000..122a60a --- /dev/null +++ b/bindings.lua @@ -0,0 +1,79 @@ +-- bindings.lua +-- Functions for the Telegram API. +-- Requires ssl.https ('HTTPS'), socket.url ('URL'), and a json decoder ('JSON'). +-- Also requires config.BOT_API_KEY. + +local BASE_URL = 'https://api.telegram.org/bot' .. config.BOT_API_KEY .. '/' + +local function send_request(url) + + local dat, res = HTTPS.request(url) + local tab = JSON.decode(dat) + + if res ~= 200 then + print('Connection error.') + return false + end + + if not tab.ok then + print(tab.description) + return false + end + + return tab + +end + +function get_me() + + local url = BASE_URL .. 'getMe' + return send_request(url) + +end + +function get_updates(offset) + + local url = BASE_URL .. 'getUpdates' + + if offset then + url = url .. '?offset=' .. offset + end + + return send_request(url) + +end + +function send_message(chat_id, text, disable_web_page_preview, reply_to_message_id) + + local url = BASE_URL .. 'sendMessage?chat_id=' .. chat_id .. '&text=' .. URL.escape(text) + + if disable_web_page_preview == true then + url = url .. '&disable_web_page_preview=true' + end + + if reply_to_message_id then + url = url .. '&reply_to_message_id=' .. reply_to_message_id + end + + return send_request(url) + +end + +function send_chat_action(chat_id, action) + + local url = BASE_URL .. 'sendChatAction?chat_id=' .. chat_id .. '&action=' .. action + return send_request(url) + +end + +function send_location(chat_id, latitude, longitude, reply_to_message_id) + + local url = BASE_URL .. 'sendLocation?chat_id=' .. chat_id .. '&latitude=' .. latitude .. '&longitude=' .. longitude + + if reply_to_message_id then + url = url .. '&reply_to_message_id=' .. reply_to_message_id + end + + return send_request(url) + +end diff --git a/bot.lua b/bot.lua new file mode 100644 index 0000000..0e2b97b --- /dev/null +++ b/bot.lua @@ -0,0 +1,99 @@ +-- bot.lua +-- Run this! + +HTTP = require('socket.http') +HTTPS = require('ssl.https') +JSON = require('dkjson') +URL = require('socket.url') + +VERSION = 2.01 + +function on_msg_receive(msg) + + if msg.date < os.time() -5 then return end -- don't react to old messages + if not msg.text then return end -- don't react to media messages + if msg.forward_from then return end -- don't react to forwarded messages + + local lower = string.lower(msg.text) + for i,v in pairs(plugins) do + for j,w in pairs(v.triggers) do + if string.match(lower, w) then + send_chat_action(msg.chat.id, 'typing') + v.action(msg) + end + end + end +end + +function bot_init() + + print('\nLoading configuration...') + + local jstr = io.open('config.json') + local jstr = jstr:read('*all') + config = JSON.decode(jstr) + print(#config.plugins .. ' plugins enabled.') + + require('bindings') + require('utilities') + + print('\nFetching bot information...') + + bot = get_me().result + for k,v in pairs(bot) do + print('',k,v) + end + + print('Bot information retrieved!\n') + print('Loading plugins...') + + plugins = {} + for i,v in ipairs(config.plugins) do + print('',v) + local p = loadfile('plugins/'..v)() + table.insert(plugins, p) + end + + print('Done! Plugins loaded: ' .. #plugins .. '\n') + print('Generating help message...') + + help_message = 'Available commands:\n' + for i,v in ipairs(plugins) do + if v.doc then + local a = string.sub(v.doc, 1, string.find(v.doc, '\n')-1) + print(a) + help_message = help_message .. ' - ' .. a .. '\n' + end + end + help_message = help_message .. [[ + *Arguments: [optional] + Use "!help " for specific information. + otouto v]] .. VERSION .. [[ by @topkecleon. + ]] + + print('Help message generated!\n') + + is_started = true + +end + +bot_init() +reminders = {} +last_update = 0 +while is_started == true do + + for i,v in ipairs(get_updates(last_update).result) do + if v.update_id > last_update then + last_update = v.update_id + on_msg_receive(v.message) + end + end + + for k,v in pairs(reminders) do + if os.time() > v.alarm then + send_message(v.chat_id, 'Reminder: '..v.text) + table.remove(reminders, i) + end + end + +end diff --git a/config.json b/config.json new file mode 100644 index 0000000..a2d24a7 --- /dev/null +++ b/config.json @@ -0,0 +1,40 @@ +{ + "BOT_API_KEY": "", + "ADMIN_ID": 0, + "BIBLIA_API_KEY": "", + "GIPHY_API_KEY": "", + "TIME_OFFSET": 0, + "plugins": [ + "8ball.lua", + "admin.lua", + "bandersnatch.lua", + "bible.lua", + "btc.lua", + "calc.lua", + "commit.lua", + "dice.lua", + "dogify.lua", + "echo.lua", + "fortune.lua", + "gImages.lua", + "giphy.lua", + "gMaps.lua", + "gSearch.lua", + "hackernews.lua", + "help.lua", + "hex.lua", + "imdb.lua", + "personality.lua", + "pokedex.lua", + "reddit.lua", + "remind.lua", + "shrug.lua", + "slap.lua", + "time.lua", + "urbandictionary.lua", + "weather.lua", + "whoami.lua", + "xkcd.lua" + ] +} + diff --git a/plugin.lua b/plugin.lua new file mode 100644 index 0000000..9eef8aa --- /dev/null +++ b/plugin.lua @@ -0,0 +1,20 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !example + Info about the command. +]] + +PLUGIN.triggers = { + '^!example', + '^!e$' +} + +function PLUGIN.action(msg) + + local message = 'Example output.' + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/8ball.lua b/plugins/8ball.lua new file mode 100644 index 0000000..2594372 --- /dev/null +++ b/plugins/8ball.lua @@ -0,0 +1,54 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !8ball + Magic 8-ball. Returns a standard 8ball message, unless called with "y/n", where it will return a less verbose answer. +]] + +PLUGIN.triggers = { + '^!helix', + '^!8ball', + 'y/n%p?$' +} + +PLUGIN.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." +} + +PLUGIN.yesno = {'Absolutely.', 'In your dreams.', 'Yes.', 'No.', 'Maybe.'} + +function PLUGIN.action(msg) + + math.randomseed(os.time()) + + if string.match(string.lower(msg.text), 'y/n') then + message = PLUGIN.yesno[math.random(#PLUGIN.yesno)] + else + message = PLUGIN.answers[math.random(#PLUGIN.answers)] + end + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/admin.lua b/plugins/admin.lua new file mode 100644 index 0000000..2d1370d --- /dev/null +++ b/plugins/admin.lua @@ -0,0 +1,39 @@ +local PLUGIN = {} + +PLUGIN.triggers = { + '^!admin ' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + + local message = 'Command not found.' + + if msg.from.id ~= config.ADMIN_ID then + message = 'Permission denied.' + + elseif string.lower(first_word(input)) == 'run' then + + local output = string.sub(input, 5) + local output = io.popen(output) + message = output:read('*all') + output:close() + + elseif string.lower(first_word(input)) == 'reload' then + + bot_init() + message = 'Bot reloaded!' + + elseif string.lower(first_word(input)) == 'halt' then + + is_started = false + message = 'Shutting down...' + + end + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/bandersnatch.lua b/plugins/bandersnatch.lua new file mode 100644 index 0000000..3fcc17d --- /dev/null +++ b/plugins/bandersnatch.lua @@ -0,0 +1,33 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !bandersnatch + This is a Benedict Cumberbatch name generator. +]] + +PLUGIN.triggers = { + '^!bandersnatch', + '^!bc$' +} + +PLUGIN.fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" } + +PLUGIN.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" } + +PLUGIN.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 PLUGIN.action(msg) + + math.randomseed(os.time()) + + local message = '' + if math.random(10) == 10 then + message = PLUGIN.fullnames[math.random(#PLUGIN.fullnames)] + else + message = PLUGIN.firstnames[math.random(#PLUGIN.firstnames)] .. ' ' .. PLUGIN.lastnames[math.random(#PLUGIN.lastnames)] + end + + send_msg(msg, message) +end + +return PLUGIN diff --git a/plugins/bible.lua b/plugins/bible.lua new file mode 100644 index 0000000..24afc5d --- /dev/null +++ b/plugins/bible.lua @@ -0,0 +1,32 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !bible + Returns a verse from the bible, King James Version. Use a standard or abbreviated reference (John 3:16, Jn3:16). + http://biblia.com +]] + +PLUGIN.triggers = { + '^!bible', + '^!b ' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local url = 'http://api.biblia.com/v1/bible/content/KJV.txt?key=' .. config.BIBLIA_API_KEY .. '&passage=' .. URL.escape(input) + local message, res = HTTP.request(url) + + if res ~= 200 then + message = 'Connection error.' + end + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/btc.lua b/plugins/btc.lua new file mode 100644 index 0000000..96b495f --- /dev/null +++ b/plugins/btc.lua @@ -0,0 +1,55 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !btc [amount] + Gives bitcoin prices for the given currency, and optionally conversion of an amount to and from that currency. + BitcoinAverage Price Index https://bitcoinaverage.com/ +]] + +PLUGIN.triggers = { + '^!btc' +} + +function PLUGIN.action(msg) + + local url = nil + local arg1 = 'USD' + local arg2 = 1 + + local jstr, res = HTTPS.request('https://api.bitcoinaverage.com/ticker/global/') + + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + + local jdat = JSON.decode(jstr) + + if string.len(msg.text) > 6 then + arg1 = string.upper(string.sub(msg.text, 6, 8)) + end + if string.len(msg.text) > 9 then + arg2 = tonumber(string.sub(msg.text, 10)) + end + + for k,v in pairs(jdat) do + if k == arg1 then + url = v .. '/' + break + end + end + + if url then + jstr, b = HTTPS.request(url) + else + return send_msg(msg, 'Error: Currency not found.') + end + + jdat = JSON.decode(jstr) + local m = arg2 .. ' BTC = ' .. jdat['24h_avg']*arg2 ..' '.. arg1 .. '\n' + m = m .. arg2 ..' '.. arg1 .. ' = ' .. string.format("%.8f", arg2/jdat['24h_avg']) .. ' BTC' + + send_msg(msg, m) + +end + +return PLUGIN diff --git a/plugins/calc.lua b/plugins/calc.lua new file mode 100644 index 0000000..4b7d7c0 --- /dev/null +++ b/plugins/calc.lua @@ -0,0 +1,30 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !calc + This command solves math expressions and does conversion between common units. See mathjs.org/docs/expressions/syntax for a list of accepted syntax. +]] + +PLUGIN.triggers = { + '^!calc' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local url = 'http://api.mathjs.org/v1/?expr=' .. URL.escape(input) + local message, res = HTTP.request(url) + + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + + send_msg(msg, message) +end + +return PLUGIN + diff --git a/plugins/commit.lua b/plugins/commit.lua new file mode 100644 index 0000000..7a827ad --- /dev/null +++ b/plugins/commit.lua @@ -0,0 +1,420 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !commit + http://whatthecommit.com. +]] + +PLUGIN.triggers = { + '^!commit' +} + +function PLUGIN.action(msg) + math.randomseed(os.time()) + send_msg(msg, PLUGIN.commits[math.random(#PLUGIN.commits)]) +end + +PLUGIN.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
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", + "Don’t 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 I’m supposed to do it, but I can't think of something better.", + "Don’t 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!" +} + +return PLUGIN diff --git a/plugins/dice.lua b/plugins/dice.lua new file mode 100644 index 0000000..d1b313d --- /dev/null +++ b/plugins/dice.lua @@ -0,0 +1,71 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !roll [range] + Roll a die. Use any positive number for range or use D&D notation. + Example: !roll 4D100 will roll a 100-sided die four times. +]] + +PLUGIN.triggers = { + '^!roll' +} + +function PLUGIN.action(msg) + + math.randomseed(os.time()) + + local input = get_input(msg.text) + if not input then + input = 6 + else + input = string.upper(input) + end + + if tonumber(input) then + range = tonumber(input) + rolls = 1 + elseif string.find(input, 'D') then + local dloc = string.find(input, 'D') + if dloc == 1 then + rolls = 1 + else + rolls = string.sub(input, 1, dloc-1) + end + range = string.sub(input, dloc+1) + if not tonumber(rolls) or not tonumber(range) then + return send_msg(msg, 'Invalid syntax.') + end + else + return send_msg(msg, 'Invalid syntax.') + end + + if tonumber(rolls) == 1 then + results = 'Random (1-' .. range .. '):\t' + elseif tonumber(rolls) > 1 then + results = rolls .. 'D' .. range .. ':\n' + else + return send_msg(msg, 'Invalid syntax.') + end + + if tonumber(range) < 2 then + return send_msg(msg, 'Invalid syntax.') + end + + if tonumber(rolls) > 100 then + return send_msg(msg, 'Maximum dice is 100.') + end + + if tonumber(range) > 100000 then + return send_msg(msg, 'Maximum range is 100000.') + end + + for i = 1, tonumber(rolls) do + results = results .. math.random(range) .. '\t' + end + + send_msg(msg, results) + +end + +return PLUGIN + diff --git a/plugins/dogify.lua b/plugins/dogify.lua new file mode 100644 index 0000000..11c7704 --- /dev/null +++ b/plugins/dogify.lua @@ -0,0 +1,28 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !dogify + Produces a doge image from dogr.io. Newlines are indicated by a forward slash. Words do not need to be spaced, but spacing is supported. Will post a previewed link rather than an image. +]] + +PLUGIN.triggers = { + '^!doge ', + '^!dogify ' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local input = string.gsub(input, ' ', '') + + url = 'http://dogr.io/' .. input .. '.png' + + send_message(msg.chat.id, url, false, msg.message_id) + +end + +return PLUGIN diff --git a/plugins/echo.lua b/plugins/echo.lua new file mode 100644 index 0000000..4bec525 --- /dev/null +++ b/plugins/echo.lua @@ -0,0 +1,23 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !echo + Repeat a string. +]] + +PLUGIN.triggers = { + '^!echo' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + send_msg(msg, latcyr(input)) + +end + +return PLUGIN diff --git a/plugins/fortune.lua b/plugins/fortune.lua new file mode 100644 index 0000000..99f2712 --- /dev/null +++ b/plugins/fortune.lua @@ -0,0 +1,22 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !fortune + Get a random fortune from the UNIX fortune program. +]] + +PLUGIN.triggers = { + '^!fortune', + '^!f$' +} + +function PLUGIN.action(msg) + local output = io.popen('fortune') + message = '' + for l in output:lines() do + message = message .. l .. '\n' + end + send_msg(msg, message) +end + +return PLUGIN diff --git a/plugins/gImages.lua b/plugins/gImages.lua new file mode 100644 index 0000000..30e8a59 --- /dev/null +++ b/plugins/gImages.lua @@ -0,0 +1,67 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !images + This command performs a Google Images search for the given query. One random top result is returned. Safe search is enabled by default; use '!insfw' to get potentially NSFW results. +]] + +PLUGIN.triggers = { + '^!images?', + '^!img', + '^!i ', + '^!insfw' +} + +PLUGIN.exts = { + '.png$', + '.jpg$', + '.jpeg$', + '.jpe$', + '.gif$' +} + +function PLUGIN.action(msg) + + local url = 'http://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8' + + if not string.match(msg.text, '^!insfw ') then + url = url .. '&safe=active' + end + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + url = url .. '&q=' .. URL.escape(input) + + local jstr, res = HTTP.request(url) + + if res ~= 200 then + send_msg(target, 'Connection error.', ok_cb, false) + return + end + + local jdat = JSON.decode(jstr) + + if #jdat.responseData.results < 1 then + send_msg(target, 'No results found.', ok_cb, false) + return + end + + is_real = false + while is_real == false do + local i = math.random(#jdat.responseData.results) + result_url = jdat.responseData.results[i].url + for i,v in pairs(PLUGIN.exts) do + if string.match(string.lower(result_url), v) then + is_real = true + end + end + end + + send_message(msg.chat.id, result_url, false, msg.message_id) + +end + +return PLUGIN diff --git a/plugins/gMaps.lua b/plugins/gMaps.lua new file mode 100644 index 0000000..9dfa2ab --- /dev/null +++ b/plugins/gMaps.lua @@ -0,0 +1,40 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !loc + Sends location data for query, taken from Google Maps. Works for countries, cities, landmarks, etc. +]] + +PLUGIN.triggers = { + '^!loc' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) + local jstr, res = HTTP.request(url) + + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + + local jdat = JSON.decode(jstr) + + if jdat.status ~= 'OK' then + local message = 'Error: \"' .. input .. '\" not found.' + return send_msg(msg, message) + end + + local lat = jdat.results[1].geometry.location.lat + local lng = jdat.results[1].geometry.location.lng + send_location(msg.chat.id, lat, lng, msg.message_id) + +end + +return PLUGIN + diff --git a/plugins/gSearch.lua b/plugins/gSearch.lua new file mode 100644 index 0000000..c22b24a --- /dev/null +++ b/plugins/gSearch.lua @@ -0,0 +1,57 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !google + This command performs a Google search for the given query. Four results are returned. Safe search is enabled by default; use '!gnsfw' to get potentially NSFW results. Four results are returned for a group chat, or eight in a private message. +]] + +PLUGIN.triggers = { + '^!g ', + '^!google', + '^!gnsfw' +} + +function PLUGIN.action(msg) + + local url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0' + + if not string.match(msg.text, '^!gnsfw ') then + url = url .. '&safe=active' + end + + if not msg.chat.title then + url = url .. '&rsz=8' + end + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + url = url .. '&q=' .. URL.escape(input) + + local jstr, res = HTTP.request(url) + + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + + local jdat = JSON.decode(jstr) + + if #jdat.responseData.results < 1 then + return send_msg(msg, 'No results found.') + end + + message = '' + + for i = 1, #jdat.responseData.results do + local result_url = jdat.responseData.results[i].unescapedUrl + local result_title = jdat.responseData.results[i].titleNoFormatting + message = message .. ' - ' .. result_title ..'\n'.. result_url .. '\n' + end + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/giphy.lua b/plugins/giphy.lua new file mode 100644 index 0000000..67d303a --- /dev/null +++ b/plugins/giphy.lua @@ -0,0 +1,53 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !giphy [query] + Returns a random or search-resulted GIF from giphy.com. Results are limited to PG-13 by default; use '!gifnsfw' to get potentially NSFW results. +]] + +PLUGIN.triggers = { + '^!giphy', + '^!gifnsfw' +} + +function PLUGIN.action(msg) + + local search_url = 'http://api.giphy.com/v1/gifs/search?limit=10&api_key=' .. config.GIPHY_API_KEY + local random_url = 'http://tv.giphy.com/v1/gifs/random?api_key=' .. config.GIPHY_API_KEY + local result_url = '' + + if string.match(msg.text, '^!giphynsfw') then + search_url = search_url .. '&rating=r&q=' + random_url = random_url .. '&rating=r' + else + search_url = search_url .. '&rating=pg-13&q=' + random_url = random_url .. '&rating=pg-13' + end + + local input = get_input(msg.text) + + if not input then + + local jstr, res = HTTP.request(random_url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + local jdat = JSON.decode(jstr) + result_url = jdat.data.image_url + + else + + local jstr, res = HTTP.request(search_url .. input) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + local jdat = JSON.decode(jstr) + result_url = jdat.data[math.random(#jdat.data)].images.original.url + + end + + send_message(msg.chat.id, result_url, false, msg.message_id) + +end + +return PLUGIN diff --git a/plugins/hackernews.lua b/plugins/hackernews.lua new file mode 100644 index 0000000..0b30a95 --- /dev/null +++ b/plugins/hackernews.lua @@ -0,0 +1,35 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !hackernews + Returns some top stories from Hacker News. Four in a group or eight in a private message. +]] + +PLUGIN.triggers = { + '^!hackernews', + '^!hn$' +} + +function PLUGIN.action(msg) + + local message = '' + local jstr = HTTPS.request('https://hacker-news.firebaseio.com/v0/topstories.json') + local stories = JSON.decode(jstr) + + local limit = 4 + if msg.chat.id == msg.from.id then + limit = 8 + end + + for i = 1, limit do + url = 'https://hacker-news.firebaseio.com/v0/item/'..stories[i]..'.json' + jstr = HTTPS.request(url) + jdat = JSON.decode(jstr) + message = message .. jdat.title .. '\n' .. jdat.url .. '\n' + end + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/help.lua b/plugins/help.lua new file mode 100644 index 0000000..a265683 --- /dev/null +++ b/plugins/help.lua @@ -0,0 +1,38 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !help [command] + Get list of basic information for all commands, or more detailed documentation on a specified command. +]] + +PLUGIN.triggers = { + '^!help', + '^!h$' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + + if input then + for i,v in ipairs(plugins) do + if v.doc then + if '!' .. input == trim_string(first_word(v.doc)) then + return send_msg(msg, v.doc) + end + end + end + end + + if msg.from.id ~= msg.chat.id then + if not send_message(msg.from.id, help_message, true, msg.message_id) then + return send_msg(msg, help_message) -- Unable to PM user who hasn't PM'd first. + end + return send_msg(msg, 'I have sent you the requested information in a private message.') + else + return send_msg(msg, help_message) + end + +end + +return PLUGIN diff --git a/plugins/hex.lua b/plugins/hex.lua new file mode 100644 index 0000000..955a4d2 --- /dev/null +++ b/plugins/hex.lua @@ -0,0 +1,29 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !hex + This function converts a number to or from hexadecimal. +]] + +PLUGIN.triggers = { + '^!hex ' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + + if string.sub(input, 1, 2) == '0x' then + send_msg(msg, tonumber(input)) + + elseif tonumber(input) then + send_msg(msg, string.format('%x', input)) + + else + send_msg(msg, 'Invalid number.') + + end + +end + +return PLUGIN diff --git a/plugins/imdb.lua b/plugins/imdb.lua new file mode 100644 index 0000000..c0bf295 --- /dev/null +++ b/plugins/imdb.lua @@ -0,0 +1,40 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !imdb + This function retrieves the IMDb info for a given film or television series, including the year, genre, imdb rating, runtime, and a summation of the plot. +]] + +PLUGIN.triggers = { + '^!imdb' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local url = 'http://www.imdbapi.com/?t=' .. URL.escape(input) + local jstr, res = HTTP.request(url) + local jdat = JSON.decode(jstr) + + if res ~= 200 then + return send_msg(msg, 'Error connecting to server.') + end + + if jdat.Response ~= 'True' then + return send_msg(msg, jdat.Error) + end + + local message = jdat.Title ..' ('.. jdat.Year ..')\n' + message = message .. jdat.imdbRating ..' | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n' + message = message .. jdat.Plot .. '\n' + message = message .. 'http://imdb.com/title/' .. jdat.imdbID + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/personality.lua b/plugins/personality.lua new file mode 100644 index 0000000..ddb917c --- /dev/null +++ b/plugins/personality.lua @@ -0,0 +1,38 @@ +local PLUGIN = {} + +PLUGIN.triggers = { + 'otouto%p?$', + '^tadaima%p?$', + '^i\'m home%p?$', + '^i\'m back%p?$' +} + +function PLUGIN.action(msg) -- I WISH LUA HAD PROPER REGEX SUPPORT + + local input = string.lower(msg.text) + + for i = 2, #PLUGIN.triggers do + if string.match(input, PLUGIN.triggers[i]) then + return send_message(msg.chat.id, 'Welcome back, ' .. msg.from.first_name .. '!') + end + end + + if input:match('thanks,? otouto') or input:match('thank you,? otouto') then + return send_message(msg.chat.id, 'No problem, ' .. msg.from.first_name .. '!') + end + + if input:match('hello,? otouto') or input:match('hey,? otouto') or input:match('hi,? otouto') then + return send_message(msg.chat.id, 'Hi, ' .. msg.from.first_name .. '!') + end + + if input:match('i hate you,? otouto') or input:match('screw you,? otouto') or input:match('fuck you,? otouto') then + return send_msg(msg, '; _ ;') + end + + if string.match(input, 'i love you,? otouto') then + return send_msg(msg, '<3') + end + +end + +return PLUGIN diff --git a/plugins/pokedex.lua b/plugins/pokedex.lua new file mode 100644 index 0000000..4557b6d --- /dev/null +++ b/plugins/pokedex.lua @@ -0,0 +1,57 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !dex + Get Pokedex information for a given Pokemon. + Includes national ID number, type, height, weight, and a description from a random regional dex. +]] + +PLUGIN.triggers = { + '^!dex' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local base_url = 'http://pokeapi.co' + local poke_type = nil + + local dex_url = base_url .. '/api/v1/pokemon/' .. input + local dex_jstr, res = HTTP.request(dex_url) + if res ~= 200 then + return send_msg(msg, 'Pokemon not found..') + end + + local dex_jdat = JSON.decode(dex_jstr) + + local desc_url = base_url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri + local desc_jstr, res = HTTP.request(desc_url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + + local desc_jdat = JSON.decode(desc_jstr) + + for k,v in pairs(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 info_line = 'Height: ' .. dex_jdat.height/10 .. 'm, Weight: ' .. dex_jdat.weight/10 .. 'kg' + + local m = dex_jdat.name ..' #'.. dex_jdat.national_id ..'\n'.. poke_type ..'\n'.. info_line ..'\n'.. desc_jdat.description:gsub('POKMON', 'POKeMON') + + send_msg(msg, m) + +end + +return PLUGIN diff --git a/plugins/pun.lua b/plugins/pun.lua new file mode 100644 index 0000000..70bcd6a --- /dev/null +++ b/plugins/pun.lua @@ -0,0 +1,142 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !pun + Get a random pun. + Have a recommendation? PM @topkecleon. +]] + +PLUGIN.triggers = { + '^!pun' +} + +PLUGIN.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, you’re 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 marries? 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 caught 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." +} + +function PLUGIN.action(msg) + math.randomseed(os.time()) + send_msg(msg, PLUGIN.puns[math.random(#PLUGIN.puns)]) +end + +return PLUGIN diff --git a/plugins/reddit.lua b/plugins/reddit.lua new file mode 100644 index 0000000..fd2d6f1 --- /dev/null +++ b/plugins/reddit.lua @@ -0,0 +1,84 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !reddit [r/subreddit | query] + This command returns top results for a given query or subreddit. NSFW posts are marked as such. +]] + +PLUGIN.triggers = { + '^!reddit', + '^!r$', + '^!r ' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + local jdat = {} + local message = '' + + if input then + + if string.match(input, '^r/') then + + local url = 'http://www.reddit.com/' .. first_word(input) .. '/.json' + local jstr, res = HTTP.request(url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + jdat = JSON.decode(jstr) + if #jdat.data.children == 0 then + return send_msg(msg, 'Subreddit not found.') + end + + else + + local url = 'http://www.reddit.com/search.json?q=' .. URL.escape(input) + local jstr, res = HTTP.request(url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + jdat = JSON.decode(jstr) + if #jdat.data.children == 0 then + return send_msg(msg, 'No results found.') + end + + end + + else + + url = 'https://www.reddit.com/.json' + local jstr, res = HTTP.request(url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + jdat = JSON.decode(jstr) + + end + + local limit = 4 + if #jdat.data.children < limit then + limit = #jdat.data.children + end + + for i = 1, limit do + + if jdat.data.children[i].data.over_18 then + message = message .. '[NSFW] ' + end + + url = '\n' + if not jdat.data.children[i].data.is_self then + url = '\n' .. jdat.data.children[i].data.url .. '\n' + end + + local short_url = '[redd.it/' .. jdat.data.children[i].data.id .. '] ' + message = message .. short_url .. jdat.data.children[i].data.title .. url + + end + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/remind.lua b/plugins/remind.lua new file mode 100644 index 0000000..f1d4498 --- /dev/null +++ b/plugins/remind.lua @@ -0,0 +1,55 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !remind + Set a reminder for yourself. First argument is the number of minutes until you wish to be reminded. +]] + +PLUGIN.triggers = { + '^!remind$', + '^!remind ' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local delay = first_word(input) + if not tonumber(delay) then + return send_msg(msg, 'The delay must be a number.') + end + + if string.len(msg.text) <= string.len(delay) + 9 then + return send_msg(msg, 'Please include a reminder.') + end + local text = string.sub(msg.text, string.len(delay)+10) -- this is gross + if msg.from.username then + text = text .. '\n@' .. msg.from.username + end + + local delay = tonumber(delay) + + local reminder = { + alarm = os.time() + (delay * 60), + chat_id = msg.chat.id, + text = text + } + + table.insert(reminders, reminder) + + if delay <= 1 then + delay = (delay * 60) .. ' seconds' + else + delay = delay .. ' minutes' + end + + local message = 'Your reminder has been set for ' .. delay .. ' from now:\n' .. text + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/shrug.lua b/plugins/shrug.lua new file mode 100644 index 0000000..c47ed56 --- /dev/null +++ b/plugins/shrug.lua @@ -0,0 +1,12 @@ +local PLUGIN = {} + +PLUGIN.triggers = { + '^!shrug', + '/shrug' +} + +function PLUGIN.action(msg) + send_msg(msg, '¯\\_(ツ)_/¯') +end + +return PLUGIN diff --git a/plugins/slap.lua b/plugins/slap.lua new file mode 100644 index 0000000..2325deb --- /dev/null +++ b/plugins/slap.lua @@ -0,0 +1,116 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !slap [victim] + Slap someone! +]] + +PLUGIN.triggers = { + '^!slap' +} + +function PLUGIN.getSlap(slapper, victim) + slaps = { + victim .. " was shot by " .. slapper .. ".", + victim .. " was pricked to death.", + victim .. " walked into a cactus while trying to escape " .. slapper .. ".", + victim .. " drowned.", + victim .. " drowned whilst trying to escape " .. slapper .. ".", + victim .. " blew up.", + victim .. " was blown up by " .. slapper .. ".", + 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 " .. slapper .. ".", + victim .. " was blown from a high place by " .. slapper .. ".", + victim .. " was squashed by a falling anvil.", + victim .. " went up in flames.", + victim .. " burned to death.", + victim .. " was burnt to a crisp whilst fighting " .. slapper .. ".", + victim .. " walked into a fire whilst fighting " .. slapper .. ".", + victim .. " tried to swim in lava.", + victim .. " tried to swim in lava while trying to escape " .. slapper .. ".", + victim .. " was struck by lightning.", + victim .. " was slain by " .. slapper .. ".", + victim .. " got finished off by " .. slapper .. ".", + victim .. " was killed by magic.", + victim .. " was killed by " .. slapper .. " using magic.", + victim .. " starved to death.", + victim .. " suffocated in a wall.", + victim .. " fell out of the world.", + victim .. " was knocked into the void by " .. slapper .. ".", + victim .. " withered away.", + victim .. " was pummeled by " .. slapper .. ".", + victim .. " was fragged by " .. slapper .. ".", + victim .. " was desynchronized.", + victim .. " was wasted.", + victim .. " was busted by " .. slapper .. ".", + 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 " .. slapper .. ".", + victim .. " died. I blame " .. slapper .. ".", + victim .. " was axe-murdered by " .. slapper .. ".", + victim .. "'s melon was split by " .. slapper .. ".", + victim .. " was slice and diced by " .. slapper .. ".", + victim .. " was split from crotch to sternum by " .. slapper .. ".", + victim .. "'s death put another notch in " .. slapper .. "'s axe.", + victim .. " died impossibly!", + victim .. " died from " .. slapper .. "'s mysterious tropical disease.", + victim .. " escaped infection by dying.", + victim .. " played hot-potato with a grenade.", + victim .. " was knifed by " .. slapper .. ".", + victim .. " fell on his sword.", + victim .. " ate a grenade.", + victim .. " practiced being " .. slapper .. "'s clay pigeon.", + victim .. " is what's for dinner!", + victim .. " was terminated by " .. slapper .. ".", + 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.", + slapper .. " threw " .. victim .. " off a building.", + victim .. " is sleeping with the fishes.", + victim .. " got a premature burial.", + slapper .. " replaced all of " .. victim .. "'s music with Nickelback.", + slapper .. " spammed " .. victim .. "'s email.", + slapper .. " made " .. victim .. " a knuckle sandwich.", + slapper .. " slapped " .. victim .. " with pure nothing.", + slapper .. " hit " .. victim .. " with a small, interstellar spaceship.", + victim .. " was quickscoped by " .. slapper .. ".", + slapper .. " put " .. victim .. " in check-mate.", + slapper .. " encrypted " .. victim .. " and deleted the private key.", + slapper .. " put " .. victim .. " in the friendzone.", + slapper .. " slaps " .. victim .. " with a DMCA takedown request!", + victim .. " became a corpse blanket for " .. slapper .. ".", + "Death is when the monsters get you. Death comes for " .. victim .. ".", + "Cowards die many times before their death. " .. victim .. " never tasted death but once." + } + return slaps[math.random(#slaps)] +end + +function PLUGIN.action(msg) + + math.randomseed(os.time()) + + local victim = get_input(msg.text) + if victim then + slapper = msg.from.first_name + else + victim = msg.from.first_name + slapper = 'otouto' + end + + local message = PLUGIN.getSlap(slapper, victim) + send_message(target, message) + +end + +return PLUGIN diff --git a/plugins/time.lua b/plugins/time.lua new file mode 100644 index 0000000..524ce18 --- /dev/null +++ b/plugins/time.lua @@ -0,0 +1,41 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !time + Sends the time and timezone for a given location. +]] + +PLUGIN.triggers = { + '^!time' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + coords = get_coords(input) + if not coords then + local message = 'Error: \"' .. input .. '\" not found.' + return send_msg(msg, message) + end + + local url = 'http://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '×tamp='..os.time() + local jstr, res = HTTPS.request(url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + local jdat = JSON.decode(jstr) + + local timestamp = os.time() + jdat.rawOffset + jdat.dstOffset + config.TIME_OFFSET + timestamp = os.date("%H:%M on %A, %B %d.", timestamp) + local timeloc = (string.gsub((string.sub(jdat.timeZoneId, string.find(jdat.timeZoneId, '/')+1)), '_', ' ')) + local message = "The time in " .. timeloc .. " is " .. timestamp + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/urbandictionary.lua b/plugins/urbandictionary.lua new file mode 100644 index 0000000..d440daf --- /dev/null +++ b/plugins/urbandictionary.lua @@ -0,0 +1,42 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !ud + Returns the first definition for a given term from Urban Dictionary. +]] + +PLUGIN.triggers = { + '^!ud', + '^!urbandictionary' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + local url = 'http://api.urbandictionary.com/v0/define?term=' .. URL.escape(input) + local jstr, res = HTTP.request(url) + + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + + local jdat = JSON.decode(jstr) + + if jdat.result_type == "no_results" then + return send_msg(msg, 'No results found.') + end + + message = '"' .. jdat.list[1].word .. '"\n' .. trim_string(jdat.list[1].definition) + if string.len(jdat.list[1].example) > 0 then + message = message .. '\n\nExample:\n' .. trim_string(jdat.list[1].example) + end + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/weather.lua b/plugins/weather.lua new file mode 100644 index 0000000..6a5d0ef --- /dev/null +++ b/plugins/weather.lua @@ -0,0 +1,41 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !weather + Returns the current temperature and weather conditions for a specified location. + Non-city locations are accepted; "!weather Buckingham Palace" will return the weather for Westminster. +]] + +PLUGIN.triggers = { + '^!weather' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + if not input then + return send_msg(msg, PLUGIN.doc) + end + + coords = get_coords(input) + if not coords then + local message = 'Error: \"' .. input .. '\" not found.' + return send_msg(msg, message) + end + + local url = 'http://api.openweathermap.org/data/2.5/weather?lat=' .. coords.lat .. '&lon=' .. coords.lon + local jstr, res = HTTP.request(url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + local jdat = JSON.decode(jstr) + + local celsius = jdat.main.temp - 273.15 + local fahrenheit = tonumber(string.format("%.2f", celsius * (9/5) + 32)) + local message = jdat.name .. ': ' .. celsius .. '°C / ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.' + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/whoami.lua b/plugins/whoami.lua new file mode 100644 index 0000000..010fdfd --- /dev/null +++ b/plugins/whoami.lua @@ -0,0 +1,37 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !whoami + Get the user ID for yourself and the group. +]] + +PLUGIN.triggers = { + '^!whoami', + '^!ping', + '^/ping' +} + +function PLUGIN.action(msg) + + local from_name = msg.from.first_name + if msg.from.last_name then + from_name = from_name .. ' ' .. msg.from.last_name + end + if msg.from.username then + from_name = '@' .. msg.from.username .. ', AKA ' .. from_name + end + from_name = from_name .. ' (' .. msg.from.id .. ')' + + if msg.from.id == msg.chat.id then + to_name = '@' .. bot.username .. ' (' .. bot.id .. ')' + else + to_name = string.gsub(msg.chat.title, '_', ' ') .. ' (' .. string.gsub(msg.chat.id, '-', '') .. ')' + end + + local message = 'You are ' .. from_name .. ' and you are messaging ' .. to_name .. '.' + + send_msg(msg, message) + +end + +return PLUGIN diff --git a/plugins/xkcd.lua b/plugins/xkcd.lua new file mode 100644 index 0000000..445bdbd --- /dev/null +++ b/plugins/xkcd.lua @@ -0,0 +1,47 @@ +local PLUGIN = {} + +PLUGIN.doc = [[ + !xkcd [search] + This command returns an xkcd strip, its number, and its "secret" text. You may search for a specific strip or get a random one. +]] + +PLUGIN.triggers = { + '^!xkcd' +} + +function PLUGIN.action(msg) + + local input = get_input(msg.text) + local url = 'http://xkcd.com/info.0.json' + local jstr, res = HTTP.request(url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + local latest = JSON.decode(jstr).num + + if input then + url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&safe=active&q=site%3axkcd%2ecom%20' .. URL.escape(input) + local jstr, res = HTTP.request(url) + if res ~= 200 then + print('here') + return send_msg(msg, 'Connection error.') + end + url = JSON.decode(jstr).responseData.results[1].url .. 'info.0.json' + else + math.randomseed(os.time()) + url = 'http://xkcd.com/' .. math.random(latest) .. '/info.0.json' + end + + local jstr, res = HTTP.request(url) + if res ~= 200 then + return send_msg(msg, 'Connection error.') + end + local jdat = JSON.decode(jstr) + + local message = '[' .. jdat.num .. '] ' .. jdat.alt .. '\n' .. jdat.img + + send_message(msg.chat.id, message, false, msg.message_id) + +end + +return PLUGIN diff --git a/utilities.lua b/utilities.lua new file mode 100644 index 0000000..53e2223 --- /dev/null +++ b/utilities.lua @@ -0,0 +1,84 @@ +-- utilities.lua +-- Functions shared among plugins. + +function first_word(str, idx) -- get the indexed word in a string + str = string.gsub(str, '\n', ' ') + if not string.find(str, ' ') then return str end + str = str .. ' ' + if not idx then idx = 1 end + if idx ~= 1 then + for i = 2, idx do + str = string.sub(str, string.find(str, ' ') + 1) + end + end + str = string.sub(str, 1, string.find(str, ' ')) + return string.sub(str, 1, -2) +end + +function get_input(text) -- returns string or false + if not string.find(text, ' ') then + return false + end + return string.sub(text, string.find(text, ' ')+1) +end + +function trim_string(text) -- another alias + return string.gsub(text, "^%s*(.-)%s*$", "%1") +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'] = 'у', + ['!'] = 'ǃ' +} + +function latcyr(str) + for k,v in pairs(lc_list) do + str = string.gsub(str, k, v) + end + return str +end + +function send_msg(msg, message) + send_message(msg.chat.id, message, true, msg.message_id) +end + +function get_coords(input) + + local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) + local jstr, res = HTTP.request(url) + if res ~= 200 then + return false + end + + local jdat = JSON.decode(jstr) + if jdat.status == 'ZERO_RESULTS' then + return false + end + + return { lat = jdat.results[1].geometry.location.lat, lon = jdat.results[1].geometry.location.lng } + +end