├── .husky
├── .gitignore
├── pre-commit
└── .gitattributes
├── .prettierignore
├── src
├── hooks
│ ├── index.ts
│ └── fetch.ts
├── index.html
├── player_api
│ ├── index.ts
│ ├── helpers.ts
│ ├── yt-api.ts
│ └── manager.ts
├── index.js
├── watch.css
├── block-webos-cast.ts
├── globals.ts
├── userScript.ts
├── remove-endscreen.ts
├── auto-account-select.ts
├── yt-fixes.css
├── font-fix.css
├── shorts.js
├── lang-settings-fix.ts
├── screensaver-fix.ts
├── watch.js
├── video-quality.ts
├── ui.css
├── domrect-polyfill.js
├── adblock.js
├── custom-event-target.ts
├── app_api
│ └── index.ts
├── utils.js
├── config.js
├── thumbnail-quality.ts
├── ui.js
└── sponsorblock.js
├── .gitignore
├── .github
├── FUNDING.yml
├── release.yml
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── 3-other.yml
│ ├── 2-feature-req.yml
│ └── 1-bug.yml
├── workflows
│ ├── main.yml
│ └── release.yml
└── dependabot.yml
├── assets
├── icon.png
├── largeIcon.png
├── appinfo.json
└── icon.svg
├── screenshots
├── 1_sm.jpg
└── 2_sm.jpg
├── .editorconfig
├── .vscode
├── extensions.json
└── settings.json
├── postcss.config.ts
├── tsconfig.json
├── .browserslistrc
├── lint-staged.config.js
├── .prettierrc.js
├── .gitattributes
├── tools
├── sync-version.cjs
├── deploy.js
└── gen-manifest.cjs
├── tsconfig.tooling.json
├── tsconfig.base.json
├── babel.config.js
├── webpack.config.js
├── package.json
├── eslint.config.ts
├── README.md
├── CHANGELOG.md
└── LICENSE
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx lint-staged
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | *-polyfill.*
3 |
--------------------------------------------------------------------------------
/.husky/.gitattributes:
--------------------------------------------------------------------------------
1 | pre-commit text eol=lf
2 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './fetch';
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.ipk
2 | /node_modules/
3 | /dist/
4 | .DS_Store
5 | /.vscode/
6 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [informatic, throwaway96]
2 | ko_fi: throwaway96
3 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webosbrew/youtube-webos/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/largeIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webosbrew/youtube-webos/HEAD/assets/largeIcon.png
--------------------------------------------------------------------------------
/screenshots/1_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webosbrew/youtube-webos/HEAD/screenshots/1_sm.jpg
--------------------------------------------------------------------------------
/screenshots/2_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webosbrew/youtube-webos/HEAD/screenshots/2_sm.jpg
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/player_api/index.ts:
--------------------------------------------------------------------------------
1 | export * from './yt-api';
2 | export * from './manager';
3 | export type { EventMapOf } from '../custom-event-target';
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | insert_final_newline = true
8 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { extractLaunchParams, handleLaunch } from './utils';
2 |
3 | function main() {
4 | handleLaunch(extractLaunchParams());
5 | }
6 |
7 | main();
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "editorconfig.editorconfig",
4 | "esbenp.prettier-vscode",
5 | "dbaeumer.vscode-eslint"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/postcss.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'postcss-load-config';
2 |
3 | const config: Config = {
4 | plugins: {
5 | 'postcss-preset-env': {}
6 | }
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 |
6 | "lib": ["ESNext", "DOM"]
7 | },
8 | "include": ["./src"],
9 | "references": [{ "path": "./tsconfig.tooling.json" }]
10 | }
11 |
--------------------------------------------------------------------------------
/src/watch.css:
--------------------------------------------------------------------------------
1 | .webOs-watch {
2 | position: fixed;
3 | right: 0;
4 | top: 0;
5 | margin: 1rem 2rem;
6 | background-color: rgba(0, 0, 0, 0.3);
7 | border-radius: 0.5rem;
8 | padding: 0.4rem;
9 | font-size: 1.2rem;
10 | letter-spacing: 0.05rem;
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "javascript.preferences.quoteStyle": "single",
3 | "prettier.requireConfig": true,
4 | "prettier.useEditorConfig": true,
5 | "editor.formatOnSave": true,
6 | "prettier.ignorePath": ".prettierignore",
7 | "typescript.tsdk": "node_modules\\typescript\\lib"
8 | }
9 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - dependencies
5 | categories:
6 | - title: 🎉 Enhancements
7 | labels:
8 | - enhancement
9 | - title: 🛠 Bug Fixes
10 | labels:
11 | - bug
12 | - title: Other Changes
13 | labels:
14 | - '*'
15 |
--------------------------------------------------------------------------------
/src/block-webos-cast.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Fixes webosbrew/youtube-webos/issues/343
3 | */
4 |
5 | import { FetchRegistry } from './hooks';
6 |
7 | FetchRegistry.getInstance().addEventListener('request', (evt) => {
8 | const { url, resource, init } = evt.detail;
9 | if (url.pathname === '/wake_cast_core') evt.preventDefault();
10 | });
11 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # WAM uses WebKit on webOS 1 and 2
2 | safari 7 # [WebKit 537.71] for webOS 1 [WebKit 537.41]
3 | safari 8 # [WebKit 538.35] for webOS 2 [WebKit 538.2]
4 | chrome 38 # webOS 3.x
5 | chrome 53 # webOS 4.x
6 | chrome 68 # webOS 5
7 | chrome 79 # webOS 6
8 | chrome 87 # webOS 7 (22)
9 | chrome 94 # webOS 8 (23)
10 | chrome 108 # webOS 9 (24)
11 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | function globForCode(/** @type {string} */ toolName) {
2 | return '*.?(c|m){js,ts}?(x)' + `*(${toolName})`;
3 | }
4 |
5 | /** @type {import('lint-staged').Configuration} */
6 | export default {
7 | '*': 'prettier --ignore-unknown --write',
8 | [globForCode('eslint')]: 'eslint --concurrency auto',
9 | [globForCode('tsc')]: () => 'tsc -b'
10 | };
11 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import('prettier').Config} */
2 | const config = {
3 | trailingComma: 'none',
4 | singleQuote: true,
5 | endOfLine: 'auto',
6 | overrides: [
7 | {
8 | files: ['tsconfig.json', 'jsconfig.json', 'tsconfig.*.json'],
9 | options: {
10 | parser: 'jsonc'
11 | }
12 | }
13 | ]
14 | };
15 |
16 | export default config;
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: webOS Brew Discord
4 | url: https://discord.gg/xWqRVEm
5 | about: Join our Discord community for discussions and support.
6 | - name: webOS Brew App Repository
7 | url: https://github.com/webosbrew/apps-repo
8 | about: Report issues with a webOS Brew app not related to youtube-webos.
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | *.css text
4 | *.html text
5 | *.js text
6 | *.json text
7 | *.md text
8 | *.mjs text
9 | *.svg text
10 | *.yml text
11 |
12 | .browserslistrc text
13 | .editorconfig text
14 | .eslintignore text
15 | .gitattributes text
16 | .gitignore text
17 | .prettierignore text
18 | LICENSE text
19 |
20 | *.sh text eol=lf
21 |
22 | *.jpg binary
23 | *.png binary
24 |
--------------------------------------------------------------------------------
/tools/sync-version.cjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require('fs');
4 |
5 | const packageInfo = require('../package.json');
6 | const appinfo = require('../assets/appinfo.json');
7 |
8 | fs.writeFileSync(
9 | 'assets/appinfo.json',
10 | `${JSON.stringify(
11 | {
12 | ...appinfo,
13 | version: packageInfo.version
14 | },
15 | null,
16 | 2
17 | )}\n`
18 | );
19 |
--------------------------------------------------------------------------------
/tools/deploy.js:
--------------------------------------------------------------------------------
1 | import { spawnSync } from 'node:child_process';
2 | import { normalize } from 'node:path';
3 |
4 | import pkgJson from '../package.json' with { type: 'json' };
5 |
6 | process.exit(
7 | spawnSync(
8 | normalize('./node_modules/.bin/ares-install'),
9 | [normalize(`./youtube.leanback.v4_${pkgJson.version}_all.ipk`)],
10 | { stdio: 'inherit', shell: true }
11 | ).status ?? 0
12 | );
13 |
--------------------------------------------------------------------------------
/src/globals.ts:
--------------------------------------------------------------------------------
1 | export type webOSLaunchParams = Record;
2 |
3 | declare global {
4 | interface Window {
5 | launchParams?: webOSLaunchParams;
6 | __ytaf_debug__?: boolean;
7 | }
8 |
9 | interface Document {
10 | addEventListener(
11 | eventName: 'webOSRelaunch',
12 | listener: (evt: CustomEvent) => void,
13 | useCapture?: boolean
14 | ): void;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.tooling.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {
4 | "checkJs": true,
5 |
6 | // Remove when webpack-contrib/copy-webpack-plugin/issues/793 is fixed.
7 | "skipLibCheck": true,
8 |
9 | "module": "NodeNext",
10 |
11 | "moduleResolution": "NodeNext"
12 | },
13 | "include": [
14 | "./tools",
15 | "./assets/appinfo.json",
16 | "./package.json",
17 | "./webpack.config.js",
18 | "eslint.config.ts",
19 | "babel.config.js",
20 | ".prettierrc.js",
21 | "lint-staged.config.js",
22 | "postcss.config.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 |
16 | - name: Set up Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: lts/*
20 | cache: npm
21 |
22 | - run: npm ci
23 | - run: npm run build
24 | - run: npm run package
25 |
26 | - name: Upload artifact
27 | uses: actions/upload-artifact@v4
28 | with:
29 | name: youtube_adfree_ipk
30 | path: |
31 | ${{github.workspace}}/youtube.leanback.v4_*_all.ipk
32 |
--------------------------------------------------------------------------------
/src/userScript.ts:
--------------------------------------------------------------------------------
1 | import 'whatwg-fetch';
2 | import './domrect-polyfill';
3 |
4 | import { handleLaunch } from './utils';
5 |
6 | document.addEventListener(
7 | 'webOSRelaunch',
8 | (evt) => {
9 | console.info('RELAUNCH:', evt, window.launchParams);
10 | handleLaunch(evt.detail);
11 | },
12 | true
13 | );
14 |
15 | import './app_api/index';
16 | import './adblock.js';
17 | import './shorts.js';
18 | import './sponsorblock.js';
19 | import './ui.js';
20 | import './font-fix.css';
21 | import './thumbnail-quality';
22 | import './screensaver-fix';
23 | import './yt-fixes.css';
24 | import './watch.js';
25 | import './video-quality';
26 | import './lang-settings-fix';
27 | import './remove-endscreen';
28 | import './hooks';
29 | import './block-webos-cast';
30 | import './auto-account-select';
31 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'github-actions'
4 | directory: '/'
5 | schedule:
6 | interval: 'weekly'
7 | - package-ecosystem: 'npm'
8 | directory: '/'
9 | versioning-strategy: 'increase'
10 | schedule:
11 | interval: 'weekly'
12 | groups:
13 | minor-and-patch:
14 | applies-to: version-updates
15 | update-types:
16 | - 'minor'
17 | - 'patch'
18 | security:
19 | applies-to: security-updates
20 | patterns:
21 | - '*'
22 | ignore:
23 | - dependency-name: '*'
24 | versions: ['0.x']
25 | update-types:
26 | - 'version-update:semver-minor'
27 | - dependency-name: '*'
28 | update-types:
29 | - 'version-update:semver-major'
30 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Language
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "lib": ["ESNext"],
7 | "esModuleInterop": true,
8 |
9 | // Module handling
10 | "composite": true,
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "verbatimModuleSyntax": true,
14 | "isolatedModules": true,
15 |
16 | // Linting
17 | "exactOptionalPropertyTypes": true,
18 | "noErrorTruncation": true,
19 | "strict": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedIndexedAccess": true,
22 | "noImplicitOverride": true,
23 | "noUncheckedSideEffectImports": false, // TODO: enable
24 | "moduleDetection": "force",
25 |
26 | // Emit
27 | "emitDeclarationOnly": true,
28 | "outDir": "./node_modules/.cache/tsc_build"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/remove-endscreen.ts:
--------------------------------------------------------------------------------
1 | import kindOf from 'which-builtin-type';
2 |
3 | import { configRead } from './config';
4 |
5 | function isObject(value: unknown): value is object {
6 | // @ts-expect-error - bad types
7 | return kindOf(value) === 'Object';
8 | }
9 |
10 | type JSONReviver = Parameters[1];
11 |
12 | const originalParse = JSON.parse;
13 |
14 | function jsonParse(str: string, reviver?: JSONReviver) {
15 | const res = originalParse(str, reviver) as unknown;
16 |
17 | if (!configRead('removeEndscreen')) {
18 | return res;
19 | }
20 |
21 | if (
22 | isObject(res) &&
23 | 'endscreen' in res &&
24 | isObject(res.endscreen) &&
25 | 'endscreenRenderer' in res.endscreen
26 | ) {
27 | console.debug('Removed endscreen from JSON response', res.endscreen);
28 | delete res.endscreen;
29 | }
30 |
31 | return res;
32 | }
33 |
34 | JSON.parse = jsonParse;
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3-other.yml:
--------------------------------------------------------------------------------
1 | name: Other Issue
2 | description: My issue does not fit into any of the other categories.
3 | labels: ['needs triage']
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | **WARNING: Please read this carefully to avoid having your issue closed without a response.**
9 |
10 | This form is for issues with youtube-webos (YTAF) that do not fit into any of the other categories.
11 |
12 | If your issue is a bug or feature request, please use the appropriate form or your issue may be closed, and you will be asked to open a new issue with the correct form.
13 | - type: textarea
14 | id: other_description
15 | attributes:
16 | label: Description
17 | description: Describe your issue in detail.
18 | placeholder: I have a problem and it is not a bug or feature request...
19 | validations:
20 | required: true
21 |
--------------------------------------------------------------------------------
/tools/gen-manifest.cjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const crypto = require('crypto');
4 | const fs = require('fs');
5 |
6 | const outfile = process.argv[2];
7 | if (!outfile) throw new Error('Usage: gen-manifest ');
8 |
9 | const appinfo = require('../assets/appinfo.json');
10 | const ipkfile = `${appinfo.id}_${appinfo.version}_all.ipk`;
11 | const ipkhash = crypto
12 | .createHash('sha256')
13 | .update(fs.readFileSync(ipkfile))
14 | .digest('hex');
15 |
16 | fs.writeFileSync(
17 | outfile,
18 | JSON.stringify({
19 | id: appinfo.id,
20 | version: appinfo.version,
21 | type: appinfo.type,
22 | title: appinfo.title,
23 | // appDescription: appinfo.appDescription,
24 | iconUri:
25 | 'https://raw.githubusercontent.com/webosbrew/youtube-webos/main/assets/largeIcon.png',
26 | sourceUrl: 'https://github.com/webosbrew/youtube-webos',
27 | rootRequired: false,
28 | ipkUrl: ipkfile,
29 | ipkHash: {
30 | sha256: ipkhash
31 | }
32 | })
33 | );
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build & Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - '!*'
7 | tags:
8 | - 'v*.*'
9 |
10 | jobs:
11 | build-and-release:
12 | runs-on: ubuntu-latest
13 | permissions:
14 | contents: write
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 |
19 | - name: Set up Node.js
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: lts/*
23 | cache: npm
24 |
25 | - run: npm ci
26 | - run: npm run build
27 | - run: npm run package
28 | - run: npm run manifest
29 |
30 | - name: Create release
31 | uses: ncipollo/release-action@v1
32 | with:
33 | name: 'Release ${{ github.ref }}'
34 | prerelease: true
35 | draft: false
36 | allowUpdates: true
37 | omitNameDuringUpdate: true
38 | omitBodyDuringUpdate: true
39 | omitDraftDuringUpdate: true
40 | omitPrereleaseDuringUpdate: true
41 | artifacts: '*.ipk,*.manifest.json'
42 |
--------------------------------------------------------------------------------
/src/auto-account-select.ts:
--------------------------------------------------------------------------------
1 | import { configRead } from './config';
2 |
3 | import { ResolveCommandRegistry, type ResolveCommandHook } from './app_api';
4 |
5 | const registry = await ResolveCommandRegistry.getInstance();
6 |
7 | const hook: ResolveCommandHook = function (resolveCommand, payload, extra) {
8 | if (!configRead('autoAccountSelect')) {
9 | resolveCommand(payload, extra);
10 | return;
11 | }
12 |
13 | const finalEndpoint = payload?.startAccountSelectorCommand // @ts-expect-error TS doesn't allow optional chaining on `unknown`. See: github.com/microsoft/TypeScript/issues/37700
14 | ?.nextEndpoint as unknown;
15 |
16 | registry.dispatchCommand({
17 | onIdentityChanged: {
18 | identityActionContext: {
19 | nextEndpoint: finalEndpoint,
20 | eventTrigger: 'ACCOUNT_EVENT_TRIGGER_WHOS_WATCHING',
21 | reloadRequired: undefined
22 | },
23 | isSameIdentity: true
24 | },
25 | commandMetadata: { webCommandMetadata: { clientAction: true } }
26 | });
27 | };
28 |
29 | registry.setHook('startAccountSelectorCommand', hook);
30 |
--------------------------------------------------------------------------------
/src/yt-fixes.css:
--------------------------------------------------------------------------------
1 | /* Fixes transparency effect for the video player */
2 | .ytLrWatchDefaultShadow {
3 | display: block !important;
4 | height: 100% !important;
5 | pointer-events: none !important;
6 | position: absolute !important;
7 | width: 100% !important;
8 | }
9 |
10 | .ytLrWatchDefaultShadow,
11 | [idomkey='shadow'] {
12 | background-image: linear-gradient(
13 | to bottom,
14 | rgba(0, 0, 0, 0) 0,
15 | rgba(0, 0, 0, 0.8) 90%
16 | ) !important;
17 | background-color: rgba(0, 0, 0, 0.3) !important;
18 | }
19 |
20 | .ytLrWatchDefault2025Shadow {
21 | background-color: rgba(11, 11, 11, 0.5) !important;
22 | }
23 |
24 | /* Fixes shorts thumbnails */
25 | .ytLrTileHeaderRendererShorts {
26 | z-index: -1;
27 | }
28 |
29 | /**
30 | * Dirty hack to fix offset