Skip to content

Commit

Permalink
Merge pull request #195 from adsuth/error-handling-for-injections
Browse files Browse the repository at this point in the history
Error handling for injections
  • Loading branch information
ynshung authored Dec 20, 2023
2 parents 52ca5a0 + f7d683c commit 9639313
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 97 deletions.
47 changes: 8 additions & 39 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,19 @@ import {
PolyDictionary,
StringDictionary,
} from "./lib/definitions";
import { getCurrentId, getVideo } from "./lib/getters";
import { getVideo } from "./lib/getters";
import { handleKeyEvent } from "./lib/handleKeyEvent";
import {
retrieveFeaturesFromStorage,
retrieveKeybindsFromStorage,
retrieveOptionsFromStorage,
retrieveSettingsFromStorage,
} from "./lib/retrieveFromStorage";
import { handleSkipShortsWithLowLikes } from "./lib/SkipShortsWithLowLikes";

// need this to ensure css is loaded in the dist
import "./css/content.css";
import { handleInjectionChecks } from "./lib/InjectionSuccess";
import { hasVideoEnded, isVideoPlaying } from "./lib/VideoState";
import { handleAutoplay, handleEnableAutoplay } from "./lib/Autoplay";
import { handleAutomaticallyOpenComments } from "./lib/AutomaticallyOpenComments";
import { handleProgressBarNotAppearing } from "./lib/ProgressBar";
import { handleHideShortsOverlay } from "./lib/HideShortsOverlay";
import { setPlaybackRate } from "./lib/PlaybackRate";
import { main } from "./main";

/**
* content.ts
Expand All @@ -33,7 +28,7 @@ import { handleHideShortsOverlay } from "./lib/HideShortsOverlay";
* For popup code, see ./main.tsx
*/

