Added raw file youtube support as holdover until invidious embed is
viable. This does not help with region locking, but at least allows age restricted videos with no sign in, doesnt run any google scripts, and does not run ads.
This commit is contained in:
parent
5535917f08
commit
3f653c4893
|
|
@ -1238,7 +1238,7 @@ PlaylistModule.prototype.startPlayback = function (time) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lead-in time of 3 seconds to allow clients to buffer */
|
/* Lead-in time of 3 seconds to allow clients to buffer */
|
||||||
time = time || (media.seconds > 0 ? -3 : 0);
|
time = time || (media.seconds > 0 ? (media.type == "yt" ? -6 : -3) : 0); //if its a yt vid make it 6 for the link pull
|
||||||
media.paused = time < 0;
|
media.paused = time < 0;
|
||||||
media.currentTime = time;
|
media.currentTime = time;
|
||||||
|
|
||||||
|
|
@ -1259,7 +1259,15 @@ PlaylistModule.prototype.startPlayback = function (time) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sendChangeMedia(self.channel.users);
|
if(self.current.media.type == "yt"){//if its yt
|
||||||
|
InfoGetter.getYTRaw(self.current.media.id,function(url){//get raw link from invidious api
|
||||||
|
self.current.media.meta.rawLink = url;//set to meta
|
||||||
|
self.sendChangeMedia(self.channel.users);//fuggin SEND IT
|
||||||
|
});
|
||||||
|
|
||||||
|
}else{
|
||||||
|
self.sendChangeMedia(self.channel.users);
|
||||||
|
}
|
||||||
self.channel.notifyModules("onMediaChange", [self.current.media]);
|
self.channel.notifyModules("onMediaChange", [self.current.media]);
|
||||||
|
|
||||||
/* Only start the timer if the media item is not live, i.e. has a duration */
|
/* Only start the timer if the media item is not live, i.e. has a duration */
|
||||||
|
|
|
||||||
|
|
@ -594,5 +594,26 @@ module.exports = {
|
||||||
} else {
|
} else {
|
||||||
callback("Unknown media type '" + type + "'", null);
|
callback("Unknown media type '" + type + "'", null);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
getYTRaw: function (id, cb){
|
||||||
|
var options = {
|
||||||
|
host: Config.get("invidious-source"),
|
||||||
|
port: 443,
|
||||||
|
path: "/api/v1/videos/" + id,
|
||||||
|
method: "GET",
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
urlRetrieve(https, options, function (status, data) {
|
||||||
|
if(status !== 200) {
|
||||||
|
console.log("Invidious HTTPS error code: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
var vid = JSON.parse(data);
|
||||||
|
|
||||||
|
if(vid.formatStreams[0] != null){//TEMPORARY FOR FRONTEND DEV PURPOSES, PULL LINK AND SET AGAIN WHEN VIDEO QUEUED(shit expires)
|
||||||
|
cb(vid.formatStreams[vid.formatStreams.length - 1].url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ Media.prototype = {
|
||||||
embed: this.meta.embed,
|
embed: this.meta.embed,
|
||||||
gdrive_subtitles: this.meta.gdrive_subtitles,
|
gdrive_subtitles: this.meta.gdrive_subtitles,
|
||||||
textTracks: this.meta.textTracks,
|
textTracks: this.meta.textTracks,
|
||||||
mixer: this.meta.mixer
|
mixer: this.meta.mixer,
|
||||||
|
rawLink: this.meta.rawLink
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ mixin lcheckbox(id, label)
|
||||||
.checkbox
|
.checkbox
|
||||||
input(type="checkbox", id=id)
|
input(type="checkbox", id=id)
|
||||||
|
|
||||||
mixin rcheckbox(id, label)
|
mixin rcheckbox(id, label, title)
|
||||||
.form-group
|
.form-group
|
||||||
.col-sm-8.col-sm-offset-4
|
.col-sm-8.col-sm-offset-4
|
||||||
.checkbox
|
.checkbox
|
||||||
label(for=id)
|
label(for=id, title=title)
|
||||||
input(type="checkbox", id=id)
|
input(type="checkbox", id=id,title=title)
|
||||||
= label
|
= label
|
||||||
|
|
||||||
mixin textbox(id, label, placeholder)
|
mixin textbox(id, label, placeholder)
|
||||||
|
|
@ -33,7 +33,6 @@ mixin us-general
|
||||||
option(value="/css/themes/fore.st.css") fore.st
|
option(value="/css/themes/fore.st.css") fore.st
|
||||||
.col-sm-4
|
.col-sm-4
|
||||||
.col-sm-8
|
.col-sm-8
|
||||||
p.text-danger Changing layouts may require refreshing to take effect.
|
|
||||||
+rcheckbox("us-no-channelcss", "Ignore Channel CSS")
|
+rcheckbox("us-no-channelcss", "Ignore Channel CSS")
|
||||||
+rcheckbox("us-no-channeljs", "Ignore Channel Javascript")
|
+rcheckbox("us-no-channeljs", "Ignore Channel Javascript")
|
||||||
.clear
|
.clear
|
||||||
|
|
@ -55,19 +54,21 @@ mixin us-playback
|
||||||
form.form-horizontal(action="javascript:void(0)")
|
form.form-horizontal(action="javascript:void(0)")
|
||||||
+rcheckbox("us-synch", "Synchronize video playback")
|
+rcheckbox("us-synch", "Synchronize video playback")
|
||||||
+textbox("us-synch-accuracy", "Synch threshold (seconds)", "2")
|
+textbox("us-synch-accuracy", "Synch threshold (seconds)", "2")
|
||||||
+rcheckbox("us-wmode-transparent", "Set wmode=transparent")
|
+rcheckbox("us-wmode-transparent", "Set wmode=transparent", "Allows elements to be placed over the video. May cause a bit of lag on toasters.")
|
||||||
.form-group
|
|
||||||
.col-sm-4
|
|
||||||
.col-sm-8
|
|
||||||
p.text-info Setting <code>wmode=transparent</code> allows objects to be displayed above the video player, but may cause performance issues on some systems.
|
|
||||||
+rcheckbox("us-hidevideo", "Remove the video player")
|
+rcheckbox("us-hidevideo", "Remove the video player")
|
||||||
+rcheckbox("us-playlistbuttons", "Hide playlist buttons by default")
|
+rcheckbox("us-playlistbuttons", "Hide playlist buttons by default")
|
||||||
+rcheckbox("us-oldbtns", "Compact playlist buttons")
|
+rcheckbox("us-oldbtns", "Compact playlist buttons")
|
||||||
+rcheckbox("us-video-orientation", "Show video orientation buttons above player")
|
+rcheckbox("us-video-orientation", "Show video orientation buttons above player")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label.col-sm-4(for="#us-default-quality") Quality Preference
|
label.control-label.col-sm-4(for="#us-default-quality",title="Will not work automagically on official YT embeds because google are dicks.") Quality Preference
|
||||||
.col-sm-8
|
.col-sm-8
|
||||||
select#us-default-quality.form-control
|
select#us-yt-source.form-control(title="YT Embed may not work with all site features.")
|
||||||
|
option(value="vid.puffyan.us") Raw File Link(720p)
|
||||||
|
option(value="OFYT") Official YT Embed
|
||||||
|
.form-group
|
||||||
|
label.control-label.col-sm-4(for="#us-default-quality",title="Will not work automagically on official YT embeds because google are dicks.") Quality Preference
|
||||||
|
.col-sm-8
|
||||||
|
select#us-default-quality.form-control(title="Will not work automagically on officialy YT embeds because google are dicks.")
|
||||||
option(value="auto") Auto
|
option(value="auto") Auto
|
||||||
option(value="240") 240p
|
option(value="240") 240p
|
||||||
option(value="360") 360p
|
option(value="360") 360p
|
||||||
|
|
@ -75,10 +76,6 @@ mixin us-playback
|
||||||
option(value="720") 720p
|
option(value="720") 720p
|
||||||
option(value="1080") 1080p
|
option(value="1080") 1080p
|
||||||
option(value="best") Highest Available
|
option(value="best") Highest Available
|
||||||
.form-group
|
|
||||||
.col-sm-4
|
|
||||||
.col-sm-8
|
|
||||||
p.text-info Due to technical changes on YouTube's side, the CyTube quality preference can no longer be automatically applied on YouTube videos. See <a href="https://github.com/calzoneman/sync/issues/726" rel="noopener noreferer" target="_blank">this GitHub issue</a> for details.
|
|
||||||
|
|
||||||
mixin us-chat
|
mixin us-chat
|
||||||
#us-chat.tab-pane
|
#us-chat.tab-pane
|
||||||
|
|
@ -89,27 +86,24 @@ mixin us-chat
|
||||||
+rcheckbox("us-sort-rank", "Sort userlist by rank")
|
+rcheckbox("us-sort-rank", "Sort userlist by rank")
|
||||||
+rcheckbox("us-sort-afk", "Sort AFKers to bottom")
|
+rcheckbox("us-sort-afk", "Sort AFKers to bottom")
|
||||||
+rcheckbox("us-legacy-emote", "Use legacy Cytube emote menu")
|
+rcheckbox("us-legacy-emote", "Use legacy Cytube emote menu")
|
||||||
.col-sm-4
|
|
||||||
.col-sm-8
|
|
||||||
p.text-info The following 3 options apply to how and when you will be notified if a new chat message is received while CyTube is not the active window.
|
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label.col-sm-4(for="#us-blink-title") Blink page title on new messages
|
label.control-label.col-sm-4(for="#us-blink-title",title="Only applies when not active window/tab.") Blink page title on new messages
|
||||||
.col-sm-8
|
.col-sm-8
|
||||||
select#us-blink-title.form-control
|
select#us-blink-title.form-control(title="Only applies when not active window/tab.")
|
||||||
option(value="never") Never
|
option(value="never") Never
|
||||||
option(value="onlyping") Only when I am mentioned or PMed
|
option(value="onlyping") Only when I am mentioned or PMed
|
||||||
option(value="always") Always
|
option(value="always") Always
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label.col-sm-4(for="#us-ping-sound") Notification sound on new messages
|
label.control-label.col-sm-4(for="#us-ping-sound",title="Only applies when not active window/tab.") Notification sound on new messages
|
||||||
.col-sm-8
|
.col-sm-8
|
||||||
select#us-ping-sound.form-control
|
select#us-ping-sound.form-control(title="Only applies when not active window/tab.")
|
||||||
option(value="never") Never
|
option(value="never") Never
|
||||||
option(value="onlyping") Only when I am mentioned or PMed
|
option(value="onlyping") Only when I am mentioned or PMed
|
||||||
option(value="always") Always
|
option(value="always") Always
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label.col-sm-4(for="#us-notifications") Desktop notifications on new messages
|
label.control-label.col-sm-4(for="#us-notifications",title="Only applies when not active window/tab.") Desktop notifications on new messages
|
||||||
.col-sm-8
|
.col-sm-8
|
||||||
select#us-notifications.form-control
|
select#us-notifications.form-control(title="Only applies when not active window/tab.")
|
||||||
option(value="never") Never
|
option(value="never") Never
|
||||||
option(value="onlyping") Only when I am mentioned or PMed
|
option(value="onlyping") Only when I am mentioned or PMed
|
||||||
option(value="always") Always
|
option(value="always") Always
|
||||||
|
|
|
||||||
|
|
@ -859,7 +859,6 @@ Callbacks = {
|
||||||
if (isNaN(VOLUME) || VOLUME > 1 || VOLUME < 0) {
|
if (isNaN(VOLUME) || VOLUME > 1 || VOLUME < 0) {
|
||||||
VOLUME = 1;
|
VOLUME = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadNext() {
|
function loadNext() {
|
||||||
if(PLAYER){
|
if(PLAYER){
|
||||||
PLAYER.latch();
|
PLAYER.latch();
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,7 @@ var USEROPTS = {
|
||||||
sort_rank : getOrDefault("sort_rank", true),
|
sort_rank : getOrDefault("sort_rank", true),
|
||||||
sort_afk : getOrDefault("sort_afk", false),
|
sort_afk : getOrDefault("sort_afk", false),
|
||||||
legacy_emote : getOrDefault("legacy_emote", false),
|
legacy_emote : getOrDefault("legacy_emote", false),
|
||||||
|
yt_source : getOrDefault("yt_source", "vid.puffyan.us"),
|
||||||
show_seconds : getOrDefault("show_seconds", false),
|
show_seconds : getOrDefault("show_seconds", false),
|
||||||
default_quality : getOrDefault("default_quality", "auto"),
|
default_quality : getOrDefault("default_quality", "auto"),
|
||||||
boop : getOrDefault("boop", "never"),
|
boop : getOrDefault("boop", "never"),
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,10 @@ fpset.ocall = function(){
|
||||||
|
|
||||||
$("<form>").append(
|
$("<form>").append(
|
||||||
$("<label>").prop("for","qs-yt-source").html("Youtube Source: "),
|
$("<label>").prop("for","qs-yt-source").html("Youtube Source: "),
|
||||||
$("<select>").prop("id","qs-yt-source").addClass("qs-form"),
|
$("<select>").prop("id","qs-yt-source").addClass("qs-form").change(function(){
|
||||||
|
USEROPTS.yt_source = $("#qs-yt-source").val();
|
||||||
|
processOpts();
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -310,9 +313,11 @@ fpset.ocall = function(){
|
||||||
|
|
||||||
fpset.loadSettings = function(){
|
fpset.loadSettings = function(){
|
||||||
$("#us-theme").children().clone().appendTo($("#qs-theme"));
|
$("#us-theme").children().clone().appendTo($("#qs-theme"));
|
||||||
|
$("#us-yt-source").children().clone().appendTo($("#qs-yt-source"));
|
||||||
$("#qs-theme").val(USEROPTS.theme);
|
$("#qs-theme").val(USEROPTS.theme);
|
||||||
$("#qs-orient-buttons").prop("checked", USEROPTS.show_orientation);
|
$("#qs-orient-buttons").prop("checked", USEROPTS.show_orientation);
|
||||||
$("#qs-sync-threshold").val(USEROPTS.sync_accuracy);
|
$("#qs-sync-threshold").val(USEROPTS.sync_accuracy);
|
||||||
|
$("#qs-yt-source").val(USEROPTS.yt_source);
|
||||||
$("#qs-legacy-emote").prop("checked", USEROPTS.legacy_emote);
|
$("#qs-legacy-emote").prop("checked", USEROPTS.legacy_emote);
|
||||||
$("#us-blink-title").children().clone().appendTo($("#qs-blink-title"));
|
$("#us-blink-title").children().clone().appendTo($("#qs-blink-title"));
|
||||||
$("#qs-blink-title").val(USEROPTS.blink_title);
|
$("#qs-blink-title").val(USEROPTS.blink_title);
|
||||||
|
|
|
||||||
|
|
@ -242,30 +242,43 @@
|
||||||
}
|
}
|
||||||
this.setMediaProperties(data);
|
this.setMediaProperties(data);
|
||||||
this.pauseSeekRaceCondition = false;
|
this.pauseSeekRaceCondition = false;
|
||||||
waitUntilDefined(window, 'YT', (function(_this) {
|
if(USEROPTS.yt_source == "OFYT"){
|
||||||
return function() {
|
waitUntilDefined(window, 'YT', (function(_this) {//THIS IS WHERE YT VIDEO EMBED IS CREATED, RIP DIS BITCH!
|
||||||
return waitUntilDefined(YT, 'Player', function() {
|
return function() {
|
||||||
var wmode;
|
return waitUntilDefined(YT, 'Player', function() {
|
||||||
removeOld();
|
var wmode;
|
||||||
wmode = USEROPTS.wmode_transparent ? 'transparent' : 'opaque';
|
removeOld();
|
||||||
return _this.yt = new YT.Player('ytapiplayer', {
|
wmode = USEROPTS.wmode_transparent ? 'transparent' : 'opaque';
|
||||||
videoId: data.id,
|
return _this.yt = new YT.Player('ytapiplayer', {
|
||||||
playerVars: {
|
videoId: data.id,
|
||||||
autohide: 1,
|
playerVars: {
|
||||||
autoplay: 1,
|
autohide: 1,
|
||||||
controls: 1,
|
autoplay: 1,
|
||||||
iv_load_policy: 3,
|
controls: 1,
|
||||||
rel: 0,
|
iv_load_policy: 3,
|
||||||
wmode: wmode
|
rel: 0,
|
||||||
},
|
wmode: wmode
|
||||||
events: {
|
},
|
||||||
onReady: _this.onReady.bind(_this),
|
events: {
|
||||||
onStateChange: _this.onStateChange.bind(_this)
|
onReady: _this.onReady.bind(_this),
|
||||||
}
|
onStateChange: _this.onStateChange.bind(_this)
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
})(this));
|
};
|
||||||
|
})(this));
|
||||||
|
}else{
|
||||||
|
var video;
|
||||||
|
video = $('<iframe/>');
|
||||||
|
removeOld(video);
|
||||||
|
video.attr({
|
||||||
|
src: "https://" + USEROPTS.yt_source + "/embed/" + data.id,
|
||||||
|
webkitallowfullscreen: true,
|
||||||
|
mozallowfullscreen: true,
|
||||||
|
allowfullscreen: true
|
||||||
|
});
|
||||||
|
this.inv = video;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
YouTubePlayer.prototype.load = function(data) {
|
YouTubePlayer.prototype.load = function(data) {
|
||||||
|
|
@ -299,6 +312,10 @@
|
||||||
}
|
}
|
||||||
if ((ev.data === YT.PlayerState.PAUSED && !this.paused) || (ev.data === YT.PlayerState.PLAYING && this.paused)) {
|
if ((ev.data === YT.PlayerState.PAUSED && !this.paused) || (ev.data === YT.PlayerState.PLAYING && this.paused)) {
|
||||||
this.paused = ev.data === YT.PlayerState.PAUSED;
|
this.paused = ev.data === YT.PlayerState.PAUSED;
|
||||||
|
if (CLIENT.leader) {
|
||||||
|
sendVideoUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (ev.data === YT.PlayerState.ENDED && CLIENT.leader) {
|
if (ev.data === YT.PlayerState.ENDED && CLIENT.leader) {
|
||||||
return socket.emit('playNext');
|
return socket.emit('playNext');
|
||||||
|
|
@ -729,7 +746,6 @@
|
||||||
});
|
});
|
||||||
_this.player.on('canplay', function() {
|
_this.player.on('canplay', function() {
|
||||||
handleWindowResize();
|
handleWindowResize();
|
||||||
return console.log(_this.player.children);
|
|
||||||
});
|
});
|
||||||
_this.player.on('timeupdate', function() {
|
_this.player.on('timeupdate', function() {
|
||||||
return setDur();
|
return setDur();
|
||||||
|
|
@ -1742,14 +1758,24 @@
|
||||||
error = error1;
|
error = error1;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
if (data.meta.direct && data.type === 'vi') {
|
if ((data.meta.direct && data.type === 'vi')) {
|
||||||
try {
|
try {
|
||||||
return window.PLAYER = new VideoJSPlayer(data);
|
return window.PLAYER = new VideoJSPlayer(data);
|
||||||
} catch (error1) {
|
} catch (error1) {
|
||||||
e = error1;
|
e = error1;
|
||||||
return console.error(e);
|
return console.error(e);
|
||||||
}
|
}
|
||||||
} else if (data.type in TYPE_MAP) {
|
} else if ((USEROPTS.yt_source !== "OFYT" && data.type == "yt")) {
|
||||||
|
data.id = data.meta.rawLink;//set link and spoof mov/h264
|
||||||
|
data.type = "fi";
|
||||||
|
data.meta.codec = "mov/h264";
|
||||||
|
try {
|
||||||
|
return window.PLAYER = new FilePlayer(data);
|
||||||
|
} catch (error1) {
|
||||||
|
e = error1;
|
||||||
|
return console.error(e);
|
||||||
|
}
|
||||||
|
}else if (data.type in TYPE_MAP) {
|
||||||
try {
|
try {
|
||||||
return window.PLAYER = TYPE_MAP[data.type](data);
|
return window.PLAYER = TYPE_MAP[data.type](data);
|
||||||
} catch (error1) {
|
} catch (error1) {
|
||||||
|
|
|
||||||
|
|
@ -713,6 +713,7 @@ function showUserOptions() {
|
||||||
$("#us-playlistbuttons").prop("checked", USEROPTS.qbtn_hide);
|
$("#us-playlistbuttons").prop("checked", USEROPTS.qbtn_hide);
|
||||||
$("#us-oldbtns").prop("checked", USEROPTS.qbtn_idontlikechange);
|
$("#us-oldbtns").prop("checked", USEROPTS.qbtn_idontlikechange);
|
||||||
$("#us-video-orientation").prop("checked", USEROPTS.show_orientation);
|
$("#us-video-orientation").prop("checked", USEROPTS.show_orientation);
|
||||||
|
$("#us-yt-source").val(USEROPTS.yt_source);
|
||||||
$("#us-default-quality").val(USEROPTS.default_quality || "auto");
|
$("#us-default-quality").val(USEROPTS.default_quality || "auto");
|
||||||
|
|
||||||
$("#us-chat-timestamp").prop("checked", USEROPTS.show_timestamps);
|
$("#us-chat-timestamp").prop("checked", USEROPTS.show_timestamps);
|
||||||
|
|
@ -751,6 +752,7 @@ function saveUserOptions() {
|
||||||
USEROPTS.qbtn_hide = $("#us-playlistbuttons").prop("checked");
|
USEROPTS.qbtn_hide = $("#us-playlistbuttons").prop("checked");
|
||||||
USEROPTS.qbtn_idontlikechange = $("#us-oldbtns").prop("checked");
|
USEROPTS.qbtn_idontlikechange = $("#us-oldbtns").prop("checked");
|
||||||
USEROPTS.show_orientation = $("#us-video-orientation").prop("checked");
|
USEROPTS.show_orientation = $("#us-video-orientation").prop("checked");
|
||||||
|
USEROPTS.yt_source = $("#us-yt-source").val();
|
||||||
USEROPTS.default_quality = $("#us-default-quality").val();
|
USEROPTS.default_quality = $("#us-default-quality").val();
|
||||||
|
|
||||||
USEROPTS.show_timestamps = $("#us-chat-timestamp").prop("checked");
|
USEROPTS.show_timestamps = $("#us-chat-timestamp").prop("checked");
|
||||||
|
|
@ -850,6 +852,14 @@ function applyOpts() {
|
||||||
USEROPTS.notifications = "never";
|
USEROPTS.notifications = "never";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if((USEROPTS.yt_source == "OFYT" && window.PLAYER.mediaType == "fi" && window.PLAYER.mediaId.includes("google")) || (USEROPTS.yt_source != "OFYT" && window.PLAYER.mediaType == "yt")){
|
||||||
|
PLAYER.mediaType = "";
|
||||||
|
PLAYER.mediaId = "";
|
||||||
|
|
||||||
|
console.log("switch");
|
||||||
|
socket.emit("playerReady");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTimeout(t) {
|
function parseTimeout(t) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue