mirror of
https://github.com/iv-org/invidious.git
synced 2025-07-31 01:38:31 +00:00
Merge remote-tracking branch 'upstream/master' into side-menu
This commit is contained in:
commit
31b587baea
@ -58,6 +58,7 @@ div {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,11 +81,15 @@ a.pure-button-primary:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.thumbnail {
|
div.thumbnail {
|
||||||
|
padding: 28.125%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.thumbnail {
|
img.thumbnail {
|
||||||
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
@ -255,6 +260,41 @@ img.thumbnail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-play-control,
|
||||||
|
.vjs-volume-panel,
|
||||||
|
.vjs-current-time,
|
||||||
|
.vjs-time-control,
|
||||||
|
.vjs-duration,
|
||||||
|
.vjs-progress-control,
|
||||||
|
.vjs-remaining-time {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-captions-button {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-quality-selector {
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-playback-rate {
|
||||||
|
order: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-share-control {
|
||||||
|
order: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-fullscreen-control {
|
||||||
|
order: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-control-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.video-js .vjs-control-bar,
|
.video-js .vjs-control-bar,
|
||||||
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
|
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
|
||||||
background-color: rgba(35, 35, 35, 0.75);
|
background-color: rgba(35, 35, 35, 0.75);
|
||||||
@ -326,29 +366,17 @@ img.thumbnail {
|
|||||||
padding-top: 82vh;
|
padding-top: 82vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video.video-js {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#player-container {
|
#player-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 82vh;
|
padding-bottom: 82vh;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress-container {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: #a0a0a0;
|
|
||||||
color: rgba(35, 35, 35, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#download-progress {
|
|
||||||
width: 0%;
|
|
||||||
border-radius: 2px;
|
|
||||||
height: 10px;
|
|
||||||
background-color: rgba(0, 182, 240, 1);
|
|
||||||
color: #fff;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-control-group label {
|
.pure-control-group label {
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
}
|
}
|
||||||
|
2
assets/css/video-js.min.css
vendored
2
assets/css/video-js.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* videojs-share
|
* videojs-share
|
||||||
* @version 2.0.1
|
* @version 3.0.0
|
||||||
* @copyright 2018 Mikhail Khazov <mkhazov.work@gmail.com>
|
* @copyright 2018 Mikhail Khazov <mkhazov.work@gmail.com>
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-modal-dialog-content{display:flex;align-items:center;padding:0;background-image:linear-gradient(to bottom, rgba(0,0,0,0.77), rgba(0,0,0,0.75))}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{position:absolute;right:0;top:5px;width:30px;height:30px;color:#fff;cursor:pointer;opacity:0.9;transition:opacity 0.25s ease-out}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button:before{content:'×';font-size:20px;line-height:15px}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button:hover{opacity:1}.video-js .vjs-share{display:flex;flex-direction:column;justify-content:space-around;align-items:center;width:100%;height:100%;max-height:400px}.video-js .vjs-share__top,.video-js .vjs-share__middle,.video-js .vjs-share__bottom{display:flex}.video-js .vjs-share__top,.video-js .vjs-share__middle{flex-direction:column;justify-content:space-between}.video-js .vjs-share__middle{padding:0 25px}.video-js .vjs-share__title{align-self:center;font-size:22px;color:#fff}.video-js .vjs-share__subtitle{width:100%;margin:0 auto 12px;font-size:16px;color:#fff;opacity:0.7}.video-js .vjs-share__short-link-wrapper{position:relative;display:block;width:100%;height:40px;margin:0 auto;margin-bottom:15px;border:0;color:rgba(255,255,255,0.65);background-color:#363636;outline:none;overflow:hidden;flex-shrink:0}.video-js .vjs-share__short-link{display:block;width:100%;height:100%;padding:0 40px 0 15px;border:0;color:rgba(255,255,255,0.65);background-color:#363636;outline:none}.video-js .vjs-share__btn{position:absolute;right:0;bottom:0;height:40px;width:40px;display:flex;align-items:center;padding:0 11px;border:0;color:#fff;background-color:#2e2e2e;background-size:18px 19px;background-position:center;background-repeat:no-repeat;cursor:pointer;outline:none;transition:width 0.3s ease-out, padding 0.3s ease-out}.video-js .vjs-share__btn svg{flex-shrink:0}.video-js .vjs-share__btn span{position:relative;padding-left:10px;opacity:0;transition:opacity 0.3s ease-out}.video-js .vjs-share__btn:hover{justify-content:center;width:100%;padding:0 40px;background-image:none}.video-js .vjs-share__btn:hover span{opacity:1}.video-js .vjs-share__socials{display:flex;flex-wrap:wrap;justify-content:center;align-content:flex-start;transition:width 0.3s ease-out, height 0.3s ease-out}.video-js .vjs-share__social{display:flex;justify-content:center;align-items:center;flex-shrink:0;width:32px;height:32px;margin-right:6px;margin-bottom:6px;cursor:pointer;font-size:8px;transition:transform 0.3s ease-out, filter 0.2s ease-out;border:none;outline:none}.video-js .vjs-share__social:hover{filter:brightness(115%)}.video-js .vjs-share__social svg{width:100%;max-height:24px}.video-js .vjs-share__social_vk{background-color:#5d7294}.video-js .vjs-share__social_ok{background-color:#ed7c20}.video-js .vjs-share__social_mail{background-color:#134785}.video-js .vjs-share__social_tw{background-color:#76aaeb}.video-js .vjs-share__social_reddit{background-color:#ff4500}.video-js .vjs-share__social_fbFeed{background-color:#475995}.video-js .vjs-share__social_messenger{background-color:#0084ff}.video-js .vjs-share__social_gp{background-color:#d53f35}.video-js .vjs-share__social_linkedin{background-color:#0077b5}.video-js .vjs-share__social_viber{background-color:#766db5}.video-js .vjs-share__social_telegram{background-color:#4bb0e2}.video-js .vjs-share__social_whatsapp{background-color:#78c870}.video-js .vjs-share__bottom{justify-content:center}@media (max-height: 220px){.video-js .vjs-share .hidden-xs{display:none}}@media (max-height: 350px){.video-js .vjs-share .hidden-sm{display:none}}@media (min-height: 400px){.video-js .vjs-share__title{margin-bottom:15px}.video-js .vjs-share__short-link-wrapper{margin-bottom:30px}}@media (min-width: 320px){.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{right:5px;top:10px}}@media (min-width: 660px){.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{right:20px;top:20px}.video-js .vjs-share__social{width:40px;height:40px}}
|
.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-modal-dialog-content{display:flex;align-items:center;padding:0;background-image:linear-gradient(to bottom, rgba(0,0,0,0.77), rgba(0,0,0,0.75))}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{position:absolute;right:0;top:5px;width:30px;height:30px;color:#fff;cursor:pointer;opacity:0.9;transition:opacity 0.25s ease-out}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button:before{content:'×';font-size:20px;line-height:15px}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button:hover{opacity:1}.video-js .vjs-share{display:flex;flex-direction:column;justify-content:space-around;align-items:center;width:100%;height:100%;max-height:400px}.video-js .vjs-share__top,.video-js .vjs-share__middle,.video-js .vjs-share__bottom{display:flex}.video-js .vjs-share__top,.video-js .vjs-share__middle{flex-direction:column;justify-content:space-between}.video-js .vjs-share__middle{padding:0 25px}.video-js .vjs-share__title{align-self:center;font-size:22px;color:#fff}.video-js .vjs-share__subtitle{width:100%;margin:0 auto 12px;font-size:16px;color:#fff;opacity:0.7}.video-js .vjs-share__short-link-wrapper{position:relative;display:block;width:100%;height:40px;margin:0 auto;margin-bottom:15px;border:0;color:rgba(255,255,255,0.65);background-color:#363636;outline:none;overflow:hidden;flex-shrink:0}.video-js .vjs-share__short-link{display:block;width:100%;height:100%;padding:0 40px 0 15px;border:0;color:rgba(255,255,255,0.65);background-color:#363636;outline:none}.video-js .vjs-share__btn{position:absolute;right:0;bottom:0;height:40px;width:40px;display:flex;align-items:center;padding:0 11px;border:0;color:#fff;background-color:#2e2e2e;background-size:18px 19px;background-position:center;background-repeat:no-repeat;cursor:pointer;outline:none;transition:width 0.3s ease-out, padding 0.3s ease-out}.video-js .vjs-share__btn svg{flex-shrink:0}.video-js .vjs-share__btn span{position:relative;padding-left:10px;opacity:0;transition:opacity 0.3s ease-out}.video-js .vjs-share__btn:hover{justify-content:center;width:100%;padding:0 40px;background-image:none}.video-js .vjs-share__btn:hover span{opacity:1}.video-js .vjs-share__socials{display:flex;flex-wrap:wrap;justify-content:center;align-content:flex-start;transition:width 0.3s ease-out, height 0.3s ease-out}.video-js .vjs-share__social{display:flex;justify-content:center;align-items:center;flex-shrink:0;width:32px;height:32px;margin-right:6px;margin-bottom:6px;cursor:pointer;font-size:8px;transition:transform 0.3s ease-out, filter 0.2s ease-out;border:none;outline:none}.video-js .vjs-share__social:hover{filter:brightness(115%)}.video-js .vjs-share__social svg{overflow:visible;max-height:24px}.video-js .vjs-share__social_vk{background-color:#5d7294}.video-js .vjs-share__social_ok{background-color:#ed7c20}.video-js .vjs-share__social_mail{background-color:#134785}.video-js .vjs-share__social_tw{background-color:#76aaeb}.video-js .vjs-share__social_reddit{background-color:#ff4500}.video-js .vjs-share__social_fbFeed{background-color:#475995}.video-js .vjs-share__social_messenger{background-color:#0084ff}.video-js .vjs-share__social_gp{background-color:#d53f35}.video-js .vjs-share__social_linkedin{background-color:#0077b5}.video-js .vjs-share__social_viber{background-color:#766db5}.video-js .vjs-share__social_telegram{background-color:#4bb0e2}.video-js .vjs-share__social_whatsapp{background-color:#78c870}.video-js .vjs-share__bottom{justify-content:center}@media (max-height: 220px){.video-js .vjs-share .hidden-xs{display:none}}@media (max-height: 350px){.video-js .vjs-share .hidden-sm{display:none}}@media (min-height: 400px){.video-js .vjs-share__title{margin-bottom:15px}.video-js .vjs-share__short-link-wrapper{margin-bottom:30px}}@media (min-width: 320px){.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{right:5px;top:10px}}@media (min-width: 660px){.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{right:20px;top:20px}.video-js .vjs-share__social{width:40px;height:40px}}
|
||||||
|
19
assets/js/video.min.js
vendored
19
assets/js/video.min.js
vendored
File diff suppressed because one or more lines are too long
4
assets/js/videojs-share.min.js
vendored
4
assets/js/videojs-share.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,413 +0,0 @@
|
|||||||
/*
|
|
||||||
* Video.js Hotkeys
|
|
||||||
* https://github.com/ctd1500/videojs-hotkeys
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Chris Dougherty
|
|
||||||
* Licensed under the Apache-2.0 license.
|
|
||||||
*/
|
|
||||||
|
|
||||||
;(function(root, factory) {
|
|
||||||
if (typeof window !== 'undefined' && window.videojs) {
|
|
||||||
factory(window.videojs);
|
|
||||||
} else if (typeof define === 'function' && define.amd) {
|
|
||||||
define('videojs-hotkeys', ['video.js'], function (module) {
|
|
||||||
return factory(module.default || module);
|
|
||||||
});
|
|
||||||
} else if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports = factory(require('video.js'));
|
|
||||||
}
|
|
||||||
}(this, function (videojs) {
|
|
||||||
"use strict";
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window['videojs_hotkeys'] = { version: "0.2.22" };
|
|
||||||
}
|
|
||||||
|
|
||||||
var hotkeys = function(options) {
|
|
||||||
var player = this;
|
|
||||||
var pEl = player.el();
|
|
||||||
var doc = document;
|
|
||||||
var def_options = {
|
|
||||||
volumeStep: 0.1,
|
|
||||||
seekStep: 5,
|
|
||||||
enableMute: true,
|
|
||||||
enableVolumeScroll: true,
|
|
||||||
enableHoverScroll: true,
|
|
||||||
enableFullscreen: true,
|
|
||||||
enableNumbers: true,
|
|
||||||
enableJogStyle: false,
|
|
||||||
alwaysCaptureHotkeys: false,
|
|
||||||
enableModifiersForNumbers: true,
|
|
||||||
enableInactiveFocus: true,
|
|
||||||
skipInitialFocus: false,
|
|
||||||
playPauseKey: playPauseKey,
|
|
||||||
rewindKey: rewindKey,
|
|
||||||
forwardKey: forwardKey,
|
|
||||||
volumeUpKey: volumeUpKey,
|
|
||||||
volumeDownKey: volumeDownKey,
|
|
||||||
muteKey: muteKey,
|
|
||||||
fullscreenKey: fullscreenKey,
|
|
||||||
customKeys: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
var cPlay = 1,
|
|
||||||
cRewind = 2,
|
|
||||||
cForward = 3,
|
|
||||||
cVolumeUp = 4,
|
|
||||||
cVolumeDown = 5,
|
|
||||||
cMute = 6,
|
|
||||||
cFullscreen = 7;
|
|
||||||
|
|
||||||
// Use built-in merge function from Video.js v5.0+ or v4.4.0+
|
|
||||||
var mergeOptions = videojs.mergeOptions || videojs.util.mergeOptions;
|
|
||||||
options = mergeOptions(def_options, options || {});
|
|
||||||
|
|
||||||
var volumeStep = options.volumeStep,
|
|
||||||
seekStep = options.seekStep,
|
|
||||||
enableMute = options.enableMute,
|
|
||||||
enableVolumeScroll = options.enableVolumeScroll,
|
|
||||||
enableHoverScroll = options.enableHoverScroll,
|
|
||||||
enableFull = options.enableFullscreen,
|
|
||||||
enableNumbers = options.enableNumbers,
|
|
||||||
enableJogStyle = options.enableJogStyle,
|
|
||||||
alwaysCaptureHotkeys = options.alwaysCaptureHotkeys,
|
|
||||||
enableModifiersForNumbers = options.enableModifiersForNumbers,
|
|
||||||
enableInactiveFocus = options.enableInactiveFocus,
|
|
||||||
skipInitialFocus = options.skipInitialFocus;
|
|
||||||
|
|
||||||
// Set default player tabindex to handle keydown and doubleclick events
|
|
||||||
if (!pEl.hasAttribute('tabIndex')) {
|
|
||||||
pEl.setAttribute('tabIndex', '-1');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove player outline to fix video performance issue
|
|
||||||
pEl.style.outline = "none";
|
|
||||||
|
|
||||||
if (alwaysCaptureHotkeys || !player.autoplay()) {
|
|
||||||
if (!skipInitialFocus) {
|
|
||||||
player.one('play', function() {
|
|
||||||
pEl.focus(); // Fixes the .vjs-big-play-button handing focus back to body instead of the player
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableInactiveFocus) {
|
|
||||||
player.on('userinactive', function() {
|
|
||||||
// When the control bar fades, re-apply focus to the player if last focus was a control button
|
|
||||||
var cancelFocusingPlayer = function() {
|
|
||||||
clearTimeout(focusingPlayerTimeout);
|
|
||||||
};
|
|
||||||
var focusingPlayerTimeout = setTimeout(function() {
|
|
||||||
player.off('useractive', cancelFocusingPlayer);
|
|
||||||
var activeElement = doc.activeElement;
|
|
||||||
var controlBar = pEl.querySelector('.vjs-control-bar');
|
|
||||||
if (activeElement && activeElement.parentElement == controlBar) {
|
|
||||||
pEl.focus();
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
player.one('useractive', cancelFocusingPlayer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
player.on('play', function() {
|
|
||||||
// Fix allowing the YouTube plugin to have hotkey support.
|
|
||||||
var ifblocker = pEl.querySelector('.iframeblocker');
|
|
||||||
if (ifblocker && ifblocker.style.display === '') {
|
|
||||||
ifblocker.style.display = "block";
|
|
||||||
ifblocker.style.bottom = "39px";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var keyDown = function keyDown(event) {
|
|
||||||
var ewhich = event.which, wasPlaying, seekTime;
|
|
||||||
var ePreventDefault = event.preventDefault;
|
|
||||||
var duration = player.duration();
|
|
||||||
// When controls are disabled, hotkeys will be disabled as well
|
|
||||||
if (player.controls()) {
|
|
||||||
|
|
||||||
// Don't catch keys if any control buttons are focused, unless alwaysCaptureHotkeys is true
|
|
||||||
var activeEl = doc.activeElement;
|
|
||||||
if (alwaysCaptureHotkeys ||
|
|
||||||
activeEl == pEl ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-tech') ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-control-bar') ||
|
|
||||||
activeEl == pEl.querySelector('.iframeblocker')) {
|
|
||||||
|
|
||||||
switch (checkKeys(event, player)) {
|
|
||||||
// Spacebar toggles play/pause
|
|
||||||
case cPlay:
|
|
||||||
ePreventDefault();
|
|
||||||
if (alwaysCaptureHotkeys) {
|
|
||||||
// Prevent control activation with space
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player.paused()) {
|
|
||||||
player.play();
|
|
||||||
} else {
|
|
||||||
player.pause();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Seeking with the left/right arrow keys
|
|
||||||
case cRewind: // Seek Backward
|
|
||||||
wasPlaying = !player.paused();
|
|
||||||
ePreventDefault();
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.pause();
|
|
||||||
}
|
|
||||||
seekTime = player.currentTime() - seekStepD(event);
|
|
||||||
// The flash player tech will allow you to seek into negative
|
|
||||||
// numbers and break the seekbar, so try to prevent that.
|
|
||||||
if (seekTime <= 0) {
|
|
||||||
seekTime = 0;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case cForward: // Seek Forward
|
|
||||||
wasPlaying = !player.paused();
|
|
||||||
ePreventDefault();
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.pause();
|
|
||||||
}
|
|
||||||
seekTime = player.currentTime() + seekStepD(event);
|
|
||||||
// Fixes the player not sending the end event if you
|
|
||||||
// try to seek past the duration on the seekbar.
|
|
||||||
if (seekTime >= duration) {
|
|
||||||
seekTime = wasPlaying ? duration - .001 : duration;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Volume control with the up/down arrow keys
|
|
||||||
case cVolumeDown:
|
|
||||||
ePreventDefault();
|
|
||||||
if (!enableJogStyle) {
|
|
||||||
player.volume(player.volume() - volumeStep);
|
|
||||||
} else {
|
|
||||||
seekTime = player.currentTime() - 1;
|
|
||||||
if (player.currentTime() <= 1) {
|
|
||||||
seekTime = 0;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case cVolumeUp:
|
|
||||||
ePreventDefault();
|
|
||||||
if (!enableJogStyle) {
|
|
||||||
player.volume(player.volume() + volumeStep);
|
|
||||||
} else {
|
|
||||||
seekTime = player.currentTime() + 1;
|
|
||||||
if (seekTime >= duration) {
|
|
||||||
seekTime = duration;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Toggle Mute with the M key
|
|
||||||
case cMute:
|
|
||||||
if (enableMute) {
|
|
||||||
player.muted(!player.muted());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Toggle Fullscreen with the F key
|
|
||||||
case cFullscreen:
|
|
||||||
if (enableFull) {
|
|
||||||
if (player.isFullscreen()) {
|
|
||||||
player.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
player.requestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Number keys from 0-9 skip to a percentage of the video. 0 is 0% and 9 is 90%
|
|
||||||
if ((ewhich > 47 && ewhich < 59) || (ewhich > 95 && ewhich < 106)) {
|
|
||||||
// Do not handle if enableModifiersForNumbers set to false and keys are Ctrl, Cmd or Alt
|
|
||||||
if (enableModifiersForNumbers || !(event.metaKey || event.ctrlKey || event.altKey)) {
|
|
||||||
if (enableNumbers) {
|
|
||||||
var sub = 48;
|
|
||||||
if (ewhich > 95) {
|
|
||||||
sub = 96;
|
|
||||||
}
|
|
||||||
var number = ewhich - sub;
|
|
||||||
ePreventDefault();
|
|
||||||
player.currentTime(player.duration() * number * 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle any custom hotkeys
|
|
||||||
for (var customKey in options.customKeys) {
|
|
||||||
var customHotkey = options.customKeys[customKey];
|
|
||||||
// Check for well formed custom keys
|
|
||||||
if (customHotkey && customHotkey.key && customHotkey.handler) {
|
|
||||||
// Check if the custom key's condition matches
|
|
||||||
if (customHotkey.key(event)) {
|
|
||||||
ePreventDefault();
|
|
||||||
customHotkey.handler(player, options, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var doubleClick = function doubleClick(event) {
|
|
||||||
// When controls are disabled, hotkeys will be disabled as well
|
|
||||||
if (player.controls()) {
|
|
||||||
|
|
||||||
// Don't catch clicks if any control buttons are focused
|
|
||||||
var activeEl = event.relatedTarget || event.toElement || doc.activeElement;
|
|
||||||
if (activeEl == pEl ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-tech') ||
|
|
||||||
activeEl == pEl.querySelector('.iframeblocker')) {
|
|
||||||
|
|
||||||
if (enableFull) {
|
|
||||||
if (player.isFullscreen()) {
|
|
||||||
player.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
player.requestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var volumeHover = false;
|
|
||||||
var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel');
|
|
||||||
volumeSelector.onmouseover = function() { volumeHover = true; }
|
|
||||||
volumeSelector.onmouseout = function() { volumeHover = false; }
|
|
||||||
|
|
||||||
var mouseScroll = function mouseScroll(event) {
|
|
||||||
if (enableHoverScroll) {
|
|
||||||
// If we leave this undefined then it can match non-existent elements below
|
|
||||||
var activeEl = 0;
|
|
||||||
} else {
|
|
||||||
var activeEl = doc.activeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When controls are disabled, hotkeys will be disabled as well
|
|
||||||
if (player.controls()) {
|
|
||||||
if (alwaysCaptureHotkeys ||
|
|
||||||
activeEl == pEl ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-tech') ||
|
|
||||||
activeEl == pEl.querySelector('.iframeblocker') ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-control-bar') ||
|
|
||||||
volumeHover) {
|
|
||||||
|
|
||||||
if (enableVolumeScroll) {
|
|
||||||
event = window.event || event;
|
|
||||||
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (delta == 1) {
|
|
||||||
player.volume(player.volume() + volumeStep);
|
|
||||||
} else if (delta == -1) {
|
|
||||||
player.volume(player.volume() - volumeStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var checkKeys = function checkKeys(e, player) {
|
|
||||||
// Allow some modularity in defining custom hotkeys
|
|
||||||
|
|
||||||
// Play/Pause check
|
|
||||||
if (options.playPauseKey(e, player)) {
|
|
||||||
return cPlay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek Backward check
|
|
||||||
if (options.rewindKey(e, player)) {
|
|
||||||
return cRewind;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek Forward check
|
|
||||||
if (options.forwardKey(e, player)) {
|
|
||||||
return cForward;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume Up check
|
|
||||||
if (options.volumeUpKey(e, player)) {
|
|
||||||
return cVolumeUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume Down check
|
|
||||||
if (options.volumeDownKey(e, player)) {
|
|
||||||
return cVolumeDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mute check
|
|
||||||
if (options.muteKey(e, player)) {
|
|
||||||
return cMute;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fullscreen check
|
|
||||||
if (options.fullscreenKey(e, player)) {
|
|
||||||
return cFullscreen;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function playPauseKey(e) {
|
|
||||||
// Space bar or MediaPlayPause
|
|
||||||
return (e.which === 32 || e.which === 179);
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewindKey(e) {
|
|
||||||
// Left Arrow or MediaRewind
|
|
||||||
return (e.which === 37 || e.which === 177);
|
|
||||||
}
|
|
||||||
|
|
||||||
function forwardKey(e) {
|
|
||||||
// Right Arrow or MediaForward
|
|
||||||
return (e.which === 39 || e.which === 176);
|
|
||||||
}
|
|
||||||
|
|
||||||
function volumeUpKey(e) {
|
|
||||||
// Up Arrow
|
|
||||||
return (e.which === 38);
|
|
||||||
}
|
|
||||||
|
|
||||||
function volumeDownKey(e) {
|
|
||||||
// Down Arrow
|
|
||||||
return (e.which === 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
function muteKey(e) {
|
|
||||||
// M key
|
|
||||||
return (e.which === 77);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullscreenKey(e) {
|
|
||||||
// F key
|
|
||||||
return (e.which === 70);
|
|
||||||
}
|
|
||||||
|
|
||||||
function seekStepD(e) {
|
|
||||||
// SeekStep caller, returns an int, or a function returning an int
|
|
||||||
return (typeof seekStep === "function" ? seekStep(e) : seekStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
player.on('keydown', keyDown);
|
|
||||||
player.on('dblclick', doubleClick);
|
|
||||||
player.on('mousewheel', mouseScroll);
|
|
||||||
player.on("DOMMouseScroll", mouseScroll);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
var registerPlugin = videojs.registerPlugin || videojs.plugin;
|
|
||||||
registerPlugin('hotkeys', hotkeys);
|
|
||||||
}));
|
|
5
assets/js/videojs.hotkeys.min.js
vendored
5
assets/js/videojs.hotkeys.min.js
vendored
@ -1,2 +1,3 @@
|
|||||||
/* videojs-hotkeys v0.2.22 - https://github.com/ctd1500/videojs-hotkeys */
|
/* videojs-hotkeys v0.2.25 - https://github.com/ctd1500/videojs-hotkeys */
|
||||||
!function(e,t){"undefined"!=typeof window&&window.videojs?t(window.videojs):"function"==typeof define&&define.amd?define("videojs-hotkeys",["video.js"],function(e){return t(e.default||e)}):"undefined"!=typeof module&&module.exports&&(module.exports=t(require("video.js")))}(0,function(s){"use strict";"undefined"!=typeof window&&(window.videojs_hotkeys={version:"0.2.22"});(s.registerPlugin||s.plugin)("hotkeys",function(m){var y=this,v=y.el(),f=document,e={volumeStep:.1,seekStep:5,enableMute:!0,enableVolumeScroll:!0,enableHoverScroll:!0,enableFullscreen:!0,enableNumbers:!0,enableJogStyle:!1,alwaysCaptureHotkeys:!1,enableModifiersForNumbers:!0,enableInactiveFocus:!0,skipInitialFocus:!1,playPauseKey:function(e){return 32===e.which||179===e.which},rewindKey:function(e){return 37===e.which||177===e.which},forwardKey:function(e){return 39===e.which||176===e.which},volumeUpKey:function(e){return 38===e.which},volumeDownKey:function(e){return 40===e.which},muteKey:function(e){return 77===e.which},fullscreenKey:function(e){return 70===e.which},customKeys:{}},t=s.mergeOptions||s.util.mergeOptions,d=(m=t(e,m||{})).volumeStep,n=m.seekStep,p=m.enableMute,r=m.enableVolumeScroll,o=m.enableHoverScroll,b=m.enableFullscreen,h=m.enableNumbers,w=m.enableJogStyle,k=m.alwaysCaptureHotkeys,S=m.enableModifiersForNumbers,u=m.enableInactiveFocus,l=m.skipInitialFocus;v.hasAttribute("tabIndex")||v.setAttribute("tabIndex","-1"),v.style.outline="none",!k&&y.autoplay()||l||y.one("play",function(){v.focus()}),u&&y.on("userinactive",function(){var n=function(){clearTimeout(e)},e=setTimeout(function(){y.off("useractive",n);var e=f.activeElement,t=v.querySelector(".vjs-control-bar");e&&e.parentElement==t&&v.focus()},10);y.one("useractive",n)}),y.on("play",function(){var e=v.querySelector(".iframeblocker");e&&""===e.style.display&&(e.style.display="block",e.style.bottom="39px")});var i=!1,c=v.querySelector(".vjs-volume-menu-button")||v.querySelector(".vjs-volume-panel");c.onmouseover=function(){i=!0},c.onmouseout=function(){i=!1};var a=function(e){if(o)var t=0;else t=f.activeElement;if(y.controls()&&(k||t==v||t==v.querySelector(".vjs-tech")||t==v.querySelector(".iframeblocker")||t==v.querySelector(".vjs-control-bar")||i)&&r){e=window.event||e;var n=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail));e.preventDefault(),1==n?y.volume(y.volume()+d):-1==n&&y.volume(y.volume()-d)}},K=function(e,t){return m.playPauseKey(e,t)?1:m.rewindKey(e,t)?2:m.forwardKey(e,t)?3:m.volumeUpKey(e,t)?4:m.volumeDownKey(e,t)?5:m.muteKey(e,t)?6:m.fullscreenKey(e,t)?7:void 0};function q(e){return"function"==typeof n?n(e):n}return y.on("keydown",function(e){var t,n,r=e.which,o=e.preventDefault,u=y.duration();if(y.controls()){var l=f.activeElement;if(k||l==v||l==v.querySelector(".vjs-tech")||l==v.querySelector(".vjs-control-bar")||l==v.querySelector(".iframeblocker"))switch(K(e,y)){case 1:o(),k&&e.stopPropagation(),y.paused()?y.play():y.pause();break;case 2:t=!y.paused(),o(),t&&y.pause(),(n=y.currentTime()-q(e))<=0&&(n=0),y.currentTime(n),t&&y.play();break;case 3:t=!y.paused(),o(),t&&y.pause(),u<=(n=y.currentTime()+q(e))&&(n=t?u-.001:u),y.currentTime(n),t&&y.play();break;case 5:o(),w?(n=y.currentTime()-1,y.currentTime()<=1&&(n=0),y.currentTime(n)):y.volume(y.volume()-d);break;case 4:o(),w?(u<=(n=y.currentTime()+1)&&(n=u),y.currentTime(n)):y.volume(y.volume()+d);break;case 6:p&&y.muted(!y.muted());break;case 7:b&&(y.isFullscreen()?y.exitFullscreen():y.requestFullscreen());break;default:if((47<r&&r<59||95<r&&r<106)&&(S||!(e.metaKey||e.ctrlKey||e.altKey))&&h){var i=48;95<r&&(i=96);var c=r-i;o(),y.currentTime(y.duration()*c*.1)}for(var a in m.customKeys){var s=m.customKeys[a];s&&s.key&&s.handler&&s.key(e)&&(o(),s.handler(y,m,e))}}}}),y.on("dblclick",function(e){if(y.controls()){var t=e.relatedTarget||e.toElement||f.activeElement;t!=v&&t!=v.querySelector(".vjs-tech")&&t!=v.querySelector(".iframeblocker")||b&&(y.isFullscreen()?y.exitFullscreen():y.requestFullscreen())}}),y.on("mousewheel",a),y.on("DOMMouseScroll",a),this})});
|
!function(e,n){"undefined"!=typeof window&&window.videojs?n(window.videojs):"function"==typeof define&&define.amd?define("videojs-hotkeys",["video.js"],function(e){return n(e.default||e)}):"undefined"!=typeof module&&module.exports&&(module.exports=n(require("video.js")))}(0,function(e){"use strict";"undefined"!=typeof window&&(window.videojs_hotkeys={version:"0.2.25"});(e.registerPlugin||e.plugin)("hotkeys",function(n){function t(e){return"function"==typeof s?s(e):s}function r(e){null!=e&&"function"==typeof e.then&&e.then(null,function(e){})}var o=this,u=o.el(),l=document,i={volumeStep:.1,seekStep:5,enableMute:!0,enableVolumeScroll:!0,enableHoverScroll:!1,enableFullscreen:!0,enableNumbers:!0,enableJogStyle:!1,alwaysCaptureHotkeys:!1,enableModifiersForNumbers:!0,enableInactiveFocus:!0,skipInitialFocus:!1,playPauseKey:function(e){return 32===e.which||179===e.which},rewindKey:function(e){return 37===e.which||177===e.which},forwardKey:function(e){return 39===e.which||176===e.which},volumeUpKey:function(e){return 38===e.which},volumeDownKey:function(e){return 40===e.which},muteKey:function(e){return 77===e.which},fullscreenKey:function(e){return 70===e.which},customKeys:{}},c=e.mergeOptions||e.util.mergeOptions,a=(n=c(i,n||{})).volumeStep,s=n.seekStep,m=n.enableMute,f=n.enableVolumeScroll,y=n.enableHoverScroll,v=n.enableFullscreen,d=n.enableNumbers,p=n.enableJogStyle,b=n.alwaysCaptureHotkeys,h=n.enableModifiersForNumbers,w=n.enableInactiveFocus,k=n.skipInitialFocus,S=e.VERSION;u.hasAttribute("tabIndex")||u.setAttribute("tabIndex","-1"),u.style.outline="none",!b&&o.autoplay()||k||o.one("play",function(){u.focus()}),w&&o.on("userinactive",function(){var e=function(){clearTimeout(n)},n=setTimeout(function(){o.off("useractive",e);var n=l.activeElement,t=u.querySelector(".vjs-control-bar");n&&n.parentElement==t&&u.focus()},10);o.one("useractive",e)}),o.on("play",function(){var e=u.querySelector(".iframeblocker");e&&""===e.style.display&&(e.style.display="block",e.style.bottom="39px")});var K=!1,q=u.querySelector(".vjs-volume-menu-button")||u.querySelector(".vjs-volume-panel");null!=q&&(q.onmouseover=function(){K=!0},q.onmouseout=function(){K=!1});var j=function(e){if(y)n=0;else var n=l.activeElement;if(o.controls()&&(b||n==u||n==u.querySelector(".vjs-tech")||n==u.querySelector(".iframeblocker")||n==u.querySelector(".vjs-control-bar")||K)&&f){e=window.event||e;var t=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail));e.preventDefault(),1==t?o.volume(o.volume()+a):-1==t&&o.volume(o.volume()-a)}},F=function(e,t){return n.playPauseKey(e,t)?1:n.rewindKey(e,t)?2:n.forwardKey(e,t)?3:n.volumeUpKey(e,t)?4:n.volumeDownKey(e,t)?5:n.muteKey(e,t)?6:n.fullscreenKey(e,t)?7:void 0};return o.on("keydown",function(e){var i,c,s=e.which,f=e.preventDefault,y=o.duration();if(o.controls()){var w=l.activeElement;if(b||w==u||w==u.querySelector(".vjs-tech")||w==u.querySelector(".vjs-control-bar")||w==u.querySelector(".iframeblocker"))switch(F(e,o)){case 1:f(),b&&e.stopPropagation(),o.paused()?r(o.play()):o.pause();break;case 2:i=!o.paused(),f(),i&&o.pause(),(c=o.currentTime()-t(e))<=0&&(c=0),o.currentTime(c),i&&r(o.play());break;case 3:i=!o.paused(),f(),i&&o.pause(),(c=o.currentTime()+t(e))>=y&&(c=i?y-.001:y),o.currentTime(c),i&&r(o.play());break;case 5:f(),p?(c=o.currentTime()-1,o.currentTime()<=1&&(c=0),o.currentTime(c)):o.volume(o.volume()-a);break;case 4:f(),p?((c=o.currentTime()+1)>=y&&(c=y),o.currentTime(c)):o.volume(o.volume()+a);break;case 6:m&&o.muted(!o.muted());break;case 7:v&&(o.isFullscreen()?o.exitFullscreen():o.requestFullscreen());break;default:if((s>47&&s<59||s>95&&s<106)&&(h||!(e.metaKey||e.ctrlKey||e.altKey))&&d){var k=48;s>95&&(k=96);var S=s-k;f(),o.currentTime(o.duration()*S*.1)}for(var K in n.customKeys){var q=n.customKeys[K];q&&q.key&&q.handler&&q.key(e)&&(f(),q.handler(o,n,e))}}}}),o.on("dblclick",function(e){if(null!=S&&S<="7.1.0"&&o.controls()){var n=e.relatedTarget||e.toElement||l.activeElement;n!=u&&n!=u.querySelector(".vjs-tech")&&n!=u.querySelector(".iframeblocker")||v&&(o.isFullscreen()?o.exitFullscreen():o.requestFullscreen())}}),o.on("mousewheel",j),o.on("DOMMouseScroll",j),this})});
|
||||||
|
//# sourceMappingURL=videojs.hotkeys.min.js.map
|
@ -1,5 +1,3 @@
|
|||||||
video_threads: 0
|
|
||||||
crawl_threads: 0
|
|
||||||
channel_threads: 1
|
channel_threads: 1
|
||||||
feed_threads: 1
|
feed_threads: 1
|
||||||
db:
|
db:
|
||||||
|
7
config/migrate-scripts/migrate-db-1c8075c.sh
Executable file
7
config/migrate-scripts/migrate-db-1c8075c.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
psql invidious -c "ALTER TABLE channel_videos DROP COLUMN live_now CASCADE"
|
||||||
|
psql invidious -c "ALTER TABLE channel_videos DROP COLUMN premiere_timestamp CASCADE"
|
||||||
|
|
||||||
|
psql invidious -c "ALTER TABLE channel_videos ADD COLUMN live_now bool"
|
||||||
|
psql invidious -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz"
|
4
config/migrate-scripts/migrate-db-6e51189.sh
Executable file
4
config/migrate-scripts/migrate-db-6e51189.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
psql invidious -c "ALTER TABLE channel_videos ADD COLUMN live_now bool;"
|
||||||
|
psql invidious -c "UPDATE channel_videos SET live_now = false;"
|
3
config/migrate-scripts/migrate-db-88b7097.sh
Executable file
3
config/migrate-scripts/migrate-db-88b7097.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
psql invidious -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz;"
|
@ -11,6 +11,8 @@ CREATE TABLE public.channel_videos
|
|||||||
ucid text,
|
ucid text,
|
||||||
author text,
|
author text,
|
||||||
length_seconds integer,
|
length_seconds integer,
|
||||||
|
live_now boolean,
|
||||||
|
premiere_timestamp timestamp with time zone,
|
||||||
CONSTRAINT channel_videos_id_key UNIQUE (id)
|
CONSTRAINT channel_videos_id_key UNIQUE (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?",
|
"Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?",
|
||||||
"Yes": "Oui",
|
"Yes": "Oui",
|
||||||
"No": "Non",
|
"No": "Non",
|
||||||
"Import and Export Data": "Importer et Exporter les Données",
|
"Import and Export Data": "Importer et exporter des données",
|
||||||
"Import": "Importer",
|
"Import": "Importer",
|
||||||
"Import Invidious data": "Importer des données Invidious",
|
"Import Invidious data": "Importer des données Invidious",
|
||||||
"Import YouTube subscriptions": "Importer des abonnements YouTube",
|
"Import YouTube subscriptions": "Importer des abonnements YouTube",
|
||||||
@ -45,19 +45,19 @@
|
|||||||
"Email:": "E-mail :",
|
"Email:": "E-mail :",
|
||||||
"Google verification code:": "Code de vérification Google :",
|
"Google verification code:": "Code de vérification Google :",
|
||||||
"Preferences": "Préférences",
|
"Preferences": "Préférences",
|
||||||
"Player preferences": "Préférences du Lecteur",
|
"Player preferences": "Préférences du lecteur",
|
||||||
"Always loop: ": "Lire en boucle : ",
|
"Always loop: ": "Lire en boucle : ",
|
||||||
"Autoplay: ": "Lire Automatiquement : ",
|
"Autoplay: ": "Lire automatiquement : ",
|
||||||
"Autoplay next video: ": "Lire automatiquement la vidéo suivante : ",
|
"Autoplay next video: ": "Lire automatiquement la vidéo suivante : ",
|
||||||
"Listen by default: ": "Audio Uniquement par défaut : ",
|
"Listen by default: ": "Audio uniquement : ",
|
||||||
"Proxy videos? ": "Souhaitez vous charger les vidéos à travers un proxy ?",
|
"Proxy videos? ": "Charger les vidéos à travers un proxy ? ",
|
||||||
"Default speed: ": "Vitesse par défaut : ",
|
"Default speed: ": "Vitesse par défaut : ",
|
||||||
"Preferred video quality: ": "Qualité vidéo souhaitée : ",
|
"Preferred video quality: ": "Qualité vidéo souhaitée : ",
|
||||||
"Player volume: ": "Volume du lecteur : ",
|
"Player volume: ": "Volume du lecteur : ",
|
||||||
"Default comments: ": "Source des Commentaires : ",
|
"Default comments: ": "Source des commentaires : ",
|
||||||
"Default captions: ": "Sous-titres principal : ",
|
"Default captions: ": "Sous-titres par défaut : ",
|
||||||
"Fallback captions: ": "Sous-titres secondaire : ",
|
"Fallback captions: ": "Fallback captions: ",
|
||||||
"Show related videos? ": "Voir les vidéos liées à ce sujet ? ",
|
"Show related videos? ": "Voir les vidéos liées ? ",
|
||||||
"Visual preferences": "Préférences du site",
|
"Visual preferences": "Préférences du site",
|
||||||
"Dark mode: ": "Mode Sombre : ",
|
"Dark mode: ": "Mode Sombre : ",
|
||||||
"Thin mode: ": "Mode Simplifié : ",
|
"Thin mode: ": "Mode Simplifié : ",
|
||||||
@ -82,13 +82,13 @@
|
|||||||
"Watch history": "Historique de visionnage",
|
"Watch history": "Historique de visionnage",
|
||||||
"Delete account": "Supprimer votre compte",
|
"Delete account": "Supprimer votre compte",
|
||||||
"Administrator preferences": "Préferences d'Administrateur",
|
"Administrator preferences": "Préferences d'Administrateur",
|
||||||
"Default homepage: ": "Page d'accueil par defaut :",
|
"Default homepage: ": "Page d'accueil par défaut : ",
|
||||||
"Feed menu: ": "Menu des Flux :",
|
"Feed menu: ": "Menu des Flux : ",
|
||||||
"Top enabled? ": "Top activé ?",
|
"Top enabled? ": "Top activé ? ",
|
||||||
"CAPTCHA enabled? ": "CAPTCHA activé ?",
|
"CAPTCHA enabled? ": "CAPTCHA activé ? ",
|
||||||
"Login enabled? ": "Connexion activé ?",
|
"Login enabled? ": "Connexion activé ? ",
|
||||||
"Registration enabled? ": "Inscription activé ?",
|
"Registration enabled? ": "Inscription activée ? ",
|
||||||
"Report statistics? ": "Telemetrie activé ?",
|
"Report statistics? ": "Télémétrie activé ? ",
|
||||||
"Save preferences": "Enregistrer les préférences",
|
"Save preferences": "Enregistrer les préférences",
|
||||||
"Subscription manager": "Gestionnaire d'abonnement",
|
"Subscription manager": "Gestionnaire d'abonnement",
|
||||||
"`x` subscriptions": "`x` abonnements",
|
"`x` subscriptions": "`x` abonnements",
|
||||||
@ -108,11 +108,11 @@
|
|||||||
"License: ": "Licence : ",
|
"License: ": "Licence : ",
|
||||||
"Family friendly? ": "Tout Public ? ",
|
"Family friendly? ": "Tout Public ? ",
|
||||||
"Wilson score: ": "Score de Wilson : ",
|
"Wilson score: ": "Score de Wilson : ",
|
||||||
"Engagement: ": "Poucentage de spectateur aillant aimé Liker ou Disliker la vidéo : ",
|
"Engagement: ": "Poucentage de spectateur aillant aimé Like ou Dislike la vidéo : ",
|
||||||
"Whitelisted regions: ": "Régions en liste blanche : ",
|
"Whitelisted regions: ": "Régions en liste blanche : ",
|
||||||
"Blacklisted regions: ": "Régions sur liste noire : ",
|
"Blacklisted regions: ": "Régions sur liste noire : ",
|
||||||
"Shared `x`": "Partagée `x`",
|
"Shared `x`": "Partagée `x`",
|
||||||
"Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript sois désactivé. Cliquez ici pour voir les commentaires. Gardez à l'esprit que le chargement peut prendre plus de temps.",
|
"Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript soit désactivé. Cliquez ici pour voir les commentaires sans. Gardez à l'esprit que le chargement peut prendre plus de temps.",
|
||||||
"View YouTube comments": "Voir les commentaires YouTube",
|
"View YouTube comments": "Voir les commentaires YouTube",
|
||||||
"View more comments on Reddit": "Voir plus de commentaires sur Reddit",
|
"View more comments on Reddit": "Voir plus de commentaires sur Reddit",
|
||||||
"View `x` comments": "Voir `x` commentaires",
|
"View `x` comments": "Voir `x` commentaires",
|
||||||
@ -124,11 +124,11 @@
|
|||||||
"Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.",
|
"Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.",
|
||||||
"Invalid TFA code": "Code d'authentification à deux facteurs invalide",
|
"Invalid TFA code": "Code d'authentification à deux facteurs invalide",
|
||||||
"Login failed. This may be because two-factor authentication is not enabled on your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.",
|
"Login failed. This may be because two-factor authentication is not enabled on your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.",
|
||||||
"Invalid answer": "Réponse non valide",
|
"Invalid answer": "Réponse invalide",
|
||||||
"Invalid CAPTCHA": "CAPTCHA invalide",
|
"Invalid CAPTCHA": "CAPTCHA invalide",
|
||||||
"CAPTCHA is a required field": "Veuillez rentrez un CAPTCHA",
|
"CAPTCHA is a required field": "Veuillez entrer un CAPTCHA",
|
||||||
"User ID is a required field": "Veuillez rentrez un Identifiant Utilisateur",
|
"User ID is a required field": "Veuillez entrer un Identifiant Utilisateur",
|
||||||
"Password is a required field": "Veuillez rentrez un Mot de passe",
|
"Password is a required field": "Veuillez entrer un Mot de passe",
|
||||||
"Invalid username or password": "Nom d'utilisateur ou mot de passe invalide",
|
"Invalid username or password": "Nom d'utilisateur ou mot de passe invalide",
|
||||||
"Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"S'identifier avec Google\"",
|
"Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"S'identifier avec Google\"",
|
||||||
"Password cannot be empty": "Le mot de passe ne peut pas être vide",
|
"Password cannot be empty": "Le mot de passe ne peut pas être vide",
|
||||||
@ -268,7 +268,7 @@
|
|||||||
"`x` hours": "`x` heures",
|
"`x` hours": "`x` heures",
|
||||||
"`x` minutes": "`x` minutes",
|
"`x` minutes": "`x` minutes",
|
||||||
"`x` seconds": "`x` secondes",
|
"`x` seconds": "`x` secondes",
|
||||||
"Fallback comments: ": "Commentaires secondaires : ",
|
"Fallback comments: ": "Fallback comments: ",
|
||||||
"Popular": "Populaire",
|
"Popular": "Populaire",
|
||||||
"Top": "Top",
|
"Top": "Top",
|
||||||
"About": "A Propos",
|
"About": "A Propos",
|
||||||
@ -289,5 +289,5 @@
|
|||||||
"Video mode": "Mode Vidéo",
|
"Video mode": "Mode Vidéo",
|
||||||
"Videos": "Vidéos",
|
"Videos": "Vidéos",
|
||||||
"Playlists": "Liste de lecture",
|
"Playlists": "Liste de lecture",
|
||||||
"Current version: ": "Version actuelle :"
|
"Current version: ": "Version :"
|
||||||
}
|
}
|
||||||
|
582
locales/pl.json
582
locales/pl.json
@ -1,293 +1,293 @@
|
|||||||
{
|
{
|
||||||
"`x` subscribers": "`x` subskrybcji",
|
"`x` subscribers": "`x` subskrybcji",
|
||||||
"`x` videos": "`x` filmów",
|
"`x` videos": "`x` filmów",
|
||||||
"LIVE": "NA ŻYWO",
|
"LIVE": "NA ŻYWO",
|
||||||
"Shared `x` ago": "Udostępniono `x` temu",
|
"Shared `x` ago": "Udostępniono `x` temu",
|
||||||
"Unsubscribe": "Odsubskrybuj",
|
"Unsubscribe": "Odsubskrybuj",
|
||||||
"Subscribe": "Subskrybuj",
|
"Subscribe": "Subskrybuj",
|
||||||
"Login to subscribe to `x`": "Zaloguj się, aby subskrybować `x`",
|
"Login to subscribe to `x`": "Zaloguj się, aby subskrybować `x`",
|
||||||
"View channel on YouTube": "Wyświetl kanał na YouTube",
|
"View channel on YouTube": "Wyświetl kanał na YouTube",
|
||||||
"newest": "najnowsze",
|
"newest": "najnowsze",
|
||||||
"oldest": "najstarsze",
|
"oldest": "najstarsze",
|
||||||
"popular": "popularne",
|
"popular": "popularne",
|
||||||
"last": "",
|
"last": "ostatnie",
|
||||||
"Next page": "Następna strona",
|
"Next page": "Następna strona",
|
||||||
"Previous page": "Poprzednia strona",
|
"Previous page": "Poprzednia strona",
|
||||||
"Clear watch history?": "Wyczyścić historię?",
|
"Clear watch history?": "Wyczyścić historię?",
|
||||||
"Yes": "Tak",
|
"Yes": "Tak",
|
||||||
"No": "Nie",
|
"No": "Nie",
|
||||||
"Import and Export Data": "Import i eksport danych",
|
"Import and Export Data": "Import i eksport danych",
|
||||||
"Import": "Import",
|
"Import": "Import",
|
||||||
"Import Invidious data": "Importuj dane Invidious",
|
"Import Invidious data": "Importuj dane Invidious",
|
||||||
"Import YouTube subscriptions": "Importuj subskrybcje z YouTube",
|
"Import YouTube subscriptions": "Importuj subskrybcje z YouTube",
|
||||||
"Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)",
|
"Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)",
|
||||||
"Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)",
|
"Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)",
|
||||||
"Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)",
|
"Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)",
|
||||||
"Export": "Eksport",
|
"Export": "Eksport",
|
||||||
"Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML",
|
"Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML",
|
||||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)",
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)",
|
||||||
"Export data as JSON": "Eksportuj dane jako JSON",
|
"Export data as JSON": "Eksportuj dane jako JSON",
|
||||||
"Delete account?": "Usunąć konto?",
|
"Delete account?": "Usunąć konto?",
|
||||||
"History": "Historia",
|
"History": "Historia",
|
||||||
"An alternative front-end to YouTube": "Alternatywny front-end dla YouTube",
|
"An alternative front-end to YouTube": "Alternatywny front-end dla YouTube",
|
||||||
"JavaScript license information": "Informacja o licencji JavaScript",
|
"JavaScript license information": "Informacja o licencji JavaScript",
|
||||||
"source": "źródło",
|
"source": "źródło",
|
||||||
"Login": "Zaloguj",
|
"Login": "Zaloguj",
|
||||||
"Login/Register": "Zaloguj/Zarejestruj",
|
"Login/Register": "Zaloguj/Zarejestruj",
|
||||||
"Login to Google": "Zaloguj do Google",
|
"Login to Google": "Zaloguj do Google",
|
||||||
"User ID:": "ID użytkownika:",
|
"User ID:": "ID użytkownika:",
|
||||||
"Password:": "Hasło:",
|
"Password:": "Hasło:",
|
||||||
"Time (h:mm:ss):": "Godzina (h:mm:ss):",
|
"Time (h:mm:ss):": "Godzina (h:mm:ss):",
|
||||||
"Text CAPTCHA": "Tekst CAPTCHA",
|
"Text CAPTCHA": "Tekst CAPTCHA",
|
||||||
"Image CAPTCHA": "Obraz CAPTCHA",
|
"Image CAPTCHA": "Obraz CAPTCHA",
|
||||||
"Sign In": "Zaloguj się",
|
"Sign In": "Zaloguj się",
|
||||||
"Register": "Zarejestruj się",
|
"Register": "Zarejestruj się",
|
||||||
"Email:": "Email:",
|
"Email:": "Email:",
|
||||||
"Google verification code:": "Kod weryfikacyjny Google:",
|
"Google verification code:": "Kod weryfikacyjny Google:",
|
||||||
"Preferences": "Preferencje",
|
"Preferences": "Preferencje",
|
||||||
"Player preferences": "Ustawienia odtwarzacza",
|
"Player preferences": "Ustawienia odtwarzacza",
|
||||||
"Always loop: ": "Zawsze zapętlaj: ",
|
"Always loop: ": "Zawsze zapętlaj: ",
|
||||||
"Autoplay: ": "Autoodtwarzanie: ",
|
"Autoplay: ": "Autoodtwarzanie: ",
|
||||||
"Autoplay next video: ": "Odtwórz następny film: ",
|
"Autoplay next video: ": "Odtwórz następny film: ",
|
||||||
"Listen by default: ": "Tryb dźwiękowy: ",
|
"Listen by default: ": "Tryb dźwiękowy: ",
|
||||||
"Proxy videos? ": "",
|
"Proxy videos? ": "Filmy przez proxy? ",
|
||||||
"Default speed: ": "Domyślna prędkość: ",
|
"Default speed: ": "Domyślna prędkość: ",
|
||||||
"Preferred video quality: ": "Preferowana jakość filmów: ",
|
"Preferred video quality: ": "Preferowana jakość filmów: ",
|
||||||
"Player volume: ": "Głośność odtwarzacza: ",
|
"Player volume: ": "Głośność odtwarzacza: ",
|
||||||
"Default comments: ": "Domyślne komentarze: ",
|
"Default comments: ": "Domyślne komentarze: ",
|
||||||
"Default captions: ": "Domyślne napisy: ",
|
"Default captions: ": "Domyślne napisy: ",
|
||||||
"Fallback captions: ": "Zastępcze napisy: ",
|
"Fallback captions: ": "Zastępcze napisy: ",
|
||||||
"Show related videos? ": "Pokaż powiązane filmy? ",
|
"Show related videos? ": "Pokaż powiązane filmy? ",
|
||||||
"Visual preferences": "Preferencje Wizualne",
|
"Visual preferences": "Preferencje Wizualne",
|
||||||
"Dark mode: ": "Ciemny motyw: ",
|
"Dark mode: ": "Ciemny motyw: ",
|
||||||
"Thin mode: ": "Tryb minimalny: ",
|
"Thin mode: ": "Tryb minimalny: ",
|
||||||
"Subscription preferences": "Preferencje subskrybcji",
|
"Subscription preferences": "Preferencje subskrybcji",
|
||||||
"Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ",
|
"Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ",
|
||||||
"Number of videos shown in feed: ": "Liczba filmów widoczna na stronie subskrybcji: ",
|
"Number of videos shown in feed: ": "Liczba filmów widoczna na stronie subskrybcji: ",
|
||||||
"Sort videos by: ": "Sortuj filmy: ",
|
"Sort videos by: ": "Sortuj filmy: ",
|
||||||
"published": "po czasie publikacji",
|
"published": "po czasie publikacji",
|
||||||
"published - reverse": "po czasie publikacji od najstarszych",
|
"published - reverse": "po czasie publikacji od najstarszych",
|
||||||
"alphabetically": "alfabetycznie",
|
"alphabetically": "alfabetycznie",
|
||||||
"alphabetically - reverse": "alfabetycznie od tyłu",
|
"alphabetically - reverse": "alfabetycznie od tyłu",
|
||||||
"channel name": "po nazwie kanału",
|
"channel name": "po nazwie kanału",
|
||||||
"channel name - reverse": "po nazwie kanału od tyłu",
|
"channel name - reverse": "po nazwie kanału od tyłu",
|
||||||
"Only show latest video from channel: ": "Pokazuj tylko najnowszy film z kanału: ",
|
"Only show latest video from channel: ": "Pokazuj tylko najnowszy film z kanału: ",
|
||||||
"Only show latest unwatched video from channel: ": "Pokazuj tylko najnowszy nie obejrzany film z kanału: ",
|
"Only show latest unwatched video from channel: ": "Pokazuj tylko najnowszy nie obejrzany film z kanału: ",
|
||||||
"Only show unwatched: ": "Pokazuj tylko nie obejrzane: ",
|
"Only show unwatched: ": "Pokazuj tylko nie obejrzane: ",
|
||||||
"Only show notifications (if there are any): ": "Pokazuj tylko powiadomienia (jeśli są): ",
|
"Only show notifications (if there are any): ": "Pokazuj tylko powiadomienia (jeśli są): ",
|
||||||
"Data preferences": "Preferencje danych",
|
"Data preferences": "Preferencje danych",
|
||||||
"Clear watch history": "Wyczyść historię",
|
"Clear watch history": "Wyczyść historię",
|
||||||
"Import/Export data": "Import/Eksport danych",
|
"Import/Export data": "Import/Eksport danych",
|
||||||
"Manage subscriptions": "Organizuj subskrybcje",
|
"Manage subscriptions": "Organizuj subskrybcje",
|
||||||
"Watch history": "Historia",
|
"Watch history": "Historia",
|
||||||
"Delete account": "Usuń konto",
|
"Delete account": "Usuń konto",
|
||||||
"Administrator preferences": "Preferencje administratora",
|
"Administrator preferences": "Preferencje administratora",
|
||||||
"Default homepage: ": "Domyślna strona główna: ",
|
"Default homepage: ": "Domyślna strona główna: ",
|
||||||
"Feed menu: ": "",
|
"Feed menu: ": "",
|
||||||
"Top enabled? ": "",
|
"Top enabled? ": "",
|
||||||
"CAPTCHA enabled? ": "CAPTCHA aktywna? ",
|
"CAPTCHA enabled? ": "CAPTCHA aktywna? ",
|
||||||
"Login enabled? ": "Logowanie włączone? ",
|
"Login enabled? ": "Logowanie włączone? ",
|
||||||
"Registration enabled? ": "Rejestracja włączona? ",
|
"Registration enabled? ": "Rejestracja włączona? ",
|
||||||
"Report statistics? ": "Raportować statystyki? ",
|
"Report statistics? ": "Raportować statystyki? ",
|
||||||
"Save preferences": "Zapisz preferencje",
|
"Save preferences": "Zapisz preferencje",
|
||||||
"Subscription manager": "Manager subskrybcji",
|
"Subscription manager": "Manager subskrybcji",
|
||||||
"`x` subscriptions": "`x` subskrybcji",
|
"`x` subscriptions": "`x` subskrybcji",
|
||||||
"Import/Export": "Import/Eksport",
|
"Import/Export": "Import/Eksport",
|
||||||
"unsubscribe": "odsubskrybuj",
|
"unsubscribe": "odsubskrybuj",
|
||||||
"Subscriptions": "Subskrybcje",
|
"Subscriptions": "Subskrybcje",
|
||||||
"`x` unseen notifications": "`x` niewidzianych powiadomień",
|
"`x` unseen notifications": "`x` niewidzianych powiadomień",
|
||||||
"search": "szukaj",
|
"search": "szukaj",
|
||||||
"Sign out": "Wyloguj",
|
"Sign out": "Wyloguj",
|
||||||
"Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.",
|
"Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.",
|
||||||
"Source available here.": "Kod źródłowy dostępny tutaj.",
|
"Source available here.": "Kod źródłowy dostępny tutaj.",
|
||||||
"View JavaScript license information.": "Wyświetl informację o licencji JavaScript.",
|
"View JavaScript license information.": "Wyświetl informację o licencji JavaScript.",
|
||||||
"View privacy policy.": "",
|
"View privacy policy.": "Polityka prywatności.",
|
||||||
"Trending": "Na czasie",
|
"Trending": "Na czasie",
|
||||||
"Watch video on Youtube": "Zobacz film na YouTube",
|
"Watch video on Youtube": "Zobacz film na YouTube",
|
||||||
"Genre: ": "Gatunek: ",
|
"Genre: ": "Gatunek: ",
|
||||||
"License: ": "Licencja: ",
|
"License: ": "Licencja: ",
|
||||||
"Family friendly? ": "Przyjazny rodzinie? ",
|
"Family friendly? ": "Przyjazny rodzinie? ",
|
||||||
"Wilson score: ": "Punktacja Wilsona: ",
|
"Wilson score: ": "Punktacja Wilsona: ",
|
||||||
"Engagement: ": "Zaangażowanie: ",
|
"Engagement: ": "Zaangażowanie: ",
|
||||||
"Whitelisted regions: ": "Dostępny na obszarach: ",
|
"Whitelisted regions: ": "Dostępny na obszarach: ",
|
||||||
"Blacklisted regions: ": "Niedostępny na obszarach: ",
|
"Blacklisted regions: ": "Niedostępny na obszarach: ",
|
||||||
"Shared `x`": "Udostępniono `x`",
|
"Shared `x`": "Udostępniono `x`",
|
||||||
"Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.",
|
"Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.",
|
||||||
"View YouTube comments": "Wyświetl komentarze z YouTube",
|
"View YouTube comments": "Wyświetl komentarze z YouTube",
|
||||||
"View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie",
|
"View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie",
|
||||||
"View `x` comments": "Wyświetl `x` komentarzy",
|
"View `x` comments": "Wyświetl `x` komentarzy",
|
||||||
"View Reddit comments": "Wyświetl komentarze z Redditta",
|
"View Reddit comments": "Wyświetl komentarze z Redditta",
|
||||||
"Hide replies": "Ukryj odpowiedzi",
|
"Hide replies": "Ukryj odpowiedzi",
|
||||||
"Show replies": "Pokaż odpowiedzi",
|
"Show replies": "Pokaż odpowiedzi",
|
||||||
"Incorrect password": "Niepoprawne hasło",
|
"Incorrect password": "Niepoprawne hasło",
|
||||||
"Quota exceeded, try again in a few hours": "Przekroczony limit zapytań, spróbuj ponownie za kilka godzin",
|
"Quota exceeded, try again in a few hours": "Przekroczony limit zapytań, spróbuj ponownie za kilka godzin",
|
||||||
"Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.",
|
"Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.",
|
||||||
"Invalid TFA code": "Niepoprawny kod TFA",
|
"Invalid TFA code": "Niepoprawny kod TFA",
|
||||||
"Login failed. This may be because two-factor authentication is not enabled on your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.",
|
"Login failed. This may be because two-factor authentication is not enabled on your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.",
|
||||||
"Invalid answer": "Niepoprawna odpowiedź",
|
"Invalid answer": "Niepoprawna odpowiedź",
|
||||||
"Invalid CAPTCHA": "CAPTCHA wykonane błędnie",
|
"Invalid CAPTCHA": "CAPTCHA wykonane błędnie",
|
||||||
"CAPTCHA is a required field": "CAPTCHA jest polem wymaganym",
|
"CAPTCHA is a required field": "CAPTCHA jest polem wymaganym",
|
||||||
"User ID is a required field": "ID użytkownika jest polem wymaganym",
|
"User ID is a required field": "ID użytkownika jest polem wymaganym",
|
||||||
"Password is a required field": "Hasło jest polem wymaganym",
|
"Password is a required field": "Hasło jest polem wymaganym",
|
||||||
"Invalid username or password": "Niepoprawny login lub hasło",
|
"Invalid username or password": "Niepoprawny login lub hasło",
|
||||||
"Please sign in using 'Sign in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"",
|
"Please sign in using 'Sign in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"",
|
||||||
"Password cannot be empty": "Hasło nie może być puste",
|
"Password cannot be empty": "Hasło nie może być puste",
|
||||||
"Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków",
|
"Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków",
|
||||||
"Please sign in": "Proszę się zalogować",
|
"Please sign in": "Proszę się zalogować",
|
||||||
"Invidious Private Feed for `x`": "",
|
"Invidious Private Feed for `x`": "",
|
||||||
"channel:`x`": "kanał:`x",
|
"channel:`x`": "kanał:`x",
|
||||||
"Deleted or invalid channel": "Usunięty lub niepoprawny kanał",
|
"Deleted or invalid channel": "Usunięty lub niepoprawny kanał",
|
||||||
"This channel does not exist.": "Ten kanał nie istnieje.",
|
"This channel does not exist.": "Ten kanał nie istnieje.",
|
||||||
"Could not get channel info.": "Nie udało się uzyskać informacji o kanale.",
|
"Could not get channel info.": "Nie udało się uzyskać informacji o kanale.",
|
||||||
"Could not fetch comments": "Nie udało się pobrać komentarzy",
|
"Could not fetch comments": "Nie udało się pobrać komentarzy",
|
||||||
"View `x` replies": "Wyświetl `x` odpowiedzi",
|
"View `x` replies": "Wyświetl `x` odpowiedzi",
|
||||||
"`x` ago": "`x` temu",
|
"`x` ago": "`x` temu",
|
||||||
"Load more": "Wczytaj więcej",
|
"Load more": "Wczytaj więcej",
|
||||||
"`x` points": "`x` punktów",
|
"`x` points": "`x` punktów",
|
||||||
"Could not create mix.": "Nie udało się utworzyć miksu.",
|
"Could not create mix.": "Nie udało się utworzyć miksu.",
|
||||||
"Playlist is empty": "Lista odtwarzania jest pusta",
|
"Playlist is empty": "Lista odtwarzania jest pusta",
|
||||||
"Invalid playlist.": "Niepoprawna lista.",
|
"Invalid playlist.": "Niepoprawna lista.",
|
||||||
"Playlist does not exist.": "Lista odtwarzania nie istnieje.",
|
"Playlist does not exist.": "Lista odtwarzania nie istnieje.",
|
||||||
"Could not pull trending pages.": "Nie udało się pobrać strony na czasie.",
|
"Could not pull trending pages.": "Nie udało się pobrać strony na czasie.",
|
||||||
"Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym",
|
"Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym",
|
||||||
"Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym",
|
"Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym",
|
||||||
"Invalid challenge": "Niepoprawne wyzwanie",
|
"Invalid challenge": "Niepoprawne wyzwanie",
|
||||||
"Invalid token": "Niepoprawny token",
|
"Invalid token": "Niepoprawny token",
|
||||||
"Invalid user": "Niepoprawny użytkownik",
|
"Invalid user": "Niepoprawny użytkownik",
|
||||||
"Token is expired, please try again": "Token wygasł, spróbuj ponownie",
|
"Token is expired, please try again": "Token wygasł, spróbuj ponownie",
|
||||||
"English": "angielski",
|
"English": "angielski",
|
||||||
"English (auto-generated)": "angielski (automatycznie generowane)",
|
"English (auto-generated)": "angielski (automatycznie generowane)",
|
||||||
"Afrikaans": "afrykanerski",
|
"Afrikaans": "afrykanerski",
|
||||||
"Albanian": "albański",
|
"Albanian": "albański",
|
||||||
"Amharic": "amharski",
|
"Amharic": "amharski",
|
||||||
"Arabic": "arabski",
|
"Arabic": "arabski",
|
||||||
"Armenian": "armeński",
|
"Armenian": "armeński",
|
||||||
"Azerbaijani": "azerski",
|
"Azerbaijani": "azerski",
|
||||||
"Bangla": "bengalski",
|
"Bangla": "bengalski",
|
||||||
"Basque": "baskijski",
|
"Basque": "baskijski",
|
||||||
"Belarusian": "białoruski",
|
"Belarusian": "białoruski",
|
||||||
"Bosnian": "bośniacki",
|
"Bosnian": "bośniacki",
|
||||||
"Bulgarian": "bułgarski",
|
"Bulgarian": "bułgarski",
|
||||||
"Burmese": "birmański",
|
"Burmese": "birmański",
|
||||||
"Catalan": "kataloński",
|
"Catalan": "kataloński",
|
||||||
"Cebuano": "cebuański",
|
"Cebuano": "cebuański",
|
||||||
"Chinese (Simplified)": "chiński (uproszczony)",
|
"Chinese (Simplified)": "chiński (uproszczony)",
|
||||||
"Chinese (Traditional)": "chiński (tradycyjny)",
|
"Chinese (Traditional)": "chiński (tradycyjny)",
|
||||||
"Corsican": "korsykański",
|
"Corsican": "korsykański",
|
||||||
"Croatian": "chorwacki",
|
"Croatian": "chorwacki",
|
||||||
"Czech": "czeski",
|
"Czech": "czeski",
|
||||||
"Danish": "duński",
|
"Danish": "duński",
|
||||||
"Dutch": "holenderski",
|
"Dutch": "holenderski",
|
||||||
"Esperanto": "esperanto",
|
"Esperanto": "esperanto",
|
||||||
"Estonian": "estoński",
|
"Estonian": "estoński",
|
||||||
"Filipino": "filipiński",
|
"Filipino": "filipiński",
|
||||||
"Finnish": "fiński",
|
"Finnish": "fiński",
|
||||||
"French": "francuski",
|
"French": "francuski",
|
||||||
"Galician": "galicyjski",
|
"Galician": "galicyjski",
|
||||||
"Georgian": "gruziński",
|
"Georgian": "gruziński",
|
||||||
"German": "niemiecki",
|
"German": "niemiecki",
|
||||||
"Greek": "grecki",
|
"Greek": "grecki",
|
||||||
"Gujarati": "gudźarati",
|
"Gujarati": "gudźarati",
|
||||||
"Haitian Creole": "kreolski haitański",
|
"Haitian Creole": "kreolski haitański",
|
||||||
"Hausa": "hausa",
|
"Hausa": "hausa",
|
||||||
"Hawaiian": "hawajski",
|
"Hawaiian": "hawajski",
|
||||||
"Hebrew": "hebrajski",
|
"Hebrew": "hebrajski",
|
||||||
"Hindi": "hindi",
|
"Hindi": "hindi",
|
||||||
"Hmong": "hmong",
|
"Hmong": "hmong",
|
||||||
"Hungarian": "węgierski",
|
"Hungarian": "węgierski",
|
||||||
"Icelandic": "islandzki",
|
"Icelandic": "islandzki",
|
||||||
"Igbo": "ibo",
|
"Igbo": "ibo",
|
||||||
"Indonesian": "indonezyjski",
|
"Indonesian": "indonezyjski",
|
||||||
"Irish": "irlandzki",
|
"Irish": "irlandzki",
|
||||||
"Italian": "włoski",
|
"Italian": "włoski",
|
||||||
"Japanese": "japoński",
|
"Japanese": "japoński",
|
||||||
"Javanese": "jawajski",
|
"Javanese": "jawajski",
|
||||||
"Kannada": "kannada",
|
"Kannada": "kannada",
|
||||||
"Kazakh": "kazachski",
|
"Kazakh": "kazachski",
|
||||||
"Khmer": "khmerski",
|
"Khmer": "khmerski",
|
||||||
"Korean": "koreański",
|
"Korean": "koreański",
|
||||||
"Kurdish": "kurdyjski",
|
"Kurdish": "kurdyjski",
|
||||||
"Kyrgyz": "kirgiski",
|
"Kyrgyz": "kirgiski",
|
||||||
"Lao": "laotański",
|
"Lao": "laotański",
|
||||||
"Latin": "łaciński",
|
"Latin": "łaciński",
|
||||||
"Latvian": "łotewski",
|
"Latvian": "łotewski",
|
||||||
"Lithuanian": "litewski",
|
"Lithuanian": "litewski",
|
||||||
"Luxembourgish": "luksemburski",
|
"Luxembourgish": "luksemburski",
|
||||||
"Macedonian": "macedoński",
|
"Macedonian": "macedoński",
|
||||||
"Malagasy": "malgaski",
|
"Malagasy": "malgaski",
|
||||||
"Malay": "malajski",
|
"Malay": "malajski",
|
||||||
"Malayalam": "malajalam",
|
"Malayalam": "malajalam",
|
||||||
"Maltese": "maltański",
|
"Maltese": "maltański",
|
||||||
"Maori": "maoryski",
|
"Maori": "maoryski",
|
||||||
"Marathi": "marathi",
|
"Marathi": "marathi",
|
||||||
"Mongolian": "mongolski",
|
"Mongolian": "mongolski",
|
||||||
"Nepali": "nepalski",
|
"Nepali": "nepalski",
|
||||||
"Norwegian": "norweski",
|
"Norwegian": "norweski",
|
||||||
"Nyanja": "njandża",
|
"Nyanja": "njandża",
|
||||||
"Pashto": "paszto",
|
"Pashto": "paszto",
|
||||||
"Persian": "perski",
|
"Persian": "perski",
|
||||||
"Polish": "polski",
|
"Polish": "polski",
|
||||||
"Portuguese": "portugalski",
|
"Portuguese": "portugalski",
|
||||||
"Punjabi": "pendżabski",
|
"Punjabi": "pendżabski",
|
||||||
"Romanian": "rumuński",
|
"Romanian": "rumuński",
|
||||||
"Russian": "rosyjski",
|
"Russian": "rosyjski",
|
||||||
"Samoan": "samoański",
|
"Samoan": "samoański",
|
||||||
"Scottish Gaelic": "gaelicki szkocki",
|
"Scottish Gaelic": "gaelicki szkocki",
|
||||||
"Serbian": "serbski",
|
"Serbian": "serbski",
|
||||||
"Shona": "shona",
|
"Shona": "shona",
|
||||||
"Sindhi": "sindhi",
|
"Sindhi": "sindhi",
|
||||||
"Sinhala": "syngaleski",
|
"Sinhala": "syngaleski",
|
||||||
"Slovak": "słowacki",
|
"Slovak": "słowacki",
|
||||||
"Slovenian": "słoweński",
|
"Slovenian": "słoweński",
|
||||||
"Somali": "somalijski",
|
"Somali": "somalijski",
|
||||||
"Southern Sotho": "sotho południowy",
|
"Southern Sotho": "sotho południowy",
|
||||||
"Spanish": "hiszpański",
|
"Spanish": "hiszpański",
|
||||||
"Spanish (Latin America)": "hiszpański (ameryka łacińska)",
|
"Spanish (Latin America)": "hiszpański (ameryka łacińska)",
|
||||||
"Sundanese": "sundajski",
|
"Sundanese": "sundajski",
|
||||||
"Swahili": "suahili",
|
"Swahili": "suahili",
|
||||||
"Swedish": "szwedzki",
|
"Swedish": "szwedzki",
|
||||||
"Tajik": "tadżycki",
|
"Tajik": "tadżycki",
|
||||||
"Tamil": "tamilski",
|
"Tamil": "tamilski",
|
||||||
"Telugu": "telugu",
|
"Telugu": "telugu",
|
||||||
"Thai": "tajski",
|
"Thai": "tajski",
|
||||||
"Turkish": "turecki",
|
"Turkish": "turecki",
|
||||||
"Ukrainian": "ukraiński",
|
"Ukrainian": "ukraiński",
|
||||||
"Urdu": "urdu",
|
"Urdu": "urdu",
|
||||||
"Uzbek": "uzbecki",
|
"Uzbek": "uzbecki",
|
||||||
"Vietnamese": "wietnamski",
|
"Vietnamese": "wietnamski",
|
||||||
"Welsh": "walijski",
|
"Welsh": "walijski",
|
||||||
"Western Frisian": "zachodniofryzyjski",
|
"Western Frisian": "zachodniofryzyjski",
|
||||||
"Xhosa": "xhosa",
|
"Xhosa": "xhosa",
|
||||||
"Yiddish": "jidysz",
|
"Yiddish": "jidysz",
|
||||||
"Yoruba": "joruba",
|
"Yoruba": "joruba",
|
||||||
"Zulu": "zuluski",
|
"Zulu": "zuluski",
|
||||||
"`x` years": "`x` lat",
|
"`x` years": "`x` lat",
|
||||||
"`x` months": "`x` miesięcy",
|
"`x` months": "`x` miesięcy",
|
||||||
"`x` weeks": "`x` tygodni",
|
"`x` weeks": "`x` tygodni",
|
||||||
"`x` days": "`x` dni",
|
"`x` days": "`x` dni",
|
||||||
"`x` hours": "`x` godzin",
|
"`x` hours": "`x` godzin",
|
||||||
"`x` minutes": "`x` minut",
|
"`x` minutes": "`x` minut",
|
||||||
"`x` seconds": "`x` sekund",
|
"`x` seconds": "`x` sekund",
|
||||||
"Fallback comments: ": "Zastępcze komentarze: ",
|
"Fallback comments: ": "Zastępcze komentarze: ",
|
||||||
"Popular": "Popularne",
|
"Popular": "Popularne",
|
||||||
"Top": "Na czasie",
|
"Top": "Najczęściej oglądane",
|
||||||
"About": "Informacje",
|
"About": "Informacje",
|
||||||
"Rating: ": "Ocena: ",
|
"Rating: ": "Ocena: ",
|
||||||
"Language: ": "Język: ",
|
"Language: ": "Język: ",
|
||||||
"Default": "Domyślnie",
|
"Default": "Domyślnie",
|
||||||
"Music": "Muzyka",
|
"Music": "Muzyka",
|
||||||
"Gaming": "Gry",
|
"Gaming": "Gry",
|
||||||
"News": "Wiadomości",
|
"News": "Wiadomości",
|
||||||
"Movies": "Filmy",
|
"Movies": "Filmy",
|
||||||
"Download": "Pobierz",
|
"Download": "Pobierz",
|
||||||
"Download as: ": "Pobierz jako: ",
|
"Download as: ": "Pobierz jako: ",
|
||||||
"%A %B %-d, %Y": "",
|
"%A %B %-d, %Y": "",
|
||||||
"(edited)": "(edytowany)",
|
"(edited)": "(edytowany)",
|
||||||
"Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube",
|
"Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube",
|
||||||
"`x` marked it with a ❤": "'x' oznaczonych ❤",
|
"`x` marked it with a ❤": "'x' oznaczonych ❤",
|
||||||
"Audio mode": "Tryb audio",
|
"Audio mode": "Tryb audio",
|
||||||
"Video mode": "Tryb wideo",
|
"Video mode": "Tryb wideo",
|
||||||
"Videos": "Filmy",
|
"Videos": "Filmy",
|
||||||
"Playlists": "Playlisty",
|
"Playlists": "Playlisty",
|
||||||
"Current version: ": "Aktualna wersja: "
|
"Current version: ": "Aktualna wersja: "
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ name: invidious
|
|||||||
version: 0.15.0
|
version: 0.15.0
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Omar Roth <omarroth@hotmail.com>
|
- Omar Roth <omarroth@protonmail.com>
|
||||||
|
|
||||||
targets:
|
targets:
|
||||||
invidious:
|
invidious:
|
||||||
|
510
src/invidious.cr
510
src/invidious.cr
File diff suppressed because it is too large
Load Diff
@ -10,13 +10,15 @@ end
|
|||||||
|
|
||||||
class ChannelVideo
|
class ChannelVideo
|
||||||
add_mapping({
|
add_mapping({
|
||||||
id: String,
|
id: String,
|
||||||
title: String,
|
title: String,
|
||||||
published: Time,
|
published: Time,
|
||||||
updated: Time,
|
updated: Time,
|
||||||
ucid: String,
|
ucid: String,
|
||||||
author: String,
|
author: String,
|
||||||
length_seconds: {type: Int32, default: 0},
|
length_seconds: {type: Int32, default: 0},
|
||||||
|
live_now: {type: Bool, default: false},
|
||||||
|
premiere_timestamp: {type: Time?, default: nil},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -117,10 +119,27 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
|
|||||||
author = entry.xpath_node("author/name").not_nil!.content
|
author = entry.xpath_node("author/name").not_nil!.content
|
||||||
ucid = entry.xpath_node("channelid").not_nil!.content
|
ucid = entry.xpath_node("channelid").not_nil!.content
|
||||||
|
|
||||||
length_seconds = videos.select { |video| video.id == video_id }[0]?.try &.length_seconds
|
channel_video = videos.select { |video| video.id == video_id }[0]?
|
||||||
|
|
||||||
|
length_seconds = channel_video.try &.length_seconds
|
||||||
length_seconds ||= 0
|
length_seconds ||= 0
|
||||||
|
|
||||||
video = ChannelVideo.new(video_id, title, published, Time.now, ucid, author, length_seconds)
|
live_now = channel_video.try &.live_now
|
||||||
|
live_now ||= false
|
||||||
|
|
||||||
|
premiere_timestamp = channel_video.try &.premiere_timestamp
|
||||||
|
|
||||||
|
video = ChannelVideo.new(
|
||||||
|
video_id,
|
||||||
|
title,
|
||||||
|
published,
|
||||||
|
Time.now,
|
||||||
|
ucid,
|
||||||
|
author,
|
||||||
|
length_seconds,
|
||||||
|
live_now,
|
||||||
|
premiere_timestamp
|
||||||
|
)
|
||||||
|
|
||||||
db.exec("UPDATE users SET notifications = notifications || $1 \
|
db.exec("UPDATE users SET notifications = notifications || $1 \
|
||||||
WHERE updated < $2 AND $3 = ANY(subscriptions) AND $1 <> ALL(notifications)", video.id, video.published, ucid)
|
WHERE updated < $2 AND $3 = ANY(subscriptions) AND $1 <> ALL(notifications)", video.id, video.published, ucid)
|
||||||
@ -128,9 +147,12 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
|
|||||||
video_array = video.to_a
|
video_array = video.to_a
|
||||||
args = arg_array(video_array)
|
args = arg_array(video_array)
|
||||||
|
|
||||||
|
# We don't include the 'premire_timestamp' here because channel pages don't include them,
|
||||||
|
# meaning the above timestamp is always null
|
||||||
db.exec("INSERT INTO channel_videos VALUES (#{args}) \
|
db.exec("INSERT INTO channel_videos VALUES (#{args}) \
|
||||||
ON CONFLICT (id) DO UPDATE SET title = $2, published = $3, \
|
ON CONFLICT (id) DO UPDATE SET title = $2, published = $3, \
|
||||||
updated = $4, ucid = $5, author = $6, length_seconds = $7", video_array)
|
updated = $4, ucid = $5, author = $6, length_seconds = $7, \
|
||||||
|
live_now = $8", video_array)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
page = 1
|
page = 1
|
||||||
@ -157,7 +179,17 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
|
|||||||
end
|
end
|
||||||
|
|
||||||
count = nodeset.size
|
count = nodeset.size
|
||||||
videos = videos.map { |video| ChannelVideo.new(video.id, video.title, video.published, Time.now, video.ucid, video.author, video.length_seconds) }
|
videos = videos.map { |video| ChannelVideo.new(
|
||||||
|
video.id,
|
||||||
|
video.title,
|
||||||
|
video.published,
|
||||||
|
Time.now,
|
||||||
|
video.ucid,
|
||||||
|
video.author,
|
||||||
|
video.length_seconds,
|
||||||
|
video.live_now,
|
||||||
|
video.premiere_timestamp
|
||||||
|
) }
|
||||||
|
|
||||||
videos.each do |video|
|
videos.each do |video|
|
||||||
ids << video.id
|
ids << video.id
|
||||||
@ -170,8 +202,12 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
|
|||||||
video_array = video.to_a
|
video_array = video.to_a
|
||||||
args = arg_array(video_array)
|
args = arg_array(video_array)
|
||||||
|
|
||||||
db.exec("INSERT INTO channel_videos VALUES (#{args}) ON CONFLICT (id) DO UPDATE SET title = $2, \
|
# We don't include the 'premire_timestamp' here because channel pages don't include them,
|
||||||
published = $3, updated = $4, ucid = $5, author = $6, length_seconds = $7", video_array)
|
# meaning the above timestamp is always null
|
||||||
|
db.exec("INSERT INTO channel_videos VALUES (#{args}) \
|
||||||
|
ON CONFLICT (id) DO UPDATE SET title = $2, published = $3, \
|
||||||
|
updated = $4, ucid = $5, author = $6, length_seconds = $7, \
|
||||||
|
live_now = $8", video_array)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -308,13 +308,13 @@ def template_youtube_comments(comments, locale)
|
|||||||
<p>
|
<p>
|
||||||
<b>
|
<b>
|
||||||
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{child["author"]}</a>
|
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{child["author"]}</a>
|
||||||
</b>
|
</b>
|
||||||
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
||||||
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
||||||
|
|
|
|
||||||
<a href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "Youtube permalink of the comment")}">[YT]</a>
|
<a href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "Youtube permalink of the comment")}">[YT]</a>
|
||||||
|
|
|
|
||||||
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
||||||
END_HTML
|
END_HTML
|
||||||
|
|
||||||
if child["creatorHeart"]?
|
if child["creatorHeart"]?
|
||||||
@ -372,8 +372,8 @@ def template_reddit_comments(root, locale)
|
|||||||
|
|
||||||
content = <<-END_HTML
|
content = <<-END_HTML
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" onclick="toggle_parent(this)">[ - ]</a>
|
<a href="javascript:void(0)" onclick="toggle_parent(this)">[ - ]</a>
|
||||||
<b><a href="https://www.reddit.com/user/#{author}">#{author}</a></b>
|
<b><a href="https://www.reddit.com/user/#{author}">#{author}</a></b>
|
||||||
#{translate(locale, "`x` points", number_with_separator(score))}
|
#{translate(locale, "`x` points", number_with_separator(score))}
|
||||||
#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}
|
#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}
|
||||||
</p>
|
</p>
|
||||||
|
133
src/invidious/helpers/handlers.cr
Normal file
133
src/invidious/helpers/handlers.cr
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
module HTTP::Handler
|
||||||
|
@@exclude_routes_tree = Radix::Tree(String).new
|
||||||
|
|
||||||
|
macro exclude(paths, method = "GET")
|
||||||
|
class_name = {{@type.name}}
|
||||||
|
method_downcase = {{method.downcase}}
|
||||||
|
class_name_method = "#{class_name}/#{method_downcase}"
|
||||||
|
({{paths}}).each do |path|
|
||||||
|
@@exclude_routes_tree.add class_name_method + path, '/' + method_downcase + path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_match?(env : HTTP::Server::Context)
|
||||||
|
@@exclude_routes_tree.find(radix_path(env.request.method, env.request.path)).found?
|
||||||
|
end
|
||||||
|
|
||||||
|
private def radix_path(method : String, path : String)
|
||||||
|
"#{self.class}/#{method.downcase}#{path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kemal::RouteHandler
|
||||||
|
exclude ["/api/v1/*"]
|
||||||
|
|
||||||
|
# Processes the route if it's a match. Otherwise renders 404.
|
||||||
|
private def process_request(context)
|
||||||
|
raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_found?
|
||||||
|
content = context.route.handler.call(context)
|
||||||
|
|
||||||
|
if !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(context.response.status_code) && exclude_match?(context)
|
||||||
|
raise Kemal::Exceptions::CustomException.new(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
context.response.print(content)
|
||||||
|
context
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kemal::ExceptionHandler
|
||||||
|
exclude ["/api/v1/*"]
|
||||||
|
|
||||||
|
private def call_exception_with_status_code(context : HTTP::Server::Context, exception : Exception, status_code : Int32)
|
||||||
|
return if context.response.closed?
|
||||||
|
return if exclude_match? context
|
||||||
|
|
||||||
|
if !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(status_code)
|
||||||
|
context.response.content_type = "text/html" unless context.response.headers.has_key?("Content-Type")
|
||||||
|
context.response.status_code = status_code
|
||||||
|
context.response.print Kemal.config.error_handlers[status_code].call(context, exception)
|
||||||
|
context
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class FilteredCompressHandler < Kemal::Handler
|
||||||
|
exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/ggpht/*"]
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
return call_next env if exclude_match? env
|
||||||
|
|
||||||
|
{% if flag?(:without_zlib) %}
|
||||||
|
call_next env
|
||||||
|
{% else %}
|
||||||
|
request_headers = env.request.headers
|
||||||
|
|
||||||
|
if request_headers.includes_word?("Accept-Encoding", "gzip")
|
||||||
|
env.response.headers["Content-Encoding"] = "gzip"
|
||||||
|
env.response.output = Gzip::Writer.new(env.response.output, sync_close: true)
|
||||||
|
elsif request_headers.includes_word?("Accept-Encoding", "deflate")
|
||||||
|
env.response.headers["Content-Encoding"] = "deflate"
|
||||||
|
env.response.output = Flate::Writer.new(env.response.output, sync_close: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
call_next env
|
||||||
|
{% end %}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class APIHandler < Kemal::Handler
|
||||||
|
only ["/api/v1/*"]
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
return call_next env unless only_match? env
|
||||||
|
|
||||||
|
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
|
||||||
|
# Here we swap out the socket IO so we can modify the response as needed
|
||||||
|
output = env.response.output
|
||||||
|
env.response.output = IO::Memory.new
|
||||||
|
|
||||||
|
begin
|
||||||
|
call_next env
|
||||||
|
|
||||||
|
env.response.output.rewind
|
||||||
|
response = env.response.output.gets_to_end
|
||||||
|
|
||||||
|
if env.response.headers["Content-Type"]?.try &.== "application/json"
|
||||||
|
response = JSON.parse(response)
|
||||||
|
|
||||||
|
if env.params.query["pretty"]? && env.params.query["pretty"] == "1"
|
||||||
|
response = response.to_pretty_json
|
||||||
|
else
|
||||||
|
response = response.to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
ensure
|
||||||
|
env.response.output = output
|
||||||
|
env.response.puts response
|
||||||
|
|
||||||
|
env.response.flush
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DenyFrame < Kemal::Handler
|
||||||
|
exclude ["/embed/*"]
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
return call_next env if exclude_match? env
|
||||||
|
|
||||||
|
env.response.headers["X-Frame-Options"] = "sameorigin"
|
||||||
|
call_next env
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Temp fix for https://github.com/crystal-lang/crystal/issues/7383
|
||||||
|
class HTTP::Client
|
||||||
|
private def handle_response(response)
|
||||||
|
# close unless response.keep_alive?
|
||||||
|
response
|
||||||
|
end
|
||||||
|
end
|
@ -1,7 +1,5 @@
|
|||||||
class Config
|
class Config
|
||||||
YAML.mapping({
|
YAML.mapping({
|
||||||
video_threads: Int32, # Number of threads to use for updating videos in cache (mostly non-functional)
|
|
||||||
crawl_threads: Int32, # Number of threads to use for finding new videos from YouTube (used to populate "top" page)
|
|
||||||
channel_threads: Int32, # Number of threads to use for crawling videos from channels (for updating subscriptions)
|
channel_threads: Int32, # Number of threads to use for crawling videos from channels (for updating subscriptions)
|
||||||
feed_threads: Int32, # Number of threads to use for updating feeds
|
feed_threads: Int32, # Number of threads to use for updating feeds
|
||||||
db: NamedTuple( # Database configuration
|
db: NamedTuple( # Database configuration
|
||||||
@ -28,61 +26,6 @@ user: String,
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
class FilteredCompressHandler < Kemal::Handler
|
|
||||||
exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/api/*", "/ggpht/*"]
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
return call_next env if exclude_match? env
|
|
||||||
|
|
||||||
{% if flag?(:without_zlib) %}
|
|
||||||
call_next env
|
|
||||||
{% else %}
|
|
||||||
request_headers = env.request.headers
|
|
||||||
|
|
||||||
if request_headers.includes_word?("Accept-Encoding", "gzip")
|
|
||||||
env.response.headers["Content-Encoding"] = "gzip"
|
|
||||||
env.response.output = Gzip::Writer.new(env.response.output, sync_close: true)
|
|
||||||
elsif request_headers.includes_word?("Accept-Encoding", "deflate")
|
|
||||||
env.response.headers["Content-Encoding"] = "deflate"
|
|
||||||
env.response.output = Flate::Writer.new(env.response.output, sync_close: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
call_next env
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class APIHandler < Kemal::Handler
|
|
||||||
only ["/api/v1/*"]
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
return call_next env unless only_match? env
|
|
||||||
|
|
||||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
|
||||||
|
|
||||||
call_next env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DenyFrame < Kemal::Handler
|
|
||||||
exclude ["/embed/*"]
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
return call_next env if exclude_match? env
|
|
||||||
|
|
||||||
env.response.headers["X-Frame-Options"] = "sameorigin"
|
|
||||||
call_next env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Temp fix for https://github.com/crystal-lang/crystal/issues/7383
|
|
||||||
class HTTP::Client
|
|
||||||
private def handle_response(response)
|
|
||||||
# close unless response.keep_alive?
|
|
||||||
response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def rank_videos(db, n)
|
def rank_videos(db, n)
|
||||||
top = [] of {Float64, String}
|
top = [] of {Float64, String}
|
||||||
|
|
||||||
@ -325,6 +268,11 @@ def extract_items(nodeset, ucid = nil, author_name = nil)
|
|||||||
paid = true
|
paid = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
premiere_timestamp = node.xpath_node(%q(.//ul[@class="yt-lockup-meta-info"]/li/span[@class="localized-date"])).try &.["data-timestamp"]?.try &.to_i64
|
||||||
|
if premiere_timestamp
|
||||||
|
premiere_timestamp = Time.unix(premiere_timestamp)
|
||||||
|
end
|
||||||
|
|
||||||
items << SearchVideo.new(
|
items << SearchVideo.new(
|
||||||
title: title,
|
title: title,
|
||||||
id: id,
|
id: id,
|
||||||
@ -337,7 +285,8 @@ def extract_items(nodeset, ucid = nil, author_name = nil)
|
|||||||
length_seconds: length_seconds,
|
length_seconds: length_seconds,
|
||||||
live_now: live_now,
|
live_now: live_now,
|
||||||
paid: paid,
|
paid: paid,
|
||||||
premium: premium
|
premium: premium,
|
||||||
|
premiere_timestamp: premiere_timestamp
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,51 +1,3 @@
|
|||||||
def crawl_videos(db, logger)
|
|
||||||
ids = Deque(String).new
|
|
||||||
random = Random.new
|
|
||||||
|
|
||||||
search(random.base64(3)).as(Tuple)[1].each do |video|
|
|
||||||
if video.is_a?(SearchVideo)
|
|
||||||
ids << video.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
loop do
|
|
||||||
if ids.empty?
|
|
||||||
search(random.base64(3)).as(Tuple)[1].each do |video|
|
|
||||||
if video.is_a?(SearchVideo)
|
|
||||||
ids << video.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
id = ids[0]
|
|
||||||
video = get_video(id, db)
|
|
||||||
rescue ex
|
|
||||||
logger.write("#{id} : #{ex.message}\n")
|
|
||||||
next
|
|
||||||
ensure
|
|
||||||
ids.delete(id)
|
|
||||||
end
|
|
||||||
|
|
||||||
rvs = [] of Hash(String, String)
|
|
||||||
video.info["rvs"]?.try &.split(",").each do |rv|
|
|
||||||
rvs << HTTP::Params.parse(rv).to_h
|
|
||||||
end
|
|
||||||
|
|
||||||
rvs.each do |rv|
|
|
||||||
if rv.has_key?("id") && !db.query_one?("SELECT EXISTS (SELECT true FROM videos WHERE id = $1)", rv["id"], as: Bool)
|
|
||||||
ids.delete(id)
|
|
||||||
ids << rv["id"]
|
|
||||||
if ids.size == 150
|
|
||||||
ids.shift
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Fiber.yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def refresh_channels(db, logger, max_threads = 1, full_refresh = false)
|
def refresh_channels(db, logger, max_threads = 1, full_refresh = false)
|
||||||
max_channel = Channel(Int32).new
|
max_channel = Channel(Int32).new
|
||||||
|
|
||||||
@ -82,30 +34,14 @@ def refresh_channels(db, logger, max_threads = 1, full_refresh = false)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sleep 1.minute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
max_channel.send(max_threads)
|
max_channel.send(max_threads)
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_videos(db, logger)
|
|
||||||
loop do
|
|
||||||
db.query("SELECT id FROM videos ORDER BY updated") do |rs|
|
|
||||||
rs.each do
|
|
||||||
begin
|
|
||||||
id = rs.read(String)
|
|
||||||
video = get_video(id, db)
|
|
||||||
rescue ex
|
|
||||||
logger.write("#{id} : #{ex.message}\n")
|
|
||||||
next
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Fiber.yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def refresh_feeds(db, logger, max_threads = 1)
|
def refresh_feeds(db, logger, max_threads = 1)
|
||||||
max_channel = Channel(Int32).new
|
max_channel = Channel(Int32).new
|
||||||
|
|
||||||
@ -129,15 +65,26 @@ def refresh_feeds(db, logger, max_threads = 1)
|
|||||||
active_threads += 1
|
active_threads += 1
|
||||||
spawn do
|
spawn do
|
||||||
begin
|
begin
|
||||||
|
db.query("SELECT * FROM #{view_name} LIMIT 1") do |rs|
|
||||||
|
# View doesn't contain same number of rows as ChannelVideo
|
||||||
|
if ChannelVideo.from_rs(rs)[0]?.try &.to_a.size.try &.!= rs.column_count
|
||||||
|
db.exec("DROP MATERIALIZED VIEW #{view_name}")
|
||||||
|
raise "valid schema does not exist"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
db.exec("REFRESH MATERIALIZED VIEW #{view_name}")
|
db.exec("REFRESH MATERIALIZED VIEW #{view_name}")
|
||||||
rescue ex
|
rescue ex
|
||||||
# Create view if it doesn't exist
|
# Create view if it doesn't exist
|
||||||
if ex.message.try &.ends_with? "does not exist"
|
if ex.message.try &.ends_with?("does not exist")
|
||||||
db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \
|
# While iterating through, we may have an email stored from a deleted account
|
||||||
SELECT * FROM channel_videos WHERE \
|
if db.query_one?("SELECT true FROM users WHERE email = $1", email, as: Bool)
|
||||||
ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{email.gsub("'", "\\'")}')::text[]) \
|
db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \
|
||||||
ORDER BY published DESC;")
|
SELECT * FROM channel_videos WHERE \
|
||||||
logger.write("CREATE #{view_name}")
|
ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{email.gsub("'", "\\'")}')::text[]) \
|
||||||
|
ORDER BY published DESC;")
|
||||||
|
logger.write("CREATE #{view_name}\n")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
logger.write("REFRESH #{email} : #{ex.message}\n")
|
logger.write("REFRESH #{email} : #{ex.message}\n")
|
||||||
end
|
end
|
||||||
@ -147,6 +94,8 @@ def refresh_feeds(db, logger, max_threads = 1)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sleep 1.minute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -169,7 +118,6 @@ def subscribe_to_feeds(db, logger, key, config)
|
|||||||
end
|
end
|
||||||
|
|
||||||
sleep 1.minute
|
sleep 1.minute
|
||||||
Fiber.yield
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -200,7 +148,7 @@ def pull_top_videos(config, db)
|
|||||||
end
|
end
|
||||||
|
|
||||||
yield videos
|
yield videos
|
||||||
Fiber.yield
|
sleep 1.minute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -215,7 +163,7 @@ def pull_popular_videos(db)
|
|||||||
ORDER BY ucid, published DESC", subscriptions, as: ChannelVideo).sort_by { |video| video.published }.reverse
|
ORDER BY ucid, published DESC", subscriptions, as: ChannelVideo).sort_by { |video| video.published }.reverse
|
||||||
|
|
||||||
yield videos
|
yield videos
|
||||||
Fiber.yield
|
sleep 1.minute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -228,6 +176,7 @@ def update_decrypt_function
|
|||||||
end
|
end
|
||||||
|
|
||||||
yield decrypt_function
|
yield decrypt_function
|
||||||
|
sleep 1.minute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -239,7 +188,8 @@ def find_working_proxies(regions)
|
|||||||
# proxies = filter_proxies(proxies)
|
# proxies = filter_proxies(proxies)
|
||||||
|
|
||||||
yield region, proxies
|
yield region, proxies
|
||||||
Fiber.yield
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sleep 1.minute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -8,6 +8,7 @@ class PlaylistVideo
|
|||||||
published: Time,
|
published: Time,
|
||||||
playlists: Array(String),
|
playlists: Array(String),
|
||||||
index: Int32,
|
index: Int32,
|
||||||
|
live_now: Bool,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -101,8 +102,10 @@ def extract_playlist(plid, nodeset, index)
|
|||||||
anchor = video.xpath_node(%q(.//td[@class="pl-video-time"]/div/div[1]))
|
anchor = video.xpath_node(%q(.//td[@class="pl-video-time"]/div/div[1]))
|
||||||
if anchor && !anchor.content.empty?
|
if anchor && !anchor.content.empty?
|
||||||
length_seconds = decode_length_seconds(anchor.content)
|
length_seconds = decode_length_seconds(anchor.content)
|
||||||
|
live_now = false
|
||||||
else
|
else
|
||||||
length_seconds = 0
|
length_seconds = 0
|
||||||
|
live_now = true
|
||||||
end
|
end
|
||||||
|
|
||||||
videos << PlaylistVideo.new(
|
videos << PlaylistVideo.new(
|
||||||
@ -114,6 +117,7 @@ def extract_playlist(plid, nodeset, index)
|
|||||||
published: Time.now,
|
published: Time.now,
|
||||||
playlists: [plid],
|
playlists: [plid],
|
||||||
index: index + offset,
|
index: index + offset,
|
||||||
|
live_now: live_now
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
class SearchVideo
|
class SearchVideo
|
||||||
add_mapping({
|
add_mapping({
|
||||||
title: String,
|
title: String,
|
||||||
id: String,
|
id: String,
|
||||||
author: String,
|
author: String,
|
||||||
ucid: String,
|
ucid: String,
|
||||||
published: Time,
|
published: Time,
|
||||||
views: Int64,
|
views: Int64,
|
||||||
description: String,
|
description: String,
|
||||||
description_html: String,
|
description_html: String,
|
||||||
length_seconds: Int32,
|
length_seconds: Int32,
|
||||||
live_now: Bool,
|
live_now: Bool,
|
||||||
paid: Bool,
|
paid: Bool,
|
||||||
premium: Bool,
|
premium: Bool,
|
||||||
|
premiere_timestamp: Time?,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -255,8 +255,12 @@ def validate_response(challenge, token, user_id, operation, key, db, locale)
|
|||||||
challenge = OpenSSL::HMAC.digest(:sha256, key, challenge)
|
challenge = OpenSSL::HMAC.digest(:sha256, key, challenge)
|
||||||
challenge = Base64.urlsafe_encode(challenge)
|
challenge = Base64.urlsafe_encode(challenge)
|
||||||
|
|
||||||
if db.query_one?("SELECT EXISTS (SELECT true FROM nonces WHERE nonce = $1)", nonce, as: Bool)
|
if nonce = db.query_one?("SELECT * FROM nonces WHERE nonce = $1", nonce, as: {String, Time})
|
||||||
db.exec("DELETE FROM nonces * WHERE nonce = $1", nonce)
|
if nonce[1] > Time.now
|
||||||
|
db.exec("UPDATE nonces SET expire = $1 WHERE nonce = $2", Time.new(1990, 1, 1), nonce[0])
|
||||||
|
else
|
||||||
|
raise translate(locale, "Invalid token")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
raise translate(locale, "Invalid token")
|
raise translate(locale, "Invalid token")
|
||||||
end
|
end
|
||||||
@ -270,7 +274,7 @@ def validate_response(challenge, token, user_id, operation, key, db, locale)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if challenge_user_id != user_id
|
if challenge_user_id != user_id
|
||||||
raise translate(locale, "Invalid user")
|
raise translate(locale, "Invalid token")
|
||||||
end
|
end
|
||||||
|
|
||||||
if expire < Time.now.to_unix
|
if expire < Time.now.to_unix
|
||||||
@ -296,7 +300,7 @@ def generate_captcha(key, db)
|
|||||||
clock_svg = <<-END_SVG
|
clock_svg = <<-END_SVG
|
||||||
<svg viewBox="0 0 100 100" width="200px">
|
<svg viewBox="0 0 100 100" width="200px">
|
||||||
<circle cx="50" cy="50" r="45" fill="#eee" stroke="black" stroke-width="2"></circle>
|
<circle cx="50" cy="50" r="45" fill="#eee" stroke="black" stroke-width="2"></circle>
|
||||||
|
|
||||||
<text x="69" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 1</text>
|
<text x="69" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 1</text>
|
||||||
<text x="82.909" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 2</text>
|
<text x="82.909" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 2</text>
|
||||||
<text x="88" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 3</text>
|
<text x="88" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 3</text>
|
||||||
@ -328,7 +332,22 @@ def generate_captcha(key, db)
|
|||||||
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
|
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
|
||||||
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
|
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
|
||||||
|
|
||||||
challenge, token = create_response(answer, "sign_in", key, db)
|
return {
|
||||||
|
question: image,
|
||||||
return {image: image, challenge: challenge, token: token}
|
tokens: [create_response(answer, "sign_in", key, db)],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_text_captcha(key, db)
|
||||||
|
response = HTTP::Client.get(TEXTCAPTCHA_URL).body
|
||||||
|
response = JSON.parse(response)
|
||||||
|
|
||||||
|
tokens = response["a"].as_a.map do |answer|
|
||||||
|
create_response(answer.as_s, "sign_in", key, db)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
question: response["q"].as_s,
|
||||||
|
tokens: tokens,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
@ -250,6 +250,63 @@ class Video
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allow_ratings
|
||||||
|
allow_ratings = player_response["videoDetails"].try &.["allowRatings"]?.try &.as_bool
|
||||||
|
|
||||||
|
if allow_ratings.nil?
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return allow_ratings
|
||||||
|
end
|
||||||
|
|
||||||
|
def live_now
|
||||||
|
live_now = self.player_response["videoDetails"]?.try &.["isLive"]?.try &.as_bool
|
||||||
|
|
||||||
|
if live_now.nil?
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return live_now
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_listed
|
||||||
|
is_listed = player_response["videoDetails"].try &.["isCrawlable"]?.try &.as_bool
|
||||||
|
|
||||||
|
if is_listed.nil?
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return is_listed
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_upcoming
|
||||||
|
is_upcoming = player_response["videoDetails"].try &.["isUpcoming"]?.try &.as_bool
|
||||||
|
|
||||||
|
if is_upcoming.nil?
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return is_upcoming
|
||||||
|
end
|
||||||
|
|
||||||
|
def premiere_timestamp
|
||||||
|
if self.is_upcoming
|
||||||
|
premiere_timestamp = player_response["playabilityStatus"]?
|
||||||
|
.try &.["liveStreamability"]?
|
||||||
|
.try &.["liveStreamabilityRenderer"]?
|
||||||
|
.try &.["offlineSlate"]?
|
||||||
|
.try &.["liveStreamOfflineSlateRenderer"]?
|
||||||
|
.try &.["scheduledStartTime"].as_s.to_i64
|
||||||
|
end
|
||||||
|
|
||||||
|
if premiere_timestamp
|
||||||
|
premiere_timestamp = Time.unix(premiere_timestamp)
|
||||||
|
end
|
||||||
|
|
||||||
|
return premiere_timestamp
|
||||||
|
end
|
||||||
|
|
||||||
def keywords
|
def keywords
|
||||||
keywords = self.player_response["videoDetails"]?.try &.["keywords"]?.try &.as_a
|
keywords = self.player_response["videoDetails"]?.try &.["keywords"]?.try &.as_a
|
||||||
keywords ||= [] of String
|
keywords ||= [] of String
|
||||||
@ -644,6 +701,10 @@ def fetch_video(id, proxies, region)
|
|||||||
raise "Video unavailable."
|
raise "Video unavailable."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if !info["title"]?
|
||||||
|
raise "Video unavailable."
|
||||||
|
end
|
||||||
|
|
||||||
title = info["title"]
|
title = info["title"]
|
||||||
author = info["author"]
|
author = info["author"]
|
||||||
ucid = info["ucid"]
|
ucid = info["ucid"]
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
|
@ -39,7 +39,9 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
|
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
|
||||||
|
<% if item.length_seconds != 0 %>
|
||||||
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p><%= item.title %></p>
|
<p><%= item.title %></p>
|
||||||
@ -55,7 +57,7 @@
|
|||||||
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
|
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
|
||||||
<% if item.responds_to?(:live_now) && item.live_now %>
|
<% if item.responds_to?(:live_now) && item.live_now %>
|
||||||
<p class="length"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
|
<p class="length"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
|
||||||
<% else %>
|
<% elsif item.length_seconds != 0 %>
|
||||||
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@ -65,8 +67,10 @@
|
|||||||
<p>
|
<p>
|
||||||
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if Time.now - item.published > 1.minute %>
|
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
|
||||||
|
<h5><%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %></h5>
|
||||||
|
<% elsif Time.now - item.published > 1.minute %>
|
||||||
<h5><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></h5>
|
<h5><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></h5>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
@ -90,7 +94,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if item.responds_to?(:live_now) && item.live_now %>
|
<% if item.responds_to?(:live_now) && item.live_now %>
|
||||||
<p class="length"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
|
<p class="length"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
|
||||||
<% else %>
|
<% elsif item.length_seconds != 0 %>
|
||||||
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@ -100,8 +104,10 @@
|
|||||||
<p>
|
<p>
|
||||||
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if Time.now - item.published > 1.minute %>
|
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
|
||||||
|
<h5><%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %></h5>
|
||||||
|
<% elsif Time.now - item.published > 1.minute %>
|
||||||
<h5><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></h5>
|
<h5><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></h5>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<video style="outline:none;width:100%" playsinline poster="<%= thumbnail %>" title="<%= HTML.escape(video.title) %>"
|
<video style="outline:none;width:100%" playsinline poster="<%= thumbnail %>" title="<%= HTML.escape(video.title) %>"
|
||||||
id="player" class="video-js"
|
id="player" class="video-js"
|
||||||
onmouseenter='this["data-title"]=this["title"];this["title"]=""'
|
onmouseenter='this["data-title"]=this["title"];this["title"]=""'
|
||||||
onmouseleave='this["title"]=this["data-title"];this["data-title"]=""'
|
onmouseleave='this["title"]=this["data-title"];this["data-title"]=""'
|
||||||
@ -44,7 +44,7 @@ var options = {
|
|||||||
aspectRatio: "<%= aspect_ratio %>",
|
aspectRatio: "<%= aspect_ratio %>",
|
||||||
<% end %>
|
<% end %>
|
||||||
preload: "auto",
|
preload: "auto",
|
||||||
playbackRates: [0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
|
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
|
||||||
controlBar: {
|
controlBar: {
|
||||||
children: [
|
children: [
|
||||||
"playToggle",
|
"playToggle",
|
||||||
@ -78,6 +78,7 @@ var player = videojs("player", options, function() {
|
|||||||
volumeStep: 0.1,
|
volumeStep: 0.1,
|
||||||
seekStep: 5,
|
seekStep: 5,
|
||||||
enableModifiersForNumbers: false,
|
enableModifiersForNumbers: false,
|
||||||
|
enableHoverScroll: true,
|
||||||
customKeys: {
|
customKeys: {
|
||||||
// Toggle play with K Key
|
// Toggle play with K Key
|
||||||
play: {
|
play: {
|
||||||
@ -151,7 +152,7 @@ player.on('error', function(event) {
|
|||||||
}
|
}
|
||||||
player.currentTime(currentTime);
|
player.currentTime(currentTime);
|
||||||
player.playbackRate(playbackRate);
|
player.playbackRate(playbackRate);
|
||||||
|
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
player.play();
|
player.play();
|
||||||
}
|
}
|
||||||
@ -191,7 +192,7 @@ var bpb = player.getChild('bigPlayButton');
|
|||||||
|
|
||||||
if (bpb) {
|
if (bpb) {
|
||||||
bpb.hide();
|
bpb.hide();
|
||||||
|
|
||||||
player.ready(function() {
|
player.ready(function() {
|
||||||
new Promise(function(resolve, reject) {
|
new Promise(function(resolve, reject) {
|
||||||
setTimeout(() => resolve(1), 1);
|
setTimeout(() => resolve(1), 1);
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<% if user %>
|
<% if user %>
|
||||||
<% if subscriptions.includes? ucid %>
|
<% if subscriptions.includes? ucid %>
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary"
|
<a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary"
|
||||||
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
||||||
<b><%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %></b>
|
<b><%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %></b>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary"
|
<a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary"
|
||||||
href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
||||||
<b><%= translate(locale, "Subscribe") %> | <%= sub_count_text %></b>
|
<b><%= translate(locale, "Subscribe") %> | <%= sub_count_text %></b>
|
||||||
</a>
|
</a>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" class="pure-button pure-button-primary"
|
<a id="subscribe" class="pure-button pure-button-primary"
|
||||||
href="/login?referer=<%= env.get("current_page") %>">
|
href="/login?referer=<%= env.get("current_page") %>">
|
||||||
<b><%= translate(locale, "Login to subscribe to `x`", author) %></b>
|
<b><%= translate(locale, "Login to subscribe to `x`", author) %></b>
|
||||||
</a>
|
</a>
|
||||||
|
@ -4,13 +4,13 @@ if (subscribe_button.getAttribute('onclick')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function subscribe(timeouts = 0) {
|
function subscribe(timeouts = 0) {
|
||||||
subscribe_button = document.getElementById("subscribe");
|
subscribe_button = document.getElementById("subscribe");
|
||||||
|
|
||||||
if (timeouts > 10) {
|
if (timeouts > 10) {
|
||||||
console.log("Failed to subscribe.");
|
console.log("Failed to subscribe.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>";
|
var url = "/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>";
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.responseType = "json";
|
xhr.responseType = "json";
|
||||||
@ -21,7 +21,7 @@ function subscribe(timeouts = 0) {
|
|||||||
var fallback = subscribe_button.innerHTML;
|
var fallback = subscribe_button.innerHTML;
|
||||||
subscribe_button.onclick = unsubscribe;
|
subscribe_button.onclick = unsubscribe;
|
||||||
subscribe_button.innerHTML = '<b><%= translate(locale, "Unsubscribe").gsub("'", "\\'") %> | <%= sub_count_text %></b>'
|
subscribe_button.innerHTML = '<b><%= translate(locale, "Unsubscribe").gsub("'", "\\'") %> | <%= sub_count_text %></b>'
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.readyState == 4) {
|
if (xhr.readyState == 4) {
|
||||||
if (xhr.status != 200) {
|
if (xhr.status != 200) {
|
||||||
@ -30,7 +30,7 @@ function subscribe(timeouts = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
xhr.ontimeout = function() {
|
||||||
console.log("Subscribing timed out.");
|
console.log("Subscribing timed out.");
|
||||||
|
|
||||||
@ -39,8 +39,8 @@ function subscribe(timeouts = 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function unsubscribe(timeouts = 0) {
|
function unsubscribe(timeouts = 0) {
|
||||||
subscribe_button = document.getElementById("subscribe");
|
subscribe_button = document.getElementById("subscribe");
|
||||||
|
|
||||||
if (timeouts > 10) {
|
if (timeouts > 10) {
|
||||||
console.log("Failed to subscribe");
|
console.log("Failed to subscribe");
|
||||||
return;
|
return;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="file" id="import_youtube" name="import_youtube">
|
<input type="file" id="import_youtube" name="import_youtube">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_freetube"><%= translate(locale, "Import FreeTube subscriptions (.db)") %></label>
|
<label for="import_freetube"><%= translate(locale, "Import FreeTube subscriptions (.db)") %></label>
|
||||||
<input type="file" id="import_freetube" name="import_freetube">
|
<input type="file" id="import_freetube" name="import_freetube">
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<label for="import_newpipe"><%= translate(locale, "Import NewPipe data (.zip)") %></label>
|
<label for="import_newpipe"><%= translate(locale, "Import NewPipe data (.zip)") %></label>
|
||||||
<input type="file" id="import_newpipe" name="import_newpipe">
|
<input type="file" id="import_newpipe" name="import_newpipe">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-controls">
|
<div class="pure-controls">
|
||||||
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Import") %></button>
|
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Import") %></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
<title><%= HTML.escape(video.title) %> - Invidious</title>
|
<title><%= HTML.escape(video.title) %> - Invidious</title>
|
||||||
<style>
|
<style>
|
||||||
#player {
|
#player {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
z-index: -100;
|
z-index: -100;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/dashjs@2.9.0/dist/dash.mediaplayer.debug.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/Dash-Industry-Forum/dash.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="/js/silvermine-videojs-quality-selector.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/omarroth/videojs-quality-selector"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -47,7 +47,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/video.js@6.12.1/dist/video.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/videojs/video.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -61,7 +61,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-contrib-quality-levels@2.0.7/dist/videojs-contrib-quality-levels.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/videojs/videojs-contrib-quality-levels"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -75,7 +75,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-contrib-dash@2.8.2/dist/videojs-dash.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/videojs/videojs-contrib-dash"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -89,7 +89,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/@videojs/http-streaming@1.2.2/dist/videojs-http-streaming.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/videojs/http-streaming"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -103,7 +103,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-markers@1.0.1/dist/videojs-markers.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/spchuang/videojs-markers"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -117,7 +117,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-share@2.0.1/dist/videojs-share.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/mkhazov/videojs-share"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -131,7 +131,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="/js/videojs.hotkeys.js"><%= translate(locale, "source") %></a>
|
<a href="https://github.com/ctd1500/videojs-hotkeys"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<a class="pure-button <% if account_type == "invidious" %>pure-button-disabled<% end %>" href="/login">
|
<a class="pure-button <% if account_type == "invidious" %>pure-button-disabled<% end %>" href="/login?type=invidious">
|
||||||
<%= translate(locale, "Login/Register") %>
|
<%= translate(locale, "Login/Register") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -22,55 +22,84 @@
|
|||||||
<% if account_type == "invidious" %>
|
<% if account_type == "invidious" %>
|
||||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post">
|
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<% if email %>
|
||||||
|
<input name="email" type="hidden" value="<%= email %>">
|
||||||
|
<% else %>
|
||||||
<label for="email"><%= translate(locale, "User ID:") %></label>
|
<label for="email"><%= translate(locale, "User ID:") %></label>
|
||||||
<input required class="pure-input-1" name="email" type="text" placeholder="User ID">
|
<input required class="pure-input-1" name="email" type="text" placeholder="User ID">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if password %>
|
||||||
|
<input name="password" type="hidden" value="<%= password %>">
|
||||||
|
<% else %>
|
||||||
<label for="password"><%= translate(locale, "Password:") %></label>
|
<label for="password"><%= translate(locale, "Password:") %></label>
|
||||||
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if config.captcha_enabled %>
|
<% if captcha %>
|
||||||
<% if captcha_type == "image" %>
|
<% case captcha_type when %>
|
||||||
<img style="width:100%" src='<%= captcha.not_nil![:image] %>'/>
|
<% when "image" %>
|
||||||
<input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>">
|
<% captcha = captcha.not_nil! %>
|
||||||
<input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>">
|
<img style="width:100%" src='<%= captcha[:question] %>'/>
|
||||||
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||||
<input required type="text" name="answer" type="text" placeholder="h:mm:ss">
|
<input type="hidden" name="challenge[<%= i %>]" value="<%= token[0] %>">
|
||||||
|
<input type="hidden" name="token[<%= i %>]" value="<%= token[1] %>">
|
||||||
<label>
|
|
||||||
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious&captcha=text">
|
|
||||||
<%= translate(locale, "Text CAPTCHA") %>
|
|
||||||
</a>
|
|
||||||
</label>
|
|
||||||
<% else %>
|
|
||||||
<% text_captcha.not_nil![:tokens].each_with_index do |token, i| %>
|
|
||||||
<input type="hidden" name="text_challenge<%= i %>" value="<%= token[0] %>">
|
|
||||||
<input type="hidden" name="text_token<%= i %>" value="<%= token[1] %>">
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<label for="text_answer"><%= text_captcha.not_nil![:question] %></label>
|
<input type="hidden" name="captcha_type" value="image">
|
||||||
<input required type="text" name="text_answer" type="text" placeholder="Answer">
|
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
||||||
|
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
|
||||||
|
<% when "text" %>
|
||||||
|
<% captcha = captcha.not_nil! %>
|
||||||
|
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||||
|
<input type="hidden" name="challenge[<%= i %>]" value="<%= token[0] %>">
|
||||||
|
<input type="hidden" name="token[<%= i %>]" value="<%= token[1] %>">
|
||||||
|
<% end %>
|
||||||
|
<input type="hidden" name="captcha_type" value="text">
|
||||||
|
<label for="answer"><%= captcha[:question] %></label>
|
||||||
|
<input type="text" name="answer" type="text" placeholder="Answer">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||||
|
<%= translate(locale, "Register") %>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<% case captcha_type when %>
|
||||||
|
<% when "image" %>
|
||||||
<label>
|
<label>
|
||||||
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious">
|
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
|
||||||
|
<%= translate(locale, "Text CAPTCHA") %>
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
<% when "text" %>
|
||||||
|
<label>
|
||||||
|
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image">
|
||||||
<%= translate(locale, "Image CAPTCHA") %>
|
<%= translate(locale, "Image CAPTCHA") %>
|
||||||
</a>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||||
|
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
|
||||||
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button>
|
|
||||||
<% if config.registration_enabled %>
|
|
||||||
<button type="submit" name="action" value="register" class="pure-button pure-button-primary"><%= translate(locale, "Register") %></button>
|
|
||||||
<% end %>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<% elsif account_type == "google" %>
|
<% elsif account_type == "google" %>
|
||||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>" method="post">
|
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=google" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<% if email %>
|
||||||
|
<input name="email" type="hidden" value="<%= email %>">
|
||||||
|
<% else %>
|
||||||
<label for="email"><%= translate(locale, "Email:") %></label>
|
<label for="email"><%= translate(locale, "Email:") %></label>
|
||||||
<input required class="pure-input-1" name="email" type="email" placeholder="Email">
|
<input required class="pure-input-1" name="email" type="email" placeholder="Email">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if password %>
|
||||||
|
<input name="password" type="hidden" value="<%= password %>">
|
||||||
|
<% else %>
|
||||||
<label for="password"><%= translate(locale, "Password:") %></label>
|
<label for="password"><%= translate(locale, "Password:") %></label>
|
||||||
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if tfa %>
|
<% if tfa %>
|
||||||
<label for="tfa"><%= translate(locale, "Google verification code:") %></label>
|
<label for="tfa"><%= translate(locale, "Google verification code:") %></label>
|
||||||
|
@ -19,4 +19,4 @@
|
|||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
|
@ -41,7 +41,7 @@ function update_value(element) {
|
|||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="speed"><%= translate(locale, "Default speed: ") %></label>
|
<label for="speed"><%= translate(locale, "Default speed: ") %></label>
|
||||||
<select name="speed" id="speed">
|
<select name="speed" id="speed">
|
||||||
<% {2.0, 1.5, 1.25, 1.0, 0.75, 0.5}.each do |option| %>
|
<% {2.0, 1.5, 1.25, 1.0, 0.75, 0.5, 0.25}.each do |option| %>
|
||||||
<option <% if preferences.speed == option %> selected <% end %>><%= option %></option>
|
<option <% if preferences.speed == option %> selected <% end %>><%= option %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
|
@ -5,15 +5,15 @@
|
|||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<%= Markdown.to_html(<<-END_PRIVACY_POLICY
|
<%= Markdown.to_html(<<-END_PRIVACY_POLICY
|
||||||
## Privacy
|
## Privacy
|
||||||
|
|
||||||
This document concerns what data you provide to this website, the purpose of the data, how the data is stored, and how the data can be removed.
|
This document concerns what data you provide to this website, the purpose of the data, how the data is stored, and how the data can be removed.
|
||||||
|
|
||||||
### Data you directly provide
|
### Data you directly provide
|
||||||
|
|
||||||
Data that you provide to the website for the purpose of the site's operation (for example: an account name, account password, or channel subscription) will be stored in the website's database until the user decides to remove it. This data will not be intentionally shared with anyone or anything.
|
Data that you provide to the website for the purpose of the site's operation (for example: an account name, account password, or channel subscription) will be stored in the website's database until the user decides to remove it. This data will not be intentionally shared with anyone or anything.
|
||||||
|
|
||||||
Information stored about a registered user is limited to:
|
Information stored about a registered user is limited to:
|
||||||
|
|
||||||
- a list of session tokens for remaining logged in across devices
|
- a list of session tokens for remaining logged in across devices
|
||||||
- the last time an account was updated (to provide accurate notifications)
|
- the last time an account was updated (to provide accurate notifications)
|
||||||
- a list of video IDs identifying notifications from a user's subscriptions
|
- a list of video IDs identifying notifications from a user's subscriptions
|
||||||
@ -23,51 +23,51 @@
|
|||||||
- a hashed password if applicable (not present on google accounts)
|
- a hashed password if applicable (not present on google accounts)
|
||||||
- a randomly generated token for providing an RSS feed of a user's subscriptions
|
- a randomly generated token for providing an RSS feed of a user's subscriptions
|
||||||
- a list of video IDs identifying watched videos
|
- a list of video IDs identifying watched videos
|
||||||
|
|
||||||
The above list reflects [this code](https://github.com/omarroth/invidious/blob/master/src/invidious/users.cr#L14-L51).
|
The above list reflects [this code](https://github.com/omarroth/invidious/blob/master/src/invidious/users.cr#L14-L51).
|
||||||
|
|
||||||
Users can clear their watch history using the [clear watch history](/clear_watch_history) page.
|
Users can clear their watch history using the [clear watch history](/clear_watch_history) page.
|
||||||
|
|
||||||
If a user is logged in with a Google account, no password will ever be stored. This website uses the session token provided by Google to identify a user, but does not store the information required to make requests on a user's behalf without their knowledge or consent.
|
If a user is logged in with a Google account, no password will ever be stored. This website uses the session token provided by Google to identify a user, but does not store the information required to make requests on a user's behalf without their knowledge or consent.
|
||||||
|
|
||||||
### Data you passively provide
|
### Data you passively provide
|
||||||
|
|
||||||
When you request any resource from this website (for example: a page, a font, an image, or an API endpoint) information about the request may be logged.
|
When you request any resource from this website (for example: a page, a font, an image, or an API endpoint) information about the request may be logged.
|
||||||
|
|
||||||
Information about a request is limited to:
|
Information about a request is limited to:
|
||||||
|
|
||||||
- the time the request was made
|
- the time the request was made
|
||||||
- the status code of the response
|
- the status code of the response
|
||||||
- the method of the request
|
- the method of the request
|
||||||
- the requested URL
|
- the requested URL
|
||||||
- how long it took to complete the request.
|
- how long it took to complete the request.
|
||||||
|
|
||||||
No identifying information is logged, such as the visitor's cookie, user-agent, or IP address. Here are a couple lines to serve as an example:
|
No identifying information is logged, such as the visitor's cookie, user-agent, or IP address. Here are a couple lines to serve as an example:
|
||||||
|
|
||||||
```
|
```
|
||||||
2019-01-19 16:37:47 +00:00 200 GET /api/v1/comments/xrlETJYzH-c?format=html&hl=en-US 1345.88ms
|
2019-01-19 16:37:47 +00:00 200 GET /api/v1/comments/xrlETJYzH-c?format=html&hl=en-US 1345.88ms
|
||||||
2019-01-19 16:37:53 +00:00 200 GET /vi/r5P-f5arPXE/maxres.jpg 1085.41ms
|
2019-01-19 16:37:53 +00:00 200 GET /vi/r5P-f5arPXE/maxres.jpg 1085.41ms
|
||||||
2019-01-19 16:37:54 +00:00 200 GET /watch 7.04ms
|
2019-01-19 16:37:54 +00:00 200 GET /watch 7.04ms
|
||||||
```
|
```
|
||||||
|
|
||||||
This website does not store the visitor's user-agent or IP address and does not use fingerprinting, advertisements, or tracking of any form.
|
This website does not store the visitor's user-agent or IP address and does not use fingerprinting, advertisements, or tracking of any form.
|
||||||
|
|
||||||
This website provides links to googlevideo.com to provide audio and video playback. googlevideo.com is owned by Google and is subject to their [privacy policy](https://policies.google.com/privacy).
|
This website provides links to googlevideo.com to provide audio and video playback. googlevideo.com is owned by Google and is subject to their [privacy policy](https://policies.google.com/privacy).
|
||||||
|
|
||||||
### Data stored in your browser
|
### Data stored in your browser
|
||||||
|
|
||||||
This website uses browser cookies to authenticate registered users. This data consists of:
|
This website uses browser cookies to authenticate registered users. This data consists of:
|
||||||
|
|
||||||
- An account token to keep you logged into the website between visits, which is sent when any page is loaded while you are logged in
|
- An account token to keep you logged into the website between visits, which is sent when any page is loaded while you are logged in
|
||||||
|
|
||||||
This website also provides an option to store site preferences, such as the theme or locale, without an account. Using this feature will store a cookie in the visitor's browser containing their preferences. This cookie is sent on every request and does not contain any identifying information.
|
This website also provides an option to store site preferences, such as the theme or locale, without an account. Using this feature will store a cookie in the visitor's browser containing their preferences. This cookie is sent on every request and does not contain any identifying information.
|
||||||
|
|
||||||
You can remove this data from your browser by logging out of this website, or by using your browser's cookie-related controls to delete the data.
|
You can remove this data from your browser by logging out of this website, or by using your browser's cookie-related controls to delete the data.
|
||||||
|
|
||||||
### Removal of data
|
### Removal of data
|
||||||
|
|
||||||
To remove data stored in your browser, you can log out of the website, or you can use your browser's cookie-related controls to delete the data.
|
To remove data stored in your browser, you can log out of the website, or you can use your browser's cookie-related controls to delete the data.
|
||||||
|
|
||||||
To remove data that has been stored in the website's database, you can use the [delete my account](/delete_account) page.
|
To remove data that has been stored in the website's database, you can use the [delete my account](/delete_account) page.
|
||||||
END_PRIVACY_POLICY
|
END_PRIVACY_POLICY
|
||||||
)
|
)
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
|
@ -181,50 +181,35 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
|
<i class="icon ion-logo-bitcoin"></i>
|
||||||
|
BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</div>
|
||||||
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
|
<i class="icon ion-logo-bitcoin"></i>
|
||||||
|
BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</div>
|
||||||
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
|
<i class="icon ion-logo-usd"></i>
|
||||||
|
<a href="https://liberapay.com/omarroth">Liberapay</a>
|
||||||
|
/
|
||||||
|
<a href="https://patreon.com/omarroth">Patreon</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<%= content %>
|
<i class="icon ion-logo-javascript"></i>
|
||||||
|
<a rel="jslicense" href="/licenses">
|
||||||
|
<%= translate(locale, "View JavaScript license information.") %>
|
||||||
|
</a>
|
||||||
|
/
|
||||||
|
<i class="icon ion-ios-paper"></i>
|
||||||
|
<a href="/privacy">
|
||||||
|
<%= translate(locale, "View privacy policy.") %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<div class="pure-g">
|
<i class="icon ion-logo-github"></i>
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
<%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %>
|
||||||
<a href="https://github.com/omarroth/invidious">
|
<i class="icon ion-logo-github"></i>
|
||||||
<%= translate(locale, "Released under the AGPLv3 by Omar Roth.") %>
|
<%= CURRENT_BRANCH %></div>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
|
||||||
<i class="icon ion-logo-bitcoin"></i>
|
|
||||||
BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</div>
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
|
||||||
<i class="icon ion-logo-bitcoin"></i>
|
|
||||||
BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</div>
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
|
||||||
<i class="icon ion-logo-usd"></i>
|
|
||||||
<a href="https://liberapay.com/omarroth">Liberapay</a>
|
|
||||||
/
|
|
||||||
<a href="https://patreon.com/omarroth">Patreon</a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
|
||||||
<i class="icon ion-logo-javascript"></i>
|
|
||||||
<a rel="jslicense" href="/licenses">
|
|
||||||
<%= translate(locale, "View JavaScript license information.") %>
|
|
||||||
</a>
|
|
||||||
/
|
|
||||||
<i class="icon ion-ios-paper"></i>
|
|
||||||
<a href="/privacy">
|
|
||||||
<%= translate(locale, "View privacy policy.") %>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
|
||||||
<i class="icon ion-logo-github"></i>
|
|
||||||
<%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %>
|
|
||||||
<i class="icon ion-logo-github"></i>
|
|
||||||
<%= CURRENT_BRANCH %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-md-2-24"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/js/ui.js"></script>
|
<script src="/js/ui.js"></script>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<meta name="twitter:url" content="<%= host_url %>/watch?v=<%= video.id %>">
|
<meta name="twitter:url" content="<%= host_url %>/watch?v=<%= video.id %>">
|
||||||
<meta name="twitter:title" content="<%= HTML.escape(video.title) %>">
|
<meta name="twitter:title" content="<%= HTML.escape(video.title) %>">
|
||||||
<meta name="twitter:description" content="<%= description %>">
|
<meta name="twitter:description" content="<%= description %>">
|
||||||
<meta name="twitter:image" content="/vi/<%= video.id %>/hqdefault.jpg">
|
<meta name="twitter:image" content="<%= host_url %>/vi/<%= video.id %>/maxres.jpg">
|
||||||
<meta name="twitter:player" content="<%= host_url %>/embed/<%= video.id %>">
|
<meta name="twitter:player" content="<%= host_url %>/embed/<%= video.id %>">
|
||||||
<meta name="twitter:player:width" content="1280">
|
<meta name="twitter:player:width" content="1280">
|
||||||
<meta name="twitter:player:height" content="720">
|
<meta name="twitter:player:height" content="720">
|
||||||
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<h1>
|
<h1>
|
||||||
<%= HTML.escape(video.title) %>
|
<%= HTML.escape(video.title) %>
|
||||||
<% if params[:listen] %>
|
<% if params[:listen] %>
|
||||||
<a title="<%=translate(locale, "Video mode")%>" href="/watch?<%= env.params.query %>&listen=0">
|
<a title="<%=translate(locale, "Video mode")%>" href="/watch?<%= env.params.query %>&listen=0">
|
||||||
<i class="icon ion-ios-videocam"></i>
|
<i class="icon ion-ios-videocam"></i>
|
||||||
@ -53,23 +53,23 @@
|
|||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<p><a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch video on Youtube") %></a></p>
|
<p><a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch video on Youtube") %></a></p>
|
||||||
|
|
||||||
<form class="pure-form pure-form-stacked" action="/latest_version" method="get" rel="noopener" target="_blank">
|
<form class="pure-form pure-form-stacked" action="/latest_version" method="get" rel="noopener" target="_blank">
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="download_widget"><%= translate(locale, "Download as: ") %></label>
|
<label for="download_widget"><%= translate(locale, "Download as: ") %></label>
|
||||||
<select style="width:100%" name="download_widget" id="download_widget">
|
<select style="width:100%" name="download_widget" id="download_widget">
|
||||||
<% video_streams.each do |option| %>
|
<% video_streams.each do |option| %>
|
||||||
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split("/")[1].split(";")[0] %>"}'>
|
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'>
|
||||||
<%= option["quality_label"] %> - <%= option["type"].split(";")[0] %> @ <%= option["fps"] %>fps - video only
|
<%= option["quality_label"] %> - <%= option["type"].split(";")[0] %> @ <%= option["fps"] %>fps - video only
|
||||||
</option>
|
</option>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% audio_streams.each do |option| %>
|
<% audio_streams.each do |option| %>
|
||||||
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split("/")[1].split(";")[0] %>"}'>
|
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'>
|
||||||
<%= option["type"].split(";")[0] %> @ <%= option["bitrate"] %>k - audio only
|
<%= option["type"].split(";")[0] %> @ <%= option["bitrate"] %>k - audio only
|
||||||
</option>
|
</option>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% fmt_stream.each do |option| %>
|
<% fmt_stream.each do |option| %>
|
||||||
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split("/")[1].split(";")[0] %>"}'>
|
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'>
|
||||||
<%= itag_to_metadata?(option["itag"]).try &.["height"]? || "~240" %>p - <%= option["type"].split(";")[0] %>
|
<%= itag_to_metadata?(option["itag"]).try &.["height"]? || "~240" %>p - <%= option["type"].split(";")[0] %>
|
||||||
</option>
|
</option>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -150,7 +150,7 @@
|
|||||||
|
|
||||||
<% if params[:related_videos] %>
|
<% if params[:related_videos] %>
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
|
|
||||||
<% if !rvs.empty? %>
|
<% if !rvs.empty? %>
|
||||||
<div id="continue" <% if plid %>style="display:none"<% end %>>
|
<div id="continue" <% if plid %>style="display:none"<% end %>>
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
@ -187,7 +187,7 @@
|
|||||||
<script>
|
<script>
|
||||||
<% if !rvs.empty? && !plid && params[:continue] %>
|
<% if !rvs.empty? && !plid && params[:continue] %>
|
||||||
player.on('ended', function() {
|
player.on('ended', function() {
|
||||||
location.assign("/watch?v="
|
location.assign("/watch?v="
|
||||||
+ "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>"
|
+ "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>"
|
||||||
+ "&continue=1"
|
+ "&continue=1"
|
||||||
<% if params[:listen] %>
|
<% if params[:listen] %>
|
||||||
@ -206,7 +206,7 @@ player.on('ended', function() {
|
|||||||
function continue_autoplay(target) {
|
function continue_autoplay(target) {
|
||||||
if (target.checked) {
|
if (target.checked) {
|
||||||
player.on('ended', function() {
|
player.on('ended', function() {
|
||||||
location.assign("/watch?v="
|
location.assign("/watch?v="
|
||||||
+ "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>"
|
+ "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>"
|
||||||
+ "&continue=1"
|
+ "&continue=1"
|
||||||
<% if params[:listen] %>
|
<% if params[:listen] %>
|
||||||
@ -249,7 +249,7 @@ function get_playlist(timeouts = 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playlist.innerHTML = ' \
|
playlist.innerHTML = ' \
|
||||||
<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3> \
|
<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3> \
|
||||||
<hr>'
|
<hr>'
|
||||||
|
|
||||||
var plid = "<%= plid %>"
|
var plid = "<%= plid %>"
|
||||||
@ -270,10 +270,10 @@ function get_playlist(timeouts = 0) {
|
|||||||
if (xhr.readyState == 4) {
|
if (xhr.readyState == 4) {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
playlist.innerHTML = xhr.response.playlistHtml;
|
playlist.innerHTML = xhr.response.playlistHtml;
|
||||||
|
|
||||||
if (xhr.response.nextVideo) {
|
if (xhr.response.nextVideo) {
|
||||||
player.on('ended', function() {
|
player.on('ended', function() {
|
||||||
location.assign("/watch?v="
|
location.assign("/watch?v="
|
||||||
+ xhr.response.nextVideo
|
+ xhr.response.nextVideo
|
||||||
+ "&list=<%= plid %>"
|
+ "&list=<%= plid %>"
|
||||||
<% if params[:listen] %>
|
<% if params[:listen] %>
|
||||||
@ -300,7 +300,7 @@ function get_playlist(timeouts = 0) {
|
|||||||
|
|
||||||
comments = document.getElementById("playlist");
|
comments = document.getElementById("playlist");
|
||||||
comments.innerHTML =
|
comments.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3><hr>';
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
|
||||||
get_playlist(timeouts + 1);
|
get_playlist(timeouts + 1);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -319,7 +319,7 @@ function get_reddit_comments(timeouts = 0) {
|
|||||||
|
|
||||||
var fallback = comments.innerHTML;
|
var fallback = comments.innerHTML;
|
||||||
comments.innerHTML =
|
comments.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>";
|
var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>";
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
@ -355,7 +355,7 @@ function get_reddit_comments(timeouts = 0) {
|
|||||||
contentHtml: xhr.response.contentHtml
|
contentHtml: xhr.response.contentHtml
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
<% if preferences && preferences.comments[1] == "youtube" %>
|
<% if preferences && preferences.comments[1] == "youtube" %>
|
||||||
get_youtube_comments();
|
get_youtube_comments();
|
||||||
<% else %>
|
<% else %>
|
||||||
comments.innerHTML = fallback;
|
comments.innerHTML = fallback;
|
||||||
@ -382,7 +382,7 @@ function get_youtube_comments(timeouts = 0) {
|
|||||||
|
|
||||||
var fallback = comments.innerHTML;
|
var fallback = comments.innerHTML;
|
||||||
comments.innerHTML =
|
comments.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
var url = "/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("preferences").as(Preferences).locale %>";
|
var url = "/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("preferences").as(Preferences).locale %>";
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
@ -416,7 +416,7 @@ function get_youtube_comments(timeouts = 0) {
|
|||||||
comments.innerHTML = "";
|
comments.innerHTML = "";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
<% if preferences && preferences.comments[1] == "youtube" %>
|
<% if preferences && preferences.comments[1] == "youtube" %>
|
||||||
get_youtube_comments();
|
get_youtube_comments();
|
||||||
<% else %>
|
<% else %>
|
||||||
comments.innerHTML = "";
|
comments.innerHTML = "";
|
||||||
@ -429,7 +429,7 @@ function get_youtube_comments(timeouts = 0) {
|
|||||||
console.log("Pulling comments timed out.");
|
console.log("Pulling comments timed out.");
|
||||||
|
|
||||||
comments.innerHTML =
|
comments.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
get_youtube_comments(timeouts + 1);
|
get_youtube_comments(timeouts + 1);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -440,7 +440,7 @@ function get_youtube_replies(target, load_more) {
|
|||||||
var body = target.parentNode.parentNode;
|
var body = target.parentNode.parentNode;
|
||||||
var fallback = body.innerHTML;
|
var fallback = body.innerHTML;
|
||||||
body.innerHTML =
|
body.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
var url = '/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("preferences").as(Preferences).locale %>&continuation=' +
|
var url = '/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("preferences").as(Preferences).locale %>&continuation=' +
|
||||||
continuation;
|
continuation;
|
||||||
|
Loading…
Reference in New Issue
Block a user