├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── bot
├── bot.lua
└── utils.lua
├── botapi.lua
├── data
└── .gitkeep
├── libs
├── JSON.lua
├── hookio.js
├── mimetype.lua
└── redis.lua
├── merbot
├── patches
└── merbot.patch
└── plugins
├── 9gag.lua
├── administration.lua
├── apod.lua
├── bing.lua
├── btc.lua
├── calculator.lua
├── cats.lua
├── currency.lua
├── dilbert.lua
├── dogify.lua
├── forecast.lua
├── gmaps.lua
├── gsmarena.lua
├── hackernews.lua
├── help.lua
├── id.lua
├── imdb.lua
├── isup.lua
├── kaskus.lua
├── kbbi.lua
├── logger.lua
├── patterns.lua
├── plugins.lua
├── quran.lua
├── reddit.lua
├── rss.lua
├── salat.lua
├── stats.lua
├── sudo.lua
├── time.lua
├── translate.lua
├── urbandictionary.lua
├── webshot.lua
├── whois.lua
├── xkcd.lua
└── yify.lua
/.gitignore:
--------------------------------------------------------------------------------
1 | log
2 | tg/
3 | data/
4 | .luarocks/
5 | .telegram-cli/
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "tg"]
2 | path = tg
3 | url = https://github.com/vysheng/tg
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
merbot 2 | 3 |
**A Telegram Group Administration Bot** 4 | 5 | 6 |
7 |
8 | **Merbot** is a dedicated Telegram group administration bot based on [telegram-bot](https://github.com/yagop/telegram-bot).
9 |
10 | Consult [Merbot's wiki](https://github.com/rizaumami/merbot/wiki) for documentation.
11 |
12 | Please open a [github issue](https://github.com/rizaumami/merbot/issues) if you found an issue with `merbot`.
13 |
14 | ***
15 |
16 | _Thanks to [rizkymsyahputra](https://github.com/rizkymsyahputra) for the awesome merbot artwork_.
17 |
--------------------------------------------------------------------------------
/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 | vardump(msg)
18 | msg = pre_process_service_msg(msg)
19 | if msg_valid(msg) then
20 | msg = pre_process_msg(msg)
21 | if msg then
22 | match_plugins(msg)
23 | mark_read(get_receiver(msg), ok_cb, false)
24 | end
25 | end
26 | end
27 |
28 | function ok_cb(extra, success, result)
29 | end
30 |
31 | function on_binlog_replay_end()
32 | started = true
33 | postpone (cron_plugins, false, 60*5.0)
34 | -- See plugins/isup.lua as an example for cron
35 |
36 | _config = load_config()
37 |
38 | -- load plugins
39 | plugins = {}
40 | load_plugins()
41 | end
42 |
43 | function msg_valid(msg)
44 | -- Don't process outgoing messages
45 | -- if msg.out then
46 | -- print('\27[36mNot valid: msg from us\27[39m')
47 | -- return false
48 | -- end
49 |
50 | -- Before bot was started
51 | if msg.date < now then
52 | print('\27[36mNot valid: old msg\27[39m')
53 | return false
54 | end
55 |
56 | if msg.unread == 0 then
57 | print('\27[36mNot valid: readed\27[39m')
58 | return false
59 | end
60 |
61 | if not msg.to.peer_id then
62 | print('\27[36mNot valid: To id not provided\27[39m')
63 | return false
64 | end
65 |
66 | if not msg.from.peer_id then
67 | print('\27[36mNot valid: From id not provided\27[39m')
68 | return false
69 | end
70 |
71 | if msg.from.peer_id == tonumber(_config.bot_api.uid) then
72 | print('\27[36mNot valid: Msg from our companion bot\27[39m')
73 | return false
74 | end
75 |
76 | -- if msg.from.peer_id == our_id then
77 | -- print('\27[36mNot valid: Msg from our id\27[39m')
78 | -- return false
79 | -- end
80 |
81 | if msg.to.peer_type == 'encr_chat' then
82 | print('\27[36mNot valid: Encrypted chat\27[39m')
83 | return false
84 | end
85 |
86 | if msg.from.peer_id == 777000 then
87 | print('\27[36mNot valid: Telegram message\27[39m')
88 | return false
89 | end
90 |
91 | return true
92 | end
93 |
94 | local function process_api_msg(msg)
95 | if not is_chat_msg(msg) and msg.from.peer_id == _config.bot_api.uid then
96 | local loadapimsg = loadstring(msg.text)
97 | local apimsg = loadapimsg().message
98 | local target = tostring(apimsg.chat.id):gsub('-', '')
99 |
100 | if apimsg.chat.type == 'supergroup' or apimsg.chat.type == 'channel' then
101 | target = tostring(apimsg.chat.id):gsub('-100', '')
102 | end
103 |
104 | if not _config.administration[tonumber(target)] or apimsg.chat.type == 'supergroup' then
105 | msg.from.api = true
106 | msg.from.first_name = apimsg.from.first_name
107 | msg.from.peer_id = apimsg.from.id
108 | msg.from.username = apimsg.from.username
109 | msg.to.peer_id = apimsg.chat.id
110 | msg.to.peer_type = apimsg.chat.type
111 | msg.id = apimsg.message_id
112 | msg.text = apimsg.text
113 |
114 | if apimsg.chat.type == 'group' or apimsg.chat.type == 'supergroup' or apimsg.chat.type == 'channel' then
115 | msg.to.title = apimsg.chat.title
116 | msg.to.username = apimsg.chat.username
117 | end
118 |
119 | if apimsg.chat.type == 'private' then
120 | msg.to.first_name = apimsg.chat.first_name
121 | msg.to.username = apimsg.chat.username
122 | end
123 |
124 | if apimsg.reply_to_message then
125 | msg.reply_to_message = apimsg.reply_to_message
126 | end
127 |
128 | if apimsg.new_chat_title then
129 | msg.action = { title = apimsg.new_chat_title, type = 'chat_rename' }
130 | end
131 |
132 | if apimsg.new_chat_participant then
133 | msg.action.type = 'chat_add_user'
134 | msg.action.user.first_name = apimsg.new_chat_participant.first_name
135 | msg.action.user.peer_id = apimsg.new_chat_participant.id
136 | msg.action.user.username = apimsg.new_chat_participant.username
137 | end
138 |
139 | if apimsg.left_chat_participant then
140 | msg.action.type = 'chat_del_user'
141 | msg.action.user.first_name = apimsg.new_chat_participant.first_name
142 | msg.action.user.peer_id = apimsg.new_chat_participant.id
143 | msg.action.user.username = apimsg.new_chat_participant.username
144 | end
145 |
146 | if apimsg.new_chat_photo then
147 | msg.action.type = 'chat_change_photo'
148 | end
149 |
150 | if apimsg.delete_chat_photo then
151 | msg.action.type = 'chat_delete_photo'
152 | end
153 |
154 | -- if apimsg.group_chat_created then
155 | -- msg.action = { title = apimsg.group_chat_created, type = 'chat_created' }
156 | -- end
157 | -- if apimsg.supergroup_chat_created then
158 | -- msg.action = { title = apimsg.supergroup_chat_created , type = '' }
159 | -- end
160 | -- if apimsg.channel_chat_created then
161 | -- msg.action = { title = apimsg.channel_chat_created, type = '' }
162 | -- end
163 | -- if apimsg.migrate_to_chat_id then
164 | -- msg.action = { title = apimsg.migrate_to_chat_id, type = '' }
165 | -- end
166 | -- if apimsg.migrate_from_chat_id then
167 | -- msg.action = { title = apimsg.migrate_from_chat_id, type = 'migrated_from' }
168 | -- end
169 | end
170 | end
171 | return msg
172 | end
173 |
174 | function pre_process_service_msg(msg)
175 | if msg.service then
176 | local action = msg.action or {type=''}
177 | -- Double ! to discriminate of normal actions
178 | msg.text = '!!tgservice ' .. action.type
179 |
180 | -- wipe the data to allow the bot to read service messages
181 | if msg.out then
182 | msg.out = false
183 | end
184 | if msg.from.peer_id == our_id then
185 | msg.from.peer_id = 0
186 | end
187 | end
188 |
189 | -- if is_chat_msg(msg) then
190 | -- msg.is_processed_by_tgcli = true
191 | -- end
192 |
193 | -- if not msg.is_processed_by_tgcli then
194 | -- msg = process_api_msg(msg)
195 | -- end
196 |
197 | local msg = process_api_msg(msg)
198 |
199 | return msg
200 | end
201 |
202 | -- Apply plugin.pre_process function
203 | function pre_process_msg(msg)
204 | for name,plugin in pairs(plugins) do
205 | if plugin.pre_process and msg then
206 | print('Preprocess', name)
207 | msg = plugin.pre_process(msg)
208 | end
209 | end
210 | return msg
211 | end
212 |
213 | -- Go over enabled plugins patterns.
214 | function match_plugins(msg)
215 | for name, plugin in pairs(plugins) do
216 | match_plugin(plugin, name, msg)
217 | end
218 | end
219 |
220 | -- Check if plugin is on _config.disabled_plugin_on_chat table
221 | local function is_plugin_disabled_on_chat(plugin_name, receiver)
222 | local disabled_chats = _config.disabled_plugin_on_chat
223 | -- Table exists and chat has disabled plugins
224 | if disabled_chats and disabled_chats[receiver] then
225 | -- Checks if plugin is disabled on this chat
226 | for disabled_plugin,disabled in pairs(disabled_chats[receiver]) do
227 | if disabled_plugin == plugin_name and disabled then
228 | local warning = 'Plugin ' .. disabled_plugin .. ' is disabled on this chat'
229 | print(warning)
230 | send_msg(receiver, warning, ok_cb, false)
231 | return true
232 | end
233 | end
234 | end
235 | return false
236 | end
237 |
238 | function match_plugin(plugin, plugin_name, msg)
239 | -- Go over patterns. If one matches it's enough.
240 | for k, pattern in pairs(plugin.patterns) do
241 | local matches = match_pattern(pattern, msg.text)
242 | if matches then
243 | print('msg matches: ', pattern)
244 |
245 | if is_plugin_disabled_on_chat(plugin_name, get_receiver(msg)) then
246 | return nil
247 | end
248 | -- Function exists
249 | if plugin.run then
250 | -- If plugin is for privileged users only
251 | if not warns_user_not_allowed(plugin, msg) then
252 | local result = plugin.run(msg, matches)
253 | if result then
254 | send_large_msg(get_receiver(msg), result)
255 | end
256 | end
257 | end
258 | -- One patterns matches
259 | return
260 | end
261 | end
262 | end
263 |
264 | -- DEPRECATED, use send_large_msg(destination, text)
265 | function _send_msg(destination, text)
266 | send_large_msg(destination, text)
267 | end
268 |
269 | -- Create a basic config.lua file and saves it.
270 | function create_config()
271 | print('\n\27[1;33m Some functions and plugins using bot API as sender.\n'
272 | .. ' Please provide bots API token to ensure it\'s works as intended.\n'
273 | .. ' You can ENTER to skip and then fill the required info into data/config.lua.\27[0;39;49m\n')
274 |
275 | io.write('\27[1m Input your bot API key (token) here: \27[0;39;49m')
276 |
277 | local bot_api_key = io.read()
278 | local response = {}
279 |
280 | local botid = api_getme(bot_api_key)
281 |
282 | -- A simple config with basic plugins and ourselves as privileged user
283 | _config = {
284 | administration = {},
285 | administrators = {},
286 | api_key = {
287 | bing_url = 'https://datamarket.azure.com/dataset/bing/search',
288 | bing = '',
289 | forecast_url = 'https://developer.forecast.io/',
290 | forecast = '',
291 | globalquran_url = 'http://globalquran.com/contribute/signup.php',
292 | globalquran = '',
293 | muslimsalat_url = 'http://muslimsalat.com/panel/signup.php',
294 | muslimsalat = '',
295 | nasa_api_url = 'http://api.nasa.gov',
296 | nasa_api = '',
297 | thecatapi_url = 'http://thecatapi.com/docs.html',
298 | thecatapi = '',
299 | yandex_url = 'http://tech.yandex.com/keys/get',
300 | yandex = '',
301 | },
302 | autoleave = false,
303 | bot_api = {
304 | key = bot_api_key,
305 | master = our_id,
306 | uid = botid.id,
307 | uname = botid.username
308 | },
309 | disabled_channels = {},
310 | disabled_plugin_on_chat = {},
311 | enabled_plugins = {
312 | '9gag',
313 | 'administration',
314 | 'bing',
315 | 'calculator',
316 | 'cats',
317 | 'currency',
318 | 'dilbert',
319 | 'dogify',
320 | 'forecast',
321 | 'gmaps',
322 | 'hackernews',
323 | 'help',
324 | 'id',
325 | 'imdb',
326 | 'isup',
327 | 'patterns',
328 | 'plugins',
329 | 'reddit',
330 | 'rss',
331 | 'salat',
332 | 'stats',
333 | 'sudo',
334 | 'time',
335 | 'urbandictionary',
336 | 'webshot',
337 | 'whois',
338 | 'xkcd',
339 | },
340 | globally_banned = {},
341 | mkgroup = {founded = '', founder = '', title = '', gtype = '', uid = ''},
342 | realm = {},
343 | sudo_users = {[our_id] = our_id}
344 | }
345 | save_config()
346 | end
347 |
348 | -- Save the content of _config to config.lua
349 | function save_config()
350 | serialize_to_file(_config, './data/config.lua')
351 | print ('Saved config into ./data/config.lua')
352 | end
353 |
354 | -- Returns the config from config.lua file.
355 | -- If file doesn't exist, create it.
356 | function load_config()
357 | local exist = os.execute('test -s .telegram-cli/auth')
358 | if not exist then
359 | print('\n\27[1;33m You are not logged in.\n'
360 | .. ' Please log your bot in first, then restart merbot.\27[0;39;49m\n')
361 | return
362 | end
363 | local f = io.open('./data/config.lua', 'r')
364 | -- If config.lua doesn't exist
365 | if not f then
366 | print ('Created new config file: data/config.lua')
367 | create_config()
368 | print('\27[1;33m \n'
369 | .. ' Required datas has been saved to ./data/config.lua.\n'
370 | .. ' Please run bot-api.lua in another tmux/multiplexer window.\27[0;39;49m\n')
371 | else
372 | f:close()
373 | end
374 | local config = loadfile('./data/config.lua')()
375 | for v,user in pairs(config.sudo_users) do
376 | print('Allowed user: ' .. user)
377 | end
378 | return config
379 | end
380 |
381 | function on_our_id (id)
382 | our_id = id
383 | end
384 |
385 | function on_user_update (user, what)
386 | --vardump (user)
387 | end
388 |
389 | function on_chat_update (chat, what)
390 | --vardump(chat)
391 | end
392 |
393 | function on_secret_chat_update (schat, what)
394 | --vardump(schat)
395 | end
396 |
397 | function on_get_difference_end ()
398 | end
399 |
400 | -- Enable plugins in config.json
401 | function load_plugins()
402 | if _config then
403 | for k, v in pairs(_config.enabled_plugins) do
404 | print('Loading plugin', v)
405 |
406 | local ok, err = pcall(function()
407 | plug = loadfile('plugins/' .. v .. '.lua')()
408 | plugins[v] = plug
409 | end)
410 |
411 | if not ok then
412 | print('\27[31mError loading plugin ' .. v .. '\27[39m')
413 | print('\27[31m' .. err .. '\27[39m')
414 | else
415 | if plug.is_need_api_key then
416 | local keyname = _config.api_key[plug.is_need_api_key[1]]
417 | if not keyname or keyname == '' then
418 | table.remove(_config.enabled_plugins, k)
419 | save_config()
420 | print('\27[33mMissing ' .. v .. ' api key\27[39m')
421 | print('\27[33m' .. v .. '.lua will not be enabled.\27[39m')
422 | end
423 | end
424 | end
425 | end
426 | end
427 | end
428 |
429 | function load_data(filename)
430 | if not filename then
431 | _groups_data = {}
432 | else
433 | _groups_data = loadfile(filename)()
434 | end
435 | return _groups_data
436 | end
437 |
438 | function save_data(data, file)
439 | file = io.open(file, 'w+')
440 | local serialized = serpent.block(data, {comment = false, name = '_'})
441 | file:write(serialized)
442 | file:close()
443 | end
444 |
445 | -- Call and postpone execution for cron plugins
446 | function cron_plugins()
447 | for name, plugin in pairs(plugins) do
448 | -- Only plugins with cron function
449 | if plugin.cron ~= nil then
450 | plugin.cron()
451 | end
452 | end
453 | -- Called again in 5 mins
454 | postpone (cron_plugins, false, 5*60.0)
455 | end
456 |
457 | -- Start and load values
458 | our_id = 0
459 | now = os.time()
460 | math.randomseed(now)
461 | started = false
462 |
--------------------------------------------------------------------------------
/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 | multipart = require 'multipart-post'
8 |
9 | json = (loadfile "./libs/JSON.lua")()
10 | mimetype = (loadfile "./libs/mimetype.lua")()
11 | redis = (loadfile "./libs/redis.lua")()
12 |
13 | http.TIMEOUT = 10
14 | tgclie = './tg/bin/telegram-cli -k ./tg/tg-server-pub -c ./data/tg-cli.config -p default -De %q'
15 |
16 | function get_receiver(msg)
17 | if msg.to.peer_type == 'user' then
18 | return 'user#id' .. msg.from.peer_id
19 | end
20 | if msg.to.peer_type == 'chat' then
21 | return 'chat#id' .. msg.to.peer_id
22 | end
23 | if msg.to.peer_type == 'encr_chat' then
24 | return msg.to.print_name
25 | end
26 | if msg.to.peer_type == 'channel' then
27 | return 'channel#id' .. msg.to.peer_id
28 | end
29 | end
30 |
31 | function get_receiver_api(msg)
32 | if msg.to.peer_type == 'user' or msg.to.peer_type == 'private' then
33 | return msg.from.peer_id
34 | end
35 | if msg.to.peer_type == 'chat' or msg.to.peer_type == 'group' then
36 | if not msg.from.api then
37 | return '-' .. msg.to.peer_id
38 | else
39 | return msg.to.peer_id
40 | end
41 | end
42 | --TODO testing needed
43 | -- if msg.to.peer_type == 'encr_chat' then
44 | -- return msg.to.print_name
45 | -- end
46 | if msg.to.peer_type == 'channel' or msg.to.peer_type == 'supergroup' then
47 | if not msg.from.api then
48 | return '-100' .. msg.to.peer_id
49 | else
50 | return msg.to.peer_id
51 | end
52 | end
53 | end
54 |
55 | function is_chat_msg(msg)
56 | if msg.to.peer_type == 'private' or msg.to.peer_type == 'user' then
57 | return false
58 | else
59 | return true
60 | end
61 | end
62 |
63 | function is_realm(msg)
64 | if msg.to.peer_id == _config.realm.gid then
65 | return true
66 | else
67 | return false
68 | end
69 | end
70 |
71 | function string.random(length)
72 | local str = '';
73 | for i = 1, length do
74 | math.random(97, 122)
75 | str = str .. string.char(math.random(97, 122));
76 | end
77 | return str;
78 | end
79 |
80 | function string:split(sep)
81 | local sep, fields = sep or ':', {}
82 | local pattern = string.format("([^%s]+)", sep)
83 | self:gsub(pattern, function(c) fields[#fields+1] = c end)
84 | return fields
85 | end
86 |
87 | -- Removes spaces
88 | function string:trim()
89 | return self:gsub("^%s*(.-)%s*$", "%1")
90 | end
91 |
92 | function get_http_file_name(url, headers)
93 | -- Eg: foo.var
94 | local file_name = url:match("[^%w]+([%.%w]+)$")
95 | -- Any delimited alphanumeric on the url
96 | file_name = file_name or url:match("[^%w]+(%w+)[^%w]+$")
97 | -- Random name, hope content-type works
98 | file_name = file_name or str:random(5)
99 |
100 | local content_type = headers['content-type']
101 |
102 | local extension = nil
103 | if content_type then
104 | extension = mimetype.get_mime_extension(content_type)
105 | end
106 | if extension then
107 | file_name = file_name .. '.' .. extension
108 | end
109 |
110 | local disposition = headers['content-disposition']
111 | if disposition then
112 | -- attachment; filename=CodeCogsEqn.png
113 | file_name = disposition:match('filename=([^;]+)') or file_name
114 | end
115 |
116 | return file_name
117 | end
118 |
119 | -- Saves file to /tmp/.
120 | -- If file_name isn't provided, will get the text after the last "/" for
121 | -- filename and content-type for extension
122 | function download_to_file(url, file_name)
123 | print('url to download: ' .. url)
124 |
125 | local respbody = {}
126 | local options = {
127 | url = url,
128 | sink = ltn12.sink.table(respbody),
129 | redirect = true
130 | }
131 |
132 | -- nil, code, headers, status
133 | local response = nil
134 |
135 | if url:starts('https') then
136 | options.redirect = false
137 | response = {https.request(options)}
138 | else
139 | response = {http.request(options)}
140 | end
141 |
142 | local code = response[2]
143 | local headers = response[3]
144 | local status = response[4]
145 |
146 | if code ~= 200 then return nil end
147 |
148 | file_name = file_name or get_http_file_name(url, headers)
149 |
150 | local file_path = '/tmp/' .. file_name
151 | print('Saved to: ' .. file_path)
152 |
153 | file = io.open(file_path, "w+")
154 | file:write(table.concat(respbody))
155 | file:close()
156 |
157 | return file_path
158 | end
159 |
160 | function vardump(value)
161 | print(serpent.block(value, {comment=false}))
162 | end
163 |
164 | -- http://stackoverflow.com/a/11130774/3163199
165 | function scandir(directory)
166 | local i, t, popen = 0, {}, io.popen
167 | for filename in popen('ls -a "' .. directory .. '"'):lines() do
168 | i = i + 1
169 | t[i] = filename
170 | end
171 | return t
172 | end
173 |
174 | -- http://www.lua.org/manual/5.2/manual.html#pdf-io.popen
175 | function run_command(str)
176 | local cmd = io.popen(str)
177 | local result = cmd:read('*all')
178 | cmd:close()
179 | return result
180 | end
181 |
182 | function is_administrate(msg, gid)
183 | local var = true
184 | if not _config.administration[gid] then
185 | var = false
186 | send_message(msg, 'I do not administrate this group', 'html')
187 | end
188 | return var
189 | end
190 |
191 | -- User has privileges
192 | function is_sudo(user_id)
193 | local var = false
194 | if _config.sudo_users[user_id] then
195 | var = true
196 | end
197 | return var
198 | end
199 |
200 | -- User is a global administrator
201 | function is_admin(user_id)
202 | local var = false
203 | if _config.administrators[user_id] then
204 | var = true
205 | end
206 | if _config.sudo_users[user_id] then
207 | var = true
208 | end
209 | return var
210 | end
211 |
212 | -- User is a group owner
213 | function is_owner(msg, chat_id, user_id)
214 | local var = false
215 | local data = load_data(_config.administration[chat_id])
216 | if data.owners == nil then
217 | var = false
218 | elseif data.owners[user_id] then
219 | var = true
220 | end
221 | if _config.administrators[user_id] then
222 | var = true
223 | end
224 | if _config.sudo_users[user_id] then
225 | var = true
226 | end
227 | return var
228 | end
229 |
230 | -- User is a group moderator
231 | function is_mod(msg, chat_id, user_id)
232 | local var = false
233 | local data = load_data(_config.administration[chat_id])
234 | if data.moderators == nil then
235 | var = false
236 | elseif data.moderators[user_id] then
237 | var = true
238 | end
239 | if data.owners == nil then
240 | var = false
241 | elseif data.owners[user_id] then
242 | var = true
243 | end
244 | if _config.administrators[user_id] then
245 | var = true
246 | end
247 | if _config.sudo_users[user_id] then
248 | var = true
249 | end
250 | return var
251 | end
252 |
253 | -- Returns bot api properties (as getMe method)
254 | function api_getme(bot_api_key)
255 | local response = {}
256 | local getme = https.request{
257 | url = 'https://api.telegram.org/bot' .. bot_api_key .. '/getMe',
258 | method = "POST",
259 | sink = ltn12.sink.table(response),
260 | }
261 | local body = table.concat(response or {"no response"})
262 | local jbody = json:decode(body)
263 |
264 | if jbody.ok then
265 | botid = jbody.result
266 | else
267 | print('Error: ' .. jbody.error_code .. ', ' .. jbody.description)
268 | botid = {id = '', username = ''}
269 | end
270 |
271 | return botid
272 | end
273 |
274 | -- Returns the name of the sender
275 | function get_name(msg)
276 | local name = msg.from.first_name
277 | if name == nil then
278 | name = msg.from.peer_id
279 | end
280 | return name
281 | end
282 |
283 | -- Returns at table of lua files inside plugins
284 | function plugins_names()
285 | local files = {}
286 | for k, v in pairs(scandir('plugins')) do
287 | -- Ends with .lua
288 | if (v:match(".lua$")) then
289 | table.insert(files, v)
290 | end
291 | end
292 | return files
293 | end
294 |
295 | -- Function name explains what it does.
296 | function file_exists(name)
297 | local f = io.open(name,'r')
298 | if f ~= nil then
299 | io.close(f)
300 | return true
301 | else
302 | return false
303 | end
304 | end
305 |
306 | -- Save into file the data serialized for lua.
307 | -- Set uglify true to minify the file.
308 | function serialize_to_file(data, file, uglify)
309 | file = io.open(file, 'w+')
310 | local serialized
311 | if not uglify then
312 | serialized = serpent.block(data, {
313 | comment = false,
314 | name = '_'
315 | })
316 | else
317 | serialized = serpent.dump(data)
318 | end
319 | file:write(serialized)
320 | file:close()
321 | end
322 |
323 | -- Returns true if the string is empty
324 | function string:isempty()
325 | return self == nil or self == ''
326 | end
327 |
328 | -- Returns true if the string is blank
329 | function string:isblank()
330 | self = self:trim()
331 | return self:isempty()
332 | end
333 |
334 | -- Returns true if String starts with Start
335 | function string:starts(text)
336 | return text == self:sub(1, string.len(text))
337 | end
338 |
339 | -- which bot messages sent by
340 | function send_message(msg, text, markdown)
341 | if msg.from.api then
342 | if msg.reply_to_message then
343 | msg.id = msg.reply_to_message.message_id
344 | end
345 | bot_sendMessage(get_receiver_api(msg), text, true, msg.id, markdown)
346 | else
347 | if msg.reply_id then
348 | msg.id = msg.reply_id
349 | end
350 | -- this will strip all html tags
351 | local text = text:gsub('<.->', '')
352 | reply_msg(msg.id, text, ok_cb, true)
353 | end
354 | end
355 |
356 | -- Send image to user and delete it when finished.
357 | -- cb_function and cb_extra are optionals callback
358 | function _send_photo(receiver, file_path, cb_function, cb_extra)
359 | local cb_extra = {
360 | file_path = file_path,
361 | cb_function = cb_function,
362 | cb_extra = cb_extra
363 | }
364 | -- Call to remove with optional callback
365 | send_photo(receiver, file_path, cb_function, cb_extra)
366 | end
367 |
368 | -- Download the image and send to receiver, it will be deleted.
369 | -- cb_function and cb_extra are optionals callback
370 | function send_photo_from_url(receiver, url, cb_function, cb_extra)
371 | -- If callback not provided
372 | cb_function = cb_function or ok_cb
373 | cb_extra = cb_extra or false
374 |
375 | local file_path = download_to_file(url, false)
376 | if not file_path then -- Error
377 | local text = 'Error downloading the image'
378 | send_msg(receiver, text, cb_function, cb_extra)
379 | else
380 | print('File path: ' .. file_path)
381 | _send_photo(receiver, file_path, cb_function, cb_extra)
382 | end
383 | end
384 |
385 | -- Same as send_photo_from_url but as callback function
386 | function send_photo_from_url_callback(cb_extra, success, result)
387 | local receiver = cb_extra.receiver
388 | local url = cb_extra.url
389 |
390 | local file_path = download_to_file(url, false)
391 | if not file_path then -- Error
392 | local text = 'Error downloading the image'
393 | send_msg(receiver, text, ok_cb, false)
394 | else
395 | print('File path: ' .. file_path)
396 | _send_photo(receiver, file_path, ok_cb, false)
397 | end
398 | end
399 |
400 | -- Send multiple images asynchronous.
401 | -- param urls must be a table.
402 | function send_photos_from_url(receiver, urls)
403 | local cb_extra = {
404 | receiver = receiver,
405 | urls = urls,
406 | remove_path = nil
407 | }
408 | send_photos_from_url_callback(cb_extra)
409 | end
410 |
411 | -- Use send_photos_from_url.
412 | -- This function might be difficult to understand.
413 | function send_photos_from_url_callback(cb_extra, success, result)
414 | -- cb_extra is a table containing receiver, urls and remove_path
415 | local receiver = cb_extra.receiver
416 | local urls = cb_extra.urls
417 | local remove_path = cb_extra.remove_path
418 |
419 | -- The previously image to remove
420 | if remove_path ~= nil then
421 | os.remove(remove_path)
422 | print('Deleted: ' .. remove_path)
423 | end
424 |
425 | -- Nil or empty, exit case (no more urls)
426 | if urls == nil or #urls == 0 then
427 | return false
428 | end
429 |
430 | -- Take the head and remove from urls table
431 | local head = table.remove(urls, 1)
432 |
433 | local file_path = download_to_file(head, false)
434 | local cb_extra = {
435 | receiver = receiver,
436 | urls = urls,
437 | remove_path = file_path
438 | }
439 |
440 | -- Send first and postpone the others as callback
441 | send_photo(receiver, file_path, send_photos_from_url_callback, cb_extra)
442 | end
443 |
444 | -- Callback to remove a file
445 | function rmtmp_cb(cb_extra, success, result)
446 | local file_path = cb_extra.file_path
447 | local cb_function = cb_extra.cb_function or ok_cb
448 | local cb_extra = cb_extra.cb_extra
449 |
450 | if file_path ~= nil then
451 | os.remove(file_path)
452 | print('Deleted: ' .. file_path)
453 | end
454 | -- Finally call the callback
455 | cb_function(cb_extra, success, result)
456 | end
457 |
458 | -- Send document to user and delete it when finished.
459 | -- cb_function and cb_extra are optionals callback
460 | function _send_document(receiver, file_path, cb_function, cb_extra)
461 | local cb_extra = {
462 | file_path = file_path,
463 | cb_function = cb_function or ok_cb,
464 | cb_extra = cb_extra or false
465 | }
466 | -- Call to remove with optional callback
467 | send_document(receiver, file_path, rmtmp_cb, cb_extra)
468 | end
469 |
470 | -- Download the image and send to receiver, it will be deleted.
471 | -- cb_function and cb_extra are optionals callback
472 | function send_document_from_url(receiver, url, cb_function, cb_extra)
473 | local file_path = download_to_file(url, false)
474 | print('File path: ' .. file_path)
475 | _send_document(receiver, file_path, cb_function, cb_extra)
476 | end
477 |
478 | -- Parameters in ?a=1&b=2 style
479 | function format_http_params(params, is_get)
480 | local str = ''
481 | -- If is get add ? to the beginning
482 | if is_get then str = '?' end
483 | local first = true -- Frist param
484 | for k,v in pairs (params) do
485 | if v then -- nil value
486 | if first then
487 | first = false
488 | str = str .. k .. '=' .. v
489 | else
490 | str = str .. '&' .. k .. '=' .. v
491 | end
492 | end
493 | end
494 | return str
495 | end
496 |
497 | -- Check if user can use the plugin and warns user
498 | -- Returns true if user was warned and false if not warned (is allowed)
499 | function warns_user_not_allowed(plugin, msg)
500 | if not user_allowed(plugin, msg) then
501 | local text = 'This plugin requires privileged user'
502 | reply_msg(msg.id, text, ok_cb, true)
503 | return true
504 | else
505 | return false
506 | end
507 | end
508 |
509 | -- Check if user can use the plugin
510 | function user_allowed(plugin, msg)
511 | if plugin.moderated and not is_mod(msg, msg.to.peer_id, msg.from.peer_id) then
512 | if plugin.moderated and not is_owner(msg, msg.to.peer_id, msg.from.peer_id) then
513 | if plugin.moderated and not is_admin(msg.from.peer_id) then
514 | if plugin.moderated and not is_sudo(msg.from.peer_id) then
515 | return false
516 | end
517 | end
518 | end
519 | end
520 | -- If plugins privileged = true
521 | if plugin.privileged and not is_sudo(msg.from.peer_id) then
522 | return false
523 | end
524 | return true
525 | end
526 |
527 | function send_order_msg(destination, msgs)
528 | local cb_extra = {
529 | destination = destination,
530 | msgs = msgs
531 | }
532 | send_order_msg_callback(cb_extra, true)
533 | end
534 |
535 | function send_order_msg_callback(cb_extra, success, result)
536 | local destination = cb_extra.destination
537 | local msgs = cb_extra.msgs
538 | local file_path = cb_extra.file_path
539 | if file_path ~= nil then
540 | os.remove(file_path)
541 | print('Deleted: ' .. file_path)
542 | end
543 | if type(msgs) == 'string' then
544 | send_large_msg(destination, msgs)
545 | elseif type(msgs) ~= 'table' then
546 | return
547 | end
548 | if #msgs < 1 then
549 | return
550 | end
551 | local msg = table.remove(msgs, 1)
552 | local new_cb_extra = {
553 | destination = destination,
554 | msgs = msgs
555 | }
556 | if type(msg) == 'string' then
557 | send_msg(destination, msg, send_order_msg_callback, new_cb_extra)
558 | elseif type(msg) == 'table' then
559 | local typ = msg[1]
560 | local nmsg = msg[2]
561 | new_cb_extra.file_path = nmsg
562 | if typ == 'document' then
563 | send_document(destination, nmsg, send_order_msg_callback, new_cb_extra)
564 | elseif typ == 'image' or typ == 'photo' then
565 | send_photo(destination, nmsg, send_order_msg_callback, new_cb_extra)
566 | elseif typ == 'audio' then
567 | send_audio(destination, nmsg, send_order_msg_callback, new_cb_extra)
568 | elseif typ == 'video' then
569 | send_video(destination, nmsg, send_order_msg_callback, new_cb_extra)
570 | else
571 | send_file(destination, nmsg, send_order_msg_callback, new_cb_extra)
572 | end
573 | end
574 | end
575 |
576 | -- Same as send_large_msg_callback but friendly params
577 | function send_large_msg(destination, text)
578 | local cb_extra = {
579 | destination = destination,
580 | text = text
581 | }
582 | send_large_msg_callback(cb_extra, true)
583 | end
584 |
585 | -- If text is longer than 4096 chars, send multiple msg.
586 | -- https://core.telegram.org/method/messages.sendMessage
587 | function send_large_msg_callback(cb_extra, success, result)
588 | local text_max = 4096
589 |
590 | local destination = cb_extra.destination
591 | local text = cb_extra.text
592 | local text_len = string.len(text)
593 | local num_msg = math.ceil(text_len / text_max)
594 |
595 | if num_msg <= 1 then
596 | send_msg(destination, text, ok_cb, false)
597 | else
598 |
599 | local my_text = text:sub(1, 4096)
600 | local rest = text:sub(4096, text_len)
601 |
602 | local cb_extra = {
603 | destination = destination,
604 | text = rest
605 | }
606 |
607 | send_msg(destination, my_text, send_large_msg_callback, cb_extra)
608 | end
609 | end
610 |
611 | -- Returns a table with matches or nil
612 | function match_pattern(pattern, text, lower_case)
613 | if text then
614 | local matches = {}
615 | if lower_case then
616 | matches = { string.match(text:lower(), pattern) }
617 | else
618 | matches = { string.match(text, pattern) }
619 | end
620 | if next(matches) then
621 | return matches
622 | end
623 | end
624 | -- nil
625 | end
626 |
627 | -- Function to read data from files
628 | function load_from_file(file, default_data)
629 | local f = io.open(file, 'r+')
630 | -- If file doesn't exists
631 | if f == nil then
632 | -- Create a new empty table
633 | default_data = default_data or {}
634 | serialize_to_file(default_data, file)
635 | print ('Created file', file)
636 | else
637 | print ('Data loaded from file', file)
638 | f:close()
639 | end
640 | return loadfile (file)()
641 | end
642 |
643 | -- See http://stackoverflow.com/a/14899740
644 | function unescape_html(str)
645 | local map = {
646 | ["lt"] = "<",
647 | ["gt"] = ">",
648 | ["amp"] = "&",
649 | ["quot"] = '"',
650 | ["apos"] = "'"
651 | }
652 | new = str:gsub('(&(#?x?)([%d%a]+);)', function(orig, n, s)
653 | var = map[s] or n == "#" and string.char(s)
654 | var = var or n == "#x" and string.char(tonumber(s,16))
655 | var = var or orig
656 | return var
657 | end)
658 | return new
659 | end
660 |
661 | function markdown_escape(text)
662 | text = text:gsub('_', '\\_')
663 | text = text:gsub('%[', '\\[')
664 | text = text:gsub('%]', '\\]')
665 | text = text:gsub('%*', '\\*')
666 | text = text:gsub('`', '\\`')
667 | return text
668 | end
669 |
670 | function group_into_three(number)
671 | while true do
672 | number, k = string.gsub(number, "^(-?%d+)(%d%d%d)", '%1.%2')
673 |
674 | if (k==0) then
675 | break
676 | end
677 | end
678 | return number
679 | end
680 |
681 | function pairsByKeys(t, f)
682 | local a = {}
683 | for n in pairs(t) do
684 | a[#a+1] = n
685 | end
686 | table.sort(a, f)
687 | local i = 0 -- iterator variable
688 | local iter = function () -- iterator function
689 | i = i + 1
690 | if a[i] == nil then
691 | return nil
692 | else
693 | return a[i], t[a[i]]
694 | end
695 | end
696 | return iter
697 | end
698 |
699 | -- Gets coordinates for a location.
700 | function get_coords(msg, input)
701 | local url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input)
702 |
703 | local jstr, res = http.request(url)
704 | if res ~= 200 then
705 | reply_msg(msg.id, 'Connection error.', ok_cb, true)
706 | return
707 | end
708 |
709 | local jdat = json:decode(jstr)
710 | if jdat.status == 'ZERO_RESULTS' then
711 | reply_msg(msg.id, 'ZERO_RESULTS', ok_cb, true)
712 | return
713 | end
714 |
715 | return {
716 | lat = jdat.results[1].geometry.location.lat,
717 | lon = jdat.results[1].geometry.location.lng,
718 | formatted_address = jdat.results[1].formatted_address
719 | }
720 | end
721 |
722 | -- Text formatting is server side. And (until now) only for API bots.
723 | -- So, here is a simple workaround; send message through Telegram official API.
724 | -- You need to provide your API bots TOKEN in config.lua.
725 | local function bot(method, parameters, file)
726 | local parameters = parameters or {}
727 | for k,v in pairs(parameters) do
728 | parameters[k] = tostring(v)
729 | end
730 | if file and next(file) ~= nil then
731 | local file_type, file_name = next(file)
732 | local file_file = io.open(file_name, 'r')
733 | local file_data = {
734 | filename = file_name,
735 | data = file_file:read('*a')
736 | }
737 | file_file:close()
738 | parameters[file_type] = file_data
739 | end
740 | if next(parameters) == nil then
741 | parameters = {''}
742 | end
743 |
744 | if parameters.reply_to_message_id and #parameters.reply_to_message_id > 30 then
745 | parameters.reply_to_message_id = nil
746 | end
747 |
748 | local response = {}
749 | local body, boundary = multipart.encode(parameters)
750 | local success = https.request{
751 | url = 'https://api.telegram.org/bot' .. _config.bot_api.key .. '/' .. method,
752 | method = 'POST',
753 | headers = {
754 | ["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
755 | ["Content-Length"] = #body,
756 | },
757 | source = ltn12.source.string(body),
758 | sink = ltn12.sink.table(response)
759 | }
760 | local data = table.concat(response)
761 | local jdata = json:decode(data)
762 | if not jdata.ok then
763 | vardump(jdata)
764 | end
765 | end
766 |
767 | function bot_sendMessage(chat_id, text, disable_web_page_preview, reply_to_message_id, parse_mode)
768 | return bot('sendMessage', {
769 | chat_id = chat_id,
770 | text = text,
771 | disable_web_page_preview = disable_web_page_preview,
772 | reply_to_message_id = reply_to_message_id,
773 | parse_mode = parse_mode or nil
774 | } )
775 | end
776 |
777 | function bot_sendPhoto(chat_id, photo, caption, disable_notification, reply_to_message_id)
778 | return bot('sendPhoto', {
779 | chat_id = chat_id,
780 | caption = caption,
781 | disable_notification = disable_notification,
782 | reply_to_message_id = reply_to_message_id,
783 | }, {photo = photo} )
784 | end
785 |
786 | function bot_sendLocation(chat_id, latitude, longitude, disable_notification, reply_to_message_id)
787 | return bot('sendLocation', {
788 | chat_id = chat_id,
789 | latitude = latitude,
790 | longitude = longitude,
791 | disable_notification = disable_notification,
792 | reply_to_message_id = reply_to_message_id,
793 | })
794 | end
795 |
796 | function bot_sendDocument(chat_id, document, caption, disable_notification, reply_to_message_id)
797 | return bot('sendDocument', {
798 | chat_id = chat_id,
799 | document = document,
800 | caption = caption,
801 | disable_notification = disable_notification,
802 | reply_to_message_id = reply_to_message_id,
803 | }, {document = document})
804 | end
805 |
--------------------------------------------------------------------------------
/botapi.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | As for now, telegram-cli have unresolved bug that it's unable to read messages
3 | in supergroup with 200+ members.
4 |
5 | I cannot C, hence this shitty workaround.
6 | This is an api bot script that relaying all command messages to telegram-cli
7 | bot account.
8 |
9 | Run this script in separate tmux/multiplexer window.
10 | --]]
11 |
12 | package.path = package.path .. ';.luarocks/share/lua/5.2/?.lua'
13 | .. ';.luarocks/share/lua/5.2/?/init.lua'
14 | package.cpath = package.cpath .. ';.luarocks/lib/lua/5.2/?.so'
15 |
16 | local https = require "ssl.https"
17 | local serpent = require "serpent"
18 | local json = (loadfile "./libs/JSON.lua")()
19 |
20 | local config = (loadfile './data/config.lua')()
21 | local url = 'https://api.telegram.org/bot' .. config.bot_api.key
22 | local offset = 0
23 |
24 | local function getUpdates()
25 | local response = {}
26 | local success, code, headers, status = https.request{
27 | url = url .. '/getUpdates?timeout=20&limit=1&offset=' .. offset,
28 | method = "POST",
29 | sink = ltn12.sink.table(response),
30 | }
31 |
32 | local body = table.concat(response or {"no response"})
33 | if (success == 1) then
34 | return json:decode(body)
35 | else
36 | return nil, "Request Error"
37 | end
38 | end
39 |
40 | function vardump(value)
41 | print(serpent.block(value, {comment=false}))
42 | end
43 |
44 | local function run()
45 | while true do
46 | local updates = getUpdates()
47 | vardump(updates)
48 | if(updates) then
49 | if (updates.result) then
50 | for i=1, #updates.result do
51 | local msg = updates.result[i]
52 | offset = msg.update_id + 1
53 | print '=================================================='
54 | vardump(msg)
55 | if msg.message and msg.message.date > (os.time() - 5) then
56 | if msg.message.text and msg.message.text:match('^[!/]') then
57 | https.request{
58 | url = url .. '/sendMessage?chat_id=' .. config.bot_api.master .. '&text=' .. serpent.dump(msg),
59 | method = "POST",
60 | }
61 | end
62 | end
63 | end
64 | end
65 | end
66 | end
67 | end
68 |
69 | return run()
--------------------------------------------------------------------------------
/data/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rizaumami/merbot/5f9b7ffaf57d7680accc2ae795bb388705a44364/data/.gitkeep
--------------------------------------------------------------------------------
/libs/hookio.js:
--------------------------------------------------------------------------------
1 | // https://unnikked.ga/build-telegram-bot-hook-io/
2 |
3 | module['exports'] = function relayBot (hook) {
4 | var request = require('request');
5 | var msg = hook.params.message;
6 | var TOKEN = hook.env.merbot_key; // change env to reflect your bot
7 | var MASTER = hook.env.mimin_teh_bot; // change env to your tg-cli bots id
8 | var closetag = '}};return _;end';
9 | var textmessage = msg.text;
10 |
11 | if (textmessage.match(/^!.*/)) {
12 | var fmessage = 'do local _={message={chat={title="' + msg.chat.title +
13 | '",id=' + msg.chat.id +
14 | ',type="' + msg.chat.type +
15 | '",username="' + msg.chat.username +
16 | '",first_name="' + msg.chat.first_name +
17 | '"},from={first_name="' + msg.from.first_name +
18 | '",id=' + msg.from.id +
19 | ',username="' + msg.from.username +
20 | '"},date=' + msg.date +
21 | ',text="' + msg.text +
22 | '",message_id=' + msg.message_id;
23 |
24 | if (msg.new_chat_title) {
25 | var fmessage = fmessage + ',new_chat_title="' + msg.new_chat_title + '"'
26 | }
27 |
28 | if (msg.new_chat_participant) {
29 | var fmessage = fmessage + ',new_chat_participant={first_name="' +
30 | msg.new_chat_participant.first_name +
31 | '",id=' + msg.new_chat_participant.id +
32 | ',username="' + msg.new_chat_participant.username + '"}';
33 | }
34 |
35 | if (msg.left_chat_participant) {
36 | var fmessage = fmessage + ',left_chat_participan={first_name="' +
37 | msg.left_chat_participant.first_name +
38 | '",id=' + msg.left_chat_participant.id +
39 | ', username="' + msg.left_chat_participant.username + '"}';
40 | }
41 |
42 | if (msg.new_chat_photo) {
43 | var fmessage = fmessage + ',new_chat_photo={}'
44 | }
45 |
46 | if (msg.delete_chat_photo) {
47 | var fmessage = fmessage + ',delete_chat_photo = true'
48 | }
49 |
50 | if (msg.reply_to_message) {
51 | var reply = msg.reply_to_message
52 | var fmessage = fmessage +
53 | ',reply_to_message={chat={id=' + reply.chat.id +
54 | ',title="' + reply.chat.title +
55 | '",type="' + reply.chat.type +
56 | '",username="' + reply.chat.username +
57 | '"},date=' + reply.date +
58 | ',from={first_name="' + reply.from.first_name +
59 | '",id=' + reply.from.id +
60 | ',type=' + reply.from.type +
61 | ',username="' + reply.from.username +
62 | '"},message_id=' + reply.message_id +
63 | ',text="' + reply.text + '"},';
64 | }
65 |
66 | request
67 | .post('https://api.telegram.org/bot' + TOKEN + '/sendMessage')
68 | .form({
69 | "chat_id": MASTER,
70 | "text": fmessage + closetag,
71 | });
72 | };
73 | };
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/merbot:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #set -euo
4 |
5 | THIS_DIR=$(cd "$(dirname "$0")"; pwd)
6 | cd "$THIS_DIR"
7 |
8 | update() {
9 | git pull
10 | git submodule update --init --recursive
11 | install_rocks
12 | }
13 |
14 | # Will install luarocks on THIS_DIR/.luarocks
15 | install_luarocks() {
16 | git clone https://github.com/keplerproject/luarocks.git
17 | cd luarocks
18 | git checkout tags/v2.4.0 # Current stable
19 |
20 | PREFIX="$THIS_DIR/.luarocks"
21 |
22 | ./configure --prefix="$PREFIX" --sysconfdir="$PREFIX"/luarocks --force-config
23 |
24 | RET=$?
25 | if [ $RET -ne 0 ]; then
26 | printf '\e[1;31m%s\n\e[0;39;49m' 'Error. Exiting.'
27 | exit $RET
28 | fi
29 |
30 | make build && make install
31 | RET=$?
32 | if [ $RET -ne 0 ]; then
33 | printf '\e[1;31m%s\n\e[0;39;49m' 'Error. Exiting.'
34 | exit $RET
35 | fi
36 |
37 | cd ..
38 | rm -rf luarocks
39 | }
40 |
41 | install_rocks() {
42 | ./.luarocks/bin/luarocks install lbase64 20120807-3
43 | RET=$?
44 | if [ $RET -ne 0 ]; then
45 | printf '\e[1;31m%s\n\e[0;39;49m' 'Error. Exiting.'
46 | exit $RET
47 | fi
48 | for i in luasec luasocket oauth redis-lua lua-cjson fakeredis xml feedparser serpent multipart-post; do
49 | ./.luarocks/bin/luarocks install "$i"
50 | RET=$?
51 | if [ $RET -ne 0 ]; then
52 | printf '\e[1;31m%s\n\e[0;39;49m' 'Error. Exiting.'
53 | exit $RET
54 | fi
55 | done
56 | }
57 |
58 | merbot_upstart() {
59 | printf '%s\n' "
60 | description 'Merbots upstart script.'
61 |
62 | respawn
63 | respawn limit 15 5
64 |
65 | start on runlevel [2345]
66 | stop on shutdown
67 |
68 | setuid $(whoami)
69 | exec /bin/sh $(pwd)/merbot
70 | " | sudo tee /etc/init/merbot.conf > /dev/null
71 |
72 | [[ -f /etc/init/merbot.conf ]] && printf '%s\n' '
73 |
74 | Upstart script installed to /etc/init/merbot.conf.
75 | Now you can:
76 | Start merbot with : sudo start merbot
77 | See merbot status with: sudo status merbot
78 | Stop merbot with : sudo stop merbot
79 |
80 | '
81 | }
82 |
83 | merbotapi_upstart() {
84 | printf '%s\n' "
85 | description 'Merbot APIs upstart script.'
86 |
87 | respawn
88 | respawn limit 15 5
89 |
90 | start on runlevel [2345]
91 | stop on shutdown
92 |
93 | setuid $(whoami)
94 | exec /usr/bin/lua $(pwd)/botapi.lua
95 | " | sudo tee /etc/init/merbotapi.conf > /dev/null
96 |
97 | [[ -f /etc/init/merbotapi.conf ]] && printf '%s\n' '
98 |
99 | Upstart script installed to /etc/init/merbotapi.conf.
100 | Now you can:
101 | Start merbot api with : sudo start merbotapi
102 | See merbot api status with: sudo status merbotapi
103 | Stop merbot api with : sudo stop merbotapi
104 |
105 | '
106 | }
107 |
108 | tgcli_config() {
109 | mkdir -p "$THIS_DIR"/.telegram-cli
110 | printf '%s\n' "
111 | default_profile = \"default\";
112 |
113 | default = {
114 | config_directory = \"$THIS_DIR/.telegram-cli\";
115 | auth_file = \"$THIS_DIR/.telegram-cli/auth\";
116 | test = false;
117 | msg_num = true;
118 | log_level = 2;
119 | };
120 | " > "$THIS_DIR"/data/tg-cli.config
121 | }
122 |
123 | install() {
124 | git pull
125 | git submodule update --init --recursive
126 | patch -i 'patches/merbot.patch' -p 0 --batch --forward
127 | RET=$?;
128 |
129 | cd tg
130 | if [ $RET -ne 0 ]; then
131 | autoconf -i
132 | fi
133 | ./configure && make
134 |
135 | RET=$?
136 | if [ $RET -ne 0 ]; then
137 | printf '\e[1;31m%s\n\e[0;39;49m' 'Error. Exiting.'
138 | exit $RET
139 | fi
140 | cd ..
141 | install_luarocks
142 | install_rocks
143 | tgcli_config
144 | }
145 |
146 | if [[ "$1" = "install" ]]; then
147 | install
148 | elif [[ "$1" = "update" ]]; then
149 | update
150 | elif [[ "$1" = "upstart" ]]; then
151 | merbot_upstart
152 | merbotapi_upstart
153 | else
154 | if [[ ! -f ./tg/telegram.h ]]; then
155 | printf '\e[1;31m%s\n\e[0;39;49m' ' tg not found' " Run $0 install"
156 | exit 1
157 | fi
158 |
159 | if [[ ! -f ./tg/bin/telegram-cli ]]; then
160 | printf '\e[1;31m%s\n\e[0;39;49m' ' tg binary not found' " Run $0 install"
161 | exit 1
162 | fi
163 | tgcli_config
164 | rm ./telegram-cli/state
165 | ./tg/bin/telegram-cli -k ./tg/tg-server-pub --disable-link-preview -s ./bot/bot.lua -l 1 -E -c ./data/tg-cli.config -p default "$@"
166 | fi
167 |
--------------------------------------------------------------------------------
/patches/merbot.patch:
--------------------------------------------------------------------------------
1 | --- tg/lua-tg.c 2016-04-21 23:15:20.657750668 +0700
2 | +++ /home/iza/lua-tg.c 2016-04-21 23:15:02.649847315 +0700
3 | @@ -161,6 +161,7 @@
4 | my_lua_checkstack (luaState, 4);
5 | lua_add_string_field ("title", P->channel.title);
6 | lua_add_string_field ("about", P->channel.about);
7 | + lua_add_string_field ("username", P->channel.username);
8 | lua_add_num_field ("participants_count", P->channel.participants_count);
9 | lua_add_num_field ("admins_count", P->channel.admins_count);
10 | lua_add_num_field ("kicked_count", P->channel.kicked_count);
11 | @@ -288,11 +289,24 @@
12 | lua_add_string_field ("caption", M->caption);
13 | break;
14 | case tgl_message_media_document:
15 | + lua_newtable (luaState);
16 | + lua_add_string_field ("type", "document");
17 | + lua_add_string_field ("caption", M->document->caption);
18 | + break;
19 | case tgl_message_media_audio:
20 | + lua_newtable (luaState);
21 | + lua_add_string_field ("type", "audio");
22 | + lua_add_string_field ("caption", M->caption);
23 | + break;
24 | case tgl_message_media_video:
25 | + lua_newtable (luaState);
26 | + lua_add_string_field ("type", "video");
27 | + lua_add_string_field ("caption", M->caption);
28 | + break;
29 | case tgl_message_media_document_encr:
30 | lua_newtable (luaState);
31 | - lua_add_string_field ("type", "document");
32 | + lua_add_string_field ("type", "encr_document");
33 | + lua_add_string_field ("caption", M->document->caption);
34 | break;
35 | case tgl_message_media_unsupported:
36 | lua_newtable (luaState);
37 | @@ -680,6 +694,12 @@
38 | lq_send_video,
39 | lq_send_text,
40 | lq_reply,
41 | + lq_reply_audio,
42 | + lq_reply_document,
43 | + lq_reply_file,
44 | + lq_reply_location,
45 | + lq_reply_photo,
46 | + lq_reply_video,
47 | lq_fwd,
48 | lq_fwd_media,
49 | lq_load_photo,
50 | @@ -712,13 +732,37 @@
51 | lq_status_online,
52 | lq_status_offline,
53 | lq_send_location,
54 | + lq_post_location,
55 | lq_extf,
56 | lq_import_chat_link,
57 | lq_export_chat_link,
58 | lq_channel_invite_user,
59 | lq_channel_kick_user,
60 | lq_channel_get_admins,
61 | - lq_channel_get_users
62 | + lq_channel_get_users,
63 | + lq_block_user,
64 | + lq_unblock_user,
65 | + lq_channel_set_username,
66 | + lq_rename_channel,
67 | + lq_export_channel_link,
68 | + lq_channel_set_about,
69 | + lq_channel_set_admin,
70 | + lq_channel_set_mod,
71 | + lq_channel_del_admin,
72 | + lq_channel_set_photo,
73 | + lq_chat_upgrade,
74 | + lq_contact_search,
75 | + lq_create_channel,
76 | + lq_get_message,
77 | + lq_channel_join,
78 | + lq_channel_leave,
79 | + lq_channel_list,
80 | + lq_post_audio,
81 | + lq_post_document,
82 | + lq_post_file,
83 | + lq_post_photo,
84 | + lq_post_text,
85 | + lq_post_video,
86 | };
87 |
88 | struct lua_query_extra {
89 | @@ -1138,6 +1182,38 @@
90 | free (cb);
91 | }
92 |
93 | +void lua_contact_search_cb (struct tgl_state *TLSR, void *cb_extra, int success, tgl_peer_t *C) {
94 | + assert (TLSR == TLS);
95 | + struct lua_query_extra *cb = cb_extra;
96 | + lua_settop (luaState, 0);
97 | + //lua_checkstack (luaState, 20);
98 | + my_lua_checkstack (luaState, 20);
99 | +
100 | + lua_rawgeti (luaState, LUA_REGISTRYINDEX, cb->func);
101 | + lua_rawgeti (luaState, LUA_REGISTRYINDEX, cb->param);
102 | +
103 | + lua_pushnumber (luaState, success);
104 | +
105 | + if (success) {
106 | + push_peer (C->id, (void *)C);
107 | + } else {
108 | + lua_pushboolean (luaState, 0);
109 | + }
110 | +
111 | + assert (lua_gettop (luaState) == 4);
112 | +
113 | + int r = ps_lua_pcall (luaState, 3, 0, 0);
114 | +
115 | + luaL_unref (luaState, LUA_REGISTRYINDEX, cb->func);
116 | + luaL_unref (luaState, LUA_REGISTRYINDEX, cb->param);
117 | +
118 | + if (r) {
119 | + logprintf ("lua: %s\n", lua_tostring (luaState, -1));
120 | + }
121 | +
122 | + free (cb);
123 | +}
124 | +
125 | #define LUA_STR_ARG(n) lua_ptr[n].str, strlen (lua_ptr[n].str)
126 |
127 | void lua_do_all (void) {
128 | @@ -1199,6 +1275,30 @@
129 | case lq_send_text:
130 | tgl_do_send_text (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, 0, lua_msg_cb, lua_ptr[p].ptr);
131 | p += 3;
132 | + break;
133 | + case lq_post_audio:
134 | + tgl_do_send_document (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_AUDIO | TGLMF_POST_AS_CHANNEL, lua_msg_cb, lua_ptr[p].ptr);
135 | + p += 3;
136 | + break;
137 | + case lq_post_document:
138 | + tgl_do_send_document (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, NULL, 0, TGLMF_POST_AS_CHANNEL, lua_msg_cb, lua_ptr[p].ptr);
139 | + p += 3;
140 | + break;
141 | + case lq_post_file:
142 | + tgl_do_send_document (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_AUTO | TGLMF_POST_AS_CHANNEL, lua_msg_cb, lua_ptr[p].ptr);
143 | + p += 3;
144 | + break;
145 | + case lq_post_photo:
146 | + tgl_do_send_document (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_PHOTO | TGLMF_POST_AS_CHANNEL, lua_msg_cb, lua_ptr[p].ptr);
147 | + p += 3;
148 | + break;
149 | + case lq_post_text:
150 | + tgl_do_send_text (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, 256, lua_msg_cb, lua_ptr[p].ptr);
151 | + p += 3;
152 | + break;
153 | + case lq_post_video:
154 | + tgl_do_send_document (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_VIDEO | TGLMF_POST_AS_CHANNEL, lua_msg_cb, lua_ptr[p].ptr);
155 | + p += 3;
156 | break;
157 | case lq_chat_set_photo:
158 | tgl_do_set_chat_photo (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, lua_empty_cb, lua_ptr[p].ptr);
159 | @@ -1238,6 +1338,30 @@
160 | tgl_do_reply_message (TLS, &lua_ptr[p + 1].msg_id, LUA_STR_ARG (p + 2), 0, lua_msg_cb, lua_ptr[p].ptr);
161 | p += 3;
162 | break;
163 | + case lq_reply_audio:
164 | + tgl_do_reply_document (TLS, &lua_ptr[p + 1].msg_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_AUDIO, lua_msg_cb, lua_ptr[p].ptr);
165 | + p += 3;
166 | + break;
167 | + case lq_reply_document:
168 | + tgl_do_reply_document (TLS, &lua_ptr[p + 1].msg_id, lua_ptr[p + 2].str, NULL, 0, 0, lua_msg_cb, lua_ptr[p].ptr);
169 | + p += 3;
170 | + break;
171 | + case lq_reply_file:
172 | + tgl_do_reply_document (TLS, &lua_ptr[p + 1].msg_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_AUTO, lua_msg_cb, lua_ptr[p].ptr);
173 | + p += 3;
174 | + break;
175 | + case lq_reply_location: // TODO - I DON'T UNDERSTAND WHY IT'S NOT WORKING
176 | + tgl_do_reply_location (TLS, &lua_ptr[p + 1].msg_id, lua_ptr[p + 2].dnum, lua_ptr[p + 3].dnum, 0, lua_msg_cb, lua_ptr[p].ptr);
177 | + p += 4;
178 | + break;
179 | + case lq_reply_photo:
180 | + tgl_do_reply_document (TLS, &lua_ptr[p + 1].msg_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_PHOTO, lua_msg_cb, lua_ptr[p].ptr);
181 | + p += 3;
182 | + break;
183 | + case lq_reply_video:
184 | + tgl_do_reply_document (TLS, &lua_ptr[p + 1].msg_id, lua_ptr[p + 2].str, NULL, 0, TGL_SEND_MSG_FLAG_DOCUMENT_VIDEO, lua_msg_cb, lua_ptr[p].ptr);
185 | + p += 3;
186 | + break;
187 | case lq_fwd:
188 | tmp_msg_id = &lua_ptr[p + 2].msg_id;
189 | tgl_do_forward_messages (TLS, lua_ptr[p + 1].peer_id, 1, (void *)&tmp_msg_id, 0, lua_one_msg_cb, lua_ptr[p].ptr);
190 | @@ -1347,6 +1471,10 @@
191 | tgl_do_send_location (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].dnum, lua_ptr[p + 3].dnum, 0, lua_msg_cb, lua_ptr[p].ptr);
192 | p += 4;
193 | break;
194 | + case lq_post_location:
195 | + tgl_do_send_location (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].dnum, lua_ptr[p + 3].dnum, 256, lua_msg_cb, lua_ptr[p].ptr);
196 | + p += 4;
197 | + break;
198 | case lq_channel_invite_user:
199 | tgl_do_channel_invite_user (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].peer_id, lua_empty_cb, lua_ptr[p].ptr);
200 | p += 3;
201 | @@ -1363,6 +1491,73 @@
202 | tgl_do_channel_get_members (TLS, lua_ptr[p + 1].peer_id, 100, 0, 0, lua_contact_list_cb, lua_ptr[p].ptr);
203 | p += 2;
204 | break;
205 | + case lq_block_user:
206 | + tgl_do_block_user (TLS, lua_ptr[p + 1].peer_id, lua_empty_cb, lua_ptr[p].ptr);
207 | + p += 2;
208 | + break;
209 | + case lq_unblock_user:
210 | + tgl_do_unblock_user (TLS, lua_ptr[p + 1].peer_id, lua_empty_cb, lua_ptr[p].ptr);
211 | + p += 2;
212 | + break;
213 | + case lq_channel_set_username:
214 | + tgl_do_channel_set_username (TLS, lua_ptr[p + 1].peer_id, LUA_STR_ARG (p + 2), lua_empty_cb, lua_ptr[p].ptr);
215 | + p += 3;
216 | + break;
217 | + case lq_rename_channel:
218 | + tgl_do_rename_channel (TLS, lua_ptr[p + 1].peer_id, LUA_STR_ARG (p + 2), lua_empty_cb, lua_ptr[p].ptr);
219 | + p += 3;
220 | + break;
221 | + case lq_contact_search:
222 | + tgl_do_contact_search (TLS, LUA_STR_ARG (p + 1), lua_contact_search_cb, lua_ptr[p].ptr);
223 | + p += 2;
224 | + break;
225 | + case lq_create_channel:
226 | + tgl_do_create_channel (TLS, 1, &lua_ptr[p + 1].peer_id, LUA_STR_ARG (p + 2), LUA_STR_ARG (p + 3), 1,lua_empty_cb, lua_ptr[p].ptr);
227 | + p += 4;
228 | + break;
229 | + case lq_get_message:
230 | + tgl_do_get_message (TLS, &lua_ptr[p + 1].msg_id, lua_msg_cb, lua_ptr[p].ptr);
231 | + p += 2;
232 | + break;
233 | + case lq_channel_set_about:
234 | + tgl_do_channel_set_about (TLS, lua_ptr[p + 1].peer_id, LUA_STR_ARG (p + 2), lua_empty_cb, lua_ptr[p].ptr);
235 | + p += 3;
236 | + break;
237 | + case lq_channel_set_admin:
238 | + tgl_do_channel_set_admin (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].peer_id, 2, lua_empty_cb, lua_ptr[p].ptr);
239 | + p += 3;
240 | + break;
241 | + case lq_channel_set_mod:
242 | + tgl_do_channel_set_admin (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].peer_id, 1, lua_empty_cb, lua_ptr[p].ptr);
243 | + p += 3;
244 | + break;
245 | + case lq_channel_del_admin:
246 | + tgl_do_channel_set_admin (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].peer_id, 0, lua_empty_cb, lua_ptr[p].ptr);
247 | + p += 3;
248 | + break;
249 | + case lq_channel_set_photo:
250 | + tgl_do_set_channel_photo (TLS, lua_ptr[p + 1].peer_id, lua_ptr[p + 2].str, lua_empty_cb, lua_ptr[p].ptr);
251 | + p += 3;
252 | + break;
253 | + case lq_chat_upgrade:
254 | + tgl_do_upgrade_group (TLS, lua_ptr[p + 1].peer_id, lua_empty_cb, lua_ptr[p].ptr);
255 | + p += 2;
256 | + break;
257 | + case lq_export_channel_link:
258 | + tgl_do_export_channel_link (TLS, lua_ptr[p + 1].peer_id, lua_str_cb, lua_ptr[p].ptr);
259 | + p += 2;
260 | + break;
261 | + case lq_channel_list:
262 | + tgl_do_get_channels_dialog_list (TLS, 100, 0, lua_dialog_list_cb, lua_ptr[p ++].ptr);
263 | + break;
264 | + case lq_channel_join:
265 | + tgl_do_join_channel (TLS, lua_ptr[p + 1].peer_id, lua_empty_cb, lua_ptr[p].ptr);
266 | + p += 2;
267 | + break;
268 | + case lq_channel_leave:
269 | + tgl_do_leave_channel (TLS, lua_ptr[p + 1].peer_id, lua_empty_cb, lua_ptr[p].ptr);
270 | + p += 2;
271 | + break;
272 | /*
273 | lq_delete_msg,
274 | lq_restore_msg,
275 | @@ -1436,6 +1631,12 @@
276 | {"load_document", lq_load_document, { lfp_msg, lfp_none }},
277 | {"load_document_thumb", lq_load_document_thumb, { lfp_msg, lfp_none }},
278 | {"reply_msg", lq_reply, { lfp_msg, lfp_string, lfp_none }},
279 | + {"reply_file", lq_reply_file, {lfp_msg, lfp_string, lfp_none}},
280 | + {"reply_audio", lq_send_audio, {lfp_msg, lfp_string, lfp_none}},
281 | + {"reply_location", lq_reply_location, { lfp_msg, lfp_double, lfp_double, lfp_none }},
282 | + {"reply_document", lq_reply_document, {lfp_msg, lfp_string, lfp_none}},
283 | + {"reply_photo", lq_reply_photo, {lfp_msg, lfp_string, lfp_none}},
284 | + {"reply_video", lq_reply_video, {lfp_msg, lfp_string, lfp_none}},
285 | {"fwd_msg", lq_fwd, { lfp_peer, lfp_msg, lfp_none }},
286 | {"fwd_media", lq_fwd_media, { lfp_peer, lfp_msg, lfp_none }},
287 | {"chat_info", lq_chat_info, { lfp_chat, lfp_none }},
288 | @@ -1460,7 +1661,8 @@
289 | {"send_contact", lq_send_contact, { lfp_peer, lfp_string, lfp_string, lfp_string, lfp_none }},
290 | {"status_online", lq_status_online, { lfp_none }},
291 | {"status_offline", lq_status_offline, { lfp_none }},
292 | - {"send_location", lq_send_location, { lfp_peer, lfp_double, lfp_double, lfp_none }},
293 | + {"send_location", lq_send_location, { lfp_peer, lfp_double, lfp_double, lfp_none }},
294 | + {"post_location", lq_post_location, { lfp_peer, lfp_double, lfp_double, lfp_none }},
295 | {"ext_function", lq_extf, { lfp_string, lfp_none }},
296 | {"import_chat_link", lq_import_chat_link, { lfp_string, lfp_none }},
297 | {"export_chat_link", lq_export_chat_link, { lfp_chat, lfp_none }},
298 | @@ -1468,6 +1670,31 @@
299 | {"channel_kick_user", lq_channel_kick_user, { lfp_channel, lfp_user, lfp_none }},
300 | {"channel_get_admins", lq_channel_get_admins, { lfp_channel, lfp_none }},
301 | {"channel_get_users", lq_channel_get_users, { lfp_channel, lfp_none }},
302 | + {"block_user", lq_block_user, { lfp_user, lfp_none }},
303 | + {"unblock_user", lq_unblock_user, { lfp_user, lfp_none }},
304 | + {"import_channel_link", lq_import_chat_link, { lfp_string, lfp_none }},
305 | + {"channel_set_username", lq_channel_set_username, { lfp_channel, lfp_string, lfp_none }},
306 | + {"rename_channel", lq_rename_channel, { lfp_channel, lfp_string, lfp_none }},
307 | + {"resolve_username", lq_contact_search, { lfp_string, lfp_none }},
308 | + {"create_channel", lq_create_channel, { lfp_peer, lfp_string, lfp_string, lfp_none }},
309 | + {"get_message", lq_get_message, { lfp_msg, lfp_none }},
310 | + {"export_channel_link", lq_export_channel_link, { lfp_channel, lfp_none }},
311 | + {"channel_set_admin", lq_channel_set_admin, { lfp_channel, lfp_user,lfp_none }},
312 | + {"channel_set_mod", lq_channel_set_mod, { lfp_channel, lfp_peer, lfp_none }},
313 | + {"channel_del_admin", lq_channel_del_admin, { lfp_channel, lfp_user,lfp_none }},
314 | + {"channel_del_mod", lq_channel_del_admin, { lfp_channel, lfp_user,lfp_none }},
315 | + {"channel_set_about", lq_channel_set_about, { lfp_channel, lfp_string, lfp_none }},
316 | + {"channel_set_photo", lq_channel_set_photo, { lfp_channel, lfp_string, lfp_none }},
317 | + {"chat_upgrade", lq_chat_upgrade, { lfp_peer, lfp_none }},
318 | + {"channel_leave", lq_channel_leave, { lfp_channel, lfp_none }},
319 | + {"channel_join", lq_channel_join, { lfp_channel, lfp_none }},
320 | + {"get_channel_list", lq_channel_list, { lfp_none }},
321 | + {"post_audio", lq_post_audio, { lfp_peer, lfp_string, lfp_none }},
322 | + {"post_document", lq_post_document, { lfp_peer, lfp_string, lfp_none }},
323 | + {"post_file", lq_post_file, { lfp_peer, lfp_string, lfp_none }},
324 | + {"post_photo", lq_post_photo, { lfp_peer, lfp_string, lfp_none }},
325 | + {"post_text", lq_post_text, { lfp_peer, lfp_string, lfp_none }},
326 | + {"post_video", lq_post_video, { lfp_peer, lfp_string, lfp_none }},
327 | { 0, 0, { lfp_none}}
328 | };
329 |
330 |
--------------------------------------------------------------------------------
/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 |
7 | if c ~= 200 then
8 | return nil
9 | end
10 |
11 | local gag = json:decode(b)
12 | -- random max json table size
13 | local i = math.random(#gag)
14 | local link_image = gag[i].src
15 | local title = gag[i].title
16 |
17 | if link_image:sub(0,2) == '//' then
18 | link_image = msg.text:sub(3,-1)
19 | end
20 |
21 | return link_image, title
22 | end
23 |
24 | local function run(msg, matches)
25 | local url, title = get_9GAG()
26 | local gag_file = '/tmp/gag.jpg'
27 | local g_file = ltn12.sink.file(io.open(gag_file, 'w'))
28 | http.request {
29 | url = url,
30 | sink = g_file,
31 | }
32 |
33 | if msg.from.api then
34 | bot_sendPhoto(get_receiver_api(msg), gag_file, nil, true, msg.id)
35 | else
36 | reply_photo(msg.id, gag_file, ok_cb, true)
37 | end
38 | end
39 |
40 | return {
41 | description = '9GAG for Telegram',
42 | usage = {
43 | '
!9gag
',
44 | 'Send random image from 9gag',
45 | },
46 | patterns = {
47 | '^!9gag$'
48 | },
49 | run = run
50 | }
51 |
52 | end
53 |
--------------------------------------------------------------------------------
/plugins/apod.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function run(msg, matches)
4 | check_api_key(msg, 'nasa_api', 'http://api.nasa.gov')
5 |
6 | if matches[1] == 'setapikey nasa_api' and is_sudo(msg.from.peer_id) then
7 | _config.api_key.nasa_api = matches[2]
8 | save_config()
9 | send_message(msg, 'NASA api key has been saved.', 'html')
10 | return
11 | end
12 |
13 | local apodate = '' .. os.date("%F") .. '\n\n'
14 | local url = 'https://api.nasa.gov/planetary/apod?api_key=' .. _config.api_key.nasa_api
15 |
16 | if matches[2] then
17 | if matches[2]:match('%d%d%d%d%-%d%d%-%d%d$') then
18 | url = url .. '&date=' .. URL.escape(matches[2])
19 | apodate = '' .. matches[2] .. '\n\n'
20 | else
21 | send_message(msg, 'Request must be in following format:\n!' .. matches[1] .. ' YYYY-MM-DD
', 'html')
22 | return
23 | end
24 | end
25 |
26 | local str, res = https.request(url)
27 |
28 | if res ~= 200 then
29 | send_message(msg, 'Connection error', 'html')
30 | return
31 | end
32 |
33 | local jstr = json:decode(str)
34 |
35 | if jstr.error then
36 | send_message(msg, 'No results found', 'html')
37 | return
38 | end
39 |
40 | local img_url = jstr.hdurl or jstr.url
41 | local apod = apodate .. '' .. jstr.title .. ''
42 |
43 | if matches[1] == 'apodtext' then
44 | apod = apod .. '\n\n' .. jstr.explanation
45 | end
46 |
47 | if jstr.copyright then
48 | apod = apod .. '\n\nCopyright: ' .. jstr.copyright .. ''
49 | end
50 |
51 | bot_sendMessage(get_receiver_api(msg), apod, false, msg.id, 'html')
52 | end
53 |
54 | return {
55 | description = "Returns the NASA's Astronomy Picture of the Day.",
56 | usage = {
57 | sudo = {
58 | '!setapikey nasa_api [api_key]
',
59 | 'Set NASA APOD API key.'
60 | },
61 | user = {
62 | '!apod
',
63 | 'Returns the Astronomy Picture of the Day (APOD).',
64 | '',
65 | '!apod YYYY-MM-DD
',
66 | 'Returns the YYYY-MM-DD
APOD.',
67 | 'Example: !apod 2016-08-17
',
68 | '',
69 | '!apodtext
',
70 | 'Returns the explanation of the APOD.',
71 | '',
72 | '!apodtext YYYY-MM-DD
',
73 | 'Returns the explanation of YYYY-MM-DD
APOD.',
74 | 'Example: !apodtext 2016-08-17
',
75 | '',
76 | },
77 | },
78 | patterns = {
79 | '^!(apod)$',
80 | '^!(apodtext)$',
81 | '^!(apod) (%g+)$',
82 | '^!(apodtext) (%g+)$',
83 | '^!(setapikey nasa_api) (.*)$'
84 | },
85 | run = run
86 | }
87 |
88 | end
89 |
--------------------------------------------------------------------------------
/plugins/bing.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Get Bing search API from from https://datamarket.azure.com/dataset/bing/search
3 | Set the key by: !setapi bing [bing_api_key] or manually inserted into config.lua
4 | --]]
5 |
6 | do
7 |
8 | local mime = require('mime')
9 |
10 | local function bingo(msg, burl, terms)
11 | local burl = burl:format(URL.escape("'" .. terms .. "'"))
12 | local limit = 5
13 |
14 | if not is_chat_msg(msg) then
15 | limit = 8
16 | end
17 |
18 | local resbody = {}
19 | local bang, bing, bung = https.request{
20 | url = burl .. '&$top=' .. limit,
21 | headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. _config.api_key.bing) },
22 | sink = ltn12.sink.table(resbody),
23 | }
24 | local dat = json:decode(table.concat(resbody))
25 | local jresult = dat.d.results
26 |
27 | if next(jresult) == nil then
28 | send_message(msg, 'No Bing results for: ' .. terms, 'html')
29 | else
30 | local reslist = {}
31 | for i = 1, #jresult do
32 | local result = jresult[i]
33 | reslist[i] = '' .. i .. '. '
34 | .. '' .. result.Title .. ''
35 | end
36 |
37 | local reslist = table.concat(reslist, '\n')
38 | local header = 'Bing results for ' .. terms .. ' :\n'
39 |
40 | bot_sendMessage(get_receiver_api(msg), header .. reslist, true, msg.id, 'html')
41 | end
42 | end
43 |
44 | local function bing_by_reply(extra, success, result)
45 | local terms = result.text
46 |
47 | bingo(extra.msg, extra.burl, terms)
48 | end
49 |
50 | local function run(msg, matches)
51 | check_api_key(msg, 'bing', 'https://datamarket.azure.com/dataset/bing/search')
52 |
53 | if matches[1] == 'setapikey bing' and is_sudo(msg.from.peer_id) then
54 | _config.api_key.bing = matches[2]
55 | save_config()
56 | send_message(msg, 'Bing api key has been saved.', 'html')
57 | return
58 | end
59 |
60 | local burl = "https://api.datamarket.azure.com/Data.ashx/Bing/Search/Web?Query=%s&$format=json"
61 |
62 | if matches[1]:match('nsfw') then
63 | burl = burl .. '&Adult=%%27Off%%27'
64 | else
65 | burl = burl .. '&Adult=%%27Strict%%27'
66 | end
67 |
68 | if msg.reply_id then
69 | get_message(msg.reply_id, bing_by_reply, {msg=msg, burl=burl})
70 | else
71 | if msg.reply_to_message then
72 | bingo(msg, burl, msg.reply_to_message.text)
73 | else
74 | bingo(msg, burl, matches[2])
75 | end
76 | end
77 | end
78 |
79 | --------------------------------------------------------------------------------
80 |
81 | return {
82 | description = 'Returns 5 (if group) or 8 (if private message) Bing results.\n'
83 | .. 'Safe search is enabled by default, use !bnsfw
or !bingnsfw
to disable it.',
84 | usage = {
85 | sudo = {
86 | '!setapikey bing [api_key]
',
87 | 'Set Bing API key.'
88 | },
89 | user = {
90 | '!bing [terms]
',
91 | '!b [terms]
',
92 | 'Safe searches Bing',
93 | '',
94 | '!bing
',
95 | '!b
',
96 | 'Safe searches Bing by reply. The search terms is the replied message text.',
97 | '',
98 | '!bingnsfw [terms]
',
99 | '!bnsfw [terms]
',
100 | 'Searches Bing (include NSFW)',
101 | '',
102 | '!bingnsfw
',
103 | '!bnsfw
',
104 | 'Searches Bing (include NSFW). The search terms is the replied message text.'
105 | },
106 | },
107 | patterns = {
108 | '^!(b)$', '^!(bing)$',
109 | '^!b(nsfw)$', '^!bing(nsfw)$',
110 | '^!(b) (.*)$', '^!(bing) (.*)$',
111 | '^!b(nsfw) (.*)$', '^!bing(nsfw) (.*)$',
112 | '^!(setapikey bing) (.*)$',
113 | },
114 | run = run
115 | }
116 |
117 | end
118 |
--------------------------------------------------------------------------------
/plugins/btc.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | -- See https://bitcoinaverage.com/api
4 | local function run(msg, matches)
5 | local base_url = 'https://api.bitcoinaverage.com/ticker/global/'
6 | local currency = 'USD'
7 |
8 | if matches[2] then
9 | currency = matches[2]:upper()
10 | end
11 |
12 | -- Do request on bitcoinaverage, the final / is critical!
13 | local res, code = https.request(base_url .. currency .. '/')
14 |
15 | if code ~= 200 then return nil end
16 |
17 | local data = json:decode(res)
18 | local ask = string.gsub(data.ask, '%.', ',')
19 | local bid = string.gsub(data.bid, '%.', ',')
20 | local index = 'BTC in ' .. currency .. ':\n'
21 | .. '• Buy: ' .. group_into_three(ask) .. '\n'
22 | .. '• Sell: ' .. group_into_three(bid)
23 |
24 | bot_sendMessage(get_receiver_api(msg), index, true, msg.id, 'html')
25 | end
26 |
27 | --------------------------------------------------------------------------------
28 |
29 | return {
30 | description = 'Displays the current Bitcoin price.',
31 | usage = {
32 | '!btc
',
33 | 'Displays Bitcoin price in USD',
34 | '',
35 | '!btc [currency]
',
36 | 'Displays Bitcoin price in [currency]
',
37 | '[currency]
is in ISO 4217 format.',
38 | '',
39 | },
40 | patterns = {
41 | '^!(btc)$',
42 | '^!(btc) (%a%a%a)$',
43 | },
44 | run = run
45 | }
46 |
47 | end
--------------------------------------------------------------------------------
/plugins/calculator.lua:
--------------------------------------------------------------------------------
1 | -- Function reference: http://mathjs.org/docs/reference/functions/categorical.html
2 |
3 | do
4 |
5 | local function mathjs(msg, exp)
6 | local result = http.request('http://api.mathjs.org/v1/?expr=' .. URL.escape(exp))
7 |
8 | if not result then
9 | result = 'Unexpected error\nIs api.mathjs.org up?'
10 | end
11 |
12 | send_message(msg, '' .. result .. '', 'html')
13 | end
14 |
15 | local function run(msg, matches)
16 | mathjs(msg, matches[1])
17 | end
18 |
19 | return {
20 | description = "Calculate math expressions with mathjs.org API.",
21 | usage = {
22 | '!calc [expression]
',
23 | '!calculator [expression]
',
24 | 'Evaluates the expression and sends the result.',
25 | },
26 | patterns = {
27 | "^!calc (.*)$",
28 | "^!calculator (.*)"
29 | },
30 | run = run
31 | }
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/plugins/cats.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | function run(msg, matches)
4 | local filetype = '&type=jpg'
5 |
6 | if matches[1] == 'gif' then
7 | filetype = '&type=gif'
8 | end
9 |
10 | local url = 'http://thecatapi.com/api/images/get?format=html' .. filetype .. '&api_key=' .. _config.api_key.thecatapi
11 | local str, res = http.request(url)
12 |
13 | if res ~= 200 then
14 | send_message(msg, 'Connection error', 'html')
15 | return
16 | end
17 |
18 | local str = str:match('')
19 |
20 | bot_sendMessage(get_receiver_api(msg), 'Cat!', false, msg.id, 'html')
21 | end
22 |
23 | return {
24 | description = 'Returns a cat!',
25 | usage = {
26 | '
!cat
',
27 | '!cats
',
28 | 'Returns a picture of cat!',
29 | '',
30 | '!cat gif
',
31 | '!cats gif
',
32 | 'Returns an animated picture of cat!',
33 | },
34 | patterns = {
35 | '^!cats?$',
36 | '^!cats? (gif)$',
37 | },
38 | run = run,
39 | is_need_api_key = {'thecatapi', 'http://thecatapi.com/docs.html'}
40 | }
41 |
42 | end
--------------------------------------------------------------------------------
/plugins/currency.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function get_word(s, i)
4 | s = s or ''
5 | i = i or 1
6 | local t = {}
7 |
8 | for w in s:gmatch('%g+') do
9 | table.insert(t, w)
10 | end
11 |
12 | return t[i] or false
13 | end
14 |
15 | local function run(msg, matches)
16 | local input = msg.text:upper()
17 |
18 | if not input:match('%a%a%a TO %a%a%a') then
19 | send_message(msg, 'Example: !cash 5 USD to IDR
', 'html')
20 | return
21 | end
22 |
23 | local from = input:match('(%a%a%a) TO')
24 | local to = input:match('TO (%a%a%a)')
25 | local amount = get_word(input, 2)
26 | local amount = tonumber(amount) or 1
27 | local result = 1
28 | local url = 'https://www.google.com/finance/converter'
29 |
30 | if from ~= to then
31 | local url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount
32 | local str, res = https.request(url)
33 |
34 | if res ~= 200 then
35 | send_message(msg, 'Connection error', 'html')
36 | return
37 | end
38 |
39 | str = str:match('(.*) %u+')
40 |
41 | if not str then
42 | send_message(msg, 'Connection error', 'html')
43 | return
44 | end
45 |
46 | result = string.format('%.2f', str):gsub('%.', ',')
47 | end
48 |
49 | local headerapi = '' .. amount .. ' ' .. from .. ' = ' .. group_into_three(result) .. ' ' .. to .. '\n\n'
50 | local source = 'Source: Google Finance\n' .. os.date('%F %T %Z') .. '
'
51 |
52 | send_message(msg, headerapi .. source, 'html')
53 | end
54 |
55 | --------------------------------------------------------------------------------
56 |
57 | return {
58 | description = 'Returns (Google Finance) exchange rates for various currencies.',
59 | usage = {
60 | '!cash [amount] [from] to [to]
',
61 | 'Example:',
62 | ' * !cash 5 USD to EUR
',
63 | ' * !currency 1 usd to idr
',
64 | },
65 | patterns = {
66 | '^!cash (.*)$',
67 | '^!currency (.*)$',
68 | },
69 | run = run
70 | }
71 |
72 | end
73 |
--------------------------------------------------------------------------------
/plugins/dilbert.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function run(msg, matches)
4 | local input = os.date('%F')
5 |
6 | if matches[2] then
7 | if matches[2]:match('^%d%d%d%d%-%d%d%-%d%d$') then
8 | input = matches[2]
9 | else
10 | send_message(msg, 'Request must be in following format:\n'
11 | .. '!' .. matches[1] .. ' YYYY-MM-DD
', 'html')
12 | return
13 | end
14 | end
15 |
16 | local url = 'http://dilbert.com/strip/' .. URL.escape(input)
17 | local str, res = http.request(url)
18 |
19 | if res ~= 200 then
20 | send_message(msg, 'Connection error', 'html')
21 | return
22 | end
23 |
24 | local strip_filename = '/tmp/' .. input .. '.gif'
25 | local strip_file = io.open(strip_filename)
26 |
27 | if strip_file then
28 | strip_file:close()
29 | strip_file = strip_filename
30 | else
31 | local strip_url = str:match('')
32 | strip_file = download_to_file(strip_url, input .. '.gif')
33 | end
34 |
35 | local strip_title = str:match('')
36 | local strip_date = str:match('')
37 |
38 | if msg.from.api then
39 | bot_sendPhoto(get_receiver_api(msg), strip_file, strip_date .. '. ' .. strip_title, true, msg.id)
40 | else
41 | local cmd = 'send_photo %s %s %s'
42 | local command = cmd:format(get_receiver(msg), strip_file, strip_date .. '. ' .. strip_title)
43 | os.execute(tgclie:format(command))
44 | end
45 | end
46 |
47 | return {
48 | description = 'Returns the latest Dilbert strip or that of the provided date.\n'
49 | .. 'Dates before the first strip will return the first strip.\n'
50 | .. 'Dates after the last trip will return the last strip.\n'
51 | .. 'Source: dilbert.com',
52 | usage = {
53 | '!dilbert
',
54 | 'Returns todays Dilbert comic',
55 | '',
56 | '!dilbert YYYY-MM-DD
',
57 | 'Returns Dilbert comic published on YYYY-MM-DD
',
58 | 'Example: !dilbert 2016-08-17
',
59 | '',
60 | },
61 | patterns = {
62 | '^!(dilbert)$',
63 | '^!(dilbert) (%g+)$'
64 | },
65 | run = run
66 | }
67 |
68 | end
--------------------------------------------------------------------------------
/plugins/dogify.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function run(msg, matches)
4 | local base = 'http://dogr.io/'
5 | local dogetext = URL.escape(matches[1])
6 | local dogetext = string.gsub(dogetext, '%%2f', '/')
7 | local url = base .. dogetext .. '.png?split=false&.png'
8 | local urlm = 'https?://[%%%w-_%.%?%.:/%+=&]+'
9 |
10 | if string.match(url, urlm) == url then
11 | bot_sendMessage(get_receiver_api(msg), '[doge](' .. url .. ')', false, msg.id, 'markdown')
12 | else
13 | print("Can't build a good URL with parameter " .. matches[1])
14 | end
15 | end
16 |
17 | return {
18 | description = 'Create a doge image with you words.',
19 | usage = {
20 | '!dogify (your/words/with/slashes)
',
21 | '!doge (your/words/with/slashes)
',
22 | 'Create a doge with the image and words.',
23 | 'Example: !doge wow/merbot/soo/cool
',
24 | },
25 | patterns = {
26 | '^!dogify (.+)$',
27 | '^!doge (.+)$',
28 | },
29 | run = run
30 | }
31 |
32 | end
--------------------------------------------------------------------------------
/plugins/forecast.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function round(val, decimal)
4 | local exp = decimal and 10^decimal or 1
5 | return math.ceil(val * exp - 0.5) / exp
6 | end
7 |
8 | local function wemoji(weather_data)
9 | if weather_data.icon == 'clear-day' then
10 | return '☀️'
11 | elseif weather_data.icon == 'clear-night' then
12 | return '🌙'
13 | elseif weather_data.icon == 'rain' then
14 | return '☔️'
15 | elseif weather_data.icon == 'snow' then
16 | return '❄️'
17 | elseif weather_data.icon == 'sleet' then
18 | return '🌨'
19 | elseif weather_data.icon == 'wind' then
20 | return '💨'
21 | elseif weather_data.icon == 'fog' then
22 | return '🌫'
23 | elseif weather_data.icon == 'cloudy' then
24 | return '☁️☁️'
25 | elseif weather_data.icon == 'partly-cloudy-day' then
26 | return '🌤'
27 | elseif weather_data.icon == 'partly-cloudy-night' then
28 | return '🌙☁️'
29 | else
30 | return ''
31 | end
32 | end
33 |
34 | -- Use timezone api to get the time in the lat
35 | local function getforecast(msg, area)
36 | local coords, code = get_coords(msg, area)
37 | local lat = coords.lat
38 | local long = coords.lon
39 | local address = coords.formatted_address
40 | local url = 'https://api.darksky.net/forecast/'
41 | local units = '?units=si'
42 | local url = url .. _config.api_key.forecast .. '/' .. URL.escape(lat) .. ',' .. URL.escape(long) .. units
43 | local res, code = https.request(url)
44 |
45 | if code ~= 200 then
46 | return nil
47 | end
48 |
49 | local jcast = json:decode(res)
50 | local todate = os.date('%A, %F', jcast.currently.time)
51 |
52 | local forecast = 'Weather for: ' .. address .. '\n' .. todate .. '\n\n'
53 | .. 'Right now ' .. wemoji(jcast.currently) .. '\n'
54 | .. jcast.currently.summary .. ' - Feels like ' .. round(jcast.currently.apparentTemperature) .. '°C\n\n'
55 | .. 'Next 24 hours ' .. wemoji(jcast.hourly) .. '\n' .. jcast.hourly.summary .. '\n\n'
56 | .. 'Next 7 days ' .. wemoji(jcast.daily) .. '\n' .. jcast.daily.summary .. '\n\n'
57 | .. 'Powered by Dark Sky'
58 |
59 | bot_sendMessage(get_receiver_api(msg), forecast, true, msg.id, 'html')
60 | end
61 |
62 | local function run(msg, matches)
63 | if matches[1] == 'setapikey forecast' and is_sudo(msg.from.peer_id) then
64 | _config.api_key.forecast = matches[2]
65 | save_config()
66 | send_message(msg, 'Dark Sky API key has been saved.', 'html')
67 | return
68 | else
69 | return getforecast(msg, matches[1])
70 | end
71 | end
72 |
73 | return {
74 | description = 'Returns forecast from forecast.io.',
75 | usage = {
76 | '!cast [area]
',
77 | '!forecast [area]
',
78 | '!weather [area]
',
79 | 'Forecast for that [area]
.',
80 | 'Example: !weather dago parung panjang
',
81 | },
82 | patterns = {
83 | '^!cast (.*)$',
84 | '^!forecast (.*)$',
85 | '^!weather (.*)$',
86 | '^!(setapikey forecast) (.*)$'
87 | },
88 | run = run,
89 | is_need_api_key = {'forecast', 'https://darksky.net/dev/'}
90 | }
91 |
92 | end
93 |
94 |
95 |
--------------------------------------------------------------------------------
/plugins/gmaps.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function run(msg, matches)
4 | local coords = get_coords(msg, matches[1])
5 |
6 | if coords then
7 | if msg.from.api then
8 | bot_sendLocation(get_receiver_api(msg), coords.lat, coords.lon, true, msg.id)
9 | else
10 | send_location(get_receiver(msg), coords.lat, coords.lon, ok_cb, true)
11 | end
12 | end
13 | end
14 |
15 | return {
16 | description = 'Returns a location from Google Maps.',
17 | usage = {
18 | '!loc [query]
',
19 | '!location [query]
',
20 | '!gmaps [query]
',
21 | 'Returns Google Maps of [query]
.',
22 | 'Example: !loc raja ampat
',
23 | },
24 | patterns = {
25 | '^!gmaps (.*)$',
26 | '^!location (.*)$',
27 | '^!loc (.*)$',
28 | },
29 | run = run
30 | }
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/plugins/gsmarena.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Get Bing search API from from https://datamarket.azure.com/dataset/bing/search
3 | Set the key by: !setapi bing [bing_api_key] or manually inserted into config.lua
4 | --]]
5 |
6 | do
7 |
8 | local mime = require('mime')
9 | local function get_galink(msg, query)
10 | local burl = "https://api.datamarket.azure.com/Data.ashx/Bing/Search/Web?Query=%s&$format=json&$top=1"
11 | local burl = burl:format(URL.escape("'site:gsmarena.com intitle:" .. query .. "'"))
12 | local resbody = {}
13 | local bang, bing, bung = https.request{
14 | url = burl,
15 | headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. _config.api_key.bing) },
16 | sink = ltn12.sink.table(resbody),
17 | }
18 | local dat = json:decode(table.concat(resbody))
19 | local jresult = dat.d.results
20 |
21 | if next(jresult) ~= nil then
22 | return jresult[1].Url
23 | end
24 | end
25 |
26 | local function run(msg, matches)
27 | local phone = get_galink(msg, matches[2])
28 | local slug = phone:gsub('^.+/', '')
29 | local slug = slug:gsub('.php', '')
30 | local ibacor = 'http://ibacor.com/api/gsm-arena?view=product&slug='
31 | local res, code = http.request(ibacor .. slug)
32 | local gsm = json:decode(res)
33 | local phdata = {}
34 |
35 | if gsm == nil or gsm.status == 'error' or next(gsm.data) == nil then
36 | send_message(msg, 'No phones found!\n'
37 | .. 'Request must be in the following format:\n'
38 | .. '!gsm brand type
', 'html')
39 | return
40 | end
41 | if not gsm.data.platform then
42 | gsm.data.platform = {}
43 | end
44 | if gsm.data.launch.status == 'Discontinued' then
45 | launch = gsm.data.launch.status .. '. Was announced in ' .. gsm.data.launch.announced
46 | else
47 | launch = gsm.data.launch.status
48 | end
49 | if gsm.data.platform.os then
50 | phdata[1] = 'OS: ' .. gsm.data.platform.os
51 | end
52 | if gsm.data.platform.chipset then
53 | phdata[2] = 'Chipset: ' .. gsm.data.platform.chipset
54 | end
55 | if gsm.data.platform.cpu then
56 | phdata[3] = 'CPU: ' .. gsm.data.platform.cpu
57 | end
58 | if gsm.data.platform.gpu then
59 | phdata[4] = 'GPU: ' .. gsm.data.platform.gpu
60 | end
61 | if gsm.data.camera.primary then
62 | local phcam = 'Camera: ' .. gsm.data.camera.primary:gsub(',.*$', '') .. ', ' .. (gsm.data.camera.video or '')
63 | phdata[5] = phcam:gsub(', check quality', '')
64 | end
65 | if gsm.data.memory.internal then
66 | phdata[6] = 'RAM: ' .. gsm.data.memory.internal
67 | end
68 |
69 | local gadata = table.concat(phdata, '\n')
70 | local title = '' .. gsm.title .. '\n\n'
71 | local dimensions = gsm.data.body.dimensions:gsub('%(.-%)', '')
72 | local display = gsm.data.display.size:gsub(' .*$', '"') .. ', '
73 | .. gsm.data.display.resolution:gsub('%(.-%)', '')
74 | local output = title .. 'Status: ' .. launch .. '\n'
75 | .. 'Dimensions: ' .. dimensions .. '\n'
76 | .. 'Weight: ' .. gsm.data.body.weight:gsub('%(.-%)', '') .. '\n'
77 | .. 'SIM: ' .. gsm.data.body.sim .. '\n'
78 | .. 'Display: ' .. display .. '\n'
79 | .. gadata
80 | .. '\nMC: ' .. gsm.data.memory.card_slot .. '\n'
81 | .. 'Battery: ' .. gsm.data.battery._empty_:gsub('battery', '') .. '\n'
82 | .. 'More on gsmarena.com ...'
83 |
84 | bot_sendMessage(get_receiver_api(msg), output:gsub('
', ''), false, msg.id, 'html')
85 | end
86 |
87 | --------------------------------------------------------------------------------
88 |
89 | return {
90 | description = 'Returns mobile phone specification.',
91 | usage = {
92 | '!phone [phone]
',
93 | '!gsm [phone]
',
94 | 'Returns phone
specification.',
95 | 'Example: !gsm xiaomi mi4c
',
96 | },
97 | patterns = {
98 | '^!(phone) (.*)$',
99 | '^!(gsmarena) (.*)$',
100 | '^!(gsm) (.*)$'
101 | },
102 | run = run
103 | }
104 |
105 | end
106 |
--------------------------------------------------------------------------------
/plugins/hackernews.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function run(msg, matches)
4 | local jstr, res = https.request('https://hacker-news.firebaseio.com/v0/topstories.json')
5 |
6 | if res ~= 200 then
7 | bot_sendMessage(get_receiver_api(msg), 'Connection error.', true, msg.id, 'html')
8 | return
9 | end
10 |
11 | local jdat = json:decode(jstr)
12 | local res_count = 8
13 | local header = 'Hacker News\n\n'
14 | local hackernew = {}
15 |
16 | for i = 1, res_count do
17 | local res_url = 'https://hacker-news.firebaseio.com/v0/item/' .. jdat[i] .. '.json'
18 | local jstr, res = https.request(res_url)
19 |
20 | if res ~= 200 then
21 | send_message(msg, 'Connection error', 'html')
22 | return
23 | end
24 |
25 | local res_jdat = json:decode(jstr)
26 | local title = res_jdat.title:gsub('%[.+%]', ''):gsub('%(.+%)', ''):gsub('&', '&')
27 |
28 | if title:len() > 48 then
29 | title = title:sub(1, 45) .. '...'
30 | end
31 |
32 | local url = res_jdat.url
33 |
34 | if not url then
35 | send_message(msg, 'Connection error', 'html')
36 | return
37 | end
38 |
39 | local title = unescape_html(title)
40 |
41 | hackernew[i] = '' .. i .. '. ' .. title .. '\n'
42 | end
43 |
44 | local hackernews = table.concat(hackernew)
45 |
46 | bot_sendMessage(get_receiver_api(msg), header .. hackernews, true, msg.id, 'html')
47 | end
48 |
49 | return {
50 | description = 'Returns top stories from Hacker News.',
51 | usage = {
52 | '!hackernews
',
53 | '!hn
',
54 | 'Returns top stories from Hacker News.',
55 | },
56 | patterns = {
57 | '^!(hackernews)$',
58 | '^!(hn)$',
59 | },
60 | run = run
61 | }
62 |
63 | end
64 |
--------------------------------------------------------------------------------
/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, number, requester)
13 | local plugin = ''
14 |
15 | if number then
16 | local i = 0
17 |
18 | for name in pairsByKeys(plugins) do
19 | if plugins[name].hidden then
20 | name = nil
21 | else
22 | i = i + 1
23 | if i == tonumber(number) then
24 | plugin = plugins[name]
25 | end
26 | end
27 | end
28 | else
29 | plugin = plugins[name]
30 | if not plugin then return nil end
31 | end
32 |
33 | local text = ''
34 |
35 | if (type(plugin.usage) == 'table') then
36 | for ku,usage in pairs(plugin.usage) do
37 | if ku == 'user' then -- usage for user
38 | if (type(plugin.usage.user) == 'table') then
39 | for k,v in pairs(plugin.usage.user) do
40 | text = text .. v .. '\n'
41 | end
42 | elseif has_usage_data(plugin) then -- Is not empty
43 | text = text .. plugin.usage.user .. '\n'
44 | end
45 | elseif ku == 'moderator' then -- usage for moderator
46 | if requester == 'moderator' or requester == 'owner' or requester == 'admin' or requester == 'sudo' then
47 | if (type(plugin.usage.moderator) == 'table') then
48 | for k,v in pairs(plugin.usage.moderator) do
49 | text = text .. v .. '\n'
50 | end
51 | elseif has_usage_data(plugin) then -- Is not empty
52 | text = text .. plugin.usage.moderator .. '\n'
53 | end
54 | end
55 | elseif ku == 'owner' then -- usage for owner
56 | if requester == 'owner' or requester == 'admin' or requester == 'sudo' then
57 | if (type(plugin.usage.owner) == 'table') then
58 | for k,v in pairs(plugin.usage.owner) do
59 | text = text .. v .. '\n'
60 | end
61 | elseif has_usage_data(plugin) then -- Is not empty
62 | text = text .. plugin.usage.owner .. '\n'
63 | end
64 | end
65 | elseif ku == 'admin' then -- usage for admin
66 | if requester == 'admin' or requester == 'sudo' then
67 | if (type(plugin.usage.admin) == 'table') then
68 | for k,v in pairs(plugin.usage.admin) do
69 | text = text .. v .. '\n'
70 | end
71 | elseif has_usage_data(plugin) then -- Is not empty
72 | text = text .. plugin.usage.admin .. '\n'
73 | end
74 | end
75 | elseif ku == 'sudo' then -- usage for sudo
76 | if requester == 'sudo' then
77 | if (type(plugin.usage.sudo) == 'table') then
78 | for k,v in pairs(plugin.usage.sudo) do
79 | text = text .. v .. '\n'
80 | end
81 | elseif has_usage_data(plugin) then -- Is not empty
82 | text = text .. plugin.usage.sudo .. '\n'
83 | end
84 | end
85 | else
86 | text = text .. usage .. '\n'
87 | end
88 | end
89 | elseif has_usage_data(plugin) then -- Is not empty
90 | text = text .. plugin.usage
91 | end
92 | return text
93 | end
94 |
95 |
96 | -- !help command
97 | local function telegram_help(msg)
98 | local i = 0
99 | local text = 'Plugins\n\n'
100 | -- Plugins names
101 | for name in pairsByKeys(plugins) do
102 | if plugins[name].hidden then
103 | name = nil
104 | else
105 | i = i + 1
106 | text = text .. '' .. i .. '. ' .. name .. '\n'
107 | end
108 | end
109 | text = text .. '\n' .. 'There are ' .. i .. ' plugins help available.\n'
110 | .. '- !help [plugin name]
for more info.\n'
111 | .. '- !help [plugin number]
for more info.\n'
112 | .. '- !help all
to show all info.'
113 |
114 | bot_sendMessage(get_receiver_api(msg), text, true, msg.id, 'html')
115 | end
116 |
117 | -- -- !help all command
118 | -- local function help_all(requester)
119 | -- local ret = ''
120 | -- for name in pairsByKeys(plugins) do
121 | -- if plugins[name].hidden then
122 | -- name = nil
123 | -- else
124 | -- ret = ret .. plugin_help(name, nil, requester)
125 | -- end
126 | -- end
127 | -- return ret
128 | -- end
129 |
130 | --------------------------------------------------------------------------------
131 |
132 | local function run(msg, matches)
133 | local uid = msg.from.peer_id
134 | local gid = msg.to.peer_id
135 |
136 | if is_sudo(uid) then
137 | requester = 'sudo'
138 | elseif is_admin(uid) then
139 | requester = 'admin'
140 | elseif is_owner(msg, gid, uid) then
141 | requester = 'owner'
142 | elseif is_mod(msg, gid, uid) then
143 | requester = 'moderator'
144 | else
145 | requester = 'user'
146 | end
147 |
148 | if msg.text == '!help' then
149 | return telegram_help(msg)
150 | elseif matches[1] == 'all' then
151 | send_message(msg, 'Please read @thefinemanual', 'html')
152 | --return help_all(requester)
153 | else
154 | local text = ''
155 |
156 | if tonumber(matches[1]) then
157 | text = plugin_help(nil, matches[1], requester)
158 | else
159 | text = plugin_help(matches[1], nil, requester)
160 | end
161 | if not text then
162 | send_message(msg, 'No help entry for "' .. matches[1] .. '".\n'
163 | .. 'Please visit @thefinemanual for the complete list.', 'html')
164 | return
165 | end
166 | if text == 'text' then
167 | send_message(msg, 'The plugins is not for your privilege.', 'html')
168 | return
169 | end
170 |
171 | bot_sendMessage(get_receiver_api(msg), text, true, msg.id, 'html')
172 | end
173 | end
174 |
175 | --------------------------------------------------------------------------------
176 |
177 | return {
178 | description = 'Help plugin. Get info from other plugins.',
179 | usage = {
180 | '!help
',
181 | 'Show list of plugins.',
182 | '',
183 | '!help all
',
184 | 'Show all commands for every plugin.',
185 | '',
186 | '!help [plugin_name]
',
187 | 'Commands for that plugin.',
188 | '',
189 | '!help [number]
',
190 | 'Commands for that plugin. Type !help to get the plugin number.'
191 | },
192 | patterns = {
193 | '^!help$',
194 | '^!help (%g+)$',
195 | },
196 | run = run
197 | }
198 |
199 | end
200 |
--------------------------------------------------------------------------------
/plugins/id.lua:
--------------------------------------------------------------------------------
1 | --[[
2 |
3 | Print user identification/informations by replying their post or by providing
4 | their username or print_name.
5 |
6 | !id Name :
' .. (result.first_name or '') .. ' ' .. (result.last_name or '') .. '\n'
37 | .. 'First name:
' .. (result.first_name or '') .. '\n'
38 | .. 'Last name :
' .. (result.last_name or '') .. '\n'
39 | .. 'User name :
' .. user_name .. '\n'
40 | .. 'ID :' .. result.peer_id .. '
'
41 |
42 | bot_sendMessage(get_receiver_api(msg), text, true, msg.id, 'html')
43 | else
44 | bot_sendMessage(get_receiver_api(msg), 'Failed to resolve '
45 | .. extra.usr .. '
IDs.\nCheck if ' .. extra.usr .. '
is correct.', true, msg.id, 'html')
46 | end
47 | end
48 |
49 | local function scan_name(extra, success, result)
50 | local msg = extra.msg
51 | local uname = extra.uname
52 |
53 | if msg.to.peer_type == 'channel' then
54 | member_list = result
55 | else
56 | member_list = result.members
57 | end
58 |
59 | local founds = {}
60 |
61 | for k,member in pairs(member_list) do
62 | local fields = {'first_name', 'last_name', 'print_name'}
63 |
64 | for k,field in pairs(fields) do
65 | if member[field] and type(member[field]) == 'string' then
66 | local gp_member = member[field]:lower()
67 | if gp_member:match(uname:lower()) then
68 | founds[tostring(member.id)] = member
69 | end
70 | end
71 | end
72 | end
73 | if next(founds) == nil then -- Empty table
74 | bot_sendMessage(get_receiver_api(msg), uname .. ' not found on this chat', true, msg.id, 'html')
75 | else
76 | local text = ''
77 |
78 | for k,user in pairs(founds) do
79 | if user.username then
80 | user_name = ' @' .. user.username
81 | else
82 | user_name = ''
83 | end
84 | text = 'Name :
' .. (user.first_name or '') .. ' ' .. (user.last_name or '') .. '\n'
85 | .. 'First name:
' .. (user.first_name or '') .. '\n'
86 | .. 'Last name :
' .. (user.last_name or '') .. '\n'
87 | .. 'User name :
' .. user_name .. '\n'
88 | .. 'ID :' .. user.peer_id .. '
\n\n'
89 | end
90 |
91 | bot_sendMessage(get_receiver_api(msg), text, true, msg.id, 'html')
92 | end
93 | end
94 |
95 | local function action_by_reply(extra, success, result)
96 | if result.from.username then
97 | user_name = ' @' .. result.from.username
98 | else
99 | user_name = ''
100 | end
101 |
102 | local text = 'Name :
' .. (result.from.first_name or '') .. ' ' .. (result.from.last_name or '') .. '\n'
103 | .. 'First name:
' .. (result.from.first_name or '') .. '\n'
104 | .. 'Last name :
' .. (result.from.last_name or '') .. '\n'
105 | .. 'User name :
' .. user_name .. '\n'
106 | .. 'ID :' .. result.from.peer_id .. '
'
107 |
108 | bot_sendMessage(get_receiver_api(extra), text, true, extra.id, 'html')
109 | end
110 |
111 | local function returnids(extra, success, result)
112 | local msg = extra.msg
113 | local cmd = extra.cmd
114 |
115 | if msg.to.peer_type == 'channel' then
116 | chat_id = msg.to.peer_id
117 | chat_title = msg.to.title
118 | member_list = result
119 | else
120 | chat_id = result.peer_id
121 | chat_title = result.title
122 | member_list = result.members
123 | end
124 |
125 | local list = '' .. chat_title .. ' - ' .. chat_id .. '
\n\n'
126 | local text = chat_title .. ' - ' .. chat_id .. '\n\n'
127 |
128 | i = 0
129 |
130 | for k,v in pairsByKeys(member_list) do
131 | i = i+1
132 |
133 | if v.username then
134 | user_name = ' @' .. v.username
135 | else
136 | user_name = ''
137 | end
138 |
139 | list = list .. '' .. i .. '. ' .. v.peer_id .. '
-' .. user_name .. ' ' .. (v.first_name or '') .. (v.last_name or '') .. '\n'
140 |
141 | if #list > 2048 then
142 | send_group_members(extra, list)
143 | list = ''
144 | end
145 | text = text .. i .. '. ' .. v.peer_id .. ' -' .. user_name .. ' ' .. (v.first_name or '') .. (v.last_name or '') .. '\n'
146 | end
147 |
148 | if cmd == 'txt' or cmd == 'pmtxt' then
149 | local textfile = '/tmp/chat_info_' .. msg.to.peer_id .. '_' .. os.date("%y%m%d.%H%M%S") .. '.txt'
150 | local file = io.open(textfile, 'w')
151 | file:write(text)
152 | file:flush()
153 | file:close()
154 |
155 | if cmd == 'txt' then
156 | send_document(get_receiver(msg), textfile, rmtmp_cb, {file_path=textfile})
157 | elseif cmd == 'pmtxt' then
158 | send_document('user#id' .. msg.from.peer_id, textfile, rmtmp_cb, {file_path=textfile})
159 | end
160 | end
161 |
162 | send_group_members(extra, list)
163 | end
164 |
165 | --------------------------------------------------------------------------------
166 |
167 | local function run(msg, matches)
168 | local gid = msg.to.peer_id
169 | local uid = msg.from.peer_id
170 |
171 | if is_mod(msg, gid, uid) then
172 | if msg.reply_id and msg.text == '!id' then
173 | get_message(msg.reply_id, action_by_reply, msg)
174 | elseif matches[1] == 'chat' then
175 | if msg.to.peer_type == 'channel' then
176 | channel_get_users('channel#id' .. gid, returnids, {msg=msg, cmd=matches[2]})
177 | end
178 | if msg.to.peer_type == 'chat' then
179 | chat_info('chat#id' .. gid, returnids, {msg=msg, cmd=matches[2]})
180 | end
181 | elseif matches[1] == '@' then
182 | resolve_username(matches[2], resolve_user, {msg=msg, usr=matches[2]})
183 | elseif matches[1]:match('%d+$') then
184 | user_info('user#id' .. matches[1], resolve_user, {msg=msg, usr=matches[1]})
185 | elseif matches[1] == 'name' then
186 | if msg.to.peer_type == 'channel' then
187 | channel_get_users('channel#id' .. gid, scan_name, {msg=msg, uname=matches[2]})
188 | end
189 | if msg.to.peer_type == 'chat' then
190 | chat_info('chat#id' .. gid, scan_name, {msg=msg, uname=matches[2]})
191 | end
192 | end
193 | end
194 |
195 | if not msg.reply_id and msg.text == '!id' then
196 | if msg.from.username then
197 | user_name = '@' .. msg.from.username
198 | else
199 | user_name = ''
200 | end
201 |
202 | local text = 'Name :
' .. (msg.from.first_name or '') .. ' ' .. (msg.from.last_name or '') .. '\n'
203 | .. 'First name:
' .. (msg.from.first_name or '') .. '\n'
204 | .. 'Last name :
' .. (msg.from.last_name or '') .. '\n'
205 | .. 'User name :
' .. user_name .. '\n'
206 | .. 'ID :' .. uid .. '
'
207 |
208 | if not is_chat_msg(msg) then
209 | bot_sendMessage(get_receiver_api(msg), text, true, msg.id, 'html')
210 | else
211 | bot_sendMessage(get_receiver_api(msg), text .. '\n\nYou are in group ' .. msg.to.title .. ' [' .. tostring(gid):gsub('-', '') .. '
]', true, msg.id, 'html')
212 | end
213 | end
214 |
215 | end
216 |
217 | --------------------------------------------------------------------------------
218 |
219 | return {
220 | description = 'Know your id or the id of a chat members.',
221 | usage = {
222 | moderator = {
223 | '!id
',
224 | 'Return ID of replied user if used by reply.',
225 | '',
226 | '!id chat
',
227 | 'Return the IDs of the current chat members.',
228 | '',
229 | '!id chat txt
',
230 | 'Return the IDs of the current chat members and send it as text file.',
231 | '',
232 | '!id chat pm
',
233 | 'Return the IDs of the current chat members and send it to PM.',
234 | '',
235 | '!id chat pmtxt
',
236 | 'Return the IDs of the current chat members, save it as text file and then send it to PM.',
237 | '',
238 | '!id [user_id]
',
239 | 'Return the IDs of the user_id.',
240 | '',
241 | '!id @[user_name]
',
242 | 'Return the member username ID from the current chat.',
243 | '',
244 | '!id [name]
',
245 | 'Search for users with name on first_name
, last_name
, or print_name
on current chat.'
246 | },
247 | user = {
248 | '!id
',
249 | 'Return your ID and the chat id if you are in one.'
250 | },
251 | },
252 | patterns = {
253 | '^!(id)$',
254 | '^!id (chat)$',
255 | '^!id (chat) (.+)$',
256 | '^!id (name) (.*)$',
257 | '^!id (@)(.+)$',
258 | '^!id (%d+)$',
259 | },
260 | run = run
261 | }
262 |
263 | end
264 |
--------------------------------------------------------------------------------
/plugins/imdb.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function run(msg, matches)
4 | local omdbapi = 'http://www.omdbapi.com/?plot=full&r=json'
5 | local movietitle = matches[1]
6 |
7 | if matches[1]:match(' %d%d%d%d$') then
8 | local movieyear = matches[1]:match('%d%d%d%d$')
9 | movietitle = matches[1]:match('^.+ ')
10 | omdbapi = omdbapi .. '&y=' .. movieyear
11 | end
12 |
13 | local success, code = http.request(omdbapi .. '&t=' .. URL.escape(movietitle))
14 |
15 | if success then
16 | jomdb = json:decode(success)
17 | end
18 |
19 | if jomdb.Response == 'False' then
20 | send_message(msg, '' .. jomdb.Error .. '', 'html')
21 | return
22 | end
23 |
24 | if not jomdb then
25 | send_message(msg, '' .. json:decode(code) .. '', 'html')
26 | return
27 | end
28 |
29 | local omdb = '' .. jomdb.Title .. '\n\n'
30 | .. 'Year: ' .. jomdb.Year .. '\n'
31 | .. 'Rated: ' .. jomdb.Rated .. '\n'
32 | .. 'Runtime: ' .. jomdb.Runtime .. '\n'
33 | .. 'Genre: ' .. jomdb.Genre .. '\n'
34 | .. 'Director: ' .. jomdb.Director .. '\n'
35 | .. 'Writer: ' .. jomdb.Writer .. '\n'
36 | .. 'Actors: ' .. jomdb.Actors .. '\n'
37 | .. 'Country: ' .. jomdb.Country .. '\n'
38 | .. 'Awards: ' .. jomdb.Awards .. '\n'
39 | .. 'Plot: ' .. jomdb.Plot .. '\n\n'
40 | .. 'IMDB:\n'
41 | .. 'Metascore: ' .. jomdb.Metascore .. '\n'
42 | .. 'Rating: ' .. jomdb.imdbRating .. '\n'
43 | .. 'Votes: ' .. jomdb.imdbVotes .. '\n\n'
44 |
45 | bot_sendMessage(get_receiver_api(msg), omdb, false, msg.id, 'html')
46 | end
47 |
48 | return {
49 | description = 'The Open Movie Database plugin for Telegram.',
50 | usage = {
51 | '!imdb [movie]
',
52 | '!omdb [movie]
',
53 | 'Returns IMDb entry for [movie]
',
54 | 'Example: !imdb the matrix
',
55 | '',
56 | '!imdb [movie] [year]
',
57 | '!omdb [movie] [year]
',
58 | 'Returns IMDb entry for [movie]
that was released in [year]
',
59 | 'Example: !imdb the matrix 2003
',
60 | },
61 | patterns = {
62 | '^![io]mdb (.+)$',
63 | },
64 | run = run
65 | }
66 |
67 | end
68 |
--------------------------------------------------------------------------------
/plugins/isup.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local socket = require('socket')
4 | local cronned = load_from_file('data/isup.lua')
5 |
6 | local function save_cron(msg, url, delete)
7 | local origin = get_receiver(msg)
8 |
9 | if not cronned[origin] then
10 | cronned[origin] = {}
11 | end
12 | if not delete then
13 | table.insert(cronned[origin], url)
14 | else
15 | for k,v in pairs(cronned[origin]) do
16 | if v == url then
17 | table.remove(cronned[origin], k)
18 | end
19 | end
20 | end
21 |
22 | serialize_to_file(cronned, 'data/isup.lua')
23 | send_message(msg, 'Saved!', 'html')
24 | end
25 |
26 | local function is_up_socket(ip, port)
27 | print('Connect to', ip, port)
28 | local c = socket.try(socket.tcp())
29 | c:settimeout(3)
30 | local conn = c:connect(ip, port)
31 |
32 | if not conn then
33 | return false
34 | else
35 | c:close()
36 | return true
37 | end
38 | end
39 |
40 | local function is_up_http(url)
41 | -- Parse URL from input, default to http
42 | local parsed_url = URL.parse(url, { scheme = 'http', authority = '' })
43 | -- Fix URLs without subdomain not parsed properly
44 | if not parsed_url.host and parsed_url.path then
45 | parsed_url.host = parsed_url.path
46 | parsed_url.path = ""
47 | end
48 | -- Re-build URL
49 | local url = URL.build(parsed_url)
50 | local protocols = {
51 | ['https'] = https,
52 | ['http'] = http
53 | }
54 | local options = {
55 | url = url,
56 | redirect = false,
57 | method = 'GET'
58 | }
59 | local response = { protocols[parsed_url.scheme].request(options) }
60 | local code = tonumber(response[2])
61 |
62 | if code == nil or code >= 400 then
63 | return false
64 | end
65 | return true
66 | end
67 |
68 | local function isup(url)
69 | local pattern = '^(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?):?(%d?%d?%d?%d?%d?)$'
70 | local ip,port = string.match(url, pattern)
71 | local result = nil
72 |
73 | -- !isup 8.8.8.8:53
74 | if ip then
75 | port = port or '80'
76 | result = is_up_socket(ip, port)
77 | else
78 | result = is_up_http(url)
79 | end
80 |
81 | return result
82 | end
83 |
84 | local function cron()
85 | for chan, urls in pairs(cronned) do
86 | for k,url in pairs(urls) do
87 | print('Checking', url)
88 | if not isup(url) then
89 | local text = url .. ' looks DOWN from here. 😱'
90 | send_msg(chan, text, ok_cb, false)
91 | end
92 | end
93 | end
94 | end
95 |
96 | local function run(msg, matches)
97 | if matches[1] == 'cron delete' then
98 | if not is_sudo(msg) then
99 | send_message(msg, 'This command requires privileged user', 'html')
100 | end
101 | return save_cron(msg, matches[2], true)
102 | elseif matches[1] == 'cron' then
103 | if not is_sudo(msg) then
104 | send_message(msg, 'This command requires privileged user', 'html')
105 | end
106 | return save_cron(msg, matches[2])
107 | elseif isup(matches[1]) then
108 | send_message(msg, matches[1] .. ' looks UP from here. 😃', 'html')
109 | else
110 | send_message(msg, matches[1] .. ' looks DOWN from here. 😱', 'html')
111 | end
112 | end
113 |
114 | return {
115 | description = 'Check if a website or server is up.',
116 | usage = {
117 | '!isup [host]
',
118 | 'Performs a HTTP request or Socket (ip:port) connection',
119 | '',
120 | '!isup cron [host]
',
121 | 'Every 5mins check if host is up. (Requires privileged user)',
122 | '',
123 | '!isup cron delete [host]
',
124 | 'Disable checking that host.'
125 | },
126 | patterns = {
127 | '^!isup (cron delete) (.*)$',
128 | '^!isup (cron) (.*)$',
129 | '^!isup (.*)$',
130 | '^!ping (.*)$',
131 | '^!ping (cron delete) (.*)$',
132 | '^!ping (cron) (.*)$'
133 | },
134 | run = run,
135 | cron = cron
136 | }
137 |
138 | end
139 |
--------------------------------------------------------------------------------
/plugins/kaskus.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local kaskus_forums = {
4 | [8] = "Lounge Video",
5 | [9] = "Jokes & Cartoon",
6 | [10] = "Berita dan Politik",
7 | [11] = "Movies",
8 | [13] = "Website, Webmaster, Webdeveloper",
9 | [14] = "CCPB - Shareware & Freeware",
10 | [15] = "Disturbing Picture",
11 | [16] = "Heart to Heart",
12 | [18] = "Can You Solve This Game?",
13 | [19] = "Computer Stuff",
14 | [21] = "The Lounge",
15 | [22] = "Buat Latihan Posting",
16 | [23] = "Supranatural",
17 | [24] = "Lifestyle",
18 | [26] = "Anime & Manga Haven",
19 | [28] = "Otomotif",
20 | [29] = "Cooking & Resto Guide",
21 | [30] = "Bisnis",
22 | [31] = "Kritik, Saran, Pertanyaan Seputar KASKUS",
23 | [32] = "Hewan Peliharaan",
24 | [33] = "Music",
25 | [34] = "All About Design",
26 | [35] = "Sports",
27 | [37] = "Ragnarok Online",
28 | [38] = "Web-based Games",
29 | [39] = "Girls & Boys's Corner",
30 | [44] = "Games",
31 | [49] = "B-Log Collections",
32 | [50] = "Poetry",
33 | [51] = "Stories from the Heart",
34 | [54] = "Photography",
35 | [58] = "KaskusRadio.com",
36 | [59] = "Gosip Nyok!",
37 | [60] = "Selera Nusantara (Indonesian Food)",
38 | [61] = "The Rest of the World (International Food)",
39 | [62] = "Oriental Exotic (Asian food)",
40 | [63] = "The KASKUS Bar",
41 | [65] = "Linux dan OS Selain Microsoft & Mac",
42 | [66] = "Buku",
43 | [67] = "Education",
44 | [70] = "Model Kit & R",
45 | [73] = "Indonesia",
46 | [74] = "Australia",
47 | [76] = "USA",
48 | [77] = "Singapore",
49 | [78] = "DKI Jakarta",
50 | [79] = "Melbourne",
51 | [80] = "Sydney",
52 | [81] = "Palembang",
53 | [82] = "Germany",
54 | [83] = "Canada",
55 | [84] = "Yogyakarta",
56 | [85] = "Netherlands",
57 | [87] = "Music Review",
58 | [88] = "Help, Tips & Tutorial",
59 | [89] = "Bandung",
60 | [90] = "Malaysia",
61 | [91] = "Kalimantan Timur - Kalimantan Utara",
62 | [92] = "Surabaya",
63 | [93] = "Medan",
64 | [94] = "Health",
65 | [96] = "East USA",
66 | [97] = "Bangka - Belitung",
67 | [98] = "Outdoor Adventure & Nature Clubs",
68 | [100] = "Online Games",
69 | [103] = "Welcome to KASKUS",
70 | [104] = "Soccer & Futsal Room",
71 | [105] = "Ask da Girls",
72 | [106] = "Perth",
73 | [107] = "Bogor",
74 | [108] = "Japan",
75 | [109] = "Asia",
76 | [111] = "Semarang",
77 | [112] = "Kendaraan Roda 2",
78 | [113] = "English",
79 | [114] = "Ask da Boys",
80 | [115] = "China",
81 | [116] = "AMHelpdesk",
82 | [117] = "Riau Raya",
83 | [119] = "Console & Handheld Games",
84 | [122] = "Western Comic",
85 | [123] = "Mamalia",
86 | [124] = "Burung",
87 | [125] = "Reptil",
88 | [126] = "Saltwater Fish",
89 | [127] = "Freshwater Fish",
90 | [129] = "United Kingdom",
91 | [133] = "Malang",
92 | [136] = "Feedback & Testimonial",
93 | [137] = "Music Corner",
94 | [140] = "Militer",
95 | [144] = "Martial Arts",
96 | [145] = "Lampung",
97 | [146] = "Kalimantan Selatan",
98 | [151] = "Gratisan",
99 | [156] = "Minangkabau",
100 | [157] = "Europe",
101 | [158] = "Regional Lainnya",
102 | [160] = "Solo",
103 | [161] = "Aceh",
104 | [162] = "Kalimantan Barat",
105 | [164] = "Banten",
106 | [166] = "Palu",
107 | [167] = "Bali",
108 | [170] = "Makassar",
109 | [171] = "TV",
110 | [173] = "Spiritual",
111 | [176] = "Programmer Forum",
112 | [177] = "KASKUS Plus Lounge",
113 | [179] = "Manado",
114 | [181] = "Banyumas",
115 | [183] = "Internet Service & Networking",
116 | [186] = "Lounge Pictures",
117 | [187] = "Airsoft Indonesia",
118 | [188] = "Surat Pembaca",
119 | [191] = "Debate Club",
120 | [192] = "Tanaman",
121 | [193] = "Wedding & Family",
122 | [194] = "Timur Tengah",
123 | [195] = "Elektronik",
124 | [196] = "Fashion & Mode",
125 | [197] = "Flora & Fauna",
126 | [198] = "Koleksi, Hobi & Mainan",
127 | [199] = "Properti",
128 | [200] = "CD & DVD",
129 | [201] = "Makanan & Minuman",
130 | [202] = "Jasa",
131 | [204] = "Jual Beli Feedback & Testimonials",
132 | [205] = "Otomotif",
133 | [210] = "Handphone & Gadget",
134 | [220] = "Alat-Alat Olahraga",
135 | [221] = "Alat-Alat Musik",
136 | [227] = "Buku",
137 | [229] = "Obat-obatan",
138 | [234] = "Arsitektur",
139 | [235] = "Travellers",
140 | [236] = "Muscle Building",
141 | [239] = "KASKUS Corner",
142 | [240] = "KASKUS Peduli",
143 | [243] = "Hardware Computer",
144 | [244] = "Latest Release",
145 | [246] = "Sejarah & Xenology",
146 | [247] = "Civitas Academica",
147 | [248] = "Restaurant Review",
148 | [249] = "Inspirasi",
149 | [250] = "Berita Luar Negeri",
150 | [251] = "Plamo",
151 | [252] = "Figures",
152 | [253] = "Gallery & Tutorial",
153 | [263] = "KASKUS Celeb",
154 | [270] = "Blacklist Jual Beli",
155 | [271] = "Official Testimonials Jual Beli",
156 | [272] = "Tips & Tutorial Jual Beli",
157 | [273] = "Product Review",
158 | [274] = "Fat-loss,Gain-Mass,Nutrisi Diet & Suplementasi Fitness",
159 | [275] = "Nightlife & Events",
160 | [276] = "Grappling",
161 | [277] = "Entrepreneur Corner",
162 | [278] = "Lowongan Kerja",
163 | [279] = "Forex, Option, Saham, & Derivatifnya",
164 | [280] = "HYIP",
165 | [281] = "Electronics",
166 | [282] = "Audio & Video",
167 | [283] = "Antik",
168 | [284] = "Karya Seni & Desain",
169 | [286] = "Industri & Supplier",
170 | [293] = "Kamera & Aksesoris",
171 | [295] = "Perawatan Tubuh & Wajah",
172 | [296] = "Furniture",
173 | [297] = "Video Games",
174 | [298] = "Perkakas",
175 | [299] = "Kerajinan Tangan",
176 | [300] = "Perlengkapan Kantor",
177 | [303] = "Perlengkapan Rumah Tangga",
178 | [304] = "Tur & Paket Perjalanan",
179 | [305] = "Perlengkapan Anak & Bayi",
180 | [306] = "Fashion",
181 | [316] = "Tiket",
182 | [317] = "Komputer",
183 | [331] = "Pictures",
184 | [332] = "Racing",
185 | [382] = "KASKUS Theater",
186 | [383] = "Macintosh",
187 | [384] = "Brisbane",
188 | [385] = "Kalimantan Tengah",
189 | [386] = "Music Event",
190 | [387] = "Ilmu Marketing",
191 | [388] = "Ilmu Marketing & Research",
192 | [389] = "Budaya",
193 | [390] = "TokuSenKa",
194 | [391] = "REQUEST @ CCPB",
195 | [392] = "Hobby & Community",
196 | [393] = "Pisau",
197 | [394] = "Sepeda",
198 | [395] = "Mancing",
199 | [397] = "ISP",
200 | [398] = "Kepulauan Riau",
201 | [402] = "Tegal",
202 | [403] = "Karesidenan Besuki",
203 | [405] = "Bekasi",
204 | [407] = "Depok",
205 | [411] = "Karesidenan Madiun",
206 | [412] = "Cirebon",
207 | [419] = "Taman Bacaan CCPB",
208 | [421] = "Visit USA",
209 | [423] = "West USA",
210 | [425] = "Central USA",
211 | [427] = "Papua",
212 | [429] = "Kids & Parenting",
213 | [431] = "Anime",
214 | [433] = "Manga, Manhua, & Manhwa",
215 | [435] = "KASKUS Promo",
216 | [437] = "Domestik",
217 | [439] = "Mancanegara",
218 | [440] = "Basketball",
219 | [442] = "Templates & Scripts Stuff",
220 | [443] = "Hosting Stuff",
221 | [452] = "Karesidenan Kediri",
222 | [457] = "Jawa Tengah & Yogyakarta",
223 | [458] = "Jawa Barat, Jakarta & Banten",
224 | [459] = "Jawa Timur & Bali",
225 | [460] = "Sumatera",
226 | [461] = "Kalimantan",
227 | [462] = "Sulawesi",
228 | [463] = "Indonesia Timur",
229 | [464] = "English Education & Literature",
230 | [465] = "Fun with English",
231 | [466] = "Penawaran Kerjasama & Investasi",
232 | [467] = "Forex",
233 | [468] = "Options",
234 | [469] = "Saham",
235 | [470] = "Forex Broker",
236 | [471] = "The Online Business",
237 | [472] = "MLM, Member Get Member, & Sejenisnya",
238 | [473] = "Gathering, Event Report & Bakti Sosial",
239 | [474] = "Event from Kaskuser",
240 | [475] = "Others",
241 | [476] = "Others",
242 | [477] = "Others",
243 | [478] = "Others",
244 | [479] = "Cinta Indonesiaku",
245 | [480] = "Arsitektur",
246 | [481] = "Kuliner",
247 | [482] = "Pakaian",
248 | [483] = "Lagu, Tarian & Alat Musik",
249 | [484] = "Kerajinan & Ukiran",
250 | [485] = "Kekayaan Alam, Flora & Fauna",
251 | [486] = "Kesusastraan, Bahasa & Dongeng",
252 | [487] = "Tata Cara Adat, Upacara & Ritual",
253 | [488] = "Permainan Rakyat",
254 | [489] = "Seni Peran",
255 | [490] = "Properti Sejarah Nasional",
256 | [491] = "Mobile Broadband",
257 | [528] = "PC Games",
258 | [537] = "Sport Games",
259 | [538] = "Racket",
260 | [539] = "Badminton",
261 | [540] = "Korea Selatan",
262 | [542] = "Pahlawan & Tokoh Nasional",
263 | [543] = "Batam",
264 | [544] = "Young on Top KASKUS Community (YOTKC)",
265 | [545] = "Pro Wrestling",
266 | [546] = "Dunia Kerja & Profesi",
267 | [548] = "Jambi",
268 | [552] = "Fanstuff",
269 | [555] = "Jember",
270 | [557] = "Hardware Review Lab",
271 | [558] = "Fitness & Healthy Body",
272 | [559] = "Quit Smoking, Alcohol & Drugs",
273 | [561] = "Kendari",
274 | [564] = "Karesidenan Pati",
275 | [565] = "FutSal",
276 | [566] = "Surat Terbuka Jual Beli",
277 | [567] = "Gresik",
278 | [568] = "Mac OSX Info & Discussion",
279 | [569] = "Mac Applications & Games",
280 | [570] = "Kendaraan Roda 4",
281 | [571] = "The Exclusive Business Club (ExBC)",
282 | [572] = "Penawaran Kerjasama, BO, Distribusi, Reseller, & Agen",
283 | [575] = "Militer dan Kepolisian",
284 | [576] = "Kepolisian",
285 | [578] = "Teknik",
286 | [579] = "Sipil",
287 | [580] = "Radio Komunikasi",
288 | [581] = "Lampu Senter",
289 | [583] = "Mojokerto",
290 | [584] = "Gorontalo",
291 | [585] = "Sukabumi",
292 | [586] = "Bengkulu",
293 | [587] = "Bromo",
294 | [588] = "Online Gaming",
295 | [591] = "Jam",
296 | [594] = "Melek Hukum",
297 | [595] = "UKM",
298 | [596] = "Catatan Perjalanan OANC",
299 | [597] = "Sains & Teknologi",
300 | [598] = "Klaten",
301 | [599] = "Tasikmalaya",
302 | [614] = "Serba Serbi",
303 | [615] = "Bisnis",
304 | [617] = "America",
305 | [619] = "Indie Filmmaker",
306 | [620] = "Perencanaan Keuangan",
307 | [621] = "Film Indonesia",
308 | [626] = "Karawang",
309 | [627] = "Karesidenan Kedu",
310 | [628] = "Sidoarjo",
311 | [629] = "KASKUS Playground",
312 | [629] = "Official Forum",
313 | [630] = "Green Lifestyle",
314 | [633] = "Pemilu & Pilkada",
315 | [651] = "Karesidenan Bojonegoro",
316 | [652] = "Madura",
317 | [653] = "Cilacap",
318 | [654] = "Garut",
319 | [655] = "Komik & Ilustrasi",
320 | [662] = "Jual Beli Latihan Posting",
321 | [663] = "Jual Beli Zone",
322 | [668] = "Suara KASKUSers",
323 | [669] = "Cerita Pejalan Domestik",
324 | [670] = "Cerita Pejalan Mancanegara",
325 | [671] = "Animasi",
326 | [673] = "Home Appliance",
327 | [674] = "Vaporizer",
328 | [675] = "Ngampus di KASKUS",
329 | [677] = "KASKUS Online Bazaar",
330 | [678] = "Gemstone",
331 | [679] = "Gemstone",
332 | [683] = "Koleksi Idola",
333 | [684] = "MotoGP",
334 | [685] = "F1",
335 | [689] = "Stand Up Comedy",
336 | [708] = "Indonesia Pusaka",
337 | [709] = "Private Servers",
338 | [710] = "Moba",
339 | [711] = "Dota 2",
340 | [712] = "Pilkada",
341 | [713] = "Deals",
342 | [715] = "Sista",
343 | [716] = "Fashionista",
344 | [717] = "Beauty",
345 | [718] = "Women’s Health",
346 | [723] = "Liga Mahasiswa ( Lima )",
347 | [724] = "Health Consultation",
348 | [725] = "Healthy Lifestyle",
349 | [726] = "Liga Mahasiswa ( Lima )",
350 | [727] = "Deals",
351 | [729] = "Official Merchandise",
352 | [730] = "Metrotvnews.com",
353 | [731] = "Berita Olahraga",
354 | [732] = "Berita Dunia Hiburan",
355 | [733] = "Citizen Journalism",
356 | [734] = "B-Log Personal",
357 | [735] = "B-Log Community",
358 | [736] = "Series & Film Asia",
359 | [737] = "Reksa Dana",
360 | }
361 |
362 | local url = 'http://m.kaskus.co.id/'
363 |
364 | local function escape_html(text)
365 | local text = text:gsub('<', '<')
366 | local text = text:gsub('>', '>')
367 | local text = text:gsub('"', '"')
368 | local text = text:gsub("'", '&apos')
369 | return text
370 | end
371 |
372 | local function get_hot_thread(msg)
373 | local hot, code = http.request(url .. 'forum/hotthread/index/?ref=forumlanding&med=hot_thread')
374 | local hotblock = hot:match('Hot Threads
.-Kembali ke Atas')
375 | local hotthread = {}
376 |
377 | i = 0
378 | for hotgrab in hotblock:gmatch('' .. hotgrab:gsub('<.->', '') .. ''
381 | end
382 |
383 | local hotthread = table.concat(hotthread, '\n')
384 | local header = 'Kaskus Hot Thread\n\n'
385 |
386 | bot_sendMessage(get_receiver_api(msg), header .. hotthread, true, msg.id, 'html')
387 | end
388 |
389 | local function get_kaskus_thread(msg, forum_id)
390 | local res, kaskus = http.request(url .. 'forum/' .. forum_id)
391 | local kasthread = {}
392 |
393 | i = 0
394 | for grabbedlink in res:gmatch(' thread_.- ', '')
399 | kasthread[i] = '' .. i .. '. ' .. lema .. '" di kbbi.web.id' , 'html')
40 | return
41 | end
42 |
43 | if #matches == 2 then
44 | kbbi_desc = res:match('%-%- ' .. matches[2] .. '.-
')
45 | title = '' .. matches[1] .. '\n\n'
46 | else
47 | local grabbedlema = res:match('{"x":1,"w":.-}')
48 | local jlema = json:decode(grabbedlema)
49 | title = '' .. jlema.w .. '\n\n'
50 |
51 | if jlema.d:match('
') then
52 | kbbi_desc = jlema.d:match('^.-
')
53 | else
54 | kbbi_desc = jlema.d
55 | end
56 | end
57 | print(cleanup_tag(title .. kbbi_desc))
58 | bot_sendMessage(get_receiver_api(msg), cleanup_tag(title .. kbbi_desc), true, msg.id, 'html')
59 | end
60 |
61 | --------------------------------------------------------------------------------
62 |
63 | return {
64 | description = 'Kamus Besar Bahasa Indonesia dari http://kbbi.web.id.',
65 | usage = {
66 | '!kbbi [lema]
',
67 | 'Menampilkan arti dari [lema]
'
68 | },
69 | patterns = {
70 | '^!kbbi (%w+)$',
71 | '^!kbbi (%w+) (%w+)$'
72 | },
73 | run = run
74 | }
75 |
76 | end
77 |
--------------------------------------------------------------------------------
/plugins/logger.lua:
--------------------------------------------------------------------------------
1 | --[[
2 |
3 | Save message as json format to a log file.
4 | Remove phone number, in case if there is phone contact in bots account.
5 | Basically, it's just dumping the message into a file. So, don't be surprised
6 | if the log file size is big.
7 |
8 | --]]
9 |
10 | do
11 |
12 | local function pre_process(msg)
13 | local gid = tonumber(msg.to.peer_id)
14 |
15 | if _config.administration[gid] and is_chat_msg(msg) then
16 | local message = serpent.dump(msg, {comment=false})
17 | local message = message:match('do local _=(.*);return _;end')
18 | local message = message:gsub('phone="%d+"', '')
19 | local logfile = io.open('data/' .. gid .. '/' .. gid .. '.log', 'a')
20 | logfile:write(message .. '\n')
21 | logfile:close()
22 | end
23 |
24 | return msg
25 | end
26 |
27 | local function run(msg, matches)
28 | local uid = msg.from.peer_id
29 | local loggid = msg.to.peer_id
30 | local receiver = get_receiver(msg)
31 |
32 | if matches[2] and matches[2]:match('^%d+$') then
33 | loggid = matches[2]
34 | end
35 |
36 | if is_owner(msg, loggid, uid) then
37 | if matches[1] == 'get' then
38 | send_document(receiver, './data/' .. loggid .. '/' .. loggid .. '.log', ok_cb, false)
39 | elseif matches[1] == 'pm' then
40 | send_document('user#id' .. uid, './data/' .. loggid .. '/' .. loggid .. '.log', ok_cb, false)
41 | end
42 | else
43 | reply_msg(msg.id, 'You have no privilege to get ' .. loggid .. ' log.', ok_cb, true)
44 | end
45 | end
46 |
47 | --------------------------------------------------------------------------------
48 |
49 | return {
50 | description = 'Logging group messages.',
51 | usage = {
52 | sudo = {
53 | '!log get [chat_id]
',
54 | 'Send chat_id
chat log.',
55 | '',
56 | '!log pm [chat_id]
',
57 | 'Send chat_id
chat log to private message'
58 | },
59 | owner = {
60 | '!log get
',
61 | 'Send chat log to its chat group',
62 | '',
63 | '!log pm
',
64 | 'Send chat log to private message'
65 | },
66 | },
67 | patterns = {
68 | '^!log (%a+)$',
69 | '^!log (%a+) (%d+)$'
70 | },
71 | run = run,
72 | pre_process = pre_process,
73 | }
74 |
75 | end
76 |
--------------------------------------------------------------------------------
/plugins/patterns.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function regex(msg, text)
4 | local patterns = msg.text:match('s/.*')
5 | local m1, m2 = patterns:match('^s/(.-)/(.-)/?$')
6 |
7 | if not m2 or m2:match('\n') then
8 | return
9 | end
10 |
11 | local substring = text:gsub(m1, m2)
12 |
13 | send_message(msg, 'Did you mean:\n"' .. substring:sub(1, 4000) .. '"', 'html')
14 | end
15 |
16 | local function patterns_by_reply(extra, success, result)
17 | local text = result.text or ''
18 | regex(extra, text)
19 | end
20 |
21 | local function run(msg, matches)
22 | if msg.reply_id then
23 | get_message(msg.reply_id, patterns_by_reply, msg)
24 | end
25 | if msg.from.api and msg.reply_to_message then
26 | regex(msg, msg.reply_to_message.text)
27 | end
28 | end
29 |
30 | return {
31 | description = 'Replace patterns in a message.',
32 | usage = {
33 | '/s/from/to/
',
34 | '/s/from/to
',
35 | 's/from/to
',
36 | '!s/from/to/
',
37 | '!s/from/to
',
38 | 'Replace from
with to
'
39 | },
40 | patterns = {
41 | '^/?s/.-/.-/?$',
42 | '^!s/.-/.-/?$'
43 | },
44 | run = run
45 | }
46 |
47 | end
48 |
--------------------------------------------------------------------------------
/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, msg)
25 | local text = ''
26 | local psum = 0
27 | for k, v in pairs(plugins_names()) do
28 | -- ✅ enabled, ❌ disabled
29 | local status = '❌'
30 | psum = psum+1
31 | pact = 0
32 | -- Check if is enabled
33 | for k2, v2 in pairs(_config.enabled_plugins) do
34 | if v == v2 .. '.lua' then
35 | status = '✅'
36 | end
37 | pact = pact+1
38 | end
39 | if not only_enabled or status == '✅' then
40 | -- get the name
41 | v = v:match('(.*)%.lua')
42 | text = text .. status .. ' ' .. v .. '\n'
43 | end
44 | end
45 | local text = text .. '\n' .. psum .. ' plugins installed.\n'
46 | .. '✅ ' .. pact .. ' enabled.\n❌ ' .. psum-pact .. ' disabled.'
47 | reply_msg(msg.id, text, ok_cb, true)
48 | end
49 |
50 | local function reload_plugins(only_enabled, msg)
51 | plugins = {}
52 | load_plugins()
53 | return list_plugins(true, msg)
54 | end
55 |
56 | --------------------------------------------------------------------------------
57 |
58 | local function run(msg, matches)
59 | local plugin = matches[2]
60 | local receiver = get_receiver(msg)
61 |
62 | if is_sudo(msg.from.peer_id) then
63 |
64 | -- Enable a plugin
65 | if not matches[3] then
66 | if matches[1] == 'enable' then
67 | print("enable: " .. plugin)
68 | print('checking if ' .. plugin .. ' exists')
69 |
70 | -- Check if plugin is enabled
71 | if plugin_enabled(plugin) then
72 | reply_msg(msg.id, 'Plugin ' .. plugin .. ' is enabled', ok_cb, true)
73 | end
74 |
75 | -- Checks if plugin exists
76 | if plugin_exists(plugin) then
77 | -- Add to the config table
78 | table.insert(_config.enabled_plugins, plugin)
79 | print(plugin .. ' added to _config table')
80 | save_config()
81 | -- Reload the plugins
82 | return reload_plugins(false, msg)
83 | else
84 | reply_msg(msg.id, 'Plugin ' .. plugin .. ' does not exists', ok_cb, true)
85 | end
86 | end
87 |
88 | -- Disable a plugin
89 | if matches[1] == 'disable' then
90 | print("disable: " .. plugin)
91 |
92 | -- Check if plugins exists
93 | if not plugin_exists(plugin) then
94 | reply_msg(msg.id, 'Plugin ' .. plugin .. ' does not exists', ok_cb, true)
95 | end
96 |
97 | local k = plugin_enabled(plugin)
98 | -- Check if plugin is enabled
99 | if not k then
100 | reply_msg(msg.id, 'Plugin ' .. plugin .. ' not enabled', ok_cb, true)
101 | end
102 |
103 | -- Disable and reload
104 | table.remove(_config.enabled_plugins, k)
105 | save_config( )
106 | return reload_plugins(true, msg)
107 | end
108 | end
109 |
110 | -- Reload all the plugins!
111 | if matches[1] == 'reload' then
112 | return reload_plugins(false, msg)
113 | end
114 | end
115 |
116 | if is_mod(msg, msg.to.peer_id, msg.from.peer_id) then
117 | -- Show the available plugins
118 | if matches[1] == '!plugins' then
119 | return list_plugins(false, msg)
120 | end
121 |
122 | -- Re-enable a plugin for this chat
123 | if matches[3] == 'chat' then
124 | if matches[1] == 'enable' then
125 | print('enable ' .. plugin .. ' on this chat')
126 | if not _config.disabled_plugin_on_chat then
127 | reply_msg(msg.id, "There aren't any disabled plugins", ok_cb, true)
128 | end
129 |
130 | if not _config.disabled_plugin_on_chat[receiver] then
131 | reply_msg(msg.id, "There aren't any disabled plugins for this chat", ok_cb, true)
132 | end
133 |
134 | if not _config.disabled_plugin_on_chat[receiver][plugin] then
135 | reply_msg(msg.id, 'This plugin is not disabled', ok_cb, true)
136 | end
137 |
138 | _config.disabled_plugin_on_chat[receiver][plugin] = false
139 | save_config()
140 | reply_msg(msg.id, 'Plugin ' .. plugin .. ' is enabled again', ok_cb, true)
141 | end
142 |
143 | -- Disable a plugin on a chat
144 | if matches[1] == 'disable' then
145 | print('disable ' .. plugin .. ' on this chat')
146 | if not plugin_exists(plugin) then
147 | reply_msg(msg.id, "Plugin doesn't exists", ok_cb, true)
148 | end
149 |
150 | if not _config.disabled_plugin_on_chat then
151 | _config.disabled_plugin_on_chat = {}
152 | end
153 |
154 | if not _config.disabled_plugin_on_chat[receiver] then
155 | _config.disabled_plugin_on_chat[receiver] = {}
156 | end
157 |
158 | _config.disabled_plugin_on_chat[receiver][plugin] = true
159 | save_config()
160 | reply_msg(msg.id, 'Plugin ' .. plugin .. ' disabled on this chat', ok_cb, true)
161 | end
162 | end
163 | end
164 | end
165 |
166 | --------------------------------------------------------------------------------
167 |
168 | return {
169 | description = 'Plugin to manage other plugins. Enable, disable or reload.',
170 | usage = {
171 | sudo = {
172 | '!plugins enable [plugin]
',
173 | 'Enable plugin.',
174 | '',
175 | '!plugins disable [plugin]
',
176 | 'Disable plugin.',
177 | '',
178 | '!plugins reload
',
179 | 'Reloads all plugins.'
180 | },
181 | moderator = {
182 | '!plugins
',
183 | 'List all plugins.',
184 | '',
185 | '!plugins enable [plugin] chat
',
186 | 'Re-enable plugin only this chat.',
187 | '',
188 | '!plugins disable [plugin] chat
',
189 | 'Disable plugin only this chat.'
190 | },
191 | },
192 | patterns = {
193 | '^!plugins$',
194 | '^!plugins? (enable) ([%w_%.%-]+)$',
195 | '^!plugins? (disable) ([%w_%.%-]+)$',
196 | '^!plugins? (enable) ([%w_%.%-]+) (chat)$',
197 | '^!plugins? (disable) ([%w_%.%-]+) (chat)$',
198 | '^!plugins? (reload)$'
199 | },
200 | run = run
201 | }
202 |
203 | end
204 |
--------------------------------------------------------------------------------
/plugins/quran.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local surah_name = {
4 | [1] = "Al-Fatihah",
5 | [2] = "Al-Baqarah",
6 | [3] = "Ali-Imran",
7 | [4] = "An-Nisaa'",
8 | [5] = "Al-Maaidah",
9 | [6] = "Al-An'aam",
10 | [7] = "Al-A'raaf",
11 | [8] = "Al-Anfaal",
12 | [9] = "At-Taubah",
13 | [10] = "Yunus",
14 | [11] = "Huud",
15 | [12] = "Yusuf",
16 | [13] = "Ar-Ra'd",
17 | [14] = "Ibrahim",
18 | [15] = "Al-Hijr",
19 | [16] = "An-Nahl",
20 | [17] = "Al-Israa'",
21 | [18] = "Al-Kahfi",
22 | [19] = "Maryam",
23 | [20] = "Thaahaa",
24 | [21] = "Al-Anbiyaa'",
25 | [22] = "Al-Hajj",
26 | [23] = "Al-Mukminuun",
27 | [24] = "An-Nuur",
28 | [25] = "Al-Furqaan",
29 | [26] = "Ash-Shu'araa",
30 | [27] = "An-Naml",
31 | [28] = "Al-Qashash",
32 | [29] = "Al-Ankabuut",
33 | [30] = "Ar-Ruum",
34 | [31] = "Luqman",
35 | [32] = "As-Sajdah",
36 | [33] = "Al-Ahzaab",
37 | [34] = "Saba'",
38 | [35] = "Faathir",
39 | [36] = "Yasiin",
40 | [37] = "As-Shaaffaat",
41 | [38] = "Shaad",
42 | [39] = "Az-Zumar",
43 | [40] = "Al-Ghaafir",
44 | [41] = "Fushshilat",
45 | [42] = "Asy-Syuura",
46 | [43] = "Az-Zukhruf",
47 | [44] = "Ad-Dukhaan",
48 | [45] = "Al-Jaatsiyah",
49 | [46] = "Al-Ahqaaf",
50 | [47] = "Muhammad",
51 | [48] = "Al-Fath",
52 | [49] = "Al-Hujuraat",
53 | [50] = "Qaaf",
54 | [51] = "Adz-Dzaariyat",
55 | [52] = "Ath-Thur",
56 | [53] = "An-Najm",
57 | [54] = "Al-Qamar",
58 | [55] = "Ar-Rahmaan",
59 | [56] = "Al-Waaqi'ah",
60 | [57] = "Al-Hadiid",
61 | [58] = "Al-Mujaadilah",
62 | [59] = "Al-Hasyr",
63 | [60] = "Al-Mumtahanah",
64 | [61] = "Ash-Shaff",
65 | [62] = "Al-Jumu'ah",
66 | [63] = "Al-Munaafiquun",
67 | [64] = "At-Taghaabuun",
68 | [65] = "Ath-Thaalaq",
69 | [66] = "At-Tahrim",
70 | [67] = "Al-Mulk",
71 | [68] = "Al-Qalam",
72 | [69] = "Al-Haaqqah",
73 | [70] = "Al-Ma'aarij",
74 | [71] = "Nuuh",
75 | [72] = "Al-Jin",
76 | [73] = "Al-Muzzammil",
77 | [74] = "Al-Muddatstsir",
78 | [75] = "Al-Qiyaamah",
79 | [76] = "Al-Insaan",
80 | [77] = "Al-Mursalaat",
81 | [78] = "An-Naba'",
82 | [79] = "An-Naazi'aat",
83 | [80] = "'Abasa",
84 | [81] = "At-Takwir",
85 | [82] = "Al-Infithaar",
86 | [83] = "Al-Mutaffifin",
87 | [84] = "Al-Insyiqaaq",
88 | [85] = "Al-Buruuj",
89 | [86] = "Ath-Thaariq",
90 | [87] = "Al-A'laa",
91 | [88] = "Al-Ghaashiyah",
92 | [89] = "Al-Fajr",
93 | [90] = "Al-Balad",
94 | [91] = "Asy-Syams",
95 | [92] = "Al-Lail",
96 | [93] = "Ad-Dhuhaa",
97 | [94] = "Alam Nasyrah",
98 | [95] = "At-Tiin",
99 | [96] = "Al-'Alaq",
100 | [97] = "Al-Qadr",
101 | [98] = "Al-Bayyinah",
102 | [99] = "Al-Zalzalah",
103 | [100] = "Al-'Aadiyaat",
104 | [101] = "Al-Qaari'ah",
105 | [102] = "At-Takaatsur",
106 | [103] = "Al-'Ashr",
107 | [104] = "Al-Humazah",
108 | [105] = "Al-Fiil",
109 | [106] = "Quraisy",
110 | [107] = "Al-Maa'uun",
111 | [108] = "Al-Kautsar",
112 | [109] = "Al-Kaafiruun",
113 | [110] = "An-Nashr",
114 | [111] = "Al-Lahab",
115 | [112] = "Al-Ikhlaas",
116 | [113] = "Al-Falaq",
117 | [114] = "An-Naas"
118 | }
119 |
120 | local language = {
121 | ['ar'] = "ar.muyassar",
122 | ['az'] = "az.musayev",
123 | ['bg'] = "bg.theophanov",
124 | ['bn'] = "bn.bengali",
125 | ['bs'] = "bs.mlivo",
126 | ['cs'] = "cs.hrbek",
127 | ['de'] = "de.aburida",
128 | ['dv'] = "dv.divehi",
129 | ['en'] = "en.yusufali",
130 | ['es'] = "es.cortes",
131 | ['fa'] = "fa.makarem",
132 | ['fr'] = "fr.hamidullah",
133 | ['ha'] = "ha.gumi",
134 | ['hi'] = "hi.hindi",
135 | ['id'] = "id.indonesian",
136 | ['it'] = "it.piccardo",
137 | ['ja'] = "ja.japanese",
138 | ['ko'] = "ko.korean",
139 | ['ku'] = "ku.asan",
140 | ['ml'] = "ml.abdulhameed",
141 | ['ms'] = "ms.basmeih",
142 | ['nl'] = "nl.keyzer",
143 | ['no'] = "no.berg",
144 | ['pl'] = "pl.bielawskiego",
145 | ['pt'] = "pt.elhayek",
146 | ['ro'] = "ro.grigore",
147 | ['ru'] = "ru.kuliev",
148 | ['sd'] = "sd.amroti",
149 | ['so'] = "so.abduh",
150 | ['sq'] = "sq.ahmeti",
151 | ['sv'] = "sv.bernstrom",
152 | ['sw'] = "sw.barwani",
153 | ['ta'] = "ta.tamil",
154 | ['tg'] = "tg.ayati",
155 | ['th'] = "th.thai",
156 | ['tr'] = "tr.ozturk",
157 | ['tt'] = "tt.nugman",
158 | ['ug'] = "ug.saleh",
159 | ['ur'] = "ur.ahmedali",
160 | ['uz'] = "uz.sodik",
161 | ['zh'] = "zh.majian"
162 | }
163 |
164 | local function get_verse_num(verse)
165 | for i=1, 6666 do
166 | if verse.quran['quran-simple'][tostring(i)] then
167 | return tostring(i)
168 | end
169 | end
170 | end
171 |
172 | local function get_ayah(msg, surah, ayah, verse, lang)
173 | local gq = 'http://api.globalquran.com/ayah/'
174 | local gq_lang = nil
175 |
176 | if lang then
177 | if language[tostring(lang)] then
178 | translation = language[tostring(lang)]
179 | else
180 | translation = lang
181 | end
182 | end
183 |
184 | if verse then
185 | gq_ayah = gq .. verse .. '/quran-simple?key=' .. _config.api_key.globalquran
186 | if lang then
187 | gq_lang = gq .. verse .. '/' .. translation .. '?key=' .. _config.api_key.globalquran
188 | end
189 | end
190 |
191 | if surah and ayah then
192 | gq_ayah = gq .. surah .. ':' .. ayah .. '/quran-simple?key=' .. _config.api_key.globalquran
193 | if lang then
194 | gq_lang = gq .. surah .. ':' .. ayah .. '/' .. translation .. '?key=' .. _config.api_key.globalquran
195 | end
196 | end
197 |
198 | local verse_trans = ''
199 | local res_ayah, code_ayah = http.request(gq_ayah)
200 | local jayah = json:decode(res_ayah)
201 | local verse_num = get_verse_num(jayah)
202 |
203 |
204 | if gq_lang then
205 | local res_lang, code_lang = http.request(gq_lang)
206 | local jlang = json:decode(res_lang)
207 | if next(jlang.quran) == nil then
208 | send_message(msg, 'Unknown language.\nPlease consult http://api.globalquran.com/quran for full list of the supported languages.', 'html')
209 | return
210 | else
211 | verse_trans = jlang.quran[translation][verse_num].verse
212 | end
213 | end
214 |
215 | local surah_num = jayah.quran['quran-simple'][verse_num].surah
216 | local ayah_num = jayah.quran['quran-simple'][verse_num].ayah
217 | local gq_output = jayah.quran['quran-simple'][verse_num].verse .. '\n\n'
218 | .. verse_trans .. ' (' .. surah_name[surah_num] .. ':' .. ayah_num .. ')'
219 |
220 | send_message(msg, gq_output, 'html')
221 | end
222 |
223 | function run(msg, matches)
224 | check_api_key(msg, 'globalquran', 'http://globalquran.com/contribute/signup.php')
225 |
226 | if matches[1] == 'setapikey globalquran' and is_sudo(msg.from.peer_id) then
227 | _config.api_key.globalquran = matches[2]
228 | save_config()
229 | send_message(msg, 'Global Quran api key has been saved.', 'html')
230 | return
231 | end
232 |
233 | if #matches == 1 then
234 | print('method #1')
235 | get_ayah(msg, nil, nil, matches[1], nil)
236 | end
237 |
238 | if #matches == 2 then
239 | print('method #2')
240 | get_ayah(msg, nil, nil, matches[1], matches[2])
241 | end
242 |
243 | if #matches == 3 then
244 | print('method #3')
245 | get_ayah(msg, matches[1], matches[3], nil, nil)
246 | end
247 |
248 | if #matches == 4 then
249 | print('method #4')
250 | get_ayah(msg, matches[1], matches[3], nil, matches[4])
251 | end
252 | end
253 |
254 | return {
255 | description = "Returns Al Qur'an verse.",
256 | usage = {
257 | sudo = {
258 | '!setapikey globalquran [api_key]
',
259 | 'Set Global Quran API key.'
260 | },
261 | user = {
262 | '!quran [verse number]
',
263 | "Returns Qur'an verse by its number.",
264 | 'Example: !quran 17
',
265 | '',
266 | '!quran [verse number] [lang]
',
267 | "Returns Qur'an verse and its translation.",
268 | 'Example: !quran 17 id
',
269 | '',
270 | '!quran [surah]:[ayah]
',
271 | "Returns Qur'an verse by surah and ayah number.",
272 | 'Example: !quran 17:8
',
273 | '',
274 | '!quran [surah]:[ayah] [lang]
',
275 | "Returns Qur'an verse and its translation by surah and ayah number.",
276 | 'Example: !quran 17:8 id
',
277 | '',
278 | 'lang
is ISO 639-1 language code.'
279 | },
280 | },
281 | patterns = {
282 | '^!quran ([%d]+)$',
283 | '^!quran ([%d]+) (%g.*)$',
284 | '^!quran ([%d]+)(:)([%d]+)$',
285 | '^!quran ([%d]+)(:)([%d]+) (%g.*)$',
286 | '^!(setapikey globalquran) (.*)$'
287 | },
288 | run = run
289 | }
290 |
291 | end
--------------------------------------------------------------------------------
/plugins/reddit.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function run(msg, matches)
4 | local thread_limit = 5
5 | local is_nsfw = false
6 |
7 | if not is_chat_msg(msg) then
8 | thread_limit = 8
9 | end
10 |
11 | if matches[1] == 'nsfw' then
12 | is_nsfw = true
13 | end
14 |
15 | if matches[2] then
16 | if matches[2]:match('^r/') then
17 | url = 'https://www.reddit.com/' .. URL.escape(matches[2]) .. '/.json?limit=' .. thread_limit
18 | else
19 | url = 'https://www.reddit.com/search.json?q=' .. URL.escape(matches[2]) .. '&limit=' .. thread_limit
20 | end
21 | elseif msg.text == '!reddit' then
22 | url = 'https://www.reddit.com/.json?limit=' .. thread_limit
23 | end
24 |
25 | -- Do the request
26 | local res, code = https.request(url)
27 |
28 | if code ~= 200 then
29 | send_message(msg, "There doesn't seem to be anything...", 'html')
30 | end
31 |
32 | local jdat = json:decode(res)
33 | local jdata_child = jdat.data.children
34 |
35 | if #jdata_child == 0 then
36 | return nil
37 | end
38 |
39 | local threadit = {}
40 | local long_url = ''
41 |
42 | for k=1, #jdata_child do
43 | local redd = jdata_child[k].data
44 |
45 | if not redd.is_self then
46 | local link = URL.parse(redd.url)
47 | long_url = '\nLink: ' .. link.scheme .. '://' .. link.host .. ''
48 | end
49 |
50 | local title = unescape_html(redd.title)
51 |
52 | if redd.over_18 and not is_nsfw then
53 | threadit[k] = ''
54 | elseif redd.over_18 and is_nsfw then
55 | threadit[k] = '' .. k .. '. NSFW ' .. '' .. title .. '' .. long_url
56 | else
57 | threadit[k] = '' .. k .. '. ' .. '' .. title .. '' .. long_url
58 | end
59 | end
60 |
61 | local threadit = table.concat(threadit, '\n')
62 | local subreddit = '' .. (matches[2] or 'redd.it') .. '\n\n'
63 | local subreddit = subreddit .. threadit
64 |
65 | if not threadit:match('%w+') then
66 | send_message(msg, 'You must be 18+ to view this community.', 'html')
67 | else
68 | bot_sendMessage(get_receiver_api(msg), subreddit, true, msg.id, 'html')
69 | end
70 | end
71 |
72 | --------------------------------------------------------------------------------
73 |
74 | return {
75 | description = 'Returns the five (if group) or eight (if private message) top posts for the given subreddit or query, or from the frontpage.',
76 | usage = {
77 | '!reddit
',
78 | 'Reddit frontpage.',
79 | '',
80 | '!reddit r/[query]
',
81 | '!r r/[query]
',
82 | 'Subreddit',
83 | 'Example: !r r/linux
',
84 | '',
85 | '!redditnsfw [query]
',
86 | '!rnsfw [query]
',
87 | 'Subreddit (include NSFW).',
88 | 'Example: !rnsfw r/juicyasians
',
89 | '',
90 | '!reddit [query]
',
91 | '!r [query]
',
92 | 'Search subreddit.',
93 | 'Example: !r telegram bot
',
94 | '',
95 | '!redditnsfw [query]
',
96 | '!rnsfw [query]
',
97 | 'Search subreddit (include NSFW).',
98 | 'Example: !rnsfw maria ozawa
',
99 | },
100 | patterns = {
101 | '^!reddit$',
102 | '^!(r) (.*)$',
103 | '^!(reddit) (.*)$',
104 | '^!r(nsfw) (.*)$',
105 | '^!reddit(nsfw) (.*)$'
106 | },
107 | run = run
108 | }
109 |
110 | end
111 |
--------------------------------------------------------------------------------
/plugins/rss.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function get_base_redis(id, option, extra)
4 | local ex = ''
5 |
6 | if option ~= nil then
7 | ex = ex .. ':' .. option
8 | if extra ~= nil then
9 | ex = ex .. ':' .. extra
10 | end
11 | end
12 | return 'rss:' .. id .. ex
13 | end
14 |
15 | local function prot_url(url)
16 | local url, h = url:gsub('http://', '')
17 | local url, hs = url:gsub('https://', '')
18 | local protocol = 'http'
19 |
20 | if hs == 1 then
21 | protocol = 'https'
22 | end
23 | return url, protocol
24 | end
25 |
26 | local function get_rss(url, prot)
27 | local res, code = nil, 0
28 |
29 | if prot == 'http' then
30 | res, code = http.request(url)
31 | elseif prot == 'https' then
32 | res, code = https.request(url)
33 | end
34 | if code ~= 200 then
35 | return nil, 'Error while doing the petition to ' .. url
36 | end
37 |
38 | local parsed = feedparser.parse(res)
39 |
40 | if parsed == nil then
41 | return nil, 'Error decoding the RSS.\nAre you sure that ' .. url .. ' is an RSS?'
42 | end
43 | return parsed, nil
44 | end
45 |
46 | local function get_new_entries(last, nentries)
47 | local entries = {}
48 |
49 | for k,v in pairs(nentries) do
50 | if v.id == last then
51 | return entries
52 | else
53 | table.insert(entries, v)
54 | end
55 | end
56 | return entries
57 | end
58 |
59 | local function print_subs(msg, id)
60 | local subscriber = msg.to.title
61 |
62 | if id:match('user') then
63 | subscriber = 'You'
64 | end
65 |
66 | local uhash = get_base_redis(id)
67 | local subs = redis:smembers(uhash)
68 | local text = subscriber .. ' are subscribed to:\n---------\n'
69 |
70 | for k,v in pairs(subs) do
71 | text = text .. k .. ') ' .. v .. '\n'
72 | end
73 |
74 | reply_msg(msg.id, text, ok_cb, true)
75 | end
76 |
77 | local function subscribe(msg, id, url)
78 | local baseurl, protocol = prot_url(url)
79 | local prothash = get_base_redis(baseurl, 'protocol')
80 | local lasthash = get_base_redis(baseurl, 'last_entry')
81 | local lhash = get_base_redis(baseurl, 'subs')
82 | local uhash = get_base_redis(id)
83 |
84 | if redis:sismember(uhash, baseurl) then
85 | reply_msg(msg.id, 'You are already subscribed to ' .. url, ok_cb, true)
86 | end
87 |
88 | local parsed, err = get_rss(url, protocol)
89 |
90 | if err ~= nil then
91 | return err
92 | end
93 |
94 | local last_entry = ''
95 |
96 | if #parsed.entries > 0 then
97 | last_entry = parsed.entries[1].id
98 | end
99 |
100 | local name = parsed.feed.title
101 |
102 | redis:set(prothash, protocol)
103 | redis:set(lasthash, last_entry)
104 | redis:sadd(lhash, id)
105 | redis:sadd(uhash, baseurl)
106 |
107 | reply_msg(msg.id, 'You had been subscribed to ' .. name, ok_cb, true)
108 | end
109 |
110 | local function unsubscribe(msg, id, n)
111 | if #n > 3 then
112 | reply_msg(msg.id, "I don't think that you have that many subscriptions.", ok_cb, true)
113 | end
114 |
115 | n = tonumber(n)
116 | local uhash = get_base_redis(id)
117 | local subs = redis:smembers(uhash)
118 |
119 | if n < 1 or n > #subs then
120 | reply_msg(msg.id, 'Subscription id out of range!', ok_cb, true)
121 | end
122 |
123 | local sub = subs[n]
124 | local lhash = get_base_redis(sub, 'subs')
125 |
126 | redis:srem(uhash, sub)
127 | redis:srem(lhash, id)
128 |
129 | local left = redis:smembers(lhash)
130 |
131 | if #left < 1 then -- no one subscribed, remove it
132 | local prothash = get_base_redis(sub, 'protocol')
133 | local lasthash = get_base_redis(sub, 'last_entry')
134 | redis:del(prothash)
135 | redis:del(lasthash)
136 | end
137 |
138 | reply_msg(msg.id, 'You had been unsubscribed from ' .. sub, ok_cb, true)
139 | end
140 |
141 | local function cron()
142 | -- sync every 15 mins?
143 | local keys = redis:keys(get_base_redis('*', 'subs'))
144 |
145 | for k,v in pairs(keys) do
146 | local base = v:match('rss:(.+):subs') -- Get the URL base
147 | local prot = redis:get(get_base_redis(base, 'protocol'))
148 | local last = redis:get(get_base_redis(base, 'last_entry'))
149 | local url = prot .. '://' .. base
150 | local parsed, err = get_rss(url, prot)
151 |
152 | if err ~= nil then
153 | return
154 | end
155 |
156 | local newentr = get_new_entries(last, parsed.entries)
157 | local subscribers = {}
158 | local text = '' -- Send only one message with all updates
159 |
160 | for k2, v2 in pairs(newentr) do
161 | local title = v2.title or 'No title'
162 | local link = v2.link or v2.id or 'No Link'
163 | text = text .. k2 .. '. ' .. title .. '\n' .. link .. '\n'
164 | end
165 | if text ~= '' then
166 | local newlast = newentr[1].id
167 | redis:set(get_base_redis(base, 'last_entry'), newlast)
168 | for k2, receiver in pairs(redis:smembers(v)) do
169 | send_msg(receiver, text, ok_cb, false)
170 | end
171 | end
172 | end
173 | end
174 |
175 | --------------------------------------------------------------------------------
176 |
177 | local function run(msg, matches)
178 |
179 | local uid = msg.from.peer_id
180 |
181 | -- comment this line if you want this plugin works for all members.
182 | if not is_owner(msg, msg.to.peer_id , uid) then return nil end
183 |
184 | local id = get_receiver(msg)
185 |
186 | if matches[1] == '!rss'then
187 | print_subs(msg, id)
188 | end
189 | if matches[1] == 'sync' then
190 | if not is_sudo(uid) then
191 | reply_msg(msg.id, 'Only sudo users can sync the RSS.', ok_cb, true)
192 | else
193 | cron()
194 | end
195 | end
196 | if matches[1] == 'subscribe' or matches[1] == 'sub' then
197 | subscribe(msg, id, matches[2])
198 | end
199 | if matches[1] == 'unsubscribe' or matches[1] == 'uns' then
200 | unsubscribe(msg, id, matches[2])
201 | end
202 | end
203 |
204 | --------------------------------------------------------------------------------
205 |
206 |
207 | return {
208 | 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.',
209 | usage = {
210 | admin = {
211 | '!rss
',
212 | 'Get your rss (or chat rss) subscriptions',
213 | '',
214 | '!rss subscribe [url]
',
215 | 'Subscribe to that url',
216 | '',
217 | '!rss unsubscribe [id]
',
218 | 'Unsubscribe of that id',
219 | '',
220 | '!rss sync
',
221 | 'Download now the updates and send it. Only sudo users can use this option.'
222 | },
223 | },
224 | patterns = {
225 | '^!rss$',
226 | '^!rss (subscribe) (https?://[%w-_%.%?%.:/%+=&]+)$',
227 | '^!rss (sub) (https?://[%w-_%.%?%.:/%+=&]+)$',
228 | '^!rss (unsubscribe) (%d+)$',
229 | '^!rss (uns) (%d+)$',
230 | '^!rss (sync)$'
231 | },
232 | run = run,
233 | cron = cron
234 | }
235 |
236 | end
237 |
--------------------------------------------------------------------------------
/plugins/salat.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local base_api = 'http://muslimsalat.com'
4 | local calculation = {
5 | [1] = 'Egyptian General Authority of Survey',
6 | [2] = 'University Of Islamic Sciences, Karachi (Shafi)',
7 | [3] = 'University Of Islamic Sciences, Karachi (Hanafi)',
8 | [4] = 'Islamic Circle of North America',
9 | [5] = 'Muslim World League',
10 | [6] = 'Umm Al-Qura',
11 | [7] = 'Fixed Isha'
12 | }
13 |
14 | local function get_time(lat, lng)
15 | local api = 'https://maps.googleapis.com/maps/api/timezone/json?'
16 | local timestamp = os.time(os.date('!*t'))
17 | local parameters = 'location=' .. URL.escape(lat) .. ',' .. URL.escape(lng)
18 | .. '×tamp=' .. URL.escape(timestamp)
19 | local res,code = https.request(api .. parameters)
20 |
21 | if code ~= 200 then
22 | return nil
23 | end
24 |
25 | local data = json:decode(res)
26 |
27 | if (data.status == 'ZERO_RESULTS') then
28 | return nil
29 | end
30 | if (data.status == 'OK') then
31 | return timestamp + data.rawOffset + data.dstOffset
32 | end
33 | end
34 |
35 | function totwentyfour(twelvehour)
36 | local hour, minute, meridiem = string.match(twelvehour, '^(.-):(.-) (.-)$')
37 | local hour = tonumber(hour)
38 | if (meridiem == 'am') and (hour == 12) then
39 | hour = 0
40 | elseif (meridiem == 'pm') and (hour < 12) then
41 | hour = hour + 12
42 | end
43 |
44 | if hour < 10 then
45 | hour = '0' .. hour
46 | end
47 |
48 | return (hour .. ':' .. minute)
49 | end
50 |
51 | function run(msg, matches)
52 | check_api_key(msg, 'muslimsalat', 'http://muslimsalat.com/panel/signup.php')
53 |
54 | if matches[1] == 'setapikey muslimsalat' and is_sudo(msg.from.peer_id) then
55 | _config.api_key.muslimsalat = matches[2]
56 | save_config()
57 | send_message(msg, 'Muslim salat api key has been saved.', 'html')
58 | return
59 | end
60 |
61 | local area = matches[1]
62 | local method = 5
63 | local notif = ''
64 | local url = base_api .. '/' .. URL.escape(area) .. '.json'
65 |
66 | if matches[2] and matches[1]:match('%d') then
67 | local c_method = tonumber(matches[1])
68 |
69 | if c_method == 0 or c_method > 7 then
70 | local text = 'Calculation method is out of range\n'
71 | .. 'Consult !help salat
'
72 | send_message(msg, text, 'html')
73 | return
74 | else
75 | method = c_method
76 | url = base_api .. '/' .. URL.escape(matches[2]) .. '.json'
77 | notif = '\n\nMethod: ' .. calculation[method]
78 | area = matches[2]
79 | end
80 | end
81 |
82 | local res, code = http.request(url .. '/' .. method .. '?key=' .. _config.api_key.muslimsalat)
83 |
84 | if code ~= 200 then
85 | send_message(msg, 'Error: ' .. code .. '
', 'html')
86 | return
87 | end
88 |
89 | local salat = json:decode(res)
90 | local localTime = get_time(salat.latitude, salat.longitude)
91 |
92 | if salat.title == '' then
93 | salat_area = area .. ', ' .. salat.country
94 | else
95 | salat_area = salat.title
96 | end
97 |
98 | local is_salat_time = 'Salat time\n\n'
99 | .. '' .. salat_area .. '\n\n'
100 | .. 'Time : ' .. os.date('%T', localTime) .. '\n'
101 | .. 'Qibla : ' .. salat.qibla_direction .. '°\n\n'
102 | .. 'Fajr : ' .. totwentyfour(salat.items[1].fajr) .. '\n'
103 | .. 'Sunrise : ' .. totwentyfour(salat.items[1].shurooq) .. '\n'
104 | .. 'Dhuhr : ' .. totwentyfour(salat.items[1].dhuhr) .. '\n'
105 | .. 'Asr : ' .. totwentyfour(salat.items[1].asr) .. '\n'
106 | .. 'Maghrib : ' .. totwentyfour(salat.items[1].maghrib) .. '\n'
107 | .. 'Isha : ' .. totwentyfour(salat.items[1].isha) .. '
' .. notif
108 |
109 | bot_sendMessage(get_receiver_api(msg), is_salat_time, true, msg.id, 'html')
110 | end
111 |
112 | return {
113 | description = 'Returns todays prayer times.',
114 | usage = {
115 | sudo = {
116 | '!setapikey muslimsalat [api_key]
',
117 | 'Set Muslim Salat API key.'
118 | },
119 | user = {
120 | '!salat [area]
',
121 | 'Returns todays prayer times for that area',
122 | 'Example: !salat bandung
',
123 | '',
124 | '!salat [method] [area]
',
125 | 'Returns todays prayer times for that area calculated by [method]
:',
126 | '1 = Egyptian General Authority of Survey',
127 | '2 = University Of Islamic Sciences, Karachi (Shafi)',
128 | '3 = University Of Islamic Sciences, Karachi (Hanafi)',
129 | '4 = Islamic Circle of North America',
130 | '5 = Muslim World League',
131 | '6 = Umm Al-Qura',
132 | '7 = Fixed Isha',
133 | 'Example: !salat 2 denpasar
',
134 | },
135 | },
136 | patterns = {
137 | '^!salat (%a.*)$',
138 | '^!salat (%d) (%a.*)$',
139 | '^!(setapikey muslimsalat) (.*)$'
140 | },
141 | run = run
142 | }
143 |
144 | end
145 |
146 |
--------------------------------------------------------------------------------
/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 | local text = ''
11 |
12 | if user.first_name then
13 | text = user.first_name .. ' '
14 | end
15 | if user.last_name then
16 | text = text .. user.last_name
17 | end
18 | return text
19 | end
20 |
21 | -- Returns a table with `name` and `msgs`
22 | local function get_msgs_user_chat(user_id, chat_id)
23 | local user_info = {}
24 | local uhash = 'user:' .. user_id
25 | local user = redis:hgetall(uhash)
26 | local um_hash = 'msgs:' .. user_id .. ':' .. chat_id
27 | user_info.msgs = tonumber(redis:get(um_hash) or 0)
28 | user_info.id = user_id
29 | user_info.name = user_print_name(user)
30 | return user_info
31 | end
32 |
33 | local function chat_stats(msg, chat_id)
34 | -- Users on chat
35 | local hash = 'chat:' .. chat_id .. ':users'
36 | local users = redis:smembers(hash)
37 | local users_info = {}
38 |
39 | -- Get user info
40 | for i = 1, #users do
41 | local user_id = users[i]
42 | local user_info = get_msgs_user_chat(user_id, chat_id)
43 | table.insert(users_info, user_info)
44 | end
45 |
46 | -- Sort users by msgs number
47 | table.sort(users_info, function(a, b)
48 | if a.msgs and b.msgs then
49 | return a.msgs > b.msgs
50 | end
51 | end)
52 |
53 | local text = ''
54 |
55 | for k,user in pairs(users_info) do
56 | text = text .. '*' .. k .. '*. `' .. user.id .. '` - ' .. markdown_escape(user.name) .. ' = *' .. user.msgs .. '*\n'
57 | end
58 | bot_sendMessage(get_receiver_api(msg), text, true, msg.id, 'markdown')
59 | end
60 |
61 | --------------------------------------------------------------------------------
62 |
63 | -- Save stats, ban user
64 | local function pre_process(msg)
65 | -- Ignore service msg
66 | if msg.service then
67 | print('Service message')
68 | return msg
69 | end
70 |
71 | -- Save user on Redis
72 | if msg.from.peer_type == 'user' then
73 | local hash = 'user:' .. msg.from.peer_id
74 | print('Saving user', hash)
75 | if msg.from.print_name then
76 | redis:hset(hash, 'print_name', msg.from.print_name)
77 | end
78 | if msg.from.first_name then
79 | redis:hset(hash, 'first_name', msg.from.first_name)
80 | end
81 | if msg.from.last_name then
82 | redis:hset(hash, 'last_name', msg.from.last_name)
83 | end
84 | end
85 |
86 | -- Save stats on Redis
87 | if msg.to.peer_type == 'chat' or msg.to.peer_type == 'channel' then
88 | -- User is on chat
89 | local hash = 'chat:' .. msg.to.peer_id .. ':users'
90 | redis:sadd(hash, msg.from.peer_id)
91 | end
92 |
93 | -- Total user msgs
94 | local hash = 'msgs:' .. msg.from.peer_id .. ':' .. msg.to.peer_id
95 | redis:incr(hash)
96 |
97 | -- Check flood
98 | if msg.from.peer_type == 'user' then
99 | local hash = 'user:' .. msg.from.peer_id .. ':msgs'
100 | local msgs = tonumber(redis:get(hash) or 0)
101 |
102 | if msgs > NUM_MSG_MAX then
103 | print('User ' .. msg.from.peer_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 | --------------------------------------------------------------------------------
139 |
140 | local function run(msg, matches)
141 | if is_administrate(msg, msg.to.peer_id) then
142 | if matches[1]:lower() == 'stats' then
143 | if not matches[2] then
144 | if is_chat_msg(msg) then
145 | return chat_stats(msg, msg.to.peer_id)
146 | else
147 | return 'Stats works only on chats'
148 | end
149 | end
150 |
151 | if matches[2] == 'bot' then
152 | if not is_sudo(msg.from.peer_id) then
153 | return 'Bot stats requires privileged user'
154 | else
155 | return bot_stats()
156 | end
157 | end
158 |
159 | if matches[2] == 'chat' then
160 | if not is_sudo(msg.from.peer_id) then
161 | return 'This command requires privileged user'
162 | else
163 | return chat_stats(msg, matches[3])
164 | end
165 | end
166 | end
167 | end
168 | end
169 |
170 | --------------------------------------------------------------------------------
171 |
172 | return {
173 | description = 'Plugin to update user stats.',
174 | usage = {
175 | '!stats
',
176 | 'Returns a list of Username [telegram_id] : msg_num
',
177 | '',
178 | '!stats chat [chat_id]
',
179 | 'Show stats for chat_id',
180 | '',
181 | '!stats bot
',
182 | 'Shows bot stats (sudo users)'
183 | },
184 | patterns = {
185 | '^!([Ss]tats)$',
186 | '^!([Ss]tats) (chat) (%d+)',
187 | '^!([Ss]tats) (bot)'
188 | },
189 | run = run,
190 | pre_process = pre_process
191 | }
192 |
193 | end
194 |
--------------------------------------------------------------------------------
/plugins/sudo.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function cb_getdialog(extra, success, result)
4 | vardump(extra)
5 | vardump(result)
6 | end
7 |
8 | local function parsed_url(link)
9 | local parsed_link = URL.parse(link)
10 | local parsed_path = URL.parse_path(parsed_link.path)
11 |
12 | for k,segment in pairs(parsed_path) do
13 | if segment == 'joinchat' then
14 | invite_link = parsed_path[k+1]:gsub('[ %c].+$', '')
15 | break
16 | end
17 | end
18 | return invite_link
19 | end
20 |
21 | local function action_by_reply(extra, success, result)
22 | local hash = parsed_url(result.text)
23 | join = import_chat_link(hash, ok_cb, false)
24 | end
25 |
26 | --------------------------------------------------------------------------------
27 |
28 | function run(msg, matches)
29 |
30 | if not is_sudo(msg.from.peer_id) then
31 | return
32 | end
33 |
34 | if matches[1] == 'bin' then
35 | local input = matches[2]:gsub('—', '--')
36 | local header = '$ ' .. input .. '
\n'
37 | local stdout = io.popen(input):read('*all')
38 |
39 | bot_sendMessage(get_receiver_api(msg), header .. '' .. stdout .. '
', true, msg.id, 'html')
40 | end
41 |
42 | if matches[1] == 'bot' then
43 | if matches[2] == 'token' then
44 | if not _config.bot_api then
45 | _config.bot_api = {key = '', uid = '', uname = '', master = ''}
46 | end
47 | local botid = api_getme(matches[3])
48 | _config.bot_api.key = matches[3]
49 | _config.bot_api.uid = botid.id
50 | _config.bot_api.uname = botid.username
51 | save_config()
52 | send_message(msg, 'Bot API key has been saved', 'html')
53 | end
54 | end
55 | if matches[1] == "block" then
56 | block_user("user#id" .. matches[2], ok_cb, false)
57 |
58 | if is_mod(matches[2], msg.to.peer_id) then
59 | return "You can't block moderators."
60 | end
61 | if is_admin(matches[2]) then
62 | return "You can't block administrators."
63 | end
64 | block_user("user#id" .. matches[2], ok_cb, false)
65 | return "User blocked"
66 | end
67 |
68 | if matches[1] == "unblock" then
69 | unblock_user("user#id" .. matches[2], ok_cb, false)
70 | return "User unblocked"
71 | end
72 |
73 | if matches[1] == "join" then
74 | if msg.reply_id then
75 | get_message(msg.reply_id, action_by_reply, msg)
76 | elseif matches[2] then
77 | local hash = parsed_url(matches[2])
78 | join = import_channel_link(hash, ok_cb, false)
79 | end
80 | end
81 |
82 | if matches[1] == 'setlang' then
83 | _config.lang = matches[2]
84 | save_config()
85 |
86 | send_message(msg, 'Set bot language to ' .. matches[2], 'html')
87 | end
88 | end
89 |
90 | --------------------------------------------------------------------------------
91 |
92 | return {
93 | description = 'Various sudo commands.',
94 | usage = {
95 | sudo = {
96 | '!bin [command]
',
97 | 'Run a system command.',
98 | '',
99 | '!block [user_id]
',
100 | 'Block user_id to PM.',
101 | '',
102 | '!unblock [user_id]
',
103 | 'Allowed user_id to PM.',
104 | '',
105 | '!bot restart
',
106 | 'Restart bot.',
107 | '',
108 | '!bot status
',
109 | 'Print bot status.',
110 | '',
111 | '!bot token [bot_api_key]
',
112 | 'Input bot API key.',
113 | '',
114 | '!join
',
115 | 'Join a group by replying a message containing invite link.',
116 | '',
117 | '!join [invite_link]
',
118 | 'Join into a group by providing their [invite_link].',
119 | '',
120 | '!version
',
121 | 'Shows bot version',
122 | },
123 | },
124 | patterns = {
125 | '^!(bin) (.*)$',
126 | '^!(block) (.*)$',
127 | '^!(unblock) (.*)$',
128 | '^!(block) (%d+)$',
129 | '^!(unblock) (%d+)$',
130 | '^!(bot) (%g+) (.*)$',
131 | '^!(join)$',
132 | '^!(join) (.*)$',
133 | '^!(setlang) (%g+)$'
134 | },
135 | run = run
136 | }
137 |
138 | end
--------------------------------------------------------------------------------
/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 | do
7 |
8 | -- Globals
9 | -- If you have a google api key for the geocoding/timezone api
10 | local api_key = nil
11 | local dateFormat = '%A, %F %T'
12 |
13 | -- Need the utc time for the google api
14 | local function utctime()
15 | return os.time(os.date('!*t'))
16 | end
17 |
18 | -- Use timezone api to get the time in the lat,
19 | -- Note: this needs an API key
20 | local function get_time(lat, lng)
21 | local api = 'https://maps.googleapis.com/maps/api/timezone/json?'
22 |
23 | -- Get a timestamp (server time is relevant here)
24 | local timestamp = utctime()
25 | local parameters = 'location=' .. URL.escape(lat) .. ',' .. URL.escape(lng)
26 | .. '×tamp=' .. URL.escape(timestamp)
27 |
28 | if api_key ~=nil then
29 | parameters = parameters .. '&key=' .. api_key
30 | end
31 |
32 | local res,code = https.request(api .. parameters)
33 |
34 | if code ~= 200 then
35 | return nil
36 | end
37 |
38 | local data = json:decode(res)
39 |
40 | if (data.status == 'ZERO_RESULTS') then
41 | return nil
42 | end
43 | if (data.status == 'OK') then
44 | -- Construct what we want
45 | -- The local time in the location is: timestamp + rawOffset + dstOffset
46 | local localTime = timestamp + data.rawOffset + data.dstOffset
47 | return localTime, data.timeZoneId
48 | end
49 | return localTime
50 | end
51 |
52 | local function getformattedLocalTime(msg, area)
53 | if area == nil then
54 | send_message(msg, 'The time in nowhere is never.', 'html')
55 | end
56 |
57 | local coordinats, code = get_coords(msg, area)
58 |
59 | if not coordinats then
60 | send_message(msg, 'It seems that in "' .. area .. '" they do not have a concept of time.', 'html')
61 | return
62 | end
63 |
64 | local lat = coordinats.lat
65 | local long = coordinats.lon
66 | local localTime, timeZoneId = get_time(lat, long)
67 |
68 | send_message(msg, 'The local time in ' .. area .. ' (' .. timeZoneId .. ') is:\n'
69 | .. '' .. os.date(dateFormat,localTime) .. '', 'html')
70 | end
71 |
72 | local function run(msg, matches)
73 | return getformattedLocalTime(msg, matches[1])
74 | end
75 |
76 | return {
77 | description = 'Displays the local time in an area',
78 | usage = {
79 | '!time [area]
',
80 | 'Displays the local time in that [area]
',
81 | 'Example: !time yogyakarta
',
82 | },
83 | patterns = {
84 | '^!time (.*)$'
85 | },
86 | run = run
87 | }
88 |
89 | end
90 |
91 |
--------------------------------------------------------------------------------
/plugins/translate.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function yandex_translate(msg, source_lang, target_lang, text)
4 | if source_lang and target_lang then
5 | lang = source_lang .. '-' .. target_lang
6 | elseif target_lang then
7 | lang = target_lang
8 | elseif not source_lang and not target_lang then
9 | lang = _config.lang or 'en'
10 | end
11 |
12 | local url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. _config.api_key.yandex .. '&lang=' .. lang .. '&text=' .. URL.escape(text)
13 | local str, res= https.request(url)
14 | local jstr = json:decode(str)
15 |
16 | if jstr.code == 200 then
17 | send_message(msg, jstr.text[1], 'html')
18 | else
19 | send_message(msg, jstr.message, 'html')
20 | end
21 | end
22 |
23 | local function trans_by_reply(extra, success, result)
24 | yandex_translate(extra.msg, extra.srclang, extra.tolang, result.text)
25 | end
26 |
27 | local function run(msg, matches)
28 | check_api_key(msg, 'yandex', 'http://tech.yandex.com/keys/get')
29 |
30 | if matches[1] == 'setapikey yandex' and is_sudo(msg.from.peer_id) then
31 | _config.api_key.yandex = matches[2]
32 | save_config()
33 | send_message(msg, 'Muslim salat api key has been saved.', 'html')
34 | return
35 | end
36 |
37 | -- comment this line if you want this plugin to works in private message.
38 | --if not is_chat_msg(msg) and not is_admin(msg.from.peer_id) then return nil end
39 |
40 | if msg.reply_id then
41 | if matches[1] == 'translate' then
42 | -- Third pattern
43 | if #matches == 1 then
44 | print("First")
45 | get_message(msg.reply_id, trans_by_reply, {msg=msg, srclang=nil, tolang=nil})
46 | end
47 |
48 | -- Second pattern
49 | if #matches == 2 then
50 | print("Second")
51 | get_message(msg.reply_id, trans_by_reply, {msg=msg, srclang=nil, tolang=matches[2]})
52 | end
53 |
54 | -- First pattern
55 | if #matches == 3 then
56 | print("Third")
57 | get_message(msg.reply_id, trans_by_reply, {msg=msg, srclang=matches[2], tolang=matches[3]})
58 | end
59 | end
60 | else
61 | if msg.reply_to_message then
62 | local text = msg.reply_to_message.text
63 | if matches[1] == 'translate' then
64 | if #matches == 1 then
65 | print("First")
66 | yandex_translate(msg, nil, nil, text)
67 | end
68 |
69 | if #matches == 2 then
70 | print("Second")
71 | yandex_translate(msg, nil, matches[2], text)
72 | end
73 |
74 | if #matches == 3 then
75 | print("Third")
76 | yandex_translate(msg, matches[2], matches[3], text)
77 | end
78 | end
79 | else
80 | if matches[1] == 'trans' then
81 | -- Third pattern
82 | if #matches == 2 then
83 | print("Fourth")
84 | local text = matches[2]
85 | yandex_translate(msg, nil, nil, text)
86 | end
87 |
88 | -- Second pattern
89 | if #matches == 3 then
90 | print("Fifth")
91 | local target = matches[2]
92 | local text = matches[3]
93 | yandex_translate(msg, nil, target, text)
94 | end
95 |
96 | -- First pattern
97 | if #matches == 4 then
98 | print("Sixth")
99 | local source = matches[2]
100 | local target = matches[3]
101 | local text = matches[4]
102 | yandex_translate(msg, source, target, text)
103 | end
104 | end
105 | end
106 | end
107 | end
108 |
109 | return {
110 | description = "Translate some text",
111 | usage = {
112 | sudo = {
113 | '!setapikey yandex [api_key]
',
114 | 'Set Yandex Translate API key.'
115 | },
116 | user = {
117 | '!trans text
',
118 | 'Translate the text
into the default language (or english).',
119 | 'Example: !trans terjemah
',
120 | '',
121 | '!trans target_lang text
',
122 | 'Translate the text
to target_lang
.',
123 | 'Example: !trans en terjemah
',
124 | '',
125 | '!trans source,target text
',
126 | 'Translate the source
to target
.',
127 | 'Example: !trans id,en terjemah
',
128 | '',
129 | 'Use !translate
when reply!',
130 | '',
131 | '!translate
',
132 | 'By reply. Translate the replied text into the default language (or english).',
133 | '',
134 | '!translate target_lang
',
135 | 'By reply. Translate the replied text into target_lang
.',
136 | '',
137 | '!translate source,target
',
138 | 'By reply. Translate the replied text source
to target
.',
139 | '',
140 | 'Languages are two letter ISO 639-1 language code',
141 | },
142 | },
143 | patterns = {
144 | "^!(trans) ([%w]+),([%a]+) (.+)",
145 | "^!(trans) ([%w]+) (.+)",
146 | "^!(trans) (.+)",
147 | "^!(translate) ([%w]+),([%a]+)",
148 | "^!(translate) ([%w]+)",
149 | "^!(translate)",
150 | '^!(setapikey yandex) (.*)$'
151 | },
152 | run = run
153 | }
154 |
155 | end
156 |
--------------------------------------------------------------------------------
/plugins/urbandictionary.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function get_udescription(msg, matches)
4 | local url = 'http://api.urbandictionary.com/v0/define?term=' .. URL.escape(matches)
5 |
6 | local jstr, res = http.request(url)
7 | if res ~= 200 then
8 | send_message(msg, 'Connection error', 'html')
9 | return
10 | end
11 |
12 | local jdat = json:decode(jstr)
13 | if jdat.result_type == 'no_results' then
14 | send_message(msg, "There aren't any definitions for " .. matches .. " yet.", 'html')
15 | return
16 | end
17 |
18 | local output = jdat.list[1].definition:trim()
19 | if string.len(jdat.list[1].example) > 0 then
20 | output = output .. '\n\n' .. jdat.list[1].example:trim()
21 | end
22 |
23 | send_message(msg, output, nil)
24 | end
25 |
26 | local function ud_by_reply(extra, success, result)
27 | if extra.to.peer_id == result.to.peer_id then
28 | get_udescription(result, result.text)
29 | else
30 | reply_msg(extra.id, "Sorry, I can't resolve a username from an old message", ok_cb, true)
31 | end
32 | end
33 |
34 | local function run(msg, matches)
35 | if msg.reply_id then
36 | if matches[1] == 'urbandictionary' or matches[1] == 'ud' or matches[1] == 'urban' then
37 | get_message(msg.reply_id, ud_by_reply, msg)
38 | end
39 | else
40 | if msg.reply_to_message then
41 | get_udescription(msg, msg.reply_to_message.text)
42 | else
43 | get_udescription(msg, matches[1])
44 | end
45 | end
46 | end
47 |
48 | return {
49 | description = 'Returns a definition from Urban Dictionary.',
50 | usage = {
51 | '!ud [query]
',
52 | '!urban [query]
',
53 | '!urbandictionary [query]
',
54 | 'Returns a [query]
definition from urbandictionary.com',
55 | 'Example: !ud fam
',
56 | '',
57 | '!ud
',
58 | '!urban
',
59 | '!urbandictionary
',
60 | 'By reply. Returns a [query]
definition from urbandictionary.com',
61 | 'The [query]
is the replied message text.'
62 | },
63 | patterns = {
64 | '^!(urbandictionary)$',
65 | '^!(ud)$',
66 | '^!(urban)$',
67 | '^!urbandictionary (.+)$',
68 | '^!ud (.+)$',
69 | '^!urban (.+)$'
70 | },
71 | run = run
72 | }
73 |
74 | end
75 |
--------------------------------------------------------------------------------
/plugins/webshot.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local helpers = require 'OAuth.helpers'
4 | local base = 'https://screenshotmachine.com/'
5 | local url = base .. 'processor.php'
6 |
7 | local function get_webshot_url(param)
8 | local response_body = {}
9 | local request_constructor = {
10 | url = url,
11 | method = 'GET',
12 | sink = ltn12.sink.table(response_body),
13 | headers = {
14 | referer = base,
15 | dnt = '1',
16 | origin = base,
17 | ['User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0'
18 | },
19 | redirect = false
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 |
30 | if not ok or response_code ~= 200 then
31 | return nil
32 | end
33 |
34 | local response = table.concat(response_body)
35 |
36 | return string.match(response, "href='(.-)'")
37 | end
38 |
39 | local function run(msg, matches)
40 | local find = get_webshot_url(matches[1])
41 |
42 | if find then
43 | local imgurl = base .. find
44 | local receiver = get_receiver(msg)
45 | --send_photo_from_url(receiver, imgurl)
46 | local webshotimg = download_to_file(imgurl, nil)
47 |
48 | if msg.from.api then
49 | bot_sendPhoto(get_receiver_api(msg), webshotimg, nil, true, msg.id)
50 | else
51 | reply_photo(msg.id, webshotimg, ok_cb, true)
52 | end
53 | end
54 | end
55 |
56 | return {
57 | description = 'Send an screenshot of a website.',
58 | usage = {
59 | '!webshot [url]
',
60 | 'Take an screenshot of the [url]
and send it back to you.'
61 | },
62 | patterns = {
63 | '^!webshot (https?://[%w-_%.%?%.:/%+=&]+)$',
64 | },
65 | run = run
66 | }
67 |
68 | end
69 |
--------------------------------------------------------------------------------
/plugins/whois.lua:
--------------------------------------------------------------------------------
1 | -- dependency: whois
2 | -- install on your system, i.e sudo aptitude install whois
3 |
4 | do
5 |
6 | local whofile = '/tmp/whois.txt'
7 |
8 | local function whoinfo()
9 | local file = io.open(whofile, 'r')
10 | local content = file:read "*a"
11 | file:close()
12 | return content:sub(1, 4000)
13 | end
14 |
15 | local function run(msg, matches)
16 | local result = os.execute('whois ' .. matches[1] .. ' > ' .. whofile)
17 |
18 | if not result then
19 | if whoinfo():match('no match') then
20 | send_message(msg, 'No match for ' .. matches[1] .. '\n'
21 | .. '* type the URL correctly\n'
22 | .. '* exclude http(s) and www from the URL.', 'html')
23 | elseif not os.execute('which whois') then
24 | send_message(msg, 'sh: 1: whois: not found.\n'
25 | .. 'Please install whois
package on your system.', 'html')
26 | end
27 | return
28 | end
29 |
30 | if matches[2] then
31 | if matches[2] == 'txt' then
32 | if msg.from.api then
33 | bot_sendDocument(get_receiver_api(msg), whofile, nil, true, msg.id)
34 | else
35 | reply_file(msg.id, whofile, ok_cb, true)
36 | end
37 | end
38 | if matches[2] == 'pm' and is_chat_msg(msg) then
39 | bot_sendMessage(msg.from.peer_id, whoinfo(), true, nil, nil)
40 | end
41 | if matches[2] == 'pmtxt' and is_chat_msg(msg) then
42 | bot_sendDocument(msg.from.peer_id, whofile, nil, true, nil)
43 | end
44 | else
45 | send_message(msg, whoinfo(), nil)
46 | end
47 | end
48 |
49 | return {
50 | description = 'Whois lookup.',
51 | usage = {
52 | '!whois [url]
',
53 | 'Returns whois lookup for [url]
',
54 | '',
55 | '!whois [url] txt
',
56 | 'Returns whois lookup for [url]
and then send as text file.',
57 | '',
58 | '!whois [url] pm
',
59 | 'Returns whois lookup for [url]
into requester PM.',
60 | '',
61 | '!whois [url] pmtxt
',
62 | 'Returns whois lookup file for [url]
and then send into requester PM.',
63 | },
64 | patterns = {
65 | '^!whois (%g+)$',
66 | '^!whois (%g+) (txt)$',
67 | '^!whois (%g+) (pm)$',
68 | '^!whois (%g+) (pmtxt)$'
69 | },
70 | run = run
71 | }
72 |
73 | end
74 |
--------------------------------------------------------------------------------
/plugins/xkcd.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | function get_last_id(msg)
4 | local res, code = https.request('http://xkcd.com/info.0.json')
5 |
6 | if code ~= 200 then
7 | send_message(msg, 'HTTP ERROR', 'html')
8 | end
9 |
10 | local data = json:decode(res)
11 |
12 | return data.num
13 | end
14 |
15 | function get_xkcd(msg, id)
16 | local res,code = http.request('http://xkcd.com/' .. id .. '/info.0.json')
17 |
18 | if code ~= 200 then
19 | send_message(msg, 'HTTP ERROR', 'html')
20 | end
21 |
22 | local data = json:decode(res)
23 | local link_image = data.img
24 |
25 | if link_image:sub(0,2) == '//' then
26 | link_image = msg.text:sub(3,-1)
27 | end
28 |
29 | return link_image, data.num, data.title, data.alt
30 | end
31 |
32 | function get_xkcd_random(msg)
33 | local last = get_last_id(msg)
34 | local i = math.random(1, last)
35 | return get_xkcd(msg, i)
36 | end
37 |
38 | function run(msg, matches)
39 | if matches[1] == 'xkcd' then
40 | url, num, title, alt = get_xkcd_random(msg)
41 | else
42 | url, num, title, alt = get_xkcd(msg, matches[1])
43 | end
44 |
45 | local relevantxkcd = '' .. title .. '\n\n' .. alt .. '\n\n'
46 |
47 | bot_sendMessage(get_receiver_api(msg), relevantxkcd, false, msg.id, 'html')
48 | end
49 |
50 | return {
51 | description = 'Send comic images from xkcd',
52 | usage = {
53 | '!xkcd
',
54 | 'Send random xkcd image and title.',
55 | '',
56 | '!xkcd (id)
',
57 | 'Send an xkcd image and title.',
58 | 'Example: !xkcd 149
',
59 | },
60 | patterns = {
61 | '^!(xkcd)$',
62 | '^!xkcd (%d+)',
63 | },
64 | run = run
65 | }
66 |
67 | end
68 |
--------------------------------------------------------------------------------
/plugins/yify.lua:
--------------------------------------------------------------------------------
1 | do
2 |
3 | local function search_yify(msg, query)
4 | local url = 'https://yts.ag/api/v2/list_movies.json?limit=1&query_term=' .. URL.escape(query)
5 | local resp = {}
6 | local b,c = https.request {
7 | url = url,
8 | protocol = 'tlsv1',
9 | sink = ltn12.sink.table(resp)
10 | }
11 | local resp = table.concat(resp)
12 | local jresult = json:decode(resp)
13 |
14 | if not jresult.data.movies then
15 | send_message(msg, 'No torrent results for: ' .. query, 'html')
16 | else
17 | local yify = jresult.data.movies[1]
18 | local yts = yify.torrents
19 | local yifylist = {}
20 |
21 | for i=1, #yts do
22 | yifylist[i] = '' .. yts[i].quality .. ': .torrent\n'
23 | .. 'Seeds: ' .. yts[i].seeds .. '
| ' .. 'Peers: ' .. yts[i].peers .. '
| ' .. 'Size: ' .. yts[i].size .. '
'
24 | end
25 |
26 | local torrlist = table.concat(yifylist, '\n\n')
27 | local title = '' .. yify.title_long .. ''
28 | local output = title .. '\n\n'
29 | .. '' .. yify.year .. ' | ' .. yify.rating .. '/10 | ' .. yify.runtime .. '
min\n\n'
30 | .. torrlist .. '\n\n' .. yify.synopsis:sub(1, 2000) .. ' More on yts.ag ...'
31 |
32 | bot_sendMessage(get_receiver_api(msg), output, false, msg.id, 'html')
33 | end
34 | end
35 |
36 | local function run(msg, matches)
37 | return search_yify(msg, matches[1])
38 | end
39 |
40 | return {
41 | description = 'Search YTS YIFY movies.',
42 | usage = {
43 | '!yify [search term]
',
44 | '!yts [search term]
',
45 | 'Search YTS YIFY movie torrents from yts.ag',
46 | 'Example: !yts ex machina
',
47 | },
48 | patterns = {
49 | '^!yify (.+)$',
50 | '^!yts (.+)$'
51 | },
52 | run = run,
53 | }
54 |
55 | end
--------------------------------------------------------------------------------