Full Code of ajayyy/SponsorBlock for AI

master 7f881e808c76 cached
141 files
883.0 KB
204.1k tokens
728 symbols
1 requests
Download .txt
Showing preview only (927K chars total). Download the full file or copy to clipboard to get everything.
Repository: ajayyy/SponsorBlock
Branch: master
Commit: 7f881e808c76
Files: 141
Total size: 883.0 KB

Directory structure:
gitextract_jhhxv7ay/

├── .editorconfig
├── .eslintrc.json
├── .github/
│   ├── FUNDING.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── ci.yml
│       ├── release.yml
│       ├── take-action.yml
│       ├── tests.yml
│       ├── update-oss-attribution.yml
│       └── updateInvidous.yml
├── .gitignore
├── .gitmodules
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE-APPSTORE.txt
├── LICENSE-HISTORY.txt
├── README.md
├── ci/
│   ├── generateList.ts
│   ├── invidiousCI.ts
│   ├── invidiousType.ts
│   ├── invidiouslist.json
│   ├── pipedCI.ts
│   └── prettify.ts
├── config.json.example
├── crowdin.yml
├── jest.config.js
├── manifest/
│   ├── beta-manifest-extra.json
│   ├── chrome-manifest-extra.json
│   ├── firefox-beta-manifest-extra.json
│   ├── firefox-manifest-extra.json
│   ├── manifest-v2-extra.json
│   ├── manifest.json
│   └── safari-manifest-extra.json
├── oss-attribution/
│   └── licenseInfos.json
├── package.json
├── public/
│   ├── content.css
│   ├── help/
│   │   ├── index.html
│   │   └── styles.css
│   ├── icons/
│   │   └── beep.oga
│   ├── libs/
│   │   └── Source+Sans+Pro.css
│   ├── options/
│   │   ├── options.css
│   │   └── options.html
│   ├── oss-attribution/
│   │   └── attribution.txt
│   ├── permissions/
│   │   ├── index.html
│   │   └── styles.css
│   ├── popup.css
│   ├── popup.html
│   ├── res/
│   │   └── countries.json
│   └── shared.css
├── src/
│   ├── background.ts
│   ├── components/
│   │   ├── CategoryPillComponent.tsx
│   │   ├── ChapterVoteComponent.tsx
│   │   ├── NoticeComponent.tsx
│   │   ├── NoticeTextSectionComponent.tsx
│   │   ├── SelectorComponent.tsx
│   │   ├── SkipNoticeComponent.tsx
│   │   ├── SponsorTimeEditComponent.tsx
│   │   ├── SubmissionNoticeComponent.tsx
│   │   └── options/
│   │       ├── AdvancedSkipOptionsComponent.tsx
│   │       ├── CategoryChooserComponent.tsx
│   │       ├── CategorySkipOptionsComponent.tsx
│   │       ├── KeybindComponent.tsx
│   │       ├── KeybindDialogComponent.tsx
│   │       ├── NumberInputOptionComponent.tsx
│   │       ├── SelectOptionComponent.tsx
│   │       ├── ToggleOptionComponent.tsx
│   │       ├── UnsubmittedVideoListComponent.tsx
│   │       ├── UnsubmittedVideoListItem.tsx
│   │       └── UnsubmittedVideosComponent.tsx
│   ├── config.ts
│   ├── content.ts
│   ├── dearrowPromotion.ts
│   ├── document.ts
│   ├── globals.d.ts
│   ├── help.ts
│   ├── js-components/
│   │   ├── previewBar.ts
│   │   └── skipButtonControlBar.ts
│   ├── messageTypes.ts
│   ├── options.ts
│   ├── permissions.ts
│   ├── popup/
│   │   ├── PopupComponent.tsx
│   │   ├── SegmentListComponent.tsx
│   │   ├── SegmentSubmissionComponent.tsx
│   │   ├── YourWorkComponent.tsx
│   │   ├── popup.tsx
│   │   └── popupUtils.ts
│   ├── render/
│   │   ├── AdvancedSkipOptions.tsx
│   │   ├── CategoryChooser.tsx
│   │   ├── CategoryPill.tsx
│   │   ├── ChapterVote.tsx
│   │   ├── GenericNotice.tsx
│   │   ├── RectangleTooltip.tsx
│   │   ├── SkipNotice.tsx
│   │   ├── SubmissionNotice.tsx
│   │   ├── Tooltip.tsx
│   │   ├── UnsubmittedVideos.tsx
│   │   └── UpcomingNotice.tsx
│   ├── svg-icons/
│   │   ├── checkIcon.tsx
│   │   ├── clipboardIcon.tsx
│   │   ├── lock_svg.tsx
│   │   ├── pencilIcon.tsx
│   │   ├── pencil_svg.tsx
│   │   ├── resetIcon.tsx
│   │   ├── sb_svg.tsx
│   │   ├── thumbs_down_svg.tsx
│   │   └── thumbs_up_svg.tsx
│   ├── types.ts
│   ├── utils/
│   │   ├── arrayUtils.ts
│   │   ├── categoryUtils.ts
│   │   ├── compatibility.ts
│   │   ├── configUtils.ts
│   │   ├── constants.ts
│   │   ├── crossExtension.ts
│   │   ├── exporter.ts
│   │   ├── genericUtils.ts
│   │   ├── logger.ts
│   │   ├── mobileUtils.ts
│   │   ├── noticeUtils.ts
│   │   ├── pageCleaner.ts
│   │   ├── pageUtils.ts
│   │   ├── requests.ts
│   │   ├── segmentData.ts
│   │   ├── skipProfiles.ts
│   │   ├── skipRule.ts
│   │   ├── skipRule.type.ts
│   │   ├── thumbnails.ts
│   │   ├── urlParser.ts
│   │   ├── videoLabels.ts
│   │   └── warnings.ts
│   └── utils.ts
├── test/
│   ├── exporter.test.ts
│   ├── previewBar.test.ts
│   ├── selenium.test.ts
│   └── urlParser.test.ts
├── tsconfig-production.json
├── tsconfig.json
└── webpack/
    ├── configDiffPlugin.js
    ├── webpack.common.js
    ├── webpack.dev.js
    ├── webpack.manifest.js
    └── webpack.prod.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true

[*.{js,json,ts,tsx}]
charset = utf-8
indent_style = space
indent_size = 4

[package.json]
indent_style = space
indent_size = 2


================================================
FILE: .eslintrc.json
================================================
{
    "env": {
        "browser": true,
        "es2021": true,
        "node": true,
        "jest": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "plugins": ["react", "@typescript-eslint"],
    "rules": {
        "@typescript-eslint/no-unused-vars": "error",
        "no-self-assign": "off",
        "@typescript-eslint/no-empty-interface": "off",
        "react/prop-types": [2, { "ignore": ["children"] }],
        "@typescript-eslint/member-delimiter-style": "warn",
        "@typescript-eslint/no-non-null-assertion": "off",
        "@typescript-eslint/ban-ts-comment": "off",
        "@typescript-eslint/no-this-alias": "off"
    },
    "settings": {
        "react": {
            "version": "detect"
        }
    }
}


================================================
FILE: .github/FUNDING.yml
================================================
github: ajayyy-org
patreon: ajayyy
custom: [sponsor.ajay.app/donate]


================================================
FILE: .github/pull_request_template.md
================================================
- [ ] I agree to license my contribution under GPL-3.0 and agree to allow distribution on app stores as outlined in [LICENSE-APPSTORE](https://github.com/ajayyy/SponsorBlock/blob/master/LICENSE-APPSTORE.txt)

To test this pull request, follow the [instructions in the wiki](https://github.com/ajayyy/SponsorBlock/wiki/Testing-a-Pull-Request).

***


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on: [push, pull_request]

jobs:

  build:
    name: Create artifacts
    runs-on: ubuntu-latest

    steps:
      # Initialization
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - name: Copy configuration
        run: cp config.json.example config.json

      # Run linter
      - name: Lint
        run: npm run lint

      # Create Chrome artifacts
      - name: Create Chrome artifacts
        run: npm run build:chrome
      - uses: actions/upload-artifact@v4
        with:
          name: ChromeExtension
          path: dist
      - run: mkdir ./builds
      - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
        with:
          args: zip -qq -r ./builds/ChromeExtension.zip ./dist

      # Create Firefox artifacts
      - name: Create Firefox artifacts
        run: npm run build:firefox
      - uses: actions/upload-artifact@v4
        with:
          name: FirefoxExtension
          path: dist
      - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
        with:
          args: zip -qq -r ./builds/FirefoxExtension.zip ./dist

      # Create Beta artifacts (Builds with the name changed to beta)
      - name: Create Chrome Beta artifacts
        run: npm run build:chrome -- --env stream=beta
      - uses: actions/upload-artifact@v4
        with:
          name: ChromeExtensionBeta
          path: dist
      - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
        with:
          args: zip -qq -r ./builds/ChromeExtensionBeta.zip ./dist

      - name: Create Firefox Beta artifacts
        run: npm run build:firefox -- --env stream=beta
      - uses: actions/upload-artifact@v4
        with:
          name: FirefoxExtensionBeta
          path: dist
      - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
        with:
          args: zip -qq -r ./builds/FirefoxExtensionBeta.zip ./dist



================================================
FILE: .github/workflows/release.yml
================================================
name: Upload Release Build

on: 
  release:
    types: [published]

jobs:

  build:
    name: Upload Release
    runs-on: ubuntu-latest

    steps:
      # Initialization
      - name: Checkout release branch w/submodules
        uses: actions/checkout@v5
        with:
          submodules: recursive
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - name: Copy configuration
        run: cp config.json.example config.json

      # Create source artifact with submodule
      - name: Create directory
        run: cd ..; mkdir ./builds
      - name: Zip Source code
        run: zip -r ../builds/SourceCodeUseThisOne.zip *
      - name: Upload Source to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          args: ../builds/SourceCodeUseThisOne.zip
          name: SourceCodeUseThisOne.zip
          path: ../builds/SourceCodeUseThisOne.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Checkout source maps branch
        uses: actions/checkout@v5
        with:
          path: source-maps
          ref: source-maps
      - name: Set up committer info
        run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
      - run: npm ci

      # Create Firefox artifacts
      - name: Create Firefox artifacts
        run: npm run build:firefox -- --env ghpSourceMaps
      - run: mkdir ./builds
      - name: Move Firefox source maps to source map repo
        run: |
          VERSION=`jq -r '.version' ./dist/manifest.json`
          mkdir -p "./source-maps/firefox/$VERSION/"
          mv -v ./dist/**/*.js.map "./source-maps/firefox/$VERSION/"
          cp -v ./dist/**/*.js.LICENSE.txt "./source-maps/firefox/$VERSION/"
      - name: Zip Artifacts
        run: cd ./dist ; zip -r ../builds/FirefoxExtension.zip *
      - name: Upload FirefoxExtension to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          args: builds/FirefoxExtension.zip
          name: FirefoxExtension.zip
          path: ./builds/FirefoxExtension.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}
      
      # Create Chrome artifacts
      - name: Create Chrome artifacts
        run: npm run build:chrome -- --env ghpSourceMaps
      - name: Move Chrome source maps to source map repo
        run: |
          VERSION=`jq -r '.version' ./dist/manifest.json`
          mkdir -p "./source-maps/chrome/$VERSION/"
          mv -v ./dist/**/*.js.map "./source-maps/chrome/$VERSION/"
          cp -v ./dist/**/*.js.LICENSE.txt "./source-maps/chrome/$VERSION/"
      - name: Zip Artifacts
        run: cd ./dist ; zip -r ../builds/ChromeExtension.zip *
      - name: Upload ChromeExtension to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          args: builds/ChromeExtension.zip
          name: ChromeExtension.zip
          path: ./builds/ChromeExtension.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      # Create Edge artifacts
      - name: Clear dist for Edge
        run: rm -rf ./dist
      - name: Create Edge artifacts
        run: npm run build:edge -- --env ghpSourceMaps
      - name: Move Edge source maps to source map repo
        run: |
          VERSION=`jq -r '.version' ./dist/manifest.json`
          mkdir -p "./source-maps/edge/$VERSION/"
          mv -v ./dist/**/*.js.map "./source-maps/edge/$VERSION/"
          cp -v ./dist/**/*.js.LICENSE.txt "./source-maps/edge/$VERSION/"
      - name: Zip Artifacts
        run: cd ./dist ; zip -r ../builds/EdgeExtension.zip *
      - name: Upload EdgeExtension to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          args: builds/EdgeExtension.zip
          name: EdgeExtension.zip
          path: ./builds/EdgeExtension.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      # Create Safari artifacts
      - name: Create Safari artifacts
        run: npm run build:safari -- --env ghpSourceMaps
      - name: Move Safari source maps to source map repo
        run: |
          VERSION=`jq -r '.version' ./dist/manifest.json`
          mkdir -p "./source-maps/safari/$VERSION/"
          mv -v ./dist/**/*.js.map "./source-maps/safari/$VERSION/"
          cp -v ./dist/**/*.js.LICENSE.txt "./source-maps/safari/$VERSION/"
      - name: Zip Artifacts
        run: cd ./dist ; zip -r ../builds/SafariExtension.zip *
      - name: Upload SafariExtension to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          args: builds/SafariExtension.zip
          name: SafariExtension.zip
          path: ./builds/SafariExtension.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      # Create Beta artifacts (Builds with the name changed to beta)
      - name: Create Chrome Beta artifacts
        run: npm run build:chrome -- --env stream=beta --env ghpSourceMaps
      - name: Move Chrome Beta source maps to source map repo
        run: |
          VERSION=`jq -r '.version' ./dist/manifest.json`
          mkdir -p "./source-maps/chrome-beta/$VERSION/"
          mv -v ./dist/**/*.js.map "./source-maps/chrome-beta/$VERSION/"
          cp -v ./dist/**/*.js.LICENSE.txt "./source-maps/chrome-beta/$VERSION/"
      - name: Zip Artifacts
        run: cd ./dist ; zip -r ../builds/ChromeExtensionBeta.zip *
      - name: Upload ChromeExtensionBeta to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          args: builds/ChromeExtensionBeta.zip
          name: ChromeExtensionBeta.zip
          path: ./builds/ChromeExtensionBeta.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      # Firefox Beta
      - name: Create Firefox Beta artifacts
        run: npm run build:firefox -- --env stream=beta --env autoupdate --env ghpSourceMaps
      - uses: actions/upload-artifact@v4
        with:
          name: FirefoxExtensionBeta
          path: dist
      - name: Move Firefox Beta source maps to source map repo
        run: |
          VERSION=`jq -r '.version' ./dist/manifest.json`
          mkdir -p "./source-maps/firefox-beta/$VERSION/"
          mv -v ./dist/**/*.js.map "./source-maps/firefox-beta/$VERSION/"
          cp -v ./dist/**/*.js.LICENSE.txt "./source-maps/firefox-beta/$VERSION/"
      - name: Zip Artifacts
        run: cd ./dist ; zip -r ../builds/FirefoxExtensionBeta.zip *

      # Create Firefox Signed Beta version
      - name: Create Firefox Signed Beta artifacts
        run: npm run web-sign
        env:
          WEB_EXT_API_KEY: ${{ secrets.WEB_EXT_API_KEY }}
          WEB_EXT_API_SECRET: ${{ secrets.WEB_EXT_API_SECRET }}
      - name: Rename signed file
        run: mv ./web-ext-artifacts/* ./web-ext-artifacts/FirefoxSignedInstaller.xpi
      - uses: actions/upload-artifact@v4
        with:
          name: FirefoxExtensionSigned.xpi
          path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi

      - name: Upload FirefoxSignedInstaller.xpi to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          args: web-ext-artifacts/FirefoxSignedInstaller.xpi
          name: FirefoxSignedInstaller.xpi
          path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Commit & push new source maps
        if: always()
        run: |
          VERSION=`jq -r '.version' ./dist/manifest.json`
          cd ./source-maps
          git add .
          git commit -m "Publish source maps for version $VERSION"
          git push

      - name: Prepare new github pages deployment
        shell: python
        run: |
          from pathlib import Path
          import json
          import shutil
          import os

          # config stuff
          installer_name = "FirefoxSignedInstaller.xpi"
          owner, repo_name = os.environ["GITHUB_REPOSITORY"].split("/")
          owner = owner.lower()

          # create the github paged dir
          ghp_dir = Path("./github-pages")
          ghp_dir.mkdir(parents=True, exist_ok=True)

          # move in the installer
          Path("./web-ext-artifacts", installer_name).rename(ghp_dir / installer_name)

          # read manifest.json and extract parameters
          with open("./dist/manifest.json") as f:
            manifest = json.load(f)
          current_version = manifest["version"]
          ext_id = manifest["browser_specific_settings"]["gecko"]["id"]

          # generate updates file
          updates = {
            "addons": {
              ext_id: {
                "updates": [
                  {
                    "version": current_version,
                    # param doesn't actually matter, it's just a cachebuster
                    "update_link": f"https://{owner}.github.io/{repo_name}/{installer_name}?v={current_version}",
                  },
                ],
              },
            },
          }
          (ghp_dir / "updates.json").write_text(json.dumps(updates))

          # copy in source maps
          def only_sourcemaps(cur, ls):
            if '/' in cur:
              return []
            return set(ls) - {"chrome", "chrome-beta", "edge", "firefox", "firefox-beta", "safari"}
          shutil.copytree("source-maps", ghp_dir, ignore=only_sourcemaps, dirs_exist_ok=True)

      - name: Upload new github pages deployment
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./github-pages

  deploy-ghp:
    name: Deploy to github pages
    needs: build
    permissions:
      id-token: write
      pages: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        id: deployment
        uses: actions/deploy-pages@v4


================================================
FILE: .github/workflows/take-action.yml
================================================
# .github/workflows/take.yml 
name: Assign issue to contributor
on: 
  issue_comment:

jobs:
  assign:
    name: Take an issue
    runs-on: ubuntu-latest
    steps:
    - name: take the issue
      uses: bdougie/take-action@28b86cd8d25593f037406ecbf96082db2836e928
      env:
        GITHUB_TOKEN: ${{ github.token }}


================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests

on: [push, pull_request]

jobs:
  test:
    name: Run tests
    runs-on: ubuntu-latest

    steps:
      # Initialization
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      
      - name: Copy configuration
        run: cp config.json.example config.json

      - name: Set up WireGuard Connection
        uses: niklaskeerl/easy-wireguard-action@50341d5f4b8245ff3a90e278aca67b2d283c78d0
        with:
          WG_CONFIG_FILE: ${{ secrets.WG_CONFIG_FILE }}

      - name: Run tests
        run: npm run test

      - name: Upload results on fail
        if: ${{ failure() }}
        uses: actions/upload-artifact@v4
        with:
          name: Test Results
          path: ./test-results

================================================
FILE: .github/workflows/update-oss-attribution.yml
================================================
name: update oss attributions
on:
  push:
    branches:
      - master
    paths:
      - 'package.json'
      - 'package-lock.json'
  workflow_dispatch:

jobs:
  update-oss:
    runs-on: ubuntu-latest 
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - name: Install and generate attribution
        run: |
          npm ci
          npm i -g oss-attribution-generator
          generate-attribution
          mv ./oss-attribution/attribution.txt ./public/oss-attribution/attribution.txt
      - name: Prettify attributions
        run: |
          cd ci && npx ts-node prettify.ts

      - name: Create pull request to update list
        uses: peter-evans/create-pull-request@v7
        # v4.2.3
        with:
          commit-message: Update OSS Attribution
          author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
          branch: ci/oss_attribution
          title: Update OSS Attribution
          body: Automated OSS Attribution update


================================================
FILE: .github/workflows/updateInvidous.yml
================================================
name: update invidious
on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 1 * *' # check every month

jobs:
  check-list:
    runs-on: ubuntu-latest 
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Download instance lists
        run: |
          wget https://api.invidious.io/instances.json -O ci/invidious_instances.json
          wget  https://github.com/TeamPiped/piped-uptime/raw/master/history/summary.json -O ci/piped_instances.json
      - name: Install dependencies
        run: npm ci
      - name: "Run CI"
        run: npm run ci:invidious

      - name: Create pull request to update list
        uses: peter-evans/create-pull-request@v7
        # v4.2.3
        with:
          commit-message: Update Invidious List
          author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
          branch: ci/update_invidious_list
          title: Update Invidious List
          body: Automated Invidious list update

================================================
FILE: .gitignore
================================================
config.json
ignored
.idea/
node_modules
web-ext-artifacts
.vscode/
dist/
tmp/
.DS_Store
ci/invidious_instances.json
ci/piped_instances.json
test-results

================================================
FILE: .gitmodules
================================================
[submodule "public/_locales"]
	path = public/_locales
	url = https://github.com/ajayyy/ExtensionTranslations
[submodule "maze-utils"]
	path = maze-utils
	url = https://github.com/ajayyy/maze-utils


================================================
FILE: CONTRIBUTING.md
================================================
If you make any contributions to SponsorBlock after this file was created, you are agreeing that any code you have contributed will be licensed under GPL-3.0 and agree to allow distribution on app stores as outlined in LICENSE-APPSTORE.

# Translations
https://crowdin.com/project/sponsorblock

# Building
## Building locally
0. You must have [Node.js 22 or later](https://nodejs.org/) and npm installed. Works best on Linux
1. Clone with submodules
  ```bash
  git clone --recursive https://github.com/ajayyy/SponsorBlock
  ```
  Or if you already cloned it, pull submodules with
  ```bash
  git submodule update --init --recursive
  ```
2. Copy the file `config.json.example` to `config.json` and adjust configuration as desired.
  - Comments are invalid in JSON, make sure they are all removed.
  - You will need to repeat this step in the future if you get build errors related to `CompileConfig` or `property does not exist on type ConfigClass`. This can happen for example when a new category is added.
3. Run `npm ci` in the repository to install dependencies.
4. Run `npm run build:dev` (for Chrome) or `npm run build:dev:firefox` (for Firefox) to generate a development version of the extension with source maps.
    - You can also run `npm run build` (for Chrome) or `npm run build:firefox` (for Firefox) to generate a production build.
5. The built extension is now in `dist/`. You can load this folder directly in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/#manifest), or convert it to a zip file to load it as a [temporary extension](https://developer.mozilla.org/docs/Tools/about:debugging#loading_a_temporary_extension) in Firefox.

## Developing with a clean profile and hot reloading
Run `npm run dev` (for Chrome) or `npm run dev:firefox` (for Firefox) to run the extension using a clean browser profile with hot reloading. This uses [`web-ext run`](https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#commands).

Known chromium bug: Extension is not loaded properly on first start. Visit `chrome://extensions/` and reload the extension.

For Firefox for Android, use `npm run dev:firefox-android -- --adb-device <ip-address of the device>`. See the [Firefox documentation](https://extensionworkshop.com/documentation/develop/developing-extensions-for-firefox-for-android/#debug-your-extension) for more information. You may need to edit package.json and add the parameters directly there.



================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.


================================================
FILE: LICENSE-APPSTORE.txt
================================================
The developers are aware that the terms of service that
apply to apps distributed via Apple's App Store services and similar app stores may conflict
with rights granted under the SponsorBlock license, the GNU General
Public License, version 3. The copyright holders of the SponsorBlock 
project do not wish this conflict to prevent the otherwise-compliant 
distribution of derived apps via the App Store and similar app stores. 
Therefore, we have committed not to pursue any license
violation that results solely from the conflict between the GNU GPLv3
and the Apple App Store terms of service or similar app stores. In
other words, as long as you comply with the GPL in all other respects,
including its requirements to provide users with source code and the
text of the license, we will not object to your distribution of the
SponsorBlock project through the App Store.

================================================
FILE: LICENSE-HISTORY.txt
================================================
Prior to commit 7338af3b384e2297eaf710443121ac840099a9f1, this project was licensed under LGPL 3.0.

You must follow LICENSE instead if you want to use any newer version.

----

                   GNU LESSER GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.


  This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.

  0. Additional Definitions.

  As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.

  "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.

  An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.

  A "Combined Work" is a work produced by combining or linking an
Application with the Library.  The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".

  The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.

  The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.

  1. Exception to Section 3 of the GNU GPL.

  You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.

  2. Conveying Modified Versions.

  If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:

   a) under this License, provided that you make a good faith effort to
   ensure that, in the event an Application does not supply the
   function or data, the facility still operates, and performs
   whatever part of its purpose remains meaningful, or

   b) under the GNU GPL, with none of the additional permissions of
   this License applicable to that copy.

  3. Object Code Incorporating Material from Library Header Files.

  The object code form of an Application may incorporate material from
a header file that is part of the Library.  You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:

   a) Give prominent notice with each copy of the object code that the
   Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the object code with a copy of the GNU GPL and this license
   document.

  4. Combined Works.

  You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:

   a) Give prominent notice with each copy of the Combined Work that
   the Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the Combined Work with a copy of the GNU GPL and this license
   document.

   c) For a Combined Work that displays copyright notices during
   execution, include the copyright notice for the Library among
   these notices, as well as a reference directing the user to the
   copies of the GNU GPL and this license document.

   d) Do one of the following:

       0) Convey the Minimal Corresponding Source under the terms of this
       License, and the Corresponding Application Code in a form
       suitable for, and under terms that permit, the user to
       recombine or relink the Application with a modified version of
       the Linked Version to produce a modified Combined Work, in the
       manner specified by section 6 of the GNU GPL for conveying
       Corresponding Source.

       1) Use a suitable shared library mechanism for linking with the
       Library.  A suitable mechanism is one that (a) uses at run time
       a copy of the Library already present on the user's computer
       system, and (b) will operate properly with a modified version
       of the Library that is interface-compatible with the Linked
       Version.

   e) Provide Installation Information, but only if you would otherwise
   be required to provide such information under section 6 of the
   GNU GPL, and only to the extent that such information is
   necessary to install and execute a modified version of the
   Combined Work produced by recombining or relinking the
   Application with a modified version of the Linked Version. (If
   you use option 4d0, the Installation Information must accompany
   the Minimal Corresponding Source and Corresponding Application
   Code. If you use option 4d1, you must provide the Installation
   Information in the manner specified by section 6 of the GNU GPL
   for conveying Corresponding Source.)

  5. Combined Libraries.

  You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:

   a) Accompany the combined library with a copy of the same work based
   on the Library, uncombined with any other library facilities,
   conveyed under the terms of this License.

   b) Give prominent notice with the combined library that part of it
   is a work based on the Library, and explaining where to find the
   accompanying uncombined form of the same work.

  6. Revised Versions of the GNU Lesser General Public License.

  The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.

  Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.

  If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.


================================================
FILE: README.md
================================================
<p align="center">
  <a href="https://sponsor.ajay.app"><img src="public/icons/LogoSponsorBlocker256px.png" alt="Logo"></img></a>

  <br/>
  <sub>Logo by <a href="https://github.com/munadikieh">@munadikieh</a></sub>
</p>

<h1 align="center">SponsorBlock</h1>

<p align="center">
  <b>Download:</b>
  <a href="https://chrome.google.com/webstore/detail/mnjggcdmjocbbbhaepdhchncahnbgone">Chrome/Chromium</a> |
  <a href="https://addons.mozilla.org/addon/sponsorblock/?src=external-github">Firefox</a> |
  <a href="https://github.com/ajayyy/SponsorBlock/wiki/Android">Android</a> |
  <a href="https://github.com/ajayyy/SponsorBlock/wiki/Edge">Edge</a> |
  <a href="https://github.com/ajayyy/SponsorBlock/wiki/Safari">Safari for MacOS and iOS</a> |
  <a href="https://sponsor.ajay.app">Website</a> |
  <a href="https://sponsor.ajay.app/stats">Stats</a>
</p>

<p align="center">
  <b>3rd-Party Ports:</b>
  <a href="https://github.com/ajayyy/SponsorBlock/wiki/3rd-Party-Ports#mpv-media-player">MPV</a> |
  <a href="https://github.com/ajayyy/SponsorBlock/wiki/3rd-Party-Ports#kodi">Kodi</a> |
  <a href="https://github.com/ajayyy/SponsorBlock/wiki/3rd-Party-Ports#Chromecast">Chromecast</a> |
  <a href="https://github.com/ajayyy/SponsorBlock/wiki/3rd-Party-Ports#ios">iOS</a>
</p>

<p align="center">
    <a href="https://addons.mozilla.org/addon/sponsorblock/?src=external-github"><img src="https://img.shields.io/amo/users/sponsorblock?label=Firefox%20Users" alt="Badge"></img></a>
    <a href="https://chrome.google.com/webstore/detail/mnjggcdmjocbbbhaepdhchncahnbgone"><img src="https://img.shields.io/chrome-web-store/users/mnjggcdmjocbbbhaepdhchncahnbgone?label=Chrome%20Users" alt="Badge"></img></a>
    <a href="https://sponsor.ajay.app/stats"><img src="https://img.shields.io/badge/dynamic/json?label=Submissions&query=totalSubmissions&suffix=%20segments&url=http%3A%2F%2Fsponsor.ajay.app%2Fapi%2FgetTotalStats&color=darkred" alt="Badge"></img></a>
    <a href="https://sponsor.ajay.app/stats"><img src="https://img.shields.io/badge/dynamic/json?label=Active%20Users&query=apiUsers&url=http%3A%2F%2Fsponsor.ajay.app%2Fapi%2FgetTotalStats&color=darkblue" alt="Badge"></img></a>
    <a href="https://sponsor.ajay.app/stats"><img src="https://img.shields.io/badge/dynamic/json?label=Time%20Saved%20From%20Skips&query=daysSaved&url=http%3A%2F%2Fsponsor.ajay.app%2Fapi%2FgetDaysSavedFormatted&color=darkgreen&suffix=%20days" alt="Badge"></img></a>
</p>



SponsorBlock is an open-source crowdsourced browser extension to skip sponsor segments in YouTube videos. Users submit when a sponsor happens from the extension, and the extension automatically skips sponsors it knows about. It also supports skipping other categories, such as intros, outros and reminders to subscribe.

It also supports Invidious.

**Translate:** [![Crowdin](https://badges.crowdin.net/sponsorblock/localized.svg)](https://crowdin.com/project/sponsorblock)

# Important Links

See the [Wiki](https://github.com/ajayyy/SponsorBlock/wiki) for important links.

# Server

The backend server code is available here: https://github.com/ajayyy/SponsorBlockServer

To make sure that this project doesn't die, I have made the database publicly downloadable at https://sponsor.ajay.app/database ([License](https://github.com/ajayyy/SponsorBlock/wiki/Database-and-API-License)). If you are planning on using the database in another project, please read the [API Docs](https://wiki.sponsor.ajay.app/index.php/API_Docs) page for more information.

The dataset and API are now being used in some [ports](https://github.com/ajayyy/SponsorBlock/wiki/3rd-Party-Ports) as well as a [neural network](https://github.com/andrewzlee/NeuralBlock).

# API

You can read the API docs [here](https://wiki.sponsor.ajay.app/w/API_Docs).

# Building
See [CONTRIBUTING.md](CONTRIBUTING.md)

# Credit

The awesome [Invidious API](https://docs.invidious.io/) was previously used, and the server is now using [NewLeaf](https://git.sr.ht/~cadence/NewLeaf) to get video info from YouTube.

Originally forked from [YTSponsorSkip](https://github.com/NDevTK/YTSponsorSkip), but very little code remains.

Icons made by:
* <a href="https://www.flaticon.com/authors/gregor-cresnar" title="Gregor Cresnar">Gregor Cresnar</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> and are licensed by <a href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a>
* <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> and are licensed by <a href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a>
* <a href="https://iconmonstr.com/about/#creator">Alexander Kahlkopf</a> from <a href="https://iconmonstr.com/">iconmonstr.com</a> and are licensed by <a href="https://iconmonstr.com/license/">iconmonstr License</a>


### License

This project is licensed under GNU GPL v3 or any later version


================================================
FILE: ci/generateList.ts
================================================
/*
This file is only ran by GitHub Actions in order to populate the Invidious instances list

This file should not be shipped with the extension
*/

/*
Criteria for inclusion:
Invidious
- uptime >= 80%
- must have been up for at least 90 days
- HTTPS only
- url includes name (this is to avoid redirects)

Piped
- 30d uptime >= 90%
- available for at least 80/90 days
- must have been up for at least 90 days
- must not be a wildcard redirect to piped.video
- must be currently up
- must have a functioning frontend
- must have a functioning API
*/

import { writeFile, existsSync } from "fs"
import { join } from "path"
import { getInvidiousList } from "./invidiousCI";
// import { getPipedList } from "./pipedCI";

const checkPath = (path: string) => existsSync(path);
const fixArray = (arr: string[]) => [...new Set(arr)].sort()

async function generateList() {
  // import file from https://api.invidious.io/instances.json
  const invidiousPath = join(__dirname, "invidious_instances.json");
  // import file from https://github.com/TeamPiped/piped-uptime/raw/master/history/summary.json
  const pipedPath = join(__dirname, "piped_instances.json");

  // check if files exist
  if (!checkPath(invidiousPath) || !checkPath(pipedPath)) {
    console.log("Missing files")
    process.exit(1);
  }

  // static non-invidious instances
  const staticInstances = ["www.youtubekids.com"];
  // invidious instances
  const invidiousList = fixArray(getInvidiousList())
  // piped instnaces
  // const pipedList = fixArray(await getPipedList())

  console.log([...staticInstances, ...invidiousList])

  writeFile(
    join(__dirname, "./invidiouslist.json"),
    JSON.stringify([...staticInstances, ...invidiousList]),
    (err) => {
      if (err) return console.log(err);
    }
  );
}
generateList()


================================================
FILE: ci/invidiousCI.ts
================================================
import { InvidiousInstance, monitor } from "./invidiousType"

import * as data from "../ci/invidious_instances.json";

// only https servers
const mapped = (data as InvidiousInstance[])
  .filter((i) =>
    i[1]?.type === "https"
    && i[1]?.monitor?.enabled
  )
  .map((instance) => {
    const monitor = instance[1].monitor as monitor;
    return {
      name: instance[0],
      url: instance[1].uri,
      uptime: monitor.uptime || 0,
      down: monitor.down ?? false,
      created_at: monitor.created_at,
    }
  });

// reliability and sanity checks
const reliableCheck = mapped
  .filter(instance => {
    const uptime = instance.uptime > 80 && !instance.down;
    const nameIncluded = instance.url.includes(instance.name);
    const ninetyDays = 90 * 24 * 60 * 60 * 1000;
    const ninetyDaysAgo = new Date(Date.now() - ninetyDays);
    const createdAt = new Date(instance.created_at).getTime() < ninetyDaysAgo.getTime();
    return uptime && nameIncluded && createdAt;
  })

export const getInvidiousList = (): string[] =>
  reliableCheck.map(instance => instance.name).sort()

================================================
FILE: ci/invidiousType.ts
================================================
export type InvidiousInstance = [
  string,
  {
    flag: string;
    region: string;
    stats: null | ivStats;
    cors: null | boolean;
    api: null | boolean;
    type: "https" | "http" | "onion" | "i2p";
    uri: string;
    monitor: null | monitor;
  }
]

export type monitor = {
  token: string;
  url: string;
  alias: string;
  last_status: number;
  uptime: number;
  down: boolean;
  down_since: null | string;
  up_since: null | string;
  error: null | string;
  period: number;
  apdex_t: number;
  string_match: string;
  enabled: boolean;
  published: boolean;
  disabled_locations: string[];
  recipients: string[];
  last_check_at: string;
  next_check_at: string;
  created_at: string;
  mute_until: null | string;
  favicon_url: string;
  custom_headers: Record<string, string>;
  http_verb: string;
  http_body: string;
  ssl: {
    tested_at: string;
    expires_at: string;
    valid: boolean;
    error: null | string;
  };
}

export type ivStats = {
  version: string;
  software: {
    name: "invidious" | string;
    version: string;
    branch: "master" | string;
  };
  openRegistrations: boolean;
  usage: {
    users: {
      total: number;
      activeHalfyear: number;
      activeMonth: number;
    };
  };
  metadata: {
    updatedAt: number;
    lastChannelRefreshedAt: number;
  };
  playback: {
    totalRequests: number;
    successfulRequests: number;
    ratio: number;
  };
}

================================================
FILE: ci/invidiouslist.json
================================================
["www.youtubekids.com","inv.nadeko.net","inv.tux.pizza","invidious.adminforge.de","invidious.jing.rocks","invidious.nerdvpn.de","invidious.perennialte.ch","invidious.privacyredirect.com","invidious.reallyaweso.me","invidious.yourdevice.ch","iv.ggtyler.dev","iv.nboeck.de","yewtu.be"]

================================================
FILE: ci/pipedCI.ts
================================================
import * as data from "../ci/piped_instances.json";

type percent = string
type dailyMinutesDown = Record<string, number>

type PipedInstance = {
  name: string;
  url: string;
  icon: string;
  slug: string;
  status: string;
  uptime: percent;
  uptimeDay: percent;
  uptimeWeek: percent;
  uptimeMonth: percent;
  uptimeYear: percent;
  time: number;
  timeDay: number;
  timeWeek: number;
  timeMonth: number;
  timeYear: number;
  dailyMinutesDown: dailyMinutesDown
}

const percentNumber = (percent: percent) => Number(percent.replace("%", ""))
const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000)

function dailyMinuteFilter (dailyMinutesDown: dailyMinutesDown) {
  let daysDown = 0
  for (const [date, minsDown] of Object.entries(dailyMinutesDown)) {
    if (new Date(date) >= ninetyDaysAgo && minsDown > 1000) { // if within 90 days and down for more than 1000 minutes
      daysDown++
    }
  }
  // return true f less than 10 days down
  return daysDown < 10
}

const getHost = (url: string) => new URL(url).host

const getWatchPage = async (instance: PipedInstance) =>
  fetch(`https://${getHost(instance.url)}`, { redirect: "manual" })
    .then(res => res.headers.get("Location"))
    .catch(e => { console.log (e); return null })

const siteOK = async (instance) => {
  // check if entire site is redirect
  const notRedirect = await fetch(instance.url, { redirect: "manual" })
    .then(res => res.status == 200)
  // only allow kavin to return piped.video
  // if (instance.url.startsWith("https://piped.video") && instance.slug !== "kavin-rocks-official") return false
  // check if frontend is OK
  const watchPageStatus = await fetch(instance.frontendUrl)
    .then(res => res.ok)
  // test API - stream returns ok result
  const streamStatus = await fetch(`${instance.apiUrl}/streams/BaW_jenozKc`)
    .then(res => res.ok)
  // get startTime of monitor
  const age = await fetch(instance.historyUrl)
    .then(res => res.text())
    .then(text => { // startTime greater than 90 days ago
      const date = text.match(/startTime: (.+)/)[1]
      return Date.parse(date) < ninetyDaysAgo.valueOf()
    })
  // console.log(notRedirect, watchPageStatus, streamStatus, age, instance.frontendUrl, instance.apiUrl)
  return notRedirect && watchPageStatus && streamStatus && age
}

const staticFilters = (data as PipedInstance[])
  .filter(instance => {
    const isup = instance.status === "up"
    const monthCheck = percentNumber(instance.uptimeMonth) >= 90
    const dailyMinuteCheck = dailyMinuteFilter(instance.dailyMinutesDown)
    return isup && monthCheck && dailyMinuteCheck
  })
  .map(async instance => {
    // get frontend url
    const frontendUrl = await getWatchPage(instance)
    if (!frontendUrl) return null // return false if frontend doesn't resolve
    // get api base
    const apiUrl = instance.url.replace("/healthcheck", "")
    const historyUrl = `https://raw.githubusercontent.com/TeamPiped/piped-uptime/master/history/${instance.slug}.yml`
    const pass = await siteOK({ apiUrl, historyUrl, frontendUrl, url: instance.url })
    const frontendHost = getHost(frontendUrl)
    return pass ? frontendHost : null
  })

export async function getPipedList(): Promise<string[]> {
  const instances = await Promise.all(staticFilters)
    .then(arr => arr.filter(i => i !== null))
  return instances
}


================================================
FILE: ci/prettify.ts
================================================
import { writeFile } from 'fs';

import * as license from "../oss-attribution/licenseInfos.json";

const result = JSON.stringify(license, null, 2);
writeFile("../oss-attribution/licenseInfos.json", result, err => { if (err) return console.log(err) } );

================================================
FILE: config.json.example
================================================
{
    "serverAddress": "https://sponsor.ajay.app",
    "testingServerAddress": "https://sponsor.ajay.app/test",
    "serverAddressComment": "This specifies the default SponsorBlock server to connect to",
    "categoryList": ["sponsor", "selfpromo", "exclusive_access", "interaction", "poi_highlight", "intro", "outro", "preview", "hook", "filler", "chapter", "music_offtopic"],
    "categorySupport": {
        "sponsor": ["skip", "mute", "full"],
        "selfpromo": ["skip", "mute", "full"],
        "exclusive_access": ["full"],
        "interaction": ["skip", "mute"],
        "intro": ["skip", "mute"],
        "outro": ["skip", "mute"],
        "preview": ["skip", "mute"],
        "hook": ["skip", "mute"],
        "filler": ["skip", "mute"],
        "music_offtopic": ["skip"],
        "poi_highlight": ["poi"],
        "chapter": ["chapter"]
    },
    "wikiLinks": {
        "sponsor": "https://wiki.sponsor.ajay.app/w/Sponsor",
        "selfpromo": "https://wiki.sponsor.ajay.app/w/Unpaid/Self_Promotion",
        "exclusive_access": "https://wiki.sponsor.ajay.app/w/Exclusive_Access",
        "interaction": "https://wiki.sponsor.ajay.app/w/Interaction_Reminder_(Subscribe)",
        "intro": "https://wiki.sponsor.ajay.app/w/Intermission/Intro_Animation",
        "outro": "https://wiki.sponsor.ajay.app/w/Endcards/Credits",
        "preview": "https://wiki.sponsor.ajay.app/w/Preview/Recap",
        "hook": "https://wiki.sponsor.ajay.app/w/Hook/Greetings",
        "filler": "https://wiki.sponsor.ajay.app/w/Tangents/Jokes",
        "music_offtopic": "https://wiki.sponsor.ajay.app/w/Music:_Non-Music_Section",
        "poi_highlight": "https://wiki.sponsor.ajay.app/w/Highlight",
        "guidelines": "https://wiki.sponsor.ajay.app/w/Guidelines",
        "mute": "https://wiki.sponsor.ajay.app/w/Mute_Segment",
        "chapter": "https://wiki.sponsor.ajay.app/w/Chapter"
    },
    "extensionImportList": {
        "chromium": [
            "enamippconapkdmgfgjchkhakpfinmaj"
        ],
        "firefox": [
            "deArrow@ajay.app",
            "deArrowBETA@ajay.app"
        ],
        "safari": [
            "app.ajay.dearrow.extension"
        ]
    }
}


================================================
FILE: crowdin.yml
================================================
files:
  - source: /public/_locales/en/*
    translation: /public/_locales/%two_letters_code%/%original_file_name%


================================================
FILE: jest.config.js
================================================
module.exports = {
    "roots": [
        "test"
    ],
    "transform": {
        "^.+\\.ts$": "ts-jest"
    },
    "reporters": ["default", "github-actions"]
}; 


================================================
FILE: manifest/beta-manifest-extra.json
================================================
{
    "name": "BETA - SponsorBlock"
}
  

================================================
FILE: manifest/chrome-manifest-extra.json
================================================
{
  "host_permissions": [
    "https://*.youtube.com/*",
    "https://sponsor.ajay.app/*"
  ],
  "optional_host_permissions": [
    "*://*/*"
  ],
  "web_accessible_resources": [{
    "resources": [
      "icons/LogoSponsorBlocker256px.png",
      "icons/IconSponsorBlocker256px.png",
      "icons/PlayerStartIconSponsorBlocker.svg",
      "icons/PlayerStopIconSponsorBlocker.svg",
      "icons/PlayerUploadIconSponsorBlocker.svg",
      "icons/PlayerUploadFailedIconSponsorBlocker.svg",
      "icons/PlayerCancelSegmentIconSponsorBlocker.svg",
      "icons/clipboard.svg",
      "icons/settings.svg",
      "icons/pencil.svg",
      "icons/check.svg",
      "icons/check-smaller.svg",
      "icons/upvote.png",
      "icons/downvote.png",
      "icons/thumbs_down.svg",
      "icons/thumbs_down_locked.svg",
      "icons/thumbs_up.svg",
      "icons/help.svg",
      "icons/report.png",
      "icons/close.png",
      "icons/skipIcon.svg",
      "icons/refresh.svg",
      "icons/beep.oga",
      "icons/pause.svg",
      "icons/stop.svg",
      "icons/skip.svg",
      "icons/heart.svg",
      "icons/visible.svg",
      "icons/not_visible.svg",
      "icons/sort.svg",
      "icons/money.svg",
      "icons/segway.png",
      "icons/close-smaller.svg",
      "icons/right-arrow.svg",
      "icons/campaign.svg",
      "icons/star.svg",
      "icons/lightbulb.svg",
      "icons/bolt.svg",
      "icons/stopwatch.svg",
      "icons/music-note.svg",
      "icons/import.svg",
      "icons/export.svg",
      "icons/PlayerInfoIconSponsorBlocker.svg",
      "icons/PlayerDeleteIconSponsorBlocker.svg",
      "icons/dearrow.svg",
      "icons/sb-pride.png",
      "icons/pride.svg",
      "popup.html",
      "popup.css",
      "content.css",
      "shared.css",
      "js/document.js",
      "libs/Source+Sans+Pro.css",
      "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2",
      "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2",
      "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2",
      "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2"
    ],
    "matches": ["<all_urls>"]
  }],
  "content_scripts": [
    {
        "world": "MAIN",
        "js": [
            "./js/document.js"
        ],
        "matches": [
            "https://*.youtube.com/*",
            "https://www.youtube-nocookie.com/embed/*"
        ],
        "exclude_matches": [
          "https://accounts.youtube.com/RotateCookiesPage*"
      ],
        "all_frames": true,
        "run_at": "document_start"
    },
    {
      "world": "ISOLATED",
      "js": [
          "./js/content.js"
      ],
      "css": [
        "content.css",
        "shared.css"
      ],
      "matches": [
          "https://*.youtube.com/*",
          "https://www.youtube-nocookie.com/embed/*"
      ],
      "exclude_matches": [
        "https://accounts.youtube.com/RotateCookiesPage*"
      ],
      "all_frames": true,
      "run_at": "document_start"
  }
  ],
  "action": {
    "default_title": "SponsorBlock",
    "default_popup": "popup.html",
    "default_icon": {
        "16": "icons/IconSponsorBlocker16px.png",
        "32": "icons/IconSponsorBlocker32px.png",
        "64": "icons/IconSponsorBlocker64px.png",
        "128": "icons/IconSponsorBlocker128px.png"
    },
    "theme_icons": [
        {
            "light": "icons/IconSponsorBlocker16px.png",
            "dark": "icons/IconSponsorBlocker16px.png",
            "size": 16
        },
        {
            "light": "icons/IconSponsorBlocker32px.png",
            "dark": "icons/IconSponsorBlocker32px.png",
            "size": 32
        },
        {
            "light": "icons/IconSponsorBlocker64px.png",
            "dark": "icons/IconSponsorBlocker64px.png",
            "size": 64
        },
        {
            "light": "icons/IconSponsorBlocker128px.png",
            "dark": "icons/IconSponsorBlocker128px.png",
            "size": 128
        },
        {
            "light": "icons/IconSponsorBlocker256px.png",
            "dark": "icons/IconSponsorBlocker256px.png",
            "size": 256
        },
        {
            "light": "icons/IconSponsorBlocker512px.png",
            "dark": "icons/IconSponsorBlocker512px.png",
            "size": 512
        },
        {
            "light": "icons/IconSponsorBlocker1024px.png",
            "dark": "icons/IconSponsorBlocker1024px.png",
            "size": 1024
        }
    ]
  },
  "background": {
    "service_worker": "./js/background.js"
  },
  "manifest_version": 3
}


