├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── bot ├── bot.lua └── utils.lua ├── data └── .gitkeep ├── etc └── telegram.conf ├── launch.sh ├── libs ├── JSON.lua ├── mimetype.lua └── redis.lua ├── patches └── disable-python-and-libjansson.patch └── plugins ├── 9gag.lua ├── anti-bot.lua ├── anti-flood.lua ├── banhammer.lua ├── boobs.lua ├── btc.lua ├── bugzilla.lua ├── calculator.lua ├── channels.lua ├── chuck_norris.lua ├── danbooru.lua ├── dogify.lua ├── download_media.lua ├── echo.lua ├── eur.lua ├── expand.lua ├── face.lua ├── fortunes_uc3m.lua ├── get.lua ├── giphy.lua ├── gnuplot.lua ├── google.lua ├── gps.lua ├── hackernews.lua ├── hello.lua ├── help.lua ├── id.lua ├── images.lua ├── imdb.lua ├── img_google.lua ├── invite.lua ├── inviteme.lua ├── isX.lua ├── isup.lua ├── kickme.lua ├── location.lua ├── lyrics.lua ├── magic8ball.lua ├── media.lua ├── meme.lua ├── minecraft.lua ├── pili.lua ├── plugins.lua ├── pokedex.lua ├── qr.lua ├── quotes.lua ├── rae.lua ├── remind.lua ├── roll.lua ├── rss.lua ├── search_youtube.lua ├── service_entergroup.lua ├── service_template.lua ├── set.lua ├── stats.lua ├── steam.lua ├── tex.lua ├── time.lua ├── torrent_search.lua ├── translate.lua ├── trivia.lua ├── tweet.lua ├── twitter.lua ├── twitter_send.lua ├── version.lua ├── vote.lua ├── weather.lua ├── webshot.lua ├── wiki.lua ├── xkcd.lua ├── yoda.lua └── youtube.lua /.gitignore: -------------------------------------------------------------------------------- 1 | res/ 2 | data/ 3 | .luarocks -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tg"] 2 | path = tg 3 | url = https://github.com/vysheng/tg 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | 3 | before_install: 4 | - sudo apt-get update -qq 5 | - sudo apt-get install -qq libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev make unzip git libjansson-dev python2.7-dev 6 | - ./launch.sh install 7 | 8 | script: 9 | - luac -p bot/*.lua 10 | - luac -p plugins/*.lua 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | :warning: UNMAINTAINED / DEPRECATED :warning: 3 | ============ 4 | This proyect is no longer active since [Telegram Bot API](https://core.telegram.org/bots/api/) creation. It's better using the API rather than telegram-bot, almost every feature on Telegram-cli is available on API. You can use [node-telegram-bot-api](https://github.com/yagop/node-telegram-bot-api) Node.js module if you want. Or search your favourite one [here](https://www.reddit.com/r/TelegramBots/comments/4ly273/updated_collection_of_api_wrappers/). 5 | 6 | 7 | telegram-bot 8 | ============ 9 | 10 | [![https://yago.me/tg-bot](https://img.shields.io/badge/💬_Telegram-Bot_Dev._Group-blue.svg)](https://yago.me/tg-bot) [![https://telegram.me/Yago_Perez](https://img.shields.io/badge/💬_Telegram-Yago__Perez-blue.svg)](https://telegram.me/Yago_Perez) [![https://gitter.im/yagop/telegram-bot](https://img.shields.io/badge/💬_Gitter-Join_Chat-green.svg)](https://gitter.im/yagop/telegram-bot) 11 | [![Donate button](https://img.shields.io/badge/Red_Cross-donate-yellow.svg)](https://www.icrc.org/ "Donate to Red Cross Society") 12 | 13 | A Telegram Bot based on plugins using [tg](https://github.com/vysheng/tg). 14 | 15 | Multimedia 16 | ---------- 17 | - When user sends image (png, jpg, jpeg) URL download and send it to origin. 18 | - When user sends media (gif, mp4, pdf, etc.) URL download and send it to origin. 19 | - When user sends twitter URL, send text and images to origin. Requires OAuth Key. 20 | - When user sends YouTube URL, send to origin video image. 21 | 22 | ![http://i.imgur.com/0FGUvU0.png](http://i.imgur.com/0FGUvU0.png) ![http://i.imgur.com/zW7WWWt.png](http://i.imgur.com/zW7WWWt.png) ![http://i.imgur.com/zW7WWWt.png](http://i.imgur.com/kPK7paz.png) 23 | 24 | Bot Commands 25 | ------------ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 |
NameDescriptionUsage
9gag.lua9GAG for telegram!9gag: Send random image from 9gag
boobs.luaGets a random boobs or butts pic!boobs: Get a boobs NSFW image. :underage:
!butts: Get a butts NSFW image. :underage:
btc.luaBitcoin global average market value (in EUR or USD)!btc [EUR|USD] [amount]
bugzilla.luaLookup bugzilla status update/bot bugzilla [bug number]
calculator.luaCalculate math expressions with mathjs API!calc [expression]: evaluates the expression and sends the result.
channels.luaPlugin to manage channels. Enable or disable channel.!channel enable: enable current channel
!channel disable: disable current channel
danbooru.luaGets a random fresh or popular image from Danbooru!danbooru - gets a random fresh image from Danbooru :underage:
!danboorud - random daily popular image :underage:
!danbooruw - random weekly popular image :underage:
!danboorum - random monthly popular image :underage:
dogify.luaCreate a doge image with words.!dogify (words/or phrases/separated/by/slashes) - Create a doge image with the words.
download_media.luaWhen bot receives a media msg, download the media.
echo.luaSimplest plugin ever!!echo [whatever]: echoes the msg
eur.luaReal-time EURUSD market price!eur [USD]
expand.luaExpand a shorten URL to the original.!expand [url]
fortunes_uc3m.luaFortunes from Universidad Carlos III!uc3m
get.luaRetrieves variables saved with !set!get (value_name): Returns the value_name value.
giphy.luaGIFs from telegram with Giphy API!gif (term): Search and sends GIF from Giphy. If no param, sends a trending GIF.
!giphy (term): Search and sends GIF from Giphy. If no param, sends a trending GIF.
gnuplot.luaGnuplot plugin!gnuplot [single variable function]: Plot single variable function.
google.luaSearches Google and send results!google [terms]: Searches Google and send results
gps.luagenerates a map showing the given GPS coordinates!gps latitude,longitude: generates a map showing the given GPS coordinates
hackernews.luaShow top 5 hacker news (ycombinator.com)!hackernews
hello.luaSays hello to someonesay hello to [name]
help.luaHelp plugin. Get info from other plugins. !help: Show list of plugins.
!help all: Show all commands for every plugin.
!help [plugin name]: Commands for that plugin.
id.luaKnow your id or the id of a chat members.!id: Return your ID and the chat id if you are in one.
!id(s) chat: Return the IDs of the chat members.
images.luaWhen user sends image URL (ends with png, jpg, jpeg) download and send it to origin.
imdb.luaIMDB plugin for Telegram!imdb [movie]
img_google.luaSearch image with Google API and sends it.!img [term]: Random search an image with Google API.
invite.luaInvite other user to the chat group!invite name [user_name]
!invite id [user_id]
isup.luaCheck if a website or server is up.!isup [host]: Performs a HTTP request or Socket (ip:port) connection
!isup cron [host]: Every 5mins check if host is up. (Requires privileged user)
!isup cron delete [host]: Disable checking that host.
location.luaGets information about a location, maplink and overview!loc (location): Gets information about a location, maplink and overview
magic8ball.luaMagic 8Ball!magic8ball
media.luaWhen user sends media URL (ends with gif, mp4, pdf, etc.) download and send it to origin.
meme.luaGenerate a meme image with up and bottom texts. 189 | !meme search (name): Return the name of the meme that match.
!meme list: Return the link where you can see the memes.
!meme listall: Return the list of all memes. Only admin can call it.
!meme [name] - [text_up] - [text_down]: Generate a meme with the picture that match with that name with the texts provided.
!meme [name] "[text_up]" "[text_down]": Generate a meme with the picture that match with that name with the texts provided.
190 |
minecraft.luaSearches Minecraft server and sends info!mine [ip]: Searches Minecraft server on specified IP and sends info. Default port: 25565
!mine [ip] [port]: Searches Minecraft server on specified IP and port and sends info.
pili.luaShorten an URL with pili.la service!pili [url]: Short the url
plugins.luaPlugin to manage other plugins. Enable, disable or reload.!plugins: list all plugins.
!plugins enable [plugin]: enable plugin.
!plugins disable [plugin]: disable plugin.
!plugins disable [plugin] chat: disable plugin only this chat.
!plugins reload: reloads all plugins.
qr.luaGiven a text it returns a qr code!qr [text] : returns a black and white qr code
!qr "[background color]" "[data color]" [text] : returns a colored qr code (see !help qr to see how specify colors).
quotes.luaQuote plugin, you can create and retrieves random quotes!addquote [msg]
!quote
rae.luaSpanish dictionary!rae [word]: Search that word in Spanish dictionary.
roll.luaRoll some dice! 226 | !roll d 227 | 228 | | 229 | 230 | d 231 | 232 |
rss.luaManage User/Chat RSS subscriptions.!rss: Get the rss subscriptions.
!rss subscribe (url): Subscribe to that url.
!rss unsubscribe (id): Unsubscribe of that id.
!rss sync: Sync the rss subscriptios now. Only sudo users can use this option.
search_youtube.luaSearch video on YouTube and send it.!youtube [term]: Search for a YouTube video and send it.
set.luaPlugin for saving values. get.lua plugin is necessary to retrieve them.!set [value_name] [data]: Saves the data with the value_name name.
stats.luaPlugin to update user stats.!stats: Returns a list of Username [telegram_id]: msg_num
steam.luaGrabs Steam info for Steam links.
tex.luaConvert LaTeX equation to image!tex [equation]: Convert LaTeX equation to image
time.luaDisplays the local time in an area!time [area]: Displays the local time in that area
translate.luaTranslate some text!translate text. Translate the text to English.
!translate target_lang text.
!translate source,target text
tweet.luaRandom tweet from user!tweet id [id]: Get a random tweet from the user with that ID
!tweet id [id] last: Get a random tweet from the user with that ID
!tweet name [name]: Get a random tweet from the user with that name
!tweet name [name] last: Get a random tweet from the user with that name
twitter.luaWhen user sends twitter URL, send text and images to origin. Requires OAuth Key.
twitter_send.luaSends a tweet!tw [text]: Sends the Tweet with the configured account.
version.luaShows bot version 292 | !version: Shows bot version
vote.luaPlugin for voting in groups.!voting reset: Reset all the votes.
!vote [number]: Cast the vote.
!voting stats: Shows the statistics of voting.
weather.luaweather in that city (Madrid is default)!weather (city)
webshot.luaTake an screenshot of a web.!webshot [url]
wiki.luaSearches Wikipedia and send results!wiki [terms]: Searches wiki and send results
!wiki_set [wiki]: sets the wikimedia site for this chat
!wiki_get: gets the current wikimedia site
xkcd.luaSend comic images from xkcd!xkcd (id): Send an xkcd image and title. If not id, send a random one
youtube.luaSends YouTube info and image.
326 | 327 | [Installation](https://github.com/yagop/telegram-bot/wiki/Installation) 328 | ------------ 329 | ```bash 330 | # Tested on Ubuntu 14.04, for other OSs check out https://github.com/yagop/telegram-bot/wiki/Installation 331 | sudo apt-get install libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev make unzip git redis-server g++ libjansson-dev libpython-dev expat libexpat1-dev 332 | ``` 333 | 334 | ```bash 335 | # After those dependencies, lets install the bot 336 | cd $HOME 337 | git clone https://github.com/yagop/telegram-bot.git 338 | cd telegram-bot 339 | ./launch.sh install 340 | ./launch.sh # Will ask you for a phone number & confirmation code. 341 | ``` 342 | 343 | Enable more [`plugins`](https://github.com/yagop/telegram-bot/tree/master/plugins) 344 | ------------- 345 | See the plugins list with `!plugins` command. 346 | 347 | Enable a disabled plugin by `!plugins enable [name]`. 348 | 349 | Disable an enabled plugin by `!plugins disable [name]`. 350 | 351 | Those commands require a privileged user, privileged users are defined inside `data/config.lua` (generated by the bot), stop the bot and edit if necessary. 352 | 353 | 354 | Run it as a daemon 355 | ------------ 356 | If your Linux/Unix comes with [upstart](http://upstart.ubuntu.com/) you can run the bot by this way 357 | ```bash 358 | $ sed -i "s/yourusername/$(whoami)/g" etc/telegram.conf 359 | $ sed -i "s_telegrambotpath_$(pwd)_g" etc/telegram.conf 360 | $ sudo cp etc/telegram.conf /etc/init/ 361 | $ sudo start telegram # To start it 362 | $ sudo stop telegram # To stop it 363 | ``` 364 | 365 | Contact me 366 | ------------ 367 | You can contact me [via Telegram](https://telegram.me/yago_perez) but if you have an issue please [open](https://github.com/yagop/telegram-bot/issues) one. 368 | 369 | [Join](https://telegram.me/joinchat/ALJ3izwBCNXSswCHOKMwGw) on the TelegramBot Discussion Group. 370 | -------------------------------------------------------------------------------- /bot/bot.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ';.luarocks/share/lua/5.2/?.lua' 2 | ..';.luarocks/share/lua/5.2/?/init.lua' 3 | package.cpath = package.cpath .. ';.luarocks/lib/lua/5.2/?.so' 4 | 5 | require("./bot/utils") 6 | 7 | local f = assert(io.popen('/usr/bin/git describe --tags', 'r')) 8 | VERSION = assert(f:read('*a')) 9 | f:close() 10 | 11 | -- This function is called when tg receive a msg 12 | function on_msg_receive (msg) 13 | if not started then 14 | return 15 | end 16 | 17 | local receiver = get_receiver(msg) 18 | 19 | -- vardump(msg) 20 | msg = pre_process_service_msg(msg) 21 | if msg_valid(msg) then 22 | msg = pre_process_msg(msg) 23 | if msg then 24 | match_plugins(msg) 25 | mark_read(receiver, ok_cb, false) 26 | end 27 | end 28 | end 29 | 30 | function ok_cb(extra, success, result) 31 | end 32 | 33 | function on_binlog_replay_end() 34 | started = true 35 | postpone (cron_plugins, false, 60*5.0) 36 | -- See plugins/isup.lua as an example for cron 37 | 38 | _config = load_config() 39 | 40 | -- load plugins 41 | plugins = {} 42 | load_plugins() 43 | end 44 | 45 | function msg_valid(msg) 46 | -- Don't process outgoing messages 47 | if msg.out then 48 | print('\27[36mNot valid: msg from us\27[39m') 49 | return false 50 | end 51 | 52 | -- Before bot was started 53 | if msg.date < now then 54 | print('\27[36mNot valid: old msg\27[39m') 55 | return false 56 | end 57 | 58 | if msg.unread == 0 then 59 | print('\27[36mNot valid: readed\27[39m') 60 | return false 61 | end 62 | 63 | if not msg.to.id then 64 | print('\27[36mNot valid: To id not provided\27[39m') 65 | return false 66 | end 67 | 68 | if not msg.from.id then 69 | print('\27[36mNot valid: From id not provided\27[39m') 70 | return false 71 | end 72 | 73 | if msg.from.id == our_id then 74 | print('\27[36mNot valid: Msg from our id\27[39m') 75 | return false 76 | end 77 | 78 | if msg.to.type == 'encr_chat' then 79 | print('\27[36mNot valid: Encrypted chat\27[39m') 80 | return false 81 | end 82 | 83 | if msg.from.id == 777000 then 84 | print('\27[36mNot valid: Telegram message\27[39m') 85 | return false 86 | end 87 | 88 | return true 89 | end 90 | 91 | -- 92 | function pre_process_service_msg(msg) 93 | if msg.service then 94 | local action = msg.action or {type=""} 95 | -- Double ! to discriminate of normal actions 96 | msg.text = "!!tgservice " .. action.type 97 | 98 | -- wipe the data to allow the bot to read service messages 99 | if msg.out then 100 | msg.out = false 101 | end 102 | if msg.from.id == our_id then 103 | msg.from.id = 0 104 | end 105 | end 106 | return msg 107 | end 108 | 109 | -- Apply plugin.pre_process function 110 | function pre_process_msg(msg) 111 | for name,plugin in pairs(plugins) do 112 | if plugin.pre_process and msg then 113 | print('Preprocess', name) 114 | msg = plugin.pre_process(msg) 115 | end 116 | end 117 | 118 | return msg 119 | end 120 | 121 | -- Go over enabled plugins patterns. 122 | function match_plugins(msg) 123 | for name, plugin in pairs(plugins) do 124 | match_plugin(plugin, name, msg) 125 | end 126 | end 127 | 128 | -- Check if plugin is on _config.disabled_plugin_on_chat table 129 | local function is_plugin_disabled_on_chat(plugin_name, receiver) 130 | local disabled_chats = _config.disabled_plugin_on_chat 131 | -- Table exists and chat has disabled plugins 132 | if disabled_chats and disabled_chats[receiver] then 133 | -- Checks if plugin is disabled on this chat 134 | for disabled_plugin,disabled in pairs(disabled_chats[receiver]) do 135 | if disabled_plugin == plugin_name and disabled then 136 | local warning = 'Plugin '..disabled_plugin..' is disabled on this chat' 137 | print(warning) 138 | send_msg(receiver, warning, ok_cb, false) 139 | return true 140 | end 141 | end 142 | end 143 | return false 144 | end 145 | 146 | function match_plugin(plugin, plugin_name, msg) 147 | local receiver = get_receiver(msg) 148 | 149 | -- Go over patterns. If one matches it's enough. 150 | for k, pattern in pairs(plugin.patterns) do 151 | local matches = match_pattern(pattern, msg.text) 152 | if matches then 153 | print("msg matches: ", pattern) 154 | 155 | if is_plugin_disabled_on_chat(plugin_name, receiver) then 156 | return nil 157 | end 158 | -- Function exists 159 | if plugin.run then 160 | -- If plugin is for privileged users only 161 | if not warns_user_not_allowed(plugin, msg) then 162 | local result = plugin.run(msg, matches) 163 | if result then 164 | send_large_msg(receiver, result) 165 | end 166 | end 167 | end 168 | -- One patterns matches 169 | return 170 | end 171 | end 172 | end 173 | 174 | -- DEPRECATED, use send_large_msg(destination, text) 175 | function _send_msg(destination, text) 176 | send_large_msg(destination, text) 177 | end 178 | 179 | -- Save the content of _config to config.lua 180 | function save_config( ) 181 | serialize_to_file(_config, './data/config.lua') 182 | print ('saved config into ./data/config.lua') 183 | end 184 | 185 | -- Returns the config from config.lua file. 186 | -- If file doesn't exist, create it. 187 | function load_config( ) 188 | local f = io.open('./data/config.lua', "r") 189 | -- If config.lua doesn't exist 190 | if not f then 191 | print ("Created new config file: data/config.lua") 192 | create_config() 193 | else 194 | f:close() 195 | end 196 | local config = loadfile ("./data/config.lua")() 197 | for v,user in pairs(config.sudo_users) do 198 | print("Allowed user: " .. user) 199 | end 200 | return config 201 | end 202 | 203 | -- Create a basic config.json file and saves it. 204 | function create_config( ) 205 | -- A simple config with basic plugins and ourselves as privileged user 206 | config = { 207 | enabled_plugins = { 208 | "9gag", 209 | "eur", 210 | "echo", 211 | "btc", 212 | "get", 213 | "giphy", 214 | "google", 215 | "gps", 216 | "help", 217 | "id", 218 | "images", 219 | "img_google", 220 | "location", 221 | "media", 222 | "plugins", 223 | "channels", 224 | "set", 225 | "stats", 226 | "time", 227 | "version", 228 | "weather", 229 | "xkcd", 230 | "youtube" }, 231 | sudo_users = {our_id}, 232 | disabled_channels = {} 233 | } 234 | serialize_to_file(config, './data/config.lua') 235 | print ('saved config into ./data/config.lua') 236 | end 237 | 238 | function on_our_id (id) 239 | our_id = id 240 | end 241 | 242 | function on_user_update (user, what) 243 | --vardump (user) 244 | end 245 | 246 | function on_chat_update (chat, what) 247 | --vardump (chat) 248 | end 249 | 250 | function on_secret_chat_update (schat, what) 251 | --vardump (schat) 252 | end 253 | 254 | function on_get_difference_end () 255 | end 256 | 257 | -- Enable plugins in config.json 258 | function load_plugins() 259 | for k, v in pairs(_config.enabled_plugins) do 260 | print("Loading plugin", v) 261 | 262 | local ok, err = pcall(function() 263 | local t = loadfile("plugins/"..v..'.lua')() 264 | plugins[v] = t 265 | end) 266 | 267 | if not ok then 268 | print('\27[31mError loading plugin '..v..'\27[39m') 269 | print('\27[31m'..err..'\27[39m') 270 | end 271 | 272 | end 273 | end 274 | 275 | -- Call and postpone execution for cron plugins 276 | function cron_plugins() 277 | 278 | for name, plugin in pairs(plugins) do 279 | -- Only plugins with cron function 280 | if plugin.cron ~= nil then 281 | plugin.cron() 282 | end 283 | end 284 | 285 | -- Called again in 5 mins 286 | postpone (cron_plugins, false, 5*60.0) 287 | end 288 | 289 | -- Start and load values 290 | our_id = 0 291 | now = os.time() 292 | math.randomseed(now) 293 | started = false 294 | -------------------------------------------------------------------------------- /bot/utils.lua: -------------------------------------------------------------------------------- 1 | URL = require "socket.url" 2 | http = require "socket.http" 3 | https = require "ssl.https" 4 | ltn12 = require "ltn12" 5 | serpent = require "serpent" 6 | feedparser = require "feedparser" 7 | 8 | json = (loadfile "./libs/JSON.lua")() 9 | mimetype = (loadfile "./libs/mimetype.lua")() 10 | redis = (loadfile "./libs/redis.lua")() 11 | 12 | http.TIMEOUT = 10 13 | 14 | function get_receiver(msg) 15 | if msg.to.type == 'user' then 16 | return 'user#id'..msg.from.id 17 | end 18 | if msg.to.type == 'chat' then 19 | return 'chat#id'..msg.to.id 20 | end 21 | if msg.to.type == 'encr_chat' then 22 | return msg.to.print_name 23 | end 24 | end 25 | 26 | function is_chat_msg( msg ) 27 | if msg.to.type == 'chat' then 28 | return true 29 | end 30 | return false 31 | end 32 | 33 | function string.random(length) 34 | local str = ""; 35 | for i = 1, length do 36 | math.random(97, 122) 37 | str = str..string.char(math.random(97, 122)); 38 | end 39 | return str; 40 | end 41 | 42 | function string:split(sep) 43 | local sep, fields = sep or ":", {} 44 | local pattern = string.format("([^%s]+)", sep) 45 | self:gsub(pattern, function(c) fields[#fields+1] = c end) 46 | return fields 47 | end 48 | 49 | -- DEPRECATED 50 | function string.trim(s) 51 | print("string.trim(s) is DEPRECATED use string:trim() instead") 52 | return s:gsub("^%s*(.-)%s*$", "%1") 53 | end 54 | 55 | -- Removes spaces 56 | function string:trim() 57 | return self:gsub("^%s*(.-)%s*$", "%1") 58 | end 59 | 60 | function get_http_file_name(url, headers) 61 | -- Eg: foo.var 62 | local file_name = url:match("[^%w]+([%.%w]+)$") 63 | -- Any delimited alphanumeric on the url 64 | file_name = file_name or url:match("[^%w]+(%w+)[^%w]+$") 65 | -- Random name, hope content-type works 66 | file_name = file_name or str:random(5) 67 | 68 | local content_type = headers["content-type"] 69 | 70 | local extension = nil 71 | if content_type then 72 | extension = mimetype.get_mime_extension(content_type) 73 | end 74 | if extension then 75 | file_name = file_name.."."..extension 76 | end 77 | 78 | local disposition = headers["content-disposition"] 79 | if disposition then 80 | -- attachment; filename=CodeCogsEqn.png 81 | file_name = disposition:match('filename=([^;]+)') or file_name 82 | end 83 | 84 | return file_name 85 | end 86 | 87 | -- Saves file to /tmp/. If file_name isn't provided, 88 | -- will get the text after the last "/" for filename 89 | -- and content-type for extension 90 | function download_to_file(url, file_name) 91 | print("url to download: "..url) 92 | 93 | local respbody = {} 94 | local options = { 95 | url = url, 96 | sink = ltn12.sink.table(respbody), 97 | redirect = true 98 | } 99 | 100 | -- nil, code, headers, status 101 | local response = nil 102 | 103 | if url:starts('https') then 104 | options.redirect = false 105 | response = {https.request(options)} 106 | else 107 | response = {http.request(options)} 108 | end 109 | 110 | local code = response[2] 111 | local headers = response[3] 112 | local status = response[4] 113 | 114 | if code ~= 200 then return nil end 115 | 116 | file_name = file_name or get_http_file_name(url, headers) 117 | 118 | local file_path = "/tmp/"..file_name 119 | print("Saved to: "..file_path) 120 | 121 | file = io.open(file_path, "w+") 122 | file:write(table.concat(respbody)) 123 | file:close() 124 | 125 | return file_path 126 | end 127 | 128 | function vardump(value) 129 | print(serpent.block(value, {comment=false})) 130 | end 131 | 132 | -- taken from http://stackoverflow.com/a/11130774/3163199 133 | function scandir(directory) 134 | local i, t, popen = 0, {}, io.popen 135 | for filename in popen('ls -a "'..directory..'"'):lines() do 136 | i = i + 1 137 | t[i] = filename 138 | end 139 | return t 140 | end 141 | 142 | -- http://www.lua.org/manual/5.2/manual.html#pdf-io.popen 143 | function run_command(str) 144 | local cmd = io.popen(str) 145 | local result = cmd:read('*all') 146 | cmd:close() 147 | return result 148 | end 149 | 150 | -- User has privileges 151 | function is_sudo(msg) 152 | local var = false 153 | -- Check users id in config 154 | for v,user in pairs(_config.sudo_users) do 155 | if user == msg.from.id then 156 | var = true 157 | end 158 | end 159 | return var 160 | end 161 | 162 | -- Returns the name of the sender 163 | function get_name(msg) 164 | local name = msg.from.first_name 165 | if name == nil then 166 | name = msg.from.id 167 | end 168 | return name 169 | end 170 | 171 | -- Returns at table of lua files inside plugins 172 | function plugins_names( ) 173 | local files = {} 174 | for k, v in pairs(scandir("plugins")) do 175 | -- Ends with .lua 176 | if (v:match(".lua$")) then 177 | table.insert(files, v) 178 | end 179 | end 180 | return files 181 | end 182 | 183 | -- Function name explains what it does. 184 | function file_exists(name) 185 | local f = io.open(name,"r") 186 | if f ~= nil then 187 | io.close(f) 188 | return true 189 | else 190 | return false 191 | end 192 | end 193 | 194 | -- Save into file the data serialized for lua. 195 | -- Set uglify true to minify the file. 196 | function serialize_to_file(data, file, uglify) 197 | file = io.open(file, 'w+') 198 | local serialized 199 | if not uglify then 200 | serialized = serpent.block(data, { 201 | comment = false, 202 | name = '_' 203 | }) 204 | else 205 | serialized = serpent.dump(data) 206 | end 207 | file:write(serialized) 208 | file:close() 209 | end 210 | 211 | -- Returns true if the string is empty 212 | function string:isempty() 213 | return self == nil or self == '' 214 | end 215 | 216 | -- Returns true if the string is blank 217 | function string:isblank() 218 | self = self:trim() 219 | return self:isempty() 220 | end 221 | 222 | -- DEPRECATED!!!!! 223 | function string.starts(String, Start) 224 | print("string.starts(String, Start) is DEPRECATED use string:starts(text) instead") 225 | return Start == string.sub(String,1,string.len(Start)) 226 | end 227 | 228 | -- Returns true if String starts with Start 229 | function string:starts(text) 230 | return text == string.sub(self,1,string.len(text)) 231 | end 232 | 233 | -- Send image to user and delete it when finished. 234 | -- cb_function and cb_extra are optionals callback 235 | function _send_photo(receiver, file_path, cb_function, cb_extra) 236 | local cb_extra = { 237 | file_path = file_path, 238 | cb_function = cb_function, 239 | cb_extra = cb_extra 240 | } 241 | -- Call to remove with optional callback 242 | send_photo(receiver, file_path, cb_function, cb_extra) 243 | end 244 | 245 | -- Download the image and send to receiver, it will be deleted. 246 | -- cb_function and cb_extra are optionals callback 247 | function send_photo_from_url(receiver, url, cb_function, cb_extra) 248 | -- If callback not provided 249 | cb_function = cb_function or ok_cb 250 | cb_extra = cb_extra or false 251 | 252 | local file_path = download_to_file(url, false) 253 | if not file_path then -- Error 254 | local text = 'Error downloading the image' 255 | send_msg(receiver, text, cb_function, cb_extra) 256 | else 257 | print("File path: "..file_path) 258 | _send_photo(receiver, file_path, cb_function, cb_extra) 259 | end 260 | end 261 | 262 | -- Same as send_photo_from_url but as callback function 263 | function send_photo_from_url_callback(cb_extra, success, result) 264 | local receiver = cb_extra.receiver 265 | local url = cb_extra.url 266 | 267 | local file_path = download_to_file(url, false) 268 | if not file_path then -- Error 269 | local text = 'Error downloading the image' 270 | send_msg(receiver, text, ok_cb, false) 271 | else 272 | print("File path: "..file_path) 273 | _send_photo(receiver, file_path, ok_cb, false) 274 | end 275 | end 276 | 277 | -- Send multiple images asynchronous. 278 | -- param urls must be a table. 279 | function send_photos_from_url(receiver, urls) 280 | local cb_extra = { 281 | receiver = receiver, 282 | urls = urls, 283 | remove_path = nil 284 | } 285 | send_photos_from_url_callback(cb_extra) 286 | end 287 | 288 | -- Use send_photos_from_url. 289 | -- This function might be difficult to understand. 290 | function send_photos_from_url_callback(cb_extra, success, result) 291 | -- cb_extra is a table containing receiver, urls and remove_path 292 | local receiver = cb_extra.receiver 293 | local urls = cb_extra.urls 294 | local remove_path = cb_extra.remove_path 295 | 296 | -- The previously image to remove 297 | if remove_path ~= nil then 298 | os.remove(remove_path) 299 | print("Deleted: "..remove_path) 300 | end 301 | 302 | -- Nil or empty, exit case (no more urls) 303 | if urls == nil or #urls == 0 then 304 | return false 305 | end 306 | 307 | -- Take the head and remove from urls table 308 | local head = table.remove(urls, 1) 309 | 310 | local file_path = download_to_file(head, false) 311 | local cb_extra = { 312 | receiver = receiver, 313 | urls = urls, 314 | remove_path = file_path 315 | } 316 | 317 | -- Send first and postpone the others as callback 318 | send_photo(receiver, file_path, send_photos_from_url_callback, cb_extra) 319 | end 320 | 321 | -- Callback to remove a file 322 | function rmtmp_cb(cb_extra, success, result) 323 | local file_path = cb_extra.file_path 324 | local cb_function = cb_extra.cb_function or ok_cb 325 | local cb_extra = cb_extra.cb_extra 326 | 327 | if file_path ~= nil then 328 | os.remove(file_path) 329 | print("Deleted: "..file_path) 330 | end 331 | -- Finally call the callback 332 | cb_function(cb_extra, success, result) 333 | end 334 | 335 | -- Send document to user and delete it when finished. 336 | -- cb_function and cb_extra are optionals callback 337 | function _send_document(receiver, file_path, cb_function, cb_extra) 338 | local cb_extra = { 339 | file_path = file_path, 340 | cb_function = cb_function or ok_cb, 341 | cb_extra = cb_extra or false 342 | } 343 | -- Call to remove with optional callback 344 | send_document(receiver, file_path, rmtmp_cb, cb_extra) 345 | end 346 | 347 | -- Download the image and send to receiver, it will be deleted. 348 | -- cb_function and cb_extra are optionals callback 349 | function send_document_from_url(receiver, url, cb_function, cb_extra) 350 | local file_path = download_to_file(url, false) 351 | print("File path: "..file_path) 352 | _send_document(receiver, file_path, cb_function, cb_extra) 353 | end 354 | 355 | -- Parameters in ?a=1&b=2 style 356 | function format_http_params(params, is_get) 357 | local str = '' 358 | -- If is get add ? to the beginning 359 | if is_get then str = '?' end 360 | local first = true -- Frist param 361 | for k,v in pairs (params) do 362 | if v then -- nil value 363 | if first then 364 | first = false 365 | str = str..k.. "="..v 366 | else 367 | str = str.."&"..k.. "="..v 368 | end 369 | end 370 | end 371 | return str 372 | end 373 | 374 | -- Check if user can use the plugin and warns user 375 | -- Returns true if user was warned and false if not warned (is allowed) 376 | function warns_user_not_allowed(plugin, msg) 377 | if not user_allowed(plugin, msg) then 378 | local text = 'This plugin requires privileged user' 379 | local receiver = get_receiver(msg) 380 | send_msg(receiver, text, ok_cb, false) 381 | return true 382 | else 383 | return false 384 | end 385 | end 386 | 387 | -- Check if user can use the plugin 388 | function user_allowed(plugin, msg) 389 | if plugin.privileged and not is_sudo(msg) then 390 | return false 391 | end 392 | return true 393 | end 394 | 395 | 396 | function send_order_msg(destination, msgs) 397 | local cb_extra = { 398 | destination = destination, 399 | msgs = msgs 400 | } 401 | send_order_msg_callback(cb_extra, true) 402 | end 403 | 404 | function send_order_msg_callback(cb_extra, success, result) 405 | local destination = cb_extra.destination 406 | local msgs = cb_extra.msgs 407 | local file_path = cb_extra.file_path 408 | if file_path ~= nil then 409 | os.remove(file_path) 410 | print("Deleted: " .. file_path) 411 | end 412 | if type(msgs) == 'string' then 413 | send_large_msg(destination, msgs) 414 | elseif type(msgs) ~= 'table' then 415 | return 416 | end 417 | if #msgs < 1 then 418 | return 419 | end 420 | local msg = table.remove(msgs, 1) 421 | local new_cb_extra = { 422 | destination = destination, 423 | msgs = msgs 424 | } 425 | if type(msg) == 'string' then 426 | send_msg(destination, msg, send_order_msg_callback, new_cb_extra) 427 | elseif type(msg) == 'table' then 428 | local typ = msg[1] 429 | local nmsg = msg[2] 430 | new_cb_extra.file_path = nmsg 431 | if typ == 'document' then 432 | send_document(destination, nmsg, send_order_msg_callback, new_cb_extra) 433 | elseif typ == 'image' or typ == 'photo' then 434 | send_photo(destination, nmsg, send_order_msg_callback, new_cb_extra) 435 | elseif typ == 'audio' then 436 | send_audio(destination, nmsg, send_order_msg_callback, new_cb_extra) 437 | elseif typ == 'video' then 438 | send_video(destination, nmsg, send_order_msg_callback, new_cb_extra) 439 | else 440 | send_file(destination, nmsg, send_order_msg_callback, new_cb_extra) 441 | end 442 | end 443 | end 444 | 445 | -- Same as send_large_msg_callback but friendly params 446 | function send_large_msg(destination, text) 447 | local cb_extra = { 448 | destination = destination, 449 | text = text 450 | } 451 | send_large_msg_callback(cb_extra, true) 452 | end 453 | 454 | -- If text is longer than 4096 chars, send multiple msg. 455 | -- https://core.telegram.org/method/messages.sendMessage 456 | function send_large_msg_callback(cb_extra, success, result) 457 | local text_max = 4096 458 | 459 | local destination = cb_extra.destination 460 | local text = cb_extra.text 461 | local text_len = string.len(text) 462 | local num_msg = math.ceil(text_len / text_max) 463 | 464 | if num_msg <= 1 then 465 | send_msg(destination, text, ok_cb, false) 466 | else 467 | 468 | local my_text = string.sub(text, 1, 4096) 469 | local rest = string.sub(text, 4096, text_len) 470 | 471 | local cb_extra = { 472 | destination = destination, 473 | text = rest 474 | } 475 | 476 | send_msg(destination, my_text, send_large_msg_callback, cb_extra) 477 | end 478 | end 479 | 480 | -- Returns a table with matches or nil 481 | function match_pattern(pattern, text, lower_case) 482 | if text then 483 | local matches = {} 484 | if lower_case then 485 | matches = { string.match(text:lower(), pattern) } 486 | else 487 | matches = { string.match(text, pattern) } 488 | end 489 | if next(matches) then 490 | return matches 491 | end 492 | end 493 | -- nil 494 | end 495 | 496 | -- Function to read data from files 497 | function load_from_file(file, default_data) 498 | local f = io.open(file, "r+") 499 | -- If file doesn't exists 500 | if f == nil then 501 | -- Create a new empty table 502 | default_data = default_data or {} 503 | serialize_to_file(default_data, file) 504 | print ('Created file', file) 505 | else 506 | print ('Data loaded from file', file) 507 | f:close() 508 | end 509 | return loadfile (file)() 510 | end 511 | 512 | -- See http://stackoverflow.com/a/14899740 513 | function unescape_html(str) 514 | local map = { 515 | ["lt"] = "<", 516 | ["gt"] = ">", 517 | ["amp"] = "&", 518 | ["quot"] = '"', 519 | ["apos"] = "'" 520 | } 521 | new = string.gsub(str, '(&(#?x?)([%d%a]+);)', function(orig, n, s) 522 | var = map[s] or n == "#" and string.char(s) 523 | var = var or n == "#x" and string.char(tonumber(s,16)) 524 | var = var or orig 525 | return var 526 | end) 527 | return new 528 | end 529 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yagop/telegram-bot/1b70f84e9284a3b5f49ae38eb66c8be4e3e48d6e/data/.gitkeep -------------------------------------------------------------------------------- /etc/telegram.conf: -------------------------------------------------------------------------------- 1 | description "Telegram-bot upstart script" 2 | 3 | respawn 4 | respawn limit 15 5 5 | 6 | start on runlevel [2345] 7 | stop on shutdown 8 | 9 | setuid yourusername 10 | exec /bin/sh telegrambotpath/launch.sh 11 | -------------------------------------------------------------------------------- /launch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | THIS_DIR=$(cd $(dirname $0); pwd) 4 | cd $THIS_DIR 5 | 6 | update() { 7 | git pull 8 | git submodule update --init --recursive 9 | install_rocks 10 | } 11 | 12 | # Will install luarocks on THIS_DIR/.luarocks 13 | install_luarocks() { 14 | git clone https://github.com/keplerproject/luarocks.git 15 | cd luarocks 16 | git checkout tags/v2.2.1 # Current stable 17 | 18 | PREFIX="$THIS_DIR/.luarocks" 19 | 20 | ./configure --prefix=$PREFIX --sysconfdir=$PREFIX/luarocks --force-config 21 | 22 | RET=$?; if [ $RET -ne 0 ]; 23 | then echo "Error. Exiting."; exit $RET; 24 | fi 25 | 26 | make build && make install 27 | RET=$?; if [ $RET -ne 0 ]; 28 | then echo "Error. Exiting.";exit $RET; 29 | fi 30 | 31 | cd .. 32 | rm -rf luarocks 33 | } 34 | 35 | install_rocks() { 36 | ./.luarocks/bin/luarocks install luasec 37 | RET=$?; if [ $RET -ne 0 ]; 38 | then echo "Error. Exiting."; exit $RET; 39 | fi 40 | 41 | ./.luarocks/bin/luarocks install lbase64 20120807-3 42 | RET=$?; if [ $RET -ne 0 ]; 43 | then echo "Error. Exiting."; exit $RET; 44 | fi 45 | 46 | ./.luarocks/bin/luarocks install luasocket 47 | RET=$?; if [ $RET -ne 0 ]; 48 | then echo "Error. Exiting."; exit $RET; 49 | fi 50 | 51 | ./.luarocks/bin/luarocks install oauth 52 | RET=$?; if [ $RET -ne 0 ]; 53 | then echo "Error. Exiting."; exit $RET; 54 | fi 55 | 56 | ./.luarocks/bin/luarocks install redis-lua 57 | RET=$?; if [ $RET -ne 0 ]; 58 | then echo "Error. Exiting."; exit $RET; 59 | fi 60 | 61 | ./.luarocks/bin/luarocks install lua-cjson 62 | RET=$?; if [ $RET -ne 0 ]; 63 | then echo "Error. Exiting."; exit $RET; 64 | fi 65 | 66 | ./.luarocks/bin/luarocks install fakeredis 67 | RET=$?; if [ $RET -ne 0 ]; 68 | then echo "Error. Exiting."; exit $RET; 69 | fi 70 | 71 | ./.luarocks/bin/luarocks install xml 72 | RET=$?; if [ $RET -ne 0 ]; 73 | then echo "Error. Exiting."; exit $RET; 74 | fi 75 | 76 | ./.luarocks/bin/luarocks install feedparser 77 | RET=$?; if [ $RET -ne 0 ]; 78 | then echo "Error. Exiting."; exit $RET; 79 | fi 80 | 81 | ./.luarocks/bin/luarocks install serpent 82 | RET=$?; if [ $RET -ne 0 ]; 83 | then echo "Error. Exiting."; exit $RET; 84 | fi 85 | } 86 | 87 | install() { 88 | git pull 89 | git submodule update --init --recursive 90 | patch -i "patches/disable-python-and-libjansson.patch" -p 0 --batch --forward 91 | RET=$?; 92 | 93 | cd tg 94 | if [ $RET -ne 0 ]; then 95 | autoconf -i 96 | fi 97 | ./configure && make 98 | 99 | RET=$?; if [ $RET -ne 0 ]; then 100 | echo "Error. Exiting."; exit $RET; 101 | fi 102 | cd .. 103 | install_luarocks 104 | install_rocks 105 | } 106 | 107 | if [ "$1" = "install" ]; then 108 | install 109 | elif [ "$1" = "update" ]; then 110 | update 111 | else 112 | if [ ! -f ./tg/telegram.h ]; then 113 | echo "tg not found" 114 | echo "Run $0 install" 115 | exit 1 116 | fi 117 | 118 | if [ ! -f ./tg/bin/telegram-cli ]; then 119 | echo "tg binary not found" 120 | echo "Run $0 install" 121 | exit 1 122 | fi 123 | 124 | ./tg/bin/telegram-cli -k ./tg/tg-server.pub -s ./bot/bot.lua -l 1 -E $@ 125 | fi 126 | -------------------------------------------------------------------------------- /libs/mimetype.lua: -------------------------------------------------------------------------------- 1 | -- Thanks to https://github.com/catwell/lua-toolbox/blob/master/mime.types 2 | do 3 | 4 | local mimetype = {} 5 | 6 | -- TODO: Add more? 7 | local types = { 8 | ["text/html"] = "html", 9 | ["text/css"] = "css", 10 | ["text/xml"] = "xml", 11 | ["image/gif"] = "gif", 12 | ["image/jpeg"] = "jpg", 13 | ["application/x-javascript"] = "js", 14 | ["application/atom+xml"] = "atom", 15 | ["application/rss+xml"] = "rss", 16 | ["text/mathml"] = "mml", 17 | ["text/plain"] = "txt", 18 | ["text/vnd.sun.j2me.app-descriptor"] = "jad", 19 | ["text/vnd.wap.wml"] = "wml", 20 | ["text/x-component"] = "htc", 21 | ["image/png"] = "png", 22 | ["image/tiff"] = "tiff", 23 | ["image/vnd.wap.wbmp"] = "wbmp", 24 | ["image/x-icon"] = "ico", 25 | ["image/x-jng"] = "jng", 26 | ["image/x-ms-bmp"] = "bmp", 27 | ["image/svg+xml"] = "svg", 28 | ["image/webp"] = "webp", 29 | ["application/java-archive"] = "jar", 30 | ["application/mac-binhex40"] = "hqx", 31 | ["application/msword"] = "doc", 32 | ["application/pdf"] = "pdf", 33 | ["application/postscript"] = "ps", 34 | ["application/rtf"] = "rtf", 35 | ["application/vnd.ms-excel"] = "xls", 36 | ["application/vnd.ms-powerpoint"] = "ppt", 37 | ["application/vnd.wap.wmlc"] = "wmlc", 38 | ["application/vnd.google-earth.kml+xml"] = "kml", 39 | ["application/vnd.google-earth.kmz"] = "kmz", 40 | ["application/x-7z-compressed"] = "7z", 41 | ["application/x-cocoa"] = "cco", 42 | ["application/x-java-archive-diff"] = "jardiff", 43 | ["application/x-java-jnlp-file"] = "jnlp", 44 | ["application/x-makeself"] = "run", 45 | ["application/x-perl"] = "pl", 46 | ["application/x-pilot"] = "prc", 47 | ["application/x-rar-compressed"] = "rar", 48 | ["application/x-redhat-package-manager"] = "rpm", 49 | ["application/x-sea"] = "sea", 50 | ["application/x-shockwave-flash"] = "swf", 51 | ["application/x-stuffit"] = "sit", 52 | ["application/x-tcl"] = "tcl", 53 | ["application/x-x509-ca-cert"] = "crt", 54 | ["application/x-xpinstall"] = "xpi", 55 | ["application/xhtml+xml"] = "xhtml", 56 | ["application/zip"] = "zip", 57 | ["application/octet-stream"] = "bin", 58 | ["audio/midi"] = "mid", 59 | ["audio/mpeg"] = "mp3", 60 | ["audio/ogg"] = "ogg", 61 | ["audio/x-m4a"] = "m4a", 62 | ["audio/x-realaudio"] = "ra", 63 | ["video/3gpp"] = "3gpp", 64 | ["video/mp4"] = "mp4", 65 | ["video/mpeg"] = "mpeg", 66 | ["video/quicktime"] = "mov", 67 | ["video/webm"] = "webm", 68 | ["video/x-flv"] = "flv", 69 | ["video/x-m4v"] = "m4v", 70 | ["video/x-mng"] = "mng", 71 | ["video/x-ms-asf"] = "asf", 72 | ["video/x-ms-wmv"] = "wmv", 73 | ["video/x-msvideo"] = "avi" 74 | } 75 | 76 | -- Returns the common file extension from a content-type 77 | function mimetype.get_mime_extension(content_type) 78 | return types[content_type] 79 | end 80 | 81 | -- Returns the mimetype and subtype 82 | function mimetype.get_content_type(extension) 83 | for k,v in pairs(types) do 84 | if v == extension then 85 | return k 86 | end 87 | end 88 | end 89 | 90 | -- Returns the mimetype without the subtype 91 | function mimetype.get_content_type_no_sub(extension) 92 | for k,v in pairs(types) do 93 | if v == extension then 94 | -- Before / 95 | return k:match('([%w-]+)/') 96 | end 97 | end 98 | end 99 | 100 | return mimetype 101 | end -------------------------------------------------------------------------------- /libs/redis.lua: -------------------------------------------------------------------------------- 1 | local Redis = require 'redis' 2 | local FakeRedis = require 'fakeredis' 3 | 4 | local params = { 5 | host = os.getenv('REDIS_HOST') or '127.0.0.1', 6 | port = tonumber(os.getenv('REDIS_PORT') or 6379) 7 | } 8 | 9 | local database = os.getenv('REDIS_DB') 10 | local password = os.getenv('REDIS_PASSWORD') 11 | 12 | -- Overwrite HGETALL 13 | Redis.commands.hgetall = Redis.command('hgetall', { 14 | response = function(reply, command, ...) 15 | local new_reply = { } 16 | for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end 17 | return new_reply 18 | end 19 | }) 20 | 21 | local redis = nil 22 | 23 | -- Won't launch an error if fails 24 | local ok = pcall(function() 25 | redis = Redis.connect(params) 26 | end) 27 | 28 | if not ok then 29 | 30 | local fake_func = function() 31 | print('\27[31mCan\'t connect with Redis, install/configure it!\27[39m') 32 | end 33 | fake_func() 34 | fake = FakeRedis.new() 35 | 36 | print('\27[31mRedis addr: '..params.host..'\27[39m') 37 | print('\27[31mRedis port: '..params.port..'\27[39m') 38 | 39 | redis = setmetatable({fakeredis=true}, { 40 | __index = function(a, b) 41 | if b ~= 'data' and fake[b] then 42 | fake_func(b) 43 | end 44 | return fake[b] or fake_func 45 | end }) 46 | 47 | else 48 | if password then 49 | redis:auth(password) 50 | end 51 | if database then 52 | redis:select(database) 53 | end 54 | end 55 | 56 | 57 | return redis 58 | -------------------------------------------------------------------------------- /patches/disable-python-and-libjansson.patch: -------------------------------------------------------------------------------- 1 | --- tg/configure.ac 2015-10-24 14:23:51.964259062 +0200 2 | +++ tg/configure.ac 2015-10-24 14:05:10.111062758 +0200 3 | @@ -61,93 +61,43 @@ 4 | ],[ 5 | ]) 6 | 7 | +# liblua is required 8 | AC_MSG_CHECKING([for liblua]) 9 | -AC_ARG_ENABLE(liblua,[--enable-liblua/--disable-liblua], 10 | - [ 11 | - if test "x$enableval" = "xno" ; then 12 | - AC_MSG_RESULT([disabled]) 13 | - else 14 | - AC_MSG_RESULT([enabled]) 15 | - AX_PROG_LUA([],[], 16 | - [ 17 | - AX_LUA_HEADERS([],[AC_MSG_ERROR([No lua headers found. Try --disable-liblua])]) 18 | - AX_LUA_LIBS([],[AC_MSG_ERROR([No lua libs found. Try --disable-liblua])]) 19 | - [EXTRA_LIBS="${EXTRA_LIBS} ${LUA_LIB}" ; ] 20 | - [CPPFLAGS="${CPPFLAGS} ${LUA_INCLUDE}" ; ] 21 | - AC_DEFINE(USE_LUA,1,[use lua]) 22 | - ], 23 | - [ 24 | - AC_MSG_ERROR([No lua found. Try --disable-liblua]) 25 | - ]) 26 | - fi 27 | - ],[ 28 | - AC_MSG_RESULT([enabled]) 29 | - AX_PROG_LUA([],[], 30 | - [ 31 | - AX_LUA_HEADERS([],[AC_MSG_ERROR([No lua headers found. Try --disable-liblua])]) 32 | - AX_LUA_LIBS([],[AC_MSG_ERROR([No lua libs found. Try --disable-liblua])]) 33 | - [EXTRA_LIBS="${EXTRA_LIBS} ${LUA_LIB}" ; ] 34 | - [CPPFLAGS="${CPPFLAGS} ${LUA_INCLUDE}" ; ] 35 | - AC_DEFINE(USE_LUA,1,[use lua]) 36 | - ], 37 | - [ 38 | - AC_MSG_ERROR([No lua found. Try --disable-liblua]) 39 | - ]) 40 | - ]) 41 | - 42 | -AC_MSG_CHECKING([for python]) 43 | -AC_ARG_ENABLE(python,[--enable-python/--disable-python], 44 | +AC_MSG_RESULT([enabled]) 45 | +AX_PROG_LUA([],[], 46 | [ 47 | - if test "x$enableval" = "xno" ; then 48 | - AC_MSG_RESULT([disabled]) 49 | - else 50 | - AC_MSG_RESULT([enabled]) 51 | - 52 | - AX_PYTHON() 53 | - AC_SUBST([PYTHON_FOUND]) 54 | - if test $PYTHON_FOUND = no ; then 55 | - AC_MSG_ERROR([No supported python lib version found. Try --disable-python]) 56 | - else 57 | - AC_SUBST([PYTHON_LIBS]) 58 | - AC_SUBST([PYTHON_CFLAGS]) 59 | - EXTRA_LIBS="${EXTRA_LIBS} -l${PYTHON_LIB}" 60 | - CPPFLAGS="${CPPFLAGS} -I${PYTHON_INCLUDE_DIR}" 61 | - AC_DEFINE(USE_PYTHON,1,[use python]) 62 | - fi 63 | - fi 64 | - ],[ 65 | - AC_MSG_RESULT([enabled]) 66 | - 67 | - AX_PYTHON() 68 | - AC_SUBST([PYTHON_FOUND]) 69 | - if test $PYTHON_FOUND = no ; then 70 | - AC_MSG_ERROR([No supported python lib version found. Try --disable-python]) 71 | - else 72 | - AC_SUBST([PYTHON_LIBS]) 73 | - AC_SUBST([PYTHON_CFLAGS]) 74 | - EXTRA_LIBS="${EXTRA_LIBS} -l${PYTHON_LIB}" 75 | - CPPFLAGS="${CPPFLAGS} -I${PYTHON_INCLUDE_DIR}" 76 | - AC_DEFINE(USE_PYTHON,1,[use python]) 77 | - fi 78 | + AX_LUA_HEADERS([],[AC_MSG_ERROR([No lua headers found. Install them])]) 79 | + AX_LUA_LIBS([],[AC_MSG_ERROR([No lua libs found. Install them])]) 80 | + [EXTRA_LIBS="${EXTRA_LIBS} ${LUA_LIB}" ; ] 81 | + [CPPFLAGS="${CPPFLAGS} ${LUA_INCLUDE}" ; ] 82 | + AC_DEFINE(USE_LUA,1,[use lua]) 83 | + ], 84 | + [ 85 | + AC_MSG_ERROR([No lua found. Install lua]) 86 | ]) 87 | 88 | +# Optional 89 | +AC_MSG_CHECKING([for python]) 90 | +AX_PYTHON() 91 | +AC_SUBST([PYTHON_FOUND]) 92 | +if test $PYTHON_FOUND = no ; then 93 | + AC_MSG_RESULT([disabled]) 94 | +else 95 | + AC_SUBST([PYTHON_LIBS]) 96 | + AC_SUBST([PYTHON_CFLAGS]) 97 | + EXTRA_LIBS="${EXTRA_LIBS} -l${PYTHON_LIB}" 98 | + CPPFLAGS="${CPPFLAGS} -I${PYTHON_INCLUDE_DIR}" 99 | + AC_DEFINE(USE_PYTHON,1,[use python]) 100 | +fi 101 | 102 | - 103 | +# Optional 104 | AC_MSG_CHECKING([for libjansson]) 105 | -AC_ARG_ENABLE(json,[--enable-json/--disable-json], 106 | - [ 107 | - if test "x$enableval" = "xno" ; then 108 | - AC_MSG_RESULT([disabled]) 109 | - else 110 | - AC_MSG_RESULT([enabled]) 111 | - AC_CHECK_LIB([jansson],[json_array_set_new],[],AC_MSG_ERROR([No libjansson found. Try --disable-json])) 112 | - AC_DEFINE(USE_JSON,1,[use json]) 113 | - fi 114 | - ],[ 115 | +AC_CHECK_LIB([jansson],[json_array_set_new], 116 | + [ 117 | AC_MSG_RESULT([enabled]) 118 | - AC_CHECK_LIB([jansson],[json_array_set_new],[],AC_MSG_ERROR([No libjansson found. Try --disable-json])) 119 | AC_DEFINE(USE_JSON,1,[use json]) 120 | - ]) 121 | + ], 122 | + [AC_MSG_RESULT([disabled])]) 123 | 124 | #check for custom prog name 125 | AC_MSG_CHECKING([progname]) 126 | @@ -193,4 +143,3 @@ 127 | AC_SUBST(EXTRA_LIBS) 128 | AC_CONFIG_FILES([Makefile]) 129 | AC_OUTPUT 130 | - 131 | -------------------------------------------------------------------------------- /plugins/9gag.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local function get_9GAG() 4 | local url = "http://api-9gag.herokuapp.com/" 5 | local b,c = http.request(url) 6 | if c ~= 200 then return nil end 7 | local gag = json:decode(b) 8 | -- random max json table size 9 | local i = math.random(#gag) 10 | local link_image = gag[i].src 11 | local title = gag[i].title 12 | if link_image:sub(0,2) == '//' then 13 | link_image = msg.text:sub(3,-1) 14 | end 15 | return link_image, title 16 | end 17 | 18 | local function send_title(cb_extra, success, result) 19 | if success then 20 | send_msg(cb_extra[1], cb_extra[2], ok_cb, false) 21 | end 22 | end 23 | 24 | local function run(msg, matches) 25 | local receiver = get_receiver(msg) 26 | local url, title = get_9GAG() 27 | send_photo_from_url(receiver, url, send_title, {receiver, title}) 28 | return false 29 | end 30 | 31 | return { 32 | description = "9GAG for Telegram", 33 | usage = "!9gag: Send random image from 9gag", 34 | patterns = {"^!9gag$"}, 35 | run = run 36 | } 37 | 38 | end -------------------------------------------------------------------------------- /plugins/anti-bot.lua: -------------------------------------------------------------------------------- 1 | 2 | local function isBotAllowed (userId, chatId) 3 | local hash = 'anti-bot:allowed:'..chatId..':'..userId 4 | local banned = redis:get(hash) 5 | return banned 6 | end 7 | 8 | local function allowBot (userId, chatId) 9 | local hash = 'anti-bot:allowed:'..chatId..':'..userId 10 | redis:set(hash, true) 11 | end 12 | 13 | local function disallowBot (userId, chatId) 14 | local hash = 'anti-bot:allowed:'..chatId..':'..userId 15 | redis:del(hash) 16 | end 17 | 18 | -- Is anti-bot enabled on chat 19 | local function isAntiBotEnabled (chatId) 20 | local hash = 'anti-bot:enabled:'..chatId 21 | local enabled = redis:get(hash) 22 | return enabled 23 | end 24 | 25 | local function enableAntiBot (chatId) 26 | local hash = 'anti-bot:enabled:'..chatId 27 | redis:set(hash, true) 28 | end 29 | 30 | local function disableAntiBot (chatId) 31 | local hash = 'anti-bot:enabled:'..chatId 32 | redis:del(hash) 33 | end 34 | 35 | local function isABot (user) 36 | -- Flag its a bot 0001000000000000 37 | local binFlagIsBot = 4096 38 | local result = bit32.band(user.flags, binFlagIsBot) 39 | return result == binFlagIsBot 40 | end 41 | 42 | local function kickUser(userId, chatId) 43 | local chat = 'chat#id'..chatId 44 | local user = 'user#id'..userId 45 | chat_del_user(chat, user, function (data, success, result) 46 | if success ~= 1 then 47 | print('I can\'t kick '..data.user..' but should be kicked') 48 | end 49 | end, {chat=chat, user=user}) 50 | end 51 | 52 | local function run (msg, matches) 53 | -- We wont return text if is a service msg 54 | if matches[1] ~= 'chat_add_user' and matches[1] ~= 'chat_add_user_link' then 55 | if msg.to.type ~= 'chat' then 56 | return 'Anti-flood works only on channels' 57 | end 58 | end 59 | 60 | local chatId = msg.to.id 61 | if matches[1] == 'enable' then 62 | enableAntiBot(chatId) 63 | return 'Anti-bot enabled on this chat' 64 | end 65 | if matches[1] == 'disable' then 66 | disableAntiBot(chatId) 67 | return 'Anti-bot disabled on this chat' 68 | end 69 | if matches[1] == 'allow' then 70 | local userId = matches[2] 71 | allowBot(userId, chatId) 72 | return 'Bot '..userId..' allowed' 73 | end 74 | if matches[1] == 'disallow' then 75 | local userId = matches[2] 76 | disallowBot(userId, chatId) 77 | return 'Bot '..userId..' disallowed' 78 | end 79 | if matches[1] == 'chat_add_user' or matches[1] == 'chat_add_user_link' then 80 | local user = msg.action.user or msg.from 81 | if isABot(user) then 82 | print('It\'s a bot!') 83 | if isAntiBotEnabled(chatId) then 84 | print('Anti bot is enabled') 85 | local userId = user.id 86 | if not isBotAllowed(userId, chatId) then 87 | kickUser(userId, chatId) 88 | else 89 | print('This bot is allowed') 90 | end 91 | end 92 | end 93 | end 94 | end 95 | 96 | return { 97 | description = 'When bot enters group kick it.', 98 | usage = { 99 | '!antibot enable: Enable Anti-bot on current chat', 100 | '!antibot disable: Disable Anti-bot on current chat', 101 | '!antibot allow : Allow on this chat', 102 | '!antibot disallow : Disallow on this chat' 103 | }, 104 | patterns = { 105 | '^!antibot (allow) (%d+)$', 106 | '^!antibot (disallow) (%d+)$', 107 | '^!antibot (enable)$', 108 | '^!antibot (disable)$', 109 | '^!!tgservice (chat_add_user)$', 110 | '^!!tgservice (chat_add_user_link)$' 111 | }, 112 | run = run 113 | } 114 | -------------------------------------------------------------------------------- /plugins/anti-flood.lua: -------------------------------------------------------------------------------- 1 | local NUM_MSG_MAX = 5 -- Max number of messages per TIME_CHECK seconds 2 | local TIME_CHECK = 5 3 | 4 | local function kick_user(user_id, chat_id) 5 | local chat = 'chat#id'..chat_id 6 | local user = 'user#id'..user_id 7 | chat_del_user(chat, user, function (data, success, result) 8 | if success ~= 1 then 9 | local text = 'I can\'t kick '..data.user..' but should be kicked' 10 | send_msg(data.chat, '', ok_cb, nil) 11 | end 12 | end, {chat=chat, user=user}) 13 | end 14 | 15 | local function run (msg, matches) 16 | if msg.to.type ~= 'chat' then 17 | return 'Anti-flood works only on channels' 18 | else 19 | local chat = msg.to.id 20 | local hash = 'anti-flood:enabled:'..chat 21 | if matches[1] == 'enable' then 22 | redis:set(hash, true) 23 | return 'Anti-flood enabled on chat' 24 | end 25 | if matches[1] == 'disable' then 26 | redis:del(hash) 27 | return 'Anti-flood disabled on chat' 28 | end 29 | end 30 | end 31 | 32 | local function pre_process (msg) 33 | -- Ignore service msg 34 | if msg.service then 35 | print('Service message') 36 | return msg 37 | end 38 | 39 | local hash_enable = 'anti-flood:enabled:'..msg.to.id 40 | local enabled = redis:get(hash_enable) 41 | 42 | if enabled then 43 | print('anti-flood enabled') 44 | -- Check flood 45 | if msg.from.type == 'user' then 46 | -- Increase the number of messages from the user on the chat 47 | local hash = 'anti-flood:'..msg.from.id..':'..msg.to.id..':msg-num' 48 | local msgs = tonumber(redis:get(hash) or 0) 49 | if msgs > NUM_MSG_MAX then 50 | local receiver = get_receiver(msg) 51 | local user = msg.from.id 52 | local text = 'User '..user..' is flooding' 53 | local chat = msg.to.id 54 | 55 | send_msg(receiver, text, ok_cb, nil) 56 | if msg.to.type ~= 'chat' then 57 | print("Flood in not a chat group!") 58 | elseif user == tostring(our_id) then 59 | print('I won\'t kick myself') 60 | elseif is_sudo(msg) then 61 | print('I won\'t kick an admin!') 62 | else 63 | -- Ban user 64 | -- TODO: Check on this plugin bans 65 | local bhash = 'banned:'..msg.to.id..':'..msg.from.id 66 | redis:set(bhash, true) 67 | kick_user(user, chat) 68 | end 69 | msg = nil 70 | end 71 | redis:setex(hash, TIME_CHECK, msgs+1) 72 | end 73 | end 74 | return msg 75 | end 76 | 77 | return { 78 | description = 'Plugin to kick flooders from group.', 79 | usage = {}, 80 | patterns = { 81 | '^!antiflood (enable)$', 82 | '^!antiflood (disable)$' 83 | }, 84 | run = run, 85 | privileged = true, 86 | pre_process = pre_process 87 | } 88 | -------------------------------------------------------------------------------- /plugins/banhammer.lua: -------------------------------------------------------------------------------- 1 | local function is_user_whitelisted(id) 2 | local hash = 'whitelist:user#id'..id 3 | local white = redis:get(hash) or false 4 | return white 5 | end 6 | 7 | local function is_chat_whitelisted(id) 8 | local hash = 'whitelist:chat#id'..id 9 | local white = redis:get(hash) or false 10 | return white 11 | end 12 | 13 | local function kick_user(user_id, chat_id) 14 | local chat = 'chat#id'..chat_id 15 | local user = 'user#id'..user_id 16 | 17 | if user_id == tostring(our_id) then 18 | send_msg(chat, "I won't kick myself!", ok_cb, true) 19 | else 20 | chat_del_user(chat, user, ok_cb, true) 21 | end 22 | end 23 | 24 | local function ban_user(user_id, chat_id) 25 | local chat = 'chat#id'..chat_id 26 | if user_id == tostring(our_id) then 27 | send_msg(chat, "I won't kick myself!", ok_cb, true) 28 | else 29 | -- Save to redis 30 | local hash = 'banned:'..chat_id..':'..user_id 31 | redis:set(hash, true) 32 | -- Kick from chat 33 | kick_user(user_id, chat_id) 34 | end 35 | end 36 | 37 | local function is_banned(user_id, chat_id) 38 | local hash = 'banned:'..chat_id..':'..user_id 39 | local banned = redis:get(hash) 40 | return banned or false 41 | end 42 | 43 | local function pre_process(msg) 44 | 45 | -- SERVICE MESSAGE 46 | if msg.action and msg.action.type then 47 | local action = msg.action.type 48 | -- Check if banned user joins chat 49 | if action == 'chat_add_user' or action == 'chat_add_user_link' then 50 | local user_id 51 | if msg.action.link_issuer then 52 | user_id = msg.from.id 53 | else 54 | user_id = msg.action.user.id 55 | end 56 | print('Checking invited user '..user_id) 57 | local banned = is_banned(user_id, msg.to.id) 58 | if banned then 59 | print('User is banned!') 60 | kick_user(user_id, msg.to.id) 61 | end 62 | end 63 | -- No further checks 64 | return msg 65 | end 66 | 67 | -- BANNED USER TALKING 68 | if msg.to.type == 'chat' then 69 | local user_id = msg.from.id 70 | local chat_id = msg.to.id 71 | local banned = is_banned(user_id, chat_id) 72 | if banned then 73 | print('Banned user talking!') 74 | ban_user(user_id, chat_id) 75 | msg.text = '' 76 | end 77 | end 78 | 79 | -- WHITELIST 80 | local hash = 'whitelist:enabled' 81 | local whitelist = redis:get(hash) 82 | local issudo = is_sudo(msg) 83 | 84 | -- Allow all sudo users even if whitelist is allowed 85 | if whitelist and not issudo then 86 | print('Whitelist enabled and not sudo') 87 | -- Check if user or chat is whitelisted 88 | local allowed = is_user_whitelisted(msg.from.id) 89 | 90 | if not allowed then 91 | print('User '..msg.from.id..' not whitelisted') 92 | if msg.to.type == 'chat' then 93 | allowed = is_chat_whitelisted(msg.to.id) 94 | if not allowed then 95 | print ('Chat '..msg.to.id..' not whitelisted') 96 | else 97 | print ('Chat '..msg.to.id..' whitelisted :)') 98 | end 99 | end 100 | else 101 | print('User '..msg.from.id..' allowed :)') 102 | end 103 | 104 | if not allowed then 105 | msg.text = '' 106 | end 107 | 108 | else 109 | print('Whitelist not enabled or is sudo') 110 | end 111 | 112 | return msg 113 | end 114 | 115 | local function run(msg, matches) 116 | 117 | -- Silent ignore 118 | if not is_sudo(msg) then 119 | return nil 120 | end 121 | 122 | if matches[1] == 'ban' then 123 | local user_id = matches[3] 124 | local chat_id = msg.to.id 125 | 126 | if msg.to.type == 'chat' then 127 | if matches[2] == 'user' then 128 | ban_user(user_id, chat_id) 129 | return 'User '..user_id..' banned' 130 | end 131 | if matches[2] == 'delete' then 132 | local hash = 'banned:'..chat_id..':'..user_id 133 | redis:del(hash) 134 | return 'User '..user_id..' unbanned' 135 | end 136 | else 137 | return 'This isn\'t a chat group' 138 | end 139 | end 140 | 141 | if matches[1] == 'kick' then 142 | if msg.to.type == 'chat' then 143 | kick_user(matches[2], msg.to.id) 144 | else 145 | return 'This isn\'t a chat group' 146 | end 147 | end 148 | 149 | if matches[1] == 'whitelist' then 150 | if matches[2] == 'enable' then 151 | local hash = 'whitelist:enabled' 152 | redis:set(hash, true) 153 | return 'Enabled whitelist' 154 | end 155 | 156 | if matches[2] == 'disable' then 157 | local hash = 'whitelist:enabled' 158 | redis:del(hash) 159 | return 'Disabled whitelist' 160 | end 161 | 162 | if matches[2] == 'user' then 163 | local hash = 'whitelist:user#id'..matches[3] 164 | redis:set(hash, true) 165 | return 'User '..matches[3]..' whitelisted' 166 | end 167 | 168 | if matches[2] == 'chat' then 169 | if msg.to.type ~= 'chat' then 170 | return 'This isn\'t a chat group' 171 | end 172 | local hash = 'whitelist:chat#id'..msg.to.id 173 | redis:set(hash, true) 174 | return 'Chat '..msg.to.id..' whitelisted' 175 | end 176 | 177 | if matches[2] == 'delete' and matches[3] == 'user' then 178 | local hash = 'whitelist:user#id'..matches[4] 179 | redis:del(hash) 180 | return 'User '..matches[4]..' removed from whitelist' 181 | end 182 | 183 | if matches[2] == 'delete' and matches[3] == 'chat' then 184 | if msg.to.type ~= 'chat' then 185 | return 'This isn\'t a chat group' 186 | end 187 | local hash = 'whitelist:chat#id'..msg.to.id 188 | redis:del(hash) 189 | return 'Chat '..msg.to.id..' removed from whitelist' 190 | end 191 | 192 | end 193 | end 194 | 195 | return { 196 | description = "Plugin to manage bans, kicks and white/black lists.", 197 | usage = { 198 | "!whitelist /: Enable or disable whitelist mode", 199 | "!whitelist user : Allow user to use the bot when whitelist mode is enabled", 200 | "!whitelist chat: Allow everybody on current chat to use the bot when whitelist mode is enabled", 201 | "!whitelist delete user : Remove user from whitelist", 202 | "!whitelist delete chat: Remove chat from whitelist", 203 | "!ban user : Kick user from chat and kicks it if joins chat again", 204 | "!ban delete : Unban user", 205 | "!kick Kick user from chat group" 206 | }, 207 | patterns = { 208 | "^!(whitelist) (enable)$", 209 | "^!(whitelist) (disable)$", 210 | "^!(whitelist) (user) (%d+)$", 211 | "^!(whitelist) (chat)$", 212 | "^!(whitelist) (delete) (user) (%d+)$", 213 | "^!(whitelist) (delete) (chat)$", 214 | "^!(ban) (user) (%d+)$", 215 | "^!(ban) (delete) (%d+)$", 216 | "^!(kick) (%d+)$", 217 | "^!!tgservice (.+)$", 218 | }, 219 | run = run, 220 | pre_process = pre_process 221 | } 222 | -------------------------------------------------------------------------------- /plugins/boobs.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | -- Recursive function 4 | local function getRandomButts(attempt) 5 | attempt = attempt or 0 6 | attempt = attempt + 1 7 | 8 | local res,status = http.request("http://api.obutts.ru/noise/1") 9 | 10 | if status ~= 200 then return nil end 11 | local data = json:decode(res)[1] 12 | 13 | -- The OpenBoobs API sometimes returns an empty array 14 | if not data and attempt <= 3 then 15 | print('Cannot get that butts, trying another one...') 16 | return getRandomButts(attempt) 17 | end 18 | 19 | return 'http://media.obutts.ru/' .. data.preview 20 | end 21 | 22 | local function getRandomBoobs(attempt) 23 | attempt = attempt or 0 24 | attempt = attempt + 1 25 | 26 | local res,status = http.request("http://api.oboobs.ru/noise/1") 27 | 28 | if status ~= 200 then return nil end 29 | local data = json:decode(res)[1] 30 | 31 | -- The OpenBoobs API sometimes returns an empty array 32 | if not data and attempt < 10 then 33 | print('Cannot get that boobs, trying another one...') 34 | return getRandomBoobs(attempt) 35 | end 36 | 37 | return 'http://media.oboobs.ru/' .. data.preview 38 | end 39 | 40 | local function run(msg, matches) 41 | local url = nil 42 | 43 | if matches[1] == "!boobs" then 44 | url = getRandomBoobs() 45 | end 46 | 47 | if matches[1] == "!butts" then 48 | url = getRandomButts() 49 | end 50 | 51 | if url ~= nil then 52 | local receiver = get_receiver(msg) 53 | send_photo_from_url(receiver, url) 54 | else 55 | return 'Error getting boobs/butts for you, please try again later.' 56 | end 57 | end 58 | 59 | return { 60 | description = "Gets a random boobs or butts pic", 61 | usage = { 62 | "!boobs: Get a boobs NSFW image. 🔞", 63 | "!butts: Get a butts NSFW image. 🔞" 64 | }, 65 | patterns = { 66 | "^!boobs$", 67 | "^!butts$" 68 | }, 69 | run = run 70 | } 71 | 72 | end 73 | -------------------------------------------------------------------------------- /plugins/btc.lua: -------------------------------------------------------------------------------- 1 | -- See https://bitcoinaverage.com/api 2 | local function getBTCX(amount,currency) 3 | local base_url = 'https://api.bitcoinaverage.com/ticker/global/' 4 | -- Do request on bitcoinaverage, the final / is critical! 5 | local res,code = https.request(base_url..currency.."/") 6 | 7 | if code ~= 200 then return nil end 8 | local data = json:decode(res) 9 | 10 | -- Easy, it's right there 11 | text = "BTC/"..currency..'\n'..'Buy: '..data.ask..'\n'..'Sell: '..data.bid 12 | 13 | -- If we have a number as second parameter, calculate the bitcoin amount 14 | if amount~=nil then 15 | btc = tonumber(amount) / tonumber(data.ask) 16 | text = text.."\n "..currency .." "..amount.." = BTC "..btc 17 | end 18 | return text 19 | end 20 | 21 | local function run(msg, matches) 22 | local cur = 'EUR' 23 | local amt = nil 24 | 25 | -- Get the global match out of the way 26 | if matches[1] == "!btc" then 27 | return getBTCX(amt,cur) 28 | end 29 | 30 | if matches[2] ~= nil then 31 | -- There is a second match 32 | amt = matches[2] 33 | cur = string.upper(matches[1]) 34 | else 35 | -- Just a EUR or USD param 36 | cur = string.upper(matches[1]) 37 | end 38 | return getBTCX(amt,cur) 39 | end 40 | 41 | return { 42 | description = "Bitcoin global average market value (in EUR or USD)", 43 | usage = "!btc [EUR|USD] [amount]", 44 | patterns = { 45 | "^!btc$", 46 | "^!btc ([Ee][Uu][Rr])$", 47 | "^!btc ([Uu][Ss][Dd])$", 48 | "^!btc (EUR) (%d+[%d%.]*)$", 49 | "^!btc (USD) (%d+[%d%.]*)$" 50 | }, 51 | run = run 52 | } 53 | -------------------------------------------------------------------------------- /plugins/bugzilla.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local BASE_URL = "https://bugzilla.mozilla.org/rest/" 4 | 5 | local function bugzilla_login() 6 | local url = BASE_URL.."login?login=" .. _config.bugzilla.username .. "&password=" .. _config.bugzilla.password 7 | print("accessing " .. url) 8 | local res,code = https.request( url ) 9 | local data = json:decode(res) 10 | return data 11 | end 12 | 13 | local function bugzilla_check(id) 14 | -- data = bugzilla_login() 15 | local url = BASE_URL.."bug/" .. id .. "?api_key=" .. _config.bugzilla.apikey 16 | -- print(url) 17 | local res,code = https.request( url ) 18 | local data = json:decode(res) 19 | return data 20 | end 21 | 22 | local function bugzilla_listopened(email) 23 | local url = BASE_URL.."bug?include_fields=id,summary,status,whiteboard,resolution&email1=" .. email .. "&email2=" .. email .. "&emailassigned_to2=1&emailreporter1=1&emailtype1=substring&emailtype2=substring&f1=bug_status&f2=bug_status&n1=1&n2=1&o1=equals&o2=equals&resolution=---&v1=closed&v2=resolved&api_key=" .. _config.bugzilla.apikey 24 | local res,code = https.request( url ) 25 | print(res) 26 | local data = json:decode(res) 27 | return data 28 | end 29 | 30 | local function run(msg, matches) 31 | 32 | local response = "" 33 | 34 | if matches[1] == "status" then 35 | local data = bugzilla_check(matches[2]) 36 | vardump(data) 37 | if data.error == true then 38 | return "Sorry, API failed with message: " .. data.message 39 | else 40 | response = "Bug #"..matches[1]..":\nReporter: "..data.bugs[1].creator 41 | response = response .. "\n Last update: "..data.bugs[1].last_change_time 42 | response = response .. "\n Status: "..data.bugs[1].status.." "..data.bugs[1].resolution 43 | response = response .. "\n Whiteboard: "..data.bugs[1].whiteboard 44 | response = response .. "\n Access: https://bugzilla.mozilla.org/show_bug.cgi?id=" .. matches[1] 45 | print(response) 46 | end 47 | elseif matches[1] == "list" then 48 | local data = bugzilla_listopened(matches[2]) 49 | 50 | vardump(data) 51 | if data.error == true then 52 | return "Sorry, API failed with message: " .. data.message 53 | else 54 | 55 | -- response = "Bug #"..matches[1]..":\nReporter: "..data.bugs[1].creator 56 | -- response = response .. "\n Last update: "..data.bugs[1].last_change_time 57 | -- response = response .. "\n Status: "..data.bugs[1].status.." "..data.bugs[1].resolution 58 | -- response = response .. "\n Whiteboard: "..data.bugs[1].whiteboard 59 | -- response = response .. "\n Access: https://bugzilla.mozilla.org/show_bug.cgi?id=" .. matches[1] 60 | local total = table.map_length(data.bugs) 61 | 62 | print("total bugs: " .. total) 63 | local response = "There are " .. total .. " number of bug(s) assigned/reported by " .. matches[2] 64 | 65 | if total > 0 then 66 | response = response .. ": " 67 | 68 | for tableKey, bug in pairs(data.bugs) do 69 | response = response .. "\n #" .. bug.id 70 | response = response .. "\n Status: " .. bug.status .. " " .. bug.resolution 71 | response = response .. "\n Whiteboard: " .. bug.whiteboard 72 | response = response .. "\n Summary: " .. bug.summary 73 | end 74 | end 75 | end 76 | 77 | end 78 | return response 79 | end 80 | 81 | -- (table) 82 | -- [bugs] = (table) 83 | -- [1] = (table) 84 | -- [status] = (string) ASSIGNED 85 | -- [id] = (number) 927704 86 | -- [whiteboard] = (string) [approved][full processed] 87 | -- [summary] = (string) Budget Request - Arief Bayu Purwanto - https://reps.mozilla.org/e/mozilla-summit-2013/ 88 | -- [2] = (table) 89 | -- [status] = (string) ASSIGNED 90 | -- [id] = (number) 1049337 91 | -- [whiteboard] = (string) [approved][full processed][waiting receipts][waiting report and photos] 92 | -- [summary] = (string) Budget Request - Arief Bayu Purwanto - https://reps.mozilla.org/e/workshop-firefox-os-pada-workshop-media-sosial-untuk-perubahan-1/ 93 | -- total bugs: 2 94 | 95 | return { 96 | description = "Lookup bugzilla status update", 97 | usage = "/bot bugzilla [bug number]", 98 | patterns = { 99 | "^/bugzilla (status) (.*)$", 100 | "^/bugzilla (list) (.*)$" 101 | }, 102 | run = run 103 | } 104 | 105 | end -------------------------------------------------------------------------------- /plugins/calculator.lua: -------------------------------------------------------------------------------- 1 | -- Function reference: http://mathjs.org/docs/reference/functions/categorical.html 2 | 3 | local function mathjs(exp) 4 | local url = 'http://api.mathjs.org/v1/' 5 | url = url..'?expr='..URL.escape(exp) 6 | local b,c = http.request(url) 7 | local text = nil 8 | if c == 200 then 9 | text = 'Result: '..b 10 | 11 | elseif c == 400 then 12 | text = b 13 | else 14 | text = 'Unexpected error\n' 15 | ..'Is api.mathjs.org up?' 16 | end 17 | return text 18 | end 19 | 20 | local function run(msg, matches) 21 | return mathjs(matches[1]) 22 | end 23 | 24 | return { 25 | description = "Calculate math expressions with mathjs API", 26 | usage = "!calc [expression]: evaluates the expression and sends the result.", 27 | patterns = { 28 | "^!calc (.*)$" 29 | }, 30 | run = run 31 | } 32 | -------------------------------------------------------------------------------- /plugins/channels.lua: -------------------------------------------------------------------------------- 1 | -- Checks if bot was disabled on specific chat 2 | local function is_channel_disabled( receiver ) 3 | if not _config.disabled_channels then 4 | return false 5 | end 6 | 7 | if _config.disabled_channels[receiver] == nil then 8 | return false 9 | end 10 | 11 | return _config.disabled_channels[receiver] 12 | end 13 | 14 | local function enable_channel(receiver) 15 | if not _config.disabled_channels then 16 | _config.disabled_channels = {} 17 | end 18 | 19 | if _config.disabled_channels[receiver] == nil then 20 | return 'Channel isn\'t disabled' 21 | end 22 | 23 | _config.disabled_channels[receiver] = false 24 | 25 | save_config() 26 | return "Channel re-enabled" 27 | end 28 | 29 | local function disable_channel( receiver ) 30 | if not _config.disabled_channels then 31 | _config.disabled_channels = {} 32 | end 33 | 34 | _config.disabled_channels[receiver] = true 35 | 36 | save_config() 37 | return "Channel disabled" 38 | end 39 | 40 | local function pre_process(msg) 41 | local receiver = get_receiver(msg) 42 | 43 | -- If sender is sudo then re-enable the channel 44 | if is_sudo(msg) then 45 | if msg.text == "!channel enable" then 46 | enable_channel(receiver) 47 | end 48 | end 49 | 50 | if is_channel_disabled(receiver) then 51 | msg.text = "" 52 | end 53 | 54 | return msg 55 | end 56 | 57 | local function run(msg, matches) 58 | local receiver = get_receiver(msg) 59 | -- Enable a channel 60 | if matches[1] == 'enable' then 61 | return enable_channel(receiver) 62 | end 63 | -- Disable a channel 64 | if matches[1] == 'disable' then 65 | return disable_channel(receiver) 66 | end 67 | end 68 | 69 | return { 70 | description = "Plugin to manage channels. Enable or disable channel.", 71 | usage = { 72 | "!channel enable: enable current channel", 73 | "!channel disable: disable current channel" }, 74 | patterns = { 75 | "^!channel? (enable)", 76 | "^!channel? (disable)" }, 77 | run = run, 78 | privileged = true, 79 | pre_process = pre_process 80 | } -------------------------------------------------------------------------------- /plugins/chuck_norris.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local function unescape(str) 4 | str = string.gsub( str, '<', '<' ) 5 | str = string.gsub( str, '>', '>' ) 6 | str = string.gsub( str, '"', '"' ) 7 | str = string.gsub( str, ''', "'" ) 8 | str = string.gsub( str, '&#(%d+);', function(n) return string.char(n) end ) 9 | str = string.gsub( str, '&#x(%d+);', function(n) return string.char(tonumber(n,16)) end ) 10 | str = string.gsub( str, '&', '&' ) -- Be sure to do this after all others 11 | return str 12 | end 13 | 14 | local function chuck() 15 | local random = http.request("http://api.icndb.com/jokes/random") 16 | local decode = json:decode(random) 17 | local joke = decode.value.joke 18 | local unescape = unescape(joke) 19 | return unescape 20 | end 21 | 22 | function run(msg) 23 | local joke = chuck() 24 | return joke 25 | end 26 | 27 | return { 28 | description = "Get random Chuck Norris jokes.", 29 | usage = "!chuck", 30 | patterns = { 31 | "^!chuck$" 32 | }, 33 | run = run 34 | } 35 | 36 | end 37 | -------------------------------------------------------------------------------- /plugins/danbooru.lua: -------------------------------------------------------------------------------- 1 | do 2 | local URL = "http://danbooru.donmai.us" 3 | local URL_NEW = "/posts.json" 4 | local URL_POP = "/explore/posts/popular.json" 5 | 6 | local scale_day = "?scale=day" 7 | local scale_week = "?scale=week" 8 | local scale_month = "?scale=month" 9 | 10 | local function get_post(url) 11 | local b, c, h = http.request(url) 12 | if c ~= 200 then return nil end 13 | local posts = json:decode(b) 14 | 15 | return posts[math.random(#posts)] 16 | end 17 | 18 | local function run(msg, matches) 19 | 20 | local url = URL 21 | 22 | if matches[1] == "!danbooru" then 23 | url = url .. URL_NEW 24 | else 25 | url = url .. URL_POP 26 | 27 | if matches[1] == "d" then 28 | url = url .. scale_day 29 | elseif matches[1] == "w" then 30 | url = url .. scale_week 31 | elseif matches[1] == "m" then 32 | url = url .. scale_month 33 | end 34 | end 35 | 36 | local post = get_post(url) 37 | 38 | if post then 39 | vardump(post) 40 | local img = URL .. post.large_file_url 41 | send_photo_from_url(get_receiver(msg), img) 42 | 43 | local txt = '' 44 | if post.tag_string_artist ~= '' then 45 | txt = 'Artist: ' .. post.tag_string_artist .. '\n' 46 | end 47 | if post.tag_string_character ~= '' then 48 | txt = txt .. 'Character: ' .. post.tag_string_character .. '\n' 49 | end 50 | if post.file_size ~= '' then 51 | txt = txt .. '[' .. math.ceil(post.file_size/1000) .. 'kb] ' .. URL .. post.file_url 52 | end 53 | return txt 54 | end 55 | end 56 | 57 | return { 58 | description = "Gets a random fresh or popular image from Danbooru", 59 | usage = { 60 | "!danbooru - gets a random fresh image from Danbooru 🔞", 61 | "!danboorud - random daily popular image 🔞", 62 | "!danbooruw - random weekly popular image 🔞", 63 | "!danboorum - random monthly popular image 🔞" 64 | }, 65 | patterns = { 66 | "^!danbooru$", 67 | "^!danbooru ?(d)$", 68 | "^!danbooru ?(w)$", 69 | "^!danbooru ?(m)$" 70 | }, 71 | run = run 72 | } 73 | 74 | end -------------------------------------------------------------------------------- /plugins/dogify.lua: -------------------------------------------------------------------------------- 1 | local function run(msg, matches) 2 | local base = "http://dogr.io/" 3 | local path = string.gsub(matches[1], " ", "%%20") 4 | local url = base .. path .. '.png?split=false&.png' 5 | local urlm = "https?://[%%%w-_%.%?%.:/%+=&]+" 6 | 7 | if string.match(url, urlm) == url then 8 | local receiver = get_receiver(msg) 9 | send_photo_from_url(receiver, url) 10 | else 11 | print("Can't build a good URL with parameter " .. matches[1]) 12 | end 13 | end 14 | 15 | return { 16 | description = "Create a doge image with you words", 17 | usage = { 18 | "!dogify (your/words/with/slashes): Create a doge with the image and words" 19 | }, 20 | patterns = { 21 | "^!dogify (.+)$", 22 | }, 23 | run = run 24 | } 25 | -------------------------------------------------------------------------------- /plugins/download_media.lua: -------------------------------------------------------------------------------- 1 | local function callback(extra, success, result) 2 | if success then 3 | print('File downloaded to:', result) 4 | else 5 | print('Error downloading: '..extra) 6 | end 7 | end 8 | 9 | local function run(msg, matches) 10 | if msg.media then 11 | if msg.media.type == 'document' then 12 | load_document(msg.id, callback, msg.id) 13 | end 14 | if msg.media.type == 'photo' then 15 | load_photo(msg.id, callback, msg.id) 16 | end 17 | if msg.media.type == 'video' then 18 | load_video(msg.id, callback, msg.id) 19 | end 20 | if msg.media.type == 'audio' then 21 | load_audio(msg.id, callback, msg.id) 22 | end 23 | end 24 | end 25 | 26 | local function pre_process(msg) 27 | if not msg.text and msg.media then 28 | msg.text = '['..msg.media.type..']' 29 | end 30 | return msg 31 | end 32 | 33 | return { 34 | description = "When bot receives a media msg, download the media.", 35 | usage = "", 36 | run = run, 37 | patterns = { 38 | '%[(document)%]', 39 | '%[(photo)%]', 40 | '%[(video)%]', 41 | '%[(audio)%]' 42 | }, 43 | pre_process = pre_process 44 | } -------------------------------------------------------------------------------- /plugins/echo.lua: -------------------------------------------------------------------------------- 1 | 2 | local function run(msg, matches) 3 | local text = matches[1] 4 | local b = 1 5 | 6 | while b ~= 0 do 7 | text = text:trim() 8 | text,b = text:gsub('^!+','') 9 | end 10 | return text 11 | end 12 | 13 | return { 14 | description = "Simplest plugin ever!", 15 | usage = "!echo [whatever]: echoes the msg", 16 | patterns = { 17 | "^!echo +(.+)$" 18 | }, 19 | run = run 20 | } 21 | -------------------------------------------------------------------------------- /plugins/eur.lua: -------------------------------------------------------------------------------- 1 | do 2 | -- TODO: More currencies 3 | 4 | -- See http://webrates.truefx.com/rates/connect.html 5 | local function getEURUSD(usd) 6 | local url = 'http://webrates.truefx.com/rates/connect.html?c=EUR/USD&f=csv&s=n' 7 | local res,code = http.request(url) 8 | local rates = res:split(", ") 9 | local symbol = rates[1] 10 | local timestamp = rates[2] 11 | local sell = rates[3]..rates[4] 12 | local buy = rates[5]..rates[6] 13 | local text = symbol..'\n'..'Buy: '..buy..'\n'..'Sell: '..sell 14 | if usd then 15 | local eur = tonumber(usd) / tonumber(buy) 16 | text = text.."\n "..usd.."USD = "..eur.."EUR" 17 | end 18 | return text 19 | end 20 | 21 | local function run(msg, matches) 22 | if matches[1] == "!eur" then 23 | return getEURUSD(nil) 24 | end 25 | return getEURUSD(matches[1]) 26 | end 27 | 28 | return { 29 | description = "Real-time EURUSD market price", 30 | usage = "!eur [USD]", 31 | patterns = { 32 | "^!eur$", 33 | "^!eur (%d+[%d%.]*)$", 34 | }, 35 | run = run 36 | } 37 | 38 | end -------------------------------------------------------------------------------- /plugins/expand.lua: -------------------------------------------------------------------------------- 1 | local function run(msg, patterns) 2 | local response_body = {} 3 | local request_constructor = { 4 | url = patterns[1], 5 | method = "HEAD", 6 | sink = ltn12.sink.table(response_body), 7 | headers = {}, 8 | redirect = false 9 | } 10 | 11 | local ok, response_code, response_headers, response_status_line = http.request(request_constructor) 12 | if ok and response_headers.location then 13 | return " 👍 " .. response_headers.location 14 | else 15 | return "Can't expand the url." 16 | end 17 | end 18 | 19 | return { 20 | description = "Expand a shortened URL to the original one.", 21 | usage = "!expand [url]: Return the original URL", 22 | patterns = { 23 | "^!expand (https?://[%w-_%.%?%.:/%+=&]+)$" 24 | }, 25 | run = run 26 | } 27 | -------------------------------------------------------------------------------- /plugins/face.lua: -------------------------------------------------------------------------------- 1 | local https = require("ssl.https") 2 | local ltn12 = require "ltn12" 3 | 4 | -- Edit data/mashape.lua with your Mashape API key 5 | -- http://docs.mashape.com/api-keys 6 | local mashape = load_from_file('data/mashape.lua', { 7 | api_key = '' 8 | }) 9 | 10 | local function request(imageUrl) 11 | local api_key = mashape.api_key 12 | if api_key:isempty() then 13 | return nil, 'Configure your Mashape API Key' 14 | end 15 | 16 | local api = "https://faceplusplus-faceplusplus.p.mashape.com/detection/detect?" 17 | local parameters = "attribute=gender%2Cage%2Crace" 18 | parameters = parameters .. "&url="..(URL.escape(imageUrl) or "") 19 | local url = api..parameters 20 | local headers = { 21 | ["X-Mashape-Key"] = api_key, 22 | ["Accept"] = "Accept: application/json" 23 | } 24 | print(url) 25 | local respbody = {} 26 | local body, code = https.request{ 27 | url = url, 28 | method = "GET", 29 | headers = headers, 30 | sink = ltn12.sink.table(respbody), 31 | protocol = "tlsv1" 32 | } 33 | if code ~= 200 then return "", code end 34 | local body = table.concat(respbody) 35 | return body, code 36 | end 37 | 38 | local function parseData(data) 39 | local jsonBody = json:decode(data) 40 | local response = "" 41 | if jsonBody.error ~= nil then 42 | if jsonBody.error == "IMAGE_ERROR_FILE_TOO_LARGE" then 43 | response = response .. "The image is too big. Provide a smaller image." 44 | elseif jsonBody.error == "IMAGE_ERROR_FAILED_TO_DOWNLOAD" then 45 | response = response .. "Is that a valid url for an image?" 46 | else 47 | response = response .. jsonBody.error 48 | end 49 | elseif jsonBody.face == nil or #jsonBody.face == 0 then 50 | response = response .. "No faces found" 51 | else 52 | response = response .. #jsonBody.face .." face(s) found:\n\n" 53 | for k,face in pairs(jsonBody.face) do 54 | local raceP = "" 55 | if face.attribute.race.confidence > 85.0 then 56 | raceP = face.attribute.race.value:lower() 57 | elseif face.attribute.race.confidence > 50.0 then 58 | raceP = "(probably "..face.attribute.race.value:lower()..")" 59 | else 60 | raceP = "(posibly "..face.attribute.race.value:lower()..")" 61 | end 62 | if face.attribute.gender.confidence > 85.0 then 63 | response = response .. "There is a " 64 | else 65 | response = response .. "There may be a " 66 | end 67 | response = response .. raceP .. " " .. face.attribute.gender.value:lower() .. " " 68 | response = response .. ", " .. face.attribute.age.value .. "(±".. face.attribute.age.range ..") years old \n" 69 | end 70 | end 71 | return response 72 | end 73 | 74 | local function run(msg, matches) 75 | --return request('http://www.uni-regensburg.de/Fakultaeten/phil_Fak_II/Psychologie/Psy_II/beautycheck/english/durchschnittsgesichter/m(01-32)_gr.jpg') 76 | local data, code = request(matches[1]) 77 | if code ~= 200 then return "There was an error. "..code end 78 | return parseData(data) 79 | end 80 | 81 | return { 82 | description = "Who is in that photo?", 83 | usage = { 84 | "!face [url]", 85 | "!recognise [url]" 86 | }, 87 | patterns = { 88 | "^!face (.*)$", 89 | "^!recognise (.*)$" 90 | }, 91 | run = run 92 | } 93 | -------------------------------------------------------------------------------- /plugins/fortunes_uc3m.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local function get_fortunes_uc3m() 4 | local i = math.random(0,178) -- max 178 5 | local web = "http://www.gul.es/fortunes/f"..i 6 | local b, c, h = http.request(web) 7 | return b 8 | end 9 | 10 | 11 | local function run(msg, matches) 12 | return get_fortunes_uc3m() 13 | end 14 | 15 | return { 16 | description = "Fortunes from Universidad Carlos III", 17 | usage = "!uc3m", 18 | patterns = { 19 | "^!uc3m$" 20 | }, 21 | run = run 22 | } 23 | 24 | end -------------------------------------------------------------------------------- /plugins/get.lua: -------------------------------------------------------------------------------- 1 | local function get_variables_hash(msg) 2 | if msg.to.type == 'chat' then 3 | return 'chat:'..msg.to.id..':variables' 4 | end 5 | if msg.to.type == 'user' then 6 | return 'user:'..msg.from.id..':variables' 7 | end 8 | end 9 | 10 | local function list_variables(msg) 11 | local hash = get_variables_hash(msg) 12 | 13 | if hash then 14 | local names = redis:hkeys(hash) 15 | local text = '' 16 | for i=1, #names do 17 | text = text..names[i]..'\n' 18 | end 19 | return text 20 | end 21 | end 22 | 23 | local function get_value(msg, var_name) 24 | local hash = get_variables_hash(msg) 25 | if hash then 26 | local value = redis:hget(hash, var_name) 27 | if not value then 28 | return'Not found, use "!get" to list variables' 29 | else 30 | return var_name..' => '..value 31 | end 32 | end 33 | end 34 | 35 | local function run(msg, matches) 36 | if matches[2] then 37 | return get_value(msg, matches[2]) 38 | else 39 | return list_variables(msg) 40 | end 41 | end 42 | 43 | return { 44 | description = "Retrieves variables saved with !set", 45 | usage = "!get (value_name): Returns the value_name value.", 46 | patterns = { 47 | "^(!get) (.+)$", 48 | "^!get$" 49 | }, 50 | run = run 51 | } 52 | -------------------------------------------------------------------------------- /plugins/giphy.lua: -------------------------------------------------------------------------------- 1 | -- Idea by https://github.com/asdofindia/telegram-bot/ 2 | -- See http://api.giphy.com/ 3 | 4 | do 5 | 6 | local BASE_URL = 'http://api.giphy.com/v1' 7 | local API_KEY = 'dc6zaTOxFJmzC' -- public beta key 8 | 9 | local function get_image(response) 10 | local images = json:decode(response).data 11 | if #images == 0 then return nil end -- No images 12 | local i = math.random(#images) 13 | local image = images[i] -- A random one 14 | 15 | if image.images.downsized then 16 | return image.images.downsized.url 17 | end 18 | 19 | if image.images.original then 20 | return image.original.url 21 | end 22 | 23 | return nil 24 | end 25 | 26 | local function get_random_top() 27 | local url = BASE_URL.."/gifs/trending?api_key="..API_KEY 28 | local response, code = http.request(url) 29 | if code ~= 200 then return nil end 30 | return get_image(response) 31 | end 32 | 33 | local function search(text) 34 | text = URL.escape(text) 35 | local url = BASE_URL.."/gifs/search?q="..text.."&api_key="..API_KEY 36 | local response, code = http.request(url) 37 | if code ~= 200 then return nil end 38 | return get_image(response) 39 | end 40 | 41 | local function run(msg, matches) 42 | local gif_url = nil 43 | 44 | -- If no search data, a random trending GIF will be sent 45 | if matches[1] == "!gif" or matches[1] == "!giphy" then 46 | gif_url = get_random_top() 47 | else 48 | gif_url = search(matches[1]) 49 | end 50 | 51 | if not gif_url then 52 | return "Error: GIF not found" 53 | end 54 | 55 | local receiver = get_receiver(msg) 56 | print("GIF URL"..gif_url) 57 | 58 | send_document_from_url(receiver, gif_url) 59 | end 60 | 61 | return { 62 | description = "GIFs from telegram with Giphy API", 63 | usage = { 64 | "!gif (term): Search and sends GIF from Giphy. If no param, sends a trending GIF.", 65 | "!giphy (term): Search and sends GIF from Giphy. If no param, sends a trending GIF." 66 | }, 67 | patterns = { 68 | "^!gif$", 69 | "^!gif (.*)", 70 | "^!giphy (.*)", 71 | "^!giphy$" 72 | }, 73 | run = run 74 | } 75 | 76 | end 77 | -------------------------------------------------------------------------------- /plugins/gnuplot.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Gnuplot plugin by psykomantis 3 | * dependencies: 4 | * - gnuplot 5.00 5 | * - libgd2-xpm-dev (on Debian distr) for more info visit: https://libgd.github.io/pages/faq.html 6 | * 7 | ]] 8 | 9 | -- Gnuplot needs absolute path for the plot, so i run some commands to find where we are 10 | local outputFile = io.popen("pwd","r") 11 | io.input(outputFile) 12 | local _pwd = io.read("*line") 13 | io.close(outputFile) 14 | local _absolutePlotPath = _pwd .. "/data/plot.png" 15 | local _scriptPath = "./data/gnuplotScript.gpl" 16 | 17 | do 18 | 19 | local function gnuplot(msg, fun) 20 | local receiver = get_receiver(msg) 21 | 22 | -- We generate the plot commands 23 | local formattedString = [[ 24 | set grid 25 | set terminal png 26 | set output "]] .. _absolutePlotPath .. [[" 27 | plot ]] .. fun 28 | 29 | local file = io.open(_scriptPath,"w"); 30 | file:write(formattedString) 31 | file:close() 32 | 33 | os.execute("gnuplot " .. _scriptPath) 34 | os.remove (_scriptPath) 35 | 36 | return _send_photo(receiver, _absolutePlotPath) 37 | end 38 | 39 | -- Check all dependencies before executing 40 | local function checkDependencies() 41 | local status = os.execute("gnuplot -h") 42 | if(status==true) then 43 | status = os.execute("gnuplot -e 'set terminal png'") 44 | if(status == true) then 45 | return 0 -- OK ready to go! 46 | else 47 | return 1 -- missing libgd2-xpm-dev 48 | end 49 | else 50 | return 2 -- missing gnuplot 51 | end 52 | end 53 | 54 | local function run(msg, matches) 55 | local status = checkDependencies() 56 | if(status == 0) then 57 | return gnuplot(msg,matches[1]) 58 | elseif(status == 1) then 59 | return "It seems that this bot miss a dependency :/" 60 | else 61 | return "It seems that this bot doesn't have gnuplot :/" 62 | end 63 | end 64 | 65 | return { 66 | description = "use gnuplot through telegram, only plot single variable function", 67 | usage = "!gnuplot [single variable function]", 68 | patterns = {"^!gnuplot (.+)$"}, 69 | run = run 70 | } 71 | 72 | end 73 | -------------------------------------------------------------------------------- /plugins/google.lua: -------------------------------------------------------------------------------- 1 | local function googlethat(query) 2 | local api = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&" 3 | local parameters = "q=".. (URL.escape(query) or "") 4 | 5 | -- Do the request 6 | local res, code = https.request(api..parameters) 7 | if code ~=200 then return nil end 8 | local data = json:decode(res) 9 | 10 | local results = {} 11 | for key,result in ipairs(data.responseData.results) do 12 | table.insert(results, { 13 | result.titleNoFormatting, 14 | result.unescapedUrl or result.url 15 | }) 16 | end 17 | return results 18 | end 19 | 20 | local function stringlinks(results) 21 | local stringresults="" 22 | for key,val in ipairs(results) do 23 | stringresults=stringresults..val[1].." - "..val[2].."\n" 24 | end 25 | return stringresults 26 | end 27 | 28 | local function run(msg, matches) 29 | local results = googlethat(matches[1]) 30 | return stringlinks(results) 31 | end 32 | 33 | return { 34 | description = "Searches Google and send results", 35 | usage = "!google [terms]: Searches Google and send results", 36 | patterns = { 37 | "^!google (.*)$", 38 | "^%.[g|G]oogle (.*)$" 39 | }, 40 | run = run 41 | } 42 | -------------------------------------------------------------------------------- /plugins/gps.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function run(msg, matches) 4 | local lat = matches[1] 5 | local lon = matches[2] 6 | local receiver = get_receiver(msg) 7 | 8 | local zooms = {16, 18} 9 | local urls = {} 10 | for i = 1, #zooms do 11 | local zoom = zooms[i] 12 | local url = "http://maps.googleapis.com/maps/api/staticmap?zoom=" .. zoom .. "&size=600x300&maptype=roadmap¢er=" .. lat .. "," .. lon .. "&markers=color:blue%7Clabel:X%7C" .. lat .. "," .. lon 13 | table.insert(urls, url) 14 | end 15 | 16 | send_photos_from_url(receiver, urls) 17 | 18 | return "www.google.es/maps/place/@" .. lat .. "," .. lon 19 | end 20 | 21 | return { 22 | description = "generates a map showing the given GPS coordinates", 23 | usage = "!gps latitude,longitude: generates a map showing the given GPS coordinates", 24 | patterns = {"^!gps ([^,]*)[,%s]([^,]*)$"}, 25 | run = run 26 | } 27 | 28 | end -------------------------------------------------------------------------------- /plugins/hackernews.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function run(msg, matches) 4 | local result = 'Hacker News Top5:\n' 5 | local top_stories_json, code = https.request('https://hacker-news.firebaseio.com/v0/topstories.json') 6 | if code ~=200 then return nil end 7 | local top_stories = json:decode(top_stories_json) 8 | for i = 1, 5 do 9 | local story_json, code = https.request('https://hacker-news.firebaseio.com/v0/item/'..top_stories[i]..'.json') 10 | if code ~=200 then return nil end 11 | local story = json:decode(story_json) 12 | result = result .. i .. '. ' .. story.title .. ' - ' .. story.url .. '\n' 13 | end 14 | return result 15 | end 16 | 17 | return { 18 | description = "Show top 5 hacker news (ycombinator.com)", 19 | usage = "!hackernews", 20 | patterns = {"^!hackernews$"}, 21 | run = run 22 | } 23 | 24 | end 25 | -------------------------------------------------------------------------------- /plugins/hello.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function run(msg, matches) 4 | return "Hello, " .. matches[1] 5 | end 6 | 7 | return { 8 | description = "Says hello to someone", 9 | usage = "say hello to [name]", 10 | patterns = { 11 | "^say hello to (.*)$", 12 | "^Say hello to (.*)$" 13 | }, 14 | run = run 15 | } 16 | 17 | end -------------------------------------------------------------------------------- /plugins/help.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | -- Returns true if is not empty 4 | local function has_usage_data(dict) 5 | if (dict.usage == nil or dict.usage == '') then 6 | return false 7 | end 8 | return true 9 | end 10 | 11 | -- Get commands for that plugin 12 | local function plugin_help(name) 13 | local plugin = plugins[name] 14 | if not plugin then return nil end 15 | 16 | local text = "" 17 | if (type(plugin.usage) == "table") then 18 | for ku,usage in pairs(plugin.usage) do 19 | text = text..usage..'\n' 20 | end 21 | text = text..'\n' 22 | elseif has_usage_data(plugin) then -- Is not empty 23 | text = text..plugin.usage..'\n\n' 24 | end 25 | return text 26 | end 27 | 28 | -- !help command 29 | local function telegram_help() 30 | local text = "Plugin list: \n\n" 31 | -- Plugins names 32 | for name in pairs(plugins) do 33 | text = text..name..'\n' 34 | end 35 | text = text..'\n'..'Write "!help [plugin name]" for more info.' 36 | text = text..'\n'..'Or "!help all" to show all info.' 37 | return text 38 | end 39 | 40 | -- !help all command 41 | local function help_all() 42 | local ret = "" 43 | for name in pairs(plugins) do 44 | ret = ret .. plugin_help(name) 45 | end 46 | return ret 47 | end 48 | 49 | local function run(msg, matches) 50 | if matches[1] == "!help" then 51 | return telegram_help() 52 | elseif matches[1] == "!help all" then 53 | return help_all() 54 | else 55 | local text = plugin_help(matches[1]) 56 | if not text then 57 | text = telegram_help() 58 | end 59 | return text 60 | end 61 | end 62 | 63 | return { 64 | description = "Help plugin. Get info from other plugins. ", 65 | usage = { 66 | "!help: Show list of plugins.", 67 | "!help all: Show all commands for every plugin.", 68 | "!help [plugin name]: Commands for that plugin." 69 | }, 70 | patterns = { 71 | "^!help$", 72 | "^!help all", 73 | "^!help (.+)" 74 | }, 75 | run = run 76 | } 77 | 78 | end -------------------------------------------------------------------------------- /plugins/id.lua: -------------------------------------------------------------------------------- 1 | local function user_print_name(user) 2 | if user.print_name then 3 | return user.print_name 4 | end 5 | local text = '' 6 | if user.first_name then 7 | text = user.last_name..' ' 8 | end 9 | if user.lastname then 10 | text = text..user.last_name 11 | end 12 | return text 13 | end 14 | 15 | local function returnids(cb_extra, success, result) 16 | local receiver = cb_extra.receiver 17 | local chat_id = "chat#id"..result.id 18 | local chatname = result.print_name 19 | 20 | local text = 'IDs for chat '..chatname 21 | ..' ('..chat_id..')\n' 22 | ..'There are '..result.members_num..' members' 23 | ..'\n---------\n' 24 | for k,v in pairs(result.members) do 25 | text = text .. v.print_name .. " (user#id" .. v.id .. ")\n" 26 | end 27 | send_large_msg(receiver, text) 28 | end 29 | 30 | local function run(msg, matches) 31 | local receiver = get_receiver(msg) 32 | if matches[1] == "!id" then 33 | local text = user_print_name(msg.from) .. ' (user#id' .. msg.from.id .. ')' 34 | if is_chat_msg(msg) then 35 | text = text .. "\nYou are in group " .. user_print_name(msg.to) .. " (chat#id" .. msg.to.id .. ")" 36 | end 37 | return text 38 | elseif matches[1] == "chat" then 39 | -- !ids? (chat) (%d+) 40 | if matches[2] and is_sudo(msg) then 41 | local chat = 'chat#id'..matches[2] 42 | chat_info(chat, returnids, {receiver=receiver}) 43 | else 44 | if not is_chat_msg(msg) then 45 | return "You are not in a group." 46 | end 47 | local chat = get_receiver(msg) 48 | chat_info(chat, returnids, {receiver=receiver}) 49 | end 50 | elseif matches[1] == "member" and matches[2] == "@" then 51 | local nick = matches[3] 52 | local chat = get_receiver(msg) 53 | if not is_chat_msg(msg) then 54 | return "You are not in a group." 55 | end 56 | chat_info(chat, function (extra, success, result) 57 | local receiver = extra.receiver 58 | local nick = extra.nick 59 | local found 60 | for k,user in pairs(result.members) do 61 | if user.username == nick then 62 | found = user 63 | end 64 | end 65 | if not found then 66 | send_msg(receiver, "User not found on this chat.", ok_cb, false) 67 | else 68 | local text = "ID: "..found.id 69 | send_msg(receiver, text, ok_cb, false) 70 | end 71 | end, {receiver=chat, nick=nick}) 72 | elseif matches[1] == "members" and matches[2] == "name" then 73 | local text = matches[3] 74 | local chat = get_receiver(msg) 75 | if not is_chat_msg(msg) then 76 | return "You are not in a group." 77 | end 78 | chat_info(chat, function (extra, success, result) 79 | local members = result.members 80 | local receiver = extra.receiver 81 | local text = extra.text 82 | 83 | local founds = {} 84 | for k,member in pairs(members) do 85 | local fields = {'first_name', 'print_name', 'username'} 86 | for k,field in pairs(fields) do 87 | if member[field] and type(member[field]) == "string" then 88 | if member[field]:match(text) then 89 | local id = tostring(member.id) 90 | founds[id] = member 91 | end 92 | end 93 | end 94 | end 95 | if next(founds) == nil then -- Empty table 96 | send_msg(receiver, "User not found on this chat.", ok_cb, false) 97 | else 98 | local text = "" 99 | for k,user in pairs(founds) do 100 | local first_name = user.first_name or "" 101 | local print_name = user.print_name or "" 102 | local user_name = user.user_name or "" 103 | local id = user.id or "" -- This would be funny 104 | text = text.."First name: "..first_name.."\n" 105 | .."Print name: "..print_name.."\n" 106 | .."User name: "..user_name.."\n" 107 | .."ID: "..id 108 | end 109 | send_msg(receiver, text, ok_cb, false) 110 | end 111 | end, {receiver=chat, text=text}) 112 | end 113 | end 114 | 115 | return { 116 | description = "Know your id or the id of a chat members.", 117 | usage = { 118 | "!id: Return your ID and the chat id if you are in one.", 119 | "!ids chat: Return the IDs of the current chat members.", 120 | "!ids chat : Return the IDs of the members.", 121 | "!id member @: Return the member @ ID from the current chat", 122 | "!id members name : Search for users with on first_name, print_name or username on current chat" 123 | }, 124 | patterns = { 125 | "^!id$", 126 | "^!ids? (chat) (%d+)$", 127 | "^!ids? (chat)$", 128 | "^!id (member) (@)(.+)", 129 | "^!id (members) (name) (.+)" 130 | }, 131 | run = run 132 | } 133 | -------------------------------------------------------------------------------- /plugins/images.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function run(msg, matches) 4 | local url = matches[1] 5 | local receiver = get_receiver(msg) 6 | send_photo_from_url(receiver, url) 7 | end 8 | 9 | return { 10 | description = "When user sends image URL (ends with png, jpg, jpeg) download and send it to origin.", 11 | usage = "", 12 | patterns = { 13 | "(https?://[%w-_%.%?%.:/%+=&]+%.png)$", 14 | "(https?://[%w-_%.%?%.:/%+=&]+%.jpg)$", 15 | "(https?://[%w-_%.%?%.:/%+=&]+%.jpeg)$", 16 | }, 17 | run = run 18 | } 19 | 20 | end 21 | -------------------------------------------------------------------------------- /plugins/imdb.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local function imdb(movie) 4 | local http = require("socket.http") 5 | local movie = movie:gsub(' ', '+') 6 | local url = "http://www.omdbapi.com/?t=" .. movie 7 | local response, code, headers = http.request(url) 8 | 9 | if code ~= 200 then 10 | return "Error: " .. code 11 | end 12 | 13 | if #response > 0 then 14 | local r = json:decode(response) 15 | vardump(r) 16 | if r.Error then 17 | return r.Error 18 | end 19 | r['Url'] = "http://imdb.com/title/" .. r.imdbID 20 | local t = "" 21 | for k, v in pairs(r) do 22 | t = t..k..": "..v.. "\n" 23 | end 24 | return t 25 | end 26 | return nil 27 | end 28 | 29 | local function run(msg, matches) 30 | return imdb(matches[1]) 31 | end 32 | 33 | return { 34 | description = "IMDB plugin for telegram", 35 | usage = "!imdb [movie]", 36 | patterns = {"^!imdb (.+)"}, 37 | run = run 38 | } 39 | 40 | end 41 | -------------------------------------------------------------------------------- /plugins/img_google.lua: -------------------------------------------------------------------------------- 1 | do 2 | local mime = require("mime") 3 | 4 | local google_config = load_from_file('data/google.lua') 5 | local cache = {} 6 | 7 | --[[ 8 | local function send_request(url) 9 | local t = {} 10 | local options = { 11 | url = url, 12 | sink = ltn12.sink.table(t), 13 | method = "GET" 14 | } 15 | local a, code, headers, status = http.request(options) 16 | return table.concat(t), code, headers, status 17 | end]]-- 18 | 19 | local function get_google_data(text) 20 | local url = "http://ajax.googleapis.com/ajax/services/search/images?" 21 | url = url.."v=1.0&rsz=5" 22 | url = url.."&q="..URL.escape(text) 23 | url = url.."&imgsz=small|medium|large" 24 | if google_config.api_keys then 25 | local i = math.random(#google_config.api_keys) 26 | local api_key = google_config.api_keys[i] 27 | if api_key then 28 | url = url.."&key="..api_key 29 | end 30 | end 31 | 32 | local res, code = http.request(url) 33 | 34 | if code ~= 200 then 35 | print("HTTP Error code:", code) 36 | return nil 37 | end 38 | 39 | local google = json:decode(res) 40 | return google 41 | end 42 | 43 | -- Returns only the useful google data to save on cache 44 | local function simple_google_table(google) 45 | local new_table = {} 46 | new_table.responseData = {} 47 | new_table.responseDetails = google.responseDetails 48 | new_table.responseStatus = google.responseStatus 49 | new_table.responseData.results = {} 50 | local results = google.responseData.results 51 | for k,result in pairs(results) do 52 | new_table.responseData.results[k] = {} 53 | new_table.responseData.results[k].unescapedUrl = result.unescapedUrl 54 | new_table.responseData.results[k].url = result.url 55 | end 56 | return new_table 57 | end 58 | 59 | local function save_to_cache(query, data) 60 | -- Saves result on cache 61 | if string.len(query) <= 7 then 62 | local text_b64 = mime.b64(query) 63 | if not cache[text_b64] then 64 | local simple_google = simple_google_table(data) 65 | cache[text_b64] = simple_google 66 | end 67 | end 68 | end 69 | 70 | local function process_google_data(google, receiver, query) 71 | if google.responseStatus == 403 then 72 | local text = 'ERROR: Reached maximum searches per day' 73 | send_msg(receiver, text, ok_cb, false) 74 | 75 | elseif google.responseStatus == 200 then 76 | local data = google.responseData 77 | 78 | if not data or not data.results or #data.results == 0 then 79 | local text = 'Image not found.' 80 | send_msg(receiver, text, ok_cb, false) 81 | return false 82 | end 83 | 84 | -- Random image from table 85 | local i = math.random(#data.results) 86 | local url = data.results[i].unescapedUrl or data.results[i].url 87 | local old_timeout = http.TIMEOUT or 10 88 | http.TIMEOUT = 5 89 | send_photo_from_url(receiver, url) 90 | http.TIMEOUT = old_timeout 91 | 92 | save_to_cache(query, google) 93 | 94 | else 95 | local text = 'ERROR!' 96 | send_msg(receiver, text, ok_cb, false) 97 | end 98 | end 99 | 100 | function run(msg, matches) 101 | local receiver = get_receiver(msg) 102 | local text = matches[1] 103 | local text_b64 = mime.b64(text) 104 | local cached = cache[text_b64] 105 | if cached then 106 | process_google_data(cached, receiver, text) 107 | else 108 | local data = get_google_data(text) 109 | process_google_data(data, receiver, text) 110 | end 111 | end 112 | 113 | return { 114 | description = "Search image with Google API and sends it.", 115 | usage = "!img [term]: Random search an image with Google API.", 116 | patterns = { 117 | "^!img (.*)$" 118 | }, 119 | run = run 120 | } 121 | 122 | end 123 | -------------------------------------------------------------------------------- /plugins/invite.lua: -------------------------------------------------------------------------------- 1 | -- Invite other user to the chat group. 2 | -- Use !invite name User_name or !invite id id_number 3 | -- The User_name is the print_name (there are no spaces but _) 4 | 5 | do 6 | 7 | local function callback(extra, success, result) 8 | vardump(success) 9 | vardump(result) 10 | end 11 | 12 | local function run(msg, matches) 13 | local user = matches[2] 14 | 15 | -- User submitted a user name 16 | if matches[1] == "name" then 17 | user = string.gsub(user," ","_") 18 | end 19 | 20 | -- User submitted an id 21 | if matches[1] == "id" then 22 | user = 'user#id'..user 23 | end 24 | 25 | -- The message must come from a chat group 26 | if msg.to.type == 'chat' then 27 | local chat = 'chat#id'..msg.to.id 28 | chat_add_user(chat, user, callback, false) 29 | return "Add: "..user.." to "..chat 30 | else 31 | return 'This isnt a chat group!' 32 | end 33 | 34 | end 35 | 36 | return { 37 | description = "Invite other user to the chat group", 38 | usage = { 39 | "!invite name [user_name]", 40 | "!invite id [user_id]" }, 41 | patterns = { 42 | "^!invite (name) (.*)$", 43 | "^!invite (id) (%d+)$" 44 | }, 45 | run = run 46 | } 47 | 48 | end -------------------------------------------------------------------------------- /plugins/inviteme.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local function parsed_url(link) 4 | local parsed_link = URL.parse(link) 5 | local parsed_path = URL.parse_path(parsed_link.path) 6 | return parsed_path[2] 7 | end 8 | 9 | function run(msg, matches) 10 | local hash = parsed_url(matches[1]) 11 | join = import_chat_link(hash,ok_cb,false) 12 | end 13 | 14 | 15 | return { 16 | description = "Invite me into a group chat", 17 | usage = "!inviteme [invite link]", 18 | patterns = { 19 | "^!inviteme (.*)$" 20 | }, 21 | run = run 22 | } 23 | 24 | end 25 | -------------------------------------------------------------------------------- /plugins/isX.lua: -------------------------------------------------------------------------------- 1 | local https = require "ssl.https" 2 | local ltn12 = require "ltn12" 3 | 4 | local function request(imageUrl) 5 | -- Edit data/mashape.lua with your Mashape API key 6 | -- http://docs.mashape.com/api-keys 7 | local mashape = load_from_file('data/mashape.lua', { 8 | api_key = '' 9 | }) 10 | 11 | local api_key = mashape.api_key 12 | if api_key:isempty() then 13 | return nil, 'Configure your Mashape API Key' 14 | end 15 | 16 | local api = "https://sphirelabs-advanced-porn-nudity-and-adult-content-detection.p.mashape.com/v1/get/index.php?" 17 | local parameters = "&url="..(URL.escape(imageUrl) or "") 18 | local url = api..parameters 19 | local respbody = {} 20 | local headers = { 21 | ["X-Mashape-Key"] = api_key, 22 | ["Accept"] = "Accept: application/json" 23 | } 24 | print(url) 25 | local body, code, headers, status = https.request{ 26 | url = url, 27 | method = "GET", 28 | headers = headers, 29 | sink = ltn12.sink.table(respbody), 30 | protocol = "tlsv1" 31 | } 32 | if code ~= 200 then return "", code end 33 | local body = table.concat(respbody) 34 | return body, code 35 | end 36 | 37 | local function parseData(data) 38 | local jsonBody = json:decode(data) 39 | local response = "" 40 | print(data) 41 | if jsonBody["Error Occured"] ~= nil then 42 | response = response .. jsonBody["Error Occured"] 43 | elseif jsonBody["Is Porn"] == nil or jsonBody["Reason"] == nil then 44 | response = response .. "I don't know if that has adult content or not." 45 | else 46 | if jsonBody["Is Porn"] == "True" then 47 | response = response .. "Beware!\n" 48 | end 49 | response = response .. jsonBody["Reason"] 50 | end 51 | return jsonBody["Is Porn"], response 52 | end 53 | 54 | local function run(msg, matches) 55 | local data, code = request(matches[1]) 56 | if code ~= 200 then return "There was an error. "..code end 57 | local isPorn, result = parseData(data) 58 | return result 59 | end 60 | 61 | return { 62 | description = "Does this photo contain adult content?", 63 | usage = { 64 | "!isx [url]", 65 | "!isporn [url]" 66 | }, 67 | patterns = { 68 | "^!is[x|X] (.*)$", 69 | "^!is[p|P]orn (.*)$" 70 | }, 71 | run = run 72 | } -------------------------------------------------------------------------------- /plugins/isup.lua: -------------------------------------------------------------------------------- 1 | do 2 | local socket = require("socket") 3 | local cronned = load_from_file('data/isup.lua') 4 | 5 | local function save_cron(msg, url, delete) 6 | local origin = get_receiver(msg) 7 | if not cronned[origin] then 8 | cronned[origin] = {} 9 | end 10 | if not delete then 11 | table.insert(cronned[origin], url) 12 | else 13 | for k,v in pairs(cronned[origin]) do 14 | if v == url then 15 | table.remove(cronned[origin], k) 16 | end 17 | end 18 | end 19 | serialize_to_file(cronned, 'data/isup.lua') 20 | return 'Saved!' 21 | end 22 | 23 | local function is_up_socket(ip, port) 24 | print('Connect to', ip, port) 25 | local c = socket.try(socket.tcp()) 26 | c:settimeout(3) 27 | local conn = c:connect(ip, port) 28 | if not conn then 29 | return false 30 | else 31 | c:close() 32 | return true 33 | end 34 | end 35 | 36 | local function is_up_http(url) 37 | -- Parse URL from input, default to http 38 | local parsed_url = URL.parse(url, { scheme = 'http', authority = '' }) 39 | -- Fix URLs without subdomain not parsed properly 40 | if not parsed_url.host and parsed_url.path then 41 | parsed_url.host = parsed_url.path 42 | parsed_url.path = "" 43 | end 44 | -- Re-build URL 45 | local url = URL.build(parsed_url) 46 | 47 | local protocols = { 48 | ["https"] = https, 49 | ["http"] = http 50 | } 51 | local options = { 52 | url = url, 53 | redirect = false, 54 | method = "GET" 55 | } 56 | local response = { protocols[parsed_url.scheme].request(options) } 57 | local code = tonumber(response[2]) 58 | if code == nil or code >= 400 then 59 | return false 60 | end 61 | return true 62 | end 63 | 64 | local function isup(url) 65 | local pattern = '^(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?):?(%d?%d?%d?%d?%d?)$' 66 | local ip,port = string.match(url, pattern) 67 | local result = nil 68 | 69 | -- !isup 8.8.8.8:53 70 | if ip then 71 | port = port or '80' 72 | result = is_up_socket(ip, port) 73 | else 74 | result = is_up_http(url) 75 | end 76 | 77 | return result 78 | end 79 | 80 | local function cron() 81 | for chan, urls in pairs(cronned) do 82 | for k,url in pairs(urls) do 83 | print('Checking', url) 84 | if not isup(url) then 85 | local text = url..' looks DOWN from here. 😱' 86 | send_msg(chan, text, ok_cb, false) 87 | end 88 | end 89 | end 90 | end 91 | 92 | local function run(msg, matches) 93 | if matches[1] == 'cron delete' then 94 | if not is_sudo(msg) then 95 | return 'This command requires privileged user' 96 | end 97 | return save_cron(msg, matches[2], true) 98 | 99 | elseif matches[1] == 'cron' then 100 | if not is_sudo(msg) then 101 | return 'This command requires privileged user' 102 | end 103 | return save_cron(msg, matches[2]) 104 | 105 | elseif isup(matches[1]) then 106 | return matches[1]..' looks UP from here. 😃' 107 | else 108 | return matches[1]..' looks DOWN from here. 😱' 109 | end 110 | end 111 | 112 | return { 113 | description = "Check if a website or server is up.", 114 | usage = { 115 | "!isup [host]: Performs a HTTP request or Socket (ip:port) connection", 116 | "!isup cron [host]: Every 5mins check if host is up. (Requires privileged user)", 117 | "!isup cron delete [host]: Disable checking that host." 118 | }, 119 | patterns = { 120 | "^!isup (cron delete) (.*)$", 121 | "^!isup (cron) (.*)$", 122 | "^!isup (.*)$", 123 | "^!ping (.*)$", 124 | "^!ping (cron delete) (.*)$", 125 | "^!ping (cron) (.*)$" 126 | }, 127 | run = run, 128 | cron = cron 129 | } 130 | 131 | end 132 | -------------------------------------------------------------------------------- /plugins/kickme.lua: -------------------------------------------------------------------------------- 1 | local function kick_user(user_id, chat_id) 2 | local chat = 'chat#id'..chat_id 3 | local user = 'user#id'..user_id 4 | chat_del_user(chat, user, function (data, success, result) 5 | if success ~= 1 then 6 | send_msg(data.chat, 'Error while kicking user', ok_cb, nil) 7 | end 8 | end, {chat=chat, user=user}) 9 | end 10 | 11 | local function run (msg, matches) 12 | local user = msg.from.id 13 | local chat = msg.to.id 14 | 15 | if msg.to.type ~= 'chat' then 16 | return "Not a chat group!" 17 | elseif user == tostring(our_id) then 18 | --[[ A robot must protect its own existence as long as such protection does 19 | not conflict with the First or Second Laws. ]]-- 20 | return "I won't kick myself!" 21 | elseif is_sudo(msg) then 22 | return "I won't kick an admin!" 23 | else 24 | kick_user(user, chat) 25 | end 26 | end 27 | 28 | return { 29 | description = "Bot kicks user", 30 | usage = { 31 | "!kickme" 32 | }, 33 | patterns = { 34 | "^!kickme$" 35 | }, 36 | run = run 37 | } 38 | -------------------------------------------------------------------------------- /plugins/location.lua: -------------------------------------------------------------------------------- 1 | -- Implement a command !loc [area] which uses 2 | -- the static map API to get a location image 3 | 4 | -- Not sure if this is the proper way 5 | -- Intent: get_latlong is in time.lua, we need it here 6 | -- loadfile "time.lua" 7 | 8 | -- Globals 9 | -- If you have a google api key for the geocoding/timezone api 10 | do 11 | 12 | local api_key = nil 13 | 14 | local base_api = "https://maps.googleapis.com/maps/api" 15 | 16 | function get_staticmap(area) 17 | local api = base_api .. "/staticmap?" 18 | 19 | -- Get a sense of scale 20 | local lat,lng,acc,types = get_latlong(area) 21 | 22 | local scale = types[1] 23 | if scale=="locality" then zoom=8 24 | elseif scale=="country" then zoom=4 25 | else zoom = 13 end 26 | 27 | local parameters = 28 | "size=600x300" .. 29 | "&zoom=" .. zoom .. 30 | "¢er=" .. URL.escape(area) .. 31 | "&markers=color:red"..URL.escape("|"..area) 32 | 33 | if api_key ~=nil and api_key ~= "" then 34 | parameters = parameters .. "&key="..api_key 35 | end 36 | return lat, lng, api..parameters 37 | end 38 | 39 | 40 | function run(msg, matches) 41 | local receiver = get_receiver(msg) 42 | local lat,lng,url = get_staticmap(matches[1]) 43 | 44 | -- Send the actual location, is a google maps link 45 | send_location(receiver, lat, lng, ok_cb, false) 46 | 47 | -- Send a picture of the map, which takes scale into account 48 | send_photo_from_url(receiver, url) 49 | 50 | -- Return a link to the google maps stuff is now not needed anymore 51 | return nil 52 | end 53 | 54 | return { 55 | description = "Gets information about a location, maplink and overview", 56 | usage = "!loc (location): Gets information about a location, maplink and overview", 57 | patterns = {"^!loc (.*)$"}, 58 | run = run 59 | } 60 | 61 | end -------------------------------------------------------------------------------- /plugins/lyrics.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local BASE_LNM_URL = 'http://api.lyricsnmusic.com/songs' 4 | local LNM_APIKEY = '1f5ea5cf652d9b2ba5a5118a11dba5' 5 | 6 | local BASE_LYRICS_URL = 'http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect' 7 | 8 | local function getInfo(query) 9 | print('Getting info of ' .. query) 10 | 11 | local url = BASE_LNM_URL..'?api_key='..LNM_APIKEY 12 | ..'&q='..URL.escape(query) 13 | 14 | local b, c = http.request(url) 15 | if c ~= 200 then 16 | return nil 17 | end 18 | 19 | local result = json:decode(b) 20 | local artist = result[1].artist.name 21 | local track = result[1].title 22 | return artist, track 23 | end 24 | 25 | local function getLyrics(query) 26 | 27 | local artist, track = getInfo(query) 28 | if artist and track then 29 | local url = BASE_LYRICS_URL..'?artist='..URL.escape(artist) 30 | ..'&song='..URL.escape(track) 31 | 32 | local b, c = http.request(url) 33 | if c ~= 200 then 34 | return nil 35 | end 36 | 37 | local xml = require("xml") 38 | local result = xml.load(b) 39 | 40 | if not result then 41 | return nil 42 | end 43 | 44 | if xml.find(result, 'LyricSong') then 45 | track = xml.find(result, 'LyricSong')[1] 46 | end 47 | 48 | if xml.find(result, 'LyricArtist') then 49 | artist = xml.find(result, 'LyricArtist')[1] 50 | end 51 | 52 | local lyric 53 | if xml.find(result, 'Lyric') then 54 | lyric = xml.find(result, 'Lyric')[1] 55 | else 56 | lyric = nil 57 | end 58 | 59 | local cover 60 | if xml.find(result, 'LyricCovertArtUrl') then 61 | cover = xml.find(result, 'LyricCovertArtUrl')[1] 62 | else 63 | cover = nil 64 | end 65 | 66 | return artist, track, lyric, cover 67 | 68 | else 69 | return nil 70 | end 71 | 72 | end 73 | 74 | 75 | local function run(msg, matches) 76 | local artist, track, lyric, cover = getLyrics(matches[1]) 77 | if track and artist and lyric then 78 | if cover then 79 | local receiver = get_receiver(msg) 80 | send_photo_from_url(receiver, cover) 81 | end 82 | return '🎵 ' .. artist .. ' - ' .. track .. ' 🎵\n----------\n' .. lyric 83 | else 84 | return 'Oops! Lyrics not found or something like that! :/' 85 | end 86 | end 87 | 88 | return { 89 | description = 'Getting lyrics of a song', 90 | usage = '!lyrics [track or artist - track]: Search and get lyrics of the song', 91 | patterns = { 92 | '^!lyrics? (.*)$' 93 | }, 94 | run = run 95 | } 96 | 97 | end 98 | -------------------------------------------------------------------------------- /plugins/magic8ball.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function run(msg, matches) 4 | local answers = {'It is certain','It is decidedly so','Without a doubt', 5 | 'Yes definitely','You may rely on it','As I see it, yes', 6 | 'Most likely','Outlook good','Yes','Signs point to yes', 7 | 'Reply hazy try again','Ask again later', 8 | 'Better not tell you now','Cannot predict now', 9 | 'Concentrate and ask again','Don\'t count on it', 10 | 'My reply is no','My sources say no','Outlook not so good', 11 | 'Very doubtful'} 12 | return answers[math.random(#answers)] 13 | end 14 | 15 | return { 16 | description = "Magic 8Ball", 17 | usage = "!magic8ball", 18 | patterns = {"^!magic8ball"}, 19 | run = run 20 | } 21 | 22 | end 23 | -------------------------------------------------------------------------------- /plugins/media.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local function run(msg, matches) 4 | local receiver = get_receiver(msg) 5 | local url = matches[1] 6 | local ext = matches[2] 7 | 8 | local file = download_to_file(url) 9 | local cb_extra = {file_path=file} 10 | 11 | local mime_type = mimetype.get_content_type_no_sub(ext) 12 | 13 | if ext == 'gif' then 14 | print('send_file') 15 | send_document(receiver, file, rmtmp_cb, cb_extra) 16 | 17 | elseif mime_type == 'text' then 18 | print('send_document') 19 | send_document(receiver, file, rmtmp_cb, cb_extra) 20 | 21 | elseif mime_type == 'image' then 22 | print('send_photo') 23 | send_photo(receiver, file, rmtmp_cb, cb_extra) 24 | 25 | elseif mime_type == 'audio' then 26 | print('send_audio') 27 | send_audio(receiver, file, rmtmp_cb, cb_extra) 28 | 29 | elseif mime_type == 'video' then 30 | print('send_video') 31 | send_video(receiver, file, rmtmp_cb, cb_extra) 32 | 33 | else 34 | print('send_file') 35 | send_file(receiver, file, rmtmp_cb, cb_extra) 36 | end 37 | 38 | end 39 | 40 | return { 41 | description = "When user sends media URL (ends with gif, mp4, pdf, etc.) download and send it to origin.", 42 | usage = "", 43 | patterns = { 44 | "(https?://[%w-_%.%?%.:/%+=&]+%.(gif))$", 45 | "(https?://[%w-_%.%?%.:/%+=&]+%.(mp4))$", 46 | "(https?://[%w-_%.%?%.:/%+=&]+%.(pdf))$", 47 | "(https?://[%w-_%.%?%.:/%+=&]+%.(ogg))$", 48 | "(https?://[%w-_%.%?%.:/%+=&]+%.(zip))$", 49 | "(https?://[%w-_%.%?%.:/%+=&]+%.(mp3))$", 50 | "(https?://[%w-_%.%?%.:/%+=&]+%.(rar))$", 51 | "(https?://[%w-_%.%?%.:/%+=&]+%.(wmv))$", 52 | "(https?://[%w-_%.%?%.:/%+=&]+%.(doc))$", 53 | "(https?://[%w-_%.%?%.:/%+=&]+%.(avi))$", 54 | "(https?://[%w-_%.%?%.:/%+=&]+%.(webp))$" 55 | }, 56 | run = run 57 | } 58 | 59 | end 60 | -------------------------------------------------------------------------------- /plugins/meme.lua: -------------------------------------------------------------------------------- 1 | local helpers = require "OAuth.helpers" 2 | 3 | local _file_memes = './data/memes.lua' 4 | local _cache = {} 5 | 6 | local function post_petition(url, arguments) 7 | local response_body = {} 8 | local request_constructor = { 9 | url = url, 10 | method = "POST", 11 | sink = ltn12.sink.table(response_body), 12 | headers = {}, 13 | redirect = false 14 | } 15 | 16 | local source = arguments 17 | if type(arguments) == "table" then 18 | local source = helpers.url_encode_arguments(arguments) 19 | end 20 | request_constructor.headers["Content-Type"] = "application/x-www-form-urlencoded" 21 | request_constructor.headers["Content-Length"] = tostring(#source) 22 | request_constructor.source = ltn12.source.string(source) 23 | 24 | local ok, response_code, response_headers, response_status_line = http.request(request_constructor) 25 | 26 | if not ok then 27 | return nil 28 | end 29 | 30 | response_body = json:decode(table.concat(response_body)) 31 | 32 | return response_body 33 | end 34 | 35 | local function upload_memes(memes) 36 | local base = "http://hastebin.com/" 37 | local pet = post_petition(base .. "documents", memes) 38 | if pet == nil then 39 | return '', '' 40 | end 41 | local key = pet.key 42 | return base .. key, base .. 'raw/' .. key 43 | end 44 | 45 | local function analyze_meme_list() 46 | local function get_m(res, n) 47 | local r = "(.*).*" 48 | local start = string.find(res, "", n) 49 | if start == nil then 50 | return nil, nil 51 | end 52 | local final = string.find(res, "", n) + #"" 53 | local sub = string.sub(res, start, final) 54 | local f = string.match(sub, r) 55 | return f, final 56 | end 57 | local res, code = http.request('http://apimeme.com/') 58 | local r = "(.*).*" 59 | local n = 0 60 | local f, n = get_m(res, n) 61 | local ult = {} 62 | while f ~= nil do 63 | print(f) 64 | table.insert(ult, f) 65 | f, n = get_m(res, n) 66 | end 67 | return ult 68 | end 69 | 70 | 71 | local function get_memes() 72 | local memes = analyze_meme_list() 73 | return { 74 | last_time = os.time(), 75 | memes = memes 76 | } 77 | end 78 | 79 | local function load_data() 80 | local data = load_from_file(_file_memes) 81 | if not next(data) or data.memes == {} or os.time() - data.last_time > 86400 then 82 | data = get_memes() 83 | -- Upload only if changed? 84 | link, rawlink = upload_memes(table.concat(data.memes, '\n')) 85 | data.link = link 86 | data.rawlink = rawlink 87 | serialize_to_file(data, _file_memes) 88 | end 89 | return data 90 | end 91 | 92 | local function match_n_word(list1, list2) 93 | local n = 0 94 | for k,v in pairs(list1) do 95 | for k2, v2 in pairs(list2) do 96 | if v2:find(v) then 97 | n = n + 1 98 | end 99 | end 100 | end 101 | return n 102 | end 103 | 104 | local function match_meme(name) 105 | local _memes = load_data() 106 | local name = name:lower():split(' ') 107 | local max = 0 108 | local id = nil 109 | for k,v in pairs(_memes.memes) do 110 | local n = match_n_word(name, v:lower():split(' ')) 111 | if n > 0 and n > max then 112 | max = n 113 | id = v 114 | end 115 | end 116 | return id 117 | end 118 | 119 | local function generate_meme(id, textup, textdown) 120 | local base = "http://apimeme.com/meme" 121 | local arguments = { 122 | meme=id, 123 | top=textup, 124 | bottom=textdown 125 | } 126 | return base .. "?" .. helpers.url_encode_arguments(arguments) 127 | end 128 | 129 | local function get_all_memes_names() 130 | local _memes = load_data() 131 | local text = 'Last time: ' .. _memes.last_time .. '\n-----------\n' 132 | for k, v in pairs(_memes.memes) do 133 | text = text .. '- ' .. v .. '\n' 134 | end 135 | text = text .. '--------------\n' .. 'You can see the images here: http://apimeme.com/' 136 | return text 137 | end 138 | 139 | local function callback_send(cb_extra, success, data) 140 | if success == 0 then 141 | send_msg(cb_extra.receiver, "Something wrong happened, probably that meme had been removed from server: " .. cb_extra.url, ok_cb, false) 142 | end 143 | end 144 | 145 | local function run(msg, matches) 146 | local receiver = get_receiver(msg) 147 | if matches[1] == 'list' then 148 | local _memes = load_data() 149 | return 'I have ' .. #_memes.memes .. ' meme names.\nCheck this link to see all :)\n' .. _memes.link 150 | elseif matches[1] == 'listall' then 151 | if not is_sudo(msg) then 152 | return "You can't list this way, use \"!meme list\"" 153 | else 154 | return get_all_memes_names() 155 | end 156 | elseif matches[1] == "search" then 157 | local meme_id = match_meme(matches[2]) 158 | if meme_id == nil then 159 | return "I can't match that search with any meme." 160 | end 161 | return "With that search your meme is " .. meme_id 162 | end 163 | local searchterm = string.gsub(matches[1]:lower(), ' ', '') 164 | 165 | local meme_id = _cache[searchterm] or match_meme(matches[1]) 166 | if not meme_id then 167 | return 'I don\'t understand the meme name "' .. matches[1] .. '"' 168 | end 169 | 170 | _cache[searchterm] = meme_id 171 | print("Generating meme: " .. meme_id .. " with texts " .. matches[2] .. ' and ' .. matches[3]) 172 | local url_gen = generate_meme(meme_id, matches[2], matches[3]) 173 | send_photo_from_url(receiver, url_gen, callback_send, {receiver=receiver, url=url_gen}) 174 | 175 | return nil 176 | end 177 | 178 | return { 179 | description = "Generate a meme image with up and bottom texts.", 180 | usage = { 181 | "!meme search (name): Return the name of the meme that match.", 182 | "!meme list: Return the link where you can see the memes.", 183 | "!meme listall: Return the list of all memes. Only admin can call it.", 184 | '!meme [name] - [text_up] - [text_down]: Generate a meme with the picture that match with that name with the texts provided.', 185 | '!meme [name] "[text_up]" "[text_down]": Generate a meme with the picture that match with that name with the texts provided.', 186 | }, 187 | patterns = { 188 | "^!meme (search) (.+)$", 189 | '^!meme (list)$', 190 | '^!meme (listall)$', 191 | '^!meme (.+) "(.*)" "(.*)"$', 192 | '^!meme "(.+)" "(.*)" "(.*)"$', 193 | "^!meme (.+) %- (.*) %- (.*)$" 194 | }, 195 | run = run 196 | } 197 | -------------------------------------------------------------------------------- /plugins/minecraft.lua: -------------------------------------------------------------------------------- 1 | local usage = { 2 | "!mine [ip]: Searches Minecraft server on specified ip and sends info. Default port: 25565", 3 | "!mine [ip] [port]: Searches Minecraft server on specified ip and port and sends info.", 4 | } 5 | local ltn12 = require "ltn12" 6 | 7 | local function mineSearch(ip, port, receiver) --25565 8 | local responseText = "" 9 | local api = "https://api.syfaro.net/server/status" 10 | local parameters = "?ip="..(URL.escape(ip) or "").."&port="..(URL.escape(port) or "").."&players=true&favicon=true" 11 | local http = require("socket.http") 12 | local respbody = {} 13 | local body, code, headers, status = http.request{ 14 | url = api..parameters, 15 | method = "GET", 16 | redirect = true, 17 | sink = ltn12.sink.table(respbody) 18 | } 19 | local body = table.concat(respbody) 20 | if (status == nil) then return "ERROR: status = nil" end 21 | if code ~=200 then return "ERROR: "..code..". Status: "..status end 22 | local jsonData = json:decode(body) 23 | responseText = responseText..ip..":"..port.." ->\n" 24 | if (jsonData.motd ~= nil) then 25 | local tempMotd = "" 26 | tempMotd = jsonData.motd:gsub('%§.', '') 27 | if (jsonData.motd ~= nil) then responseText = responseText.." Motd: "..tempMotd.."\n" end 28 | end 29 | if (jsonData.online ~= nil) then 30 | responseText = responseText.." Online: "..tostring(jsonData.online).."\n" 31 | end 32 | if (jsonData.players ~= nil) then 33 | if (jsonData.players.max ~= nil) then 34 | responseText = responseText.." Max Players: "..jsonData.players.max.."\n" 35 | end 36 | if (jsonData.players.now ~= nil) then 37 | responseText = responseText.." Players online: "..jsonData.players.now.."\n" 38 | end 39 | if (jsonData.players.sample ~= nil and jsonData.players.sample ~= false) then 40 | responseText = responseText.." Players: "..table.concat(jsonData.players.sample, ", ").."\n" 41 | end 42 | end 43 | if (jsonData.favicon ~= nil and false) then 44 | --send_photo(receiver, jsonData.favicon) --(decode base64 and send) 45 | end 46 | return responseText 47 | end 48 | 49 | local function parseText(chat, text) 50 | if (text == nil or text == "!mine") then 51 | return usage 52 | end 53 | ip, port = string.match(text, "^!mine (.-) (.*)$") 54 | if (ip ~= nil and port ~= nil) then 55 | return mineSearch(ip, port, chat) 56 | end 57 | local ip = string.match(text, "^!mine (.*)$") 58 | if (ip ~= nil) then 59 | return mineSearch(ip, "25565", chat) 60 | end 61 | return "ERROR: no input ip?" 62 | end 63 | 64 | 65 | local function run(msg, matches) 66 | local chat_id = tostring(msg.to.id) 67 | local result = parseText(chat_id, msg.text) 68 | return result 69 | end 70 | 71 | return { 72 | description = "Searches Minecraft server and sends info", 73 | usage = usage, 74 | patterns = { 75 | "^!mine (.*)$" 76 | }, 77 | run = run 78 | } -------------------------------------------------------------------------------- /plugins/pili.lua: -------------------------------------------------------------------------------- 1 | local helpers = require "OAuth.helpers" 2 | 3 | local url = "http://pili.la/api.php" 4 | 5 | local function run(msg, matches) 6 | local url_req = matches[1] 7 | 8 | local request = { 9 | url = url_req 10 | } 11 | 12 | local url = url .. "?" .. helpers.url_encode_arguments(request) 13 | 14 | local res, code = http.request(url) 15 | if code ~= 200 then 16 | return "Sorry, can't connect" 17 | end 18 | 19 | return res 20 | end 21 | 22 | 23 | return { 24 | description = "Shorten an URL with the awesome http://pili.la", 25 | usage = { 26 | "!pili [url]: Shorten the URL" 27 | }, 28 | patterns = { 29 | "^!pili (https?://[%w-_%.%?%.:/%+=&]+)$" 30 | }, 31 | run = run 32 | } 33 | -------------------------------------------------------------------------------- /plugins/plugins.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | -- Returns the key (index) in the config.enabled_plugins table 4 | local function plugin_enabled( name ) 5 | for k,v in pairs(_config.enabled_plugins) do 6 | if name == v then 7 | return k 8 | end 9 | end 10 | -- If not found 11 | return false 12 | end 13 | 14 | -- Returns true if file exists in plugins folder 15 | local function plugin_exists( name ) 16 | for k,v in pairs(plugins_names()) do 17 | if name..'.lua' == v then 18 | return true 19 | end 20 | end 21 | return false 22 | end 23 | 24 | local function list_plugins(only_enabled) 25 | local text = '' 26 | for k, v in pairs( plugins_names( )) do 27 | -- ✔ enabled, ❌ disabled 28 | local status = '❌' 29 | -- Check if is enabled 30 | for k2, v2 in pairs(_config.enabled_plugins) do 31 | if v == v2..'.lua' then 32 | status = '✔' 33 | end 34 | end 35 | if not only_enabled or status == '✔' then 36 | -- get the name 37 | v = string.match (v, "(.*)%.lua") 38 | text = text..v..' '..status..'\n' 39 | end 40 | end 41 | return text 42 | end 43 | 44 | local function reload_plugins( ) 45 | plugins = {} 46 | load_plugins() 47 | return list_plugins(true) 48 | end 49 | 50 | 51 | local function enable_plugin( plugin_name ) 52 | print('checking if '..plugin_name..' exists') 53 | -- Check if plugin is enabled 54 | if plugin_enabled(plugin_name) then 55 | return 'Plugin '..plugin_name..' is enabled' 56 | end 57 | -- Checks if plugin exists 58 | if plugin_exists(plugin_name) then 59 | -- Add to the config table 60 | table.insert(_config.enabled_plugins, plugin_name) 61 | print(plugin_name..' added to _config table') 62 | save_config() 63 | -- Reload the plugins 64 | return reload_plugins( ) 65 | else 66 | return 'Plugin '..plugin_name..' does not exists' 67 | end 68 | end 69 | 70 | local function disable_plugin( name, chat ) 71 | -- Check if plugins exists 72 | if not plugin_exists(name) then 73 | return 'Plugin '..name..' does not exists' 74 | end 75 | local k = plugin_enabled(name) 76 | -- Check if plugin is enabled 77 | if not k then 78 | return 'Plugin '..name..' not enabled' 79 | end 80 | -- Disable and reload 81 | table.remove(_config.enabled_plugins, k) 82 | save_config( ) 83 | return reload_plugins(true) 84 | end 85 | 86 | local function disable_plugin_on_chat(receiver, plugin) 87 | if not plugin_exists(plugin) then 88 | return "Plugin doesn't exists" 89 | end 90 | 91 | if not _config.disabled_plugin_on_chat then 92 | _config.disabled_plugin_on_chat = {} 93 | end 94 | 95 | if not _config.disabled_plugin_on_chat[receiver] then 96 | _config.disabled_plugin_on_chat[receiver] = {} 97 | end 98 | 99 | _config.disabled_plugin_on_chat[receiver][plugin] = true 100 | 101 | save_config() 102 | return 'Plugin '..plugin..' disabled on this chat' 103 | end 104 | 105 | local function reenable_plugin_on_chat(receiver, plugin) 106 | if not _config.disabled_plugin_on_chat then 107 | return 'There aren\'t any disabled plugins' 108 | end 109 | 110 | if not _config.disabled_plugin_on_chat[receiver] then 111 | return 'There aren\'t any disabled plugins for this chat' 112 | end 113 | 114 | if not _config.disabled_plugin_on_chat[receiver][plugin] then 115 | return 'This plugin is not disabled' 116 | end 117 | 118 | _config.disabled_plugin_on_chat[receiver][plugin] = false 119 | save_config() 120 | return 'Plugin '..plugin..' is enabled again' 121 | end 122 | 123 | local function run(msg, matches) 124 | -- Show the available plugins 125 | if matches[1] == '!plugins' then 126 | return list_plugins() 127 | end 128 | 129 | -- Re-enable a plugin for this chat 130 | if matches[1] == 'enable' and matches[3] == 'chat' then 131 | local receiver = get_receiver(msg) 132 | local plugin = matches[2] 133 | print("enable "..plugin..' on this chat') 134 | return reenable_plugin_on_chat(receiver, plugin) 135 | end 136 | 137 | -- Enable a plugin 138 | if matches[1] == 'enable' then 139 | local plugin_name = matches[2] 140 | print("enable: "..matches[2]) 141 | return enable_plugin(plugin_name) 142 | end 143 | 144 | -- Disable a plugin on a chat 145 | if matches[1] == 'disable' and matches[3] == 'chat' then 146 | local plugin = matches[2] 147 | local receiver = get_receiver(msg) 148 | print("disable "..plugin..' on this chat') 149 | return disable_plugin_on_chat(receiver, plugin) 150 | end 151 | 152 | -- Disable a plugin 153 | if matches[1] == 'disable' then 154 | print("disable: "..matches[2]) 155 | return disable_plugin(matches[2]) 156 | end 157 | 158 | -- Reload all the plugins! 159 | if matches[1] == 'reload' then 160 | return reload_plugins(true) 161 | end 162 | end 163 | 164 | return { 165 | description = "Plugin to manage other plugins. Enable, disable or reload.", 166 | usage = { 167 | "!plugins: list all plugins.", 168 | "!plugins enable [plugin]: enable plugin.", 169 | "!plugins disable [plugin]: disable plugin.", 170 | "!plugins disable [plugin] chat: disable plugin only this chat.", 171 | "!plugins reload: reloads all plugins." }, 172 | patterns = { 173 | "^!plugins$", 174 | "^!plugins? (enable) ([%w_%.%-]+)$", 175 | "^!plugins? (disable) ([%w_%.%-]+)$", 176 | "^!plugins? (enable) ([%w_%.%-]+) (chat)", 177 | "^!plugins? (disable) ([%w_%.%-]+) (chat)", 178 | "^!plugins? (reload)$" }, 179 | run = run, 180 | privileged = true 181 | } 182 | 183 | end -------------------------------------------------------------------------------- /plugins/pokedex.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local images_enabled = true; 4 | 5 | local function get_sprite(path) 6 | local url = "http://pokeapi.co/"..path 7 | print(url) 8 | local b,c = http.request(url) 9 | local data = json:decode(b) 10 | local image = data.image 11 | return image 12 | end 13 | 14 | local function callback(extra) 15 | send_msg(extra.receiver, extra.text, ok_cb, false) 16 | end 17 | 18 | local function send_pokemon(query, receiver) 19 | local url = "http://pokeapi.co/api/v1/pokemon/" .. query .. "/" 20 | local b,c = http.request(url) 21 | local pokemon = json:decode(b) 22 | 23 | if pokemon == nil then 24 | return 'No pokémon found.' 25 | end 26 | 27 | -- api returns height and weight x10 28 | local height = tonumber(pokemon.height)/10 29 | local weight = tonumber(pokemon.weight)/10 30 | 31 | local text = 'Pokédex ID: ' .. pokemon.pkdx_id 32 | ..'\nName: ' .. pokemon.name 33 | ..'\nWeight: ' .. weight.." kg" 34 | ..'\nHeight: ' .. height.." m" 35 | ..'\nSpeed: ' .. pokemon.speed 36 | 37 | local image = nil 38 | 39 | if images_enabled and pokemon.sprites and pokemon.sprites[1] then 40 | local sprite = pokemon.sprites[1].resource_uri 41 | image = get_sprite(sprite) 42 | end 43 | 44 | if image then 45 | image = "http://pokeapi.co"..image 46 | local extra = { 47 | receiver = receiver, 48 | text = text 49 | } 50 | send_photo_from_url(receiver, image, callback, extra) 51 | else 52 | return text 53 | end 54 | end 55 | 56 | local function run(msg, matches) 57 | local receiver = get_receiver(msg) 58 | local query = URL.escape(matches[1]) 59 | return send_pokemon(query, receiver) 60 | end 61 | 62 | return { 63 | description = "Pokedex searcher for Telegram", 64 | usage = "!pokedex [Name/ID]: Search the pokédex for Name/ID and get info of the pokémon!", 65 | patterns = { 66 | "^!pokedex (.*)$", 67 | "^!pokemon (.+)$" 68 | }, 69 | run = run 70 | } 71 | 72 | end 73 | -------------------------------------------------------------------------------- /plugins/qr.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * qr plugin uses: 3 | * - http://goqr.me/api/doc/create-qr-code/ 4 | * psykomantis 5 | ]] 6 | 7 | local function get_hex(str) 8 | local colors = { 9 | red = "f00", 10 | blue = "00f", 11 | green = "0f0", 12 | yellow = "ff0", 13 | purple = "f0f", 14 | white = "fff", 15 | black = "000", 16 | gray = "ccc" 17 | } 18 | 19 | for color, value in pairs(colors) do 20 | if color == str then 21 | return value 22 | end 23 | end 24 | 25 | return str 26 | end 27 | 28 | local function qr(receiver, text, color, bgcolor) 29 | 30 | local url = "http://api.qrserver.com/v1/create-qr-code/?" 31 | .."size=600x600" --fixed size otherways it's low detailed 32 | .."&data="..URL.escape(text:trim()) 33 | 34 | if color then 35 | url = url.."&color="..get_hex(color) 36 | end 37 | if bgcolor then 38 | url = url.."&bgcolor="..get_hex(bgcolor) 39 | end 40 | 41 | local response, code, headers = http.request(url) 42 | 43 | if code ~= 200 then 44 | return "Oops! Error: " .. code 45 | end 46 | 47 | if #response > 0 then 48 | send_photo_from_url(receiver, url) 49 | return 50 | 51 | end 52 | return "Oops! Something strange happened :(" 53 | end 54 | 55 | local function run(msg, matches) 56 | local receiver = get_receiver(msg) 57 | 58 | local text = matches[1] 59 | local color 60 | local back 61 | 62 | if #matches > 1 then 63 | text = matches[3] 64 | color = matches[2] 65 | back = matches[1] 66 | end 67 | 68 | return qr(receiver, text, color, back) 69 | end 70 | 71 | return { 72 | description = {"qr code plugin for telegram, given a text it returns the qr code"}, 73 | usage = { 74 | "!qr [text]", 75 | '!qr "[background color]" "[data color]" [text]\n' 76 | .."Color through text: red|green|blue|purple|black|white|gray\n" 77 | .."Colors through hex notation: (\"a56729\" is brown)\n" 78 | .."Or colors through decimals: (\"255-192-203\" is pink)" 79 | }, 80 | patterns = { 81 | '^!qr "(%w+)" "(%w+)" (.+)$', 82 | "^!qr (.+)$" 83 | }, 84 | run = run 85 | } -------------------------------------------------------------------------------- /plugins/quotes.lua: -------------------------------------------------------------------------------- 1 | local quotes_file = './data/quotes.lua' 2 | local quotes_table 3 | 4 | function read_quotes_file() 5 | local f = io.open(quotes_file, "r+") 6 | 7 | if f == nil then 8 | print ('Created a new quotes file on '..quotes_file) 9 | serialize_to_file({}, quotes_file) 10 | else 11 | print ('Quotes loaded: '..quotes_file) 12 | f:close() 13 | end 14 | return loadfile (quotes_file)() 15 | end 16 | 17 | function save_quote(msg) 18 | local to_id = tostring(msg.to.id) 19 | 20 | if msg.text:sub(11):isempty() then 21 | return "Usage: !addquote quote" 22 | end 23 | 24 | if quotes_table == nil then 25 | quotes_table = {} 26 | end 27 | 28 | if quotes_table[to_id] == nil then 29 | print ('New quote key to_id: '..to_id) 30 | quotes_table[to_id] = {} 31 | end 32 | 33 | local quotes = quotes_table[to_id] 34 | quotes[#quotes+1] = msg.text:sub(11) 35 | 36 | serialize_to_file(quotes_table, quotes_file) 37 | 38 | return "done!" 39 | end 40 | 41 | function get_quote(msg) 42 | local to_id = tostring(msg.to.id) 43 | local quotes_phrases 44 | 45 | quotes_table = read_quotes_file() 46 | quotes_phrases = quotes_table[to_id] 47 | 48 | return quotes_phrases[math.random(1,#quotes_phrases)] 49 | end 50 | 51 | function run(msg, matches) 52 | if string.match(msg.text, "!quote$") then 53 | return get_quote(msg) 54 | elseif string.match(msg.text, "!addquote (.+)$") then 55 | quotes_table = read_quotes_file() 56 | return save_quote(msg) 57 | end 58 | end 59 | 60 | return { 61 | description = "Save quote", 62 | description = "Quote plugin, you can create and retrieve random quotes", 63 | usage = { 64 | "!addquote [msg]", 65 | "!quote", 66 | }, 67 | patterns = { 68 | "^!addquote (.+)$", 69 | "^!quote$", 70 | }, 71 | run = run 72 | } 73 | -------------------------------------------------------------------------------- /plugins/rae.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function getDulcinea( text ) 4 | -- Powered by https://github.com/javierhonduco/dulcinea 5 | 6 | local api = "http://dulcinea.herokuapp.com/api/?query=" 7 | local query_url = api..text 8 | 9 | local b, code = http.request(query_url) 10 | 11 | if code ~= 200 then 12 | return "Error: HTTP Connection" 13 | end 14 | 15 | dulcinea = json:decode(b) 16 | 17 | if dulcinea.status == "error" then 18 | return "Error: " .. dulcinea.message 19 | end 20 | 21 | while dulcinea.type == "multiple" do 22 | text = dulcinea.response[1].id 23 | b = http.request(api..text) 24 | dulcinea = json:decode(b) 25 | end 26 | 27 | local text = "" 28 | 29 | local responses = #dulcinea.response 30 | 31 | if responses == 0 then 32 | return "Error: 404 word not found" 33 | end 34 | 35 | if (responses > 5) then 36 | responses = 5 37 | end 38 | 39 | for i = 1, responses, 1 do 40 | text = text .. dulcinea.response[i].word .. "\n" 41 | local meanings = #dulcinea.response[i].meanings 42 | if (meanings > 5) then 43 | meanings = 5 44 | end 45 | for j = 1, meanings, 1 do 46 | local meaning = dulcinea.response[i].meanings[j].meaning 47 | text = text .. meaning .. "\n\n" 48 | end 49 | end 50 | 51 | return text 52 | end 53 | 54 | function run(msg, matches) 55 | return getDulcinea(matches[1]) 56 | end 57 | 58 | return { 59 | description = "Spanish dictionary", 60 | usage = "!rae [word]: Search that word in Spanish dictionary.", 61 | patterns = {"^!rae (.*)$"}, 62 | run = run 63 | } 64 | 65 | end -------------------------------------------------------------------------------- /plugins/remind.lua: -------------------------------------------------------------------------------- 1 | local filename='data/remind.lua' 2 | local cronned = load_from_file(filename) 3 | 4 | local function save_cron(msg, text,date) 5 | local origin = get_receiver(msg) 6 | if not cronned[date] then 7 | cronned[date] = {} 8 | end 9 | local arr = { origin, text } ; 10 | table.insert(cronned[date], arr) 11 | serialize_to_file(cronned, filename) 12 | return 'Saved!' 13 | end 14 | 15 | local function delete_cron(date) 16 | for k,v in pairs(cronned) do 17 | if k == date then 18 | cronned[k]=nil 19 | end 20 | end 21 | serialize_to_file(cronned, filename) 22 | end 23 | 24 | local function cron() 25 | for date, values in pairs(cronned) do 26 | if date < os.time() then --time's up 27 | send_msg(values[1][1], "Time's up:"..values[1][2], ok_cb, false) 28 | delete_cron(date) --TODO: Maybe check for something else? Like user 29 | end 30 | 31 | end 32 | end 33 | 34 | local function actually_run(msg, delay,text) 35 | if (not delay or not text) then 36 | return "Usage: !remind [delay: 2h3m1s] text" 37 | end 38 | save_cron(msg, text,delay) 39 | return "I'll remind you on " .. os.date("%x at %H:%M:%S",delay) .. " about '" .. text .. "'" 40 | end 41 | 42 | local function run(msg, matches) 43 | local sum = 0 44 | for i = 1, #matches-1 do 45 | local b,_ = string.gsub(matches[i],"[a-zA-Z]","") 46 | if string.find(matches[i], "s") then 47 | sum=sum+b 48 | end 49 | if string.find(matches[i], "m") then 50 | sum=sum+b*60 51 | end 52 | if string.find(matches[i], "h") then 53 | sum=sum+b*3600 54 | end 55 | end 56 | 57 | local date=sum+os.time() 58 | local text = matches[#matches] 59 | 60 | local text = actually_run(msg, date, text) 61 | return text 62 | end 63 | 64 | return { 65 | description = "remind plugin", 66 | usage = { 67 | "!remind [delay: 2hms] text", 68 | "!remind [delay: 2h3m] text", 69 | "!remind [delay: 2h3m1s] text" 70 | }, 71 | patterns = { 72 | "^!remind ([0-9]+[hmsdHMSD]) (.+)$", 73 | "^!remind ([0-9]+[hmsdHMSD])([0-9]+[hmsdHMSD]) (.+)$", 74 | "^!remind ([0-9]+[hmsdHMSD])([0-9]+[hmsdHMSD])([0-9]+[hmsdHMSD]) (.+)$" 75 | }, 76 | run = run, 77 | cron = cron 78 | } 79 | -------------------------------------------------------------------------------- /plugins/roll.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local ROLL_USAGE = "!roll d| d" 4 | local DEFAULT_SIDES = 100 5 | local DEFAULT_NUMBER_OF_DICE = 1 6 | local MAX_NUMBER_OF_DICE = 100 7 | 8 | function run(msg, matches) 9 | local str = matches[1] 10 | local sides = DEFAULT_SIDES 11 | local number_of_dice = DEFAULT_NUMBER_OF_DICE 12 | 13 | if str:find("!roll%s+d%d+%s*$") then 14 | sides = tonumber(str:match("[^d]%d+%s*$")) 15 | elseif str:find("!roll%s+%d+d%d+%s*$") then 16 | number_of_dice = tonumber(str:match("%s+%d+")) 17 | sides = tonumber(str:match("%d+%s*$")) 18 | end 19 | 20 | if number_of_dice > MAX_NUMBER_OF_DICE then 21 | number_of_dice = MAX_NUMBER_OF_DICE 22 | end 23 | 24 | local padding = #string.format("%d", sides) 25 | local results = "" 26 | 27 | local fmt = "%s %0"..padding.."d" 28 | for i=1,number_of_dice do 29 | results = string.format(fmt, results, math.random(sides)) 30 | end 31 | 32 | return string.format("Rolling %dd%d:\n%s", number_of_dice, sides, results) 33 | end 34 | 35 | return { 36 | description = "Roll some dice!", 37 | usage = ROLL_USAGE, 38 | patterns = { 39 | "^!roll%s*.*" 40 | }, 41 | run = run 42 | } 43 | 44 | end 45 | -------------------------------------------------------------------------------- /plugins/rss.lua: -------------------------------------------------------------------------------- 1 | local function get_base_redis(id, option, extra) 2 | local ex = '' 3 | if option ~= nil then 4 | ex = ex .. ':' .. option 5 | if extra ~= nil then 6 | ex = ex .. ':' .. extra 7 | end 8 | end 9 | return 'rss:' .. id .. ex 10 | end 11 | 12 | local function prot_url(url) 13 | local url, h = string.gsub(url, "http://", "") 14 | local url, hs = string.gsub(url, "https://", "") 15 | local protocol = "http" 16 | if hs == 1 then 17 | protocol = "https" 18 | end 19 | return url, protocol 20 | end 21 | 22 | local function get_rss(url, prot) 23 | local res, code = nil, 0 24 | if prot == "http" then 25 | res, code = http.request(url) 26 | elseif prot == "https" then 27 | res, code = https.request(url) 28 | end 29 | if code ~= 200 then 30 | return nil, "Error while doing the petition to " .. url 31 | end 32 | local parsed = feedparser.parse(res) 33 | if parsed == nil then 34 | return nil, "Error decoding the RSS.\nAre you sure that " .. url .. " it's a RSS?" 35 | end 36 | return parsed, nil 37 | end 38 | 39 | local function get_new_entries(last, nentries) 40 | local entries = {} 41 | for k,v in pairs(nentries) do 42 | if v.id == last then 43 | return entries 44 | else 45 | table.insert(entries, v) 46 | end 47 | end 48 | return entries 49 | end 50 | 51 | local function print_subs(id) 52 | local uhash = get_base_redis(id) 53 | local subs = redis:smembers(uhash) 54 | local text = id .. ' are subscribed to:\n---------\n' 55 | for k,v in pairs(subs) do 56 | text = text .. k .. ") " .. v .. '\n' 57 | end 58 | return text 59 | end 60 | 61 | local function subscribe(id, url) 62 | local baseurl, protocol = prot_url(url) 63 | 64 | local prothash = get_base_redis(baseurl, "protocol") 65 | local lasthash = get_base_redis(baseurl, "last_entry") 66 | local lhash = get_base_redis(baseurl, "subs") 67 | local uhash = get_base_redis(id) 68 | 69 | if redis:sismember(uhash, baseurl) then 70 | return "You are already subscribed to " .. url 71 | end 72 | 73 | local parsed, err = get_rss(url, protocol) 74 | if err ~= nil then 75 | return err 76 | end 77 | 78 | local last_entry = "" 79 | if #parsed.entries > 0 then 80 | last_entry = parsed.entries[1].id 81 | end 82 | 83 | local name = parsed.feed.title 84 | 85 | redis:set(prothash, protocol) 86 | redis:set(lasthash, last_entry) 87 | redis:sadd(lhash, id) 88 | redis:sadd(uhash, baseurl) 89 | 90 | return "You had been subscribed to " .. name 91 | end 92 | 93 | local function unsubscribe(id, n) 94 | if #n > 3 then 95 | return "I don't think that you have that many subscriptions." 96 | end 97 | n = tonumber(n) 98 | 99 | local uhash = get_base_redis(id) 100 | local subs = redis:smembers(uhash) 101 | if n < 1 or n > #subs then 102 | return "Subscription id out of range!" 103 | end 104 | local sub = subs[n] 105 | local lhash = get_base_redis(sub, "subs") 106 | 107 | redis:srem(uhash, sub) 108 | redis:srem(lhash, id) 109 | 110 | local left = redis:smembers(lhash) 111 | if #left < 1 then -- no one subscribed, remove it 112 | local prothash = get_base_redis(sub, "protocol") 113 | local lasthash = get_base_redis(sub, "last_entry") 114 | redis:del(prothash) 115 | redis:del(lasthash) 116 | end 117 | 118 | return "You had been unsubscribed from " .. sub 119 | end 120 | 121 | local function cron() 122 | -- sync every 15 mins? 123 | local keys = redis:keys(get_base_redis("*", "subs")) 124 | for k,v in pairs(keys) do 125 | local base = string.match(v, "rss:(.+):subs") -- Get the URL base 126 | local prot = redis:get(get_base_redis(base, "protocol")) 127 | local last = redis:get(get_base_redis(base, "last_entry")) 128 | local url = prot .. "://" .. base 129 | local parsed, err = get_rss(url, prot) 130 | if err ~= nil then 131 | return 132 | end 133 | local newentr = get_new_entries(last, parsed.entries) 134 | local subscribers = {} 135 | local text = '' -- Send only one message with all updates 136 | for k2, v2 in pairs(newentr) do 137 | local title = v2.title or 'No title' 138 | local link = v2.link or v2.id or 'No Link' 139 | text = text .. "[rss](" .. link .. ") - " .. title .. '\n' 140 | end 141 | if text ~= '' then 142 | local newlast = newentr[1].id 143 | redis:set(get_base_redis(base, "last_entry"), newlast) 144 | for k2, receiver in pairs(redis:smembers(v)) do 145 | send_msg(receiver, text, ok_cb, false) 146 | end 147 | end 148 | end 149 | end 150 | 151 | local function run(msg, matches) 152 | local id = "user#id" .. msg.from.id 153 | 154 | if is_chat_msg(msg) then 155 | id = "chat#id" .. msg.to.id 156 | end 157 | 158 | if matches[1] == "!rss"then 159 | return print_subs(id) 160 | end 161 | if matches[1] == "sync" then 162 | if not is_sudo(msg) then 163 | return "Only sudo users can sync the RSS." 164 | end 165 | cron() 166 | end 167 | if matches[1] == "subscribe" or matches[1] == "sub" then 168 | return subscribe(id, matches[2]) 169 | end 170 | 171 | if matches[1] == "unsubscribe" or matches[1] == "uns" then 172 | return unsubscribe(id, matches[2]) 173 | end 174 | end 175 | 176 | 177 | return { 178 | description = "Manage User/Chat RSS subscriptions. If you are in a chat group, the RSS subscriptions will be of that chat. If you are in an one-to-one talk with the bot, the RSS subscriptions will be yours.", 179 | usage = { 180 | "!rss: Get your rss (or chat rss) subscriptions", 181 | "!rss subscribe (url): Subscribe to that url", 182 | "!rss unsubscribe (id): Unsubscribe of that id", 183 | "!rss sync: Download now the updates and send it. Only sudo users can use this option." 184 | }, 185 | patterns = { 186 | "^!rss$", 187 | "^!rss (subscribe) (https?://[%w-_%.%?%.:/%+=&]+)$", 188 | "^!rss (sub) (https?://[%w-_%.%?%.:/%+=&]+)$", 189 | "^!rss (unsubscribe) (%d+)$", 190 | "^!rss (uns) (%d+)$", 191 | "^!rss (sync)$" 192 | }, 193 | run = run, 194 | cron = cron 195 | } 196 | -------------------------------------------------------------------------------- /plugins/search_youtube.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local google_config = load_from_file('data/google.lua') 4 | 5 | local function httpsRequest(url) 6 | print(url) 7 | local res,code = https.request(url) 8 | if code ~= 200 then return nil end 9 | return json:decode(res) 10 | end 11 | 12 | local function searchYoutubeVideos(text) 13 | local url = 'https://www.googleapis.com/youtube/v3/search?' 14 | url = url..'part=snippet'..'&maxResults=4'..'&type=video' 15 | url = url..'&q='..URL.escape(text) 16 | if google_config.api_keys then 17 | local i = math.random(#google_config.api_keys) 18 | local api_key = google_config.api_keys[i] 19 | if api_key then 20 | url = url.."&key="..api_key 21 | end 22 | end 23 | 24 | local data = httpsRequest(url) 25 | 26 | if not data then 27 | print("HTTP Error") 28 | return nil 29 | elseif not data.items then 30 | return nil 31 | end 32 | return data.items 33 | end 34 | 35 | local function run(msg, matches) 36 | local text = '' 37 | local items = searchYoutubeVideos(matches[1]) 38 | if not items then 39 | return "Error!" 40 | end 41 | for k,item in pairs(items) do 42 | text = text..'http://youtu.be/'..item.id.videoId..' '.. 43 | item.snippet.title..'\n\n' 44 | end 45 | return text 46 | end 47 | 48 | return { 49 | description = "Search video on youtube and send it.", 50 | usage = "!youtube [term]: Search for a youtube video and send it.", 51 | patterns = { 52 | "^!youtube (.*)" 53 | }, 54 | run = run 55 | } 56 | 57 | end 58 | -------------------------------------------------------------------------------- /plugins/service_entergroup.lua: -------------------------------------------------------------------------------- 1 | local add_user_cfg = load_from_file('data/add_user_cfg.lua') 2 | 3 | local function template_add_user(base, to_username, from_username, chat_name, chat_id) 4 | base = base or '' 5 | to_username = '@' .. (to_username or '') 6 | from_username = '@' .. (from_username or '') 7 | chat_name = chat_name or '' 8 | chat_id = "chat#id" .. (chat_id or '') 9 | if to_username == "@" then 10 | to_username = '' 11 | end 12 | if from_username == "@" then 13 | from_username = '' 14 | end 15 | base = string.gsub(base, "{to_username}", to_username) 16 | base = string.gsub(base, "{from_username}", from_username) 17 | base = string.gsub(base, "{chat_name}", chat_name) 18 | base = string.gsub(base, "{chat_id}", chat_id) 19 | return base 20 | end 21 | 22 | function chat_new_user_link(msg) 23 | local pattern = add_user_cfg.initial_chat_msg 24 | local to_username = msg.from.username 25 | local from_username = '[link](@' .. (msg.action.link_issuer.username or '') .. ')' 26 | local chat_name = msg.to.print_name 27 | local chat_id = msg.to.id 28 | pattern = template_add_user(pattern, to_username, from_username, chat_name, chat_id) 29 | if pattern ~= '' then 30 | local receiver = get_receiver(msg) 31 | send_msg(receiver, pattern, ok_cb, false) 32 | end 33 | end 34 | 35 | function chat_new_user(msg) 36 | local pattern = add_user_cfg.initial_chat_msg 37 | local to_username = msg.action.user.username 38 | local from_username = msg.from.username 39 | local chat_name = msg.to.print_name 40 | local chat_id = msg.to.id 41 | pattern = template_add_user(pattern, to_username, from_username, chat_name, chat_id) 42 | if pattern ~= '' then 43 | local receiver = get_receiver(msg) 44 | send_msg(receiver, pattern, ok_cb, false) 45 | end 46 | end 47 | 48 | 49 | local function run(msg, matches) 50 | if not msg.service then 51 | return "Are you trying to troll me?" 52 | end 53 | if matches[1] == "chat_add_user" then 54 | chat_new_user(msg) 55 | elseif matches[1] == "chat_add_user_link" then 56 | chat_new_user_link(msg) 57 | end 58 | end 59 | 60 | return { 61 | description = "Service plugin that sends a custom message when an user enters a chat.", 62 | usage = "", 63 | patterns = { 64 | "^!!tgservice (chat_add_user)$", 65 | "^!!tgservice (chat_add_user_link)$" 66 | }, 67 | run = run 68 | } 69 | -------------------------------------------------------------------------------- /plugins/service_template.lua: -------------------------------------------------------------------------------- 1 | local function run(msg, matches) 2 | -- avoid this plugins to process user messages 3 | if not msg.service then 4 | -- return "Are you trying to troll me?" 5 | return nil 6 | end 7 | print("Service message received: " .. matches[1]) 8 | end 9 | 10 | 11 | return { 12 | description = "Template for service plugins", 13 | usage = "", 14 | patterns = { 15 | "^!!tgservice (.*)$" -- Do not use the (.*) match in your service plugin 16 | }, 17 | run = run 18 | } 19 | -------------------------------------------------------------------------------- /plugins/set.lua: -------------------------------------------------------------------------------- 1 | local function save_value(msg, name, value) 2 | if (not name or not value) then 3 | return "Usage: !set var_name value" 4 | end 5 | 6 | local hash = nil 7 | if msg.to.type == 'chat' then 8 | hash = 'chat:'..msg.to.id..':variables' 9 | end 10 | if msg.to.type == 'user' then 11 | hash = 'user:'..msg.from.id..':variables' 12 | end 13 | if hash then 14 | redis:hset(hash, name, value) 15 | return "Saved "..name.." => "..value 16 | end 17 | end 18 | 19 | local function run(msg, matches) 20 | local name = string.sub(matches[1], 1, 50) 21 | local value = string.sub(matches[2], 1, 1000) 22 | 23 | local text = save_value(msg, name, value) 24 | return text 25 | end 26 | 27 | return { 28 | description = "Plugin for saving values. get.lua plugin is necessary to retrieve them.", 29 | usage = "!set [value_name] [data]: Saves the data with the value_name name.", 30 | patterns = { 31 | "!set ([^%s]+) (.+)$" 32 | }, 33 | run = run 34 | } 35 | 36 | -------------------------------------------------------------------------------- /plugins/stats.lua: -------------------------------------------------------------------------------- 1 | -- Saves the number of messages from a user 2 | -- Can check the number of messages with !stats 3 | 4 | do 5 | 6 | local NUM_MSG_MAX = 5 7 | local TIME_CHECK = 4 -- seconds 8 | 9 | local function user_print_name(user) 10 | if user.print_name then 11 | return user.print_name 12 | end 13 | 14 | local text = '' 15 | if user.first_name then 16 | text = user.last_name..' ' 17 | end 18 | if user.lastname then 19 | text = text..user.last_name 20 | end 21 | 22 | return text 23 | end 24 | 25 | -- Returns a table with `name` and `msgs` 26 | local function get_msgs_user_chat(user_id, chat_id) 27 | local user_info = {} 28 | local uhash = 'user:'..user_id 29 | local user = redis:hgetall(uhash) 30 | local um_hash = 'msgs:'..user_id..':'..chat_id 31 | user_info.msgs = tonumber(redis:get(um_hash) or 0) 32 | user_info.name = user_print_name(user)..' ('..user_id..')' 33 | return user_info 34 | end 35 | 36 | local function chat_stats(chat_id) 37 | -- Users on chat 38 | local hash = 'chat:'..chat_id..':users' 39 | local users = redis:smembers(hash) 40 | local users_info = {} 41 | 42 | -- Get user info 43 | for i = 1, #users do 44 | local user_id = users[i] 45 | local user_info = get_msgs_user_chat(user_id, chat_id) 46 | table.insert(users_info, user_info) 47 | end 48 | 49 | -- Sort users by msgs number 50 | table.sort(users_info, function(a, b) 51 | if a.msgs and b.msgs then 52 | return a.msgs > b.msgs 53 | end 54 | end) 55 | 56 | local text = '' 57 | for k,user in pairs(users_info) do 58 | text = text..user.name..' => '..user.msgs..'\n' 59 | end 60 | 61 | return text 62 | end 63 | 64 | -- Save stats, ban user 65 | local function pre_process(msg) 66 | -- Ignore service msg 67 | if msg.service then 68 | print('Service message') 69 | return msg 70 | end 71 | 72 | -- Save user on Redis 73 | if msg.from.type == 'user' then 74 | local hash = 'user:'..msg.from.id 75 | print('Saving user', hash) 76 | if msg.from.print_name then 77 | redis:hset(hash, 'print_name', msg.from.print_name) 78 | end 79 | if msg.from.first_name then 80 | redis:hset(hash, 'first_name', msg.from.first_name) 81 | end 82 | if msg.from.last_name then 83 | redis:hset(hash, 'last_name', msg.from.last_name) 84 | end 85 | end 86 | 87 | -- Save stats on Redis 88 | if msg.to.type == 'chat' then 89 | -- User is on chat 90 | local hash = 'chat:'..msg.to.id..':users' 91 | redis:sadd(hash, msg.from.id) 92 | end 93 | 94 | -- Total user msgs 95 | local hash = 'msgs:'..msg.from.id..':'..msg.to.id 96 | redis:incr(hash) 97 | 98 | -- Check flood 99 | if msg.from.type == 'user' then 100 | local hash = 'user:'..msg.from.id..':msgs' 101 | local msgs = tonumber(redis:get(hash) or 0) 102 | if msgs > NUM_MSG_MAX then 103 | print('User '..msg.from.id..'is flooding '..msgs) 104 | msg = nil 105 | end 106 | redis:setex(hash, TIME_CHECK, msgs+1) 107 | end 108 | 109 | return msg 110 | end 111 | 112 | local function bot_stats() 113 | 114 | local redis_scan = [[ 115 | local cursor = '0' 116 | local count = 0 117 | 118 | repeat 119 | local r = redis.call("SCAN", cursor, "MATCH", KEYS[1]) 120 | cursor = r[1] 121 | count = count + #r[2] 122 | until cursor == '0' 123 | return count]] 124 | 125 | -- Users 126 | local hash = 'msgs:*:'..our_id 127 | local r = redis:eval(redis_scan, 1, hash) 128 | local text = 'Users: '..r 129 | 130 | hash = 'chat:*:users' 131 | r = redis:eval(redis_scan, 1, hash) 132 | text = text..'\nChats: '..r 133 | 134 | return text 135 | 136 | end 137 | 138 | local function run(msg, matches) 139 | if matches[1]:lower() == "stats" then 140 | 141 | if not matches[2] then 142 | if msg.to.type == 'chat' then 143 | local chat_id = msg.to.id 144 | return chat_stats(chat_id) 145 | else 146 | return 'Stats works only on chats' 147 | end 148 | end 149 | 150 | if matches[2] == "bot" then 151 | if not is_sudo(msg) then 152 | return "Bot stats requires privileged user" 153 | else 154 | return bot_stats() 155 | end 156 | end 157 | 158 | if matches[2] == "chat" then 159 | if not is_sudo(msg) then 160 | return "This command requires privileged user" 161 | else 162 | return chat_stats(matches[3]) 163 | end 164 | end 165 | end 166 | end 167 | 168 | return { 169 | description = "Plugin to update user stats.", 170 | usage = { 171 | "!stats: Returns a list of Username [telegram_id]: msg_num", 172 | "!stats chat : Show stats for chat_id", 173 | "!stats bot: Shows bot stats (sudo users)" 174 | }, 175 | patterns = { 176 | "^!([Ss]tats)$", 177 | "^!([Ss]tats) (chat) (%d+)", 178 | "^!([Ss]tats) (bot)" 179 | }, 180 | run = run, 181 | pre_process = pre_process 182 | } 183 | 184 | end -------------------------------------------------------------------------------- /plugins/steam.lua: -------------------------------------------------------------------------------- 1 | -- See https://wiki.teamfortress.com/wiki/User:RJackson/StorefrontAPI 2 | 3 | do 4 | 5 | local BASE_URL = 'http://store.steampowered.com/api/appdetails/' 6 | local DESC_LENTH = 200 7 | 8 | local function unescape(str) 9 | str = string.gsub( str, '<', '<' ) 10 | str = string.gsub( str, '>', '>' ) 11 | str = string.gsub( str, '"', '"' ) 12 | str = string.gsub( str, ''', "'" ) 13 | str = string.gsub( str, '&#(%d+);', function(n) return string.char(n) end ) 14 | str = string.gsub( str, '&#x(%d+);', function(n) return string.char(tonumber(n,16)) end ) 15 | str = string.gsub( str, '&', '&' ) -- Be sure to do this after all others 16 | return str 17 | end 18 | 19 | local function get_steam_data (appid) 20 | local url = BASE_URL 21 | url = url..'?appids='..appid 22 | url = url..'&cc=us' 23 | local res,code = http.request(url) 24 | if code ~= 200 then return nil end 25 | local data = json:decode(res)[appid].data 26 | return data 27 | end 28 | 29 | local function price_info (data) 30 | local price = '' -- If no data is empty 31 | 32 | if data then 33 | local initial = data.initial 34 | local final = data.final or data.initial 35 | local min = math.min(data.initial, data.final) 36 | price = tostring(min/100) 37 | if data.discount_percent and initial ~= final then 38 | price = price..data.currency..' ('..data.discount_percent..'% OFF)' 39 | end 40 | price = price..' (US)' 41 | end 42 | 43 | return price 44 | end 45 | 46 | 47 | local function send_steam_data(data, receiver) 48 | local description = string.sub(unescape(data.about_the_game:gsub("%b<>", "")), 1, DESC_LENTH) .. '...' 49 | local title = data.name 50 | local price = price_info(data.price_overview) 51 | 52 | local text = title..' '..price..'\n'..description 53 | local image_url = data.header_image 54 | local cb_extra = { 55 | receiver = receiver, 56 | url = image_url 57 | } 58 | send_msg(receiver, text, send_photo_from_url_callback, cb_extra) 59 | end 60 | 61 | 62 | local function run(msg, matches) 63 | local appid = matches[1] 64 | local data = get_steam_data(appid) 65 | local receiver = get_receiver(msg) 66 | send_steam_data(data, receiver) 67 | end 68 | 69 | return { 70 | description = "Grabs Steam info for Steam links.", 71 | usage = "", 72 | patterns = { 73 | "http://store.steampowered.com/app/([0-9]+)", 74 | }, 75 | run = run 76 | } 77 | 78 | end 79 | -------------------------------------------------------------------------------- /plugins/tex.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local function send_title(cb_extra, success, result) 4 | if success then 5 | send_msg(cb_extra[1], cb_extra[2], ok_cb, false) 6 | end 7 | end 8 | 9 | local function run(msg, matches) 10 | local eq = URL.escape(matches[1]) 11 | 12 | local url = "http://latex.codecogs.com/png.download?" 13 | .."\\dpi{300}%20\\LARGE%20"..eq 14 | 15 | local receiver = get_receiver(msg) 16 | local title = "Edit LaTeX on www.codecogs.com/eqnedit.php?latex="..eq 17 | send_photo_from_url(receiver, url, send_title, {receiver, title}) 18 | end 19 | 20 | return { 21 | description = "Convert LaTeX equation to image", 22 | usage = { 23 | "!tex [equation]: Convert LaTeX equation to image" 24 | }, 25 | patterns = { 26 | "^!tex (.+)$" 27 | }, 28 | run = run 29 | } 30 | 31 | end 32 | 33 | -------------------------------------------------------------------------------- /plugins/time.lua: -------------------------------------------------------------------------------- 1 | -- Implement a command !time [area] which uses 2 | -- 2 Google APIs to get the desired result: 3 | -- 1. Geocoding to get from area to a lat/long pair 4 | -- 2. Timezone to get the local time in that lat/long location 5 | 6 | -- Globals 7 | -- If you have a google api key for the geocoding/timezone api 8 | api_key = nil 9 | 10 | base_api = "https://maps.googleapis.com/maps/api" 11 | dateFormat = "%A %d %B - %H:%M:%S" 12 | 13 | -- Need the utc time for the google api 14 | function utctime() 15 | return os.time(os.date("!*t")) 16 | end 17 | 18 | -- Use the geocoding api to get the lattitude and longitude with accuracy specifier 19 | -- CHECKME: this seems to work without a key?? 20 | function get_latlong(area) 21 | local api = base_api .. "/geocode/json?" 22 | local parameters = "address=".. (URL.escape(area) or "") 23 | if api_key ~= nil then 24 | parameters = parameters .. "&key="..api_key 25 | end 26 | 27 | -- Do the request 28 | local res, code = https.request(api..parameters) 29 | if code ~=200 then return nil end 30 | local data = json:decode(res) 31 | 32 | if (data.status == "ZERO_RESULTS") then 33 | return nil 34 | end 35 | if (data.status == "OK") then 36 | -- Get the data 37 | lat = data.results[1].geometry.location.lat 38 | lng = data.results[1].geometry.location.lng 39 | acc = data.results[1].geometry.location_type 40 | types= data.results[1].types 41 | return lat,lng,acc,types 42 | end 43 | end 44 | 45 | -- Use timezone api to get the time in the lat, 46 | -- Note: this needs an API key 47 | function get_time(lat,lng) 48 | local api = base_api .. "/timezone/json?" 49 | 50 | -- Get a timestamp (server time is relevant here) 51 | local timestamp = utctime() 52 | local parameters = "location=" .. 53 | URL.escape(lat) .. "," .. 54 | URL.escape(lng) .. 55 | "×tamp="..URL.escape(timestamp) 56 | if api_key ~=nil then 57 | parameters = parameters .. "&key="..api_key 58 | end 59 | 60 | local res,code = https.request(api..parameters) 61 | if code ~= 200 then return nil end 62 | local data = json:decode(res) 63 | 64 | if (data.status == "ZERO_RESULTS") then 65 | return nil 66 | end 67 | if (data.status == "OK") then 68 | -- Construct what we want 69 | -- The local time in the location is: 70 | -- timestamp + rawOffset + dstOffset 71 | local localTime = timestamp + data.rawOffset + data.dstOffset 72 | return localTime, data.timeZoneId 73 | end 74 | return localTime 75 | end 76 | 77 | function getformattedLocalTime(area) 78 | if area == nil then 79 | return "The time in nowhere is never" 80 | end 81 | 82 | lat,lng,acc = get_latlong(area) 83 | if lat == nil and lng == nil then 84 | return 'It seems that in "'..area..'" they do not have a concept of time.' 85 | end 86 | local localTime, timeZoneId = get_time(lat,lng) 87 | 88 | return "The local time in "..timeZoneId.." is: ".. os.date(dateFormat,localTime) 89 | end 90 | 91 | function run(msg, matches) 92 | return getformattedLocalTime(matches[1]) 93 | end 94 | 95 | return { 96 | description = "Displays the local time in an area", 97 | usage = "!time [area]: Displays the local time in that area", 98 | patterns = {"^!time (.*)$"}, 99 | run = run 100 | } 101 | -------------------------------------------------------------------------------- /plugins/torrent_search.lua: -------------------------------------------------------------------------------- 1 | local https = require ('ssl.https') 2 | local ltn12 = require ("ltn12") 3 | 4 | local function search_kickass(query) 5 | local url = 'https://kat.cr/json.php?q='..URL.escape(query) 6 | 7 | local resp = {} 8 | 9 | local b,c = https.request 10 | { 11 | url = url, 12 | protocol = "tlsv1", 13 | sink = ltn12.sink.table(resp) 14 | } 15 | 16 | resp = table.concat(resp) 17 | 18 | local data = json:decode(resp) 19 | 20 | local text = 'Results: '..data.total_results..'\n\n' 21 | local results = math.min(#data.list, 5) 22 | for i=1,results do 23 | local torrent = data.list[i] 24 | local link = torrent.torrentLink 25 | link = link:gsub('%?title=.+','') 26 | text = text..torrent.title 27 | ..'\n'..'Seeds: '..torrent.seeds 28 | ..' '..'Leeches: '..torrent.leechs 29 | ..'\n'..link 30 | --..'\n magnet:?xt=urn:btih:'..torrent.hash 31 | ..'\n\n' 32 | end 33 | return text 34 | end 35 | 36 | local function run(msg, matches) 37 | local query = matches[1] 38 | return search_kickass(query) 39 | end 40 | 41 | return { 42 | description = "Search Torrents", 43 | usage = "!torrent : Search for torrent", 44 | patterns = { 45 | "^!torrent (.+)$" 46 | }, 47 | run = run 48 | } 49 | -------------------------------------------------------------------------------- /plugins/translate.lua: -------------------------------------------------------------------------------- 1 | 2 | --[[ 3 | -- Translate text using Google Translate. 4 | -- http://translate.google.com/translate_a/single?client=t&ie=UTF-8&oe=UTF-8&hl=en&dt=t&tl=en&sl=auto&text=hello 5 | --]] 6 | do 7 | 8 | function translate(source_lang, target_lang, text) 9 | local path = "http://translate.google.com/translate_a/single" 10 | -- URL query parameters 11 | local params = { 12 | client = "t", 13 | ie = "UTF-8", 14 | oe = "UTF-8", 15 | hl = "en", 16 | dt = "t", 17 | tl = target_lang or "en", 18 | sl = source_lang or "auto", 19 | text = URL.escape(text) 20 | } 21 | 22 | local query = format_http_params(params, true) 23 | local url = path..query 24 | 25 | local res, code = https.request(url) 26 | -- Return nil if error 27 | if code > 200 then return nil end 28 | local trans = res:gmatch("%[%[%[\"(.*)\"")():gsub("\"(.*)", "") 29 | 30 | return trans 31 | end 32 | 33 | function run(msg, matches) 34 | -- Third pattern 35 | if #matches == 1 then 36 | print("First") 37 | local text = matches[1] 38 | return translate(nil, nil, text) 39 | end 40 | 41 | -- Second pattern 42 | if #matches == 2 then 43 | print("Second") 44 | local target = matches[1] 45 | local text = matches[2] 46 | return translate(nil, target, text) 47 | end 48 | 49 | -- First pattern 50 | if #matches == 3 then 51 | print("Third") 52 | local source = matches[1] 53 | local target = matches[2] 54 | local text = matches[3] 55 | return translate(source, target, text) 56 | end 57 | 58 | end 59 | 60 | return { 61 | description = "Translate some text", 62 | usage = { 63 | "!translate text. Translate the text to English.", 64 | "!translate target_lang text.", 65 | "!translate source,target text", 66 | }, 67 | patterns = { 68 | "^!translate ([%w]+),([%a]+) (.+)", 69 | "^!translate ([%w]+) (.+)", 70 | "^!translate (.+)", 71 | }, 72 | run = run 73 | } 74 | 75 | end 76 | -------------------------------------------------------------------------------- /plugins/trivia.lua: -------------------------------------------------------------------------------- 1 | do 2 | -- Trivia plugin developed by Guy Spronck 3 | 4 | -- Returns the chat hash for storing information 5 | local function get_hash(msg) 6 | local hash = nil 7 | if msg.to.type == 'chat' then 8 | hash = 'chat:'..msg.to.id..':trivia' 9 | end 10 | if msg.to.type == 'user' then 11 | hash = 'user:'..msg.from.id..':trivia' 12 | end 13 | return hash 14 | end 15 | 16 | -- Sets the question variables 17 | local function set_question(msg, question, answer) 18 | local hash =get_hash(msg) 19 | if hash then 20 | redis:hset(hash, "question", question) 21 | redis:hset(hash, "answer", answer) 22 | redis:hset(hash, "time", os.time()) 23 | end 24 | end 25 | 26 | -- Returns the current question 27 | local function get_question( msg ) 28 | local hash = get_hash(msg) 29 | if hash then 30 | local question = redis:hget(hash, 'question') 31 | if question ~= "NA" then 32 | return question 33 | end 34 | end 35 | return nil 36 | end 37 | 38 | -- Returns the answer of the last question 39 | local function get_answer(msg) 40 | local hash = get_hash(msg) 41 | if hash then 42 | return redis:hget(hash, 'answer') 43 | else 44 | return nil 45 | end 46 | end 47 | 48 | -- Returns the time of the last question 49 | local function get_time(msg) 50 | local hash = get_hash(msg) 51 | if hash then 52 | return redis:hget(hash, 'time') 53 | else 54 | return nil 55 | end 56 | end 57 | 58 | -- This function generates a new question if available 59 | local function get_newquestion(msg) 60 | local timediff = 601 61 | if(get_time(msg)) then 62 | timediff = os.time() - get_time(msg) 63 | end 64 | if(timediff > 600 or get_question(msg) == nil)then 65 | -- Let's show the answer if no-body guessed it right. 66 | if(get_question(msg)) then 67 | send_large_msg(get_receiver(msg), "The question '" .. get_question(msg) .."' has not been answered. \nThe answer was '" .. get_answer(msg) .."'") 68 | end 69 | 70 | local url = "http://jservice.io/api/random/" 71 | local b,c = http.request(url) 72 | local query = json:decode(b) 73 | 74 | if query then 75 | local stringQuestion = "" 76 | if(query[1].category)then 77 | stringQuestion = "Category: " .. query[1].category.title .. "\n" 78 | end 79 | if query[1].question then 80 | stringQuestion = stringQuestion .. "Question: " .. query[1].question 81 | set_question(msg, query[1].question, query[1].answer:lower()) 82 | return stringQuestion 83 | end 84 | end 85 | return 'Something went wrong, please try again.' 86 | else 87 | return 'Please wait ' .. 600 - timediff .. ' seconds before requesting a new question. \nUse !triviaquestion to see the current question.' 88 | end 89 | end 90 | 91 | -- This function generates a new question when forced 92 | local function force_newquestion(msg) 93 | -- Let's show the answer if no-body guessed it right. 94 | if(get_question(msg)) then 95 | send_large_msg(get_receiver(msg), "The question '" .. get_question(msg) .."' has not been answered. \nThe answer was '" .. get_answer(msg) .."'") 96 | end 97 | 98 | local url = "http://jservice.io/api/random/" 99 | local b,c = http.request(url) 100 | local query = json:decode(b) 101 | 102 | if query then 103 | local stringQuestion = "" 104 | if(query[1].category)then 105 | stringQuestion = "Category: " .. query[1].category.title .. "\n" 106 | end 107 | if query[1].question then 108 | stringQuestion = stringQuestion .. "Question: " .. query[1].question 109 | set_question(msg, query[1].question, query[1].answer:lower()) 110 | return stringQuestion 111 | end 112 | end 113 | return 'Something went wrong, please try again.' 114 | end 115 | 116 | -- This function adds a point to the player 117 | local function give_point(msg) 118 | local hash = get_hash(msg) 119 | if hash then 120 | local score = tonumber(redis:hget(hash, msg.from.id) or 0) 121 | redis:hset(hash, msg.from.id, score+1) 122 | end 123 | end 124 | 125 | -- This function checks for a correct answer 126 | local function check_answer(msg, answer) 127 | if(get_answer(msg)) then -- Safety for first time use 128 | if(get_answer(msg) == "NA")then 129 | -- Question has not been set, give a new one 130 | --get_newquestion(msg) 131 | return "No question set, please use !trivia first." 132 | elseif (get_answer(msg) == answer:lower()) then -- Question is set, lets check the answer 133 | set_question(msg, "NA", "NA") -- Correct, clear the answer 134 | give_point(msg) -- gives out point to player for correct answer 135 | return msg.from.print_name .. " has answered correctly! \nUse !trivia to get a new question." 136 | else 137 | return "Sorry " .. msg.from.print_name .. ", but '" .. answer .. "' is not the correct answer!" 138 | end 139 | else 140 | return "No question set, please use !trivia first." 141 | end 142 | end 143 | 144 | local function user_print_name(user) 145 | if user.print_name then 146 | return user.print_name 147 | end 148 | 149 | local text = '' 150 | if user.first_name then 151 | text = user.last_name..' ' 152 | end 153 | if user.lastname then 154 | text = text..user.last_name 155 | end 156 | 157 | return text 158 | end 159 | 160 | 161 | local function get_user_score(msg, user_id, chat_id) 162 | local user_info = {} 163 | local uhash = 'user:'..user_id 164 | local user = redis:hgetall(uhash) 165 | local hash = 'chat:'..msg.to.id..':trivia' 166 | user_info.score = tonumber(redis:hget(hash, user_id) or 0) 167 | user_info.name = user_print_name(user)..' ('..user_id..')' 168 | return user_info 169 | end 170 | 171 | -- Function to print score 172 | local function trivia_scores(msg) 173 | if msg.to.type == 'chat' then 174 | -- Users on chat 175 | local hash = 'chat:'..msg.to.id..':users' 176 | local users = redis:smembers(hash) 177 | local users_info = {} 178 | 179 | -- Get user info 180 | for i = 1, #users do 181 | local user_id = users[i] 182 | local user_info = get_user_score(msg, user_id, msg.to.id) 183 | table.insert(users_info, user_info) 184 | end 185 | 186 | table.sort(users_info, function(a, b) 187 | if a.score and b.score then 188 | return a.score > b.score 189 | end 190 | end) 191 | 192 | local text = '' 193 | for k,user in pairs(users_info) do 194 | text = text..user.name..' => '..user.score..'\n' 195 | end 196 | 197 | return text 198 | else 199 | return "This function is only available in group chats." 200 | end 201 | end 202 | 203 | local function run(msg, matches) 204 | if(matches[1] == "!triviascore" or matches[1] == "!triviascores") then 205 | -- Output all scores 206 | return trivia_scores(msg) 207 | elseif(matches[1] == "!triviaquestion")then 208 | return "Question: " .. get_question(msg) 209 | elseif(matches[1] == "!triviaskip") then 210 | if is_sudo(msg) then 211 | return force_newquestion(msg) 212 | end 213 | elseif(matches[1] ~= "!trivia") then 214 | return check_answer(msg, matches[1]) 215 | end 216 | 217 | return get_newquestion(msg) 218 | end 219 | 220 | return { 221 | description = "Trivia plugin for Telegram", 222 | usage = { 223 | "!trivia to obtain a new question.", 224 | "!trivia [answer] to answer the question.", 225 | "!triviaquestion to show the current question.", 226 | "!triviascore to get a scoretable of all players.", 227 | "!triviaskip to skip a question (requires sudo)" 228 | }, 229 | patterns = {"^!trivia (.*)$", 230 | "^!trivia$", 231 | "^!triviaquestion$", 232 | "^!triviascore$", 233 | "^!triviascores$", 234 | "^!triviaskip$"}, 235 | run = run 236 | } 237 | 238 | end 239 | -------------------------------------------------------------------------------- /plugins/tweet.lua: -------------------------------------------------------------------------------- 1 | local OAuth = require "OAuth" 2 | 3 | local consumer_key = "" 4 | local consumer_secret = "" 5 | local access_token = "" 6 | local access_token_secret = "" 7 | 8 | local twitter_url = "https://api.twitter.com/1.1/statuses/user_timeline.json" 9 | 10 | local client = OAuth.new(consumer_key, 11 | consumer_secret, 12 | { RequestToken = "https://api.twitter.com/oauth/request_token", 13 | AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"}, 14 | AccessToken = "https://api.twitter.com/oauth/access_token"}, 15 | { OAuthToken = access_token, 16 | OAuthTokenSecret = access_token_secret}) 17 | 18 | 19 | local function send_generics_from_url_callback(cb_extra, success, result) 20 | -- cb_extra is a table containing receiver, urls and remove_path 21 | local receiver = cb_extra.receiver 22 | local urls = cb_extra.urls 23 | local remove_path = cb_extra.remove_path 24 | local f = cb_extra.func 25 | 26 | -- The previously image to remove 27 | if remove_path ~= nil then 28 | os.remove(remove_path) 29 | print("Deleted: "..remove_path) 30 | end 31 | 32 | -- Nil or empty, exit case (no more urls) 33 | if urls == nil or #urls == 0 then 34 | return false 35 | end 36 | 37 | -- Take the head and remove from urls table 38 | local head = table.remove(urls, 1) 39 | 40 | local file_path = download_to_file(head, false) 41 | local cb_extra = { 42 | receiver = receiver, 43 | urls = urls, 44 | remove_path = file_path, 45 | func = f 46 | } 47 | 48 | -- Send first and postpone the others as callback 49 | f(receiver, file_path, send_generics_from_url_callback, cb_extra) 50 | end 51 | 52 | local function send_generics_from_url(f, receiver, urls) 53 | local cb_extra = { 54 | receiver = receiver, 55 | urls = urls, 56 | remove_path = nil, 57 | func = f 58 | } 59 | send_generics_from_url_callback(cb_extra) 60 | end 61 | 62 | local function send_gifs_from_url(receiver, urls) 63 | send_generics_from_url(send_document, receiver, urls) 64 | end 65 | 66 | local function send_videos_from_url(receiver, urls) 67 | send_generics_from_url(send_video, receiver, urls) 68 | end 69 | 70 | local function send_all_files(receiver, urls) 71 | local data = { 72 | images = { 73 | func = send_photos_from_url, 74 | urls = {} 75 | }, 76 | gifs = { 77 | func = send_gifs_from_url, 78 | urls = {} 79 | }, 80 | videos = { 81 | func = send_videos_from_url, 82 | urls = {} 83 | } 84 | } 85 | 86 | local table_to_insert = nil 87 | for i,url in pairs(urls) do 88 | local _, _, extension = string.match(url, "(https?)://([^\\]-([^\\%.]+))$") 89 | local mime_type = mimetype.get_content_type_no_sub(extension) 90 | if extension == 'gif' then 91 | table_to_insert = data.gifs.urls 92 | elseif mime_type == 'image' then 93 | table_to_insert = data.images.urls 94 | elseif mime_type == 'video' then 95 | table_to_insert = data.videos.urls 96 | else 97 | table_to_insert = nil 98 | end 99 | if table_to_insert then 100 | table.insert(table_to_insert, url) 101 | end 102 | end 103 | for k, v in pairs(data) do 104 | if #v.urls > 0 then 105 | end 106 | v.func(receiver, v.urls) 107 | end 108 | end 109 | 110 | local function check_keys() 111 | if consumer_key:isempty() then 112 | return "Twitter Consumer Key is empty, write it in plugins/tweet.lua" 113 | end 114 | if consumer_secret:isempty() then 115 | return "Twitter Consumer Secret is empty, write it in plugins/tweet.lua" 116 | end 117 | if access_token:isempty() then 118 | return "Twitter Access Token is empty, write it in plugins/tweet.lua" 119 | end 120 | if access_token_secret:isempty() then 121 | return "Twitter Access Token Secret is empty, write it in plugins/tweet.lua" 122 | end 123 | return "" 124 | end 125 | 126 | 127 | local function analyze_tweet(tweet) 128 | local header = "Tweet from " .. tweet.user.name .. " (@" .. tweet.user.screen_name .. ")\n" -- "Link: https://twitter.com/statuses/" .. tweet.id_str 129 | local text = tweet.text 130 | 131 | -- replace short URLs 132 | if tweet.entities.url then 133 | for k, v in pairs(tweet.entities.urls) do 134 | local short = v.url 135 | local long = v.expanded_url 136 | text = text:gsub(short, long) 137 | end 138 | end 139 | 140 | -- remove urls 141 | local urls = {} 142 | if tweet.extended_entities and tweet.extended_entities.media then 143 | for k, v in pairs(tweet.extended_entities.media) do 144 | if v.video_info and v.video_info.variants then -- If it's a video! 145 | table.insert(urls, v.video_info.variants[1].url) 146 | else -- If not, is an image 147 | table.insert(urls, v.media_url) 148 | end 149 | text = text:gsub(v.url, "") -- Replace the URL in text 150 | end 151 | end 152 | 153 | return header, text, urls 154 | end 155 | 156 | 157 | local function sendTweet(receiver, tweet) 158 | local header, text, urls = analyze_tweet(tweet) 159 | -- send the parts 160 | send_msg(receiver, header .. "\n" .. text, ok_cb, false) 161 | send_all_files(receiver, urls) 162 | return nil 163 | end 164 | 165 | 166 | local function getTweet(msg, base, all) 167 | local receiver = get_receiver(msg) 168 | 169 | local response_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url, base) 170 | 171 | if response_code ~= 200 then 172 | return "Can't connect, maybe the user doesn't exist." 173 | end 174 | 175 | local response = json:decode(response_body) 176 | if #response == 0 then 177 | return "Can't retrieve any tweets, sorry" 178 | end 179 | if all then 180 | for i,tweet in pairs(response) do 181 | sendTweet(receiver, tweet) 182 | end 183 | else 184 | local i = math.random(#response) 185 | local tweet = response[i] 186 | sendTweet(receiver, tweet) 187 | end 188 | 189 | return nil 190 | end 191 | 192 | function isint(n) 193 | return n==math.floor(n) 194 | end 195 | 196 | local function run(msg, matches) 197 | local checked = check_keys() 198 | if not checked:isempty() then 199 | return checked 200 | end 201 | 202 | local base = {include_rts = 1} 203 | 204 | if matches[1] == 'id' then 205 | local userid = tonumber(matches[2]) 206 | if userid == nil or not isint(userid) then 207 | return "The id of a user is a number, check this web: http://gettwitterid.com/" 208 | end 209 | base.user_id = userid 210 | elseif matches[1] == 'name' then 211 | base.screen_name = matches[2] 212 | else 213 | return "" 214 | end 215 | 216 | local count = 200 217 | local all = false 218 | if #matches > 2 and matches[3] == 'last' then 219 | count = 1 220 | if #matches == 4 then 221 | local n = tonumber(matches[4]) 222 | if n > 10 then 223 | return "You only can ask for 10 tweets at most" 224 | end 225 | count = matches[4] 226 | all = true 227 | end 228 | end 229 | base.count = count 230 | 231 | return getTweet(msg, base, all) 232 | end 233 | 234 | 235 | return { 236 | description = "Random tweet from user", 237 | usage = { 238 | "!tweet id [id]: Get a random tweet from the user with that ID", 239 | "!tweet id [id] last: Get a random tweet from the user with that ID", 240 | "!tweet name [name]: Get a random tweet from the user with that name", 241 | "!tweet name [name] last: Get a random tweet from the user with that name" 242 | }, 243 | patterns = { 244 | "^!tweet (id) ([%w_%.%-]+)$", 245 | "^!tweet (id) ([%w_%.%-]+) (last)$", 246 | "^!tweet (id) ([%w_%.%-]+) (last) ([%d]+)$", 247 | "^!tweet (name) ([%w_%.%-]+)$", 248 | "^!tweet (name) ([%w_%.%-]+) (last)$", 249 | "^!tweet (name) ([%w_%.%-]+) (last) ([%d]+)$" 250 | }, 251 | run = run 252 | } 253 | -------------------------------------------------------------------------------- /plugins/twitter.lua: -------------------------------------------------------------------------------- 1 | local OAuth = require "OAuth" 2 | 3 | -- EDIT data/twitter.lua with the API keys 4 | local twitter_config = load_from_file('data/twitter.lua', { 5 | -- DON'T EDIT HERE. 6 | consumer_key = "", consumer_secret = "", 7 | access_token = "", access_token_secret = "" 8 | }) 9 | 10 | local client = OAuth.new(twitter_config.consumer_key, twitter_config.consumer_secret, { 11 | RequestToken = "https://api.twitter.com/oauth/request_token", 12 | AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"}, 13 | AccessToken = "https://api.twitter.com/oauth/twitter_config.access_token" 14 | }, { 15 | OAuthToken = twitter_config.access_token, 16 | OAuthTokenSecret = twitter_config.access_token_secret 17 | }) 18 | 19 | function run(msg, matches) 20 | 21 | if twitter_config.consumer_key:isempty() then 22 | return "Twitter Consumer Key is empty, write it in plugins/twitter.lua" 23 | end 24 | if twitter_config.consumer_secret:isempty() then 25 | return "Twitter Consumer Secret is empty, write it in plugins/twitter.lua" 26 | end 27 | if twitter_config.access_token:isempty() then 28 | return "Twitter Access Token is empty, write it in plugins/twitter.lua" 29 | end 30 | if twitter_config.access_token_secret:isempty() then 31 | return "Twitter Access Token Secret is empty, write it in plugins/twitter.lua" 32 | end 33 | 34 | local twitter_url = "https://api.twitter.com/1.1/statuses/show/" .. matches[1] .. ".json" 35 | local response_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url) 36 | local response = json:decode(response_body) 37 | 38 | local header = "Tweet from " .. response.user.name .. " (@" .. response.user.screen_name .. ")\n" 39 | local text = response.text 40 | 41 | -- replace short URLs 42 | if response.entities.url then 43 | for k, v in pairs(response.entities.urls) do 44 | local short = v.url 45 | local long = v.expanded_url 46 | text = text:gsub(short, long) 47 | end 48 | end 49 | 50 | -- remove images 51 | local images = {} 52 | if response.extended_entities and response.extended_entities.media then 53 | for k, v in pairs(response.extended_entities.media) do 54 | local url = v.url 55 | local pic = v.media_url 56 | text = text:gsub(url, "") 57 | table.insert(images, pic) 58 | end 59 | end 60 | 61 | -- send the parts 62 | local receiver = get_receiver(msg) 63 | send_msg(receiver, header .. "\n" .. text, ok_cb, false) 64 | send_photos_from_url(receiver, images) 65 | return nil 66 | end 67 | 68 | 69 | return { 70 | description = "When user sends twitter URL, send text and images to origin. Requires OAuth Key.", 71 | usage = "", 72 | patterns = { 73 | "https://twitter.com/[^/]+/status/([0-9]+)" 74 | }, 75 | run = run 76 | } 77 | -------------------------------------------------------------------------------- /plugins/twitter_send.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local OAuth = require "OAuth" 4 | 5 | local consumer_key = "" 6 | local consumer_secret = "" 7 | local access_token = "" 8 | local access_token_secret = "" 9 | 10 | local client = OAuth.new(consumer_key, consumer_secret, { 11 | RequestToken = "https://api.twitter.com/oauth/request_token", 12 | AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"}, 13 | AccessToken = "https://api.twitter.com/oauth/access_token" 14 | }, { 15 | OAuthToken = access_token, 16 | OAuthTokenSecret = access_token_secret 17 | }) 18 | 19 | function run(msg, matches) 20 | if consumer_key:isempty() then 21 | return "Twitter Consumer Key is empty, write it in plugins/twitter_send.lua" 22 | end 23 | if consumer_secret:isempty() then 24 | return "Twitter Consumer Secret is empty, write it in plugins/twitter_send.lua" 25 | end 26 | if access_token:isempty() then 27 | return "Twitter Access Token is empty, write it in plugins/twitter_send.lua" 28 | end 29 | if access_token_secret:isempty() then 30 | return "Twitter Access Token Secret is empty, write it in plugins/twitter_send.lua" 31 | end 32 | 33 | if not is_sudo(msg) then 34 | return "You aren't allowed to send tweets" 35 | end 36 | 37 | local response_code, response_headers, response_status_line, response_body = 38 | client:PerformRequest("POST", "https://api.twitter.com/1.1/statuses/update.json", { 39 | status = matches[1] 40 | }) 41 | if response_code ~= 200 then 42 | return "Error: "..response_code 43 | end 44 | return "Tweet sent" 45 | end 46 | 47 | return { 48 | description = "Sends a tweet", 49 | usage = "!tw [text]: Sends the Tweet with the configured account.", 50 | patterns = {"^!tw (.+)"}, 51 | run = run 52 | } 53 | 54 | end 55 | -------------------------------------------------------------------------------- /plugins/version.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function run(msg, matches) 4 | return 'Telegram Bot '.. VERSION .. [[ 5 | Checkout http://git.io/6jdjGg 6 | GNU GPL v2 license.]] 7 | end 8 | 9 | return { 10 | description = "Shows bot version", 11 | usage = "!version: Shows bot version", 12 | patterns = { 13 | "^!version$" 14 | }, 15 | run = run 16 | } 17 | 18 | end 19 | -------------------------------------------------------------------------------- /plugins/vote.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local _file_votes = './data/votes.lua' 4 | 5 | function read_file_votes () 6 | local f = io.open(_file_votes, "r+") 7 | if f == nil then 8 | print ('Created voting file '.._file_votes) 9 | serialize_to_file({}, _file_votes) 10 | else 11 | print ('Values loaded: '.._file_votes) 12 | f:close() 13 | end 14 | return loadfile (_file_votes)() 15 | end 16 | 17 | function clear_votes (chat) 18 | local _votes = read_file_votes () 19 | _votes [chat] = {} 20 | serialize_to_file(_votes, _file_votes) 21 | end 22 | 23 | function votes_result (chat) 24 | local _votes = read_file_votes () 25 | local results = {} 26 | local result_string = "" 27 | if _votes [chat] == nil then 28 | _votes[chat] = {} 29 | end 30 | for user,vote in pairs (_votes[chat]) do 31 | if (results [vote] == nil) then 32 | results [vote] = user 33 | else 34 | results [vote] = results [vote] .. ", " .. user 35 | end 36 | end 37 | for vote,users in pairs (results) do 38 | result_string = result_string .. vote .. " : " .. users .. "\n" 39 | end 40 | return result_string 41 | end 42 | 43 | 44 | function save_vote(chat, user, vote) 45 | local _votes = read_file_votes () 46 | if _votes[chat] == nil then 47 | _votes[chat] = {} 48 | end 49 | _votes[chat][user] = vote 50 | 51 | serialize_to_file(_votes, _file_votes) 52 | 53 | end 54 | 55 | function run(msg, matches) 56 | if (matches[1] == "ing") then 57 | if (matches [2] == "reset") then 58 | clear_votes (tostring(msg.to.id)) 59 | return "Voting statistics reset.." 60 | elseif (matches [2] == "stats") then 61 | local votes_result = votes_result (tostring(msg.to.id)) 62 | if (votes_result == "") then 63 | votes_result = "[No votes registered]\n" 64 | end 65 | return "Voting statistics :\n" .. votes_result 66 | end 67 | else 68 | save_vote(tostring(msg.to.id), msg.from.print_name, tostring(tonumber(matches[2]))) 69 | return "Vote registered : " .. msg.from.print_name .. " " .. tostring(tonumber(matches [2])) 70 | end 71 | end 72 | 73 | return { 74 | description = "Plugin for voting in groups.", 75 | usage = { 76 | "!voting reset: Reset all the votes.", 77 | "!vote [number]: Cast the vote.", 78 | "!voting stats: Shows the statistics of voting." 79 | }, 80 | patterns = { 81 | "^!vot(ing) (reset)", 82 | "^!vot(ing) (stats)", 83 | "^!vot(e) ([0-9]+)$" 84 | }, 85 | run = run 86 | } 87 | 88 | end -------------------------------------------------------------------------------- /plugins/weather.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | local BASE_URL = "http://api.openweathermap.org/data/2.5/weather" 4 | 5 | local function get_weather(location) 6 | print("Finding weather in ", location) 7 | location = string.gsub(location," ","+") 8 | local url = BASE_URL 9 | url = url..'?q='..location 10 | url = url..'&units=metric' 11 | url = url..'&appid=bd82977b86bf27fb59a04b61b657fb6f' 12 | 13 | local b, c, h = http.request(url) 14 | if c ~= 200 then return nil end 15 | 16 | local weather = json:decode(b) 17 | local city = weather.name 18 | local country = weather.sys.country 19 | local temp = 'The temperature in '..city 20 | ..' (' ..country..')' 21 | ..' is '..weather.main.temp..'°C' 22 | local conditions = 'Current conditions are: ' 23 | .. weather.weather[1].description 24 | 25 | if weather.weather[1].main == 'Clear' then 26 | conditions = conditions .. ' ☀' 27 | elseif weather.weather[1].main == 'Clouds' then 28 | conditions = conditions .. ' ☁☁' 29 | elseif weather.weather[1].main == 'Rain' then 30 | conditions = conditions .. ' ☔' 31 | elseif weather.weather[1].main == 'Thunderstorm' then 32 | conditions = conditions .. ' ☔☔☔☔' 33 | end 34 | 35 | return temp .. '\n' .. conditions 36 | end 37 | 38 | local function run(msg, matches) 39 | local city = 'Madrid,ES' 40 | 41 | if matches[1] ~= '!weather' then 42 | city = matches[1] 43 | end 44 | local text = get_weather(city) 45 | if not text then 46 | text = 'Can\'t get weather from that city.' 47 | end 48 | return text 49 | end 50 | 51 | return { 52 | description = "weather in that city (Madrid is default)", 53 | usage = "!weather (city)", 54 | patterns = { 55 | "^!weather$", 56 | "^!weather (.*)$" 57 | }, 58 | run = run 59 | } 60 | 61 | end 62 | -------------------------------------------------------------------------------- /plugins/webshot.lua: -------------------------------------------------------------------------------- 1 | local helpers = require "OAuth.helpers" 2 | 3 | local base = 'https://screenshotmachine.com/' 4 | local url = base .. 'processor.php' 5 | 6 | local function get_webshot_url(param) 7 | local response_body = {} 8 | local request_constructor = { 9 | url = url, 10 | method = "GET", 11 | sink = ltn12.sink.table(response_body), 12 | headers = { 13 | referer = base, 14 | dnt = "1", 15 | origin = base, 16 | ["User-Agent"] = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36" 17 | }, 18 | redirect = false 19 | } 20 | 21 | local arguments = { 22 | urlparam = param, 23 | size = "FULL" 24 | } 25 | 26 | request_constructor.url = url .. "?" .. helpers.url_encode_arguments(arguments) 27 | 28 | local ok, response_code, response_headers, response_status_line = https.request(request_constructor) 29 | if not ok or response_code ~= 200 then 30 | return nil 31 | end 32 | 33 | local response = table.concat(response_body) 34 | return string.match(response, "href='(.-)'") 35 | end 36 | 37 | local function run(msg, matches) 38 | local find = get_webshot_url(matches[1]) 39 | if find then 40 | local imgurl = base .. find 41 | local receiver = get_receiver(msg) 42 | send_photo_from_url(receiver, imgurl) 43 | end 44 | end 45 | 46 | 47 | return { 48 | description = "Send an screenshot of a website.", 49 | usage = { 50 | "!webshot [url]: Take an screenshot of the web and send it back to you." 51 | }, 52 | patterns = { 53 | "^!webshot (https?://[%w-_%.%?%.:/%+=&]+)$", 54 | }, 55 | run = run 56 | } 57 | -------------------------------------------------------------------------------- /plugins/wiki.lua: -------------------------------------------------------------------------------- 1 | -- http://git.io/vUA4M 2 | local socket = require "socket" 3 | local JSON = require "cjson" 4 | 5 | local wikiusage = { 6 | "!wiki [text]: Read extract from default Wikipedia (EN)", 7 | "!wiki(lang) [text]: Read extract from 'lang' Wikipedia. Example: !wikies hola", 8 | "!wiki search [text]: Search articles on default Wikipedia (EN)", 9 | "!wiki(lang) search [text]: Search articles on 'lang' Wikipedia. Example: !wikies search hola", 10 | } 11 | 12 | local Wikipedia = { 13 | -- http://meta.wikimedia.org/wiki/List_of_Wikipedias 14 | wiki_server = "https://%s.wikipedia.org", 15 | wiki_path = "/w/api.php", 16 | wiki_load_params = { 17 | action = "query", 18 | prop = "extracts", 19 | format = "json", 20 | exchars = 300, 21 | exsectionformat = "plain", 22 | explaintext = "", 23 | redirects = "" 24 | }, 25 | wiki_search_params = { 26 | action = "query", 27 | list = "search", 28 | srlimit = 20, 29 | format = "json", 30 | }, 31 | default_lang = "en", 32 | } 33 | 34 | function Wikipedia:getWikiServer(lang) 35 | return string.format(self.wiki_server, lang or self.default_lang) 36 | end 37 | 38 | --[[ 39 | -- return decoded JSON table from Wikipedia 40 | --]] 41 | function Wikipedia:loadPage(text, lang, intro, plain, is_search) 42 | local request, sink = {}, {} 43 | local query = "" 44 | local parsed 45 | 46 | if is_search then 47 | for k,v in pairs(self.wiki_search_params) do 48 | query = query .. k .. '=' .. v .. '&' 49 | end 50 | parsed = URL.parse(self:getWikiServer(lang)) 51 | parsed.path = self.wiki_path 52 | parsed.query = query .. "srsearch=" .. URL.escape(text) 53 | else 54 | self.wiki_load_params.explaintext = plain and "" or nil 55 | for k,v in pairs(self.wiki_load_params) do 56 | query = query .. k .. '=' .. v .. '&' 57 | end 58 | parsed = URL.parse(self:getWikiServer(lang)) 59 | parsed.path = self.wiki_path 60 | parsed.query = query .. "titles=" .. URL.escape(text) 61 | end 62 | 63 | -- HTTP request 64 | request['url'] = URL.build(parsed) 65 | print(request['url']) 66 | request['method'] = 'GET' 67 | request['sink'] = ltn12.sink.table(sink) 68 | 69 | local httpRequest = parsed.scheme == 'http' and http.request or https.request 70 | local code, headers, status = socket.skip(1, httpRequest(request)) 71 | 72 | if not headers or not sink then 73 | return nil 74 | end 75 | 76 | local content = table.concat(sink) 77 | if content ~= "" then 78 | local ok, result = pcall(JSON.decode, content) 79 | if ok and result then 80 | return result 81 | else 82 | return nil 83 | end 84 | else 85 | return nil 86 | end 87 | end 88 | 89 | -- extract intro passage in wiki page 90 | function Wikipedia:wikintro(text, lang) 91 | local result = self:loadPage(text, lang, true, true) 92 | 93 | if result and result.query then 94 | 95 | local query = result.query 96 | if query and query.normalized then 97 | text = query.normalized[1].to or text 98 | end 99 | 100 | local page = query.pages[next(query.pages)] 101 | 102 | if page and page.extract then 103 | return text..": "..page.extract 104 | else 105 | local text = "Extract not found for "..text 106 | text = text..'\n'..table.concat(wikiusage, '\n') 107 | return text 108 | end 109 | else 110 | return "Sorry an error happened" 111 | end 112 | end 113 | 114 | -- search for term in wiki 115 | function Wikipedia:wikisearch(text, lang) 116 | local result = self:loadPage(text, lang, true, true, true) 117 | 118 | if result and result.query then 119 | local titles = "" 120 | for i,item in pairs(result.query.search) do 121 | titles = titles .. "\n" .. item["title"] 122 | end 123 | titles = titles ~= "" and titles or "No results found" 124 | return titles 125 | else 126 | return "Sorry, an error occurred" 127 | end 128 | 129 | end 130 | 131 | local function run(msg, matches) 132 | -- TODO: Remember language (i18 on future version) 133 | -- TODO: Support for non Wikipedias but Mediawikis 134 | local search, term, lang 135 | if matches[1] == "search" then 136 | search = true 137 | term = matches[2] 138 | lang = nil 139 | elseif matches[2] == "search" then 140 | search = true 141 | term = matches[3] 142 | lang = matches[1] 143 | else 144 | term = matches[2] 145 | lang = matches[1] 146 | end 147 | if not term then 148 | term = lang 149 | lang = nil 150 | end 151 | if term == "" then 152 | local text = "Usage:\n" 153 | text = text..table.concat(wikiusage, '\n') 154 | return text 155 | end 156 | 157 | local result 158 | if search then 159 | result = Wikipedia:wikisearch(term, lang) 160 | else 161 | -- TODO: Show the link 162 | result = Wikipedia:wikintro(term, lang) 163 | end 164 | return result 165 | end 166 | 167 | return { 168 | description = "Searches Wikipedia and send results", 169 | usage = wikiusage, 170 | patterns = { 171 | "^![Ww]iki(%w+) (search) (.+)$", 172 | "^![Ww]iki (search) ?(.*)$", 173 | "^![Ww]iki(%w+) (.+)$", 174 | "^![Ww]iki ?(.*)$" 175 | }, 176 | run = run 177 | } 178 | -------------------------------------------------------------------------------- /plugins/xkcd.lua: -------------------------------------------------------------------------------- 1 | do 2 | 3 | function get_last_id() 4 | local res,code = https.request("http://xkcd.com/info.0.json") 5 | if code ~= 200 then return "HTTP ERROR" end 6 | local data = json:decode(res) 7 | return data.num 8 | end 9 | 10 | function get_xkcd(id) 11 | local res,code = http.request("http://xkcd.com/"..id.."/info.0.json") 12 | if code ~= 200 then return "HTTP ERROR" end 13 | local data = json:decode(res) 14 | local link_image = data.img 15 | if link_image:sub(0,2) == '//' then 16 | link_image = msg.text:sub(3,-1) 17 | end 18 | return link_image, data.title, data.alt 19 | end 20 | 21 | 22 | function get_xkcd_random() 23 | local last = get_last_id() 24 | local i = math.random(1, last) 25 | return get_xkcd(i) 26 | end 27 | 28 | function send_title(cb_extra, success, result) 29 | if success then 30 | local message = cb_extra[2] .. "\n" .. cb_extra[3] 31 | send_msg(cb_extra[1], message, ok_cb, false) 32 | end 33 | end 34 | 35 | function run(msg, matches) 36 | local receiver = get_receiver(msg) 37 | if matches[1] == "!xkcd" then 38 | url, title, alt = get_xkcd_random() 39 | else 40 | url, title, alt = get_xkcd(matches[1]) 41 | end 42 | file_path = download_to_file(url) 43 | send_photo(receiver, file_path, send_title, {receiver, title, alt}) 44 | return false 45 | end 46 | 47 | return { 48 | description = "Send comic images from xkcd", 49 | usage = {"!xkcd (id): Send an xkcd image and title. If not id, send a random one"}, 50 | patterns = { 51 | "^!xkcd$", 52 | "^!xkcd (%d+)", 53 | "xkcd.com/(%d+)" 54 | }, 55 | run = run 56 | } 57 | 58 | end 59 | -------------------------------------------------------------------------------- /plugins/yoda.lua: -------------------------------------------------------------------------------- 1 | local ltn12 = require "ltn12" 2 | local https = require "ssl.https" 3 | 4 | -- Edit data/mashape.lua with your Mashape API key 5 | -- http://docs.mashape.com/api-keys 6 | local mashape = load_from_file('data/mashape.lua', { 7 | api_key = '' 8 | }) 9 | 10 | local function request(text) 11 | local api = "https://yoda.p.mashape.com/yoda?" 12 | text = string.gsub(text, " ", "+") 13 | local parameters = "sentence="..(text or "") 14 | local url = api..parameters 15 | 16 | local api_key = mashape.api_key 17 | if api_key:isempty() then 18 | return 'Configure your Mashape API Key' 19 | end 20 | 21 | local headers = { 22 | ["X-Mashape-Key"] = api_key, 23 | ["Accept"] = "text/plain" 24 | } 25 | 26 | local respbody = {} 27 | local body, code = https.request{ 28 | url = url, 29 | method = "GET", 30 | headers = headers, 31 | sink = ltn12.sink.table(respbody), 32 | protocol = "tlsv1" 33 | } 34 | if code ~= 200 then return code end 35 | local body = table.concat(respbody) 36 | return body 37 | end 38 | 39 | local function run(msg, matches) 40 | return request(matches[1]) 41 | end 42 | 43 | return { 44 | description = "Listen to Yoda and learn from his words!", 45 | usage = "!yoda You will learn how to speak like me someday.", 46 | patterns = { 47 | "^![y|Y]oda (.*)$" 48 | }, 49 | run = run 50 | } 51 | -------------------------------------------------------------------------------- /plugins/youtube.lua: -------------------------------------------------------------------------------- 1 | do 2 | local google_config = load_from_file('data/google.lua') 3 | 4 | local function httpsRequest(url) 5 | print(url) 6 | local res,code = https.request(url) 7 | if code ~= 200 then return nil end 8 | return json:decode(res) 9 | end 10 | 11 | function get_yt_data (yt_code) 12 | local url = 'https://www.googleapis.com/youtube/v3/videos?' 13 | url = url .. 'id=' .. URL.escape(yt_code) .. '&part=snippet' 14 | if google_config.api_keys then 15 | local i = math.random(#google_config.api_keys) 16 | local api_key = google_config.api_keys[i] 17 | if api_key then 18 | url = url.."&key="..api_key 19 | end 20 | end 21 | return httpsRequest(url) 22 | end 23 | 24 | function send_youtube_data(data, receiver) 25 | local title = data.title 26 | local description = data.description 27 | local uploader = data.channelTitle 28 | local text = title..' ('..uploader..')\n'..description 29 | local image_url = data.thumbnails.high.url or data.thumbnails.default.url 30 | local cb_extra = { 31 | receiver = receiver, 32 | url = image_url 33 | } 34 | send_msg(receiver, text, send_photo_from_url_callback, cb_extra) 35 | end 36 | 37 | function run(msg, matches) 38 | local yt_code = matches[1] 39 | local data = get_yt_data(yt_code) 40 | if data == nil or #data.items == 0 then 41 | return "I didn't find info about that video." 42 | end 43 | local senddata = data.items[1].snippet 44 | local receiver = get_receiver(msg) 45 | send_youtube_data(senddata, receiver) 46 | end 47 | 48 | return { 49 | description = "Sends YouTube info and image.", 50 | usage = "", 51 | patterns = { 52 | "youtu.be/([_A-Za-z0-9-]+)", 53 | "youtube.com/watch%?v=([_A-Za-z0-9-]+)", 54 | }, 55 | run = run 56 | } 57 | 58 | end 59 | --------------------------------------------------------------------------------