Compare commits

...

78 Commits

Author SHA1 Message Date
Ajay
6cd697dc32 bump version 2023-08-25 16:03:33 -04:00
Ajay
9946bd1af2 Use chromep in another spot 2023-08-25 16:03:17 -04:00
Ajay
3b06d72270 Fix invidious support of Firefox 2023-08-25 16:01:11 -04:00
Ajay
4bd0556464 Remove webnavigation optional permission from firefox 2023-08-23 22:44:41 -04:00
Ajay
7e12a914d5 bump translations 2023-08-22 23:29:01 -04:00
Ajay
25eaf4fa20 bump version 2023-08-22 23:26:21 -04:00
Ajay
b3efa1f787 Add compatibility with video speed controller extension 2023-08-22 15:23:04 -04:00
Ajay
9a18e70e34 Fix rate change listener not set up properly
Fixes #1820
2023-08-22 15:22:03 -04:00
Ajay
64ece9cb73 Fix chrome api being used in tests 2023-08-14 11:49:51 -04:00
Ajay
66c974b011 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2023-08-14 09:28:58 -04:00
Ajay
d8cc93c841 Fix for Firefox not offering promise based APIs in mv2 2023-08-14 09:28:56 -04:00
Ajay Ramachandran
de22accfda Merge pull request #1828 from mchangrh/contributing-translations
add translations to contributing
2023-08-13 15:13:24 -04:00
Michael C
e5b0b60dde add translations to contributing 2023-08-13 15:05:08 -04:00
Ajay Ramachandran
32d98e6544 Add more info about testing on android 2023-08-11 22:38:38 -04:00
Ajay
3dde05eda2 Add more theme icons for browser popup 2023-08-11 21:33:38 -04:00
Ajay
6aeefaae64 Don't reregister contentscripts if not necessary 2023-08-11 12:39:48 -04:00
Ajay
93d695e6c2 Fix error sending messages to closed popups 2023-08-11 12:15:05 -04:00
Ajay
160924feee Update maze utils to improve performance on Invidious, and fix preview bar error
thanks @raphj
2023-08-11 12:08:19 -04:00
Ajay
e3f3ed20e6 Enable non persistent background page on Firefox 2023-08-11 11:39:06 -04:00
Ajay
52149f4c0f bump version 2023-08-09 18:30:21 -04:00
Ajay
cbc586f9ac Potential fix for ctrl+click having a blank html video
Other potential fix for #1820
2023-08-09 18:30:05 -04:00
Ajay
fc8e20be0d Impove incorrect video check, and add better logging to it
Potential fix for #1820
2023-08-09 18:24:21 -04:00
Ajay
368059eb0d bump version 2023-08-01 22:45:53 -04:00
Ajay
ea77375fcc Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2023-08-01 22:45:37 -04:00
Ajay
16005e417d Remove maze utils symlink 2023-08-01 22:45:01 -04:00
Ajay Ramachandran
7c5b750264 use npm ci for consistency 2023-08-01 19:57:07 -04:00
Ajay Ramachandran
cc6b65c6c4 suggest using Linux 2023-08-01 15:52:50 -04:00
Ajay Ramachandran
d2c99c2d77 Merge pull request #1815 from mchangrh/contributing-update
[docs] move building from readme, formatting tweaks
2023-08-01 15:51:39 -04:00
Michael C
84c25e3042 [docs] move building to readme, formatting tweaks
- reformatted windows build issues
- updated contributing to reflect LGPL-3.0-or-later
- replaced Invidio.us with Invidious
- replaced API docs link
- updated Invidious API link
2023-08-01 15:20:24 -04:00
Ajay
8840dba90f bump version 2023-07-29 22:39:37 -04:00
Ajay
856dded58f update translations 2023-07-29 22:39:26 -04:00
Ajay
1d1bd2a003 Only add host permission on chrome 2023-07-29 01:49:27 -04:00
Ajay
dc91ee76ca Add support for live updating in chrome 2023-07-29 01:41:57 -04:00
Ajay
90bb9a4d02 Remove log 2023-07-28 20:44:19 -04:00
Ajay
d12d847f2f Fix it sometimes looping instead of going to next video when autoskipping at the end for playlists
Fix #1804
2023-07-28 20:42:06 -04:00
Ajay
31a9de252d Fix skip loop issue
Fixes #1811
2023-07-28 20:34:09 -04:00
Ajay
299cb485c3 Fix chapter name sometimes disappearing 2023-07-28 19:37:54 -04:00
Ajay
882d462849 Add category color to skip notice 2023-07-28 18:42:27 -04:00
Ajay
fc3710b37b Clear preview bar on update 2023-07-28 18:39:51 -04:00
Ajay
3ac170ad01 Fix skip to highlight button on live update 2023-07-28 18:34:05 -04:00
Ajay
4069545603 Support live updates on firefox 2023-07-28 16:30:28 -04:00
Ajay
db9fc11f13 bump version 2023-07-19 20:31:38 -04:00
Ajay
76667f68ec Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2023-07-19 20:30:18 -04:00
Ajay
e27a287a68 Better zoom to fill compatibility check 2023-07-19 20:30:16 -04:00
Ajay Ramachandran
ce3731774d Merge pull request #1803 from ajayyy/dependabot/npm_and_yarn/word-wrap-1.2.4
Bump word-wrap from 1.2.3 to 1.2.4
2023-07-18 17:12:08 -04:00
dependabot[bot]
8cbf2bb50b Bump word-wrap from 1.2.3 to 1.2.4
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-18 20:27:55 +00:00
Ajay
6f8c44b2eb Fix category colors sometimes not working 2023-07-18 13:26:41 -04:00
Ajay
bbc5c11667 bump version 2023-07-14 21:04:50 -04:00
Ajay
d074fdec96 Make category tooltip work on mobile and make it clear after voting 2023-07-14 21:04:33 -04:00
Ajay
5d98208596 update translations 2023-07-14 20:44:02 -04:00
Ajay Ramachandran
4480ad4d84 Merge pull request #1790 from ajayyy/ci/oss_attribution
Update OSS Attribution
2023-07-14 20:41:56 -04:00
Ajay
df6c6cb30f Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2023-07-14 20:41:33 -04:00
Ajay
0f2da334e8 Update maze utils 2023-07-14 20:41:28 -04:00
github-actions[bot]
7562340ec2 Update OSS Attribution 2023-07-15 00:39:10 +00:00
Ajay Ramachandran
fee5b7be44 Merge pull request #1796 from mchangrh/youtubekids
Add support for YT Kids
2023-07-14 20:38:14 -04:00
Ajay Ramachandran
e89f75e4b6 Merge pull request #1798 from mchangrh/piped-ci
Piped CI
2023-07-14 20:37:57 -04:00
Ajay
8bcaf906fb Add warning if zoom to fill is installed 2023-07-14 20:37:08 -04:00
Ajay
cee00a87c1 Remove text from category pills on mobile and don't make them take full height of title box 2023-07-14 17:55:55 -04:00
Ajay
9b627f4e8f Use background script for hashing if not available in page
For Invidious instances on http
2023-07-11 17:28:18 -04:00
Ajay
af977840c6 Change to using hostname 2023-07-11 17:22:23 -04:00
Ajay
e8370121d5 Ignore port when checking if on invidious instance 2023-07-11 17:14:43 -04:00
Ajay
3175d7f8e9 remove port from invidious domain as it is not needed 2023-07-11 17:05:08 -04:00
Ajay
ab4035bbc5 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2023-07-11 16:51:13 -04:00
Ajay
3d88d29d93 potential fix for IP invidious instances 2023-07-11 16:51:12 -04:00
Michael C
1ac361b4df add YouTube Kids to supportedSites 2023-07-08 13:29:45 -04:00
Michael C
24c37324b8 only include src 2023-07-07 03:38:33 -04:00
Michael C
21bce341f0 support piped instances in invidious list 2023-07-07 03:14:49 -04:00
Michael C
0d9c3dc807 add youtube kids to invidious, type invidious 2023-07-06 18:25:50 -04:00
Michael C
60f9274438 add selectors for YT kids 2023-07-06 18:02:13 -04:00
Ajay Ramachandran
e23722baa0 Merge pull request #1795 from mchangrh/windows-contribute
update CONTRIBUTING for windows
2023-07-06 17:21:25 -04:00
Michael C
d25e8c7360 update CONTRIBUTING for windows 2023-07-06 16:33:03 -04:00
Ajay
87bf472ee4 Add link to yt-neuter 2023-07-03 14:34:23 -04:00
Ajay
e28e196a17 update maze utils, lots of effeciency changes 2023-07-03 02:10:42 -04:00
Ajay
0f7ed9926c Remove maze utils from npm deps 2023-06-30 03:07:57 -04:00
Ajay
75eb63632f Fix maze utils warnings 2023-06-30 03:01:09 -04:00
Ajay
e1982f265e Move maze utils to a submodule, move tooltip out 2023-06-30 02:46:27 -04:00
Ajay Ramachandran
e2a01bb744 Merge pull request #1786 from raphj/patch-1
Update the git clone with submodule command in README
2023-06-26 15:11:05 -04:00
raphj
8c8e41bc89 Update the git clone with submodule command in README 2023-06-26 09:59:00 +02:00
60 changed files with 821 additions and 671 deletions