================================================
FILE: manifest/firefox-beta-manifest-extra.json
================================================
{
    "browser_specific_settings": {
        "gecko": {
            "id": "sponsorBlockerBETA@ajay.app"
        }
    }
}
  

================================================
FILE: manifest/firefox-manifest-extra.json
================================================
{
  "browser_specific_settings": {
    "gecko": {
      "id": "sponsorBlocker@ajay.app",
      "strict_min_version": "102.0"
    },
    "gecko_android": {
      "strict_min_version": "113.0"
    }
  },
  "background": {
    "persistent": false
  },
  "browser_action": {
    "default_area": "navbar"
  }
}


================================================
FILE: manifest/manifest-v2-extra.json
================================================
{
    "web_accessible_resources": [
        "icons/LogoSponsorBlocker256px.png",
        "icons/IconSponsorBlocker256px.png",
        "icons/PlayerStartIconSponsorBlocker.svg",
        "icons/PlayerStopIconSponsorBlocker.svg",
        "icons/PlayerUploadIconSponsorBlocker.svg",
        "icons/PlayerUploadFailedIconSponsorBlocker.svg",
        "icons/PlayerCancelSegmentIconSponsorBlocker.svg",
        "icons/clipboard.svg",
        "icons/settings.svg",
        "icons/pencil.svg",
        "icons/check.svg",
        "icons/check-smaller.svg",
        "icons/upvote.png",
        "icons/downvote.png",
        "icons/thumbs_down.svg",
        "icons/thumbs_down_locked.svg",
        "icons/thumbs_up.svg",
        "icons/help.svg",
        "icons/report.png",
        "icons/close.png",
        "icons/skipIcon.svg",
        "icons/refresh.svg",
        "icons/beep.oga",
        "icons/pause.svg",
        "icons/stop.svg",
        "icons/skip.svg",
        "icons/heart.svg",
        "icons/visible.svg",
        "icons/not_visible.svg",
        "icons/sort.svg",
        "icons/money.svg",
        "icons/segway.png",
        "icons/close-smaller.svg",
        "icons/right-arrow.svg",
        "icons/campaign.svg",
        "icons/star.svg",
        "icons/lightbulb.svg",
        "icons/bolt.svg",
        "icons/stopwatch.svg",
        "icons/music-note.svg",
        "icons/import.svg",
        "icons/export.svg",
        "icons/PlayerInfoIconSponsorBlocker.svg",
        "icons/PlayerDeleteIconSponsorBlocker.svg",
        "icons/dearrow.svg",
        "icons/sb-pride.png",
        "icons/pride.svg",
        "popup.html",
        "popup.css",
        "content.css",
        "shared.css",
        "js/document.js",
        "libs/Source+Sans+Pro.css",
        "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2",
        "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2",
        "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2",
        "libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2"
    ],
    "permissions": [
        "https://sponsor.ajay.app/*"
    ],
    "optional_permissions": [
        "*://*/*"
    ],
    "browser_action": {
        "default_title": "SponsorBlock",
        "default_popup": "popup.html",
        "default_icon": {
            "16": "icons/IconSponsorBlocker16px.png",
            "32": "icons/IconSponsorBlocker32px.png",
            "64": "icons/IconSponsorBlocker64px.png",
            "128": "icons/IconSponsorBlocker128px.png"
        },
        "theme_icons": [
            {
                "light": "icons/IconSponsorBlocker16px.png",
                "dark": "icons/IconSponsorBlocker16px.png",
                "size": 16
            },
            {
                "light": "icons/IconSponsorBlocker32px.png",
                "dark": "icons/IconSponsorBlocker32px.png",
                "size": 32
            },
            {
                "light": "icons/IconSponsorBlocker64px.png",
                "dark": "icons/IconSponsorBlocker64px.png",
                "size": 64
            },
            {
                "light": "icons/IconSponsorBlocker128px.png",
                "dark": "icons/IconSponsorBlocker128px.png",
                "size": 128
            },
            {
                "light": "icons/IconSponsorBlocker256px.png",
                "dark": "icons/IconSponsorBlocker256px.png",
                "size": 256
            },
            {
                "light": "icons/IconSponsorBlocker512px.png",
                "dark": "icons/IconSponsorBlocker512px.png",
                "size": 512
            },
            {
                "light": "icons/IconSponsorBlocker1024px.png",
                "dark": "icons/IconSponsorBlocker1024px.png",
                "size": 1024
            }
        ]
    },
    "background": {
        "scripts":[
            "./js/background.js"
        ]
    },
    "content_scripts": [{
        "run_at": "document_start",
        "matches": [
            "https://*.youtube.com/*",
            "https://www.youtube-nocookie.com/embed/*"
        ],
        "exclude_matches": [
            "https://accounts.youtube.com/RotateCookiesPage*"
        ],
        "all_frames": true,
        "js": [
            "./js/content.js"
        ],
        "css": [
            "content.css",
            "shared.css"
        ]
    }],
    "manifest_version": 2
}  


================================================
FILE: manifest/manifest.json
================================================
{
    "name": "__MSG_fullName__",
    "short_name": "SponsorBlock",
    "version": "6.1.2",
    "default_locale": "en",
    "description": "__MSG_Description__",
    "homepage_url": "https://sponsor.ajay.app",
    "icons": {
        "16": "icons/IconSponsorBlocker16px.png",
        "32": "icons/IconSponsorBlocker32px.png",
        "64": "icons/IconSponsorBlocker64px.png",
        "128": "icons/IconSponsorBlocker128px.png",
        "256": "icons/IconSponsorBlocker256px.png",
        "512": "icons/IconSponsorBlocker512px.png",
        "1024": "icons/IconSponsorBlocker1024px.png"
    },
    "permissions": [
        "storage",
        "scripting",
        "unlimitedStorage"
    ],
    "options_ui": {
        "page": "options/options.html",
        "open_in_tab": true
    }
}  


================================================
FILE: manifest/safari-manifest-extra.json
================================================
{
  "background": {
    "persistent": false
  },
  "optional_permissions": [
    "webNavigation"
  ],
  "browser_action": {
    "default_icon": {
      "16": "icons/SafariIconSponsorBlocker16px.png",
      "32": "icons/SafariIconSponsorBlocker32px.png",
      "64": "icons/SafariIconSponsorBlocker64px.png",
      "128": "icons/SafariIconSponsorBlocker128px.png"
    }
  },
  "browser_specific_settings": {
    "safari": {
      "strict_min_version": "14.0"
    }
  }
}


================================================
FILE: oss-attribution/licenseInfos.json
================================================
{
  "content-scripts-register-polyfill": {
    "ignore": false,
    "name": "content-scripts-register-polyfill",
    "version": "4.0.2",
    "authors": "Federico Brigante <me@fregante.com> (https://fregante.com)",
    "url": "https://github.com/fregante/content-scripts-register-polyfill",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  "escape-string-regexp": {
    "ignore": false,
    "name": "escape-string-regexp",
    "version": "5.0.0",
    "authors": "Sindre Sorhus <sindresorhus@gmail.com>",
    "url": "https://github.com/sindresorhus/escape-string-regexp",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  "js-tokens": {
    "ignore": false,
    "name": "js-tokens",
    "version": "4.0.0",
    "authors": "Simon Lydell",
    "url": "https://github.com/lydell/js-tokens",
    "license": "MIT",
    "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  "loose-envify": {
    "ignore": false,
    "name": "loose-envify",
    "version": "1.4.0",
    "authors": "Andres Suarez <zertosh@gmail.com>",
    "url": "https://github.com/zertosh/loose-envify",
    "license": "MIT",
    "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Andres Suarez <zertosh@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  "react-dom": {
    "ignore": false,
    "name": "react-dom",
    "version": "18.2.0",
    "url": "https://github.com/facebook/react",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Facebook, Inc. and its affiliates.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  "react": {
    "ignore": false,
    "name": "react",
    "version": "18.2.0",
    "url": "https://github.com/facebook/react",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Facebook, Inc. and its affiliates.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  "scheduler": {
    "ignore": false,
    "name": "scheduler",
    "version": "0.23.0",
    "url": "https://github.com/facebook/react",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Facebook, Inc. and its affiliates.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  "webext-content-scripts": {
    "ignore": false,
    "name": "webext-content-scripts",
    "version": "2.5.5",
    "authors": "Federico Brigante <me@fregante.com> (https://fregante.com)",
    "url": "https://github.com/fregante/webext-content-scripts",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  "webext-patterns": {
    "ignore": false,
    "name": "webext-patterns",
    "version": "1.3.0",
    "authors": "Federico Brigante <me@fregante.com> (https://fregante.com)",
    "url": "https://github.com/fregante/webext-patterns",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  "webext-polyfill-kinda": {
    "ignore": false,
    "name": "webext-polyfill-kinda",
    "version": "1.0.2",
    "authors": "Federico Brigante <me@fregante.com> (https://fregante.com)",
    "url": "https://github.com/fregante/webext-polyfill-kinda",
    "license": "MIT",
    "licenseText": "MIT License\n\nCopyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  }
}

================================================
FILE: package.json
================================================
{
  "name": "sponsorblock",
  "version": "1.0.0",
  "description": "",
  "main": "background.js",
  "dependencies": {
    "content-scripts-register-polyfill": "^4.0.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "overrides": {
    "content-scripts-register-polyfill": {
      "webext-content-scripts": "v2.5.5"
    }
  },
  "devDependencies": {
    "@types/chrome": "^0.0.220",
    "@types/firefox-webext-browser": "^111.0.0",
    "@types/jest": "^29.4.0",
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@types/selenium-webdriver": "^4.1.13",
    "@types/wicg-mediasession": "^1.1.4",
    "@typescript-eslint/eslint-plugin": "^5.54.1",
    "@typescript-eslint/parser": "^5.54.1",
    "chromedriver": "^140.0.0",
    "concurrently": "^7.6.0",
    "copy-webpack-plugin": "^11.0.0",
    "eslint": "^8.35.0",
    "eslint-plugin-react": "^7.32.2",
    "fork-ts-checker-webpack-plugin": "^7.3.0",
    "jest": "^29.5.0",
    "jest-environment-jsdom": "^30.2.0",
    "rimraf": "^4.3.1",
    "schema-utils": "^4.0.0",
    "selenium-webdriver": "^4.8.1",
    "ts-jest": "^29.0.5",
    "ts-loader": "^9.4.2",
    "ts-node": "^10.9.1",
    "typescript": "4.9",
    "web-ext": "^8.10.0",
    "webpack": "^5.105.0",
    "webpack-cli": "^4.10.0",
    "webpack-merge": "^5.8.0"
  },
  "scripts": {
    "web-run": "npm run web-run:chrome",
    "web-sign": "web-ext sign --channel unlisted -s dist",
    "web-run:firefox": "cd dist && web-ext run --start-url https://addons.mozilla.org/firefox/addon/ublock-origin/",
    "web-run:firefox-android": "cd dist && web-ext run -t firefox-android --firefox-apk org.mozilla.fenix",
    "web-run:chrome": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm -t chromium",
    "build": "npm run build:chrome",
    "build:chrome": "webpack --env browser=chrome --config webpack/webpack.prod.js",
    "build:firefox": "webpack --env browser=firefox --config webpack/webpack.prod.js",
    "build:safari": "webpack --env browser=safari --config webpack/webpack.prod.js",
    "build:edge": "webpack --env browser=edge --config webpack/webpack.prod.js",
    "build:dev": "npm run build:dev:chrome",
    "build:dev:chrome": "webpack --env browser=chrome --config webpack/webpack.dev.js",
    "build:dev:firefox": "webpack --env browser=firefox --config webpack/webpack.dev.js",
    "build:watch": "npm run build:watch:chrome",
    "build:watch:chrome": "webpack --env browser=chrome --config webpack/webpack.dev.js --watch",
    "build:watch:firefox": "webpack --env browser=firefox --config webpack/webpack.dev.js --watch",
    "ci:invidious": "ts-node ci/generateList.ts",
    "dev": "npm run build:dev && concurrently \"npm run web-run\" \"npm run build:watch\"",
    "dev:firefox": "npm run build:dev:firefox && concurrently \"npm run web-run:firefox\" \"npm run build:watch:firefox\"",
    "dev:firefox-android": "npm run build:dev:firefox && concurrently \"npm run web-run:firefox-android\" \"npm run build:watch:firefox\"",
    "clean": "rimraf dist",
    "test": "npm run build:chrome && npx jest",
    "test-without-building": "npx jest",
    "lint": "eslint src",
    "lint:fix": "eslint src --fix"
  },
  "engines": {
    "node": ">=16"
  },
  "funding": [
    {
      "type": "individual",
      "url": "https://sponsor.ajay.app/donate"
    },
    {
      "type": "github",
      "url": "https://github.com/sponsors/ajayyy-org"
    },
    {
      "type": "patreon",
      "url": "https://www.patreon.com/ajayyy"
    },
    {
      "type": "individual",
      "url": "https://paypal.me/ajayyy"
    }
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ajayyy/SponsorBlock.git"
  },
  "author": "Ajay Ramachandran",
  "license": "GPL-3.0",
  "private": true
}


================================================
FILE: public/content.css
================================================
:root {
	--skip-notice-right: 10px;
	--skip-notice-padding: 5px;
	--skip-notice-margin: 5px;
	--skip-notice-border-horizontal: 5px;
	--skip-notice-border-vertical: 10px;
	--sb-dark-red-outline: rgb(130,0,0,0.9);
}

.sbhidden {
	display: none;
}

/* Vorapi compatibility */
#player-api_VORAPI_ELEMENT_ID #previewbar {
	z-index: 999;
}

#previewbar {
    overflow: visible;
    padding: 0;
    margin: 0;
    position: absolute;
    width: 100%;
	pointer-events: none;

	height: 100%;
	transform: scaleY(0.667) translateY(-30%) translateY(1.5px);
	z-index: 42;

	transition: transform .1s cubic-bezier(0,0,0.2,1);
}

/* Prevent bar from covering highlights on YTTV */
#previewbar.sponsorblock-yttv-container  {
	z-index: unset;
}

ytu-time-bar.ytu-storyboard {
	text-align: center;
}

/* May 2024 hover preview */
.YtPlayerProgressBarProgressBar #previewbar {
	transform: none;
}

.ytp-big-mode #previewbar {
	transform: scaleY(0.625) translateY(-30%) translateY(1.5px);
}

.ytp-big-mode .sponsorTwoTooltips .sponsorCategoryTooltip {
	top: 75px !important;
}

.progress-bar-line > #previewbar {
	height: 3px;
}

div:hover > #previewbar.sbNotInvidious {
	transform: scaleY(1);
}

/* Vorapis */
.v3 #previewbar.sbNotInvidious {
	transform: scaleY(1);
}
.sponsorCategoryTooltipVisible.ytp-progress-tooltip {
	width: 216px !important;
  	/* left: 264.308px !important; */
}

