diff --git a/.gitignore b/.gitignore deleted file mode 100755 index 6f5a830..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -config.lua -loc/weeb.lua -*.json diff --git a/README.md b/README.md index 2cbc73c..9b81f7c 100755 --- a/README.md +++ b/README.md @@ -1,306 +1,372 @@ -# otouto +#otouto +The plugin-wielding, multipurpose Telegram bot. -The plugin-wielding, multi-purpose Telegram bot. +The public bot runs on [@mokubot](https://telegram.me/otouto). -Public bot runs on [@mokubot](http://telegram.me/mokubot). +otouto is licensed under the GNU General Public License. A copy of the license has been included in [LICENSE](https://github.com/topkecleon/otouto/blob/master/LICENSE). -To start, send "/start" or say "Hello, otouto." +##What is it? +otouto is an independently-developed Telegram API bot written in Lua. otouto was created in February 2015, open-sourced in June, and is being augmented to this day. +Bot commands and functions use a comprehensive plugin system, similar to that of (yagop's telegram-bot)[github.com/yagop/telegram-bot]. The aim of the project is to host every desirable feature in one bot. ##Plugins +Below are listed many (but not all) of otouto's plugins. This list will be updated as more plugins are added. + +>**echo.lua** + +>**Command:** /echo <text> + +>**Function:** Repeats a string of text. + +>**Notes:** Replaces letters with corresponding characters from the Cyrillic alphabet. + +**gSearch.lua** + +>**Command:** /google [query] + +>**Function:** Returns four or eight Google results, depending on whether it is run in a group chat or a private message. + +>**Aliases:** /g, /gnsfw, /googlensfw + +>**Notes:** If "nsfw" is appended to the command, Safe Search will not be used. + +**gImages.lua** + +>**Command:** /images [query] + +>**Function:** Returns a random top result from Google Image. + +>**Aliases:** /i, /gimages, /gnsfw + +>**Notes:** If "nsfw" is appended to the command, Safe Search and image preview will not be used. + +**gMaps.lua** + +>**Command:** /location [query] + +>**Function:** Returns location data from Google Maps. + +>**Aliases:** /loc + +**translate.lua** + +>**Command:** /translate [text] + +>**Function:** Translates the replied-to message or the given string to the configured language. + +**youtube.lua** + +>**Command:** /youtube [query] + +>**Function:** Returns the top video result from YouTube. + +>**Aliases:** /yt + +**wikipedia.lua** + +>**Command:** /wikipedia [query] + +>**Function:** Returns the top paragraph of and the link to a Wikipedia article. + +>**Aliases:** /wiki + +**lastfm.lua** + +>**Command:** /lastfm + +>**Function**: Returns help for the last.fm actions. + +>**Actions**: + +>>**/np** [username] + +>>Returns the current- or last-played song for the given username. If no username is given, it will use your configured last.fm username or your Telegram username. +>>**/fmset** <username> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
about.lua/aboutInformation about the bot
admin.lua/admin [command]Support for admin
bandersnatch.lua/bandersnatchBenedict Cumberbatch name generator
bible.lua/bible <verse>King James Version
blacklist.lua/blacklistBlacklist
btc.lua/btc <currency> [amount]Bitcoin prices and conversion
calc.lua/calc <expression>Calculator
cats.lua/calc <expression>Solve math expression and convert units
chatter.lua@[user_name_bot], [message]Chatter Bot
commit.lua/commithttp://whatthecommit.com
currency.lua/cash <from> <to> [amount]Convert an amount from one currency to another
dgmp.lua/index and /listgroupsPlugin to display the groups, descriptions, etc.
dice.lua/roll [arg]Roll a die. Accepts D&D notation
dogify.lua/dogify <lines/separatedby/slashes>Create a doge image
echo.lua/echo <text>Repeat a string
floodcontrol.luaNo commandFlood control
fortune.lua/fortuneRandom fortunes
gImages.lua/images <query>Google Images search
giphy.lua/giphy [query]Giphy search or random
gMaps.lua/loc <location>Google Maps search
gSearch.lua/google <query>Google Search
hackernews.lua/hackernewsTop stories from Hackernews
hearthstone.lua/hearthstone <card>Information about a Hearthstone card
help.lua/help [command]List commands
hex.lua/hex <number>Convert to and from hexadecimal
imdb.lua/imdb <movie | TV series>IMDb movie/television info
interactions.luaNo commandWelcome
kickass.lua/torrent <query>Search Kickass Torrents
lastfm.lua/lastfm [username]Get current- or last-played track
lmgtfy.lua/lmgtfyOpen page of lmgtfy
moderation.lua/modhelpSupport for moderator or admin
nick.lua/nick <nickname>Set your nickname for the bot
owm.lua/weather <location>Temperature and weather conditions
pokedex.lua/dex <pokemon>Pokedex!
pun.lua/punPuns
reaction.lua/reactionsGet a list emoticons
reddit.lua/reddit [r/subreddit | query]Posts from reddit
remind.lua/remind <delay> <message>Set a reminder for yourself or a group
slap.lua/slap [victim]Slap someone!
spotify.lua/spotify <music>Search Spotify
time.lua/time <location>Get the time for a place
translate.lua/translate [target lang]Message to translate
urbandictionary.lua/ud <term>Urban Dictionary search
weather.lua/weather <location>Get the weather for a place
whoami.lua/whoGet user and group IDs
wikipedia.lua/wiki <topic>Search Wikipedia
xkcd.lua/xkcd [search]Xkcd strips and alt text
youtube.lua/youtube <query>Search Youtube
8ball.lua/8ballMagic 8-ball.
+>>Sets your last.fm username. Use /fmset - to delete it. +**hackernews.lua** +>**Command:** /hackernews + +>**Function:** Returns the top four or eight headlines on Hacker News, depending on whether it is run in a group chat or private message. + +>**Aliases:** /hn + +**imdb.lua** + +>**Command:** /imdb <query> + +>**Function:** Returns movie information from IMDb. + +**calc.lua** + +>**Command:** /calc <expression> + +>**Function:** Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org. + +**bible.lua** + +>**Command:** /bible <reference> + +>**Function:** Returns a Bible verse. Results provided by biblia.com + +>**Aliases:** /b + +**urbandictionary.lua** + +>**Command:** /urbandictionary <query> + +>**Function:** Returns the top definition from Urban Dictionary. + +>**Aliases:** /ud, /urban + +**time.lua** + +>**Command:** /time <query> + +>**Function:** Returns the time, date, and timezone for a given location. + +**weather.lua** + +>**Command:** /weather <query> + +>**Function:** Returns the current weather conditions for a given location. + +**nick.lua** + +>**Command:** /nick <nickname> + +>**Function:** Set your nickname. Use "/nick -" to delete it. + +**whoami.lua** + +>**Command:** /whoami + +>**Function:** Returns user and chat info for your or the replied-to message. + +**8ball.lua** + +>**Command:** /8ball + +>**Function:** Returns an answer from a magic 8-ball. + +**dice.lua** + +>**Command:** /roll <nDr> + +>**Function:** Returns RNG dice rolls. Uses D&D notation. + +>**Examples:** + +>>/roll 4D20 + +>>/roll 6 + +**reddit.lua** + +>**Command:** /reddit [r/subreddit | query] + +>**Function:** Returns the top four or eight results, depending on whether it is run in a group chat or private message, from a given subreddit, query, or r/all. + +>**Aliases:** /r + +>**Notes:** You may also get results for a subreddit by entering "/r/subreddit". + +>**Examples:** + +>> /reddit zelda + +>> /reddit r/gaming + +>>/r/talesfromtechsupport + +**xkcd.lua** + +>**Command:** /xkcd [query] + +>**Function:** Returns an xkcd strip and it's alt text. If not query is given, it will use a random strip. + +**slap.lua** + +>**Command:** /slap <target> + +>**Function:** Give someone a good slap (or worse). + +**commit.lua** + +>**Command:** /commit + +>**Function:** Returns a commit message from whatthecommit.com. + +**fortune.lua** + +>**Command:** /fortune + +>**Function:** Returns a UNIX fortune. + +**pun.lua** + +>**Command:** /pun + +>**Function:** Returns a pun. + +**pokedex.lua** + +>**Command:** /pokedex <query> + +>**Function:** Returns a Pokedex entry. + +>**Aliases:** /dex + +**currency.lua** + +>**Command:** /cash [amount] <from> to <from> + +>**Function:** Converts an amount of one currency to another. + +>**Examples:** + +>>/cash 5 USD to EUR + +>>/cash BTC to GBP + +**cats.lua** + +>**Command:** /cat + +>**Function:** Returns a cat pic. + +**admin.lua** + +>**Command:** /admin [command] + +>**Function:** Runs an admin command or returns a list of them. + +>**Notes:** Only usable by the configured admin. + +**blacklist.lua** + +>**Command:** /blacklist [id] + +>**Function:** Blacklists or unblacklists the specified ID or replied-to user. + +>**Notes:** Only usable by the configured admin. + +##Liberbot Plugins +Some plugins are only useful when the bot is used in a Liberbot group: moderation.lua, antisquig.lua, floodcontrol.lua. + +floodcontrol.lua makes the bot compliant to Liberbot's bot floodcontrol. It should prevent your bot from being globally banned from Liberbot groups. + +moderation.lua allows realm administrators to assign moderators for a group. This only works if the bot is made a realm administrator. + +You must configure this plugin in the "moderation" section of config.lua, in the following way: + +``` +moderation = { + admins = { + ['123456789'] = 'Adam', + ['1337420'] = 'Eve' + }, + admin_group = -8675309, + realm_name = 'My Realm' +} +``` + +Where Adam and Eve are realm administrators, and their IDs are set as the keys in the form of strings. admin_group is the ID, a negative number, of the realm administration group. realm_name is the name as a string. + +Once this is set up, put the bot in the admin group and run /add and /modlist to get started. + +antisquig.lua is an extension to moderation.lua. It will automatically kick a user who posts Arabic script. + +##Other Plugins +There are other plugins not listed above: help.lua, about.lua, chatter.lua, greetings.lua. + +help.lua is self-explanatory. When the plugin loads, it compiles a list of commands, and will return them. + +about.lua returns the content of the about_text string in config.lua. + +chatter.lua will let the user interact with a chatterbot, if he replies to a message sent by the bot. + +greetings.lua is where things get tricky. It allows the bot to respond to several greetings with a preconfigured response. This is configured in the "greetings" section of config.lua: + +``` +greetings = { + ['Hello, #NAME.'] = { + 'hello', + 'hey', + 'hi' + }, + ['Goodbye, #NAME.'] = { + 'goodbye', + 'bye', + } +} +``` + +Where the key is the preconfigured response (where #NAME will be replaced with the user's name or nickname) and the strings in the table are the expected greetings (followed by the bot's name and possible punctuation). ##Setup +You **must** have lua-socket and lua-sec installed. For uploading photos and other files, you must have curl installed. The fortune.lua plugin requires that fortune is installed. -Requires Lua, lua-socket and lua-sec. [dkjson](http://github.com/LuaDist/dkjson/) is provided. Written for Lua 5.2 but will probably run on 5.3. +For weather.lua, lastfm.lua, and bible.lua to work, you must have API keys for openweathermap.org, last.fm, and biblia.com, respectively. cats.lua uses an API key to get more results, though it is not required. -You must have a Telegram bot and auth token from the [BotFather](http://telegram.me/botfather) to run this bot. telegram-cli is not required. +>**Before you do anything, edit config.lua and make the following changes:** -###Configuration +>* Edit bot_api_key with your authentication token from Botfather. +>* Set admin as your Telegram ID as a number. -To begin, copy config.lua.default to config.lua and add the relevant information. +You may also want to set your time_offset (a positive or negative number, in seconds, representing your computer's difference from UTC), your lang (lowercase, two-letter code representing your language), and modify your about_text. Some plugins will not be enabled by default, as they are for specific uses. If you want to use them, add them to the plugins table. -Most config.lua entries are self-explanatory. +To start the bot, run -Add your bot API key, and other API keys if desirable. -The plugins which require API keys that are not provided are disabled by default. -The provided Giphy key is the public test key, and is subject to rate limitaton. +`./launch.sh` -The "fortune.lua" plugin requires the fortune program to be installed on the host computer. +To stop the bot, press Ctrl+C twice. -"time_offset" is the time difference, in seconds, between your system clock. It is sometimes necessary for accurate output of the time plugin. - -"admins" table includes the ID numbers, as integers, of any privileged users. These will have access to the admin plugin and any addition privileged commands. - -"people" table is for the personality plugin: -`["55994550"] = "topkecleon"` - -ID number must be a string. The second string is the nickname to be given to the identified user when a personality greeting is triggered. - -To run: +You may also start the bot manually with `lua bot.lua` +though that will not cause it to automatically restart. ##Support +Do not contact me through private messages for support. -Do not private message me for support. +For otouto, bot, and other Lua support in general, join the Bot Development group. Send "/join 16314802" to [@Liberbot](https://telegram.me/liberbot). If this does not work the first time, you may need to send it up to seven more times, thanks to Telegram's automatic spam-prevention mechanism. -For support for otouto as well as general Lua and bot assistance, please join the Bot Development group (send "/join 16314802" to [@Liberbot](http://telegram.me/liberbot). After you read the rules and the pastebin, I will assist you there. +##Development +Everyone is free to contribute to otouto. If you would like to write a plugin, here I will lay out various things that are important to know about the plugin system. -###PS +Every plugin has four components, and half of them are optional: action, triggers, doc, cron. -Since there seems to be some confusion on the matter, otouto is **not** a port of yagop's telegram-bot. I am friends with yagop, and he is part of the Bot Development group, but our codebases are and always have been entirely separate. otouto was a CLI bot like telegram-bot before the new API, but they were entirely separate, non-intermingled projects. +triggers is a table of strings using Lua patterns which, when matched by a message's text, will "trigger" the action function. This is not optional. + +action is the main function of a plugin. It accepts the "msg" table, which is all the components of the message, as an argument. This is not optional. + +doc is the documentation. The first line is the expected command and arguments. Arguments in square braces are considered optional and those in angled braces are considered required. This is optional. + +cron is a function run every five seconds. This is optional. + +The on_msg_receive function adds a few variables to the "msg" table: msg.from.id_str, msg.to.id_str, msg.text_lower. These are self-explanatory and make code a lot neater. + +Return values from the action function are optional, but when they are used, they determine the fate of the message. When false/nil is returned, on_msg_receive stops and the script moves on to waiting for the next message. When true is returned, on_msg_receive continues going through the plugins for a match. When a table is returned, that table becomes the "msg" table, and on_msg_receive continues. + +When a plugin action or cron function fails, the script will catch the error and print it, and, if applicable, the text which triggered the plugin, and continue. + +---- + +Interactions with the Telegram bot API are straightforward. Every function is named the same as the API method it utilizes. The order of expected arguments is laid out in bindings.lua. + +There are three functions which are not API methods: sendRequest, curlRequest, and sendReply. The first two are used by the other functions. sendReply is used directly. It expects the "msg" table as its first argument, and a string of text as its second. It will send a reply without image preview to the initial message. + +---- + +Several functions and methods used by multiple plugins and possibly the main script are kept in utilities.lua. Refer to that file for documentation. + +---- + +otouto uses dkjson, a pure-Lua JSON parser. This is provided with the code and does not need to be downloaded or installed separately. diff --git a/bindings.lua b/bindings.lua index e24646a..2417428 100755 --- a/bindings.lua +++ b/bindings.lua @@ -1,51 +1,48 @@ --- 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 BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' +if not config.bot_api_key then + error('You did not set your bot token in config.lua!') +end -local function send_request(url) +sendRequest = function(url) local dat, res = HTTPS.request(url) local tab = JSON.decode(dat) if res ~= 200 then - print('Connection error.') - return false + return false, res end if not tab.ok then - print(tab.description) - return false + return false, tab.description end return tab end -function get_me() +getMe = function() - local url = BASE_URL .. 'getMe' - return send_request(url) + local url = BASE_URL .. '/getMe' + return sendRequest(url) end -function get_updates(offset) +getUpdates = function(offset) - local url = BASE_URL .. 'getUpdates?timeout=30' + local url = BASE_URL .. '/getUpdates?timeout=20' if offset then url = url .. '&offset=' .. offset end - return send_request(url) + return sendRequest(url) end -function send_message(chat_id, text, disable_web_page_preview, reply_to_message_id) +sendMessage = function(chat_id, text, disable_web_page_preview, reply_to_message_id) - local url = BASE_URL .. 'sendMessage?chat_id=' .. chat_id .. '&text=' .. URL.escape(text) + 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' @@ -55,33 +52,165 @@ function send_message(chat_id, text, disable_web_page_preview, reply_to_message_ url = url .. '&reply_to_message_id=' .. reply_to_message_id end - return send_request(url) + return sendRequest(url) end -function send_chat_action(chat_id, action) +sendReply = function(msg, text) - local url = BASE_URL .. 'sendChatAction?chat_id=' .. chat_id .. '&action=' .. action - return send_request(url) + return sendMessage(msg.chat.id, text, true, msg.message_id) end -function send_location(chat_id, latitude, longitude, reply_to_message_id) +sendChatAction = function(chat_id, action) + -- Support actions are typing, upload_photo, record_video, upload_video, record_audio, upload_audio, upload_document, find_location - local url = BASE_URL .. 'sendLocation?chat_id=' .. chat_id .. '&latitude=' .. latitude .. '&longitude=' .. longitude + local url = BASE_URL .. '/sendChatAction?chat_id=' .. chat_id .. '&action=' .. action + return sendRequest(url) + +end + +sendLocation = function(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) + return sendRequest(url) end -function forward_message(chat_id, from_chat_id, message_id) +forwardMessage = function(chat_id, from_chat_id, message_id) - local url = BASE_URL .. 'forwardMessage?chat_id=' .. chat_id .. '&from_chat_id=' .. from_chat_id .. '&message_id=' .. message_id + local url = BASE_URL .. '/forwardMessage?chat_id=' .. chat_id .. '&from_chat_id=' .. from_chat_id .. '&message_id=' .. message_id - return send_request(url) + return sendRequest(url) + +end + +curlRequest = function(curl_command) + + local dat = io.popen(curl_command):read('*all') + local tab = JSON.decode(dat) + + if not tab.ok then + return false, tab.description + end + + return tab + +end + +sendPhoto = function(chat_id, photo, caption, reply_to_message_id) + + local url = BASE_URL .. '/sendPhoto' + + local curl_command = 'curl "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "photo=@' .. photo .. '"' + + if reply_to_message_id then + curl_command = curl_command .. ' -F "reply_to_message_id=' .. reply_to_message_id .. '"' + end + + if caption then + curl_command = curl_command .. ' -F "caption=' .. caption .. '"' + end + + return curlRequest(curl_command) + +end + +sendDocument = function(chat_id, document, reply_to_message_id) + + local url = BASE_URL .. '/sendDocument' + + local curl_command = 'curl "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "document=@' .. document .. '"' + + if reply_to_message_id then + curl_command = curl_command .. ' -F "reply_to_message_id=' .. reply_to_message_id .. '"' + end + + return curlRequest(curl_command) + +end + +sendSticker = function(chat_id, sticker, reply_to_message_id) + + local url = BASE_URL .. '/sendSticker' + + local curl_command = 'curl "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "sticker=@' .. sticker .. '"' + + if reply_to_message_id then + curl_command = curl_command .. ' -F "reply_to_message_id=' .. reply_to_message_id .. '"' + end + + return curlRequest(curl_command) + +end + +sendAudio = function(chat_id, audio, reply_to_message_id, duration, performer, title) + + local url = BASE_URL .. '/sendAudio' + + local curl_command = 'curl "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "audio=@' .. audio .. '"' + + if reply_to_message_id then + curl_command = curl_command .. ' -F "reply_to_message_id=' .. reply_to_message_id .. '"' + end + + if duration then + curl_command = curl_command .. ' -F "duration=' .. duration .. '"' + end + + if performer then + curl_command = curl_command .. ' -F "performer=' .. performer .. '"' + end + + if title then + curl_command = curl_command .. ' -F "title=' .. title .. '"' + end + + return curlRequest(curl_command) + +end + +sendVideo = function(chat_id, video, reply_to_message_id, duration, performer, title) + + local url = BASE_URL .. '/sendVideo' + + local curl_command = 'curl "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "video=@' .. video .. '"' + + if reply_to_message_id then + curl_command = curl_command .. ' -F "reply_to_message_id=' .. reply_to_message_id .. '"' + end + + if caption then + curl_command = curl_command .. ' -F "caption=' .. caption .. '"' + end + + if duration then + curl_command = curl_command .. ' -F "duration=' .. duration .. '"' + end + + return curlRequest(curl_command) + +end + +sendVoice = function(chat_id, voice, reply_to_message_id) + + local url = BASE_URL .. '/sendVoice' + + local curl_command = 'curl "' .. url .. '" -F "chat_id=' .. chat_id .. '" -F "voice=@' .. voice .. '"' + + if reply_to_message_id then + curl_command = curl_command .. ' -F "reply_to_message_id=' .. reply_to_message_id .. '"' + end + + if duration then + curl_command = curl_command .. ' -F "duration=' .. duration .. '"' + end + + return curlRequest(curl_command) end diff --git a/bot.lua b/bot.lua index aa2aebd..2fbdc6d 100755 --- a/bot.lua +++ b/bot.lua @@ -1,115 +1,95 @@ HTTP = require('socket.http') -HTTPS= require('ssl.https') -URL = require('socket.url') +HTTPS = require('ssl.https') +URL = require('socket.url') JSON = require('dkjson') -VERSION = '2.11' +version = '3.0.1' -function on_msg_receive(msg) +bot_init = function() -- The function run when the bot is started or reloaded. - if blacklist[tostring(msg.from.id)] then return end - if floodcontrol[-msg.chat.id] then -- This stuff is useful for the moderation plugin to not be completely unusable when floodcontrol is activated. - msg.flood = msg.chat.id - msg.chat.id = msg.from.id - end + config = dofile("config.lua") -- Load configuration file. + dofile("bindings.lua") -- Load Telegram bindings. + dofile("utilities.lua") -- Load miscellaneous and cross-plugin functions. - if msg.new_chat_participant and msg.new_chat_participant.id == bot.id then - msg.text = '/about' - end -- If bot is added to a group, send the about message. - - if msg.date < os.time() - 10 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 - if v.typing then - send_chat_action(msg.chat.id, 'typing') - end - local a,b = pcall(function() -- Janky error handling - v.action(msg) - end) - if not a then - print('',msg.text,'\n',b) -- debugging - send_msg(msg, b) - end - end - end - end -end - -function bot_init() - - print('Loading configuration...') - - config = dofile('config.lua') - require('bindings') - require('utilities') - blacklist = load_data('blacklist.json') - - print('Fetching bot information...') - - bot = get_me() - while bot == false do - print('Failure fetching bot information. Trying again...') - bot = get_me() + bot = nil + while not bot do -- Get bot info and retry if unable to connect. + bot = getMe() end bot = bot.result - print('Loading plugins...') - - plugins = {} + plugins = {} -- Load plugins. for i,v in ipairs(config.plugins) do - local p = dofile('plugins/'..v) + local p = dofile("plugins/"..v) table.insert(plugins, p) end - print('Plugins loaded: ' .. #plugins .. '. Generating help message...') + print('@'..bot.username .. ', AKA ' .. bot.first_name ..' ('..bot.id..')') - help_message = '' - for i,v in ipairs(plugins) do - if v.doc then - local a = string.sub(v.doc, 1, string.find(v.doc, '\n')-1) - help_message = help_message .. ' - ' .. a .. '\n' - end - end + -- Generate a random seed and "pop" the first random number. :) + math.randomseed(os.time()) + math.random() - print('@'.. bot.username ..', AKA '.. bot.first_name ..' ('.. bot.id ..')') - - is_started = true + last_update = last_update or 0 -- Set loop variables: Update offset, + last_cron = last_cron or os.time() -- the time of the last cron job, + is_started = true -- whether the bot should be running or not. end -bot_init() -last_update = 0 -last_cron = os.time() +on_msg_receive = function(msg) -- The fn run whenever a message is received. -while is_started do + if not msg.text then msg.text = '' end -- So about.lua works. + if msg.date < os.time() - 5 then return end -- Do not process old messages. - local res = get_updates(last_update+1) - if not res then - print('Error getting updates.') - else - for i,v in ipairs(res.result) do - if v.update_id > last_update then - last_update = v.update_id - on_msg_receive(v.message) + msg.chat.id_str = tostring(msg.chat.id) + msg.from.id_str = tostring(msg.from.id) + msg.text_lower = msg.text:lower() + + for i,v in ipairs(plugins) do + for k,w in pairs(v.triggers) do + if string.match(msg.text_lower, w) then + local success, result = pcall(function() + return v.action(msg) + end) + if not success then + sendReply(msg, 'An unexpected error occurred.') + print(msg.text, result) + return + end + -- If the action returns a table, make that table msg. + if type(result) == 'table' then + msg = result + -- If the action returns true, don't stop. + elseif result ~= true then + return + end end end end - -- cron-like thing - -- run PLUGIN.cron() every five seconds - if last_cron < os.time() - 5 then - for k,v in pairs(plugins) do - if v.cron then - a,b = pcall(function() v.cron() end) - if not a then print(b) end +end + +bot_init() -- Actually start the script. Run the bot_init function. + +while is_started do -- Start a loop while the bot should be running. + + local res = getUpdates(last_update+1) -- Get the latest updates! + if res then + for i,v in ipairs(res.result) do -- Go through every new damned message. + last_update = v.update_id + on_msg_receive(v.message) + end + else + print(config.errors.connection) + end + + if last_cron < os.time() - 5 then -- Run cron jobs if the time has come. + for i,v in ipairs(plugins) do + if v.cron then -- Call each plugin's cron function, if it has one. + local res, err = pcall(function() v.cron() end) + if not res then print('ERROR: '..err) end end end - last_cron = os.time() + last_cron = os.time() -- And finally, update the variable. end end diff --git a/config.lua b/config.lua new file mode 100755 index 0000000..cb6bf35 --- /dev/null +++ b/config.lua @@ -0,0 +1,97 @@ +return { + bot_api_key = '', + lastfm_api_key = '', + owm_api_key = '', + biblia_api_key = '', + thecatapi_key = '', + time_offset = 0, + lang = 'en', + admin = 00000000, + about_text = [[ +I am otouto, the plugin-wielding, multi-purpose Telegram bot written by topkecleon. + +Send /help to get started. + +Join my channel for news about updates! +telegram.me/otouto +]] , + errors = { + connection = 'Connection error.', + results = 'No results found.', + argument = 'Invalid argument.', + syntax = 'Invalid syntax.', + antisquig = 'This group is English only.', + moderation = 'I do not moderate this group.', + not_mod = 'This command must be run by a moderator.', + not_admin = 'This command must be run by an administrator.', + chatter_connection = 'I don\'t feel like talking right now.', + chatter_response = 'I don\'t know what to say to that.' + }, + greetings = { + ['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' + } + }, + moderation = { + admins = { + ['00000000'] = 'You' + }, + admin_group = -00000000, + realm_name = 'My Realm' + }, + plugins = { + 'blacklist.lua', + 'floodcontrol.lua', + 'admin.lua', + 'about.lua', + 'whoami.lua', + 'nick.lua', + 'echo.lua', + 'gSearch.lua', + 'gImages.lua', + 'gMaps.lua', + 'youtube.lua', + 'wikipedia.lua', + 'hackernews.lua', + 'imdb.lua', + 'calc.lua', + 'urbandictionary.lua', + 'time.lua', + 'eightball.lua', + 'reactions.lua', + 'dice.lua', + 'reddit.lua', + 'xkcd.lua', + 'slap.lua', + 'commit.lua', + 'pun.lua', + 'pokedex.lua', + 'bandersnatch.lua', + 'currency.lua', + 'cats.lua', + + 'help.lua', + 'greetings.lua' + } +} diff --git a/config.lua.default b/config.lua.default deleted file mode 100755 index 52f924a..0000000 --- a/config.lua.default +++ /dev/null @@ -1,63 +0,0 @@ -return { - bot_api_key = '', - lastfm_api_key = '', - biblia_api_key = '', - thecatapi_key = '', - giphy_api_key = 'dc6zaTOxFJmzC', - time_offset = 0, - locale = dofile('loc/en.lua'), - admins = { - [000] = 'name' - }, - plugins = { - 'about.lua', - 'help.lua', - 'admin.lua', - 'gSearch.lua', - 'gImages.lua', - 'reddit.lua', - 'giphy.lua', - 'xkcd.lua', - 'gMaps.lua', - 'wikipedia.lua', - 'imdb.lua', - 'urbandictionary.lua', - 'spotify.lua', - 'youtube.lua', - 'kickass.lua', - 'hackernews.lua', - 'cats.lua', - 'time.lua', - 'weather.lua', - 'calc.lua', - 'dice.lua', - 'remind.lua', - '8ball.lua', - 'bandersnatch.lua', - 'btc.lua', - 'chatter.lua', - 'commit.lua', - 'dogify.lua', - 'echo.lua', - 'hex.lua', - 'interactions.lua', - 'pokedex.lua', - 'pun.lua', - 'reaction.lua', - 'slap.lua', - 'whoami.lua', - 'lmgtfy.lua', - 'translate.lua', - 'currency.lua', - 'blacklist.lua', - 'nick.lua', - 'floodcontrol.lua' - }, - moderation = { - realm = -000, - realmname = 'Realm name or ident', - admins = { - ['000'] = 'nickname', - } - } -} diff --git a/console.lua b/console.lua deleted file mode 100755 index 6596216..0000000 --- a/console.lua +++ /dev/null @@ -1,34 +0,0 @@ -JSON = require('dkjson') -URL = require('socket.url') -HTTP = require('socket.http') -HTTPS= require('ssl.https') - -require('utilities') -config = require('config') -require('bindings') - -data = load_data('moderation.json') - -print('Fetching bot data...') -bot = get_me().result -if not bot then - error('Failure fetching bot information.') -end -for k,v in pairs(bot) do - print('',k,v) -end - -print('Loading plugins...') -plugins = {} -for i,v in ipairs(config.plugins) do - local p = dofile('plugins/'..v) - table.insert(plugins, p) -end - -clear = function() - for i = 1, 100 do - print('\n') - end -end - -print('You are now in the otouto console!') diff --git a/dkjson.lua b/dkjson.lua index b7475ba..fa50b9f 100755 --- a/dkjson.lua +++ b/dkjson.lua @@ -1,178 +1,23 @@ - -- Module options: - local always_try_using_lpeg = true - local register_global_module_table = false - local global_module_name = 'json' +-- Module options: +local always_try_using_lpeg = true +local register_global_module_table = false +local global_module_name = 'json' - --[==[ +--[==[ David Kolf's JSON module for Lua 5.1/5.2 -======================================== -*Version 2.4* +Version 2.5 -In the default configuration this module writes no global values, not even -the module table. Import it using - json = require ("dkjson") - -In environments where `require` or a similiar function are not available -and you cannot receive the return value of the module, you can set the -option `register_global_module_table` to `true`. The module table will -then be saved in the global variable with the name given by the option -`global_module_name`. - -Exported functions and values: - -`json.encode (object [, state])` --------------------------------- - -Create a string representing the object. `Object` can be a table, -a string, a number, a boolean, `nil`, `json.null` or any object with -a function `__tojson` in its metatable. A table can only use strings -and numbers as keys and its values have to be valid objects as -well. It raises an error for any invalid data types or reference -cycles. - -`state` is an optional table with the following fields: - - - `indent` - When `indent` (a boolean) is set, the created string will contain - newlines and indentations. Otherwise it will be one long line. - - `keyorder` - `keyorder` is an array to specify the ordering of keys in the - encoded output. If an object has keys which are not in this array - they are written after the sorted keys. - - `level` - This is the initial level of indentation used when `indent` is - set. For each level two spaces are added. When absent it is set - to 0. - - `buffer` - `buffer` is an array to store the strings for the result so they - can be concatenated at once. When it isn't given, the encode - function will create it temporary and will return the - concatenated result. - - `bufferlen` - When `bufferlen` is set, it has to be the index of the last - element of `buffer`. - - `tables` - `tables` is a set to detect reference cycles. It is created - temporary when absent. Every table that is currently processed - is used as key, the value is `true`. - -When `state.buffer` was set, the return value will be `true` on -success. Without `state.buffer` the return value will be a string. - -`json.decode (string [, position [, null]])` --------------------------------------------- - -Decode `string` starting at `position` or at 1 if `position` was -omitted. - -`null` is an optional value to be returned for null values. The -default is `nil`, but you could set it to `json.null` or any other -value. - -The return values are the object or `nil`, the position of the next -character that doesn't belong to the object, and in case of errors -an error message. - -Two metatables are created. Every array or object that is decoded gets -a metatable with the `__jsontype` field set to either `array` or -`object`. If you want to provide your own metatables use the syntax - - json.decode (string, position, null, objectmeta, arraymeta) - -To prevent the assigning of metatables pass `nil`: - - json.decode (string, position, null, nil) - -`.__jsonorder` -------------------------- - -`__jsonorder` can overwrite the `keyorder` for a specific table. - -`.__jsontype` ------------------------- - -`__jsontype` can be either `"array"` or `"object"`. This value is only -checked for empty tables. (The default for empty tables is `"array"`). - -`.__tojson (self, state)` ------------------------------------- - -You can provide your own `__tojson` function in a metatable. In this -function you can either add directly to the buffer and return true, -or you can return a string. On errors nil and a message should be -returned. - -`json.null` ------------ - -You can use this value for setting explicit `null` values. - -`json.version` --------------- - -Set to `"dkjson 2.4"`. - -`json.quotestring (string)` ---------------------------- - -Quote a UTF-8 string and escape critical characters using JSON -escape sequences. This function is only necessary when you build -your own `__tojson` functions. - -`json.addnewline (state)` -------------------------- - -When `state.indent` is set, add a newline to `state.buffer` and spaces -according to `state.level`. - -LPeg support ------------- - -When the local configuration variable `always_try_using_lpeg` is set, -this module tries to load LPeg to replace the `decode` function. The -speed increase is significant. You can get the LPeg module at - . -When LPeg couldn't be loaded, the pure Lua functions stay active. - -In case you don't want this module to require LPeg on its own, -disable the option `always_try_using_lpeg` in the options section at -the top of the module. - -In this case you can later load LPeg support using - -### `json.use_lpeg ()` - -Require the LPeg module and replace the functions `quotestring` and -and `decode` with functions that use LPeg patterns. -This function returns the module table, so you can load the module -using: - - json = require "dkjson".use_lpeg() - -Alternatively you can use `pcall` so the JSON module still works when -LPeg isn't found. - - json = require "dkjson" - pcall (json.use_lpeg) - -### `json.using_lpeg` - -This variable is set to `true` when LPeg was loaded successfully. - ---------------------------------------------------------------------- - -Contact -------- +For the documentation see the corresponding readme.txt or visit +. You can contact the author by sending an e-mail to 'david' at the domain 'dkolf.de'. ---------------------------------------------------------------------- -*Copyright (C) 2010-2013 David Heiko Kolf* +Copyright (C) 2010-2013 David Heiko Kolf Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -194,13 +39,7 @@ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - - diff --git a/example-plugin.lua b/example-plugin.lua deleted file mode 100755 index 90c357b..0000000 --- a/example-plugin.lua +++ /dev/null @@ -1,47 +0,0 @@ -local doc = [[ - /example [optional] - Info about the plugin goes here. -]] - -local triggers = { - '^/example', - '^/e ' -} - -local action = function(msg) do - - -- do stuff - -end - -local cron = function() do - - -- do stuff - -end - -return { - doc = doc, - triggers = triggers, - action = action, - cron = cron, - typing = true -} - ---[[ - -Here's an example plugin. - -"doc" is a string. It contains info on the plugin's usage. -The first line should be only the command and its expected arguments. Arguments should be encased in <> if they are required, and [] if they are optional. -The entire thing is sent as a message when "/help example" is used. - -"triggers" is a table of triggers. A trigger is a string that should pattern-match the desired input. - -"action" is the main function. It's what the plugin does. It takes a single argument, msg, which is a table of the contents of a message. - -"cron" is another function. It is run every five seconds without regard to triggers or messages. - -"typing" is a boolean. Set it to true if you want the bot to send a typing notification when the plugin is triggered. - -]]-- diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..f096845 --- /dev/null +++ b/launch.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +while true; do + lua bot.lua + sleep 5s +done diff --git a/loc/en.lua b/loc/en.lua deleted file mode 100755 index b5fc4e8..0000000 --- a/loc/en.lua +++ /dev/null @@ -1,22 +0,0 @@ -return { - interactions = { -- Add to this table as you'd like. - ['Hello, #NAME.'] = { - 'hello', - 'hey', - 'hi' - }, - ['Goodbye, #NAME.'] = { - 'bye', - 'later', - 'see ya' - } - }, - errors = { - connection = 'Connection error.', - results = 'No results found.', - argument = 'Invalid argument.', - syntax = 'Invalid syntax.' - }, - translate = 'en' -} - diff --git a/plugins/about.lua b/plugins/about.lua index 84c1ab3..ad766e1 100755 --- a/plugins/about.lua +++ b/plugins/about.lua @@ -1,31 +1,30 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local doc = [[ /about - Information about the bot. + Get info about the bot. ]] -PLUGIN.triggers = { - '^/about', - '^/info' +local triggers = { + '' } -function PLUGIN.action(msg) +local action = function(msg) - local message = [[ - I am ]] .. bot.first_name .. [[: a plugin-wielding, multi-purpose Telegram bot. - Send /help for a list of commands. + local message = config.about_text .. '\nBased on otouto v'..version..' by topkecleon.\notouto v3 is licensed under the GPLv2.\ntopkecleon.github.io/otouto' - Based on otouto v]] .. VERSION .. [[ by @topkecleon. - otouto v2 is licensed under the GPLv2. - topkecleon.github.io/otouto + if msg.new_chat_participant and msg.new_chat_participant.id == bot.id then + sendMessage(msg.chat.id, message) + return + elseif string.match(msg.text_lower, '^/about[@'..bot.username..']*') then + sendReply(msg, message) + return + end - Join the update/news channel! - telegram.me/otouto - ]] -- Please do not remove this message. ^.^ - - send_message(msg.chat.id, message, true) + return true end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/admin.lua b/plugins/admin.lua index e23c513..c9d3259 100755 --- a/plugins/admin.lua +++ b/plugins/admin.lua @@ -1,45 +1,75 @@ -local PLUGIN = {} - -PLUGIN.triggers = { - '^/admin ' +local triggers = { + '^/admin[@'..bot.username..']*' } -function PLUGIN.action(msg) +local commands = { - if msg.date < os.time() - 1 then return end - - local input = get_input(msg.text) - - local message = config.locale.errors.argument - - if not config.admins[msg.from.id] then - return send_msg(msg, 'Permission denied.') - end - - if string.lower(first_word(input)) == 'run' then - - local output = get_input(input) - if not output then - return send_msg(msg, config.locale.errors.argument) + ['run'] = function(cmd) + local cmd = cmd:input() + if not cmd then + return 'Please enter a command to run.' end - local output = io.popen(output) - message = output:read('*all') - output:close() + return io.popen(cmd):read('*all') + end, - elseif string.lower(first_word(input)) == 'reload' then + ['lua'] = function(cmd) + local cmd = cmd:input() + if not cmd then + return 'Please enter a command to run.' + end + local a = loadstring(cmd)() + if a then + return a + else + return 'Done!' + end + end, + ['reload'] = function(cmd) bot_init() - message = 'Bot reloaded!' - - elseif string.lower(first_word(input)) == 'halt' then + return 'Bot reloaded!' + end, + ['halt'] = function(cmd) is_started = false - message = 'Shutting down...' + return 'Stopping bot!' + end, + ['error'] = function(cmd) + error('Intentional test error.') end - send_msg(msg, message) +} + +local action = function(msg) + + if msg.from.id ~= config.admin then + return + end + + local input = msg.text:input() + if not input then + local list = 'Specify a command: ' + for k,v in pairs(commands) do + list = list .. k .. ', ' + end + list = list:gsub(', $', '.') + sendReply(msg, list) + return + end + + for k,v in pairs(commands) do + if string.match(get_word(input, 1), k) then + sendReply(msg, v(input)) + return + end + end + + sendReply(msg, 'Specify a command: run, reload, halt.') end -return PLUGIN +return { + action = action, + triggers = triggers +} diff --git a/plugins/antisquig.lua b/plugins/antisquig.lua new file mode 100755 index 0000000..4d9f1b5 --- /dev/null +++ b/plugins/antisquig.lua @@ -0,0 +1,46 @@ + -- Put this at the very top of your plugin list, even before blacklist.lua. + +antisquig = {} + +local triggers = { + '[\216-\219][\128-\191]' +} + +local action = function(msg) + + local moddat = load_data('moderation.json') + + if not moddat[msg.chat.id_str] then + return true + end + + if moddat[msg.chat.id_str][msg.from.id_str] or config.moderation.admins[msg.from.id_str] then + return true + end + + if antisquig[msg.from.id] == true then + return + end + antisquig[msg.from.id] = true + + sendReply(msg, config.errors.antisquig) + sendMessage(config.moderation.admin_group, '/kick ' .. msg.from.id .. ' from ' .. math.abs(msg.chat.id)) + sendMessage(config.moderation.admin_group, 'ANTISQUIG: ' .. msg.from.first_name .. ' kicked from ' .. msg.chat.title .. '.') + +end + + -- When a user is kicked for squigglies, his ID is added to this table. + -- That user will not be kicked again as long as his ID is in the table. + -- The table is emptied every five seconds. + -- Thus the bot will not spam the group or admin group when a user posts more than one infringing messages. +local cron = function() + + antisquig = {} + +end + +return { + action = action, + triggers = triggers, + cron = cron +} diff --git a/plugins/bandersnatch.lua b/plugins/bandersnatch.lua index 09946fd..8c9d9d4 100755 --- a/plugins/bandersnatch.lua +++ b/plugins/bandersnatch.lua @@ -1,33 +1,35 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local doc = [[ /bandersnatch - This is a Benedict Cumberbatch name generator. + Shun the frumious Bandersnatch. ]] -PLUGIN.triggers = { - '^/bandersnatch', - '^/bc$' +local triggers = { + '^/bandersnatch[@'..bot.username..']*', + '^/bc[@'..bot.username..']*' } -PLUGIN.fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" } +local fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" } -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" } +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" } -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" } +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 PLUGIN.action(msg) +local action = function(msg) - math.randomseed(os.time()) + local message - local message = '' if math.random(10) == 10 then - message = PLUGIN.fullnames[math.random(#PLUGIN.fullnames)] + message = fullnames[math.random(#fullnames)] else - message = PLUGIN.firstnames[math.random(#PLUGIN.firstnames)] .. ' ' .. PLUGIN.lastnames[math.random(#PLUGIN.lastnames)] + message = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)] end - send_msg(msg, message) + sendReply(msg, message) + end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/bible.lua b/plugins/bible.lua index 79735bf..63d7669 100755 --- a/plugins/bible.lua +++ b/plugins/bible.lua @@ -1,32 +1,50 @@ -local PLUGIN = {} +if not config.biblia_api_key then + print('Missing config value: biblia_api_key.') + print('bible.lua will not be enabled.') + return +end -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 +local doc = [[ + /bible + Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com. ]] -PLUGIN.triggers = { - '^/bible', - '^/b ' +local triggers = { + '^/b[ible]*[@'..bot.username..']*$', + '^/b[ible]*[@'..bot.username..']* ' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - return send_msg(msg, PLUGIN.doc) + sendReply(msg, doc) + return end - local url = 'http://api.biblia.com/v1/bible/content/KJV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) + local url = 'http://api.biblia.com/v1/bible/content/ASV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) + local message, res = HTTP.request(url) - if res ~= 200 then - message = config.locale.errors.connection + if message:len() == 0 then + url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) + message, res = HTTP.request(url) end - send_msg(msg, message) + if res ~= 200 then + message = config.errors.results + end + + if message:len() > 4000 then + message = 'The text is too long to post here. Try being more specific.' + end + + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/blacklist.lua b/plugins/blacklist.lua index be983ed..6cd32c7 100755 --- a/plugins/blacklist.lua +++ b/plugins/blacklist.lua @@ -1,44 +1,49 @@ - -- Admins can blacklist a user from utilizing this bot. Use via reply or with an ID as an argument. Un-blacklist a user with the same command. + -- 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 triggers = { - '^/blacklist', - '^/listofcolor' + '' } -local action = function(msg) + local action = function(msg) - if not config.admins[msg.from.id] then - return send_msg(msg, 'Permission denied.') + local blacklist = load_data('blacklist.json') + + if blacklist[msg.from.id_str] then + return -- End if the sender is blacklisted. end - local name - local input = get_input(msg.text) + if not string.match(msg.text_lower, '^/blacklist') then + return true + end + + if msg.from.id ~= config.admin then + return -- End if the user isn't admin. + end + + local input = msg.text:input() if not input then if msg.reply_to_message then - input = msg.reply_to_message.from.id - name = msg.reply_to_message.from.first_name + input = tostring(msg.reply_to_message.from.id) else - return send_msg(msg, 'Must be used via reply or by specifying a user\'s ID.') + sendReply(msg, 'You must use this command via reply or by specifying a user\'s ID.') + return end end - local id = tostring(input) - if not name then name = id end - - if blacklist[id] then - blacklist[id] = nil - send_message(msg.chat.id, name .. ' has been removed from the blacklist.') + if blacklist[input] then + blacklist[input] = nil + sendReply(msg, input .. ' has been removed from the blacklist.') else - blacklist[id] = true - send_message(msg.chat.id, name .. ' has been blacklisted.') + blacklist[input] = true + sendReply(msg, input .. ' has been added to the blacklist.') end save_data('blacklist.json', blacklist) -end + end -return { - doc = doc, - triggers = triggers, - action = action + return { + action = action, + triggers = triggers } diff --git a/plugins/btc.lua b/plugins/btc.lua deleted file mode 100755 index 3f81eb2..0000000 --- a/plugins/btc.lua +++ /dev/null @@ -1,66 +0,0 @@ -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, config.locale.errors.connection) - end - - local jdat = JSON.decode(jstr) - - local input = get_input(msg.text) - if input then - arg1 = string.upper(string.sub(input, 1, 3)) - arg2 = string.sub(input, 5) - if not tonumber(arg2) then - return send_msg(msg, config.locale.errors.argument) - end - end - - for k,v in pairs(jdat) do - if k == arg1 then - url = v .. '/' - break - end - end - - if url then - jstr, res = HTTPS.request(url) - else - return send_msg(msg, config.locale.errors.results) - end - - if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) - end - - jdat = JSON.decode(jstr) - - if not jdat['24h_avg'] then - return send_msg(msg, config.locale.errors.results) - end - - 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 index 2f4adb1..3dd904e 100755 --- a/plugins/calc.lua +++ b/plugins/calc.lua @@ -1,30 +1,38 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local 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. + Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org. ]] -PLUGIN.triggers = { - '^/calc' +local triggers = { + '^/calc[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - return send_msg(msg, PLUGIN.doc) + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end end - local url = 'http://api.mathjs.org/v1/?expr=' .. URL.escape(input) - local message, res = HTTP.request(url) + local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input) - if res ~= 200 then - return send_msg(msg, config.locale.errors.syntax) + local ans, res = HTTPS.request(url) + if not ans then + sendReply(msg, config.errors.connection) + return end - send_msg(msg, message) + sendReply(msg, ans) + end -return PLUGIN - +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/cats.lua b/plugins/cats.lua index 5dfb6a5..c60b5ab 100755 --- a/plugins/cats.lua +++ b/plugins/cats.lua @@ -1,10 +1,15 @@ +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 + local doc = [[ /cat - Get a cat pic! + Returns a cat! ]] local triggers = { - '^/cats?' + '^/cat[@'..bot.username..']*$' } local action = function(msg) @@ -14,20 +19,20 @@ local action = function(msg) url = url .. '&api_key=' .. config.thecatapi_key end - local jstr, res = HTTP.request(url) + local str, res = HTTP.request(url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end - jstr = jstr:match('') + str = str:match('') - send_message(msg.chat.id, jstr, false, msg.message_id) + sendMessage(msg.chat.id, str) end return { - doc = doc, - triggers = triggers, action = action, - typing = true + triggers = triggers, + doc = doc } diff --git a/plugins/chatter.lua b/plugins/chatter.lua index d6c05fa..904e70f 100755 --- a/plugins/chatter.lua +++ b/plugins/chatter.lua @@ -1,36 +1,36 @@ - -- shout-out to @luksireiku for showing me this site + -- Put this absolutely at the end, even after greetings.lua. -local PLUGIN = {} - -PLUGIN.typing = true - -PLUGIN.triggers = { - '^@' .. bot.username .. ', ', - '^' .. bot.first_name .. ', ' +local triggers = { + '' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + if not msg.reply_to_message then + return true + elseif msg.reply_to_message and msg.reply_to_message.from.id ~= bot.id then + return true + end - local url = 'http://www.simsimi.com/requestChat?lc=en&ft=1.0&req=' .. URL.escape(input) + sendChatAction(msg.chat.id, 'typing') + + local url = 'http://www.simsimi.com/requestChat?lc=en&ft=1.0&req=' .. URL.escape(msg.text_lower) local jstr, res = HTTP.request(url) - if res ~= 200 then - return send_message(msg.chat.id, "I don't feel like talking right now.") + sendMessage(msg.chat.id, config.errors.chatter_connection) + return end local jdat = JSON.decode(jstr) - - if string.match(jdat.res.msg, '^I HAVE NO RESPONSE.') or not jdat then - jdat.res.msg = "I don't know what to say to that." - end - local message = jdat.res.msg + if message:match('^I HAVE NO RESPONSE.') then + message = config.errors.chatter_response + end + -- Let's clean up the response a little. Capitalization & punctuation. - filter = { + local filter = { ['%aimi?%aimi?'] = bot.first_name, ['^%s*(.-)%s*$'] = '%1', ['^%l'] = string.upper, @@ -45,8 +45,11 @@ function PLUGIN.action(msg) message = message .. '.' end - send_message(msg.chat.id, message) + sendMessage(msg.chat.id, message) end -return PLUGIN +return { + action = action, + triggers = triggers +} diff --git a/plugins/commit.lua b/plugins/commit.lua index f7ee22c..96332c4 100755 --- a/plugins/commit.lua +++ b/plugins/commit.lua @@ -1,20 +1,15 @@ -local PLUGIN = {} + -- Commits from https://github.com/ngerakines/commitment. -PLUGIN.doc = [[ +local doc = [[ /commit - http://whatthecommit.com. + Returns a commit message from whatthecommit.com. ]] -PLUGIN.triggers = { - '^/commit' +local triggers = { + '^/commit[@'..bot.username..']*' } -function PLUGIN.action(msg) - math.randomseed(os.time()) - send_msg(msg, PLUGIN.commits[math.random(#PLUGIN.commits)]) -end - -PLUGIN.commits = { +local commits = { "One does not simply merge into master", "Merging the merge", "Another bug bites the dust", @@ -417,4 +412,14 @@ PLUGIN.commits = { "One little whitespace gets its very own commit! Oh, life is so erratic!" } -return PLUGIN +local action = function(msg) + + sendMessage(msg.chat.id, commits[math.random(#commits)]) + +end + +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/currency.lua b/plugins/currency.lua index 2efba7c..2702957 100755 --- a/plugins/currency.lua +++ b/plugins/currency.lua @@ -1,54 +1,54 @@ local doc = [[ - /cash [amount] - Convert an amount from one currency to another. - Example: /cash USD EUR 5 + /cash [amount] to + Example: /cash 5 USD to EUR + Returns exchange rates for various currencies. ]] local triggers = { - '^/cash' + '^/cash[@'..bot.username..']*' } local action = function(msg) - local input = get_input(msg.text) - if not input then - return send_msg(msg, doc) + local input = msg.text:upper() + if not input:match('%a%a%a TO %a%a%a') then + sendReply(msg, doc) + return end - local url = 'http://www.google.com/finance/converter' -- thanks juan :^) + local from = input:match('(%a%a%a) TO') + local to = input:match('TO (%a%a%a)') + local amount = input:match('([%d]+) %a%a%a TO %a%a%a') or 1 + local result = 1 - local from = first_word(input):upper() - local to = first_word(input, 2):upper() - local amount = first_word(input, 3) - local result - - if not tonumber(amount) then - amount = 1 - result = 1 - end + local url = 'https://www.google.com/finance/converter' if from ~= to then local url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount - - local str, res = HTTP.request(url) + local str, res = HTTPS.request(url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end - local str = str:match('(.*) %u+') - if not str then return send_msg(msg, config.locale.errors.results) end - result = string.format('%.2f', str) + str = str:match('(.*) %u+') + if not str then + sendReply(msg, config.errors.results) + return + end + + result = str:format('%.2f') end local message = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to - send_msg(msg, message) + sendReply(msg, message) end return { - doc = doc, + action = action, triggers = triggers, - action = action + doc = doc } diff --git a/plugins/dgmp.lua b/plugins/dgmp.lua deleted file mode 100755 index 8275278..0000000 --- a/plugins/dgmp.lua +++ /dev/null @@ -1,94 +0,0 @@ - -- This is an experimental plugin for indexing a list of compliant groups, their descriptions, how to join them, etc. It is not complete nor fully implemented. The current test bot is @dgmpbot. - -local triggers = { - '^/index', - '^/listgroups' -} - -local dgmp_index = function(msg) - - local dgmp = load_data('dgmp.json') - - local input = get_input(msg.text) - if not input then return end - - input = JSON.decode(input) - if not input then return end - - local id = tostring(input.chatid) - - if not dgmp[id] then - dgmp[id] = {} - end - group = dgmp[id] - - group.chatname = input.chatname - if input.usercount then - group.usercount = input.usercount - end - if input.description then - group.description = input.description - end - if input.joininstructions then - group.joininstructions = input.joininstructions - end - - save_data('dgmp.json', dgmp) - -end - -local dgmp_list = function(msg) - - local dgmp = load_data('dgmp.json') - - local input = get_input(msg.text) - if not input then - input = '' - else - input = string.lower(input) - end - - local output = '' - for k,v in pairs(dgmp) do - if string.find(string.lower(v.chatname), input) then - output = output .. v.chatname .. ' (' .. k .. ')\n' - if v.description then - output = output .. v.description .. '\n' - end - if v.usercount then - output = output .. 'Users: ' .. v.usercount .. '\n' - end - if v.joininstructions then - output = output .. 'How to join: ' .. v.joininstructions .. '\n' - end - output = output .. '\n' - end - end - - if string.len(output) > 4000 then - output = 'List is too long! Please use a (better) search query.' - end - - output = trim_string(output) - if string.len(output) == 0 then - output = 'No results found.' - end - - send_msg(msg, output) - -end - -local action = function(msg) - - if string.match(msg.text, '^/index') then - dgmp_index(msg) - elseif string.match(msg.text, '^/listgroups') then - dgmp_list(msg) - end - -end - -return { - triggers = triggers, - action = action -} diff --git a/plugins/dice.lua b/plugins/dice.lua index a92b242..e40ec6e 100755 --- a/plugins/dice.lua +++ b/plugins/dice.lua @@ -1,67 +1,54 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ - /roll [arg] - 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. +local doc = [[ + /roll + Returns a set of dice rolls, where n is the number of rolls and r is the range. If only a range is given, returns only one roll. ]] -PLUGIN.triggers = { - '^/roll' +local triggers = { + '^/roll[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - math.randomseed(os.time()) - - local input = get_input(msg.text) + local input = msg.text_lower:input() if not input then - input = 6 + sendReply(msg, doc) + 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 - input = string.upper(input) + sendReply(msg, doc) + return 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, config.locale.errors.argument) - end - else - return send_msg(msg, config.locale.errors.argument) + count = tonumber(count) + range = tonumber(range) + + if range < 2 then + sendReply(msg, 'The minimum range is 2.') + return + end + if range > 1000 or count > 1000 then + sendReply(msg, 'The maximum range and count are 1000.') + return 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, config.locale.errors.syntax) + local message = '' + for i = 1, count do + message = message .. math.random(range) .. '\t' end - if tonumber(range) < 2 then - return send_msg(msg, config.locale.errors.syntax) - end - - if tonumber(rolls) > 100 or tonumber(range) > 100000 then - return send_msg(msg, 'Max 100D100000') - end - - for i = 1, tonumber(rolls) do - results = results .. math.random(range) .. '\t' - end - - send_msg(msg, results) + sendReply(msg, message) end -return PLUGIN - +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/dogify.lua b/plugins/dogify.lua deleted file mode 100755 index 3201407..0000000 --- a/plugins/dogify.lua +++ /dev/null @@ -1,29 +0,0 @@ -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, ' ', '') - local input = string.lower(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 index 509049a..53a256a 100755 --- a/plugins/echo.lua +++ b/plugins/echo.lua @@ -1,23 +1,26 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local doc = [[ /echo - Repeat a string. + Repeat a string of text! ]] -PLUGIN.triggers = { - '^/echo' +local triggers = { + '^/echo[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) - if not input then - return send_msg(msg, PLUGIN.doc) + local input = msg.text:input() + + if input then + sendReply(msg, latcyr(input)) + else + sendReply(msg, doc) end - send_message(msg.chat.id, latcyr(input)) - end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/8ball.lua b/plugins/eightball.lua similarity index 54% rename from plugins/8ball.lua rename to plugins/eightball.lua index b13c116..00e9fde 100755 --- a/plugins/8ball.lua +++ b/plugins/eightball.lua @@ -1,17 +1,14 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local doc = [[ /8ball - Magic 8-ball. Returns a standard 8ball message, unless called with "y/n", where it will return a less verbose answer. + Returns an answer from a magic 8-ball! ]] -PLUGIN.triggers = { - '^/helix', +local triggers = { '^/8ball', 'y/n%p?$' } -PLUGIN.answers = { +local ball_answers = { "It is certain.", "It is decidedly so.", "Without a doubt.", @@ -35,24 +32,33 @@ PLUGIN.answers = { "There is a time and place for everything, but not now." } -PLUGIN.yesno = {'Absolutely.', 'In your dreams.', 'Yes.', 'No.'} +local yesno_answers = { + 'Absolutely.', + 'In your dreams.', + 'Yes.', + 'No.' +} -function PLUGIN.action(msg) - - math.randomseed(os.time()) +local action = function(msg) if msg.reply_to_message then msg = msg.reply_to_message end - if string.match(string.lower(msg.text), 'y/n') then - message = PLUGIN.yesno[math.random(#PLUGIN.yesno)] + local message + + if msg.text:match('y/n%p?$') then + message = yesno_answers[math.random(#yesno_answers)] else - message = PLUGIN.answers[math.random(#PLUGIN.answers)] + message = ball_answers[math.random(#ball_answers)] end - send_msg(msg, message) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/floodcontrol.lua b/plugins/floodcontrol.lua index bdee5b9..c8e05df 100755 --- a/plugins/floodcontrol.lua +++ b/plugins/floodcontrol.lua @@ -1,31 +1,37 @@ -floodcontrol = {} + -- Liberbot-compliant floodcontrol. + -- Put this after moderation.lua or blacklist.lua. + +floodcontrol = floodcontrol or {} local triggers = { - '/floodcontrol' + '' } local action = function(msg) - local input, output - - if msg.from.id ~= 100547061 then -- Only acknowledge Liberbot. - if not config.admins[msg.from.id] then -- or an admin. :) - return - end + if floodcontrol[-msg.chat.id] then + return end - input = get_input(msg.text) -- Remove the first word from the input. - input = JSON.decode(input) -- Parse the JSON into a table. - if not input.groupid then return end -- If no group is specified, end. - if not input.duration then -- If no duration is specified, set it to 5min. + local input = msg.text_lower:match('^/floodcontrol[@'..bot.username..']* (.+)') + if not input then return true end + + if msg.from.id ~= 100547061 and msg.from.id ~= config.admin then + return -- Only run for Liberbot or the admin. + end + + input = JSON.decode(input) + + if not input.groupid then + return + end + if not input.duration then input.duration = 600 end floodcontrol[input.groupid] = os.time() + input.duration - local s = input.groupid .. ' silenced for ' .. input.duration .. ' seconds.' - - send_message(-34496439, s) -- Set this to whatever, or comment it out. I use it to send this data to my private bot group. + print(input.groupid .. ' silenced for ' .. input.duration .. ' seconds.') end @@ -40,7 +46,7 @@ local cron = function() end return { - triggers = triggers, action = action, + triggers = triggers, cron = cron } diff --git a/plugins/fortune.lua b/plugins/fortune.lua index 2aeed62..edcb38d 100755 --- a/plugins/fortune.lua +++ b/plugins/fortune.lua @@ -1,22 +1,30 @@ -local PLUGIN = {} + -- Requires that the "fortune" program is installed on your computer. -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) + local s = io.popen('fortune'):read('*all') + if s:match('fortune: command not found') then + print('fortune is not installed on this computer.') + print('fortune.lua will not be enabled.') + return end -return PLUGIN +local doc = [[ + /fortune + Returns a UNIX fortune. +]] + +local triggers = { + '^/fortune[@'..bot.username..']*' +} + +local action = function(msg) + + local message = io.popen('fortune'):read('*all') + sendMessage(msg.chat.id, message) + +end + +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/gImages.lua b/plugins/gImages.lua index d9d823b..1ce9c70 100755 --- a/plugins/gImages.lua +++ b/plugins/gImages.lua @@ -1,78 +1,58 @@ -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. - Want images sent directly to chat? Try @ImageBot. +local doc = [[ + /image + Returns a randomized top result from Google Images. Safe search is enabled by default; use "/insfw" to disable it. NSFW results will not display an image preview. ]] -PLUGIN.triggers = { - '^/images?', - '^/img', - '^/i ', - '^/insfw' +local triggers = { + '^/i[mage]*[nsfw]*[@'..bot.username..']*$', + '^/i[mage]*[nsfw]*[@'..bot.username..']* ' } -PLUGIN.exts = { - '.png$', - '.jpg$', - '.jpeg$', - '.jpe$', - '.gif$' -} +local action = function(msg) -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' + local input = msg.text:input() + if not input then + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end end - local input = get_input(msg.text) - if not input then - if msg.reply_to_message then - msg = msg.reply_to_message - input = msg.text - else - return send_msg(msg, PLUGIN.doc) - end + local url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8' + + if not string.match(msg.text, '^/i[mage]*nsfw') then + url = url .. '&safe=active' end url = url .. '&q=' .. URL.escape(input) - local jstr, res = HTTP.request(url) - + local jstr, res = HTTPS.request(url) if res ~= 200 then - send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) return end local jdat = JSON.decode(jstr) - if #jdat.responseData.results < 1 then - send_msg(msg, config.locale.errors.results) + sendReply(msg, config.errors.results) return end - local is_real = false - local counter = 0 - while is_real == false do - counter = counter + 1 - if counter > 5 then - return send_msg(msg, config.locale.errors.results) - end - 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 + local i = math.random(#jdat.responseData.results) + local result = jdat.responseData.results[i].url - send_message(msg.chat.id, result_url, false, msg.message_id) + if string.match(msg.text, '^/i[mage]*nsfw') then + sendReply(msg, result) + else + sendMessage(msg.chat.id, result, false, msg.message_id) + end end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/gMaps.lua b/plugins/gMaps.lua index 45e4819..6edf648 100755 --- a/plugins/gMaps.lua +++ b/plugins/gMaps.lua @@ -1,45 +1,37 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ - /loc - Sends location data for query, taken from Google Maps. Works for countries, cities, landmarks, etc. +local doc = [[ + /location + Returns a location from Google Maps. ]] -PLUGIN.triggers = { - '^/loc' +triggers = { + '^/loc[ation]*[@'..bot.username..']*$', + '^/loc[ation]*[@'..bot.username..']* ' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - if msg.reply_to_message then - msg = msg.reply_to_message - input = msg.text + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text else - return send_msg(msg, PLUGIN.doc) + sendReply(msg, doc) + return end 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, config.locale.errors.connection) + local coords = get_coords(input) + if type(coords) == 'string' then + sendReply(msg, coords) + return end - local jdat = JSON.decode(jstr) - - if jdat.status ~= 'OK' then - local message = config.locale.errors.results - 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) + sendLocation(msg.chat.id, coords.lat, coords.lon, msg.message_id) end -return PLUGIN - +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/gSearch.lua b/plugins/gSearch.lua index 72ed1e8..d11330e 100755 --- a/plugins/gSearch.lua +++ b/plugins/gSearch.lua @@ -1,65 +1,62 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local 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. + Returns four (if group) or eight (if private message) results from Google. Safe search is enabled by default, use "/gnsfw" to disable it. ]] -PLUGIN.triggers = { - '^/g ', - '^/g$', - '^/google', - '^/gnsfw' +local triggers = { + '^/g[oogle]*[nsfw]*[@'..bot.username..']*$', + '^/g[oogle]*[nsfw]*[@'..bot.username..']* ' } -function PLUGIN.action(msg) +local action = function(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) + local input = msg.text:input() if not input then - if msg.reply_to_message then - msg = msg.reply_to_message - input = msg.text + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text else - return send_msg(msg, PLUGIN.doc) + sendReply(msg, doc) + return end end + local url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0' + + if msg.from.id == msg.chat.id then + url = url .. '&rsz=8' + else + url = url .. '&rsz=4' + end + + if not string.match(msg.text, '^/g[oogle]*nsfw') then + url = url .. '&safe=active' + end + url = url .. '&q=' .. URL.escape(input) - local jstr, res = HTTP.request(url) - + local jstr, res = HTTPS.request(url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end local jdat = JSON.decode(jstr) - if #jdat.responseData.results < 1 then - return send_msg(msg, config.locale.errors.results) + sendReply(msg, config.errors.results) + return end local 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' + for i,v in ipairs(jdat.responseData.results) do + message = message .. jdat.responseData.results[i].titleNoFormatting .. '\n ' .. jdat.responseData.results[i].unescapedUrl .. '\n' end - local message = message:gsub('&', '&') -- blah - - send_msg(msg, message) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/giphy.lua b/plugins/giphy.lua deleted file mode 100755 index 9c910d3..0000000 --- a/plugins/giphy.lua +++ /dev/null @@ -1,57 +0,0 @@ -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. - Want GIFs sent directly to chat? Try @ImageBot. -]] - -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, config.locale.errors.connection) - 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, config.locale.errors.connection) - end - local jdat = JSON.decode(jstr) - if #jdat.data == 0 then - return send_msg(msg, config.locale.errors.results) - end - 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/greetings.lua b/plugins/greetings.lua new file mode 100755 index 0000000..9f1c476 --- /dev/null +++ b/plugins/greetings.lua @@ -0,0 +1,29 @@ + -- Put this on the bottom of your plugin list, after help.lua. + +local triggers = { + bot.first_name .. '%p?$' +} + +local action = function(msg) + + local nicks = load_data('nicknames.json') + + local nick = nicks[msg.from.id_str] or msg.from.first_name + + for k,v in pairs(config.greetings) do + for key,val in pairs(v) do + if msg.text_lower:match(val..',? '..bot.first_name) then + sendMessage(msg.chat.id, latcyr(k:gsub('#NAME', nick))) + return + end + end + end + + return true + +end + +return { + action = action, + triggers = triggers +} diff --git a/plugins/hackernews.lua b/plugins/hackernews.lua index 75a94db..da43f4f 100755 --- a/plugins/hackernews.lua +++ b/plugins/hackernews.lua @@ -1,37 +1,46 @@ -local PLUGIN = {} - -PLUGIN.typing = true -- usually takes a few seconds to load - -PLUGIN.doc = [[ +local doc = [[ /hackernews - Returns some top stories from Hacker News. Four in a group or eight in a private message. + Returns four (if group) or eight (if private message) top stories from Hacker News. ]] -PLUGIN.triggers = { - '^/hackernews', - '^/hn$' +local triggers = { + '^/hackernews[@'..bot.username..']*', + '^/hn[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) + + local jstr, res = HTTPS.request('https://hacker-news.firebaseio.com/v0/topstories.json') + if res ~= 200 then + sendReply(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 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 + 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 + sendReply(msg, config.errors.connection) + return + end + local res_jdat = JSON.decode(jstr) + message = message .. res_jdat.title .. '\n ' .. res_jdat.url .. '\n' 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) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/hearthstone.lua b/plugins/hearthstone.lua deleted file mode 100755 index dd43c43..0000000 --- a/plugins/hearthstone.lua +++ /dev/null @@ -1,107 +0,0 @@ - -- Get info for a hearthstone card. - -local jstr, res = HTTP.request('http://hearthstonejson.com/json/AllSets.json') -if res ~= 200 then - return print('Error connecting to the Hearthstone database. hearthstone.lua will not be enabled.') -end -jdat = JSON.decode(jstr) -hs_dat = {} - -for k,v in pairs(jdat) do - for key,val in pairs(v) do - table.insert(hs_dat, val) - end -end - -local doc = [[ - /hearthstone - Get information about a Hearthstone card. -]] - -local triggers = { - '^/hearthstone', - '^/hs' -} - -local fmt_card = function(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 - - 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 - -local action = function(msg) - - local input = get_input(msg.text) - if not input then return send_msg(msg, doc) end - input = string.lower(input) - - local output = '' - for k,v in pairs(hs_dat) do - if string.match(string.lower(v.name), input) then - output = output .. fmt_card(v) .. '\n\n' - end - end - - output = trim_string(output) - if string.len(output) == 0 then - return send_msg(msg, config.locale.errors.results) - end - - send_msg(msg, output) - -end - -return { - doc = doc, - triggers = triggers, - action = action -} diff --git a/plugins/help.lua b/plugins/help.lua index 79f3d2f..e00b36d 100755 --- a/plugins/help.lua +++ b/plugins/help.lua @@ -1,45 +1,37 @@ -local PLUGIN = {} + -- This plugin should go at the end of your plugin list in + -- config.lua, but not after greetings.lua. -PLUGIN.doc = [[ - /help [command] - Get list of basic information for all commands, or more detailed documentation on a specified command. -]] +local help_text = 'Available commands:\n' -PLUGIN.triggers = { - '^/help', - '^/h$', - '^/start$' +for i,v in ipairs(plugins) do + if v.doc then + local a = string.sub(v.doc, 1, string.find(v.doc, '\n')-1) + help_text = help_text .. a .. '\n' + end +end + +local help_text = help_text .. 'Arguments: [optional]' + +local triggers = { + '^/h[elp]*[@'..bot.username..']*$', + '^/start[@'..bot.username..']*' } -function PLUGIN.action(msg) - - if string.find(msg.text, '@') and not string.match(msg.text, 'help@'..bot.username) then return end - - 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 - - local message = 'Available commands:\n' .. help_message .. [[ - *Arguments: [optional] - ]] +local action = function(msg) if msg.from.id ~= msg.chat.id then - if not send_message(msg.from.id, message, true, msg.message_id) then - return send_msg(msg, message) -- Unable to PM user who hasn't PM'd first. + if sendMessage(msg.from.id, help_text) then + sendReply(msg, 'I have sent you the requested information in a private message.') + else + sendReply(msg, help_text) end - return send_msg(msg, 'I have sent you the requested information in a private message.') else - return send_msg(msg, message) + sendReply(msg, help_text) end end -return PLUGIN +return { + action = action, + triggers = triggers +} diff --git a/plugins/hex.lua b/plugins/hex.lua deleted file mode 100755 index 2a369d4..0000000 --- a/plugins/hex.lua +++ /dev/null @@ -1,29 +0,0 @@ -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, config.locale.errors.argument) - - end - -end - -return PLUGIN diff --git a/plugins/imdb.lua b/plugins/imdb.lua index 0f2cab3..eed77ef 100755 --- a/plugins/imdb.lua +++ b/plugins/imdb.lua @@ -1,36 +1,37 @@ -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. +local doc = [[ + /imdb + Returns an IMDb entry. ]] -PLUGIN.triggers = { - '^/imdb' +local triggers = { + '^/imdb[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - if msg.reply_to_message then - msg = msg.reply_to_message - input = msg.text + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text else - return send_msg(msg, PLUGIN.doc) + sendReply(msg, doc) + return end end local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input) - local jstr, res = HTTP.request(url) - local jdat = JSON.decode(jstr) - if res ~= 200 or not jdat then - return send_msg(msg, config.locale.errors.connection) + local jstr, res = HTTP.request(url) + if res ~= 200 then + sendReply(msg, config.errors.connection) + return end + local jdat = JSON.decode(jstr) + if jdat.Response ~= 'True' then - return send_msg(msg, jdat.Error) + sendReply(msg, config.errors.results) + return end local message = jdat.Title ..' ('.. jdat.Year ..')\n' @@ -38,8 +39,12 @@ function PLUGIN.action(msg) message = message .. jdat.Plot .. '\n' message = message .. 'http://imdb.com/title/' .. jdat.imdbID - send_msg(msg, message) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/interactions.lua b/plugins/interactions.lua deleted file mode 100755 index d6714c3..0000000 --- a/plugins/interactions.lua +++ /dev/null @@ -1,36 +0,0 @@ -local PLUGIN = {} - -PLUGIN.triggers = { - bot.first_name .. '%p?$', - '^tadaima%p?$', - '^i\'m home%p?$', - '^i\'m back%p?$' -} - -function PLUGIN.action(msg) - - local input = string.lower(msg.text) - - local data = load_data('nicknames.json') - local id = tostring(msg.from.id) - local nick = msg.from.first_name - - if data[id] then nick = data[id] end - - for i = 2, #PLUGIN.triggers do - if string.match(input, PLUGIN.triggers[i]) then - return send_message(msg.chat.id, 'Welcome back, ' .. nick .. '!') - end - end - - for k,v in pairs(config.locale.interactions) do - for key,val in pairs(v) do - if input:match(val..',? '..bot.first_name) then - return send_message(msg.chat.id, latcyr(k:gsub('#NAME', nick))) - end - end - end - -end - -return PLUGIN diff --git a/plugins/kickass.lua b/plugins/kickass.lua deleted file mode 100755 index 7e1b41c..0000000 --- a/plugins/kickass.lua +++ /dev/null @@ -1,69 +0,0 @@ - -- Kickass Torrents - -- Based on @Imandaneshi's torrent.lua - -- https://github.com/Imandaneshi/Jack/blob/master/plugins/torrent.lua - -local doc = [[ - /torrent - Search Kickass Torrents. Results may be NSFW. -]] - -local triggers = { - '^/torrent', - '^/kickass' -} - -local action = function(msg) - - local url = 'http://kat.cr/json.php?q=' - - local input = get_input(msg.text) - if not input then - return send_msg(msg, doc) - end - - local jstr, res = HTTPS.request(url..URL.escape(input)) - if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) - end - - local jdat = JSON.decode(jstr) - if #jdat.total_results == 0 then - return send_msg(msg, config.locale.errors.results) - end - - local limit = 4 -- If the request is made in a PM, send 8 results instead of 4. - if msg.chat.id == msg.from.id then - limit = 8 - end - if #jdat.total_results < limit then -- If there are not that many results, do as many as possible. - limit = #jdat.total_results - end - - for i,v in ipairs(jdat.list) do -- Remove any entries that have zero seeds. - if v.seeds == 0 then - table.remove(jdat.list, i) - end - end - - if #jdat.list == 0 then - return send_msg(msg, config.locale.errors.results) - end - - local message = '' - for i = 1, limit do - local torrenturl = jdat.list[i].torrentLink:sub(1, jdat.list[i].torrentLink:find('?')-1) -- Clean up the torrent link. - message = message .. jdat.list[i].title .. '\n' .. jdat.list[i].category .. ' | ' .. string.format('%.3f', jdat.list[i].size/1000000) .. 'MB | ' .. jdat.list[i].seeds .. 'S/' .. jdat.list[i].peers .. 'L\n' .. torrenturl .. '\n\n' - end - - message = message:gsub('&', '&') - - send_msg(msg, message) - -end - -return { - doc = doc, - triggers = triggers, - action = action, - typing = true -} diff --git a/plugins/lastfm.lua b/plugins/lastfm.lua index 452f842..40f2fc7 100755 --- a/plugins/lastfm.lua +++ b/plugins/lastfm.lua @@ -1,89 +1,101 @@ -local PLUGIN = {} +if not config.lastfm_api_key then + print('Missing config value: lastfm_api_key.') + print('lastfm.lua will not be enabled.') + return +end -PLUGIN.doc = [[ - /lastfm [username] - Get current- or last-played track data from last.fm. If a username is specified, it will return info for that username rather than your own. - "/fmset username" will configure your last.fm username. +local doc = [[ + /lastfm + /np [username] + Returns what you are or were last listening to. If you specify a username, info will be returned for that username. + /fmset + Sets your last.fm username. Otherwise, /np will use your Telegram username. Use "/fmset -" to delete it. ]] -PLUGIN.triggers = { - '^/lastfm', - '^/np$', - '^/fm$', - '^/fmset' +local triggers = { + '^/lastfm[@'..bot.username..']*', + '^/np[@'..bot.username..']*', + '^/fmset[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - if msg.text:match('^/fmset') then - - local input = get_input(msg.text) - if not input then - return send_msg(msg, PLUGIN.doc) - end - - local data = load_data('lastfm.json') - local id = tostring(msg.from.id) - - data[id] = input - - save_data('lastfm.json', data) - - send_msg(msg, 'Your last.fm username has been set to ' .. input .. '.') + lastfm = load_data('lastfm.json') + local input = msg.text:input() + if string.match(msg.text, '^/lastfm') then + sendReply(msg, doc:sub(10)) return - - end - - local base_url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user=' - - local input = get_input(msg.text) - if not input then - local data = load_data('lastfm.json') - if data[tostring(msg.from.id)] then - input = data[tostring(msg.from.id)] - elseif msg.from.username then - input = msg.from.username + elseif string.match(msg.text, '^/fmset') then + if not input then + sendReply(msg, doc) + elseif input == '-' then + lastfm[msg.from.id_str] = nil + sendReply(msg, 'Your last.fm username has been forgotten.') else - return send_msg(msg, 'Please provide a valid last.fm username.\nYou can set yours with /fmset.') + lastfm[msg.from.id_str] = input + sendReply(msg, 'Your last.fm username has been set to "' .. input .. '".') end + save_data('lastfm.json', lastfm) + return end - local jstr, res = HTTP.request(base_url..input) + local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user=' + local username + if input then + username = input + elseif lastfm[msg.from.id_str] then + username = lastfm[msg.from.id_str] + elseif msg.from.username then + username = msg.from.username + else + sendReply(msg, 'Please specify your last.fm username or set it with /fmset.') + return + end + + url = url .. URL.escape(username) + + jstr, res = HTTPS.request(url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end local jdat = JSON.decode(jstr) - if jdat.error then - return send_msg(msg, 'Please provide a valid last.fm username.\nYou can set yours with /fmset.') - end - - if not jdat.recenttracks.track then - return send_msg(msg, 'No history for that user.') + sendReply(msg, jdat.error) + return end local jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track - - local message = '🎵 ' .. msg.from.first_name .. ' last listened to:\n' - if jdat['@attr'] and jdat['@attr'].nowplaying then - message = '🎵 ' .. msg.from.first_name .. ' is listening to:\n' + if not jdat then + sendReply(msg, 'No history for this user.') + return end - local name = jdat.name or 'Unknown' - local artist + local message = input or msg.from.first_name + message = '🎵 ' .. message + + if jdat['@attr'] and jdat['@attr'].nowplaying then + message = message .. ' is currently listening to:\n' + else + message = message .. ' last listened to:\n' + end + + local title = jdat.name or 'Unknown' + local artist = 'Unknown' if jdat.artist then artist = jdat.artist['#text'] - else - artist = 'Unknown' end - local message = message .. name .. ' - ' .. artist - - send_message(msg.chat.id, message) + message = message .. title .. ' - ' .. artist + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/lmgtfy.lua b/plugins/lmgtfy.lua deleted file mode 100755 index 22bebff..0000000 --- a/plugins/lmgtfy.lua +++ /dev/null @@ -1,18 +0,0 @@ -local PLUGIN = {} - -PLUGIN.triggers = { - '^/lmgtfy' -} - -function PLUGIN.action(msg) - - if not msg.reply_to_message then return end - msg = msg.reply_to_message - - local message = 'http://lmgtfy.com/?q=' .. URL.escape(msg.text) - - send_msg(msg, message) - -end - -return PLUGIN diff --git a/plugins/moderation.lua b/plugins/moderation.lua index 02f05d7..6257974 100755 --- a/plugins/moderation.lua +++ b/plugins/moderation.lua @@ -1,373 +1,296 @@ ---[[ + -- Moderation for Liberbot groups. + -- The bot must be made an admin. + -- Put this near the top, after blacklist. + -- If you want to enable antisquig, put that at the top, before blacklist. -This plugin will ONLY WORK in Liberbot-administered groups. + local triggers = { + '^/modhelp[@'..bot.username..']*$', + '^/modlist[@'..bot.username..']*$', + '^/modcast[@'..bot.username..']*', + '^/add[@'..bot.username..']*$', + '^/remove[@'..bot.username..']*$', + '^/promote[@'..bot.username..']*$', + '^/demote[@'..bot.username..']*', + '^/modkick[@'..bot.username..']*', + '^/modban[@'..bot.username..']*', + } -This works using the settings in the "moderation" section of config.lua. -"realm" should be set to the group ID of the admin group. A negative number. -"data" will be the file name of where the moderation 'database' will be stored. The file will be created if it does not exist. -"admins" is a table of administrators for the Liberbot admin group. They will have the power to add groups and moderators to the database. The value can be a nickname for the admin, but it only needs to be true for it to work. +local commands = { -Your bot should have privacy mode disabled. + ['^/modhelp[@'..bot.username..']*$'] = function(msg) -]]-- + local moddat = load_data('moderation.json') -local help = {} - -help.trigger = '^/modhelp' - -help.action = function(msg) - - local data = load_data('moderation.json') - - local do_send = false - if data[tostring(msg.chat.id)] and data[tostring(msg.chat.id)][tostring(msg.from.id)] then do_send = true end - if config.moderation.admins[tostring(msg.from.id)] then do_send = true end - if do_send == false then return end - - local message = [[ - Moderator commands: - /modban - Ban a user via reply or username. - /modkick - Kick a user via reply or username. - /modlist - Get a list of moderators for this group. - Administrator commands: - /add - Add this group to the database. - /remove - Remove this group from the database. - /promote - Promote a user via reply. - /demote - Demote a user via reply. - /modcast - Send a broastcast to every group. - /hammer - Ban a user from all groups via reply or username. - ]] - - send_message(msg.chat.id, message) - -end - - -local ban = {} - -ban.trigger = '^/modban' - -ban.action = function(msg) - - if msg.flood then - msg.chat.id = msg.flood - end - - local data = load_data('moderation.json') - - if not data[tostring(msg.chat.id)] then return end - if not data[tostring(msg.chat.id)][tostring(msg.from.id)] then - if not config.moderation.admins[tostring(msg.from.id)] then - return + if not moddat[msg.chat.id_str] then + return config.errors.moderation end - end - local target = get_target(msg) - if not target then - return send_message(msg.chat.id, 'No one to remove.\nBots must be removed by username.') - end + local message = [[ + /modlist - List the moderators and administrators of this group. + Moderator commands: + /modkick - Kick a user from this group. + /modban - Ban a user from this group. + Administrator commands: + /add - Add this group to the moderation system. + /remove - Remove this group from the moderation system. + /promote - Promote a user to a moderator. + /demote - Demote a moderator to a user. + /modcast - Send a broadcast to every moderated group. + ]] - if msg.reply_to_message and data[tostring(msg.chat.id)][tostring(msg.reply_to_message.from.id)] then - return send_message(msg.chat.id, 'Cannot remove a moderator.') - end + return message - local chat_id = math.abs(msg.chat.id) + end, - send_message(config.moderation.realm, '/ban ' .. target .. ' from ' .. chat_id) + ['^/modlist[@'..bot.username..']*$'] = function(msg) - if msg.reply_to_message then - target = msg.reply_to_message.from.first_name - end + local moddat = load_data('moderation.json') - send_message(config.moderation.realm, target .. ' banned from ' .. msg.chat.title .. ' by ' .. msg.from.first_name .. '.') - -end - - -local kick = {} - -kick.trigger = '^/modkick' - -kick.action = function(msg) - - if msg.flood then - msg.chat.id = msg.flood - end - - local data = load_data('moderation.json') - - if not data[tostring(msg.chat.id)] then return end - if not data[tostring(msg.chat.id)][tostring(msg.from.id)] then - if not config.moderation.admins[tostring(msg.from.id)] then - return + if not moddat[msg.chat.id_str] then + return config.errors.moderation end - end - local target = get_target(msg) - if not target then - return send_message(msg.chat.id, 'No one to remove.\nBots must be removed by username.') - end + local message = '' - if msg.reply_to_message and data[tostring(msg.chat.id)][tostring(msg.reply_to_message.from.id)] then - return send_message(msg.chat.id, 'Cannot remove a moderator.') - end - - local chat_id = math.abs(msg.chat.id) - - send_message(config.moderation.realm, '/kick ' .. target .. ' from ' .. chat_id) - - if msg.reply_to_message then - target = msg.reply_to_message.from.first_name - end - - send_message(config.moderation.realm, target .. ' kicked from ' .. msg.chat.title .. ' by ' .. msg.from.first_name .. '.') - -end - - -local add = {} - -add.trigger = '^/[mod]*add$' - -add.action = function(msg) - - local data = load_data('moderation.json') - - if not config.moderation.admins[tostring(msg.from.id)] then return end - - if data[tostring(msg.chat.id)] then - return send_message(msg.chat.id, 'Group is already added.') - end - - data[tostring(msg.chat.id)] = {} - save_data('moderation.json', data) - - send_message(msg.chat.id, 'Group has been added.') - -end - - -local rem = {} - -rem.trigger = '^/[mod]*rem[ove]*$' - -rem.action = function(msg) - - local data = load_data('moderation.json') - - if not config.moderation.admins[tostring(msg.from.id)] then return end - - if not data[tostring(msg.chat.id)] then - return send_message(msg.chat.id, 'Group is not added.') - end - - data[tostring(msg.chat.id)] = nil - save_data('moderation.json', data) - - send_message(msg.chat.id, 'Group has been removed.') - -end - - -local promote = {} - -promote.trigger = '^/[mod]*prom[ote]*$' - -promote.action = function(msg) - - local data = load_data('moderation.json') - local chatid = tostring(msg.chat.id) - - if not config.moderation.admins[tostring(msg.from.id)] then return end - - if not data[chatid] then - return send_message(msg.chat.id, 'Group is not added.') - end - - if not msg.reply_to_message then - return send_message(msg.chat.id, 'Promotions must be done via reply.') - end - - local targid = tostring(msg.reply_to_message.from.id) - - if data[chatid][targid] then - return send_message(msg.chat.id, msg.reply_to_message.from.first_name..' is already a moderator.') - end - - if config.moderation.admins[targid] then - return send_message(msg.chat.id, 'Administrators do not need to be promoted.') - end - - if not msg.reply_to_message.from.username then - msg.reply_to_message.from.username = msg.reply_to_message.from.first_name - end - - data[chatid][targid] = msg.reply_to_message.from.first_name - save_data('moderation.json', data) - - send_message(msg.chat.id, msg.reply_to_message.from.first_name..' has been promoted.') - -end - - -local demote = {} - -demote.trigger = '^/[mod]*dem[ote]*' - -demote.action = function(msg) - - local data = load_data('moderation.json') - - if not config.moderation.admins[tostring(msg.from.id)] then return end - - if not data[tostring(msg.chat.id)] then - return send_message(msg.chat.id, 'Group is not added.') - end - - local input = get_input(msg.text) - if not input then - if msg.reply_to_message then - input = msg.reply_to_message.from.id - else - return send_msg('Demotions must be done by reply or by specifying a moderator\'s ID.') + for k,v in pairs(moddat[msg.chat.id_str]) do + message = message .. ' - ' .. v .. ' (' .. k .. ')\n' end + + if message ~= '' then + message = 'Moderators for ' .. msg.chat.title .. ':\n' .. message .. '\n' + end + + message = message .. 'Administrators for ' .. config.moderation.realm_name .. ':\n' + for k,v in pairs(config.moderation.admins) do + message = message .. ' - ' .. v .. ' (' .. k .. ')\n' + end + + return message + + end, + + ['^/modcast[@'..bot.username..']*'] = function(msg) + + local message = msg.text:input() + if not message then + return 'You must include a message.' + end + + if msg.chat.id ~= config.moderation.admin_group then + return 'This command must be run in the administration group.' + end + + if not config.moderation.admins[msg.from.id_str] then + return config.errors.not_admin + end + + local moddat = load_data('moderation.json') + + for k,v in pairs(moddat) do + sendMessage(k, message) + end + + return 'Your broadcast has been sent.' + + end, + + ['^/add[@'..bot.username..']*$'] = function(msg) + + if not config.moderation.admins[msg.from.id_str] then + return config.errors.not_admin + end + + local moddat = load_data('moderation.json') + + if moddat[msg.chat.id_str] then + return 'I am already moderating this group.' + end + + moddat[msg.chat.id_str] = {} + save_data('moderation.json', moddat) + return 'I am now moderating this group.' + + end, + + ['^/remove[@'..bot.username..']*$'] = function(msg) + + if not config.moderation.admins[msg.from.id_str] then + return config.errors.not_admin + end + + local moddat = load_data('moderation.json') + + if not moddat[msg.chat.id_str] then + return config.errors.moderation + end + + moddat[msg.chat.id_str] = nil + save_data('moderation.json', moddat) + return 'I am no longer moderating this group.' + + end, + + ['^/promote[@'..bot.username..']*$'] = function(msg) + + local moddat = load_data('moderation.json') + + if not moddat[msg.chat.id_str] then + return config.errors.moderation + end + + if not config.moderation.admins[msg.from.id_str] then + return config.errors.not_admin + end + + if not msg.reply_to_message then + return 'Promotions must be done via reply.' + end + + local modid = tostring(msg.reply_to_message.from.id) + local modname = msg.reply_to_message.from.first_name + + if config.moderation.admins[modid] then + return modname .. ' is already an administrator.' + end + + if moddat[msg.chat.id_str][modid] then + return modname .. ' is already a moderator.' + end + + moddat[msg.chat.id_str][modid] = modname + save_data('moderation.json', moddat) + + return modname .. ' is now a moderator.' + + end, + + ['^/demote[@'..bot.username..']*'] = function(msg) + + local moddat = load_data('moderation.json') + + if not moddat[msg.chat.id_str] then + return config.errors.moderation + end + + if not config.moderation.admins[msg.from.id_str] then + return config.errors.not_admin + end + + local modid = msg.text:input() + + if not modid then + if msg.reply_to_message then + modid = tostring(msg.reply_to_message.from.id) + else + return 'Demotions must be done via reply or specification of a moderator\'s ID.' + end + end + + if config.moderation.admins[modid] then + return config.moderation.admins[modid] .. ' is an administrator.' + end + + if not moddat[msg.chat.id_str][modid] then + return 'User is not a moderator.' + end + + local modname = moddat[msg.chat.id_str][modid] + moddat[msg.chat.id_str][modid] = nil + save_data('moderation.json', moddat) + + return modname .. ' is no longer a moderator.' + + end, + + ['/modkick[@'..bot.username..']*'] = function(msg) + + local moddat = load_data('moderation.json') + + if not moddat[msg.chat.id_str] then + return config.errors.moderation + end + + if not moddat[msg.chat.id_str][msg.from.id_str] then + if not config.moderation.admins[msg.from.id_str] then + return config.errors.not_mod + end + end + + local userid = msg.text:input() + local usernm = userid + + if not userid then + if msg.reply_to_message then + userid = tostring(msg.reply_to_message.from.id) + usernm = msg.reply_to_message.from.first_name + else + return 'Kicks must be done via reply or specification of a user/bot\'s ID or username.' + end + end + + if moddat[msg.chat.id_str][userid] or config.moderation.admins[userid] then + return 'You cannot kick a moderator.' + end + + sendMessage(config.moderation.admin_group, '/kick ' .. userid .. ' from ' .. math.abs(msg.chat.id)) + + sendMessage(config.moderation.admin_group, usernm .. ' kicked from ' .. msg.chat.title .. ' by ' .. msg.from.first_name .. '.') + + end, + + ['^/modban[@'..bot.username..']*'] = function(msg) + + local moddat = load_data('moderation.json') + + if not moddat[msg.chat.id_str] then + return config.errors.moderation + end + + if not moddat[msg.chat.id_str][msg.from.id_str] then + if not config.moderation.admins[msg.from.id_str] then + return config.errors.not_mod + end + end + + local userid = msg.text:input() + local usernm = userid + + if not userid then + if msg.reply_to_message then + userid = tostring(msg.reply_to_message.from.id) + usernm = msg.reply_to_message.from.first_name + else + return 'Bans must be done via reply or specification of a user/bot\'s ID or username.' + end + end + + if moddat[msg.chat.id_str][userid] or config.moderation.admins[userid] then + return 'You cannot ban a moderator.' + end + + sendMessage(config.moderation.admin_group, '/ban ' .. userid .. ' from ' .. math.abs(msg.chat.id)) + + sendMessage(config.moderation.admin_group, usernm .. ' banned from ' .. msg.chat.title .. ' by ' .. msg.from.first_name .. '.') + end - if not data[tostring(msg.chat.id)][tostring(input)] then - return send_message(msg.chat.id, input..' is not a moderator.') - end - - data[tostring(msg.chat.id)][tostring(input)] = nil - save_data('moderation.json', data) - - send_message(msg.chat.id, input..' has been demoted.') - -end - - -local broadcast = {} - -broadcast.trigger = '^/modcast' - -broadcast.action = function(msg) - - local data = load_data('moderation.json') - - if not config.moderation.admins[tostring(msg.from.id)] then return end - - if msg.chat.id ~= config.moderation.realm then - return send_message(msg.chat.id, 'This command must be run in the admin group.') - end - - local message = get_input(msg.text) - - if not message then - return send_message(msg.chat.id, 'You must specify a message to broadcast.') - end - - for k,v in pairs(data) do - send_message(k, message) - end - -end - - -local modlist = {} - -modlist.trigger = '^/modlist' - -modlist.action = function(msg) - - local data = load_data('moderation.json') - - if not data[tostring(msg.chat.id)] then - return send_message(msg.chat.id, 'Group is not added.') - end - - local message = '' - - for k,v in pairs(data[tostring(msg.chat.id)]) do - message = message ..' - '..v.. ' (' .. k .. ')\n' - end - - if message ~= '' then - message = 'Moderators for ' .. msg.chat.title .. ':\n' .. message .. '\n' - end - - message = message .. 'Administrators for ' .. config.moderation.realmname .. ':\n' - for k,v in pairs(config.moderation.admins) do - message = message ..' - '..v.. ' (' .. k .. ')\n' - end - - send_message(msg.chat.id, message) - -end - - -local badmin = {} - -badmin.trigger = '^/hammer' - -badmin.action = function(msg) - - if msg.flood then - msg.chat.id = msg.flood - end - - if not config.moderation.admins[tostring(msg.from.id)] then return end - - local target = get_target(msg) - if not target then - return send_message(msg.chat.id, 'No one to remove.\nBots must be removed by username.') - end - - send_message(config.moderation.realm, '/ban ' .. target .. ' from all') - - if msg.reply_to_message then - target = msg.reply_to_message.from.first_name - end - - send_message(config.moderation.realm, target .. ' was banhammered by ' .. msg.from.first_name .. '.') - -end - - -local modactions = { - help, - ban, - kick, - add, - rem, - promote, - demote, - broadcast, - modlist, - badmin -} - - -local triggers = { - '^/modhelp', - '^/modlist', - '^/modcast', - '^/[mod]*add$', - '^/[mod]*rem[ove]*$', - '^/[mod]*prom[ote]*$', - '^/[mod]*dem[ote]*', - '^/modkick', - '^/modban', - '^/hammer' } local action = function(msg) - for k,v in pairs(modactions) do - if string.match(msg.text, v.trigger) then - return v.action(msg) + + for k,v in pairs(commands) do + if string.match(msg.text, k) then + local output = v(msg) + if output then + sendReply(msg, output) + end + return end end + end return { - triggers = triggers, - action = action + action = action, + triggers = triggers } diff --git a/plugins/nick.lua b/plugins/nick.lua index c863d75..4369663 100755 --- a/plugins/nick.lua +++ b/plugins/nick.lua @@ -1,42 +1,42 @@ local doc = [[ /nick - Set your nickname for the bot to call you. - Use -- to clear your nickname. + Set your nickname. Use "/whoami" to check your nickname and "/nick -" to delete it. ]] local triggers = { - '^/nick' + '^/nick[@'..bot.username..']*' } local action = function(msg) - local data = load_data('nicknames.json') - local id = tostring(msg.from.id) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - local message = '' - if data[id] then - message = '\nYour nickname is currently ' .. data[id] .. '.' - end - return send_msg(msg, doc..message) + sendReply(msg, doc) + return true end - if input == '--' then - data[id] = nil - save_data('nicknames.json', data) - send_msg(msg, 'Your nickname has been deleted.') - return + if string.len(input) > 32 then + sendReply(msg, 'The character limit for nicknames is 32.') + return true end - input = input:sub(1,64):gsub('\n',' ') - data[id] = input - save_data('nicknames.json', data) - send_msg(msg, 'Your nickname has been set to ' .. input .. '.') + nicks = load_data('nicknames.json') + + if input == '-' then + nicks[msg.from.id_str] = nil + sendReply(msg, 'Your nickname has been deleted.') + else + nicks[msg.from.id_str] = input + sendReply(msg, 'Your nickname has been set to "' .. input .. '".') + end + + save_data('nicknames.json', nicks) + return true end return { - doc = doc, + action = action, triggers = triggers, - action = action + doc = doc } diff --git a/plugins/owm.lua b/plugins/owm.lua deleted file mode 100755 index 19f9688..0000000 --- a/plugins/owm.lua +++ /dev/null @@ -1,40 +0,0 @@ -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 - return send_msg(msg, config.locale.errors.results) - 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, config.locale.errors.connection) - 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 = celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.' - - send_msg(msg, message) - -end - -return PLUGIN diff --git a/plugins/pokedex.lua b/plugins/pokedex.lua index 70b64ea..73e14e1 100755 --- a/plugins/pokedex.lua +++ b/plugins/pokedex.lua @@ -1,42 +1,46 @@ -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. +local doc = [[ + /pokedex + Returns a Pokedex entry from pokeapi.co. ]] -PLUGIN.triggers = { - '^/dex' +local triggers = { + '^/[poke]*dex[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text:lower()) + local input = msg.text_lower:input() if not input then - return send_msg(msg, PLUGIN.doc) + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end end - local base_url = 'http://pokeapi.co' - local poke_type = nil + local url = 'http://pokeapi.co' - local dex_url = base_url .. '/api/v1/pokemon/' .. input + local dex_url = url .. '/api/v1/pokemon/' .. input local dex_jstr, res = HTTP.request(dex_url) if res ~= 200 then - return send_msg(msg, config.locale.errors.results) + sendReply(msg, config.errors.connection) + return 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_url = 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, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end local desc_jdat = JSON.decode(desc_jstr) - for k,v in pairs(dex_jdat.types) do + local poke_type + for i,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 @@ -46,12 +50,14 @@ function PLUGIN.action(msg) end poke_type = poke_type .. ' type' - local info_line = 'Height: ' .. dex_jdat.height/10 .. 'm, Weight: ' .. dex_jdat.weight/10 .. 'kg' + local message = dex_jdat.name .. ' #' .. dex_jdat.national_id .. '\n' .. poke_type .. '\nHeight: ' .. dex_jdat.height/10 .. 'm, Weight: ' .. dex_jdat.weight/10 .. 'kg\n' .. desc_jdat.description:gsub('POKMON', 'POKeMON') - 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) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/pun.lua b/plugins/pun.lua index 030603e..1a653f6 100755 --- a/plugins/pun.lua +++ b/plugins/pun.lua @@ -1,17 +1,13 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local doc = [[ /pun - Get a random pun. - Have a recommendation? PM @topkecleon. + Returns a pun. ]] -PLUGIN.triggers = { - '^/pun$', - '^/pun@' +local triggers = { + '^/pun[@'..bot.username..']*' } -PLUGIN.puns = { +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.", @@ -135,9 +131,14 @@ PLUGIN.puns = { "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)]) +local action = function(msg) + + sendReply(msg, puns[math.random(#puns)]) + end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/reaction.lua b/plugins/reaction.lua deleted file mode 100755 index 34808e9..0000000 --- a/plugins/reaction.lua +++ /dev/null @@ -1,47 +0,0 @@ -local doc = [[ - /reactions - Get a list of the available reaction emoticons. -]] - -local triggers = { - ['¯\\_(ツ)_/¯'] = '/shrug$', - ['( ͡° ͜ʖ ͡°)'] = '/lenny$', - ['(╯°□°)╯︵ ┻━┻'] = '/flip$', - ['┌(┌ ^o^)┐'] = '/homo$', - ['ಠ_ಠ'] = '/look$', - ['SHOTS FIRED'] = '/shot$' -} - -local action = function(msg) - - local message = string.lower(msg.text) - - for k,v in pairs(triggers) do - if string.match(message, v) then - message = k - end - end - - if msg.reply_to_message then - send_msg(msg.reply_to_message, message) - else - send_message(msg.chat.id, message) - end - -end - --- The following block of code will generate a list of reactions add the trigger "/reactions" to display it. --- Thanks to @Imandaneshi for the idea and early implementation. -local help = '' -for k,v in pairs(triggers) do - if v ~= '^/reactions?' then - help = help .. v:gsub('%$', ': ') .. k .. '\n' - end -end -triggers[help] = '^/reactions' - -return { - triggers = triggers, - action = action, - doc = doc -} diff --git a/plugins/reactions.lua b/plugins/reactions.lua new file mode 100755 index 0000000..4d1405d --- /dev/null +++ b/plugins/reactions.lua @@ -0,0 +1,37 @@ +local doc = [[ + /reactions + Returns a list of "reaction" emoticon commands. +]] + +local triggers = { + ['¯\\_(ツ)_/¯'] = '/shrug$', + ['( ͡° ͜ʖ ͡°)'] = '/lenny$', + ['(╯°□°)╯︵ ┻━┻'] = '/flip$', + ['┌(┌ ^o^)┐'] = '/homo$', + ['ಠ_ಠ'] = '/look$', + ['SHOTS FIRED'] = '/shot$' +} + + -- Generate a "help" message triggered by "/reactions". +local help = '' +for k,v in pairs(triggers) do + help = help .. v:gsub('%$', ': ') .. k .. '\n' +end +triggers[help] = '^/reactions$' + +local action = function(msg) + + for k,v in pairs(triggers) do + if string.match(msg.text_lower, v) then + sendMessage(msg.chat.id, k) + return + end + end + +end + +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/reddit.lua b/plugins/reddit.lua index 672a78e..7122061 100755 --- a/plugins/reddit.lua +++ b/plugins/reddit.lua @@ -1,84 +1,66 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ +local doc = [[ /reddit [r/subreddit | query] - This command returns top results for a given query or subreddit. NSFW posts are marked as such. + Returns the four (if group) or eight (if private message) top posts for the given subreddit or query, or from the frontpage. ]] -PLUGIN.triggers = { - '^/reddit', - '^/r$', - '^/r ' +local triggers = { + '^/r[eddit]*[@'..bot.username..']*$', + '^/r[eddit]*[@'..bot.username..']* ', + '^/r/' } -function PLUGIN.action(msg) +local action = function(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, config.locale.errors.connection) - end - jdat = JSON.decode(jstr) - if #jdat.data.children == 0 then - return send_msg(msg, config.locale.errors.results) - 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, config.locale.errors.connection) - end - jdat = JSON.decode(jstr) - if #jdat.data.children == 0 then - return send_msg(msg, config.locale.errors.results) - end - - end - - else - - url = 'https://www.reddit.com/.json' - local jstr, res = HTTP.request(url) - if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) - end - jdat = JSON.decode(jstr) - - end + msg.text_lower = msg.text_lower:gsub('/r/', '/r r/') + local input = msg.text_lower:input() + local url local limit = 4 - if #jdat.data.children < limit then - limit = #jdat.data.children + if msg.chat.id == msg.from.id then + limit = 8 end - for i = 1, limit do + if input then + if input:match('^r/') then + url = 'http://www.reddit.com/' .. input .. '/.json?limit=' .. limit + else + url = 'http://www.reddit.com/search.json?q=' .. input .. '&limit=' .. limit + end + else + url = 'http://www.reddit.com/.json?limit=' .. limit + end - if jdat.data.children[i].data.over_18 then + local jstr, res = HTTP.request(url) + if res ~= 200 then + sendReply(msg, config.errors.connection) + return + end + + local jdat = JSON.decode(jstr) + if #jdat.data.children == 0 then + sendReply(msg, config.errors.results) + return + end + + local message = '' + for i,v in ipairs(jdat.data.children) do + if v.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' + local long_url = '\n' + if not v.data.is_self then + long_url = '\n' .. v.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 - + local short_url = '[redd.it/' .. v.data.id .. '] ' + message = message .. short_url .. v.data.title .. long_url end - send_msg(msg, message) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/remind.lua b/plugins/remind.lua deleted file mode 100755 index b237274..0000000 --- a/plugins/remind.lua +++ /dev/null @@ -1,70 +0,0 @@ -reminders = {} - -local doc = [[ - /remind - Set a reminder for yourself. First argument is the number of minutes until you wish to be reminded. -]] - -local triggers = { - '^/remind' -} - -local action = function(msg) - - local input = get_input(msg.text) - if not input then - return send_msg(msg, 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) - if msg.from.username then - text = text .. '\n@' .. msg.from.username - end - - local delay = tonumber(delay) - - local rem = { - alarm = os.time() + (delay * 60), - chat_id = msg.chat.id, - text = text - } - - table.insert(reminders, rem) - - 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 - -local cron = function() - - for i,v in ipairs(reminders) do - if os.time() > v.alarm then - send_message(v.chat_id, text) - table.remove(reminders, i) - end - end - -end - -return { - doc = doc, - triggers = triggers, - action = action, - cron = cron -} diff --git a/plugins/slap.lua b/plugins/slap.lua index a618878..2bd3dda 100755 --- a/plugins/slap.lua +++ b/plugins/slap.lua @@ -1,136 +1,129 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ - /slap [victim] - Slap someone! +local doc = [[ + /slap [target] + Give someone a good slap (or worse) through reply or specification of a target. ]] -PLUGIN.triggers = { - '^/slap' +local triggers = { + '^/slap[@'..bot.username..']*' } -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.", - 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 .. " RSA-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 +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.' +} -function PLUGIN.action(msg) +local action = function(msg) - math.randomseed(os.time()) - - local slapper, victim, sid, vid - - victim = get_input(msg.text) - if victim then - slapper = msg.from.first_name - else - victim = msg.from.first_name - vid = msg.from.id - slapper = bot.first_name - end + local nicks = load_data('nicknames.json') + local victim = msg.text:input() if msg.reply_to_message then - victim = msg.reply_to_message.from.first_name - vid = msg.reply_to_message.from.id - slapper = msg.from.first_name - sid = msg.from.id - if slapper == victim then - slapper = bot.first_name - sid = bot.id + if nicks[tostring(msg.reply_to_message.from.id)] then + victim = nicks[tostring(msg.reply_to_message.from.id)] + else + victim = msg.reply_to_message.from.first_name end end - nicks = load_data('nicknames.json') -- Try to replace slapper/victim names with nicknames. - sid = tostring(sid) - vid = tostring(vid) - if nicks[sid] then slapper = nicks[sid] end - if nicks[vid] then victim = nicks[vid] end + local victor = msg.from.first_name + if nicks[msg.from.id_str] then + victor = nicks[msg.from.id_str] + end - local message = PLUGIN.getSlap(slapper, victim) - send_message(msg.chat.id, latcyr(message)) + if not victim then + victim = victor + victor = bot.first_name + end + + local message = slaps[math.random(#slaps)] + message = message:gsub('$victim', victim) + message = message:gsub('$victor', victor) + + sendMessage(msg.chat.id, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/spotify.lua b/plugins/spotify.lua deleted file mode 100755 index b09359b..0000000 --- a/plugins/spotify.lua +++ /dev/null @@ -1,52 +0,0 @@ --- Spotify Plugin for bot based on otouto --- ByTiagoDanin - Telegram.me/tiagodanin -local PLUGIN = {} - -PLUGIN.doc = [[ - /spotify - Track Spotify music. -]] - -PLUGIN.triggers = { - '^/spoti$', - '^/spotify' -} - -function PLUGIN.action(msg) - - local input = get_input(msg.text) - if not input then - return send_msg(msg, PLUGIN.doc) - end - --URL API - local BASE_URL = "https://api.spotify.com/v1/search" - local URLP = "?q=".. (URL.escape(input) or "").."&type=track&limit=5" -- Limit 5 - -- Decode json - local decj, tim = HTTPS.request(BASE_URL..URLP) - if tim ~=200 then return nil end - -- Table - local spotify = JSON.decode(decj) - local tables = {} - for pri,result in ipairs(spotify.tracks.items) do - table.insert(tables, { - spotify.tracks.total, - result.name .. ' - ' .. result.artists[1].name, - result.external_urls.spotify - }) - end - -- Print Tables - local gets = "" - for pri,cont in ipairs(tables) do - gets=gets.."▶️ "..cont[2].."\n"..cont[3].."\n" - end - -- ERRO 404 - local text_end = gets -- Text END - if gets == "" then - text_end = "Not found music" - end - -- Send MSG - send_msg(msg, text_end) - -end - -return PLUGIN diff --git a/plugins/time.lua b/plugins/time.lua index 5a9f5f5..4978477 100755 --- a/plugins/time.lua +++ b/plugins/time.lua @@ -1,48 +1,53 @@ - -- time_offset is the number of seconds necessary to correct your system clock to UTC. - -local PLUGIN = {} - -PLUGIN.doc = [[ +local doc = [[ /time - Sends the time and timezone for a given location. + Returns the time, date, and timezone for the given location. ]] -PLUGIN.triggers = { - '^/time' +local triggers = { + '^/time[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - return send_msg(msg, PLUGIN.doc) + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end end local coords = get_coords(input) - if not coords then - return send_msg(msg, config.locale.errors.results) + if type(coords) == 'string' then + sendReply(msg, coords) + return end local url = 'https://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, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end local jdat = JSON.decode(jstr) local timestamp = os.time() + jdat.rawOffset + jdat.dstOffset + config.time_offset - local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600 if utcoff == math.abs(utcoff) then utcoff = '+' .. utcoff end - local message = os.date('%I:%M %p\n', timestamp) .. os.date('%A, %B %d, %Y\n', timestamp) .. jdat.timeZoneName .. ' (UTC' .. utcoff .. ')' - send_msg(msg, message) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/translate.lua b/plugins/translate.lua index 0cf4817..2d46831 100755 --- a/plugins/translate.lua +++ b/plugins/translate.lua @@ -1,41 +1,40 @@ - -- Glanced at https://github.com/yagop/telegram-bot/blob/master/plugins/translate.lua - - local PLUGIN = {} - - PLUGIN.triggers = { - '^/translate' -} - -PLUGIN.doc = [[ - /translate [target lang] - Reply to a message to translate it to the default language. +local doc = [[ + /translate [text] + Translates input or the replied-to message into the bot's language. ]] -PLUGIN.action = function(msg) +local triggers = { + '^/translate[@'..bot.username..']*' +} - if not msg.reply_to_message then - return send_msg(msg, PLUGIN.doc) +local action = function(msg) + + local input = msg.text:input() + if not input then + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end end - local tl = config.locale.translate or 'en' - local input = get_input(msg.text) - if input then - tl = input - end - - local url = 'http://translate.google.com/translate_a/single?client=t&ie=UTF-8&oe=UTF-8&hl=en&dt=t&tl=' .. tl .. '&sl=auto&text=' .. URL.escape(msg.reply_to_message.text) - - local str, res = HTTP.request(url) + local url = 'https://translate.google.com/translate_a/single?client=t&ie=UTF-8&oe=UTF-8&hl=en&dt=t&sl=auto&tl=' .. config.lang .. '&text=' .. URL.escape(input) + local str, res = HTTPS.request(url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end - local output = str:gmatch("%[%[%[\"(.*)\"")():gsub("\"(.*)", "") - local output = latcyr(output) + local output = latcyr(str:gmatch("%[%[%[\"(.*)\"")():gsub("\"(.*)", "")) - send_msg(msg.reply_to_message, output) + sendReply(msg.reply_to_message or msg, output) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/urbandictionary.lua b/plugins/urbandictionary.lua index 0b7030d..d7feb15 100755 --- a/plugins/urbandictionary.lua +++ b/plugins/urbandictionary.lua @@ -1,48 +1,50 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ - /ud - Returns the first definition for a given term from Urban Dictionary. +local doc = [[ + /urbandictionary + Returns a definition from Urban Dictionary. ]] -PLUGIN.triggers = { - '^/ud', - '^/urbandictionary', - '^/urban' +local triggers = { + '^/u[rban]*d[ictionary]*[@'..bot.username..']*', + '^/urban[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - if msg.reply_to_message then - msg = msg.reply_to_message - input = msg.text + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text else - return send_msg(msg, PLUGIN.doc) + sendReply(msg, doc) + return end end local url = 'http://api.urbandictionary.com/v0/define?term=' .. URL.escape(input) - local jstr, res = HTTP.request(url) + local jstr, res = HTTP.request(url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end local jdat = JSON.decode(jstr) - if jdat.result_type == "no_results" then - return send_msg(msg, config.locale.errors.results) + sendReply(msg, config.errors.results) + return end - message = '"' .. jdat.list[1].word .. '"\n' .. trim_string(jdat.list[1].definition) + local message = '"' .. jdat.list[1].word .. '"\n' .. jdat.list[1].definition:trim() if string.len(jdat.list[1].example) > 0 then - message = message .. '\n\nExample:\n' .. trim_string(jdat.list[1].example) + message = message .. '\n\nExample:\n' .. jdat.list[1].example:trim() end - send_msg(msg, message) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/weather.lua b/plugins/weather.lua index ffedd43..a8327e1 100755 --- a/plugins/weather.lua +++ b/plugins/weather.lua @@ -1,41 +1,56 @@ -local PLUGIN = {} +if not config.owm_api_key then + print('Missing config value: owm_api_key.') + print('weather.lua will not be enabled.') + return +end -PLUGIN.doc = [[ +local 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. - Results and weather data are powered by Yahoo. + Returns the current weather conditions for a given location. ]] -PLUGIN.triggers = { - '^/weather' +local triggers = { + '^/weather[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) + local input = msg.text:input() if not input then - return send_msg(msg, PLUGIN.doc) + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end end - local url = 'https://query.yahooapis.com/v1/public/yql?q=select%20item.condition%20from%20weather.forecast%20where%20woeid%20in%20%28select%20woeid%20from%20geo.places%281%29%20where%20text%3D%22' .. URL.escape(input) .. '%22%29&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys' + local coords = get_coords(input) + if type(coords) == 'string' then + sendReply(msg, coords) + return + end + + local url = 'http://api.openweathermap.org/data/2.5/weather?APPID=' .. config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon local jstr, res = HTTP.request(url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end + local jdat = JSON.decode(jstr) - if not jdat.query.results then - return send_msg(msg, config.locale.errors.results) - end - local data = jdat.query.results.channel.item.condition - local fahrenheit = data.temp - local celsius = string.format('%.0f', (fahrenheit - 32) * 5/9) - local message = celsius .. '°C | ' .. fahrenheit .. '°F, ' .. data.text .. '.' + local celsius = string.format('%.2f', jdat.main.temp - 273.15) + local fahrenheit = string.format('%.2f', celsius * (9/5) + 32) + local message = celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.' - send_msg(msg, message) + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/whoami.lua b/plugins/whoami.lua index 8589379..38f31e3 100755 --- a/plugins/whoami.lua +++ b/plugins/whoami.lua @@ -1,34 +1,13 @@ -local PLUGIN = {} - -PLUGIN.doc = [[ - /whoami - Get the user ID for yourself and the group. Use it in a reply to get info for the sender of the original message. -]] - -PLUGIN.triggers = { - '^/whoami', - '^/ping', - '^/who$' +local triggers = { + '^/who[ami]*[@'..bot.username..']*$' } -function PLUGIN.action(msg) - - 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 action = function(msg) if msg.reply_to_message then msg = msg.reply_to_message end - local nicknames = load_data('nicknames.json') - local message = '' - if nicknames[tostring(msg.from.id)] then - message = 'Hi, ' .. nicknames[tostring(msg.from.id)] .. '!\n' - end - local from_name = msg.from.first_name if msg.from.last_name then from_name = from_name .. ' ' .. msg.from.last_name @@ -38,10 +17,25 @@ function PLUGIN.action(msg) end from_name = from_name .. ' (' .. msg.from.id .. ')' - local message = message .. 'You are ' .. from_name .. ' and you are messaging ' .. to_name .. '.' + local to_name + if msg.chat.title then + to_name = msg.chat.title .. ' (' .. math.abs(msg.chat.id) .. ').' + else + to_name = '@' .. bot.username .. ', AKA ' .. bot.first_name .. ' (' .. bot.id .. ').' + end - send_msg(msg, message) + local message = 'You are ' .. from_name .. ' and you are messaging ' .. to_name + + local nicks = load_data('nicknames.json') + if nicks[msg.from.id_str] then + message = message .. '\nYour nickname is ' .. nicks[msg.from.id_str] .. '.' + end + + sendReply(msg, message) end -return PLUGIN +return { + action = action, + triggers = triggers +} diff --git a/plugins/wikipedia.lua b/plugins/wikipedia.lua index 404db67..86063f9 100755 --- a/plugins/wikipedia.lua +++ b/plugins/wikipedia.lua @@ -1,70 +1,67 @@ local doc = [[ - /wiki - Search Wikipedia for a relevant article and return its summary. + /wikipedia + Returns an article from Wikipedia. ]] local triggers = { - '^/wiki', - '^/w ' + '^/w[iki[pedia]*]*[@'..bot.username..']*$', + '^/w[iki[pedia]*]*[@'..bot.username..']* ' } local action = function(msg) - local gurl = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=1&q=site:wikipedia.org%20' - local wurl = 'http://en.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles=' - - local input = get_input(msg.text) + local input = msg.text:input() if not input then - return send_msg(msg, doc) + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end end - local jstr, res = HTTP.request(gurl..URL.escape(input)) - if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) - end - local title = JSON.decode(jstr) - local url = title.responseData.results[1].url - title = title.responseData.results[1].titleNoFormatting - title = title:gsub(' %- Wikipedia, the free encyclopedia', '') + local gurl = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=1&q=site:wikipedia.org%20' + local wurl = 'https://en.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles=' - jstr, res = HTTPS.request(wurl..URL.escape(title)) + local jstr, res = HTTPS.request(gurl .. URL.escape(input)) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.error.connection) + return end + + local jdat = JSON.decode(jstr) + local url = jdat.responseData.results[1].url + local title = jdat.responseData.results[1].titleNoFormatting:gsub(' %- Wikipedia, the free encyclopedia', '') + + jstr, res = HTTPS.request(wurl .. URL.escape(title)) + if res ~= 200 then + sendReply(msg, config.error.connection) + return + end + local text = JSON.decode(jstr).query.pages - for k,v in pairs(text) do text = v.extract break -- Seriously, there's probably a way more elegant solution. end - if not text then - return send_msg(msg, config.locale.errors.results) + sendReply(msg, config.error.connection) + return end - --[[ Uncomment this block for more than one-paragraph summaries. - local l = text:find('

