otouto v3 is out!

Everything reworked and rewritten.
Antisquig is now a plugin to work with moderation.lua.
The bot can now upload photos, stickers, and other files.
Return values in plugin functions to affect the bot's behavior.
All this and more!
This commit is contained in:
topkecleon 2015-11-24 21:22:04 -05:00
parent 3bd9d5302e
commit cacfea1fa5
64 changed files with 2262 additions and 2890 deletions

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
config.lua
loc/weeb.lua
*.json

618
README.md
View File

@ -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>
<table>
<thead>
<tr>
<td>about.lua</td>
<td>/about</td>
<td>Information about the bot</td>
</tr>
<tr>
<td>admin.lua</td>
<td>/admin [command]</td>
<td>Support for admin</td>
</tr>
<tr>
<td>bandersnatch.lua</td>
<td>/bandersnatch</td>
<td>Benedict Cumberbatch name generator</td>
</tr>
<tr>
<td>bible.lua</td>
<td>/bible &lt;verse&gt;</td>
<td>King James Version</td>
</tr>
<tr>
<td>blacklist.lua</td>
<td>/blacklist</td>
<td>Blacklist</td>
</tr>
<tr>
<td>btc.lua</td>
<td>/btc &lt;currency&gt; [amount]</td>
<td>Bitcoin prices and conversion</td>
</tr>
<tr>
<td>calc.lua</td>
<td>/calc &lt;expression&gt;</td>
<td>Calculator</td>
</tr>
<tr>
<td>cats.lua</td>
<td>/calc &lt;expression&gt;</td>
<td>Solve math expression and convert units</td>
</tr>
<tr>
<td>chatter.lua</td>
<td>@[user_name_bot], [message]</td>
<td>Chatter Bot</td>
</tr>
<tr>
<td>commit.lua</td>
<td>/commit</td>
<td>http://whatthecommit.com</td>
</tr>
<tr>
<td>currency.lua</td>
<td>/cash &lt;from&gt; &lt;to&gt; [amount]</td>
<td>Convert an amount from one currency to another</td>
</tr>
<tr>
<td>dgmp.lua</td>
<td>/index and /listgroups</td>
<td>Plugin to display the groups, descriptions, etc.</td>
</tr>
<tr>
<td>dice.lua</td>
<td>/roll [arg]</td>
<td>Roll a die. Accepts D&D notation</td>
</tr>
<tr>
<td>dogify.lua</td>
<td>/dogify &lt;lines/separatedby/slashes&gt;</td>
<td>Create a doge image</td>
</tr>
<tr>
<td>echo.lua</td>
<td>/echo &lt;text&gt;</td>
<td>Repeat a string</td>
</tr>
<tr>
<td>floodcontrol.lua</td>
<td>No command</td>
<td>Flood control</td>
</tr>
<tr>
<td>fortune.lua</td>
<td>/fortune</td>
<td>Random fortunes</td>
</tr>
<tr>
<td>gImages.lua</td>
<td>/images &lt;query&gt;</td>
<td>Google Images search</td>
</tr>
<tr>
<td>giphy.lua</td>
<td>/giphy [query]</td>
<td>Giphy search or random</td>
</tr>
<tr>
<td>gMaps.lua</td>
<td>/loc &lt;location&gt;</td>
<td>Google Maps search</td>
</tr>
<tr>
<td>gSearch.lua</td>
<td>/google &lt;query&gt;</td>
<td>Google Search</td>
</tr>
<tr>
<td>hackernews.lua</td>
<td>/hackernews</td>
<td>Top stories from Hackernews</td>
</tr>
<tr>
<td>hearthstone.lua</td>
<td>/hearthstone &lt;card&gt;</td>
<td>Information about a Hearthstone card</td>
</tr>
<tr>
<td>help.lua</td>
<td>/help [command]</td>
<td>List commands</td>
</tr>
<tr>
<td>hex.lua</td>
<td>/hex &lt;number&gt;</td>
<td>Convert to and from hexadecimal</td>
</tr>
<tr>
<td>imdb.lua</td>
<td>/imdb &lt;movie | TV series&gt;</td>
<td>IMDb movie/television info</td>
</tr>
<tr>
<td>interactions.lua</td>
<td>No command</td>
<td>Welcome</td>
</tr>
<tr>
<td>kickass.lua</td>
<td>/torrent &lt;query&gt;</td>
<td>Search Kickass Torrents</td>
</tr>
<tr>
<td>lastfm.lua</td>
<td>/lastfm [username]</td>
<td>Get current- or last-played track</td>
</tr>
<tr>
<td>lmgtfy.lua</td>
<td>/lmgtfy</td>
<td>Open page of lmgtfy</td>
</tr>
<tr>
<td>moderation.lua</td>
<td>/modhelp</td>
<td>Support for moderator or admin</td>
</tr>
<tr>
<td>nick.lua</td>
<td>/nick &lt;nickname&gt;</td>
<td>Set your nickname for the bot</td>
</tr>
<tr>
<td>owm.lua</td>
<td>/weather &lt;location&gt;</td>
<td>Temperature and weather conditions</td>
</tr>
<tr>
<td>pokedex.lua</td>
<td>/dex &lt;pokemon&gt;</td>
<td>Pokedex!</td>
</tr>
<tr>
<td>pun.lua</td>
<td>/pun</td>
<td>Puns</td>
</tr>
<tr>
<td>reaction.lua</td>
<td>/reactions</td>
<td>Get a list emoticons</td>
</tr>
<tr>
<td>reddit.lua</td>
<td>/reddit [r/subreddit | query]</td>
<td>Posts from reddit</td>
</tr>
<tr>
<td>remind.lua</td>
<td>/remind &lt;delay&gt; &lt;message&gt;</td>
<td>Set a reminder for yourself or a group</td>
</tr>
<tr>
<td>slap.lua</td>
<td>/slap [victim]</td>
<td>Slap someone!</td>
</tr>
<tr>
<td>spotify.lua</td>
<td>/spotify &lt;music&gt;</td>
<td>Search Spotify</td>
</tr>
<tr>
<td>time.lua</td>
<td>/time &lt;location&gt;</td>
<td>Get the time for a place</td>
</tr>
<tr>
<td>translate.lua</td>
<td>/translate [target lang]</td>
<td>Message to translate</td>
</tr>
<tr>
<td>urbandictionary.lua</td>
<td>/ud &lt;term&gt;</td>
<td>Urban Dictionary search</td>
</tr>
<tr>
<td>weather.lua</td>
<td>/weather &lt;location&gt;</td>
<td>Get the weather for a place</td>
</tr>
<tr>
<td>whoami.lua</td>
<td>/who</td>
<td>Get user and group IDs</td>
</tr>
<tr>
<td>wikipedia.lua</td>
<td>/wiki &lt;topic&gt;</td>
<td>Search Wikipedia</td>
</tr>
<tr>
<td>xkcd.lua</td>
<td>/xkcd [search]</td>
<td>Xkcd strips and alt text</td>
</tr>
<tr>
<td>youtube.lua</td>
<td>/youtube &lt;query&gt;</td>
<td>Search Youtube</td>
</tr>
<tr>
<td>8ball.lua</td>
<td>/8ball</td>
<td>Magic 8-ball.</td>
</tr>
</tbody>
</table>
>>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 &lt;query&gt;
>**Function:** Returns movie information from IMDb.
**calc.lua**
>**Command:** /calc &lt;expression&gt;
>**Function:** Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org.
**bible.lua**
>**Command:** /bible &lt;reference&gt;
>**Function:** Returns a Bible verse. Results provided by biblia.com
>**Aliases:** /b
**urbandictionary.lua**
>**Command:** /urbandictionary &lt;query&gt;
>**Function:** Returns the top definition from Urban Dictionary.
>**Aliases:** /ud, /urban
**time.lua**
>**Command:** /time &lt;query&gt;
>**Function:** Returns the time, date, and timezone for a given location.
**weather.lua**
>**Command:** /weather &lt;query&gt;
>**Function:** Returns the current weather conditions for a given location.
**nick.lua**
>**Command:** /nick &lt;nickname&gt;
>**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 &lt;nDr&gt;
>**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 &lt;target&gt;
>**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 &lt;query&gt;
>**Function:** Returns a Pokedex entry.
>**Aliases:** /dex
**currency.lua**
>**Command:** /cash [amount] &lt;from&gt; to &lt;from&gt;
>**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.

