Quellcode durchsuchen

feat: expose on plugin interface:
getDomain, waitVideoElementReady, getVideoSelector, getVideoElement

Sv443 vor 8 Monaten
Ursprung
Commit
9e086eb2b3
4 geänderte Dateien mit 143 neuen und 20 gelöschten Zeilen
  1. 24 18
      changelog.md
  2. 105 0
      contributing.md
  3. 5 1
      src/interface.ts
  4. 9 1
      src/types.ts

+ 24 - 18
changelog.md

@@ -47,17 +47,13 @@
   - Added license for plugin-related source code, see [license-for-plugins.txt](https://github.com/Sv443/BetterYTM/blob/develop/license-for-plugins.txt)
   - Added advanced feature to change the startup timeout (only impacts plugin initialization for now)
   - Now using a blue logo is instead of the red BetterYTM logo when the script was compiled in development (preview) mode
-  - SelectorObserver changes:
-    - Added `ytMasthead` instance for the title bar on YT
-    - Renamed all YT-specific instances to have the `yt` prefix
-      - `watchFlexy` renamed to `ytWatchFlexy`
-      - `watchMetadata` renamed to `ytWatchMetadata`
   - Fixed missing configuration keys in development/preview mode instead of potentially breaking the script
   - Added Storybook for easier and faster development of components
   - Removed the `@updateURL` and `@downloadURL` directives because their use is controversial and the script has a built-in update check now
   - Migrated to pnpm for faster compilation times
-  - Moved `NanoEmitter` class over to the [UserUtils library](https://github.com/Sv443-Network/UserUtils#nanoemitter) (it is still re-exported by the plugin interface as always)
-- **Plugin Changes:**
+  - Moved `NanoEmitter` class over to the [UserUtils library](https://github.com/Sv443-Network/UserUtils#nanoemitter) (it is still re-exported by the plugin interface as before)
+- **Plugin Changes:**  
+  <sup>See the [contributing guide](https://github.com/Sv443/BetterYTM/blob/main/contributing.md) for the latest documentation of the plugin interface</sup>
   - Added new components:
     -  `createLongBtn()` to create a button with an icon and text (works either as normal or as a toggle button)  
       The design follows that of the subscribe button on YTM's channel pages, but the consistent class names make it easy to style it differently.
@@ -72,17 +68,27 @@
     - `getAutoLikeData()` to return the current auto-like data (authenticated function)
     - `saveAutoLikeData()` to overwrite the auto-like data (authenticated function)
     - `fetchVideoVotes()` to fetch the approximate like and dislike count of a video from [Return Youtube Dislike](https://returnyoutubedislike.com/)
-  - Added new SelectorObserver instance `browseResponse` for pages like `/channel/{id}`
-  - Renamed event `bytm:initPlugins` to `bytm:registerPlugins` to be more consistent
-  - Added events
-    - `bytm:featureInitStarted` - emitted when the feature initialization process starts
-    - `bytm:featureInitialized` - emitted every time a feature has been initialized and is passed the feature's identifier string
-    - `bytm:dialogClosed` - emitted when a BytmDialog is closed and gets passed the instance
-    - `bytm:dialogClosed:id` - emitted only when the dialog with the given ID is closed and gets passed the instance
-    - `bytm:siteEvent:pathChanged` - emitted whenever the URL path (`location.pathname`) changes
-  - Now the event `bytm:siteEvent:fullscreenToggled` is only emitted once per fullscreen change
-  - Changed `event` property returned by `registerPlugin()` from nanoevents Emitter to NanoEmitter instance (see [the UserUtils docs](https://github.com/Sv443-Network/UserUtils#nanoemitter))  
-    In practice this changes nothing, but it benefits from plugins having access to the additional methods `once()` for immediately unsubscribing from an event after it was emitted once and `unsubscribeAll()` to remove all event listeners.
+    - `getDomain()` returns the current domain ("yt" or "ytm")
+    - `waitVideoElementReady()` returns a promise that resolves when the video element is ready
+    - `getVideoSelector()` returns the video element selector for the current domain
+    - `getVideoElement()` returns the video element for the current domain
+  - SelectorObserver / `addSelectorListener()` changes:
+    - Added `ytMasthead` instance for the title bar on YT
+    - Renamed all YT-specific instances to have the `yt` prefix
+      - `watchFlexy` renamed to `ytWatchFlexy`
+      - `watchMetadata` renamed to `ytWatchMetadata`
+    - Added new SelectorObserver instance `browseResponse` for pages like `/channel/{id}`
+  - Event changes:
+    - Added events
+      - `bytm:featureInitStarted` - emitted when the feature initialization process starts
+      - `bytm:featureInitialized` - emitted every time a feature has been initialized and is passed the feature's identifier string
+      - `bytm:dialogClosed` - emitted when a BytmDialog is closed and gets passed the instance
+      - `bytm:dialogClosed:id` - emitted only when the dialog with the given ID is closed and gets passed the instance
+      - `bytm:siteEvent:pathChanged` - emitted whenever the URL path (`location.pathname`) changes
+    - Now the event `bytm:siteEvent:fullscreenToggled` is only emitted once per fullscreen change
+    - Renamed event `bytm:initPlugins` to `bytm:registerPlugins` to be more consistent
+    - Changed `event` property returned by `registerPlugin()` from nanoevents Emitter to NanoEmitter instance (see [the UserUtils docs](https://github.com/Sv443-Network/UserUtils#nanoemitter))  
+      In practice this changes nothing, but it benefits from plugins having access to the additional methods `once()` for immediately unsubscribing from an event after it was emitted once and `unsubscribeAll()` to remove all event listeners.
 
 </details>
 

+ 105 - 0
contributing.md

@@ -365,6 +365,7 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
   - [registerPlugin()](#registerplugin) - Registers a plugin with BetterYTM with the given plugin definition object
   - [getPluginInfo()](#getplugininfo) 🔒 - Returns the plugin info object for the specified plugin - also used to check if a certain plugin is registered
 - BYTM-specific:
+  - [getDomain()](#getdomain) - Returns the current domain of the page as a constant string (either "yt" or "ytm")
   - [getResourceUrl()](#getresourceurl) - Returns a `blob:` URL provided by the local userscript extension for the specified BYTM resource file
   - [getSessionId()](#getsessionid) - Returns the unique session ID that is generated on every started session
 - DOM:
@@ -376,6 +377,9 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
   - [getVideoTime()](#getvideotime) - Returns the current video time (on both YT and YTM)
   - [getThumbnailUrl()](#getthumbnailurl) - Returns the URL to the thumbnail of the currently playing video
   - [getBestThumbnailUrl()](#getbestthumbnailurl) - Returns the URL to the best quality thumbnail of the currently playing video
+  - [waitVideoElementReady()](#waitvideoelementready) - Waits for the video element to be queryable in the DOM - has to be called after `bytm:observersReady`
+  - [getVideoSelector()](#getvideoselector) - Returns the CSS selector for the video element of the current domain
+  - [getVideoElement()](#getvideoelement) - Returns the video element of the current domain
 - Components:
   - [createHotkeyInput()](#createhotkeyinput) - Creates a hotkey input element
   - [createToggleInput()](#createtoggleinput) - Creates a toggle input element
@@ -560,6 +564,36 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 
 <br>
 
+> #### getDomain()
+> Usage:
+> ```ts
+> unsafeWindow.BYTM.getDomain(): "yt" | "ytm"
+> ```
+>   
+> Description:  
+> Returns the current domain of the page as a constant string.  
+> It will return `"yt"` for YouTube and `"ytm"` for YouTube Music.  
+> Throws an error if the domain is not supported by BetterYTM.  
+>   
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+> 
+> ```ts
+> try {
+>   const domain = unsafeWindow.BYTM.getDomain();
+> 
+>   if(domain === "yt")
+>     console.log("Running on YouTube");
+>   else
+>     console.log("Running on YouTube Music");^
+> }
+> catch(err) {
+>   console.error("Running on an unsupported domain:", err);
+> }
+> ```
+> </details>
+
+<br>
+
 > #### getResourceUrl()
 > Usage:  
 > ```ts
@@ -783,6 +817,77 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 
 <br>
 
+> #### waitVideoElementReady()
+> Usage:
+> ```ts
+> unsafeWindow.BYTM.waitVideoElementReady(): Promise<HTMLVideoElement>
+> ```
+>   
+> Description:  
+> Waits for the video element to be queryable in the DOM.  
+> If it already exists, the Promise will resolve immediately.  
+> This function has to be called after the `bytm:observersReady` event has been dispatched.  
+>   
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+> 
+> ```ts
+> unsafeWindow.addEventListener("bytm:observersReady", async () => {
+>   const videoElement = await unsafeWindow.BYTM.waitVideoElementReady();
+>   console.log("The video element:", videoElement);
+> });
+> ```
+> </details>
+
+<br>
+
+> #### getVideoSelector()
+> Usage:  
+> ```ts
+> unsafeWindow.BYTM.getVideoSelector(): string
+> ```
+>   
+> Description:  
+> Returns the CSS selector for the video element of the current [domain.](#getdomain)  
+>   
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+> 
+> ```ts
+> const domain = unsafeWindow.BYTM.getDomain();
+> const videoSelector = unsafeWindow.BYTM.getVideoSelector();
+> 
+> console.log(domain, videoSelector); // "ytm", "ytmusic-player video"
+> ```
+> </details>
+
+<br>
+
+> #### getVideoElement()
+> Usage:  
+> ```ts
+> unsafeWindow.BYTM.getVideoElement(): HTMLVideoElement | null
+> ```
+>   
+> Description:  
+> Returns the video element of the current domain (YT or YTM).  
+>   
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+> 
+> ```ts
+> async function pauseVideo() {
+>   const videoElement = await unsafeWindow.BYTM.waitVideoElementReady();
+>   videoElement?.pause();
+> }
+> 
+> window.addEventListener("bytm:observersReady", () => {
+>   document.querySelector("#my-button").addEventListener("click", () => {
+>     pauseVideo();
+>   });
+> });
+> ```
+> </details>
+
+<br>
+
 > #### setLocale()
 > Usage:  
 > ```ts

+ 5 - 1
src/interface.ts

@@ -1,7 +1,7 @@
 import * as UserUtils from "@sv443-network/userutils";
 import * as compareVersions from "compare-versions";
 import { mode, branch, host, buildNumber, compressionFormat, scriptInfo } from "./constants.js";
-import { getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, t, tp, type TrLocale, info, error, onInteraction, getThumbnailUrl, getBestThumbnailUrl, fetchVideoVotes } from "./utils/index.js";
+import { getDomain, waitVideoElementReady, getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, t, tp, type TrLocale, info, error, onInteraction, getThumbnailUrl, getBestThumbnailUrl, fetchVideoVotes, getVideoElement, getVideoSelector } from "./utils/index.js";
 import { addSelectorListener } from "./observers.js";
 import { getFeatures, setFeatures } from "./config.js";
 import { autoLikeStore, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features/index.js";
@@ -115,6 +115,7 @@ const globalFuncs: InterfaceFunctions = {
   /**/ getPluginInfo,
 
   // bytm-specific:
+  getDomain,
   getResourceUrl,
   getSessionId,
 
@@ -124,6 +125,9 @@ const globalFuncs: InterfaceFunctions = {
   getVideoTime,
   getThumbnailUrl,
   getBestThumbnailUrl,
+  waitVideoElementReady,
+  getVideoSelector,
+  getVideoElement,
 
   // translations:
   /**/ setLocale: setLocaleInterface,

+ 9 - 1
src/types.ts

@@ -4,7 +4,7 @@ import type { scriptInfo } from "./constants.js";
 import type { addSelectorListener } from "./observers.js";
 import type resources from "../assets/resources.json";
 import type locales from "../assets/locales.json";
-import type { getResourceUrl, getSessionId, getVideoTime, TrLocale, t, tp, fetchVideoVotes, onInteraction, getThumbnailUrl, getBestThumbnailUrl, getLocale, hasKey, hasKeyFor } from "./utils/index.js";
+import type { getResourceUrl, getSessionId, getVideoTime, TrLocale, t, tp, fetchVideoVotes, onInteraction, getThumbnailUrl, getBestThumbnailUrl, getLocale, hasKey, hasKeyFor, getVideoSelector, getVideoElement, getDomain, waitVideoElementReady } from "./utils/index.js";
 import type { SiteEventsMap } from "./siteEvents.js";
 import type { InterfaceEventsMap, getAutoLikeDataInterface, getFeaturesInterface, getPluginInfo, registerPlugin, saveAutoLikeDataInterface, saveFeaturesInterface, setLocaleInterface } from "./interface.js";
 import type { BytmDialog, ExImDialog, createCircularBtn, createHotkeyInput, createRipple, createToggleInput, showIconToast, showToast } from "./components/index.js";
@@ -254,6 +254,8 @@ export type InterfaceFunctions = {
   getPluginInfo: typeof getPluginInfo;
 
   // bytm-specific:
+  /** Returns the current domain as a constant string representation */
+  getDomain: typeof getDomain;
   /**
    * Returns the URL of a resource as defined in `assets/resources.json`  
    * There are also some resources like translation files that get added by `tools/post-build.ts`  
@@ -280,6 +282,12 @@ export type InterfaceFunctions = {
   getThumbnailUrl: typeof getThumbnailUrl;
   /** Returns the thumbnail URL with the best quality for the provided video ID */
   getBestThumbnailUrl: typeof getBestThumbnailUrl;
+  /** Resolves the returned promise when the video element is queryable in the DOM */
+  waitVideoElementReady: typeof waitVideoElementReady;
+  /** Returns the video element's CSS selector for the current domain */
+  getVideoSelector: typeof getVideoSelector;
+  /** Returns the video element for the current domain or null if none could be found */
+  getVideoElement: typeof getVideoElement;
 
   // translations:
   /** 🔒 Sets the locale for all new translations */