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 ##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> >>Sets your last.fm username. Use /fmset - to delete it.
<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>
**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 ##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. `./launch.sh`
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.
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. You may also start the bot manually with
"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:
`lua bot.lua` `lua bot.lua`
though that will not cause it to automatically restart.
##Support ##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 local BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key
-- 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 .. '/' 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 dat, res = HTTPS.request(url)
local tab = JSON.decode(dat) local tab = JSON.decode(dat)
if res ~= 200 then if res ~= 200 then
print('Connection error.') return false, res
return false
end end
if not tab.ok then if not tab.ok then
print(tab.description) return false, tab.description
return false
end end
return tab return tab
end end
function get_me() getMe = function()
local url = BASE_URL .. 'getMe' local url = BASE_URL .. '/getMe'
return send_request(url) return sendRequest(url)
end 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 if offset then
url = url .. '&offset=' .. offset url = url .. '&offset=' .. offset
end end
return send_request(url) return sendRequest(url)
end 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 if disable_web_page_preview == true then
url = url .. '&disable_web_page_preview=true' 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 url = url .. '&reply_to_message_id=' .. reply_to_message_id
end end
return send_request(url) return sendRequest(url)
end end
function send_chat_action(chat_id, action) sendReply = function(msg, text)
local url = BASE_URL .. 'sendChatAction?chat_id=' .. chat_id .. '&action=' .. action return sendMessage(msg.chat.id, text, true, msg.message_id)
return send_request(url)
end 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 if reply_to_message_id then
url = url .. '&reply_to_message_id=' .. reply_to_message_id url = url .. '&reply_to_message_id=' .. reply_to_message_id
end end
return send_request(url) return sendRequest(url)
end 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 end

152
bot.lua
View File

