mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-24 08:28:31 +03:00
Compare commits
175 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52bd85b850 | ||
|
|
09e7c41479 | ||
|
|
bdcb2d05c7 | ||
|
|
a522e3065c | ||
|
|
72c62d0fa4 | ||
|
|
133ea360d7 | ||
|
|
e722ded58a | ||
|
|
6d37180d00 | ||
|
|
14d50b9e70 | ||
|
|
cfe314742d | ||
|
|
9a71e8bb8c | ||
|
|
dc2c7cc425 | ||
|
|
7bf3237b72 | ||
|
|
b48c854926 | ||
|
|
0bb7bef52c | ||
|
|
4ffa019c68 | ||
|
|
9c2007e0cf | ||
|
|
9176854d56 | ||
|
|
65c72d38ea | ||
|
|
6f54c8a731 | ||
|
|
ca7eb50a82 | ||
|
|
a7030fab9f | ||
|
|
0bb3528cde | ||
|
|
c8c141f5c9 | ||
|
|
88cfa023c9 | ||
|
|
41a2fc2cb3 | ||
|
|
0f0e404920 | ||
|
|
f34fe5a032 | ||
|
|
e4c9afecbd | ||
|
|
79e855a038 | ||
|
|
09a3a4e6d4 | ||
|
|
e271f2cbcc | ||
|
|
1cc4c18665 | ||
|
|
e650b7183a | ||
|
|
4eb097b422 | ||
|
|
04a9f82bdc | ||
|
|
39cfdc7b6c | ||
|
|
a8701b02a1 | ||
|
|
3f1ad528c3 | ||
|
|
ae685baeef | ||
|
|
d2ee67f3cf | ||
|
|
d440a4d52a | ||
|
|
7566b71ccd | ||
|
|
109b7ed5bc | ||
|
|
3eb853154f | ||
|
|
ee3ce8aa46 | ||
|
|
1557af5d2a | ||
|
|
465e7065ca | ||
|
|
a3f8419c49 | ||
|
|
dde443ccec | ||
|
|
01b1380b78 | ||
|
|
c51b18465e | ||
|
|
ad9888cf52 | ||
|
|
7856791f90 | ||
|
|
273ee63ec7 | ||
|
|
be36583aee | ||
|
|
433bbbf904 | ||
|
|
6c2ee76198 | ||
|
|
42f59898f3 | ||
|
|
8ab126f502 | ||
|
|
4954abf9e3 | ||
|
|
30a21d5ff5 | ||
|
|
d1b2def47c | ||
|
|
48cdabe2a5 | ||
|
|
bc2db0cf2c | ||
|
|
843ef37dcd | ||
|
|
ed260a0667 | ||
|
|
2e131c2a95 | ||
|
|
f5e884b6aa | ||
|
|
a9929d0c93 | ||
|
|
3fb43d1c0e | ||
|
|
a1b2855538 | ||
|
|
07236baed5 | ||
|
|
f991435857 | ||
|
|
faa3259165 | ||
|
|
c96bafb6f7 | ||
|
|
9b7680f0e6 | ||
|
|
16e01b7494 | ||
|
|
6cd697dc32 | ||
|
|
9946bd1af2 | ||
|
|
3b06d72270 | ||
|
|
4bd0556464 | ||
|
|
7e12a914d5 | ||
|
|
25eaf4fa20 | ||
|
|
b3efa1f787 | ||
|
|
9a18e70e34 | ||
|
|
64ece9cb73 | ||
|
|
66c974b011 | ||
|
|
d8cc93c841 | ||
|
|
de22accfda | ||
|
|
e5b0b60dde | ||
|
|
32d98e6544 | ||
|
|
3dde05eda2 | ||
|
|
6aeefaae64 | ||
|
|
93d695e6c2 | ||
|
|
160924feee | ||
|
|
e3f3ed20e6 | ||
|
|
52149f4c0f | ||
|
|
cbc586f9ac | ||
|
|
fc8e20be0d | ||
|
|
368059eb0d | ||
|
|
ea77375fcc | ||
|
|
16005e417d | ||
|
|
7c5b750264 | ||
|
|
cc6b65c6c4 | ||
|
|
d2c99c2d77 | ||
|
|
84c25e3042 | ||
|
|
8840dba90f | ||
|
|
856dded58f | ||
|
|
1d1bd2a003 | ||
|
|
dc91ee76ca | ||
|
|
90bb9a4d02 | ||
|
|
d12d847f2f | ||
|
|
31a9de252d | ||
|
|
299cb485c3 | ||
|
|
882d462849 | ||
|
|
fc3710b37b | ||
|
|
3ac170ad01 | ||
|
|
4069545603 | ||
|
|
db9fc11f13 | ||
|
|
76667f68ec | ||
|
|
e27a287a68 | ||
|
|
ce3731774d | ||
|
|
8cbf2bb50b | ||
|
|
6f8c44b2eb | ||
|
|
bbc5c11667 | ||
|
|
d074fdec96 | ||
|
|
5d98208596 | ||
|
|
4480ad4d84 | ||
|
|
df6c6cb30f | ||
|
|
0f2da334e8 | ||
|
|
7562340ec2 | ||
|
|
fee5b7be44 | ||
|
|
e89f75e4b6 | ||
|
|
8bcaf906fb | ||
|
|
cee00a87c1 | ||
|
|
9b627f4e8f | ||
|
|
af977840c6 | ||
|
|
e8370121d5 | ||
|
|
3175d7f8e9 | ||
|
|
ab4035bbc5 | ||
|
|
3d88d29d93 | ||
|
|
1ac361b4df | ||
|
|
24c37324b8 | ||
|
|
21bce341f0 | ||
|
|
0d9c3dc807 | ||
|
|
60f9274438 | ||
|
|
e23722baa0 | ||
|
|
d25e8c7360 | ||
|
|
87bf472ee4 | ||
|
|
e28e196a17 | ||
|
|
0f7ed9926c | ||
|
|
75eb63632f | ||
|
|
e1982f265e | ||
|
|
e2a01bb744 | ||
|
|
8c8e41bc89 | ||
|
|
88d1cc1650 | ||
|
|
dd7a673637 | ||
|
|
35ac452a30 | ||
|
|
de654e0016 | ||
|
|
b4adee720f | ||
|
|
d4f668559c | ||
|
|
6b2b26faf5 | ||
|
|
52135e7478 | ||
|
|
10a0537b78 | ||
|
|
b383acc902 | ||
|
|
f5706d9fe3 | ||
|
|
fea33945c7 | ||
|
|
22826e3b36 | ||
|
|
c7dad09555 | ||
|
|
198ae9c41f | ||
|
|
1b2be35bd6 | ||
|
|
ff3d8ff2d6 | ||
|
|
992e727d93 | ||
|
|
edaed61612 |
@@ -24,7 +24,10 @@
|
|||||||
"no-self-assign": "off",
|
"no-self-assign": "off",
|
||||||
"@typescript-eslint/no-empty-interface": "off",
|
"@typescript-eslint/no-empty-interface": "off",
|
||||||
"react/prop-types": [2, { "ignore": ["children"] }],
|
"react/prop-types": [2, { "ignore": ["children"] }],
|
||||||
"@typescript-eslint/member-delimiter-style": "warn"
|
"@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": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
|
|||||||
7
.github/workflows/updateInvidous.yml
vendored
7
.github/workflows/updateInvidous.yml
vendored
@@ -11,9 +11,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Download instance list
|
- name: Download instance lists
|
||||||
run: |
|
run: |
|
||||||
wget https://api.invidious.io/instances.json -O ci/data.json
|
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
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: "Run CI"
|
- name: "Run CI"
|
||||||
@@ -24,7 +25,7 @@ jobs:
|
|||||||
# v4.2.3
|
# v4.2.3
|
||||||
with:
|
with:
|
||||||
commit-message: Update Invidious List
|
commit-message: Update Invidious List
|
||||||
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||||
branch: ci/update_invidious_list
|
branch: ci/update_invidious_list
|
||||||
title: Update Invidious List
|
title: Update Invidious List
|
||||||
body: Automated Invidious list update
|
body: Automated Invidious list update
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,5 +7,6 @@ web-ext-artifacts
|
|||||||
dist/
|
dist/
|
||||||
tmp/
|
tmp/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
ci/data.json
|
ci/invidious_instances.json
|
||||||
|
ci/piped_instances.json
|
||||||
test-results
|
test-results
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
|||||||
[submodule "public/_locales"]
|
[submodule "public/_locales"]
|
||||||
path = public/_locales
|
path = public/_locales
|
||||||
url = https://github.com/ajayyy/ExtensionTranslations
|
url = https://github.com/ajayyy/ExtensionTranslations
|
||||||
|
[submodule "maze-utils"]
|
||||||
|
path = maze-utils
|
||||||
|
url = https://github.com/ajayyy/maze-utils
|
||||||
|
|||||||
@@ -1 +1,31 @@
|
|||||||
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 LGPL-3.0.
|
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 LGPL-3.0 or later.
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
https://crowdin.com/project/sponsorblock
|
||||||
|
|
||||||
|
# Building
|
||||||
|
## Building locally
|
||||||
|
0. You must have [Node.js 16 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.
|
||||||
|
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
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.
|
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 Invidio.us.
|
It also supports Invidious.
|
||||||
|
|
||||||
**Translate:** [](https://crowdin.com/project/sponsorblock)
|
**Translate:** [](https://crowdin.com/project/sponsorblock)
|
||||||
|
|
||||||
@@ -56,47 +56,14 @@ The dataset and API are now being used in some [ports](https://github.com/ajayyy
|
|||||||
|
|
||||||
# API
|
# API
|
||||||
|
|
||||||
You can read the API docs [here](https://wiki.sponsor.ajay.app/index.php/API_Docs).
|
You can read the API docs [here](https://wiki.sponsor.ajay.app/w/API_Docs).
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
You must have [Node.js 16](https://nodejs.org/) and npm installed.
|
|
||||||
|
|
||||||
1. Clone with submodules
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/ajayyy/SponsorBlock --recurse-submodules=yes
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
- You will need to repeat this step in the future if you get build errors related to `CompileConfig`. This can happen for example when a new category is added.
|
|
||||||
|
|
||||||
3. Run `npm install` 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/en-US/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.
|
|
||||||
|
|
||||||
# Credit
|
# Credit
|
||||||
|
|
||||||
The awesome [Invidious API](https://docs.invidious.io/API.md) was previously used, and the server is now using [NewLeaf](https://git.sr.ht/~cadence/NewLeaf) as a to get video info from YouTube.
|
The awesome [Invidious API](https://docs.invidious.io/) was previously used, and the server is now using [NewLeaf](https://git.sr.ht/~cadence/NewLeaf) as a to get video info from YouTube.
|
||||||
|
|
||||||
Originally forked from [YTSponsorSkip](https://github.com/NDevTK/YTSponsorSkip), but very little code remains.
|
Originally forked from [YTSponsorSkip](https://github.com/NDevTK/YTSponsorSkip), but very little code remains.
|
||||||
|
|
||||||
|
|||||||
63
ci/generateList.ts
Normal file
63
ci/generateList.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
- 30d uptime >= 90%
|
||||||
|
- available for at least 80/90 days
|
||||||
|
- 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()
|
||||||
@@ -1,55 +1,31 @@
|
|||||||
/*
|
import { InvidiousInstance, instanceMap } from "./invidiousType"
|
||||||
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
|
import * as data from "../ci/invidious_instances.json";
|
||||||
*/
|
|
||||||
|
|
||||||
import { writeFile, existsSync } from 'fs';
|
|
||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
// import file from https://api.invidious.io/instances.json
|
|
||||||
if (!existsSync(join(__dirname, "data.json"))) {
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
import * as data from "../ci/data.json";
|
|
||||||
|
|
||||||
type instanceMap = {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
dailyRatios: {ratio: string; label: string }[];
|
|
||||||
thirtyDayUptime: string;
|
|
||||||
}[]
|
|
||||||
|
|
||||||
// only https servers
|
// only https servers
|
||||||
const mapped: instanceMap = data
|
const mapped: instanceMap = data
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
.filter((i: InvidiousInstance) => i[1]?.type === "https")
|
||||||
.filter((i: any) => i[1]?.type === 'https')
|
.map((instance: InvidiousInstance) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
.map((instance: any) => {
|
|
||||||
return {
|
return {
|
||||||
name: instance[0],
|
name: instance[0],
|
||||||
url: instance[1].uri,
|
url: instance[1].uri,
|
||||||
dailyRatios: instance[1].monitor.dailyRatios,
|
dailyRatios: instance[1].monitor.dailyRatios,
|
||||||
thirtyDayUptime: instance[1]?.monitor['30dRatio'].ratio,
|
thirtyDayUptime: instance[1]?.monitor["30dRatio"].ratio,
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// reliability and sanity checks
|
// reliability and sanity checks
|
||||||
const reliableCheck = mapped
|
const reliableCheck = mapped
|
||||||
.filter((instance) => {
|
.filter(instance => {
|
||||||
// 30d uptime >= 90%
|
// 30d uptime >= 90%
|
||||||
const thirtyDayUptime = Number(instance.thirtyDayUptime) >= 90
|
const thirtyDayUptime = Number(instance.thirtyDayUptime) >= 90;
|
||||||
// available for at least 80/90 days
|
// available for at least 80/90 days
|
||||||
const dailyRatioCheck = instance.dailyRatios.filter(status => status.label !== "black")
|
const dailyRatioCheck = instance.dailyRatios.filter(status => status.label !== "black");
|
||||||
return (thirtyDayUptime && dailyRatioCheck.length >= 80)
|
return thirtyDayUptime && dailyRatioCheck.length >= 80;
|
||||||
})
|
})
|
||||||
// url includes name
|
// url includes name
|
||||||
.filter(instance => instance.url.includes(instance.name))
|
.filter(instance => instance.url.includes(instance.name));
|
||||||
|
|
||||||
// finally map to array
|
export function getInvidiousList(): string[] {
|
||||||
const result: string[] = reliableCheck.map(instance => instance.name).sort()
|
return reliableCheck.map(instance => instance.name).sort()
|
||||||
writeFile(join(__dirname, "./invidiouslist.json"), JSON.stringify(result), (err) => {
|
}
|
||||||
if (err) return console.log(err);
|
|
||||||
})
|
|
||||||
54
ci/invidiousType.ts
Normal file
54
ci/invidiousType.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
type ratio = {
|
||||||
|
ratio: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type instanceMap = {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
dailyRatios: {ratio: string; label: string }[];
|
||||||
|
thirtyDayUptime: string;
|
||||||
|
}[]
|
||||||
|
|
||||||
|
export type InvidiousInstance = [
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
flag: string;
|
||||||
|
region: string;
|
||||||
|
stats: null | {
|
||||||
|
version: string;
|
||||||
|
software: {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
branch: string;
|
||||||
|
};
|
||||||
|
openRegistrations: boolean;
|
||||||
|
usage: {
|
||||||
|
users: {
|
||||||
|
total: number;
|
||||||
|
activeHalfyear: number;
|
||||||
|
activeMonth: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
metadata: {
|
||||||
|
updatedAt: number;
|
||||||
|
lastChannelRefreshedAt: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
cors: boolean | null;
|
||||||
|
api: boolean | null;
|
||||||
|
type: "https" | "http" | "onion" | "i2p";
|
||||||
|
uri: string;
|
||||||
|
monitor: null | {
|
||||||
|
monitorId: number;
|
||||||
|
createdAt: number;
|
||||||
|
statusClass: string;
|
||||||
|
name: string;
|
||||||
|
url: string | null;
|
||||||
|
type: "HTTP(s)";
|
||||||
|
dailyRatios: ratio[];
|
||||||
|
"90dRatio": ratio;
|
||||||
|
"30dRatio": ratio;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1 +1 @@
|
|||||||
["inv.bp.projectsegfau.lt","inv.odyssey346.dev","inv.riverside.rocks","inv.vern.cc","invidious.baczek.me","invidious.epicsite.xyz","invidious.esmailelbob.xyz","invidious.flokinet.to","invidious.lidarshield.cloud","invidious.nerdvpn.de","invidious.privacydev.net","invidious.snopyta.org","invidious.tiekoetter.com","invidious.weblibre.org","iv.melmac.space","vid.puffyan.us","watch.thekitty.zone","y.com.sb","yewtu.be","yt.artemislena.eu","yt.funami.tech","yt.oelrichsgarcia.de"]
|
["www.youtubekids.com","anontube.lvkaszus.pl","inv.citw.lgbt","inv.in.projectsegfau.lt","inv.tux.pizza","inv.zzls.xyz","invidious.asir.dev","invidious.drgns.space","invidious.fdn.fr","invidious.flokinet.to","invidious.io.lol","invidious.lunar.icu","invidious.nerdvpn.de","invidious.no-logs.com","invidious.perennialte.ch","invidious.privacydev.net","invidious.private.coffee","invidious.projectsegfau.lt","invidious.protokolla.fi","invidious.slipfox.xyz","iv.datura.network","iv.ggtyler.dev","iv.melmac.space","iv.nboeck.de","onion.tube","vid.priv.au","vid.puffyan.us","yewtu.be","yt.artemislena.eu","yt.cdaut.de","yt.drgnz.club","yt.oelrichsgarcia.de"]
|
||||||
92
ci/pipedCI.ts
Normal file
92
ci/pipedCI.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -31,10 +31,16 @@
|
|||||||
"mute": "https://wiki.sponsor.ajay.app/w/Mute_Segment",
|
"mute": "https://wiki.sponsor.ajay.app/w/Mute_Segment",
|
||||||
"chapter": "https://wiki.sponsor.ajay.app/w/Chapter"
|
"chapter": "https://wiki.sponsor.ajay.app/w/Chapter"
|
||||||
},
|
},
|
||||||
"extensionCommunicationAllowList": [
|
"extensionImportList": {
|
||||||
"enamippconapkdmgfgjchkhakpfinmaj",
|
"chromium": [
|
||||||
"deArrow@ajay.app",
|
"enamippconapkdmgfgjchkhakpfinmaj"
|
||||||
"deArrowBETA@ajay.app",
|
],
|
||||||
"app.ajay.dearrow.extension"
|
"firefox": [
|
||||||
]
|
"deArrow@ajay.app",
|
||||||
|
"deArrowBETA@ajay.app"
|
||||||
|
],
|
||||||
|
"safari": [
|
||||||
|
"app.ajay.dearrow.extension"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
{
|
{
|
||||||
"optional_permissions": [
|
"optional_permissions": [
|
||||||
"declarativeContent"
|
"declarativeContent",
|
||||||
|
"webNavigation"
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"persistent": false
|
"persistent": false
|
||||||
}
|
},
|
||||||
|
"permissions": [
|
||||||
|
"https://*.youtube.com/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
{
|
{
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "sponsorBlocker@ajay.app"
|
"id": "sponsorBlocker@ajay.app",
|
||||||
|
"strict_min_version": "48.0"
|
||||||
|
},
|
||||||
|
"gecko_android": {
|
||||||
|
"strict_min_version": "79.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"persistent": false
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"scripting"
|
||||||
|
],
|
||||||
|
"browser_action": {
|
||||||
|
"default_area": "navbar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "__MSG_fullName__",
|
"name": "__MSG_fullName__",
|
||||||
"short_name": "SponsorBlock",
|
"short_name": "SponsorBlock",
|
||||||
"version": "5.4.7",
|
"version": "5.4.27",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"description": "__MSG_Description__",
|
"description": "__MSG_Description__",
|
||||||
"homepage_url": "https://sponsor.ajay.app",
|
"homepage_url": "https://sponsor.ajay.app",
|
||||||
@@ -17,9 +17,7 @@
|
|||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"content.css",
|
"content.css",
|
||||||
"shared.css",
|
"shared.css"
|
||||||
"./libs/Source+Sans+Pro.css",
|
|
||||||
"popup.css"
|
|
||||||
]
|
]
|
||||||
}],
|
}],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
@@ -67,6 +65,7 @@
|
|||||||
"icons/export.svg",
|
"icons/export.svg",
|
||||||
"icons/PlayerInfoIconSponsorBlocker.svg",
|
"icons/PlayerInfoIconSponsorBlocker.svg",
|
||||||
"icons/PlayerDeleteIconSponsorBlocker.svg",
|
"icons/PlayerDeleteIconSponsorBlocker.svg",
|
||||||
|
"icons/dearrow.svg",
|
||||||
"popup.html",
|
"popup.html",
|
||||||
"popup.css",
|
"popup.css",
|
||||||
"content.css",
|
"content.css",
|
||||||
@@ -83,8 +82,7 @@
|
|||||||
"https://sponsor.ajay.app/*"
|
"https://sponsor.ajay.app/*"
|
||||||
],
|
],
|
||||||
"optional_permissions": [
|
"optional_permissions": [
|
||||||
"*://*/*",
|
"*://*/*"
|
||||||
"webNavigation"
|
|
||||||
],
|
],
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_title": "SponsorBlock",
|
"default_title": "SponsorBlock",
|
||||||
@@ -115,6 +113,21 @@
|
|||||||
"light": "icons/IconSponsorBlocker128px.png",
|
"light": "icons/IconSponsorBlocker128px.png",
|
||||||
"dark": "icons/IconSponsorBlocker128px.png",
|
"dark": "icons/IconSponsorBlocker128px.png",
|
||||||
"size": 128
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"background": {
|
"background": {
|
||||||
"persistent": false
|
"persistent": false
|
||||||
}
|
},
|
||||||
|
"permissions": [
|
||||||
|
"scripting"
|
||||||
|
],
|
||||||
|
"optional_permissions": [
|
||||||
|
"webNavigation"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1
maze-utils
Submodule
1
maze-utils
Submodule
Submodule maze-utils added at 6bdc9402c6
File diff suppressed because one or more lines are too long
75
package-lock.json
generated
75
package-lock.json
generated
@@ -27,7 +27,6 @@
|
|||||||
],
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ajayyy/maze-utils": "1.1.29",
|
|
||||||
"content-scripts-register-polyfill": "^4.0.2",
|
"content-scripts-register-polyfill": "^4.0.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
@@ -66,32 +65,6 @@
|
|||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ajayyy/maze-utils": {
|
|
||||||
"version": "1.1.29",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.29.tgz",
|
|
||||||
"integrity": "sha512-P8htPiJjGmkkkELYducx0eiUo3/W0MEG6G/S3oJ/buFNxXRFXWb9ZTzu7/RSmEgmRFBRiKtXyGbjDhsYQDf1BA==",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
|
||||||
@@ -12988,12 +12961,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webext-content-scripts": {
|
"node_modules/webext-content-scripts": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/webext-content-scripts/-/webext-content-scripts-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/webext-content-scripts/-/webext-content-scripts-2.5.5.tgz",
|
||||||
"integrity": "sha512-N1Xq/E8dx0lVAOyPquuo+2Vj9Fx1GoqCFo79lWeJHbemaBJ53N3BHBmbJJYsQ8FOP1xiwN4bPRQY2dpSjHAD3Q==",
|
"integrity": "sha512-CIq1LA/nHIXE43v8qlpqNPcbsSzGuQBkeykbqOWvKJ1Rx/q7zgdZsLgxwyoonWiQcJczslVmGWCfdBY04JwIyw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"webext-patterns": "^1.3.0",
|
"webext-patterns": "^1.3.0",
|
||||||
"webext-polyfill-kinda": "^1.0.0"
|
"webext-polyfill-kinda": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/fregante"
|
"url": "https://github.com/sponsors/fregante"
|
||||||
@@ -13022,9 +12998,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webext-polyfill-kinda": {
|
"node_modules/webext-polyfill-kinda": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/webext-polyfill-kinda/-/webext-polyfill-kinda-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/webext-polyfill-kinda/-/webext-polyfill-kinda-1.0.2.tgz",
|
||||||
"integrity": "sha512-Py/d3w/bC0KntuO60ePSWHsdrebZ3uYBLeFUjyPkDV3yTEQib0MRFvPh57t8XjImu4ylBoEAsFjzh/r22UtxMw==",
|
"integrity": "sha512-rqQUKeBTOicej0tjDJWDQlOTnDcm9yYJTzgI+7rMdyYV4QHmYMRm+yjkcVgECkg/Wu9MboZ4lYeBPdp1Ep9WgQ==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/fregante"
|
"url": "https://github.com/sponsors/fregante"
|
||||||
}
|
}
|
||||||
@@ -13359,8 +13335,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -13601,11 +13578,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ajayyy/maze-utils": {
|
|
||||||
"version": "1.1.29",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.29.tgz",
|
|
||||||
"integrity": "sha512-P8htPiJjGmkkkELYducx0eiUo3/W0MEG6G/S3oJ/buFNxXRFXWb9ZTzu7/RSmEgmRFBRiKtXyGbjDhsYQDf1BA=="
|
|
||||||
},
|
|
||||||
"@ampproject/remapping": {
|
"@ampproject/remapping": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
|
||||||
@@ -16770,7 +16742,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/content-scripts-register-polyfill/-/content-scripts-register-polyfill-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/content-scripts-register-polyfill/-/content-scripts-register-polyfill-4.0.2.tgz",
|
||||||
"integrity": "sha512-8hDm+tu3BkxHZP7EUIIIo/495F6QNXF7cI9Lwr4PQaiohw2wWmi9k2SE4W4kNrAaLnFw6RZ2ev8EmrQb+sCoGQ==",
|
"integrity": "sha512-8hDm+tu3BkxHZP7EUIIIo/495F6QNXF7cI9Lwr4PQaiohw2wWmi9k2SE4W4kNrAaLnFw6RZ2ev8EmrQb+sCoGQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"webext-content-scripts": "^2.5.2",
|
"webext-content-scripts": "v2.5.5",
|
||||||
"webext-patterns": "^1.3.0",
|
"webext-patterns": "^1.3.0",
|
||||||
"webext-polyfill-kinda": "^1.0.0"
|
"webext-polyfill-kinda": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -23127,12 +23099,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"webext-content-scripts": {
|
"webext-content-scripts": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/webext-content-scripts/-/webext-content-scripts-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/webext-content-scripts/-/webext-content-scripts-2.5.5.tgz",
|
||||||
"integrity": "sha512-N1Xq/E8dx0lVAOyPquuo+2Vj9Fx1GoqCFo79lWeJHbemaBJ53N3BHBmbJJYsQ8FOP1xiwN4bPRQY2dpSjHAD3Q==",
|
"integrity": "sha512-CIq1LA/nHIXE43v8qlpqNPcbsSzGuQBkeykbqOWvKJ1Rx/q7zgdZsLgxwyoonWiQcJczslVmGWCfdBY04JwIyw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"webext-patterns": "^1.3.0",
|
"webext-patterns": "^1.3.0",
|
||||||
"webext-polyfill-kinda": "^1.0.0"
|
"webext-polyfill-kinda": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"webext-patterns": {
|
"webext-patterns": {
|
||||||
@@ -23151,9 +23123,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"webext-polyfill-kinda": {
|
"webext-polyfill-kinda": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/webext-polyfill-kinda/-/webext-polyfill-kinda-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/webext-polyfill-kinda/-/webext-polyfill-kinda-1.0.2.tgz",
|
||||||
"integrity": "sha512-Py/d3w/bC0KntuO60ePSWHsdrebZ3uYBLeFUjyPkDV3yTEQib0MRFvPh57t8XjImu4ylBoEAsFjzh/r22UtxMw=="
|
"integrity": "sha512-rqQUKeBTOicej0tjDJWDQlOTnDcm9yYJTzgI+7rMdyYV4QHmYMRm+yjkcVgECkg/Wu9MboZ4lYeBPdp1Ep9WgQ=="
|
||||||
},
|
},
|
||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
@@ -23380,8 +23352,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"wrap-ansi": {
|
"wrap-ansi": {
|
||||||
|
|||||||
@@ -4,11 +4,15 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "background.js",
|
"main": "background.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ajayyy/maze-utils": "1.1.29",
|
|
||||||
"content-scripts-register-polyfill": "^4.0.2",
|
"content-scripts-register-polyfill": "^4.0.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"content-scripts-register-polyfill": {
|
||||||
|
"webext-content-scripts": "v2.5.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.0.220",
|
"@types/chrome": "^0.0.220",
|
||||||
"@types/firefox-webext-browser": "^111.0.0",
|
"@types/firefox-webext-browser": "^111.0.0",
|
||||||
@@ -56,7 +60,7 @@
|
|||||||
"build:watch": "npm run build:watch:chrome",
|
"build:watch": "npm run build:watch:chrome",
|
||||||
"build:watch:chrome": "webpack --env browser=chrome --config webpack/webpack.dev.js --watch",
|
"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",
|
"build:watch:firefox": "webpack --env browser=firefox --config webpack/webpack.dev.js --watch",
|
||||||
"ci:invidious": "ts-node ci/invidiousCI.ts",
|
"ci:invidious": "ts-node ci/generateList.ts",
|
||||||
"dev": "npm run build:dev && concurrently \"npm run web-run\" \"npm run build:watch\"",
|
"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": "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\"",
|
"dev:firefox-android": "npm run build:dev:firefox && concurrently \"npm run web-run:firefox-android\" \"npm run build:watch:firefox\"",
|
||||||
|
|||||||
Submodule public/_locales updated: 306f1deff3...b31f76dd29
@@ -7,7 +7,7 @@
|
|||||||
--sb-dark-red-outline: rgb(130,0,0,0.9);
|
--sb-dark-red-outline: rgb(130,0,0,0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.sbhidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,13 @@ div:hover > #previewbar.sbNotInvidious {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.previewbar.requiredSegment {
|
.previewbar.requiredSegment {
|
||||||
transform: scaleY(3)
|
transform: scaleY(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewbar.selectedSegment {
|
||||||
|
opacity: 1 !important;
|
||||||
|
z-index: 100;
|
||||||
|
transform: scaleY(1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure settings are upfront */
|
/* Make sure settings are upfront */
|
||||||
@@ -145,7 +151,7 @@ div:hover > .sponsorBlockChapterBar {
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playerButton.hidden:not(.autoHiding) {
|
.playerButton.sbhidden:not(.autoHiding) {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +169,13 @@ div:hover > .sponsorBlockChapterBar {
|
|||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoHiding:not(.hidden) {
|
.autoHiding:not(.sbhidden) {
|
||||||
transform: translateX(0%) scale(1);
|
transform: translateX(0%) scale(1);
|
||||||
/* opacity is from YouTube page */
|
/* opacity is from YouTube page */
|
||||||
transition: transform 0.2s, width 0.2s, opacity .1s cubic-bezier(0.4,0.0,1,1) !important;
|
transition: transform 0.2s, width 0.2s, opacity .1s cubic-bezier(0.4,0.0,1,1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoHiding.hidden {
|
.autoHiding.sbhidden {
|
||||||
transform: translateX(100%) scale(0);
|
transform: translateX(100%) scale(0);
|
||||||
/* opacity is from YouTube page */
|
/* opacity is from YouTube page */
|
||||||
transition: transform 0.2s, width 0.2s, opacity .1s cubic-bezier(0.4,0.0,1,1) !important;
|
transition: transform 0.2s, width 0.2s, opacity .1s cubic-bezier(0.4,0.0,1,1) !important;
|
||||||
@@ -177,7 +183,7 @@ div:hover > .sponsorBlockChapterBar {
|
|||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoHiding.hidden.autoHideLeft {
|
.autoHiding.sbhidden.autoHideLeft {
|
||||||
transform: translateX(-100%) scale(0);
|
transform: translateX(-100%) scale(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +204,11 @@ div:hover > .sponsorBlockChapterBar {
|
|||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#categoryPill .sbPillNoText .sponsorSkipLogo {
|
||||||
|
margin-top: calc(2.6rem - 18px);
|
||||||
|
margin-bottom: calc(2.6rem - 18px);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; }
|
from { opacity: 0; }
|
||||||
}
|
}
|
||||||
@@ -238,11 +249,6 @@ div:hover > .sponsorBlockChapterBar {
|
|||||||
border-collapse: unset;
|
border-collapse: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sponsorSkipNoticeParent {
|
|
||||||
min-width: 350px;
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sponsorSkipNotice {
|
.sponsorSkipNotice {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -266,7 +272,7 @@ div:hover > .sponsorBlockChapterBar {
|
|||||||
max-width: calc(100% - 50px);
|
max-width: calc(100% - 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sponsorSkipNotice .hidden {
|
.sponsorSkipNotice .sbhidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,8 +571,8 @@ div:hover > .sponsorBlockChapterBar {
|
|||||||
.sponsorTimeEditButton {
|
.sponsorTimeEditButton {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|
||||||
margin-left: 20px;
|
margin-left: 13px;
|
||||||
margin-right: 20px;
|
margin-right: 13px;
|
||||||
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
||||||
@@ -685,7 +691,7 @@ input::-webkit-inner-spin-button {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skipButtonControlBarContainer.hidden {
|
.skipButtonControlBarContainer.sbhidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,11 +736,13 @@ input::-webkit-inner-spin-button {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
|
width: max-content;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
z-index: 1000;
|
z-index: 10000;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sponsorBlockTooltip a {
|
.sponsorBlockTooltip a {
|
||||||
@@ -757,6 +765,12 @@ input::-webkit-inner-spin-button {
|
|||||||
right: 50%;
|
right: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sponsorBlockTooltip.sbTriangle.sbTopTriangle::after {
|
||||||
|
bottom: 100%;
|
||||||
|
top: unset;
|
||||||
|
border-color: transparent transparent rgba(28, 28, 28, 0.7) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.sponsorBlockLockedColor {
|
.sponsorBlockLockedColor {
|
||||||
color: #ffc83d !important;
|
color: #ffc83d !important;
|
||||||
}
|
}
|
||||||
@@ -771,6 +785,14 @@ input::-webkit-inner-spin-button {
|
|||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#categoryPillParent {
|
||||||
|
height: fit-content;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.sponsorBlockCategoryPill {
|
.sponsorBlockCategoryPill {
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
@@ -853,3 +875,7 @@ input::-webkit-inner-spin-button {
|
|||||||
.sponsorThumbnailLabel:hover span {
|
.sponsorThumbnailLabel:hover span {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sponsorblock-chapter-visible {
|
||||||
|
display: inherit !important;
|
||||||
|
}
|
||||||
@@ -34,6 +34,20 @@
|
|||||||
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>.
|
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>
|
</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>
|
||||||
|
|
||||||
<p style="margin-bottom: 0; margin-top: 0" class="bigText center">__MSG_helpPageReviewOptions__</p>
|
<p style="margin-bottom: 0; margin-top: 0" class="bigText center">__MSG_helpPageReviewOptions__</p>
|
||||||
|
|
||||||
<p class="smallText">
|
<p class="smallText">
|
||||||
|
|||||||
@@ -322,4 +322,33 @@ svg {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
background-color: var(--disabled);
|
background-color: var(--disabled);
|
||||||
color: grey;
|
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;
|
||||||
}
|
}
|
||||||
54
public/icons/dearrow.svg
Normal file
54
public/icons/dearrow.svg
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="800px"
|
||||||
|
height="800px"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
class="iconify iconify--twemoji"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
version="1.1"
|
||||||
|
id="svg10"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs14" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview12"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.65479573"
|
||||||
|
inkscape:cx="493.2836"
|
||||||
|
inkscape:cy="514.66432"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="983"
|
||||||
|
inkscape:window-x="435"
|
||||||
|
inkscape:window-y="768"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg10" />
|
||||||
|
<path
|
||||||
|
fill="#1213BD"
|
||||||
|
d="M36 18.302c0 4.981-2.46 9.198-5.655 12.462s-7.323 5.152-12.199 5.152s-9.764-1.112-12.959-4.376S0 23.283 0 18.302s2.574-9.38 5.769-12.644S13.271 0 18.146 0s9.394 2.178 12.589 5.442C33.931 8.706 36 13.322 36 18.302z"
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
fill="#ffffff"
|
||||||
|
d="m 30.394282,18.410186 c 0,3.468849 -1.143025,6.865475 -3.416513,9.137917 -2.273489,2.272442 -5.670115,2.92874 -9.137918,2.92874 -3.467803,0 -6.373515,-1.147212 -8.6470033,-3.419654 -2.2734888,-2.272442 -3.5871299,-5.178154 -3.5871299,-8.647003 0,-3.46885 0.9420533,-6.746149 3.2144954,-9.0196379 2.2724418,-2.2734888 5.5507878,-3.9513905 9.0196378,-3.9513905 3.46885,0 6.492841,1.9322561 8.76633,4.204698 2.273489,2.2724424 3.788101,5.2974804 3.788101,8.7663304 z"
|
||||||
|
id="path4"
|
||||||
|
style="fill:#88c9f9;fill-opacity:1;stroke-width:1.04673" />
|
||||||
|
<path
|
||||||
|
fill="#292f33"
|
||||||
|
d="m 23.95823,17.818306 c 0,3.153748 -2.644888,5.808102 -5.798635,5.808102 -3.153748,0 -5.599825,-2.654354 -5.599825,-5.808102 0,-3.153747 2.446077,-5.721714 5.599825,-5.721714 3.153747,0 5.798635,2.567967 5.798635,5.721714 z"
|
||||||
|
id="path8"
|
||||||
|
style="stroke-width:1.18339;fill:#0a62a5;fill-opacity:1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -38,7 +38,7 @@
|
|||||||
--white: black;
|
--white: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.medium-description, .switch-container, .optionLabel, .categoryTableElement {
|
.medium-description, .switch-container, .optionLabel, .categoryTableElement, .promotion-description {
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ input[type='number'] {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden, .sbhidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,4 +701,32 @@ svg {
|
|||||||
.no-bottom-border {
|
.no-bottom-border {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
padding: 20px 0px 0px 0px !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;
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,21 @@
|
|||||||
|
|
||||||
</div>
|
</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="toggle" data-sync="muteSegments">
|
<div data-type="toggle" data-sync="muteSegments">
|
||||||
<div class="switch-container">
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
@@ -167,6 +182,20 @@
|
|||||||
<div class="small-description">__MSG_whatShowCategoryWithoutPermission__</div>
|
<div class="small-description">__MSG_whatShowCategoryWithoutPermission__</div>
|
||||||
</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>
|
||||||
|
|
||||||
<div id="interface" class="option-group hidden">
|
<div id="interface" class="option-group hidden">
|
||||||
@@ -330,6 +359,18 @@
|
|||||||
<div class="small-description">__MSG_showTimeWithSkipsDescription__</div>
|
<div class="small-description">__MSG_showTimeWithSkipsDescription__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-type="toggle" data-sync="cleanPopup" data-no-safari="true">
|
||||||
|
<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>
|
||||||
|
|
||||||
<div data-type="toggle" data-sync="darkMode">
|
<div data-type="toggle" data-sync="darkMode">
|
||||||
<div class="switch-container">
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
@@ -342,6 +383,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 data-type="toggle" data-toggle-type="reverse" data-sync="showDonationLink" data-no-safari="true">
|
||||||
<div class="switch-container">
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
@@ -375,6 +428,11 @@
|
|||||||
<div class="inline"></div>
|
<div class="inline"></div>
|
||||||
</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="startSponsorKeybind">
|
<div data-type="keybind-change" data-sync="startSponsorKeybind">
|
||||||
<label class="optionLabel">__MSG_setStartSponsorShortcut__:</label>
|
<label class="optionLabel">__MSG_setStartSponsorShortcut__:</label>
|
||||||
<div class="inline"></div>
|
<div class="inline"></div>
|
||||||
@@ -465,7 +523,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="small-description">(__MSG_supportedSites__ Invidious, CloudTube, Piped)</div>
|
<div class="small-description">(__MSG_supportedSites__ Invidious, CloudTube, Piped, YouTube Kids)</div>
|
||||||
<div class="small-description">__MSG_supportOtherSitesDescription__ </div>
|
<div class="small-description">__MSG_supportOtherSitesDescription__ </div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,174 +1,3 @@
|
|||||||
@ajayyy/maze-utils
|
|
||||||
1.1.7 <https://github.com/ajayyy/SponsorBlock>
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
******************************
|
|
||||||
|
|
||||||
content-scripts-register-polyfill
|
content-scripts-register-polyfill
|
||||||
4.0.2 <https://github.com/fregante/content-scripts-register-polyfill>
|
4.0.2 <https://github.com/fregante/content-scripts-register-polyfill>
|
||||||
MIT License
|
MIT License
|
||||||
@@ -335,7 +164,7 @@ SOFTWARE.
|
|||||||
******************************
|
******************************
|
||||||
|
|
||||||
webext-content-scripts
|
webext-content-scripts
|
||||||
2.5.2 <https://github.com/fregante/webext-content-scripts>
|
2.5.5 <https://github.com/fregante/webext-content-scripts>
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)
|
Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)
|
||||||
@@ -365,7 +194,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
******************************
|
******************************
|
||||||
|
|
||||||
webext-polyfill-kinda
|
webext-polyfill-kinda
|
||||||
1.0.0 <https://github.com/fregante/webext-polyfill-kinda>
|
1.0.2 <https://github.com/fregante/webext-polyfill-kinda>
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)
|
Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden, .sbhidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sponsorBlockPopupBody .hidden {
|
#sponsorBlockPopupBody .hidden, #sponsorBlockPopupBody .sbhidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +130,7 @@
|
|||||||
top: 5px;
|
top: 5px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sbCloseButton:hover {
|
.sbCloseButton:hover {
|
||||||
@@ -260,19 +261,6 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Buttons that appear under a segment on click
|
|
||||||
*/
|
|
||||||
.voteButton {
|
|
||||||
height: 20px;
|
|
||||||
padding: 0 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voteButton:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Voted!" text that appears after voting on a segment
|
* "Voted!" text that appears after voting on a segment
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
__MSG_betaServerWarning__
|
__MSG_betaServerWarning__
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<header class="sbPopupLogo">
|
<header id="sbPopupLogo" class="sbPopupLogo">
|
||||||
<img src="icons/IconSponsorBlocker256px.png" alt="SponsorBlock" width="40" height="40" id="sponsorBlockPopupLogo">
|
<img src="icons/IconSponsorBlocker256px.png" alt="SponsorBlock" width="40" height="40" id="sponsorBlockPopupLogo">
|
||||||
<p class="u-mZ">SponsorBlock</p>
|
<p class="u-mZ">SponsorBlock</p>
|
||||||
</header>
|
</header>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Your Work box -->
|
<!-- Your Work box -->
|
||||||
<div class="sbYourWorkBox">
|
<div id="sbYourWorkBox" class="sbYourWorkBox">
|
||||||
<h1 class="sbHeader" style="padding: 8px 15px;">
|
<h1 class="sbHeader" style="padding: 8px 15px;">
|
||||||
__MSG_yourWork__
|
__MSG_yourWork__
|
||||||
</h1>
|
</h1>
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
<a href="https://github.com/ajayyy/SponsorBlock" target="_blank" rel="noopener">GitHub</a>
|
<a href="https://github.com/ajayyy/SponsorBlock" target="_blank" rel="noopener">GitHub</a>
|
||||||
<a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a>
|
<a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a>
|
||||||
<a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a>
|
<a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a>
|
||||||
<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">$</a>
|
<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">__MSG_Donate__</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<button id="showNoticeAgain" style="display: none">__MSG_showNotice__</button>
|
<button id="showNoticeAgain" style="display: none">__MSG_showNotice__</button>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sponsorSkipNoticeParent {
|
.sponsorSkipNoticeParent {
|
||||||
min-width: 350px;
|
min-width: 390px;
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
max-width: calc(100% - 50px);
|
max-width: calc(100% - 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sponsorSkipNotice .hidden {
|
.sponsorSkipNotice .sbhidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,4 +216,17 @@
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Buttons that appear under a segment on click
|
||||||
|
*/
|
||||||
|
.voteButton {
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voteButton:hover {
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
@@ -3,15 +3,20 @@ import * as CompileConfig from "../config.json";
|
|||||||
import Config from "./config";
|
import Config from "./config";
|
||||||
import { Registration } from "./types";
|
import { Registration } from "./types";
|
||||||
import "content-scripts-register-polyfill";
|
import "content-scripts-register-polyfill";
|
||||||
import { sendRealRequestToCustomServer, setupBackgroundRequestProxy } from "@ajayyy/maze-utils/lib/background-request-proxy";
|
import { sendRealRequestToCustomServer, setupBackgroundRequestProxy } from "../maze-utils/src/background-request-proxy";
|
||||||
import { setupTabUpdates } from "@ajayyy/maze-utils/lib/tab-updates";
|
import { setupTabUpdates } from "../maze-utils/src/tab-updates";
|
||||||
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
|
import { generateUserID } from "../maze-utils/src/setup";
|
||||||
|
|
||||||
// Make the config public for debugging purposes
|
// Make the config public for debugging purposes
|
||||||
|
|
||||||
window.SB = Config;
|
window.SB = Config;
|
||||||
|
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
|
import { getExtensionIdsToImportFrom } from "./utils/crossExtension";
|
||||||
|
import { isFirefoxOrSafari } from "../maze-utils/src";
|
||||||
|
import { injectUpdatedScripts } from "../maze-utils/src/cleanup";
|
||||||
|
import { logWarn } from "./utils/logger";
|
||||||
|
import { chromeP } from "../maze-utils/src/browserApi";
|
||||||
const utils = new Utils({
|
const utils = new Utils({
|
||||||
registerFirefoxContentScript,
|
registerFirefoxContentScript,
|
||||||
unregisterFirefoxContentScript
|
unregisterFirefoxContentScript
|
||||||
@@ -23,7 +28,7 @@ const popupPort: Record<string, chrome.runtime.Port> = {};
|
|||||||
const contentScriptRegistrations = {};
|
const contentScriptRegistrations = {};
|
||||||
|
|
||||||
// Register content script if needed
|
// Register content script if needed
|
||||||
utils.wait(() => Config.config !== null).then(function() {
|
utils.wait(() => Config.isReady()).then(function() {
|
||||||
if (Config.config.supportInvidious) utils.setupExtraSiteContentScripts();
|
if (Config.config.supportInvidious) utils.setupExtraSiteContentScripts();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,7 +76,11 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
|||||||
case "infoUpdated":
|
case "infoUpdated":
|
||||||
case "videoChanged":
|
case "videoChanged":
|
||||||
if (sender.tab) {
|
if (sender.tab) {
|
||||||
popupPort[sender.tab.id]?.postMessage(request);
|
try {
|
||||||
|
popupPort[sender.tab.id]?.postMessage(request);
|
||||||
|
} catch (e) {
|
||||||
|
// This can happen if the popup is closed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
@@ -80,7 +89,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
chrome.runtime.onMessageExternal.addListener((request, sender, callback) => {
|
chrome.runtime.onMessageExternal.addListener((request, sender, callback) => {
|
||||||
if (CompileConfig.extensionCommunicationAllowList.includes(sender.id)) {
|
if (getExtensionIdsToImportFrom().includes(sender.id)) {
|
||||||
if (request.message === "requestConfig") {
|
if (request.message === "requestConfig") {
|
||||||
callback({
|
callback({
|
||||||
userID: Config.config.userID,
|
userID: Config.config.userID,
|
||||||
@@ -131,6 +140,11 @@ chrome.runtime.onInstalled.addListener(function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
|
// Only do this once the old version understands how to clean itself up
|
||||||
|
if (!isFirefoxOrSafari() && chrome.runtime.getManifest().version !== "5.4.13") {
|
||||||
|
injectUpdatedScripts().catch(logWarn);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,27 +153,60 @@ chrome.runtime.onInstalled.addListener(function () {
|
|||||||
*
|
*
|
||||||
* @param {JSON} options
|
* @param {JSON} options
|
||||||
*/
|
*/
|
||||||
function registerFirefoxContentScript(options: Registration) {
|
async function registerFirefoxContentScript(options: Registration) {
|
||||||
const oldRegistration = contentScriptRegistrations[options.id];
|
if ("scripting" in chrome && "getRegisteredContentScripts" in chrome.scripting) {
|
||||||
if (oldRegistration) oldRegistration.unregister();
|
const existingRegistrations = await chromeP.scripting.getRegisteredContentScripts({
|
||||||
|
ids: [options.id]
|
||||||
|
}).catch(() => []);
|
||||||
|
|
||||||
|
if (existingRegistrations.length > 0
|
||||||
|
&& existingRegistrations[0].matches.every((match) => options.matches.includes(match))) {
|
||||||
|
// No need to register another script, already registered
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await unregisterFirefoxContentScript(options.id);
|
||||||
|
|
||||||
|
if ("scripting" in chrome && "getRegisteredContentScripts" in chrome.scripting) {
|
||||||
|
await chromeP.scripting.registerContentScripts([{
|
||||||
|
id: options.id,
|
||||||
|
runAt: "document_start",
|
||||||
|
matches: options.matches,
|
||||||
|
allFrames: options.allFrames,
|
||||||
|
js: options.js,
|
||||||
|
css: options.css,
|
||||||
|
persistAcrossSessions: true,
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
chrome.contentScripts.register({
|
||||||
|
allFrames: options.allFrames,
|
||||||
|
js: options.js?.map?.(file => ({file})),
|
||||||
|
css: options.css?.map?.(file => ({file})),
|
||||||
|
matches: options.matches
|
||||||
|
}).then((registration) => void (contentScriptRegistrations[options.id] = registration));
|
||||||
|
}
|
||||||
|
|
||||||
chrome.contentScripts.register({
|
|
||||||
allFrames: options.allFrames,
|
|
||||||
js: options.js,
|
|
||||||
css: options.css,
|
|
||||||
matches: options.matches
|
|
||||||
}).then((registration) => void (contentScriptRegistrations[options.id] = registration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only works on Firefox.
|
* Only works on Firefox.
|
||||||
* Firefox requires that this is handled by the background script
|
* Firefox requires that this is handled by the background script
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
function unregisterFirefoxContentScript(id: string) {
|
async function unregisterFirefoxContentScript(id: string) {
|
||||||
if (contentScriptRegistrations[id]) {
|
if ("scripting" in chrome && "getRegisteredContentScripts" in chrome.scripting) {
|
||||||
contentScriptRegistrations[id].unregister();
|
try {
|
||||||
delete contentScriptRegistrations[id];
|
await chromeP.scripting.unregisterContentScripts({
|
||||||
|
ids: [id]
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Not registered yet
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (contentScriptRegistrations[id]) {
|
||||||
|
contentScriptRegistrations[id].unregister();
|
||||||
|
delete contentScriptRegistrations[id];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
|
|||||||
import { VoteResponse } from "../messageTypes";
|
import { VoteResponse } from "../messageTypes";
|
||||||
import { AnimationUtils } from "../utils/animationUtils";
|
import { AnimationUtils } from "../utils/animationUtils";
|
||||||
import { Tooltip } from "../render/Tooltip";
|
import { Tooltip } from "../render/Tooltip";
|
||||||
import { getErrorMessage } from "@ajayyy/maze-utils/lib/formating";
|
import { getErrorMessage } from "../../maze-utils/src/formating";
|
||||||
|
|
||||||
export interface CategoryPillProps {
|
export interface CategoryPillProps {
|
||||||
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>;
|
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>;
|
||||||
|
showTextByDefault: boolean;
|
||||||
|
showTooltipOnClick: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryPillState {
|
export interface CategoryPillState {
|
||||||
@@ -43,18 +45,23 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span style={style}
|
<span style={style}
|
||||||
className={"sponsorBlockCategoryPill"}
|
className={"sponsorBlockCategoryPill" + (!this.props.showTextByDefault ? " sbPillNoText" : "")}
|
||||||
aria-label={this.getTitleText()}
|
aria-label={this.getTitleText()}
|
||||||
onClick={(e) => this.toggleOpen(e)}
|
onClick={(e) => this.toggleOpen(e)}
|
||||||
onMouseEnter={() => this.openTooltip()}
|
onMouseEnter={() => this.openTooltip()}
|
||||||
onMouseLeave={() => this.closeTooltip()}>
|
onMouseLeave={() => this.closeTooltip()}>
|
||||||
|
|
||||||
<span className="sponsorBlockCategoryPillTitleSection">
|
<span className="sponsorBlockCategoryPillTitleSection">
|
||||||
<img className="sponsorSkipLogo sponsorSkipObject"
|
<img className="sponsorSkipLogo sponsorSkipObject"
|
||||||
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
|
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
|
||||||
</img>
|
</img>
|
||||||
<span className="sponsorBlockCategoryPillTitle">
|
|
||||||
{chrome.i18n.getMessage("category_" + this.state.segment?.category)}
|
{
|
||||||
</span>
|
(this.props.showTextByDefault || this.state.open) &&
|
||||||
|
<span className="sponsorBlockCategoryPillTitle">
|
||||||
|
{chrome.i18n.getMessage("category_" + this.state.segment?.category)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{this.state.open && (
|
{this.state.open && (
|
||||||
@@ -81,7 +88,10 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
|
|||||||
{/* Close Button */}
|
{/* Close Button */}
|
||||||
<img src={chrome.extension.getURL("icons/close.png")}
|
<img src={chrome.extension.getURL("icons/close.png")}
|
||||||
className="categoryPillClose"
|
className="categoryPillClose"
|
||||||
onClick={() => this.setState({ show: false })}>
|
onClick={() => {
|
||||||
|
this.setState({ show: false });
|
||||||
|
this.closeTooltip();
|
||||||
|
}}>
|
||||||
</img>
|
</img>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -91,6 +101,14 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (this.state.show) {
|
if (this.state.show) {
|
||||||
|
if (this.props.showTooltipOnClick) {
|
||||||
|
if (this.state.open) {
|
||||||
|
this.closeTooltip();
|
||||||
|
} else {
|
||||||
|
this.openTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ open: !this.state.open });
|
this.setState({ open: !this.state.open });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,6 +126,8 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
|
|||||||
open: false,
|
open: false,
|
||||||
show: type === 1
|
show: type === 1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.closeTooltip();
|
||||||
} else if (response.statusCode !== 403) {
|
} else if (response.statusCode !== 403) {
|
||||||
alert(getErrorMessage(response.statusCode, response.responseText));
|
alert(getErrorMessage(response.statusCode, response.responseText));
|
||||||
}
|
}
|
||||||
@@ -127,7 +147,11 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
|
|||||||
}
|
}
|
||||||
|
|
||||||
private openTooltip(): void {
|
private openTooltip(): void {
|
||||||
const tooltipMount = document.querySelector("#above-the-fold") as HTMLElement;
|
if (this.tooltip) {
|
||||||
|
this.tooltip.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltipMount = document.querySelector("#above-the-fold, ytm-slim-owner-renderer") as HTMLElement;
|
||||||
if (tooltipMount) {
|
if (tooltipMount) {
|
||||||
this.tooltip = new Tooltip({
|
this.tooltip = new Tooltip({
|
||||||
text: this.getTitleText(),
|
text: this.getTitleText(),
|
||||||
@@ -143,7 +167,7 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
|
|||||||
}
|
}
|
||||||
|
|
||||||
private closeTooltip(): void {
|
private closeTooltip(): void {
|
||||||
this.tooltip?.close();
|
this.tooltip?.close?.();
|
||||||
this.tooltip = null;
|
this.tooltip = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
|
|||||||
import { VoteResponse } from "../messageTypes";
|
import { VoteResponse } from "../messageTypes";
|
||||||
import { AnimationUtils } from "../utils/animationUtils";
|
import { AnimationUtils } from "../utils/animationUtils";
|
||||||
import { Tooltip } from "../render/Tooltip";
|
import { Tooltip } from "../render/Tooltip";
|
||||||
import { getErrorMessage } from "@ajayyy/maze-utils/lib/formating";
|
import { getErrorMessage } from "../../maze-utils/src/formating";
|
||||||
|
|
||||||
export interface ChapterVoteProps {
|
export interface ChapterVoteProps {
|
||||||
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>;
|
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>;
|
||||||
@@ -44,7 +44,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
|
|||||||
<>
|
<>
|
||||||
{/* Upvote Button */}
|
{/* Upvote Button */}
|
||||||
<button id={"sponsorTimesDownvoteButtonsContainerUpvoteChapter"}
|
<button id={"sponsorTimesDownvoteButtonsContainerUpvoteChapter"}
|
||||||
className={"playerButton sbPlayerUpvote ytp-button " + (!this.state.show ? "hidden" : "")}
|
className={"playerButton sbPlayerUpvote ytp-button " + (!this.state.show ? "sbhidden" : "")}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
title={chrome.i18n.getMessage("upvoteButtonInfo")}
|
title={chrome.i18n.getMessage("upvoteButtonInfo")}
|
||||||
onClick={(e) => this.vote(e, 1)}>
|
onClick={(e) => this.vote(e, 1)}>
|
||||||
@@ -55,7 +55,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
|
|||||||
|
|
||||||
{/* Downvote Button */}
|
{/* Downvote Button */}
|
||||||
<button id={"sponsorTimesDownvoteButtonsContainerDownvoteChapter"}
|
<button id={"sponsorTimesDownvoteButtonsContainerDownvoteChapter"}
|
||||||
className={"playerButton sbPlayerDownvote ytp-button " + (!this.state.show ? "hidden" : "")}
|
className={"playerButton sbPlayerDownvote ytp-button " + (!this.state.show ? "sbhidden" : "")}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
title={chrome.i18n.getMessage("reportButtonInfo")}
|
title={chrome.i18n.getMessage("reportButtonInfo")}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
|
import SbSvg from "../svg-icons/sb_svg";
|
||||||
|
|
||||||
enum CountdownMode {
|
enum CountdownMode {
|
||||||
Timer,
|
Timer,
|
||||||
@@ -28,6 +29,7 @@ export interface NoticeProps {
|
|||||||
extraClass?: string;
|
extraClass?: string;
|
||||||
hideLogo?: boolean;
|
hideLogo?: boolean;
|
||||||
hideRightInfo?: boolean;
|
hideRightInfo?: boolean;
|
||||||
|
logoFill?: string;
|
||||||
|
|
||||||
// Callback for when this is closed
|
// Callback for when this is closed
|
||||||
closeListener: () => void;
|
closeListener: () => void;
|
||||||
@@ -122,10 +124,10 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
|||||||
<td className="noticeLeftIcon">
|
<td className="noticeLeftIcon">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
{!this.props.hideLogo &&
|
{!this.props.hideLogo &&
|
||||||
<img id={"sponsorSkipLogo" + this.idSuffix}
|
<SbSvg
|
||||||
className="sponsorSkipLogo sponsorSkipObject"
|
id={"sponsorSkipLogo" + this.idSuffix}
|
||||||
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
|
fill={this.props.logoFill}
|
||||||
</img>
|
className="sponsorSkipLogo sponsorSkipObject"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<span id={"sponsorSkipMessage" + this.idSuffix}
|
<span id={"sponsorSkipMessage" + this.idSuffix}
|
||||||
@@ -194,21 +196,21 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
|||||||
<span
|
<span
|
||||||
id={"skipNoticeTimerText" + this.idSuffix}
|
id={"skipNoticeTimerText" + this.idSuffix}
|
||||||
key="skipNoticeTimerText"
|
key="skipNoticeTimerText"
|
||||||
className={this.state.countdownMode !== CountdownMode.Timer ? "hidden" : ""} >
|
className={this.state.countdownMode !== CountdownMode.Timer ? "sbhidden" : ""} >
|
||||||
{chrome.i18n.getMessage("NoticeTimeAfterSkip").replace("{seconds}", this.state.countdownTime.toString())}
|
{chrome.i18n.getMessage("NoticeTimeAfterSkip").replace("{seconds}", this.state.countdownTime.toString())}
|
||||||
</span>
|
</span>
|
||||||
),(
|
),(
|
||||||
<img
|
<img
|
||||||
id={"skipNoticeTimerPaused" + this.idSuffix}
|
id={"skipNoticeTimerPaused" + this.idSuffix}
|
||||||
key="skipNoticeTimerPaused"
|
key="skipNoticeTimerPaused"
|
||||||
className={this.state.countdownMode !== CountdownMode.Paused ? "hidden" : ""}
|
className={this.state.countdownMode !== CountdownMode.Paused ? "sbhidden" : ""}
|
||||||
src={chrome.runtime.getURL("icons/pause.svg")}
|
src={chrome.runtime.getURL("icons/pause.svg")}
|
||||||
alt={chrome.i18n.getMessage("paused")} />
|
alt={chrome.i18n.getMessage("paused")} />
|
||||||
),(
|
),(
|
||||||
<img
|
<img
|
||||||
id={"skipNoticeTimerStopped" + this.idSuffix}
|
id={"skipNoticeTimerStopped" + this.idSuffix}
|
||||||
key="skipNoticeTimerStopped"
|
key="skipNoticeTimerStopped"
|
||||||
className={this.state.countdownMode !== CountdownMode.Stopped ? "hidden" : ""}
|
className={this.state.countdownMode !== CountdownMode.Stopped ? "sbhidden" : ""}
|
||||||
src={chrome.runtime.getURL("icons/stop.svg")}
|
src={chrome.runtime.getURL("icons/stop.svg")}
|
||||||
alt={chrome.i18n.getMessage("manualPaused")} />
|
alt={chrome.i18n.getMessage("manualPaused")} />
|
||||||
)];
|
)];
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
|
|||||||
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
||||||
import PencilSvg from "../svg-icons/pencil_svg";
|
import PencilSvg from "../svg-icons/pencil_svg";
|
||||||
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
|
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
|
||||||
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
|
import { generateUserID } from "../../maze-utils/src/setup";
|
||||||
import { keybindToString } from "@ajayyy/maze-utils/lib/config";
|
import { keybindToString } from "../../maze-utils/src/config";
|
||||||
|
|
||||||
enum SkipButtonState {
|
enum SkipButtonState {
|
||||||
Undo, // Unskip
|
Undo, // Unskip
|
||||||
@@ -177,7 +177,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
|||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NoticeComponent noticeTitle={this.state.noticeTitle}
|
<NoticeComponent
|
||||||
|
noticeTitle={this.state.noticeTitle}
|
||||||
amountOfPreviousNotices={this.amountOfPreviousNotices}
|
amountOfPreviousNotices={this.amountOfPreviousNotices}
|
||||||
showInSecondSlot={this.showInSecondSlot}
|
showInSecondSlot={this.showInSecondSlot}
|
||||||
idSuffix={this.idSuffix}
|
idSuffix={this.idSuffix}
|
||||||
@@ -191,6 +192,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
|||||||
ref={this.noticeRef}
|
ref={this.noticeRef}
|
||||||
closeListener={() => this.closeListener()}
|
closeListener={() => this.closeListener()}
|
||||||
smaller={this.state.smaller}
|
smaller={this.state.smaller}
|
||||||
|
logoFill={Config.config.barTypes[this.segments[0].category].color}
|
||||||
limitWidth={true}
|
limitWidth={true}
|
||||||
firstColumn={firstColumn}
|
firstColumn={firstColumn}
|
||||||
bottomRow={[...this.getMessageBoxes(), ...this.getBottomRow() ]}
|
bottomRow={[...this.getMessageBoxes(), ...this.getBottomRow() ]}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ import * as React from "react";
|
|||||||
import * as CompileConfig from "../../config.json";
|
import * as CompileConfig from "../../config.json";
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
import { ActionType, Category, ChannelIDStatus, ContentContainer, SponsorTime } from "../types";
|
import { ActionType, Category, ChannelIDStatus, ContentContainer, SponsorTime } from "../types";
|
||||||
import Utils from "../utils";
|
|
||||||
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
|
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
|
||||||
import { RectangleTooltip } from "../render/RectangleTooltip";
|
import { RectangleTooltip } from "../render/RectangleTooltip";
|
||||||
import SelectorComponent, { SelectorOption } from "./SelectorComponent";
|
import SelectorComponent, { SelectorOption } from "./SelectorComponent";
|
||||||
import { DEFAULT_CATEGORY } from "../utils/categoryUtils";
|
import { DEFAULT_CATEGORY } from "../utils/categoryUtils";
|
||||||
import { getFormattedTime, getFormattedTimeToSeconds } from "@ajayyy/maze-utils/lib/formating";
|
import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
|
||||||
|
import { asyncRequestToServer } from "../utils/requests";
|
||||||
const utils = new Utils();
|
|
||||||
|
|
||||||
export interface SponsorTimeEditProps {
|
export interface SponsorTimeEditProps {
|
||||||
index: number;
|
index: number;
|
||||||
@@ -128,6 +126,14 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
style={timeDisplayStyle}
|
style={timeDisplayStyle}
|
||||||
className="sponsorTimeDisplay">
|
className="sponsorTimeDisplay">
|
||||||
|
|
||||||
|
{sponsorTime.actionType !== ActionType.Poi ? (
|
||||||
|
<span id={"startButton" + this.idSuffix}
|
||||||
|
className="sponsorNowButton"
|
||||||
|
onClick={() => this.setTimeTo(0, 0)}>
|
||||||
|
{chrome.i18n.getMessage("bracketStart")}
|
||||||
|
</span>
|
||||||
|
): ""}
|
||||||
|
|
||||||
<span id={"nowButton0" + this.idSuffix}
|
<span id={"nowButton0" + this.idSuffix}
|
||||||
className="sponsorNowButton"
|
className="sponsorNowButton"
|
||||||
onClick={() => this.setTimeToNow(0)}>
|
onClick={() => this.setTimeToNow(0)}>
|
||||||
@@ -138,6 +144,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
type="text"
|
type="text"
|
||||||
style={{color: "inherit", backgroundColor: "inherit"}}
|
style={{color: "inherit", backgroundColor: "inherit"}}
|
||||||
value={this.state.sponsorTimeEdits[0]}
|
value={this.state.sponsorTimeEdits[0]}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
onKeyUp={(e) => e.stopPropagation()}
|
||||||
onChange={(e) => this.handleOnChange(0, e, sponsorTime, e.target.value)}
|
onChange={(e) => this.handleOnChange(0, e, sponsorTime, e.target.value)}
|
||||||
onWheel={(e) => this.changeTimesWhenScrolling(0, e, sponsorTime)}>
|
onWheel={(e) => this.changeTimesWhenScrolling(0, e, sponsorTime)}>
|
||||||
</input>
|
</input>
|
||||||
@@ -153,6 +161,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
type="text"
|
type="text"
|
||||||
style={{color: "inherit", backgroundColor: "inherit"}}
|
style={{color: "inherit", backgroundColor: "inherit"}}
|
||||||
value={this.state.sponsorTimeEdits[1]}
|
value={this.state.sponsorTimeEdits[1]}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
onKeyUp={(e) => e.stopPropagation()}
|
||||||
onChange={(e) => this.handleOnChange(1, e, sponsorTime, e.target.value)}
|
onChange={(e) => this.handleOnChange(1, e, sponsorTime, e.target.value)}
|
||||||
onWheel={(e) => this.changeTimesWhenScrolling(1, e, sponsorTime)}>
|
onWheel={(e) => this.changeTimesWhenScrolling(1, e, sponsorTime)}>
|
||||||
</input>
|
</input>
|
||||||
@@ -238,6 +248,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
ref={this.descriptionOptionRef}
|
ref={this.descriptionOptionRef}
|
||||||
type="text"
|
type="text"
|
||||||
value={this.state.description}
|
value={this.state.description}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
onKeyUp={(e) => e.stopPropagation()}
|
||||||
onContextMenu={(e) => e.stopPropagation()}
|
onContextMenu={(e) => e.stopPropagation()}
|
||||||
onChange={(e) => this.descriptionUpdate(e.target.value)}
|
onChange={(e) => this.descriptionUpdate(e.target.value)}
|
||||||
onFocus={() => this.setState({chapterNameSelectorOpen: true})}>
|
onFocus={() => this.setState({chapterNameSelectorOpen: true})}>
|
||||||
@@ -282,11 +294,10 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
</span>
|
</span>
|
||||||
): ""}
|
): ""}
|
||||||
|
|
||||||
{(!isNaN(segment[1]) && ![ActionType.Poi, ActionType.Full].includes(sponsorTime.actionType))
|
{(!isNaN(segment[1]) && ![ActionType.Poi, ActionType.Full].includes(sponsorTime.actionType)) ? (
|
||||||
&& sponsorTime.actionType === ActionType.Chapter ? (
|
<span id={"sponsorTimePreviewEndButton" + this.idSuffix}
|
||||||
<span id={"sponsorTimePreviewButton" + this.idSuffix}
|
|
||||||
className="sponsorTimeEditButton"
|
className="sponsorTimeEditButton"
|
||||||
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey)}>
|
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey, true)}>
|
||||||
{chrome.i18n.getMessage("End")}
|
{chrome.i18n.getMessage("End")}
|
||||||
</span>
|
</span>
|
||||||
): ""}
|
): ""}
|
||||||
@@ -577,7 +588,24 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
getFormattedTime(sponsorTime.segment[1], true)];
|
getFormattedTime(sponsorTime.segment[1], true)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastEditTime = 0;
|
||||||
|
editTimeTimeout: NodeJS.Timeout | null = null;
|
||||||
saveEditTimes(): void {
|
saveEditTimes(): void {
|
||||||
|
// Rate limit edits
|
||||||
|
const timeSinceLastEdit = Date.now() - this.lastEditTime;
|
||||||
|
if (timeSinceLastEdit < 200) {
|
||||||
|
if (!this.editTimeTimeout) {
|
||||||
|
this.editTimeTimeout = setTimeout(() => {
|
||||||
|
this.saveEditTimes();
|
||||||
|
}, timeSinceLastEdit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastEditTime = Date.now();
|
||||||
|
this.editTimeTimeout = null;
|
||||||
|
|
||||||
const sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
|
const sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
|
||||||
const category = this.categoryOptionRef.current.value as Category
|
const category = this.categoryOptionRef.current.value as Category
|
||||||
|
|
||||||
@@ -624,7 +652,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
: CompileConfig.categorySupport[category]?.[0] ?? ActionType.Skip
|
: CompileConfig.categorySupport[category]?.[0] ?? ActionType.Skip
|
||||||
}
|
}
|
||||||
|
|
||||||
previewTime(ctrlPressed = false, shiftPressed = false): void {
|
previewTime(ctrlPressed = false, shiftPressed = false, skipToEndTime = false): void {
|
||||||
const sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
|
const sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
|
||||||
const index = this.props.index;
|
const index = this.props.index;
|
||||||
let seekTime = 2;
|
let seekTime = 2;
|
||||||
@@ -633,13 +661,11 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
|
|
||||||
const startTime = sponsorTimes[index].segment[0];
|
const startTime = sponsorTimes[index].segment[0];
|
||||||
const endTime = sponsorTimes[index].segment[1];
|
const endTime = sponsorTimes[index].segment[1];
|
||||||
const isChapter = sponsorTimes[index].actionType === ActionType.Chapter;
|
|
||||||
|
|
||||||
// If segment starts at 0:00, start playback at the end of the segment
|
// If segment starts at 0:00, start playback at the end of the segment
|
||||||
const skipToEndTime = startTime === 0 || isChapter;
|
const skipTime = (startTime === 0 || skipToEndTime) ? endTime : (startTime - (seekTime * this.props.contentContainer().v.playbackRate));
|
||||||
const skipTime = skipToEndTime ? endTime : (startTime - (seekTime * this.props.contentContainer().v.playbackRate));
|
|
||||||
|
|
||||||
this.props.contentContainer().previewTime(skipTime, !isChapter);
|
this.props.contentContainer().previewTime(skipTime, !skipToEndTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
inspectTime(): void {
|
inspectTime(): void {
|
||||||
@@ -699,7 +725,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
if (this.props.contentContainer().channelIDInfo.status !== ChannelIDStatus.Found) return;
|
if (this.props.contentContainer().channelIDInfo.status !== ChannelIDStatus.Found) return;
|
||||||
|
|
||||||
this.fetchingSuggestions = true;
|
this.fetchingSuggestions = true;
|
||||||
const result = await utils.asyncRequestToServer("GET", "/api/chapterNames", {
|
const result = await asyncRequestToServer("GET", "/api/chapterNames", {
|
||||||
description,
|
description,
|
||||||
channelID: this.props.contentContainer().channelIDInfo.id
|
channelID: this.props.contentContainer().channelIDInfo.id
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
|||||||
|
|
||||||
guidelinesReminder: GenericNotice;
|
guidelinesReminder: GenericNotice;
|
||||||
|
|
||||||
|
lastSegmentCount: number;
|
||||||
|
|
||||||
constructor(props: SubmissionNoticeProps) {
|
constructor(props: SubmissionNoticeProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.noticeRef = React.createRef();
|
this.noticeRef = React.createRef();
|
||||||
@@ -47,12 +49,14 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
|||||||
|
|
||||||
const noticeTitle = chrome.i18n.getMessage("confirmNoticeTitle");
|
const noticeTitle = chrome.i18n.getMessage("confirmNoticeTitle");
|
||||||
|
|
||||||
|
this.lastSegmentCount = this.props.contentContainer().sponsorTimesSubmitting.length;
|
||||||
|
|
||||||
// Setup state
|
// Setup state
|
||||||
this.state = {
|
this.state = {
|
||||||
noticeTitle,
|
noticeTitle,
|
||||||
messages: [],
|
messages: [],
|
||||||
idSuffix: "SubmissionNotice"
|
idSuffix: "SubmissionNotice"
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
@@ -73,6 +77,18 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const currentSegmentCount = this.props.contentContainer().sponsorTimesSubmitting.length;
|
||||||
|
if (currentSegmentCount > this.lastSegmentCount) {
|
||||||
|
this.lastSegmentCount = currentSegmentCount;
|
||||||
|
|
||||||
|
const scrollElement = this.noticeRef.current.getElement().current.querySelector("#sponsorSkipNoticeMiddleRowSubmissionNotice");
|
||||||
|
scrollElement.scrollTo({
|
||||||
|
top: scrollElement.scrollHeight + 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render(): React.ReactElement {
|
render(): React.ReactElement {
|
||||||
const sortButton =
|
const sortButton =
|
||||||
<img id={"sponsorSkipSortButton" + this.state.idSuffix}
|
<img id={"sponsorSkipSortButton" + this.state.idSuffix}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
export interface TooltipProps {
|
|
||||||
text: string;
|
|
||||||
show: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TooltipState {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class TooltipComponent extends React.Component<TooltipProps, TooltipState> {
|
|
||||||
|
|
||||||
constructor(props: TooltipProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactElement {
|
|
||||||
const style: React.CSSProperties = {
|
|
||||||
display: this.props.show ? "flex" : "none",
|
|
||||||
position: "absolute",
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span style={style}
|
|
||||||
className={"sponsorBlockTooltip"} >
|
|
||||||
<span className="sponsorBlockTooltipText">
|
|
||||||
{this.props.text}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TooltipComponent;
|
|
||||||
@@ -139,6 +139,12 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
|||||||
case "autoSkip":
|
case "autoSkip":
|
||||||
option = CategorySkipOption.AutoSkip;
|
option = CategorySkipOption.AutoSkip;
|
||||||
|
|
||||||
|
if (this.props.category === "filler" && !Config.config.isVip) {
|
||||||
|
if (!confirm(chrome.i18n.getMessage("FillerWarning"))) {
|
||||||
|
event.target.value = "disable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
|||||||
import { createRoot, Root } from 'react-dom/client';
|
import { createRoot, Root } from 'react-dom/client';
|
||||||
import Config from "../../config";
|
import Config from "../../config";
|
||||||
import KeybindDialogComponent from "./KeybindDialogComponent";
|
import KeybindDialogComponent from "./KeybindDialogComponent";
|
||||||
import { formatKey, Keybind, keybindEquals, keybindToString } from "@ajayyy/maze-utils/lib/config";
|
import { formatKey, Keybind, keybindEquals, keybindToString } from "../../../maze-utils/src/config";
|
||||||
|
|
||||||
export interface KeybindProps {
|
export interface KeybindProps {
|
||||||
option: string;
|
option: string;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ChangeEvent } from "react";
|
import { ChangeEvent } from "react";
|
||||||
import Config from "../../config";
|
import Config from "../../config";
|
||||||
import { Keybind, formatKey, keybindEquals } from "@ajayyy/maze-utils/lib/config";
|
import { Keybind, formatKey, keybindEquals } from "../../../maze-utils/src/config";
|
||||||
|
|
||||||
export interface KeybindDialogProps {
|
export interface KeybindDialogProps {
|
||||||
option: string;
|
option: string;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as CompileConfig from "../config.json";
|
import * as CompileConfig from "../config.json";
|
||||||
import * as invidiousList from "../ci/invidiouslist.json";
|
import * as invidiousList from "../ci/invidiouslist.json";
|
||||||
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, VideoID, SponsorHideType } from "./types";
|
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, VideoID, SponsorHideType } from "./types";
|
||||||
import { Keybind, ProtoConfig, keybindEquals } from "@ajayyy/maze-utils/lib/config";
|
import { Keybind, ProtoConfig, keybindEquals } from "../maze-utils/src/config";
|
||||||
import { HashedValue } from "@ajayyy/maze-utils/lib/hash";
|
import { HashedValue } from "../maze-utils/src/hash";
|
||||||
|
|
||||||
export interface Permission {
|
export interface Permission {
|
||||||
canSubmit: boolean;
|
canSubmit: boolean;
|
||||||
@@ -54,6 +54,7 @@ interface SBConfig {
|
|||||||
showDonationLink: boolean;
|
showDonationLink: boolean;
|
||||||
showPopupDonationCount: number;
|
showPopupDonationCount: number;
|
||||||
showUpsells: boolean;
|
showUpsells: boolean;
|
||||||
|
showNewFeaturePopups: boolean;
|
||||||
donateClicked: number;
|
donateClicked: number;
|
||||||
autoHideInfoButton: boolean;
|
autoHideInfoButton: boolean;
|
||||||
autoSkipOnMusicVideos: boolean;
|
autoSkipOnMusicVideos: boolean;
|
||||||
@@ -72,6 +73,12 @@ interface SBConfig {
|
|||||||
useVirtualTime: boolean;
|
useVirtualTime: boolean;
|
||||||
showSegmentFailedToFetchWarning: boolean;
|
showSegmentFailedToFetchWarning: boolean;
|
||||||
allowScrollingToEdit: boolean;
|
allowScrollingToEdit: boolean;
|
||||||
|
deArrowInstalled: boolean;
|
||||||
|
showDeArrowPromotion: boolean;
|
||||||
|
showDeArrowInSettings: boolean;
|
||||||
|
shownDeArrowPromotion: boolean;
|
||||||
|
showZoomToFillError2: boolean;
|
||||||
|
cleanPopup: boolean;
|
||||||
|
|
||||||
// Used to cache calculated text color info
|
// Used to cache calculated text color info
|
||||||
categoryPillColors: {
|
categoryPillColors: {
|
||||||
@@ -82,6 +89,7 @@ interface SBConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
skipKeybind: Keybind;
|
skipKeybind: Keybind;
|
||||||
|
skipToHighlightKeybind: Keybind;
|
||||||
startSponsorKeybind: Keybind;
|
startSponsorKeybind: Keybind;
|
||||||
submitKeybind: Keybind;
|
submitKeybind: Keybind;
|
||||||
nextChapterKeybind: Keybind;
|
nextChapterKeybind: Keybind;
|
||||||
@@ -144,6 +152,10 @@ class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function migrateOldSyncFormats(config: SBConfig) {
|
function migrateOldSyncFormats(config: SBConfig) {
|
||||||
|
if (config["showZoomToFillError"]) {
|
||||||
|
chrome.storage.sync.remove("showZoomToFillError");
|
||||||
|
}
|
||||||
|
|
||||||
if (!config["chapterCategoryAdded"]) {
|
if (!config["chapterCategoryAdded"]) {
|
||||||
config["chapterCategoryAdded"] = true;
|
config["chapterCategoryAdded"] = true;
|
||||||
|
|
||||||
@@ -292,6 +304,7 @@ const syncDefaults = {
|
|||||||
showDonationLink: true,
|
showDonationLink: true,
|
||||||
showPopupDonationCount: 0,
|
showPopupDonationCount: 0,
|
||||||
showUpsells: true,
|
showUpsells: true,
|
||||||
|
showNewFeaturePopups: true,
|
||||||
donateClicked: 0,
|
donateClicked: 0,
|
||||||
autoHideInfoButton: true,
|
autoHideInfoButton: true,
|
||||||
autoSkipOnMusicVideos: false,
|
autoSkipOnMusicVideos: false,
|
||||||
@@ -305,6 +318,12 @@ const syncDefaults = {
|
|||||||
useVirtualTime: true,
|
useVirtualTime: true,
|
||||||
showSegmentFailedToFetchWarning: true,
|
showSegmentFailedToFetchWarning: true,
|
||||||
allowScrollingToEdit: true,
|
allowScrollingToEdit: true,
|
||||||
|
deArrowInstalled: false,
|
||||||
|
showDeArrowPromotion: true,
|
||||||
|
showDeArrowInSettings: true,
|
||||||
|
shownDeArrowPromotion: false,
|
||||||
|
showZoomToFillError2: true,
|
||||||
|
cleanPopup: false,
|
||||||
|
|
||||||
categoryPillColors: {},
|
categoryPillColors: {},
|
||||||
|
|
||||||
@@ -316,6 +335,7 @@ const syncDefaults = {
|
|||||||
* TODO: Find a way to skip having to update these checks. Maybe storing keybinds in a Map?
|
* TODO: Find a way to skip having to update these checks. Maybe storing keybinds in a Map?
|
||||||
*/
|
*/
|
||||||
skipKeybind: { key: "Enter" },
|
skipKeybind: { key: "Enter" },
|
||||||
|
skipToHighlightKeybind: { key: "Enter", ctrl: true },
|
||||||
startSponsorKeybind: { key: ";" },
|
startSponsorKeybind: { key: ";" },
|
||||||
submitKeybind: { key: "'" },
|
submitKeybind: { key: "'" },
|
||||||
nextChapterKeybind: { key: "ArrowRight", ctrl: true },
|
nextChapterKeybind: { key: "ArrowRight", ctrl: true },
|
||||||
|
|||||||
261
src/content.ts
261
src/content.ts
@@ -24,24 +24,31 @@ import SubmissionNotice from "./render/SubmissionNotice";
|
|||||||
import { Message, MessageResponse, VoteResponse } from "./messageTypes";
|
import { Message, MessageResponse, VoteResponse } from "./messageTypes";
|
||||||
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
||||||
import { getStartTimeFromUrl } from "./utils/urlParser";
|
import { getStartTimeFromUrl } from "./utils/urlParser";
|
||||||
import { getControls, getExistingChapters, getHashParams, isVisible } from "./utils/pageUtils";
|
import { getControls, getExistingChapters, getHashParams, isPlayingPlaylist, isVisible } from "./utils/pageUtils";
|
||||||
import { CategoryPill } from "./render/CategoryPill";
|
import { CategoryPill } from "./render/CategoryPill";
|
||||||
import { AnimationUtils } from "./utils/animationUtils";
|
import { AnimationUtils } from "./utils/animationUtils";
|
||||||
import { GenericUtils } from "./utils/genericUtils";
|
import { GenericUtils } from "./utils/genericUtils";
|
||||||
import { logDebug } from "./utils/logger";
|
import { logDebug, logWarn } from "./utils/logger";
|
||||||
import { importTimes } from "./utils/exporter";
|
import { importTimes } from "./utils/exporter";
|
||||||
import { ChapterVote } from "./render/ChapterVote";
|
import { ChapterVote } from "./render/ChapterVote";
|
||||||
import { openWarningDialog } from "./utils/warnings";
|
import { openWarningDialog } from "./utils/warnings";
|
||||||
import { isFirefoxOrSafari, waitFor } from "@ajayyy/maze-utils";
|
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
|
||||||
import { getErrorMessage, getFormattedTime } from "@ajayyy/maze-utils/lib/formating";
|
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
|
||||||
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube } from "@ajayyy/maze-utils/lib/video";
|
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube } from "../maze-utils/src/video";
|
||||||
import { Keybind, StorageChangesObject, isSafari, keybindEquals } from "@ajayyy/maze-utils/lib/config";
|
import { Keybind, StorageChangesObject, isSafari, keybindEquals } from "../maze-utils/src/config";
|
||||||
import { findValidElement } from "@ajayyy/maze-utils/lib/dom"
|
import { findValidElement } from "../maze-utils/src/dom"
|
||||||
import { getHash, HashedValue } from "@ajayyy/maze-utils/lib/hash";
|
import { getHash, HashedValue } from "../maze-utils/src/hash";
|
||||||
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
|
import { generateUserID } from "../maze-utils/src/setup";
|
||||||
import { updateAll } from "@ajayyy/maze-utils/lib/thumbnailManagement";
|
import { updateAll } from "../maze-utils/src/thumbnailManagement";
|
||||||
import { setupThumbnailListener } from "./utils/thumbnails";
|
import { setupThumbnailListener } from "./utils/thumbnails";
|
||||||
import * as documentScript from "../dist/js/document.js";
|
import * as documentScript from "../dist/js/document.js";
|
||||||
|
import { runCompatibilityChecks } from "./utils/compatibility";
|
||||||
|
import { cleanPage } from "./utils/pageCleaner";
|
||||||
|
import { addCleanupListener } from "../maze-utils/src/cleanup";
|
||||||
|
import { hideDeArrowPromotion, tryShowingDeArrowPromotion } from "./dearrowPromotion";
|
||||||
|
import { asyncRequestToServer } from "./utils/requests";
|
||||||
|
|
||||||
|
cleanPage();
|
||||||
|
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
@@ -49,9 +56,13 @@ utils.wait(() => Config.isReady(), 5000, 10).then(() => {
|
|||||||
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
||||||
addCSS();
|
addCSS();
|
||||||
setCategoryColorCSSVariables();
|
setCategoryColorCSSVariables();
|
||||||
|
|
||||||
|
runCompatibilityChecks();
|
||||||
});
|
});
|
||||||
|
|
||||||
const skipBuffer = 0.003;
|
const skipBuffer = 0.003;
|
||||||
|
// If this close to the end, skip to the end
|
||||||
|
const endTimeSkipBuffer = 0.5;
|
||||||
|
|
||||||
//was sponsor data found when doing SponsorsLookup
|
//was sponsor data found when doing SponsorsLookup
|
||||||
let sponsorDataFound = false;
|
let sponsorDataFound = false;
|
||||||
@@ -64,6 +75,7 @@ const skipNotices: SkipNotice[] = [];
|
|||||||
let activeSkipKeybindElement: ToggleSkippable = null;
|
let activeSkipKeybindElement: ToggleSkippable = null;
|
||||||
let retryFetchTimeout: NodeJS.Timeout = null;
|
let retryFetchTimeout: NodeJS.Timeout = null;
|
||||||
let shownSegmentFailedToFetchWarning = false;
|
let shownSegmentFailedToFetchWarning = false;
|
||||||
|
let selectedSegment: SegmentUUID | null = null;
|
||||||
|
|
||||||
// JSON video info
|
// JSON video info
|
||||||
let videoInfo: VideoInfo = null;
|
let videoInfo: VideoInfo = null;
|
||||||
@@ -254,6 +266,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
|||||||
case "reskip":
|
case "reskip":
|
||||||
reskipSponsorTime(sponsorTimes.find((segment) => segment.UUID === request.UUID), true);
|
reskipSponsorTime(sponsorTimes.find((segment) => segment.UUID === request.UUID), true);
|
||||||
break;
|
break;
|
||||||
|
case "selectSegment":
|
||||||
|
selectSegment(request.UUID);
|
||||||
|
break;
|
||||||
case "submitVote":
|
case "submitVote":
|
||||||
vote(request.type, request.UUID).then((response) => sendResponse(response));
|
vote(request.type, request.UUID).then((response) => sendResponse(response));
|
||||||
return true;
|
return true;
|
||||||
@@ -388,6 +403,8 @@ function resetValues() {
|
|||||||
for (let i = 0; i < skipNotices.length; i++) {
|
for (let i = 0; i < skipNotices.length; i++) {
|
||||||
skipNotices.pop()?.close();
|
skipNotices.pop()?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hideDeArrowPromotion();
|
||||||
}
|
}
|
||||||
|
|
||||||
function videoIDChange(): void {
|
function videoIDChange(): void {
|
||||||
@@ -428,9 +445,13 @@ function videoIDChange(): void {
|
|||||||
// Clear unsubmitted segments from the previous video
|
// Clear unsubmitted segments from the previous video
|
||||||
sponsorTimesSubmitting = [];
|
sponsorTimesSubmitting = [];
|
||||||
updateSponsorTimesSubmitting();
|
updateSponsorTimesSubmitting();
|
||||||
|
|
||||||
|
tryShowingDeArrowPromotion().catch(logWarn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMobileControlsMutations(): void {
|
function handleMobileControlsMutations(): void {
|
||||||
|
if (!chrome.runtime?.id) return;
|
||||||
|
|
||||||
updateVisibilityOfPlayerControlsButton();
|
updateVisibilityOfPlayerControlsButton();
|
||||||
|
|
||||||
skipButtonControlBar?.updateMobileControls();
|
skipButtonControlBar?.updateMobileControls();
|
||||||
@@ -482,7 +503,7 @@ function createPreviewBar(): void {
|
|||||||
selector: ".vjs-progress-holder",
|
selector: ".vjs-progress-holder",
|
||||||
isVisibleCheck: false
|
isVisibleCheck: false
|
||||||
}, {
|
}, {
|
||||||
// For Youtube Music
|
// For Youtube Music and YTKids
|
||||||
// there are two sliders, one for volume and one for progress - both called #progressContainer
|
// there are two sliders, one for volume and one for progress - both called #progressContainer
|
||||||
selector: "#progress-bar>#sliderContainer>div>#sliderBar>#progressContainer",
|
selector: "#progress-bar>#sliderContainer>div>#sliderBar>#progressContainer",
|
||||||
}, {
|
}, {
|
||||||
@@ -498,7 +519,7 @@ function createPreviewBar(): void {
|
|||||||
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const chapterVote = new ChapterVote(voteAsync);
|
const chapterVote = new ChapterVote(voteAsync);
|
||||||
previewBar = new PreviewBar(el, isOnMobileYouTube(), isOnInvidious(), chapterVote, () => importExistingChapters(false));
|
previewBar = new PreviewBar(el, isOnMobileYouTube(), isOnInvidious(), chapterVote, () => importExistingChapters(true));
|
||||||
|
|
||||||
updatePreviewBar();
|
updatePreviewBar();
|
||||||
|
|
||||||
@@ -570,7 +591,8 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||||||
|
|
||||||
updateActiveSegment(currentTime);
|
updateActiveSegment(currentTime);
|
||||||
|
|
||||||
if (getVideo().paused) return;
|
if (getVideo().paused
|
||||||
|
|| (getVideo().currentTime >= getVideo().duration - 0.01 && getVideo().duration > 1)) return;
|
||||||
const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
|
const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
|
||||||
|
|
||||||
const currentSkip = skipInfo.array[skipInfo.index];
|
const currentSkip = skipInfo.array[skipInfo.index];
|
||||||
@@ -649,8 +671,12 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||||||
forcedSkipTime = skipTime[0] + 0.001;
|
forcedSkipTime = skipTime[0] + 0.001;
|
||||||
} else {
|
} else {
|
||||||
forcedSkipTime = skipTime[1];
|
forcedSkipTime = skipTime[1];
|
||||||
forcedIncludeIntersectingSegments = true;
|
|
||||||
forcedIncludeNonIntersectingSegments = false;
|
forcedIncludeNonIntersectingSegments = false;
|
||||||
|
|
||||||
|
// Only if not at the end of the video
|
||||||
|
if (Math.abs(skipTime[1] - getVideo().duration) > endTimeSkipBuffer) {
|
||||||
|
forcedIncludeIntersectingSegments = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
forcedSkipTime = forceVideoTime + 0.001;
|
forcedSkipTime = forceVideoTime + 0.001;
|
||||||
@@ -661,7 +687,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||||||
|
|
||||||
// Don't pretend to be earlier than we are, could result in loops
|
// Don't pretend to be earlier than we are, could result in loops
|
||||||
if (forcedSkipTime !== null && forceVideoTime > forcedSkipTime) {
|
if (forcedSkipTime !== null && forceVideoTime > forcedSkipTime) {
|
||||||
forcedSkipTime = null;
|
forcedSkipTime = forceVideoTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
startSponsorSchedule(forcedIncludeIntersectingSegments, forcedSkipTime, forcedIncludeNonIntersectingSegments);
|
startSponsorSchedule(forcedIncludeIntersectingSegments, forcedSkipTime, forcedIncludeNonIntersectingSegments);
|
||||||
@@ -755,11 +781,12 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
|
|||||||
const currentVideoID = getYouTubeVideoID();
|
const currentVideoID = getYouTubeVideoID();
|
||||||
const recordedVideoID = videoID || getVideoID();
|
const recordedVideoID = videoID || getVideoID();
|
||||||
if (currentVideoID !== recordedVideoID || (sponsorTime
|
if (currentVideoID !== recordedVideoID || (sponsorTime
|
||||||
&& (!sponsorTimes || !sponsorTimes?.some((time) => time.segment === sponsorTime.segment))
|
&& (!sponsorTimes || !sponsorTimes?.some((time) => time.segment[0] === sponsorTime.segment[0] && time.segment[1] === sponsorTime.segment[1]))
|
||||||
&& !sponsorTimesSubmitting.some((time) => time.segment === sponsorTime.segment))) {
|
&& !sponsorTimesSubmitting.some((time) => time.segment[0] === sponsorTime.segment[0] && time.segment[1] === sponsorTime.segment[1]))) {
|
||||||
// Something has really gone wrong
|
// Something has really gone wrong
|
||||||
console.error("[SponsorBlock] The videoID recorded when trying to skip is different than what it should be.");
|
console.error("[SponsorBlock] The videoID recorded when trying to skip is different than what it should be.");
|
||||||
console.error("[SponsorBlock] VideoID recorded: " + recordedVideoID + ". Actual VideoID: " + currentVideoID);
|
console.error("[SponsorBlock] VideoID recorded: " + recordedVideoID + ". Actual VideoID: " + currentVideoID);
|
||||||
|
console.error("[SponsorBlock] SponsorTime", sponsorTime, "sponsorTimes", sponsorTimes, "sponsorTimesSubmitting", sponsorTimesSubmitting);
|
||||||
|
|
||||||
// Video ID change occured
|
// Video ID change occured
|
||||||
checkVideoIDChange();
|
checkVideoIDChange();
|
||||||
@@ -770,18 +797,38 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let playbackRateCheckInterval: NodeJS.Timeout | null = null;
|
||||||
|
let lastPlaybackSpeed = 1;
|
||||||
|
let setupVideoListenersFirstTime = true;
|
||||||
function setupVideoListeners() {
|
function setupVideoListeners() {
|
||||||
//wait until it is loaded
|
//wait until it is loaded
|
||||||
getVideo().addEventListener('loadstart', videoOnReadyListener)
|
getVideo().addEventListener('loadstart', videoOnReadyListener)
|
||||||
getVideo().addEventListener('durationchange', durationChangeListener);
|
getVideo().addEventListener('durationchange', durationChangeListener);
|
||||||
|
|
||||||
|
if (setupVideoListenersFirstTime) {
|
||||||
|
addCleanupListener(() => {
|
||||||
|
getVideo().removeEventListener('loadstart', videoOnReadyListener);
|
||||||
|
getVideo().removeEventListener('durationchange', durationChangeListener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!Config.config.disableSkipping) {
|
if (!Config.config.disableSkipping) {
|
||||||
switchingVideos = false;
|
switchingVideos = false;
|
||||||
|
|
||||||
let startedWaiting = false;
|
let startedWaiting = false;
|
||||||
let lastPausedAtZero = true;
|
let lastPausedAtZero = true;
|
||||||
|
|
||||||
getVideo().addEventListener('play', () => {
|
const rateChangeListener = () => {
|
||||||
|
updateVirtualTime();
|
||||||
|
clearWaitingTime();
|
||||||
|
|
||||||
|
startSponsorSchedule();
|
||||||
|
};
|
||||||
|
getVideo().addEventListener('ratechange', rateChangeListener);
|
||||||
|
// Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740)
|
||||||
|
getVideo().addEventListener('videoSpeed_ratechange', rateChangeListener);
|
||||||
|
|
||||||
|
const playListener = () => {
|
||||||
// If it is not the first event, then the only way to get to 0 is if there is a seek event
|
// If it is not the first event, then the only way to get to 0 is if there is a seek event
|
||||||
// This check makes sure that changing the video resolution doesn't cause the extension to think it
|
// This check makes sure that changing the video resolution doesn't cause the extension to think it
|
||||||
// gone back to the begining
|
// gone back to the begining
|
||||||
@@ -811,9 +858,10 @@ function setupVideoListeners() {
|
|||||||
|
|
||||||
startSponsorSchedule();
|
startSponsorSchedule();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
getVideo().addEventListener('play', playListener);
|
||||||
|
|
||||||
});
|
const playingListener = () => {
|
||||||
getVideo().addEventListener('playing', () => {
|
|
||||||
updateVirtualTime();
|
updateVirtualTime();
|
||||||
lastPausedAtZero = false;
|
lastPausedAtZero = false;
|
||||||
|
|
||||||
@@ -839,8 +887,31 @@ function setupVideoListeners() {
|
|||||||
|
|
||||||
startSponsorSchedule();
|
startSponsorSchedule();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
getVideo().addEventListener('seeking', () => {
|
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
|
||||||
|
lastPlaybackSpeed = getVideo().playbackRate;
|
||||||
|
|
||||||
|
// Video speed controller compatibility
|
||||||
|
// That extension makes rate change events not propagate
|
||||||
|
if (document.body.classList.contains("vsc-initialized")) {
|
||||||
|
playbackRateCheckInterval = setInterval(() => {
|
||||||
|
if ((!getVideoID() || getVideo().paused) && playbackRateCheckInterval) {
|
||||||
|
// Video is gone, stop checking
|
||||||
|
clearInterval(playbackRateCheckInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getVideo().playbackRate !== lastPlaybackSpeed) {
|
||||||
|
lastPlaybackSpeed = getVideo().playbackRate;
|
||||||
|
|
||||||
|
rateChangeListener();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getVideo().addEventListener('playing', playingListener);
|
||||||
|
|
||||||
|
const seekingListener = () => {
|
||||||
lastKnownVideoTime.fromPause = false;
|
lastKnownVideoTime.fromPause = false;
|
||||||
|
|
||||||
if (!getVideo().paused){
|
if (!getVideo().paused){
|
||||||
@@ -864,45 +935,54 @@ function setupVideoListeners() {
|
|||||||
lastPausedAtZero = true;
|
lastPausedAtZero = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
getVideo().addEventListener('ratechange', () => {
|
getVideo().addEventListener('seeking', seekingListener);
|
||||||
updateVirtualTime();
|
|
||||||
clearWaitingTime();
|
|
||||||
|
|
||||||
startSponsorSchedule();
|
|
||||||
});
|
|
||||||
// Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740)
|
|
||||||
getVideo().addEventListener('videoSpeed_ratechange', () => {
|
|
||||||
updateVirtualTime();
|
|
||||||
clearWaitingTime();
|
|
||||||
|
|
||||||
startSponsorSchedule();
|
|
||||||
});
|
|
||||||
const stoppedPlayback = () => {
|
const stoppedPlayback = () => {
|
||||||
// Reset lastCheckVideoTime
|
// Reset lastCheckVideoTime
|
||||||
lastCheckVideoTime = -1;
|
lastCheckVideoTime = -1;
|
||||||
lastCheckTime = 0;
|
lastCheckTime = 0;
|
||||||
|
|
||||||
|
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
|
||||||
|
|
||||||
lastKnownVideoTime.videoTime = null;
|
lastKnownVideoTime.videoTime = null;
|
||||||
lastKnownVideoTime.preciseTime = null;
|
lastKnownVideoTime.preciseTime = null;
|
||||||
updateWaitingTime();
|
updateWaitingTime();
|
||||||
|
|
||||||
cancelSponsorSchedule();
|
cancelSponsorSchedule();
|
||||||
};
|
};
|
||||||
getVideo().addEventListener('pause', () => {
|
const pauseListener = () => {
|
||||||
lastKnownVideoTime.fromPause = true;
|
lastKnownVideoTime.fromPause = true;
|
||||||
|
|
||||||
stoppedPlayback();
|
stoppedPlayback();
|
||||||
});
|
};
|
||||||
getVideo().addEventListener('waiting', () => {
|
getVideo().addEventListener('pause', pauseListener);
|
||||||
|
const waitingListener = () => {
|
||||||
logDebug("[SB] Not skipping due to buffering");
|
logDebug("[SB] Not skipping due to buffering");
|
||||||
startedWaiting = true;
|
startedWaiting = true;
|
||||||
|
|
||||||
stoppedPlayback();
|
stoppedPlayback();
|
||||||
});
|
};
|
||||||
|
getVideo().addEventListener('waiting', waitingListener);
|
||||||
|
|
||||||
startSponsorSchedule();
|
startSponsorSchedule();
|
||||||
|
|
||||||
|
if (setupVideoListenersFirstTime) {
|
||||||
|
addCleanupListener(() => {
|
||||||
|
getVideo().removeEventListener('play', playListener);
|
||||||
|
getVideo().removeEventListener('playing', playingListener);
|
||||||
|
getVideo().removeEventListener('seeking', seekingListener);
|
||||||
|
getVideo().removeEventListener('ratechange', rateChangeListener);
|
||||||
|
getVideo().removeEventListener('videoSpeed_ratechange', rateChangeListener);
|
||||||
|
getVideo().removeEventListener('pause', pauseListener);
|
||||||
|
getVideo().removeEventListener('waiting', waitingListener);
|
||||||
|
|
||||||
|
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupVideoListenersFirstTime = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateVirtualTime() {
|
function updateVirtualTime() {
|
||||||
@@ -964,6 +1044,7 @@ function setupSkipButtonControlBar() {
|
|||||||
openNotice: true,
|
openNotice: true,
|
||||||
forceAutoSkip: true
|
forceAutoSkip: true
|
||||||
}),
|
}),
|
||||||
|
selectSegment,
|
||||||
onMobileYouTube: isOnMobileYouTube()
|
onMobileYouTube: isOnMobileYouTube()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -998,7 +1079,7 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
|||||||
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
|
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
|
||||||
|
|
||||||
const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4) as VideoID & HashedValue;
|
const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4) as VideoID & HashedValue;
|
||||||
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||||
categories,
|
categories,
|
||||||
actionTypes: getEnabledActionTypes(),
|
actionTypes: getEnabledActionTypes(),
|
||||||
userAgent: `${chrome.runtime.id}`,
|
userAgent: `${chrome.runtime.id}`,
|
||||||
@@ -1138,7 +1219,7 @@ function getEnabledActionTypes(forceFullVideo = false): ActionType[] {
|
|||||||
|
|
||||||
async function lockedCategoriesLookup(): Promise<void> {
|
async function lockedCategoriesLookup(): Promise<void> {
|
||||||
const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4);
|
const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4);
|
||||||
const response = await utils.asyncRequestToServer("GET", "/api/lockCategories/" + hashPrefix);
|
const response = await asyncRequestToServer("GET", "/api/lockCategories/" + hashPrefix);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
try {
|
try {
|
||||||
@@ -1242,6 +1323,11 @@ function updatePreviewBarPositionMobile(parent: HTMLElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectSegment(UUID: SegmentUUID): void {
|
||||||
|
selectedSegment = UUID;
|
||||||
|
updatePreviewBar();
|
||||||
|
}
|
||||||
|
|
||||||
function updatePreviewBar(): void {
|
function updatePreviewBar(): void {
|
||||||
if (previewBar === null) return;
|
if (previewBar === null) return;
|
||||||
|
|
||||||
@@ -1267,7 +1353,8 @@ function updatePreviewBar(): void {
|
|||||||
showLarger: segment.actionType === ActionType.Poi,
|
showLarger: segment.actionType === ActionType.Poi,
|
||||||
description: segment.description,
|
description: segment.description,
|
||||||
source: segment.source,
|
source: segment.source,
|
||||||
requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID.startsWith(requiredSegment))
|
requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID?.startsWith(requiredSegment)),
|
||||||
|
selectedSegment: selectedSegment && segment.UUID === selectedSegment
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1314,16 +1401,20 @@ async function channelIDChange(channelIDInfo: ChannelIDInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function videoElementChange(newVideo: boolean): void {
|
function videoElementChange(newVideo: boolean): void {
|
||||||
if (newVideo) {
|
waitFor(() => Config.isReady()).then(() => {
|
||||||
setupVideoListeners();
|
if (newVideo) {
|
||||||
setupSkipButtonControlBar();
|
setupVideoListeners();
|
||||||
setupCategoryPill();
|
setupSkipButtonControlBar();
|
||||||
}
|
setupCategoryPill();
|
||||||
|
}
|
||||||
checkPreviewbarState();
|
|
||||||
|
checkPreviewbarState();
|
||||||
// Incase the page is still transitioning, check again in a few seconds
|
|
||||||
setTimeout(checkPreviewbarState, 5000);
|
// Incase the page is still transitioning, check again in a few seconds
|
||||||
|
setTimeout(checkPreviewbarState, 100);
|
||||||
|
setTimeout(checkPreviewbarState, 1000);
|
||||||
|
setTimeout(checkPreviewbarState, 5000);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPreviewbarState(): void {
|
function checkPreviewbarState(): void {
|
||||||
@@ -1522,7 +1613,7 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped:
|
|||||||
counted = true;
|
counted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullSkip) utils.asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
|
if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1543,9 +1634,14 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
|||||||
// for some reason you also can't skip to 1 second before the end
|
// for some reason you also can't skip to 1 second before the end
|
||||||
if (v.loop && v.duration > 1 && skipTime[1] >= v.duration - 1) {
|
if (v.loop && v.duration > 1 && skipTime[1] >= v.duration - 1) {
|
||||||
v.currentTime = 0;
|
v.currentTime = 0;
|
||||||
} else if (navigator.vendor === "Apple Computer, Inc." && v.duration > 1 && skipTime[1] >= v.duration) {
|
} else if (v.duration > 1 && skipTime[1] >= v.duration
|
||||||
|
&& (navigator.vendor === "Apple Computer, Inc." || isPlayingPlaylist())) {
|
||||||
// MacOS will loop otherwise #1027
|
// MacOS will loop otherwise #1027
|
||||||
|
// Sometimes playlists loop too #1804
|
||||||
v.currentTime = v.duration - 0.001;
|
v.currentTime = v.duration - 0.001;
|
||||||
|
} else if (v.duration > 1 && Math.abs(skipTime[1] - v.duration) < endTimeSkipBuffer
|
||||||
|
&& isFirefoxOrSafari() && !isSafari()) {
|
||||||
|
v.currentTime = v.duration;
|
||||||
} else {
|
} else {
|
||||||
if (inMuteSegment(skipTime[1], true)) {
|
if (inMuteSegment(skipTime[1], true)) {
|
||||||
// Make sure not to mute if skipping into a mute segment
|
// Make sure not to mute if skipping into a mute segment
|
||||||
@@ -1575,9 +1671,10 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
|||||||
beep.play();
|
beep.play();
|
||||||
beep.addEventListener("ended", () => {
|
beep.addEventListener("ended", () => {
|
||||||
navigator.mediaSession.metadata = null;
|
navigator.mediaSession.metadata = null;
|
||||||
setTimeout(() =>
|
setTimeout(() => {
|
||||||
navigator.mediaSession.metadata = oldMetadata
|
navigator.mediaSession.metadata = oldMetadata;
|
||||||
);
|
beep.remove();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1926,6 +2023,7 @@ function openInfoMenu() {
|
|||||||
const frame = document.createElement("iframe");
|
const frame = document.createElement("iframe");
|
||||||
frame.width = "374";
|
frame.width = "374";
|
||||||
frame.height = "500";
|
frame.height = "500";
|
||||||
|
frame.style.borderRadius = "12px";
|
||||||
frame.addEventListener("load", () => frame.contentWindow.postMessage("", "*"));
|
frame.addEventListener("load", () => frame.contentWindow.postMessage("", "*"));
|
||||||
frame.src = chrome.extension.getURL("popup.html");
|
frame.src = chrome.extension.getURL("popup.html");
|
||||||
popup.appendChild(frame);
|
popup.appendChild(frame);
|
||||||
@@ -2011,7 +2109,7 @@ async function vote(type: number, UUID: SegmentUUID, category?: Category, skipNo
|
|||||||
//success (treat rate limits as a success)
|
//success (treat rate limits as a success)
|
||||||
skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category);
|
skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category);
|
||||||
} else if (response.successType == -1) {
|
} else if (response.successType == -1) {
|
||||||
if (response.statusCode === 403 && response.responseText.startsWith("Vote rejected due to a warning from a moderator.")) {
|
if (response.statusCode === 403 && response.responseText.startsWith("Vote rejected due to a tip from a moderator.")) {
|
||||||
openWarningDialog(skipNoticeContentContainer);
|
openWarningDialog(skipNoticeContentContainer);
|
||||||
} else {
|
} else {
|
||||||
skipNotice.setNoticeInfoMessage.bind(skipNotice)(getErrorMessage(response.statusCode, response.responseText))
|
skipNotice.setNoticeInfoMessage.bind(skipNotice)(getErrorMessage(response.statusCode, response.responseText))
|
||||||
@@ -2151,7 +2249,7 @@ async function sendSubmitMessage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await utils.asyncRequestToServer("POST", "/api/skipSegments", {
|
const response = await asyncRequestToServer("POST", "/api/skipSegments", {
|
||||||
videoID: getVideoID(),
|
videoID: getVideoID(),
|
||||||
userID: Config.config.userID,
|
userID: Config.config.userID,
|
||||||
segments: sponsorTimesSubmitting,
|
segments: sponsorTimesSubmitting,
|
||||||
@@ -2201,7 +2299,7 @@ async function sendSubmitMessage() {
|
|||||||
playerButtons.submit.button.style.animation = "unset";
|
playerButtons.submit.button.style.animation = "unset";
|
||||||
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker.svg");
|
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker.svg");
|
||||||
|
|
||||||
if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a warning from a moderator.")) {
|
if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a tip from a moderator.")) {
|
||||||
openWarningDialog(skipNoticeContentContainer);
|
openWarningDialog(skipNoticeContentContainer);
|
||||||
} else {
|
} else {
|
||||||
alert(getErrorMessage(response.status, response.responseText));
|
alert(getErrorMessage(response.status, response.responseText));
|
||||||
@@ -2286,11 +2384,21 @@ function previousChapter(): void {
|
|||||||
function addHotkeyListener(): void {
|
function addHotkeyListener(): void {
|
||||||
document.addEventListener("keydown", hotkeyListener);
|
document.addEventListener("keydown", hotkeyListener);
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
const onLoad = () => {
|
||||||
// Allow us to stop propagation to YouTube by being deeper
|
// Allow us to stop propagation to YouTube by being deeper
|
||||||
document.removeEventListener("keydown", hotkeyListener);
|
document.removeEventListener("keydown", hotkeyListener);
|
||||||
document.body.addEventListener("keydown", hotkeyListener);
|
document.body.addEventListener("keydown", hotkeyListener);
|
||||||
});
|
|
||||||
|
addCleanupListener(() => {
|
||||||
|
document.body.removeEventListener("keydown", hotkeyListener);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
onLoad();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", onLoad);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hotkeyListener(e: KeyboardEvent): void {
|
function hotkeyListener(e: KeyboardEvent): void {
|
||||||
@@ -2306,14 +2414,23 @@ function hotkeyListener(e: KeyboardEvent): void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const skipKey = Config.config.skipKeybind;
|
const skipKey = Config.config.skipKeybind;
|
||||||
|
const skipToHighlightKey = Config.config.skipToHighlightKeybind;
|
||||||
const startSponsorKey = Config.config.startSponsorKeybind;
|
const startSponsorKey = Config.config.startSponsorKeybind;
|
||||||
const submitKey = Config.config.submitKeybind;
|
const submitKey = Config.config.submitKeybind;
|
||||||
const nextChapterKey = Config.config.nextChapterKeybind;
|
const nextChapterKey = Config.config.nextChapterKeybind;
|
||||||
const previousChapterKey = Config.config.previousChapterKeybind;
|
const previousChapterKey = Config.config.previousChapterKeybind;
|
||||||
|
|
||||||
if (keybindEquals(key, skipKey)) {
|
if (keybindEquals(key, skipKey)) {
|
||||||
if (activeSkipKeybindElement)
|
if (activeSkipKeybindElement) {
|
||||||
activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement);
|
activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (keybindEquals(key, skipToHighlightKey)) {
|
||||||
|
if (skipButtonControlBar) {
|
||||||
|
skipButtonControlBar.toggleSkip.call(skipButtonControlBar);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (keybindEquals(key, startSponsorKey)) {
|
} else if (keybindEquals(key, startSponsorKey)) {
|
||||||
startOrEndTimingNewSegment();
|
startOrEndTimingNewSegment();
|
||||||
@@ -2346,8 +2463,8 @@ function hotkeyListener(e: KeyboardEvent): void {
|
|||||||
* Adds the CSS to the page if needed. Required on optional sites with Chrome.
|
* Adds the CSS to the page if needed. Required on optional sites with Chrome.
|
||||||
*/
|
*/
|
||||||
function addCSS() {
|
function addCSS() {
|
||||||
if (!isFirefoxOrSafari() && Config.config.invidiousInstances.includes(new URL(document.URL).host)) {
|
if (!isFirefoxOrSafari() && Config.config.invidiousInstances.includes(new URL(document.URL).hostname)) {
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
const onLoad = () => {
|
||||||
const head = document.getElementsByTagName("head")[0];
|
const head = document.getElementsByTagName("head")[0];
|
||||||
|
|
||||||
for (const file of utils.css) {
|
for (const file of utils.css) {
|
||||||
@@ -2359,7 +2476,13 @@ function addCSS() {
|
|||||||
|
|
||||||
head.appendChild(fileref);
|
head.appendChild(fileref);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
onLoad();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", onLoad);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2444,7 +2567,9 @@ function setCategoryColorCSSVariables() {
|
|||||||
if (!styleContainer) {
|
if (!styleContainer) {
|
||||||
styleContainer = document.createElement("style");
|
styleContainer = document.createElement("style");
|
||||||
styleContainer.id = "sbCategoryColorStyle";
|
styleContainer.id = "sbCategoryColorStyle";
|
||||||
document.head.appendChild(styleContainer)
|
|
||||||
|
const head = (document.head || document.documentElement);
|
||||||
|
head.appendChild(styleContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
let css = ":root {"
|
let css = ":root {"
|
||||||
|
|||||||
71
src/dearrowPromotion.ts
Normal file
71
src/dearrowPromotion.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { waitFor } from "../maze-utils/src";
|
||||||
|
import { getYouTubeTitleNode } from "../maze-utils/src/elements";
|
||||||
|
import { getHash } from "../maze-utils/src/hash";
|
||||||
|
import { getVideoID, isOnInvidious, isOnMobileYouTube } from "../maze-utils/src/video";
|
||||||
|
import Config from "./config";
|
||||||
|
import { Tooltip } from "./render/Tooltip";
|
||||||
|
import { isDeArrowInstalled } from "./utils/crossExtension";
|
||||||
|
import { isVisible } from "./utils/pageUtils";
|
||||||
|
import { asyncRequestToServer } from "./utils/requests";
|
||||||
|
|
||||||
|
let tooltip: Tooltip = null;
|
||||||
|
export async function tryShowingDeArrowPromotion() {
|
||||||
|
if (Config.config.showDeArrowPromotion
|
||||||
|
&& !isOnMobileYouTube()
|
||||||
|
&& !isOnInvidious()
|
||||||
|
&& document.URL.includes("watch")
|
||||||
|
&& Config.config.showUpsells
|
||||||
|
&& Config.config.showNewFeaturePopups
|
||||||
|
&& (Config.config.skipCount > 30 || !Config.config.trackViewCount)) {
|
||||||
|
|
||||||
|
if (!await isDeArrowInstalled()) {
|
||||||
|
try {
|
||||||
|
const element = await waitFor(() => getYouTubeTitleNode(), 5000, 500, (e) => isVisible(e)) as HTMLElement;
|
||||||
|
if (element && element.innerText && badTitle(element.innerText)) {
|
||||||
|
const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4);
|
||||||
|
const deArrowData = await asyncRequestToServer("GET", "/api/branding/" + hashPrefix);
|
||||||
|
if (!deArrowData.ok) return;
|
||||||
|
|
||||||
|
const deArrowDataJson = JSON.parse(deArrowData.responseText);
|
||||||
|
const title = deArrowDataJson?.[getVideoID()]?.titles?.[0];
|
||||||
|
if (title && title.title && (title.locked || title.votes > 0)) {
|
||||||
|
Config.config.showDeArrowPromotion = false;
|
||||||
|
|
||||||
|
tooltip = new Tooltip({
|
||||||
|
text: chrome.i18n.getMessage("DeArrowTitleReplacementSuggestion") + "\n\n" + title.title,
|
||||||
|
linkOnClick: () => {
|
||||||
|
window.open("https://dearrow.ajay.app");
|
||||||
|
Config.config.shownDeArrowPromotion = true;
|
||||||
|
},
|
||||||
|
referenceNode: element,
|
||||||
|
prependElement: element.firstElementChild as HTMLElement,
|
||||||
|
timeout: 15000,
|
||||||
|
positionRealtive: false,
|
||||||
|
containerAbsolute: true,
|
||||||
|
bottomOffset: "inherit",
|
||||||
|
topOffset: "55px",
|
||||||
|
leftOffset: "0",
|
||||||
|
rightOffset: "0",
|
||||||
|
topTriangle: true,
|
||||||
|
center: true,
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch { } // eslint-disable-line no-empty
|
||||||
|
} else {
|
||||||
|
Config.config.showDeArrowPromotion = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two upper case words (at least 2 letters long)
|
||||||
|
*/
|
||||||
|
function badTitle(title: string): boolean {
|
||||||
|
return !!title.match(/\p{Lu}{2,} \p{Lu}{2,}[.!? ]/u);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideDeArrowPromotion(): void {
|
||||||
|
if (tooltip) tooltip.close();
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import { init } from "@ajayyy/maze-utils/lib/injected/document";
|
import { init } from "../maze-utils/src/injected/document";
|
||||||
|
|
||||||
init();
|
init();
|
||||||
37
src/help.ts
37
src/help.ts
@@ -1,10 +1,41 @@
|
|||||||
import { localizeHtmlPage } from "@ajayyy/maze-utils/lib/setup";
|
import { localizeHtmlPage } from "../maze-utils/src/setup";
|
||||||
import Config from "./config";
|
import Config from "./config";
|
||||||
import { showDonationLink } from "./utils/configUtils";
|
import { showDonationLink } from "./utils/configUtils";
|
||||||
|
|
||||||
import { waitFor } from "@ajayyy/maze-utils";
|
import { waitFor } from "../maze-utils/src";
|
||||||
|
import { isDeArrowInstalled } from "./utils/crossExtension";
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', init);
|
if (document.readyState === "complete") {
|
||||||
|
init();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeArrow promotion
|
||||||
|
waitFor(() => Config.isReady()).then(() => {
|
||||||
|
if (Config.config.showNewFeaturePopups && Config.config.showUpsells) {
|
||||||
|
isDeArrowInstalled().then((installed) => {
|
||||||
|
if (!installed) {
|
||||||
|
const deArrowPromotion = document.getElementById("dearrow-link");
|
||||||
|
deArrowPromotion.classList.remove("hidden");
|
||||||
|
|
||||||
|
deArrowPromotion.addEventListener("click", () => Config.config.showDeArrowPromotion = false);
|
||||||
|
|
||||||
|
const text = deArrowPromotion.querySelector("#dearrow-link-text");
|
||||||
|
text.textContent = `${chrome.i18n.getMessage("DeArrowPromotionMessage2").split("?")[0]}? ${chrome.i18n.getMessage("DeArrowPromotionMessage3")}`;
|
||||||
|
|
||||||
|
const closeButton = deArrowPromotion.querySelector(".close-button");
|
||||||
|
closeButton.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
deArrowPromotion.classList.add("hidden");
|
||||||
|
Config.config.showDeArrowPromotion = false;
|
||||||
|
Config.config.showDeArrowInSettings = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
localizeHtmlPage();
|
localizeHtmlPage();
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import { ActionType, Category, SegmentContainer, SponsorHideType, SponsorSourceT
|
|||||||
import { partition } from "../utils/arrayUtils";
|
import { partition } from "../utils/arrayUtils";
|
||||||
import { DEFAULT_CATEGORY, shortCategoryName } from "../utils/categoryUtils";
|
import { DEFAULT_CATEGORY, shortCategoryName } from "../utils/categoryUtils";
|
||||||
import { normalizeChapterName } from "../utils/exporter";
|
import { normalizeChapterName } from "../utils/exporter";
|
||||||
import { getFormattedTimeToSeconds } from "@ajayyy/maze-utils/lib/formating";
|
import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
|
||||||
import { findValidElement } from "@ajayyy/maze-utils/lib/dom";
|
import { findValidElement } from "../../maze-utils/src/dom";
|
||||||
|
import { addCleanupListener } from "../../maze-utils/src/cleanup";
|
||||||
|
|
||||||
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
|
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
|
||||||
const MIN_CHAPTER_SIZE = 0.003;
|
const MIN_CHAPTER_SIZE = 0.003;
|
||||||
@@ -26,6 +27,7 @@ export interface PreviewBarSegment {
|
|||||||
description: string;
|
description: string;
|
||||||
source: SponsorSourceType;
|
source: SponsorSourceType;
|
||||||
requiredSegment?: boolean;
|
requiredSegment?: boolean;
|
||||||
|
selectedSegment?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChapterGroup extends SegmentContainer {
|
interface ChapterGroup extends SegmentContainer {
|
||||||
@@ -97,7 +99,8 @@ class PreviewBar {
|
|||||||
this.chapterTooltip = document.createElement("div");
|
this.chapterTooltip = document.createElement("div");
|
||||||
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
|
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
|
||||||
|
|
||||||
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper");
|
// global chaper tooltip or duration tooltip
|
||||||
|
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info");
|
||||||
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip)") as HTMLElement;
|
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip)") as HTMLElement;
|
||||||
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
|
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
|
||||||
|
|
||||||
@@ -200,6 +203,10 @@ class PreviewBar {
|
|||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addCleanupListener(() => {
|
||||||
|
observer.disconnect();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
|
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
|
||||||
@@ -326,6 +333,7 @@ class PreviewBar {
|
|||||||
const bar = document.createElement('li');
|
const bar = document.createElement('li');
|
||||||
bar.classList.add('previewbar');
|
bar.classList.add('previewbar');
|
||||||
if (barSegment.requiredSegment) bar.classList.add("requiredSegment");
|
if (barSegment.requiredSegment) bar.classList.add("requiredSegment");
|
||||||
|
if (barSegment.selectedSegment) bar.classList.add("selectedSegment");
|
||||||
bar.innerHTML = showLarger ? ' ' : ' ';
|
bar.innerHTML = showLarger ? ' ' : ' ';
|
||||||
|
|
||||||
const fullCategoryName = (unsubmitted ? 'preview-' : '') + category;
|
const fullCategoryName = (unsubmitted ? 'preview-' : '') + category;
|
||||||
@@ -625,6 +633,11 @@ class PreviewBar {
|
|||||||
childListObserver.observe(this.originalChapterBar, {
|
childListObserver.observe(this.originalChapterBar, {
|
||||||
childList: true
|
childList: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addCleanupListener(() => {
|
||||||
|
attributeObserver.disconnect();
|
||||||
|
childListObserver.disconnect();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateChapterAllMutation(originalChapterBar: HTMLElement, progressBar: HTMLElement, firstUpdate = false): void {
|
private updateChapterAllMutation(originalChapterBar: HTMLElement, progressBar: HTMLElement, firstUpdate = false): void {
|
||||||
@@ -773,9 +786,12 @@ class PreviewBar {
|
|||||||
if (!Config.config.showSegmentNameInChapterBar
|
if (!Config.config.showSegmentNameInChapterBar
|
||||||
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) {
|
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) {
|
||||||
const chaptersContainer = this.getChaptersContainer();
|
const chaptersContainer = this.getChaptersContainer();
|
||||||
const chapterButton = this.getChapterButton(chaptersContainer);
|
if (chaptersContainer) {
|
||||||
if (chapterButton && chapterButton.classList.contains("ytp-chapter-container-disabled")) {
|
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
|
||||||
chaptersContainer.style.display = "none";
|
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
||||||
|
|
||||||
|
chapterTitle.style.removeProperty("display");
|
||||||
|
chaptersContainer.classList.remove("sponsorblock-chapter-visible");
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
@@ -801,7 +817,7 @@ class PreviewBar {
|
|||||||
|
|
||||||
if (chaptersContainer) {
|
if (chaptersContainer) {
|
||||||
if (segments.length > 0) {
|
if (segments.length > 0) {
|
||||||
chaptersContainer.style.removeProperty("display");
|
chaptersContainer.classList.add("sponsorblock-chapter-visible");
|
||||||
|
|
||||||
const chosenSegment = segments.sort((a, b) => {
|
const chosenSegment = segments.sort((a, b) => {
|
||||||
if (a.actionType === ActionType.Chapter && b.actionType !== ActionType.Chapter) {
|
if (a.actionType === ActionType.Chapter && b.actionType !== ActionType.Chapter) {
|
||||||
@@ -818,11 +834,11 @@ class PreviewBar {
|
|||||||
chapterButton.disabled = false;
|
chapterButton.disabled = false;
|
||||||
|
|
||||||
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
||||||
chapterTitle.innerText = "";
|
chapterTitle.style.display = "none";
|
||||||
|
|
||||||
const chapterCustomText = (chapterTitle.querySelector(".sponsorChapterText") || (() => {
|
const chapterCustomText = (chapterTitle.parentElement.querySelector(".sponsorChapterText") || (() => {
|
||||||
const elem = document.createElement("div");
|
const elem = document.createElement("div");
|
||||||
chapterTitle.appendChild(elem);
|
chapterTitle.parentElement.insertBefore(elem, chapterTitle);
|
||||||
elem.classList.add("sponsorChapterText");
|
elem.classList.add("sponsorChapterText");
|
||||||
return elem;
|
return elem;
|
||||||
})()) as HTMLDivElement;
|
})()) as HTMLDivElement;
|
||||||
@@ -853,11 +869,10 @@ class PreviewBar {
|
|||||||
} else {
|
} else {
|
||||||
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
|
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
|
||||||
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
||||||
if (chapterTitle.innerText === "") {
|
|
||||||
chaptersContainer.style.display = "none";
|
chapterTitle.style.removeProperty("display");
|
||||||
} else {
|
chaptersContainer.classList.remove("sponsorblock-chapter-visible");
|
||||||
chaptersContainer.style.removeProperty("display");
|
|
||||||
}
|
|
||||||
this.chapterVote.setVisibility(false);
|
this.chapterVote.setVisibility(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
import { SponsorTime } from "../types";
|
import { SegmentUUID, SponsorTime } from "../types";
|
||||||
import { getSkippingText } from "../utils/categoryUtils";
|
import { getSkippingText } from "../utils/categoryUtils";
|
||||||
import { AnimationUtils } from "../utils/animationUtils";
|
import { AnimationUtils } from "../utils/animationUtils";
|
||||||
import { keybindToString } from "@ajayyy/maze-utils/lib/config";
|
import { keybindToString } from "../../maze-utils/src/config";
|
||||||
|
|
||||||
export interface SkipButtonControlBarProps {
|
export interface SkipButtonControlBarProps {
|
||||||
skip: (segment: SponsorTime) => void;
|
skip: (segment: SponsorTime) => void;
|
||||||
|
selectSegment: (UUID: SegmentUUID) => void;
|
||||||
onMobileYouTube: boolean;
|
onMobileYouTube: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ export class SkipButtonControlBar {
|
|||||||
|
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.classList.add("skipButtonControlBarContainer");
|
this.container.classList.add("skipButtonControlBarContainer");
|
||||||
this.container.classList.add("hidden");
|
this.container.classList.add("sbhidden");
|
||||||
if (this.onMobileYouTube) this.container.classList.add("mobile");
|
if (this.onMobileYouTube) this.container.classList.add("mobile");
|
||||||
|
|
||||||
this.skipIcon = document.createElement("img");
|
this.skipIcon = document.createElement("img");
|
||||||
@@ -54,8 +55,18 @@ export class SkipButtonControlBar {
|
|||||||
this.container.appendChild(this.skipIcon);
|
this.container.appendChild(this.skipIcon);
|
||||||
this.container.appendChild(this.textContainer);
|
this.container.appendChild(this.textContainer);
|
||||||
this.container.addEventListener("click", () => this.toggleSkip());
|
this.container.addEventListener("click", () => this.toggleSkip());
|
||||||
this.container.addEventListener("mouseenter", () => this.stopTimer());
|
this.container.addEventListener("mouseenter", () => {
|
||||||
this.container.addEventListener("mouseleave", () => this.startTimer());
|
this.stopTimer();
|
||||||
|
|
||||||
|
if (this.segment) {
|
||||||
|
props.selectSegment(this.segment.UUID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.container.addEventListener("mouseleave", () => {
|
||||||
|
this.startTimer();
|
||||||
|
|
||||||
|
props.selectSegment(null);
|
||||||
|
});
|
||||||
if (this.onMobileYouTube) {
|
if (this.onMobileYouTube) {
|
||||||
this.container.addEventListener("touchstart", (e) => this.handleTouchStart(e));
|
this.container.addEventListener("touchstart", (e) => this.handleTouchStart(e));
|
||||||
this.container.addEventListener("touchmove", (e) => this.handleTouchMove(e));
|
this.container.addEventListener("touchmove", (e) => this.handleTouchMove(e));
|
||||||
@@ -103,7 +114,7 @@ export class SkipButtonControlBar {
|
|||||||
|
|
||||||
this.refreshText();
|
this.refreshText();
|
||||||
this.container?.classList?.remove("textDisabled");
|
this.container?.classList?.remove("textDisabled");
|
||||||
this.textContainer?.classList?.remove("hidden");
|
this.textContainer?.classList?.remove("sbhidden");
|
||||||
AnimationUtils.disableAutoHideAnimation(this.skipIcon);
|
AnimationUtils.disableAutoHideAnimation(this.skipIcon);
|
||||||
|
|
||||||
this.startTimer();
|
this.startTimer();
|
||||||
@@ -111,8 +122,8 @@ export class SkipButtonControlBar {
|
|||||||
|
|
||||||
refreshText(): void {
|
refreshText(): void {
|
||||||
if (this.segment) {
|
if (this.segment) {
|
||||||
this.chapterText?.classList?.add("hidden");
|
this.chapterText?.classList?.add("sbhidden");
|
||||||
this.container.classList.remove("hidden");
|
this.container.classList.remove("sbhidden");
|
||||||
this.textContainer.innerText = this.getTitle();
|
this.textContainer.innerText = this.getTitle();
|
||||||
this.skipIcon.setAttribute("title", this.getTitle());
|
this.skipIcon.setAttribute("title", this.getTitle());
|
||||||
}
|
}
|
||||||
@@ -134,10 +145,10 @@ export class SkipButtonControlBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disable(): void {
|
disable(): void {
|
||||||
this.container.classList.add("hidden");
|
this.container.classList.add("sbhidden");
|
||||||
|
|
||||||
this.chapterText?.classList?.remove("hidden");
|
this.chapterText?.classList?.remove("sbhidden");
|
||||||
this.getChapterPrefix()?.classList?.remove("hidden");
|
this.getChapterPrefix()?.classList?.remove("sbhidden");
|
||||||
|
|
||||||
this.enabled = false;
|
this.enabled = false;
|
||||||
}
|
}
|
||||||
@@ -147,8 +158,10 @@ export class SkipButtonControlBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleSkip(): void {
|
toggleSkip(): void {
|
||||||
this.skip(this.segment);
|
if (this.segment && this.enabled) {
|
||||||
this.disableText();
|
this.skip(this.segment);
|
||||||
|
this.disableText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableText(): void {
|
disableText(): void {
|
||||||
@@ -158,10 +171,10 @@ export class SkipButtonControlBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.container.classList.add("textDisabled");
|
this.container.classList.add("textDisabled");
|
||||||
this.textContainer?.classList?.add("hidden");
|
this.textContainer?.classList?.add("sbhidden");
|
||||||
this.chapterText?.classList?.remove("hidden");
|
this.chapterText?.classList?.remove("sbhidden");
|
||||||
|
|
||||||
this.getChapterPrefix()?.classList?.add("hidden");
|
this.getChapterPrefix()?.classList?.add("sbhidden");
|
||||||
|
|
||||||
AnimationUtils.enableAutoHideAnimation(this.skipIcon);
|
AnimationUtils.enableAutoHideAnimation(this.skipIcon);
|
||||||
if (this.onMobileYouTube) {
|
if (this.onMobileYouTube) {
|
||||||
@@ -182,7 +195,7 @@ export class SkipButtonControlBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getTitle(): string {
|
private getTitle(): string {
|
||||||
return getSkippingText([this.segment], false) + (this.showKeybindHint ? " (" + keybindToString(Config.config.skipKeybind) + ")" : "");
|
return getSkippingText([this.segment], false) + (this.showKeybindHint ? " (" + keybindToString(Config.config.skipToHighlightKeybind) + ")" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private getChapterPrefix(): HTMLElement {
|
private getChapterPrefix(): HTMLElement {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ interface IsInfoFoundMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface SkipMessage {
|
interface SkipMessage {
|
||||||
message: "unskip" | "reskip";
|
message: "unskip" | "reskip" | "selectSegment";
|
||||||
UUID: SegmentUUID;
|
UUID: SegmentUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,17 +13,23 @@ import CategoryChooser from "./render/CategoryChooser";
|
|||||||
import UnsubmittedVideos from "./render/UnsubmittedVideos";
|
import UnsubmittedVideos from "./render/UnsubmittedVideos";
|
||||||
import KeybindComponent from "./components/options/KeybindComponent";
|
import KeybindComponent from "./components/options/KeybindComponent";
|
||||||
import { showDonationLink } from "./utils/configUtils";
|
import { showDonationLink } from "./utils/configUtils";
|
||||||
import { localizeHtmlPage } from "@ajayyy/maze-utils/lib/setup";
|
import { localizeHtmlPage } from "../maze-utils/src/setup";
|
||||||
import { StorageChangesObject } from "@ajayyy/maze-utils/lib/config";
|
import { StorageChangesObject } from "../maze-utils/src/config";
|
||||||
import { getHash } from "@ajayyy/maze-utils/lib/hash";
|
import { getHash } from "../maze-utils/src/hash";
|
||||||
import { isFirefoxOrSafari } from "@ajayyy/maze-utils";
|
import { isFirefoxOrSafari } from "../maze-utils/src";
|
||||||
|
import { isDeArrowInstalled } from "./utils/crossExtension";
|
||||||
|
import { asyncRequestToServer } from "./utils/requests";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
let embed = false;
|
let embed = false;
|
||||||
|
|
||||||
const categoryChoosers: CategoryChooser[] = [];
|
const categoryChoosers: CategoryChooser[] = [];
|
||||||
const unsubmittedVideos: UnsubmittedVideos[] = [];
|
const unsubmittedVideos: UnsubmittedVideos[] = [];
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', init);
|
if (document.readyState === "complete") {
|
||||||
|
init();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
localizeHtmlPage();
|
localizeHtmlPage();
|
||||||
@@ -68,6 +74,30 @@ async function init() {
|
|||||||
donate.classList.add("hidden");
|
donate.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeArrow promotion
|
||||||
|
if (Config.config.showNewFeaturePopups && Config.config.showUpsells && Config.config.showDeArrowInSettings) {
|
||||||
|
isDeArrowInstalled().then((installed) => {
|
||||||
|
if (!installed) {
|
||||||
|
const deArrowPromotion = document.getElementById("deArrowPromotion");
|
||||||
|
deArrowPromotion.classList.remove("hidden");
|
||||||
|
|
||||||
|
deArrowPromotion.addEventListener("click", () => Config.config.showDeArrowPromotion = false);
|
||||||
|
|
||||||
|
const closeButton = deArrowPromotion.querySelector(".close-button");
|
||||||
|
closeButton.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
deArrowPromotion.classList.add("hidden");
|
||||||
|
Config.config.showDeArrowPromotion = false;
|
||||||
|
Config.config.showDeArrowInSettings = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const skipToHighlightKeybind = document.querySelector(`[data-sync="skipToHighlightKeybind"] .optionLabel`) as HTMLElement;
|
||||||
|
skipToHighlightKeybind.innerText = `${chrome.i18n.getMessage("skip_to_category").replace("{0}", chrome.i18n.getMessage("category_poi_highlight")).replace("?", "")}:`;
|
||||||
|
|
||||||
// Set all of the toggle options to the correct option
|
// Set all of the toggle options to the correct option
|
||||||
const optionsContainer = document.getElementById("options");
|
const optionsContainer = document.getElementById("options");
|
||||||
const optionsElements = optionsContainer.querySelectorAll("*");
|
const optionsElements = optionsContainer.querySelectorAll("*");
|
||||||
@@ -432,7 +462,12 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
|||||||
let instanceList = Config.config[option];
|
let instanceList = Config.config[option];
|
||||||
if (!instanceList) instanceList = [];
|
if (!instanceList) instanceList = [];
|
||||||
|
|
||||||
instanceList.push(textBox.value.trim().toLowerCase());
|
let domain = textBox.value.trim().toLowerCase();
|
||||||
|
if (domain.includes(":")) {
|
||||||
|
domain = domain.split(":")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceList.push(domain);
|
||||||
|
|
||||||
Config.config[option] = instanceList;
|
Config.config[option] = instanceList;
|
||||||
|
|
||||||
@@ -533,7 +568,7 @@ function activatePrivateTextChange(element: HTMLElement) {
|
|||||||
switch (option) {
|
switch (option) {
|
||||||
case "userID":
|
case "userID":
|
||||||
if (Config.config[option]) {
|
if (Config.config[option]) {
|
||||||
utils.asyncRequestToServer("GET", "/api/userInfo", {
|
asyncRequestToServer("GET", "/api/userInfo", {
|
||||||
publicUserID: getHash(Config.config[option]),
|
publicUserID: getHash(Config.config[option]),
|
||||||
values: ["warnings", "banned"]
|
values: ["warnings", "banned"]
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import Config from "./config";
|
import Config from "./config";
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
import { localizeHtmlPage } from "@ajayyy/maze-utils/lib/setup";
|
import { localizeHtmlPage } from "../maze-utils/src/setup";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
// This is needed, if Config is not imported before Utils, things break.
|
// This is needed, if Config is not imported before Utils, things break.
|
||||||
// Probably due to cyclic dependencies
|
// Probably due to cyclic dependencies
|
||||||
Config.config;
|
Config.config;
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', init);
|
if (document.readyState === "complete") {
|
||||||
|
init();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
localizeHtmlPage();
|
localizeHtmlPage();
|
||||||
|
|||||||
43
src/popup.ts
43
src/popup.ts
@@ -21,12 +21,13 @@ import {
|
|||||||
import { showDonationLink } from "./utils/configUtils";
|
import { showDonationLink } from "./utils/configUtils";
|
||||||
import { AnimationUtils } from "./utils/animationUtils";
|
import { AnimationUtils } from "./utils/animationUtils";
|
||||||
import { shortCategoryName } from "./utils/categoryUtils";
|
import { shortCategoryName } from "./utils/categoryUtils";
|
||||||
import { localizeHtmlPage } from "@ajayyy/maze-utils/lib/setup";
|
import { localizeHtmlPage } from "../maze-utils/src/setup";
|
||||||
import { exportTimes } from "./utils/exporter";
|
import { exportTimes } from "./utils/exporter";
|
||||||
import GenericNotice from "./render/GenericNotice";
|
import GenericNotice from "./render/GenericNotice";
|
||||||
import { getErrorMessage, getFormattedTime } from "@ajayyy/maze-utils/lib/formating";
|
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
|
||||||
import { StorageChangesObject } from "@ajayyy/maze-utils/lib/config";
|
import { StorageChangesObject } from "../maze-utils/src/config";
|
||||||
import { getHash } from "@ajayyy/maze-utils/lib/hash";
|
import { getHash } from "../maze-utils/src/hash";
|
||||||
|
import { asyncRequestToServer, sendRequestToServer } from "./utils/requests";
|
||||||
|
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
@@ -108,6 +109,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
const PageElements: PageElements = {};
|
const PageElements: PageElements = {};
|
||||||
|
|
||||||
[
|
[
|
||||||
|
"sbPopupLogo",
|
||||||
|
"sbYourWorkBox",
|
||||||
|
"videoInfo",
|
||||||
|
"sbFooter",
|
||||||
"sponsorBlockPopupBody",
|
"sponsorBlockPopupBody",
|
||||||
"sponsorblockPopup",
|
"sponsorblockPopup",
|
||||||
"sponsorStart",
|
"sponsorStart",
|
||||||
@@ -198,6 +203,16 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
}
|
}
|
||||||
PageElements.sbDonate.addEventListener("click", () => Config.config.donateClicked = Config.config.donateClicked + 1);
|
PageElements.sbDonate.addEventListener("click", () => Config.config.donateClicked = Config.config.donateClicked + 1);
|
||||||
|
|
||||||
|
if (Config.config.cleanPopup) {
|
||||||
|
PageElements.sbPopupLogo.classList.add("hidden");
|
||||||
|
PageElements.sbYourWorkBox.classList.add("hidden");
|
||||||
|
PageElements.sbFooter.classList.add("hidden");
|
||||||
|
PageElements.sponsorTimesDonateContainer.classList.add("hidden");
|
||||||
|
PageElements.mainControls.classList.add("hidden");
|
||||||
|
|
||||||
|
PageElements.videoInfo.style.marginTop = "10px";
|
||||||
|
}
|
||||||
|
|
||||||
if (Config.config.testingServer) {
|
if (Config.config.testingServer) {
|
||||||
PageElements.sbBetaServerWarning.classList.remove("hidden");
|
PageElements.sbBetaServerWarning.classList.remove("hidden");
|
||||||
PageElements.sbBetaServerWarning.addEventListener("click", function () {
|
PageElements.sbBetaServerWarning.addEventListener("click", function () {
|
||||||
@@ -281,7 +296,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
|
|
||||||
const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions"];
|
const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions"];
|
||||||
|
|
||||||
utils.asyncRequestToServer("GET", "/api/userInfo", {
|
asyncRequestToServer("GET", "/api/userInfo", {
|
||||||
publicUserID: await getHash(Config.config.userID),
|
publicUserID: await getHash(Config.config.userID),
|
||||||
values
|
values
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
@@ -687,6 +702,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
: chrome.i18n.getMessage("skipSegment");
|
: chrome.i18n.getMessage("skipSegment");
|
||||||
skipButton.addEventListener("click", () => skipSegment(actionType, UUID, skipButton));
|
skipButton.addEventListener("click", () => skipSegment(actionType, UUID, skipButton));
|
||||||
votingButtons.addEventListener("dblclick", () => skipSegment(actionType, UUID));
|
votingButtons.addEventListener("dblclick", () => skipSegment(actionType, UUID));
|
||||||
|
votingButtons.addEventListener("dblclick", () => skipSegment(actionType, UUID));
|
||||||
|
votingButtons.addEventListener("mouseenter", () => selectSegment(UUID));
|
||||||
|
|
||||||
//add thumbs up, thumbs down and uuid copy buttons to the container
|
//add thumbs up, thumbs down and uuid copy buttons to the container
|
||||||
voteButtonsContainer.appendChild(upvoteButton);
|
voteButtonsContainer.appendChild(upvoteButton);
|
||||||
@@ -718,6 +735,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
|
|
||||||
container.appendChild(votingButtons);
|
container.appendChild(votingButtons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container.addEventListener("mouseleave", () => selectSegment(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitTimes() {
|
function submitTimes() {
|
||||||
@@ -800,7 +819,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
PageElements.setUsernameStatus.style.display = "unset";
|
PageElements.setUsernameStatus.style.display = "unset";
|
||||||
PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("Loading");
|
PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("Loading");
|
||||||
|
|
||||||
utils.sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (response) {
|
sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (response) {
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
//submitted
|
//submitted
|
||||||
PageElements.submitUsername.style.display = "none";
|
PageElements.submitUsername.style.display = "none";
|
||||||
@@ -968,6 +987,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectSegment(UUID: SegmentUUID | null): void {
|
||||||
|
sendTabMessage({
|
||||||
|
message: "selectSegment",
|
||||||
|
UUID: UUID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should skipping be disabled (visuals stay)
|
* Should skipping be disabled (visuals stay)
|
||||||
*/
|
*/
|
||||||
@@ -1041,9 +1067,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
*/
|
*/
|
||||||
function getFormattedHours(minutes) {
|
function getFormattedHours(minutes) {
|
||||||
minutes = Math.round(minutes * 10) / 10;
|
minutes = Math.round(minutes * 10) / 10;
|
||||||
const days = Math.floor(minutes / 1440);
|
const years = Math.floor(minutes / 525600); // Assumes 365.0 days in a year
|
||||||
|
const days = Math.floor(minutes / 1440) % 365;
|
||||||
const hours = Math.floor(minutes / 60) % 24;
|
const hours = Math.floor(minutes / 60) % 24;
|
||||||
return (days > 0 ? days + chrome.i18n.getMessage("dayAbbreviation") + " " : "") + (hours > 0 ? hours + chrome.i18n.getMessage("hourAbbreviation") + " " : "") + (minutes % 60).toFixed(1);
|
return (years > 0 ? years + chrome.i18n.getMessage("yearAbbreviation") + " " : "") + (days > 0 ? days + chrome.i18n.getMessage("dayAbbreviation") + " " : "") + (hours > 0 ? hours + chrome.i18n.getMessage("hourAbbreviation") + " " : "") + (minutes % 60).toFixed(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function contentConfigUpdateListener(changes: StorageChangesObject) {
|
function contentConfigUpdateListener(changes: StorageChangesObject) {
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import Config from "../config";
|
|||||||
import { VoteResponse } from "../messageTypes";
|
import { VoteResponse } from "../messageTypes";
|
||||||
import { Category, SegmentUUID, SponsorTime } from "../types";
|
import { Category, SegmentUUID, SponsorTime } from "../types";
|
||||||
import { Tooltip } from "./Tooltip";
|
import { Tooltip } from "./Tooltip";
|
||||||
import { waitFor } from "@ajayyy/maze-utils";
|
import { waitFor } from "../../maze-utils/src";
|
||||||
import { getYouTubeTitleNode } from "@ajayyy/maze-utils/lib/elements";
|
import { getYouTubeTitleNode } from "../../maze-utils/src/elements";
|
||||||
|
import { addCleanupListener } from "../../maze-utils/src/cleanup";
|
||||||
|
|
||||||
const id = "categoryPill";
|
const id = "categoryPill";
|
||||||
|
|
||||||
@@ -24,6 +25,12 @@ export class CategoryPill {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.ref = React.createRef();
|
this.ref = React.createRef();
|
||||||
|
|
||||||
|
addCleanupListener(() => {
|
||||||
|
if (this.mutationObserver) {
|
||||||
|
this.mutationObserver.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async attachToPage(onMobileYouTube: boolean, onInvidious: boolean,
|
async attachToPage(onMobileYouTube: boolean, onInvidious: boolean,
|
||||||
@@ -47,7 +54,11 @@ export class CategoryPill {
|
|||||||
|
|
||||||
this.root = createRoot(this.container);
|
this.root = createRoot(this.container);
|
||||||
this.ref = React.createRef();
|
this.ref = React.createRef();
|
||||||
this.root.render(<CategoryPillComponent ref={this.ref} vote={this.vote} />);
|
this.root.render(<CategoryPillComponent
|
||||||
|
ref={this.ref}
|
||||||
|
vote={this.vote}
|
||||||
|
showTextByDefault={!this.onMobileYouTube}
|
||||||
|
showTooltipOnClick={this.onMobileYouTube} />);
|
||||||
|
|
||||||
if (this.onMobileYouTube) {
|
if (this.onMobileYouTube) {
|
||||||
if (this.mutationObserver) {
|
if (this.mutationObserver) {
|
||||||
@@ -76,6 +87,7 @@ export class CategoryPill {
|
|||||||
// Use a parent because YouTube does weird things to the top level object
|
// Use a parent because YouTube does weird things to the top level object
|
||||||
// react would have to rerender if container was the top level
|
// react would have to rerender if container was the top level
|
||||||
const parent = document.createElement("span");
|
const parent = document.createElement("span");
|
||||||
|
parent.id = "categoryPillParent";
|
||||||
parent.appendChild(this.container);
|
parent.appendChild(this.container);
|
||||||
|
|
||||||
referenceNode.prepend(parent);
|
referenceNode.prepend(parent);
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import NoticeComponent from "../components/NoticeComponent";
|
|||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
import { ButtonListener, ContentContainer } from "../types";
|
import { ContentContainer } from "../types";
|
||||||
import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent";
|
import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent";
|
||||||
|
import { ButtonListener } from "../../maze-utils/src/components/component-types";
|
||||||
|
|
||||||
export interface TextBox {
|
export interface TextBox {
|
||||||
icon: string;
|
icon: string;
|
||||||
@@ -46,6 +47,7 @@ export default class GenericNotice {
|
|||||||
const referenceNode = options.referenceNode ?? utils.findReferenceNode();
|
const referenceNode = options.referenceNode ?? utils.findReferenceNode();
|
||||||
|
|
||||||
this.noticeElement = document.createElement("div");
|
this.noticeElement = document.createElement("div");
|
||||||
|
this.noticeElement.className = "sponsorSkipNoticeContainer";
|
||||||
this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
|
this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
|
||||||
|
|
||||||
referenceNode.prepend(this.noticeElement);
|
referenceNode.prepend(this.noticeElement);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class SkipNotice {
|
|||||||
idSuffix += amountOfPreviousNotices;
|
idSuffix += amountOfPreviousNotices;
|
||||||
|
|
||||||
this.noticeElement = document.createElement("div");
|
this.noticeElement = document.createElement("div");
|
||||||
|
this.noticeElement.className = "sponsorSkipNoticeContainer";
|
||||||
this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
|
this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
|
||||||
|
|
||||||
referenceNode.prepend(this.noticeElement);
|
referenceNode.prepend(this.noticeElement);
|
||||||
|
|||||||
@@ -1,129 +1,7 @@
|
|||||||
import * as React from "react";
|
import { GenericTooltip, TooltipProps } from "../../maze-utils/src/components/Tooltip";
|
||||||
import { createRoot, Root } from 'react-dom/client';
|
|
||||||
import { ButtonListener } from "../types";
|
|
||||||
|
|
||||||
export interface TooltipProps {
|
export class Tooltip extends GenericTooltip {
|
||||||
text?: string;
|
|
||||||
link?: string;
|
|
||||||
linkOnClick?: () => void;
|
|
||||||
referenceNode: HTMLElement;
|
|
||||||
prependElement?: HTMLElement; // Element to append before
|
|
||||||
bottomOffset?: string;
|
|
||||||
leftOffset?: string;
|
|
||||||
rightOffset?: string;
|
|
||||||
timeout?: number;
|
|
||||||
opacity?: number;
|
|
||||||
displayTriangle?: boolean;
|
|
||||||
extraClass?: string;
|
|
||||||
showLogo?: boolean;
|
|
||||||
showGotIt?: boolean;
|
|
||||||
positionRealtive?: boolean;
|
|
||||||
buttons?: ButtonListener[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Tooltip {
|
|
||||||
text?: string;
|
|
||||||
container: HTMLDivElement;
|
|
||||||
|
|
||||||
timer: NodeJS.Timeout;
|
|
||||||
root: Root;
|
|
||||||
|
|
||||||
constructor(props: TooltipProps) {
|
constructor(props: TooltipProps) {
|
||||||
props.bottomOffset ??= "70px";
|
super(props, "icons/IconSponsorBlocker256px.png")
|
||||||
props.leftOffset ??= "inherit";
|
|
||||||
props.rightOffset ??= "inherit";
|
|
||||||
props.opacity ??= 0.7;
|
|
||||||
props.displayTriangle ??= true;
|
|
||||||
props.extraClass ??= "";
|
|
||||||
props.showLogo ??= true;
|
|
||||||
props.showGotIt ??= true;
|
|
||||||
props.positionRealtive ??= true;
|
|
||||||
this.text = props.text;
|
|
||||||
|
|
||||||
this.container = document.createElement('div');
|
|
||||||
this.container.id = "sponsorTooltip" + props.text;
|
|
||||||
if (props.positionRealtive) this.container.style.position = "relative";
|
|
||||||
|
|
||||||
if (props.prependElement) {
|
|
||||||
props.referenceNode.insertBefore(this.container, props.prependElement);
|
|
||||||
} else {
|
|
||||||
props.referenceNode.appendChild(this.container);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.timeout) {
|
|
||||||
this.timer = setTimeout(() => this.close(), props.timeout * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const backgroundColor = `rgba(28, 28, 28, ${props.opacity})`;
|
|
||||||
|
|
||||||
this.root = createRoot(this.container);
|
|
||||||
this.root.render(
|
|
||||||
<div style={{bottom: props.bottomOffset, left: props.leftOffset, right: props.rightOffset, backgroundColor}}
|
|
||||||
className={"sponsorBlockTooltip" + (props.displayTriangle ? " sbTriangle" : "") + ` ${props.extraClass}`}>
|
|
||||||
<div>
|
|
||||||
{props.showLogo ?
|
|
||||||
<img className="sponsorSkipLogo sponsorSkipObject"
|
|
||||||
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
|
|
||||||
</img>
|
|
||||||
: null}
|
|
||||||
{this.text ?
|
|
||||||
<span className={`sponsorSkipObject${!props.showLogo ? ` sponsorSkipObjectFirst` : ``}`}>
|
|
||||||
{this.text + (props.link ? ". " : "")}
|
|
||||||
{props.link ?
|
|
||||||
<a style={{textDecoration: "underline"}}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href={props.link}>
|
|
||||||
{chrome.i18n.getMessage("LearnMore")}
|
|
||||||
</a>
|
|
||||||
: (props.linkOnClick ?
|
|
||||||
<a style={{textDecoration: "underline", marginLeft: "5px", cursor: "pointer"}}
|
|
||||||
onClick={props.linkOnClick}>
|
|
||||||
{chrome.i18n.getMessage("LearnMore")}
|
|
||||||
</a>
|
|
||||||
: null)}
|
|
||||||
</span>
|
|
||||||
: null}
|
|
||||||
|
|
||||||
{this.getButtons(props.buttons)}
|
|
||||||
</div>
|
|
||||||
{props.showGotIt ?
|
|
||||||
<button className="sponsorSkipObject sponsorSkipNoticeButton"
|
|
||||||
style ={{float: "right" }}
|
|
||||||
onClick={() => this.close()}>
|
|
||||||
|
|
||||||
{chrome.i18n.getMessage("GotIt")}
|
|
||||||
</button>
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getButtons(buttons?: ButtonListener[]): JSX.Element[] {
|
|
||||||
if (buttons) {
|
|
||||||
const result: JSX.Element[] = [];
|
|
||||||
|
|
||||||
for (const button of buttons) {
|
|
||||||
result.push(
|
|
||||||
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
|
|
||||||
key={button.name}
|
|
||||||
onClick={(e) => button.listener(e)}>
|
|
||||||
|
|
||||||
{button.name}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): void {
|
|
||||||
this.root.unmount();
|
|
||||||
this.container.remove();
|
|
||||||
|
|
||||||
if (this.timer) clearTimeout(this.timer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
55
src/svg-icons/sb_svg.tsx
Normal file
55
src/svg-icons/sb_svg.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export interface SbIconProps {
|
||||||
|
id?: string;
|
||||||
|
fill?: string;
|
||||||
|
className?: string;
|
||||||
|
width?: string;
|
||||||
|
height?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SbSvg({
|
||||||
|
id = "",
|
||||||
|
fill = "#ff0000",
|
||||||
|
className = "",
|
||||||
|
onClick
|
||||||
|
}: SbIconProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 565.15 568"
|
||||||
|
id={id}
|
||||||
|
className={className}
|
||||||
|
onClick={() => onClick?.() } >
|
||||||
|
<g
|
||||||
|
id="Layer_2"
|
||||||
|
data-name="Layer 2">
|
||||||
|
<g
|
||||||
|
id="Layer_1-2"
|
||||||
|
data-name="Layer 1"
|
||||||
|
style={{
|
||||||
|
fill
|
||||||
|
}}>
|
||||||
|
<path
|
||||||
|
d="M282.58,568a65,65,0,0,1-34.14-9.66C95.41,463.94,2.54,300.46,0,121A64.91,64.91,0,0,1,34,62.91a522.56,522.56,0,0,1,497.16,0,64.91,64.91,0,0,1,34,58.12c-2.53,179.43-95.4,342.91-248.42,437.3A65,65,0,0,1,282.58,568Zm0-548.31A502.24,502.24,0,0,0,43.4,80.22a45.27,45.27,0,0,0-23.7,40.53c2.44,172.67,91.81,330,239.07,420.83a46.19,46.19,0,0,0,47.61,0C453.64,450.73,543,293.42,545.45,120.75a45.26,45.26,0,0,0-23.7-40.54A502.26,502.26,0,0,0,282.58,19.69Z"
|
||||||
|
id="path8"
|
||||||
|
style={{
|
||||||
|
fill
|
||||||
|
}} />
|
||||||
|
<path
|
||||||
|
style={{
|
||||||
|
fill
|
||||||
|
}}
|
||||||
|
d="M 284.70508 42.693359 A 479.9 479.9 0 0 0 54.369141 100.41992 A 22.53 22.53 0 0 0 42.669922 120.41992 C 45.069922 290.25992 135.67008 438.63977 270.83008 522.00977 A 22.48 22.48 0 0 0 294.32031 522.00977 C 429.48031 438.63977 520.08047 290.25992 522.48047 120.41992 A 22.53 22.53 0 0 0 510.7793 100.41992 A 479.9 479.9 0 0 0 284.70508 42.693359 z M 220.41016 145.74023 L 411.2793 255.93945 L 220.41016 366.14062 L 220.41016 145.74023 z "
|
||||||
|
id="path10" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<polygon style={{
|
||||||
|
fill: "#fff"
|
||||||
|
}}
|
||||||
|
points="411.28 255.94 220.41 145.74 220.41 366.14 411.28 255.94"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -99,8 +99,8 @@ export interface Registration {
|
|||||||
message: string;
|
message: string;
|
||||||
id: string;
|
id: string;
|
||||||
allFrames: boolean;
|
allFrames: boolean;
|
||||||
js: browser.extensionTypes.ExtensionFileOrCode[];
|
js: string[];
|
||||||
css: browser.extensionTypes.ExtensionFileOrCode[];
|
css: string[];
|
||||||
matches: string[];
|
matches: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +220,4 @@ export enum NoticeVisbilityMode {
|
|||||||
MiniForAll = 2,
|
MiniForAll = 2,
|
||||||
FadedForAutoSkip = 3,
|
FadedForAutoSkip = 3,
|
||||||
FadedForAll = 4
|
FadedForAll = 4
|
||||||
}
|
|
||||||
|
|
||||||
export interface ButtonListener {
|
|
||||||
name: string;
|
|
||||||
listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
|
||||||
}
|
}
|
||||||
80
src/utils.ts
80
src/utils.ts
@@ -1,12 +1,10 @@
|
|||||||
import Config, { VideoDownvotes } from "./config";
|
import Config, { VideoDownvotes } from "./config";
|
||||||
import { CategorySelection, SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType, CategorySkipOption } from "./types";
|
import { CategorySelection, SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType, CategorySkipOption } from "./types";
|
||||||
|
|
||||||
import { getHash, HashedValue } from "@ajayyy/maze-utils/lib/hash";
|
import { getHash, HashedValue } from "../maze-utils/src/hash";
|
||||||
import * as CompileConfig from "../config.json";
|
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
|
||||||
import { isFirefoxOrSafari, waitFor } from "@ajayyy/maze-utils";
|
import { findValidElementFromSelector } from "../maze-utils/src/dom";
|
||||||
import { findValidElementFromSelector } from "@ajayyy/maze-utils/lib/dom";
|
import { isSafari } from "../maze-utils/src/config";
|
||||||
import { FetchResponse, sendRequestToCustomServer } from "@ajayyy/maze-utils/lib/background-request-proxy"
|
|
||||||
import { isSafari } from "@ajayyy/maze-utils/lib/config";
|
|
||||||
|
|
||||||
export default class Utils {
|
export default class Utils {
|
||||||
|
|
||||||
@@ -47,9 +45,13 @@ export default class Utils {
|
|||||||
* @param {CallableFunction} callback
|
* @param {CallableFunction} callback
|
||||||
*/
|
*/
|
||||||
setupExtraSitePermissions(callback: (granted: boolean) => void): void {
|
setupExtraSitePermissions(callback: (granted: boolean) => void): void {
|
||||||
let permissions = ["webNavigation"];
|
const permissions = [];
|
||||||
if (!isSafari()) permissions.push("declarativeContent");
|
if (!isFirefoxOrSafari()) {
|
||||||
if (isFirefoxOrSafari() && !isSafari()) permissions = [];
|
permissions.push("declarativeContent");
|
||||||
|
}
|
||||||
|
if (!isFirefoxOrSafari() || isSafari()) {
|
||||||
|
permissions.push("webNavigation");
|
||||||
|
}
|
||||||
|
|
||||||
chrome.permissions.request({
|
chrome.permissions.request({
|
||||||
origins: this.getPermissionRegex(),
|
origins: this.getPermissionRegex(),
|
||||||
@@ -73,21 +75,12 @@ export default class Utils {
|
|||||||
* For now, it is just SB.config.invidiousInstances.
|
* For now, it is just SB.config.invidiousInstances.
|
||||||
*/
|
*/
|
||||||
setupExtraSiteContentScripts(): void {
|
setupExtraSiteContentScripts(): void {
|
||||||
const firefoxJS = [];
|
|
||||||
for (const file of this.js) {
|
|
||||||
firefoxJS.push({file});
|
|
||||||
}
|
|
||||||
const firefoxCSS = [];
|
|
||||||
for (const file of this.css) {
|
|
||||||
firefoxCSS.push({file});
|
|
||||||
}
|
|
||||||
|
|
||||||
const registration: Registration = {
|
const registration: Registration = {
|
||||||
message: "registerContentScript",
|
message: "registerContentScript",
|
||||||
id: "invidious",
|
id: "invidious",
|
||||||
allFrames: true,
|
allFrames: true,
|
||||||
js: firefoxJS,
|
js: this.js,
|
||||||
css: firefoxCSS,
|
css: this.css,
|
||||||
matches: this.getPermissionRegex()
|
matches: this.getPermissionRegex()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,50 +238,6 @@ export default class Utils {
|
|||||||
return permissionRegex;
|
return permissionRegex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a request to a custom server
|
|
||||||
*
|
|
||||||
* @param type The request type. "GET", "POST", etc.
|
|
||||||
* @param address The address to add to the SponsorBlock server address
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
asyncRequestToCustomServer(type: string, url: string, data = {}): Promise<FetchResponse> {
|
|
||||||
return sendRequestToCustomServer(type, url, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a request to the SponsorBlock server with address added as a query
|
|
||||||
*
|
|
||||||
* @param type The request type. "GET", "POST", etc.
|
|
||||||
* @param address The address to add to the SponsorBlock server address
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
async asyncRequestToServer(type: string, address: string, data = {}): Promise<FetchResponse> {
|
|
||||||
const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
|
|
||||||
|
|
||||||
return await (this.asyncRequestToCustomServer(type, serverAddress + address, data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a request to the SponsorBlock server with address added as a query
|
|
||||||
*
|
|
||||||
* @param type The request type. "GET", "POST", etc.
|
|
||||||
* @param address The address to add to the SponsorBlock server address
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
sendRequestToServer(type: string, address: string, callback?: (response: FetchResponse) => void): void {
|
|
||||||
const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
|
|
||||||
|
|
||||||
// Ask the background script to do the work
|
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
message: "sendRequest",
|
|
||||||
type,
|
|
||||||
url: serverAddress + address
|
|
||||||
}, (response) => {
|
|
||||||
callback(response);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
findReferenceNode(): HTMLElement {
|
findReferenceNode(): HTMLElement {
|
||||||
const selectors = [
|
const selectors = [
|
||||||
"#player-container-id", // Mobile YouTube
|
"#player-container-id", // Mobile YouTube
|
||||||
@@ -299,7 +248,8 @@ export default class Utils {
|
|||||||
"#main-panel.ytmusic-player-page", // YouTube music
|
"#main-panel.ytmusic-player-page", // YouTube music
|
||||||
"#player-container .video-js", // Invidious
|
"#player-container .video-js", // Invidious
|
||||||
".main-video-section > .video-container", // Cloudtube
|
".main-video-section > .video-container", // Cloudtube
|
||||||
".shaka-video-container" // Piped
|
".shaka-video-container", // Piped
|
||||||
|
"#player-container.ytk-player", // YT Kids
|
||||||
];
|
];
|
||||||
|
|
||||||
let referenceNode = findValidElementFromSelector(selectors)
|
let referenceNode = findValidElementFromSelector(selectors)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function applyLoadingAnimation(element: HTMLElement, time: number, callback?: ()
|
|||||||
|
|
||||||
function setupCustomHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): { hide: () => void; show: () => void } {
|
function setupCustomHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): { hide: () => void; show: () => void } {
|
||||||
if (enabled) element.classList.add("autoHiding");
|
if (enabled) element.classList.add("autoHiding");
|
||||||
element.classList.add("hidden");
|
element.classList.add("sbhidden");
|
||||||
element.classList.add("animationDone");
|
element.classList.add("animationDone");
|
||||||
if (!rightSlide) element.classList.add("autoHideLeft");
|
if (!rightSlide) element.classList.add("autoHideLeft");
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ function setupCustomHideAnimation(element: Element, container: Element, enabled
|
|||||||
hide: () => {
|
hide: () => {
|
||||||
mouseEntered = false;
|
mouseEntered = false;
|
||||||
if (element.classList.contains("autoHiding")) {
|
if (element.classList.contains("autoHiding")) {
|
||||||
element.classList.add("hidden");
|
element.classList.add("sbhidden");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
show: () => {
|
show: () => {
|
||||||
@@ -46,7 +46,7 @@ function setupCustomHideAnimation(element: Element, container: Element, enabled
|
|||||||
|
|
||||||
// Wait for next event loop
|
// Wait for next event loop
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (mouseEntered) element.classList.remove("hidden")
|
if (mouseEntered) element.classList.remove("sbhidden")
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -61,12 +61,12 @@ function setupAutoHideAnimation(element: Element, container: Element, enabled =
|
|||||||
|
|
||||||
function enableAutoHideAnimation(element: Element): void {
|
function enableAutoHideAnimation(element: Element): void {
|
||||||
element.classList.add("autoHiding");
|
element.classList.add("autoHiding");
|
||||||
element.classList.add("hidden");
|
element.classList.add("sbhidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableAutoHideAnimation(element: Element): void {
|
function disableAutoHideAnimation(element: Element): void {
|
||||||
element.classList.remove("autoHiding");
|
element.classList.remove("autoHiding");
|
||||||
element.classList.remove("hidden");
|
element.classList.remove("sbhidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AnimationUtils = {
|
export const AnimationUtils = {
|
||||||
|
|||||||
15
src/utils/compatibility.ts
Normal file
15
src/utils/compatibility.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Config from "../config";
|
||||||
|
|
||||||
|
export function runCompatibilityChecks() {
|
||||||
|
if (Config.config.showZoomToFillError2 && document.URL.includes("watch?v=")) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const zoomToFill = document.querySelector(".zoomtofillBtn");
|
||||||
|
|
||||||
|
if (zoomToFill) {
|
||||||
|
alert(chrome.i18n.getMessage("zoomToFillUnsupported"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.config.showZoomToFillError2 = false;
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/utils/crossExtension.ts
Normal file
44
src/utils/crossExtension.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import * as CompileConfig from "../../config.json";
|
||||||
|
|
||||||
|
import Config from "../config";
|
||||||
|
import { isSafari } from "../../maze-utils/src/config";
|
||||||
|
import { isFirefoxOrSafari } from "../../maze-utils/src";
|
||||||
|
|
||||||
|
export function isDeArrowInstalled(): Promise<boolean> {
|
||||||
|
if (Config.config.deArrowInstalled) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
} else {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const extensionIds = getExtensionIdsToImportFrom();
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
for (const id of extensionIds) {
|
||||||
|
chrome.runtime.sendMessage(id, { message: "isInstalled" }, (response) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count === extensionIds.length) {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
if (response) {
|
||||||
|
Config.config.deArrowInstalled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getExtensionIdsToImportFrom(): string[] {
|
||||||
|
if (isSafari()) {
|
||||||
|
return CompileConfig.extensionImportList.safari;
|
||||||
|
} else if (isFirefoxOrSafari()) {
|
||||||
|
return CompileConfig.extensionImportList.firefox;
|
||||||
|
} else {
|
||||||
|
return CompileConfig.extensionImportList.chromium;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ActionType, Category, SegmentUUID, SponsorSourceType, SponsorTime } from "../types";
|
import { ActionType, Category, SegmentUUID, SponsorSourceType, SponsorTime } from "../types";
|
||||||
import { shortCategoryName } from "./categoryUtils";
|
import { shortCategoryName } from "./categoryUtils";
|
||||||
import * as CompileConfig from "../../config.json";
|
import * as CompileConfig from "../../config.json";
|
||||||
import { getFormattedTime, getFormattedTimeToSeconds } from "@ajayyy/maze-utils/lib/formating";
|
import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
|
||||||
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
|
import { generateUserID } from "../../maze-utils/src/setup";
|
||||||
|
|
||||||
const inTest = typeof chrome === "undefined";
|
const inTest = typeof chrome === "undefined";
|
||||||
|
|
||||||
@@ -106,5 +106,5 @@ export function exportTimesAsHashParam(segments: SponsorTime[]): string {
|
|||||||
|
|
||||||
|
|
||||||
export function normalizeChapterName(description: string): string {
|
export function normalizeChapterName(description: string): string {
|
||||||
return description.toLowerCase().replace(/\.|:|-/g, "").replace(/\s+/g, " ");
|
return description.toLowerCase().replace(/[.:'’`‛‘"‟”-]/ug, "").replace(/\s+/g, " ");
|
||||||
}
|
}
|
||||||
@@ -4,20 +4,17 @@ function getLuminance(color: string): number {
|
|||||||
return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b));
|
return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* From https://stackoverflow.com/a/5624139 */
|
/* Converts hex color to rgb color */
|
||||||
function hexToRgb(hex: string): {r: number; g: number; b: number} {
|
const hexChars = "0123456789abcdef";
|
||||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
|
||||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
if (hex.length == 4)
|
||||||
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
|
hex = "#" + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
||||||
return r + r + g + g + b + b;
|
return /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
});
|
? {
|
||||||
|
r: hexChars.indexOf(hex[1]) * 16 + hexChars.indexOf(hex[2]),
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
g: hexChars.indexOf(hex[3]) * 16 + hexChars.indexOf(hex[4]),
|
||||||
return result ? {
|
b: hexChars.indexOf(hex[5]) * 16 + hexChars.indexOf(hex[6]),
|
||||||
r: parseInt(result[1], 16),
|
}: null;
|
||||||
g: parseInt(result[2], 16),
|
|
||||||
b: parseInt(result[3], 16)
|
|
||||||
} : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,4 +28,4 @@ function indexesOf<T>(array: T[], value: T): number[] {
|
|||||||
export const GenericUtils = {
|
export const GenericUtils = {
|
||||||
getLuminance,
|
getLuminance,
|
||||||
indexesOf
|
indexesOf
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/utils/pageCleaner.ts
Normal file
8
src/utils/pageCleaner.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export function cleanPage() {
|
||||||
|
// For live-updates
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
for (const element of document.querySelectorAll("#categoryPillParent, .playerButton, .sponsorThumbnailLabel, #submissionNoticeContainer, .sponsorSkipNoticeContainer, #sponsorBlockPopupContainer, .skipButtonControlBarContainer, #previewbar, .sponsorBlockChapterBar")) {
|
||||||
|
element.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ActionType, Category, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
import { ActionType, Category, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
||||||
import { getFormattedTimeToSeconds } from "@ajayyy/maze-utils/lib/formating";
|
import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
|
||||||
|
|
||||||
export function getControls(): HTMLElement {
|
export function getControls(): HTMLElement {
|
||||||
const controlsSelectors = [
|
const controlsSelectors = [
|
||||||
@@ -93,4 +93,8 @@ export function getExistingChapters(currentVideoID: VideoID, duration: number):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return chapters;
|
return chapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPlayingPlaylist() {
|
||||||
|
return !!document.URL.includes("&list=");
|
||||||
}
|
}
|
||||||
47
src/utils/requests.ts
Normal file
47
src/utils/requests.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Config from "../config";
|
||||||
|
import * as CompileConfig from "../../config.json";
|
||||||
|
import { FetchResponse, sendRequestToCustomServer } from "../../maze-utils/src/background-request-proxy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to a custom server
|
||||||
|
*
|
||||||
|
* @param type The request type. "GET", "POST", etc.
|
||||||
|
* @param address The address to add to the SponsorBlock server address
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
export function asyncRequestToCustomServer(type: string, url: string, data = {}): Promise<FetchResponse> {
|
||||||
|
return sendRequestToCustomServer(type, url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the SponsorBlock server with address added as a query
|
||||||
|
*
|
||||||
|
* @param type The request type. "GET", "POST", etc.
|
||||||
|
* @param address The address to add to the SponsorBlock server address
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
export async function asyncRequestToServer(type: string, address: string, data = {}): Promise<FetchResponse> {
|
||||||
|
const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
|
||||||
|
|
||||||
|
return await (asyncRequestToCustomServer(type, serverAddress + address, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the SponsorBlock server with address added as a query
|
||||||
|
*
|
||||||
|
* @param type The request type. "GET", "POST", etc.
|
||||||
|
* @param address The address to add to the SponsorBlock server address
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
export function sendRequestToServer(type: string, address: string, callback?: (response: FetchResponse) => void): void {
|
||||||
|
const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
|
||||||
|
|
||||||
|
// Ask the background script to do the work
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
message: "sendRequest",
|
||||||
|
type,
|
||||||
|
url: serverAddress + address
|
||||||
|
}, (response) => {
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { isOnInvidious, parseYouTubeVideoIDFromURL } from "@ajayyy/maze-utils/lib/video";
|
import { isOnInvidious, parseYouTubeVideoIDFromURL } from "../../maze-utils/src/video";
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
import { getVideoLabel } from "./videoLabels";
|
import { getVideoLabel } from "./videoLabels";
|
||||||
import { setThumbnailListener } from "@ajayyy/maze-utils/lib/thumbnailManagement";
|
import { setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
|
||||||
|
|
||||||
export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
|
export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
|
||||||
await Promise.all(thumbnails.map((t) => labelThumbnail(t)));
|
await Promise.all(thumbnails.map((t) => labelThumbnail(t)));
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Category, CategorySkipOption, VideoID } from "../types";
|
import { Category, CategorySkipOption, VideoID } from "../types";
|
||||||
import { getHash } from "@ajayyy/maze-utils/lib/hash";
|
import { getHash } from "../../maze-utils/src/hash";
|
||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
import { logWarn } from "./logger";
|
import { logWarn } from "./logger";
|
||||||
|
import { asyncRequestToServer } from "./requests";
|
||||||
|
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry |
|
|||||||
return cachedEntry;
|
return cachedEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await utils.asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}`);
|
const response = await asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}`);
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
// No video labels or server down
|
// No video labels or server down
|
||||||
labelCache[hashPrefix] = {
|
labelCache[hashPrefix] = {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { objectToURI } from "@ajayyy/maze-utils";
|
import { objectToURI } from "../../maze-utils/src";
|
||||||
import { getHash } from "@ajayyy/maze-utils/lib/hash";
|
import { getHash } from "../../maze-utils/src/hash";
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
import GenericNotice, { NoticeOptions } from "../render/GenericNotice";
|
import GenericNotice, { NoticeOptions } from "../render/GenericNotice";
|
||||||
import { ContentContainer } from "../types";
|
import { ContentContainer } from "../types";
|
||||||
import Utils from "../utils";
|
import { asyncRequestToServer } from "./requests";
|
||||||
const utils = new Utils();
|
|
||||||
|
|
||||||
export interface ChatConfig {
|
export interface ChatConfig {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -13,20 +12,20 @@ export interface ChatConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function openWarningDialog(contentContainer: ContentContainer): Promise<void> {
|
export async function openWarningDialog(contentContainer: ContentContainer): Promise<void> {
|
||||||
const userInfo = await utils.asyncRequestToServer("GET", "/api/userInfo", {
|
const userInfo = await asyncRequestToServer("GET", "/api/userInfo", {
|
||||||
publicUserID: await getHash(Config.config.userID),
|
publicUserID: await getHash(Config.config.userID),
|
||||||
values: ["warningReason"]
|
values: ["warningReason"]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userInfo.ok) {
|
if (userInfo.ok) {
|
||||||
const warningReason = JSON.parse(userInfo.responseText)?.warningReason;
|
const warningReason = JSON.parse(userInfo.responseText)?.warningReason;
|
||||||
const userNameData = await utils.asyncRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID);
|
const userNameData = await asyncRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID);
|
||||||
const userName = userNameData.ok ? JSON.parse(userNameData.responseText).userName : "";
|
const userName = userNameData.ok ? JSON.parse(userNameData.responseText).userName : "";
|
||||||
const publicUserID = await getHash(Config.config.userID);
|
const publicUserID = await getHash(Config.config.userID);
|
||||||
|
|
||||||
let notice: GenericNotice = null;
|
let notice: GenericNotice = null;
|
||||||
const options: NoticeOptions = {
|
const options: NoticeOptions = {
|
||||||
title: chrome.i18n.getMessage("warningTitle"),
|
title: chrome.i18n.getMessage("deArrowMessageRecieved"),
|
||||||
textBoxes: [{
|
textBoxes: [{
|
||||||
text: chrome.i18n.getMessage("warningChatInfo"),
|
text: chrome.i18n.getMessage("warningChatInfo"),
|
||||||
icon: null
|
icon: null
|
||||||
@@ -43,7 +42,7 @@ export async function openWarningDialog(contentContainer: ContentContainer): Pro
|
|||||||
{
|
{
|
||||||
name: chrome.i18n.getMessage("warningConfirmButton"),
|
name: chrome.i18n.getMessage("warningConfirmButton"),
|
||||||
listener: async () => {
|
listener: async () => {
|
||||||
const result = await utils.asyncRequestToServer("POST", "/api/warnUser", {
|
const result = await asyncRequestToServer("POST", "/api/warnUser", {
|
||||||
userID: Config.config.userID,
|
userID: Config.config.userID,
|
||||||
enabled: false
|
enabled: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,5 +16,8 @@
|
|||||||
"dom",
|
"dom",
|
||||||
"dom.iterable"
|
"dom.iterable"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -16,5 +16,8 @@
|
|||||||
"dom",
|
"dom",
|
||||||
"dom.iterable"
|
"dom.iterable"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,8 @@ module.exports = env => {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.tsx', '.js']
|
extensions: ['.ts', '.tsx', '.js'],
|
||||||
|
symlinks: false
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// Prehook to start building document script before normal build
|
// Prehook to start building document script before normal build
|
||||||
|
|||||||
Reference in New Issue
Block a user