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:
Ajay Ramachandran
2020-12-24 12:41:40 -05:00
52 changed files with 1128 additions and 1591 deletions

View File

@@ -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() {

View File

@@ -1,6 +1,5 @@
import * as React from "react";
import Config from "../config"
import * as CompileConfig from "../../config.json";
import CategorySkipOptionsComponent from "./CategorySkipOptionsComponent";

View File

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

View File

@@ -28,7 +28,7 @@ export interface NoticeState {
class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
countdownInterval: NodeJS.Timeout;
idSuffix: any;
idSuffix: string;
amountOfPreviousNotices: number;

View File

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

View File

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

View File

@@ -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();
}

View File

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

View File

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

View File

@@ -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"));
});
}
}

View File

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

View File

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

View File

@@ -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) {