@ -1,115 +1,95 @@
HTTP = require('socket.http') HTTP = require('socket.http')
HTTPS= require('ssl.https') HTTPS = require('ssl.https')
URL = require('socket.url') URL = require('socket.url')
JSON = require('dkjson') 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 config = dofile("config.lua") -- Load configuration file.
if floodcontrol[-msg.chat.id] then -- This stuff is useful for the moderation plugin to not be completely unusable when floodcontrol is activated. dofile("bindings.lua") -- Load Telegram bindings.
msg.flood = msg.chat.id dofile("utilities.lua") -- Load miscellaneous and cross-plugin functions.
msg.chat.id = msg.from.id
end
if msg.new_chat_participant and msg.new_chat_participant.id == bot.id then bot = nil
msg.text = '/about' while not bot do -- Get bot info and retry if unable to connect.
end -- If bot is added to a group, send the about message. bot = getMe()
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()
end end
bot = bot.result bot = bot.result
print('Loading plugins...') plugins = {} -- Load plugins.
plugins = {}
for i,v in ipairs(config.plugins) do for i,v in ipairs(config.plugins) do
local p = dofile('plugins/'..v) local p = dofile("plugins/"..v)
table.insert(plugins, p) table.insert(plugins, p)
end end
print('Plugins loaded: ' .. #plugins .. '. Generating help message...') print('@'..bot.username .. ', AKA ' .. bot.first_name ..' ('..bot.id..')')
help_message = '' -- Generate a random seed and "pop" the first random number. :)
for i,v in ipairs(plugins) do math.randomseed(os.time())
if v.doc then math.random()
local a = string.sub(v.doc, 1, string.find(v.doc, '\n')-1)
help_message = help_message .. ' - ' .. a .. '\n'
end
end
print('@'.. bot.username ..', AKA '.. bot.first_name ..' ('.. bot.id ..')') 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 is_started = true -- whether the bot should be running or not.
end end
bot_init() on_msg_receive = function(msg) -- The fn run whenever a message is received.
last_update = 0
last_cron = os.time()
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) msg.chat.id_str = tostring(msg.chat.id)
if not res then msg.from.id_str = tostring(msg.from.id)
print('Error getting updates.') msg.text_lower = msg.text:lower()
else
for i,v in ipairs(res.result) do for i,v in ipairs(plugins) do
if v.update_id > last_update then for k,w in pairs(v.triggers) do
last_update = v.update_id if string.match(msg.text_lower, w) then
on_msg_receive(v.message) 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 end
end end
-- cron-like thing end
-- run PLUGIN.cron() every five seconds
if last_cron < os.time() - 5 then bot_init() -- Actually start the script. Run the bot_init function.
for k,v in pairs(plugins) do
if v.cron then while is_started do -- Start a loop while the bot should be running.
a,b = pcall(function() v.cron() end)
if not a then print(b) end 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
end end
last_cron = os.time() last_cron = os.time() -- And finally, update the variable.
end end
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: -- Module options:
local always_try_using_lpeg = true local always_try_using_lpeg = true
local register_global_module_table = false local register_global_module_table = false
local global_module_name = 'json' local global_module_name = 'json'
--[==[ --[==[
David Kolf's JSON module for Lua 5.1/5.2 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") For the documentation see the corresponding readme.txt or visit
<http://dkolf.de/src/dkjson-lua.fsl/>.
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
-------
You can contact the author by sending an e-mail to 'david' at the You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'. 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 Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the 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 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. 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: -- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = 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 strmatch = string.match
local concat = table.concat local concat = table.concat
local json = { version = "dkjson 2.4" } local json = { version = "dkjson 2.5" }
if register_global_module_table then if register_global_module_table then
_G[global_module_name] = json _G[global_module_name] = json
@ -368,7 +207,7 @@ end
local encode2 -- forward declaration 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) local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON." 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 end
buffer[buflen+1] = quotestring (key) buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":" 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 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 valtype = type (value)
local valmeta = getmetatable (value) local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson local valtojson = valmeta and valmeta.__tojson
if valtojson then if valtojson then
if tables[value] then if tables[value] then
return nil, "reference cycle" return exception('reference cycle', value, state, buffer, buflen)
end end
tables[value] = true tables[value] = true
local state = { state.bufferlen = buflen
indent = indent, level = level, buffer = buffer,
bufferlen = buflen, tables = tables, keyorder = globalorder
}
local ret, msg = valtojson (value, state) 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 tables[value] = nil
buflen = state.bufferlen buflen = appendcustom(ret, buffer, state)
if type (ret) == 'string' then
buflen = buflen + 1
buffer[buflen] = ret
end
elseif value == nil then elseif value == nil then
buflen = buflen + 1 buflen = buflen + 1
buffer[buflen] = "null" buffer[buflen] = "null"
@ -428,7 +286,7 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
buffer[buflen] = quotestring (value) buffer[buflen] = quotestring (value)
elseif valtype == 'table' then elseif valtype == 'table' then
if tables[value] then if tables[value] then
return nil, "reference cycle" return exception('reference cycle', value, state, buffer, buflen)
end end
tables[value] = true tables[value] = true
level = level + 1 level = level + 1
@ -441,7 +299,7 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
buflen = buflen + 1 buflen = buflen + 1
buffer[buflen] = "[" buffer[buflen] = "["
for i = 1, n do 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 not buflen then return nil, msg end
if i < n then if i < n then
buflen = buflen + 1 buflen = buflen + 1
@ -463,20 +321,20 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
local v = value[k] local v = value[k]
if v then if v then
used[k] = true 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 prev = true -- add a seperator before the next element
end end
end end
for k,v in pairs (value) do for k,v in pairs (value) do
if not used[k] then 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 if not buflen then return nil, msg end
prev = true -- add a seperator before the next element prev = true -- add a seperator before the next element
end end
end end
else -- unordered else -- unordered
for k,v in pairs (value) do 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 if not buflen then return nil, msg end
prev = true -- add a seperator before the next element prev = true -- add a seperator before the next element
end end
@ -489,7 +347,8 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
end end
tables[value] = nil tables[value] = nil
else 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 end
return buflen return buflen
end end
@ -498,15 +357,18 @@ function json.encode (value, state)
state = state or {} state = state or {}
local oldbuffer = state.buffer local oldbuffer = state.buffer
local buffer = oldbuffer or {} local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint() updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0, 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 if not ret then
error (msg, 2) error (msg, 2)
elseif oldbuffer then elseif oldbuffer == buffer then
state.bufferlen = ret state.bufferlen = ret
return true return true
else else
state.bufferlen = nil
state.buffer = nil
return concat (buffer) return concat (buffer)
end end
end end
@ -534,9 +396,17 @@ local function scanwhite (str, pos)
while true do while true do
pos = strfind (str, "%S", pos) pos = strfind (str, "%S", pos)
if not pos then return nil end 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 -- UTF-8 Byte Order Mark
pos = pos + 3 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 else
return pos return pos
end end
@ -749,7 +619,9 @@ function json.use_lpeg ()
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
end 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 PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
@ -840,4 +712,3 @@ end
return json 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 = {} local doc = [[
PLUGIN.doc = [[
/about /about
Information about the bot. Get info about the bot.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/about', ''
'^/info'
} }
function PLUGIN.action(msg) local action = function(msg)
local message = [[ local message = config.about_text .. '\nBased on otouto v'..version..' by topkecleon.\notouto v3 is licensed under the GPLv2.\ntopkecleon.github.io/otouto'
I am ]] .. bot.first_name .. [[: a plugin-wielding, multi-purpose Telegram bot.
Send /help for a list of commands.
Based on otouto v]] .. VERSION .. [[ by @topkecleon. if msg.new_chat_participant and msg.new_chat_participant.id == bot.id then
otouto v2 is licensed under the GPLv2. sendMessage(msg.chat.id, message)
topkecleon.github.io/otouto return
elseif string.match(msg.text_lower, '^/about[@'..bot.username..']*') then
sendReply(msg, message)
return
end
Join the update/news channel! return true
telegram.me/otouto
]] -- Please do not remove this message. ^.^
send_message(msg.chat.id, message, true)
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,45 +1,75 @@
local PLUGIN = {} local triggers = {
'^/admin[@'..bot.username..']*'
PLUGIN.triggers = {
'^/admin '
} }
function PLUGIN.action(msg) local commands = {
if msg.date < os.time() - 1 then return end ['run'] = function(cmd)
local cmd = cmd:input()
local input = get_input(msg.text) if not cmd then
return 'Please enter a command to run.'
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)
end end
local output = io.popen(output) return io.popen(cmd):read('*all')
message = output:read('*all') end,
output:close()
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() bot_init()
message = 'Bot reloaded!' return 'Bot reloaded!'
end,
elseif string.lower(first_word(input)) == 'halt' then
['halt'] = function(cmd)
is_started = false is_started = false
message = 'Shutting down...' return 'Stopping bot!'
end,
['error'] = function(cmd)
error('Intentional test error.')
end 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 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 = {} local doc = [[
PLUGIN.doc = [[
/bandersnatch /bandersnatch
This is a Benedict Cumberbatch name generator. Shun the frumious Bandersnatch.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/bandersnatch', '^/bandersnatch[@'..bot.username..']*',
'^/bc$' '^/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 if math.random(10) == 10 then
message = PLUGIN.fullnames[math.random(#PLUGIN.fullnames)] message = fullnames[math.random(#fullnames)]
else else
message = PLUGIN.firstnames[math.random(#PLUGIN.firstnames)] .. ' ' .. PLUGIN.lastnames[math.random(#PLUGIN.lastnames)] message = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)]
end end
send_msg(msg, message) sendReply(msg, message)
end 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 = [[ local doc = [[
/bible <verse> /bible <reference>
Returns a verse from the bible, King James Version. Use a standard or abbreviated reference (John 3:16, Jn3:16). Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com.
http://biblia.com
]] ]]
PLUGIN.triggers = { local triggers = {
'^/bible', '^/b[ible]*[@'..bot.username..']*$',
'^/b ' '^/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 if not input then
return send_msg(msg, PLUGIN.doc) sendReply(msg, doc)
return
end 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) local message, res = HTTP.request(url)
if res ~= 200 then if message:len() == 0 then
message = config.locale.errors.connection 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 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 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 = { local triggers = {
'^/blacklist', ''
'^/listofcolor'
} }
local action = function(msg) local action = function(msg)
if not config.admins[msg.from.id] then local blacklist = load_data('blacklist.json')
return send_msg(msg, 'Permission denied.')
if blacklist[msg.from.id_str] then
return -- End if the sender is blacklisted.
end end
local name if not string.match(msg.text_lower, '^/blacklist') then
local input = get_input(msg.text) 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 not input then
if msg.reply_to_message then if msg.reply_to_message then
input = msg.reply_to_message.from.id input = tostring(msg.reply_to_message.from.id)
name = msg.reply_to_message.from.first_name
else 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
end end
local id = tostring(input) if blacklist[input] then
if not name then name = id end blacklist[input] = nil
sendReply(msg, input .. ' has been removed from the blacklist.')
if blacklist[id] then
blacklist[id] = nil
send_message(msg.chat.id, name .. ' has been removed from the blacklist.')
else else
blacklist[id] = true blacklist[input] = true
send_message(msg.chat.id, name .. ' has been blacklisted.') sendReply(msg, input .. ' has been added to the blacklist.')
end end
save_data('blacklist.json', blacklist) save_data('blacklist.json', blacklist)
end end
return { return {
doc = doc, action = action,
triggers = triggers, triggers = triggers
action = action
} }

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 = {} local doc = [[
PLUGIN.doc = [[
/calc <expression> /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 = { local triggers = {
'^/calc' '^/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 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 end
local url = 'http://api.mathjs.org/v1/?expr=' .. URL.escape(input) local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input)
local message, res = HTTP.request(url)
if res ~= 200 then local ans, res = HTTPS.request(url)
return send_msg(msg, config.locale.errors.syntax) if not ans then
sendReply(msg, config.errors.connection)
return
end end
send_msg(msg, message) sendReply(msg, ans)
end 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 = [[ local doc = [[
/cat /cat
Get a cat pic! Returns a cat!
]] ]]
local triggers = { local triggers = {
'^/cats?' '^/cat[@'..bot.username..']*$'
} }
local action = function(msg) local action = function(msg)
@ -14,20 +19,20 @@ local action = function(msg)
url = url .. '&api_key=' .. config.thecatapi_key url = url .. '&api_key=' .. config.thecatapi_key
end end
local jstr, res = HTTP.request(url) local str, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end 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 end
return { return {
doc = doc,
triggers = triggers,
action = action, 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 = {} local triggers = {
''
PLUGIN.typing = true
PLUGIN.triggers = {
'^@' .. bot.username .. ', ',
'^' .. bot.first_name .. ', '
} }
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) local jstr, res = HTTP.request(url)
if res ~= 200 then 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 end
local jdat = JSON.decode(jstr) 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 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. -- Let's clean up the response a little. Capitalization & punctuation.
filter = { local filter = {
['%aimi?%aimi?'] = bot.first_name, ['%aimi?%aimi?'] = bot.first_name,
['^%s*(.-)%s*$'] = '%1', ['^%s*(.-)%s*$'] = '%1',
['^%l'] = string.upper, ['^%l'] = string.upper,
@ -45,8 +45,11 @@ function PLUGIN.action(msg)
message = message .. '.' message = message .. '.'
end end
send_message(msg.chat.id, message) sendMessage(msg.chat.id, message)
end 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 /commit
http://whatthecommit.com. Returns a commit message from whatthecommit.com.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/commit' '^/commit[@'..bot.username..']*'
} }
function PLUGIN.action(msg) local commits = {
math.randomseed(os.time())
send_msg(msg, PLUGIN.commits[math.random(#PLUGIN.commits)])
end
PLUGIN.commits = {
"One does not simply merge into master", "One does not simply merge into master",
"Merging the merge", "Merging the merge",
"Another bug bites the dust", "Another bug bites the dust",
@ -417,4 +412,14 @@ PLUGIN.commits = {
"One little whitespace gets its very own commit! Oh, life is so erratic!" "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 = [[ local doc = [[
/cash <from> <to> [amount] /cash [amount] <from> to <to>
Convert an amount from one currency to another. Example: /cash 5 USD to EUR
Example: /cash USD EUR 5 Returns exchange rates for various currencies.
]] ]]
local triggers = { local triggers = {
'^/cash' '^/cash[@'..bot.username..']*'
} }
local action = function(msg) local action = function(msg)
local input = get_input(msg.text) local input = msg.text:upper()
if not input then if not input:match('%a%a%a TO %a%a%a') then
return send_msg(msg, doc) sendReply(msg, doc)
return
end 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 url = 'https://www.google.com/finance/converter'
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
if from ~= to then if from ~= to then
local url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount local url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount
local str, res = HTTPS.request(url)
local str, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local str = str:match('<span class=bld>(.*) %u+</span>') str = str:match('<span class=bld>(.*) %u+</span>')
if not str then return send_msg(msg, config.locale.errors.results) end if not str then
result = string.format('%.2f', str) sendReply(msg, config.errors.results)
return
end
result = str:format('%.2f')
end end
local message = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to local message = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to
send_msg(msg, message) sendReply(msg, message)
end end
return { return {
doc = doc, action = action,
triggers = triggers, 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 = {} local doc = [[
/roll <nDr>
PLUGIN.doc = [[ 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.
/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.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/roll' '^/roll[@'..bot.username..']*'
} }
function PLUGIN.action(msg) local action = function(msg)
math.randomseed(os.time()) local input = msg.text_lower:input()
local input = get_input(msg.text)
if not input then 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 else
input = string.upper(input) sendReply(msg, doc)
return
end end
if tonumber(input) then count = tonumber(count)
range = tonumber(input) range = tonumber(range)
rolls = 1
elseif string.find(input, 'D') then if range < 2 then
local dloc = string.find(input, 'D') sendReply(msg, 'The minimum range is 2.')
if dloc == 1 then return
rolls = 1 end
else if range > 1000 or count > 1000 then
rolls = string.sub(input, 1, dloc-1) sendReply(msg, 'The maximum range and count are 1000.')
end return
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)
end end
if tonumber(rolls) == 1 then local message = ''
results = 'Random (1-' .. range .. '):\t' for i = 1, count do
elseif tonumber(rolls) > 1 then message = message .. math.random(range) .. '\t'
results = rolls .. 'D' .. range .. ':\n'
else
return send_msg(msg, config.locale.errors.syntax)
end end
if tonumber(range) < 2 then sendReply(msg, message)
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)
end 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 = {} local doc = [[
PLUGIN.doc = [[
/echo <text> /echo <text>
Repeat a string. Repeat a string of text!
]] ]]
PLUGIN.triggers = { local triggers = {
'^/echo' '^/echo[@'..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 input then
sendReply(msg, latcyr(input))
else
sendReply(msg, doc)
end end
send_message(msg.chat.id, latcyr(input))
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,17 +1,14 @@
local PLUGIN = {} local doc = [[
PLUGIN.doc = [[
/8ball /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 = { local triggers = {
'^/helix',
'^/8ball', '^/8ball',
'y/n%p?$' 'y/n%p?$'
} }
PLUGIN.answers = { local ball_answers = {
"It is certain.", "It is certain.",
"It is decidedly so.", "It is decidedly so.",
"Without a doubt.", "Without a doubt.",
@ -35,24 +32,33 @@ PLUGIN.answers = {
"There is a time and place for everything, but not now." "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) local action = function(msg)
math.randomseed(os.time())
if msg.reply_to_message then if msg.reply_to_message then
msg = msg.reply_to_message msg = msg.reply_to_message
end end
if string.match(string.lower(msg.text), 'y/n') then local message
message = PLUGIN.yesno[math.random(#PLUGIN.yesno)]
if msg.text:match('y/n%p?$') then
message = yesno_answers[math.random(#yesno_answers)]
else else
message = PLUGIN.answers[math.random(#PLUGIN.answers)] message = ball_answers[math.random(#ball_answers)]
end end
send_msg(msg, message) sendReply(msg, message)
end 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 = { local triggers = {
'/floodcontrol' ''
} }
local action = function(msg) local action = function(msg)
local input, output if floodcontrol[-msg.chat.id] then
return
if msg.from.id ~= 100547061 then -- Only acknowledge Liberbot.
if not config.admins[msg.from.id] then -- or an admin. :)
return
end
end 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 input.duration = 600
end end
floodcontrol[input.groupid] = os.time() + input.duration floodcontrol[input.groupid] = os.time() + input.duration
local s = input.groupid .. ' silenced for ' .. input.duration .. ' seconds.' print(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.
end end
@ -40,7 +46,7 @@ local cron = function()
end end
return { return {
triggers = triggers,
action = action, action = action,
triggers = triggers,
cron = cron cron = cron
} }

View File

@ -1,22 +1,30 @@
local PLUGIN = {} -- Requires that the "fortune" program is installed on your computer.
PLUGIN.doc = [[ local s = io.popen('fortune'):read('*all')
/fortune if s:match('fortune: command not found') then
Get a random fortune from the UNIX fortune program. print('fortune is not installed on this computer.')
]] print('fortune.lua will not be enabled.')
return
PLUGIN.triggers = {
'^/fortune',
'^/f$'
}
function PLUGIN.action(msg)
local output = io.popen('fortune')
message = ''
for l in output:lines() do
message = message .. l .. '\n'
end
send_msg(msg, message)
end 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 = {} local doc = [[
/image <query>
PLUGIN.doc = [[ 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.
/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.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/images?', '^/i[mage]*[nsfw]*[@'..bot.username..']*$',
'^/img', '^/i[mage]*[nsfw]*[@'..bot.username..']* '
'^/i ',
'^/insfw'
} }
PLUGIN.exts = { local action = function(msg)
'.png$',
'.jpg$',
'.jpeg$',
'.jpe$',
'.gif$'
}
function PLUGIN.action(msg) local input = msg.text:input()
if not input then
local url = 'http://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8' if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
if not string.match(msg.text, '^/insfw ') then else
url = url .. '&safe=active' sendReply(msg, doc)
return
end
end end
local input = get_input(msg.text) local url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8'
if not input then
if msg.reply_to_message then if not string.match(msg.text, '^/i[mage]*nsfw') then
msg = msg.reply_to_message url = url .. '&safe=active'
input = msg.text
else
return send_msg(msg, PLUGIN.doc)
end
end end
url = url .. '&q=' .. URL.escape(input) url = url .. '&q=' .. URL.escape(input)
local jstr, res = HTTP.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if #jdat.responseData.results < 1 then if #jdat.responseData.results < 1 then
send_msg(msg, config.locale.errors.results) sendReply(msg, config.errors.results)
return return
end end
local is_real = false local i = math.random(#jdat.responseData.results)
local counter = 0 local result = jdat.responseData.results[i].url
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
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 end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,45 +1,37 @@
local PLUGIN = {} local doc = [[
/location <query>
PLUGIN.doc = [[ Returns a location from Google Maps.
/loc <location>
Sends location data for query, taken from Google Maps. Works for countries, cities, landmarks, etc.
]] ]]
PLUGIN.triggers = { triggers = {
'^/loc' '^/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 not input then
if msg.reply_to_message then if msg.reply_to_message and msg.reply_to_message.text then
msg = msg.reply_to_message input = msg.reply_to_message.text
input = msg.text
else else
return send_msg(msg, PLUGIN.doc) sendReply(msg, doc)
return
end end
end end
local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) local coords = get_coords(input)
local jstr, res = HTTP.request(url) if type(coords) == 'string' then
sendReply(msg, coords)
if res ~= 200 then return
return send_msg(msg, config.locale.errors.connection)
end end
local jdat = JSON.decode(jstr) sendLocation(msg.chat.id, coords.lat, coords.lon, msg.message_id)
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)
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,65 +1,62 @@
local PLUGIN = {} local doc = [[
PLUGIN.doc = [[
/google <query> /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 = { local triggers = {
'^/g ', '^/g[oogle]*[nsfw]*[@'..bot.username..']*$',
'^/g$', '^/g[oogle]*[nsfw]*[@'..bot.username..']* '
'^/google',
'^/gnsfw'
} }
function PLUGIN.action(msg) local action = function(msg)
local url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0' local input = msg.text:input()
if not string.match(msg.text, '^/gnsfw ') then
url = url .. '&safe=active'
end
if not msg.chat.title then
url = url .. '&rsz=8'
end
local input = get_input(msg.text)
if not input then if not input then
if msg.reply_to_message then if msg.reply_to_message and msg.reply_to_message.text then
msg = msg.reply_to_message input = msg.reply_to_message.text
input = msg.text
else else
return send_msg(msg, PLUGIN.doc) sendReply(msg, doc)
return
end end
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) url = url .. '&q=' .. URL.escape(input)
local jstr, res = HTTP.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if #jdat.responseData.results < 1 then if #jdat.responseData.results < 1 then
return send_msg(msg, config.locale.errors.results) sendReply(msg, config.errors.results)
return
end end
local message = '' local message = ''
for i,v in ipairs(jdat.responseData.results) do
for i = 1, #jdat.responseData.results do message = message .. jdat.responseData.results[i].titleNoFormatting .. '\n ' .. jdat.responseData.results[i].unescapedUrl .. '\n'
local result_url = jdat.responseData.results[i].unescapedUrl
local result_title = jdat.responseData.results[i].titleNoFormatting
message = message .. ' - ' .. result_title ..'\n'.. result_url .. '\n'
end end
local message = message:gsub('&amp;', '&') -- blah sendReply(msg, message)
send_msg(msg, message)
end 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 = {} local doc = [[
PLUGIN.typing = true -- usually takes a few seconds to load
PLUGIN.doc = [[
/hackernews /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 = { local triggers = {
'^/hackernews', '^/hackernews[@'..bot.username..']*',
'^/hn$' '^/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 message = ''
local jstr = HTTPS.request('https://hacker-news.firebaseio.com/v0/topstories.json') for i = 1, res_count do
local stories = JSON.decode(jstr) local res_url = 'https://hacker-news.firebaseio.com/v0/item/' .. jdat[i] .. '.json'
jstr, res = HTTPS.request(res_url)
local limit = 4 if res ~= 200 then
if msg.chat.id == msg.from.id then sendReply(msg, config.errors.connection)
limit = 8 return
end
local res_jdat = JSON.decode(jstr)
message = message .. res_jdat.title .. '\n ' .. res_jdat.url .. '\n'
end end
for i = 1, limit do sendReply(msg, message)
url = 'https://hacker-news.firebaseio.com/v0/item/'..stories[i]..'.json'
jstr = HTTPS.request(url)
jdat = JSON.decode(jstr)
message = message .. jdat.title .. '\n' .. jdat.url .. '\n'
end
send_msg(msg, message)
end 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 = [[ local help_text = 'Available commands:\n'
/help [command]
Get list of basic information for all commands, or more detailed documentation on a specified command.
]]
PLUGIN.triggers = { for i,v in ipairs(plugins) do
'^/help', if v.doc then
'^/h$', local a = string.sub(v.doc, 1, string.find(v.doc, '\n')-1)
'^/start$' 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) local action = function(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]
]]
if msg.from.id ~= msg.chat.id then if msg.from.id ~= msg.chat.id then
if not send_message(msg.from.id, message, true, msg.message_id) then if sendMessage(msg.from.id, help_text) then
return send_msg(msg, message) -- Unable to PM user who hasn't PM'd first. sendReply(msg, 'I have sent you the requested information in a private message.')
else
sendReply(msg, help_text)
end end
return send_msg(msg, 'I have sent you the requested information in a private message.')
else else
return send_msg(msg, message) sendReply(msg, help_text)
end end
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 = {} local doc = [[
/imdb <query>
PLUGIN.doc = [[ Returns an IMDb entry.
/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.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/imdb' '^/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 not input then
if msg.reply_to_message then if msg.reply_to_message and msg.reply_to_message.text then
msg = msg.reply_to_message input = msg.reply_to_message.text
input = msg.text
else else
return send_msg(msg, PLUGIN.doc) sendReply(msg, doc)
return
end end
end end
local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input) 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 local jstr, res = HTTP.request(url)
return send_msg(msg, config.locale.errors.connection) if res ~= 200 then
sendReply(msg, config.errors.connection)
return
end end
local jdat = JSON.decode(jstr)
if jdat.Response ~= 'True' then if jdat.Response ~= 'True' then
return send_msg(msg, jdat.Error) sendReply(msg, config.errors.results)
return
end end
local message = jdat.Title ..' ('.. jdat.Year ..')\n' local message = jdat.Title ..' ('.. jdat.Year ..')\n'
@ -38,8 +39,12 @@ function PLUGIN.action(msg)
message = message .. jdat.Plot .. '\n' message = message .. jdat.Plot .. '\n'
message = message .. 'http://imdb.com/title/' .. jdat.imdbID message = message .. 'http://imdb.com/title/' .. jdat.imdbID
send_msg(msg, message) sendReply(msg, message)
end 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 = [[ local doc = [[
/lastfm [username] /lastfm
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. /np [username]
"/fmset username" will configure your last.fm 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 = { local triggers = {
'^/lastfm', '^/lastfm[@'..bot.username..']*',
'^/np$', '^/np[@'..bot.username..']*',
'^/fm$', '^/fmset[@'..bot.username..']*'
'^/fmset'
} }
function PLUGIN.action(msg) local action = function(msg)
if msg.text:match('^/fmset') then lastfm = load_data('lastfm.json')
local input = msg.text:input()
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 .. '.')
if string.match(msg.text, '^/lastfm') then
sendReply(msg, doc:sub(10))
return return
elseif string.match(msg.text, '^/fmset') then
end if not input then
sendReply(msg, doc)
local base_url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user=' elseif input == '-' then
lastfm[msg.from.id_str] = nil
local input = get_input(msg.text) sendReply(msg, 'Your last.fm username has been forgotten.')
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
else 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 end
save_data('lastfm.json', lastfm)
return
end 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 if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.error then if jdat.error then
return send_msg(msg, 'Please provide a valid last.fm username.\nYou can set yours with /fmset.') sendReply(msg, jdat.error)
end return
if not jdat.recenttracks.track then
return send_msg(msg, 'No history for that user.')
end end
local jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track local jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track
if not jdat then
local message = '🎵 ' .. msg.from.first_name .. ' last listened to:\n' sendReply(msg, 'No history for this user.')
if jdat['@attr'] and jdat['@attr'].nowplaying then return
message = '🎵 ' .. msg.from.first_name .. ' is listening to:\n'
end end
local name = jdat.name or 'Unknown' local message = input or msg.from.first_name
local artist 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 if jdat.artist then
artist = jdat.artist['#text'] artist = jdat.artist['#text']
else
artist = 'Unknown'
end end
local message = message .. name .. ' - ' .. artist message = message .. title .. ' - ' .. artist
sendReply(msg, message)
send_message(msg.chat.id, message)
end 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. local commands = {
"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.
Your bot should have privacy mode disabled. ['^/modhelp[@'..bot.username..']*$'] = function(msg)
]]-- local moddat = load_data('moderation.json')
local help = {} if not moddat[msg.chat.id_str] then
return config.errors.moderation
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
end end
end
local target = get_target(msg) local message = [[
if not target then /modlist - List the moderators and administrators of this group.
return send_message(msg.chat.id, 'No one to remove.\nBots must be removed by username.') Moderator commands:
end /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 message
return send_message(msg.chat.id, 'Cannot remove a moderator.')
end
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 local moddat = load_data('moderation.json')
target = msg.reply_to_message.from.first_name
end
send_message(config.moderation.realm, target .. ' banned from ' .. msg.chat.title .. ' by ' .. msg.from.first_name .. '.') if not moddat[msg.chat.id_str] then
return config.errors.moderation
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
end end
end
local target = get_target(msg) local message = ''
if not target then
return send_message(msg.chat.id, 'No one to remove.\nBots must be removed by username.')
end
if msg.reply_to_message and data[tostring(msg.chat.id)][tostring(msg.reply_to_message.from.id)] then for k,v in pairs(moddat[msg.chat.id_str]) do
return send_message(msg.chat.id, 'Cannot remove a moderator.') message = message .. ' - ' .. v .. ' (' .. k .. ')\n'
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.')
end 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 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) local action = function(msg)
for k,v in pairs(modactions) do
if string.match(msg.text, v.trigger) then for k,v in pairs(commands) do
return v.action(msg) if string.match(msg.text, k) then
local output = v(msg)
if output then
sendReply(msg, output)
end
return
end end
end end
end end
return { return {
triggers = triggers, action = action,
action = action triggers = triggers
} }

View File

@ -1,42 +1,42 @@
local doc = [[ local doc = [[
/nick <nickname> /nick <nickname>
Set your nickname for the bot to call you. Set your nickname. Use "/whoami" to check your nickname and "/nick -" to delete it.
Use -- to clear your nickname.
]] ]]
local triggers = { local triggers = {
'^/nick' '^/nick[@'..bot.username..']*'
} }
local action = function(msg) local action = function(msg)
local data = load_data('nicknames.json') local input = msg.text:input()
local id = tostring(msg.from.id)
local input = get_input(msg.text)
if not input then if not input then
local message = '' sendReply(msg, doc)
if data[id] then return true
message = '\nYour nickname is currently ' .. data[id] .. '.'
end
return send_msg(msg, doc..message)
end end
if input == '--' then if string.len(input) > 32 then
data[id] = nil sendReply(msg, 'The character limit for nicknames is 32.')
save_data('nicknames.json', data) return true
send_msg(msg, 'Your nickname has been deleted.')
return
end end
input = input:sub(1,64):gsub('\n',' ') nicks = load_data('nicknames.json')
data[id] = input
save_data('nicknames.json', data) if input == '-' then
send_msg(msg, 'Your nickname has been set to ' .. input .. '.') 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 end
return { return {
doc = doc, action = action,
triggers = triggers, 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 = {} local doc = [[
/pokedex <query>
PLUGIN.doc = [[ Returns a Pokedex entry from pokeapi.co.
/dex <pokemon>
Get Pokedex information for a given Pokemon.
Includes national ID number, type, height, weight, and a description from a random regional dex.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/dex' '^/[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 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 end
local base_url = 'http://pokeapi.co' local url = 'http://pokeapi.co'
local poke_type = nil
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) local dex_jstr, res = HTTP.request(dex_url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.results) sendReply(msg, config.errors.connection)
return
end end
local dex_jdat = JSON.decode(dex_jstr) 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) local desc_jstr, res = HTTP.request(desc_url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local desc_jdat = JSON.decode(desc_jstr) 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) local type_name = v.name:gsub("^%l", string.upper)
if not poke_type then if not poke_type then
poke_type = type_name poke_type = type_name
@ -46,12 +50,14 @@ function PLUGIN.action(msg)
end end
poke_type = poke_type .. ' type' 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') sendReply(msg, message)
send_msg(msg, m)
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,17 +1,13 @@
local PLUGIN = {} local doc = [[
PLUGIN.doc = [[
/pun /pun
Get a random pun. Returns a pun.
Have a recommendation? PM @topkecleon.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/pun$', '^/pun[@'..bot.username..']*'
'^/pun@'
} }
PLUGIN.puns = { local puns = {
"The person who invented the door-knock won the No-bell prize.", "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.", "I couldn't work out how to fasten my seatbelt. Then it clicked.",
"Never trust atoms; they make up everything.", "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." "In democracy, it's your vote that counts. In feudalism, it's your count that votes."
} }
function PLUGIN.action(msg) local action = function(msg)
math.randomseed(os.time())
send_msg(msg, PLUGIN.puns[math.random(#PLUGIN.puns)]) sendReply(msg, puns[math.random(#puns)])
end 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 = {} local doc = [[
PLUGIN.doc = [[
/reddit [r/subreddit | query] /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 = { local triggers = {
'^/reddit', '^/r[eddit]*[@'..bot.username..']*$',
'^/r$', '^/r[eddit]*[@'..bot.username..']* ',
'^/r ' '^/r/'
} }
function PLUGIN.action(msg) local action = function(msg)
local input = get_input(msg.text) msg.text_lower = msg.text_lower:gsub('/r/', '/r r/')
local jdat = {} local input = msg.text_lower:input()
local message = '' local url
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
local limit = 4 local limit = 4
if #jdat.data.children < limit then if msg.chat.id == msg.from.id then
limit = #jdat.data.children limit = 8
end 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] ' message = message .. '[NSFW] '
end end
local long_url = '\n'
url = '\n' if not v.data.is_self then
if not jdat.data.children[i].data.is_self then long_url = '\n' .. v.data.url .. '\n'
url = '\n' .. jdat.data.children[i].data.url .. '\n'
end end
local short_url = '[redd.it/' .. v.data.id .. '] '
local short_url = '[redd.it/' .. jdat.data.children[i].data.id .. '] ' message = message .. short_url .. v.data.title .. long_url
message = message .. short_url .. jdat.data.children[i].data.title .. url
end end
send_msg(msg, message) sendReply(msg, message)
end 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 = {} local doc = [[
/slap [target]
PLUGIN.doc = [[ Give someone a good slap (or worse) through reply or specification of a target.
/slap [victim]
Slap someone!
]] ]]
PLUGIN.triggers = { local triggers = {
'^/slap' '^/slap[@'..bot.username..']*'
} }
function PLUGIN.getSlap(slapper, victim) local slaps = {
slaps = { '$victim was shot by $victor.',
victim .. " was shot by " .. slapper .. ".", '$victim was pricked to death.',
victim .. " was pricked to death.", '$victim walked into a cactus while trying to escape $victor.',
victim .. " walked into a cactus while trying to escape " .. slapper .. ".", '$victim drowned.',
victim .. " drowned.", '$victim drowned whilst trying to escape $victor.',
victim .. " drowned whilst trying to escape " .. slapper .. ".", '$victim blew up.',
victim .. " blew up.", '$victim was blown up by $victor.',
victim .. " was blown up by " .. slapper .. ".", '$victim hit the ground too hard.',
victim .. " hit the ground too hard.", '$victim fell from a high place.',
victim .. " fell from a high place.", '$victim fell off a ladder.',
victim .. " fell off a ladder.", '$victim fell into a patch of cacti.',
victim .. " fell into a patch of cacti.", '$victim was doomed to fall by $victor.',
victim .. " was doomed to fall by " .. slapper .. ".", '$victim was blown from a high place by $victor.',
victim .. " was blown from a high place by " .. slapper .. ".", '$victim was squashed by a falling anvil.',
victim .. " was squashed by a falling anvil.", '$victim went up in flames.',
victim .. " went up in flames.", '$victim burned to death.',
victim .. " burned to death.", '$victim was burnt to a crisp whilst fighting $victor.',
victim .. " was burnt to a crisp whilst fighting " .. slapper .. ".", '$victim walked into a fire whilst fighting $victor.',
victim .. " walked into a fire whilst fighting " .. slapper .. ".", '$victim tried to swim in lava.',
victim .. " tried to swim in lava.", '$victim tried to swim in lava while trying to escape $victor.',
victim .. " tried to swim in lava while trying to escape " .. slapper .. ".", '$victim was struck by lightning.',
victim .. " was struck by lightning.", '$victim was slain by $victor.',
victim .. " was slain by " .. slapper .. ".", '$victim got finished off by $victor.',
victim .. " got finished off by " .. slapper .. ".", '$victim was killed by magic.',
victim .. " was killed by magic.", '$victim was killed by $victor using magic.',
victim .. " was killed by " .. slapper .. " using magic.", '$victim starved to death.',
victim .. " starved to death.", '$victim suffocated in a wall.',
victim .. " suffocated in a wall.", '$victim fell out of the world.',
victim .. " fell out of the world.", '$victim was knocked into the void by $victor.',
victim .. " was knocked into the void by " .. slapper .. ".", '$victim withered away.',
victim .. " withered away.", '$victim was pummeled by $victor.',
victim .. " was pummeled by " .. slapper .. ".", '$victim was fragged by $victor.',
victim .. " was fragged by " .. slapper .. ".", '$victim was desynchronized.',
victim .. " was desynchronized.", '$victim was wasted.',
victim .. " was wasted.", '$victim was busted.',
victim .. " was busted.", '$victim\'s bones are scraped clean by the desolate wind.',
victim .. "'s bones are scraped clean by the desolate wind.", '$victim has died of dysentery.',
victim .. " has died of dysentery.", '$victim fainted.',
victim .. " fainted.", '$victim is out of usable Pokemon! $victim whited out!',
victim .. " is out of usable Pokemon! " .. victim .. " whited out!", '$victim is out of usable Pokemon! $victim blacked out!',
victim .. " is out of usable Pokemon! " .. victim .. " blacked out!", '$victim whited out!',
victim .. " whited out!", '$victim blacked out!',
victim .. " blacked out!", '$victim says goodbye to this cruel world.',
victim .. " says goodbye to this cruel world.", '$victim got rekt.',
victim .. " got rekt.", '$victim was sawn in half by $victor.',
victim .. " was sawn in half by " .. slapper .. ".", '$victim died. I blame $victor.',
victim .. " died. I blame " .. slapper .. ".", '$victim was axe-murdered by $victor.',
victim .. " was axe-murdered by " .. slapper .. ".", '$victim\'s melon was split by $victor.',
victim .. "'s melon was split by " .. slapper .. ".", '$victim was slice and diced by $victor.',
victim .. " was slice and diced by " .. slapper .. ".", '$victim was split from crotch to sternum by $victor.',
victim .. " was split from crotch to sternum by " .. slapper .. ".", '$victim\'s death put another notch in $victor\'s axe.',
victim .. "'s death put another notch in " .. slapper .. "'s axe.", '$victim died impossibly!',
victim .. " died impossibly!", '$victim died from $victor\'s mysterious tropical disease.',
victim .. " died from " .. slapper .. "'s mysterious tropical disease.", '$victim escaped infection by dying.',
victim .. " escaped infection by dying.", '$victim played hot-potato with a grenade.',
victim .. " played hot-potato with a grenade.", '$victim was knifed by $victor.',
victim .. " was knifed by " .. slapper .. ".", '$victim fell on his sword.',
victim .. " fell on his sword.", '$victim ate a grenade.',
victim .. " ate a grenade.", '$victim practiced being $victor\'s clay pigeon.',
victim .. " practiced being " .. slapper .. "'s clay pigeon.", '$victim is what\'s for dinner!',
victim .. " is what's for dinner!", '$victim was terminated by $victor.',
victim .. " was terminated by " .. slapper .. ".", '$victim was shot before being thrown out of a plane.',
victim .. " was shot before being thrown out of a plane.", '$victim was not invincible.',
victim .. " was not invincible.", '$victim has encountered an error.',
victim .. " has encountered an error.", '$victim died and reincarnated as a goat.',
victim .. " died and reincarnated as a goat.", '$victor threw $victim off a building.',
slapper .. " threw " .. victim .. " off a building.", '$victim is sleeping with the fishes.',
victim .. " is sleeping with the fishes.", '$victim got a premature burial.',
victim .. " got a premature burial.", '$victor replaced all of $victim\'s music with Nickelback.',
slapper .. " replaced all of " .. victim .. "'s music with Nickelback.", '$victor spammed $victim\'s email.',
slapper .. " spammed " .. victim .. "'s email.", '$victor made $victim a knuckle sandwich.',
slapper .. " made " .. victim .. " a knuckle sandwich.", '$victor slapped $victim with pure nothing.',
slapper .. " slapped " .. victim .. " with pure nothing.", '$victor hit $victim with a small, interstellar spaceship.',
slapper .. " hit " .. victim .. " with a small, interstellar spaceship.", '$victim was quickscoped by $victor.',
victim .. " was quickscoped by " .. slapper .. ".", '$victor put $victim in check-mate.',
slapper .. " put " .. victim .. " in check-mate.", '$victor RSA-encrypted $victim and deleted the private key.',
slapper .. " RSA-encrypted " .. victim .. " and deleted the private key.", '$victor put $victim in the friendzone.',
slapper .. " put " .. victim .. " in the friendzone.", '$victor slaps $victim with a DMCA takedown request!',
slapper .. " slaps " .. victim .. " with a DMCA takedown request!", '$victim became a corpse blanket for $victor.',
victim .. " became a corpse blanket for " .. slapper .. ".", 'Death is when the monsters get you. Death comes for $victim.',
"Death is when the monsters get you. Death comes for " .. victim .. ".", 'Cowards die many times before their death. $victim never tasted death but once.'
"Cowards die many times before their death. " .. victim .. " never tasted death but once." }
}
return slaps[math.random(#slaps)]
end
function PLUGIN.action(msg) local action = function(msg)
math.randomseed(os.time()) local nicks = load_data('nicknames.json')
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 victim = msg.text:input()
if msg.reply_to_message then if msg.reply_to_message then
victim = msg.reply_to_message.from.first_name if nicks[tostring(msg.reply_to_message.from.id)] then
vid = msg.reply_to_message.from.id victim = nicks[tostring(msg.reply_to_message.from.id)]
slapper = msg.from.first_name else
sid = msg.from.id victim = msg.reply_to_message.from.first_name
if slapper == victim then
slapper = bot.first_name
sid = bot.id
end end
end end
nicks = load_data('nicknames.json') -- Try to replace slapper/victim names with nicknames. local victor = msg.from.first_name
sid = tostring(sid) if nicks[msg.from.id_str] then
vid = tostring(vid) victor = nicks[msg.from.id_str]
if nicks[sid] then slapper = nicks[sid] end end
if nicks[vid] then victim = nicks[vid] end
local message = PLUGIN.getSlap(slapper, victim) if not victim then
send_message(msg.chat.id, latcyr(message)) 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 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 doc = [[
local PLUGIN = {}
PLUGIN.doc = [[
/time <location> /time <location>
Sends the time and timezone for a given location. Returns the time, date, and timezone for the given location.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/time' '^/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 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 end
local coords = get_coords(input) local coords = get_coords(input)
if not coords then if type(coords) == 'string' then
return send_msg(msg, config.locale.errors.results) sendReply(msg, coords)
return
end end
local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..os.time() local url = 'https://maps.googleapis.com/maps/api/timezone/json?location=' .. coords.lat ..','.. coords.lon .. '&timestamp='..os.time()
local jstr, res = HTTPS.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
local timestamp = os.time() + jdat.rawOffset + jdat.dstOffset + config.time_offset local timestamp = os.time() + jdat.rawOffset + jdat.dstOffset + config.time_offset
local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600 local utcoff = (jdat.rawOffset + jdat.dstOffset) / 3600
if utcoff == math.abs(utcoff) then if utcoff == math.abs(utcoff) then
utcoff = '+' .. utcoff utcoff = '+' .. utcoff
end end
local message = os.date('%I:%M %p\n', timestamp) .. os.date('%A, %B %d, %Y\n', timestamp) .. jdat.timeZoneName .. ' (UTC' .. utcoff .. ')' 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 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 doc = [[
/translate [text]
local PLUGIN = {} Translates input or the replied-to message into the bot's language.
PLUGIN.triggers = {
'^/translate'
}
PLUGIN.doc = [[
/translate [target lang]
Reply to a message to translate it to the default language.
]] ]]
PLUGIN.action = function(msg) local triggers = {
'^/translate[@'..bot.username..']*'
}
if not msg.reply_to_message then local action = function(msg)
return send_msg(msg, PLUGIN.doc)
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 end
local tl = config.locale.translate or 'en' 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 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 str, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local output = str:gmatch("%[%[%[\"(.*)\"")():gsub("\"(.*)", "") local output = latcyr(str:gmatch("%[%[%[\"(.*)\"")():gsub("\"(.*)", ""))
local output = latcyr(output)
send_msg(msg.reply_to_message, output) sendReply(msg.reply_to_message or msg, output)
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,48 +1,50 @@
local PLUGIN = {} local doc = [[
/urbandictionary <query>
PLUGIN.doc = [[ Returns a definition from Urban Dictionary.
/ud <term>
Returns the first definition for a given term from Urban Dictionary.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/ud', '^/u[rban]*d[ictionary]*[@'..bot.username..']*',
'^/urbandictionary', '^/urban[@'..bot.username..']*'
'^/urban'
} }
function PLUGIN.action(msg) local action = function(msg)
local input = get_input(msg.text) local input = msg.text:input()
if not input then if not input then
if msg.reply_to_message then if msg.reply_to_message and msg.reply_to_message.text then
msg = msg.reply_to_message input = msg.reply_to_message.text
input = msg.text
else else
return send_msg(msg, PLUGIN.doc) sendReply(msg, doc)
return
end end
end end
local url = 'http://api.urbandictionary.com/v0/define?term=' .. URL.escape(input) 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 if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if jdat.result_type == "no_results" then if jdat.result_type == "no_results" then
return send_msg(msg, config.locale.errors.results) sendReply(msg, config.errors.results)
return
end 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 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 end
send_msg(msg, message) sendReply(msg, message)
end 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> /weather <location>
Returns the current temperature and weather conditions for a specified location. Returns the current weather conditions for a given location.
Non-city locations are accepted; "/weather Buckingham Palace" will return the weather for Westminster.
Results and weather data are powered by Yahoo.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/weather' '^/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 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 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) local jstr, res = HTTP.request(url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local jdat = JSON.decode(jstr) 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('%.2f', jdat.main.temp - 273.15)
local celsius = string.format('%.0f', (fahrenheit - 32) * 5/9) local fahrenheit = string.format('%.2f', celsius * (9/5) + 32)
local message = celsius .. '°C | ' .. fahrenheit .. '°F, ' .. data.text .. '.' local message = celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.'
send_msg(msg, message) sendReply(msg, message)
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,34 +1,13 @@
local PLUGIN = {} local triggers = {
'^/who[ami]*[@'..bot.username..']*$'
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$'
} }
function PLUGIN.action(msg) local action = function(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
if msg.reply_to_message then if msg.reply_to_message then
msg = msg.reply_to_message msg = msg.reply_to_message
end 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 local from_name = msg.from.first_name
if msg.from.last_name then if msg.from.last_name then
from_name = from_name .. ' ' .. msg.from.last_name from_name = from_name .. ' ' .. msg.from.last_name
@ -38,10 +17,25 @@ function PLUGIN.action(msg)
end end
from_name = from_name .. ' (' .. msg.from.id .. ')' 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 end
return PLUGIN return {
action = action,
triggers = triggers
}

View File

@ -1,70 +1,67 @@
local doc = [[ local doc = [[
/wiki <topic> /wikipedia <query>
Search Wikipedia for a relevant article and return its summary. Returns an article from Wikipedia.
]] ]]
local triggers = { local triggers = {
'^/wiki', '^/w[iki[pedia]*]*[@'..bot.username..']*$',
'^/w ' '^/w[iki[pedia]*]*[@'..bot.username..']* '
} }
local action = function(msg) 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 input = msg.text:input()
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)
if not input then 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 end
local jstr, res = HTTP.request(gurl..URL.escape(input)) local gurl = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=1&q=site:wikipedia.org%20'
if res ~= 200 then local wurl = 'https://en.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles='
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', '')
jstr, res = HTTPS.request(wurl..URL.escape(title)) local jstr, res = HTTPS.request(gurl .. URL.escape(input))
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.error.connection)
return
end 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 local text = JSON.decode(jstr).query.pages
for k,v in pairs(text) do for k,v in pairs(text) do
text = v.extract text = v.extract
break -- Seriously, there's probably a way more elegant solution. break -- Seriously, there's probably a way more elegant solution.
end end
if not text then if not text then
return send_msg(msg, config.locale.errors.results) sendReply(msg, config.error.connection)
return
end 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('</?.->', '') text = text:gsub('</?.->', '')
local l = text:find('\n')
local l = text:find('\n') -- Comment this block for more than one-paragraph summaries.
if l then if l then
text = text:sub(1, l-1) text = text:sub(1, l-1)
end end
text = text .. '\n' .. url text = text .. '\n' .. url
send_msg(msg, text) sendReply(msg, text)
end end
return { return {
doc = doc,
triggers = triggers,
action = action, action = action,
typing = true triggers = triggers,
doc = doc
} }

View File

@ -1,52 +1,56 @@
local PLUGIN = {} local doc = [[
/xkcd [query]
PLUGIN.doc = [[ Returns an xkcd strip and its alt text. If there is no query, it will be randomized.
/xkcd [search]
This command returns an xkcd strip, its number, and its "secret" text. You may search for a specific strip or get a random one.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/xkcd' '^/xkcd[@'..bot.username..']*'
} }
function PLUGIN.action(msg) local action = function(msg)
local input = get_input(msg.text) local input = msg.text:input()
local url = 'http://xkcd.com/info.0.json'
local jstr, res = HTTP.request(url) local jstr, res = HTTP.request('http://xkcd.com/info.0.json')
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
local latest = JSON.decode(jstr).num local latest = JSON.decode(jstr).num
local jdat local res_url
if input then 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 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 = HTTP.request(url) local jstr, res = HTTPS.request(url)
if res ~= 200 then if res ~= 200 then
print('here') sendReply(msg, config.errors.connection)
return send_msg(msg, config.locale.errors.connection) return
end end
jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
if #jdat.responseData.results == 0 then if #jdat.responseData.results == 0 then
return send_msg(msg, config.locale.errors.results) sendReply(msg, config.errors.results)
return
end end
url = jdat.responseData.results[1].url .. 'info.0.json' res_url = jdat.responseData.results[1].url .. 'info.0.json'
else else
math.randomseed(os.time()) res_url = 'http://xkcd.com/' .. math.random(latest) .. '/info.0.json'
url = 'http://xkcd.com/' .. math.random(latest) .. '/info.0.json'
end end
local jstr, res = HTTP.request(url) local jstr, res = HTTP.request(res_url)
if res ~= 200 then if res ~= 200 then
return send_msg(msg, config.locale.errors.connection) sendReply(msg, config.errors.connection)
return
end end
jdat = JSON.decode(jstr) local jdat = JSON.decode(jstr)
local message = '[' .. jdat.num .. '] ' .. jdat.alt .. '\n' .. jdat.img local message = '[' .. jdat.num .. '] ' .. jdat.alt .. '\n' .. jdat.img
sendMessage(msg.chat.id, message, false, msg.message_id)
send_message(msg.chat.id, message, false, msg.message_id)
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,44 +1,44 @@
-- Youtube Plugin for bot based on otouto -- Thanks to @TiagoDanin for writing the original plugin.
-- Glanced at https://github.com/yagop/telegram-bot/blob/master/plugins/youtube.lua
local PLUGIN = {}
PLUGIN.doc = [[ local doc = [[
/youtube <query> /youtube <query>
Search videos on YouTube. Returns the top result from YouTube.
]] ]]
PLUGIN.triggers = { local triggers = {
'^/youtube', '^/y[ou]*t[ube]*[@'..bot.username..']*'
'^/yt'
} }
function PLUGIN.action(msg) local action = function(msg)
-- BASE
local input = get_input(msg.text) local input = msg.text:input()
if not input then if not input then
return send_msg(msg, PLUGIN.doc) if msg.reply_to_message and msg.reply_to_message.text then
end input = msg.reply_to_message.text
--URL API else
local url = 'https://www.googleapis.com/youtube/v3/search?' sendReply(msg, doc)
url = url..'part=snippet'..'&maxResults=4'..'&type=video' return
url = url..'&q='..URL.escape(input).."&key=AIzaSyAfe7SI8kwQqaoouvAmevBfKumaLf-3HzI" end
-- JSON end
local res,code = HTTPS.request(url)
if code ~= 200 then return nil end local url = 'https://www.googleapis.com/youtube/v3/search?key=AIzaSyAfe7SI8kwQqaoouvAmevBfKumaLf-3HzI&type=video&part=snippet&maxResults=1&q=' .. URL.escape(input)
local data_JSON = JSON.decode(res)
-- Print Items local jstr, res = HTTPS.request(url)
local text = "" if res ~= 200 then
for k,item in pairs(data_JSON.items) do sendReply(msg, config.errors.connection)
text = text .. item.snippet.title .. '\n' .. 'http://youtu.be/' .. item.id.videoId .. '\n\n' return
end end
-- END - ERRO 404
local text_end = text local jdat = JSON.decode(jstr)
if text == "" then
text_end = "Not found video" local message = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId
end
-- Send MSG sendMessage(msg.chat.id, message, false, msg.message_id)
send_message(msg.chat.id, text_end)
end end
return PLUGIN return {
action = action,
triggers = triggers,
doc = doc
}

View File

@ -1,42 +1,42 @@
-- utilities.lua -- utilities.lua
-- Functions shared among plugins. -- 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', ' ') 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 .. ' ' str = str .. ' '
if not idx then idx = 1 end
if idx ~= 1 then if idx ~= 1 then
for i = 2, idx do for i = 2, idx do
str = string.sub(str, string.find(str, ' ') + 1) str = string.sub(str, string.find(str, ' ') + 1)
end end
end end
str = string.sub(str, 1, string.find(str, ' ')) str = str:sub(1, str:find(' '))
return string.sub(str, 1, -2)
return str:sub(1, -2)
end end
function get_input(text) -- returns string or false function string:input() -- Returns the string after the first space.
if not string.find(text, ' ') then if not self:find(' ') then
return false return false
end end
return string.sub(text, string.find(text, ' ')+1) return self:sub(self:find(' ')+1)
end end
function get_target(msg) -- I swear, I copied this from PIL, not yago! :)
function string:trim() -- Trims whitespace from a string.
if msg.reply_to_message then local s = self:gsub('^%s*(.-)%s*$', '%1')
return msg.reply_to_message.from.id return s
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")
end end
local lc_list = { 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 for k,v in pairs(lc_list) do
str = string.gsub(str, k, v) str = string.gsub(str, k, v)
end end
return str return str
end end
function send_msg(msg, message) function load_data(filename) -- Loads a JSON file as a table.
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)
local f = io.open(filename) local f = io.open(filename)
if not f then if not f then
@ -110,11 +89,33 @@ function load_data(filename)
end end
function save_data(filename, data) function save_data(filename, data) -- Saves a table to a JSON file.
local s = JSON.encode(data) local s = JSON.encode(data)
local f = io.open(filename, 'w') local f = io.open(filename, 'w')
f:write(s) f:write(s)
f:close() 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 end