mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2026-03-24 22:58:18 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into improvements
This commit is contained in:
@@ -35,15 +35,18 @@ class CategoryChooserComponent extends React.Component<CategoryChooserProps, Cat
|
||||
{chrome.i18n.getMessage("category")}
|
||||
</td>
|
||||
|
||||
<td id={"CategorySkipOption"}>
|
||||
<td id={"CategorySkipOption"}
|
||||
className="skipOption">
|
||||
{chrome.i18n.getMessage("skipOption")}
|
||||
</td>
|
||||
|
||||
<td id={"CategoryColorOption"}>
|
||||
<td id={"CategoryColorOption"}
|
||||
className="colorOption">
|
||||
{chrome.i18n.getMessage("seekBarColor")}
|
||||
</td>
|
||||
|
||||
<td id={"CategoryPreviewColorOption"}>
|
||||
<td id={"CategoryPreviewColorOption"}
|
||||
className="previewColorOption">
|
||||
{chrome.i18n.getMessage("previewColor")}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -24,8 +24,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
|
||||
// Setup state
|
||||
this.state = {
|
||||
color: props.defaultColor || Config.config.barTypes[this.props.category].color,
|
||||
previewColor: props.defaultPreviewColor || Config.config.barTypes["preview-" + this.props.category].color,
|
||||
color: props.defaultColor || Config.config.barTypes[this.props.category]?.color,
|
||||
previewColor: props.defaultPreviewColor || Config.config.barTypes["preview-" + this.props.category]?.color,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
{chrome.i18n.getMessage("category_" + this.props.category)}
|
||||
</td>
|
||||
|
||||
<td id={this.props.category + "SkipOption"}>
|
||||
<td id={this.props.category + "SkipOption"}
|
||||
className="skipOption">
|
||||
<select
|
||||
className="categoryOptionsSelector"
|
||||
defaultValue={defaultOption}
|
||||
@@ -68,7 +69,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<td id={this.props.category + "ColorOption"}>
|
||||
<td id={this.props.category + "ColorOption"}
|
||||
className="colorOption">
|
||||
<input
|
||||
className="categoryColorTextBox option-text-box"
|
||||
type="color"
|
||||
@@ -76,7 +78,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
value={this.state.color} />
|
||||
</td>
|
||||
|
||||
<td id={this.props.category + "PreviewColorOption"}>
|
||||
<td id={this.props.category + "PreviewColorOption"}
|
||||
className="previewColorOption">
|
||||
<input
|
||||
className="categoryColorTextBox option-text-box"
|
||||
type="color"
|
||||
|
||||
@@ -25,7 +25,8 @@ export interface NoticeProps {
|
||||
// Callback for when this is closed
|
||||
closeListener: () => void,
|
||||
|
||||
zIndex?: number
|
||||
zIndex?: number,
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export interface NoticeState {
|
||||
@@ -81,7 +82,8 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
|
||||
render(): React.ReactElement {
|
||||
const noticeStyle: React.CSSProperties = {
|
||||
zIndex: this.props.zIndex || (1000 + this.amountOfPreviousNotices)
|
||||
zIndex: this.props.zIndex || (1000 + this.amountOfPreviousNotices),
|
||||
...(this.props.style ?? {})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -135,9 +135,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
}
|
||||
|
||||
render(): React.ReactElement {
|
||||
const noticeStyle: React.CSSProperties = {
|
||||
zIndex: 1500 + this.amountOfPreviousNotices
|
||||
}
|
||||
const noticeStyle: React.CSSProperties = { }
|
||||
if (this.contentContainer().onMobileYouTube) {
|
||||
noticeStyle.bottom = "4em";
|
||||
noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
|
||||
@@ -155,6 +153,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
timed={true}
|
||||
maxCountdownTime={this.state.maxCountdownTime}
|
||||
videoSpeed={() => this.contentContainer().v?.playbackRate}
|
||||
style={noticeStyle}
|
||||
ref={this.noticeRef}
|
||||
closeListener={() => this.closeListener()}
|
||||
smaller={this.props.smaller}
|
||||
|
||||
@@ -18,6 +18,7 @@ interface SBConfig {
|
||||
showTimeWithSkips: boolean,
|
||||
disableSkipping: boolean,
|
||||
trackViewCount: boolean,
|
||||
trackViewCountInPrivate: boolean,
|
||||
dontShowNotice: boolean,
|
||||
hideVideoPlayerControls: boolean,
|
||||
hideInfoButtonPlayerControls: boolean,
|
||||
@@ -155,6 +156,7 @@ const Config: SBObject = {
|
||||
showTimeWithSkips: true,
|
||||
disableSkipping: false,
|
||||
trackViewCount: true,
|
||||
trackViewCountInPrivate: true,
|
||||
dontShowNotice: false,
|
||||
hideVideoPlayerControls: false,
|
||||
hideInfoButtonPlayerControls: false,
|
||||
@@ -224,11 +226,11 @@ const Config: SBObject = {
|
||||
opacity: "0.7"
|
||||
},
|
||||
"preview": {
|
||||
color: "#ff1684",
|
||||
color: "#0b9d65",
|
||||
opacity: "0.7"
|
||||
},
|
||||
"preview-preview": {
|
||||
color: "#9b044c",
|
||||
color: "#065b3a",
|
||||
opacity: "0.7"
|
||||
},
|
||||
"music_offtopic": {
|
||||
@@ -352,6 +354,14 @@ function migrateOldFormats(config: SBConfig) {
|
||||
|
||||
config.categorySelections = config.categorySelections;
|
||||
}
|
||||
|
||||
// Remove some old unused options
|
||||
if (config["sponsorVideoID"] !== undefined) {
|
||||
chrome.storage.sync.remove("sponsorVideoID");
|
||||
}
|
||||
if (config["previousVideoID"] !== undefined) {
|
||||
chrome.storage.sync.remove("previousVideoID");
|
||||
}
|
||||
}
|
||||
|
||||
async function setupConfig() {
|
||||
|
||||
176
src/content.ts
176
src/content.ts
@@ -33,15 +33,15 @@ let channelIDInfo: ChannelIDInfo;
|
||||
// Skips are rescheduled every seeking event.
|
||||
// Skips are canceled every seeking event
|
||||
let currentSkipSchedule: NodeJS.Timeout = null;
|
||||
let seekListenerSetUp = false
|
||||
|
||||
/** Has the sponsor been skipped */
|
||||
let sponsorSkipped: boolean[] = [];
|
||||
|
||||
//the video
|
||||
let video: HTMLVideoElement;
|
||||
let videoMutationObserver: MutationObserver = null;
|
||||
// List of videos that have had event listeners added to them
|
||||
const videoRootsWithEventListeners: HTMLDivElement[] = [];
|
||||
const videosWithEventListeners: HTMLVideoElement[] = [];
|
||||
|
||||
let onInvidious;
|
||||
let onMobileYouTube;
|
||||
@@ -49,9 +49,6 @@ let onMobileYouTube;
|
||||
//the video id of the last preview bar update
|
||||
let lastPreviewBarUpdate;
|
||||
|
||||
//whether the duration listener listening for the duration changes of the video has been setup yet
|
||||
let durationListenerSetUp = false;
|
||||
|
||||
// Is the video currently being switched
|
||||
let switchingVideos = null;
|
||||
|
||||
@@ -77,6 +74,7 @@ const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImage
|
||||
|
||||
// Direct Links after the config is loaded
|
||||
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document.URL)));
|
||||
addHotkeyListener();
|
||||
|
||||
//the amount of times the sponsor lookup has retried
|
||||
//this only happens if there is an error
|
||||
@@ -475,48 +473,60 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
|
||||
}
|
||||
}
|
||||
|
||||
async function sponsorsLookup(id: string) {
|
||||
video = document.querySelector('video') // Youtube video player
|
||||
//there is no video here
|
||||
if (video == null) {
|
||||
setTimeout(() => sponsorsLookup(id), 100);
|
||||
return;
|
||||
function setupVideoMutationListener() {
|
||||
const videoContainer = document.querySelector(".html5-video-container");
|
||||
if (!videoContainer || videoMutationObserver !== null || onInvidious) return;
|
||||
|
||||
videoMutationObserver = new MutationObserver(refreshVideoAttachments);
|
||||
|
||||
videoMutationObserver.observe(videoContainer, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
function refreshVideoAttachments() {
|
||||
const newVideo = document.querySelector('video');
|
||||
if (newVideo && newVideo !== video) {
|
||||
video = newVideo;
|
||||
|
||||
if (!videosWithEventListeners.includes(video)) {
|
||||
videosWithEventListeners.push(video);
|
||||
|
||||
setupVideoListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addHotkeyListener();
|
||||
function setupVideoListeners() {
|
||||
//wait until it is loaded
|
||||
video.addEventListener('durationchange', durationChangeListener);
|
||||
|
||||
if (!durationListenerSetUp) {
|
||||
durationListenerSetUp = true;
|
||||
|
||||
//wait until it is loaded
|
||||
video.addEventListener('durationchange', durationChangeListener);
|
||||
}
|
||||
|
||||
if (!seekListenerSetUp && !Config.config.disableSkipping) {
|
||||
seekListenerSetUp = true;
|
||||
if (!Config.config.disableSkipping) {
|
||||
switchingVideos = false;
|
||||
|
||||
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();
|
||||
|
||||
|
||||
// Make sure it doesn't get double called with the playing event
|
||||
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
video.addEventListener('playing', () => {
|
||||
// Make sure it doesn't get double called with the play event
|
||||
@@ -524,7 +534,7 @@ async function sponsorsLookup(id: string) {
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
});
|
||||
@@ -533,7 +543,7 @@ async function sponsorsLookup(id: string) {
|
||||
// Reset lastCheckVideoTime
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
});
|
||||
@@ -544,12 +554,23 @@ async function sponsorsLookup(id: string) {
|
||||
// Reset lastCheckVideoTime
|
||||
lastCheckVideoTime = -1;
|
||||
lastCheckTime = 0;
|
||||
|
||||
|
||||
cancelSponsorSchedule();
|
||||
});
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
async function sponsorsLookup(id: string) {
|
||||
if (!video) refreshVideoAttachments();
|
||||
//there is still no video here
|
||||
if (!video) {
|
||||
setTimeout(() => sponsorsLookup(id), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
setupVideoMutationListener();
|
||||
|
||||
//check database for sponsor times
|
||||
//made true once a setTimeout has been created to try again after a server error
|
||||
@@ -566,22 +587,12 @@ async function sponsorsLookup(id: string) {
|
||||
categories
|
||||
}).then(async (response: FetchResponse) => {
|
||||
if (response?.ok) {
|
||||
let result = JSON.parse(response.responseText);
|
||||
result = result.filter((video) => video.videoID === id);
|
||||
if (result.length > 0) {
|
||||
result = result[0].segments;
|
||||
if (result.length === 0) { // return if no segments found
|
||||
retryFetch(id);
|
||||
return;
|
||||
}
|
||||
} else { // return if no video found
|
||||
retryFetch(id);
|
||||
return;
|
||||
}
|
||||
|
||||
const recievedSegments: SponsorTime[] = result;
|
||||
if (!recievedSegments.length) {
|
||||
console.error("[SponsorBlock] Server returned malformed response: " + JSON.stringify(recievedSegments));
|
||||
const recievedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
||||
?.filter((video) => video.videoID === id)
|
||||
?.map((video) => video.segments)[0];
|
||||
if (!recievedSegments || !recievedSegments.length) {
|
||||
// return if no video found
|
||||
retryFetch();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,6 +608,7 @@ async function sponsorsLookup(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const oldSegments = sponsorTimes || [];
|
||||
sponsorTimes = recievedSegments;
|
||||
|
||||
// Hide all submissions smaller than the minimum duration
|
||||
@@ -608,6 +620,15 @@ async function sponsorsLookup(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const segment of oldSegments) {
|
||||
const otherSegment = sponsorTimes.find((other) => segment.UUID === other.UUID);
|
||||
if (otherSegment) {
|
||||
// If they downvoted it, or changed the category, keep it
|
||||
otherSegment.hidden = segment.hidden;
|
||||
otherSegment.category = segment.category;
|
||||
}
|
||||
}
|
||||
|
||||
startSkipScheduleCheckingForStartSponsors();
|
||||
|
||||
// Reset skip save
|
||||
@@ -623,20 +644,24 @@ async function sponsorsLookup(id: string) {
|
||||
|
||||
sponsorLookupRetries = 0;
|
||||
} else if (response?.status === 404) {
|
||||
retryFetch(id);
|
||||
retryFetch();
|
||||
} else if (sponsorLookupRetries < 15 && !recheckStarted) {
|
||||
recheckStarted = true;
|
||||
|
||||
//TODO lower when server becomes better (back to 1 second)
|
||||
//some error occurred, try again in a second
|
||||
setTimeout(() => sponsorsLookup(id), 5000 + Math.random() * 15000 + 5000 * sponsorLookupRetries);
|
||||
setTimeout(() => {
|
||||
if (sponsorVideoID && sponsorTimes?.length === 0) {
|
||||
sponsorsLookup(sponsorVideoID);
|
||||
}
|
||||
}, 5000 + Math.random() * 15000 + 5000 * sponsorLookupRetries);
|
||||
|
||||
sponsorLookupRetries++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function retryFetch(id: string): void {
|
||||
function retryFetch(): void {
|
||||
if (!Config.config.refetchWhenNotFound) return;
|
||||
|
||||
sponsorDataFound = false;
|
||||
@@ -647,7 +672,11 @@ function retryFetch(id: string): void {
|
||||
|
||||
//if less than 3 days old
|
||||
if (Date.now() - new Date(dateUploaded).getTime() < 259200000) {
|
||||
setTimeout(() => sponsorsLookup(id), 30000 + Math.random() * 90000);
|
||||
setTimeout(() => {
|
||||
if (sponsorVideoID && sponsorTimes?.length === 0) {
|
||||
sponsorsLookup(sponsorVideoID);
|
||||
}
|
||||
}, 10000 + Math.random() * 30000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -692,7 +721,7 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
* Get the video info for the current tab from YouTube
|
||||
*/
|
||||
async function getVideoInfo(): Promise<void> {
|
||||
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID);
|
||||
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID + "&html5=1");
|
||||
|
||||
if (result.ok) {
|
||||
const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1];
|
||||
@@ -706,7 +735,7 @@ async function getVideoInfo(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoID(url: string) {
|
||||
function getYouTubeVideoID(url: string): string | boolean {
|
||||
// For YouTube TV support
|
||||
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
|
||||
|
||||
@@ -753,7 +782,7 @@ function getYouTubeVideoID(url: string) {
|
||||
*/
|
||||
function updatePreviewBarPositionMobile(parent: HTMLElement) {
|
||||
if (document.getElementById("previewbar") === null) {
|
||||
previewBar.updatePosition(parent);
|
||||
previewBar.createElement(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,16 +834,19 @@ function updatePreviewBar(): void {
|
||||
async function whitelistCheck() {
|
||||
const whitelistedChannels = Config.config.whitelistedChannels;
|
||||
|
||||
const channelID = document.querySelector(".ytd-channel-name a")?.getAttribute("href")?.replace(/\/.+\//, "") // YouTube
|
||||
const getChannelID = () => videoInfo?.videoDetails?.channelId
|
||||
?? document.querySelector(".ytd-channel-name a")?.getAttribute("href")?.replace(/\/.+\//, "") // YouTube
|
||||
?? document.querySelector(".ytp-title-channel-logo")?.getAttribute("href")?.replace(/https:\/.+\//, "") // YouTube Embed
|
||||
?? document.querySelector("a > .channel-profile")?.parentElement?.getAttribute("href")?.replace(/\/.+\//, ""); // Invidious
|
||||
|
||||
if (channelID) {
|
||||
try {
|
||||
await utils.wait(() => !!getChannelID(), 6000, 20);
|
||||
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Found,
|
||||
id: channelID
|
||||
id: getChannelID()
|
||||
}
|
||||
} else {
|
||||
} catch (e) {
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Failed,
|
||||
id: null
|
||||
@@ -824,7 +856,7 @@ async function whitelistCheck() {
|
||||
}
|
||||
|
||||
//see if this is a whitelisted channel
|
||||
if (whitelistedChannels != undefined && whitelistedChannels.includes(channelID)) {
|
||||
if (whitelistedChannels != undefined && whitelistedChannels.includes(getChannelID())) {
|
||||
channelWhitelisted = true;
|
||||
}
|
||||
|
||||
@@ -956,8 +988,8 @@ function previewTime(time: number, unpause = true) {
|
||||
|
||||
//send telemetry and count skip
|
||||
function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped: number, fullSkip: boolean) {
|
||||
if (!Config.config.trackViewCount) return;
|
||||
|
||||
if (!Config.config.trackViewCount || (!Config.config.trackViewCountInPrivate && chrome.extension.inIncognitoContext)) return;
|
||||
|
||||
let counted = false;
|
||||
for (const segment of skippingSegments) {
|
||||
const index = sponsorTimes.indexOf(segment);
|
||||
@@ -1091,7 +1123,7 @@ async function createButtons(): Promise<void> {
|
||||
/** Creates any missing buttons on the player and updates their visiblity. */
|
||||
async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
|
||||
// Not on a proper video yet
|
||||
if (!sponsorVideoID) return;
|
||||
if (!sponsorVideoID || onMobileYouTube) return;
|
||||
|
||||
await createButtons();
|
||||
|
||||
@@ -1108,7 +1140,7 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
|
||||
/** Updates the visibility of buttons on the player related to creating segments. */
|
||||
function updateEditButtonsOnPlayer(): void {
|
||||
// Don't try to update the buttons if we aren't on a YouTube video page
|
||||
if (!sponsorVideoID) return;
|
||||
if (!sponsorVideoID || onMobileYouTube) return;
|
||||
|
||||
const buttonsEnabled = !Config.config.hideVideoPlayerControls && !onInvidious;
|
||||
|
||||
@@ -1183,6 +1215,9 @@ function startOrEndTimingNewSegment() {
|
||||
// Save the newly created segment
|
||||
Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting);
|
||||
|
||||
// Make sure they know if someone has already submitted something it while they were watching
|
||||
sponsorsLookup(sponsorVideoID);
|
||||
|
||||
updateEditButtonsOnPlayer();
|
||||
updateSponsorTimesSubmitting(false);
|
||||
}
|
||||
@@ -1531,21 +1566,14 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
|
||||
return sponsorTimesMessage;
|
||||
}
|
||||
|
||||
function addHotkeyListener(): boolean {
|
||||
let videoRoot = document.getElementById("movie_player") as HTMLDivElement;
|
||||
if (onInvidious) videoRoot = (document.getElementById("player-container") ?? document.getElementById("player")) as HTMLDivElement;
|
||||
if (video.baseURI.startsWith("https://www.youtube.com/tv#/")) videoRoot = document.querySelector("ytlr-watch-page") as HTMLDivElement;
|
||||
|
||||
if (videoRoot && !videoRootsWithEventListeners.includes(videoRoot)) {
|
||||
videoRoot.addEventListener("keydown", hotkeyListener);
|
||||
videoRootsWithEventListeners.push(videoRoot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
function addHotkeyListener(): void {
|
||||
document.addEventListener("keydown", hotkeyListener);
|
||||
}
|
||||
|
||||
function hotkeyListener(e: KeyboardEvent): void {
|
||||
if (["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase())
|
||||
|| document.activeElement?.id?.toLowerCase()?.includes("editable")) return;
|
||||
|
||||
const key = e.key;
|
||||
|
||||
const skipKey = Config.config.skipKeybind;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
This is based on code from VideoSegments.
|
||||
Parts of this are inspired from code from VideoSegments, but rewritten and under the LGPLv3 license
|
||||
https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd51f62a4e7fef4d4/segmentsbar/segmentsbar.js
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@ class PreviewBar {
|
||||
this.onMobileYouTube = onMobileYouTube;
|
||||
this.onInvidious = onInvidious;
|
||||
|
||||
this.updatePosition(parent);
|
||||
this.createElement(parent);
|
||||
|
||||
this.setupHoverText();
|
||||
}
|
||||
@@ -135,7 +135,7 @@ class PreviewBar {
|
||||
});
|
||||
}
|
||||
|
||||
updatePosition(parent: HTMLElement): void {
|
||||
createElement(parent: HTMLElement): void {
|
||||
this.parent = parent;
|
||||
|
||||
if (this.onMobileYouTube) {
|
||||
@@ -143,22 +143,19 @@ class PreviewBar {
|
||||
parent.style.opacity = "1";
|
||||
|
||||
this.container.style.transform = "none";
|
||||
} else if (!this.onInvidious) {
|
||||
// Hover listener
|
||||
this.parent.addEventListener("mouseenter", () => this.container.classList.add("hovered"));
|
||||
|
||||
this.parent.addEventListener("mouseleave", () => this.container.classList.remove("hovered"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// On the seek bar
|
||||
this.parent.prepend(this.container);
|
||||
}
|
||||
|
||||
// TODO: call on config changes
|
||||
updateColor(segmentType: string, color: string, opacity: number): void {
|
||||
const bars = <NodeListOf<HTMLElement>> document.querySelectorAll('[data-vs-segment-type=' + segmentType + ']');
|
||||
|
||||
for (const bar of bars) {
|
||||
bar.style.backgroundColor = color;
|
||||
bar.style.opacity = String(opacity);
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.videoDuration = 0;
|
||||
this.segments = [];
|
||||
@@ -170,7 +167,6 @@ class PreviewBar {
|
||||
|
||||
set(segments: PreviewBarSegment[], videoDuration: number): void {
|
||||
this.clear();
|
||||
|
||||
if (!segments) return;
|
||||
|
||||
this.segments = segments;
|
||||
@@ -191,12 +187,11 @@ class PreviewBar {
|
||||
bar.classList.add('previewbar');
|
||||
bar.innerHTML = ' ';
|
||||
|
||||
const barSegmentType = (unsubmitted ? 'preview-' : '') + category;
|
||||
const fullCategoryName = (unsubmitted ? 'preview-' : '') + category;
|
||||
bar.setAttribute('sponsorblock-category', fullCategoryName);
|
||||
|
||||
bar.setAttribute('data-vs-segment-type', barSegmentType);
|
||||
|
||||
bar.style.backgroundColor = Config.config.barTypes[barSegmentType].color;
|
||||
if (!this.onMobileYouTube) bar.style.opacity = Config.config.barTypes[barSegmentType].opacity;
|
||||
bar.style.backgroundColor = Config.config.barTypes[fullCategoryName]?.color;
|
||||
if (!this.onMobileYouTube) bar.style.opacity = Config.config.barTypes[fullCategoryName]?.opacity;
|
||||
|
||||
bar.style.position = "absolute";
|
||||
if (segment[1] - segment[0] > 0) bar.style.width = this.timeToPercentage(segment[1] - segment[0]);
|
||||
|
||||
@@ -31,6 +31,11 @@ async function init() {
|
||||
const optionsElements = optionsContainer.querySelectorAll("*");
|
||||
|
||||
for (let i = 0; i < optionsElements.length; i++) {
|
||||
if (optionsElements[i].getAttribute("private-mode-only") === "true" && !(await isIncognitoAllowed())) {
|
||||
optionsElements[i].classList.add("hidden");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (optionsElements[i].getAttribute("option-type")) {
|
||||
case "toggle": {
|
||||
const option = optionsElements[i].getAttribute("sync-option");
|
||||
@@ -540,3 +545,7 @@ function copyDebugOutputToClipboard() {
|
||||
alert(chrome.i18n.getMessage("copyDebugInformationFailed"));
|
||||
});
|
||||
}
|
||||
|
||||
function isIncognitoAllowed(): Promise<boolean> {
|
||||
return new Promise((resolve) => chrome.extension.isAllowedIncognitoAccess(resolve));
|
||||
}
|
||||
28
src/popup.ts
28
src/popup.ts
@@ -372,8 +372,12 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
const sponsorTimeButton = document.createElement("button");
|
||||
sponsorTimeButton.className = "segmentTimeButton popupElement";
|
||||
|
||||
const prefix = utils.shortCategoryName(segmentTimes[i].category) + ": ";
|
||||
|
||||
const categoryColorCircle = document.createElement("span");
|
||||
categoryColorCircle.id = "sponsorTimesCategoryColorCircle" + UUID;
|
||||
categoryColorCircle.style.backgroundColor = Config.config.barTypes[segmentTimes[i].category]?.color;
|
||||
categoryColorCircle.classList.add("dot");
|
||||
categoryColorCircle.classList.add("sponsorTimesCategoryColorCircle");
|
||||
|
||||
let extraInfo = "";
|
||||
if (segmentTimes[i].hidden === SponsorHideType.Downvoted) {
|
||||
//this one is downvoted
|
||||
@@ -382,14 +386,15 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
//this one is too short
|
||||
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
|
||||
}
|
||||
|
||||
const textNode = document.createTextNode(utils.shortCategoryName(segmentTimes[i].category) + extraInfo);
|
||||
const segmentTimeFromToNode = document.createElement("div");
|
||||
segmentTimeFromToNode.innerText = utils.getFormattedTime(segmentTimes[i].segment[0], true) + " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true);
|
||||
segmentTimeFromToNode.style.margin = "5px";
|
||||
|
||||
sponsorTimeButton.innerText = prefix + utils.getFormattedTime(segmentTimes[i].segment[0], true) + " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true) + extraInfo;
|
||||
|
||||
const categoryColorCircle = document.createElement("span");
|
||||
categoryColorCircle.id = "sponsorTimesCategoryColorCircle" + UUID;
|
||||
categoryColorCircle.style.backgroundColor = Config.config.barTypes[segmentTimes[i].category].color;
|
||||
categoryColorCircle.classList.add("dot");
|
||||
categoryColorCircle.classList.add("sponsorTimesCategoryColorCircle");
|
||||
sponsorTimeButton.appendChild(categoryColorCircle);
|
||||
sponsorTimeButton.appendChild(textNode);
|
||||
sponsorTimeButton.appendChild(segmentTimeFromToNode);
|
||||
|
||||
const votingButtons = document.createElement("div");
|
||||
votingButtons.classList.add("votingButtons");
|
||||
@@ -398,7 +403,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
const voteButtonsContainer = document.createElement("div");
|
||||
voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + UUID;
|
||||
voteButtonsContainer.setAttribute("align", "center");
|
||||
voteButtonsContainer.style.display = "none"
|
||||
voteButtonsContainer.classList.add('voteButtonsContainer--hide');
|
||||
|
||||
const upvoteButton = document.createElement("img");
|
||||
upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID;
|
||||
@@ -427,7 +432,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
|
||||
//add click listener to open up vote panel
|
||||
sponsorTimeButton.addEventListener("click", function() {
|
||||
voteButtonsContainer.style.removeProperty("display");
|
||||
voteButtonsContainer.classList.toggle("voteButtonsContainer--hide");
|
||||
});
|
||||
|
||||
// Will contain request status
|
||||
@@ -441,7 +446,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
thanksForVotingText.classList.add("sponsorTimesThanksForVotingText");
|
||||
voteStatusContainer.appendChild(thanksForVotingText);
|
||||
|
||||
votingButtons.append(categoryColorCircle);
|
||||
votingButtons.append(sponsorTimeButton);
|
||||
votingButtons.append(voteButtonsContainer);
|
||||
votingButtons.append(voteStatusContainer);
|
||||
|
||||
@@ -22,10 +22,11 @@ class SkipNotice {
|
||||
this.contentContainer = contentContainer;
|
||||
|
||||
//get reference node
|
||||
let referenceNode = document.getElementById("player-container-id")
|
||||
let referenceNode = document.getElementById("player-container-id")
|
||||
?? document.getElementById("movie_player")
|
||||
?? document.querySelector("#main-panel.ytmusic-player-page") // YouTube music
|
||||
?? document.querySelector("#player-container .video-js") // Invidious
|
||||
?? document.querySelector(".main-video-section > .video-container"); // Cloudtube
|
||||
?? document.querySelector(".main-video-section > .video-container"); // Cloudtube
|
||||
if (referenceNode == null) {
|
||||
//for embeds
|
||||
const player = document.getElementById("player");
|
||||
@@ -33,16 +34,13 @@ class SkipNotice {
|
||||
let index = 1;
|
||||
|
||||
//find the child that is the video player (sometimes it is not the first)
|
||||
while (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed")) {
|
||||
while (index < player.children.length && (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed"))) {
|
||||
referenceNode = player.children[index] as HTMLElement;
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
// YouTube Music
|
||||
if (new URL(document.URL).host === "music.youtube.com") {
|
||||
referenceNode = document.querySelector("#main-panel.ytmusic-player-page");
|
||||
}
|
||||
|
||||
|
||||
const amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length;
|
||||
//this is the suffix added at the end of every id
|
||||
|
||||
Reference in New Issue
Block a user