') - if l then - text = text:sub(1, l-2) - end - ]]-- - text = text:gsub('', '') - - local l = text:find('\n') -- Comment this block for more than one-paragraph summaries. + local l = text:find('\n') if l then text = text:sub(1, l-1) end - text = text .. '\n' .. url - send_msg(msg, text) + sendReply(msg, text) end return { - doc = doc, - triggers = triggers, action = action, - typing = true + triggers = triggers, + doc = doc } diff --git a/plugins/xkcd.lua b/plugins/xkcd.lua index 505e4f6..c4d20ac 100755 --- a/plugins/xkcd.lua +++ b/plugins/xkcd.lua @@ -1,52 +1,56 @@ -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. +local doc = [[ + /xkcd [query] + Returns an xkcd strip and its alt text. If there is no query, it will be randomized. ]] -PLUGIN.triggers = { - '^/xkcd' +local triggers = { + '^/xkcd[@'..bot.username..']*' } -function PLUGIN.action(msg) +local action = function(msg) - local input = get_input(msg.text) - local url = 'http://xkcd.com/info.0.json' - local jstr, res = HTTP.request(url) + local input = msg.text:input() + + local jstr, res = HTTP.request('http://xkcd.com/info.0.json') if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end + local latest = JSON.decode(jstr).num - local jdat + local res_url 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) + local url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&safe=active&q=site%3axkcd%2ecom%20' .. URL.escape(input) + local jstr, res = HTTPS.request(url) if res ~= 200 then - print('here') - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end - jdat = JSON.decode(jstr) + local jdat = JSON.decode(jstr) if #jdat.responseData.results == 0 then - return send_msg(msg, config.locale.errors.results) + sendReply(msg, config.errors.results) + return end - url = jdat.responseData.results[1].url .. 'info.0.json' + res_url = jdat.responseData.results[1].url .. 'info.0.json' else - math.randomseed(os.time()) - url = 'http://xkcd.com/' .. math.random(latest) .. '/info.0.json' + res_url = 'http://xkcd.com/' .. math.random(latest) .. '/info.0.json' end - local jstr, res = HTTP.request(url) + local jstr, res = HTTP.request(res_url) if res ~= 200 then - return send_msg(msg, config.locale.errors.connection) + sendReply(msg, config.errors.connection) + return end - jdat = JSON.decode(jstr) + local jdat = JSON.decode(jstr) local message = '[' .. jdat.num .. '] ' .. jdat.alt .. '\n' .. jdat.img - - send_message(msg.chat.id, message, false, msg.message_id) + sendMessage(msg.chat.id, message, false, msg.message_id) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/plugins/youtube.lua b/plugins/youtube.lua index c09414a..a288951 100755 --- a/plugins/youtube.lua +++ b/plugins/youtube.lua @@ -1,44 +1,44 @@ --- Youtube Plugin for bot based on otouto --- Glanced at https://github.com/yagop/telegram-bot/blob/master/plugins/youtube.lua -local PLUGIN = {} + -- Thanks to @TiagoDanin for writing the original plugin. -PLUGIN.doc = [[ - /youtube - Search videos on YouTube. +local doc = [[ + /youtube + Returns the top result from YouTube. ]] -PLUGIN.triggers = { - '^/youtube', - '^/yt' +local triggers = { + '^/y[ou]*t[ube]*[@'..bot.username..']*' } -function PLUGIN.action(msg) - -- BASE - local input = get_input(msg.text) - if not input then - return send_msg(msg, PLUGIN.doc) - end - --URL API - local url = 'https://www.googleapis.com/youtube/v3/search?' - url = url..'part=snippet'..'&maxResults=4'..'&type=video' - url = url..'&q='..URL.escape(input).."&key=AIzaSyAfe7SI8kwQqaoouvAmevBfKumaLf-3HzI" - -- JSON - local res,code = HTTPS.request(url) - if code ~= 200 then return nil end - local data_JSON = JSON.decode(res) - -- Print Items - local text = "" - for k,item in pairs(data_JSON.items) do - text = text .. item.snippet.title .. '\n' .. 'http://youtu.be/' .. item.id.videoId .. '\n\n' - end - -- END - ERRO 404 - local text_end = text - if text == "" then - text_end = "Not found video" - end - -- Send MSG - send_message(msg.chat.id, text_end) +local action = function(msg) + + local input = msg.text:input() + if not input then + if msg.reply_to_message and msg.reply_to_message.text then + input = msg.reply_to_message.text + else + sendReply(msg, doc) + return + end + end + + local url = 'https://www.googleapis.com/youtube/v3/search?key=AIzaSyAfe7SI8kwQqaoouvAmevBfKumaLf-3HzI&type=video&part=snippet&maxResults=1&q=' .. URL.escape(input) + + local jstr, res = HTTPS.request(url) + if res ~= 200 then + sendReply(msg, config.errors.connection) + return + end + + local jdat = JSON.decode(jstr) + + local message = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId + + sendMessage(msg.chat.id, message, false, msg.message_id) end -return PLUGIN +return { + action = action, + triggers = triggers, + doc = doc +} diff --git a/utilities.lua b/utilities.lua index 75e70f8..7cf5c5f 100755 --- a/utilities.lua +++ b/utilities.lua @@ -1,42 +1,42 @@ -- utilities.lua -- Functions shared among plugins. -function first_word(str, idx) -- get the indexed word in a string +function get_word(str, idx) -- get the indexed word in a string + + local str = str:gsub('^%s*(.-)%s*$', '%1') + str = string.gsub(str, '\n', ' ') - if not string.find(str, ' ') then return str end + if not string.find(str, ' ') then + if idx == 1 then + return str + else + return false + end + 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) + str = str:sub(1, str:find(' ')) + + return str:sub(1, -2) + end -function get_input(text) -- returns string or false - if not string.find(text, ' ') then +function string:input() -- Returns the string after the first space. + if not self:find(' ') then return false end - return string.sub(text, string.find(text, ' ')+1) + return self:sub(self:find(' ')+1) end -function get_target(msg) - - if msg.reply_to_message then - return msg.reply_to_message.from.id - elseif string.find(msg.text, '@') then - local a = string.find(msg.text, '@') - return first_word(string.sub(msg.text, a)) - else - return false - end - -end - -function trim_string(text) -- another alias - return string.gsub(text, "^%s*(.-)%s*$", "%1") + -- I swear, I copied this from PIL, not yago! :) +function string:trim() -- Trims whitespace from a string. + local s = self:gsub('^%s*(.-)%s*$', '%1') + return s end local lc_list = { @@ -68,35 +68,14 @@ local lc_list = { ['!'] = 'ǃ' } -function latcyr(str) +function latcyr(str) -- Replaces letters with corresponding Cyrillic characters. 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 - -function load_data(filename) +function load_data(filename) -- Loads a JSON file as a table. local f = io.open(filename) if not f then @@ -110,11 +89,33 @@ function load_data(filename) end -function save_data(filename, data) +function save_data(filename, data) -- Saves a table to a JSON file. 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 get_coords(input) + + local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) + + local jstr, res = HTTP.request(url) + if res ~= 200 then + return config.errors.connection + 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