mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2026-03-27 16:18:38 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into feat/preview-bar-cleanup
# Conflicts: # src/content.ts # src/js-components/previewBar.ts
This commit is contained in:
@@ -2,8 +2,10 @@ import * as CompileConfig from "../config.json";
|
||||
|
||||
import Config from "./config";
|
||||
import { Registration } from "./types";
|
||||
|
||||
// Make the config public for debugging purposes
|
||||
(<any> window).SB = Config;
|
||||
|
||||
window.SB = Config;
|
||||
|
||||
import Utils from "./utils";
|
||||
const utils = new Utils({
|
||||
@@ -33,7 +35,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
chrome.runtime.openOptionsPage();
|
||||
return;
|
||||
case "openHelp":
|
||||
window.open(chrome.runtime.getURL('help/index_en.html'));
|
||||
chrome.tabs.create({url: chrome.runtime.getURL('help/index_en.html')});
|
||||
return;
|
||||
case "sendRequest":
|
||||
sendRequestToCustomServer(request.type, request.url, request.data).then(async (response) => {
|
||||
@@ -70,7 +72,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
});
|
||||
|
||||
//add help page on install
|
||||
chrome.runtime.onInstalled.addListener(function (object) {
|
||||
chrome.runtime.onInstalled.addListener(function () {
|
||||
// This let's the config sync to run fully before checking.
|
||||
// This is required on Firefox
|
||||
setTimeout(function() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from "react";
|
||||
|
||||
import Config from "../config"
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import CategorySkipOptionsComponent from "./CategorySkipOptionsComponent";
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@ import * as React from "react";
|
||||
|
||||
import Config from "../config"
|
||||
import { CategorySkipOption } from "../types";
|
||||
import Utils from "../utils";
|
||||
|
||||
const utils = new Utils();
|
||||
|
||||
export interface CategorySkipOptionsProps {
|
||||
category: string;
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface NoticeState {
|
||||
|
||||
class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
countdownInterval: NodeJS.Timeout;
|
||||
idSuffix: any;
|
||||
idSuffix: string;
|
||||
|
||||
amountOfPreviousNotices: number;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from "react";
|
||||
export interface NoticeTextSelectionProps {
|
||||
text: string,
|
||||
idSuffix: string,
|
||||
onClick?: (event: React.MouseEvent) => any
|
||||
onClick?: (event: React.MouseEvent) => unknown
|
||||
}
|
||||
|
||||
export interface NoticeTextSelectionState {
|
||||
|
||||
@@ -2,10 +2,6 @@ import * as React from "react";
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import Config from "../config"
|
||||
import { ContentContainer, SponsorHideType, SponsorTime } from "../types";
|
||||
|
||||
import Utils from "../utils";
|
||||
const utils = new Utils();
|
||||
|
||||
import NoticeComponent from "./NoticeComponent";
|
||||
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
||||
|
||||
@@ -42,7 +38,7 @@ export interface SkipNoticeState {
|
||||
|
||||
downvoting: boolean;
|
||||
choosingCategory: boolean;
|
||||
thanksForVotingText: boolean; //null until the voting buttons should be hidden
|
||||
thanksForVotingText: string; //null until the voting buttons should be hidden
|
||||
|
||||
actionState: SkipNoticeAction;
|
||||
}
|
||||
@@ -447,25 +443,21 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
});
|
||||
}
|
||||
|
||||
getUnskippedModeInfo(index: number, buttonText: string) {
|
||||
const self = this;
|
||||
const maxCountdownTime = function() {
|
||||
const sponsorTime = self.segments[index];
|
||||
const duration = Math.round((sponsorTime.segment[1] - self.contentContainer().v.currentTime) * (1 / self.contentContainer().v.playbackRate));
|
||||
getUnskippedModeInfo(index: number, buttonText: string): SkipNoticeState {
|
||||
const maxCountdownTime = () => {
|
||||
const sponsorTime = this.segments[index];
|
||||
const duration = Math.round((sponsorTime.segment[1] - this.contentContainer().v.currentTime) * (1 / this.contentContainer().v.playbackRate));
|
||||
|
||||
return Math.max(duration, 4);
|
||||
};
|
||||
|
||||
return {
|
||||
unskipText: buttonText,
|
||||
|
||||
unskipCallback: (index) => this.reskip(index),
|
||||
|
||||
//change max duration to however much of the sponsor is left
|
||||
// change max duration to however much of the sponsor is left
|
||||
maxCountdownTime: maxCountdownTime,
|
||||
|
||||
countdownTime: maxCountdownTime()
|
||||
}
|
||||
} as SkipNoticeState;
|
||||
}
|
||||
|
||||
reskip(index: number): void {
|
||||
@@ -508,7 +500,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
}
|
||||
}
|
||||
|
||||
setNoticeInfoMessageWithOnClick(onClick: (event: React.MouseEvent) => any, ...messages: string[]): void {
|
||||
setNoticeInfoMessageWithOnClick(onClick: (event: React.MouseEvent) => unknown, ...messages: string[]): void {
|
||||
this.setState({
|
||||
messages,
|
||||
messageOnClick: (event) => onClick(event)
|
||||
@@ -521,7 +513,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
});
|
||||
}
|
||||
|
||||
addVoteButtonInfo(message): void {
|
||||
addVoteButtonInfo(message: string): void {
|
||||
this.setState({
|
||||
thanksForVotingText: message
|
||||
});
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface SponsorTimeEditState {
|
||||
sponsorTimeEdits: [string, string];
|
||||
}
|
||||
|
||||
const DEFAULT_CATEGORY = "chooseACategory";
|
||||
|
||||
class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, SponsorTimeEditState> {
|
||||
|
||||
idSuffix: string;
|
||||
@@ -217,27 +219,17 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
getCategoryOptions(): React.ReactElement[] {
|
||||
const elements = [(
|
||||
<option value={"chooseACategory"}
|
||||
key={"chooseACategory"}>
|
||||
{chrome.i18n.getMessage("chooseACategory")}
|
||||
<option value={DEFAULT_CATEGORY}
|
||||
key={DEFAULT_CATEGORY}>
|
||||
{chrome.i18n.getMessage(DEFAULT_CATEGORY)}
|
||||
</option>
|
||||
)];
|
||||
|
||||
for (const category of Config.config.categorySelections) {
|
||||
for (const category of CompileConfig.categoryList) {
|
||||
elements.push(
|
||||
<option value={category.name}
|
||||
key={category.name}>
|
||||
{chrome.i18n.getMessage("category_" + category.name)}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
if (elements.length < CompileConfig.categoryList.length) {
|
||||
// Add show more button
|
||||
elements.push(
|
||||
<option value={"moreCategories"}
|
||||
key={"moreCategories"}>
|
||||
{chrome.i18n.getMessage("moreCategories")}
|
||||
<option value={category}
|
||||
key={category}>
|
||||
{chrome.i18n.getMessage("category_" + category)}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
@@ -247,15 +239,20 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
categorySelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
// See if show more categories was pressed
|
||||
if (event.target.value === "moreCategories") {
|
||||
// Open options page
|
||||
chrome.runtime.sendMessage({"message": "openConfig"});
|
||||
|
||||
// Reset option to previous
|
||||
event.target.value = this.props.contentContainer().sponsorTimesSubmitting[this.props.index].category;
|
||||
if (!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
|
||||
if (confirm(chrome.i18n.getMessage("enableThisCategoryFirst")
|
||||
.replace("{0}", chrome.i18n.getMessage("category_" + chosenCategory)))) {
|
||||
// Open options page
|
||||
chrome.runtime.sendMessage({"message": "openConfig"});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.saveEditTimes();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as CompileConfig from "../config.json";
|
||||
import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime } from "./types";
|
||||
import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject } from "./types";
|
||||
|
||||
import Utils from "./utils";
|
||||
const utils = new Utils();
|
||||
@@ -58,8 +58,8 @@ interface SBConfig {
|
||||
}
|
||||
}
|
||||
|
||||
interface SBObject {
|
||||
configListeners: Array<Function>;
|
||||
export interface SBObject {
|
||||
configListeners: Array<(changes: StorageChangesObject) => unknown>;
|
||||
defaults: SBConfig;
|
||||
localConfig: SBConfig;
|
||||
config: SBConfig;
|
||||
@@ -275,8 +275,8 @@ function decodeStoredItem<T>(id: string, data: T): T | SBMap<string, SponsorTime
|
||||
return data;
|
||||
}
|
||||
|
||||
function configProxy(): any {
|
||||
chrome.storage.onChanged.addListener((changes, namespace) => {
|
||||
function configProxy(): SBConfig {
|
||||
chrome.storage.onChanged.addListener((changes: {[key: string]: chrome.storage.StorageChange}) => {
|
||||
for (const key in changes) {
|
||||
Config.localConfig[key] = decodeStoredItem(key, changes[key].newValue);
|
||||
}
|
||||
@@ -286,8 +286,8 @@ function configProxy(): any {
|
||||
}
|
||||
});
|
||||
|
||||
const handler: ProxyHandler<any> = {
|
||||
set(obj, prop, value) {
|
||||
const handler: ProxyHandler<SBConfig> = {
|
||||
set<K extends keyof SBConfig>(obj: SBConfig, prop: K, value: SBConfig[K]) {
|
||||
Config.localConfig[prop] = value;
|
||||
|
||||
chrome.storage.sync.set({
|
||||
@@ -297,13 +297,13 @@ function configProxy(): any {
|
||||
return true;
|
||||
},
|
||||
|
||||
get(obj, prop): any {
|
||||
get<K extends keyof SBConfig>(obj: SBConfig, prop: K): SBConfig[K] {
|
||||
const data = Config.localConfig[prop];
|
||||
|
||||
return obj[prop] || data;
|
||||
},
|
||||
|
||||
deleteProperty(obj, prop) {
|
||||
deleteProperty(obj: SBConfig, prop: keyof SBConfig) {
|
||||
chrome.storage.sync.remove(<string> prop);
|
||||
|
||||
return true;
|
||||
@@ -311,11 +311,11 @@ function configProxy(): any {
|
||||
|
||||
};
|
||||
|
||||
return new Proxy({handler}, handler);
|
||||
return new Proxy<SBConfig>({handler} as unknown as SBConfig, handler);
|
||||
}
|
||||
|
||||
function fetchConfig(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.sync.get(null, function(items) {
|
||||
Config.localConfig = <SBConfig> <unknown> items; // Data is ready
|
||||
resolve();
|
||||
@@ -439,11 +439,6 @@ async function setupConfig() {
|
||||
Config.config = config;
|
||||
}
|
||||
|
||||
// Reset config
|
||||
function resetConfig() {
|
||||
Config.config = Config.defaults;
|
||||
}
|
||||
|
||||
function convertJSON(): void {
|
||||
Object.keys(Config.localConfig).forEach(key => {
|
||||
Config.localConfig[key] = decodeStoredItem(key, Config.localConfig[key]);
|
||||
|
||||
116
src/content.ts
116
src/content.ts
@@ -1,6 +1,6 @@
|
||||
import Config from "./config";
|
||||
|
||||
import { SponsorTime, CategorySkipOption, CategorySelection, VideoID, SponsorHideType, FetchResponse } from "./types";
|
||||
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types";
|
||||
|
||||
import { ContentContainer } from "./types";
|
||||
import Utils from "./utils";
|
||||
@@ -12,6 +12,7 @@ import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar";
|
||||
import SkipNotice from "./render/SkipNotice";
|
||||
import SkipNoticeComponent from "./components/SkipNoticeComponent";
|
||||
import SubmissionNotice from "./render/SubmissionNotice";
|
||||
import { Message, MessageResponse } from "./messageTypes";
|
||||
|
||||
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
||||
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
||||
@@ -25,9 +26,9 @@ let sponsorTimes: SponsorTime[] = null;
|
||||
let sponsorVideoID: VideoID = null;
|
||||
|
||||
// JSON video info
|
||||
let videoInfo: any = null;
|
||||
let videoInfo: VideoInfo = null;
|
||||
//the channel this video is about
|
||||
let channelID;
|
||||
let channelID: string;
|
||||
|
||||
// Skips are scheduled to ensure precision.
|
||||
// Skips are rescheduled every seeking event.
|
||||
@@ -53,6 +54,9 @@ let durationListenerSetUp = false;
|
||||
// Is the video currently being switched
|
||||
let switchingVideos = null;
|
||||
|
||||
// Made true every videoID change
|
||||
let firstEvent = false;
|
||||
|
||||
// Used by the play and playing listeners to make sure two aren't
|
||||
// called at the same time
|
||||
let lastCheckTime = 0;
|
||||
@@ -112,7 +116,7 @@ const skipNoticeContentContainer: ContentContainer = () => ({
|
||||
//get messages from the background script and the popup
|
||||
chrome.runtime.onMessage.addListener(messageListener);
|
||||
|
||||
function messageListener(request: any, sender: any, sendResponse: (response: any) => void): void {
|
||||
function messageListener(request: Message, sender: unknown, sendResponse: (response: MessageResponse) => void): void {
|
||||
//messages from popup script
|
||||
switch(request.message){
|
||||
case "update":
|
||||
@@ -145,27 +149,6 @@ function messageListener(request: any, sender: any, sendResponse: (response: any
|
||||
videoID: sponsorVideoID
|
||||
});
|
||||
|
||||
break;
|
||||
case "getVideoDuration":
|
||||
sendResponse({
|
||||
duration: video.duration
|
||||
});
|
||||
|
||||
break;
|
||||
case "skipToTime":
|
||||
video.currentTime = request.time;
|
||||
|
||||
// Unpause the video if needed
|
||||
if (video.paused){
|
||||
video.play();
|
||||
}
|
||||
|
||||
return;
|
||||
case "getCurrentTime":
|
||||
sendResponse({
|
||||
currentTime: getRealCurrentTime()
|
||||
});
|
||||
|
||||
break;
|
||||
case "getChannelID":
|
||||
sendResponse({
|
||||
@@ -190,7 +173,6 @@ function messageListener(request: any, sender: any, sendResponse: (response: any
|
||||
break;
|
||||
case "submitTimes":
|
||||
submitSponsorTimes();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -200,7 +182,7 @@ function messageListener(request: any, sender: any, sendResponse: (response: any
|
||||
*
|
||||
* @param {String} changes
|
||||
*/
|
||||
function contentConfigUpdateListener(changes) {
|
||||
function contentConfigUpdateListener(changes: StorageChangesObject) {
|
||||
for (const key in changes) {
|
||||
switch(key) {
|
||||
case "hideVideoPlayerControls":
|
||||
@@ -265,6 +247,8 @@ function resetValues() {
|
||||
switchingVideos = true;
|
||||
}
|
||||
|
||||
firstEvent = true;
|
||||
|
||||
// Reset advert playing flag
|
||||
isAdPlaying = false;
|
||||
}
|
||||
@@ -309,12 +293,18 @@ async function videoIDChange(id) {
|
||||
if (onMobileYouTube) {
|
||||
// Mobile YouTube workaround
|
||||
const observer = new MutationObserver(handleMobileControlsMutations);
|
||||
let controlsContainer = null;
|
||||
|
||||
observer.observe(document.getElementById("player-control-container"), {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
utils.wait(() => {
|
||||
controlsContainer = document.getElementById("player-control-container")
|
||||
return controlsContainer !== null
|
||||
}).then(() => {
|
||||
observer.observe(document.getElementById("player-control-container"), {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}).catch();
|
||||
} else {
|
||||
utils.wait(getControls).then(createPreviewBar);
|
||||
}
|
||||
@@ -368,18 +358,6 @@ async function videoIDChange(id) {
|
||||
}
|
||||
|
||||
function handleMobileControlsMutations(): void {
|
||||
updateVisibilityOfPlayerControlsButton().then((createdButtons) => {
|
||||
if (createdButtons) {
|
||||
if (sponsorTimesSubmitting != null && sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length >= 2) {
|
||||
changeStartSponsorButton(true, true);
|
||||
} else if (sponsorTimesSubmitting != null && sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length < 2) {
|
||||
changeStartSponsorButton(false, true);
|
||||
} else {
|
||||
changeStartSponsorButton(true, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (previewBar !== null) {
|
||||
if (document.body.contains(previewBar.container)) {
|
||||
const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background");
|
||||
@@ -433,7 +411,7 @@ function createPreviewBar(): void {
|
||||
* Triggered every time the video duration changes.
|
||||
* This happens when the resolution changes or at random time to clear memory.
|
||||
*/
|
||||
function durationChangeListener() {
|
||||
function durationChangeListener(): void {
|
||||
updateAdFlag();
|
||||
updatePreviewBar();
|
||||
}
|
||||
@@ -568,6 +546,12 @@ async function sponsorsLookup(id: string) {
|
||||
video.addEventListener('play', () => {
|
||||
switchingVideos = false;
|
||||
|
||||
// If it is not the first event, then the only way to get to 0 is if there is a seek event
|
||||
// This check makes sure that changing the video resolution doesn't cause the extension to think it
|
||||
// gone back to the begining
|
||||
if (!firstEvent && video.currentTime === 0) return;
|
||||
firstEvent = false;
|
||||
|
||||
// Check if an ad is playing
|
||||
updateAdFlag();
|
||||
|
||||
@@ -601,6 +585,8 @@ async function sponsorsLookup(id: string) {
|
||||
}
|
||||
});
|
||||
video.addEventListener('ratechange', () => startSponsorSchedule());
|
||||
// Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740)
|
||||
video.addEventListener('videoSpeed_ratechange', () => startSponsorSchedule());
|
||||
video.addEventListener('pause', () => {
|
||||
// Reset lastCheckVideoTime
|
||||
lastCheckVideoTime = -1;
|
||||
@@ -762,18 +748,18 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
/**
|
||||
* Get the video info for the current tab from YouTube
|
||||
*/
|
||||
function getVideoInfo() {
|
||||
sendRequestToCustomServer('GET', "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID, function(xmlhttp, error) {
|
||||
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
||||
const decodedData = decodeURIComponent(xmlhttp.responseText).match(/player_response=([^&]*)/)[1];
|
||||
if (!decodedData) {
|
||||
console.error("[SB] Failed at getting video info from YouTube.");
|
||||
return;
|
||||
}
|
||||
async function getVideoInfo(): Promise<void> {
|
||||
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID);
|
||||
|
||||
videoInfo = JSON.parse(decodedData);
|
||||
if (result.ok) {
|
||||
const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1];
|
||||
if (!decodedData) {
|
||||
console.error("[SB] Failed at getting video info from YouTube.");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
videoInfo = JSON.parse(decodedData);
|
||||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoID(url: string) {
|
||||
@@ -827,7 +813,7 @@ function updatePreviewBarPositionMobile(parent: HTMLElement) {
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreviewBar() {
|
||||
function updatePreviewBar(): void {
|
||||
if (previewBar === null) return;
|
||||
|
||||
if (isAdPlaying) {
|
||||
@@ -1074,6 +1060,8 @@ function unskipSponsorTime(segment: SponsorTime) {
|
||||
|
||||
function reskipSponsorTime(segment: SponsorTime) {
|
||||
video.currentTime = segment.segment[1];
|
||||
|
||||
startSponsorSchedule(true, segment.segment[1], false);
|
||||
}
|
||||
|
||||
function createButton(baseID, title, callback, imageName, isDraggable=false): boolean {
|
||||
@@ -1086,7 +1074,7 @@ function createButton(baseID, title, callback, imageName, isDraggable=false): bo
|
||||
newButton.classList.add("playerButton");
|
||||
newButton.classList.add("ytp-button");
|
||||
newButton.setAttribute("title", chrome.i18n.getMessage(title));
|
||||
newButton.addEventListener("click", (event: Event) => {
|
||||
newButton.addEventListener("click", () => {
|
||||
callback();
|
||||
});
|
||||
|
||||
@@ -1153,6 +1141,7 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<boolean> {
|
||||
if (!sponsorVideoID) return false;
|
||||
|
||||
const createdButtons = await createButtons();
|
||||
if (!createdButtons) return;
|
||||
|
||||
if (Config.config.hideVideoPlayerControls || onInvidious) {
|
||||
document.getElementById("startSponsorButton").style.display = "none";
|
||||
@@ -1203,6 +1192,7 @@ function startSponsorClicked() {
|
||||
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
|
||||
sponsorTimesSubmitting.push({
|
||||
@@ -1244,8 +1234,8 @@ function updateSponsorTimesSubmitting(getFromConfig = true) {
|
||||
}
|
||||
}
|
||||
|
||||
async function changeStartSponsorButton(showStartSponsor, uploadButtonVisible) {
|
||||
if(!sponsorVideoID) return false;
|
||||
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"
|
||||
@@ -1445,7 +1435,7 @@ function dontShowNoticeAgain() {
|
||||
closeAllSkipNotices();
|
||||
}
|
||||
|
||||
function sponsorMessageStarted(callback) {
|
||||
function sponsorMessageStarted(callback: (response: MessageResponse) => void) {
|
||||
video = document.querySelector('video');
|
||||
|
||||
//send back current time
|
||||
@@ -1470,8 +1460,6 @@ function submitSponsorTimes() {
|
||||
//it can't update to this info yet
|
||||
closeInfoMenu();
|
||||
|
||||
const currentVideoID = sponsorVideoID;
|
||||
|
||||
if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) {
|
||||
submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage);
|
||||
}
|
||||
@@ -1618,7 +1606,7 @@ function sendRequestToCustomServer(type, fullAddress, callback) {
|
||||
callback(xmlhttp, false);
|
||||
};
|
||||
|
||||
xmlhttp.onerror = function(ev) {
|
||||
xmlhttp.onerror = function() {
|
||||
callback(xmlhttp, true);
|
||||
};
|
||||
}
|
||||
@@ -1630,7 +1618,7 @@ function sendRequestToCustomServer(type, fullAddress, callback) {
|
||||
/**
|
||||
* Update the isAdPlaying flag and hide preview bar/controls if ad is playing
|
||||
*/
|
||||
function updateAdFlag() {
|
||||
function updateAdFlag(): void {
|
||||
const wasAdPlaying = isAdPlaying;
|
||||
isAdPlaying = document.getElementsByClassName('ad-showing').length > 0;
|
||||
|
||||
|
||||
19
src/globals.d.ts
vendored
Normal file
19
src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { SBObject } from "./config";
|
||||
declare global {
|
||||
interface Window { SB: SBObject; }
|
||||
// Remove this once the API becomes stable and types are shipped in @types/chrome
|
||||
namespace chrome {
|
||||
namespace declarativeContent {
|
||||
export interface RequestContentScriptOptions {
|
||||
allFrames?: boolean;
|
||||
css?: string[];
|
||||
instanceType?: "declarativeContent.RequestContentScript";
|
||||
js?: string[];
|
||||
matchAboutBlanck?: boolean;
|
||||
}
|
||||
export class RequestContentScript {
|
||||
constructor(options: RequestContentScriptOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/messageTypes.ts
Normal file
63
src/messageTypes.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Message and Response Types
|
||||
//
|
||||
|
||||
import { SponsorTime } from "./types";
|
||||
|
||||
interface BaseMessage {
|
||||
from?: string;
|
||||
}
|
||||
|
||||
interface DefaultMessage {
|
||||
message:
|
||||
"update"
|
||||
| "sponsorStart"
|
||||
| "sponsorDataChanged"
|
||||
| "isInfoFound"
|
||||
| "getVideoID"
|
||||
| "getChannelID"
|
||||
| "isChannelWhitelisted"
|
||||
| "submitTimes";
|
||||
}
|
||||
|
||||
interface BoolValueMessage {
|
||||
message: "whitelistChange";
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface ChangeStartSponsorButtonMessage {
|
||||
message: "changeStartSponsorButton";
|
||||
showStartSponsor: boolean;
|
||||
uploadButtonVisible: boolean;
|
||||
}
|
||||
|
||||
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | ChangeStartSponsorButtonMessage);
|
||||
|
||||
interface IsInfoFoundMessageResponse {
|
||||
found: boolean;
|
||||
sponsorTimes: SponsorTime[];
|
||||
}
|
||||
|
||||
interface GetVideoIdResponse {
|
||||
videoID: string;
|
||||
}
|
||||
|
||||
interface GetChannelIDResponse {
|
||||
channelID: string;
|
||||
}
|
||||
|
||||
interface SponsorStartResponse {
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface IsChannelWhitelistedResponse {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export type MessageResponse =
|
||||
IsInfoFoundMessageResponse
|
||||
| GetVideoIdResponse
|
||||
| GetChannelIDResponse
|
||||
| SponsorStartResponse
|
||||
| IsChannelWhitelistedResponse;
|
||||
|
||||
@@ -2,7 +2,7 @@ import Config from "./config";
|
||||
import * as CompileConfig from "../config.json";
|
||||
|
||||
// Make the config public for debugging purposes
|
||||
(<any> window).SB = Config;
|
||||
window.SB = Config;
|
||||
|
||||
import Utils from "./utils";
|
||||
import CategoryChooser from "./render/CategoryChooser";
|
||||
@@ -107,7 +107,7 @@ async function init() {
|
||||
|
||||
// Permission needed on Firefox
|
||||
if (utils.isFirefox()) {
|
||||
const permissionSuccess = await new Promise((resolve, reject) => {
|
||||
const permissionSuccess = await new Promise((resolve) => {
|
||||
chrome.permissions.request({
|
||||
origins: [textChangeInput.value + "/"],
|
||||
permissions: []
|
||||
@@ -202,7 +202,7 @@ async function init() {
|
||||
*
|
||||
* @param {String} element
|
||||
*/
|
||||
function optionsConfigUpdateListener(changes) {
|
||||
function optionsConfigUpdateListener() {
|
||||
const optionsContainer = document.getElementById("options");
|
||||
const optionsElements = optionsContainer.querySelectorAll("*");
|
||||
|
||||
@@ -243,7 +243,7 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
||||
const button = element.querySelector(".trigger-button");
|
||||
|
||||
const setButton = element.querySelector(".text-change-set");
|
||||
setButton.addEventListener("click", async function(e) {
|
||||
setButton.addEventListener("click", async function() {
|
||||
if (textBox.value == "" || textBox.value.includes("/") || textBox.value.includes("http")) {
|
||||
alert(chrome.i18n.getMessage("addInvidiousInstanceError"));
|
||||
} else {
|
||||
@@ -269,7 +269,7 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
||||
});
|
||||
|
||||
const resetButton = element.querySelector(".invidious-instance-reset");
|
||||
resetButton.addEventListener("click", function(e) {
|
||||
resetButton.addEventListener("click", function() {
|
||||
if (confirm(chrome.i18n.getMessage("resetInvidiousInstanceAlert"))) {
|
||||
// Set to a clone of the default
|
||||
Config.config[option] = Config.defaults[option].slice(0);
|
||||
@@ -536,7 +536,7 @@ function copyDebugOutputToClipboard() {
|
||||
.then(() => {
|
||||
alert(chrome.i18n.getMessage("copyDebugInformationComplete"));
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() => {
|
||||
alert(chrome.i18n.getMessage("copyDebugInformationFailed"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
41
src/popup.ts
41
src/popup.ts
@@ -2,11 +2,12 @@ import Config from "./config";
|
||||
|
||||
import Utils from "./utils";
|
||||
import { SponsorTime, SponsorHideType } from "./types";
|
||||
import { Message, MessageResponse } from "./messageTypes";
|
||||
const utils = new Utils();
|
||||
|
||||
interface MessageListener {
|
||||
(request: any, sender: any, callback: (response: any) => void): void;
|
||||
}
|
||||
(request: Message, sender: unknown, sendResponse: (response: MessageResponse) => void): void;
|
||||
}
|
||||
|
||||
class MessageHandler {
|
||||
messageListener: MessageListener;
|
||||
@@ -15,7 +16,7 @@ class MessageHandler {
|
||||
this.messageListener = messageListener;
|
||||
}
|
||||
|
||||
sendMessage(id: number, request, callback?) {
|
||||
sendMessage(id: number, request: Message, callback?) {
|
||||
if (this.messageListener) {
|
||||
this.messageListener(request, null, callback);
|
||||
} else {
|
||||
@@ -37,6 +38,8 @@ class MessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//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);
|
||||
@@ -45,7 +48,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
|
||||
await utils.wait(() => Config.config !== null);
|
||||
|
||||
const PageElements: any = {};
|
||||
type InputPageElements = {
|
||||
whitelistToggle?: HTMLInputElement,
|
||||
toggleSwitch?: HTMLInputElement,
|
||||
usernameInput?: HTMLInputElement,
|
||||
};
|
||||
type PageElements = { [key: string]: HTMLElement } & InputPageElements
|
||||
|
||||
const PageElements: PageElements = {};
|
||||
|
||||
[
|
||||
"sponsorblockPopup",
|
||||
@@ -375,7 +385,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
const sponsorTimeButton = document.createElement("button");
|
||||
sponsorTimeButton.className = "segmentTimeButton popupElement";
|
||||
|
||||
const prefix = chrome.i18n.getMessage("category_" + segmentTimes[i].category) + ": ";
|
||||
const prefix = utils.shortCategoryName(segmentTimes[i].category) + ": ";
|
||||
|
||||
let extraInfo = "";
|
||||
if (segmentTimes[i].hidden === SponsorHideType.Downvoted) {
|
||||
@@ -584,7 +594,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//converts time in seconds to minutes:seconds
|
||||
function getFormattedTime(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
@@ -715,25 +725,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
hiddenButton.style.display = "none";
|
||||
}
|
||||
|
||||
//converts time in seconds to minutes
|
||||
function getTimeInMinutes(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
|
||||
return minutes;
|
||||
}
|
||||
|
||||
//converts time in seconds to seconds past the last minute
|
||||
function getTimeInFormattedSeconds(seconds) {
|
||||
const minutes = seconds % 60;
|
||||
let secondsFormatted = minutes.toFixed(3);
|
||||
|
||||
if (minutes < 10) {
|
||||
secondsFormatted = "0" + secondsFormatted;
|
||||
}
|
||||
|
||||
return secondsFormatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts time in hours to 5h 25.1
|
||||
* If less than 1 hour, just returns minutes
|
||||
|
||||
88
src/types.ts
88
src/types.ts
@@ -3,7 +3,7 @@ import SkipNoticeComponent from "./components/SkipNoticeComponent";
|
||||
|
||||
interface ContentContainer {
|
||||
(): {
|
||||
vote: (type: any, UUID: any, category?: string, skipNotice?: SkipNoticeComponent) => void,
|
||||
vote: (type: number, UUID: string, category?: string, skipNotice?: SkipNoticeComponent) => void,
|
||||
dontShowNoticeAgain: () => void,
|
||||
unskipSponsorTime: (segment: SponsorTime) => void,
|
||||
sponsorTimes: SponsorTime[],
|
||||
@@ -15,9 +15,9 @@ interface ContentContainer {
|
||||
onMobileYouTube: boolean,
|
||||
sponsorSubmissionNotice: SubmissionNotice,
|
||||
resetSponsorSubmissionNotice: () => void,
|
||||
changeStartSponsorButton: (showStartSponsor: any, uploadButtonVisible: any) => Promise<boolean>,
|
||||
changeStartSponsorButton: (showStartSponsor: boolean, uploadButtonVisible: boolean) => Promise<boolean>,
|
||||
previewTime: (time: number, unpause?: boolean) => void,
|
||||
videoInfo: any,
|
||||
videoInfo: VideoInfo,
|
||||
getRealCurrentTime: () => number
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,86 @@ interface BackgroundScriptContainer {
|
||||
unregisterFirefoxContentScript: (id: string) => void
|
||||
}
|
||||
|
||||
interface VideoInfo {
|
||||
responseContext: {
|
||||
serviceTrackingParams: Array<{service: string, params: Array<{key: string, value: string}>}>,
|
||||
webResponseContextExtensionData: {
|
||||
hasDecorated: boolean
|
||||
}
|
||||
},
|
||||
playabilityStatus: {
|
||||
status: string,
|
||||
playableInEmbed: boolean,
|
||||
miniplayer: {
|
||||
miniplayerRenderer: {
|
||||
playbackMode: string
|
||||
}
|
||||
}
|
||||
};
|
||||
streamingData: unknown;
|
||||
playbackTracking: unknown;
|
||||
videoDetails: {
|
||||
videoId: string,
|
||||
title: string,
|
||||
lengthSeconds: string,
|
||||
keywords: string[],
|
||||
channelId: string,
|
||||
isOwnerViewing: boolean,
|
||||
shortDescription: string,
|
||||
isCrawlable: boolean,
|
||||
thumbnail: {
|
||||
thumbnails: Array<{url: string, width: number, height: number}>
|
||||
},
|
||||
averageRating: number,
|
||||
allowRatings: boolean,
|
||||
viewCount: string,
|
||||
author: string,
|
||||
isPrivate: boolean,
|
||||
isUnpluggedCorpus: boolean,
|
||||
isLiveContent: boolean,
|
||||
};
|
||||
playerConfig: unknown;
|
||||
storyboards: unknown;
|
||||
microformat: {
|
||||
playerMicroformatRenderer: {
|
||||
thumbnail: {
|
||||
thumbnails: Array<{url: string, width: number, height: number}>
|
||||
},
|
||||
embed: {
|
||||
iframeUrl: string,
|
||||
flashUrl: string,
|
||||
width: number,
|
||||
height: number,
|
||||
flashSecureUrl: string,
|
||||
},
|
||||
title: {
|
||||
simpleText: string,
|
||||
},
|
||||
description: {
|
||||
simpleText: string,
|
||||
},
|
||||
lengthSeconds: string,
|
||||
ownerProfileUrl: string,
|
||||
externalChannelId: string,
|
||||
availableCountries: string[],
|
||||
isUnlisted: boolean,
|
||||
hasYpcMetadata: boolean,
|
||||
viewCount: string,
|
||||
category: string,
|
||||
publishDate: string,
|
||||
ownerChannelName: string,
|
||||
uploadDate: string,
|
||||
}
|
||||
};
|
||||
trackingParams: string;
|
||||
attestation: unknown;
|
||||
messages: unknown;
|
||||
}
|
||||
|
||||
type VideoID = string;
|
||||
|
||||
type StorageChangesObject = { [key: string]: chrome.storage.StorageChange };
|
||||
|
||||
export {
|
||||
FetchResponse,
|
||||
VideoDurationResponse,
|
||||
@@ -91,5 +169,7 @@ export {
|
||||
SponsorHideType,
|
||||
PreviewBarOption,
|
||||
Registration,
|
||||
BackgroundScriptContainer
|
||||
BackgroundScriptContainer,
|
||||
VideoInfo,
|
||||
StorageChangesObject,
|
||||
};
|
||||
|
||||
24
src/utils.ts
24
src/utils.ts
@@ -54,18 +54,16 @@ class Utils {
|
||||
setupExtraSitePermissions(callback: (granted: boolean) => void): void {
|
||||
// Request permission
|
||||
let permissions = ["declarativeContent"];
|
||||
if (this.isFirefox()) permissions = [];
|
||||
|
||||
const self = this;
|
||||
if (this.isFirefox()) permissions = [];
|
||||
|
||||
chrome.permissions.request({
|
||||
origins: this.getInvidiousInstancesRegex(),
|
||||
permissions: permissions
|
||||
}, async function (granted) {
|
||||
}, async (granted) => {
|
||||
if (granted) {
|
||||
self.setupExtraSiteContentScripts();
|
||||
this.setupExtraSiteContentScripts();
|
||||
} else {
|
||||
self.removeExtraSiteRegistration();
|
||||
this.removeExtraSiteRegistration();
|
||||
}
|
||||
|
||||
callback(granted);
|
||||
@@ -80,7 +78,6 @@ class Utils {
|
||||
* For now, it is just SB.config.invidiousInstances.
|
||||
*/
|
||||
setupExtraSiteContentScripts(): void {
|
||||
const self = this;
|
||||
|
||||
if (this.isFirefox()) {
|
||||
const firefoxJS = [];
|
||||
@@ -107,9 +104,9 @@ class Utils {
|
||||
chrome.runtime.sendMessage(registration);
|
||||
}
|
||||
} else {
|
||||
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], function() {
|
||||
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], () => {
|
||||
const conditions = [];
|
||||
for (const regex of self.getInvidiousInstancesRegex()) {
|
||||
for (const regex of this.getInvidiousInstancesRegex()) {
|
||||
conditions.push(new chrome.declarativeContent.PageStateMatcher({
|
||||
pageUrl: { urlMatches: regex }
|
||||
}));
|
||||
@@ -119,11 +116,10 @@ class Utils {
|
||||
const rule = {
|
||||
id: "invidious",
|
||||
conditions,
|
||||
// This API is experimental and not visible by the TypeScript compiler
|
||||
actions: [new (<any> chrome.declarativeContent).RequestContentScript({
|
||||
actions: [new chrome.declarativeContent.RequestContentScript({
|
||||
allFrames: true,
|
||||
js: self.js,
|
||||
css: self.css
|
||||
js: this.js,
|
||||
css: this.css
|
||||
})]
|
||||
};
|
||||
|
||||
@@ -243,7 +239,7 @@ class Utils {
|
||||
|
||||
getLocalizedMessage(text: string): string | false {
|
||||
const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
|
||||
return v1 ? chrome.i18n.getMessage(v1) : "";
|
||||
return v1 ? chrome.i18n.getMessage(v1).replace("\n", "<br/>") : "";
|
||||
});
|
||||
|
||||
if(valNewH != text) {
|
||||
|
||||
Reference in New Issue
Block a user