mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-10 13:37:04 +03:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
045607b146 | ||
|
|
be90af7440 | ||
|
|
0a7497be27 | ||
|
|
fcf096c66d | ||
|
|
12e8c7aad8 | ||
|
|
2a2896786c | ||
|
|
20d538b6e9 | ||
|
|
36a5bc105b | ||
|
|
e6c39c099c | ||
|
|
d74ece2785 | ||
|
|
e7eeee1b9d | ||
|
|
c0a579e84b | ||
|
|
e0a602eaf4 | ||
|
|
536ac01476 | ||
|
|
18a0cf2953 | ||
|
|
7ae29f967b | ||
|
|
2509cf9919 | ||
|
|
e8c65fb21b | ||
|
|
5d155f6c74 | ||
|
|
dca6780979 | ||
|
|
65b7d6589a | ||
|
|
dbf80b4929 | ||
|
|
e181c64775 | ||
|
|
ac9b2d12fe | ||
|
|
3738b180dd | ||
|
|
3ec1e85712 | ||
|
|
3c2bf20321 | ||
|
|
2a5b76ddfb |
@@ -7,8 +7,7 @@ This file should not be shipped with the extension
|
||||
/*
|
||||
Criteria for inclusion:
|
||||
Invidious
|
||||
- 30d uptime >= 90%
|
||||
- available for at least 80/90 days
|
||||
- uptime >= 80%
|
||||
- must have been up for at least 90 days
|
||||
- HTTPS only
|
||||
- url includes name (this is to avoid redirects)
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
import { InvidiousInstance, instanceMap } from "./invidiousType"
|
||||
import { InvidiousInstance, monitor } from "./invidiousType"
|
||||
|
||||
import * as data from "../ci/invidious_instances.json";
|
||||
|
||||
// only https servers
|
||||
const mapped: instanceMap = data
|
||||
.filter((i: InvidiousInstance) => i[1]?.type === "https")
|
||||
.map((instance: InvidiousInstance) => {
|
||||
const mapped = (data as InvidiousInstance[])
|
||||
.filter((i) =>
|
||||
i[1]?.type === "https"
|
||||
&& i[1]?.monitor?.enabled
|
||||
)
|
||||
.map((instance) => {
|
||||
const monitor = instance[1].monitor as monitor;
|
||||
return {
|
||||
name: instance[0],
|
||||
url: instance[1].uri,
|
||||
dailyRatios: instance[1].monitor.dailyRatios,
|
||||
thirtyDayUptime: instance[1]?.monitor["30dRatio"].ratio,
|
||||
uptime: monitor.uptime || 0,
|
||||
down: monitor.down ?? false,
|
||||
created_at: monitor.created_at,
|
||||
}
|
||||
});
|
||||
|
||||
// reliability and sanity checks
|
||||
const reliableCheck = mapped
|
||||
.filter(instance => {
|
||||
// 30d uptime >= 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 uptime = instance.uptime > 80 && !instance.down;
|
||||
const nameIncluded = instance.url.includes(instance.name);
|
||||
const ninetyDays = 90 * 24 * 60 * 60 * 1000;
|
||||
const ninetyDaysAgo = new Date(Date.now() - ninetyDays);
|
||||
const createdAt = new Date(instance.created_at).getTime() < ninetyDaysAgo.getTime();
|
||||
return uptime && nameIncluded && createdAt;
|
||||
})
|
||||
// url includes name
|
||||
.filter(instance => instance.url.includes(instance.name));
|
||||
|
||||
export function getInvidiousList(): string[] {
|
||||
return reliableCheck.map(instance => instance.name).sort()
|
||||
}
|
||||
export const getInvidiousList = (): string[] =>
|
||||
reliableCheck.map(instance => instance.name).sort()
|
||||
@@ -1,54 +1,72 @@
|
||||
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;
|
||||
stats: null | ivStats;
|
||||
cors: null | boolean;
|
||||
api: null | boolean;
|
||||
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;
|
||||
};
|
||||
monitor: null | monitor;
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
export type monitor = {
|
||||
token: string;
|
||||
url: string;
|
||||
alias: string;
|
||||
last_status: number;
|
||||
uptime: number;
|
||||
down: boolean;
|
||||
down_since: null | string;
|
||||
up_since: null | string;
|
||||
error: null | string;
|
||||
period: number;
|
||||
apdex_t: number;
|
||||
string_match: string;
|
||||
enabled: boolean;
|
||||
published: boolean;
|
||||
disabled_locations: string[];
|
||||
recipients: string[];
|
||||
last_check_at: string;
|
||||
next_check_at: string;
|
||||
created_at: string;
|
||||
mute_until: null | string;
|
||||
favicon_url: string;
|
||||
custom_headers: Record<string, string>;
|
||||
http_verb: string;
|
||||
http_body: string;
|
||||
ssl: {
|
||||
tested_at: string;
|
||||
expires_at: string;
|
||||
valid: boolean;
|
||||
error: null | string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ivStats = {
|
||||
version: string;
|
||||
software: {
|
||||
name: "invidious" | string;
|
||||
version: string;
|
||||
branch: "master" | string;
|
||||
};
|
||||
openRegistrations: boolean;
|
||||
usage: {
|
||||
users: {
|
||||
total: number;
|
||||
activeHalfyear: number;
|
||||
activeMonth: number;
|
||||
};
|
||||
};
|
||||
metadata: {
|
||||
updatedAt: number;
|
||||
lastChannelRefreshedAt: number;
|
||||
};
|
||||
playback: {
|
||||
totalRequests: number;
|
||||
successfulRequests: number;
|
||||
ratio: number;
|
||||
};
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
["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"]
|
||||
["www.youtubekids.com","inv.nadeko.net","inv.tux.pizza","invidious.adminforge.de","invidious.jing.rocks","invidious.nerdvpn.de","invidious.perennialte.ch","invidious.privacyredirect.com","invidious.reallyaweso.me","invidious.yourdevice.ch","iv.ggtyler.dev","iv.nboeck.de","yewtu.be"]
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_fullName__",
|
||||
"short_name": "SponsorBlock",
|
||||
"version": "5.7",
|
||||
"version": "5.8",
|
||||
"default_locale": "en",
|
||||
"description": "__MSG_Description__",
|
||||
"homepage_url": "https://sponsor.ajay.app",
|
||||
|
||||
Submodule maze-utils updated: 3c7787897e...e9523b0c5d
3150
package-lock.json
generated
3150
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -38,14 +38,14 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "4.9",
|
||||
"web-ext": "^7.10.0",
|
||||
"webpack": "^5.75.0",
|
||||
"web-ext": "^8.2.0",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"web-run": "npm run web-run:chrome",
|
||||
"web-sign": "web-ext sign -s dist",
|
||||
"web-sign": "web-ext sign --channel unlisted -s dist",
|
||||
"web-run:firefox": "cd dist && web-ext run --start-url https://addons.mozilla.org/firefox/addon/ublock-origin/",
|
||||
"web-run:firefox-android": "cd dist && web-ext run -t firefox-android --firefox-apk org.mozilla.fenix",
|
||||
"web-run:chrome": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm -t chromium",
|
||||
|
||||
Submodule public/_locales updated: 7cedce2158...3c5a80ca9c
@@ -196,6 +196,8 @@
|
||||
<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://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">__MSG_Donate__</a>
|
||||
<br />
|
||||
<a id="debugLogs">__MSG_copyDebugLogs__</a>
|
||||
</footer>
|
||||
|
||||
<button id="showNoticeAgain" style="display: none">__MSG_showNotice__</button>
|
||||
|
||||
@@ -477,4 +477,26 @@ const localDefaults = {
|
||||
};
|
||||
|
||||
const Config = new ConfigClass(syncDefaults, localDefaults, migrateOldSyncFormats);
|
||||
export default Config;
|
||||
export default Config;
|
||||
|
||||
export function generateDebugDetails(): string {
|
||||
// Build output debug information object
|
||||
const output = {
|
||||
debug: {
|
||||
userAgent: navigator.userAgent,
|
||||
platform: navigator.platform,
|
||||
language: navigator.language,
|
||||
extensionVersion: chrome.runtime.getManifest().version
|
||||
},
|
||||
config: JSON.parse(JSON.stringify(Config.cachedSyncConfig)) // Deep clone config object
|
||||
};
|
||||
|
||||
// Sanitise sensitive user config values
|
||||
delete output.config.userID;
|
||||
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
|
||||
? "Default server address" : "Custom server address";
|
||||
output.config.invidiousInstances = output.config.invidiousInstances.length;
|
||||
output.config.whitelistedChannels = output.config.whitelistedChannels.length;
|
||||
|
||||
return JSON.stringify(output, null, 4);
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import { asyncRequestToServer } from "./utils/requests";
|
||||
import { isMobileControlsOpen } from "./utils/mobileUtils";
|
||||
import { defaultPreviewTime } from "./utils/constants";
|
||||
import { onVideoPage } from "../maze-utils/src/pageInfo";
|
||||
import { getSegmentsForVideo } from "./utils/segmentData";
|
||||
|
||||
cleanPage();
|
||||
|
||||
@@ -139,6 +140,9 @@ let switchingVideos = null;
|
||||
let lastCheckTime = 0;
|
||||
let lastCheckVideoTime = -1;
|
||||
|
||||
// To determine if a video resolution change is happening
|
||||
let firstPlay = true;
|
||||
|
||||
//is this channel whitelised from getting sponsors skipped
|
||||
let channelWhitelisted = false;
|
||||
|
||||
@@ -264,7 +268,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
sendResponse({ hasVideo: getVideoID() != null });
|
||||
// fetch segments
|
||||
if (getVideoID()) {
|
||||
sponsorsLookup(false);
|
||||
sponsorsLookup(false, true);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -342,6 +346,12 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
metaKey: request.metaKey
|
||||
}));
|
||||
break;
|
||||
case "getLogs":
|
||||
sendResponse({
|
||||
debug: window["SBLogs"].debug,
|
||||
warn: window["SBLogs"].warn
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
sendResponse({});
|
||||
@@ -359,7 +369,7 @@ function contentConfigUpdateListener(changes: StorageChangesObject) {
|
||||
updateVisibilityOfPlayerControlsButton()
|
||||
break;
|
||||
case "categorySelections":
|
||||
sponsorsLookup();
|
||||
sponsorsLookup(true, true);
|
||||
break;
|
||||
case "barTypes":
|
||||
setCategoryColorCSSVariables();
|
||||
@@ -381,6 +391,7 @@ function resetValues() {
|
||||
lastCheckVideoTime = -1;
|
||||
retryCount = 0;
|
||||
previewedSegment = false;
|
||||
firstPlay = true;
|
||||
|
||||
sponsorTimes = [];
|
||||
existingChaptersImported = false;
|
||||
@@ -633,7 +644,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
||||
|
||||
updateActiveSegment(currentTime);
|
||||
|
||||
if (getVideo().paused
|
||||
if ((getVideo().paused && getCurrentTime() !== 0) // Allow autoplay disabled videos to skip before playing
|
||||
|| (getCurrentTime() >= getVideoDuration() - 0.01 && getVideoDuration() > 1)) return;
|
||||
const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
|
||||
|
||||
@@ -864,6 +875,7 @@ function setupVideoListeners() {
|
||||
|
||||
let startedWaiting = false;
|
||||
let lastPausedAtZero = true;
|
||||
let lastVideoDataChange = 0;
|
||||
|
||||
const rateChangeListener = () => {
|
||||
updateVirtualTime();
|
||||
@@ -876,13 +888,10 @@ function setupVideoListeners() {
|
||||
video.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
|
||||
if (video.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA
|
||||
&& video.currentTime === 0) return;
|
||||
// Prevent video resolution changes from causing skips
|
||||
if (!firstPlay && Date.now() - lastVideoDataChange < 200 && video.currentTime === 0) return;
|
||||
|
||||
|
||||
firstPlay = false;
|
||||
updateVirtualTime();
|
||||
checkForMiniplayerPlaying();
|
||||
|
||||
@@ -1014,6 +1023,24 @@ function setupVideoListeners() {
|
||||
};
|
||||
video.addEventListener('waiting', waitingListener);
|
||||
|
||||
// When video data is changed
|
||||
const emptyListener = () => {
|
||||
lastVideoDataChange = Date.now();
|
||||
|
||||
if (firstPlay && video.currentTime === 0) {
|
||||
playListener();
|
||||
}
|
||||
}
|
||||
video.addEventListener('emptied', emptyListener);
|
||||
|
||||
// For when autoplay is off to skip before starting playback
|
||||
const metadataLoadedListener = () => {
|
||||
if (firstPlay && getCurrentTime() === 0) {
|
||||
playListener();
|
||||
}
|
||||
}
|
||||
video.addEventListener('loadedmetadata', metadataLoadedListener)
|
||||
|
||||
startSponsorSchedule();
|
||||
|
||||
if (setupVideoListenersFirstTime) {
|
||||
@@ -1025,6 +1052,8 @@ function setupVideoListeners() {
|
||||
video.removeEventListener('videoSpeed_ratechange', rateChangeListener);
|
||||
video.removeEventListener('pause', pauseListener);
|
||||
video.removeEventListener('waiting', waitingListener);
|
||||
video.removeEventListener('empty', emptyListener);
|
||||
video.removeEventListener('loadedmetadata', metadataLoadedListener);
|
||||
|
||||
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
|
||||
});
|
||||
@@ -1109,38 +1138,20 @@ function setupCategoryPill() {
|
||||
categoryPill.attachToPage(isOnMobileYouTube(), isOnInvidious(), voteAsync);
|
||||
}
|
||||
|
||||
async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
const categories: string[] = Config.config.categorySelections.map((category) => category.name);
|
||||
|
||||
const extraRequestData: Record<string, unknown> = {};
|
||||
const hashParams = getHashParams();
|
||||
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
|
||||
|
||||
async function sponsorsLookup(keepOldSubmissions = true, ignoreCache = false) {
|
||||
const videoID = getVideoID()
|
||||
if (!videoID) {
|
||||
console.error("[SponsorBlock] Attempted to fetch segments with a null/undefined videoID.");
|
||||
return;
|
||||
}
|
||||
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
|
||||
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||
categories,
|
||||
actionTypes: getEnabledActionTypes(),
|
||||
userAgent: `${chrome.runtime.id}`,
|
||||
...extraRequestData
|
||||
});
|
||||
|
||||
const segmentData = await getSegmentsForVideo(videoID, ignoreCache);
|
||||
|
||||
// store last response status
|
||||
lastResponseStatus = response?.status;
|
||||
lastResponseStatus = segmentData.status;
|
||||
if (segmentData.status === 200) {
|
||||
const receivedSegments = segmentData.segments;
|
||||
|
||||
if (response?.ok) {
|
||||
const receivedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
||||
?.filter((video) => video.videoID === getVideoID())
|
||||
?.map((video) => video.segments)?.[0]
|
||||
?.map((segment) => ({
|
||||
...segment,
|
||||
source: SponsorSourceType.Server
|
||||
}))
|
||||
?.sort((a, b) => a.segment[0] - b.segment[0]);
|
||||
if (receivedSegments && receivedSegments.length) {
|
||||
sponsorDataFound = true;
|
||||
|
||||
@@ -1180,6 +1191,7 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
}
|
||||
|
||||
// See if some segments should be hidden
|
||||
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
|
||||
const downvotedData = Config.local.downvotedSegments[hashPrefix];
|
||||
if (downvotedData) {
|
||||
for (const segment of sponsorTimes) {
|
||||
@@ -1252,18 +1264,6 @@ function importExistingChapters(wait: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
function getEnabledActionTypes(forceFullVideo = false): ActionType[] {
|
||||
const actionTypes = [ActionType.Skip, ActionType.Poi, ActionType.Chapter];
|
||||
if (Config.config.muteSegments) {
|
||||
actionTypes.push(ActionType.Mute);
|
||||
}
|
||||
if (Config.config.fullVideoSegments || forceFullVideo) {
|
||||
actionTypes.push(ActionType.Full);
|
||||
}
|
||||
|
||||
return actionTypes;
|
||||
}
|
||||
|
||||
async function lockedCategoriesLookup(): Promise<void> {
|
||||
const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4);
|
||||
const response = await asyncRequestToServer("GET", "/api/lockCategories/" + hashPrefix);
|
||||
@@ -1721,7 +1721,8 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||
}
|
||||
}
|
||||
|
||||
if (autoSkip && Config.config.audioNotificationOnSkip) {
|
||||
if (autoSkip && Config.config.audioNotificationOnSkip
|
||||
&& !isSubmittingSegment && !getVideo()?.muted) {
|
||||
const beep = new Audio(chrome.runtime.getURL("icons/beep.ogg"));
|
||||
beep.volume = getVideo().volume * 0.1;
|
||||
const oldMetadata = navigator.mediaSession.metadata
|
||||
@@ -1987,7 +1988,7 @@ function startOrEndTimingNewSegment() {
|
||||
Config.forceLocalUpdate("unsubmittedSegments");
|
||||
|
||||
// Make sure they know if someone has already submitted something it while they were watching
|
||||
sponsorsLookup();
|
||||
sponsorsLookup(true, true);
|
||||
|
||||
updateEditButtonsOnPlayer();
|
||||
updateSponsorTimesSubmitting(false);
|
||||
|
||||
@@ -17,7 +17,8 @@ interface DefaultMessage {
|
||||
| "isChannelWhitelisted"
|
||||
| "submitTimes"
|
||||
| "refreshSegments"
|
||||
| "closePopup";
|
||||
| "closePopup"
|
||||
| "getLogs";
|
||||
}
|
||||
|
||||
interface BoolValueMessage {
|
||||
@@ -104,7 +105,8 @@ export type MessageResponse =
|
||||
| Record<string, never> // empty object response {}
|
||||
| VoteResponse
|
||||
| ImportSegmentsResponse
|
||||
| RefreshSegmentsResponse;
|
||||
| RefreshSegmentsResponse
|
||||
| LogResponse;
|
||||
|
||||
export interface VoteResponse {
|
||||
successType: number;
|
||||
@@ -120,6 +122,11 @@ export interface RefreshSegmentsResponse {
|
||||
hasVideo: boolean;
|
||||
}
|
||||
|
||||
export interface LogResponse {
|
||||
debug: string[];
|
||||
warn: string[];
|
||||
}
|
||||
|
||||
export interface TimeUpdateMessage {
|
||||
message: "time";
|
||||
time: number;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import Config from "./config";
|
||||
import * as CompileConfig from "../config.json";
|
||||
import Config, { generateDebugDetails } from "./config";
|
||||
import * as invidiousList from "../ci/invidiouslist.json";
|
||||
|
||||
// Make the config public for debugging purposes
|
||||
@@ -698,32 +697,14 @@ function validateServerAddress(input: string): string {
|
||||
}
|
||||
|
||||
function copyDebugOutputToClipboard() {
|
||||
// Build output debug information object
|
||||
const output = {
|
||||
debug: {
|
||||
userAgent: navigator.userAgent,
|
||||
platform: navigator.platform,
|
||||
language: navigator.language,
|
||||
extensionVersion: chrome.runtime.getManifest().version
|
||||
},
|
||||
config: JSON.parse(JSON.stringify(Config.cachedSyncConfig)) // Deep clone config object
|
||||
};
|
||||
|
||||
// Sanitise sensitive user config values
|
||||
delete output.config.userID;
|
||||
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
|
||||
? "Default server address" : "Custom server address";
|
||||
output.config.invidiousInstances = output.config.invidiousInstances.length;
|
||||
output.config.whitelistedChannels = output.config.whitelistedChannels.length;
|
||||
|
||||
// Copy object to clipboard
|
||||
navigator.clipboard.writeText(JSON.stringify(output, null, 4))
|
||||
.then(() => {
|
||||
navigator.clipboard.writeText(generateDebugDetails())
|
||||
.then(() => {
|
||||
alert(chrome.i18n.getMessage("copyDebugInformationComplete"));
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
.catch(() => {
|
||||
alert(chrome.i18n.getMessage("copyDebugInformationFailed"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isIncognitoAllowed(): Promise<boolean> {
|
||||
|
||||
13
src/popup.ts
13
src/popup.ts
@@ -1,4 +1,4 @@
|
||||
import Config from "./config";
|
||||
import Config, { generateDebugDetails } from "./config";
|
||||
|
||||
import Utils from "./utils";
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
GetChannelIDResponse,
|
||||
IsChannelWhitelistedResponse,
|
||||
IsInfoFoundMessageResponse,
|
||||
LogResponse,
|
||||
Message,
|
||||
MessageResponse,
|
||||
PopupMessage,
|
||||
@@ -184,7 +185,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
"exportSegmentsButton",
|
||||
"importSegmentsMenu",
|
||||
"importSegmentsText",
|
||||
"importSegmentsSubmit"
|
||||
"importSegmentsSubmit",
|
||||
"debugLogs"
|
||||
|
||||
].forEach(id => PageElements[id] = document.getElementById(id));
|
||||
|
||||
@@ -255,6 +257,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
PageElements.helpButton.addEventListener("click", openHelp);
|
||||
PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments);
|
||||
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => copyToClipboard(await getHash(Config.config.userID)));
|
||||
PageElements.debugLogs.addEventListener("click", copyDebgLogs);
|
||||
|
||||
// Forward click events
|
||||
if (window !== window.top) {
|
||||
@@ -1143,6 +1146,12 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function copyDebgLogs() {
|
||||
sendTabMessage({ message: "getLogs" }, (logs: LogResponse) => {
|
||||
copyToClipboard(`${generateDebugDetails()}\n\nWarn:\n${logs.warn.join("\n")}\n\nDebug:\n${logs.debug.join("\n")}`);
|
||||
});
|
||||
}
|
||||
|
||||
function onMessage(msg: PopupMessage) {
|
||||
switch (msg.message) {
|
||||
case "time":
|
||||
|
||||
@@ -56,7 +56,13 @@ export enum ActionType {
|
||||
Poi = "poi"
|
||||
}
|
||||
|
||||
export const ActionTypes = [ActionType.Skip, ActionType.Mute];
|
||||
export const ActionTypes = [
|
||||
ActionType.Skip,
|
||||
ActionType.Mute,
|
||||
ActionType.Chapter,
|
||||
ActionType.Full,
|
||||
ActionType.Poi
|
||||
];
|
||||
|
||||
export type SegmentUUID = string & { __segmentUUIDBrand: unknown };
|
||||
export type Category = string & { __categoryBrand: unknown };
|
||||
|
||||
@@ -9,8 +9,8 @@ import { FetchResponse, sendRequestToCustomServer } from "../../maze-utils/src/b
|
||||
* @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);
|
||||
export function asyncRequestToCustomServer(type: string, url: string, data = {}, headers = {}): Promise<FetchResponse> {
|
||||
return sendRequestToCustomServer(type, url, data, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,10 +20,10 @@ export function asyncRequestToCustomServer(type: string, url: string, data = {})
|
||||
* @param address The address to add to the SponsorBlock server address
|
||||
* @param callback
|
||||
*/
|
||||
export async function asyncRequestToServer(type: string, address: string, data = {}): Promise<FetchResponse> {
|
||||
export async function asyncRequestToServer(type: string, address: string, data = {}, headers = {}): Promise<FetchResponse> {
|
||||
const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
|
||||
|
||||
return await (asyncRequestToCustomServer(type, serverAddress + address, data));
|
||||
return await (asyncRequestToCustomServer(type, serverAddress + address, data, headers));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
104
src/utils/segmentData.ts
Normal file
104
src/utils/segmentData.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { DataCache } from "../../maze-utils/src/cache";
|
||||
import { getHash, HashedValue } from "../../maze-utils/src/hash";
|
||||
import Config from "../config";
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import { ActionType, ActionTypes, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
||||
import { getHashParams } from "./pageUtils";
|
||||
import { asyncRequestToServer } from "./requests";
|
||||
|
||||
const segmentDataCache = new DataCache<VideoID, SegmentResponse>(() => {
|
||||
return {
|
||||
segments: null,
|
||||
status: 200
|
||||
};
|
||||
}, 5);
|
||||
|
||||
const pendingList: Record<VideoID, Promise<SegmentResponse>> = {};
|
||||
|
||||
export interface SegmentResponse {
|
||||
segments: SponsorTime[] | null;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export async function getSegmentsForVideo(videoID: VideoID, ignoreCache: boolean): Promise<SegmentResponse> {
|
||||
if (!ignoreCache) {
|
||||
const cachedData = segmentDataCache.getFromCache(videoID);
|
||||
if (cachedData) {
|
||||
segmentDataCache.cacheUsed(videoID);
|
||||
return cachedData;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingList[videoID]) {
|
||||
return await pendingList[videoID];
|
||||
}
|
||||
|
||||
const pendingData = fetchSegmentsForVideo(videoID);
|
||||
pendingList[videoID] = pendingData;
|
||||
|
||||
const result = await pendingData;
|
||||
delete pendingList[videoID];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse> {
|
||||
const categories: string[] = Config.config.categorySelections.map((category) => category.name);
|
||||
|
||||
const extraRequestData: Record<string, unknown> = {};
|
||||
const hashParams = getHashParams();
|
||||
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
|
||||
|
||||
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
|
||||
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||
categories: CompileConfig.categoryList,
|
||||
actionTypes: ActionTypes,
|
||||
...extraRequestData
|
||||
}, {
|
||||
"X-CLIENT-NAME": `${chrome.runtime.id}/v${chrome.runtime.getManifest().version}`
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const enabledActionTypes = getEnabledActionTypes();
|
||||
|
||||
const receivedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
||||
?.filter((video) => video.videoID === videoID)
|
||||
?.map((video) => video.segments)?.[0]
|
||||
?.filter((segment) => enabledActionTypes.includes(segment.actionType) && categories.includes(segment.category))
|
||||
?.map((segment) => ({
|
||||
...segment,
|
||||
source: SponsorSourceType.Server
|
||||
}))
|
||||
?.sort((a, b) => a.segment[0] - b.segment[0]);
|
||||
|
||||
if (receivedSegments && receivedSegments.length) {
|
||||
const result = {
|
||||
segments: receivedSegments,
|
||||
status: response.status
|
||||
};
|
||||
|
||||
segmentDataCache.setupCache(videoID).segments = result.segments;
|
||||
return result;
|
||||
} else {
|
||||
// Setup with null data
|
||||
segmentDataCache.setupCache(videoID);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
segments: null,
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
|
||||
function getEnabledActionTypes(forceFullVideo = false): ActionType[] {
|
||||
const actionTypes = [ActionType.Skip, ActionType.Poi, ActionType.Chapter];
|
||||
if (Config.config.muteSegments) {
|
||||
actionTypes.push(ActionType.Mute);
|
||||
}
|
||||
if (Config.config.fullVideoSegments || forceFullVideo) {
|
||||
actionTypes.push(ActionType.Full);
|
||||
}
|
||||
|
||||
return actionTypes;
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { isOnInvidious, parseYouTubeVideoIDFromURL } from "../../maze-utils/src/video";
|
||||
import Config from "../config";
|
||||
import { getVideoLabel } from "./videoLabels";
|
||||
import { setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
|
||||
import { getThumbnailSelector, setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
|
||||
import { VideoID } from "../types";
|
||||
import { getSegmentsForVideo } from "./segmentData";
|
||||
|
||||
export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
|
||||
await Promise.all(thumbnails.map((t) => labelThumbnail(t)));
|
||||
export async function handleThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
|
||||
await Promise.all(thumbnails.map((t) => {
|
||||
labelThumbnail(t);
|
||||
setupThumbnailHover(t);
|
||||
}));
|
||||
}
|
||||
|
||||
export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLElement | null> {
|
||||
@@ -13,9 +18,7 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
|
||||
return null;
|
||||
}
|
||||
|
||||
const link = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement
|
||||
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
|
||||
const videoID = parseYouTubeVideoIDFromURL(link.href)?.videoID;
|
||||
const videoID = extractVideoID(thumbnail);
|
||||
if (!videoID) {
|
||||
hideThumbnailLabel(thumbnail);
|
||||
return null;
|
||||
@@ -37,6 +40,37 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
|
||||
return overlay;
|
||||
}
|
||||
|
||||
export async function setupThumbnailHover(thumbnail: HTMLImageElement): Promise<void> {
|
||||
// Cache would be reset every load due to no SPA
|
||||
if (isOnInvidious()) return;
|
||||
|
||||
const mainElement = thumbnail.closest("#dismissible") as HTMLElement;
|
||||
if (mainElement) {
|
||||
mainElement.removeEventListener("mouseenter", thumbnailHoverListener);
|
||||
mainElement.addEventListener("mouseenter", thumbnailHoverListener);
|
||||
}
|
||||
}
|
||||
|
||||
function thumbnailHoverListener(e: MouseEvent) {
|
||||
if (!chrome.runtime?.id) return;
|
||||
|
||||
const thumbnail = (e.target as HTMLElement).querySelector(getThumbnailSelector()) as HTMLImageElement;
|
||||
if (!thumbnail) return;
|
||||
|
||||
// Pre-fetch data for this video
|
||||
const videoID = extractVideoID(thumbnail);
|
||||
if (videoID) {
|
||||
void getSegmentsForVideo(videoID, false);
|
||||
}
|
||||
}
|
||||
|
||||
function extractVideoID(thumbnail: HTMLImageElement): VideoID | null {
|
||||
const link = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement
|
||||
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
|
||||
|
||||
return parseYouTubeVideoIDFromURL(link.href)?.videoID;
|
||||
}
|
||||
|
||||
function getOldThumbnailLabel(thumbnail: HTMLImageElement): HTMLElement | null {
|
||||
return thumbnail.querySelector(".sponsorThumbnailLabel") as HTMLElement | null;
|
||||
}
|
||||
@@ -109,7 +143,7 @@ function insertSBIconDefinition() {
|
||||
}
|
||||
|
||||
export function setupThumbnailListener(): void {
|
||||
setThumbnailListener(labelThumbnails, () => {
|
||||
setThumbnailListener(handleThumbnails, () => {
|
||||
insertSBIconDefinition();
|
||||
}, () => Config.isReady());
|
||||
}
|
||||
@@ -185,6 +185,12 @@ module.exports = env => {
|
||||
stream: env.stream
|
||||
}),
|
||||
new configDiffPlugin()
|
||||
]
|
||||
],
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user