added new plugins
date.lua get.lua invite.lua location.lua set.lua time.lua twitter.lua weather.lua youtube.lua
This commit is contained in:
parent
6254e51e8c
commit
90428735d3
13
.gitignore
vendored
13
.gitignore
vendored
@ -25,3 +25,16 @@ plugins/derpibooru_nsfw.lua
|
|||||||
plugins/derpibooru.lua
|
plugins/derpibooru.lua
|
||||||
plugins/rss.lua
|
plugins/rss.lua
|
||||||
plugins/meme.lua
|
plugins/meme.lua
|
||||||
|
plugins/app_store.lua
|
||||||
|
plugins/bitly_create.lua
|
||||||
|
plugins/dailymotion.lua
|
||||||
|
plugins/dns.lua
|
||||||
|
plugins/gdrive.lua
|
||||||
|
plugins/instagram.lua
|
||||||
|
plugins/play_store.lua
|
||||||
|
plugins/reddit.lua
|
||||||
|
plugins/soundcloud.lua
|
||||||
|
plugins/spotify.lua
|
||||||
|
plugins/vimeo.lua
|
||||||
|
plugins/vine.lua
|
||||||
|
plugins/youtube_playlist.lua
|
11
plugins/date.lua
Normal file
11
plugins/date.lua
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
function run(msg, matches)
|
||||||
|
return os.date("%d.%m.%Y")
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Zeigt das aktuelle Datum an",
|
||||||
|
usage = "/date",
|
||||||
|
patterns = {"^/date$"},
|
||||||
|
run = run
|
||||||
|
}
|
80
plugins/get.lua
Normal file
80
plugins/get.lua
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
local _file_values = './data/values.lua'
|
||||||
|
|
||||||
|
_values = load_from_file(_file_values)
|
||||||
|
|
||||||
|
local function fetch_value(chat, value_name)
|
||||||
|
-- Chat non exists
|
||||||
|
if _values[chat] == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if value_name == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local value = _values[chat][value_name]
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_value(chat, value_name)
|
||||||
|
|
||||||
|
-- If chat values is empty
|
||||||
|
if (_values[chat] == nil) then
|
||||||
|
return "Hier sind keine Daten"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If there is not value name, return all the values.
|
||||||
|
if (value_name == nil ) then
|
||||||
|
local text = ""
|
||||||
|
for key,value in pairs(_values[chat]) do
|
||||||
|
text = text..key.." = "..value.."\n"
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
local value = _values[chat][value_name]
|
||||||
|
if ( value == nil) then
|
||||||
|
return "Konnte nicht finden "..value_name
|
||||||
|
end
|
||||||
|
return value_name.." = "..value
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run(msg, matches)
|
||||||
|
local chat_id = tostring(msg.to.id)
|
||||||
|
if matches[1] == "!get" then
|
||||||
|
return get_value(chat_id, nil)
|
||||||
|
end
|
||||||
|
return get_value(chat_id, matches[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function lex(msg)
|
||||||
|
|
||||||
|
if msg.text then
|
||||||
|
local text = msg.text
|
||||||
|
local chat_id = tostring(msg.to.id)
|
||||||
|
local s, e = text:find("%$%a+")
|
||||||
|
|
||||||
|
if s then
|
||||||
|
local var = text:sub(s + 1, e)
|
||||||
|
local value = fetch_value(chat_id, var)
|
||||||
|
|
||||||
|
if (value == nil) then
|
||||||
|
value = "(Unbekannter Wert " .. var .. ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
msg.text = text:sub(0, s - 1) .. value .. text:sub(e + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return msg
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Bekommt Variable, die mit !set gesetzt wurde",
|
||||||
|
usage = "/get (Variable)",
|
||||||
|
patterns = {
|
||||||
|
"^/get (%a+)$",
|
||||||
|
"^/get$"},
|
||||||
|
run = run,
|
||||||
|
pre_process = lex
|
||||||
|
}
|
||||||
|
|
42
plugins/invite.lua
Normal file
42
plugins/invite.lua
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
-- Invite other user to the chat group.
|
||||||
|
-- Use !invite name User_name or !invite id id_number
|
||||||
|
-- The User_name is the print_name (there are no spaces but _)
|
||||||
|
|
||||||
|
do
|
||||||
|
|
||||||
|
local function run(msg, matches)
|
||||||
|
-- User submitted a user name
|
||||||
|
if matches[1] == "name" then
|
||||||
|
user = matches[2]
|
||||||
|
user = string.gsub(user," ","_")
|
||||||
|
end
|
||||||
|
-- User submitted an id
|
||||||
|
if matches[1] == "id" then
|
||||||
|
user = matches[2]
|
||||||
|
user = 'user#id'..user
|
||||||
|
end
|
||||||
|
-- The message must come from a chat group
|
||||||
|
if msg.to.type == 'chat' then
|
||||||
|
chat = 'chat#id'..msg.to.id
|
||||||
|
else
|
||||||
|
return 'Dies ist keine Gruppe!!'
|
||||||
|
end
|
||||||
|
print ("Füge "..user.." zu "..chat.." hinzu")
|
||||||
|
status = chat_add_user (chat, user, ok_cb, false)
|
||||||
|
if not status then
|
||||||
|
return "Ein Fehler ist aufgetreten"
|
||||||
|
end
|
||||||
|
return "User "..user.." zu "..chat.." hinzugefügt!"
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Andere User zu einer Chat-Gruppe hinzufügen",
|
||||||
|
usage = "/invite name [user_name], !invite id [user_id]",
|
||||||
|
patterns = {
|
||||||
|
"^/invite (name) (.*)$",
|
||||||
|
"^/invite (id) (%d+)$"
|
||||||
|
},
|
||||||
|
run = run
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
60
plugins/location.lua
Normal file
60
plugins/location.lua
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
-- Implement a command !loc [area] which uses
|
||||||
|
-- the static map API to get a location image
|
||||||
|
|
||||||
|
-- Not sure if this is the proper way
|
||||||
|
-- Intent: get_latlong is in time.lua, we need it here
|
||||||
|
-- loadfile "time.lua"
|
||||||
|
|
||||||
|
-- Globals
|
||||||
|
-- If you have a google api key for the geocoding/timezone api
|
||||||
|
do
|
||||||
|
|
||||||
|
local api_key = nil
|
||||||
|
local base_api = "https://maps.googleapis.com/maps/api"
|
||||||
|
|
||||||
|
function get_staticmap(area)
|
||||||
|
local api = base_api .. "/staticmap?"
|
||||||
|
|
||||||
|
-- Get a sense of scale
|
||||||
|
local lat,lng,acc,types = get_latlong(area)
|
||||||
|
|
||||||
|
local scale = types[1]
|
||||||
|
if scale=="locality" then zoom=8
|
||||||
|
elseif scale=="country" then zoom=4
|
||||||
|
else zoom = 13 end
|
||||||
|
|
||||||
|
local parameters =
|
||||||
|
"size=600x300" ..
|
||||||
|
"&zoom=" .. zoom ..
|
||||||
|
"¢er=" .. URL.escape(area) ..
|
||||||
|
"&markers=color:red"..URL.escape("|"..area)
|
||||||
|
|
||||||
|
if api_key ~=nil and api_key ~= "" then
|
||||||
|
parameters = parameters .. "&key="..api_key
|
||||||
|
end
|
||||||
|
return lat, lng, api..parameters
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function run(msg, matches)
|
||||||
|
local receiver = get_receiver(msg)
|
||||||
|
local lat,lng,url = get_staticmap(matches[1])
|
||||||
|
|
||||||
|
-- Send the actual location, is a google maps link
|
||||||
|
send_location(receiver, lat, lng, ok_cb, false)
|
||||||
|
|
||||||
|
-- Send a picture of the map, which takes scale into account
|
||||||
|
send_photo_from_url(receiver, url)
|
||||||
|
|
||||||
|
-- Return a link to the google maps stuff is now not needed anymore
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Zeigt Informationen über einen Ort.",
|
||||||
|
usage = "/loc (Ort)",
|
||||||
|
patterns = {"^/loc (.*)$"},
|
||||||
|
run = run
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
30
plugins/set.lua
Normal file
30
plugins/set.lua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
local _file_values = './data/values.lua'
|
||||||
|
|
||||||
|
local function save_value(chat, text )
|
||||||
|
var_name, var_value = string.match(text, "!set (%a+) (.+)")
|
||||||
|
if (var_name == nil or var_value == nil) then
|
||||||
|
return "Benutzung: /set var_name value"
|
||||||
|
end
|
||||||
|
if _values[chat] == nil then
|
||||||
|
_values[chat] = {}
|
||||||
|
end
|
||||||
|
_values[chat][var_name] = var_value
|
||||||
|
|
||||||
|
-- Save values to file
|
||||||
|
serialize_to_file(_values, _file_values)
|
||||||
|
|
||||||
|
return "Gespeichert: "..var_name.." = "..var_value
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run(msg, matches)
|
||||||
|
local chat_id = tostring(msg.to.id)
|
||||||
|
local text = save_value(chat_id, msg.text)
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Setze Variable",
|
||||||
|
usage = "/set [Variablenname] [Daten]",
|
||||||
|
patterns = {"^/set (%a+) (.+)$"},
|
||||||
|
run = run
|
||||||
|
}
|
99
plugins/time.lua
Normal file
99
plugins/time.lua
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
-- Implement a command !time [area] which uses
|
||||||
|
-- 2 Google APIs to get the desired result:
|
||||||
|
-- 1. Geocoding to get from area to a lat/long pair
|
||||||
|
-- 2. Timezone to get the local time in that lat/long location
|
||||||
|
|
||||||
|
-- Globals
|
||||||
|
-- If you have a google api key for the geocoding/timezone api
|
||||||
|
api_key = nil
|
||||||
|
base_api = "https://maps.googleapis.com/maps/api"
|
||||||
|
dateFormat = "%A, %d. %B - %H:%M:%S"
|
||||||
|
|
||||||
|
-- Need the utc time for the google api
|
||||||
|
function utctime()
|
||||||
|
return os.time(os.date("!*t"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Use the geocoding api to get the lattitude and longitude with accuracy specifier
|
||||||
|
-- CHECKME: this seems to work without a key??
|
||||||
|
function get_latlong(area)
|
||||||
|
local api = base_api .. "/geocode/json?"
|
||||||
|
local parameters = "address=".. (URL.escape(area) or "")
|
||||||
|
if api_key ~= nil then
|
||||||
|
parameters = parameters .. "&key="..api_key
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do the request
|
||||||
|
local res, code = https.request(api..parameters)
|
||||||
|
if code ~=200 then return nil end
|
||||||
|
local data = json:decode(res)
|
||||||
|
|
||||||
|
if (data.status == "ZERO_RESULTS") then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if (data.status == "OK") then
|
||||||
|
-- Get the data
|
||||||
|
lat = data.results[1].geometry.location.lat
|
||||||
|
lng = data.results[1].geometry.location.lng
|
||||||
|
acc = data.results[1].geometry.location_type
|
||||||
|
types= data.results[1].types
|
||||||
|
return lat,lng,acc,types
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Use timezone api to get the time in the lat,
|
||||||
|
-- Note: this needs an API key
|
||||||
|
function get_time(lat,lng)
|
||||||
|
local api = base_api .. "/timezone/json?"
|
||||||
|
|
||||||
|
-- Get a timestamp (server time is relevant here)
|
||||||
|
local timestamp = utctime()
|
||||||
|
local parameters = "location=" ..
|
||||||
|
URL.escape(lat) .. "," ..
|
||||||
|
URL.escape(lng) ..
|
||||||
|
"×tamp="..URL.escape(timestamp)
|
||||||
|
if api_key ~=nil then
|
||||||
|
parameters = parameters .. "&key="..api_key
|
||||||
|
end
|
||||||
|
|
||||||
|
local res,code = https.request(api..parameters)
|
||||||
|
if code ~= 200 then return nil end
|
||||||
|
local data = json:decode(res)
|
||||||
|
|
||||||
|
if (data.status == "ZERO_RESULTS") then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if (data.status == "OK") then
|
||||||
|
-- Construct what we want
|
||||||
|
-- The local time in the location is:
|
||||||
|
-- timestamp + rawOffset + dstOffset
|
||||||
|
local localTime = timestamp + data.rawOffset + data.dstOffset
|
||||||
|
return localTime, data.timeZoneId
|
||||||
|
end
|
||||||
|
return localTime
|
||||||
|
end
|
||||||
|
|
||||||
|
function getformattedLocalTime(area)
|
||||||
|
if area == nil then
|
||||||
|
return "Die Zeit nirgendswo ist nirgendswo..."
|
||||||
|
end
|
||||||
|
|
||||||
|
lat,lng,acc = get_latlong(area)
|
||||||
|
if lat == nil and lng == nil then
|
||||||
|
return 'Sieht nicht so aus, als hätten sie in "'..area..'" eine Zeit.'
|
||||||
|
end
|
||||||
|
local localTime, timeZoneId = get_time(lat,lng)
|
||||||
|
|
||||||
|
return "Die lokale Zeit in "..timeZoneId.." ist: ".. os.date(dateFormat,localTime)
|
||||||
|
end
|
||||||
|
|
||||||
|
function run(msg, matches)
|
||||||
|
return getformattedLocalTime(matches[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Zeigt die lokale Zeit in einer Zeitzone an",
|
||||||
|
usage = "/zeit [Land/Ort]",
|
||||||
|
patterns = {"^/zeit (.*)$","^/Zeit (.*)$"},
|
||||||
|
run = run
|
||||||
|
}
|
76
plugins/twitter.lua
Normal file
76
plugins/twitter.lua
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
local OAuth = require "OAuth"
|
||||||
|
|
||||||
|
local consumer_key = cred_data.tw_consumer_key
|
||||||
|
local consumer_secret = cred_data.tw_consumer_secret
|
||||||
|
local access_token = cred_data.tw_access_token
|
||||||
|
local access_token_secret = cred_data.tw_access_token_secret
|
||||||
|
|
||||||
|
local client = OAuth.new(consumer_key, consumer_secret, {
|
||||||
|
RequestToken = "https://api.twitter.com/oauth/request_token",
|
||||||
|
AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"},
|
||||||
|
AccessToken = "https://api.twitter.com/oauth/access_token"
|
||||||
|
}, {
|
||||||
|
OAuthToken = access_token,
|
||||||
|
OAuthTokenSecret = access_token_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
function run(msg, matches)
|
||||||
|
|
||||||
|
if consumer_key:isempty() then
|
||||||
|
return "Twitter Consumer Key is empty, write it in plugins/twitter.lua"
|
||||||
|
end
|
||||||
|
if consumer_secret:isempty() then
|
||||||
|
return "Twitter Consumer Secret is empty, write it in plugins/twitter.lua"
|
||||||
|
end
|
||||||
|
if access_token:isempty() then
|
||||||
|
return "Twitter Access Token is empty, write it in plugins/twitter.lua"
|
||||||
|
end
|
||||||
|
if access_token_secret:isempty() then
|
||||||
|
return "Twitter Access Token Secret is empty, write it in plugins/twitter.lua"
|
||||||
|
end
|
||||||
|
|
||||||
|
local twitter_url = "https://api.twitter.com/1.1/statuses/show/" .. matches[1] .. ".json"
|
||||||
|
local response_code, response_headers, response_status_line, response_body = client:PerformRequest("GET", twitter_url)
|
||||||
|
local response = json:decode(response_body)
|
||||||
|
|
||||||
|
local header = "Tweet von " .. response.user.name .. " (@" .. response.user.screen_name .. ")\n"
|
||||||
|
local text = response.text
|
||||||
|
|
||||||
|
-- replace short URLs
|
||||||
|
if response.entities.url then
|
||||||
|
for k, v in pairs(response.entities.urls) do
|
||||||
|
local short = v.url
|
||||||
|
local long = v.expanded_url
|
||||||
|
text = text:gsub(short, long)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- remove images
|
||||||
|
local images = {}
|
||||||
|
if response.entities.media and response.extended_entities.media then
|
||||||
|
for k, v in pairs(response.extended_entities.media) do
|
||||||
|
local url = v.url
|
||||||
|
local pic = v.media_url
|
||||||
|
text = text:gsub(url, "")
|
||||||
|
table.insert(images, pic)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send the parts
|
||||||
|
local receiver = get_receiver(msg)
|
||||||
|
send_msg(receiver, header .. "\n" .. text, ok_cb, false)
|
||||||
|
for k, v in pairs(images) do
|
||||||
|
local file = download_to_file(v)
|
||||||
|
send_photo(receiver, file, ok_cb, false)
|
||||||
|
delay_s(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "When user sends twitter URL, send text and images to origin. Requieres OAuth Key.",
|
||||||
|
usage = "",
|
||||||
|
patterns = {"https://twitter.com/[^/]+/status/([0-9]+)"},
|
||||||
|
run = run
|
||||||
|
}
|
60
plugins/weather.lua
Normal file
60
plugins/weather.lua
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
do
|
||||||
|
|
||||||
|
local BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
|
||||||
|
|
||||||
|
|
||||||
|
local function get_weather(location)
|
||||||
|
print("Finde Wetter in ", location)
|
||||||
|
local url = BASE_URL
|
||||||
|
url = url..'?q='..location
|
||||||
|
url = url..'&lang=de&units=metric'
|
||||||
|
|
||||||
|
local b, c, h = http.request(url)
|
||||||
|
if c ~= 200 then return nil end
|
||||||
|
|
||||||
|
local weather = json:decode(b)
|
||||||
|
local city = weather.name
|
||||||
|
local country = weather.sys.country
|
||||||
|
local dot_temperature = round(weather.main.temp, 2)
|
||||||
|
local temperature = string.gsub(dot_temperature, "%.", "%,")
|
||||||
|
local temp = 'Die Temperatur in '..city
|
||||||
|
..' (' ..country..')'
|
||||||
|
..' beträgt '..temperature..'°C'
|
||||||
|
local conditions = 'Die aktuelle Wetterlage ist: '
|
||||||
|
.. weather.weather[1].description
|
||||||
|
|
||||||
|
if weather.weather[1].main == 'Clear' then
|
||||||
|
conditions = conditions .. ' ☀'
|
||||||
|
elseif weather.weather[1].main == 'Clouds' then
|
||||||
|
conditions = conditions .. ' ☁☁'
|
||||||
|
elseif weather.weather[1].main == 'Rain' then
|
||||||
|
conditions = conditions .. ' ☔'
|
||||||
|
elseif weather.weather[1].main == 'Thunderstorm' then
|
||||||
|
conditions = conditions .. ' ☔☔☔☔'
|
||||||
|
end
|
||||||
|
return temp .. '\n' .. conditions
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run(msg, matches)
|
||||||
|
local city = 'Berlin'
|
||||||
|
|
||||||
|
if matches[1] ~= '/wetter' then
|
||||||
|
city = matches[1]
|
||||||
|
end
|
||||||
|
local text = get_weather(city)
|
||||||
|
if not text then
|
||||||
|
text = 'Konnte das Wetter von dieser Stadt nicht bekommen.'
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Wetter in dieser Stadt (Berlin ist Standard)",
|
||||||
|
usage = "/wetter (Stadt)",
|
||||||
|
patterns = {
|
||||||
|
"^/wetter$",
|
||||||
|
"^/wetter(.*)$"},
|
||||||
|
run = run
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
44
plugins/youtube.lua
Normal file
44
plugins/youtube.lua
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
do
|
||||||
|
|
||||||
|
local BASE_URL = 'https://www.googleapis.com/youtube/v3'
|
||||||
|
|
||||||
|
function get_yt_data (yt_code)
|
||||||
|
local apikey = cred_data.google_apikey
|
||||||
|
local url = BASE_URL..'/videos?part=snippet,statistics&key='..apikey..'&id='..yt_code..'&fields=items(snippet(channelTitle,localized(title,description)),statistics(viewCount,likeCount,dislikeCount,commentCount))'
|
||||||
|
local res,code = https.request(url)
|
||||||
|
if code ~= 200 then return "HTTP-FEHLER" end
|
||||||
|
local data = json:decode(res).items[1]
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
function send_youtube_data(data, receiver)
|
||||||
|
local title = data.snippet.localized.title
|
||||||
|
-- local description = data.items[1].snippet.localized.description
|
||||||
|
local uploader = data.snippet.channelTitle
|
||||||
|
local viewCount = data.statistics.viewCount
|
||||||
|
local likeCount = data.statistics.likeCount
|
||||||
|
local dislikeCount = data.statistics.dislikeCount
|
||||||
|
local commentCount = data.statistics.commentCount
|
||||||
|
|
||||||
|
local text = title..'\n(Hochgeladen von: '..uploader..', '..viewCount..' mal angesehen, '..likeCount..' Likes und '..dislikeCount..' Dislikes, '..commentCount..' Kommentare)\n'
|
||||||
|
send_msg(receiver, text, ok_cb, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
function run(msg, matches)
|
||||||
|
local yt_code = matches[1]
|
||||||
|
local data = get_yt_data(yt_code)
|
||||||
|
local receiver = get_receiver(msg)
|
||||||
|
send_youtube_data(data, receiver)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
description = "Sendet YouTube-Info.",
|
||||||
|
usage = "",
|
||||||
|
patterns = {
|
||||||
|
"youtu.be/([A-Za-z0-9-_-]+)",
|
||||||
|
"youtube.com/watch%?v=([A-Za-z0-9-_-]+)",
|
||||||
|
},
|
||||||
|
run = run
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
Reference in New Issue
Block a user