interface.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import * as UserUtils from "@sv443-network/userutils";
  2. import { mode, branch, host, buildNumber, compressionFormat, scriptInfo } from "./constants";
  3. import { getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, t, tp, type TrLocale } from "./utils";
  4. import { addSelectorListener } from "./observers";
  5. import { getFeatures, saveFeatures } from "./config";
  6. import { featInfo } from "./features";
  7. import { fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features/lyrics";
  8. import type { SiteEventsMap } from "./siteEvents";
  9. import type { FeatureConfig, FeatureInfo } from "./types";
  10. const { getUnsafeWindow } = UserUtils;
  11. /** All events that can be emitted on the BYTM interface and the data they provide */
  12. export interface InterfaceEvents {
  13. /** Emitted when BYTM has finished initializing all features */
  14. "bytm:ready": undefined;
  15. /** Emitted whenever the lyrics URL for a song is loaded */
  16. "bytm:lyricsLoaded": { type: "current" | "queue", artists: string, title: string, url: string };
  17. /** Emitted whenever the locale is changed */
  18. "bytm:setLocale": { locale: TrLocale };
  19. /**
  20. * Emitted whenever the SelectorObserver instances have been initialized
  21. * Use `unsafeWindow.BYTM.addObserverListener()` to add custom listener functions to the observers
  22. */
  23. "bytm:observersReady": undefined;
  24. // additionally all events from SiteEventsMap in `src/siteEvents.ts`
  25. // are emitted in this format: "bytm:siteEvent:nameOfSiteEvent"
  26. }
  27. const globalFuncs = {
  28. addSelectorListener,
  29. getResourceUrl,
  30. getSessionId,
  31. getVideoTime,
  32. setLocale,
  33. getLocale,
  34. hasKey,
  35. hasKeyFor,
  36. t,
  37. tp,
  38. getFeatures: getFeaturesInterface,
  39. saveFeatures,
  40. fetchLyricsUrlTop,
  41. getLyricsCacheEntry,
  42. sanitizeArtists,
  43. sanitizeSong,
  44. };
  45. /** Initializes the BYTM interface */
  46. export function initInterface() {
  47. const props = {
  48. mode,
  49. branch,
  50. host,
  51. buildNumber,
  52. compressionFormat,
  53. ...scriptInfo,
  54. ...globalFuncs,
  55. UserUtils,
  56. };
  57. for(const [key, value] of Object.entries(props))
  58. setGlobalProp(key, value);
  59. log("Initialized BYTM interface");
  60. }
  61. /** Sets a global property on the window.BYTM object */
  62. export function setGlobalProp<
  63. TKey extends keyof Window["BYTM"],
  64. TValue = Window["BYTM"][TKey],
  65. > (
  66. key: TKey | (string & {}),
  67. value: TValue,
  68. ) {
  69. // use unsafeWindow so the properties are available outside of the userscript's scope
  70. const win = getUnsafeWindow();
  71. if(!win.BYTM)
  72. win.BYTM = {} as typeof win.BYTM;
  73. win.BYTM[key] = value;
  74. }
  75. /** Emits an event on the BYTM interface */
  76. export function emitInterface<
  77. TEvt extends keyof InterfaceEvents,
  78. TDetail extends InterfaceEvents[TEvt],
  79. >(
  80. type: TEvt | `bytm:siteEvent:${keyof SiteEventsMap}`,
  81. ...data: (TDetail extends undefined ? [undefined?] : [TDetail])
  82. ) {
  83. getUnsafeWindow().dispatchEvent(new CustomEvent(type, { detail: data[0] }));
  84. }
  85. //#MARKER proxy functions
  86. function getFeaturesInterface() {
  87. const features = getFeatures();
  88. for(const ftKey of Object.keys(features)) {
  89. const info = featInfo[ftKey as keyof typeof featInfo] as FeatureInfo[keyof FeatureInfo];
  90. if(info && info.valueHidden) // @ts-ignore
  91. features[ftKey as keyof typeof features] = undefined;
  92. }
  93. return features as FeatureConfig;
  94. }