mirror of
https://github.com/iv-org/invidious.git
synced 2025-08-23 14:13:18 +02:00
Compare commits
55 Commits
tvsimply-c
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
fd8dc93569 | ||
|
67f93e55d8 | ||
|
f35f529adc | ||
|
b32b077a80 | ||
|
6badb80082 | ||
|
15099ac1dd | ||
|
adc83f1c09 | ||
|
41e0e77d33 | ||
|
9ebc76462f | ||
|
0308acb624 | ||
|
cac2397494 | ||
|
cf640d808e | ||
|
80ec027c8f | ||
|
6f5f0dceca | ||
|
a8ab7b61f7 | ||
|
dd8086e6d9 | ||
|
875d8e7e41 | ||
|
1ae0f45b0e | ||
|
3335bc8c38 | ||
|
a84bb1d22e | ||
|
24252b836c | ||
|
227c041b86 | ||
|
803311713d | ||
|
64ac3b5203 | ||
|
b0c9f87fbe | ||
|
f8febbe2b2 | ||
|
436f955e0f | ||
|
4155f15bf7 | ||
|
b9171d9dab | ||
|
f3f6937ffc | ||
|
8723fdca06 | ||
|
d51e1cb051 | ||
|
cf0a68bd77 | ||
|
8cd9d53fb1 | ||
|
01cdb384e0 | ||
|
b1e7e0c45e | ||
|
0c96e0977f | ||
|
37be513e14 | ||
|
09d342b84d | ||
|
3a8d4f333f | ||
|
97354adf0f | ||
|
6497e1c418 | ||
|
f9472e4e4b | ||
|
cc643f209a | ||
|
381074fce1 | ||
|
033a44fab5 | ||
|
a3375e512e | ||
|
1d664c759f | ||
|
94f0a7a9d2 | ||
|
1d2f4b6813 | ||
|
cef0097a30 | ||
|
bef2d7b6b5 | ||
|
e67a30b124 | ||
|
bc3b3f6d69 | ||
|
73bf956af5 |
57
.github/workflows/build-nightly-container.yml
vendored
57
.github/workflows/build-nightly-container.yml
vendored
@@ -17,16 +17,26 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
platform: linux/amd64
|
||||||
|
name: "AMD64"
|
||||||
|
dockerfile: "docker/Dockerfile"
|
||||||
|
tag_suffix: ""
|
||||||
|
# GitHub doesn't have a ubuntu-latest-arm runner
|
||||||
|
- os: ubuntu-24.04-arm
|
||||||
|
platform: linux/arm64/v8
|
||||||
|
name: "ARM64"
|
||||||
|
dockerfile: "docker/Dockerfile.arm64"
|
||||||
|
tag_suffix: "-arm64"
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: arm64
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -43,45 +53,22 @@ jobs:
|
|||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: quay.io/invidious/invidious
|
images: quay.io/invidious/invidious
|
||||||
|
flavor: |
|
||||||
|
suffix=${{ matrix.tag_suffix }}
|
||||||
tags: |
|
tags: |
|
||||||
type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||||
type=raw,value=master,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
type=raw,value=master,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||||
labels: |
|
labels: |
|
||||||
quay.expires-after=12w
|
quay.expires-after=12w
|
||||||
|
|
||||||
- name: Build and push Docker AMD64 image for Push Event
|
- name: Build and push Docker ${{ matrix.name }} image for Push Event
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: ${{ matrix.dockerfile }}
|
||||||
platforms: linux/amd64
|
platforms: ${{ matrix.platform }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
build-args: |
|
build-args: |
|
||||||
"release=1"
|
"release=1"
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta-arm64
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: quay.io/invidious/invidious
|
|
||||||
flavor: |
|
|
||||||
suffix=-arm64
|
|
||||||
tags: |
|
|
||||||
type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
|
||||||
type=raw,value=master,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
|
||||||
labels: |
|
|
||||||
quay.expires-after=12w
|
|
||||||
|
|
||||||
- name: Build and push Docker ARM64 image for Push Event
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/Dockerfile.arm64
|
|
||||||
platforms: linux/arm64/v8
|
|
||||||
labels: ${{ steps.meta-arm64.outputs.labels }}
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta-arm64.outputs.tags }}
|
|
||||||
build-args: |
|
|
||||||
"release=1"
|
|
||||||
|
57
.github/workflows/build-stable-container.yml
vendored
57
.github/workflows/build-stable-container.yml
vendored
@@ -8,16 +8,26 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
platform: linux/amd64
|
||||||
|
name: "AMD64"
|
||||||
|
dockerfile: "docker/Dockerfile"
|
||||||
|
tag_suffix: ""
|
||||||
|
# GitHub doesn't have a ubuntu-latest-arm runner
|
||||||
|
- os: ubuntu-24.04-arm
|
||||||
|
platform: linux/arm64/v8
|
||||||
|
name: "ARM64"
|
||||||
|
dockerfile: "docker/Dockerfile.arm64"
|
||||||
|
tag_suffix: "-arm64"
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: arm64
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -36,46 +46,21 @@ jobs:
|
|||||||
images: quay.io/invidious/invidious
|
images: quay.io/invidious/invidious
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
|
suffix=${{ matrix.tag_suffix }}
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=raw,value=latest
|
type=raw,value=latest
|
||||||
labels: |
|
labels: |
|
||||||
quay.expires-after=12w
|
quay.expires-after=12w
|
||||||
|
|
||||||
- name: Build and push Docker AMD64 image for Push Event
|
- name: Build and push Docker ${{ matrix.name }} image for Push Event
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: ${{ matrix.dockerfile }}
|
||||||
platforms: linux/amd64
|
platforms: ${{ matrix.platform }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
build-args: |
|
build-args: |
|
||||||
"release=1"
|
"release=1"
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta-arm64
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: quay.io/invidious/invidious
|
|
||||||
flavor: |
|
|
||||||
latest=false
|
|
||||||
suffix=-arm64
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=raw,value=latest
|
|
||||||
labels: |
|
|
||||||
quay.expires-after=12w
|
|
||||||
|
|
||||||
- name: Build and push Docker ARM64 image for Push Event
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/Dockerfile.arm64
|
|
||||||
platforms: linux/arm64/v8
|
|
||||||
labels: ${{ steps.meta-arm64.outputs.labels }}
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta-arm64.outputs.tags }}
|
|
||||||
build-args: |
|
|
||||||
"release=1"
|
|
||||||
|
59
.github/workflows/ci.yml
vendored
59
.github/workflows/ci.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
|||||||
stable: false
|
stable: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -83,46 +83,43 @@ jobs:
|
|||||||
run: crystal build --warnings all --error-on-warnings --error-trace src/invidious.cr
|
run: crystal build --warnings all --error-on-warnings --error-trace src/invidious.cr
|
||||||
|
|
||||||
build-docker:
|
build-docker:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
name: "AMD64"
|
||||||
|
# GitHub doesn't have a ubuntu-latest-arm runner
|
||||||
|
- os: ubuntu-24.04-arm
|
||||||
|
name: "ARM64"
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
name: Test ${{ matrix.name }} Docker build
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Use ARM64 Dockerfile if ARM64
|
||||||
|
if: ${{ matrix.name }} == "ARM64"
|
||||||
|
run: sed -i 's/Dockerfile/Dockerfile.arm64/' docker-compose.yml
|
||||||
|
|
||||||
- name: Build Docker
|
- name: Build Docker
|
||||||
run: docker compose build --build-arg release=0
|
run: docker compose build
|
||||||
|
|
||||||
|
- name: Change hmac_key on docker-compose.yml
|
||||||
|
run: sed -i '/hmac_key/s/CHANGE_ME!!/docker-build-hmac-key/' docker-compose.yml
|
||||||
|
|
||||||
- name: Run Docker
|
- name: Run Docker
|
||||||
run: docker compose up -d
|
run: docker compose up -d
|
||||||
|
|
||||||
- name: Test Docker
|
- name: Test Docker
|
||||||
run: while curl -Isf http://localhost:3000; do sleep 1; done
|
id: test
|
||||||
|
run: curl -If http://localhost:3000 --retry 5 --retry-delay 1 --retry-all-errors
|
||||||
|
|
||||||
build-docker-arm64:
|
- name: Print Invidious container logs
|
||||||
|
# Tells Github Actions to always run this step regardless of whether the previous step has failed
|
||||||
runs-on: ubuntu-latest
|
# Without this expression this step would simply be skipped when the previous step fails.
|
||||||
|
if: success() || steps.test.conclusion == 'failure'
|
||||||
steps:
|
run: docker compose logs
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: arm64
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build Docker ARM64 image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/Dockerfile.arm64
|
|
||||||
platforms: linux/arm64/v8
|
|
||||||
build-args: release=0
|
|
||||||
|
|
||||||
- name: Test Docker
|
|
||||||
run: while curl -Isf http://localhost:3000; do sleep 1; done
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
|
|
||||||
@@ -131,7 +128,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
@@ -86,6 +86,7 @@ ul.vjs-menu-content::-webkit-scrollbar {
|
|||||||
background-color: rgba(0, 0, 0, 0.75) !important;
|
background-color: rgba(0, 0, 0, 0.75) !important;
|
||||||
border-radius: 9px !important;
|
border-radius: 9px !important;
|
||||||
padding: 5px !important;
|
padding: 5px !important;
|
||||||
|
line-height: 1.5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-play-control,
|
.vjs-play-control,
|
||||||
|
@@ -77,7 +77,7 @@ function create_notification_stream(subscriptions) {
|
|||||||
function update_ticker_count() {
|
function update_ticker_count() {
|
||||||
var notification_ticker = document.getElementById('notification_ticker');
|
var notification_ticker = document.getElementById('notification_ticker');
|
||||||
|
|
||||||
const notification_count = helpers.storage.get(STORAGE_KEY_STREAM);
|
const notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0;
|
||||||
if (notification_count > 0) {
|
if (notification_count > 0) {
|
||||||
notification_ticker.innerHTML =
|
notification_ticker.innerHTML =
|
||||||
'<span id="notification_count">' + notification_count + '</span> <i class="icon ion-ios-notifications"></i>';
|
'<span id="notification_count">' + notification_count + '</span> <i class="icon ion-ios-notifications"></i>';
|
||||||
|
@@ -5,6 +5,10 @@ var video_data = JSON.parse(document.getElementById('video_data').textContent);
|
|||||||
var options = {
|
var options = {
|
||||||
liveui: true,
|
liveui: true,
|
||||||
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
|
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
|
||||||
|
fontPercent: [0.5, 0.75, 1.25, 1.5, 1.75, 2, 3, 4],
|
||||||
|
windowOpacity: ['0', '0.5', '1'],
|
||||||
|
textOpacity: ['0.5', '1'],
|
||||||
|
persistTextTrackSettings: true,
|
||||||
controlBar: {
|
controlBar: {
|
||||||
children: [
|
children: [
|
||||||
'playToggle',
|
'playToggle',
|
||||||
@@ -180,7 +184,7 @@ var shareOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (location.pathname.startsWith('/embed/')) {
|
if (location.pathname.startsWith('/embed/')) {
|
||||||
var overlay_content = '<h1><a rel="noopener" target="_blank" href="' + location.origin + '/watch?v=' + video_data.id + '">' + player_data.title + '</a></h1>';
|
var overlay_content = '<h1><a rel="noopener noreferrer" target="_blank" href="' + location.origin + '/watch?v=' + video_data.id + '">' + player_data.title + '</a></h1>';
|
||||||
player.overlay({
|
player.overlay({
|
||||||
overlays: [
|
overlays: [
|
||||||
{ start: 'loadstart', content: overlay_content, end: 'playing', align: 'top'},
|
{ start: 'loadstart', content: overlay_content, end: 'playing', align: 'top'},
|
||||||
@@ -450,7 +454,7 @@ if (!video_data.params.listen && video_data.params.annotations) {
|
|||||||
if (target === 'current') {
|
if (target === 'current') {
|
||||||
location.href = path;
|
location.href = path;
|
||||||
} else if (target === 'new') {
|
} else if (target === 'new') {
|
||||||
open(path, '_blank');
|
open(path, '_blank', 'noopener,noreferrer');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -585,6 +589,13 @@ const toggle_captions = (function () {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// For real-time updates to captions (if currently showing)
|
||||||
|
function update_captions() {
|
||||||
|
if (document.body.querySelector('.vjs-text-track-cue')) {
|
||||||
|
toggle_captions(); toggle_captions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggle_fullscreen() {
|
function toggle_fullscreen() {
|
||||||
player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen();
|
player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen();
|
||||||
}
|
}
|
||||||
@@ -597,6 +608,34 @@ function increase_playback_rate(steps) {
|
|||||||
player.playbackRate(options.playbackRates[newIndex]);
|
player.playbackRate(options.playbackRates[newIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function increase_caption_size(steps) {
|
||||||
|
const maxIndex = options.fontPercent.length - 1;
|
||||||
|
const fontPercent = player.textTrackSettings.getValues().fontPercent || 1.25;
|
||||||
|
const curIndex = options.fontPercent.indexOf(fontPercent);
|
||||||
|
let newIndex = curIndex + steps;
|
||||||
|
newIndex = helpers.clamp(newIndex, 0, maxIndex);
|
||||||
|
player.textTrackSettings.setValues({ fontPercent: options.fontPercent[newIndex] });
|
||||||
|
update_captions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_caption_window() {
|
||||||
|
const numOptions = options.windowOpacity.length;
|
||||||
|
const windowOpacity = player.textTrackSettings.getValues().windowOpacity || '0';
|
||||||
|
const curIndex = options.windowOpacity.indexOf(windowOpacity);
|
||||||
|
const newIndex = (curIndex + 1) % numOptions;
|
||||||
|
player.textTrackSettings.setValues({ windowOpacity: options.windowOpacity[newIndex] });
|
||||||
|
update_captions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_caption_opacity() {
|
||||||
|
const numOptions = options.textOpacity.length;
|
||||||
|
const textOpacity = player.textTrackSettings.getValues().textOpacity || '1';
|
||||||
|
const curIndex = options.textOpacity.indexOf(textOpacity);
|
||||||
|
const newIndex = (curIndex + 1) % numOptions;
|
||||||
|
player.textTrackSettings.setValues({ textOpacity: options.textOpacity[newIndex] });
|
||||||
|
update_captions();
|
||||||
|
}
|
||||||
|
|
||||||
addEventListener('keydown', function (e) {
|
addEventListener('keydown', function (e) {
|
||||||
if (e.target.tagName.toLowerCase() === 'input') {
|
if (e.target.tagName.toLowerCase() === 'input') {
|
||||||
// Ignore input when focus is on certain elements, e.g. form fields.
|
// Ignore input when focus is on certain elements, e.g. form fields.
|
||||||
@@ -693,6 +732,12 @@ addEventListener('keydown', function (e) {
|
|||||||
case '>': action = increase_playback_rate.bind(this, 1); break;
|
case '>': action = increase_playback_rate.bind(this, 1); break;
|
||||||
case '<': action = increase_playback_rate.bind(this, -1); break;
|
case '<': action = increase_playback_rate.bind(this, -1); break;
|
||||||
|
|
||||||
|
case '=': action = increase_caption_size.bind(this, 1); break;
|
||||||
|
case '-': action = increase_caption_size.bind(this, -1); break;
|
||||||
|
|
||||||
|
case 'w': action = toggle_caption_window; break;
|
||||||
|
case 'o': action = toggle_caption_opacity; break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.info('Unhandled key down event: %s:', decoratedKey, e);
|
console.info('Unhandled key down event: %s:', decoratedKey, e);
|
||||||
break;
|
break;
|
||||||
|
@@ -141,7 +141,7 @@ function get_reddit_comments() {
|
|||||||
</b> \
|
</b> \
|
||||||
</p> \
|
</p> \
|
||||||
<b> \
|
<b> \
|
||||||
<a rel="noopener" target="_blank" href="https://reddit.com{permalink}">{redditPermalinkText}</a> \
|
<a rel="noopener noreferrer" target="_blank" href="https://reddit.com{permalink}">{redditPermalinkText}</a> \
|
||||||
</b> \
|
</b> \
|
||||||
</div> \
|
</div> \
|
||||||
<div>{contentHtml}</div> \
|
<div>{contentHtml}</div> \
|
||||||
|
@@ -865,7 +865,7 @@ default_user_preferences:
|
|||||||
##
|
##
|
||||||
## Default dash video quality.
|
## Default dash video quality.
|
||||||
##
|
##
|
||||||
## Note: this setting only takes effet if the
|
## Note: this setting only takes effect if the
|
||||||
## 'quality' parameter is set to "dash".
|
## 'quality' parameter is set to "dash".
|
||||||
##
|
##
|
||||||
## Accepted values:
|
## Accepted values:
|
||||||
|
@@ -14,6 +14,10 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:3000:3000"
|
- "127.0.0.1:3000:3000"
|
||||||
|
depends_on:
|
||||||
|
invidious-db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
||||||
environment:
|
environment:
|
||||||
# Please read the following file for a comprehensive list of all available
|
# Please read the following file for a comprehensive list of all available
|
||||||
# configuration options and their associated syntax:
|
# configuration options and their associated syntax:
|
||||||
|
@@ -60,7 +60,13 @@ alias IV = Invidious
|
|||||||
CONFIG = Config.load
|
CONFIG = Config.load
|
||||||
HMAC_KEY = CONFIG.hmac_key
|
HMAC_KEY = CONFIG.hmac_key
|
||||||
|
|
||||||
PG_DB = DB.open CONFIG.database_url
|
PG_DB = begin
|
||||||
|
DB.open CONFIG.database_url
|
||||||
|
rescue ex
|
||||||
|
puts "Failed to connect to PostgreSQL database: #{ex.cause.try &.message}"
|
||||||
|
puts "Check your 'config.yml' database settings or PostgreSQL settings."
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
ARCHIVE_URL = URI.parse("https://archive.org")
|
ARCHIVE_URL = URI.parse("https://archive.org")
|
||||||
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
|
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
|
||||||
REDDIT_URL = URI.parse("https://www.reddit.com")
|
REDDIT_URL = URI.parse("https://www.reddit.com")
|
||||||
@@ -221,8 +227,8 @@ error 404 do |env|
|
|||||||
Invidious::Routes::ErrorRoutes.error_404(env)
|
Invidious::Routes::ErrorRoutes.error_404(env)
|
||||||
end
|
end
|
||||||
|
|
||||||
error 500 do |env, ex|
|
error 500 do |env, exception|
|
||||||
error_template(500, ex)
|
error_template(500, exception)
|
||||||
end
|
end
|
||||||
|
|
||||||
static_headers do |env|
|
static_headers do |env|
|
||||||
|
@@ -3,8 +3,8 @@ private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000}
|
|||||||
# TODO: Add "sort_by"
|
# TODO: Add "sort_by"
|
||||||
def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
|
def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
|
||||||
if cursor.nil?
|
if cursor.nil?
|
||||||
# Egljb21tdW5pdHk%3D is the protobuf object to load "community"
|
# EgVwb3N0c_IGBAoCSgA%3D is the protobuf object to load "posts"
|
||||||
initial_data = YoutubeAPI.browse(ucid, params: "Egljb21tdW5pdHk%3D")
|
initial_data = YoutubeAPI.browse(ucid, params: "EgVwb3N0c_IGBAoCSgA%3D")
|
||||||
|
|
||||||
items = [] of JSON::Any
|
items = [] of JSON::Any
|
||||||
extract_items(initial_data) do |item|
|
extract_items(initial_data) do |item|
|
||||||
@@ -24,15 +24,21 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
|
|||||||
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
|
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def decode_ucid_from_post_protobuf(params)
|
||||||
|
decoded_protobuf = params.try { |i| URI.decode_www_form(i) }
|
||||||
|
.try { |i| Base64.decode(i) }
|
||||||
|
.try { |i| IO::Memory.new(i) }
|
||||||
|
.try { |i| Protodec::Any.parse(i) }
|
||||||
|
|
||||||
|
return decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
|
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
|
||||||
object = {
|
object = {
|
||||||
"2:string" => "community",
|
"56:embedded" => {
|
||||||
"25:embedded" => {
|
"2:string" => ucid,
|
||||||
"22:string" => post_id.to_s,
|
"3:string" => post_id.to_s,
|
||||||
},
|
"11:string" => ucid,
|
||||||
"45:embedded" => {
|
|
||||||
"2:varint" => 1_i64,
|
|
||||||
"3:varint" => 1_i64,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
params = object.try { |i| Protodec::Any.cast_json(i) }
|
params = object.try { |i| Protodec::Any.cast_json(i) }
|
||||||
@@ -40,7 +46,7 @@ def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
|
|||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
initial_data = YoutubeAPI.browse(ucid, params: params)
|
initial_data = YoutubeAPI.browse("FEpost_detail", params: params)
|
||||||
|
|
||||||
items = [] of JSON::Any
|
items = [] of JSON::Any
|
||||||
extract_items(initial_data) do |item|
|
extract_items(initial_data) do |item|
|
||||||
|
@@ -6,19 +6,19 @@ def fetch_channel_playlists(ucid, author, continuation, sort_by)
|
|||||||
case sort_by
|
case sort_by
|
||||||
when "last", "last_added"
|
when "last", "last_added"
|
||||||
# Equivalent to "&sort=lad"
|
# Equivalent to "&sort=lad"
|
||||||
# {"2:string": "playlists", "3:varint": 4, "4:varint": 1, "6:varint": 1}
|
# {"2:string": "playlists", "3:varint": 4, "4:varint": 1, "6:varint": 1, "110:embedded": {"1:embedded": {"8:string": ""}}}
|
||||||
"EglwbGF5bGlzdHMYBCABMAE%3D"
|
"EglwbGF5bGlzdHMYBCABMAHyBgQKAkIA"
|
||||||
when "oldest", "oldest_created"
|
when "oldest", "oldest_created"
|
||||||
# formerly "&sort=da"
|
# formerly "&sort=da"
|
||||||
# Not available anymore :c or maybe ??
|
# Not available anymore :c or maybe ??
|
||||||
# {"2:string": "playlists", "3:varint": 2, "4:varint": 1, "6:varint": 1}
|
# {"2:string": "playlists", "3:varint": 2, "4:varint": 1, "6:varint": 1, "110:embedded": {"1:embedded": {"8:string": ""}}}
|
||||||
"EglwbGF5bGlzdHMYAiABMAE%3D"
|
"EglwbGF5bGlzdHMYAiABMAHyBgQKAkIA"
|
||||||
# {"2:string": "playlists", "3:varint": 1, "4:varint": 1, "6:varint": 1}
|
# {"2:string": "playlists", "3:varint": 1, "4:varint": 1, "6:varint": 1}
|
||||||
# "EglwbGF5bGlzdHMYASABMAE%3D"
|
# "EglwbGF5bGlzdHMYASABMAE%3D"
|
||||||
when "newest", "newest_created"
|
when "newest", "newest_created"
|
||||||
# Formerly "&sort=dd"
|
# Formerly "&sort=dd"
|
||||||
# {"2:string": "playlists", "3:varint": 3, "4:varint": 1, "6:varint": 1}
|
# {"2:string": "playlists", "3:varint": 3, "4:varint": 1, "6:varint": 1, "110:embedded": {"1:embedded": {"8:string": ""}}}
|
||||||
"EglwbGF5bGlzdHMYAyABMAE%3D"
|
"EglwbGF5bGlzdHMYAyABMAHyBgQKAkIA"
|
||||||
end
|
end
|
||||||
|
|
||||||
initial_data = YoutubeAPI.browse(ucid, params: params || "")
|
initial_data = YoutubeAPI.browse(ucid, params: params || "")
|
||||||
|
@@ -16,23 +16,27 @@ module Invidious::Comments
|
|||||||
return parse_youtube(id, response, format, locale, thin_mode, sort_by)
|
return parse_youtube(id, response, format, locale, thin_mode, sort_by)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_community_post_comments(ucid, post_id)
|
def fetch_community_post_comments(ucid, post_id, sort_by = "top")
|
||||||
|
case sort_by
|
||||||
|
when "top"
|
||||||
|
sort_by_val = 0_i64
|
||||||
|
when "new", "newest"
|
||||||
|
sort_by_val = 1_i64
|
||||||
|
else # top
|
||||||
|
sort_by_val = 0_i64
|
||||||
|
end
|
||||||
|
|
||||||
object = {
|
object = {
|
||||||
"2:string" => "community",
|
"2:string" => "posts",
|
||||||
"25:embedded" => {
|
|
||||||
"22:string" => post_id,
|
|
||||||
},
|
|
||||||
"45:embedded" => {
|
|
||||||
"2:varint" => 1_i64,
|
|
||||||
"3:varint" => 1_i64,
|
|
||||||
},
|
|
||||||
"53:embedded" => {
|
"53:embedded" => {
|
||||||
"4:embedded" => {
|
"4:embedded" => {
|
||||||
"6:varint" => 0_i64,
|
"6:varint" => sort_by_val,
|
||||||
"27:varint" => 1_i64,
|
"15:varint" => 2_i64,
|
||||||
|
"25:varint" => 0_i64,
|
||||||
"29:string" => post_id,
|
"29:string" => post_id,
|
||||||
"30:string" => ucid,
|
"30:string" => ucid,
|
||||||
},
|
},
|
||||||
|
"7:varint" => 0_i64,
|
||||||
"8:string" => "comments-section",
|
"8:string" => "comments-section",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ module Invidious::Comments
|
|||||||
|
|
||||||
object2 = {
|
object2 = {
|
||||||
"80226972:embedded" => {
|
"80226972:embedded" => {
|
||||||
"2:string" => ucid,
|
"2:string" => "FEcomment_post_detail_page_web_top_level",
|
||||||
"3:string" => object_parsed,
|
"3:string" => object_parsed,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -320,6 +324,15 @@ module Invidious::Comments
|
|||||||
end
|
end
|
||||||
|
|
||||||
def produce_continuation(video_id, cursor = "", sort_by = "top")
|
def produce_continuation(video_id, cursor = "", sort_by = "top")
|
||||||
|
case sort_by
|
||||||
|
when "top"
|
||||||
|
sort_by_val = 0_i64
|
||||||
|
when "new", "newest"
|
||||||
|
sort_by_val = 1_i64
|
||||||
|
else # top
|
||||||
|
sort_by_val = 0_i64
|
||||||
|
end
|
||||||
|
|
||||||
object = {
|
object = {
|
||||||
"2:embedded" => {
|
"2:embedded" => {
|
||||||
"2:string" => video_id,
|
"2:string" => video_id,
|
||||||
@@ -340,21 +353,12 @@ module Invidious::Comments
|
|||||||
"1:string" => cursor,
|
"1:string" => cursor,
|
||||||
"4:embedded" => {
|
"4:embedded" => {
|
||||||
"4:string" => video_id,
|
"4:string" => video_id,
|
||||||
"6:varint" => 0_i64,
|
"6:varint" => sort_by_val,
|
||||||
},
|
},
|
||||||
"5:varint" => 20_i64,
|
"5:varint" => 20_i64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
case sort_by
|
|
||||||
when "top"
|
|
||||||
object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
|
|
||||||
when "new", "newest"
|
|
||||||
object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64
|
|
||||||
else # top
|
|
||||||
object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
|
|
||||||
end
|
|
||||||
|
|
||||||
continuation = object.try { |i| Protodec::Any.cast_json(i) }
|
continuation = object.try { |i| Protodec::Any.cast_json(i) }
|
||||||
.try { |i| Protodec::Any.from_json(i) }
|
.try { |i| Protodec::Any.from_json(i) }
|
||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
|
@@ -34,7 +34,7 @@ module Invidious::Frontend::WatchPage
|
|||||||
str << " class=\"pure-form pure-form-stacked\""
|
str << " class=\"pure-form pure-form-stacked\""
|
||||||
str << " action='#{url}'"
|
str << " action='#{url}'"
|
||||||
str << " method='post'"
|
str << " method='post'"
|
||||||
str << " rel='noopener'"
|
str << " rel='noopener noreferrer'"
|
||||||
str << " target='_blank'>"
|
str << " target='_blank'>"
|
||||||
str << '\n'
|
str << '\n'
|
||||||
|
|
||||||
|
@@ -436,7 +436,7 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
if ucid.nil?
|
if ucid.nil?
|
||||||
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
|
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
|
||||||
return error_json(400, "Invalid post ID") if response["error"]?
|
return error_json(400, "Invalid post ID") if response["error"]?
|
||||||
ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
|
ucid = decode_ucid_from_post_protobuf(response.dig("endpoint", "browseEndpoint", "params").as_s)
|
||||||
else
|
else
|
||||||
ucid = ucid.to_s
|
ucid = ucid.to_s
|
||||||
end
|
end
|
||||||
@@ -460,13 +460,15 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
|
|
||||||
format = env.params.query["format"]?
|
format = env.params.query["format"]?
|
||||||
format ||= "json"
|
format ||= "json"
|
||||||
|
sort_by = env.params.query["sort_by"]?.try &.downcase
|
||||||
|
sort_by ||= "top"
|
||||||
|
|
||||||
continuation = env.params.query["continuation"]?
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
case continuation
|
case continuation
|
||||||
when nil, ""
|
when nil, ""
|
||||||
ucid = env.params.query["ucid"]
|
ucid = env.params.query["ucid"]
|
||||||
comments = Comments.fetch_community_post_comments(ucid, id)
|
comments = Comments.fetch_community_post_comments(ucid, id, sort_by: sort_by)
|
||||||
else
|
else
|
||||||
comments = YoutubeAPI.browse(continuation: continuation)
|
comments = YoutubeAPI.browse(continuation: continuation)
|
||||||
end
|
end
|
||||||
|
@@ -190,15 +190,30 @@ module Invidious::Routes::API::V1::Misc
|
|||||||
|
|
||||||
sub_endpoint = endpoint["watchEndpoint"]? || endpoint["browseEndpoint"]? || endpoint
|
sub_endpoint = endpoint["watchEndpoint"]? || endpoint["browseEndpoint"]? || endpoint
|
||||||
params = sub_endpoint.try &.dig?("params")
|
params = sub_endpoint.try &.dig?("params")
|
||||||
|
|
||||||
|
if sub_endpoint["browseId"]?.try &.as_s == "FEpost_detail"
|
||||||
|
decoded_protobuf = params.try &.as_s.try { |i| URI.decode_www_form(i) }
|
||||||
|
.try { |i| Base64.decode(i) }
|
||||||
|
.try { |i| IO::Memory.new(i) }
|
||||||
|
.try { |i| Protodec::Any.parse(i) }
|
||||||
|
|
||||||
|
ucid = decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
|
||||||
|
post_id = decoded_protobuf.try(&.["56:0:embedded"]["3:1:string"].as_s)
|
||||||
|
else
|
||||||
|
ucid = sub_endpoint["browseId"]? if sub_endpoint["browseId"]? && sub_endpoint["browseId"]?.try &.as_s.starts_with? "UC"
|
||||||
|
post_id = nil
|
||||||
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "ucid", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]?
|
json.field "browseId", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]?
|
||||||
|
json.field "ucid", ucid if ucid != nil
|
||||||
json.field "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]?
|
json.field "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]?
|
||||||
json.field "playlistId", sub_endpoint["playlistId"].as_s if sub_endpoint["playlistId"]?
|
json.field "playlistId", sub_endpoint["playlistId"].as_s if sub_endpoint["playlistId"]?
|
||||||
json.field "startTimeSeconds", sub_endpoint["startTimeSeconds"].as_i if sub_endpoint["startTimeSeconds"]?
|
json.field "startTimeSeconds", sub_endpoint["startTimeSeconds"].as_i if sub_endpoint["startTimeSeconds"]?
|
||||||
|
json.field "postId", post_id if post_id != nil
|
||||||
json.field "params", params.try &.as_s
|
json.field "params", params.try &.as_s
|
||||||
json.field "pageType", page_type
|
json.field "pageType", page_type
|
||||||
end
|
end
|
||||||
|
@@ -284,7 +284,7 @@ module Invidious::Routes::Channels
|
|||||||
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
|
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
|
||||||
return error_template(400, "Invalid post ID") if response["error"]?
|
return error_template(400, "Invalid post ID") if response["error"]?
|
||||||
|
|
||||||
ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
|
ucid = decode_ucid_from_post_protobuf(response.dig("endpoint", "browseEndpoint", "params").as_s)
|
||||||
post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode)
|
post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ module Invidious::Routes::Embed
|
|||||||
return error_template(500, ex)
|
return error_template(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
url = "/embed/#{first_playlist_video}?#{env.params.query}"
|
url = "/embed/#{first_playlist_video.id}?#{env.params.query}"
|
||||||
|
|
||||||
if env.params.query.size > 0
|
if env.params.query.size > 0
|
||||||
url += "?#{env.params.query}"
|
url += "?#{env.params.query}"
|
||||||
|
@@ -111,16 +111,17 @@ def extract_video_info(video_id : String)
|
|||||||
if !CONFIG.invidious_companion.present?
|
if !CONFIG.invidious_companion.present?
|
||||||
if player_response.dig?("streamingData", "adaptiveFormats", 0, "url").nil?
|
if player_response.dig?("streamingData", "adaptiveFormats", 0, "url").nil?
|
||||||
LOGGER.warn("Missing URLs for adaptive formats, falling back to other YT clients.")
|
LOGGER.warn("Missing URLs for adaptive formats, falling back to other YT clients.")
|
||||||
players_fallback = {YoutubeAPI::ClientType::TvHtml5, YoutubeAPI::ClientType::WebMobile}
|
players_fallback = {YoutubeAPI::ClientType::TvSimply, YoutubeAPI::ClientType::WebMobile}
|
||||||
|
|
||||||
players_fallback.each do |player_fallback|
|
players_fallback.each do |player_fallback|
|
||||||
client_config.client_type = player_fallback
|
client_config.client_type = player_fallback
|
||||||
|
|
||||||
next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config))
|
next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config))
|
||||||
|
|
||||||
if player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "url")
|
adaptive_formats = player_fallback_response.dig?("streamingData", "adaptiveFormats")
|
||||||
|
if adaptive_formats && (adaptive_formats.dig?(0, "url") || adaptive_formats.dig?(0, "signatureCipher"))
|
||||||
streaming_data = player_response["streamingData"].as_h
|
streaming_data = player_response["streamingData"].as_h
|
||||||
streaming_data["adaptiveFormats"] = player_fallback_response["streamingData"]["adaptiveFormats"]
|
streaming_data["adaptiveFormats"] = adaptive_formats
|
||||||
player_response["streamingData"] = JSON::Any.new(streaming_data)
|
player_response["streamingData"] = JSON::Any.new(streaming_data)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -146,7 +147,11 @@ def extract_video_info(video_id : String)
|
|||||||
if streaming_data = player_response["streamingData"]?
|
if streaming_data = player_response["streamingData"]?
|
||||||
%w[formats adaptiveFormats].each do |key|
|
%w[formats adaptiveFormats].each do |key|
|
||||||
streaming_data.as_h[key]?.try &.as_a.each do |format|
|
streaming_data.as_h[key]?.try &.as_a.each do |format|
|
||||||
format.as_h["url"] = JSON::Any.new(convert_url(format))
|
format = format.as_h
|
||||||
|
if format["url"]?.nil?
|
||||||
|
format["url"] = format["signatureCipher"]
|
||||||
|
end
|
||||||
|
format["url"] = JSON::Any.new(convert_url(format))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_youtube">
|
<label for="import_youtube">
|
||||||
<a rel="noopener" target="_blank" href="https://github.com/iv-org/documentation/blob/master/docs/export-youtube-subscriptions.md">
|
<a rel="noopener noreferrer" target="_blank" href="https://github.com/iv-org/documentation/blob/master/docs/export-youtube-subscriptions.md">
|
||||||
<%= translate(locale, "Import YouTube subscriptions") %>
|
<%= translate(locale, "Import YouTube subscriptions") %>
|
||||||
</a>
|
</a>
|
||||||
</label>
|
</label>
|
||||||
|
@@ -6,10 +6,10 @@ module YoutubeAPI
|
|||||||
extend self
|
extend self
|
||||||
|
|
||||||
# For Android versions, see https://en.wikipedia.org/wiki/Android_version_history
|
# For Android versions, see https://en.wikipedia.org/wiki/Android_version_history
|
||||||
private ANDROID_APP_VERSION = "19.32.34"
|
private ANDROID_APP_VERSION = "19.35.36"
|
||||||
private ANDROID_VERSION = "12"
|
private ANDROID_VERSION = "13"
|
||||||
private ANDROID_USER_AGENT = "com.google.android.youtube/#{ANDROID_APP_VERSION} (Linux; U; Android #{ANDROID_VERSION}; US) gzip"
|
private ANDROID_USER_AGENT = "com.google.android.youtube/#{ANDROID_APP_VERSION} (Linux; U; Android #{ANDROID_VERSION}; en_US; SM-S908E Build/TP1A.220624.014) gzip"
|
||||||
private ANDROID_SDK_VERSION = 31_i64
|
private ANDROID_SDK_VERSION = 33_i64
|
||||||
|
|
||||||
private ANDROID_TS_APP_VERSION = "1.9"
|
private ANDROID_TS_APP_VERSION = "1.9"
|
||||||
private ANDROID_TS_USER_AGENT = "com.google.android.youtube/1.9 (Linux; U; Android 12; US) gzip"
|
private ANDROID_TS_USER_AGENT = "com.google.android.youtube/1.9 (Linux; U; Android 12; US) gzip"
|
||||||
@@ -17,9 +17,9 @@ module YoutubeAPI
|
|||||||
# For Apple device names, see https://gist.github.com/adamawolf/3048717
|
# For Apple device names, see https://gist.github.com/adamawolf/3048717
|
||||||
# For iOS versions, see https://en.wikipedia.org/wiki/IOS_version_history#Releases,
|
# For iOS versions, see https://en.wikipedia.org/wiki/IOS_version_history#Releases,
|
||||||
# then go to the dedicated article of the major version you want.
|
# then go to the dedicated article of the major version you want.
|
||||||
private IOS_APP_VERSION = "19.32.8"
|
private IOS_APP_VERSION = "20.11.6"
|
||||||
private IOS_USER_AGENT = "com.google.ios.youtube/#{IOS_APP_VERSION} (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)"
|
private IOS_USER_AGENT = "com.google.ios.youtube/#{IOS_APP_VERSION} (iPhone14,5; U; CPU iOS 18_5 like Mac OS X;)"
|
||||||
private IOS_VERSION = "17.6.1.21G93" # Major.Minor.Patch.Build
|
private IOS_VERSION = "18.5.0.22F76" # Major.Minor.Patch.Build
|
||||||
|
|
||||||
private WINDOWS_VERSION = "10.0"
|
private WINDOWS_VERSION = "10.0"
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ module YoutubeAPI
|
|||||||
ClientType::Web => {
|
ClientType::Web => {
|
||||||
name: "WEB",
|
name: "WEB",
|
||||||
name_proto: "1",
|
name_proto: "1",
|
||||||
version: "2.20240814.00.00",
|
version: "2.20250222.10.00",
|
||||||
screen: "WATCH_FULL_SCREEN",
|
screen: "WATCH_FULL_SCREEN",
|
||||||
os_name: "Windows",
|
os_name: "Windows",
|
||||||
os_version: WINDOWS_VERSION,
|
os_version: WINDOWS_VERSION,
|
||||||
@@ -59,7 +59,7 @@ module YoutubeAPI
|
|||||||
ClientType::WebEmbeddedPlayer => {
|
ClientType::WebEmbeddedPlayer => {
|
||||||
name: "WEB_EMBEDDED_PLAYER",
|
name: "WEB_EMBEDDED_PLAYER",
|
||||||
name_proto: "56",
|
name_proto: "56",
|
||||||
version: "1.20240812.01.00",
|
version: "1.20250219.01.00",
|
||||||
screen: "EMBED",
|
screen: "EMBED",
|
||||||
os_name: "Windows",
|
os_name: "Windows",
|
||||||
os_version: WINDOWS_VERSION,
|
os_version: WINDOWS_VERSION,
|
||||||
@@ -68,7 +68,7 @@ module YoutubeAPI
|
|||||||
ClientType::WebMobile => {
|
ClientType::WebMobile => {
|
||||||
name: "MWEB",
|
name: "MWEB",
|
||||||
name_proto: "2",
|
name_proto: "2",
|
||||||
version: "2.20240813.02.00",
|
version: "2.20250224.01.00",
|
||||||
os_name: "Android",
|
os_name: "Android",
|
||||||
os_version: ANDROID_VERSION,
|
os_version: ANDROID_VERSION,
|
||||||
platform: "MOBILE",
|
platform: "MOBILE",
|
||||||
@@ -76,7 +76,7 @@ module YoutubeAPI
|
|||||||
ClientType::WebScreenEmbed => {
|
ClientType::WebScreenEmbed => {
|
||||||
name: "WEB",
|
name: "WEB",
|
||||||
name_proto: "1",
|
name_proto: "1",
|
||||||
version: "2.20240814.00.00",
|
version: "2.20250222.10.00",
|
||||||
screen: "EMBED",
|
screen: "EMBED",
|
||||||
os_name: "Windows",
|
os_name: "Windows",
|
||||||
os_version: WINDOWS_VERSION,
|
os_version: WINDOWS_VERSION,
|
||||||
@@ -85,7 +85,7 @@ module YoutubeAPI
|
|||||||
ClientType::WebCreator => {
|
ClientType::WebCreator => {
|
||||||
name: "WEB_CREATOR",
|
name: "WEB_CREATOR",
|
||||||
name_proto: "62",
|
name_proto: "62",
|
||||||
version: "1.20240918.03.00",
|
version: "1.20241203.01.00",
|
||||||
os_name: "Windows",
|
os_name: "Windows",
|
||||||
os_version: WINDOWS_VERSION,
|
os_version: WINDOWS_VERSION,
|
||||||
platform: "DESKTOP",
|
platform: "DESKTOP",
|
||||||
@@ -171,7 +171,7 @@ module YoutubeAPI
|
|||||||
ClientType::TvHtml5 => {
|
ClientType::TvHtml5 => {
|
||||||
name: "TVHTML5",
|
name: "TVHTML5",
|
||||||
name_proto: "7",
|
name_proto: "7",
|
||||||
version: "7.20240813.07.00",
|
version: "7.20250219.14.00",
|
||||||
},
|
},
|
||||||
ClientType::TvHtml5ScreenEmbed => {
|
ClientType::TvHtml5ScreenEmbed => {
|
||||||
name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER",
|
name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER",
|
||||||
|
Reference in New Issue
Block a user