mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-19 22:18:40 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/MRuy/606
# Conflicts: # src/popup.ts
This commit is contained in:
@@ -37,6 +37,9 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
case "openHelp":
|
||||
chrome.tabs.create({url: chrome.runtime.getURL('help/index_en.html')});
|
||||
return;
|
||||
case "openPage":
|
||||
chrome.tabs.create({url: chrome.runtime.getURL(request.url)});
|
||||
return;
|
||||
case "sendRequest":
|
||||
sendRequestToCustomServer(request.type, request.url, request.data).then(async (response) => {
|
||||
callback({
|
||||
@@ -52,16 +55,6 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
case "alertPrevious":
|
||||
if (Config.config.unsubmittedWarning) {
|
||||
chrome.notifications.create("stillThere" + Math.random(), {
|
||||
type: "basic",
|
||||
title: chrome.i18n.getMessage("wantToSubmit") + " " + request.previousVideoID + "?",
|
||||
message: chrome.i18n.getMessage("leftTimes"),
|
||||
iconUrl: "./icons/LogoSponsorBlocker256px.png"
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "registerContentScript":
|
||||
registerFirefoxContentScript(request);
|
||||
return false;
|
||||
@@ -135,19 +128,22 @@ async function submitVote(type: number, UUID: string, category: string) {
|
||||
|
||||
if (response.ok) {
|
||||
return {
|
||||
successType: 1
|
||||
successType: 1,
|
||||
responseText: await response.text()
|
||||
};
|
||||
} else if (response.status == 405) {
|
||||
//duplicate vote
|
||||
return {
|
||||
successType: 0,
|
||||
statusCode: response.status
|
||||
statusCode: response.status,
|
||||
responseText: await response.text()
|
||||
};
|
||||
} else {
|
||||
//error while connect
|
||||
return {
|
||||
successType: -1,
|
||||
statusCode: response.status
|
||||
statusCode: response.status,
|
||||
responseText: await response.text()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface NoticeProps {
|
||||
timed?: boolean,
|
||||
idSuffix?: string,
|
||||
|
||||
videoSpeed?: () => number,
|
||||
|
||||
fadeIn?: boolean,
|
||||
|
||||
// Callback for when this is closed
|
||||
@@ -19,7 +21,7 @@ export interface NoticeProps {
|
||||
export interface NoticeState {
|
||||
noticeTitle: string,
|
||||
|
||||
maxCountdownTime?: () => number,
|
||||
maxCountdownTime: () => number,
|
||||
|
||||
countdownTime: number,
|
||||
countdownText: string,
|
||||
@@ -28,6 +30,8 @@ export interface NoticeState {
|
||||
|
||||
class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
countdownInterval: NodeJS.Timeout;
|
||||
intervalVideoSpeed: number;
|
||||
|
||||
idSuffix: string;
|
||||
|
||||
amountOfPreviousNotices: number;
|
||||
@@ -71,7 +75,9 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
|
||||
return (
|
||||
<table id={"sponsorSkipNotice" + this.idSuffix}
|
||||
className={"sponsorSkipObject sponsorSkipNotice" + (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")}
|
||||
className={"sponsorSkipObject sponsorSkipNotice"
|
||||
+ (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")
|
||||
+ (this.amountOfPreviousNotices > 0 ? " secondSkipNotice" : "")}
|
||||
style={noticeStyle}
|
||||
onMouseEnter={() => this.timerMouseEnter()}
|
||||
onMouseLeave={() => this.timerMouseLeave()}>
|
||||
@@ -152,7 +158,11 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
countdown(): void {
|
||||
if (!this.props.timed) return;
|
||||
|
||||
const countdownTime = this.state.countdownTime - 1;
|
||||
const countdownTime = Math.min(this.state.countdownTime - 1, this.state.maxCountdownTime());
|
||||
|
||||
if (this.props.videoSpeed && this.intervalVideoSpeed != this.props.videoSpeed()) {
|
||||
this.setupInterval();
|
||||
}
|
||||
|
||||
if (countdownTime <= 0) {
|
||||
//remove this from setInterval
|
||||
@@ -175,12 +185,19 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
countdownTime
|
||||
})
|
||||
}
|
||||
|
||||
removeFadeAnimation(): void {
|
||||
//remove the fade out class if it exists
|
||||
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
|
||||
notice.classList.remove("sponsorSkipNoticeFadeOut");
|
||||
notice.style.animation = "none";
|
||||
}
|
||||
|
||||
pauseCountdown(): void {
|
||||
if (!this.props.timed) return;
|
||||
|
||||
//remove setInterval
|
||||
clearInterval(this.countdownInterval);
|
||||
if (this.countdownInterval) clearInterval(this.countdownInterval);
|
||||
this.countdownInterval = null;
|
||||
|
||||
//reset countdown and inform the user
|
||||
@@ -189,10 +206,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
countdownText: this.state.countdownManuallyPaused ? chrome.i18n.getMessage("manualPaused") : chrome.i18n.getMessage("paused")
|
||||
});
|
||||
|
||||
//remove the fade out class if it exists
|
||||
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
|
||||
notice.classList.remove("sponsorSkipNoticeFadeOut");
|
||||
notice.style.animation = "none";
|
||||
this.removeFadeAnimation();
|
||||
}
|
||||
|
||||
startCountdown(): void {
|
||||
@@ -206,16 +220,29 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
countdownText: null
|
||||
});
|
||||
|
||||
this.countdownInterval = setInterval(this.countdown.bind(this), 1000);
|
||||
this.setupInterval();
|
||||
}
|
||||
|
||||
setupInterval(): void {
|
||||
if (this.countdownInterval) clearInterval(this.countdownInterval);
|
||||
|
||||
const intervalDuration = this.props.videoSpeed ? 1000 / this.props.videoSpeed() : 1000;
|
||||
this.countdownInterval = setInterval(this.countdown.bind(this), intervalDuration);
|
||||
|
||||
if (this.props.videoSpeed) this.intervalVideoSpeed = this.props.videoSpeed();
|
||||
}
|
||||
|
||||
resetCountdown(): void {
|
||||
if (!this.props.timed) return;
|
||||
|
||||
this.setupInterval();
|
||||
|
||||
this.setState({
|
||||
countdownTime: this.state.maxCountdownTime(),
|
||||
countdownText: null
|
||||
});
|
||||
|
||||
this.removeFadeAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ContentContainer, SponsorHideType, SponsorTime } from "../types";
|
||||
import NoticeComponent from "./NoticeComponent";
|
||||
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
||||
|
||||
enum SkipNoticeAction {
|
||||
export enum SkipNoticeAction {
|
||||
None,
|
||||
Upvote,
|
||||
Downvote,
|
||||
@@ -24,23 +24,23 @@ export interface SkipNoticeProps {
|
||||
}
|
||||
|
||||
export interface SkipNoticeState {
|
||||
noticeTitle: string;
|
||||
noticeTitle?: string;
|
||||
|
||||
messages: string[];
|
||||
messageOnClick: (event: React.MouseEvent) => unknown;
|
||||
messages?: string[];
|
||||
messageOnClick?: (event: React.MouseEvent) => unknown;
|
||||
|
||||
countdownTime: number;
|
||||
maxCountdownTime: () => number;
|
||||
countdownText: string;
|
||||
countdownTime?: number;
|
||||
maxCountdownTime?: () => number;
|
||||
countdownText?: string;
|
||||
|
||||
unskipText: string;
|
||||
unskipCallback: (index: number) => void;
|
||||
unskipText?: string;
|
||||
unskipCallback?: (index: number) => void;
|
||||
|
||||
downvoting: boolean;
|
||||
choosingCategory: boolean;
|
||||
thanksForVotingText: string; //null until the voting buttons should be hidden
|
||||
downvoting?: boolean;
|
||||
choosingCategory?: boolean;
|
||||
thanksForVotingText?: string; //null until the voting buttons should be hidden
|
||||
|
||||
actionState: SkipNoticeAction;
|
||||
actionState?: SkipNoticeAction;
|
||||
}
|
||||
|
||||
class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeState> {
|
||||
@@ -91,13 +91,6 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
}
|
||||
this.idSuffix += this.amountOfPreviousNotices;
|
||||
|
||||
if (this.amountOfPreviousNotices > 0) {
|
||||
//another notice exists
|
||||
|
||||
const previousNotice = document.getElementsByClassName("sponsorSkipNotice")[0];
|
||||
previousNotice.classList.add("secondSkipNotice")
|
||||
}
|
||||
|
||||
// Setup state
|
||||
this.state = {
|
||||
noticeTitle,
|
||||
@@ -134,7 +127,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
|
||||
render(): React.ReactElement {
|
||||
const noticeStyle: React.CSSProperties = {
|
||||
zIndex: 50 + this.amountOfPreviousNotices
|
||||
zIndex: 1000 + this.amountOfPreviousNotices
|
||||
}
|
||||
if (this.contentContainer().onMobileYouTube) {
|
||||
noticeStyle.bottom = "4em";
|
||||
@@ -148,6 +141,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
fadeIn={true}
|
||||
timed={true}
|
||||
maxCountdownTime={this.state.maxCountdownTime}
|
||||
videoSpeed={() => this.contentContainer().v?.playbackRate}
|
||||
ref={this.noticeRef}
|
||||
closeListener={() => this.closeListener()}>
|
||||
|
||||
@@ -203,7 +197,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
style={{marginLeft: "4px"}}
|
||||
onClick={() => this.prepAction(SkipNoticeAction.Unskip)}>
|
||||
|
||||
{this.state.unskipText}
|
||||
{this.state.unskipText + " (" + Config.config.skipKeybind + ")"}
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@@ -463,21 +457,23 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
reskip(index: number): void {
|
||||
this.contentContainer().reskipSponsorTime(this.segments[index]);
|
||||
|
||||
//reset countdown
|
||||
this.setState({
|
||||
const newState: SkipNoticeState = {
|
||||
unskipText: chrome.i18n.getMessage("unskip"),
|
||||
unskipCallback: this.unskip.bind(this),
|
||||
|
||||
maxCountdownTime: () => 4,
|
||||
countdownTime: 4
|
||||
});
|
||||
};
|
||||
|
||||
// See if the title should be changed
|
||||
if (!this.autoSkip) {
|
||||
this.setState({
|
||||
noticeTitle: chrome.i18n.getMessage("noticeTitle")
|
||||
});
|
||||
}
|
||||
newState.noticeTitle = chrome.i18n.getMessage("noticeTitle");
|
||||
}
|
||||
|
||||
//reset countdown
|
||||
this.setState(newState, () => {
|
||||
this.noticeRef.current.resetCountdown();
|
||||
});
|
||||
}
|
||||
|
||||
afterVote(segment: SponsorTime, type: number, category: string): void {
|
||||
|
||||
@@ -340,13 +340,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
deleteTime(): void {
|
||||
const sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
|
||||
const index = this.props.index;
|
||||
const removingIncomplete = sponsorTimes[index].segment.length < 2;
|
||||
|
||||
//if it is not a complete sponsor time
|
||||
if (sponsorTimes[index].segment.length < 2) {
|
||||
//update video player
|
||||
this.props.contentContainer().changeStartSponsorButton(true, false);
|
||||
}
|
||||
|
||||
sponsorTimes.splice(index, 1);
|
||||
|
||||
//save this
|
||||
@@ -357,13 +352,16 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
//if they are all removed
|
||||
if (sponsorTimes.length == 0) {
|
||||
this.props.submissionNotice.cancel();
|
||||
|
||||
//update video player
|
||||
this.props.contentContainer().changeStartSponsorButton(true, false);
|
||||
} else {
|
||||
//update display
|
||||
this.props.submissionNotice.forceUpdate();
|
||||
}
|
||||
|
||||
//if it is not a complete segment, or all are removed
|
||||
if (sponsorTimes.length === 0 || removingIncomplete) {
|
||||
//update video player
|
||||
this.props.contentContainer().updateEditButtonsOnPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
configUpdate(): void {
|
||||
|
||||
@@ -32,8 +32,6 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
|
||||
videoObserver: MutationObserver;
|
||||
|
||||
showingYouCapNotice: boolean;
|
||||
|
||||
constructor(props: SubmissionNoticeProps) {
|
||||
super(props);
|
||||
this.noticeRef = React.createRef();
|
||||
@@ -47,7 +45,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
this.state = {
|
||||
noticeTitle,
|
||||
messages: [],
|
||||
idSuffix: "SubmissionNotice",
|
||||
idSuffix: "SubmissionNotice"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +87,6 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{this.getYouCapMessage()}
|
||||
|
||||
{/* Last Row */}
|
||||
<tr id={"sponsorSkipNoticeSecondRow" + this.state.idSuffix}>
|
||||
|
||||
@@ -117,35 +113,6 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
);
|
||||
}
|
||||
|
||||
/** TODO: Remove */
|
||||
getYouCapMessage(): JSX.Element {
|
||||
if (Config.config.sponsorTimesContributed < 20
|
||||
|| (Config.config.hasShownYouCapNotice && !this.showingYouCapNotice)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Config.config.hasShownYouCapNotice = true;
|
||||
if (!this.showingYouCapNotice) {
|
||||
this.showingYouCapNotice = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr style={{textAlign: "center"}}>
|
||||
<p style={{width: "300px", textAlign: "center", display: "inline-block", fontSize: "11px"}}>
|
||||
Like contributing to crowdsourced projects?
|
||||
Consider checking out <a href="https://gist.github.com/ajayyy/6f2cf90dd66e51067a7ab5e63544cd4e" style={{textDecoration: "underline"}}>YouCap or NekoCap</a>,
|
||||
new open-source replacements for YouTube{"'"}s now defunct community captions.
|
||||
</p>
|
||||
|
||||
<img src={chrome.extension.getURL("icons/close.png")}
|
||||
style={{padding: "0", margin: "auto"}}
|
||||
className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton"
|
||||
onClick={() => { this.showingYouCapNotice = false; this.forceUpdate(); }}>
|
||||
</img>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
getSponsorTimeMessages(): JSX.Element[] | JSX.Element {
|
||||
const elements: JSX.Element[] = [];
|
||||
this.timeEditRefs = [];
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import * as CompileConfig from "../config.json";
|
||||
import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject } from "./types";
|
||||
import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes } from "./types";
|
||||
|
||||
import Utils from "./utils";
|
||||
const utils = new Utils();
|
||||
|
||||
interface SBConfig {
|
||||
userID: string,
|
||||
/** Contains unsubmitted segments that the user has created. */
|
||||
segmentTimes: SBMap<string, SponsorTime[]>,
|
||||
defaultCategory: string,
|
||||
whitelistedChannels: string[],
|
||||
forceChannelCheck: boolean,
|
||||
skipKeybind: string,
|
||||
startSponsorKeybind: string,
|
||||
submitKeybind: string,
|
||||
minutesSaved: number,
|
||||
@@ -17,7 +19,6 @@ interface SBConfig {
|
||||
sponsorTimesContributed: number,
|
||||
submissionCountSinceCategories: number, // New count used to show the "Read The Guidelines!!" message
|
||||
showTimeWithSkips: boolean,
|
||||
unsubmittedWarning: boolean,
|
||||
disableSkipping: boolean,
|
||||
trackViewCount: boolean,
|
||||
dontShowNotice: boolean,
|
||||
@@ -34,8 +35,8 @@ interface SBConfig {
|
||||
audioNotificationOnSkip,
|
||||
checkForUnlistedVideos: boolean,
|
||||
testingServer: boolean,
|
||||
hashPrefix: boolean,
|
||||
refetchWhenNotFound: boolean,
|
||||
ytInfoPermissionGranted: boolean,
|
||||
|
||||
// What categories should be skipped
|
||||
categorySelections: CategorySelection[],
|
||||
@@ -55,9 +56,7 @@ interface SBConfig {
|
||||
"preview-selfpromo": PreviewBarOption,
|
||||
"music_offtopic": PreviewBarOption,
|
||||
"preview-music_offtopic": PreviewBarOption,
|
||||
},
|
||||
|
||||
hasShownYouCapNotice: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SBObject {
|
||||
@@ -67,7 +66,7 @@ export interface SBObject {
|
||||
config: SBConfig;
|
||||
|
||||
// Functions
|
||||
encodeStoredItem<T>(data: T): T | Array<any>;
|
||||
encodeStoredItem<T>(data: T): T | UnencodedSegmentTimes;
|
||||
convertJSON(): void;
|
||||
}
|
||||
|
||||
@@ -145,6 +144,7 @@ const Config: SBObject = {
|
||||
defaultCategory: "chooseACategory",
|
||||
whitelistedChannels: [],
|
||||
forceChannelCheck: false,
|
||||
skipKeybind: "Enter",
|
||||
startSponsorKeybind: ";",
|
||||
submitKeybind: "'",
|
||||
minutesSaved: 0,
|
||||
@@ -152,7 +152,6 @@ const Config: SBObject = {
|
||||
sponsorTimesContributed: 0,
|
||||
submissionCountSinceCategories: 0,
|
||||
showTimeWithSkips: true,
|
||||
unsubmittedWarning: true,
|
||||
disableSkipping: false,
|
||||
trackViewCount: true,
|
||||
dontShowNotice: false,
|
||||
@@ -169,8 +168,8 @@ const Config: SBObject = {
|
||||
audioNotificationOnSkip: false,
|
||||
checkForUnlistedVideos: false,
|
||||
testingServer: false,
|
||||
hashPrefix: false,
|
||||
refetchWhenNotFound: true,
|
||||
ytInfoPermissionGranted: false,
|
||||
|
||||
categorySelections: [{
|
||||
name: "sponsor",
|
||||
@@ -231,9 +230,7 @@ const Config: SBObject = {
|
||||
color: "#a6634a",
|
||||
opacity: "0.7"
|
||||
}
|
||||
},
|
||||
|
||||
hasShownYouCapNotice: false
|
||||
}
|
||||
},
|
||||
localConfig: null,
|
||||
config: null,
|
||||
@@ -251,10 +248,10 @@ const Config: SBObject = {
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
function encodeStoredItem<T>(data: T): T | Array<any> {
|
||||
function encodeStoredItem<T>(data: T): T | UnencodedSegmentTimes {
|
||||
// if data is SBMap convert to json for storing
|
||||
if(!(data instanceof SBMap)) return data;
|
||||
return Array.from(data.entries());
|
||||
return Array.from(data.entries()).filter((element) => element[1].length > 0); // Remove empty entries
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +266,7 @@ function decodeStoredItem<T>(id: string, data: T): T | SBMap<string, SponsorTime
|
||||
if (Config.defaults[id] instanceof SBMap) {
|
||||
try {
|
||||
if (!Array.isArray(data)) return data;
|
||||
return new SBMap(id, data);
|
||||
return new SBMap(id, data as UnencodedSegmentTimes);
|
||||
} catch(e) {
|
||||
console.error("Failed to parse SBMap: " + id);
|
||||
}
|
||||
@@ -399,7 +396,7 @@ function migrateOldFormats(config: SBConfig) {
|
||||
|
||||
// Migrate old "sponsorTimes"
|
||||
if (config["sponsorTimes"]) {
|
||||
let jsonData: any = config["sponsorTimes"];
|
||||
let jsonData: unknown = config["sponsorTimes"];
|
||||
|
||||
// Check if data is stored in the old format for SBMap (a JSON string)
|
||||
if (typeof jsonData === "string") {
|
||||
@@ -413,7 +410,7 @@ function migrateOldFormats(config: SBConfig) {
|
||||
// Otherwise junk data
|
||||
if (Array.isArray(jsonData)) {
|
||||
const oldMap = new Map(jsonData);
|
||||
oldMap.forEach((sponsorTimes: number[][], key) => {
|
||||
oldMap.forEach((sponsorTimes: [number, number][], key) => {
|
||||
const segmentTimes: SponsorTime[] = [];
|
||||
for (const segment of sponsorTimes) {
|
||||
segmentTimes.push({
|
||||
|
||||
593
src/content.ts
593
src/content.ts
@@ -1,6 +1,6 @@
|
||||
import Config from "./config";
|
||||
|
||||
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types";
|
||||
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject, ChannelIDInfo, ChannelIDStatus } from "./types";
|
||||
|
||||
import { ContentContainer } from "./types";
|
||||
import Utils from "./utils";
|
||||
@@ -19,16 +19,17 @@ utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
||||
|
||||
//was sponsor data found when doing SponsorsLookup
|
||||
let sponsorDataFound = false;
|
||||
let previousVideoID: VideoID = null;
|
||||
//the actual sponsorTimes if loaded and UUIDs associated with them
|
||||
let sponsorTimes: SponsorTime[] = null;
|
||||
//what video id are these sponsors for
|
||||
let sponsorVideoID: VideoID = null;
|
||||
// List of open skip notices
|
||||
const skipNotices: SkipNotice[] = [];
|
||||
|
||||
// JSON video info
|
||||
let videoInfo: VideoInfo = null;
|
||||
//the channel this video is about
|
||||
let channelID: string;
|
||||
let channelIDInfo: ChannelIDInfo;
|
||||
|
||||
// Skips are scheduled to ensure precision.
|
||||
// Skips are rescheduled every seeking event.
|
||||
@@ -36,11 +37,13 @@ let channelID: string;
|
||||
let currentSkipSchedule: NodeJS.Timeout = null;
|
||||
let seekListenerSetUp = false
|
||||
|
||||
/** @type {Array[boolean]} Has the sponsor been skipped */
|
||||
/** Has the sponsor been skipped */
|
||||
let sponsorSkipped: boolean[] = [];
|
||||
|
||||
//the video
|
||||
let video: HTMLVideoElement;
|
||||
// List of videos that have had event listeners added to them
|
||||
const videoRootsWithEventListeners: HTMLDivElement[] = [];
|
||||
|
||||
let onInvidious;
|
||||
let onMobileYouTube;
|
||||
@@ -68,8 +71,11 @@ let channelWhitelisted = false;
|
||||
// create preview bar
|
||||
let previewBar: PreviewBar = null;
|
||||
|
||||
//the player controls on the YouTube player
|
||||
let controls = null;
|
||||
/** Element containing the player controls on the YouTube player. */
|
||||
let controls: HTMLElement | null = null;
|
||||
|
||||
/** Contains buttons created by `createButton()`. */
|
||||
const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImageElement}> = {};
|
||||
|
||||
// Direct Links after the config is loaded
|
||||
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document.URL)));
|
||||
@@ -78,10 +84,7 @@ utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYo
|
||||
//this only happens if there is an error
|
||||
let sponsorLookupRetries = 0;
|
||||
|
||||
//if showing the start sponsor button or the end sponsor button on the player
|
||||
let showingStartSponsor = true;
|
||||
|
||||
//the sponsor times being prepared to be submitted
|
||||
/** Segments created by the user which have not yet been submitted. */
|
||||
let sponsorTimesSubmitting: SponsorTime[] = [];
|
||||
|
||||
//becomes true when isInfoFound is called
|
||||
@@ -100,6 +103,7 @@ const skipNoticeContentContainer: ContentContainer = () => ({
|
||||
unskipSponsorTime,
|
||||
sponsorTimes,
|
||||
sponsorTimesSubmitting,
|
||||
skipNotices,
|
||||
v: video,
|
||||
sponsorVideoID,
|
||||
reskipSponsorTime,
|
||||
@@ -107,12 +111,15 @@ const skipNoticeContentContainer: ContentContainer = () => ({
|
||||
onMobileYouTube,
|
||||
sponsorSubmissionNotice: submissionNotice,
|
||||
resetSponsorSubmissionNotice,
|
||||
changeStartSponsorButton,
|
||||
updateEditButtonsOnPlayer,
|
||||
previewTime,
|
||||
videoInfo,
|
||||
getRealCurrentTime: getRealCurrentTime
|
||||
});
|
||||
|
||||
// value determining when to count segment as skipped and send telemetry to server (percent based)
|
||||
const manualSkipPercentCount = 0.5;
|
||||
|
||||
//get messages from the background script and the popup
|
||||
chrome.runtime.onMessage.addListener(messageListener);
|
||||
|
||||
@@ -123,11 +130,11 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
videoIDChange(getYouTubeVideoID(document.URL));
|
||||
break;
|
||||
case "sponsorStart":
|
||||
sponsorMessageStarted(sendResponse);
|
||||
startOrEndTimingNewSegment()
|
||||
|
||||
break;
|
||||
case "sponsorDataChanged":
|
||||
updateSponsorTimesSubmitting();
|
||||
sendResponse({
|
||||
creatingSegment: isSegmentCreationInProgress(),
|
||||
});
|
||||
|
||||
break;
|
||||
case "isInfoFound":
|
||||
@@ -146,13 +153,14 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
break;
|
||||
case "getVideoID":
|
||||
sendResponse({
|
||||
videoID: sponsorVideoID
|
||||
videoID: sponsorVideoID,
|
||||
creatingSegment: isSegmentCreationInProgress(),
|
||||
});
|
||||
|
||||
break;
|
||||
case "getChannelID":
|
||||
sendResponse({
|
||||
channelID: channelID
|
||||
channelID: channelIDInfo.id
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -166,10 +174,6 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
channelWhitelisted = request.value;
|
||||
sponsorsLookup(sponsorVideoID);
|
||||
|
||||
break;
|
||||
case "changeStartSponsorButton":
|
||||
changeStartSponsorButton(request.showStartSponsor, request.uploadButtonVisible);
|
||||
|
||||
break;
|
||||
case "submitTimes":
|
||||
submitSponsorTimes();
|
||||
@@ -198,28 +202,6 @@ if (!Config.configListeners.includes(contentConfigUpdateListener)) {
|
||||
Config.configListeners.push(contentConfigUpdateListener);
|
||||
}
|
||||
|
||||
//check for hotkey pressed
|
||||
document.onkeydown = function(e: KeyboardEvent){
|
||||
const key = e.key;
|
||||
|
||||
const video = document.getElementById("movie_player");
|
||||
|
||||
const startSponsorKey = Config.config.startSponsorKeybind;
|
||||
|
||||
const submitKey = Config.config.submitKeybind;
|
||||
|
||||
//is the video in focus, otherwise they could be typing a comment
|
||||
if (document.activeElement === video) {
|
||||
if(key == startSponsorKey){
|
||||
//semicolon
|
||||
startSponsorClicked();
|
||||
} else if (key == submitKey) {
|
||||
//single quote
|
||||
submitSponsorTimes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetValues() {
|
||||
lastCheckTime = 0;
|
||||
lastCheckVideoTime = -1;
|
||||
@@ -230,7 +212,10 @@ function resetValues() {
|
||||
|
||||
videoInfo = null;
|
||||
channelWhitelisted = false;
|
||||
channelID = null;
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Fetching,
|
||||
id: null
|
||||
};
|
||||
|
||||
//empty the preview bar
|
||||
if (previewBar !== null) {
|
||||
@@ -268,25 +253,25 @@ async function videoIDChange(id) {
|
||||
// Wait for options to be ready
|
||||
await utils.wait(() => Config.config !== null, 5000, 1);
|
||||
|
||||
// Get new video info
|
||||
getVideoInfo();
|
||||
|
||||
// If enabled, it will check if this video is private or unlisted and double check with the user if the sponsors should be looked up
|
||||
if (Config.config.checkForUnlistedVideos) {
|
||||
try {
|
||||
await utils.wait(() => !!videoInfo, 5000, 1);
|
||||
} catch (err) {
|
||||
alert(chrome.i18n.getMessage("adblockerIssue"));
|
||||
}
|
||||
|
||||
if (isUnlisted()) {
|
||||
const shouldContinue = confirm(chrome.i18n.getMessage("confirmPrivacy"));
|
||||
if(!shouldContinue) return;
|
||||
const shouldContinue = confirm("SponsorBlock: You have the setting 'Ignore Unlisted/Private Videos' enabled."
|
||||
+ " Due to a change in how segment fetching works, this setting is not needed anymore as it cannot leak your video ID to the server."
|
||||
+ " It instead sends just the first 4 characters of a longer hash of the videoID to the server, and filters through a subset of the database."
|
||||
+ " More info about this implementation can be found here: https://github.com/ajayyy/SponsorBlockServer/issues/25"
|
||||
+ "\n\nPlease click okay to confirm that you acknowledge this and continue using SponsorBlock.");
|
||||
if (shouldContinue) {
|
||||
Config.config.checkForUnlistedVideos = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get new video info
|
||||
getVideoInfo();
|
||||
|
||||
// Update whitelist data when the video data is loaded
|
||||
utils.wait(() => !!videoInfo, 5000, 10).then(whitelistCheck);
|
||||
whitelistCheck();
|
||||
|
||||
//setup the preview bar
|
||||
if (previewBar === null) {
|
||||
@@ -310,54 +295,22 @@ async function videoIDChange(id) {
|
||||
}
|
||||
}
|
||||
|
||||
//warn them if they had unsubmitted times
|
||||
if (previousVideoID != null) {
|
||||
//get the sponsor times from storage
|
||||
const sponsorTimes = Config.config.segmentTimes.get(previousVideoID);
|
||||
if (sponsorTimes != undefined && sponsorTimes.length > 0 && new URL(document.URL).host !== "music.youtube.com") {
|
||||
//warn them that they have unsubmitted sponsor times
|
||||
chrome.runtime.sendMessage({
|
||||
message: "alertPrevious",
|
||||
previousVideoID: previousVideoID
|
||||
});
|
||||
}
|
||||
|
||||
//set the previous video id to the currentID
|
||||
previousVideoID = id;
|
||||
} else {
|
||||
//set the previous id now, don't wait for chrome.storage.get
|
||||
previousVideoID = id;
|
||||
}
|
||||
|
||||
//close popup
|
||||
closeInfoMenu();
|
||||
|
||||
|
||||
sponsorsLookup(id);
|
||||
|
||||
//make sure everything is properly added
|
||||
updateVisibilityOfPlayerControlsButton().then(() => {
|
||||
//see if the onvideo control image needs to be changed
|
||||
const segments = Config.config.segmentTimes.get(sponsorVideoID);
|
||||
if (segments != null && segments.length > 0 && segments[segments.length - 1].segment.length >= 2) {
|
||||
changeStartSponsorButton(true, true);
|
||||
} else if (segments != null && segments.length > 0 && segments[segments.length - 1].segment.length < 2) {
|
||||
changeStartSponsorButton(false, true);
|
||||
} else {
|
||||
changeStartSponsorButton(true, false);
|
||||
}
|
||||
});
|
||||
// Make sure all player buttons are properly added
|
||||
updateVisibilityOfPlayerControlsButton();
|
||||
|
||||
//reset sponsor times submitting
|
||||
// Clear unsubmitted segments from the previous video
|
||||
sponsorTimesSubmitting = [];
|
||||
updateSponsorTimesSubmitting();
|
||||
|
||||
//see if video controls buttons should be added
|
||||
if (!onInvidious) {
|
||||
updateVisibilityOfPlayerControlsButton();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMobileControlsMutations(): void {
|
||||
updateVisibilityOfPlayerControlsButton();
|
||||
|
||||
if (previewBar !== null) {
|
||||
if (document.body.contains(previewBar.container)) {
|
||||
const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background");
|
||||
@@ -442,7 +395,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
||||
|
||||
if (video.paused) return;
|
||||
|
||||
if (Config.config.disableSkipping || channelWhitelisted || (channelID === null && Config.config.forceChannelCheck)){
|
||||
if (Config.config.disableSkipping || channelWhitelisted || (channelIDInfo.status === ChannelIDStatus.Fetching && Config.config.forceChannelCheck)){
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -532,6 +485,8 @@ async function sponsorsLookup(id: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
addHotkeyListener();
|
||||
|
||||
if (!durationListenerSetUp) {
|
||||
durationListenerSetUp = true;
|
||||
|
||||
@@ -608,33 +563,22 @@ async function sponsorsLookup(id: string) {
|
||||
}
|
||||
|
||||
// Check for hashPrefix setting
|
||||
let getRequest;
|
||||
if (Config.config.hashPrefix) {
|
||||
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
|
||||
getRequest = utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||
categories
|
||||
});
|
||||
} else {
|
||||
getRequest = utils.asyncRequestToServer('GET', "/api/skipSegments", {
|
||||
videoID: id,
|
||||
categories
|
||||
});
|
||||
}
|
||||
getRequest.then(async (response: FetchResponse) => {
|
||||
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
|
||||
utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||
categories
|
||||
}).then(async (response: FetchResponse) => {
|
||||
if (response?.ok) {
|
||||
let result = JSON.parse(response.responseText);
|
||||
if (Config.config.hashPrefix) {
|
||||
result = result.filter((video) => video.videoID === id);
|
||||
if (result.length > 0) {
|
||||
result = result[0].segments;
|
||||
if (result.length === 0) { // return if no segments found
|
||||
retryFetch(id);
|
||||
return;
|
||||
}
|
||||
} else { // return if no video found
|
||||
result = result.filter((video) => video.videoID === id);
|
||||
if (result.length > 0) {
|
||||
result = result[0].segments;
|
||||
if (result.length === 0) { // return if no segments found
|
||||
retryFetch(id);
|
||||
return;
|
||||
}
|
||||
} else { // return if no video found
|
||||
retryFetch(id);
|
||||
return;
|
||||
}
|
||||
|
||||
const recievedSegments: SponsorTime[] = result;
|
||||
@@ -682,12 +626,12 @@ async function sponsorsLookup(id: string) {
|
||||
sponsorLookupRetries = 0;
|
||||
} else if (response?.status === 404) {
|
||||
retryFetch(id);
|
||||
} else if (sponsorLookupRetries < 90 && !recheckStarted) {
|
||||
} else if (sponsorLookupRetries < 15 && !recheckStarted) {
|
||||
recheckStarted = true;
|
||||
|
||||
//TODO lower when server becomes better (back to 1 second)
|
||||
//some error occurred, try again in a second
|
||||
setTimeout(() => sponsorsLookup(id), 10000 + Math.random() * 30000);
|
||||
setTimeout(() => sponsorsLookup(id), 5000 + Math.random() * 15000 + 5000 * sponsorLookupRetries);
|
||||
|
||||
sponsorLookupRetries++;
|
||||
}
|
||||
@@ -705,8 +649,7 @@ function retryFetch(id: string): void {
|
||||
|
||||
//if less than 3 days old
|
||||
if (Date.now() - new Date(dateUploaded).getTime() < 259200000) {
|
||||
//TODO lower when server becomes better
|
||||
setTimeout(() => sponsorsLookup(id), 120000);
|
||||
setTimeout(() => sponsorsLookup(id), 30000 + Math.random() * 90000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -755,6 +698,7 @@ async function getVideoInfo(): Promise<void> {
|
||||
const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1];
|
||||
if (!decodedData) {
|
||||
console.error("[SB] Failed at getting video info from YouTube.");
|
||||
console.error("[SB] Data returned from YouTube: " + result.responseText);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -858,23 +802,34 @@ function updatePreviewBar(): void {
|
||||
}
|
||||
|
||||
//checks if this channel is whitelisted, should be done only after the channelID has been loaded
|
||||
function whitelistCheck() {
|
||||
channelID = videoInfo?.videoDetails?.channelId;
|
||||
if (!channelID) {
|
||||
channelID = null;
|
||||
async function whitelistCheck() {
|
||||
const whitelistedChannels = Config.config.whitelistedChannels;
|
||||
|
||||
const channelID = document.querySelector(".ytd-channel-name a")?.getAttribute("href")?.replace(/\/.+\//, "") // YouTube
|
||||
?? document.querySelector(".ytp-title-channel-logo")?.getAttribute("href")?.replace(/https:\/.+\//, "") // YouTube Embed
|
||||
?? document.querySelector("a > .channel-profile")?.parentElement?.getAttribute("href")?.replace(/\/.+\//, ""); // Invidious
|
||||
|
||||
if (channelID) {
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Found,
|
||||
id: channelID
|
||||
}
|
||||
} else {
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Failed,
|
||||
id: null
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//see if this is a whitelisted channel
|
||||
const whitelistedChannels = Config.config.whitelistedChannels;
|
||||
|
||||
if (whitelistedChannels != undefined && whitelistedChannels.includes(channelID)) {
|
||||
channelWhitelisted = true;
|
||||
}
|
||||
|
||||
// check if the start of segments were missed
|
||||
if (Config.config.forceChannelCheck && sponsorTimes && sponsorTimes.length > 0) startSkipScheduleCheckingForStartSponsors();
|
||||
if (Config.config.forceChannelCheck && sponsorTimes?.length > 0) startSkipScheduleCheckingForStartSponsors();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -998,6 +953,26 @@ function previewTime(time: number, unpause = true) {
|
||||
}
|
||||
}
|
||||
|
||||
//send telemetry and count skip
|
||||
function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped: number, fullSkip: boolean) {
|
||||
if (!Config.config.trackViewCount) return;
|
||||
|
||||
let counted = false;
|
||||
for (const segment of skippingSegments) {
|
||||
const index = sponsorTimes.indexOf(segment);
|
||||
if (index !== -1 && !sponsorSkipped[index]) {
|
||||
sponsorSkipped[index] = true;
|
||||
if (!counted) {
|
||||
Config.config.minutesSaved = Config.config.minutesSaved + secondsSkipped / 60;
|
||||
Config.config.skipCount = Config.config.skipCount + 1;
|
||||
counted = true;
|
||||
}
|
||||
|
||||
if (fullSkip) utils.asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//skip from the start time to the end time for a certain index sponsor time
|
||||
function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: SponsorTime[], openNotice: boolean) {
|
||||
// There will only be one submission if it is manual skip
|
||||
@@ -1016,34 +991,12 @@ function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: S
|
||||
if (openNotice) {
|
||||
//send out the message saying that a sponsor message was skipped
|
||||
if (!Config.config.dontShowNotice || !autoSkip) {
|
||||
new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer);
|
||||
skipNotices.push(new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer));
|
||||
}
|
||||
}
|
||||
|
||||
//send telemetry that a this sponsor was skipped
|
||||
if (Config.config.trackViewCount && autoSkip) {
|
||||
let alreadySkipped = false;
|
||||
let isPreviewSegment = false;
|
||||
|
||||
for (const segment of skippingSegments) {
|
||||
const index = sponsorTimes.indexOf(segment);
|
||||
if (index !== -1 && !sponsorSkipped[index]) {
|
||||
utils.asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
|
||||
|
||||
sponsorSkipped[index] = true;
|
||||
} else if (sponsorSkipped[index]) {
|
||||
alreadySkipped = true;
|
||||
}
|
||||
|
||||
if (index === -1) isPreviewSegment = true;
|
||||
}
|
||||
|
||||
// Count this as a skip
|
||||
if (!alreadySkipped && !isPreviewSegment) {
|
||||
Config.config.minutesSaved = Config.config.minutesSaved + (skipTime[1] - skipTime[0]) / 60;
|
||||
Config.config.skipCount = Config.config.skipCount + 1;
|
||||
}
|
||||
}
|
||||
if (autoSkip) sendTelemetryAndCount(skippingSegments, skipTime[1] - skipTime[0], true);
|
||||
}
|
||||
|
||||
function unskipSponsorTime(segment: SponsorTime) {
|
||||
@@ -1054,13 +1007,18 @@ function unskipSponsorTime(segment: SponsorTime) {
|
||||
}
|
||||
|
||||
function reskipSponsorTime(segment: SponsorTime) {
|
||||
const skippedTime = Math.max(segment.segment[1] - video.currentTime, 0);
|
||||
const segmentDuration = segment.segment[1] - segment.segment[0];
|
||||
const fullSkip = skippedTime / segmentDuration > manualSkipPercentCount;
|
||||
|
||||
video.currentTime = segment.segment[1];
|
||||
|
||||
sendTelemetryAndCount([segment], skippedTime, fullSkip);
|
||||
startSponsorSchedule(true, segment.segment[1], false);
|
||||
}
|
||||
|
||||
function createButton(baseID, title, callback, imageName, isDraggable=false): boolean {
|
||||
if (document.getElementById(baseID + "Button") != null) return false;
|
||||
function createButton(baseID: string, title: string, callback: () => void, imageName: string, isDraggable = false): HTMLElement {
|
||||
const existingElement = document.getElementById(baseID + "Button");
|
||||
if (existingElement !== null) return existingElement;
|
||||
|
||||
// Button HTML
|
||||
const newButton = document.createElement("button");
|
||||
@@ -1084,9 +1042,15 @@ function createButton(baseID, title, callback, imageName, isDraggable=false): bo
|
||||
newButton.appendChild(newButtonImage);
|
||||
|
||||
// Add the button to player
|
||||
controls.prepend(newButton);
|
||||
if (controls) controls.prepend(newButton);
|
||||
|
||||
return true;
|
||||
// Store the elements to prevent unnecessary querying
|
||||
playerButtons[baseID] = {
|
||||
button: newButton,
|
||||
image: newButtonImage,
|
||||
};
|
||||
|
||||
return newButton;
|
||||
}
|
||||
|
||||
function getControls(): HTMLElement | false {
|
||||
@@ -1096,8 +1060,8 @@ function getControls(): HTMLElement | false {
|
||||
// Mobile YouTube
|
||||
".player-controls-top",
|
||||
// Invidious/videojs video element's controls element
|
||||
".vjs-control-bar"
|
||||
]
|
||||
".vjs-control-bar",
|
||||
];
|
||||
|
||||
for (const controlsSelector of controlsSelectors) {
|
||||
const controls = document.querySelectorAll(controlsSelector);
|
||||
@@ -1110,53 +1074,75 @@ function getControls(): HTMLElement | false {
|
||||
return false;
|
||||
}
|
||||
|
||||
//adds all the player controls buttons
|
||||
async function createButtons(): Promise<boolean> {
|
||||
/** Creates any missing buttons on the YouTube player if possible. */
|
||||
async function createButtons(): Promise<void> {
|
||||
if (onMobileYouTube) return;
|
||||
|
||||
const result = await utils.wait(getControls).catch();
|
||||
|
||||
//set global controls variable
|
||||
controls = result;
|
||||
|
||||
let createdButton = false;
|
||||
controls = await utils.wait(getControls).catch();
|
||||
|
||||
// Add button if does not already exist in html
|
||||
createdButton = createButton("startSponsor", "sponsorStart", startSponsorClicked, "PlayerStartIconSponsorBlocker256px.png") || createdButton;
|
||||
createdButton = createButton("info", "openPopup", openInfoMenu, "PlayerInfoIconSponsorBlocker256px.png") || createdButton;
|
||||
createdButton = createButton("delete", "clearTimes", clearSponsorTimes, "PlayerDeleteIconSponsorBlocker256px.png") || createdButton;
|
||||
createdButton = createButton("submit", "SubmitTimes", submitSponsorTimes, "PlayerUploadIconSponsorBlocker256px.png") || createdButton;
|
||||
|
||||
return createdButton;
|
||||
createButton("startSegment", "sponsorStart", () => closeInfoMenuAnd(() => startOrEndTimingNewSegment()), "PlayerStartIconSponsorBlocker.svg");
|
||||
createButton("cancelSegment", "sponsorCancel", () => closeInfoMenuAnd(() => cancelCreatingSegment()), "PlayerCancelSegmentIconSponsorBlocker.svg");
|
||||
createButton("info", "openPopup", openInfoMenu, "PlayerInfoIconSponsorBlocker.svg");
|
||||
createButton("delete", "clearTimes", () => closeInfoMenuAnd(() => clearSponsorTimes()), "PlayerDeleteIconSponsorBlocker.svg");
|
||||
createButton("submit", "SubmitTimes", submitSponsorTimes, "PlayerUploadIconSponsorBlocker.svg");
|
||||
}
|
||||
|
||||
//adds or removes the player controls button to what it should be
|
||||
async function updateVisibilityOfPlayerControlsButton(): Promise<boolean> {
|
||||
//not on a proper video yet
|
||||
if (!sponsorVideoID) return false;
|
||||
/** Creates any missing buttons on the player and updates their visiblity. */
|
||||
async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
|
||||
// Not on a proper video yet
|
||||
if (!sponsorVideoID) return;
|
||||
|
||||
const createdButtons = await createButtons();
|
||||
if (!createdButtons) return;
|
||||
await createButtons();
|
||||
|
||||
if (Config.config.hideVideoPlayerControls || onInvidious) {
|
||||
document.getElementById("startSponsorButton").style.display = "none";
|
||||
document.getElementById("submitButton").style.display = "none";
|
||||
} else {
|
||||
document.getElementById("startSponsorButton").style.removeProperty("display");
|
||||
}
|
||||
updateEditButtonsOnPlayer();
|
||||
|
||||
//don't show the info button on embeds
|
||||
// Don't show the info button on embeds
|
||||
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) {
|
||||
document.getElementById("infoButton").style.display = "none";
|
||||
playerButtons.info.button.style.display = "none";
|
||||
} else {
|
||||
document.getElementById("infoButton").style.removeProperty("display");
|
||||
playerButtons.info.button.style.removeProperty("display");
|
||||
}
|
||||
|
||||
if (Config.config.hideDeleteButtonPlayerControls || onInvidious) {
|
||||
document.getElementById("deleteButton").style.display = "none";
|
||||
}
|
||||
|
||||
/** Updates the visibility of buttons on the player related to creating segments. */
|
||||
function updateEditButtonsOnPlayer(): void {
|
||||
// Don't try to update the buttons if we aren't on a YouTube video page
|
||||
if (!sponsorVideoID) return;
|
||||
|
||||
const buttonsEnabled = !Config.config.hideVideoPlayerControls && !onInvidious;
|
||||
|
||||
let creatingSegment = false;
|
||||
let submitButtonVisible = false;
|
||||
let deleteButtonVisible = false;
|
||||
|
||||
// Only check if buttons should be visible if they're enabled
|
||||
if (buttonsEnabled) {
|
||||
creatingSegment = isSegmentCreationInProgress();
|
||||
|
||||
// Show only if there are any segments to submit
|
||||
submitButtonVisible = sponsorTimesSubmitting.length > 1 || (sponsorTimesSubmitting.length > 0 && !creatingSegment);
|
||||
|
||||
// Show only if there are any segments to delete
|
||||
deleteButtonVisible = sponsorTimesSubmitting.length > 1 || (sponsorTimesSubmitting.length > 0 && !creatingSegment);
|
||||
}
|
||||
|
||||
return createdButtons;
|
||||
// Update the elements
|
||||
playerButtons.startSegment.button.style.display = buttonsEnabled ? "unset" : "none";
|
||||
playerButtons.cancelSegment.button.style.display = buttonsEnabled && creatingSegment ? "unset" : "none";
|
||||
|
||||
if (buttonsEnabled) {
|
||||
if (creatingSegment) {
|
||||
playerButtons.startSegment.image.src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker.svg");
|
||||
playerButtons.startSegment.button.setAttribute("title", chrome.i18n.getMessage("sponsorEnd"));
|
||||
} else {
|
||||
playerButtons.startSegment.image.src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker.svg");
|
||||
playerButtons.startSegment.button.setAttribute("title", chrome.i18n.getMessage("sponsorStart"));
|
||||
}
|
||||
}
|
||||
|
||||
playerButtons.submit.button.style.display = submitButtonVisible && !Config.config.hideUploadButtonPlayerControls ? "unset" : "none";
|
||||
playerButtons.delete.button.style.display = deleteButtonVisible && !Config.config.hideDeleteButtonPlayerControls ? "unset" : "none";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1177,30 +1163,50 @@ function getRealCurrentTime(): number {
|
||||
}
|
||||
}
|
||||
|
||||
function startSponsorClicked() {
|
||||
//it can't update to this info yet
|
||||
closeInfoMenu();
|
||||
|
||||
toggleStartSponsorButton();
|
||||
|
||||
//add to sponsorTimes
|
||||
if (sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length < 2) {
|
||||
//it is an end time
|
||||
sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment[1] = getRealCurrentTime();
|
||||
sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.sort((a, b) => a > b ? 1 : (a < b ? -1 : 0));
|
||||
} else {
|
||||
//it is a start time
|
||||
function startOrEndTimingNewSegment() {
|
||||
if (!isSegmentCreationInProgress()) {
|
||||
sponsorTimesSubmitting.push({
|
||||
segment: [getRealCurrentTime()],
|
||||
UUID: null,
|
||||
category: Config.config.defaultCategory
|
||||
category: Config.config.defaultCategory,
|
||||
});
|
||||
} else {
|
||||
// Finish creating the new segment
|
||||
const existingSegment = getIncompleteSegment();
|
||||
const existingTime = existingSegment.segment[0];
|
||||
const currentTime = getRealCurrentTime();
|
||||
|
||||
// Swap timestamps if the user put the segment end before the start
|
||||
existingSegment.segment = [Math.min(existingTime, currentTime), Math.max(existingTime, currentTime)];
|
||||
}
|
||||
|
||||
//save this info
|
||||
// Save the newly created segment
|
||||
Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting);
|
||||
|
||||
updateSponsorTimesSubmitting(false)
|
||||
updateEditButtonsOnPlayer();
|
||||
updateSponsorTimesSubmitting(false);
|
||||
}
|
||||
|
||||
function getIncompleteSegment(): SponsorTime {
|
||||
return sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1];
|
||||
}
|
||||
|
||||
/** Is the latest submitting segment incomplete */
|
||||
function isSegmentCreationInProgress(): boolean {
|
||||
const segment = getIncompleteSegment();
|
||||
return segment && segment?.segment?.length !== 2;
|
||||
}
|
||||
|
||||
function cancelCreatingSegment() {
|
||||
if (isSegmentCreationInProgress()) {
|
||||
sponsorTimesSubmitting.splice(sponsorTimesSubmitting.length - 1, 1);
|
||||
Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting);
|
||||
|
||||
if (sponsorTimesSubmitting.length <= 0) resetSponsorSubmissionNotice();
|
||||
}
|
||||
|
||||
updateEditButtonsOnPlayer();
|
||||
updateSponsorTimesSubmitting(false);
|
||||
}
|
||||
|
||||
function updateSponsorTimesSubmitting(getFromConfig = true) {
|
||||
@@ -1229,38 +1235,6 @@ function updateSponsorTimesSubmitting(getFromConfig = true) {
|
||||
}
|
||||
}
|
||||
|
||||
async function changeStartSponsorButton(showStartSponsor: boolean, uploadButtonVisible: boolean): Promise<boolean> {
|
||||
if(!sponsorVideoID || onMobileYouTube) return false;
|
||||
|
||||
//if it isn't visible, there is no data
|
||||
const shouldHide = (uploadButtonVisible && !(Config.config.hideDeleteButtonPlayerControls || onInvidious)) ? "unset" : "none"
|
||||
document.getElementById("deleteButton").style.display = shouldHide;
|
||||
|
||||
if (showStartSponsor) {
|
||||
showingStartSponsor = true;
|
||||
(<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker256px.png");
|
||||
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorStart"));
|
||||
|
||||
if (document.getElementById("startSponsorImage").style.display != "none" && uploadButtonVisible && !Config.config.hideUploadButtonPlayerControls && !onInvidious) {
|
||||
document.getElementById("submitButton").style.display = "unset";
|
||||
} else if (!uploadButtonVisible || onInvidious) {
|
||||
//disable submit button
|
||||
document.getElementById("submitButton").style.display = "none";
|
||||
}
|
||||
} else {
|
||||
showingStartSponsor = false;
|
||||
(<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker256px.png");
|
||||
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorEND"));
|
||||
|
||||
//disable submit button
|
||||
document.getElementById("submitButton").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function toggleStartSponsorButton() {
|
||||
changeStartSponsorButton(!showingStartSponsor, true);
|
||||
}
|
||||
|
||||
function openInfoMenu() {
|
||||
if (document.getElementById("sponsorBlockPopupContainer") != null) {
|
||||
//it's already added
|
||||
@@ -1270,7 +1244,7 @@ function openInfoMenu() {
|
||||
popupInitialised = false;
|
||||
|
||||
//hide info button
|
||||
document.getElementById("infoButton").style.display = "none";
|
||||
if (playerButtons.info) playerButtons.info.button.style.display = "none";
|
||||
|
||||
sendRequestToCustomServer('GET', chrome.extension.getURL("popup.html"), function(xmlhttp) {
|
||||
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
||||
@@ -1316,7 +1290,7 @@ function openInfoMenu() {
|
||||
const settings = <HTMLImageElement> popup.querySelector("#sbPopupIconSettings");
|
||||
const edit = <HTMLImageElement> popup.querySelector("#sbPopupIconEdit");
|
||||
const check = <HTMLImageElement> popup.querySelector("#sbPopupIconCheck");
|
||||
logo.src = chrome.extension.getURL("icons/LogoSponsorBlocker256px.png");
|
||||
logo.src = chrome.extension.getURL("icons/IconSponsorBlocker256px.png");
|
||||
settings.src = chrome.extension.getURL("icons/settings.svg");
|
||||
edit.src = chrome.extension.getURL("icons/pencil.svg");
|
||||
check.src = chrome.extension.getURL("icons/check.svg");
|
||||
@@ -1332,20 +1306,28 @@ function openInfoMenu() {
|
||||
|
||||
function closeInfoMenu() {
|
||||
const popup = document.getElementById("sponsorBlockPopupContainer");
|
||||
if (popup != null) {
|
||||
popup.remove();
|
||||
if (popup === null) return;
|
||||
|
||||
//show info button if it's not an embed
|
||||
if (!document.URL.includes("/embed/")) {
|
||||
document.getElementById("infoButton").style.display = "unset";
|
||||
}
|
||||
popup.remove();
|
||||
|
||||
// Show info button if it's not an embed
|
||||
if (!document.URL.includes("/embed/") && playerButtons.info) {
|
||||
playerButtons.info.button.style.display = "unset";
|
||||
}
|
||||
}
|
||||
|
||||
function clearSponsorTimes() {
|
||||
//it can't update to this info yet
|
||||
/**
|
||||
* The content script currently has no way to notify the info menu of changes. As a workaround we close it, thus making it query the new information when reopened.
|
||||
*
|
||||
* This function and all its uses should be removed when this issue is fixed.
|
||||
* */
|
||||
function closeInfoMenuAnd<T>(func: () => T): T {
|
||||
closeInfoMenu();
|
||||
|
||||
return func();
|
||||
}
|
||||
|
||||
function clearSponsorTimes() {
|
||||
const currentVideoID = sponsorVideoID;
|
||||
|
||||
const sponsorTimes = Config.config.segmentTimes.get(currentVideoID);
|
||||
@@ -1355,6 +1337,8 @@ function clearSponsorTimes() {
|
||||
+ "\n" + chrome.i18n.getMessage("confirmMSG")
|
||||
if(!confirm(confirmMessage)) return;
|
||||
|
||||
resetSponsorSubmissionNotice();
|
||||
|
||||
//clear the sponsor times
|
||||
Config.config.segmentTimes.delete(currentVideoID);
|
||||
|
||||
@@ -1362,9 +1346,7 @@ function clearSponsorTimes() {
|
||||
sponsorTimesSubmitting = [];
|
||||
|
||||
updatePreviewBar();
|
||||
|
||||
//set buttons to be correct
|
||||
changeStartSponsorButton(true, false);
|
||||
updateEditButtonsOnPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1409,7 +1391,7 @@ function vote(type: number, UUID: string, category?: string, skipNotice?: SkipNo
|
||||
//success (treat rate limits as a success)
|
||||
skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category);
|
||||
} else if (response.successType == -1) {
|
||||
skipNotice.setNoticeInfoMessage.bind(skipNotice)(utils.getErrorMessage(response.statusCode))
|
||||
skipNotice.setNoticeInfoMessage.bind(skipNotice)(utils.getErrorMessage(response.statusCode, response.responseText))
|
||||
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
|
||||
}
|
||||
}
|
||||
@@ -1430,31 +1412,17 @@ function dontShowNoticeAgain() {
|
||||
closeAllSkipNotices();
|
||||
}
|
||||
|
||||
function sponsorMessageStarted(callback: (response: MessageResponse) => void) {
|
||||
video = document.querySelector('video');
|
||||
|
||||
//send back current time
|
||||
callback({
|
||||
time: video.currentTime
|
||||
})
|
||||
|
||||
//update button
|
||||
toggleStartSponsorButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for the submission notice to clear itself when it closes
|
||||
*/
|
||||
function resetSponsorSubmissionNotice() {
|
||||
submissionNotice?.close();
|
||||
submissionNotice = null;
|
||||
}
|
||||
|
||||
function submitSponsorTimes() {
|
||||
if (submissionNotice !== null) return;
|
||||
|
||||
//it can't update to this info yet
|
||||
closeInfoMenu();
|
||||
|
||||
if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) {
|
||||
submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage);
|
||||
}
|
||||
@@ -1463,10 +1431,10 @@ function submitSponsorTimes() {
|
||||
|
||||
//send the message to the background js
|
||||
//called after all the checks have been made that it's okay to do so
|
||||
async function sendSubmitMessage(): Promise<void> {
|
||||
//add loading animation
|
||||
(<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker256px.png");
|
||||
document.getElementById("submitButton").style.animation = "rotate 1s 0s infinite";
|
||||
async function sendSubmitMessage() {
|
||||
// Add loading animation
|
||||
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker.svg");
|
||||
playerButtons.submit.button.style.animation = "rotate 1s 0s infinite";
|
||||
|
||||
//check if a sponsor exceeds the duration of the video
|
||||
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
|
||||
@@ -1493,17 +1461,19 @@ async function sendSubmitMessage(): Promise<void> {
|
||||
const response = await utils.asyncRequestToServer("POST", "/api/skipSegments", {
|
||||
videoID: sponsorVideoID,
|
||||
userID: Config.config.userID,
|
||||
segments: sponsorTimesSubmitting
|
||||
segments: sponsorTimesSubmitting,
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
//hide loading message
|
||||
const submitButton = document.getElementById("submitButton");
|
||||
// Handle submission success
|
||||
const submitButton = playerButtons.submit.button;
|
||||
|
||||
// Make the animation finite
|
||||
submitButton.style.animation = "rotate 1s";
|
||||
//finish this animation
|
||||
//when the animation is over, hide the button
|
||||
const animationEndListener = function() {
|
||||
changeStartSponsorButton(true, false);
|
||||
|
||||
// When the animation is over, hide the button
|
||||
const animationEndListener = () => {
|
||||
updateEditButtonsOnPlayer();
|
||||
|
||||
submitButton.style.animation = "none";
|
||||
|
||||
@@ -1512,13 +1482,11 @@ async function sendSubmitMessage(): Promise<void> {
|
||||
|
||||
submitButton.addEventListener("animationend", animationEndListener);
|
||||
|
||||
//clear the sponsor times
|
||||
// Remove segments from storage since they've already been submitted
|
||||
Config.config.segmentTimes.delete(sponsorVideoID);
|
||||
|
||||
//add submissions to current sponsors list
|
||||
if (sponsorTimes === null) sponsorTimes = [];
|
||||
|
||||
sponsorTimes = sponsorTimes.concat(sponsorTimesSubmitting);
|
||||
// Add submissions to current sponsors list
|
||||
sponsorTimes = (sponsorTimes || []).concat(sponsorTimesSubmitting);
|
||||
|
||||
// Increase contribution count
|
||||
Config.config.sponsorTimesContributed = Config.config.sponsorTimesContributed + sponsorTimesSubmitting.length;
|
||||
@@ -1532,11 +1500,11 @@ async function sendSubmitMessage(): Promise<void> {
|
||||
|
||||
updatePreviewBar();
|
||||
} else {
|
||||
//show that the upload failed
|
||||
document.getElementById("submitButton").style.animation = "unset";
|
||||
(<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker256px.png");
|
||||
// Show that the upload failed
|
||||
playerButtons.submit.button.style.animation = "unset";
|
||||
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker.svg");
|
||||
|
||||
alert(utils.getErrorMessage(response.status) + "\n\n" + (response.responseText));
|
||||
alert(utils.getErrorMessage(response.status, response.responseText));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1562,12 +1530,41 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
|
||||
return sponsorTimesMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an unlisted YouTube video.
|
||||
* Assumes that the the privacy info is available.
|
||||
*/
|
||||
function isUnlisted(): boolean {
|
||||
return videoInfo?.microformat?.playerMicroformatRenderer?.isUnlisted || videoInfo?.videoDetails?.isPrivate;
|
||||
function addHotkeyListener(): boolean {
|
||||
let videoRoot = document.getElementById("movie_player") as HTMLDivElement;
|
||||
if (onInvidious) videoRoot = (document.getElementById("player-container") ?? document.getElementById("player")) as HTMLDivElement;
|
||||
if (video.baseURI.startsWith("https://www.youtube.com/tv#/")) videoRoot = document.querySelector("ytlr-watch-page") as HTMLDivElement;
|
||||
|
||||
if (videoRoot && !videoRootsWithEventListeners.includes(videoRoot)) {
|
||||
videoRoot.addEventListener("keydown", hotkeyListener);
|
||||
videoRootsWithEventListeners.push(videoRoot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function hotkeyListener(e: KeyboardEvent): void {
|
||||
const key = e.key;
|
||||
|
||||
const skipKey = Config.config.skipKeybind;
|
||||
const startSponsorKey = Config.config.startSponsorKeybind;
|
||||
const submitKey = Config.config.submitKeybind;
|
||||
|
||||
switch (key) {
|
||||
case skipKey:
|
||||
if (skipNotices.length > 0) {
|
||||
const latestSkipNotice = skipNotices[skipNotices.length - 1];
|
||||
latestSkipNotice.toggleSkip.call(latestSkipNotice);
|
||||
}
|
||||
break;
|
||||
case startSponsorKey:
|
||||
startOrEndTimingNewSegment();
|
||||
break;
|
||||
case submitKey:
|
||||
submitSponsorTimes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,6 +1642,8 @@ function showTimeWithoutSkips(skippedDuration: number): void {
|
||||
|
||||
display.appendChild(duration);
|
||||
}
|
||||
|
||||
const durationAfterSkips = utils.getFormattedTime(video.duration - skippedDuration)
|
||||
|
||||
duration.innerText = skippedDuration <= 0 ? "" : " (" + utils.getFormattedTime(video.duration - skippedDuration) + ")";
|
||||
duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")";
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ interface DefaultMessage {
|
||||
message:
|
||||
"update"
|
||||
| "sponsorStart"
|
||||
| "sponsorDataChanged"
|
||||
| "isInfoFound"
|
||||
| "getVideoID"
|
||||
| "getChannelID"
|
||||
@@ -25,13 +24,7 @@ interface BoolValueMessage {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface ChangeStartSponsorButtonMessage {
|
||||
message: "changeStartSponsorButton";
|
||||
showStartSponsor: boolean;
|
||||
uploadButtonVisible: boolean;
|
||||
}
|
||||
|
||||
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | ChangeStartSponsorButtonMessage);
|
||||
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage);
|
||||
|
||||
interface IsInfoFoundMessageResponse {
|
||||
found: boolean;
|
||||
@@ -47,7 +40,7 @@ interface GetChannelIDResponse {
|
||||
}
|
||||
|
||||
interface SponsorStartResponse {
|
||||
time: number;
|
||||
creatingSegment: boolean;
|
||||
}
|
||||
|
||||
interface IsChannelWhitelistedResponse {
|
||||
|
||||
@@ -288,7 +288,7 @@ function invidiousInit(checkbox: HTMLInputElement, option: string) {
|
||||
if (utils.isFirefox()) permissions = [];
|
||||
|
||||
chrome.permissions.contains({
|
||||
origins: utils.getInvidiousInstancesRegex(),
|
||||
origins: utils.getPermissionRegex(),
|
||||
permissions: permissions
|
||||
}, function (result) {
|
||||
if (result != checkbox.checked) {
|
||||
|
||||
35
src/permissions.ts
Normal file
35
src/permissions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import Config from "./config";
|
||||
import Utils from "./utils";
|
||||
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);
|
||||
|
||||
async function init() {
|
||||
utils.localizeHtmlPage();
|
||||
|
||||
const domains = document.location.hash.replace("#", "").split(",");
|
||||
|
||||
const acceptButton = document.getElementById("acceptPermissionButton");
|
||||
acceptButton.addEventListener("click", () => {
|
||||
chrome.permissions.request({
|
||||
origins: utils.getPermissionRegex(domains),
|
||||
permissions: []
|
||||
}, (granted) => {
|
||||
if (granted) {
|
||||
alert(chrome.i18n.getMessage("permissionRequestSuccess"));
|
||||
|
||||
Config.config.ytInfoPermissionGranted = true;
|
||||
|
||||
chrome.tabs.getCurrent((tab) => {
|
||||
chrome.tabs.remove(tab.id);
|
||||
});
|
||||
} else {
|
||||
alert(chrome.i18n.getMessage("permissionRequestFailed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
173
src/popup.ts
173
src/popup.ts
@@ -126,8 +126,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
PageElements.optionsButton.addEventListener("click", openOptions);
|
||||
PageElements.helpButton.addEventListener("click", openHelp);
|
||||
|
||||
//if true, the button now selects the end time
|
||||
let startTimeChosen = false;
|
||||
/** If true, the content script is in the process of creating a new segment. */
|
||||
let creatingSegment = false;
|
||||
|
||||
//the start and end time pairs (2d)
|
||||
let sponsorTimes: SponsorTime[] = [];
|
||||
@@ -233,11 +233,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
|
||||
function onTabs(tabs) {
|
||||
messageHandler.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) {
|
||||
if (result != undefined && result.videoID) {
|
||||
if (result !== undefined && result.videoID) {
|
||||
currentVideoID = result.videoID;
|
||||
creatingSegment = result.creatingSegment;
|
||||
|
||||
loadTabData(tabs);
|
||||
} else if (result == undefined && chrome.runtime.lastError) {
|
||||
// this isn't a YouTube video then, or at least the content script is not loaded
|
||||
} else if (result === undefined && chrome.runtime.lastError) {
|
||||
//this isn't a YouTube video then, or at least the content script is not loaded
|
||||
displayNoVideo();
|
||||
}
|
||||
});
|
||||
@@ -253,19 +255,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
//load video times for this video
|
||||
const sponsorTimesStorage = Config.config.segmentTimes.get(currentVideoID);
|
||||
if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) {
|
||||
if (sponsorTimesStorage[sponsorTimesStorage.length - 1] != undefined && sponsorTimesStorage[sponsorTimesStorage.length - 1].segment.length < 2) {
|
||||
startTimeChosen = true;
|
||||
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
|
||||
}
|
||||
|
||||
sponsorTimes = sponsorTimesStorage;
|
||||
|
||||
//show submission section
|
||||
PageElements.submissionSection.style.display = "unset";
|
||||
|
||||
showSubmitTimesIfNecessary();
|
||||
}
|
||||
|
||||
updateSegmentEditingUI();
|
||||
|
||||
//check if this video's sponsors are known
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
@@ -321,51 +315,44 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
//the content script will get the message if a YouTube page is open
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
currentWindow: true,
|
||||
}, (tabs) => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{from: 'popup', message: 'sponsorStart'},
|
||||
startSponsorCallback
|
||||
async (response) => {
|
||||
startSponsorCallback(response);
|
||||
|
||||
// Perform a second update after the config changes take effect as a workaround for a race condition
|
||||
const removeListener = (listener: typeof lateUpdate) => {
|
||||
const index = Config.configListeners.indexOf(listener);
|
||||
if (index !== -1) Config.configListeners.splice(index, 1);
|
||||
};
|
||||
|
||||
const lateUpdate = () => {
|
||||
startSponsorCallback(response);
|
||||
removeListener(lateUpdate);
|
||||
};
|
||||
|
||||
Config.configListeners.push(lateUpdate);
|
||||
|
||||
// Remove the listener after 200ms in case the changes were propagated by the time we got the response
|
||||
setTimeout(() => removeListener(lateUpdate), 200);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function startSponsorCallback(response) {
|
||||
const sponsorTimesIndex = sponsorTimes.length - (startTimeChosen ? 1 : 0);
|
||||
function startSponsorCallback(response: {creatingSegment: boolean}) {
|
||||
creatingSegment = response.creatingSegment;
|
||||
|
||||
if (sponsorTimes[sponsorTimesIndex] == undefined) {
|
||||
sponsorTimes[sponsorTimesIndex] = {
|
||||
segment: [],
|
||||
category: Config.config.defaultCategory,
|
||||
UUID: null
|
||||
};
|
||||
// Only update the segments after a segment was created
|
||||
if (!creatingSegment) {
|
||||
sponsorTimes = Config.config.segmentTimes.get(currentVideoID) || [];
|
||||
}
|
||||
|
||||
sponsorTimes[sponsorTimesIndex].segment[startTimeChosen ? 1 : 0] = response.time;
|
||||
|
||||
const localStartTimeChosen = startTimeChosen;
|
||||
Config.config.segmentTimes.set(currentVideoID, sponsorTimes);
|
||||
|
||||
//send a message to the client script
|
||||
if (localStartTimeChosen) {
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{message: "sponsorDataChanged"}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
updateStartTimeChosen();
|
||||
|
||||
//show submission section
|
||||
PageElements.submissionSection.style.display = "unset";
|
||||
|
||||
showSubmitTimesIfNecessary();
|
||||
// Update the UI
|
||||
updateSegmentEditingUI();
|
||||
}
|
||||
|
||||
//display the video times from the array at the top, in a different section
|
||||
@@ -400,7 +387,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
|
||||
}
|
||||
|
||||
const segmentTimeFromTo = getFormattedTime(segmentTimes[i].segment[0]) + " " + chrome.i18n.getMessage("to") + " " + getFormattedTime(segmentTimes[i].segment[1]);
|
||||
const segmentTimeFromTo = utils.getFormattedTime(segmentTimes[i].segment[0], true) + " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true);
|
||||
|
||||
const segmentTimeFromToNode = document.createTextNode(segmentTimeFromTo);
|
||||
const textNode = document.createTextNode(utils.shortCategoryName(segmentTimes[i].category) + extraInfo);
|
||||
@@ -409,7 +396,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
sponsorTimeButton.appendChild(categoryColorCircle);
|
||||
sponsorTimeButton.appendChild(textNode);
|
||||
|
||||
|
||||
const votingButtons = document.createElement("div");
|
||||
votingButtons.classList.add("votingButtons");
|
||||
|
||||
@@ -431,9 +417,18 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
downvoteButton.src = chrome.extension.getURL("icons/thumbs_down.svg");
|
||||
downvoteButton.addEventListener("click", () => vote(0, UUID));
|
||||
|
||||
//add thumbs up and down buttons to the container
|
||||
//uuid button
|
||||
|
||||
const uuidButton = document.createElement("img");
|
||||
uuidButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID;
|
||||
uuidButton.className = "voteButton";
|
||||
uuidButton.src = chrome.extension.getURL("icons/clipboard.svg");
|
||||
uuidButton.addEventListener("click", () => navigator.clipboard.writeText(UUID));
|
||||
|
||||
//add thumbs up, thumbs down and uuid copy buttons to the container
|
||||
voteButtonsContainer.appendChild(upvoteButton);
|
||||
voteButtonsContainer.appendChild(downvoteButton);
|
||||
voteButtonsContainer.appendChild(uuidButton);
|
||||
|
||||
//add click listener to open up vote panel
|
||||
sponsorTimeButton.addEventListener("click", function() {
|
||||
@@ -480,34 +475,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
PageElements.showNoticeAgain.style.display = "none";
|
||||
}
|
||||
|
||||
function updateStartTimeChosen() {
|
||||
//update startTimeChosen letiable
|
||||
if (!startTimeChosen) {
|
||||
startTimeChosen = true;
|
||||
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
|
||||
} else {
|
||||
resetStartTimeChosen();
|
||||
}
|
||||
/** Updates any UI related to segment editing and submission according to the current state. */
|
||||
function updateSegmentEditingUI() {
|
||||
PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart");
|
||||
|
||||
PageElements.submissionSection.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
|
||||
}
|
||||
|
||||
//set it to false
|
||||
function resetStartTimeChosen() {
|
||||
startTimeChosen = false;
|
||||
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorStart");
|
||||
}
|
||||
|
||||
//hides and shows the submit times button when needed
|
||||
function showSubmitTimesIfNecessary() {
|
||||
//check if an end time has been specified for the latest sponsor time
|
||||
if (sponsorTimes.length > 0 && sponsorTimes[sponsorTimes.length - 1].segment.length > 1) {
|
||||
//show submit times button
|
||||
document.getElementById("submitTimesContainer").style.display = "flex";
|
||||
} else {
|
||||
//hide submit times button
|
||||
document.getElementById("submitTimesContainer").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//make the options div visible
|
||||
function openOptions() {
|
||||
chrome.runtime.sendMessage({"message": "openConfig"});
|
||||
@@ -553,7 +527,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
|
||||
PageElements.sponsorTimesContributionsContainer.classList.remove("hidden");
|
||||
} else {
|
||||
PageElements.setUsernameStatus.innerText = utils.getErrorMessage(response.status);
|
||||
PageElements.setUsernameStatus.innerText = utils.getErrorMessage(response.status, response.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -580,41 +554,26 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
|
||||
function vote(type, UUID) {
|
||||
//add loading info
|
||||
addVoteMessage(chrome.i18n.getMessage("Loading"), UUID)
|
||||
|
||||
addVoteMessage(chrome.i18n.getMessage("Loading"), UUID);
|
||||
|
||||
//send the vote message to the tab
|
||||
chrome.runtime.sendMessage({
|
||||
message: "submitVote",
|
||||
type: type,
|
||||
UUID: UUID
|
||||
}, function(response) {
|
||||
}, function (response) {
|
||||
if (response != undefined) {
|
||||
//see if it was a success or failure
|
||||
if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) {
|
||||
//success (treat rate limits as a success)
|
||||
addVoteMessage(chrome.i18n.getMessage("voted"), UUID)
|
||||
addVoteMessage(chrome.i18n.getMessage("voted"), UUID);
|
||||
} else if (response.successType == -1) {
|
||||
addVoteMessage(utils.getErrorMessage(response.statusCode), UUID)
|
||||
addVoteMessage(utils.getErrorMessage(response.statusCode, response.responseText), UUID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//converts time in seconds to minutes:seconds
|
||||
function getFormattedTime(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const secondsDisplayNumber = Math.round(seconds - minutes * 60);
|
||||
let secondsDisplay = String(secondsDisplayNumber);
|
||||
if (secondsDisplayNumber < 10) {
|
||||
//add a zero
|
||||
secondsDisplay = "0" + secondsDisplay;
|
||||
}
|
||||
|
||||
const formatted = minutes + ":" + secondsDisplay;
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
function whitelistChannel() {
|
||||
//get the channel url
|
||||
messageHandler.query({
|
||||
@@ -626,8 +585,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
{message: 'getChannelID'},
|
||||
function(response) {
|
||||
if (!response.channelID) {
|
||||
alert(chrome.i18n.getMessage("channelDataNotFound") + "\n\n" +
|
||||
chrome.i18n.getMessage("itCouldBeAdblockerIssue"));
|
||||
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -737,10 +695,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
* @param {float} seconds
|
||||
* @returns {string}
|
||||
*/
|
||||
function getFormattedHours(minues) {
|
||||
const hours = Math.floor(minues / 60);
|
||||
return (hours > 0 ? hours + "h " : "") + (minues % 60).toFixed(1);
|
||||
}
|
||||
function getFormattedHours(minutes) {
|
||||
minutes = Math.round(minutes * 10) / 10
|
||||
const hours = Math.floor(minutes / 60);
|
||||
return (hours > 0 ? hours + "h " : "") + (minutes % 60).toFixed(1);
|
||||
}
|
||||
|
||||
//end of function
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
import SkipNoticeComponent from "../components/SkipNoticeComponent";
|
||||
import SkipNoticeComponent, { SkipNoticeAction } from "../components/SkipNoticeComponent";
|
||||
import { SponsorTime, ContentContainer } from "../types";
|
||||
|
||||
class SkipNotice {
|
||||
@@ -15,6 +15,8 @@ class SkipNotice {
|
||||
skipNoticeRef: React.MutableRefObject<SkipNoticeComponent>;
|
||||
|
||||
constructor(segments: SponsorTime[], autoSkip = false, contentContainer: ContentContainer) {
|
||||
this.skipNoticeRef = React.createRef();
|
||||
|
||||
this.segments = segments;
|
||||
this.autoSkip = autoSkip;
|
||||
this.contentContainer = contentContainer;
|
||||
@@ -67,6 +69,13 @@ class SkipNotice {
|
||||
ReactDOM.unmountComponentAtNode(this.noticeElement);
|
||||
|
||||
this.noticeElement.remove();
|
||||
|
||||
const skipNotices = this.contentContainer().skipNotices;
|
||||
skipNotices.splice(skipNotices.indexOf(this), 1);
|
||||
}
|
||||
|
||||
toggleSkip(): void {
|
||||
this.skipNoticeRef.current.prepAction(SkipNoticeAction.Unskip);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
59
src/types.ts
59
src/types.ts
@@ -1,13 +1,15 @@
|
||||
import SubmissionNotice from "./render/SubmissionNotice";
|
||||
import SkipNoticeComponent from "./components/SkipNoticeComponent";
|
||||
import SkipNotice from "./render/SkipNotice";
|
||||
|
||||
interface ContentContainer {
|
||||
export interface ContentContainer {
|
||||
(): {
|
||||
vote: (type: number, UUID: string, category?: string, skipNotice?: SkipNoticeComponent) => void,
|
||||
dontShowNoticeAgain: () => void,
|
||||
unskipSponsorTime: (segment: SponsorTime) => void,
|
||||
sponsorTimes: SponsorTime[],
|
||||
sponsorTimesSubmitting: SponsorTime[],
|
||||
skipNotices: SkipNotice[],
|
||||
v: HTMLVideoElement,
|
||||
sponsorVideoID,
|
||||
reskipSponsorTime: (segment: SponsorTime) => void,
|
||||
@@ -15,42 +17,42 @@ interface ContentContainer {
|
||||
onMobileYouTube: boolean,
|
||||
sponsorSubmissionNotice: SubmissionNotice,
|
||||
resetSponsorSubmissionNotice: () => void,
|
||||
changeStartSponsorButton: (showStartSponsor: boolean, uploadButtonVisible: boolean) => Promise<boolean>,
|
||||
updateEditButtonsOnPlayer: () => void,
|
||||
previewTime: (time: number, unpause?: boolean) => void,
|
||||
videoInfo: VideoInfo,
|
||||
getRealCurrentTime: () => number
|
||||
}
|
||||
}
|
||||
|
||||
interface FetchResponse {
|
||||
export interface FetchResponse {
|
||||
responseText: string,
|
||||
status: number,
|
||||
ok: boolean
|
||||
}
|
||||
|
||||
interface VideoDurationResponse {
|
||||
export interface VideoDurationResponse {
|
||||
duration: number;
|
||||
}
|
||||
|
||||
enum CategorySkipOption {
|
||||
export enum CategorySkipOption {
|
||||
ShowOverlay,
|
||||
ManualSkip,
|
||||
AutoSkip
|
||||
}
|
||||
|
||||
interface CategorySelection {
|
||||
export interface CategorySelection {
|
||||
name: string;
|
||||
option: CategorySkipOption
|
||||
}
|
||||
|
||||
enum SponsorHideType {
|
||||
export enum SponsorHideType {
|
||||
Visible = undefined,
|
||||
Downvoted = 1,
|
||||
MinimumDuration
|
||||
}
|
||||
|
||||
interface SponsorTime {
|
||||
segment: number[];
|
||||
export interface SponsorTime {
|
||||
segment: [number] | [number, number];
|
||||
UUID: string;
|
||||
|
||||
category: string;
|
||||
@@ -58,13 +60,13 @@ interface SponsorTime {
|
||||
hidden?: SponsorHideType;
|
||||
}
|
||||
|
||||
interface PreviewBarOption {
|
||||
export interface PreviewBarOption {
|
||||
color: string,
|
||||
opacity: string
|
||||
}
|
||||
|
||||
|
||||
interface Registration {
|
||||
export interface Registration {
|
||||
message: string,
|
||||
id: string,
|
||||
allFrames: boolean,
|
||||
@@ -73,12 +75,12 @@ interface Registration {
|
||||
matches: string[]
|
||||
}
|
||||
|
||||
interface BackgroundScriptContainer {
|
||||
export interface BackgroundScriptContainer {
|
||||
registerFirefoxContentScript: (opts: Registration) => void,
|
||||
unregisterFirefoxContentScript: (id: string) => void
|
||||
}
|
||||
|
||||
interface VideoInfo {
|
||||
export interface VideoInfo {
|
||||
responseContext: {
|
||||
serviceTrackingParams: Array<{service: string, params: Array<{key: string, value: string}>}>,
|
||||
webResponseContextExtensionData: {
|
||||
@@ -154,22 +156,19 @@ interface VideoInfo {
|
||||
messages: unknown;
|
||||
}
|
||||
|
||||
type VideoID = string;
|
||||
export type VideoID = string;
|
||||
|
||||
type StorageChangesObject = { [key: string]: chrome.storage.StorageChange };
|
||||
export type StorageChangesObject = { [key: string]: chrome.storage.StorageChange };
|
||||
|
||||
export {
|
||||
FetchResponse,
|
||||
VideoDurationResponse,
|
||||
ContentContainer,
|
||||
CategorySelection,
|
||||
CategorySkipOption,
|
||||
SponsorTime,
|
||||
VideoID,
|
||||
SponsorHideType,
|
||||
PreviewBarOption,
|
||||
Registration,
|
||||
BackgroundScriptContainer,
|
||||
VideoInfo,
|
||||
StorageChangesObject,
|
||||
};
|
||||
export type UnEncodedSegmentTimes = [string, SponsorTime[]][];
|
||||
|
||||
export enum ChannelIDStatus {
|
||||
Fetching,
|
||||
Found,
|
||||
Failed
|
||||
}
|
||||
|
||||
export interface ChannelIDInfo {
|
||||
id: string,
|
||||
status: ChannelIDStatus
|
||||
}
|
||||
55
src/utils.ts
55
src/utils.ts
@@ -3,10 +3,10 @@ import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContaine
|
||||
|
||||
import * as CompileConfig from "../config.json";
|
||||
|
||||
class Utils {
|
||||
export default class Utils {
|
||||
|
||||
// Contains functions needed from the background script
|
||||
backgroundScriptContainer: BackgroundScriptContainer | null = null;
|
||||
backgroundScriptContainer: BackgroundScriptContainer | null;
|
||||
|
||||
// Used to add content scripts and CSS required
|
||||
js = [
|
||||
@@ -19,12 +19,12 @@ class Utils {
|
||||
"popup.css"
|
||||
];
|
||||
|
||||
constructor(backgroundScriptContainer?: BackgroundScriptContainer) {
|
||||
constructor(backgroundScriptContainer: BackgroundScriptContainer = null) {
|
||||
this.backgroundScriptContainer = backgroundScriptContainer;
|
||||
}
|
||||
|
||||
// Function that can be used to wait for a condition before returning
|
||||
async wait(condition: () => HTMLElement | boolean, timeout = 5000, check = 100): Promise<HTMLElement | boolean> {
|
||||
/** Function that can be used to wait for a condition before returning. */
|
||||
async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
setTimeout(() => reject("TIMEOUT"), timeout);
|
||||
|
||||
@@ -43,6 +43,12 @@ class Utils {
|
||||
});
|
||||
}
|
||||
|
||||
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
chrome.permissions.contains(permissions, resolve)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks for the optional permissions required for all extra sites.
|
||||
* It also starts the content script registrations.
|
||||
@@ -57,7 +63,7 @@ class Utils {
|
||||
if (this.isFirefox()) permissions = [];
|
||||
|
||||
chrome.permissions.request({
|
||||
origins: this.getInvidiousInstancesRegex(),
|
||||
origins: this.getPermissionRegex(),
|
||||
permissions: permissions
|
||||
}, async (granted) => {
|
||||
if (granted) {
|
||||
@@ -78,7 +84,6 @@ class Utils {
|
||||
* For now, it is just SB.config.invidiousInstances.
|
||||
*/
|
||||
setupExtraSiteContentScripts(): void {
|
||||
|
||||
if (this.isFirefox()) {
|
||||
const firefoxJS = [];
|
||||
for (const file of this.js) {
|
||||
@@ -95,7 +100,7 @@ class Utils {
|
||||
allFrames: true,
|
||||
js: firefoxJS,
|
||||
css: firefoxCSS,
|
||||
matches: this.getInvidiousInstancesRegex()
|
||||
matches: this.getPermissionRegex()
|
||||
};
|
||||
|
||||
if (this.backgroundScriptContainer) {
|
||||
@@ -106,7 +111,7 @@ class Utils {
|
||||
} else {
|
||||
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], () => {
|
||||
const conditions = [];
|
||||
for (const regex of this.getInvidiousInstancesRegex()) {
|
||||
for (const regex of this.getPermissionRegex()) {
|
||||
conditions.push(new chrome.declarativeContent.PageStateMatcher({
|
||||
pageUrl: { urlMatches: regex }
|
||||
}));
|
||||
@@ -149,7 +154,7 @@ class Utils {
|
||||
}
|
||||
|
||||
chrome.permissions.remove({
|
||||
origins: this.getInvidiousInstancesRegex()
|
||||
origins: this.getPermissionRegex()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,16 +255,20 @@ class Utils {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String[]} Invidious Instances in regex form
|
||||
* @returns {String[]} Domains in regex form
|
||||
*/
|
||||
getInvidiousInstancesRegex(): string[] {
|
||||
const invidiousInstancesRegex: string[] = [];
|
||||
for (const url of Config.config.invidiousInstances) {
|
||||
invidiousInstancesRegex.push("https://*." + url + "/*");
|
||||
invidiousInstancesRegex.push("http://*." + url + "/*");
|
||||
getPermissionRegex(domains: string[] = []): string[] {
|
||||
const permissionRegex: string[] = [];
|
||||
if (domains.length === 0) {
|
||||
domains = [...Config.config.invidiousInstances];
|
||||
}
|
||||
|
||||
return invidiousInstancesRegex;
|
||||
for (const url of domains) {
|
||||
permissionRegex.push("https://*." + url + "/*");
|
||||
permissionRegex.push("http://*." + url + "/*");
|
||||
}
|
||||
|
||||
return permissionRegex;
|
||||
}
|
||||
|
||||
generateUserID(length = 36): string {
|
||||
@@ -286,10 +295,11 @@ class Utils {
|
||||
* @param {int} statusCode
|
||||
* @returns {string} errorMessage
|
||||
*/
|
||||
getErrorMessage(statusCode: number): string {
|
||||
getErrorMessage(statusCode: number, responseText: string): string {
|
||||
let errorMessage = "";
|
||||
const postFix = (responseText ? "\n\n" + responseText : "");
|
||||
|
||||
if([400, 429, 409, 502, 0].includes(statusCode)) {
|
||||
if([400, 429, 409, 502, 503, 0].includes(statusCode)) {
|
||||
//treat them the same
|
||||
if (statusCode == 503) statusCode = 502;
|
||||
|
||||
@@ -299,7 +309,7 @@ class Utils {
|
||||
errorMessage = chrome.i18n.getMessage("connectionError") + statusCode;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
return errorMessage + postFix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -375,6 +385,9 @@ class Utils {
|
||||
//add a zero
|
||||
minutesDisplay = "0" + minutesDisplay;
|
||||
}
|
||||
if (isNaN(hours) || isNaN(minutes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formatted = (hours ? hours + ":" : "") + minutesDisplay + ":" + secondsDisplay;
|
||||
|
||||
@@ -430,5 +443,3 @@ class Utils {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Utils;
|
||||
|
||||
Reference in New Issue
Block a user