mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-12 22:47:18 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/PickleNik/1300
This commit is contained in:
@@ -23,14 +23,37 @@ if (utils.isFirefox()) {
|
||||
});
|
||||
}
|
||||
|
||||
chrome.tabs.onUpdated.addListener(function(tabId) {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
function onTabUpdatedListener(tabId: number) {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
message: 'update',
|
||||
}, () => void chrome.runtime.lastError ); // Suppress error on Firefox
|
||||
}, () => void chrome.runtime.lastError ); // Suppress error on Firefox
|
||||
}
|
||||
|
||||
function onNavigationApiAvailableChange(changes: {[key: string]: chrome.storage.StorageChange}) {
|
||||
if (changes.navigationApiAvailable) {
|
||||
if (changes.navigationApiAvailable.newValue) {
|
||||
chrome.tabs.onUpdated.removeListener(onTabUpdatedListener);
|
||||
} else {
|
||||
chrome.tabs.onUpdated.addListener(onTabUpdatedListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If Navigation API is not supported, then background has to inform content script about video change.
|
||||
// This happens on Safari, Firefox, and Chromium 101 (inclusive) and below.
|
||||
chrome.tabs.onUpdated.addListener(onTabUpdatedListener);
|
||||
utils.wait(() => Config.local !== null).then(() => {
|
||||
if (Config.local.navigationApiAvailable) {
|
||||
chrome.tabs.onUpdated.removeListener(onTabUpdatedListener);
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
switch(request.message) {
|
||||
if (!Config.configSyncListeners.includes(onNavigationApiAvailableChange)) {
|
||||
Config.configSyncListeners.push(onNavigationApiAvailableChange);
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(function (request, _, callback) {
|
||||
switch(request.message) {
|
||||
case "openConfig":
|
||||
chrome.tabs.create({url: chrome.runtime.getURL('options/options.html' + (request.hash ? '#' + request.hash : ''))});
|
||||
return;
|
||||
@@ -61,6 +84,19 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
case "unregisterContentScript":
|
||||
unregisterFirefoxContentScript(request.id)
|
||||
return false;
|
||||
case "tabs":
|
||||
chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
chrome.tabs.sendMessage(
|
||||
tabs[0].id,
|
||||
request.data,
|
||||
(response) => callback(response)
|
||||
);
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface CategorySkipOptionsState {
|
||||
}
|
||||
|
||||
class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsProps, CategorySkipOptionsState> {
|
||||
setBarColorTimeout: NodeJS.Timeout;
|
||||
|
||||
constructor(props: CategorySkipOptionsProps) {
|
||||
super(props);
|
||||
@@ -172,6 +173,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
}
|
||||
|
||||
setColorState(event: React.FormEvent<HTMLInputElement>, preview: boolean): void {
|
||||
clearTimeout(this.setBarColorTimeout);
|
||||
|
||||
if (preview) {
|
||||
this.setState({
|
||||
previewColor: event.currentTarget.value
|
||||
@@ -188,7 +191,9 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
}
|
||||
|
||||
// Make listener get called
|
||||
Config.config.barTypes = Config.config.barTypes;
|
||||
this.setBarColorTimeout = setTimeout(() => {
|
||||
Config.config.barTypes = Config.config.barTypes;
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface NoticeProps {
|
||||
|
||||
smaller?: boolean,
|
||||
limitWidth?: boolean,
|
||||
extraClass?: string,
|
||||
|
||||
// Callback for when this is closed
|
||||
closeListener: () => void,
|
||||
@@ -35,8 +36,6 @@ export interface NoticeProps {
|
||||
}
|
||||
|
||||
export interface NoticeState {
|
||||
noticeTitle: string,
|
||||
|
||||
maxCountdownTime: () => number,
|
||||
|
||||
countdownTime: number,
|
||||
@@ -54,9 +53,13 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
|
||||
amountOfPreviousNotices: number;
|
||||
|
||||
parentRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: NoticeProps) {
|
||||
super(props);
|
||||
|
||||
this.parentRef = React.createRef();
|
||||
|
||||
const maxCountdownTime = () => {
|
||||
if (this.props.maxCountdownTime) return this.props.maxCountdownTime();
|
||||
else return Config.config.skipNoticeDuration;
|
||||
@@ -71,8 +74,6 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
|
||||
// Setup state
|
||||
this.state = {
|
||||
noticeTitle: props.noticeTitle,
|
||||
|
||||
maxCountdownTime,
|
||||
|
||||
//the countdown until this notice closes
|
||||
@@ -97,9 +98,11 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
return (
|
||||
<div id={"sponsorSkipNotice" + this.idSuffix}
|
||||
className={"sponsorSkipObject sponsorSkipNoticeParent"
|
||||
+ (this.props.showInSecondSlot ? " secondSkipNotice" : "")}
|
||||
+ (this.props.showInSecondSlot ? " secondSkipNotice" : "")
|
||||
+ (this.props.extraClass ? ` ${this.props.extraClass}` : "")}
|
||||
onMouseEnter={(e) => this.onMouseEnter(e) }
|
||||
onMouseLeave={() => this.timerMouseLeave()}
|
||||
ref={this.parentRef}
|
||||
style={noticeStyle} >
|
||||
<div className={"sponsorSkipNoticeTableContainer"
|
||||
+ (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")
|
||||
@@ -123,7 +126,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
style={{float: "left"}}
|
||||
className="sponsorSkipMessage sponsorSkipObject">
|
||||
|
||||
{this.state.noticeTitle}
|
||||
{this.props.noticeTitle}
|
||||
</span>
|
||||
|
||||
{this.props.firstColumn}
|
||||
@@ -344,12 +347,6 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
if (!silent) this.props.closeListener();
|
||||
}
|
||||
|
||||
changeNoticeTitle(title: string): void {
|
||||
this.setState({
|
||||
noticeTitle: title
|
||||
});
|
||||
}
|
||||
|
||||
addNoticeInfoMessage(message: string, message2 = ""): void {
|
||||
//TODO: Replace
|
||||
|
||||
@@ -384,6 +381,10 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
document.querySelector("#sponsorSkipNotice" + this.idSuffix + " > tbody").insertBefore(thanksForVotingText2, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix));
|
||||
}
|
||||
}
|
||||
|
||||
getElement(): React.RefObject<HTMLDivElement> {
|
||||
return this.parentRef;
|
||||
}
|
||||
}
|
||||
|
||||
export default NoticeComponent;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface NoticeTextSelectionProps {
|
||||
icon?: string,
|
||||
text: string,
|
||||
idSuffix: string,
|
||||
onClick?: (event: React.MouseEvent) => unknown
|
||||
@@ -24,12 +25,21 @@ class NoticeTextSelectionComponent extends React.Component<NoticeTextSelectionPr
|
||||
}
|
||||
|
||||
return (
|
||||
<p id={"sponsorTimesInfoMessage" + this.props.idSuffix}
|
||||
<tr id={"sponsorTimesInfoMessage" + this.props.idSuffix}
|
||||
onClick={this.props.onClick}
|
||||
style={style}
|
||||
className="sponsorTimesInfoMessage">
|
||||
{this.props.text}
|
||||
</p>
|
||||
|
||||
<td>
|
||||
{this.props.icon ?
|
||||
<img src={chrome.runtime.getURL(this.props.icon)} className="sponsorTimesInfoIcon" />
|
||||
: null}
|
||||
|
||||
<span>
|
||||
{this.props.text}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import Config from "../config"
|
||||
import { Category, ContentContainer, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
|
||||
import { Category, ContentContainer, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
|
||||
import NoticeComponent from "./NoticeComponent";
|
||||
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
||||
import Utils from "../utils";
|
||||
@@ -72,7 +72,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
|
||||
amountOfPreviousNotices: number;
|
||||
showInSecondSlot: boolean;
|
||||
|
||||
|
||||
idSuffix: string;
|
||||
|
||||
noticeRef: React.MutableRefObject<NoticeComponent>;
|
||||
@@ -105,7 +105,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
if (this.segments.length > 1) {
|
||||
this.segments.sort((a, b) => a.segment[0] - b.segment[0]);
|
||||
}
|
||||
|
||||
|
||||
// This is the suffix added at the end of every id
|
||||
for (const segment of this.segments) {
|
||||
this.idSuffix += segment.UUID;
|
||||
@@ -168,7 +168,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
|
||||
}
|
||||
|
||||
// If it started out as smaller, always keep the
|
||||
// If it started out as smaller, always keep the
|
||||
// skip button there
|
||||
const showFirstSkipButton = this.props.smaller || this.segments[0].actionType === ActionType.Mute;
|
||||
const firstColumn = showFirstSkipButton ? (
|
||||
@@ -181,7 +181,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
showInSecondSlot={this.showInSecondSlot}
|
||||
idSuffix={this.idSuffix}
|
||||
fadeIn={true}
|
||||
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
|
||||
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
|
||||
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)}
|
||||
timed={true}
|
||||
maxCountdownTime={this.state.maxCountdownTime}
|
||||
@@ -205,7 +205,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
key={0}>
|
||||
|
||||
{/* Vote Button Container */}
|
||||
{!this.state.thanksForVotingText ?
|
||||
{!this.state.thanksForVotingText ?
|
||||
<td id={"sponsorTimesVoteButtonsContainer" + this.idSuffix}
|
||||
className="sponsorTimesVoteButtonsContainer">
|
||||
|
||||
@@ -268,7 +268,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
? this.getSkipButton(1) : null}
|
||||
|
||||
{/* Never show button */}
|
||||
{!this.autoSkip || this.props.startReskip ? "" :
|
||||
{!this.autoSkip || this.props.startReskip ? "" :
|
||||
<td className="sponsorSkipNoticeRightSection"
|
||||
key={1}>
|
||||
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
|
||||
@@ -343,7 +343,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
}
|
||||
|
||||
getSkipButton(buttonIndex: number): JSX.Element {
|
||||
if (this.state.showSkipButton[buttonIndex] && (this.segments.length > 1
|
||||
if (this.state.showSkipButton[buttonIndex] && (this.segments.length > 1
|
||||
|| this.segments[0].actionType !== ActionType.Poi
|
||||
|| this.props.unskipTime)) {
|
||||
|
||||
@@ -365,8 +365,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
className="sponsorSkipObject sponsorSkipNoticeButton"
|
||||
style={style}
|
||||
onClick={() => this.prepAction(buttonIndex === 1 ? SkipNoticeAction.Unskip1 : SkipNoticeAction.Unskip0)}>
|
||||
{this.getSkipButtonText(buttonIndex, forceSeek ? ActionType.Skip : null)
|
||||
+ (!forceSeek && this.state.showKeybindHint
|
||||
{this.getSkipButtonText(buttonIndex, forceSeek ? ActionType.Skip : null)
|
||||
+ (!forceSeek && this.state.showKeybindHint
|
||||
? " (" + keybindToString(Config.config.skipKeybind) + ")" : "")}
|
||||
</button>
|
||||
</span>
|
||||
@@ -379,7 +379,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
for (let i = 0; i < this.segments.length; i++) {
|
||||
elements.push(
|
||||
<button className="sponsorSkipObject sponsorSkipNoticeButton"
|
||||
style={{opacity: this.getSubmissionChooserOpacity(i),
|
||||
style={{opacity: this.getSubmissionChooserOpacity(i),
|
||||
color: this.getSubmissionChooserColor(i)}}
|
||||
onClick={() => this.performAction(i)}
|
||||
key={"submission" + i + this.segments[i].category + this.idSuffix}>
|
||||
@@ -404,7 +404,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
getSubmissionChooserColor(index: number): string {
|
||||
const isDownvote = this.state.actionState == SkipNoticeAction.Downvote;
|
||||
const isCopyDownvote = this.state.actionState == SkipNoticeAction.CopyDownvote;
|
||||
const shouldWarnUser = Config.config.isVip && (isDownvote || isCopyDownvote)
|
||||
const shouldWarnUser = Config.config.isVip && (isDownvote || isCopyDownvote)
|
||||
&& this.segments[index].locked === 1;
|
||||
|
||||
return shouldWarnUser ? this.lockedColor : this.unselectedColor;
|
||||
@@ -480,8 +480,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
|
||||
/**
|
||||
* Performs the action from the current state
|
||||
*
|
||||
* @param index
|
||||
*
|
||||
* @param index
|
||||
*/
|
||||
performAction(index: number, action?: SkipNoticeAction): void {
|
||||
switch (action ?? this.state.actionState) {
|
||||
@@ -617,11 +617,6 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
countdownTime: Config.config.skipNoticeDuration
|
||||
};
|
||||
|
||||
// See if the title should be changed
|
||||
if (!this.autoSkip) {
|
||||
newState.noticeTitle = chrome.i18n.getMessage("noticeTitle");
|
||||
}
|
||||
|
||||
//reset countdown
|
||||
this.setState(newState, () => {
|
||||
this.noticeRef.current.resetCountdown();
|
||||
@@ -723,7 +718,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
messages
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
addVoteButtonInfo(message: string): void {
|
||||
this.setState({
|
||||
thanksForVotingText: message
|
||||
@@ -786,7 +781,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
case ActionType.Mute: {
|
||||
return chrome.i18n.getMessage("unmute");
|
||||
}
|
||||
case ActionType.Skip:
|
||||
case ActionType.Skip:
|
||||
default: {
|
||||
return chrome.i18n.getMessage("unskip");
|
||||
}
|
||||
@@ -799,7 +794,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
case ActionType.Mute: {
|
||||
return chrome.i18n.getMessage("mute");
|
||||
}
|
||||
case ActionType.Skip:
|
||||
case ActionType.Skip:
|
||||
default: {
|
||||
return chrome.i18n.getMessage("reskip");
|
||||
}
|
||||
@@ -812,7 +807,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
case ActionType.Mute: {
|
||||
return chrome.i18n.getMessage("mute");
|
||||
}
|
||||
case ActionType.Skip:
|
||||
case ActionType.Skip:
|
||||
default: {
|
||||
return chrome.i18n.getMessage("skip");
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface SponsorTimeEditProps {
|
||||
|
||||
submissionNotice: SubmissionNoticeComponent;
|
||||
categoryList?: Category[];
|
||||
categoryChangeListener?: (index: number, category: Category) => void;
|
||||
}
|
||||
|
||||
export interface SponsorTimeEditState {
|
||||
@@ -365,9 +366,10 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
}
|
||||
|
||||
categorySelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
const chosenCategory = event.target.value as Category;
|
||||
|
||||
// See if show more categories was pressed
|
||||
if (event.target.value !== DEFAULT_CATEGORY && !Config.config.categorySelections.some((category) => category.name === event.target.value)) {
|
||||
const chosenCategory = event.target.value;
|
||||
event.target.value = DEFAULT_CATEGORY;
|
||||
|
||||
// Alert that they have to enable this category first
|
||||
@@ -381,8 +383,12 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
}
|
||||
|
||||
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
|
||||
this.handleReplacingLostTimes(event.target.value as Category, sponsorTime.actionType, sponsorTime);
|
||||
this.handleReplacingLostTimes(chosenCategory, sponsorTime.actionType, sponsorTime);
|
||||
this.saveEditTimes();
|
||||
|
||||
if (this.props.categoryChangeListener) {
|
||||
this.props.categoryChangeListener(this.props.index, chosenCategory);
|
||||
}
|
||||
}
|
||||
|
||||
actionTypeSelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import * as React from "react";
|
||||
import Config from "../config"
|
||||
import { ContentContainer } from "../types";
|
||||
import GenericNotice from "../render/GenericNotice";
|
||||
import { Category, ContentContainer } from "../types";
|
||||
import * as CompileConfig from "../../config.json";
|
||||
|
||||
import NoticeComponent from "./NoticeComponent";
|
||||
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
||||
import SponsorTimeEditComponent from "./SponsorTimeEditComponent";
|
||||
import { getGuidelineInfo } from "../utils/constants";
|
||||
|
||||
export interface SubmissionNoticeProps {
|
||||
// Contains functions and variables from the content script needed by the skip notice
|
||||
@@ -32,6 +35,8 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
|
||||
videoObserver: MutationObserver;
|
||||
|
||||
guidelinesReminder: GenericNotice;
|
||||
|
||||
constructor(props: SubmissionNoticeProps) {
|
||||
super(props);
|
||||
this.noticeRef = React.createRef();
|
||||
@@ -128,6 +133,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
index={i}
|
||||
contentContainer={this.props.contentContainer}
|
||||
submissionNotice={this}
|
||||
categoryChangeListener={this.categoryChangeListener.bind(this)}
|
||||
ref={timeRef}>
|
||||
</SponsorTimeEditComponent>
|
||||
);
|
||||
@@ -154,6 +160,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.guidelinesReminder?.close();
|
||||
this.noticeRef.current.close(true);
|
||||
|
||||
this.contentContainer().resetSponsorSubmissionNotice();
|
||||
@@ -190,6 +197,45 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
categoryChangeListener(index: number, category: Category): void {
|
||||
const dialogWidth = this.noticeRef?.current?.getElement()?.current?.offsetWidth;
|
||||
if (category !== "chooseACategory" && Config.config.showCategoryGuidelines
|
||||
&& this.contentContainer().v.offsetWidth > dialogWidth * 2) {
|
||||
const options = {
|
||||
title: chrome.i18n.getMessage(`category_${category}`),
|
||||
textBoxes: getGuidelineInfo(category),
|
||||
buttons: [{
|
||||
name: chrome.i18n.getMessage("FullDetails"),
|
||||
listener: () => window.open(CompileConfig.wikiLinks[category])
|
||||
},
|
||||
{
|
||||
name: chrome.i18n.getMessage("Hide"),
|
||||
listener: () => {
|
||||
Config.config.showCategoryGuidelines = false;
|
||||
this.guidelinesReminder?.close();
|
||||
this.guidelinesReminder = null;
|
||||
}
|
||||
}],
|
||||
timed: false,
|
||||
style: {
|
||||
right: `${dialogWidth + 10}px`,
|
||||
},
|
||||
extraClass: "sb-guidelines-notice"
|
||||
};
|
||||
|
||||
if (options.textBoxes) {
|
||||
if (this.guidelinesReminder) {
|
||||
this.guidelinesReminder.update(options);
|
||||
} else {
|
||||
this.guidelinesReminder = new GenericNotice(null, "GuidelinesReminder", options);
|
||||
}
|
||||
} else {
|
||||
this.guidelinesReminder?.close();
|
||||
this.guidelinesReminder = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SubmissionNoticeComponent;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import * as React from "react";
|
||||
import Config from "../config";
|
||||
import { Category, SegmentUUID, SponsorTime } from "../types";
|
||||
|
||||
export interface TooltipProps {
|
||||
text: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as CompileConfig from "../config.json";
|
||||
import * as invidiousList from "../ci/invidiouslist.json";
|
||||
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
|
||||
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
|
||||
import { keybindEquals } from "./utils/configUtils";
|
||||
|
||||
interface SBConfig {
|
||||
@@ -55,6 +55,7 @@ interface SBConfig {
|
||||
scrollToEditTimeUpdate: boolean,
|
||||
categoryPillUpdate: boolean,
|
||||
darkMode: boolean,
|
||||
showCategoryGuidelines: boolean,
|
||||
|
||||
// Used to cache calculated text color info
|
||||
categoryPillColors: {
|
||||
@@ -101,9 +102,11 @@ export type VideoDownvotes = { segments: { uuid: HashedValue, hidden: SponsorHid
|
||||
interface SBStorage {
|
||||
/* VideoID prefixes to UUID prefixes */
|
||||
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>,
|
||||
navigationApiAvailable: boolean,
|
||||
}
|
||||
|
||||
export interface SBObject {
|
||||
configLocalListeners: Array<(changes: StorageChangesObject) => unknown>;
|
||||
configSyncListeners: Array<(changes: StorageChangesObject) => unknown>;
|
||||
syncDefaults: SBConfig;
|
||||
localDefaults: SBStorage;
|
||||
@@ -113,12 +116,14 @@ export interface SBObject {
|
||||
local: SBStorage;
|
||||
forceSyncUpdate(prop: string): void;
|
||||
forceLocalUpdate(prop: string): void;
|
||||
resetToDefault(): void;
|
||||
}
|
||||
|
||||
const Config: SBObject = {
|
||||
/**
|
||||
* Callback function when an option is updated
|
||||
*/
|
||||
configLocalListeners: [],
|
||||
configSyncListeners: [],
|
||||
syncDefaults: {
|
||||
userID: null,
|
||||
@@ -166,6 +171,7 @@ const Config: SBObject = {
|
||||
scrollToEditTimeUpdate: false, // false means the tooltip will be shown
|
||||
categoryPillUpdate: false,
|
||||
darkMode: true,
|
||||
showCategoryGuidelines: true,
|
||||
|
||||
categoryPillColors: {},
|
||||
|
||||
@@ -282,14 +288,16 @@ const Config: SBObject = {
|
||||
}
|
||||
},
|
||||
localDefaults: {
|
||||
downvotedSegments: {}
|
||||
downvotedSegments: {},
|
||||
navigationApiAvailable: null
|
||||
},
|
||||
cachedSyncConfig: null,
|
||||
cachedLocalStorage: null,
|
||||
config: null,
|
||||
local: null,
|
||||
forceSyncUpdate,
|
||||
forceLocalUpdate
|
||||
forceLocalUpdate,
|
||||
resetToDefault
|
||||
};
|
||||
|
||||
// Function setup
|
||||
@@ -300,7 +308,7 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
|
||||
for (const key in changes) {
|
||||
Config.cachedSyncConfig[key] = changes[key].newValue;
|
||||
}
|
||||
|
||||
|
||||
for (const callback of Config.configSyncListeners) {
|
||||
callback(changes);
|
||||
}
|
||||
@@ -308,9 +316,13 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
|
||||
for (const key in changes) {
|
||||
Config.cachedLocalStorage[key] = changes[key].newValue;
|
||||
}
|
||||
|
||||
for (const callback of Config.configLocalListeners) {
|
||||
callback(changes);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const syncHandler: ProxyHandler<SBConfig> = {
|
||||
set<K extends keyof SBConfig>(obj: SBConfig, prop: K, value: SBConfig[K]) {
|
||||
Config.cachedSyncConfig[prop] = value;
|
||||
@@ -327,10 +339,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
|
||||
|
||||
return obj[prop] || data;
|
||||
},
|
||||
|
||||
|
||||
deleteProperty(obj: SBConfig, prop: keyof SBConfig) {
|
||||
chrome.storage.sync.remove(<string> prop);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -352,10 +364,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
|
||||
|
||||
return obj[prop] || data;
|
||||
},
|
||||
|
||||
|
||||
deleteProperty(obj: SBStorage, prop: keyof SBStorage) {
|
||||
chrome.storage.local.remove(<string> prop);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -368,8 +380,20 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
|
||||
}
|
||||
|
||||
function forceSyncUpdate(prop: string): void {
|
||||
const value = Config.cachedSyncConfig[prop];
|
||||
if (prop === "unsubmittedSegments") {
|
||||
// Early to be safe
|
||||
if (JSON.stringify(value).length + prop.length > 8000) {
|
||||
for (const key in value) {
|
||||
if (!value[key] || value[key].length <= 0) {
|
||||
delete value[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chrome.storage.sync.set({
|
||||
[prop]: Config.cachedSyncConfig[prop]
|
||||
[prop]: value
|
||||
});
|
||||
}
|
||||
|
||||
@@ -379,7 +403,7 @@ function forceLocalUpdate(prop: string): void {
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchConfig(): Promise<void> {
|
||||
async function fetchConfig(): Promise<void> {
|
||||
await Promise.all([new Promise<void>((resolve) => {
|
||||
chrome.storage.sync.get(null, function(items) {
|
||||
Config.cachedSyncConfig = <SBConfig> <unknown> items;
|
||||
@@ -387,7 +411,7 @@ async function fetchConfig(): Promise<void> {
|
||||
});
|
||||
}), new Promise<void>((resolve) => {
|
||||
chrome.storage.local.get(null, function(items) {
|
||||
Config.cachedLocalStorage = <SBStorage> <unknown> items;
|
||||
Config.cachedLocalStorage = <SBStorage> <unknown> items;
|
||||
resolve();
|
||||
});
|
||||
})]);
|
||||
@@ -431,9 +455,9 @@ function migrateOldSyncFormats(config: SBConfig) {
|
||||
if (!config["autoSkipOnMusicVideosUpdate"]) {
|
||||
config["autoSkipOnMusicVideosUpdate"] = true;
|
||||
for (const selection of config.categorySelections) {
|
||||
if (selection.name === "music_offtopic"
|
||||
if (selection.name === "music_offtopic"
|
||||
&& selection.option === CategorySkipOption.AutoSkip) {
|
||||
|
||||
|
||||
config.autoSkipOnMusicVideos = true;
|
||||
break;
|
||||
}
|
||||
@@ -522,6 +546,16 @@ function addDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
chrome.storage.sync.set({
|
||||
...Config.syncDefaults,
|
||||
userID: Config.config.userID,
|
||||
minutesSaved: Config.config.minutesSaved,
|
||||
skipCount: Config.config.skipCount,
|
||||
sponsorTimesContributed: Config.config.sponsorTimesContributed
|
||||
});
|
||||
}
|
||||
|
||||
// Sync config
|
||||
setupConfig();
|
||||
|
||||
|
||||
256
src/content.ts
256
src/content.ts
@@ -5,8 +5,6 @@ import { ContentContainer, Keybind } from "./types";
|
||||
import Utils from "./utils";
|
||||
const utils = new Utils();
|
||||
|
||||
import runThePopup from "./popup";
|
||||
|
||||
import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar";
|
||||
import SkipNotice from "./render/SkipNotice";
|
||||
import SkipNoticeComponent from "./components/SkipNoticeComponent";
|
||||
@@ -20,6 +18,7 @@ import { isSafari, keybindEquals } from "./utils/configUtils";
|
||||
import { CategoryPill } from "./render/CategoryPill";
|
||||
import { AnimationUtils } from "./utils/animationUtils";
|
||||
import { GenericUtils } from "./utils/genericUtils";
|
||||
import { logDebug } from "./utils/logger";
|
||||
|
||||
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
||||
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
||||
@@ -218,6 +217,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
utils.addHiddenSegment(sponsorVideoID, request.UUID, request.type);
|
||||
updatePreviewBar();
|
||||
break;
|
||||
case "closePopup":
|
||||
closeInfoMenu();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -275,6 +277,7 @@ function resetValues() {
|
||||
switchingVideos = false;
|
||||
} else {
|
||||
switchingVideos = true;
|
||||
logDebug("Setting switching videos to true (reset data)");
|
||||
}
|
||||
|
||||
firstEvent = true;
|
||||
@@ -292,7 +295,7 @@ function resetValues() {
|
||||
|
||||
async function videoIDChange(id) {
|
||||
//if the id has not changed return unless the video element has changed
|
||||
if (sponsorVideoID === id && isVisible(video)) return;
|
||||
if (sponsorVideoID === id && (isVisible(video) || !video)) return;
|
||||
|
||||
//set the global videoID
|
||||
sponsorVideoID = id;
|
||||
@@ -319,9 +322,6 @@ async function videoIDChange(id) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get new video info
|
||||
// getVideoInfo(); // Seems to have been replaced
|
||||
|
||||
// Update whitelist data when the video data is loaded
|
||||
whitelistCheck();
|
||||
|
||||
@@ -391,18 +391,32 @@ function handleMobileControlsMutations(): void {
|
||||
function createPreviewBar(): void {
|
||||
if (previewBar !== null) return;
|
||||
|
||||
const progressElementSelectors = [
|
||||
// For mobile YouTube
|
||||
".progress-bar-background",
|
||||
// For YouTube
|
||||
".ytp-progress-bar-container",
|
||||
".no-model.cue-range-markers",
|
||||
// For Invidious/VideoJS
|
||||
".vjs-progress-holder"
|
||||
const progressElementOptions = [{
|
||||
// For mobile YouTube
|
||||
selector: ".progress-bar-background",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For new mobile YouTube (#1287)
|
||||
selector: ".ytm-progress-bar",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For Desktop YouTube
|
||||
selector: ".ytp-progress-bar-container",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For Desktop YouTube
|
||||
selector: ".no-model.cue-range-marker",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For Invidious/VideoJS
|
||||
selector: ".vjs-progress-holder",
|
||||
isVisibleCheck: false
|
||||
}
|
||||
];
|
||||
|
||||
for (const selector of progressElementSelectors) {
|
||||
const el = findValidElement(document.querySelectorAll(selector));
|
||||
for (const option of progressElementOptions) {
|
||||
const allElements = document.querySelectorAll(option.selector) as NodeListOf<HTMLElement>;
|
||||
const el = option.isVisibleCheck ? findValidElement(allElements) : allElements[0];
|
||||
|
||||
if (el) {
|
||||
previewBar = new PreviewBar(el, onMobileYouTube, onInvidious);
|
||||
@@ -434,6 +448,8 @@ function videoOnReadyListener(): void {
|
||||
}
|
||||
|
||||
function cancelSponsorSchedule(): void {
|
||||
logDebug("Pausing skipping");
|
||||
|
||||
if (currentSkipSchedule !== null) {
|
||||
clearTimeout(currentSkipSchedule);
|
||||
currentSkipSchedule = null;
|
||||
@@ -456,20 +472,16 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
||||
// Reset lastCheckVideoTime
|
||||
lastCheckVideoTime = -1;
|
||||
lastCheckTime = 0;
|
||||
logDebug("[SB] Ad playing, pausing skipping");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logDebug(`Considering to start skipping: ${!video}, ${video?.paused}`);
|
||||
|
||||
if (!video || video.paused) return;
|
||||
if (currentTime === undefined || currentTime === null) {
|
||||
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime ?
|
||||
(performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
|
||||
if ((lastTimeFromWaitingEvent || !utils.isFirefox())
|
||||
&& !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6){
|
||||
currentTime = virtualTime;
|
||||
} else {
|
||||
currentTime = video.currentTime;
|
||||
}
|
||||
currentTime = getVirtualTime();
|
||||
}
|
||||
lastTimeFromWaitingEvent = null;
|
||||
|
||||
@@ -491,6 +503,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
||||
|
||||
const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
|
||||
|
||||
logDebug(`Ready to start skipping: ${skipInfo.index} at ${currentTime}`);
|
||||
if (skipInfo.index === -1) return;
|
||||
|
||||
const currentSkip = skipInfo.array[skipInfo.index];
|
||||
@@ -511,6 +524,8 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
||||
}
|
||||
}
|
||||
|
||||
logDebug(`Next step in starting skipping: ${!shouldSkip(currentSkip)}, ${!sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)}`);
|
||||
|
||||
// Don't skip if this category should not be skipped
|
||||
if (!shouldSkip(currentSkip) && !sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)) return;
|
||||
|
||||
@@ -520,7 +535,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
||||
let forcedIncludeNonIntersectingSegments = true;
|
||||
|
||||
if (incorrectVideoCheck(videoID, currentSkip)) return;
|
||||
forceVideoTime ||= video.currentTime;
|
||||
forceVideoTime ||= Math.max(video.currentTime, getVirtualTime());
|
||||
|
||||
if (forceVideoTime >= skipTime[0] && forceVideoTime < skipTime[1]) {
|
||||
skipToTime({
|
||||
@@ -548,9 +563,11 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
||||
} else {
|
||||
const delayTime = timeUntilSponsor * 1000 * (1 / video.playbackRate);
|
||||
if (delayTime < 300) {
|
||||
// For Firefox, use interval instead of timeout near the end to combat imprecise video time
|
||||
// Use interval instead of timeout near the end to combat imprecise video time
|
||||
const startIntervalTime = performance.now();
|
||||
const startVideoTime = Math.max(currentTime, video.currentTime);
|
||||
logDebug(`Starting setInterval skipping ${video.currentTime} to skip at ${skipTime[0]}`);
|
||||
|
||||
currentSkipInterval = setInterval(() => {
|
||||
const intervalDuration = performance.now() - startIntervalTime;
|
||||
if (intervalDuration >= delayTime || video.currentTime >= skipTime[0]) {
|
||||
@@ -565,12 +582,26 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
||||
}
|
||||
}, 1);
|
||||
} else {
|
||||
logDebug(`Starting timeout to skip ${video.currentTime} to skip at ${skipTime[0]}`);
|
||||
|
||||
// Schedule for right before to be more precise than normal timeout
|
||||
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 100));
|
||||
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 150));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getVirtualTime(): number {
|
||||
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime ?
|
||||
(performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
|
||||
|
||||
if ((lastTimeFromWaitingEvent || !utils.isFirefox())
|
||||
&& !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6) {
|
||||
return virtualTime;
|
||||
} else {
|
||||
return video.currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
function inMuteSegment(currentTime: number): boolean {
|
||||
const checkFunction = (segment) => segment.actionType === ActionType.Mute && segment.segment[0] <= currentTime && segment.segment[1] > currentTime;
|
||||
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
|
||||
@@ -641,6 +672,8 @@ function setupVideoListeners() {
|
||||
if (!Config.config.disableSkipping) {
|
||||
switchingVideos = false;
|
||||
|
||||
let startedWaiting = false;
|
||||
|
||||
video.addEventListener('play', () => {
|
||||
// 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
|
||||
@@ -652,6 +685,8 @@ function setupVideoListeners() {
|
||||
|
||||
if (switchingVideos) {
|
||||
switchingVideos = false;
|
||||
logDebug("Setting switching videos to false");
|
||||
|
||||
// If already segments loaded before video, retry to skip starting segments
|
||||
if (sponsorTimes) startSkipScheduleCheckingForStartSponsors();
|
||||
}
|
||||
@@ -671,6 +706,12 @@ function setupVideoListeners() {
|
||||
});
|
||||
video.addEventListener('playing', () => {
|
||||
updateVirtualTime();
|
||||
|
||||
if (startedWaiting) {
|
||||
startedWaiting = false;
|
||||
logDebug(`[SB] Playing event after buffering: ${Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)}`);
|
||||
}
|
||||
|
||||
// Make sure it doesn't get double called with the play event
|
||||
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||
@@ -709,8 +750,13 @@ function setupVideoListeners() {
|
||||
|
||||
cancelSponsorSchedule();
|
||||
};
|
||||
video.addEventListener('pause', paused);
|
||||
video.addEventListener('waiting', paused);
|
||||
video.addEventListener('pause', () => paused());
|
||||
video.addEventListener('waiting', () => {
|
||||
logDebug("[SB] Not skipping due to buffering");
|
||||
startedWaiting = true;
|
||||
|
||||
paused();
|
||||
});
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
@@ -899,12 +945,10 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
// See if there are any starting sponsors
|
||||
let startingSegmentTime = getStartTimeFromUrl(document.URL) || -1;
|
||||
let found = false;
|
||||
let startingSegment: SponsorTime = null;
|
||||
for (const time of sponsorTimes) {
|
||||
if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
|
||||
&& time.actionType !== ActionType.Poi) {
|
||||
startingSegmentTime = time.segment[0];
|
||||
startingSegment = time;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -914,7 +958,6 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
|
||||
&& time.actionType !== ActionType.Poi) {
|
||||
startingSegmentTime = time.segment[0];
|
||||
startingSegment = time;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -952,26 +995,6 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the video info for the current tab from YouTube
|
||||
*
|
||||
* TODO: Replace
|
||||
*/
|
||||
async function getVideoInfo(): Promise<void> {
|
||||
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID + "&html5=1&c=TVHTML5&cver=7.20190319");
|
||||
|
||||
if (result.ok) {
|
||||
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;
|
||||
}
|
||||
|
||||
videoInfo = JSON.parse(decodedData);
|
||||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoID(document: Document): string | boolean {
|
||||
const url = document.URL;
|
||||
// clips should never skip, going from clip to full video has no indications.
|
||||
@@ -1106,7 +1129,7 @@ async function whitelistCheck() {
|
||||
?? document.querySelector("a.ytp-title-channel-logo") // YouTube Embed
|
||||
?? document.querySelector(".channel-profile #channel-name")?.parentElement.parentElement // Invidious
|
||||
?? document.querySelector("a.slim-owner-icon-and-title")) // Mobile YouTube
|
||||
?.getAttribute("href")?.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/)[1];
|
||||
?.getAttribute("href")?.match(/\/(?:channel|c|user)\/(UC[a-zA-Z0-9_-]{22}|[a-zA-Z0-9_-]+)/)?.[1];
|
||||
|
||||
try {
|
||||
await utils.wait(() => !!getChannelID(), 6000, 20);
|
||||
@@ -1114,14 +1137,12 @@ async function whitelistCheck() {
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Found,
|
||||
id: getChannelID().match(/^\/?([^\s/]+)/)[0]
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Failed,
|
||||
id: null
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
//see if this is a whitelisted channel
|
||||
@@ -1666,71 +1687,30 @@ function openInfoMenu() {
|
||||
//hide info button
|
||||
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) {
|
||||
const popup = document.createElement("div");
|
||||
popup.id = "sponsorBlockPopupContainer";
|
||||
|
||||
const popup = document.createElement("div");
|
||||
popup.id = "sponsorBlockPopupContainer";
|
||||
|
||||
const frame = document.createElement("iframe");
|
||||
frame.width = "374";
|
||||
frame.height = "500";
|
||||
frame.addEventListener("load", () => frame.contentWindow.postMessage("", "*"));
|
||||
frame.src = chrome.extension.getURL("popup.html");
|
||||
popup.appendChild(frame);
|
||||
|
||||
let htmlData = xmlhttp.responseText;
|
||||
// Hack to replace head data (title, favicon)
|
||||
htmlData = htmlData.replace(/<head>[\S\s]*<\/head>/gi, "");
|
||||
// Hack to replace body and html tag with div
|
||||
htmlData = htmlData.replace(/<body/gi, "<div");
|
||||
htmlData = htmlData.replace(/<\/body/gi, "</div");
|
||||
htmlData = htmlData.replace(/<html/gi, "<div");
|
||||
htmlData = htmlData.replace(/<\/html/gi, "</div");
|
||||
|
||||
popup.innerHTML = htmlData;
|
||||
|
||||
//close button
|
||||
const closeButton = document.createElement("button");
|
||||
const closeButtonIcon = document.createElement("img");
|
||||
closeButtonIcon.src = chrome.extension.getURL("icons/close.png");
|
||||
closeButtonIcon.width = 15;
|
||||
closeButtonIcon.height = 15;
|
||||
closeButton.appendChild(closeButtonIcon);
|
||||
closeButton.setAttribute("title", chrome.i18n.getMessage("closePopup"));
|
||||
closeButton.classList.add("sbCloseButton");
|
||||
closeButton.addEventListener("click", closeInfoMenu);
|
||||
|
||||
//add the close button
|
||||
popup.prepend(closeButton);
|
||||
|
||||
const parentNodes = document.querySelectorAll("#secondary");
|
||||
let parentNode = null;
|
||||
for (let i = 0; i < parentNodes.length; i++) {
|
||||
if (parentNodes[i].firstElementChild !== null) {
|
||||
parentNode = parentNodes[i];
|
||||
}
|
||||
}
|
||||
if (parentNode == null) {
|
||||
//old youtube theme
|
||||
parentNode = document.getElementById("watch7-sidebar-contents");
|
||||
}
|
||||
|
||||
//make the logo source not 404
|
||||
//query selector must be used since getElementByID doesn't work on a node and this isn't added to the document yet
|
||||
const logo = <HTMLImageElement> popup.querySelector("#sponsorBlockPopupLogo");
|
||||
const edit = <HTMLImageElement> popup.querySelector("#sbPopupIconEdit");
|
||||
const copy = <HTMLImageElement> popup.querySelector("#sbPopupIconCopyUserID");
|
||||
const check = <HTMLImageElement> popup.querySelector("#sbPopupIconCheck");
|
||||
const refreshSegments = <HTMLImageElement> popup.querySelector("#refreshSegments");
|
||||
const heart = <HTMLImageElement> popup.querySelector(".sbHeart");
|
||||
const close = <HTMLImageElement> popup.querySelector("#sbCloseDonate");
|
||||
logo.src = chrome.extension.getURL("icons/IconSponsorBlocker256px.png");
|
||||
edit.src = chrome.extension.getURL("icons/pencil.svg");
|
||||
copy.src = chrome.extension.getURL("icons/clipboard.svg");
|
||||
check.src = chrome.extension.getURL("icons/check.svg");
|
||||
heart.src = chrome.extension.getURL("icons/heart.svg");
|
||||
close.src = chrome.extension.getURL("icons/close.png");
|
||||
refreshSegments.src = chrome.extension.getURL("icons/refresh.svg");
|
||||
|
||||
parentNode.insertBefore(popup, parentNode.firstChild);
|
||||
|
||||
//run the popup init script
|
||||
runThePopup(messageListener);
|
||||
const parentNodes = document.querySelectorAll("#secondary");
|
||||
let parentNode = null;
|
||||
for (let i = 0; i < parentNodes.length; i++) {
|
||||
if (parentNodes[i].firstElementChild !== null) {
|
||||
parentNode = parentNodes[i];
|
||||
}
|
||||
});
|
||||
}
|
||||
if (parentNode == null) {
|
||||
//old youtube theme
|
||||
parentNode = document.getElementById("watch7-sidebar-contents");
|
||||
}
|
||||
|
||||
parentNode.insertBefore(popup, parentNode.firstChild);
|
||||
}
|
||||
|
||||
function closeInfoMenu() {
|
||||
@@ -1909,8 +1889,6 @@ async function sendSubmitMessage() {
|
||||
return;
|
||||
}
|
||||
|
||||
sponsorsLookup();
|
||||
|
||||
// Add loading animation
|
||||
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker.svg");
|
||||
const stopAnimation = AnimationUtils.applyLoadingAnimation(playerButtons.submit.button, 1, () => updateEditButtonsOnPlayer());
|
||||
@@ -2093,25 +2071,6 @@ function addCSS() {
|
||||
}
|
||||
}
|
||||
|
||||
function sendRequestToCustomServer(type, fullAddress, callback) {
|
||||
const xmlhttp = new XMLHttpRequest();
|
||||
|
||||
xmlhttp.open(type, fullAddress, true);
|
||||
|
||||
if (callback != undefined) {
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
callback(xmlhttp, false);
|
||||
};
|
||||
|
||||
xmlhttp.onerror = function() {
|
||||
callback(xmlhttp, true);
|
||||
};
|
||||
}
|
||||
|
||||
//submit this request
|
||||
xmlhttp.send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the isAdPlaying flag and hide preview bar/controls if ad is playing
|
||||
*/
|
||||
@@ -2184,3 +2143,18 @@ function checkForPreloadedSegment() {
|
||||
Config.forceSyncUpdate("unsubmittedSegments");
|
||||
}
|
||||
}
|
||||
|
||||
// Register listener for URL change via Navigation API
|
||||
const navigationApiAvailable = "navigation" in window;
|
||||
if (navigationApiAvailable) {
|
||||
// TODO: Remove type cast once type declarations are updated
|
||||
(window as unknown as { navigation: EventTarget }).navigation.addEventListener("navigate", () => videoIDChange(getYouTubeVideoID(document)));
|
||||
}
|
||||
|
||||
// Record availability of Navigation API
|
||||
utils.wait(() => Config.local !== null).then(() => {
|
||||
if (Config.local.navigationApiAvailable !== navigationApiAvailable) {
|
||||
Config.local.navigationApiAvailable = navigationApiAvailable;
|
||||
Config.forceLocalUpdate("navigationApiAvailable");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import Config from "./config";
|
||||
import { showDonationLink } from "./utils/configUtils";
|
||||
|
||||
import Utils from "./utils";
|
||||
const utils = new Utils();
|
||||
import { localizeHtmlPage } from "./utils/pageUtils";
|
||||
import { GenericUtils } from "./utils/genericUtils";
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
async function init() {
|
||||
utils.localizeHtmlPage();
|
||||
localizeHtmlPage();
|
||||
|
||||
await utils.wait(() => Config.config !== null);
|
||||
await GenericUtils.wait(() => Config.config !== null);
|
||||
|
||||
if (!Config.config.darkMode) {
|
||||
document.documentElement.setAttribute("data-theme", "light");
|
||||
|
||||
@@ -144,8 +144,10 @@ class PreviewBar {
|
||||
this.parent = parent;
|
||||
|
||||
if (this.onMobileYouTube) {
|
||||
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
|
||||
parent.style.opacity = "1";
|
||||
if (parent.classList.contains("progress-bar-background")) {
|
||||
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
|
||||
parent.style.opacity = "1";
|
||||
}
|
||||
|
||||
this.container.style.transform = "none";
|
||||
} else if (!this.onInvidious) {
|
||||
|
||||
@@ -2,10 +2,7 @@ import Config from "../config";
|
||||
import { SponsorTime } from "../types";
|
||||
import { getSkippingText } from "../utils/categoryUtils";
|
||||
import { keybindToString } from "../utils/configUtils";
|
||||
|
||||
import Utils from "../utils";
|
||||
import { AnimationUtils } from "../utils/animationUtils";
|
||||
const utils = new Utils();
|
||||
|
||||
export interface SkipButtonControlBarProps {
|
||||
skip: (segment: SponsorTime) => void;
|
||||
@@ -53,7 +50,7 @@ export class SkipButtonControlBar {
|
||||
this.skipIcon.id = "sbSkipIconControlBarImage";
|
||||
|
||||
this.textContainer = document.createElement("div");
|
||||
|
||||
|
||||
this.container.appendChild(this.skipIcon);
|
||||
this.container.appendChild(this.textContainer);
|
||||
this.container.addEventListener("click", () => this.toggleSkip());
|
||||
@@ -73,7 +70,7 @@ export class SkipButtonControlBar {
|
||||
attachToPage(): void {
|
||||
const mountingContainer = this.getMountingContainer();
|
||||
this.chapterText = document.querySelector(".ytp-chapter-container");
|
||||
|
||||
|
||||
if (mountingContainer && !mountingContainer.contains(this.container)) {
|
||||
if (this.onMobileYouTube) {
|
||||
mountingContainer.appendChild(this.container);
|
||||
@@ -151,7 +148,7 @@ export class SkipButtonControlBar {
|
||||
}
|
||||
|
||||
disableText(): void {
|
||||
if (Config.config.hideVideoPlayerControls || Config.config.hideSkipButtonPlayerControls) {
|
||||
if (Config.config.hideSkipButtonPlayerControls) {
|
||||
this.disable();
|
||||
return;
|
||||
}
|
||||
@@ -172,10 +169,10 @@ export class SkipButtonControlBar {
|
||||
const overlay = document.getElementById("player-control-overlay");
|
||||
|
||||
if (overlay && this.enabled) {
|
||||
if (overlay?.classList?.contains("pointer-events-off")) {
|
||||
this.hideButton();
|
||||
} else {
|
||||
if (overlay?.classList?.contains("fadein")) {
|
||||
this.showButton();
|
||||
} else {
|
||||
this.hideButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,4 +217,3 @@ export class SkipButtonControlBar {
|
||||
this.container.style.left = this.left + "px";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ interface DefaultMessage {
|
||||
| "getChannelID"
|
||||
| "isChannelWhitelisted"
|
||||
| "submitTimes"
|
||||
| "refreshSegments";
|
||||
| "refreshSegments"
|
||||
| "closePopup";
|
||||
}
|
||||
|
||||
interface BoolValueMessage {
|
||||
|
||||
@@ -12,13 +12,14 @@ import Utils from "./utils";
|
||||
import CategoryChooser from "./render/CategoryChooser";
|
||||
import KeybindComponent from "./components/KeybindComponent";
|
||||
import { showDonationLink } from "./utils/configUtils";
|
||||
import { localizeHtmlPage } from "./utils/pageUtils";
|
||||
const utils = new Utils();
|
||||
let embed = false;
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
async function init() {
|
||||
utils.localizeHtmlPage();
|
||||
localizeHtmlPage();
|
||||
|
||||
// selected tab
|
||||
if (location.hash != "") {
|
||||
@@ -232,12 +233,22 @@ async function init() {
|
||||
}
|
||||
case "button-press": {
|
||||
const actionButton = optionsElements[i].querySelector(".trigger-button");
|
||||
const confirmMessage = optionsElements[i].getAttribute("data-confirm-message");
|
||||
|
||||
switch(optionsElements[i].getAttribute("data-sync")) {
|
||||
case "copyDebugInformation":
|
||||
actionButton.addEventListener("click", copyDebugOutputToClipboard);
|
||||
break;
|
||||
}
|
||||
actionButton.addEventListener("click", () => {
|
||||
if (confirmMessage !== null && !confirm(chrome.i18n.getMessage(confirmMessage))) {
|
||||
return;
|
||||
}
|
||||
switch (optionsElements[i].getAttribute("data-sync")) {
|
||||
case "copyDebugInformation":
|
||||
copyDebugOutputToClipboard();
|
||||
break;
|
||||
case "resetToDefault":
|
||||
Config.resetToDefault();
|
||||
window.location.reload();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Config from "./config";
|
||||
import Utils from "./utils";
|
||||
import { localizeHtmlPage } from "./utils/pageUtils";
|
||||
const utils = new Utils();
|
||||
|
||||
// This is needed, if Config is not imported before Utils, things break.
|
||||
@@ -9,7 +10,7 @@ Config.config;
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
async function init() {
|
||||
utils.localizeHtmlPage();
|
||||
localizeHtmlPage();
|
||||
|
||||
const domains = document.location.hash.replace("#", "").split(",");
|
||||
|
||||
|
||||
82
src/popup.ts
82
src/popup.ts
@@ -6,6 +6,7 @@ import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageT
|
||||
import { showDonationLink } from "./utils/configUtils";
|
||||
import { AnimationUtils } from "./utils/animationUtils";
|
||||
import { GenericUtils } from "./utils/genericUtils";
|
||||
import { localizeHtmlPage } from "./utils/pageUtils";
|
||||
const utils = new Utils();
|
||||
|
||||
interface MessageListener {
|
||||
@@ -22,13 +23,15 @@ class MessageHandler {
|
||||
sendMessage(id: number, request: Message, callback?) {
|
||||
if (this.messageListener) {
|
||||
this.messageListener(request, null, callback);
|
||||
} else {
|
||||
} else if (chrome.tabs) {
|
||||
chrome.tabs.sendMessage(id, request, callback);
|
||||
} else {
|
||||
chrome.runtime.sendMessage({ message: "tabs", data: request }, callback);
|
||||
}
|
||||
}
|
||||
|
||||
query(config, callback) {
|
||||
if (this.messageListener) {
|
||||
if (this.messageListener || !chrome.tabs) {
|
||||
// Send back dummy info
|
||||
callback([{
|
||||
url: document.URL,
|
||||
@@ -41,15 +44,17 @@ class MessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// To prevent clickjacking
|
||||
let allowPopup = window === window.top;
|
||||
window.addEventListener("message", async (e) => {
|
||||
if (e.source !== window.parent) return;
|
||||
if (e.origin.endsWith('.youtube.com')) return allowPopup = true;
|
||||
});
|
||||
|
||||
//make this a function to allow this to run on the content page
|
||||
async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
const messageHandler = new MessageHandler(messageListener);
|
||||
|
||||
utils.localizeHtmlPage();
|
||||
|
||||
await utils.wait(() => Config.config !== null);
|
||||
localizeHtmlPage();
|
||||
|
||||
type InputPageElements = {
|
||||
whitelistToggle?: HTMLInputElement,
|
||||
@@ -58,6 +63,15 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
};
|
||||
type PageElements = { [key: string]: HTMLElement } & InputPageElements
|
||||
|
||||
/** 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[] = [];
|
||||
|
||||
//current video ID of this tab
|
||||
let currentVideoID = null;
|
||||
|
||||
const PageElements: PageElements = {};
|
||||
|
||||
[
|
||||
@@ -112,9 +126,24 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
"sponsorTimesDonateContainer",
|
||||
"sbConsiderDonateLink",
|
||||
"sbCloseDonate",
|
||||
"sbBetaServerWarning"
|
||||
"sbBetaServerWarning",
|
||||
"sbCloseButton"
|
||||
].forEach(id => PageElements[id] = document.getElementById(id));
|
||||
|
||||
getSegmentsFromContentScript(false);
|
||||
await utils.wait(() => Config.config !== null && allowPopup, 5000, 5);
|
||||
document.querySelector("body").style.removeProperty("visibility");
|
||||
|
||||
PageElements.sbCloseButton.addEventListener("click", () => {
|
||||
sendTabMessage({
|
||||
message: "closePopup"
|
||||
});
|
||||
});
|
||||
|
||||
if (window !== window.top) {
|
||||
PageElements.sbCloseButton.classList.remove("hidden");
|
||||
}
|
||||
|
||||
// Hide donate button if wanted (Safari, or user choice)
|
||||
if (!showDonationLink()) {
|
||||
PageElements.sbDonate.style.display = "none";
|
||||
@@ -151,15 +180,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments);
|
||||
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => navigator.clipboard.writeText(await utils.getHash(Config.config.userID)));
|
||||
|
||||
/** 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[] = [];
|
||||
|
||||
//current video ID of this tab
|
||||
let currentVideoID = null;
|
||||
|
||||
//show proper disable skipping button
|
||||
const disableSkipping = Config.config.disableSkipping;
|
||||
if (disableSkipping != undefined && disableSkipping) {
|
||||
@@ -239,8 +259,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
// Must be delayed so it only happens once loaded
|
||||
setTimeout(() => PageElements.sponsorblockPopup.classList.remove("preload"), 250);
|
||||
|
||||
getSegmentsFromContentScript(false);
|
||||
|
||||
function showDonateWidget(viewCount: number) {
|
||||
if (Config.config.showDonationLink && Config.config.donateClicked <= 0 && Config.config.showPopupDonationCount < 5
|
||||
&& viewCount < 50000 && !Config.config.isVip && Config.config.skipCount > 10) {
|
||||
@@ -272,13 +290,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
function loadTabData(tabs, updating: boolean): void {
|
||||
async function loadTabData(tabs, updating: boolean): Promise<void> {
|
||||
if (!currentVideoID) {
|
||||
//this isn't a YouTube video then
|
||||
displayNoVideo();
|
||||
return;
|
||||
}
|
||||
|
||||
await utils.wait(() => Config.config !== null, 5000, 10);
|
||||
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? [];
|
||||
updateSegmentEditingUI();
|
||||
|
||||
@@ -588,6 +607,22 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
chrome.runtime.sendMessage({ "message": "openHelp" });
|
||||
}
|
||||
|
||||
function sendTabMessage(data: Message): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
data,
|
||||
(response) => resolve(response)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//make the options username setting option visible
|
||||
function setUsernameButton() {
|
||||
PageElements.usernameInput.value = PageElements.usernameValue.innerText;
|
||||
@@ -832,9 +867,4 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
//end of function
|
||||
}
|
||||
|
||||
if (chrome.tabs != undefined) {
|
||||
//this means it is actually opened in the popup
|
||||
runThePopup();
|
||||
}
|
||||
|
||||
export default runThePopup;
|
||||
runThePopup();
|
||||
|
||||
@@ -13,12 +13,19 @@ export interface ButtonListener {
|
||||
listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
|
||||
}
|
||||
|
||||
export interface TextBox {
|
||||
icon: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface NoticeOptions {
|
||||
title: string,
|
||||
textBoxes?: string[],
|
||||
textBoxes?: TextBox[],
|
||||
buttons?: ButtonListener[],
|
||||
fadeIn?: boolean,
|
||||
timed?: boolean
|
||||
style?: React.CSSProperties;
|
||||
extraClass?: string;
|
||||
}
|
||||
|
||||
export default class GenericNotice {
|
||||
@@ -27,9 +34,11 @@ export default class GenericNotice {
|
||||
|
||||
noticeElement: HTMLDivElement;
|
||||
noticeRef: React.MutableRefObject<NoticeComponent>;
|
||||
idSuffix: string;
|
||||
|
||||
constructor(contentContainer: ContentContainer, idSuffix: string, options: NoticeOptions) {
|
||||
this.noticeRef = React.createRef();
|
||||
this.idSuffix = idSuffix;
|
||||
|
||||
this.contentContainer = contentContainer;
|
||||
|
||||
@@ -40,39 +49,47 @@ export default class GenericNotice {
|
||||
|
||||
referenceNode.prepend(this.noticeElement);
|
||||
|
||||
this.update(options);
|
||||
}
|
||||
|
||||
update(options: NoticeOptions): void {
|
||||
ReactDOM.render(
|
||||
<NoticeComponent
|
||||
noticeTitle={options.title}
|
||||
idSuffix={idSuffix}
|
||||
idSuffix={this.idSuffix}
|
||||
fadeIn={options.fadeIn ?? true}
|
||||
timed={options.timed ?? true}
|
||||
ref={this.noticeRef}
|
||||
style={options.style}
|
||||
extraClass={options.extraClass}
|
||||
closeListener={() => this.close()} >
|
||||
|
||||
{this.getMessageBox(idSuffix, options.textBoxes)}
|
||||
{this.getMessageBox(this.idSuffix, options.textBoxes)}
|
||||
|
||||
<tr id={"sponsorSkipNoticeSpacer" + idSuffix}
|
||||
<tr id={"sponsorSkipNoticeSpacer" + this.idSuffix}
|
||||
className="sponsorBlockSpacer">
|
||||
</tr>
|
||||
|
||||
<div className="sponsorSkipNoticeRightSection"
|
||||
<tr className="sponsorSkipNoticeRightSection"
|
||||
style={{position: "relative"}}>
|
||||
|
||||
{this.getButtons(options.buttons)}
|
||||
</div>
|
||||
<td>
|
||||
{this.getButtons(options.buttons)}
|
||||
</td>
|
||||
</tr>
|
||||
</NoticeComponent>,
|
||||
this.noticeElement
|
||||
);
|
||||
}
|
||||
|
||||
getMessageBox(idSuffix: string, textBoxes: string[]): JSX.Element[] {
|
||||
getMessageBox(idSuffix: string, textBoxes: TextBox[]): JSX.Element[] {
|
||||
if (textBoxes) {
|
||||
const result = [];
|
||||
for (let i = 0; i < textBoxes.length; i++) {
|
||||
result.push(
|
||||
<NoticeTextSelectionComponent idSuffix={idSuffix}
|
||||
key={i}
|
||||
text={textBoxes[i]} />
|
||||
icon={textBoxes[i].icon}
|
||||
text={textBoxes[i].text} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
25
src/utils.ts
25
src/utils.ts
@@ -256,31 +256,6 @@ export default class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
localizeHtmlPage(): void {
|
||||
//Localize by replacing __MSG_***__ meta tags
|
||||
const localizedMessage = this.getLocalizedMessage(document.title);
|
||||
if (localizedMessage) document.title = localizedMessage;
|
||||
const objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
|
||||
for (let j = 0; j < objects.length; j++) {
|
||||
const obj = objects[j];
|
||||
const localizedMessage = this.getLocalizedMessage(obj.innerHTML.toString());
|
||||
if (localizedMessage) obj.innerHTML = localizedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
getLocalizedMessage(text: string): string | false {
|
||||
const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
|
||||
return v1 ? chrome.i18n.getMessage(v1).replace(/</g, "<")
|
||||
.replace(/"/g, """).replace(/\n/g, "<br/>") : "";
|
||||
});
|
||||
|
||||
if(valNewH != text) {
|
||||
return valNewH;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String[]} Domains in regex form
|
||||
*/
|
||||
|
||||
141
src/utils/constants.ts
Normal file
141
src/utils/constants.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { TextBox } from "../render/GenericNotice";
|
||||
import { Category } from "../types";
|
||||
|
||||
export function getGuidelineInfo(category: Category): TextBox[] {
|
||||
switch (category) {
|
||||
case "sponsor":
|
||||
return [{
|
||||
icon: "icons/money.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/close-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/segway.png",
|
||||
text: chrome.i18n.getMessage(`generic_guideline1`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "selfpromo":
|
||||
return [{
|
||||
icon: "icons/money.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/campaign.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/close-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
|
||||
}, {
|
||||
icon: "icons/segway.png",
|
||||
text: chrome.i18n.getMessage(`generic_guideline1`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "exclusive_access":
|
||||
return [{
|
||||
icon: "icons/money.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}];
|
||||
case "interaction":
|
||||
return [{
|
||||
icon: "icons/lightbulb.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/lightbulb.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/close-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
|
||||
}, {
|
||||
icon: "icons/segway.png",
|
||||
text: chrome.i18n.getMessage(`generic_guideline1`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "intro":
|
||||
return [{
|
||||
icon: "icons/check-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/close-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/segway.png",
|
||||
text: chrome.i18n.getMessage(`generic_guideline1`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "outro":
|
||||
return [{
|
||||
icon: "icons/close-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/segway.png",
|
||||
text: chrome.i18n.getMessage(`generic_guideline1`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "preview":
|
||||
return [{
|
||||
icon: "icons/check-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/check-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/close-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
|
||||
}, {
|
||||
icon: "icons/segway.png",
|
||||
text: chrome.i18n.getMessage(`generic_guideline1`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "filler":
|
||||
return [{
|
||||
icon: "icons/stopwatch.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/stopwatch.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/close-smaller.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
|
||||
}, {
|
||||
icon: "icons/segway.png",
|
||||
text: chrome.i18n.getMessage(`generic_guideline1`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "music_offtopic":
|
||||
return [{
|
||||
icon: "icons/music-note.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/music-note.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/right-arrow.svg",
|
||||
text: chrome.i18n.getMessage(`generic_guideline2`)
|
||||
}];
|
||||
case "poi_highlight":
|
||||
return [{
|
||||
icon: "icons/star.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
|
||||
}, {
|
||||
icon: "icons/bolt.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
|
||||
}, {
|
||||
icon: "icons/bolt.svg",
|
||||
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ function hexToRgb(hex: string): {r: number, g: number, b: number} {
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
}
|
||||
}
|
||||
|
||||
export const GenericUtils = {
|
||||
wait,
|
||||
|
||||
12
src/utils/logger.ts
Normal file
12
src/utils/logger.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
window["SBLogs"] = {
|
||||
debug: [],
|
||||
warn: []
|
||||
};
|
||||
|
||||
export function logDebug(message: string) {
|
||||
window["SBLogs"].debug.push(`[${new Date().toISOString()}] ${message}`);
|
||||
}
|
||||
|
||||
export function logWarn(message: string) {
|
||||
window["SBLogs"].warn.push(`[${new Date().toISOString()}] ${message}`);
|
||||
}
|
||||
@@ -61,4 +61,29 @@ export function getHashParams(): Record<string, unknown> {
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function localizeHtmlPage(): void {
|
||||
//Localize by replacing __MSG_***__ meta tags
|
||||
const localizedMessage = getLocalizedMessage(document.title);
|
||||
if (localizedMessage) document.title = localizedMessage;
|
||||
const objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
|
||||
for (let j = 0; j < objects.length; j++) {
|
||||
const obj = objects[j];
|
||||
const localizedMessage = getLocalizedMessage(obj.innerHTML.toString());
|
||||
if (localizedMessage) obj.innerHTML = localizedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocalizedMessage(text: string): string | false {
|
||||
const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
|
||||
return v1 ? chrome.i18n.getMessage(v1).replace(/</g, "<")
|
||||
.replace(/"/g, """).replace(/\n/g, "<br/>") : "";
|
||||
});
|
||||
|
||||
if (valNewH != text) {
|
||||
return valNewH;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user