View File

@@ -24,7 +24,10 @@
"no-self-assign": "off",
"@typescript-eslint/no-empty-interface": "off",
"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": {
"react": {

View File

@@ -11,9 +11,10 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Download instance list
- name: Download instance lists
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
run: npm ci
- name: "Run CI"
@@ -24,7 +25,7 @@ jobs:
# v4.2.3
with:
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
title: Update Invidious List
body: Automated Invidious list update

3
.gitignore vendored
View File

@@ -7,5 +7,6 @@ web-ext-artifacts
dist/
tmp/
.DS_Store
ci/data.json
ci/invidious_instances.json
ci/piped_instances.json
test-results

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "public/_locales"]
path = public/_locales
url = https://github.com/ajayyy/ExtensionTranslations
[submodule "maze-utils"]
path = maze-utils
url = https://github.com/ajayyy/maze-utils

View File

@@ -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.

View File

@@ -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.
It also supports Invidio.us.
It also supports Invidious.
**Translate:** [![Crowdin](https://badges.crowdin.net/sponsorblock/localized.svg)](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
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
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.
See [CONTRIBUTING.md](CONTRIBUTING.md)
# 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.

63
ci/generateList.ts Normal file
View 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()

View File

@@ -1,55 +1,31 @@
/*
This file is only ran by GitHub Actions in order to populate the Invidious instances list
import { InvidiousInstance, instanceMap } from "./invidiousType"
This file should not be shipped with the extension
*/
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;
}[]
import * as data from "../ci/invidious_instances.json";
// only https servers
const mapped: instanceMap = data
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.filter((i: any) => i[1]?.type === 'https')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.map((instance: any) => {
.filter((i: InvidiousInstance) => i[1]?.type === "https")
.map((instance: InvidiousInstance) => {
return {
name: instance[0],
url: instance[1].uri,
dailyRatios: instance[1].monitor.dailyRatios,
thirtyDayUptime: instance[1]?.monitor['30dRatio'].ratio,
thirtyDayUptime: instance[1]?.monitor["30dRatio"].ratio,
}
})
});
// reliability and sanity checks
const reliableCheck = mapped
.filter((instance) => {
.filter(instance => {
// 30d uptime >= 90%
const thirtyDayUptime = Number(instance.thirtyDayUptime) >= 90
const thirtyDayUptime = Number(instance.thirtyDayUptime) >= 90;
// available for at least 80/90 days
const dailyRatioCheck = instance.dailyRatios.filter(status => status.label !== "black")
return (thirtyDayUptime && dailyRatioCheck.length >= 80)
const dailyRatioCheck = instance.dailyRatios.filter(status => status.label !== "black");
return thirtyDayUptime && dailyRatioCheck.length >= 80;
})
// url includes name
.filter(instance => instance.url.includes(instance.name))
.filter(instance => instance.url.includes(instance.name));
// finally map to array
const result: string[] = reliableCheck.map(instance => instance.name).sort()
writeFile(join(__dirname, "./invidiouslist.json"), JSON.stringify(result), (err) => {
if (err) return console.log(err);
})
export function getInvidiousList(): string[] {
return reliableCheck.map(instance => instance.name).sort()
}

54
ci/invidiousType.ts Normal file
View 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;
};
}
]

View File

@@ -1 +1 @@
["inv.bp.projectsegfau.lt","inv.zzls.xyz","invidious.0011.lt","invidious.baczek.me","invidious.lunar.icu","invidious.privacydev.net","invidious.tiekoetter.com","iv.ggtyler.dev","iv.melmac.space","vid.puffyan.us","y.com.sb","yewtu.be","yt.artemislena.eu"]
["www.youtubekids.com","inv.bp.projectsegfau.lt","inv.tux.pizza","inv.zzls.xyz","invidious.0011.lt","invidious.lunar.icu","invidious.privacydev.net","invidious.tiekoetter.com","iv.ggtyler.dev","iv.melmac.space","vid.priv.au","vid.puffyan.us","yewtu.be","yt.artemislena.eu"]

92
ci/pipedCI.ts Normal file
View 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
}

View File

@@ -1,8 +1,12 @@
{
"optional_permissions": [
"declarativeContent"
"declarativeContent",
"webNavigation"
],
"background": {
"persistent": false
}
},
"permissions": [
"https://*.youtube.com/*"
]
}

