Skip to content

Commit

Permalink
Merge 60c46f4 into f1365be
Browse files Browse the repository at this point in the history
  • Loading branch information
NikkelM authored Nov 25, 2023
2 parents f1365be + 60c46f4 commit 28151bc
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<!--Releasenotes start-->
- Added a new option to shuffle only from shorts.
- When watching shorts, the extension will now update the last visited channel in the popup.
- Reduced the time it takes to shuffle, as the extension now uses a more sophisticated way to decide whether or not to check if a video has been deleted or not.
- The playlist that is opened when shuffling (if the option is enabled) now has a better title and tooltip.
- The popup now feels smoother in some places, and will make it clearer when some inputs are invalid.
Expand Down
36 changes: 19 additions & 17 deletions src/content.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Content script that is injected into YouTube pages
import { setDOMTextWithDelay, isVideoUrl, RandomYoutubeVideoError } from "./utils.js";
import { setDOMTextWithDelay, getPageTypeFromURL, RandomYoutubeVideoError } from "./utils.js";
import { configSync, setSyncStorageValue } from "./chromeStorage.js";
import { buildShuffleButton, shuffleButton, shuffleButtonTextElement, tryRenameUntitledList } from "./domManipulation.js";
import { chooseRandomVideo } from "./shuffleVideo.js";
Expand Down Expand Up @@ -28,16 +28,16 @@ async function startDOMObserver(event) {
// If the button previously displayed an error message, reset the text
resetShuffleButtonText();

const isVideoPage = isVideoUrl(window.location.href);
let pageType = getPageTypeFromURL(window.location.href);

// Get the channel id from the event data
let channelId = null;
let channelName = null;

if (isVideoPage) {
if (pageType == "video" || pageType == "short") {
channelId = event?.detail?.response?.playerResponse?.videoDetails?.channelId;
channelName = event?.detail?.response?.playerResponse?.videoDetails?.author;
} else {
} else if (pageType == "channel") {
channelId = event?.detail?.response?.response?.header?.c4TabbedHeaderRenderer?.channelId;
channelName = event?.detail?.response?.response?.header?.c4TabbedHeaderRenderer?.title;
}
Expand All @@ -50,25 +50,27 @@ async function startDOMObserver(event) {
// Wait until the required DOM element we add the button to is loaded
var observer = new MutationObserver(function (mutations, me) {
// ----- Channel page -----
if (!isVideoPage) {
if (pageType === "channel") {
var channelPageRequiredElementLoadComplete = document.getElementById("channel-header");

// ----- Video page -----
} else {
// Find out if we are on a video page that has completed loading the required element
} else if (pageType === "video") {
var videoPageRequiredElementLoadComplete = document.getElementById("player") && document.getElementById("above-the-fold");
// ----- Shorts page -----
} else if (pageType === "short") {
// As of now, we do not add a shuffle button to shorts pages, so we stop listening immediately
var shortsPageRequiredElementLoadComplete = true;
}

// If we are on a video page, and the required element has loaded, add the shuffle button
if (isVideoPage && videoPageRequiredElementLoadComplete) {
if (pageType === "video" && videoPageRequiredElementLoadComplete) {
me.disconnect(); // Stop observing
channelDetectedAction("video", channelId, channelName);
return;
}

// If we are NOT on a video page, we assume we are on a channel page
// If the required element has loaded, add a shuffle button
if (!isVideoPage && channelPageRequiredElementLoadComplete) {
} else if (pageType === "short" && shortsPageRequiredElementLoadComplete) {
me.disconnect(); // Stop observing
channelDetectedAction("short", channelId, channelName);
return;
} else if (pageType === "channel" && channelPageRequiredElementLoadComplete) {
me.disconnect(); // Stop observing
channelDetectedAction("channel", channelId, channelName);
return;
Expand Down Expand Up @@ -145,10 +147,10 @@ async function shuffleVideos() {

// We need this variable to make sure the button text is only changed if the shuffle hasn't finished within the time limit
var hasBeenShuffled = false;
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Shuffling...", 1000, () => { return (shuffleButtonTextElement.innerText === "\xa0Shuffle" && !hasBeenShuffled); });
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Still on it...", 5000, () => { return (shuffleButtonTextElement.innerText === "\xa0Shuffling..." && !hasBeenShuffled); });
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Shuffling...", 1000, () => { return ((shuffleButtonTextElement.innerText === "\xa0Shuffle" || shuffleButtonTextElement.innerText === "\xa0Fetching: 100%") && !hasBeenShuffled); });
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Still on it...", 5000, () => { return ((shuffleButtonTextElement.innerText === "\xa0Shuffling..." || shuffleButtonTextElement.innerText === "\xa0Fetching: 100%") && !hasBeenShuffled); });
if (configSync.shuffleIgnoreShortsOption != "1") {
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Sorting shorts...", 8000, () => { return (shuffleButtonTextElement.innerText === "\xa0Still on it..." && !hasBeenShuffled); });
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Sorting shorts...", 8000, () => { return ((shuffleButtonTextElement.innerText === "\xa0Still on it..." || shuffleButtonTextElement.innerText === "\xa0Fetching: 100%") && !hasBeenShuffled); });
}

await chooseRandomVideo(channelId, false, shuffleButtonTextElement);
Expand Down
8 changes: 5 additions & 3 deletions src/html/shufflingPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,11 @@ async function shuffleButtonClicked() {
try {
domElements.shufflingFromChannelHeading.innerText = configSync.currentChannelName;

setDOMTextWithDelay(domElements.fetchPercentageNotice, "Applying filters...", 3000, () => { return (domElements.fetchPercentageNotice.innerText === "Please wait..."); });
setDOMTextWithDelay(domElements.fetchPercentageNotice, "Should be done soon...", 10000, () => { return (domElements.fetchPercentageNotice.innerText === "Applying filters..."); });

setDOMTextWithDelay(domElements.fetchPercentageNotice, "Applying filters...", 2000, () => { console.log(domElements.fetchPercentageNotice.innerText); return (domElements.fetchPercentageNotice.innerText === "Please wait..." || domElements.fetchPercentageNotice.innerText === "\xa0Fetching: 100%"); });
setDOMTextWithDelay(domElements.fetchPercentageNotice, "Should be done soon...", 5000, () => { return (domElements.fetchPercentageNotice.innerText === "Applying filters..." || domElements.fetchPercentageNotice.innerText === "\xa0Fetching: 100%"); });
if (configSync.shuffleIgnoreShortsOption != "1") {
setDOMTextWithDelay(domElements.fetchPercentageNotice, "Sorting shorts...", 9000, () => { return (domElements.fetchPercentageNotice.innerText === "Should be done soon..." || domElements.fetchPercentageNotice.innerText === "\xa0Fetching: 100%"); });
}
await chooseRandomVideo(configSync.currentChannelId, true, domElements.fetchPercentageNotice);

// Focus this tab when the shuffle completes
Expand Down
15 changes: 15 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,24 @@ export function isVideoUrl(url) {
if (!url) return false;

const urlParts = url.split("/");
if (!urlParts[2]?.includes("youtube")) return false;
return urlParts[3]?.startsWith("watch?v=") ?? false;
}

export function getPageTypeFromURL(url) {
if (!url) return null;

const urlParts = url.split("/");
if (!urlParts[2]?.includes("youtube")) return null;
if (urlParts[3]?.startsWith("watch?v=")) {
return "video";
} else if (urlParts[3]?.startsWith("shorts")) {
return "short";
} else {
return "channel";
}
}

// ----- DOM -----
export function setDOMTextWithDelay(textElement, newText, delayMS, predicate = () => { return true; }) {
// Sets the innerHTML of a (text) DOM element after a delay, if a predicate evaluates to true
Expand Down
33 changes: 32 additions & 1 deletion test/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import expect from 'expect.js';
import sinon from 'sinon';
import { JSDOM } from 'jsdom';

import { isVideoUrl, isEmpty, getLength, addHours, delay, setDOMTextWithDelay, RandomYoutubeVideoError, YoutubeAPIError } from '../src/utils.js';
import { isVideoUrl, getPageTypeFromURL, isEmpty, getLength, addHours, delay, setDOMTextWithDelay, RandomYoutubeVideoError, YoutubeAPIError } from '../src/utils.js';

describe('utils.js', function () {
context('URL helpers', function () {
Expand All @@ -29,8 +29,39 @@ describe('utils.js', function () {
expect(isVideoUrl("https://www.google.com")).to.be(false);
expect(isVideoUrl("https://www.google.com/search?q=youtube")).to.be(false);
expect(isVideoUrl("about:blank")).to.be(false);
expect(isVideoUrl("edge://extensions/")).to.be(false);
});
});

context('getPageTypeFromURL()', function () {
it('should not break if no URL is provided', function () {
expect(getPageTypeFromURL(null)).to.be(null);
expect(getPageTypeFromURL(undefined)).to.be(null);
expect(getPageTypeFromURL("")).to.be(null);
expect(getPageTypeFromURL(0)).to.be(null);
});

it('should identify a YouTube video URL', function () {
expect(getPageTypeFromURL("https://www.youtube.com/watch?v=12345678901")).to.be("video");
});

it('should identify a YouTube shorts URL', function () {
expect(getPageTypeFromURL("https://www.youtube.com/shorts/12345678901")).to.be("short");
});

it('should identify a YouTube non-video/non-shorts URL', function () {
expect(getPageTypeFromURL("https://www.youtube.com/channel/myChannelID")).to.be("channel");
expect(getPageTypeFromURL("https://www.youtube.com/@Username")).to.be("channel");
expect(getPageTypeFromURL("https://www.youtube.com")).to.be("channel");
expect(getPageTypeFromURL("https://www.youtube.com/playlist?list=PL1234567890")).to.be("channel");
});

it('should identify a non-YouTube URL', function () {
expect(getPageTypeFromURL("https://www.google.com")).to.be(null);
expect(getPageTypeFromURL("https://www.google.com/search?q=youtube")).to.be(null);
expect(getPageTypeFromURL("about:blank")).to.be(null);
expect(getPageTypeFromURL("edge://extensions/")).to.be(null);
});
});
});

Expand Down

0 comments on commit 28151bc

Please sign in to comment.