.previewbar {
	display: inline-block;
	height: 100%;
	min-width: 1px;
}

.previewbar-yttv {
	height: 10px;
	top: 14px;
}

.previewbar.requiredSegment {
	transform: scaleY(3);
}

.previewbar.selectedSegment {
	opacity: 1 !important;
	z-index: 100;
	transform: scaleY(1.5);
}

/* Make sure settings are upfront */
.ytp-settings-menu {
	z-index: 6000 !important;
}

/* Preview Bar page hacks */

.ytp-tooltip:not(.sponsorCategoryTooltipVisible) .sponsorCategoryTooltip {
	display: none !important;
}

/* Pull up for precise seeking */
.ytp-tooltip.sponsorCategoryTooltipVisible .ytp-tooltip-edu {
	transform: translateY(-1em) !important;
}

.ytp-tooltip.sponsorCategoryTooltipVisible {
	transform: translateY(-1em) !important;
}

.ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips {
	transform: translateY(-2em) !important;
}

.ytp-tooltip.sponsorCategoryTooltipVisible.sponsorHasOriginalTooltip {
	transform: translateY(-2em) !important;
}

.ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips.sponsorHasOriginalTooltip {
	transform: translateY(-3em) !important;
}

.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible {
	transform: translateY(-2em) !important;
}

.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips {
	transform: translateY(-4em) !important;
}

#movie_player:not(.ytp-big-mode) .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper {
	transform: translateY(1em) !important;
}

#movie_player:not(.ytp-big-mode) .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper {
	transform: translateY(2em) !important;
}

/* Pull up for precise seeking */
.ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips .ytp-tooltip-edu {
	transform: translateY(-2em) !important;
}

.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper {
	transform: translateY(0.5em) !important;
}

.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper {
	transform: translateY(1.75em) !important;
}

.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper .ytp-tooltip-text {
	display: inline-block !important;
	transform: translateY(0.75em) !important;
}

.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper .ytp-tooltip-text {
	display: inline-block !important;
	transform: translateY(0.75em) !important;
}

div:hover > .sponsorBlockChapterBar {
	z-index: 41 !important;
}

/*  */

.popup {
    z-index: 10;
    width: 100%;
    height: 500px;
}

.smallLink {
	font-size: 10px;
	text-decoration: underline;
	cursor: pointer;
}

.playerButtonImage {
	height: 60%;
	top: 0;
	bottom: 0;
	display: block;
	margin: auto;
}

.sbChapterVoteButton {
	padding: 0 !important;
}

.playerButton {
	vertical-align: top;
}

.playerButton.sbhidden:not(.autoHiding) {
	display: none !important;
}

/* Removes auto width from being a ytp-player-button */
.sbPlayerDownvote {
	width: auto !important;
}

/* Adds back the padding */
.sbPlayerDownvote svg {
	padding-right: 3.6px;
}

.sbButtonYTTV {
	padding-left: 5px !important;
}

/* YTTV only */
.ytu-player-controls > .skipButtonControlBarContainer > div {
	padding-left: 5px;
	align-content: center;
}

.autoHiding {
	overflow: visible !important;
}

.autoHiding:not(.sbhidden) {
	transform: translateX(0%) scale(1);
	/* opacity is from YouTube page */
	transition: transform 0.2s, width 0.2s, opacity .1s cubic-bezier(0.4,0.0,1,1) !important;
}

.autoHiding.sbhidden {
	transform: translateX(100%) scale(0);
	/* opacity is from YouTube page */
	transition: transform 0.2s, width 0.2s, opacity .1s cubic-bezier(0.4,0.0,1,1) !important;

	width: 0px !important;
}

.autoHiding.sbhidden.autoHideLeft {
	transform: translateX(-100%) scale(0);
}

.sponsorSkipObject {
	font-family: Roboto, Arial, Helvetica, sans-serif;

	margin-left: var(--skip-notice-margin);
	margin-right: var(--skip-notice-margin);
}

.sponsorSkipObjectFirst {
	margin-left: 0;
}

.sponsorSkipLogo {
	height: 18px;

	float: left;
}

#categoryPill .sbPillNoText .sponsorSkipLogo {
	margin-top: calc(2.6rem - 18px);
    margin-bottom: calc(2.6rem - 18px);
}

@keyframes fadeIn {
	from { opacity: 0; }
}

@keyframes fadeInToFaded {
	from { opacity: 0; }
	to { opacity: 0.5; }
}

@keyframes fadeOut {
	to { opacity: 0; }
}

.sponsorBlockSpacer {
	background-color: rgb(100, 100, 100);
	border-color: rgb(100, 100, 100);

	margin-left: 5px;
}

.sbChatNotice {
	min-width: 350px;
	height: 70%;

	position: absolute;
	right: 5px;
	bottom: 100px;
	right: var(--skip-notice-right);
}

.sponsorSkipNoticeParent {
    position: absolute;

	bottom: 100px;
	right: 10px;
}

.sponsorSkipNoticeParent, .sponsorSkipNotice {
	border-spacing: 5px 10px;
	padding-left: 5px;
	padding-right: 5px;

	border-collapse: unset;
}

.sponsorSkipNotice {
	width: 100%;
}

.sponsorSkipNoticeTableContainer {
	color: white;
	background-color: rgba(28, 28, 28, 0.9);
	border-radius: 5px;
	min-width: 100%;
}

.exportCopiedNotice .sponsorSkipNoticeTableContainer {
	background-color: transparent;
}

.sponsorSkipNotice {
	transition: all 0.1s ease-out;
}

.sponsorSkipNoticeLimitWidth {
	max-width: calc(100% - 50px);
}

.sponsorSkipNotice .sbhidden {
	display: none;
}

/* For Cloudtube */
.sponsorSkipNotice td, .sponsorSkipNotice table, .sponsorSkipNotice th {
	border: none;
}

.sponsorSkipNoticeFadeIn {
	animation: fadeIn 0.5s ease-out;
}

.sponsorSkipNoticeFadeIn.sponsorSkipNoticeFaded {
	animation: fadeInToFaded 0.5s ease-out;
}

.exportCopiedNotice .sponsorSkipNoticeFadeIn {
	animation: none;
}

.sponsorSkipNoticeFaded {
	opacity: 0.5;
}

.sponsorSkipNoticeFadeOut {
	transition: opacity 3s cubic-bezier(0.55, 0.055, 0.675, 0.19);
	opacity: 0 !important;
	animation: none !important;
}

.sponsorSkipNotice .sponsorSkipNoticeTimeLeft {
	color: #eeeeee;

	border-radius: 4px;
    padding: 2px 5px;
    font-size: 12px;

	display: flex;
    align-items: center;

	border: 1px solid #eeeeee;
}

.sponsorSkipNoticeTimeLeft img {
	vertical-align: middle;
    height: 13px;

	padding-top: 7.8%;
    padding-bottom: 7.8%;
}

.noticeLeftIcon {
	display: flex;
  	align-items: center;
}

.sponsorSkipNotice .sponsorSkipNoticeUnskipSection {
	float: left;

	border-left: 1px solid rgb(150, 150, 150);
}

.sponsorSkipNoticeButton {
	background: none;
	color: rgb(235, 235, 235);
	border: none;
	display: inline-block;
	font-size: 13.3333px !important;

	cursor: pointer;

	margin-right: 10px;

    padding: 2px 5px;
}

.sponsorSkipNoticeButton:hover {
	background-color: rgba(235, 235, 235,0.2);
	border-radius: 4px;

	transition: background-color 0.4s;
}

.sponsorTimesVoteButtonsContainer {
	float: left;
	vertical-align:middle;
	padding: 2px 5px;

	margin-right: 4px;
}

.sponsorTimesVoteButtonsContainer div{
	display: inline-block;
}

.sponsorSkipNoticeRightSection {
    right: 0;
	position: absolute;

	float: right;

	margin-right: 10px;
	display: flex;
	align-items: center;
}

.sponsorSkipNoticeRightButton {
	margin-right: 0;
}

.sponsorSkipNoticeCloseButton {
	height: 10px;
	width: 10px;
	box-sizing: unset;

	padding: 2px 5px;

	margin-left: 2px;
    float: right;
}

.sponsorSkipNoticeCloseButton.biggerCloseButton {
	padding: 20px;
}

.sponsorSkipMessage {
	font-size: 14px;
	font-weight: bold;
	color: rgb(235, 235, 235);

	margin-top: auto;
	display: inline-block;
	margin-right: 10px;
	margin-bottom: auto;
}

.sponsorSkipInfo {
	font-size: 10px;
    color: #000000;
	text-align: center;
	margin-top: 0px;
}

#sponsorTimesThanksForVotingText {
	font-size: 20px;
	font-weight: bold;
    color: #000000;
	text-align: center;
	margin-top: 0px;
	margin-bottom: 0px;
}

#sponsorTimesThanksForVotingInfoText {
	font-size: 12px;
	font-weight: bold;
    color: #000000;
	text-align: center;
	margin-top: 0px;
}

.sponsorTimesVoteButtonMessage {
	float: left;
}

.sponsorTimesInfoMessage {
	font-size: 13.3333px;
    color: rgb(235, 235, 235);
	overflow-wrap: anywhere;
}

.sb-guidelines-notice .sponsorTimesInfoMessage td {
	padding-left: 5px;
	padding-top: 2px;
	padding-bottom: 2px;
    font-size: 15px;

	display: flex;
	align-items: center;
}

.sponsorTimesInfoIcon {
	width: 30px;
	padding-right: 10px;
    padding-left: 10px;
}

.segmentSummary {
	outline: none !important;
}

.submitButton {
	background-color:#ec1c1c;
	-moz-border-radius:28px;
	-webkit-border-radius:28px;
	border-radius:28px;
	border:1px solid #d31919;
	display:inline-block;
	cursor:pointer;
	color:#ffffff;
	font-size:14px;
	padding:4px 15px;
	text-decoration:none;
    text-shadow:0px 0px 0px #662727;

    margin-top: 5px;
    margin-right: 15px;
}
.submitButton:hover {
	background-color:#bf2a2a;
}

.submitButton:focus {
	outline: none;
	background-color:#bf2a2a;
}

.submitButton:active {
	position:relative;
	top:1px;
}

@keyframes rotate {
	from { transform: rotate(0deg); }
	  to { transform: rotate(360deg); }
}

.sponsorSkipButton {
	background-color:#ec1c1c;
	-moz-border-radius:28px;
	-webkit-border-radius:28px;
	border-radius:28px;
	border:1px solid #d31919;
	display:inline-block;
	cursor:pointer;
	color:#ffffff;
	font-size:14px;
	padding:4px 15px;
	text-decoration:none;
    text-shadow:0px 0px 0px #662727;

    margin-top: 5px;
    margin-right: 15px;
}
.sponsorSkipButton:hover {
	background-color:#bf2a2a;
}

.sponsorSkipButton:focus {
	outline: none;
	background-color:#bf2a2a;
}

.sponsorSkipButton:active {
	position:relative;
	top:1px;
}

.sponsorSkipDontShowButton {
	-moz-box-shadow:inset 0px 1px 0px 0px #cf866c;
	-webkit-box-shadow:inset 0px 1px 0px 0px #cf866c;
	box-shadow:inset 0px 1px 0px 0px #cf866c;
	background-color:#d0451b;
	-moz-border-radius:3px;
	-webkit-border-radius:3px;
	border-radius:3px;
	border:1px solid #942911;
	display:inline-block;
	cursor:pointer;
	color:#ffffff;
	font-size:13px;
	padding:6px 24px;
	text-decoration:none;
	text-shadow:0px 1px 0px #854629;
}
.sponsorSkipDontShowButton:hover {
	background-color:#bc3315;
}

.sponsorSkipDontShowButton:focus {
	outline: none;
	background-color:#bc3315;
}

.sponsorSkipDontShowButton:active {
	position:relative;
	top:1px;
}

/* Submission Notice */

.sponsorTimeDisplay {
	font-size: 15px;
}

.sponsorTimeEditButton {
	text-decoration: underline;

	margin-left: 13px;
	margin-right: 13px;

	font-size: 13px;

	cursor: pointer;
}

.sponsorTimeEdit > input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.sponsorTimeMessagesRow {
	max-height: 300px;
	display: flex;

	overflow: auto;
}

.sponsorTimeEdit {
	font-size: 14px;

	-moz-appearance: textfield;
	appearance: textfield;
}

.sponsorTimeEditInput {
	width: 90px;
	border: 3px solid var(--sb-dark-red-outline);
}

.sponsorTimeEditInput.sponsorChapterNameInput {
	width: auto;
	padding: 3px;
}

.sponsorNowButton {
	font-size: 11px;

	cursor: pointer;
	text-decoration: underline;
}

.sponsorTimeEditSelector {
	margin-top: 5px;
	margin-bottom: 5px;

	background-color: rgba(28, 28, 28, 0.9);
    border-color: var(--sb-dark-red-outline);
    color: white;
    border-width: 3px;
    padding: 3px;
}

.sponsorTimeEditSelector > option {
	background-color: rgba(28, 28, 28, 0.9);
	color: white;
}

.hideSegmentSubmitButton {
	cursor: pointer;
	margin: auto;
	top: 0;
	bottom: 0;
	position: absolute;
}

/* Start SelectorComponent */

.sbSelector {
	position: absolute;
	text-align: center;
	width: calc(100% - var(--skip-notice-right) - var(--skip-notice-padding) * 2 - var(--skip-notice-margin) * 2 - var(--skip-notice-border-horizontal) * 2);

	z-index: 1000;
}

.sbSelectorBackground {
	text-align: center;

	background-color: rgba(28, 28, 28, 0.9);
	border-radius: 6px;
	padding: 3px;
	margin: auto;
	width: 170px;
}

.sbSelectorOption {
    cursor: pointer;
    background-color: rgb(43, 43, 43);
    padding: 5px;
	margin: 5px;
    color: white;
    border-radius: 5px;
    font-size: 14px;

	margin-left: auto;
    margin-right: auto;
}

.sbSelectorOption:hover {
    background-color: #3a0000;
}

/* End SelectorComponent */

.helpButton {
	height: 25px;
	cursor: pointer;
	padding: 5px;

	margin: auto;
    top: 0;
    bottom: 0;
    position: absolute;
}
.helpButton:hover {
	opacity: 0.8;
}

.skipButtonControlBarContainer {
	cursor: pointer;
	display: flex;
    color: white;
	align-items: center;
}

/* July 2025 test UI */
.ytp-delhi-modern .skipButtonControlBarContainer {
    height: 48px;
    margin: auto 0;
}

.skipButtonControlBarContainer.sbhidden {
	display: none !important;
}

.skipButtonControlBarContainer.mobile {
	bottom: 30%;
    margin-left: 5px;
	position: absolute;
	height: 20px;

	background-color: #00000030;
	opacity: 0.5;
	border-radius: 10px;
	padding: 4px;
}

.skipButtonControlBarContainer.mobile.textDisabled {
	padding: 0;
	background-color: transparent;
}

.skipButtonControlBarContainer.mobile > div {
	margin: auto;
	margin-left: 5px;
}

#sbSkipIconControlBarImage {
	height: 60%;
    top: 0px;
    bottom: 0px;
    display: block;
    margin: auto;
}

.mobile #sbSkipIconControlBarImage {
	height: 100%;
	width: 20px;
}

.sponsorBlockTooltip {
    position: absolute;
    background-color: rgba(28, 28, 28, 0.7);
    border-radius: 5px;
    padding: 10px;
    max-width: 300px;
	width: max-content;
    white-space: normal;
    line-height: 1.5em;
	color: white;
	font-size: 12px;
	z-index: 10000;
	font-weight: normal;
}

.sponsorBlockTooltip a {
	color: white;
}

.sponsorBlockTooltip.sbTriangle::after {
    content: " ";
    position: absolute;
    top: 100%;
    left: 15%;
    margin-left: -15px;
    border-width: 15px;
    border-style: solid;
    border-color: rgba(28, 28, 28, 0.7) transparent transparent transparent;
}

.sponsorBlockTooltip.sbTriangle.centeredSBTriangle::after {
	left: 50%;
	right: 50%;
}

.sponsorBlockTooltip.sbTriangle.sbTopTriangle::after {
	bottom: 100%;
	top: unset;
	border-color: transparent transparent rgba(28, 28, 28, 0.7) transparent;
}

.sponsorBlockLockedColor {
	color: #ffc83d !important;
}

.sponsorBlockRectangleTooltip {
    position: absolute;
    border-radius: 5px;
    padding: 10px;
    min-width: 250px;
    min-height: 75px;
    white-space: normal;
    line-height: 1.5em;
}

/* Description on right layout */
#title > #categoryPillParent {
	font-size: 2rem;
	font-weight: bold;
	display: flex;
	justify-content: center;
	line-height: 2.8rem;
}
#title > #categoryPillParent > #categoryPill.cbPillOpen {
	margin-bottom: 5px;
}

#categoryPillParent {
	height: fit-content;
    margin-top: auto;
	margin-bottom: auto;

    position: relative;
}

.sponsorBlockCategoryPill {
    border-radius: 25px;
	padding-left: 8px;
	padding-right: 8px;
	margin-right: 3px;
	cursor: pointer;
	font-size: 75%;
	height: 100%;
	align-items: center;
	inline-size: max-content;
}

.sponsorBlockCategoryPillTitleSection {
	display: flex;
	align-items: center;
}

.sponsorBlockCategoryPillTitle {
	white-space: nowrap;
}

/* Vorapis V3 support */
#watch7-content .sponsorBlockCategoryPill {
	padding-top: 5px;
	padding-bottom: 5px;
}
#watch7-content .sponsorBlockCategoryPillTitle {
	font-size: 15px;
}

.categoryPillClose {
	display: none;
	height: 10px;
	width: 10px;
	box-sizing: unset;

	margin: 0px 0px 0px 5px;
}

.sponsorBlockCategoryPill:hover .categoryPillClose {
	display: inherit;
}

/* tweak for mobile duration */
#sponsorBlockDurationAfterSkips.ytm-time-display {
	padding-left: 4px;
	margin: 0px;
	color: #fff;
	opacity: .7;
}

/* full video labels on thumbnails */
.sponsorThumbnailLabel {
	display: none;
	position: absolute;
	top: 0;
	left: 0;
	padding: 0.5em;
	margin: 0.5em;
	border-radius: 2em;
	z-index: 1000;
	background-color: var(--category-color, #000);
	opacity: 0.7;
	box-shadow: 0 0 8px 2px #333;
	font-size: 10px;
}

.sponsorThumbnailLabel.sponsorThumbnailLabelVisible {
	display: flex;
}

.sponsorThumbnailLabel svg {
	height: 2em;
	fill: var(--category-text-color, #fff);
}

.sponsorThumbnailLabel span {
	display: none;
	padding-left: 0.25em;
	font-size: 1.5em;
	color: var(--category-text-color, #fff);
}

.sponsorThumbnailLabel:hover {
	border-radius: 0.25em;
	opacity: 1;
}

.sponsorThumbnailLabel:hover span {
	display: inline;
}

.sponsorblock-chapter-visible {
	display: block !important;
}

/* Pride theme */

.playerButton.prideTheme:nth-of-type(1) {
	filter: brightness(50%) sepia(100) saturate(100);
}

.playerButton.prideTheme:nth-of-type(2) {
	filter: sepia(100) saturate(100) hue-rotate(0deg);
}

.playerButton.prideTheme:nth-of-type(3) {
	filter: sepia(100) saturate(100) hue-rotate(45deg);
}

.playerButton.prideTheme:nth-of-type(4) {
	filter: sepia(100) saturate(100) invert() hue-rotate(5deg);
}

.playerButton.prideTheme:nth-of-type(5) {
	filter: sepia(100) saturate(100) invert() hue-rotate(35deg);
}

================================================
FILE: public/help/index.html
================================================
<!DOCTYPE html>

<head>
  <title> SponsorBlock </title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" /> 
  <link rel="icon" href="../icons/IconSponsorBlocker32px.png" type="image/png">

  <link href="styles.css" rel="stylesheet"/>

  <script src="../js/help.js"></script>
</head>

<body>

  <div id="title">
    <img src="../icons/LogoSponsorBlocker256px.png" height="80" class="profilepic"/>
    SponsorBlock
  </div>

  <div class="container sponsorBlockPageBody">

    <p class="createdBy">
      <img src="../icons/newprofilepic.jpg" height="30" class="profilepiccircle"/>
      Created By <a href="https://ajay.app">Ajay Ramachandran</a> 
    </p>

    <span class="help-page-flex-container">
      <div class="left-sidebar">
        <div class="box1">
          <p>
              __MSG_helpPageThanksForInstalling__ By using this extension, you agree to the <a href="https://gist.github.com/ajayyy/aa9f8ded2b573d4f73a3ffa0ef74f796">Privacy Policy</a> and <a href="https://gist.github.com/ajayyy/9e8100f069348e0bc062641f34d6af12">Terms of Use</a>.
          </p>
      
          <p>
              Come contribute, make some suggestions and help out on <a href="https://discord.gg/SponsorBlock">Discord</a> or on <a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org">Matrix</a>.
          </p>
      
          <a href="https://dearrow.ajay.app"
              target="_blank"
              id="dearrow-link"
              class="dearrow-link hidden"
              rel="noreferrer">
              <img src="/icons/dearrow.svg"/>
      
              <span id="dearrow-link-text">
                
              </span>
      
              <img src="/icons/close.png" class="close-button"/>
          </a>
      
          <div id="donate-component" class="donate-ask">
              <div class="donate-text">
                  <img
                      src="../icons/newprofilepic.jpg"
                      alt="Ajay's avatar"
                  ></img>
                  __MSG_supportSponsorBlock__
              </div>
      
              <a href="https://sponsor.ajay.app/donate" class="donate-button" target="_blank" rel="noopener">
                  __MSG_Donate__
              </a>
          </div>
        </div>
        <div class="box3">
          <h1>__MSG_helpPageHowSkippingWorks__</h1>
    
          <p class="projectPreview">
            __MSG_helpPageHowSkippingWorks2__
          </p>

          <div class="center"><img src="images/notice.png"></div>
            
          <p class="projectPreview">
            __MSG_helpPageHowSkippingWorks1__
          </p>
          
          <div class="center"><img style="height: 400px;" src="images/popup.png"></div>

          <h1>__MSG_Submitting__</h1>
    
          <p class="projectPreview">
              __MSG_helpPageSubmitting1__
          </p>

          <div class="center"><img src="images/votebuttons.gif"></div>

          <p class="projectPreview">
              __MSG_helpPageSubmitting2__
          </p>

          <div class="center"><img src="images/submission menu.png"></div>

          <p class="projectPreview center">
            <a href="https://wiki.sponsor.ajay.app/w/Guidelines" target="_blank">__MSG_guidelines__</a>
            <br/>
            <a href="https://wiki.sponsor.ajay.app/w/Advice_for_submitting" target="_blank">__MSG_AdviceForSubmitting__</a>
          </p>

          <h1>__MSG_helpPageCopyOfDatabase__</h1>
    
          <p>
            __MSG_helpPageCopyOfDatabase1__ <a href="https://sponsor.ajay.app/database">https://sponsor.ajay.app/database</a>. __MSG_helpPageCopyOfDatabase2__
          </p>
    
          <h4 style="display: inline">Client:</h4>
          <!-- Github logo -->
          <a href="https://github.com/ajayyy/SponsorBlock"><svg aria-hidden="true" version="1.1" viewBox="0 0 16 16" height="58px" style="padding-left: 15px"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg></a>
    
          <h4 style="display: inline; padding-left: 20px">Server:</h4>
          <!-- Github logo -->
          <a href="https://github.com/ajayyy/SponsorBlockServer"><svg aria-hidden="true" version="1.1" viewBox="0 0 16 16" height="58px" style="padding-left: 15px"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg></a>
    
          <h1>__MSG_Credits__</h1>
    
          <p>
            Thanks to all <a href="https://github.com/ajayyy/SponsorBlock/graphs/contributors">SponsorBlock contributors</a>,
            <a href="https://github.com/ajayyy/SponsorBlockServer/graphs/contributors">SponsorBlockServer contributors</a> and
            <a href="https://github.com/ajayyy/SponsorBlockSite/graphs/contributors">SponsorBlockSite contributors</a> such 
            as <a href="https://github.com/NDevTK">NDev</a>, <a href="https://github.com/Joe-Dowd">Joe Dowd</a>,
            <a href="https://mchang.name/">Michael Chang</a> and more.
          </p>

          <p>
            Logo by <a href="https://github.com/munadikieh">Munadi Kiehl</a>
          </p>
    
          <p>Some icons made by <a href="https://www.flaticon.com/authors/gregor-cresnar" title="Gregor Cresnar">Gregor Cresnar</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> and are licensed by <a href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></p>
          
          <p>Some icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> and are licensed by <a href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></p>
    
          <p style="text-align: center;"><a href="/oss-attribution/attribution.txt">Open Source Licenses</a></p>
        </div>
      </div>
    
      <div class="box2">
        <p style="margin-bottom: 0; margin-top: 0" class="bigText center">__MSG_helpPageReviewOptions__</p>
    
        <p class="smallText" style="margin-bottom: 0; margin-top: 0">
          __MSG_helpPageFeatureDisclaimer__
        </p>
    
        <iframe class="optionsFrame" src="../options/options.html#embed" style="border: none"></iframe>
      </div>
    </span>
</body>


================================================
FILE: public/help/styles.css
================================================
:root {
  --color-scheme: dark;
  --background: #333333;
  --header-color: #212121;
  --dialog-background: #181818;
  --dialog-border: white;
  --text: #c4c4c4;
  --title: #dad8d8;
  --disabled: #520000;
  --black: black;
  --white: white;
}

[data-theme="light"] {
  --color-scheme: light;
  --background: #f9f9f9;
  --header-color: white;
  --dialog-background: #f9f9f9;
  --dialog-border: #282828;
  --text: #262626;
  --title: #707070;
  --disabled: #ffcaca;
  --black: white;
  --white: black;
}

html {
  color-scheme: var(--color-scheme);
}

.bigText {
  font-size: 30px;
}

.smallText {
  font-size: 14px;
}

body {
  background-color: var(--background);
  font-family: sans-serif;
}

.center {
  text-align: center;
}

.inline {
  display: inline-block;
}

.container {
  margin: auto;
}

.projectPreview {
  position: relative;
}

.projectPreviewImage {
  position: absolute;
  left: -90px;
  width: 80px;
  top: 50%;
  transform: translateY(-50%);
}

.projectPreviewImageLarge {
  position: absolute;
  left: -210px;
  width: 200px;
  top: 50%;
  transform: translateY(-20%);
}

.createdBy {
  font-size: 14px;
  text-align: center;
  padding-top: 0px;
  padding-bottom: 0px;
}

#title {
  background-color: #636363;

  text-align: center;
  vertical-align: middle;

  font-size: 50px;
  color: var(--header-color);

  padding: 20px;
  
  text-decoration: none;

  border-radius: 15px;

  transition: font-size 1s;
}

.subtitle {
  font-size: 40px;
  color: #dad8d8;

  padding-top: 10px;

  transition: font-size 0.4s;
}

.subtitle:hover {
  font-size: 45px;

  transition: font-size 0.4s;
}

.profilepic {
  background-color: #636363 !important;
  vertical-align: middle;
}

.profilepiccircle {
  vertical-align: middle;
  overflow: hidden;
  border-radius: 50%;
}

a {
  text-decoration: underline;
  color: inherit;
}

.link {
  padding: 20px;

  height: 80px;

  transition: height 0.2s;
}

.link:hover {
  height: 95px;

  transition: height 0.2s;
}

#contact,.smalllink {
  font-size: 25px;
  color: #e8e8e8;

  text-align: center;
  
  padding: 10px;
}

#contact {
  text-decoration: none;
}