View File

@@ -3,5 +3,11 @@
"gecko": {
"id": "sponsorBlocker@ajay.app"
}
}
},
"background": {
"persistent": false
},
"permissions": [
"scripting"
]
}

View File

@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "SponsorBlock",
"version": "5.4.10",
"version": "5.4.17",
"default_locale": "en",
"description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app",
@@ -84,8 +84,7 @@
"https://sponsor.ajay.app/*"
],
"optional_permissions": [
"*://*/*",
"webNavigation"
"*://*/*"
],
"browser_action": {
"default_title": "SponsorBlock",
@@ -116,6 +115,21 @@
"light": "icons/IconSponsorBlocker128px.png",
"dark": "icons/IconSponsorBlocker128px.png",
"size": 128
},
{
"light": "icons/IconSponsorBlocker256px.png",
"dark": "icons/IconSponsorBlocker256px.png",
"size": 256
},
{
"light": "icons/IconSponsorBlocker512px.png",
"dark": "icons/IconSponsorBlocker512px.png",
"size": 512
},
{
"light": "icons/IconSponsorBlocker1024px.png",
"dark": "icons/IconSponsorBlocker1024px.png",
"size": 1024
}
]
},

View File

@@ -1,5 +1,11 @@
{
"background": {
"persistent": false
}
},
"permissions": [
"scripting"
],
"optional_permissions": [
"webNavigation"
]
}

1
maze-utils Submodule

Submodule maze-utils added at 79bef97a3b

File diff suppressed because one or more lines are too long

42
package-lock.json generated
View File

