Merge pull request #1521 from mini-bomba/popup

Update popup on segment updates + some code cleanup
This commit is contained in:
Ajay Ramachandran 2022-10-11 20:57:21 -04:00 committed by GitHub
commit cd78c46ef8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 261 additions and 287 deletions

View file

@ -106,6 +106,8 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
return true;
}
case "time":
case "infoUpdated":
case "videoChanged":
if (sender.tab) {
popupPort[sender.tab.id]?.postMessage(request);
}

View file

@ -215,7 +215,6 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
case "getVideoID":
sendResponse({
videoID: sponsorVideoID,
creatingSegment: isSegmentCreationInProgress(),
});
break;
@ -243,15 +242,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
// update video on refresh if videoID invalid
if (!sponsorVideoID) videoIDChange(getYouTubeVideoID(document));
// fetch segments
sponsorsLookup(false).then(() => sendResponse({
found: sponsorDataFound,
status: lastResponseStatus,
sponsorTimes: sponsorTimes,
time: video.currentTime,
onMobileYouTube
}));
sponsorsLookup(false);
return true;
break;
case "unskip":
unskipSponsorTime(sponsorTimes.find((segment) => segment.UUID === request.UUID), null, true);
break;
@ -384,7 +377,7 @@ function resetValues() {
categoryPill?.setVisibility(false);
}
async function videoIDChange(id): Promise<void> {
async function videoIDChange(id: string): Promise<void> {
// don't switch to invalid value
if (!id && sponsorVideoID && !document?.URL?.includes("youtube.com/clip/")) return;
//if the id has not changed return unless the video element has changed
@ -438,10 +431,14 @@ async function videoIDChange(id): Promise<void> {
}
}
//close popup
closeInfoMenu();
// Notify the popup about the video change
chrome.runtime.sendMessage({
message: "videoChanged",
videoID: sponsorVideoID,
whitelisted: channelWhitelisted
});
sponsorsLookup(id);
sponsorsLookup();
// Make sure all player buttons are properly added
updateVisibilityOfPlayerControlsButton();
@ -1004,6 +1001,14 @@ async function sponsorsLookup(keepOldSubmissions = true) {
?.sort((a, b) => a.segment[0] - b.segment[0]);
if (!recievedSegments || !recievedSegments.length) {
// return if no video found
chrome.runtime.sendMessage({
message: "infoUpdated",
found: false,
status: lastResponseStatus,
sponsorTimes: sponsorTimes,
time: video.currentTime,
onMobileYouTube
});
retryFetch(404);
return;
}
@ -1093,6 +1098,16 @@ async function sponsorsLookup(keepOldSubmissions = true) {
importExistingChapters(true);
// notify popup of segment changes
chrome.runtime.sendMessage({
message: "infoUpdated",
found: sponsorDataFound,
status: lastResponseStatus,
sponsorTimes: sponsorTimes,
time: video.currentTime,
onMobileYouTube
});
if (Config.config.isVip) {
lockedCategoriesLookup();
}
@ -1138,8 +1153,8 @@ async function lockedCategoriesLookup(): Promise<void> {
}
function retryFetch(errorCode: number): void {
if (!Config.config.refetchWhenNotFound) return;
sponsorDataFound = false;
if (!Config.config.refetchWhenNotFound) return;
if (retryFetchTimeout) clearTimeout(retryFetchTimeout);
if ((errorCode !== 404 && retryCount > 1) || (errorCode !== 404 && retryCount > 10)) {
@ -1219,12 +1234,12 @@ function startSkipScheduleCheckingForStartSponsors() {
}
}
function getYouTubeVideoID(document: Document, url?: string): string | boolean {
function getYouTubeVideoID(document: Document, url?: string): string {
url ||= document.URL;
// pageType shortcut
if (pageType === PageType.Channel) return getYouTubeVideoIDFromDocument()
if (pageType === PageType.Channel) return getYouTubeVideoIDFromDocument();
// clips should never skip, going from clip to full video has no indications.
if (url.includes("youtube.com/clip/")) return false;
if (url.includes("youtube.com/clip/")) return null;
// skip to document and don't hide if on /embed/
if (url.includes("/embed/") && url.includes("youtube.com")) return getYouTubeVideoIDFromDocument(false, PageType.Embed);
// skip to URL if matches youtube watch or invidious or matches youtube pattern
@ -1235,7 +1250,7 @@ function getYouTubeVideoID(document: Document, url?: string): string | boolean {
return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(false);
}
function getYouTubeVideoIDFromDocument(hideIcon = true, pageHint = PageType.Watch): string | boolean {
function getYouTubeVideoIDFromDocument(hideIcon = true, pageHint = PageType.Watch): string {
const selector = "a.ytp-title-link[data-sessionlink='feature=player-title']";
// get ID from document (channel trailer / embedded playlist)
const element = pageHint === PageType.Embed ? document.querySelector(selector)
@ -1247,11 +1262,11 @@ function getYouTubeVideoIDFromDocument(hideIcon = true, pageHint = PageType.Watc
pageType = pageHint;
return getYouTubeVideoIDFromURL(videoURL);
} else {
return false;
return null;
}
}
function getYouTubeVideoIDFromURL(url: string): string | boolean {
function getYouTubeVideoIDFromURL(url: string): string {
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
//Attempt to parse url
@ -1260,7 +1275,7 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
urlObject = new URL(url);
} catch (e) {
console.error("[SB] Unable to parse URL: " + url);
return false;
return null;
}
// Check if valid hostname
@ -1274,7 +1289,7 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoIDFromURL(url)));
}
return false;
return null;
} else {
onInvidious = false;
}
@ -1282,17 +1297,17 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
//Get ID from searchParam
if (urlObject.searchParams.has("v") && ["/watch", "/watch/"].includes(urlObject.pathname) || urlObject.pathname.startsWith("/tv/watch")) {
const id = urlObject.searchParams.get("v");
return id.length == 11 ? id : false;
return id.length == 11 ? id : null;
} else if (urlObject.pathname.startsWith("/embed/") || urlObject.pathname.startsWith("/shorts/")) {
try {
const id = urlObject.pathname.split("/")[2]
if (id?.length >=11 ) return id.slice(0, 11);
} catch (e) {
console.error("[SB] Video ID not valid for " + url);
return false;
return null;
}
}
return false;
return null;
}
/**
@ -1794,7 +1809,8 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
updateEditButtonsOnPlayer();
// Don't show the info button on embeds
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) {
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious
|| document.getElementById("sponsorBlockPopupContainer") != null) {
playerButtons.info.button.style.display = "none";
} else {
playerButtons.info.button.style.removeProperty("display");

View file

@ -83,15 +83,15 @@ interface GetVideoIdResponse {
videoID: string;
}
interface GetChannelIDResponse {
export interface GetChannelIDResponse {
channelID: string;
}
interface SponsorStartResponse {
export interface SponsorStartResponse {
creatingSegment: boolean;
}
interface IsChannelWhitelistedResponse {
export interface IsChannelWhitelistedResponse {
value: boolean;
}
@ -111,7 +111,7 @@ export interface VoteResponse {
responseText: string;
}
export interface ImportSegmentsResponse {
interface ImportSegmentsResponse {
importedSegments: SponsorTime[];
}
@ -120,4 +120,14 @@ export interface TimeUpdateMessage {
time: number;
}
export type PopupMessage = TimeUpdateMessage;
export type InfoUpdatedMessage = IsInfoFoundMessageResponse & {
message: "infoUpdated";
}
export interface VideoChangedPopupMessage {
message: "videoChanged";
videoID: string;
whitelisted: boolean;
}
export type PopupMessage = TimeUpdateMessage | InfoUpdatedMessage | VideoChangedPopupMessage;

View file

@ -1,8 +1,24 @@
import Config from "./config";
import Utils from "./utils";
import { SponsorTime, SponsorHideType, ActionType, SegmentUUID, SponsorSourceType, StorageChangesObject } from "./types";
import { Message, MessageResponse, IsInfoFoundMessageResponse, ImportSegmentsResponse, PopupMessage } from "./messageTypes";
import {
ActionType,
SegmentUUID,
SponsorHideType,
SponsorSourceType,
SponsorTime,
StorageChangesObject,
} from "./types";
import {
GetChannelIDResponse,
IsChannelWhitelistedResponse,
IsInfoFoundMessageResponse,
Message,
MessageResponse,
PopupMessage,
SponsorStartResponse,
VoteResponse,
} from "./messageTypes";
import { showDonationLink } from "./utils/configUtils";
import { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils";
@ -11,6 +27,7 @@ import { localizeHtmlPage } from "./utils/pageUtils";
import { exportTimes } from "./utils/exporter";
import GenericNotice from "./render/GenericNotice";
import { noRefreshFetchingChaptersAllowed } from "./utils/licenseKey";
const utils = new Utils();
interface MessageListener {
@ -67,8 +84,7 @@ 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;
let stopLoadingAnimation = null;
//the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = [];
@ -376,7 +392,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
messageHandler.sendMessage(tabs[0].id, { message: 'getVideoID' }, function (result) {
if (result !== undefined && result.videoID) {
currentVideoID = result.videoID;
creatingSegment = result.creatingSegment;
loadTabData(tabs, updating);
} else if (result === undefined && chrome.runtime.lastError) {
@ -411,7 +426,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}, (tabs) => onTabs(tabs, updating));
}
function infoFound(request: IsInfoFoundMessageResponse) {
async function infoFound(request: IsInfoFoundMessageResponse) {
// End any loading animation
if (stopLoadingAnimation != null) {
stopLoadingAnimation();
stopLoadingAnimation = null;
}
if (chrome.runtime.lastError) {
//This page doesn't have the injected content script, or at least not yet
displayNoVideo();
@ -427,13 +448,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.loadingIndicator.style.display = "none";
downloadedTimes = request.sponsorTimes ?? [];
displayDownloadedSponsorTimes(downloadedTimes, request.time);
if (request.found) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound");
PageElements.issueReporterImportExport.classList.remove("hidden");
if (request.sponsorTimes) {
displayDownloadedSponsorTimes(request.sponsorTimes, request.time);
}
} else if (request.status == 404 || request.status == 200) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
PageElements.issueReporterImportExport.classList.remove("hidden");
@ -444,35 +462,18 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
//see if whitelist button should be swapped
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{ message: 'isChannelWhitelisted' },
function (response) {
const response = await sendTabMessageAsync({ message: 'isChannelWhitelisted' }) as IsChannelWhitelistedResponse;
if (response.value) {
PageElements.whitelistChannel.style.display = "none";
PageElements.unwhitelistChannel.style.display = "unset";
PageElements.whitelistToggle.checked = true;
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
}
});
}
);
}
function sendSponsorStartMessage() {
async function sendSponsorStartMessage() {
//the content script will get the message if a YouTube page is open
messageHandler.query({
active: true,
currentWindow: true,
}, (tabs) => {
messageHandler.sendMessage(
tabs[0].id,
{ from: 'popup', message: 'sponsorStart' },
async (response) => {
const response = await sendTabMessageAsync({ from: 'popup', message: 'sponsorStart' }) as SponsorStartResponse;
startSponsorCallback(response);
// Perform a second update after the config changes take effect as a workaround for a race condition
@ -490,16 +491,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
// Remove the listener after 200ms in case the changes were propagated by the time we got the response
setTimeout(() => removeListener(lateUpdate), 200);
},
);
});
}
function startSponsorCallback(response: { creatingSegment: boolean }) {
creatingSegment = response.creatingSegment;
function startSponsorCallback(response: SponsorStartResponse) {
// Only update the segments after a segment was created
if (!creatingSegment) {
if (!response.creatingSegment) {
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] || [];
}
@ -676,19 +672,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
downloadedTimes[i].hidden = SponsorHideType.Hidden;
}
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{
sendTabMessage({
message: "hideSegment",
type: downloadedTimes[i].hidden,
UUID: UUID
}
);
});
})
});
const skipButton = document.createElement("img");
@ -733,15 +721,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
function submitTimes() {
if (sponsorTimes.length > 0) {
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{ message: 'submitTimes' },
);
});
sendTabMessage({ message: 'submitTimes' })
}
}
@ -751,9 +731,16 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.showNoticeAgain.style.display = "none";
}
function isCreatingSegment(): boolean {
const segments = Config.config.unsubmittedSegments[currentVideoID];
if (!segments) return false;
const lastSegment = segments[segments.length - 1];
return lastSegment && lastSegment?.segment?.length !== 2;
}
/** Updates any UI related to segment editing and submission according to the current state. */
function updateSegmentEditingUI() {
PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart");
PageElements.sponsorStart.innerText = chrome.i18n.getMessage(isCreatingSegment() ? "sponsorEnd" : "sponsorStart");
PageElements.submitTimes.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
PageElements.submissionHint.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
@ -772,8 +759,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
chrome.runtime.sendMessage({ "message": "openHelp" });
}
function sendTabMessage(data: Message): Promise<unknown> {
return new Promise((resolve) => {
function sendTabMessage(data: Message, callback?) {
messageHandler.query({
active: true,
currentWindow: true
@ -781,11 +767,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
messageHandler.sendMessage(
tabs[0].id,
data,
(response) => resolve(response)
callback
);
}
);
});
}
function sendTabMessageAsync(data: Message): Promise<unknown> {
return new Promise((resolve) => sendTabMessage(data, (response) => resolve(response)))
}
//make the options username setting option visible
@ -862,21 +851,15 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
thanksForVotingText.removeAttribute("innerText");
}
function vote(type, UUID) {
async function vote(type, UUID) {
//add loading info
addVoteMessage(chrome.i18n.getMessage("Loading"), UUID);
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{
const response = await sendTabMessageAsync({
message: "submitVote",
type: type,
UUID: UUID
}, function (response) {
}) as VoteResponse;
if (response != undefined) {
//see if it was a success or failure
if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) {
@ -888,20 +871,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
setTimeout(() => removeVoteMessage(UUID), 1500);
}
}
);
});
}
function whitelistChannel() {
async function whitelistChannel() {
//get the channel url
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{ message: 'getChannelID' },
function (response) {
const response = await sendTabMessageAsync({ message: 'getChannelID' }) as GetChannelIDResponse;
if (!response.channelID) {
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753");
return;
@ -928,32 +901,16 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
Config.config.whitelistedChannels = whitelistedChannels;
//send a message to the client
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id, {
sendTabMessage({
message: 'whitelistChange',
value: true
});
}
);
}
);
});
}
function unwhitelistChannel() {
async function unwhitelistChannel() {
//get the channel url
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{ message: 'getChannelID' },
function (response) {
const response = await sendTabMessageAsync({ message: 'getChannelID' }) as GetChannelIDResponse;
//get whitelisted channels
let whitelistedChannels = Config.config.whitelistedChannels;
if (whitelistedChannels == undefined) {
@ -976,49 +933,29 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
Config.config.whitelistedChannels = whitelistedChannels;
//send a message to the client
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id, {
sendTabMessage({
message: 'whitelistChange',
value: false
});
}
);
}
);
});
function startLoadingAnimation() {
stopLoadingAnimation = AnimationUtils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3);
}
function refreshSegments() {
const stopAnimation = AnimationUtils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3);
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{ message: 'refreshSegments' },
(response) => {
infoFound(response);
stopAnimation();
}
)
}
);
startLoadingAnimation();
sendTabMessage({ message: 'refreshSegments' });
}
function skipSegment(actionType: ActionType, UUID: SegmentUUID, element?: HTMLElement): void {
if (actionType === ActionType.Chapter) {
sendMessage({
sendTabMessage({
message: "unskip",
UUID: UUID
});
} else {
sendMessage({
sendTabMessage({
message: "reskip",
UUID: UUID
});
@ -1030,18 +967,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
}
function sendMessage(request: Message): void {
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
request
);
});
}
/**
* Should skipping be disabled (visuals stay)
*/
@ -1074,10 +999,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
async function importSegments() {
const text = (PageElements.importSegmentsText as HTMLInputElement).value;
await sendTabMessage({
sendTabMessage({
message: "importSegments",
data: text
}) as ImportSegmentsResponse;
});
PageElements.importSegmentsMenu.classList.add("hidden");
}
@ -1142,6 +1067,27 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
case "time":
displayDownloadedSponsorTimes(downloadedTimes, msg.time);
break;
case "infoUpdated":
infoFound(msg);
break;
case "videoChanged":
currentVideoID = msg.videoID
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? [];
updateSegmentEditingUI();
if (msg.whitelisted) {
PageElements.whitelistChannel.style.display = "none";
PageElements.unwhitelistChannel.style.display = "unset";
PageElements.whitelistToggle.checked = true;
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
}
// Clear segments list & start loading animation
// We'll get a ping once they're loaded
startLoadingAnimation();
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("Loading");
displayDownloadedSponsorTimes([], 0);
break;
}
}
}