p,li {
  font-size: 16px;
}

p,li,a,span,div {
  color: var(--text);
}

p,li,code,a {
	text-align: left;
	overflow-wrap: break-word;
}

.optionsFrame {
  width: 100%;
  height: 100%;
}

.previewImage {
	max-height: 200px;
}

img {
	max-width: 100%;

	text-align: center;
}

#recentPostTitle {
  font-size: 30px;
  color: #dad8d8;
}

#recentPostDate {
  font-size: 15px;
  color: #dad8d8;
}

h1,h2,h3,h4,h5,h6 {
  color: var(--title);
  text-align: center;

  font-size: 25px;
  margin: 5px 0px;
}

svg {
 text-decoration: none; 
}

.donate-ask {
  background-color: rgb(26, 26, 26, 0.95);
  border-radius: 15px;

  text-align: center;
  padding: 10px;

  margin: 0.7em 0px;
}

.donate-ask .donate-text {
  margin-top: 10px;
  margin-bottom: 10px;

  display: flex;
  align-items: center;
  justify-content: center;
}

.donate-ask .donate-text img {
  height: 2rem;
  border-radius: 100%;

  margin-right: 15px;
}

.donate-ask a {
  text-decoration: none;
  color: #eee;
  border-radius: 15px;
  background-color: rgb(58, 58, 58, 0.9);
  padding: 10px;

  transition: background-color 0.3s ease;

  display: block;
  width: fit-content;
  margin: auto;
  margin-top: 10px;
  margin-bottom: 10px;
}

.donate-ask a:hover {
  background-color: rgba(70, 70, 70, 0.9);
}

@media screen and (orientation:portrait) {
  .projectPreviewImage {
    position: unset;
    width: 50%;
    display: block;
    margin: auto;
    transform: none;
  }

  .projectPreviewImageLarge {
    position: unset;
    left: 0;
    width: 50%;
    display: block;
    margin: auto;
    transform: unset;
  }

  .container {
    max-width: 100%;
    margin: 5px;
    text-align: center;
  }

  p,li,code,a {
    text-align: center;
  }
}

/* keybind dialog */
.key {
  border-width: 1px;
  border-style: solid;
  border-radius: 5px;
  display: inline-block;
  min-width: 33px;
  text-align: center;
  font-weight: bold;
  border-color: var(--white);
  box-sizing: border-box;
}

.unbound, .key {
  padding: 8px;
}

#keybind-dialog .dialog {
  position: fixed;
  border-width: 3px;
  border-style: solid;
  border-radius: 15px;
  max-height: 100vh;
  width: 400px;
  overflow-x: auto;
  z-index: 100;
  padding: 15px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 14px;
  background-color: var(--dialog-background);
  border-color: var(--dialog-border);
}

#change-keybind-buttons {
  float: right;
}

#change-keybind-buttons > .option-button {
  margin: 0 2px;
}

#change-keybind-settings {
  margin: 15px 15px 30px;
}

#change-keybind-settings .key {
  vertical-align: top;
  margin: 15px 0 0 40px;
  height: 34px;
}

#change-keybind-error {
  margin-bottom: 15px;
  color: red;
  font-weight: bold;
}

.blocker {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 90;
  background-color: #00000080;
}

.option-button {
  cursor: pointer;

  background-color: #c00000;
  padding: 10px;
  color: white;
  border-radius: 5px;
  font-size: 14px;

  width: max-content;
}

.option-button:hover:not(.disabled) {
  background-color: #fc0303;
}

.option-button.disabled {
  cursor: default;
  background-color: var(--disabled);
  color: grey;
}

.dearrow-link {
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;

  font-size: 16px;
}

.dearrow-link img {
  width: 35px;
  padding: 10px
}

.dearrow-link .close-button {
  opacity: 0;
  width: 15px;
  filter: invert(0.3);
  transition: opacity 0.2s;
}

.dearrow-link:hover .close-button {
  opacity: 1;
}

.hidden {
  display: none;
}

.help-page-flex-container {
  display: flex;
  flex-direction: row;
  gap: 20px;
  margin-left: 20px;
  margin-right: 20px;
}

.left-sidebar {
  display: flex;
  flex-direction: column;

  flex: 1 1 50%;
}

.box2 {
  flex: 1 1 50%;
}

/* Mobile */
@media only screen and (max-width: 600px) {
  .box1 {
    order: 1;
  }

  .box2 {
    order: 2;
  }

  .box3 {
    order: 3;
  }

  .left-sidebar {
    display: contents;
  }

  .help-page-flex-container {
    flex-direction: column;
  }

  .optionsFrame {
    height: 500px;
  }
}

================================================
FILE: public/libs/Source+Sans+Pro.css
================================================
/* cyrillic-ext */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(6xK3dSBYKcSV-LCoeQqfX1RYOo3qNa7lqDY.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(6xK3dSBYKcSV-LCoeQqfX1RYOo3qPK7lqDY.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(6xK3dSBYKcSV-LCoeQqfX1RYOo3qNK7lqDY.woff2) format('woff2');
  unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(6xK3dSBYKcSV-LCoeQqfX1RYOo3qO67lqDY.woff2) format('woff2');
  unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(6xK3dSBYKcSV-LCoeQqfX1RYOo3qN67lqDY.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(6xK3dSBYKcSV-LCoeQqfX1RYOo3qNq7lqDY.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 700;
  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmhduz8A.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 700;
  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwkxduz8A.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 700;
  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmxduz8A.woff2) format('woff2');
  unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 700;
  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2) format('woff2');
  unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 700;
  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 700;
  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 700;
  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


================================================
FILE: public/options/options.css
================================================
/* Options page CSS */

:root {
    --color-scheme: dark;
    --background: #333333;
    --menu-background: #181818;
    --menu-foreground: white;
    --dialog-background: #181818;
    --dialog-border: white;
    --tab-color: #242424;
    --tab-button-hover: #4d0000;
    --tab-hover: white;
    --description: #dfdfdf;
    --disabled: #520000;
    --slider: #707070;
    --title: #dad8d8;
    --border-color: #484848;
    --black: black;
    --white: white;

    --selector-red: #c00000;
    --selector-red-hover: #fc0303;
    --tab-selected: #950000;
}

[data-theme="light"] {
    --color-scheme: light;
    --background: #f9f9f9;
    --menu-background: #dbdbdb;
    --menu-foreground: #212121;
    --dialog-background: #f9f9f9;
    --dialog-border: #282828;
    --tab-color: #ababab;
    --tab-button-hover: #750000;
    --tab-hover: #2e2e2e;
    --description: #262626;
    --disabled: #ffcaca;
    --slider: #bfbebe;
    --title: #707070;
    --border-color: #d9d9d9;
    --black: white;
    --white: black;
}

[data-theme="pride"] {
    --menu-background: #181818d0;
}

.medium-description, .switch-container, .optionLabel, .categoryTableElement, .promotion-description {
    color: var(--white);
}

.small-description, p, li, span, div {
    color: var(--description);
}

h1,h2,h3,h4,h5,h6 {
    color: var(--title);
}

html, body {
    color-scheme: var(--color-scheme);
    font-family: sans-serif;
    margin: 0;
    font-size: 14px;
    background-color: var(--background);
}

[data-theme="pride"] body {
    background: url("../icons/pride.svg");
    background-size: contain;
}


* {
    box-sizing: border-box;
}

#options-container {
    display: flex;
}

#menubar {
    display: flex;
    flex-direction: column;
    gap: 20px;
    flex-basis: 20%;
    min-width: 300px;
    max-width: 600px;
    border-radius: 15px;
    margin: 15px;
    z-index: 10;
    background-color: var(--menu-background);
    color: var(--menu-foreground);
}

#navigation {
    display: flex;
    flex-direction: column;
    gap: 30px;
}

.tab-heading {
    font-size: 18px;
    height: 55px;
    line-height: 55px;
    width: 80%;
    margin: 0 auto;
    border-radius: 15px;
    cursor: pointer;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
    background-color: var(--tab-color);
    color: var(--white);
}

.tab-heading:hover {
    background-color: var(--tab-button-hover);
    color: white;
}

.tab-heading.selected {
    background-color: var(--selector-red);
    color: white;
}

.tab-heading:active {
    background-color: var(--tab-selected);
    color: white;
}

[data-theme="pride"] .tab-heading:nth-of-type(1) {
    background-color: #2f0000;
}
[data-theme="pride"] .tab-heading:nth-of-type(2) {
    background-color: #3a2000;
}
[data-theme="pride"] .tab-heading:nth-of-type(3) {
    background-color: #3e3a00;
}
[data-theme="pride"] .tab-heading:nth-of-type(4) {
    background-color: #003e13;
}
[data-theme="pride"] .tab-heading:nth-of-type(5) {
    background-color: #00164a;
}

[data-theme="pride"] .tab-heading:hover:nth-of-type(1) {
    background-color: #550000;
}
[data-theme="pride"] .tab-heading:hover:nth-of-type(2),
[data-theme="pride"] #category-type tr:nth-of-type(5n) .slider,
[data-theme="pride"] [data-type="toggle"]:nth-of-type(5n) .slider {
    background-color: #824700;
}
[data-theme="pride"] .tab-heading:hover:nth-of-type(3),
[data-theme="pride"] #category-type tr:nth-of-type(5n + 1) .slider,
[data-theme="pride"] [data-type="toggle"]:nth-of-type(5n + 1) .slider {
    background-color: #867d00;
}
[data-theme="pride"] .tab-heading:hover:nth-of-type(4),
[data-theme="pride"] #category-type tr:nth-of-type(5n + 2) .slider,
[data-theme="pride"] [data-type="toggle"]:nth-of-type(5n + 2) .slider {
    background-color: #00691f;
}
[data-theme="pride"] .tab-heading:hover:nth-of-type(5),
[data-theme="pride"] #category-type tr:nth-of-type(5n + 3) .slider,
[data-theme="pride"] [data-type="toggle"]:nth-of-type(5n + 3) .slider {
    background-color: #002374;
}
[data-theme="pride"] #category-type tr:nth-of-type(5n + 4) .slider,
[data-theme="pride"] [data-type="toggle"]:nth-of-type(5n + 4) .slider {
    background-color: #400449;
}

[data-theme="pride"] #category-type tr .optionsSelector {
    color: var(--white);
}
[data-theme="pride"] .tab-heading:nth-of-type(1).selected {
    background-color: #E40303;
}
[data-theme="pride"] .tab-heading:nth-of-type(2).selected,
[data-theme="pride"] #category-type tr:nth-of-type(10n + 2) .optionsSelector,
[data-theme="pride"] #category-type tr:nth-of-type(5n) input:checked + .slider,
[data-theme="pride"] [data-type]:nth-of-type(5n) :is(input:checked + .slider, .option-button, .optionsSelector) {
    background-color: #dd7a00;
}
[data-theme="pride"] .tab-heading:nth-of-type(3).selected,
[data-theme="pride"] #category-type tr:nth-of-type(10n + 4) .optionsSelector,
[data-theme="pride"] #category-type tr:nth-of-type(2n + 1) .optionsSelector,
[data-theme="pride"] #category-type tr:nth-of-type(5n + 1) input:checked + .slider,
[data-theme="pride"] [data-type]:nth-of-type(5n + 1) :is(input:checked + .slider, .option-button, .optionsSelector) {
    background-color: #FFED00;
    color: rgb(23, 23, 23);
}
[data-theme="pride"] .tab-heading:nth-of-type(4).selected,
[data-theme="pride"] #category-type tr:nth-of-type(10n + 6) .optionsSelector,
[data-theme="pride"] #category-type tr:nth-of-type(5n + 2) input:checked + .slider,
[data-theme="pride"] [data-type]:nth-of-type(5n + 2) :is(input:checked + .slider, .option-button, .optionsSelector) {
    background-color: #008026;
}
[data-theme="pride"] .tab-heading:nth-of-type(5).selected,
[data-theme="pride"] #category-type tr:nth-of-type(10n + 8) .optionsSelector,
[data-theme="pride"] #category-type tr:nth-of-type(5n + 3) input:checked + .slider,
[data-theme="pride"] [data-type]:nth-of-type(5n + 3) :is(input:checked + .slider, .option-button, .optionsSelector) {
    background-color: #004DFF;
}
[data-theme="pride"] .tab-heading:nth-of-type(5).selected,
[data-theme="pride"] #category-type tr:nth-of-type(10n + 10) .optionsSelector,
[data-theme="pride"] #category-type tr:nth-of-type(5n + 4) input:checked + .slider,
[data-theme="pride"] [data-type]:nth-of-type(5n + 4) :is(input:checked + .slider, .option-button, .optionsSelector) {
    background-color: #750787;
}


.option-group > div, .extraOptionGroup {
    min-height: 50px;
    padding: 15px 0;
    border-image: linear-gradient(to right, var(--border-color), #00000000 80%)  1;
}
.option-group > div {
    border-bottom: 1px solid var(--border-color);
}
.extraOptionGroup {
    border-top: 1px solid var(--border-color);
}
.extraOptionGroup tr:not(:last-child) {
    padding-bottom: 15px;
    display: block;
}
#category-type {
    padding: 0;
}

#category-type .categoryExtraOptions {
    padding-bottom: 15px;
}

#music_offtopic_autoSkipOnMusicVideos {
    padding-bottom: 0;
}

.option-group > div:last-child, .option-group > #keybind-dialog {
    border-bottom: inherit;
}

.optionLabel, #version {
    font-size: 14px;
    height: 15px;
}

div[data-type="keybind-change"] .optionLabel {
    display: inline-block;
    min-width: 150px;
    margin-right: 20px;
}

input[type='number'] {
    width: 50px;
}

.key {
    border-width: 1px;
    border-style: solid;
    border-radius: 5px;
    display: inline-block;
    min-width: 33px;
    text-align: center;
    font-weight: bold;
    border-color: var(--white);
}

.unbound, .key {
    padding: 8px;
}

.keybind-buttons {
    border-radius: 5px;
    padding: 5px 3px;
    cursor: pointer;
    margin-right: 10px;
}

.keybind-buttons:hover {
    background-color: #00000030;
}

.keybind-buttons > div, .keybind-buttons > span {
    margin: 0 2px;
}

#keybind-dialog .dialog {
    position: fixed;
    border-width: 3px;
    border-style: solid;
    border-radius: 15px;
    max-height: 100vh;
    width: 400px;
    overflow-x: auto;
    z-index: 100;
    padding: 15px;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background-color: var(--dialog-background);
    border-color: var(--dialog-border);
}

#change-keybind-buttons {
    float: right;
}

#change-keybind-buttons > .option-button {
    margin: 0 2px;
}

#change-keybind-settings {
    margin: 15px 15px 30px;
}

#change-keybind-settings .key {
    vertical-align: top;
    margin: 15px 0 0 40px;
    height: 34px;
}

#change-keybind-error {
    margin-bottom: 15px;
    color: red;
    font-weight: bold;
}

.blocker {
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    z-index: 90;
    background-color: #00000080;
}

.low-profile {
    height: 23px;
    line-height: 5px;
    vertical-align: middle;
}

.center {
    text-align: center;
}

.inline {
    display: inline-block;
}

.next-line {
    padding: 15px 0 0 0;
}

.bold {
    font-weight: bold;
}

.hiding {
    opacity: 0;
}

.hidden, .sbhidden {
    display: none !important;
}

.spacing {
    margin-top: 15px;
}

.keybind-status {
    display: inline;
}

.small-description {
    font-size: 13px;
    padding: 5px 0 0 20px;
}

.small-description td {
    padding: 2.5px 0 10px 20px;
}

.indent {
    padding-left: 20px;
}

.categoryTableElement td {
    padding-top: 5px;
    border-top: 1px solid var(--border-color);
}

.medium-description {
    font-size: 15px;
}

.option-text-box {
    width: 300px;
}

.option-button {
    cursor: pointer;

    background-color: var(--selector-red);
    padding: 10px;
    color: white;
    border-radius: 5px;
    font-size: 14px;

    width: max-content;
}

.option-button:hover:not(.disabled) {
    background-color: var(--selector-red-hover);
}

.option-button.disabled {
    cursor: default;
    background-color: var(--disabled);
    color: grey;
}

.sb-toggle-option.disabled .slider {
    cursor: default;
}

/* To hide everything except upsell button */
.disabled td:not(.skipOption, .categoryExtraOptions), .disabled td.skipOption > :not(.upsellButton) {
    opacity: 0.3;
}

#options {
    height: 100vh;
    flex-basis: 80%;
    overflow: auto;
    text-align: left;
    padding: 80px 15% 0 3%;
    box-sizing: border-box;
    display: flex;
    justify-content: center;

    transition: padding 0.3s;
}

#options.embed > div {
    max-width: 100%;
}

#title .profilepic {
    height: 60px;
}

.switch-container {
	content: attr(label-name);
    width: max-content;

    font-size: 14px;

    display: flex;
    align-items: center;
}

.switch-container .switch-label {
    display: table-cell;
    vertical-align: middle;

    padding: 4px;
}

.sb-number-input {
    margin-left: 4px;
    margin-right: 4px;
}

.switch-label {
    width: inherit;
}

.switch {
    position: relative;
    display: inline-block;
    width: 40px;
    height: 24px;
    min-width: 40px;
}

.switch input { 
    opacity: 0;
    width: 0;
    height: 0;
}

.slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: var(--slider);
}

.animated * {
    -webkit-transition: .4s;
    transition: .4s;
}

.slider:before {
    position: absolute;
    content: "";
    height: 16px;
    width: 16px;
    left: 4px;
    bottom: 4px;
    background-color: white;
}

.animated .slider:before {
    -webkit-transition: .4s;
    transition: .4s;
}

input:checked + .slider {
    background-color: var(--selector-red-hover);
}

input:checked + .slider:before {
    -webkit-transform: translateX(16px);
    -ms-transform: translateX(16px);
    transform: translateX(16px);
}

/* Rounded sliders */
.slider.round {
    border-radius: 34px;
}

.slider.round:before {
    border-radius: 50%;
}



/* Boilerplate CSS from https://ajay.app (edited) */

.projectPreview {
    position: relative;
}

.projectPreviewImage {
    position: absolute;
    left: -90px;
    width: 80px;
    top: 50%;
    transform: translateY(-50%);
}

.projectPreviewImageLarge {
    position: absolute;
    left: -210px;
    width: 200px;
    top: 50%;
    transform: translateY(-20%);
}

.projectPreviewImageLargeRight {
    position: absolute;
    right: -210px;
    width: 200px;
    top: 50%;
    transform: translateY(-50%);
}

#createdBy {
    text-align: center;
    margin: auto 0 10px 0;
    height: 50px;
}

#createdBy > * {
    font-size: 14px;
}

#title {
    text-align: center;
    vertical-align: middle;
  
    font-size: 40px;
  
    padding: 40px 20px;
  
    text-decoration: none;
}

.subtitle {
    font-size: 40px;
    color: #dad8d8;
  
    padding-top: 10px;
  
    transition: font-size 0.4s;
}

.subtitle:hover {
    font-size: 45px;
  
    transition: font-size 0.4s;
}

.profilepic {
    vertical-align: middle;
}

.profilepiccircle {
    vertical-align: middle;
    overflow: hidden;
    border-radius: 50%;
}

a {
    text-decoration: underline;
    color: inherit;
}

.link {
    padding: 20px;
  
    height: 80px;
  
    transition: height 0.2s;
}

.link:hover {
    height: 95px;
  
    transition: height 0.2s;
}

#contact,.smalllink {
    font-size: 25px;
    color: #e8e8e8;
  
    text-align: center;
  
    padding: 10px;
}

#contact {
    text-decoration: none;
}

p,li {
    font-size: 20px;
    padding: 10px;
}

.previewImage {
    max-height: 200px;
}

img {
    max-width: 100%;
  
    text-align: center;
}

#recentPostTitle {
    font-size: 30px;
    color: #dad8d8;
}

#recentPostDate {
    font-size: 15px;
    color: #dad8d8;
}

svg {
    text-decoration: none; 
}

.number-container:before {
    content: attr(label-name);
    padding-right: 4px;
    width: max-content;

    font-size: 14px;
    color: white;
}

/* React styles */

.categoryTableElement {
    font-size: 16px;
}

.categoryTableElement > * {
    padding-right: 15px;
}

.categoryTableDescription > * {
    padding-bottom: 15px;
}

.optionsSelector {
    background-color: var(--selector-red);
    color: white;
    
    border: none;
    font-size: 14px;
    padding: 5px;
    border-radius: 5px;
}

.categoryColorTextBox {
    width: 60px;

    background: none;
    border: none;
}

#sbDonate {
    font-size: 10px;
}


/* Top bar navigation for smaller screens */
@media only screen and (max-height: 725px), only screen and (max-width: 1200px) {
    #options-container {
        flex-direction: column;
    }
    #menubar {
        gap: 8px;
        min-width: unset;
        max-width: unset;
        padding: 8px;
    }
    #navigation {
        gap: 8px;
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: center;
    }
    #options {
        padding: 0 50px;
    }

    #options > div {
        max-width: 70%;
    }

    .tab-heading {
        width: unset;
        min-width: unset;
        height: 35px;
        line-height: 35px;
        font-size: 16px;
        padding: 0 10px;
        margin: 0;
    }
    #title {
        width: 100%;
        font-size: 30px;
        padding: 10px;
    }
    #title .profilepic {
        height: 40px;
    }
    #createdBy {
        margin: 10px 0 0 0;
        height: unset;
        width: 100%;
    }
    #createdBy > div {
        display: inline-block;
    }
    #sbDonate {
        position: absolute;
        right: 30px;
        margin-top: 10px;
    }
    #version {
        font-size: 10px;
        height: 10px;
        transform: translate(-50px, -5px);
    }
    .sticky #menubar {
        position: fixed;
        left: 0;
        right: 0;
        margin: 0 15px;
    }
    .sticky #title, .sticky #createdBy {
        display: none;
    }
}

@media only screen and (max-width: 800px) {
    #options {
        padding: 0 15px;
    }
    #options > div {
        max-width: 100%;
    }
}

.upsellButton {
    cursor: pointer;
    vertical-align: middle;
}

.no-bottom-border {
    border: none !important;
    padding: 20px 0px 0px 0px !important;
}

.promotion-container {
    width: fit-content;
}

.dearrow-link {
    display: flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
}

.dearrow-link > img {
    width: 40px;
    margin-right: 4px;
}

.dearrow-link .close-button {
    opacity: 0;
    width: 15px;
    filter: invert(0.3);
    transition: opacity 0.2s;
    margin-left: 10px;
}
  
.dearrow-link:hover .close-button {
    opacity: 1;
}

.invalid-advanced-config {
    color: red;
}

.advanced-skip-options-menu {
    margin-top: 10px;
}

.advanced-config-help-message {
    margin-bottom: 10px;
    transition: none;
}

.categoryChooserTopRow {
    display: flex;
    align-items: center;
    justify-content: space-between;

    margin-bottom: 10px;
}

.partiallyHidden {
    opacity: 0.5;
}

.partiallyHidden:hover {
    opacity: 1;
}

.reset-button svg {
    margin-left: 5px;
    width: 10px;
    fill: var(--white);

    cursor: pointer;
}

.skipProfileMenu {
    position: absolute;
}

.configurationInfo > *:not(:last-child) {
    margin-bottom: 10px;
}

.configurationInfo .option-text-box {
    width: 100%;
}

================================================
FILE: public/options/options.html
================================================
<!DOCTYPE html>
<!-- Link to specific tabs by using their ID in the URL like: options.html#keybinds -->

<head>
  <title>__MSG_Options__ - SponsorBlock</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" /> 
  <link rel="icon" href="../icons/IconSponsorBlocker32px.png" type="image/png">
  
  <link href="options.css" rel="stylesheet"/>

  <script src="../js/options.js"></script>
</head>

