Merge branch 'develop'
This commit is contained in:
commit
1ffd231ed5
26
README.md
26
README.md
@ -12,21 +12,17 @@ Multimedia
|
|||||||
|
|
||||||
![http://i.imgur.com/0FGUvU0.png](http://i.imgur.com/0FGUvU0.png) ![http://i.imgur.com/zW7WWWt.png](http://i.imgur.com/zW7WWWt.png) ![http://i.imgur.com/zW7WWWt.png](http://i.imgur.com/kPK7paz.png)
|
![http://i.imgur.com/0FGUvU0.png](http://i.imgur.com/0FGUvU0.png) ![http://i.imgur.com/zW7WWWt.png](http://i.imgur.com/zW7WWWt.png) ![http://i.imgur.com/zW7WWWt.png](http://i.imgur.com/kPK7paz.png)
|
||||||
|
|
||||||
Default enabled commands
|
Bot Commands
|
||||||
------------
|
------------
|
||||||
```
|
<table>
|
||||||
!9gag -> send random image from 9gag
|
<thead>
|
||||||
!echo [whatever] -> echoes the msg
|
<tr>
|
||||||
!get (value_name) -> retrieves variables saved with !set
|
<td><strong>Name</strong></td>
|
||||||
!set [value_name] [data] -> Set value
|
<td><strong>Description</strong></td>
|
||||||
!img [topic] -> search image with Google API and sends it
|
<td><strong>Usage</strong></td>
|
||||||
!loc (location) -> Gets information about a location, maplink and overview
|
</tr>
|
||||||
!stats -> Numer of messages by user
|
</thead>
|
||||||
!time [area] -> Displays the local time in an area
|
<tbody><tr><td>9gag.lua</td><td>9GAG for telegram</td><td>!9gag: Send random image from 9gag</td></tr><tr><td>btc.lua</td><td>Bitcoin global average market value (in EUR or USD)</td><td>!btc [EUR|USD] [amount]</td></tr><tr><td>echo.lua</td><td>echoes the msg</td><td>!echo [whatever]</td></tr><tr><td>eur.lua</td><td>EURUSD market value</td><td>!eur [USD]</td></tr><tr><td>fortunes_uc3m.lua</td><td>Fortunes from Universidad Carlos III</td><td>!uc3m</td></tr><tr><td>get.lua</td><td>retrieves variables saved with !set</td><td>!get (value_name)</td></tr><tr><td>giphy.lua</td><td>GIFs from telegram with Giphy API</td><td>!gif (term): Search and sends GIF from Giphy. If no param, sends a trending GIF.<br>!giphy (term): Search and sends GIF from Giphy. If no param, sends a trending GIF.<br></td></tr><tr><td>google.lua</td><td>Searches Google and send results</td><td>!google [terms]</td></tr><tr><td>gps.lua</td><td>generates a map showing the given GPS coordinates</td><td>!gps latitude,longitude</td></tr><tr><td>hello.lua</td><td>Says hello to someone</td><td>say hello to [name]</td></tr><tr><td>help.lua</td><td>Lists all available commands</td><td>!help<br>!help md<br></td></tr><tr><td>images.lua</td><td>When user sends image URL (ends with png, jpg, jpeg) download and send it to origin.</td><td></td></tr><tr><td>img_google.lua</td><td>search image with Google API and sends it</td><td>!img [topic]</td></tr><tr><td>invite.lua</td><td>Invite other user to the chat group</td><td>!invite name [user_name]<br>!invite id [user_id]<br></td></tr><tr><td>location.lua</td><td>Gets information about a location, maplink and overview</td><td>!loc (location)</td></tr><tr><td>media.lua</td><td>When user sends media URL (ends with gif, mp4, pdf, etc.) download and send it to origin.</td><td></td></tr><tr><td>ping.lua</td><td>If domain is offline, send msg to peer</td><td></td></tr><tr><td>plugins.lua</td><td>Enables, disables and reloads plugins. Privileged users only.</td><td>!plugins: list all plugins<br>!plugins enable [plugin]<br>!plugins disable [plugin]<br>!plugins reload<br></td></tr><tr><td>rae.lua</td><td>Spanish dictionary</td><td>!rae [word]</td></tr><tr><td>set.lua</td><td>Set value</td><td>!set [value_name] [data]</td></tr><tr><td>stats.lua</td><td>Numer of messages by user</td><td>!stats</td></tr><tr><td>time.lua</td><td>Displays the local time in an area</td><td>!time [area]</td></tr><tr><td>twitter.lua</td><td>When user sends twitter URL, send text and images to origin. Requieres OAuth Key.</td><td></td></tr><tr><td>twitter_send.lua</td><td>Sends a tweet</td><td>!tw [text]</td></tr><tr><td>version.lua</td><td>Shows bot version</td><td>!version</td></tr><tr><td>weather.lua</td><td>weather in that city (Madrid is default)</td><td>!weather (city)</td></tr><tr><td>youtube.lua</td><td>sends YouTube image</td><td></td></tr></tbody></table>
|
||||||
!version -> Shows bot version
|
|
||||||
!google terms -> Searches Google
|
|
||||||
!help -> Lists all available commands
|
|
||||||
```
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
@ -57,7 +53,7 @@ See the plugins list with `!plugins` command.
|
|||||||
|
|
||||||
Enable a disabled plugin by `!plugins enable [name]`.
|
Enable a disabled plugin by `!plugins enable [name]`.
|
||||||
|
|
||||||
Disable an eanbled plugin by `!plugins disable [name]`.
|
Disable an enabled plugin by `!plugins disable [name]`.
|
||||||
|
|
||||||
Those commands require a privileged user, privileged users are defined inside `data/config.lua` (generated by the bot), stop de bot and edit if necessary.
|
Those commands require a privileged user, privileged users are defined inside `data/config.lua` (generated by the bot), stop de bot and edit if necessary.
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ json = (loadfile "./libs/JSON.lua")()
|
|||||||
serpent = (loadfile "./libs/serpent.lua")()
|
serpent = (loadfile "./libs/serpent.lua")()
|
||||||
require("./bot/utils")
|
require("./bot/utils")
|
||||||
|
|
||||||
VERSION = '0.8.3'
|
VERSION = '0.8.4'
|
||||||
|
|
||||||
function on_msg_receive (msg)
|
function on_msg_receive (msg)
|
||||||
vardump(msg)
|
vardump(msg)
|
||||||
|
@ -31,14 +31,18 @@ function string:split(sep)
|
|||||||
return fields
|
return fields
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Removes spaces
|
||||||
|
function string.trim(s)
|
||||||
|
return s:gsub("^%s*(.-)%s*$", "%1")
|
||||||
|
end
|
||||||
|
|
||||||
function download_to_file( url , noremove )
|
function download_to_file( url , noremove )
|
||||||
print("url to download: "..url)
|
print("url to download: "..url)
|
||||||
local ltn12 = require "ltn12"
|
local ltn12 = require "ltn12"
|
||||||
local respbody = {}
|
local respbody = {}
|
||||||
one, c, h = http.request{url=url, sink=ltn12.sink.table(respbody), redirect=true}
|
one, c, h = http.request{url=url, sink=ltn12.sink.table(respbody), redirect=true}
|
||||||
htype = h["content-type"]
|
htype = h["content-type"]
|
||||||
vardump(c)
|
|
||||||
print("content-type: "..htype)
|
|
||||||
if htype == "image/jpeg" then
|
if htype == "image/jpeg" then
|
||||||
file_name = string.random(5)..".jpg"
|
file_name = string.random(5)..".jpg"
|
||||||
file_path = "/tmp/"..file_name
|
file_path = "/tmp/"..file_name
|
||||||
@ -186,3 +190,8 @@ end
|
|||||||
function string:isempty()
|
function string:isempty()
|
||||||
return self == nil or self == ''
|
return self == nil or self == ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function string.starts(String, Start)
|
||||||
|
return Start == string.sub(String,1,string.len(Start))
|
||||||
|
end
|
@ -4,8 +4,8 @@ function run(msg, matches)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description = "echoes the msg",
|
description = "Simplest plugin ever!",
|
||||||
usage = "!echo [whatever]",
|
usage = "!echo [whatever]: echoes the msg",
|
||||||
patterns = {"^!echo (.*)$"},
|
patterns = {"^!echo (.*)$"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,8 @@ function lex(msg, text)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description = "retrieves variables saved with !set",
|
description = "Retrieves variables saved with !set",
|
||||||
usage = "!get (value_name)",
|
usage = "!get (value_name): Returns the value_name value.",
|
||||||
patterns = {
|
patterns = {
|
||||||
"^!get (%a+)$",
|
"^!get (%a+)$",
|
||||||
"^!get$"},
|
"^!get$"},
|
||||||
|
@ -29,7 +29,7 @@ end
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Searches Google and send results",
|
description = "Searches Google and send results",
|
||||||
usage = "!google [terms]",
|
usage = "!google [terms]: Searches Google and send results",
|
||||||
patterns = {
|
patterns = {
|
||||||
"^!google (.*)$",
|
"^!google (.*)$",
|
||||||
"^%.[g|G]oogle (.*)$"
|
"^%.[g|G]oogle (.*)$"
|
||||||
|
@ -25,7 +25,7 @@ end
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description = "generates a map showing the given GPS coordinates",
|
description = "generates a map showing the given GPS coordinates",
|
||||||
usage = "!gps latitude,longitude",
|
usage = "!gps latitude,longitude: generates a map showing the given GPS coordinates",
|
||||||
patterns = {"^!gps ([^,]*)[,%s]([^,]*)$"},
|
patterns = {"^!gps ([^,]*)[,%s]([^,]*)$"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
@ -30,18 +30,23 @@ function html_help()
|
|||||||
return text
|
return text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function has_usage_data(dict)
|
||||||
|
if (dict.usage == nil or dict.usage == '') then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function telegram_help( )
|
function telegram_help( )
|
||||||
local ret = ""
|
local ret = ""
|
||||||
for k, dict in pairs(plugins) do
|
for k, dict in pairs(plugins) do
|
||||||
if dict.usage ~= "" then
|
if (type(dict.usage) == "table") then
|
||||||
if (type(dict.usage) == "table") then
|
for ku,usage in pairs(dict.usage) do
|
||||||
for ku,vu in pairs(dict.usage) do
|
ret = ret..usage..'\n'
|
||||||
ret = ret..vu.." "
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ret = ret..dict.usage
|
|
||||||
end
|
end
|
||||||
ret = ret .. " -> " .. dict.description .. "\n"
|
ret = ret..'\n'
|
||||||
|
elseif has_usage_data(dict) then -- Is not empty
|
||||||
|
ret = ret..dict.usage..'\n\n'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
@ -56,8 +61,11 @@ function run(msg, matches)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Lists all available commands",
|
description = "Help plugin. Get info from other plugins. ",
|
||||||
usage = {"!help", "!help md"},
|
usage = {
|
||||||
|
"!help: Show all the help",
|
||||||
|
"!help md: Generate a GitHub Markdown table"
|
||||||
|
},
|
||||||
patterns = {
|
patterns = {
|
||||||
"^!help$",
|
"^!help$",
|
||||||
"^!help md$"
|
"^!help md$"
|
||||||
|
@ -25,8 +25,8 @@ function run(msg, matches)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description = "search image with Google API and sends it",
|
description = "Search image with Google API and sends it.",
|
||||||
usage = "!img [topic]",
|
usage = "!img [term]: Random search an image with Google API.",
|
||||||
patterns = {"^!img (.*)$"},
|
patterns = {"^!img (.*)$"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ end
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Gets information about a location, maplink and overview",
|
description = "Gets information about a location, maplink and overview",
|
||||||
usage = "!loc (location)",
|
usage = "!loc (location): Gets information about a location, maplink and overview",
|
||||||
patterns = {"^!loc (.*)$"},
|
patterns = {"^!loc (.*)$"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
@ -100,12 +100,12 @@ function run(msg, matches)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Enables, disables and reloads plugins. Privileged users only.",
|
description = "Plugin to manage other plugins. Enable, disable or reload.",
|
||||||
usage = {
|
usage = {
|
||||||
"!plugins: list all plugins",
|
"!plugins: list all plugins",
|
||||||
"!plugins enable [plugin]",
|
"!plugins enable [plugin]: enable plugin",
|
||||||
"!plugins disable [plugin]",
|
"!plugins disable [plugin]: disable plugin",
|
||||||
"!plugins reload" },
|
"!plugins reload: reloads all plugins" },
|
||||||
patterns = {
|
patterns = {
|
||||||
"^!plugins$",
|
"^!plugins$",
|
||||||
"^!plugins? (enable) (.*)$",
|
"^!plugins? (enable) (.*)$",
|
||||||
|
@ -41,7 +41,7 @@ end
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Spanish dictionary",
|
description = "Spanish dictionary",
|
||||||
usage = "!rae [word]",
|
usage = "!rae [word]: Search that word in Spanish dictionary. Powered by https://github.com/javierhonduco/dulcinea",
|
||||||
patterns = {"^!rae (.*)$"},
|
patterns = {"^!rae (.*)$"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,8 @@ function run(msg, matches)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Set value",
|
description = "Plugin for saving values. get.lua plugin is necesary to retrieve them.",
|
||||||
usage = "!set [value_name] [data]",
|
usage = "!set [value_name] [data]: Saves the data with the value_name name.",
|
||||||
patterns = {"^!set (%a+) (.+)$"},
|
patterns = {"^!set (%a+) (.+)$"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ function update_user_stats(msg)
|
|||||||
if _stats[to_id][from_id] == nil then
|
if _stats[to_id][from_id] == nil then
|
||||||
print ('New stats key from_id: '..to_id)
|
print ('New stats key from_id: '..to_id)
|
||||||
_stats[to_id][from_id] = {
|
_stats[to_id][from_id] = {
|
||||||
|
user_id = from_id,
|
||||||
name = user_name,
|
name = user_name,
|
||||||
last_name = user_last_name,
|
last_name = user_last_name,
|
||||||
print_name = user_print_name,
|
print_name = user_print_name,
|
||||||
@ -32,7 +33,7 @@ function update_user_stats(msg)
|
|||||||
print ('Updated '..to_id..' '..from_id)
|
print ('Updated '..to_id..' '..from_id)
|
||||||
local actual_num = _stats[to_id][from_id].msg_num
|
local actual_num = _stats[to_id][from_id].msg_num
|
||||||
_stats[to_id][from_id].msg_num = actual_num + 1
|
_stats[to_id][from_id].msg_num = actual_num + 1
|
||||||
-- And update last_name
|
_stats[to_id][from_id].user_id = from_id
|
||||||
_stats[to_id][from_id].last_name = user_last_name
|
_stats[to_id][from_id].last_name = user_last_name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -60,13 +61,28 @@ end
|
|||||||
local function get_stats_status( msg )
|
local function get_stats_status( msg )
|
||||||
-- vardump(stats)
|
-- vardump(stats)
|
||||||
local text = ""
|
local text = ""
|
||||||
local to_id = tostring(msg.to.id)
|
local to_id = tostring(msg.to.id)
|
||||||
|
local rank = {}
|
||||||
|
|
||||||
for id, user in pairs(_stats[to_id]) do
|
for id, user in pairs(_stats[to_id]) do
|
||||||
|
table.insert(rank, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(rank, function(a, b)
|
||||||
|
if a.msg_num and b.msg_num then
|
||||||
|
return a.msg_num > b.msg_num
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
for id, user in pairs(rank) do
|
||||||
|
-- Previous versions didn't save that
|
||||||
|
user_id = user.user_id or ''
|
||||||
|
print(">> ", id, user.name)
|
||||||
if user.last_name == nil then
|
if user.last_name == nil then
|
||||||
text = text..user.name.." ["..id.."]: "..user.msg_num.."\n"
|
text = text..user.name.." ["..user_id.."]: "..user.msg_num.."\n"
|
||||||
else
|
else
|
||||||
text = text..user.name.." "..user.last_name.." ["..id.."]: "..user.msg_num.."\n"
|
text = text..user.name.." "..user.last_name.." ["..user_id.."]: "..user.msg_num.."\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
print("usuarios: "..text)
|
print("usuarios: "..text)
|
||||||
@ -75,7 +91,7 @@ end
|
|||||||
|
|
||||||
local function run(msg, matches)
|
local function run(msg, matches)
|
||||||
if matches[1] == "stats" then -- Hack
|
if matches[1] == "stats" then -- Hack
|
||||||
return get_stats_status(msg)
|
return get_stats_status(msg)
|
||||||
else
|
else
|
||||||
print ("update stats")
|
print ("update stats")
|
||||||
update_user_stats(msg)
|
update_user_stats(msg)
|
||||||
@ -86,8 +102,8 @@ end
|
|||||||
_stats = read_file_stats()
|
_stats = read_file_stats()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Numer of messages by user",
|
description = "Plugin to update user stats.",
|
||||||
usage = "!stats",
|
usage = "!stats: Returns a list of Username [telegram_id]: msg_num",
|
||||||
patterns = {
|
patterns = {
|
||||||
".*",
|
".*",
|
||||||
"^!(stats)"
|
"^!(stats)"
|
||||||
|
@ -94,7 +94,7 @@ end
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Displays the local time in an area",
|
description = "Displays the local time in an area",
|
||||||
usage = "!time [area]",
|
usage = "!time [area]: Displays the local time in that area",
|
||||||
patterns = {"^!time (.*)$"},
|
patterns = {"^!time (.*)$"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
@ -16,16 +16,16 @@ local client = OAuth.new(consumer_key, consumer_secret, {
|
|||||||
|
|
||||||
function run(msg, matches)
|
function run(msg, matches)
|
||||||
if consumer_key:isempty() then
|
if consumer_key:isempty() then
|
||||||
return "Twitter Consumer Key is empty, write it in plugins/twitter.lua"
|
return "Twitter Consumer Key is empty, write it in plugins/twitter_send.lua"
|
||||||
end
|
end
|
||||||
if consumer_secret:isempty() then
|
if consumer_secret:isempty() then
|
||||||
return "Twitter Consumer Secret is empty, write it in plugins/twitter.lua"
|
return "Twitter Consumer Secret is empty, write it in plugins/twitter_send.lua"
|
||||||
end
|
end
|
||||||
if access_token:isempty() then
|
if access_token:isempty() then
|
||||||
return "Twitter Access Token is empty, write it in plugins/twitter.lua"
|
return "Twitter Access Token is empty, write it in plugins/twitter_send.lua"
|
||||||
end
|
end
|
||||||
if access_token_secret:isempty() then
|
if access_token_secret:isempty() then
|
||||||
return "Twitter Access Token Secret is empty, write it in plugins/twitter.lua"
|
return "Twitter Access Token Secret is empty, write it in plugins/twitter_send.lua"
|
||||||
end
|
end
|
||||||
|
|
||||||
if not is_sudo(msg) then
|
if not is_sudo(msg) then
|
||||||
@ -44,7 +44,7 @@ end
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Sends a tweet",
|
description = "Sends a tweet",
|
||||||
usage = "!tw [text]",
|
usage = "!tw [text]: Sends the Tweet with the configured accout.",
|
||||||
patterns = {"^!tw (.+)"},
|
patterns = {"^!tw (.+)"},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
@ -7,8 +7,10 @@ end
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description = "Shows bot version",
|
description = "Shows bot version",
|
||||||
usage = "!version",
|
usage = "!version: Shows bot version",
|
||||||
patterns = {"^!version$"},
|
patterns = {
|
||||||
|
"^!version$"
|
||||||
|
},
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
plugins/xkcd.lua
Normal file
54
plugins/xkcd.lua
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
function get_last_id()
|
||||||
|
local res,code = https.request("http://xkcd.com/info.0.json")
|
||||||
|
if code ~= 200 then return "HTTP ERROR" end
|
||||||
|
local data = json:decode(res)
|
||||||
|
return data.num
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_xkcd(id)
|
||||||
|
local res,code = http.request("http://xkcd.com/"..id.."/info.0.json")
|
||||||
|
if code ~= 200 then return "HTTP ERROR" end
|
||||||
|
local data = json:decode(res)
|
||||||
|
local link_image = data.img
|
||||||
|
if link_image:sub(0,2) == '//' then
|
||||||
|
link_image = msg.text:sub(3,-1)
|
||||||
|
end
|
||||||
|
return link_image, data.title
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function get_xkcd_random()
|
||||||
|
local last = get_last_id()
|
||||||
|
math.randomseed(os.time())
|
||||||
|
i = math.random(1, data.num)
|
||||||
|
return get_xkcd(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
function send_title(cb_extra, success, result)
|
||||||
|
if success then
|
||||||
|
send_msg(cb_extra[1], cb_extra[2], ok_cb, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function run(msg, matches)
|
||||||
|
local receiver = get_receiver(msg)
|
||||||
|
if matches[1] == "!xkcd" then
|
||||||
|
url, title = get_xkcd_random()
|
||||||
|
else
|
||||||
|
url, title = get_xkcd(matches[1])
|
||||||
|
end
|
||||||
|
file_path = download_to_file(url)
|
||||||
|
send_photo(receiver, file_path, send_title, {receiver, title})
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Send comic images from xkcd",
|
||||||
|
usage = {"!xkcd (id): Send an xkcd image and tigle. If not id, send a random one"},
|
||||||
|
patterns = {
|
||||||
|
"^!xkcd$",
|
||||||
|
"^!xkcd (%d+)",
|
||||||
|
"xkcd.com/(%d+)"
|
||||||
|
},
|
||||||
|
run = run
|
||||||
|
}
|
2
tg
2
tg
@ -1 +1 @@
|
|||||||
Subproject commit d74db187ef3ed9a152979274ba2ea2ad8fe8c8d3
|
Subproject commit 01f5d9a3b671edc15a5cff285601115845489985
|
Reference in New Issue
Block a user