const state = new Proxy(DEFAULT_STATE, {
export const state = new Proxy(DEFAULT_STATE, {
set(o: StateObject, prop: string, val: string | boolean | number | null) {
o[prop] = val;

Expand All @@ -44,6 +39,7 @@ const state = new Proxy(DEFAULT_STATE, {
switch (prop) {
case "playbackRate":
ytShorts.playbackRate = val as number;
setPlaybackRate(state);
break;
}
}
Expand All @@ -53,9 +49,9 @@ const state = new Proxy(DEFAULT_STATE, {
});

let keybinds: StringDictionary;
let options: PolyDictionary;
let settings: PolyDictionary;
let features: BooleanDictionary;
export let options: PolyDictionary;
export let settings: PolyDictionary;
export let features: BooleanDictionary;

// todo - add "settings" to localstorage (merge autoplay + player volume into one)
// localStorage.getItem("yt-player-volume") !== null && JSON.parse(localStorage.getItem("yt-player-volume"))["data"]["volume"]
Expand Down Expand Up @@ -90,33 +86,6 @@ let low_priority_interval = setInterval(lowPriorityCallback, 1000);
let main_interval = setInterval(main, 100);
let volume_interval = setInterval(volumeIntervalCallback, 10);

function main() {
if (window.location.toString().indexOf("youtube.com/shorts/") < 0) return;

const ytShorts = getVideo();
const currentId = getCurrentId();

if (ytShorts === null) return;
if (currentId === null) return;

if ((state.topId as number) < currentId) state.topId = currentId;

// video has to have been playing to skip.
// I'm undecided whether to use 0.5 or 1 for currentTime, as 1 isn't quite fast enough, but sometimes with 0.5, it skips a video above the minimum like count.
if (isVideoPlaying()) {
handleSkipShortsWithLowLikes(state, options);
handleAutomaticallyOpenComments(state, options); // dev note: the implementation of this feature is a good starting point to figure out how to format your own
}
if (hasVideoEnded()) {
handleAutoplay(state, settings, features["autoplay"]);
}

handleProgressBarNotAppearing();
handleEnableAutoplay();
handleInjectionChecks(state, settings, options, features);
handleHideShortsOverlay(options);
}

function volumeIntervalCallback() {
if (window.location.toString().indexOf("youtube.com/shorts/") < 0) return;
if (getVideo()) checkVolume(settings, features["volumeSlider"]);
Expand Down
6 changes: 5 additions & 1 deletion src/css/content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ input:checked + .autoplay-slider:before {
padding-bottom: 16px;
}
}
#shorts-player>div>video
{
position: relative;
}

.betterYT-progress-bar {
bottom: 0;
Expand All @@ -174,4 +178,4 @@ input:checked + .autoplay-slider:before {

.betterYT-hidden {
visibility: hidden !important;
}
}
7 changes: 3 additions & 4 deletions src/lib/ActionElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ export function populateActionElement(
const actionElement = getActionElement();
const ytShorts = getVideo();

if (!actionElement) return;
if (!ytShorts) return;
if (!actionElement) throw new Error("Action element not found");
if (!ytShorts) throw new Error("Video not found");

// adsu - idk how any of this works so im just going to leave it be
// adsu- idk how any of this works so im just going to leave it be
const betterYTContainer = document.createElement("div");
betterYTContainer.id = "betterYT-container";
betterYTContainer.setAttribute(
Expand Down Expand Up @@ -91,7 +91,6 @@ export function populateActionElement(
ytShorts.playbackRate = state.playbackRate as number;

setPlaybackRate(state);
// injectedSuccess = setTimer( currTime || 0, Math.round(ytShorts.duration || 0))

betterYTContainer.addEventListener("click", () => {
const index = CYCLABLE_PLAYBACK_RATES.indexOf(ytShorts.playbackRate);
Expand Down
9 changes: 5 additions & 4 deletions src/lib/Info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { BooleanDictionary } from "./definitions";
import { getCurrentId, getOverlayElement, getUploadDate } from "./getters";

export function setInfo(features: BooleanDictionary) {
const views_interval = setInterval(addInfo, 10);
if (!isVideoPlaying()) throw new Error("Video not playing");

function addInfo() {
const addInfo = () => {
const info = [];
if (features["uploadDate"]) {
const uploadDate = getUploadDate().replace(/(\r\n|\n|\r)/gm, "");
if (uploadDate) info.push(uploadDate);
}

if (!isVideoPlaying()) return;
const overlayElement = getOverlayElement();
const h3 = document.createElement("h3");
h3.id = `ytViews${getCurrentId()}`;
Expand All @@ -21,5 +20,7 @@ export function setInfo(features: BooleanDictionary) {
.querySelector("ytd-reel-player-header-renderer a")
?.prepend(h3);
clearInterval(views_interval);
}
};

const views_interval = setInterval(addInfo, 10);
}
127 changes: 127 additions & 0 deletions src/lib/InjectionState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// todo: change values out for localised strings
export enum InjectionItemsEnum {
EXISTING_EVENTS = "Events",
ACTION_ELEMENT = "Action Elements",
PROGRESS_BAR = "Progress Bar",
VOLUME_SLIDER = "Volume Slider",
INFO = "Info",
}

export enum InjectionStateEnum {
FAILED = 0,
NEW,
SUCCESS,
}

const MAX_NUMBER_OF_ATTEMPTS = 3;

export class InjectionStateUnit {
name: InjectionItemsEnum;
state: InjectionStateEnum;
callback: () => void;
attempts: number;

constructor(name: InjectionItemsEnum, callback: () => void) {
this.name = name;
this.callback = callback;
this.state = InjectionStateEnum.NEW;
this.attempts = 0;
}

logError(err: Error) {
console.group("%cBYS Injection Error", "color: #ff7161;");
console.log(
`%cError while injecting "${this.name}"\n\nInjection was attempted ${this.attempts} times.\n\nMessage: "${err.message}"`,
"color: #ff7161;",
);
console.groupEnd();
}

inject() {
if (this.attempts > MAX_NUMBER_OF_ATTEMPTS) return;

let state = InjectionStateEnum.SUCCESS;
this.attempts++;

//prettier-ignore
try {
this.callback();
}
catch (err) {
if (this.attempts === MAX_NUMBER_OF_ATTEMPTS)
{
this.logError(err as Error);
}

state = InjectionStateEnum.FAILED;
}
finally {
this.state = state;
// eslint-disable-next-line no-unsafe-finally
return this.state === InjectionStateEnum.SUCCESS;
}
}
}

export class InjectionState {
units: InjectionStateUnit[];
id: number;
injectionSucceeded: boolean;

constructor(id: number, ...units: InjectionStateUnit[]) {
this.units = units;
this.id = id;
this.injectionSucceeded = false;
}

injectRemainingItems() {
if (this.injectionSucceeded) return;

this.injectionSucceeded = true;

for (const unit of this.units) {
if (unit.state !== InjectionStateEnum.SUCCESS) {
// eslint-disable-next-line prettier/prettier
this.injectionSucceeded = ( unit.inject() && this.injectionSucceeded ) as boolean ;
}
}
}

/**
* Adds {@link InjectionStateUnit} to the list of injection candidates.
* This is specifically for items we know will fail if injected before the video starts,
* as otherwise we could just pass them into the constructor.
*
* This method will handle duplicate checks too.
*
* @param _unit The injection unit.
*/
addUnit(_unit: InjectionStateUnit) {
if (this.hasUnit(_unit.name)) return;
this.units.push(_unit);
this.injectionSucceeded = false;
}

hasUnit(unitName: InjectionItemsEnum) {
for (const unit of this.units) {
if (unit.name === unitName) {
return true;
}
}

return false;
}
}

/**
* Assumes set items are objects that have an `id` prop
* @returns {@link InjectionState}, or {null} if unfound
*/
export function findInjectionStateInSet(id: number, set: Set<InjectionState>) {
//prettier-ignore
for ( const item of set )
if ( item.id === id )
return item;

return null;
}
Loading

0 comments on commit 9639313

Please sign in to comment.