<body class="sponsorBlockPageBody">

	<div id="options-container">

		<div id="menubar" class="center">

			<div id="title" class="titleBar">
				<img id="title-bar-logo" src="../icons/LogoSponsorBlocker256px.png" class="profilepic" alt="SponsorBlock logo"/>
				SponsorBlock
				<div id="version"></div>
			</div>

			<div id="navigation">
				<div class="tab-heading" data-for="behavior">
					__MSG_optionsTabBehavior__
				</div>

				<div class="tab-heading" data-for="interface">
					__MSG_optionsTabInterface__
				</div>

				<div class="tab-heading" data-for="keybinds">
					__MSG_optionsTabKeyBinds__
				</div>

				<div class="tab-heading" data-for="import">
					__MSG_optionsTabBackup__
				</div>

				<div class="tab-heading" data-for="advanced">
					__MSG_optionsTabAdvanced__
				</div>
		</div>

			<div id="createdBy" class="titleBar">
				<div>
					<img src="../icons/newprofilepic.jpg" height="30" class="profilepiccircle" alt="profile picture of creator"/>
					__MSG_createdBy__ 
					<a href="https://ajay.app">Ajay Ramachandran</a> 
				</div>
				<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">(__MSG_Donate__)</a>
			</div>

		</div>

		<div id="options">

			<div id="behavior" class="option-group hidden">

				<div id="category-type" data-type="react-CategoryChooserComponent">

				</div>

				<div id="deArrowPromotion" class="promotion-container hidden">
					<a class="dearrow-link"
                        href="https://dearrow.ajay.app"
                        target="_blank"
                        rel="noreferrer">
                        <img src="/icons/dearrow.svg"/>

                        <span class="promotion-description">
                            __MSG_DeArrowPromotionMessage__
                        </span>

						<img src="/icons/close.png" class="close-button"/>
                    </a>
				</div>

				<div data-type="react-AdvancedSkipOptionsComponent" class="hide-when-skip-profile"></div>

				<div data-type="toggle" data-sync="forceChannelCheck" class="hide-when-skip-profile">
					<div class="switch-container">
						<label class="switch">
							<input id="forceChannelCheck" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="forceChannelCheck">
							__MSG_forceChannelCheck__
						</label>
					</div>
	
					<div class="small-description">__MSG_whatForceChannelCheck__</div>
				</div>

				<div data-type="toggle" data-sync="showCategoryWithoutPermission" class="hide-when-skip-profile">
					<div class="switch-container">
						<label class="switch">
							<input id="showCategoryWithoutPermission" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="showCategoryWithoutPermission">
							__MSG_enableShowCategoryWithoutPermission__
						</label>
					</div>
	
					<div class="small-description">__MSG_whatShowCategoryWithoutPermission__</div>
				</div>

				<div id="ytNeuterPromotion" class="promotion-container">
					<a class="dearrow-link"
                        href="https://github.com/mchangrh/yt-neuter/blob/main/docs/filters/sponsorblock.md#install"
                        target="_blank"
                        rel="noreferrer">

                        <span class="promotion-description">
                            __MSG_YtNeuterMessage__
                        </span>
                    </a>

					<div class="small-description">__MSG_requiresUblock__</div>
				</div>

			</div>

			<div id="interface" class="option-group hidden">

				<div data-type="number-change" data-sync="skipNoticeDuration">
					<label class="number-container">
						<span class="optionLabel">__MSG_skipNoticeDuration__</span>
						<input type="number" step="1" min="1">
					</label>
	
					<div class="small-description">__MSG_skipNoticeDurationDescription__</div>
				</div>
	
				<div>
					<div data-type="toggle" data-sync="showUpcomingNotice">
						<div class="switch-container">
							<label class="switch">
								<input id="showUpcomingNotice" type="checkbox" checked>
								<span class="slider round"></span>
							</label>
							<label class="switch-label" for="showUpcomingNotice">
								__MSG_showUpcomingNotice__
							</label>
						</div>
					</div>

					<br/>

					<div data-type="toggle" data-toggle-type="reverse" data-sync="dontShowNotice">
						<div class="switch-container">
							<label class="switch">
								<input id="dontShowNotice" type="checkbox" checked>
								<span class="slider round"></span>
							</label>
							<label class="switch-label" for="dontShowNotice">
								__MSG_showSkipNotice__
							</label>
						</div>
					</div>

					<div data-type="selector" data-sync="noticeVisibilityMode" data-dependent-on="dontShowNotice">
						<br/>
						
						<label class="optionLabel" for="noticeVisibilityMode">__MSG_noticeVisibilityLabel__:</label>
	
						<select id="noticeVisibilityMode" class="selector-element optionsSelector" >
							<option value="0">__MSG_noticeVisibilityMode0__</option>
							<option value="1">__MSG_noticeVisibilityMode1__</option>
							<option value="2">__MSG_noticeVisibilityMode2__</option>
							<option value="3">__MSG_noticeVisibilityMode3__</option>
							<option value="4">__MSG_noticeVisibilityMode4__</option>
						</select>
					</div>
				</div>
	
				<div data-type="toggle" data-sync="showCategoryGuidelines">
					<div class="switch-container">
						<label class="switch">
							<input id="showCategoryGuidelines" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="showCategoryGuidelines">
							__MSG_showCategoryGuidelines__
						</label>
					</div>
				</div>
	
				<div data-type="toggle" data-toggle-type="reverse" data-sync="hideVideoPlayerControls">
					<div class="switch-container">
						<label class="switch">
							<input id="hideVideoPlayerControls" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="hideVideoPlayerControls">
							__MSG_showButtons__
						</label>
					</div>
	
					<div class="small-description">__MSG_hideButtonsDescription__</div>
				</div>

				<div data-type="toggle" data-toggle-type="reverse" data-sync="hideDeleteButtonPlayerControls" data-dependent-on="hideVideoPlayerControls">
					<div class="switch-container">
						<label class="switch">
							<input id="hideDeleteButtonPlayerControls" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="hideDeleteButtonPlayerControls">
							__MSG_showDeleteButton__
						</label>
					</div>
				</div>
	
				<div data-type="toggle" data-toggle-type="reverse" data-sync="hideUploadButtonPlayerControls" data-dependent-on="hideVideoPlayerControls">
					<div class="switch-container">
						<label class="switch">
							<input id="hideUploadButtonPlayerControls" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="hideUploadButtonPlayerControls">
							__MSG_showUploadButton__
						</label>
					</div>
				</div>
	
				<div data-type="toggle" data-toggle-type="reverse" data-sync="hideSkipButtonPlayerControls">
					<div class="switch-container">
						<label class="switch">
							<input id="hideSkipButtonPlayerControls" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="hideSkipButtonPlayerControls">
							__MSG_showSkipButton__
						</label>
					</div>
				</div>
	
				<div data-type="toggle" data-toggle-type="reverse" data-sync="hideInfoButtonPlayerControls">
					<div class="switch-container">
						<label class="switch">
							<input id="hideInfoButtonPlayerControls" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="hideInfoButtonPlayerControls">
							__MSG_showInfoButton__
						</label>
					</div>
				</div>
	
				<div data-type="toggle" data-sync="autoHideInfoButton" data-dependent-on="hideInfoButtonPlayerControls">
					<div class="switch-container">
						<label class="switch">
							<input id="autoHideInfoButton" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="autoHideInfoButton">
							__MSG_autoHideInfoButton__
						</label>
					</div>
				</div>

				<div data-type="toggle" data-sync="allowScrollingToEdit">
					<div class="switch-container">
						<label class="switch">
							<input id="allowScrollingToEdit" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="allowScrollingToEdit">
							__MSG_allowScrollingToEdit__
						</label>
					</div>
				</div>
	
				<div data-type="toggle" data-sync="audioNotificationOnSkip">
					<div class="switch-container">
						<label class="switch">
							<input id="audioNotificationOnSkip" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="audioNotificationOnSkip">
							__MSG_audioNotification__
						</label>
					</div>
	
					<div class="small-description">__MSG_audioNotificationDescription__</div>
				</div>

				<div data-type="toggle" data-sync="showTimeWithSkips">
					<div class="switch-container">
						<label class="switch">
							<input id="showTimeWithSkips" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="showTimeWithSkips">
							__MSG_showTimeWithSkips__
						</label>
					</div>
	
					<div class="small-description">__MSG_showTimeWithSkipsDescription__</div>
				</div>

				<div>
					<div data-type="toggle" data-sync="cleanPopup">
						<div class="switch-container">
							<label class="switch">
								<input id="cleanPopup" type="checkbox" checked>
								<span class="slider round"></span>
							</label>
							<label class="switch-label" for="cleanPopup">
								__MSG_cleanPopup__
							</label>
						</div>
					</div>

					<br/>

					<div data-type="toggle" data-sync="hideSegmentCreationInPopup">
						<div class="switch-container">
							<label class="switch">
								<input id="hideSegmentCreationInPopup" type="checkbox" checked>
								<span class="slider round"></span>
							</label>
							<label class="switch-label" for="hideSegmentCreationInPopup">
								__MSG_hideSegmentCreationInPopup__
							</label>
						</div>
					</div>

					<br />

					<div data-type="selector" data-sync="segmentListDefaultTab">
						<label class="optionLabel" for="segmentListDefaultTab">__MSG_segmentListDefaultTab__:</label>

						<select id="segmentListDefaultTab" class="selector-element optionsSelector">
							<option value="0">__MSG_SegmentsCap__</option>
							<option value="1">__MSG_Chapters__</option>
						</select>
					</div>
				</div>

				<div data-type="toggle" data-sync="darkMode">
					<div class="switch-container">
						<label class="switch">
							<input id="darkMode" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="darkMode">
							__MSG_darkModeOptionsPage__
						</label>
					</div>
				</div>

				<div data-type="toggle" data-sync="prideTheme">
					<div class="switch-container">
						<label class="switch">
							<input id="prideTheme" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="prideTheme">
							__MSG_prideTheme__
						</label>
					</div>
				</div>

				<div data-type="toggle" data-toggle-type="reverse" data-sync="showNewFeaturePopups">
					<div class="switch-container">
						<label class="switch">
							<input id="showNewFeaturePopups" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="showNewFeaturePopups">
							__MSG_hideNewFeatureUpdates__
						</label>
					</div>
				</div>

				<div data-type="toggle" data-toggle-type="reverse" data-sync="showDonationLink" data-no-safari="true">
					<div class="switch-container">
						<label class="switch">
							<input id="showDonationLink" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="showDonationLink">
							__MSG_hideDonationLink__
						</label>
					</div>
				</div>

				<div data-type="toggle" data-toggle-type="reverse" data-sync="showUpsells" data-no-safari="true">
					<div class="switch-container">
						<label class="switch">
							<input id="showUpsell" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="showUpsells">
							__MSG_hideUpsells__
						</label>
					</div>
				</div>

			</div>

			<div id="keybinds" class="option-group hidden">

				<div data-type="keybind-change" data-sync="skipKeybind">	
					<label class="optionLabel">__MSG_setSkipShortcut__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="skipToHighlightKeybind">	
					<label class="optionLabel">__MSG_skip_to_category__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="closeSkipNoticeKeybind">	
					<label class="optionLabel">__MSG_setCloseSkipNoticeKeybind__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="startSponsorKeybind">	
					<label class="optionLabel">__MSG_setStartSponsorShortcut__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="submitKeybind">	
					<label class="optionLabel">__MSG_setOpenSubmissionMenuKeybind__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="previewKeybind">	
					<label class="optionLabel">__MSG_setPreviewKeybind__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="actuallySubmitKeybind">	
					<label class="optionLabel">__MSG_setSubmitKeybind__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="nextChapterKeybind">	
					<label class="optionLabel">__MSG_nextChapterKeybind__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="previousChapterKeybind">	
					<label class="optionLabel">__MSG_previousChapterKeybind__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="upvoteKeybind">	
					<label class="optionLabel">__MSG_setUpvoteKeybind__:</label>
					<div class="inline"></div>
				</div>

				<div data-type="keybind-change" data-sync="downvoteKeybind">	
					<label class="optionLabel">__MSG_setDownvoteKeybind__:</label>
					<div class="inline"></div>
				</div>
				
			</div>

			<div id="import" class="option-group hidden">

				<div data-type="private-text-change" data-sync="userID" data-confirm-message="userIDChangeWarning">
					<div class="option-button trigger-button">
						__MSG_changeUserID__
					</div>
	
					<div class="small-description">__MSG_whatChangeUserID__</div>
	
					<div class="option-hidden-section hidden spacing indent">
						<input class="option-text-box" type="text">
	
						<div class="option-button text-change-set inline low-profile">
							__MSG_setUserID__
						</div>
					</div>
				</div>

				<div data-type="react-UnsubmittedVideosComponent"></div>
	
				<div data-type="private-text-change" data-sync="*" data-confirm-message="exportOptionsWarning">
					<h2>__MSG_exportOptions__</h2>
					
					<div>
						<div class="option-button trigger-button inline">
							__MSG_exportOptionsCopy__
						</div>
						<div class="option-button download-button inline">
							__MSG_exportOptionsDownload__
						</div>
						<label for="importOptions" class="option-button inline">
							__MSG_exportOptionsUpload__
						</label>
						<input id="importOptions" type="file" class="upload-button hidden" />
					</div>
	
					<div class="small-description">__MSG_whatExportOptions__</div>
	
					<div class="option-hidden-section hidden spacing indent">	
						<textarea class="option-text-box" rows="10" style="width:80%"></textarea>
	
						<div class="option-button text-change-set">
							__MSG_setOptions__
						</div>
					</div>
				</div>

				<div data-type="private-text-change" data-sync-type="local" data-sync="*" data-confirm-message="exportOptionsWarning">
					<h2>__MSG_exportOtherData__</h2>
					
					<div>
						<div class="option-button trigger-button inline">
							__MSG_exportOptionsCopy__
						</div>
						<div class="option-button download-button inline">
							__MSG_exportOptionsDownload__
						</div>
						<label for="importLocalOptions" class="option-button inline">
							__MSG_exportOptionsUpload__
						</label>
						<input id="importLocalOptions" type="file" class="upload-button hidden" />
					</div>
	
					<div class="option-hidden-section hidden spacing indent">	
						<textarea class="option-text-box" rows="10" style="width:80%"></textarea>
	
						<div class="option-button text-change-set">
							__MSG_setOptions__
						</div>
					</div>
				</div>

				<div data-type="button-press" data-sync="resetToDefault" data-confirm-message="confirmResetToDefault">
					<div class="option-button trigger-button">
						__MSG_resetToDefault__
					</div>
				</div>

			</div>

			<div id="advanced" class="option-group hidden">

				<div id="support-invidious" data-type="toggle" data-sync="supportInvidious" data-no-safari="true">
					<div class="switch-container">
						<label class="switch">
							<input id="supportInvidious" type="checkbox">
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="supportInvidious">
							__MSG_supportOtherSites__
						</label>
					</div>
	
					<div class="small-description">(__MSG_supportedSites__ Invidious, CloudTube, Piped, YouTube Kids)</div>
					<div class="small-description">__MSG_supportOtherSitesDescription__ </div>
				</div>
	
				<div data-type="private-text-change" data-sync="invidiousInstances" data-dependent-on="supportInvidious" data-no-safari="true">
					<div class="option-button trigger-button">
						__MSG_addInvidiousInstance__
					</div>
	
					<div class="small-description">__MSG_addInvidiousInstanceDescription__</div>
	
					<div class="indent option-hidden-section hidden spacing">
						<input class="option-text-box" type="text">
						<div class="inline">
							<div class="option-button text-change-set inline low-profile">
								__MSG_add__
							</div>
							<div class="option-button text-change-reset inline low-profile">
								__MSG_cancel__
							</div>
						</div>
					</div>

					<div style="margin-top:15px">
						<span>__MSG_currentInstances__</span>
						<span data-type="display" data-sync="invidiousInstances"></span>
						<div class="option-button invidious-instance-reset spacing hidden">
							__MSG_resetInvidiousInstance__
						</div>
					</div>
				</div>

				<div data-type="toggle" data-sync="trackViewCount">
					<div class="switch-container">
						<label class="switch">
							<input id="trackViewCount" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="trackViewCount">
							__MSG_enableViewTracking__
						</label>
					</div>
	
					<div class="small-description">__MSG_whatViewTracking__</div>
				</div>
	
				<div data-type="toggle" data-sync="trackViewCountInPrivate" data-dependent-on="trackViewCount" data-private-only="true">
					<div class="switch-container">
						<label class="switch">
							<input id="trackViewCountInPrivate" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="trackViewCountInPrivate">
							__MSG_enableViewTrackingInPrivate__
						</label>
					</div>
				</div>

				<div data-type="toggle" data-sync="trackDownvotes" data-confirm-on="false" data-confirm-message="trackDownvotesWarning">
					<div class="switch-container">
						<label class="switch">
							<input id="trackDownvotes" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="trackDownvotes">
							__MSG_enableTrackDownvotes__
						</label>
					</div>
	
					<div class="small-description">__MSG_whatTrackDownvotes__</div>
				</div>

				<div data-type="toggle" data-sync="trackDownvotesInPrivate" data-confirm-on="false">
					<div class="switch-container">
						<label class="switch">
							<input id="trackDownvotesInPrivate" type="checkbox" checked>
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="trackDownvotesInPrivate">
							__MSG_enableTrackDownvotesInPrivate__
						</label>
					</div>
				</div>

				<div data-type="button-press" data-sync="copyDebugInformation" data-confirm-message="copyDebugInformation">
					<div class="option-button trigger-button">
						__MSG_copyDebugInformation__
					</div>
	
					<div class="small-description">__MSG_copyDebugInformationOptions__</div>
				</div>

				<div data-type="toggle" data-sync="testingServer" data-confirm-message="testingServerWarning" data-no-safari="true">
					<div class="switch-container">
						<label class="switch">
							<input id="testingServer" type="checkbox">
							<span class="slider round"></span>
						</label>
						<label class="switch-label" for="testingServer">
							__MSG_enableTestingServer__
						</label>
					</div>
	
					<div class="small-description">__MSG_whatEnableTestingServer__</div>
				</div>
	
				<div data-type="text-change" data-sync="serverAddress" data-dependent-on="testingServer" data-dependent-on-inverted="true">
					<label class="optionLabel inline">
						<span class="optionLabel">__MSG_customServerAddress__:</span>
	
						<input class="option-text-box" type="text" style="margin-right:10px">
					</label>

					<div class="small-description">__MSG_customServerAddressDescription__</div>
	
					<div class="next-line">
						<div class="option-button text-change-set inline">
							__MSG_save__
						</div>
		
						<div class="option-button text-change-reset inline">
							__MSG_reset__
						</div>
					</div>
				</div>

			</div>

		</div>

	</div>

</body>


================================================
FILE: public/oss-attribution/attribution.txt
================================================
content-scripts-register-polyfill
4.0.2 <https://github.com/fregante/content-scripts-register-polyfill>
MIT License

Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


******************************

escape-string-regexp
5.0.0 <https://github.com/sindresorhus/escape-string-regexp>
MIT License

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


******************************

js-tokens
4.0.0 <https://github.com/lydell/js-tokens>
The MIT License (MIT)

Copyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


******************************

loose-envify
1.4.0 <https://github.com/zertosh/loose-envify>
The MIT License (MIT)

Copyright (c) 2015 Andres Suarez <zertosh@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


******************************

react
18.2.0 <https://github.com/facebook/react>
MIT License

Copyright (c) Facebook, Inc. and its affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


******************************

react-dom
18.2.0 <https://github.com/facebook/react>
MIT License

Copyright (c) Facebook, Inc. and its affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


******************************

scheduler
0.23.0 <https://github.com/facebook/react>
MIT License

Copyright (c) Facebook, Inc. and its affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


******************************

webext-content-scripts
2.5.5 <https://github.com/fregante/webext-content-scripts>
MIT License

Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


******************************

webext-patterns
1.3.0 <https://github.com/fregante/webext-patterns>
MIT License

Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHA
Download .txt
gitextract_jhhxv7ay/

├── .editorconfig
├── .eslintrc.json
├── .github/
│   ├── FUNDING.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── ci.yml
│       ├── release.yml
│       ├── take-action.yml
│       ├── tests.yml
│       ├── update-oss-attribution.yml
│       └── updateInvidous.yml
├── .gitignore
├── .gitmodules
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE-APPSTORE.txt
├── LICENSE-HISTORY.txt
├── README.md
├── ci/
│   ├── generateList.ts
│   ├── invidiousCI.ts
│   ├── invidiousType.ts
│   ├── invidiouslist.json
│   ├── pipedCI.ts
│   └── prettify.ts
├── config.json.example
├── crowdin.yml
├── jest.config.js
├── manifest/
│   ├── beta-manifest-extra.json
│   ├── chrome-manifest-extra.json
│   ├── firefox-beta-manifest-extra.json
│   ├── firefox-manifest-extra.json
│   ├── manifest-v2-extra.json
│   ├── manifest.json
│   └── safari-manifest-extra.json
├── oss-attribution/
│   └── licenseInfos.json
├── package.json
├── public/
│   ├── content.css
│   ├── help/
│   │   ├── index.html
│   │   └── styles.css
│   ├── icons/
│   │   └── beep.oga
│   ├── libs/
│   │   └── Source+Sans+Pro.css
│   ├── options/
│   │   ├── options.css
│   │   └── options.html
│   ├── oss-attribution/
│   │   └── attribution.txt
│   ├── permissions/
│   │   ├── index.html
│   │   └── styles.css
│   ├── popup.css
│   ├── popup.html
│   ├── res/
│   │   └── countries.json
│   └── shared.css
├── src/
│   ├── background.ts
│   ├── components/
│   │   ├── CategoryPillComponent.tsx
│   │   ├── ChapterVoteComponent.tsx
│   │   ├── NoticeComponent.tsx
│   │   ├── NoticeTextSectionComponent.tsx
│   │   ├── SelectorComponent.tsx
│   │   ├── SkipNoticeComponent.tsx
│   │   ├── SponsorTimeEditComponent.tsx
│   │   ├── SubmissionNoticeComponent.tsx
│   │   └── options/
│   │       ├── AdvancedSkipOptionsComponent.tsx
│   │       ├── CategoryChooserComponent.tsx
│   │       ├── CategorySkipOptionsComponent.tsx
│   │       ├── KeybindComponent.tsx
│   │       ├── KeybindDialogComponent.tsx
│   │       ├── NumberInputOptionComponent.tsx
│   │       ├── SelectOptionComponent.tsx
│   │       ├── ToggleOptionComponent.tsx
│   │       ├── UnsubmittedVideoListComponent.tsx
│   │       ├── UnsubmittedVideoListItem.tsx
│   │       └── UnsubmittedVideosComponent.tsx
│   ├── config.ts
│   ├── content.ts
│   ├── dearrowPromotion.ts
│   ├── document.ts
│   ├── globals.d.ts
│   ├── help.ts
│   ├── js-components/
│   │   ├── previewBar.ts
│   │   └── skipButtonControlBar.ts
│   ├── messageTypes.ts
│   ├── options.ts
│   ├── permissions.ts
│   ├── popup/
│   │   ├── PopupComponent.tsx
│   │   ├── SegmentListComponent.tsx
│   │   ├── SegmentSubmissionComponent.tsx
│   │   ├── YourWorkComponent.tsx
│   │   ├── popup.tsx
│   │   └── popupUtils.ts
│   ├── render/
│   │   ├── AdvancedSkipOptions.tsx
│   │   ├── CategoryChooser.tsx
│   │   ├── CategoryPill.tsx
│   │   ├── ChapterVote.tsx
│   │   ├── GenericNotice.tsx
│   │   ├── RectangleTooltip.tsx
│   │   ├── SkipNotice.tsx
│   │   ├── SubmissionNotice.tsx
│   │   ├── Tooltip.tsx
│   │   ├── UnsubmittedVideos.tsx
│   │   └── UpcomingNotice.tsx
│   ├── svg-icons/
│   │   ├── checkIcon.tsx
│   │   ├── clipboardIcon.tsx
│   │   ├── lock_svg.tsx
│   │   ├── pencilIcon.tsx
│   │   ├── pencil_svg.tsx
│   │   ├── resetIcon.tsx
│   │   ├── sb_svg.tsx
│   │   ├── thumbs_down_svg.tsx
│   │   └── thumbs_up_svg.tsx
│   ├── types.ts
│   ├── utils/
│   │   ├── arrayUtils.ts
│   │   ├── categoryUtils.ts
│   │   ├── compatibility.ts
│   │   ├── configUtils.ts
│   │   ├── constants.ts
│   │   ├── crossExtension.ts
│   │   ├── exporter.ts
│   │   ├── genericUtils.ts
│   │   ├── logger.ts
│   │   ├── mobileUtils.ts
│   │   ├── noticeUtils.ts
│   │   ├── pageCleaner.ts
│   │   ├── pageUtils.ts
│   │   ├── requests.ts
│   │   ├── segmentData.ts
│   │   ├── skipProfiles.ts
│   │   ├── skipRule.ts
│   │   ├── skipRule.type.ts
│   │   ├── thumbnails.ts
│   │   ├── urlParser.ts
│   │   ├── videoLabels.ts
│   │   └── warnings.ts
│   └── utils.ts
├── test/
│   ├── exporter.test.ts
│   ├── previewBar.test.ts
│   ├── selenium.test.ts
│   └── urlParser.test.ts
├── tsconfig-production.json
├── tsconfig.json
└── webpack/
    ├── configDiffPlugin.js
    ├── webpack.common.js
    ├── webpack.dev.js
    ├── webpack.manifest.js
    └── webpack.prod.js
Download .txt
SYMBOL INDEX (728 symbols across 83 files)

