1
0
Эх сурвалжийг харах

feat: expose getCurrentMediaType() on interface

Sv443 7 сар өмнө
parent
commit
dfc1443734

+ 1 - 0
changelog.md

@@ -73,6 +73,7 @@
     - `fetchVideoVotes()` to fetch the approximate like and dislike count of a video from [Return Youtube Dislike](https://returnyoutubedislike.com/)
     - `getDomain()` returns the current domain ("yt" or "ytm")
     - `waitVideoElementReady()` returns a promise that resolves when the video element is ready
+    - `getCurrentMediaType()` (on YTM only) returns the current media type ("video" or "song")
   - SelectorObserver / `addSelectorListener()` changes:
     - Added `ytMasthead` instance for the title bar on YT
     - Renamed all YT-specific instances to have the `yt` prefix

+ 27 - 0
contributing.md

@@ -383,6 +383,7 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
   - [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`
+  - [getCurrentMediaType()](#getcurrentmediatype) - (On YTM only) returns the type of media that is currently playing (either "video" or "song")
 - Components:
   - [createHotkeyInput()](#createhotkeyinput) - Creates a hotkey input element
   - [createToggleInput()](#createtoggleinput) - Creates a toggle input element
@@ -870,6 +871,32 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 
 <br>
 
+> #### getCurrentMediaType()
+> Usage:  
+> ```ts
+> unsafeWindow.BYTM.getCurrentMediaType(): "video" | "song"
+> ```
+>   
+> Description:  
+> Returns the type of media that is currently playing (works on YTM only).  
+> It will return `"video"` for videos and `"song"` for songs.  
+> Throws an error if [`waitVideoElementReady()`](#waitvideoelementready) hasn't been awaited yet or the function is called on YT.  
+>   
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+> 
+> ```ts
+> try {
+>   const mediaType = unsafeWindow.BYTM.getCurrentMediaType();
+>   console.log(`The current media type is: ${mediaType}`);
+> }
+> catch(err) {
+>   console.error("Couldn't get the current media type:", err);
+> }
+> ```
+> </details>
+
+<br>
+
 > #### setLocale()
 > Usage:  
 > ```ts

+ 2 - 2
src/features/behavior.ts

@@ -1,5 +1,5 @@
 import { clamp, interceptWindowEvent, pauseFor } from "@sv443-network/userutils";
-import { domLoaded, error, getDomain, getVideoTime, getWatchId, info, log, waitVideoElementReady, clearNode, currentMediaType, getVideoElement } from "../utils/index.js";
+import { domLoaded, error, getDomain, getVideoTime, getWatchId, info, log, waitVideoElementReady, clearNode, getCurrentMediaType, getVideoElement } from "../utils/index.js";
 import { getFeature } from "../config.js";
 import { addSelectorListener } from "../observers.js";
 import { initialParams } from "../constants.js";
@@ -144,7 +144,7 @@ async function remTimeRestoreTime() {
           const vidRestoreTime = entry.songTime - (getFeature("rememberSongTimeReduction") ?? 0);
           vidElem.currentTime = clamp(Math.max(vidRestoreTime, 0), 0, vidElem.duration);
           await remTimeDeleteEntry(entry.watchID);
-          info(`Restored ${currentMediaType()} time to ${Math.floor(vidRestoreTime / 60)}m, ${(vidRestoreTime % 60).toFixed(1)}s`, LogLevel.Info);
+          info(`Restored ${getCurrentMediaType()} time to ${Math.floor(vidRestoreTime / 60)}m, ${(vidRestoreTime % 60).toFixed(1)}s`, LogLevel.Info);
         };
 
         if(!domLoaded)

+ 3 - 3
src/features/input.ts

@@ -1,5 +1,5 @@
 import { DataStore, clamp, compress, decompress } from "@sv443-network/userutils";
-import { error, getVideoTime, info, log, warn, getDomain, compressionSupported, t, clearNode, resourceAsString, getCurrentChannelId, currentMediaType, sanitizeChannelId, addStyleFromResource, isValidChannelId, getVideoElement, setInnerHtml } from "../utils/index.js";
+import { error, getVideoTime, info, log, warn, getDomain, compressionSupported, t, clearNode, resourceAsString, getCurrentChannelId, getCurrentMediaType, sanitizeChannelId, addStyleFromResource, isValidChannelId, getVideoElement, setInnerHtml } from "../utils/index.js";
 import type { AutoLikeData, Domain } from "../types.js";
 import { disableBeforeUnload } from "./behavior.js";
 import { emitSiteEvent, siteEvents } from "../siteEvents.js";
@@ -217,10 +217,10 @@ export async function initAutoLike() {
           if(likeRendererEl.getAttribute("like-status") !== "LIKE") {
             likeBtnEl.click();
             getFeature("autoLikeShowToast") && showIconToast({
-              message: t(`auto_liked_a_channels_${currentMediaType()}`, likeChan.name),
+              message: t(`auto_liked_a_channels_${getCurrentMediaType()}`, likeChan.name),
               icon: "icon-auto_like",
             });
-            log(`Auto-liked ${currentMediaType()} from channel '${likeChan.name}' (${likeChan.id})`);
+            log(`Auto-liked ${getCurrentMediaType()} from channel '${likeChan.name}' (${likeChan.id})`);
           }
         };
         timeout = setTimeout(ytmTryAutoLike, autoLikeTimeoutMs);

+ 2 - 2
src/features/layout.ts

@@ -2,7 +2,7 @@ import { addParent, autoPlural, debounce, fetchAdvanced, pauseFor } from "@sv443
 import { getFeature, getFeatures } from "../config.js";
 import { siteEvents } from "../siteEvents.js";
 import { addSelectorListener } from "../observers.js";
-import { error, getResourceUrl, log, warn, t, onInteraction, openInTab, getBestThumbnailUrl, getDomain, currentMediaType, domLoaded, waitVideoElementReady, addStyleFromResource, fetchVideoVotes, getWatchId, getLocale, tp, getVideoTime, setInnerHtml } from "../utils/index.js";
+import { error, getResourceUrl, log, warn, t, onInteraction, openInTab, getBestThumbnailUrl, getDomain, getCurrentMediaType, domLoaded, waitVideoElementReady, addStyleFromResource, fetchVideoVotes, getWatchId, getLocale, tp, getVideoTime, setInnerHtml } from "../utils/index.js";
 import { mode, scriptInfo } from "../constants.js";
 import { openCfgMenu } from "../menu/menu_old.js";
 import { createCircularBtn, createRipple } from "../components/index.js";
@@ -482,7 +482,7 @@ export async function initThumbnailOverlay() {
       const behavior = getFeature("thumbnailOverlayBehavior");
 
       let showOverlay = behavior === "always";
-      const isVideo = currentMediaType() === "video";
+      const isVideo = getCurrentMediaType() === "video";
 
       if(behavior === "videosOnly" && isVideo)
         showOverlay = true;

+ 2 - 2
src/features/lyrics.ts

@@ -1,5 +1,5 @@
 import { fetchAdvanced } from "@sv443-network/userutils";
-import { error, getResourceUrl, info, log, warn, t, tp, currentMediaType, constructUrl, onInteraction, openInTab } from "../utils/index.js";
+import { error, getResourceUrl, info, log, warn, t, tp, getCurrentMediaType, constructUrl, onInteraction, openInTab } from "../utils/index.js";
 import { emitInterface } from "../interface.js";
 import { mode, scriptInfo } from "../constants.js";
 import { getFeature } from "../config.js";
@@ -153,7 +153,7 @@ export function sanitizeArtists(artists: string) {
 export async function getCurrentLyricsUrl() {
   try {
     // In videos the video title contains both artist and song title, in "regular" YTM songs, the video title only contains the song title
-    const isVideo = currentMediaType() === "video";
+    const isVideo = getCurrentMediaType() === "video";
 
     const songTitleElem = document.querySelector<HTMLElement>(".content-info-wrapper > yt-formatted-string");
     const songMetaElem = document.querySelector<HTMLElement>("span.subtitle > yt-formatted-string :first-child");

+ 3 - 2
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 { getDomain, waitVideoElementReady, getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, t, tp, type TrLocale, info, error, onInteraction, getThumbnailUrl, getBestThumbnailUrl, fetchVideoVotes, setInnerHtml } 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, setInnerHtml, getCurrentMediaType } 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";
@@ -127,6 +127,7 @@ const globalFuncs: InterfaceFunctions = {
   getThumbnailUrl,
   getBestThumbnailUrl,
   waitVideoElementReady,
+  getCurrentMediaType,
 
   // translations:
   /**/ setLocale: setLocaleInterface,
@@ -188,7 +189,7 @@ export function initInterface() {
   log("Initialized BYTM interface");
 }
 
-/** Sets a global property on the unsafeWindow.BYTM object */
+/** Sets a global property on the unsafeWindow.BYTM object - ⚠️ use with caution as these props can be accessed by any script on the page! */
 export function setGlobalProp<
   TKey extends keyof Window["BYTM"],
   TValue = Window["BYTM"][TKey],

+ 3 - 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, getDomain, waitVideoElementReady, setInnerHtml } from "./utils/index.js";
+import type { getResourceUrl, getSessionId, getVideoTime, TrLocale, t, tp, fetchVideoVotes, onInteraction, getThumbnailUrl, getBestThumbnailUrl, getLocale, hasKey, hasKeyFor, getDomain, waitVideoElementReady, setInnerHtml, getCurrentMediaType } 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";
@@ -298,6 +298,8 @@ export type InterfaceFunctions = {
   getBestThumbnailUrl: typeof getBestThumbnailUrl;
   /** Resolves the returned promise when the video element is queryable in the DOM */
   waitVideoElementReady: typeof waitVideoElementReady;
+  /** (On YTM only) returns the current media type (video or song) */
+  getCurrentMediaType: typeof getCurrentMediaType;
 
   // translations:
   /** 🔒 Sets the locale for all new translations */

+ 5 - 6
src/utils/dom.ts

@@ -210,17 +210,16 @@ export function clearNode(element: Element) {
 }
 
 /**
- * Checks if the currently playing media is a song or a video.  
- * Only works on YTM and will throw on YT!  
- * This function should only be called after awaiting {@linkcode waitVideoElementReady}!
+ * Returns an identifier for the currently playing media type on YTM (song or video).  
+ * Only works on YTM and will throw on YT or if {@linkcode waitVideoElementReady} hasn't been awaited yet.
  */
-export function currentMediaType(): "video" | "song" {
+export function getCurrentMediaType(): "video" | "song" {
   if(getDomain() === "yt")
     throw new Error("currentMediaType() is only available on YTM!");
   const songImgElem = document.querySelector("ytmusic-player #song-image");
   if(!songImgElem)
-    throw new Error("Couldn't find the song image element. Use this function only after `await waitVideoElementReady()`!");
-  return getUnsafeWindow().getComputedStyle(songImgElem).display !== "none" ? "song" : "video";
+    throw new Error("Couldn't find the song image element. Use this function only after awaiting `waitVideoElementReady()`!");
+  return window.getComputedStyle(songImgElem).display !== "none" ? "song" : "video";
 }
 
 /** Copies the provided text to the clipboard and shows an error message for manual copying if the grant `GM.setClipboard` is not given. */