Browse Source

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

Sv443 8 months ago
parent
commit
9e086eb2b3
4 changed files with 143 additions and 20 deletions
  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 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)
   - 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
   - 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
   - Fixed missing configuration keys in development/preview mode instead of potentially breaking the script
   - Added Storybook for easier and faster development of components
   - 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
   - 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
   - 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:
   - Added new components:
     -  `createLongBtn()` to create a button with an icon and text (works either as normal or as a toggle button)  
     -  `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.
       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)
     - `getAutoLikeData()` to return the current auto-like data (authenticated function)
     - `saveAutoLikeData()` to overwrite the 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/)
     - `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>
 </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
   - [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
   - [getPluginInfo()](#getplugininfo) 🔒 - Returns the plugin info object for the specified plugin - also used to check if a certain plugin is registered
 - BYTM-specific:
 - 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
   - [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
   - [getSessionId()](#getsessionid) - Returns the unique session ID that is generated on every started session
 - DOM:
 - 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)
   - [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
   - [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
   - [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:
 - Components:
   - [createHotkeyInput()](#createhotkeyinput) - Creates a hotkey input element
   - [createHotkeyInput()](#createhotkeyinput) - Creates a hotkey input element
   - [createToggleInput()](#createtoggleinput) - Creates a toggle 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>
 <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()
 > #### getResourceUrl()
 > Usage:  
 > Usage:  
 > ```ts
 > ```ts
@@ -783,6 +817,77 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 
 
 <br>
 <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()
 > #### setLocale()
 > Usage:  
 > Usage:  
 > ```ts
 > ```ts

+ 5 - 1
src/interface.ts

@@ -1,7 +1,7 @@
 import * as UserUtils from "@sv443-network/userutils";
 import * as UserUtils from "@sv443-network/userutils";
 import * as compareVersions from "compare-versions";
 import * as compareVersions from "compare-versions";
 import { mode, branch, host, buildNumber, compressionFormat, scriptInfo } from "./constants.js";
 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 { addSelectorListener } from "./observers.js";
 import { getFeatures, setFeatures } from "./config.js";
 import { getFeatures, setFeatures } from "./config.js";
 import { autoLikeStore, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features/index.js";
 import { autoLikeStore, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features/index.js";
@@ -115,6 +115,7 @@ const globalFuncs: InterfaceFunctions = {
   /**/ getPluginInfo,
   /**/ getPluginInfo,
 
 
   // bytm-specific:
   // bytm-specific:
+  getDomain,
   getResourceUrl,
   getResourceUrl,
   getSessionId,
   getSessionId,
 
 
@@ -124,6 +125,9 @@ const globalFuncs: InterfaceFunctions = {
   getVideoTime,
   getVideoTime,
   getThumbnailUrl,
   getThumbnailUrl,
   getBestThumbnailUrl,
   getBestThumbnailUrl,
+  waitVideoElementReady,
+  getVideoSelector,
+  getVideoElement,
 
 
   // translations:
   // translations:
   /**/ setLocale: setLocaleInterface,
   /**/ 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 { addSelectorListener } from "./observers.js";
 import type resources from "../assets/resources.json";
 import type resources from "../assets/resources.json";
 import type locales from "../assets/locales.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 { SiteEventsMap } from "./siteEvents.js";
 import type { InterfaceEventsMap, getAutoLikeDataInterface, getFeaturesInterface, getPluginInfo, registerPlugin, saveAutoLikeDataInterface, saveFeaturesInterface, setLocaleInterface } from "./interface.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";
 import type { BytmDialog, ExImDialog, createCircularBtn, createHotkeyInput, createRipple, createToggleInput, showIconToast, showToast } from "./components/index.js";
@@ -254,6 +254,8 @@ export type InterfaceFunctions = {
   getPluginInfo: typeof getPluginInfo;
   getPluginInfo: typeof getPluginInfo;
 
 
   // bytm-specific:
   // 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`  
    * 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`  
    * 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;
   getThumbnailUrl: typeof getThumbnailUrl;
   /** Returns the thumbnail URL with the best quality for the provided video ID */
   /** Returns the thumbnail URL with the best quality for the provided video ID */
   getBestThumbnailUrl: typeof getBestThumbnailUrl;
   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:
   // translations:
   /** 🔒 Sets the locale for all new translations */
   /** 🔒 Sets the locale for all new translations */