FILE: ci/generateList.ts
  function generateList (line 33) | async function generateList() {

FILE: ci/invidiousType.ts
  type InvidiousInstance (line 1) | type InvidiousInstance = [
  type monitor (line 15) | type monitor = {
  type ivStats (line 48) | type ivStats = {

FILE: ci/pipedCI.ts
  type percent (line 3) | type percent = string
  type dailyMinutesDown (line 4) | type dailyMinutesDown = Record<string, number>
  type PipedInstance (line 6) | type PipedInstance = {
  function dailyMinuteFilter (line 28) | function dailyMinuteFilter (dailyMinutesDown: dailyMinutesDown) {
  function getPipedList (line 88) | async function getPipedList(): Promise<string[]> {

FILE: src/background.ts
  function registerFirefoxContentScript (line 169) | async function registerFirefoxContentScript(options: Registration) {
  function unregisterFirefoxContentScript (line 209) | async function  unregisterFirefoxContentScript(id: string) {
  function submitVote (line 226) | async function submitVote(type: number, UUID: string, category: string, ...
  function asyncRequestToServer (line 254) | async function asyncRequestToServer(type: string, address: string, data ...

FILE: src/components/CategoryPillComponent.tsx
  type CategoryPillProps (line 14) | interface CategoryPillProps {
  type CategoryPillState (line 20) | interface CategoryPillState {
  class CategoryPillComponent (line 26) | class CategoryPillComponent extends React.Component<CategoryPillProps, C...
    method constructor (line 30) | constructor(props: CategoryPillProps) {
    method render (line 42) | render(): React.ReactElement {
    method toggleOpen (line 107) | private toggleOpen(event: React.MouseEvent): void {
    method vote (line 123) | private async vote(event: React.MouseEvent, type: number): Promise<voi...
    method getColor (line 148) | private getColor(): string {
    method getTextColor (line 154) | private getTextColor(): string {
    method openTooltip (line 160) | private openTooltip(): void {
    method closeTooltip (line 180) | private closeTooltip(): void {
    method getTitleText (line 185) | getTitleText(): string {

FILE: src/components/ChapterVoteComponent.tsx
  type ChapterVoteProps (line 14) | interface ChapterVoteProps {
  type ChapterVoteState (line 19) | interface ChapterVoteState {
  class ChapterVoteComponent (line 25) | class ChapterVoteComponent extends React.Component<ChapterVoteProps, Cha...
    method constructor (line 28) | constructor(props: ChapterVoteProps) {
    method render (line 38) | render(): React.ReactElement {
    method vote (line 115) | private async vote(event: React.MouseEvent, type: number, element?: HT...

FILE: src/components/NoticeComponent.tsx
  type CountdownMode (line 5) | enum CountdownMode {
  type NoticeProps (line 11) | interface NoticeProps {
  type MouseDownInfo (line 45) | interface MouseDownInfo {
  type NoticeState (line 52) | interface NoticeState {
  class NoticeComponent (line 71) | class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
    method constructor (line 82) | constructor(props: NoticeProps) {
    method componentDidMount (line 117) | componentDidMount(): void {
    method render (line 121) | render(): React.ReactElement {
    method getCountdownElements (line 251) | getCountdownElements(): React.ReactElement[] {
    method onMouseEnter (line 276) | onMouseEnter(event: React.MouseEvent<HTMLElement, MouseEvent>): void {
    method fadedMouseEnter (line 283) | fadedMouseEnter(): void {
    method timerMouseEnter (line 291) | timerMouseEnter(): void {
    method timerMouseLeave (line 301) | timerMouseLeave(): void {
    method toggleManualPause (line 311) | toggleManualPause(): void {
    method countdown (line 324) | countdown(): void {
    method removeFadeAnimation (line 351) | removeFadeAnimation(): void {
    method pauseCountdown (line 358) | pauseCountdown(): void {
    method startCountdown (line 374) | startCountdown(): void {
    method setupInterval (line 388) | setupInterval(): void {
    method resetCountdown (line 394) | resetCountdown(): void {
    method close (line 410) | close(silent?: boolean): void {
    method addNoticeInfoMessage (line 417) | addNoticeInfoMessage(message: string, message2 = ""): void {
    method getElement (line 452) | getElement(): React.RefObject<HTMLDivElement> {
    method componentWillUnmount (line 456) | componentWillUnmount(): void {
    method handleMouseMove (line 461) | handleMouseMove(e: MouseEvent): void {

FILE: src/components/NoticeTextSectionComponent.tsx
  type NoticeTextSelectionProps (line 3) | interface NoticeTextSelectionProps {
  type NoticeTextSelectionState (line 11) | interface NoticeTextSelectionState {
  class NoticeTextSelectionComponent (line 15) | class NoticeTextSelectionComponent extends React.Component<NoticeTextSel...
    method constructor (line 17) | constructor(props: NoticeTextSelectionProps) {
    method render (line 21) | render(): React.ReactElement {
    method getTextElements (line 47) | private getTextElements(text: string): Array<string | React.ReactEleme...

FILE: src/components/SelectorComponent.tsx
  type SelectorOption (line 3) | interface SelectorOption {
  type SelectorProps (line 7) | interface SelectorProps {
  type SelectorState (line 15) | interface SelectorState {
  class SelectorComponent (line 19) | class SelectorComponent extends React.Component<SelectorProps, SelectorS...
    method constructor (line 21) | constructor(props: SelectorProps) {
    method render (line 30) | render(): React.ReactElement {
    method getOptions (line 44) | getOptions(): React.ReactElement[] {

FILE: src/components/SkipNoticeComponent.tsx
  type SkipButtonState (line 20) | enum SkipButtonState {
  type SkipNoticeProps (line 26) | interface SkipNoticeProps {
  type SkipNoticeState (line 47) | interface SkipNoticeState {
  class SkipNoticeComponent (line 76) | class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipN...
    method constructor (line 97) | constructor(props: SkipNoticeProps) {
    method render (line 173) | render(): React.ReactElement {
    method componentDidMount (line 210) | componentDidMount(): void {
    method getBottomRow (line 216) | getBottomRow(): JSX.Element[] {
    method getSkipButton (line 364) | getSkipButton(buttonIndex: number): JSX.Element {
    method getSubmissionChooser (line 399) | getSubmissionChooser(): JSX.Element[] {
    method getSubmissionChooserOpacity (line 417) | getSubmissionChooserOpacity(index: number): number {
    method getSubmissionChooserColor (line 428) | getSubmissionChooserColor(index: number): string {
    method onMouseEnter (line 437) | onMouseEnter(): void {
    method getMessageBoxes (line 445) | getMessageBoxes(): JSX.Element[] {
    method prepAction (line 475) | prepAction(action: SkipNoticeAction): void {
    method performAction (line 519) | performAction(index: number, action?: SkipNoticeAction): void {
    method noAction (line 548) | noAction(index: number): void {
    method upvote (line 557) | upvote(index: number): void {
    method downvote (line 562) | downvote(index: number): void {
    method categoryVote (line 568) | categoryVote(index: number): void {
    method copyDownvote (line 572) | copyDownvote(index: number): void {
    method unskipAction (line 602) | unskipAction(buttonIndex: number, index: number, forceSeek: boolean): ...
    method openEditingOptions (line 606) | openEditingOptions(): void {
    method getCategoryOptions (line 610) | getCategoryOptions(): React.ReactElement[] {
    method getCategoryNameClass (line 626) | getCategoryNameClass(category: string): string {
    method unskip (line 630) | unskip(buttonIndex: number, index: number, forceSeek: boolean): void {
    method reskip (line 636) | reskip(buttonIndex: number, index: number, forceSeek: boolean): void {
    method reskippedMode (line 641) | reskippedMode(buttonIndex: number): void {
    method unskippedMode (line 663) | unskippedMode(buttonIndex: number, index: number, skipButtonState: Ski...
    method getUnskippedModeInfo (line 670) | getUnskippedModeInfo(buttonIndex: number, index: number, skipButtonSta...
    method getFullDurationCountdown (line 704) | getFullDurationCountdown(index: number): () => number {
    method afterVote (line 713) | afterVote(segment: SponsorTime, type: number, category: Category): void {
    method setNoticeInfoMessageWithOnClick (line 745) | setNoticeInfoMessageWithOnClick(onClick: (event: React.MouseEvent) => ...
    method setNoticeInfoMessage (line 752) | setNoticeInfoMessage(...messages: string[]): void {
    method addVoteButtonInfo (line 758) | addVoteButtonInfo(message: string): void {
    method resetVoteButtonInfo (line 764) | resetVoteButtonInfo(): void {
    method closeListener (line 770) | closeListener(): void {
    method clearConfigListener (line 776) | clearConfigListener(): void {
    method unmutedListener (line 783) | unmutedListener(time: number): void {
    method resetStateToStart (line 793) | resetStateToStart(actionState: SkipNoticeAction = SkipNoticeAction.Non...
    method getSkipButtonText (line 803) | private getSkipButtonText(buttonIndex: number, forceType?: ActionType)...
    method getUndoText (line 814) | private getUndoText(forceType?: ActionType): string {
    method getRedoText (line 827) | private getRedoText(forceType?: ActionType): string {
    method getStartText (line 840) | private getStartText(forceType?: ActionType): string {

FILE: src/components/SponsorTimeEditComponent.tsx
  type SponsorTimeEditProps (line 17) | interface SponsorTimeEditProps {
  type SponsorTimeEditState (line 30) | interface SponsorTimeEditState {
  class SponsorTimeEditComponent (line 44) | class SponsorTimeEditComponent extends React.Component<SponsorTimeEditPr...
    method constructor (line 63) | constructor(props: SponsorTimeEditProps) {
    method componentDidMount (line 86) | componentDidMount(): void {
    method componentWillUnmount (line 108) | componentWillUnmount(): void {
    method render (line 114) | render(): React.ReactElement {
    method handleOnChange (line 344) | handleOnChange(index: number, e: React.ChangeEvent, sponsorTime: Spons...
    method changeTimesWhenScrolling (line 359) | changeTimesWhenScrolling(index: number, e: React.WheelEvent, sponsorTi...
    method showScrollToEditToolTip (line 391) | showScrollToEditToolTip(): void {
    method showToolTip (line 397) | showToolTip(text: string, id: string, buttonFunction?: () => void): bo...
    method checkToShowFullVideoWarning (line 423) | checkToShowFullVideoWarning(): void {
    method checkToShowChapterWarning (line 436) | checkToShowChapterWarning(): void {
    method getCategoryOptions (line 449) | getCategoryOptions(): React.ReactElement[] {
    method getCategoryLockedClass (line 477) | getCategoryLockedClass(category: string): string {
    method categorySelectionChange (line 481) | categorySelectionChange(event: React.ChangeEvent<HTMLSelectElement>): ...
    method actionTypeSelectionChange (line 527) | actionTypeSelectionChange(event: React.ChangeEvent<HTMLSelectElement>)...
    method handleReplacingLostTimes (line 538) | private handleReplacingLostTimes(category: Category, actionType: Actio...
    method getActionTypeOptions (line 575) | getActionTypeOptions(sponsorTime: SponsorTime): React.ReactElement[] {
    method setTimeToNow (line 590) | setTimeToNow(index: number): void {
    method setTimeToEnd (line 594) | setTimeToEnd(): void {
    method setTimeTo (line 602) | setTimeTo(index: number, time: number): void {
    method toggleEditTime (line 619) | toggleEditTime(): void {
    method getFormattedSponsorTimesEdits (line 638) | getFormattedSponsorTimesEdits(sponsorTime: SponsorTime): [string, stri...
    method saveEditTimes (line 645) | saveEditTimes(): void {
    method getNextActionType (line 709) | private getNextActionType(category: Category, actionType: ActionType):...
    method previewTime (line 714) | previewTime(ctrlPressed = false, shiftPressed = false, skipToEndTime =...
    method inspectTime (line 730) | inspectTime(): void {
    method deleteTime (line 739) | deleteTime(): void {
    method descriptionUpdate (line 771) | descriptionUpdate(description: string): void {
    method fetchSuggestions (line 783) | async fetchSuggestions(description: string): Promise<void> {
    method configUpdate (line 809) | configUpdate(): void {

FILE: src/components/SubmissionNoticeComponent.tsx
  type SubmissionNoticeProps (line 14) | interface SubmissionNoticeProps {
  type SubmissionNoticeState (line 23) | interface SubmissionNoticeState {
  class SubmissionNoticeComponent (line 29) | class SubmissionNoticeComponent extends React.Component<SubmissionNotice...
    method constructor (line 44) | constructor(props: SubmissionNoticeProps) {
    method componentDidMount (line 63) | componentDidMount(): void {
    method componentWillUnmount (line 82) | componentWillUnmount(): void {
    method componentDidUpdate (line 88) | componentDidUpdate() {
    method scrollToBottom (line 97) | scrollToBottom() {
    method render (line 104) | render(): React.ReactElement {
    method getSponsorTimeMessages (line 168) | getSponsorTimeMessages(): JSX.Element[] | JSX.Element {
    method getMessageBoxes (line 194) | getMessageBoxes(): JSX.Element[] | JSX.Element {
    method cancel (line 209) | cancel(): void {
    method submit (line 218) | submit(): void {
    method sortSegments (line 255) | sortSegments(): void {
    method exportSegments (line 265) | exportSegments() {
    method categoryChangeListener (line 289) | categoryChangeListener(index: number, category: Category): void {

FILE: src/components/options/AdvancedSkipOptionsComponent.tsx
  function AdvancedSkipOptionsComponent (line 9) | function AdvancedSkipOptionsComponent() {
  function compileConfig (line 66) | function compileConfig(config: string): AdvancedSkipRule[] | null {

FILE: src/components/options/CategoryChooserComponent.tsx
  function CategoryChooserComponent (line 13) | function CategoryChooserComponent() {
  function CategorySkipOptions (line 209) | function CategorySkipOptions({ selectedConfigurationID, selections, setS...
  function forceUpdateConfigurations (line 253) | function forceUpdateConfigurations() {
  function forceUpdateConfigurationIDs (line 263) | function forceUpdateConfigurationIDs() {
  function updateChannelList (line 273) | function updateChannelList(setChannelListText: (value: string) => void, ...
  function getConfig (line 279) | function getConfig(selectedConfigurationID: ConfigurationID | null) {
  function getConfigurationValue (line 283) | function getConfigurationValue<T>(selectedConfigurationID: Configuration...
  function updateConfigurationValue (line 295) | function updateConfigurationValue(selectedConfigurationID: Configuration...
  function ExtraOptionsComponent (line 312) | function ExtraOptionsComponent(props: {selectedConfigurationID: Configur...

FILE: src/components/options/CategorySkipOptionsComponent.tsx
  type CategorySkipOptionsProps (line 12) | interface CategorySkipOptionsProps {
  type ToggleOption (line 23) | interface ToggleOption {
  function CategorySkipOptionsComponent (line 32) | function CategorySkipOptionsComponent(props: CategorySkipOptionsProps): ...
  function skipOptionSelected (line 142) | function skipOptionSelected(event: React.ChangeEvent<HTMLSelectElement>,
  function getCategorySkipOptions (line 173) | function getCategorySkipOptions(category: Category, isDefaultConfig: boo...
  function ExtraOptionComponents (line 197) | function ExtraOptionComponents(props: {category: string; selectedConfigu...
  function ExtraOptionComponent (line 215) | function ExtraOptionComponent({option, selectedConfigurationID}: {option...
  function getExtraOptions (line 261) | function getExtraOptions(category: string): ToggleOption[] {

FILE: src/components/options/KeybindComponent.tsx
  type KeybindProps (line 7) | interface KeybindProps {
  type KeybindState (line 11) | interface KeybindState {
  class KeybindComponent (line 18) | class KeybindComponent extends React.Component<KeybindProps, KeybindStat...
    method constructor (line 19) | constructor(props: KeybindProps) {
    method render (line 24) | render(): React.ReactElement {
    method equals (line 47) | equals(other: Keybind): boolean {
    method toString (line 51) | toString(): string {
    method openEditDialog (line 55) | openEditDialog(): void {
    method closeEditDialog (line 63) | closeEditDialog(updateWith: Keybind): void {
    method unbind (line 70) | unbind(): void {

FILE: src/components/options/KeybindDialogComponent.tsx
  type KeybindDialogProps (line 6) | interface KeybindDialogProps {
  type KeybindDialogState (line 11) | interface KeybindDialogState {
  type ErrorMessage (line 16) | interface ErrorMessage {
  class KeybindDialogComponent (line 21) | class KeybindDialogComponent extends React.Component<KeybindDialogProps,...
    method constructor (line 23) | constructor(props: KeybindDialogProps) {
    method render (line 40) | render(): React.ReactElement {
    method componentDidMount (line 77) | componentDidMount(): void {
    method componentWillUnmount (line 82) | componentWillUnmount(): void {
    method isKeybindAvailable (line 119) | isKeybindAvailable(): ErrorMessage {
    method equals (line 157) | equals(other: Keybind): boolean {
    method save (line 161) | save(): void {

FILE: src/components/options/NumberInputOptionComponent.tsx
  type NumberInputOptionProps (line 4) | interface NumberInputOptionProps {
  function NumberInputOptionComponent (line 16) | function NumberInputOptionComponent(props: NumberInputOptionProps): Reac...

FILE: src/components/options/SelectOptionComponent.tsx
  type SelectOption (line 4) | interface SelectOption {
  type SelectOptionComponentProps (line 9) | interface SelectOptionComponentProps {
  function getOptions (line 54) | function getOptions(options: SelectOption[]): React.ReactNode[] {

FILE: src/components/options/ToggleOptionComponent.tsx
  type ToggleOptionProps (line 4) | interface ToggleOptionProps {
  function ToggleOptionComponent (line 16) | function ToggleOptionComponent(props: ToggleOptionProps): React.ReactEle...

FILE: src/components/options/UnsubmittedVideoListComponent.tsx
  type UnsubmittedVideoListProps (line 6) | interface UnsubmittedVideoListProps {
  type UnsubmittedVideoListState (line 10) | interface UnsubmittedVideoListState {
  class UnsubmittedVideoListComponent (line 14) | class UnsubmittedVideoListComponent extends React.Component<UnsubmittedV...
    method constructor (line 16) | constructor(props: UnsubmittedVideoListProps) {
    method render (line 25) | render(): React.ReactElement {
    method getUnsubmittedVideos (line 58) | getUnsubmittedVideos(): JSX.Element[] {

FILE: src/components/options/UnsubmittedVideoListItem.tsx
  type UnsubmittedVideosListItemProps (line 6) | interface UnsubmittedVideosListItemProps {
  type UnsubmittedVideosListItemState (line 11) | interface UnsubmittedVideosListItemState {
  class UnsubmittedVideoListItem (line 14) | class UnsubmittedVideoListItem extends React.Component<UnsubmittedVideos...
    method constructor (line 16) | constructor(props: UnsubmittedVideosListItemProps) {
    method render (line 25) | render(): React.ReactElement {
    method clearSegments (line 70) | clearSegments(): void {
    method exportSegments (line 77) | exportSegments(): void {
    method exportSegmentsAsURL (line 81) | exportSegmentsAsURL(): void {
    method copyToClipboard (line 85) | copyToClipboard(text: string): void {

FILE: src/components/options/UnsubmittedVideosComponent.tsx
  type UnsubmittedVideosProps (line 5) | interface UnsubmittedVideosProps {
  type UnsubmittedVideosState (line 9) | interface UnsubmittedVideosState {
  class UnsubmittedVideosComponent (line 13) | class UnsubmittedVideosComponent extends React.Component<UnsubmittedVide...
    method constructor (line 15) | constructor(props: UnsubmittedVideosProps) {
    method render (line 23) | render(): React.ReactElement {
    method clearAllSegments (line 49) | clearAllSegments(): void {

FILE: src/config.ts
  type SBConfig (line 8) | interface SBConfig {
  type VideoDownvotes (line 140) | type VideoDownvotes = { segments: { uuid: HashedValue; hidden: SponsorHi...
  type ConfigurationID (line 142) | type ConfigurationID = string & { __configurationID: never };
  type CustomConfiguration (line 144) | interface CustomConfiguration {
  type SBStorage (line 156) | interface SBStorage {
  class ConfigClass (line 174) | class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
    method resetToDefault (line 175) | resetToDefault() {
  function migrateOldSyncFormats (line 190) | function migrateOldSyncFormats(config: SBConfig, local: SBStorage) {
  function generateDebugDetails (line 607) | function generateDebugDetails(): string {

FILE: src/content.ts
  function messageListener (line 204) | function messageListener(request: Message, sender: unknown, sendResponse...
  function contentConfigUpdateListener (line 365) | function contentConfigUpdateListener(changes: StorageChangesObject) {
  function contentLocalConfigUpdateListener (line 386) | function contentLocalConfigUpdateListener(changes: StorageChangesObject) {
  function resetValues (line 409) | function resetValues() {
  function videoIDChange (line 457) | function videoIDChange(): void {
  function handleMobileControlsMutations (line 511) | function handleMobileControlsMutations(): void {
  function getPreviewBarAttachElement (line 537) | function getPreviewBarAttachElement(): HTMLElement | null {
  function createPreviewBar (line 598) | function createPreviewBar(): void {
  function durationChangeListener (line 615) | function durationChangeListener(): void {
  function videoOnReadyListener (line 624) | function videoOnReadyListener(): void {
  function cancelSponsorSchedule (line 630) | function cancelSponsorSchedule(): void {
  function startSponsorSchedule (line 652) | async function startSponsorSchedule(includeIntersectingSegments = false,...
  function waitForNextTimeChange (line 857) | function waitForNextTimeChange(): Promise<DOMHighResTimeStamp | null> {
  function getVirtualTime (line 863) | function getVirtualTime(): number {
  function inMuteSegment (line 875) | function inMuteSegment(currentTime: number, includeOverlap: boolean): bo...
  function incorrectVideoCheck (line 886) | function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime...
  function setupVideoListeners (line 912) | function setupVideoListeners(video: HTMLVideoElement) {
  function updateVirtualTime (line 1119) | function updateVirtualTime() {
  function updateWaitingTime (line 1160) | function updateWaitingTime(video: HTMLVideoElement): void {
  function clearWaitingTime (line 1164) | function clearWaitingTime(): void {
  function setupSkipButtonControlBar (line 1168) | function setupSkipButtonControlBar() {
  function setupCategoryPill (line 1187) | function setupCategoryPill() {
  function sponsorsLookup (line 1195) | async function sponsorsLookup(keepOldSubmissions = true, ignoreCache = f...
  function notifyPopupOfSegments (line 1276) | function notifyPopupOfSegments(): void {
  function importExistingChapters (line 1293) | function importExistingChapters(wait: boolean) {
  function handleExistingChaptersChannelChange (line 1326) | function handleExistingChaptersChannelChange() {
  function lockedCategoriesLookup (line 1332) | async function lockedCategoriesLookup(): Promise<void> {
  function startSkipScheduleCheckingForStartSponsors (line 1355) | function startSkipScheduleCheckingForStartSponsors() {
  function selectSegment (line 1409) | function selectSegment(UUID: SegmentUUID): void {
  function updatePreviewBar (line 1414) | function updatePreviewBar(): void {
  function updateCategoryPill (line 1472) | function updateCategoryPill() {
  function channelIDChange (line 1482) | async function channelIDChange() {
  function videoElementChange (line 1493) | function videoElementChange(newVideo: boolean, video: HTMLVideoElement):...
  function checkPreviewbarState (line 1512) | function checkPreviewbarState(): void {
  function getNextSkipIndex (line 1534) | function getNextSkipIndex(currentTime: number, includeIntersectingSegmen...
  function getLatestEndTimeIndex (line 1607) | function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: numbe...
  function getStartTimes (line 1657) | function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingS...
  function previewTime (line 1711) | function previewTime(time: number, unpause = true) {
  function sendTelemetryAndCount (line 1722) | function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsS...
  function skipToTime (line 1755) | function skipToTime({v, skipTime, skippingSegments, openNotice, forceAut...
  function createSkipNotice (line 1850) | function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boo...
  function createUpcomingNotice (line 1872) | function createUpcomingNotice(skippingSegments: SponsorTime[], timeLeft:...
  function unskipSponsorTime (line 1883) | function unskipSponsorTime(segment: SponsorTime, unskipTime: number = nu...
  function reskipSponsorTime (line 1896) | function reskipSponsorTime(segment: SponsorTime, forceSeek = false) {
  function createButton (line 1911) | function createButton(baseID: string, title: string, callback: () => voi...
  function shouldAutoSkip (line 1954) | function shouldAutoSkip(segment: SponsorTime): boolean {
  function shouldSkip (line 1969) | function shouldSkip(segment: SponsorTime): boolean {
  function isLoopedChapter (line 1978) | function isLoopedChapter(segment: SponsorTime): boolean{
  function createButtons (line 1984) | async function createButtons(): Promise<void> {
  function updateVisibilityOfPlayerControlsButton (line 2004) | async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
  function updateEditButtonsOnPlayer (line 2022) | function updateEditButtonsOnPlayer(): void {
  function getRealCurrentTime (line 2066) | function getRealCurrentTime(): number {
  function startOrEndTimingNewSegment (line 2081) | function startOrEndTimingNewSegment() {
  function getIncompleteSegment (line 2127) | function getIncompleteSegment(): SponsorTime {
  function isSegmentCreationInProgress (line 2132) | function isSegmentCreationInProgress(): boolean {
  function cancelCreatingSegment (line 2137) | function cancelCreatingSegment() {
  function updateSponsorTimesSubmitting (line 2154) | function updateSponsorTimesSubmitting(getFromConfig = true) {
  function openInfoMenu (line 2193) | function openInfoMenu() {
  function closeInfoMenu (line 2275) | function closeInfoMenu() {
  function clearSponsorTimes (line 2287) | function clearSponsorTimes() {
  function vote (line 2312) | async function vote(type: number, UUID: SegmentUUID, category?: Category...
  function voteAsync (line 2345) | async function voteAsync(type: number, UUID: SegmentUUID, category?: Cat...
  function closeAllSkipNotices (line 2400) | function closeAllSkipNotices(){
  function dontShowNoticeAgain (line 2407) | function dontShowNoticeAgain() {
  function resetSponsorSubmissionNotice (line 2415) | function resetSponsorSubmissionNotice(callRef = true) {
  function closeSubmissionMenu (line 2420) | function closeSubmissionMenu() {
  function openSubmissionMenu (line 2425) | function openSubmissionMenu() {
  function previewRecentSegment (line 2436) | function previewRecentSegment() {
  function submitSegments (line 2446) | function submitSegments() {
  function sendSubmitMessage (line 2457) | async function sendSubmitMessage(): Promise<boolean> {
  function getSegmentsMessage (line 2573) | function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
  function updateActiveSegment (line 2594) | function updateActiveSegment(currentTime: number): void {
  function nextChapter (line 2605) | function nextChapter(): void {
  function previousChapter (line 2620) | function previousChapter(): void {
  function handleKeybindVote (line 2644) | async function handleKeybindVote(type: number): Promise<void>{
  function addHotkeyListener (line 2661) | function addHotkeyListener(): void {
  function hotkeyListener (line 2671) | function hotkeyListener(e: KeyboardEvent): void {
  function hotkeyPropagationListener (line 2748) | function hotkeyPropagationListener(e: KeyboardEvent): void {
  function addCSS (line 2777) | function addCSS() {
  function updateAdFlag (line 2804) | function updateAdFlag(): void {
  function showTimeWithoutSkips (line 2813) | function showTimeWithoutSkips(skippedDuration: number): void {
  function checkForPreloadedSegment (line 2848) | function checkForPreloadedSegment() {
  function setCategoryColorCSSVariables (line 2882) | function setCategoryColorCSSVariables() {
  function checkForMiniplayerPlaying (line 2913) | function checkForMiniplayerPlaying() {

FILE: src/dearrowPromotion.ts
  function tryShowingDeArrowPromotion (line 13) | async function tryShowingDeArrowPromotion() {
  function badTitle (line 68) | function badTitle(title: string): boolean {
  function hideDeArrowPromotion (line 72) | function hideDeArrowPromotion(): void {

FILE: src/globals.d.ts
  type Window (line 3) | interface Window { SB: SBObject }

FILE: src/help.ts
  function init (line 40) | async function init() {

FILE: src/js-components/previewBar.ts
  constant TOOLTIP_VISIBLE_CLASS (line 20) | const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
  constant MIN_CHAPTER_SIZE (line 21) | const MIN_CHAPTER_SIZE = 0.003;
  type PreviewBarSegment (line 23) | interface PreviewBarSegment {
  type ChapterGroup (line 35) | interface ChapterGroup extends SegmentContainer {
  class PreviewBar (line 40) | class PreviewBar {
    method constructor (line 81) | constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious...
    method setupHoverText (line 104) | setupHoverText(): void {
    method setTooltipTitle (line 256) | private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLEleme...
    method createElement (line 286) | createElement(parent?: HTMLElement): void {
    method clear (line 304) | clear(): void {
    method set (line 320) | set(segments: PreviewBarSegment[], videoDuration: number): void {
    method updatePageElements (line 351) | private updatePageElements(): void {
    method update (line 367) | private update(): void {
    method createBar (line 408) | createBar(barSegment: PreviewBarSegment): HTMLLIElement {
    method createChaptersBar (line 444) | createChaptersBar(segments: PreviewBarSegment[]): void {
    method createChapterRenderGroups (line 563) | createChapterRenderGroups(segments: PreviewBarSegment[]): ChapterGroup...
    method getActionTypePrioritized (line 660) | private getActionTypePrioritized(actionTypes: ActionType[]): ActionType {
    method setupChapterSection (line 670) | private setupChapterSection(section: HTMLElement, startTime: number, e...
    method firstTimeSetupChapterSection (line 683) | private firstTimeSetupChapterSection(section: HTMLElement): void {
    method createChapterMutationObservers (line 691) | private createChapterMutationObservers(): void {
    method updateChapterAllMutation (line 736) | private updateChapterAllMutation(originalChapterBar: HTMLElement, prog...
    method updateChapterMutation (line 746) | private updateChapterMutation(changes: Record<string, HTMLElement>, pr...
    method findLeftAndScale (line 815) | private findLeftAndScale(selector: string, currentElement: HTMLElement...
    method getPartialChapterSectionStyle (line 882) | private getPartialChapterSectionStyle(element: HTMLElement, param: str...
    method updateChapterText (line 891) | updateChapterText(segments: SponsorTime[], submittingSegments: Sponsor...
    method setActiveSegments (line 924) | private setActiveSegments(segments: SponsorTime[]): void {
    method getChaptersContainer (line 1022) | private getChaptersContainer(): HTMLElement {
    method getChapterButton (line 1038) | private getChapterButton(chaptersContainer: HTMLElement): HTMLButtonEl...
    method remove (line 1043) | remove(): void {
    method chapterFilter (line 1057) | private chapterFilter(segment: PreviewBarSegment): boolean {
    method chapterGroupFilter (line 1063) | private chapterGroupFilter(segment: SegmentContainer): boolean {
    method intervalToPercentage (line 1067) | intervalToPercentage(startTime: number, endTime: number) {
    method intervalToDecimal (line 1071) | intervalToDecimal(startTime: number, endTime: number) {
    method timeToPercentage (line 1075) | timeToPercentage(time: number): string {
    method timeToRightPercentage (line 1079) | timeToRightPercentage(time: number): string {
    method timeToDecimal (line 1083) | timeToDecimal(time: number): number {
    method decimalToTime (line 1087) | decimalToTime(decimal: number): number {
    method decimalTimeConverter (line 1094) | decimalTimeConverter(value: number, isTime: boolean): number {
    method getMinimumSize (line 1141) | getMinimumSize(showLarger = false): number {
    method getSmallestSegment (line 1146) | private getSmallestSegment(timeInSeconds: number, segments: PreviewBar...
    method getChapterChevron (line 1185) | private getChapterChevron(): HTMLElement {

FILE: src/js-components/skipButtonControlBar.ts
  type SkipButtonControlBarProps (line 8) | interface SkipButtonControlBarProps {
  class SkipButtonControlBar (line 15) | class SkipButtonControlBar {
    method constructor (line 42) | constructor(props: SkipButtonControlBarProps) {
    method getElement (line 85) | getElement(): HTMLElement {
    method attachToPage (line 89) | attachToPage(): void {
    method getMountingContainer (line 110) | private getMountingContainer(): HTMLElement {
    method enable (line 120) | enable(segment: SponsorTime, duration?: number): void {
    method refreshText (line 133) | refreshText(): void {
    method setShowKeybindHint (line 142) | setShowKeybindHint(show: boolean): void {
    method stopTimer (line 148) | stopTimer(): void {
    method startTimer (line 152) | startTimer(): void {
    method disable (line 157) | disable(): void {
    method isEnabled (line 166) | isEnabled(): boolean {
    method toggleSkip (line 170) | toggleSkip(): void {
    method disableText (line 177) | disableText(): void {
    method updateMobileControls (line 195) | updateMobileControls(): void {
    method getTitle (line 205) | private getTitle(): string {
    method getChapterPrefix (line 209) | private getChapterPrefix(): HTMLElement {
    method handleTouchStart (line 214) | private handleTouchStart(event: TouchEvent): void {
    method handleTouchMove (line 219) | private handleTouchMove(event: TouchEvent): void {
    method handleTouchEnd (line 227) | private handleTouchEnd(): void {
    method updateLeftStyle (line 241) | private updateLeftStyle() {

FILE: src/messageTypes.ts
  type BaseMessage (line 8) | interface BaseMessage {
  type DefaultMessage (line 12) | interface DefaultMessage {
  type IsInfoFoundMessage (line 23) | interface IsInfoFoundMessage {
  type SkipMessage (line 28) | interface SkipMessage {
  type SubmitVoteMessage (line 33) | interface SubmitVoteMessage {
  type HideSegmentMessage (line 39) | interface HideSegmentMessage {
  type CopyToClipboardMessage (line 45) | interface CopyToClipboardMessage {
  type ImportSegmentsMessage (line 50) | interface ImportSegmentsMessage {
  type LoopChapterMessage (line 55) | interface LoopChapterMessage {
  type KeyDownMessage (line 60) | interface KeyDownMessage {
  type SetCurrentTabSkipProfileResponse (line 72) | interface SetCurrentTabSkipProfileResponse {
  type Message (line 77) | type Message = BaseMessage & (DefaultMessage | IsInfoFoundMessage | Skip...
  type IsInfoFoundMessageResponse (line 79) | interface IsInfoFoundMessageResponse {
  type GetVideoIdResponse (line 92) | interface GetVideoIdResponse {
  type GetChannelIDResponse (line 96) | interface GetChannelIDResponse {
  type SponsorStartResponse (line 101) | interface SponsorStartResponse {
  type IsChannelWhitelistedResponse (line 105) | interface IsChannelWhitelistedResponse {
  type LoopedChapterResponse (line 109) | interface LoopedChapterResponse {
  type MessageResponse (line 113) | type MessageResponse =
  type VoteResponse (line 126) | type VoteResponse = {
  type ImportSegmentsResponse (line 134) | interface ImportSegmentsResponse {
  type RefreshSegmentsResponse (line 138) | interface RefreshSegmentsResponse {
  type LogResponse (line 142) | interface LogResponse {
  type TimeUpdateMessage (line 147) | interface TimeUpdateMessage {
  type InfoUpdatedMessage (line 152) | type InfoUpdatedMessage = IsInfoFoundMessageResponse & {
  type VideoChangedPopupMessage (line 156) | interface VideoChangedPopupMessage {
  type PopupMessage (line 163) | type PopupMessage = TimeUpdateMessage | InfoUpdatedMessage | VideoChange...

FILE: src/options.ts
  function init (line 34) | async function init() {
  function createStickyHeader (line 406) | function createStickyHeader() {
  function shouldHideOption (line 426) | async function shouldHideOption(element: Element): Promise<boolean> {
  function optionsConfigUpdateListener (line 434) | function optionsConfigUpdateListener() {
  function optionsLocalConfigUpdateListener (line 447) | function optionsLocalConfigUpdateListener(changes: StorageChangesObject) {
  function updateDisplayElement (line 460) | function updateDisplayElement(element: HTMLElement) {
  function invidiousInstanceAddInit (line 489) | function invidiousInstanceAddInit(element: HTMLElement, option: string) {
  function invidiousInit (line 548) | function invidiousInit(checkbox: HTMLInputElement, option: string) {
  function invidiousOnClick (line 564) | async function invidiousOnClick(checkbox: HTMLInputElement, option: stri...
  function activatePrivateTextChange (line 574) | function activatePrivateTextChange(element: HTMLElement) {
  function setTextOption (line 642) | async function setTextOption(option: string, element: HTMLElement, value...
  function downloadConfig (line 683) | function downloadConfig(element: Element) {
  function uploadConfig (line 696) | function uploadConfig(e: Event, element: HTMLElement) {
  function validateServerAddress (line 716) | function validateServerAddress(input: string): string {
  function copyDebugOutputToClipboard (line 733) | function copyDebugOutputToClipboard() {
  function isIncognitoAllowed (line 744) | function isIncognitoAllowed(): Promise<boolean> {

FILE: src/permissions.ts
  function init (line 16) | async function init() {

FILE: src/popup/PopupComponent.tsx
  type LoadingStatus (line 16) | enum LoadingStatus {
  type LoadingData (line 26) | interface LoadingData {
  type SkipProfileAction (line 32) | type SkipProfileAction = "forJustThisVideo" | "forThisChannel" | "forThi...
  type SkipProfileOption (line 33) | interface SkipProfileOption {
  type SegmentsLoadedProps (line 38) | interface SegmentsLoadedProps {
  type LoadSegmentsProps (line 46) | interface LoadSegmentsProps extends SegmentsLoadedProps {
  type SkipProfileRadioButtonsProps (line 50) | interface SkipProfileRadioButtonsProps {
  type SkipOptionActionComponentProps (line 58) | interface SkipOptionActionComponentProps {
  function getVideoStatusText (line 294) | function getVideoStatusText(status: LoadingData): string {
  function loadSegments (line 313) | async function loadSegments(props: LoadSegmentsProps): Promise<void> {
  function segmentsLoaded (line 335) | function segmentsLoaded(response: IsInfoFoundMessageResponse, props: Seg...
  function sendMessage (line 374) | function sendMessage(request: Message): Promise<MessageResponse> {
  function setupComPort (line 387) | function setupComPort(props: SegmentsLoadedProps): void {
  function onMessage (line 393) | function onMessage(props: SegmentsLoadedProps, msg: PopupMessage) {
  function forwardClickEvents (line 413) | function forwardClickEvents(sendMessage: (request: Message) => Promise<M...
  function SkipProfileButton (line 454) | function SkipProfileButton(props: {videoID: string; setShowForceChannelC...
  function SkipProfileMenu (line 516) | function SkipProfileMenu(props: {open: boolean; videoID: string}): JSX.E...
  function SkipProfileRadioButtons (line 610) | function SkipProfileRadioButtons(props: SkipProfileRadioButtonsProps): J...
  function SkipOptionActionComponent (line 656) | function SkipOptionActionComponent(props: SkipOptionActionComponentProps...
  function updateSkipProfileSetting (line 685) | function updateSkipProfileSetting(action: SkipProfileAction, configID: C...

FILE: src/popup/SegmentListComponent.tsx
  type SegmentListComponentProps (line 16) | interface SegmentListComponentProps {
  type SegmentListTab (line 26) | enum SegmentListTab {
  type SegmentWithNesting (line 31) | interface SegmentWithNesting extends SponsorTime {
  function isSegment (line 35) | function isSegment(segment) {
  function isChapter (line 39) | function isChapter(segment) {
  function SegmentListItem (line 163) | function SegmentListItem({ segment, videoID, currentTime, isVip, loopedC...
  function InnerChapterList (line 378) | function InnerChapterList({ chapters, videoID, currentTime, isVip, loope...
  function vote (line 415) | async function vote(props: {
  function skipSegment (line 451) | function skipSegment({ segment, element, sendMessage }: {
  function selectSegment (line 475) | function selectSegment({ segment, sendMessage }: {
  function loopChapter (line 486) | function loopChapter({ segment, element, sendMessage }: {
  type ImportSegmentsProps (line 503) | interface ImportSegmentsProps {
  function ImportSegments (line 510) | function ImportSegments(props: ImportSegmentsProps) {

FILE: src/popup/SegmentSubmissionComponent.tsx
  type SegmentSubmissionComponentProps (line 7) | interface SegmentSubmissionComponentProps {

FILE: src/popup/YourWorkComponent.tsx
  function SubmissionCounts (line 143) | function SubmissionCounts(props: { isSettingUsername: boolean; submissio...
  function TimeSavedMessage (line 154) | function TimeSavedMessage({ viewCount, minutesSaved }: { viewCount: numb...
  function DonateMessage (line 196) | function DonateMessage(props: { onClose: () => void }): JSX.Element {
  function getFormattedHours (line 217) | function getFormattedHours(minutes) {

FILE: src/popup/popupUtils.ts
  function copyToClipboardPopup (line 3) | function copyToClipboardPopup(text: string, sendMessage: (request: Messa...

FILE: src/render/AdvancedSkipOptions.tsx
  class AdvancedSkipOptions (line 6) | class AdvancedSkipOptions {
    method constructor (line 7) | constructor(element: Element) {

FILE: src/render/CategoryChooser.tsx
  class CategoryChooser (line 6) | class CategoryChooser {
    method constructor (line 8) | constructor(element: Element) {

FILE: src/render/CategoryPill.tsx
  class CategoryPill (line 14) | class CategoryPill {
    method constructor (line 26) | constructor() {
    method attachToPage (line 36) | async attachToPage(onMobileYouTube: boolean, onInvidious: boolean,
    method attachToPageInternal (line 45) | private async attachToPageInternal(): Promise<void> {
    method close (line 106) | close(): void {
    method setVisibility (line 111) | setVisibility(show: boolean): void {
    method setSegment (line 121) | async setSegment(segment: SponsorTime): Promise<void> {

FILE: src/render/ChapterVote.tsx
  class ChapterVote (line 7) | class ChapterVote {
    method constructor (line 16) | constructor(vote: (type: number, UUID: SegmentUUID, category?: Categor...
    method getContainer (line 31) | getContainer(): HTMLElement {
    method close (line 35) | close(): void {
    method setVisibility (line 40) | setVisibility(show: boolean): void {
    method setSegment (line 53) | async setSegment(segment: SponsorTime): Promise<void> {

FILE: src/render/GenericNotice.tsx
  type TextBox (line 13) | interface TextBox {
  type NoticeOptions (line 18) | interface NoticeOptions {
  class GenericNotice (line 33) | class GenericNotice {
    method constructor (line 42) | constructor(contentContainer: ContentContainer, idSuffix: string, opti...
    method update (line 61) | update(options: NoticeOptions): void {
    method getMessageBoxes (line 105) | getMessageBoxes(idSuffix: string, textBoxes: TextBox[]): JSX.Element[] {
    method getButtons (line 123) | getButtons(buttons?: ButtonListener[]): JSX.Element[] {
    method close (line 144) | close(): void {

FILE: src/render/RectangleTooltip.tsx
  type RectangleTooltipProps (line 4) | interface RectangleTooltipProps {
  class RectangleTooltip (line 20) | class RectangleTooltip {
    method constructor (line 26) | constructor(props: RectangleTooltipProps) {
    method close (line 89) | close(): void {

FILE: src/render/SkipNotice.tsx
  class SkipNotice (line 12) | class SkipNotice {
    method constructor (line 23) | constructor(segments: SponsorTime[], autoSkip = false, contentContaine...
    method setShowKeybindHint (line 62) | setShowKeybindHint(value: boolean): void {
    method close (line 68) | close(): void {
    method toggleSkip (line 77) | toggleSkip(): void {
    method unmutedListener (line 81) | unmutedListener(time: number): void {
    method waitForSkipNoticeRef (line 85) | async waitForSkipNoticeRef(): Promise<SkipNoticeComponent> {

FILE: src/render/SubmissionNotice.tsx
  class SubmissionNotice (line 10) | class SubmissionNotice {
    method constructor (line 22) | constructor(contentContainer: ContentContainer, callback: () => Promis...
    method update (line 45) | update(): void {
    method close (line 49) | close(callRef = true): void {
    method submit (line 56) | submit(): void {
    method scrollToBottom (line 60) | scrollToBottom(): void {

FILE: src/render/Tooltip.tsx
  class Tooltip (line 3) | class Tooltip extends GenericTooltip {
    method constructor (line 4) | constructor(props: TooltipProps) {

FILE: src/render/UnsubmittedVideos.tsx
  class UnsubmittedVideos (line 5) | class UnsubmittedVideos {
    method constructor (line 9) | constructor(element: Element) {
    method update (line 18) | update(): void {

FILE: src/render/UpcomingNotice.tsx
  class UpcomingNotice (line 9) | class UpcomingNotice {
    method constructor (line 21) | constructor(segments: SponsorTime[], contentContainer: ContentContaine...
    method close (line 48) | close(): void {
    method sameNotice (line 55) | sameNotice(segments: SponsorTime[]): boolean {

FILE: src/svg-icons/checkIcon.tsx
  type CheckIconProps (line 3) | interface CheckIconProps {

FILE: src/svg-icons/clipboardIcon.tsx
  type ClipboardIconProps (line 3) | interface ClipboardIconProps {

FILE: src/svg-icons/pencilIcon.tsx
  type PencilIconProps (line 3) | interface PencilIconProps {

FILE: src/svg-icons/resetIcon.tsx
  type AddIconProps (line 3) | interface AddIconProps {

FILE: src/svg-icons/sb_svg.tsx
  type SbIconProps (line 3) | interface SbIconProps {
  function SbSvg (line 12) | function SbSvg({

FILE: src/types.ts
  type ContentContainer (line 5) | interface ContentContainer {
  type VideoDurationResponse (line 28) | interface VideoDurationResponse {
  type CategorySkipOption (line 32) | enum CategorySkipOption {
  type CategorySelection (line 40) | interface CategorySelection {
  type SponsorHideType (line 45) | enum SponsorHideType {
  type ActionType (line 52) | enum ActionType {
  type SegmentUUID (line 68) | type SegmentUUID = string  & { __segmentUUIDBrand: unknown };
  type Category (line 69) | type Category = string & { __categoryBrand: unknown };
  type SponsorSourceType (line 71) | enum SponsorSourceType {
  type SegmentContainer (line 78) | interface SegmentContainer {
  type SponsorTime (line 82) | interface SponsorTime extends SegmentContainer {
  type ScheduledTime (line 95) | interface ScheduledTime extends SponsorTime {
  type PreviewBarOption (line 99) | interface PreviewBarOption {
  type Registration (line 105) | interface Registration {
  type BackgroundScriptContainer (line 114) | interface BackgroundScriptContainer {
  type VideoInfo (line 119) | interface VideoInfo {
  type VideoID (line 195) | type VideoID = string;
  type UnEncodedSegmentTimes (line 197) | type UnEncodedSegmentTimes = [string, SponsorTime[]][];
  type ChannelIDStatus (line 199) | enum ChannelIDStatus {
  type ChannelIDInfo (line 205) | interface ChannelIDInfo {
  type SkipToTimeParams (line 210) | interface SkipToTimeParams {
  type ToggleSkippable (line 219) | interface ToggleSkippable {
  type NoticeVisibilityMode (line 224) | enum NoticeVisibilityMode {
  type SegmentListDefaultTab (line 232) | enum SegmentListDefaultTab {

FILE: src/utils.ts
  class Utils (line 12) | class Utils {
    method constructor (line 28) | constructor(backgroundScriptContainer: BackgroundScriptContainer = nul...
    method wait (line 32) | async wait<T>(condition: () => T, timeout = 5000, check = 100): Promis...
    method containsPermission (line 36) | containsPermission(permissions: chrome.permissions.Permissions): Promi...
    method setupExtraSitePermissions (line 50) | setupExtraSitePermissions(callback: (granted: boolean) => void): void {
    method getExtraSiteRegistration (line 70) | getExtraSiteRegistration(): Registration {
    method setupExtraSiteContentScripts (line 88) | setupExtraSiteContentScripts(): void {
    method removeExtraSiteRegistration (line 101) | removeExtraSiteRegistration(): void {
    method applyInvidiousPermissions (line 118) | applyInvidiousPermissions(enable: boolean, option = "supportInvidious"...
    method containsInvidiousPermission (line 135) | containsInvidiousPermission(): Promise<boolean> {
    method getMergedTimestamps (line 154) | getMergedTimestamps(timestamps: number[][]): [number, number][] {
    method getTimestampsDuration (line 196) | getTimestampsDuration(timestamps: number[][]): number {
    method getSponsorIndexFromUUID (line 202) | getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): nu...
    method getSponsorTimeFromUUID (line 212) | getSponsorTimeFromUUID(sponsorTimes: SponsorTime[], UUID: string): Spo...
    method getPermissionRegex (line 219) | getPermissionRegex(domains: string[] = []): string[] {
    method findReferenceNode (line 233) | findReferenceNode(): HTMLElement {
    method isContentScript (line 268) | isContentScript(): boolean {
    method isHex (line 272) | isHex(num: string): boolean {
    method addHiddenSegment (line 276) | async addHiddenSegment(videoID: VideoID, segmentUUID: string, hidden: ...

FILE: src/utils/arrayUtils.ts
  function partition (line 1) | function partition<T>(array: T[], filter: (element: T) => boolean): [T[]...

FILE: src/utils/categoryUtils.ts
  function getSkippingText (line 3) | function getSkippingText(segments: SponsorTime[], autoSkip: boolean): st...
  function getUpcomingText (line 41) | function getUpcomingText(segments: SponsorTime[]): string {
  function getVoteText (line 49) | function getVoteText(segments: SponsorTime[]): string {
  function getCategorySuffix (line 58) | function getCategorySuffix(category: Category): string {
  function shortCategoryName (line 70) | function shortCategoryName(categoryName: string): string {
  constant DEFAULT_CATEGORY (line 73) | const DEFAULT_CATEGORY = "chooseACategory";

FILE: src/utils/compatibility.ts
  function runCompatibilityChecks (line 3) | function runCompatibilityChecks() {
  function isVorapisInstalled (line 17) | function isVorapisInstalled() {

FILE: src/utils/configUtils.ts
  function showDonationLink (line 3) | function showDonationLink(): boolean {

FILE: src/utils/constants.ts
  function getGuidelineInfo (line 4) | function getGuidelineInfo(category: Category): TextBox[] {

FILE: src/utils/crossExtension.ts
  function isDeArrowInstalled (line 7) | function isDeArrowInstalled(): Promise<boolean> {
  function getExtensionIdsToImportFrom (line 36) | function getExtensionIdsToImportFrom(): string[] {

FILE: src/utils/exporter.ts
  function exportTimes (line 15) | function exportTimes(segments: SponsorTime[]): string {
  function exportTime (line 27) | function exportTime(segment: SponsorTime): string {
  function importTimes (line 35) | function importTimes(data: string, videoDuration: number): SponsorTime[] {
  function removeIf (line 99) | function removeIf(value: string, matchers: Array<{ matcher: RegExp; cond...
  function exportTimesAsHashParam (line 110) | function exportTimesAsHashParam(segments: SponsorTime[]): string {
  function normalizeChapterName (line 122) | function normalizeChapterName(description: string): string {

FILE: src/utils/genericUtils.ts
  function getLuminance (line 2) | function getLuminance(color: string): number {
  function hexToRgb (line 9) | function hexToRgb(hex: string): { r: number; g: number; b: number } | nu...
  function indexesOf (line 24) | function indexesOf<T>(array: T[], value: T): number[] {

FILE: src/utils/logger.ts
  function logDebug (line 8) | function logDebug(message: string) {
  function logWarn (line 16) | function logWarn(message: string) {

FILE: src/utils/mobileUtils.ts
  function isMobileControlsOpen (line 1) | function isMobileControlsOpen(): boolean {

FILE: src/utils/noticeUtils.ts
  type SkipNoticeAction (line 4) | enum SkipNoticeAction {
  function downvoteButtonColor (line 14) | function downvoteButtonColor(segments: SponsorTime[], actionState: SkipN...

FILE: src/utils/pageCleaner.ts
  function cleanPage (line 1) | function cleanPage() {

FILE: src/utils/pageUtils.ts
  function getControls (line 5) | function getControls(): HTMLElement {
  function isInPreviewPlayer (line 32) | function isInPreviewPlayer(element: Element): boolean {
  function isVisible (line 36) | function isVisible(element: HTMLElement): boolean {
  function getHashParams (line 40) | function getHashParams(): Record<string, unknown> {
  function hasAutogeneratedChapters (line 61) | function hasAutogeneratedChapters(): boolean {
  function getExistingChapters (line 65) | function getExistingChapters(currentVideoID: VideoID, duration: number):...
  function isPlayingPlaylist (line 110) | function isPlayingPlaylist() {

FILE: src/utils/requests.ts
  function asyncRequestToServer (line 12) | async function asyncRequestToServer(type: string, address: string, data ...

FILE: src/utils/segmentData.ts
  type SegmentResponse (line 20) | interface SegmentResponse {
  function getSegmentsForVideo (line 25) | async function getSegmentsForVideo(videoID: VideoID, ignoreCache: boolea...
  function fetchSegmentsForVideo (line 57) | async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentR...

FILE: src/utils/skipProfiles.ts
  function getSkipProfileIDForTime (line 7) | function getSkipProfileIDForTime(): ConfigurationID | null {
  function getSkipProfileIDForTab (line 15) | function getSkipProfileIDForTab(): ConfigurationID | null {
  function setCurrentTabSkipProfile (line 19) | function setCurrentTabSkipProfile(configID: ConfigurationID | null) {
  function getSkipProfileIDForVideo (line 23) | function getSkipProfileIDForVideo(): ConfigurationID | null {
  function getSkipProfileIDForChannel (line 27) | function getSkipProfileIDForChannel(): ConfigurationID | null {
  function getSkipProfileID (line 39) | function getSkipProfileID(): ConfigurationID | null {
  function getSkipProfile (line 49) | function getSkipProfile(): CustomConfiguration | null {
  type SkipProfileBoolKey (line 59) | type SkipProfileBoolKey =
  function getSkipProfileBool (line 67) | function getSkipProfileBool(key: SkipProfileBoolKey): boolean {
  function getSkipProfileNum (line 71) | function getSkipProfileNum(key: "minDuration"): number {
  function getSkipProfileValue (line 75) | function getSkipProfileValue<T>(key: keyof CustomConfiguration): T {
  function hideTooShortSegments (line 84) | function hideTooShortSegments(sponsorTimes: SponsorTime[]) {

FILE: src/utils/skipRule.ts
  constant SKIP_RULE_ATTRIBUTES (line 10) | const SKIP_RULE_ATTRIBUTES = Object.values(SkipRuleAttribute);
  constant SKIP_RULE_OPERATORS (line 11) | const SKIP_RULE_OPERATORS = Object.values(SkipRuleOperator);
  constant INVERTED_SKIP_RULE_OPERATORS (line 12) | const INVERTED_SKIP_RULE_OPERATORS = {
  constant WORD_EXTRA_CHARACTER (line 26) | const WORD_EXTRA_CHARACTER = /[a-zA-Z0-9.]/;
  constant OPERATOR_EXTRA_CHARACTER (line 27) | const OPERATOR_EXTRA_CHARACTER = /[<>=!~*&|-]/;
  constant ANY_EXTRA_CHARACTER (line 28) | const ANY_EXTRA_CHARACTER = /[a-zA-Z0-9<>=!~*&|.-]/;
  function getCategorySelection (line 30) | function getCategorySelection(segment: SponsorTime | VideoLabelsCacheDat...
  function getSkipCheckValue (line 61) | function getSkipCheckValue(segment: SponsorTime | VideoLabelsCacheData, ...
  function isSkipCheckPassing (line 120) | function isSkipCheckPassing(segment: SponsorTime | VideoLabelsCacheData,...
  function isSkipPredicatePassing (line 153) | function isSkipPredicatePassing(segment: SponsorTime | VideoLabelsCacheD...
  function getCategoryDefaultSelection (line 166) | function getCategoryDefaultSelection(category: string): CategorySelection {
  type TokenType (line 175) | type TokenType =
  type SourcePos (line 185) | interface SourcePos {
  type Span (line 189) | interface Span {
  type Token (line 194) | interface Token {
  class Lexer (line 200) | class Lexer {
    method constructor (line 208) | public constructor(source: string) {
    method makeToken (line 216) | private makeToken(type: TokenType): Token {
    method consume (line 230) | private consume(): string | null {
    method peek (line 254) | private peek(): string | null {
    method expectKeyword (line 273) | private expectKeyword(keywords: readonly string[], caseSensitive: bool...
    method skipWhitespace (line 293) | private skipWhitespace() {
    method skipLine (line 312) | private skipLine() {
    method isEof (line 326) | private isEof(): boolean {
    method resetToCurrent (line 337) | private resetToCurrent() {
    method nextToken (line 342) | public nextToken(): Token {
  type ParseError (line 559) | interface ParseError {
  class Parser (line 564) | class Parser {
    method constructor (line 576) | public constructor(lexer: Lexer) {
    method errorAt (line 596) | private errorAt(span: Span, message: string, panic: boolean) {
    method error (line 613) | private error(message: string, panic: boolean) {
    method errorAtCurrent (line 625) | private errorAtCurrent(message: string, panic: boolean) {
    method consume (line 635) | private consume() {
    method match (line 649) | private match(expected: readonly TokenType[]): boolean {
    method expect (line 668) | private expect(expected: readonly TokenType[], message: string, panic:...
    method synchronize (line 678) | private synchronize() {
    method isEof (line 693) | private isEof(): boolean {
    method parse (line 703) | public parse(): { rules: AdvancedSkipRule[]; errors: ParseError[] } {
    method parseRule (line 720) | private parseRule(): AdvancedSkipRule | null {
    method parsePredicate (line 755) | private parsePredicate(): AdvancedSkipPredicate | null {
    method parseOr (line 759) | private parseOr(): AdvancedSkipPredicate | null {
    method parseAnd (line 775) | private parseAnd(): AdvancedSkipPredicate | null {
    method parseUnary (line 791) | private parseUnary(): AdvancedSkipPredicate | null {
    method parsePrimary (line 800) | private parsePrimary(): AdvancedSkipPredicate | null {
    method parseCheck (line 810) | private parseCheck(): AdvancedSkipCheck | null {
  function parseConfig (line 856) | function parseConfig(config: string): { rules: AdvancedSkipRule[]; error...
  function configToText (line 861) | function configToText(config: AdvancedSkipRule[]): string {
  function predicateToText (line 895) | function predicateToText(predicate: AdvancedSkipPredicate, outerPreceden...
  function invertPredicate (line 914) | function invertPredicate(predicate: AdvancedSkipPredicate): AdvancedSkip...

FILE: src/utils/skipRule.type.ts
  type Permission (line 3) | interface Permission {
  type SkipRuleAttribute (line 10) | enum SkipRuleAttribute {
  type SkipRuleOperator (line 30) | enum SkipRuleOperator {
  type AdvancedSkipCheck (line 45) | interface AdvancedSkipCheck {
  type PredicateOperator (line 52) | enum PredicateOperator {
  type AdvancedSkipOperator (line 57) | interface AdvancedSkipOperator {
  type AdvancedSkipPredicate (line 65) | type AdvancedSkipPredicate = AdvancedSkipCheck | AdvancedSkipOperator;
  type AdvancedSkipRule (line 67) | interface AdvancedSkipRule {

FILE: src/utils/thumbnails.ts
  function handleThumbnails (line 9) | async function handleThumbnails(thumbnails: HTMLImageElement[]): Promise...
  function labelThumbnail (line 16) | async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTML...
  function setupThumbnailHover (line 44) | async function setupThumbnailHover(thumbnail: HTMLImageElement): Promise...
  function thumbnailHoverListener (line 55) | function thumbnailHoverListener(e: MouseEvent) {
  function getLink (line 85) | function getLink(thumbnail: HTMLImageElement): HTMLAnchorElement | null {
  function extractVideoIDFromElement (line 105) | async function extractVideoIDFromElement(thumbnail: HTMLImageElement): P...
  function getOldThumbnailLabel (line 112) | function getOldThumbnailLabel(thumbnail: HTMLImageElement): HTMLElement ...
  function hideThumbnailLabel (line 116) | function hideThumbnailLabel(thumbnail: HTMLImageElement): void {
  function createOrGetThumbnail (line 123) | function createOrGetThumbnail(thumbnail: HTMLImageElement): { overlay: H...
  function createSBIconElement (line 156) | function createSBIconElement(): SVGSVGElement {
  function insertSBIconDefinition (line 167) | function insertSBIconDefinition() {
  function setupThumbnailListener (line 183) | function setupThumbnailListener(): void {

FILE: src/utils/urlParser.ts
  function getStartTimeFromUrl (line 2) | function getStartTimeFromUrl(url: string): number {
  function urlTimeToSeconds (line 9) | function urlTimeToSeconds(time: string): number {

FILE: src/utils/videoLabels.ts
  type VideoLabelsCacheData (line 8) | interface VideoLabelsCacheData {
  type LabelCacheEntry (line 13) | interface LabelCacheEntry {
  function getLabelHashBlock (line 21) | async function getLabelHashBlock(hashPrefix: string): Promise<LabelCache...
  function getVideoLabel (line 71) | async function getVideoLabel(videoID: VideoID): Promise<Category | null> {
  function getHasStartSegment (line 87) | async function getHasStartSegment(videoID: VideoID): Promise<boolean | n...

FILE: src/utils/warnings.ts
  type ChatConfig (line 10) | interface ChatConfig {
  function openWarningDialog (line 16) | async function openWarningDialog(contentContainer: ContentContainer): Pr...
  function openChat (line 86) | function openChat(config: ChatConfig): void {

FILE: test/selenium.test.ts
  function setup (line 58) | async function setup(): Promise<WebDriver> {
  function waitForInstall (line 74) | async function waitForInstall(driver: WebDriver, startingTab = 0): Promi...
  function goToVideo (line 83) | async function goToVideo(driver: WebDriver, videoId: string): Promise<vo...
  function createSegment (line 88) | async function createSegment(driver: WebDriver, startTime: string, endTi...
  function editSegments (line 109) | async function editSegments(driver: WebDriver, index: number, expectedSt...
  function getStartTimeBox (line 143) | async function getStartTimeBox(driver: WebDriver, index: number, expecte...
  function getEndTimeBox (line 149) | async function getEndTimeBox(driver: WebDriver, index: number, expectedE...
  function getDisplayTimeBox (line 155) | async function getDisplayTimeBox(driver: WebDriver, index: number, expec...
  function setSegmentCategory (line 164) | async function setSegmentCategory(driver: WebDriver, index: number, cate...
  function setSegmentActionType (line 174) | async function setSegmentActionType(driver: WebDriver, index: number, ac...
  function autoskipSegment (line 184) | async function autoskipSegment(driver: WebDriver, startTime: number, end...
  function muteSkipSegment (line 195) | async function muteSkipSegment(driver: WebDriver, startTime: number, end...
  function toggleWhitelist (line 210) | async function toggleWhitelist(driver: WebDriver): Promise<void> {

FILE: webpack/configDiffPlugin.js
  function partialDeepEquals (line 9) | function partialDeepEquals (actual, expected, logger) {
  function printActualExpected (line 38) | function printActualExpected(key, actual, expected, logger) {
  class configDiffPlugin (line 44) | class configDiffPlugin {
    method apply (line 45) | apply(compiler) {

FILE: webpack/webpack.common.js
  class DocumentScriptCompiler (line 62) | class DocumentScriptCompiler {
    method apply (line 69) | apply(compiler) {
  method transform (line 159) | transform(content, path) {

FILE: webpack/webpack.manifest.js
  class BuildManifest (line 35) | class BuildManifest {
    method constructor (line 36) | constructor (options = {}) {
    method apply (line 42) | apply() {
  function mergeObjects (line 81) | function mergeObjects(object1, object2) {

FILE: webpack/webpack.prod.js
  function createGHPSourceMapURL (line 6) | async function createGHPSourceMapURL(env) {
Condensed preview — 141 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (953K chars).
[
  {
    "path": ".editorconfig",
    "chars": 327,
    "preview": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines wit"
  },
  {
    "path": ".eslintrc.json",
    "chars": 1027,
    "preview": "{\n    \"env\": {\n        \"browser\": true,\n        \"es2021\": true,\n        \"node\": true,\n        \"jest\": true\n    },\n    \"e"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 69,
    "preview": "github: ajayyy-org\npatreon: ajayyy\ncustom: [sponsor.ajay.app/donate]\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 348,
    "preview": "- [ ] I agree to license my contribution under GPL-3.0 and agree to allow distribution on app stores as outlined in [LIC"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2037,
    "preview": "name: CI\n\non: [push, pull_request]\n\njobs:\n\n  build:\n    name: Create artifacts\n    runs-on: ubuntu-latest\n\n    steps:\n  "
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 10003,
    "preview": "name: Upload Release Build\n\non: \n  release:\n    types: [published]\n\njobs:\n\n  build:\n    name: Upload Release\n    runs-on"
  },
  {
    "path": ".github/workflows/take-action.yml",
    "chars": 318,
    "preview": "# .github/workflows/take.yml \nname: Assign issue to contributor\non: \n  issue_comment:\n\njobs:\n  assign:\n    name: Take an"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 836,
    "preview": "name: Tests\n\non: [push, pull_request]\n\njobs:\n  test:\n    name: Run tests\n    runs-on: ubuntu-latest\n\n    steps:\n      # "
  },
  {
    "path": ".github/workflows/update-oss-attribution.yml",
    "chars": 1109,
    "preview": "name: update oss attributions\non:\n  push:\n    branches:\n      - master\n    paths:\n      - 'package.json'\n      - 'packag"
  },
  {
    "path": ".github/workflows/updateInvidous.yml",
    "chars": 1004,
    "preview": "name: update invidious\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 0 1 * *' # check every month\n\njobs:\n  check-l"
  },
  {
    "path": ".gitignore",
    "chars": 152,
    "preview": "config.json\nignored\n.idea/\nnode_modules\nweb-ext-artifacts\n.vscode/\ndist/\ntmp/\n.DS_Store\nci/invidious_instances.json\nci/p"
  },
  {
    "path": ".gitmodules",
    "chars": 197,
    "preview": "[submodule \"public/_locales\"]\n\tpath = public/_locales\n\turl = https://github.com/ajayyy/ExtensionTranslations\n[submodule "
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2487,
    "preview": "If you make any contributions to SponsorBlock after this file was created, you are agreeing that any code you have contr"
  },
  {
    "path": "LICENSE",
    "chars": 35147,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "LICENSE-APPSTORE.txt",
    "chars": 872,
    "preview": "The developers are aware that the terms of service that\napply to apps distributed via Apple's App Store services and sim"
  },
  {
    "path": "LICENSE-HISTORY.txt",
    "chars": 7830,
    "preview": "Prior to commit 7338af3b384e2297eaf710443121ac840099a9f1, this project was licensed under LGPL 3.0.\n\nYou must follow LIC"
  },
  {
    "path": "README.md",
    "chars": 5064,
    "preview": "<p align=\"center\">\n  <a href=\"https://sponsor.ajay.app\"><img src=\"public/icons/LogoSponsorBlocker256px.png\" alt=\"Logo\"><"
  },
  {
    "path": "ci/generateList.ts",
    "chars": 1796,
    "preview": "/*\nThis file is only ran by GitHub Actions in order to populate the Invidious instances list\n\nThis file should not be sh"
  },
  {
    "path": "ci/invidiousCI.ts",
    "chars": 1088,
    "preview": "import { InvidiousInstance, monitor } from \"./invidiousType\"\n\nimport * as data from \"../ci/invidious_instances.json\";\n\n/"
  },
  {
    "path": "ci/invidiousType.ts",
    "chars": 1417,
    "preview": "export type InvidiousInstance = [\n  string,\n  {\n    flag: string;\n    region: string;\n    stats: null | ivStats;\n    cor"
  },
  {
    "path": "ci/invidiouslist.json",
    "chars": 283,
    "preview": "[\"www.youtubekids.com\",\"inv.nadeko.net\",\"inv.tux.pizza\",\"invidious.adminforge.de\",\"invidious.jing.rocks\",\"invidious.nerd"
  },
  {
    "path": "ci/pipedCI.ts",
    "chars": 3353,
    "preview": "import * as data from \"../ci/piped_instances.json\";\n\ntype percent = string\ntype dailyMinutesDown = Record<string, number"
  },
  {
    "path": "ci/prettify.ts",
    "chars": 252,
    "preview": "import { writeFile } from 'fs';\n\nimport * as license from \"../oss-attribution/licenseInfos.json\";\n\nconst result = JSON.s"
  },
  {
    "path": "config.json.example",
    "chars": 2184,
    "preview": "{\n    \"serverAddress\": \"https://sponsor.ajay.app\",\n    \"testingServerAddress\": \"https://sponsor.ajay.app/test\",\n    \"ser"
  },
  {
    "path": "crowdin.yml",
    "chars": 115,
    "preview": "files:\n  - source: /public/_locales/en/*\n    translation: /public/_locales/%two_letters_code%/%original_file_name%\n"
  },
  {
    "path": "jest.config.js",
    "chars": 164,
    "preview": "module.exports = {\n    \"roots\": [\n        \"test\"\n    ],\n    \"transform\": {\n        \"^.+\\\\.ts$\": \"ts-jest\"\n    },\n    \"re"
  },
  {
    "path": "manifest/beta-manifest-extra.json",
    "chars": 40,
    "preview": "{\n    \"name\": \"BETA - SponsorBlock\"\n}\n  "
  },
  {
    "path": "manifest/chrome-manifest-extra.json",
    "chars": 4489,
    "preview": "{\n  \"host_permissions\": [\n    \"https://*.youtube.com/*\",\n    \"https://sponsor.ajay.app/*\"\n  ],\n  \"optional_host_permissi"
  },
  {
    "path": "manifest/firefox-beta-manifest-extra.json",
    "chars": 124,
    "preview": "{\n    \"browser_specific_settings\": {\n        \"gecko\": {\n            \"id\": \"sponsorBlockerBETA@ajay.app\"\n        }\n    }\n"
  },
  {
    "path": "manifest/firefox-manifest-extra.json",
    "chars": 306,
    "preview": "{\n  \"browser_specific_settings\": {\n    \"gecko\": {\n      \"id\": \"sponsorBlocker@ajay.app\",\n      \"strict_min_version\": \"10"
  },
  {
    "path": "manifest/manifest-v2-extra.json",
    "chars": 4368,
    "preview": "{\n    \"web_accessible_resources\": [\n        \"icons/LogoSponsorBlocker256px.png\",\n        \"icons/IconSponsorBlocker256px."
  },
  {
    "path": "manifest/manifest.json",
    "chars": 784,
    "preview": "{\n    \"name\": \"__MSG_fullName__\",\n    \"short_name\": \"SponsorBlock\",\n    \"version\": \"6.1.2\",\n    \"default_locale\": \"en\",\n"
  },
  {
    "path": "manifest/safari-manifest-extra.json",
    "chars": 470,
    "preview": "{\n  \"background\": {\n    \"persistent\": false\n  },\n  \"optional_permissions\": [\n    \"webNavigation\"\n  ],\n  \"browser_action\""
  },
  {
    "path": "oss-attribution/licenseInfos.json",
    "chars": 13729,
    "preview": "{\n  \"content-scripts-register-polyfill\": {\n    \"ignore\": false,\n    \"name\": \"content-scripts-register-polyfill\",\n    \"ve"
  },
  {
    "path": "package.json",
    "chars": 3824,
    "preview": "{\n  \"name\": \"sponsorblock\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"background.js\",\n  \"dependencies\": {\n  "
  },
  {
    "path": "public/content.css",
    "chars": 18127,
    "preview": ":root {\n\t--skip-notice-right: 10px;\n\t--skip-notice-padding: 5px;\n\t--skip-notice-margin: 5px;\n\t--skip-notice-border-horiz"
  },
  {
    "path": "public/help/index.html",
    "chars": 7460,
    "preview": "<!DOCTYPE html>\n\n<head>\n  <title> SponsorBlock </title>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width="
  },
  {
    "path": "public/help/styles.css",
    "chars": 6186,
    "preview": ":root {\n  --color-scheme: dark;\n  --background: #333333;\n  --header-color: #212121;\n  --dialog-background: #181818;\n  --"
  },
  {
    "path": "public/libs/Source+Sans+Pro.css",
    "chars": 4630,
    "preview": "/* cyrillic-ext */\n@font-face {\n  font-family: 'Source Sans Pro';\n  font-style: normal;\n  font-weight: 400;\n  src: local"
  },
  {
    "path": "public/options/options.css",
    "chars": 16889,
    "preview": "/* Options page CSS */\n\n:root {\n    --color-scheme: dark;\n    --background: #333333;\n    --menu-background: #181818;\n   "
  },
  {
    "path": "public/options/options.html",
    "chars": 23091,
    "preview": "<!DOCTYPE html>\n<!-- Link to specific tabs by using their ID in the URL like: options.html#keybinds -->\n\n<head>\n  <title"
  },
  {
    "path": "public/oss-attribution/attribution.txt",
    "chars": 12010,
    "preview": "content-scripts-register-polyfill\n4.0.2 <https://github.com/fregante/content-scripts-register-polyfill>\nMIT License\n\nCop"
  },
  {
    "path": "public/permissions/index.html",
    "chars": 600,
    "preview": "<!DOCTYPE html>\n\n<head>\n  <title>Permissions - SponsorBlock</title>\n  <meta charset=\"utf-8\">\n\n  <link href=\"styles.css\" "
  },
  {
    "path": "public/permissions/styles.css",
    "chars": 4985,
    "preview": "/* Options page CSS */\nhtml {\n    color-scheme: dark;\n}\n\nbody {\n    font-family: sans-serif;\n}\n\n.center {\n    text-align"
  },
  {
    "path": "public/popup.css",
    "chars": 14220,
    "preview": ":root {\n  --sb-main-font-family: \"Source Sans Pro\", sans-serif;\n  --sb-main-bg-color: #222;\n  --sb-main-fg-color: #fff;\n"
  },
  {
    "path": "public/popup.html",
    "chars": 342,
    "preview": "<html id=\"sponsorBlockPopupHTML\">\n    <head>\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\""
  },
  {
    "path": "public/res/countries.json",
    "chars": 4595,
    "preview": "{\"Albania\":{\"allowed\":true},\"Algeria\":{\"allowed\":true},\"Angola\":{\"allowed\":true},\"Argentina\":{\"allowed\":true},\"Armenia\":"
  },
  {
    "path": "public/shared.css",
    "chars": 3911,
    "preview": ".sponsorSkipNoticeParent {\n    position: absolute;\n\n\tbottom: 100px;\n\tright: var(--skip-notice-right);\n}\n\n.sponsorSkipNot"
  },
  {
    "path": "src/background.ts",
    "chars": 8943,
    "preview": "import * as CompileConfig from \"../config.json\";\n\nimport Config from \"./config\";\nimport { Registration } from \"./types\";"
  },
  {
    "path": "src/components/CategoryPillComponent.tsx",
    "chars": 7444,
    "preview": "import * as React from \"react\";\nimport Config from \"../config\";\nimport { Category, SegmentUUID, SponsorTime } from \"../t"
  },
  {
    "path": "src/components/ChapterVoteComponent.tsx",
    "chars": 7019,
    "preview": "import * as React from \"react\";\nimport Config from \"../config\";\nimport { ActionType, Category, SegmentUUID, SponsorTime "
  },
  {
    "path": "src/components/NoticeComponent.tsx",
    "chars": 17711,
    "preview": "import * as React from \"react\";\nimport Config from \"../config\";\nimport SbSvg from \"../svg-icons/sb_svg\";\n\nenum Countdown"
  },
  {
    "path": "src/components/NoticeTextSectionComponent.tsx",
    "chars": 1923,
    "preview": "import * as React from \"react\";\n\nexport interface NoticeTextSelectionProps {\n    icon?: string;\n    text: string;\n    id"
  },
  {
    "path": "src/components/SelectorComponent.tsx",
    "chars": 1588,
    "preview": "import * as React from \"react\";\n\nexport interface SelectorOption {\n    label: string;\n}\n\nexport interface SelectorProps "
  },
  {
    "path": "src/components/SkipNoticeComponent.tsx",
    "chars": 34490,
    "preview": "import * as React from \"react\";\nimport * as CompileConfig from \"../../config.json\";\nimport Config from \"../config\"\nimpor"
  },
  {
    "path": "src/components/SponsorTimeEditComponent.tsx",
    "chars": 35906,
    "preview": "import * as React from \"react\";\nimport * as CompileConfig from \"../../config.json\";\nimport Config from \"../config\";\nimpo"
  },
  {
    "path": "src/components/SubmissionNoticeComponent.tsx",
    "chars": 11987,
    "preview": "import * as React from \"react\";\nimport Config from \"../config\"\nimport GenericNotice from \"../render/GenericNotice\";\nimpo"
  },
  {
    "path": "src/components/options/AdvancedSkipOptionsComponent.tsx",
    "chars": 2940,
    "preview": "import * as React from \"react\";\n\nimport Config from \"../../config\";\nimport { configToText, parseConfig, } from \"../../ut"
  },
  {
    "path": "src/components/options/CategoryChooserComponent.tsx",
    "chars": 15099,
    "preview": "import * as React from \"react\";\n\nimport * as CompileConfig from \"../../../config.json\";\nimport { Category, CategorySelec"
  },
  {
    "path": "src/components/options/CategorySkipOptionsComponent.tsx",
    "chars": 12219,
    "preview": "import * as React from \"react\";\n\nimport Config, { ConfigurationID } from \"../../config\"\nimport * as CompileConfig from \""
  },
  {
    "path": "src/components/options/KeybindComponent.tsx",
    "chars": 2788,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from 'react-dom/client';\nimport Config from \"../../config\";\n"
  },
  {
    "path": "src/components/options/KeybindDialogComponent.tsx",
    "chars": 8114,
    "preview": "import * as React from \"react\";\nimport { ChangeEvent } from \"react\";\nimport Config from \"../../config\";\nimport { Keybind"
  },
  {
    "path": "src/components/options/NumberInputOptionComponent.tsx",
    "chars": 1828,
    "preview": "import * as React from \"react\";\nimport ResetIcon from \"../../svg-icons/resetIcon\";\n\nexport interface NumberInputOptionPr"
  },
  {
    "path": "src/components/options/SelectOptionComponent.tsx",
    "chars": 1727,
    "preview": "import * as React from \"react\";\nimport ResetIcon from \"../../svg-icons/resetIcon\";\n\nexport interface SelectOption {\n    "
  },
  {
    "path": "src/components/options/ToggleOptionComponent.tsx",
    "chars": 1798,
    "preview": "import * as React from \"react\";\nimport ResetIcon from \"../../svg-icons/resetIcon\";\n\nexport interface ToggleOptionProps {"
  },
  {
    "path": "src/components/options/UnsubmittedVideoListComponent.tsx",
    "chars": 2064,
    "preview": "import * as React from \"react\";\n\nimport Config from \"../../config\";\nimport UnsubmittedVideoListItem from \"./UnsubmittedV"
  },
  {
    "path": "src/components/options/UnsubmittedVideoListItem.tsx",
    "chars": 3514,
    "preview": "import * as React from \"react\";\n\nimport Config from \"../../config\";\nimport { exportTimes, exportTimesAsHashParam } from "
  },
  {
    "path": "src/components/options/UnsubmittedVideosComponent.tsx",
    "chars": 2141,
    "preview": "import * as React from \"react\";\nimport Config from \"../../config\";\nimport UnsubmittedVideoListComponent from \"./Unsubmit"
  },
  {
    "path": "src/config.ts",
    "chars": 20557,
    "preview": "import * as CompileConfig from \"../config.json\";\nimport * as invidiousList from \"../ci/invidiouslist.json\";\nimport { Cat"
  },
  {
    "path": "src/content.ts",
    "chars": 115024,
    "preview": "import Config from \"./config\";\nimport {\n    ActionType,\n    Category,\n    CategorySkipOption,\n    ChannelIDStatus,\n    C"
  },
  {
    "path": "src/dearrowPromotion.ts",
    "chars": 3511,
    "preview": "import { waitFor } from \"../maze-utils/src\";\nimport { getYouTubeTitleNode } from \"../maze-utils/src/elements\";\nimport { "
  },
  {
    "path": "src/document.ts",
    "chars": 68,
    "preview": "import { init } from \"../maze-utils/src/injected/document\";\n\ninit();"
  },
  {
    "path": "src/globals.d.ts",
    "chars": 94,
    "preview": "import { SBObject } from \"./config\";\ndeclare global {\n    interface Window { SB: SBObject }\n}\n"
  },
  {
    "path": "src/help.ts",
    "chars": 1862,
    "preview": "import { localizeHtmlPage } from \"../maze-utils/src/setup\";\nimport Config from \"./config\";\nimport { showDonationLink } f"
  },
  {
    "path": "src/js-components/previewBar.ts",
    "chars": 54656,
    "preview": "/*\nBased on code from\nhttps://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd51f62a4e7fef4d4/se"
  },
  {
    "path": "src/js-components/skipButtonControlBar.ts",
    "chars": 7668,
    "preview": "import Config from \"../config\";\nimport { SegmentUUID, SponsorTime } from \"../types\";\nimport { getSkippingText } from \".."
  },
  {
    "path": "src/messageTypes.ts",
    "chars": 3492,
    "preview": "//\n// Message and Response Types\n//\n\nimport { ConfigurationID } from \"./config\";\nimport { SegmentUUID, SponsorHideType, "
  },
  {
    "path": "src/options.ts",
    "chars": 29766,
    "preview": "import * as React from \"react\";\nimport { createRoot } from 'react-dom/client';\n\nimport Config, { generateDebugDetails } "
  },
  {
    "path": "src/permissions.ts",
    "chars": 985,
    "preview": "import Config from \"./config\";\nimport Utils from \"./utils\";\nimport { localizeHtmlPage } from \"../maze-utils/src/setup\";\n"
  },
  {
    "path": "src/popup/PopupComponent.tsx",
    "chars": 28560,
    "preview": "import * as React from \"react\";\nimport { YourWorkComponent } from \"./YourWorkComponent\";\nimport { isSafari } from \"../.."
  },
  {
    "path": "src/popup/SegmentListComponent.tsx",
    "chars": 23986,
    "preview": "import * as React from \"react\";\nimport { ActionType, SegmentListDefaultTab, SegmentUUID, SponsorHideType, SponsorTime, V"
  },
  {
    "path": "src/popup/SegmentSubmissionComponent.tsx",
    "chars": 2972,
    "preview": "import * as React from \"react\";\nimport { VideoID } from \"../types\";\nimport Config from \"../config\";\nimport { Message, Me"
  },
  {
    "path": "src/popup/YourWorkComponent.tsx",
    "chars": 11343,
    "preview": "import * as React from \"react\";\nimport { getHash } from \"../../maze-utils/src/hash\";\nimport { formatJSErrorMessage, getS"
  },
  {
    "path": "src/popup/popup.tsx",
    "chars": 398,
    "preview": "import * as React from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { PopupComponent } from \"./PopupCo"
  },
  {
    "path": "src/popup/popupUtils.ts",
    "chars": 377,
    "preview": "import { Message, MessageResponse } from \"../messageTypes\";\n\nexport function copyToClipboardPopup(text: string, sendMess"
  },
  {
    "path": "src/render/AdvancedSkipOptions.tsx",
    "chars": 407,
    "preview": "import * as React from \"react\";\nimport { createRoot } from 'react-dom/client';\n\nimport { AdvancedSkipOptionsComponent } "
  },
  {
    "path": "src/render/CategoryChooser.tsx",
    "chars": 388,
    "preview": "import * as React from \"react\";\nimport { createRoot } from 'react-dom/client';\n\nimport { CategoryChooserComponent } from"
  },
  {
    "path": "src/render/CategoryPill.tsx",
    "chars": 5578,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\nimport CategoryPillComponent, { Cat"
  },
  {
    "path": "src/render/ChapterVote.tsx",
    "chars": 1917,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from 'react-dom/client';\nimport ChapterVoteComponent, { Chap"
  },
  {
    "path": "src/render/GenericNotice.tsx",
    "chars": 5007,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from 'react-dom/client';\nimport NoticeComponent from \"../com"
  },
  {
    "path": "src/render/RectangleTooltip.tsx",
    "chars": 3371,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from 'react-dom/client';\n\nexport interface RectangleTooltipP"
  },
  {
    "path": "src/render/SkipNotice.tsx",
    "chars": 3882,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from 'react-dom/client';\n\nimport Utils from \"../utils\";\ncons"
  },
  {
    "path": "src/render/SubmissionNotice.tsx",
    "chars": 1787,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from 'react-dom/client';\n\nimport Utils from \"../utils\";\ncons"
  },
  {
    "path": "src/render/Tooltip.tsx",
    "chars": 239,
    "preview": "import { GenericTooltip, TooltipProps } from \"../../maze-utils/src/components/Tooltip\";\n\nexport class Tooltip extends Ge"
  },
  {
    "path": "src/render/UnsubmittedVideos.tsx",
    "chars": 573,
    "preview": "import * as React from \"react\";\nimport { createRoot } from 'react-dom/client';\nimport UnsubmittedVideosComponent from \"."
  },
  {
    "path": "src/render/UpcomingNotice.tsx",
    "chars": 2010,
    "preview": "import * as React from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\nimport { ContentContainer, SponsorT"
  },
  {
    "path": "src/svg-icons/checkIcon.tsx",
    "chars": 519,
    "preview": "import * as React from \"react\";\n\nexport interface CheckIconProps {\n  id?: string;\n  style?: React.CSSProperties;\n  class"
  },
  {
    "path": "src/svg-icons/clipboardIcon.tsx",
    "chars": 674,
    "preview": "import * as React from \"react\";\n\nexport interface ClipboardIconProps {\n  id?: string;\n  style?: React.CSSProperties;\n  c"
  },
  {
    "path": "src/svg-icons/lock_svg.tsx",
    "chars": 803,
    "preview": "import * as React from \"react\";\n\nconst lockSvg = ({\n  fill = \"#fcba03\",\n  className = \"\",\n  width = \"20\",\n  height = \"20"
  },
  {
    "path": "src/svg-icons/pencilIcon.tsx",
    "chars": 618,
    "preview": "import * as React from \"react\";\n\nexport interface PencilIconProps {\n  id?: string;\n  style?: React.CSSProperties;\n  clas"
  },
  {
    "path": "src/svg-icons/pencil_svg.tsx",
    "chars": 448,
    "preview": "import * as React from \"react\";\n\nconst pencilSvg = ({\n  fill = \"#ffffff\"\n  }): JSX.Element => (\n    <svg\n      xmlns=\"ht"
  },
  {
    "path": "src/svg-icons/resetIcon.tsx",
    "chars": 1181,
    "preview": "import * as React from \"react\";\n\nexport interface AddIconProps {\n  style?: React.CSSProperties;\n  className?: string;\n  "
  },
  {
    "path": "src/svg-icons/sb_svg.tsx",
    "chars": 1937,
    "preview": "import * as React from \"react\";\n\nexport interface SbIconProps {\n  id?: string;\n  fill?: string;\n  className?: string;\n  "
  },
  {
    "path": "src/svg-icons/thumbs_down_svg.tsx",
    "chars": 685,
    "preview": "import * as React from \"react\";\n\nconst thumbsDownSvg = ({\n  fill = \"#ffffff\",\n  className = \"\",\n  width = \"18\",\n  height"
  },
  {
    "path": "src/svg-icons/thumbs_up_svg.tsx",
    "chars": 675,
    "preview": "import * as React from \"react\";\n\nconst thumbsUpSvg = ({\n  fill = \"#ffffff\",\n  className = \"\",\n  width = \"18\",\n  height ="
  },
  {
    "path": "src/types.ts",
    "chars": 5873,
    "preview": "import SubmissionNotice from \"./render/SubmissionNotice\";\nimport SkipNoticeComponent from \"./components/SkipNoticeCompon"
  },
  {
    "path": "src/utils/arrayUtils.ts",
    "chars": 222,
    "preview": "export function partition<T>(array: T[], filter: (element: T) => boolean): [T[], T[]] {\n  const pass = [], fail = [];\n  "
  },
  {
    "path": "src/utils/categoryUtils.ts",
    "chars": 2760,
    "preview": "import { ActionType, Category, SponsorTime } from \"../types\";\n\nexport function getSkippingText(segments: SponsorTime[], "
  },
  {
    "path": "src/utils/compatibility.ts",
    "chars": 547,
    "preview": "import Config from \"../config\";\n\nexport function runCompatibilityChecks() {\n    if (Config.config.showZoomToFillError2 &"
  },
  {
    "path": "src/utils/configUtils.ts",
    "chars": 170,
    "preview": "import Config from \"../config\";\n\nexport function showDonationLink(): boolean {\n    return navigator.vendor !== \"Apple Co"
  },
  {
    "path": "src/utils/constants.ts",
    "chars": 7417,
    "preview": "import { TextBox } from \"../render/GenericNotice\";\nimport { Category } from \"../types\";\n\nexport function getGuidelineInf"
  },
  {
    "path": "src/utils/crossExtension.ts",
    "chars": 1419,
    "preview": "import * as CompileConfig from \"../../config.json\";\n\nimport Config from \"../config\";\nimport { isSafari } from \"../../maz"
  },
  {
    "path": "src/utils/exporter.ts",
    "chars": 5162,
    "preview": "import { ActionType, Category, SegmentUUID, SponsorSourceType, SponsorTime } from \"../types\";\nimport { shortCategoryName"
  },
  {
    "path": "src/utils/genericUtils.ts",
    "chars": 1050,
    "preview": "/* Gets percieved luminance of a color */\nfunction getLuminance(color: string): number {\n    const {r, g, b} = hexToRgb("
  },
  {
    "path": "src/utils/logger.ts",
    "chars": 615,
    "preview": "if (typeof (window) !== \"undefined\") {\n    window[\"SBLogs\"] = {\n        debug: [],\n        warn: []\n    };\n}\n\nexport fun"
  },
  {
    "path": "src/utils/mobileUtils.ts",
    "chars": 224,
    "preview": "export function isMobileControlsOpen(): boolean {\n    const overlay = document.getElementById(\"player-control-overlay\");"
  },
  {
    "path": "src/utils/noticeUtils.ts",
    "chars": 788,
    "preview": "import Config from \"../config\";\nimport { SponsorTime } from \"../types\";\n\nexport enum SkipNoticeAction {\n    None,\n    Up"
  },
  {
    "path": "src/utils/pageCleaner.ts",
    "chars": 424,
    "preview": "export function cleanPage() {\n    // For live-updates\n    if (document.readyState === \"complete\") {\n        for (const e"
  },
  {
    "path": "src/utils/pageUtils.ts",
    "chars": 4205,
    "preview": "import { ActionType, Category, SponsorSourceType, SponsorTime, VideoID } from \"../types\";\nimport { getFormattedTimeToSec"
  },
  {
    "path": "src/utils/requests.ts",
    "chars": 770,
    "preview": "import Config from \"../config\";\nimport * as CompileConfig from \"../../config.json\";\nimport { FetchResponse, sendRequestT"
  },
  {
    "path": "src/utils/segmentData.ts",
    "chars": 3563,
    "preview": "import { DataCache } from \"../../maze-utils/src/cache\";\nimport { getHash, HashedValue } from \"../../maze-utils/src/hash\""
  },
  {
    "path": "src/utils/skipProfiles.ts",
    "chars": 3011,
    "preview": "import { getChannelIDInfo, getVideoID } from \"../../maze-utils/src/video\";\nimport Config, { ConfigurationID, CustomConfi"
  },
  {
    "path": "src/utils/skipRule.ts",
    "chars": 32721,
    "preview": "import { getCurrentPageTitle } from \"../../maze-utils/src/elements\";\nimport { getChannelIDInfo, getVideoDuration } from "
  },
  {
    "path": "src/utils/skipRule.type.ts",
    "chars": 2032,
    "preview": "import type { CategorySkipOption } from \"../types\";\n\nexport interface Permission {\n    canSubmit: boolean;\n}\n\n// Note th"
  },
  {
    "path": "src/utils/thumbnails.ts",
    "chars": 7779,
    "preview": "import { extractVideoID, isOnInvidious } from \"../../maze-utils/src/video\";\nimport Config from \"../config\";\nimport { get"
  },
  {
    "path": "src/utils/urlParser.ts",
    "chars": 752,
    "preview": "\nexport function getStartTimeFromUrl(url: string): number {\n    const urlParams = new URLSearchParams(url);\n    const ti"
  },
  {
    "path": "src/utils/videoLabels.ts",
    "chars": 2987,
    "preview": "import { Category, CategorySkipOption, VideoID } from \"../types\";\nimport { getHash } from \"../../maze-utils/src/hash\";\ni"
  },
  {
    "path": "src/utils/warnings.ts",
    "chars": 3723,
    "preview": "import { objectToURI } from \"../../maze-utils/src\";\nimport { FetchResponse, logRequest } from \"../../maze-utils/src/back"
  },
  {
    "path": "src/utils.ts",
    "chars": 12566,
    "preview": "import Config, { VideoDownvotes } from \"./config\";\nimport { SponsorTime, BackgroundScriptContainer, Registration, VideoI"
  },
  {
    "path": "test/exporter.test.ts",
    "chars": 9760,
    "preview": "/**\n * @jest-environment jsdom\n */\n\nimport { ActionType, Category, SegmentUUID, SponsorSourceType, SponsorTime } from \"."
  },
  {
    "path": "test/previewBar.test.ts",
    "chars": 22358,
    "preview": "/**\n * @jest-environment jsdom\n */\n\nimport PreviewBar, { PreviewBarSegment } from \"../src/js-components/previewBar\";\n\nde"
  },
  {
    "path": "test/selenium.test.ts",
    "chars": 9982,
    "preview": "import { Builder, By, until, WebDriver, WebElement } from \"selenium-webdriver\";\nimport * as Chrome from \"selenium-webdri"
  },
  {
    "path": "test/urlParser.test.ts",
    "chars": 1029,
    "preview": "import { getStartTimeFromUrl } from '../src/utils/urlParser';\n\ndescribe(\"getStartTimeFromUrl\", () => {\n    it(\"parses wi"
  },
  {
    "path": "tsconfig-production.json",
    "chars": 534,
    "preview": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"target\": \"es6\",\n        \"noImplicitAny\": false,\n      "
  },
  {
    "path": "tsconfig.json",
    "chars": 533,
    "preview": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"target\": \"es6\",\n        \"noImplicitAny\": false,\n      "
  },
  {
    "path": "webpack/configDiffPlugin.js",
    "chars": 2387,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\nconst { readFile } = require(\"fs/promises\")\nlet logger;\n\nconst r"
  },
  {
    "path": "webpack/webpack.common.js",
    "chars": 7666,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nco"
  },
  {
    "path": "webpack/webpack.dev.js",
    "chars": 254,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\nconst { merge } = require('webpack-merge');\nconst common = requi"
  },
  {
    "path": "webpack/webpack.manifest.js",
    "chars": 3597,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nco"
  },
  {
    "path": "webpack/webpack.prod.js",
    "chars": 1357,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\nconst { SourceMapDevToolPlugin } = require('webpack');\nconst { m"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the ajayyy/SponsorBlock GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 141 files (883.0 KB), approximately 204.1k tokens, and a symbol index with 728 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!