├── .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