@@ -27,7 +27,6 @@
],
"license": "LGPL-3.0-or-later",
"dependencies": {
"@ajayyy/maze-utils": "1.1.37",
"content-scripts-register-polyfill": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -66,32 +65,6 @@
"node": ">=16"
}
},
"node_modules/@ajayyy/maze-utils": {
"version": "1.1.37",
"resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.37.tgz",
"integrity": "sha512-EOec/tfgTDdG2RFzfGdRpyBE3ACE7sAmzfKGzLZqhNznQGcCt/gELw49dHf7NeFzU+EU5vJ2jzTDizfB96gBMg==",
"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": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@@ -13359,8 +13332,9 @@
"dev": true
},
"node_modules/word-wrap": {
"version": "1.2.3",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -13601,11 +13575,6 @@
}
},
"dependencies": {
"@ajayyy/maze-utils": {
"version": "1.1.37",
"resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.37.tgz",
"integrity": "sha512-EOec/tfgTDdG2RFzfGdRpyBE3ACE7sAmzfKGzLZqhNznQGcCt/gELw49dHf7NeFzU+EU5vJ2jzTDizfB96gBMg=="
},
"@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@@ -23380,8 +23349,9 @@
"dev": true
},
"word-wrap": {
"version": "1.2.3",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true
},
"wrap-ansi": {

View File

@@ -4,7 +4,6 @@
"description": "",
"main": "background.js",
"dependencies": {
"@ajayyy/maze-utils": "1.1.37",
"content-scripts-register-polyfill": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -56,7 +55,7 @@
"build:watch": "npm run build:watch:chrome",
"build:watch:chrome": "webpack --env browser=chrome --config webpack/webpack.dev.js --watch",
"build:watch:firefox": "webpack --env browser=firefox --config webpack/webpack.dev.js --watch",
"ci:invidious": "ts-node ci/invidiousCI.ts",
"ci:invidious": "ts-node ci/generateList.ts",
"dev": "npm run build:dev && concurrently \"npm run web-run\" \"npm run build:watch\"",
"dev:firefox": "npm run build:dev:firefox && concurrently \"npm run web-run:firefox\" \"npm run build:watch:firefox\"",
"dev:firefox-android": "npm run build:dev:firefox && concurrently \"npm run web-run:firefox-android\" \"npm run build:watch:firefox\"",

View File

@@ -198,6 +198,11 @@ div:hover > .sponsorBlockChapterBar {
float: left;
}
#categoryPill .sbPillNoText .sponsorSkipLogo {
margin-top: calc(2.6rem - 18px);
margin-bottom: calc(2.6rem - 18px);
}
@keyframes fadeIn {
from { opacity: 0; }
}
@@ -772,6 +777,14 @@ input::-webkit-inner-spin-button {
line-height: 1.5em;
}
#categoryPillParent {
height: fit-content;
margin-top: auto;
margin-bottom: auto;
position: relative;
}
.sponsorBlockCategoryPill {
border-radius: 25px;
padding-left: 8px;
@@ -854,3 +867,7 @@ input::-webkit-inner-spin-button {
.sponsorThumbnailLabel:hover span {
display: inline;
}
.sponsorblock-chapter-visible {
display: inherit !important;
}

View File

@@ -703,7 +703,7 @@ svg {
padding: 20px 0px 0px 0px !important;
}
#deArrowPromotion {
.promotion-container {
width: fit-content;
}

View File

@@ -65,7 +65,7 @@
</div>
<div id="deArrowPromotion" class="hidden">
<div id="deArrowPromotion" class="promotion-container" class="hidden">
<a class="dearrow-link"
href="https://dearrow.ajay.app"
target="_blank"
@@ -180,6 +180,20 @@
<div class="small-description">__MSG_whatShowCategoryWithoutPermission__</div>
</div>
<div id="ytNeuterPromotion" class="promotion-container">
<a class="dearrow-link"
href="https://github.com/mchangrh/yt-neuter/blob/main/docs/filters/sponsorblock.md#install"
target="_blank"
rel="noreferrer">
<span class="promotion-description">
__MSG_YtNeuterMessage__
</span>
</a>
<div class="small-description">__MSG_requiresUblock__</div>
</div>
</div>
<div id="interface" class="option-group hidden">
@@ -490,7 +504,7 @@
</label>
</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>

View File

@@ -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
4.0.2 <https://github.com/fregante/content-scripts-register-polyfill>
MIT License

View File

@@ -3,9 +3,9 @@ import * as CompileConfig from "../config.json";
import Config from "./config";
import { Registration } from "./types";
import "content-scripts-register-polyfill";
import { sendRealRequestToCustomServer, setupBackgroundRequestProxy } from "@ajayyy/maze-utils/lib/background-request-proxy";
import { setupTabUpdates } from "@ajayyy/maze-utils/lib/tab-updates";
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
import { sendRealRequestToCustomServer, setupBackgroundRequestProxy } from "../maze-utils/src/background-request-proxy";
import { setupTabUpdates } from "../maze-utils/src/tab-updates";
import { generateUserID } from "../maze-utils/src/setup";
// Make the config public for debugging purposes
@@ -13,6 +13,10 @@ window.SB = Config;
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({
registerFirefoxContentScript,
unregisterFirefoxContentScript
@@ -24,7 +28,7 @@ const popupPort: Record<string, chrome.runtime.Port> = {};
const contentScriptRegistrations = {};
// Register content script if needed
utils.wait(() => Config.config !== null).then(function() {
utils.wait(() => Config.isReady()).then(function() {
if (Config.config.supportInvidious) utils.setupExtraSiteContentScripts();
});
@@ -72,7 +76,11 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
case "infoUpdated":
case "videoChanged":
if (sender.tab) {
try {
popupPort[sender.tab.id]?.postMessage(request);
} catch (e) {
// This can happen if the popup is closed
}
}
return false;
default:
@@ -132,6 +140,11 @@ chrome.runtime.onInstalled.addListener(function () {
}
}
}, 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);
}
});
/**
@@ -140,29 +153,62 @@ chrome.runtime.onInstalled.addListener(function () {
*
* @param {JSON} options
*/
function registerFirefoxContentScript(options: Registration) {
const oldRegistration = contentScriptRegistrations[options.id];
if (oldRegistration) oldRegistration.unregister();
async function registerFirefoxContentScript(options: Registration) {
if ("scripting" in chrome && "getRegisteredContentScripts" in chrome.scripting) {
const existingRegistrations = await chromeP.scripting.getRegisteredContentScripts({
ids: [options.id]
});
chrome.contentScripts.register({
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));
}
}
/**
* Only works on Firefox.
* Firefox requires that this is handled by the background script
*
*/
function unregisterFirefoxContentScript(id: string) {
async function unregisterFirefoxContentScript(id: string) {
if ("scripting" in chrome && "getRegisteredContentScripts" in chrome.scripting) {
try {
await chromeP.scripting.unregisterContentScripts({
ids: [id]
});
} catch (e) {
// Already registered
}
} else {
if (contentScriptRegistrations[id]) {
contentScriptRegistrations[id].unregister();
delete contentScriptRegistrations[id];
}
}
}
async function submitVote(type: number, UUID: string, category: string) {
let userID = Config.config.userID;

View File

@@ -8,10 +8,12 @@ import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { VoteResponse } from "../messageTypes";
import { AnimationUtils } from "../utils/animationUtils";
import { Tooltip } from "../render/Tooltip";
import { getErrorMessage } from "@ajayyy/maze-utils/lib/formating";
import { getErrorMessage } from "../../maze-utils/src/formating";
export interface CategoryPillProps {
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>;
showTextByDefault: boolean;
showTooltipOnClick: boolean;
}
export interface CategoryPillState {
@@ -43,18 +45,23 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
return (
<span style={style}
className={"sponsorBlockCategoryPill"}
className={"sponsorBlockCategoryPill" + (!this.props.showTextByDefault ? " sbPillNoText" : "")}
aria-label={this.getTitleText()}
onClick={(e) => this.toggleOpen(e)}
onMouseEnter={() => this.openTooltip()}
onMouseLeave={() => this.closeTooltip()}>
<span className="sponsorBlockCategoryPillTitleSection">
<img className="sponsorSkipLogo sponsorSkipObject"
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
</img>
{
(this.props.showTextByDefault || this.state.open) &&
<span className="sponsorBlockCategoryPillTitle">
{chrome.i18n.getMessage("category_" + this.state.segment?.category)}
</span>
}
</span>
{this.state.open && (
@@ -81,7 +88,10 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
{/* Close Button */}
<img src={chrome.extension.getURL("icons/close.png")}
className="categoryPillClose"
onClick={() => this.setState({ show: false })}>
onClick={() => {
this.setState({ show: false });
this.closeTooltip();
}}>
</img>
</span>
);
@@ -91,6 +101,14 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
event.stopPropagation();
if (this.state.show) {
if (this.props.showTooltipOnClick) {
if (this.state.open) {
this.closeTooltip();
} else {
this.openTooltip();
}
}
this.setState({ open: !this.state.open });
}
}
@@ -108,6 +126,8 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
open: false,
show: type === 1
});
this.closeTooltip();
} else if (response.statusCode !== 403) {
alert(getErrorMessage(response.statusCode, response.responseText));
}
@@ -127,7 +147,11 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
}
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) {
this.tooltip = new Tooltip({
text: this.getTitleText(),
@@ -143,7 +167,7 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
}
private closeTooltip(): void {
this.tooltip?.close();
this.tooltip?.close?.();
this.tooltip = null;
}

View File

@@ -8,7 +8,7 @@ import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { VoteResponse } from "../messageTypes";
import { AnimationUtils } from "../utils/animationUtils";
import { Tooltip } from "../render/Tooltip";
import { getErrorMessage } from "@ajayyy/maze-utils/lib/formating";
import { getErrorMessage } from "../../maze-utils/src/formating";
export interface ChapterVoteProps {
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>;

View File

@@ -1,5 +1,6 @@
import * as React from "react";
import Config from "../config";
import SbSvg from "../svg-icons/sb_svg";
enum CountdownMode {
Timer,
@@ -28,6 +29,7 @@ export interface NoticeProps {
extraClass?: string;
hideLogo?: boolean;
hideRightInfo?: boolean;
logoFill?: string;
// Callback for when this is closed
closeListener: () => void;
@@ -122,10 +124,10 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
<td className="noticeLeftIcon">
{/* Logo */}
{!this.props.hideLogo &&
<img id={"sponsorSkipLogo" + this.idSuffix}
className="sponsorSkipLogo sponsorSkipObject"
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
</img>
<SbSvg
id={"sponsorSkipLogo" + this.idSuffix}
fill={this.props.logoFill}
className="sponsorSkipLogo sponsorSkipObject"/>
}
<span id={"sponsorSkipMessage" + this.idSuffix}

View File

@@ -12,8 +12,8 @@ import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
import PencilSvg from "../svg-icons/pencil_svg";
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
import { keybindToString } from "@ajayyy/maze-utils/lib/config";
import { generateUserID } from "../../maze-utils/src/setup";
import { keybindToString } from "../../maze-utils/src/config";
enum SkipButtonState {
Undo, // Unskip
@@ -177,7 +177,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
) : null;
return (
<NoticeComponent noticeTitle={this.state.noticeTitle}
<NoticeComponent
noticeTitle={this.state.noticeTitle}
amountOfPreviousNotices={this.amountOfPreviousNotices}
showInSecondSlot={this.showInSecondSlot}
idSuffix={this.idSuffix}
@@ -191,6 +192,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
ref={this.noticeRef}
closeListener={() => this.closeListener()}
smaller={this.state.smaller}
logoFill={Config.config.barTypes[this.segments[0].category].color}
limitWidth={true}
firstColumn={firstColumn}
bottomRow={[...this.getMessageBoxes(), ...this.getBottomRow() ]}

View File

@@ -7,7 +7,7 @@ import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
import { RectangleTooltip } from "../render/RectangleTooltip";
import SelectorComponent, { SelectorOption } from "./SelectorComponent";
import { DEFAULT_CATEGORY } from "../utils/categoryUtils";
import { getFormattedTime, getFormattedTimeToSeconds } from "@ajayyy/maze-utils/lib/formating";
import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
const utils = new Utils();

View File

@@ -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;

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import { createRoot, Root } from 'react-dom/client';
import Config from "../../config";
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 {
option: string;

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { ChangeEvent } from "react";
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 {
option: string;

View File

@@ -1,8 +1,8 @@
import * as CompileConfig from "../config.json";
import * as invidiousList from "../ci/invidiouslist.json";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, VideoID, SponsorHideType } from "./types";
import { Keybind, ProtoConfig, keybindEquals } from "@ajayyy/maze-utils/lib/config";
import { HashedValue } from "@ajayyy/maze-utils/lib/hash";
import { Keybind, ProtoConfig, keybindEquals } from "../maze-utils/src/config";
import { HashedValue } from "../maze-utils/src/hash";
export interface Permission {
canSubmit: boolean;
@@ -75,6 +75,7 @@ interface SBConfig {
allowScrollingToEdit: boolean;
deArrowInstalled: boolean;
showDeArrowPromotion: boolean;
showZoomToFillError2: boolean;
// Used to cache calculated text color info
categoryPillColors: {
@@ -147,6 +148,10 @@ class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
}
function migrateOldSyncFormats(config: SBConfig) {
if (config["showZoomToFillError"]) {
chrome.storage.sync.remove("showZoomToFillError");
}
if (!config["chapterCategoryAdded"]) {
config["chapterCategoryAdded"] = true;
@@ -311,6 +316,7 @@ const syncDefaults = {
allowScrollingToEdit: true,
deArrowInstalled: false,
showDeArrowPromotion: true,
showZoomToFillError2: true,
categoryPillColors: {},

View File

@@ -24,7 +24,7 @@ import SubmissionNotice from "./render/SubmissionNotice";
import { Message, MessageResponse, VoteResponse } from "./messageTypes";
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
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 { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils";
@@ -32,18 +32,23 @@ import { logDebug } from "./utils/logger";
import { importTimes } from "./utils/exporter";
import { ChapterVote } from "./render/ChapterVote";
import { openWarningDialog } from "./utils/warnings";
import { isFirefoxOrSafari, waitFor } from "@ajayyy/maze-utils";
import { getErrorMessage, getFormattedTime } from "@ajayyy/maze-utils/lib/formating";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube } from "@ajayyy/maze-utils/lib/video";
import { Keybind, StorageChangesObject, isSafari, keybindEquals } from "@ajayyy/maze-utils/lib/config";
import { findValidElement, waitForElement } from "@ajayyy/maze-utils/lib/dom"
import { getHash, HashedValue } from "@ajayyy/maze-utils/lib/hash";
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
import { updateAll } from "@ajayyy/maze-utils/lib/thumbnailManagement";
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube } from "../maze-utils/src/video";
import { Keybind, StorageChangesObject, isSafari, keybindEquals } from "../maze-utils/src/config";
import { findValidElement, waitForElement } from "../maze-utils/src/dom"
import { getHash, HashedValue } from "../maze-utils/src/hash";
import { generateUserID } from "../maze-utils/src/setup";
import { updateAll } from "../maze-utils/src/thumbnailManagement";
import { setupThumbnailListener } from "./utils/thumbnails";
import * as documentScript from "../dist/js/document.js";
import { Tooltip } from "./render/Tooltip";
import { isDeArrowInstalled } from "./utils/crossExtension";
import { runCompatibilityChecks } from "./utils/compatibility";
import { cleanPage } from "./utils/pageCleaner";
import { addCleanupListener } from "../maze-utils/src/cleanup";
cleanPage();
const utils = new Utils();
@@ -87,7 +92,9 @@ utils.wait(() => Config.isReady(), 5000, 10).then(() => {
Config.config.showDeArrowPromotion = false;
}
}
}, 5000)
}, 5000);
runCompatibilityChecks();
});
const skipBuffer = 0.003;
@@ -470,6 +477,8 @@ function videoIDChange(): void {
}
function handleMobileControlsMutations(): void {
if (!chrome.runtime?.id) return;
updateVisibilityOfPlayerControlsButton();
skipButtonControlBar?.updateMobileControls();
@@ -521,7 +530,7 @@ function createPreviewBar(): void {
selector: ".vjs-progress-holder",
isVisibleCheck: false
}, {
// For Youtube Music
// For Youtube Music and YTKids
// there are two sliders, one for volume and one for progress - both called #progressContainer
selector: "#progress-bar>#sliderContainer>div>#sliderBar>#progressContainer",
}, {
@@ -700,7 +709,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
// Don't pretend to be earlier than we are, could result in loops
if (forcedSkipTime !== null && forceVideoTime > forcedSkipTime) {
forcedSkipTime = null;
forcedSkipTime = forceVideoTime;
}
startSponsorSchedule(forcedIncludeIntersectingSegments, forcedSkipTime, forcedIncludeNonIntersectingSegments);
@@ -794,11 +803,12 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
const currentVideoID = getYouTubeVideoID();
const recordedVideoID = videoID || getVideoID();
if (currentVideoID !== recordedVideoID || (sponsorTime
&& (!sponsorTimes || !sponsorTimes?.some((time) => time.segment === sponsorTime.segment))
&& !sponsorTimesSubmitting.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[0] === sponsorTime.segment[0] && time.segment[1] === sponsorTime.segment[1]))) {
// 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] VideoID recorded: " + recordedVideoID + ". Actual VideoID: " + currentVideoID);
console.error("[SponsorBlock] SponsorTime", sponsorTime, "sponsorTimes", sponsorTimes, "sponsorTimesSubmitting", sponsorTimesSubmitting);
// Video ID change occured
checkVideoIDChange();
@@ -809,18 +819,38 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
}
}
let playbackRateCheckInterval: NodeJS.Timeout | null = null;
let lastPlaybackSpeed = 1;
let setupVideoListenersFirstTime = true;
function setupVideoListeners() {
//wait until it is loaded
getVideo().addEventListener('loadstart', videoOnReadyListener)
getVideo().addEventListener('durationchange', durationChangeListener);
if (setupVideoListenersFirstTime) {
addCleanupListener(() => {
getVideo().removeEventListener('loadstart', videoOnReadyListener);
getVideo().removeEventListener('durationchange', durationChangeListener);
});
}
if (!Config.config.disableSkipping) {
switchingVideos = false;
let startedWaiting = false;
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
// This check makes sure that changing the video resolution doesn't cause the extension to think it
// gone back to the begining
@@ -850,9 +880,10 @@ function setupVideoListeners() {
startSponsorSchedule();
}
};
getVideo().addEventListener('play', playListener);
});
getVideo().addEventListener('playing', () => {
const playingListener = () => {
updateVirtualTime();
lastPausedAtZero = false;
@@ -878,8 +909,31 @@ function setupVideoListeners() {
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;
if (!getVideo().paused){
@@ -903,47 +957,56 @@ function setupVideoListeners() {
lastPausedAtZero = true;
}
}
});
getVideo().addEventListener('ratechange', () => {
updateVirtualTime();
clearWaitingTime();
};
getVideo().addEventListener('seeking', seekingListener);
startSponsorSchedule();
});
// Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740)
getVideo().addEventListener('videoSpeed_ratechange', () => {
updateVirtualTime();
clearWaitingTime();
startSponsorSchedule();
});
const stoppedPlayback = () => {
// Reset lastCheckVideoTime
lastCheckVideoTime = -1;
lastCheckTime = 0;
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
lastKnownVideoTime.videoTime = null;
lastKnownVideoTime.preciseTime = null;
updateWaitingTime();
cancelSponsorSchedule();
};
getVideo().addEventListener('pause', () => {
const pauseListener = () => {
lastKnownVideoTime.fromPause = true;
stoppedPlayback();
});
getVideo().addEventListener('waiting', () => {
};
getVideo().addEventListener('pause', pauseListener);
const waitingListener = () => {
logDebug("[SB] Not skipping due to buffering");
startedWaiting = true;
stoppedPlayback();
});
};
getVideo().addEventListener('waiting', waitingListener);
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() {
if (currentVirtualTimeInterval) clearInterval(currentVirtualTimeInterval);
@@ -1353,6 +1416,7 @@ async function channelIDChange(channelIDInfo: ChannelIDInfo) {
}
function videoElementChange(newVideo: boolean): void {
waitFor(() => Config.isReady()).then(() => {
if (newVideo) {
setupVideoListeners();
setupSkipButtonControlBar();
@@ -1365,6 +1429,7 @@ function videoElementChange(newVideo: boolean): void {
setTimeout(checkPreviewbarState, 100);
setTimeout(checkPreviewbarState, 1000);
setTimeout(checkPreviewbarState, 5000);
})
}
function checkPreviewbarState(): void {
@@ -1584,8 +1649,10 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
// 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) {
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
// Sometimes playlists loop too #1804
v.currentTime = v.duration - 0.001;
} else {
if (inMuteSegment(skipTime[1], true)) {
@@ -1616,9 +1683,10 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
beep.play();
beep.addEventListener("ended", () => {
navigator.mediaSession.metadata = null;
setTimeout(() =>
navigator.mediaSession.metadata = oldMetadata
);
setTimeout(() => {
navigator.mediaSession.metadata = oldMetadata;
beep.remove();
});
})
}
@@ -2328,11 +2396,21 @@ function previousChapter(): void {
function addHotkeyListener(): void {
document.addEventListener("keydown", hotkeyListener);
document.addEventListener("DOMContentLoaded", () => {
const onLoad = () => {
// Allow us to stop propagation to YouTube by being deeper
document.removeEventListener("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 {
@@ -2388,8 +2466,8 @@ function hotkeyListener(e: KeyboardEvent): void {
* Adds the CSS to the page if needed. Required on optional sites with Chrome.
*/
function addCSS() {
if (!isFirefoxOrSafari() && Config.config.invidiousInstances.includes(new URL(document.URL).host)) {
window.addEventListener("DOMContentLoaded", () => {
if (!isFirefoxOrSafari() && Config.config.invidiousInstances.includes(new URL(document.URL).hostname)) {
const onLoad = () => {
const head = document.getElementsByTagName("head")[0];
for (const file of utils.css) {
@@ -2401,7 +2479,13 @@ function addCSS() {
head.appendChild(fileref);
}
});
};
if (document.readyState === "complete") {
onLoad();
} else {
document.addEventListener("DOMContentLoaded", onLoad);
}
}
}
@@ -2486,7 +2570,9 @@ function setCategoryColorCSSVariables() {
if (!styleContainer) {
styleContainer = document.createElement("style");
styleContainer.id = "sbCategoryColorStyle";
document.head.appendChild(styleContainer)
const head = (document.head || document.documentElement);
head.appendChild(styleContainer)
}
let css = ":root {"

View File

@@ -1,3 +1,3 @@
import { init } from "@ajayyy/maze-utils/lib/injected/document";
import { init } from "../maze-utils/src/injected/document";
init();

View File

@@ -1,10 +1,14 @@
import { localizeHtmlPage } from "@ajayyy/maze-utils/lib/setup";
import { localizeHtmlPage } from "../maze-utils/src/setup";
import Config from "./config";
import { showDonationLink } from "./utils/configUtils";
import { waitFor } from "@ajayyy/maze-utils";
import { waitFor } from "../maze-utils/src";
window.addEventListener('DOMContentLoaded', init);
if (document.readyState === "complete") {
init();
} else {
document.addEventListener("DOMContentLoaded", init);
}
async function init() {
localizeHtmlPage();

View File

@@ -11,8 +11,9 @@ import { ActionType, Category, SegmentContainer, SponsorHideType, SponsorSourceT
import { partition } from "../utils/arrayUtils";
import { DEFAULT_CATEGORY, shortCategoryName } from "../utils/categoryUtils";
import { normalizeChapterName } from "../utils/exporter";
import { getFormattedTimeToSeconds } from "@ajayyy/maze-utils/lib/formating";
import { findValidElement } from "@ajayyy/maze-utils/lib/dom";
import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import { findValidElement } from "../../maze-utils/src/dom";
import { addCleanupListener } from "../../maze-utils/src/cleanup";
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
const MIN_CHAPTER_SIZE = 0.003;
@@ -97,7 +98,8 @@ class PreviewBar {
this.chapterTooltip = document.createElement("div");
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;
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
@@ -200,6 +202,10 @@ class PreviewBar {
childList: true,
subtree: true,
});
addCleanupListener(() => {
observer.disconnect();
});
}
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
@@ -625,6 +631,11 @@ class PreviewBar {
childListObserver.observe(this.originalChapterBar, {
childList: true
});
addCleanupListener(() => {
attributeObserver.disconnect();
childListObserver.disconnect();
});
}
private updateChapterAllMutation(originalChapterBar: HTMLElement, progressBar: HTMLElement, firstUpdate = false): void {
@@ -773,9 +784,12 @@ class PreviewBar {
if (!Config.config.showSegmentNameInChapterBar
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) {
const chaptersContainer = this.getChaptersContainer();
const chapterButton = this.getChapterButton(chaptersContainer);
if (chapterButton && chapterButton.classList.contains("ytp-chapter-container-disabled")) {
chaptersContainer.style.display = "none";
if (chaptersContainer) {
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
chapterTitle.style.removeProperty("display");
chaptersContainer.classList.remove("sponsorblock-chapter-visible");
}
return [];
@@ -801,7 +815,7 @@ class PreviewBar {
if (chaptersContainer) {
if (segments.length > 0) {
chaptersContainer.style.removeProperty("display");
chaptersContainer.classList.add("sponsorblock-chapter-visible");
const chosenSegment = segments.sort((a, b) => {
if (a.actionType === ActionType.Chapter && b.actionType !== ActionType.Chapter) {
@@ -818,11 +832,11 @@ class PreviewBar {
chapterButton.disabled = false;
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");
chapterTitle.appendChild(elem);
chapterTitle.parentElement.insertBefore(elem, chapterTitle);
elem.classList.add("sponsorChapterText");
return elem;
})()) as HTMLDivElement;
@@ -853,11 +867,10 @@ class PreviewBar {
} else {
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
if (chapterTitle.innerText === "") {
chaptersContainer.style.display = "none";
} else {
chaptersContainer.style.removeProperty("display");
}
chapterTitle.style.removeProperty("display");
chaptersContainer.classList.remove("sponsorblock-chapter-visible");
this.chapterVote.setVisibility(false);
}
}

View File

@@ -2,7 +2,7 @@ import Config from "../config";
import { SponsorTime } from "../types";
import { getSkippingText } from "../utils/categoryUtils";
import { AnimationUtils } from "../utils/animationUtils";
import { keybindToString } from "@ajayyy/maze-utils/lib/config";
import { keybindToString } from "../../maze-utils/src/config";
export interface SkipButtonControlBarProps {
skip: (segment: SponsorTime) => void;

View File

@@ -13,10 +13,10 @@ import CategoryChooser from "./render/CategoryChooser";
import UnsubmittedVideos from "./render/UnsubmittedVideos";
import KeybindComponent from "./components/options/KeybindComponent";
import { showDonationLink } from "./utils/configUtils";
import { localizeHtmlPage } from "@ajayyy/maze-utils/lib/setup";
import { StorageChangesObject } from "@ajayyy/maze-utils/lib/config";
import { getHash } from "@ajayyy/maze-utils/lib/hash";
import { isFirefoxOrSafari } from "@ajayyy/maze-utils";
import { localizeHtmlPage } from "../maze-utils/src/setup";
import { StorageChangesObject } from "../maze-utils/src/config";
import { getHash } from "../maze-utils/src/hash";
import { isFirefoxOrSafari } from "../maze-utils/src";
import { isDeArrowInstalled } from "./utils/crossExtension";
const utils = new Utils();
let embed = false;
@@ -24,7 +24,11 @@ let embed = false;
const categoryChoosers: CategoryChooser[] = [];
const unsubmittedVideos: UnsubmittedVideos[] = [];
window.addEventListener('DOMContentLoaded', init);
if (document.readyState === "complete") {
init();
} else {
document.addEventListener("DOMContentLoaded", init);
}
async function init() {
localizeHtmlPage();
@@ -445,7 +449,12 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
let instanceList = Config.config[option];
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;

View File

@@ -1,13 +1,17 @@
import Config from "./config";
import Utils from "./utils";
import { localizeHtmlPage } from "@ajayyy/maze-utils/lib/setup";
import { localizeHtmlPage } from "../maze-utils/src/setup";
const utils = new Utils();
// This is needed, if Config is not imported before Utils, things break.
// Probably due to cyclic dependencies
Config.config;
window.addEventListener('DOMContentLoaded', init);
if (document.readyState === "complete") {
init();
} else {
document.addEventListener("DOMContentLoaded", init);
}
async function init() {
localizeHtmlPage();

View File

@@ -21,12 +21,12 @@ import {
import { showDonationLink } from "./utils/configUtils";
import { AnimationUtils } from "./utils/animationUtils";
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 GenericNotice from "./render/GenericNotice";
import { getErrorMessage, getFormattedTime } from "@ajayyy/maze-utils/lib/formating";
import { StorageChangesObject } from "@ajayyy/maze-utils/lib/config";
import { getHash } from "@ajayyy/maze-utils/lib/hash";
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
import { StorageChangesObject } from "../maze-utils/src/config";
import { getHash } from "../maze-utils/src/hash";
const utils = new Utils();

View File

@@ -5,8 +5,9 @@ import Config from "../config";
import { VoteResponse } from "../messageTypes";
import { Category, SegmentUUID, SponsorTime } from "../types";
import { Tooltip } from "./Tooltip";
import { waitFor } from "@ajayyy/maze-utils";
import { getYouTubeTitleNode } from "@ajayyy/maze-utils/lib/elements";
import { waitFor } from "../../maze-utils/src";
import { getYouTubeTitleNode } from "../../maze-utils/src/elements";
import { addCleanupListener } from "../../maze-utils/src/cleanup";
const id = "categoryPill";
@@ -24,6 +25,12 @@ export class CategoryPill {
constructor() {
this.ref = React.createRef();
addCleanupListener(() => {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
}
});
}
async attachToPage(onMobileYouTube: boolean, onInvidious: boolean,
@@ -47,7 +54,11 @@ export class CategoryPill {
this.root = createRoot(this.container);
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.mutationObserver) {
@@ -76,6 +87,7 @@ export class CategoryPill {
// Use a parent because YouTube does weird things to the top level object
// react would have to rerender if container was the top level
const parent = document.createElement("span");
parent.id = "categoryPillParent";
parent.appendChild(this.container);
referenceNode.prepend(parent);

View File

@@ -5,8 +5,9 @@ import NoticeComponent from "../components/NoticeComponent";
import Utils from "../utils";
const utils = new Utils();
import { ButtonListener, ContentContainer } from "../types";
import { ContentContainer } from "../types";
import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent";
import { ButtonListener } from "../../maze-utils/src/components/component-types";
export interface TextBox {
icon: string;
@@ -46,6 +47,7 @@ export default class GenericNotice {
const referenceNode = options.referenceNode ?? utils.findReferenceNode();
this.noticeElement = document.createElement("div");
this.noticeElement.className = "sponsorSkipNoticeContainer";
this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
referenceNode.prepend(this.noticeElement);

View File

@@ -38,6 +38,7 @@ class SkipNotice {
idSuffix += amountOfPreviousNotices;
this.noticeElement = document.createElement("div");
this.noticeElement.className = "sponsorSkipNoticeContainer";
this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
referenceNode.prepend(this.noticeElement);

View File

@@ -1,154 +1,7 @@
import * as React from "react";
import { createRoot, Root } from 'react-dom/client';
import { ButtonListener } from "../types";
import { isFirefoxOrSafari } from "@ajayyy/maze-utils";
import { isSafari } from "@ajayyy/maze-utils/lib/config";
export interface TooltipProps {
text?: string;
link?: string;
linkOnClick?: () => void;
referenceNode: HTMLElement;
prependElement?: HTMLElement; // Element to append before
bottomOffset?: string;
topOffset?: string;
leftOffset?: string;
rightOffset?: string;
timeout?: number;
opacity?: number;
displayTriangle?: boolean;
extraClass?: string;
showLogo?: boolean;
showGotIt?: boolean;
center?: boolean;
positionRealtive?: boolean;
containerAbsolute?: boolean;
buttons?: ButtonListener[];
}
export class Tooltip {
text?: string;
container: HTMLDivElement;
timer: NodeJS.Timeout;
root: Root;
import { GenericTooltip, TooltipProps } from "../../maze-utils/src/components/Tooltip";
export class Tooltip extends GenericTooltip {
constructor(props: TooltipProps) {
props.bottomOffset ??= "70px";
props.topOffset ??= "inherit";
props.leftOffset ??= "inherit";
props.rightOffset ??= "inherit";
props.opacity ??= 0.7;
props.displayTriangle ??= true;
props.extraClass ??= "";
props.showLogo ??= true;
props.showGotIt ??= true;
props.positionRealtive ??= true;
props.containerAbsolute ??= false;
props.center ??= false;
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.containerAbsolute) this.container.style.position = "absolute";
if (props.center) {
if (isFirefoxOrSafari() && !isSafari()) {
this.container.style.width = "-moz-available";
} else {
this.container.style.width = "-webkit-fill-available";
}
}
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,
top: props.topOffset,
left: props.leftOffset,
right: props.rightOffset,
backgroundColor,
margin: props.center ? "auto" : null
}}
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);
super(props, "icons/IconSponsorBlocker256px.png")
}
}

55
src/svg-icons/sb_svg.tsx Normal file
View 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>
);
}

View File

@@ -99,8 +99,8 @@ export interface Registration {
message: string;
id: string;
allFrames: boolean;
js: browser.extensionTypes.ExtensionFileOrCode[];
css: browser.extensionTypes.ExtensionFileOrCode[];
js: string[];
css: string[];
matches: string[];
}
@@ -221,8 +221,3 @@ export enum NoticeVisbilityMode {
FadedForAutoSkip = 3,
FadedForAll = 4
}
export interface ButtonListener {
name: string;
listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}

View File

@@ -1,12 +1,12 @@
import Config, { VideoDownvotes } from "./config";
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 "@ajayyy/maze-utils";
import { findValidElementFromSelector } from "@ajayyy/maze-utils/lib/dom";
import { FetchResponse, sendRequestToCustomServer } from "@ajayyy/maze-utils/lib/background-request-proxy"
import { isSafari } from "@ajayyy/maze-utils/lib/config";
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { findValidElementFromSelector } from "../maze-utils/src/dom";
import { FetchResponse, sendRequestToCustomServer } from "../maze-utils/src/background-request-proxy"
import { isSafari } from "../maze-utils/src/config";
export default class Utils {
@@ -47,9 +47,13 @@ export default class Utils {
* @param {CallableFunction} callback
*/
setupExtraSitePermissions(callback: (granted: boolean) => void): void {
let permissions = ["webNavigation"];
if (!isSafari()) permissions.push("declarativeContent");
if (isFirefoxOrSafari() && !isSafari()) permissions = [];
const permissions = [];
if (!isFirefoxOrSafari()) {
permissions.push("declarativeContent");
}
if (!isFirefoxOrSafari() || isSafari()) {
permissions.push("webNavigation");
}
chrome.permissions.request({
origins: this.getPermissionRegex(),
@@ -73,21 +77,12 @@ export default class Utils {
* For now, it is just SB.config.invidiousInstances.
*/
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 = {
message: "registerContentScript",
id: "invidious",
allFrames: true,
js: firefoxJS,
css: firefoxCSS,
js: this.js,
css: this.css,
matches: this.getPermissionRegex()
};
@@ -299,7 +294,8 @@ export default class Utils {
"#main-panel.ytmusic-player-page", // YouTube music
"#player-container .video-js", // Invidious
".main-video-section > .video-container", // Cloudtube
".shaka-video-container" // Piped
".shaka-video-container", // Piped
"#player-container.ytk-player", // YT Kids
];
let referenceNode = findValidElementFromSelector(selectors)

View 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);
}
}

View File

@@ -1,8 +1,8 @@
import * as CompileConfig from "../../config.json";
import Config from "../config";
import { isSafari } from "@ajayyy/maze-utils/lib/config";
import { isFirefoxOrSafari } from "@ajayyy/maze-utils";
import { isSafari } from "../../maze-utils/src/config";
import { isFirefoxOrSafari } from "../../maze-utils/src";
export function isDeArrowInstalled(): Promise<boolean> {
if (Config.config.deArrowInstalled) {

View File

@@ -1,8 +1,8 @@
import { ActionType, Category, SegmentUUID, SponsorSourceType, SponsorTime } from "../types";
import { shortCategoryName } from "./categoryUtils";
import * as CompileConfig from "../../config.json";
import { getFormattedTime, getFormattedTimeToSeconds } from "@ajayyy/maze-utils/lib/formating";
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import { generateUserID } from "../../maze-utils/src/setup";
const inTest = typeof chrome === "undefined";

8
src/utils/pageCleaner.ts Normal file
View 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();
}
}
}

View File

@@ -1,5 +1,5 @@
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 {
const controlsSelectors = [
@@ -94,3 +94,7 @@ export function getExistingChapters(currentVideoID: VideoID, duration: number):
return chapters;
}
export function isPlayingPlaylist() {
return !!document.URL.includes("&list=");
}

View File

@@ -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 { 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> {
await Promise.all(thumbnails.map((t) => labelThumbnail(t)));

View File

@@ -1,5 +1,5 @@
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 { logWarn } from "./logger";

View File

@@ -1,5 +1,5 @@
import { objectToURI } from "@ajayyy/maze-utils";
import { getHash } from "@ajayyy/maze-utils/lib/hash";
import { objectToURI } from "../../maze-utils/src";
import { getHash } from "../../maze-utils/src/hash";
import Config from "../config";
import GenericNotice, { NoticeOptions } from "../render/GenericNotice";
import { ContentContainer } from "../types";

View File

@@ -16,5 +16,8 @@
"dom",
"dom.iterable"
]
}
},
"include": [
"src/**/*"
]
}

View File

@@ -16,5 +16,8 @@
"dom",
"dom.iterable"
]
}
},
"include": [
"src/**/*"
]
}

View File

@@ -123,7 +123,8 @@ module.exports = env => {
]
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
extensions: ['.ts', '.tsx', '.js'],
symlinks: false
},
plugins: [
// Prehook to start building document script before normal build