View File

@ -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

152
bot.lua
View File

@ -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

97
config.lua Executable file
View File

@ -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'
}
}

View File

@ -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',
}
}
}

View File

@ -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!')

View File

@ -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)
`<metatable>.__jsonorder`
-------------------------
`__jsonorder` can overwrite the `keyorder` for a specific table.
`<metatable>.__jsontype`
------------------------
`__jsontype` can be either `"array"` or `"object"`. This value is only
checked for empty tables. (The default for empty tables is `"array"`).
`<metatable>.__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
<http://www.inf.puc-rio.br/~roberto/lpeg/>.
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
<http://dkolf.de/src/dkjson-lua.fsl/>.
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.
<!-- This documentation can be parsed using Markdown to generate HTML.
The source code is enclosed in a HTML comment so it won't be displayed
by browsers, but it should be removed from the final HTML file as
it isn't a valid HTML comment (and wastes space).
-->
<!--]==]
--]==]
-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
@ -213,7 +52,7 @@ local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
local strmatch = string.match
local concat = table.concat
local json = { version = "dkjson 2.4" }
local json = { version = "dkjson 2.5" }
if register_global_module_table then
_G[global_module_name] = json
@ -368,7 +207,7 @@ end
local encode2 -- forward declaration
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder)
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
@ -382,31 +221,50 @@ local function addpair (key, value, prev, indent, level, buffer, buflen, tables,
end
buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":"
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder)
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
end
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type (res) == 'string' then
buflen = buflen + 1
buffer[buflen] = res
end
return buflen
end
local function exception(reason, value, state, buffer, buflen, defaultmessage)
defaultmessage = defaultmessage or reason
local handler = state.exception
if not handler then
return nil, defaultmessage
else
state.bufferlen = buflen
local ret, msg = handler (reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
return appendcustom(ret, buffer, state)
end
end
function json.encodeexception(reason, value, state, defaultmessage)
return quotestring("<" .. defaultmessage .. ">")
end
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type (value)
local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return nil, "reference cycle"
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
local state = {
indent = indent, level = level, buffer = buffer,
bufferlen = buflen, tables = tables, keyorder = globalorder
}
state.bufferlen = buflen
local ret, msg = valtojson (value, state)
if not ret then return nil, msg end
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
tables[value] = nil
buflen = state.bufferlen
if type (ret) == 'string' then
buflen = buflen + 1
buffer[buflen] = ret
end
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
@ -428,7 +286,7 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
buffer[buflen] = quotestring (value)
elseif valtype == 'table' then
if tables[value] then
return nil, "reference cycle"
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
@ -441,7 +299,7 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder)
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
@ -463,20 +321,20 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
local v = value[k]
if v then
used[k] = true
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
prev = true -- add a seperator before the next element
end
end
for k,v in pairs (value) do
if not used[k] then
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k,v in pairs (value) do
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
@ -489,7 +347,8 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
end
tables[value] = nil
else
return nil, "type '" .. valtype .. "' is not supported by JSON."
return exception ('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
end
return buflen
end
@ -498,15 +357,18 @@ function json.encode (value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder)
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
if not ret then
error (msg, 2)
elseif oldbuffer then
elseif oldbuffer == buffer then
state.bufferlen = ret
return true
else
state.bufferlen = nil
state.buffer = nil
return concat (buffer)
end
end
@ -534,9 +396,17 @@ local function scanwhite (str, pos)
while true do
pos = strfind (str, "%S", pos)
if not pos then return nil end
if strsub (str, pos, pos + 2) == "\239\187\191" then
local sub2 = strsub (str, pos, pos + 1)
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind (str, "[\n\r]", pos + 2)
if not pos then return nil end
elseif sub2 == "/*" then
pos = strfind (str, "*/", pos + 2)
if not pos then return nil end
pos = pos + 2
else
return pos
end
@ -749,7 +619,9 @@ function json.use_lpeg ()
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
end
local Space = (S" \n\r\t" + P"\239\187\191")^0
local SingleLineComment = P"//" * (1 - S"\n\r")^0
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
local PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
@ -840,4 +712,3 @@ end
return json
-->

View File

@ -1,47 +0,0 @@
local doc = [[
/example <required> [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.
]]--

6
launch.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
while true; do
lua bot.lua
sleep 5s
done

View File

@ -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'
}

View File

@ -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
}

View File

@ -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
}

46
plugins/antisquig.lua Executable file
View File

@ -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
}

View File

@ -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
}

View File

@ -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 <verse>
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 <reference>
Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com.
]]
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
}

View File

@ -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
}

View File

@ -1,66 +0,0 @@
local PLUGIN = {}
PLUGIN.doc = [[
/btc <currency> [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

View File

@ -1,30 +1,38 @@
local PLUGIN = {}
PLUGIN.doc = [[
local doc = [[
/calc <expression>
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
}

View File

@ -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('<img src="(.*)">')
str = str:match('<img src="(.*)">')
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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,54 +1,54 @@
local doc = [[
/cash <from> <to> [amount]
Convert an amount from one currency to another.
Example: /cash USD EUR 5
/cash [amount] <from> to <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('<span class=bld>(.*) %u+</span>')
if not str then return send_msg(msg, config.locale.errors.results) end
result = string.format('%.2f', str)
str = str:match('<span class=bld>(.*) %u+</span>')
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
}

View File

@ -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
}

View File

@ -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 <nDr>
Returns a set of dice rolls, where n is the number of rolls and r is the range. If only a range is given, returns only one roll.
]]
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
}

View File

@ -1,29 +0,0 @@
local PLUGIN = {}
PLUGIN.doc = [[
/dogify <lines/separatedby/slashes>
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

View File

@ -1,23 +1,26 @@
local PLUGIN = {}
PLUGIN.doc = [[
local doc = [[
/echo <text>
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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,78 +1,58 @@
local PLUGIN = {}
PLUGIN.doc = [[
/images <query>
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 <query>
Returns a randomized top result from Google Images. Safe search is enabled by default; use "/insfw" to disable it. NSFW results will not display an image preview.
]]
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
}

View File

@ -1,45 +1,37 @@
local PLUGIN = {}
PLUGIN.doc = [[
/loc <location>
Sends location data for query, taken from Google Maps. Works for countries, cities, landmarks, etc.
local doc = [[
/location <query>
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
}

View File

@ -1,65 +1,62 @@
local PLUGIN = {}
PLUGIN.doc = [[
local doc = [[
/google <query>
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('&amp;', '&') -- blah
send_msg(msg, message)
sendReply(msg, message)
end
return PLUGIN
return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -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

29
plugins/greetings.lua Executable file
View File

@ -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
}

View File

@ -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
}

View File

@ -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 <card>
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
}

View File

@ -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: <required> [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: <required> [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
}

View File

@ -1,29 +0,0 @@
local PLUGIN = {}
PLUGIN.doc = [[
/hex <number>
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

View File

@ -1,36 +1,37 @@
local PLUGIN = {}
PLUGIN.doc = [[
/imdb <movie | TV series>
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 <query>
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
}

View File

@ -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

View File

@ -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 <query>
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('&amp;', '&')
send_msg(msg, message)
end
return {
doc = doc,
triggers = triggers,
action = action,
typing = true
}

View File

@ -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 <username>
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
}

View File

@ -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

View File

@ -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
}

View File

@ -1,42 +1,42 @@
local doc = [[
/nick <nickname>
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
}

View File

@ -1,40 +0,0 @@
local PLUGIN = {}
PLUGIN.doc = [[
/weather <location>
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

View File

@ -1,42 +1,46 @@
local PLUGIN = {}
PLUGIN.doc = [[
/dex <pokemon>
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 <query>
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
}

View File

@ -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
}

View File

@ -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
}

37
plugins/reactions.lua Executable file
View File

@ -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
}

View File

@ -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
}

View File

@ -1,70 +0,0 @@
reminders = {}
local doc = [[
/remind <delay> <message>
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
}

View File

@ -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
}

View File

@ -1,52 +0,0 @@
-- Spotify Plugin for bot based on otouto
-- ByTiagoDanin - Telegram.me/tiagodanin
local PLUGIN = {}
PLUGIN.doc = [[
/spotify <music>
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

View File

@ -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 <location>
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 .. '&timestamp='..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
}

View File

@ -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
}

View File

@ -1,48 +1,50 @@
local PLUGIN = {}
PLUGIN.doc = [[
/ud <term>
Returns the first definition for a given term from Urban Dictionary.
local doc = [[
/urbandictionary <query>
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
}

View File

@ -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 <location>
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
}

View File

@ -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
}

View File

@ -1,70 +1,67 @@
local doc = [[
/wiki <topic>
Search Wikipedia for a relevant article and return its summary.
/wikipedia <query>
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('<h2>')
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
}

View File

@ -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
}

View File

@ -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 <query>
Search videos on YouTube.
local doc = [[
/youtube <query>
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
}

View File

@ -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