interface.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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, NanoEmitter, t, tp, type TrLocale } from "./utils";
  4. import { addSelectorListener } from "./observers";
  5. import { getFeatures, saveFeatures } from "./config";
  6. import { featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong, type LyricsCache } from "./features";
  7. import type { SiteEventsMap } from "./siteEvents";
  8. import type { FeatureConfig, FeatureInfo, LyricsCacheEntry } from "./types";
  9. import { BytmDialog, createHotkeyInput, createToggleInput } from "./components";
  10. const { getUnsafeWindow } = UserUtils;
  11. /** All events that can be emitted on the BYTM interface and the data they provide */
  12. export type InterfaceEvents = {
  13. /** Emitted when BYTM has finished initializing all features */
  14. "bytm:ready": undefined;
  15. /**
  16. * Emitted whenever the SelectorObserver instances have been initialized
  17. * Use `unsafeWindow.BYTM.addObserverListener()` to add custom listener functions to the observers
  18. */
  19. "bytm:observersReady": undefined;
  20. /** Emitted as soon as the feature config has been loaded */
  21. "bytm:configReady": FeatureConfig;
  22. /** Emitted whenever the locale is changed */
  23. "bytm:setLocale": { locale: TrLocale };
  24. /** Emitted when a dialog was opened - returns the dialog's instance */
  25. "bytm:dialogOpened": BytmDialog;
  26. /** Emitted when the dialog with the specified ID was opened - returns the dialog's instance - use `as "bytm:dialogOpened:id"` in TS to make the error go away */
  27. "bytm:dialogOpened:id": BytmDialog;
  28. /** Emitted whenever the lyrics URL for a song is loaded */
  29. "bytm:lyricsLoaded": { type: "current" | "queue", artists: string, title: string, url: string };
  30. /** Emitted when the lyrics cache has been loaded */
  31. "bytm:lyricsCacheReady": LyricsCache;
  32. /** Emitted when the lyrics cache has been cleared */
  33. "bytm:lyricsCacheCleared": undefined;
  34. /** Emitted when an entry is added to the lyrics cache - "penalized" entries have a lower TTL because they were less related in lyrics lookups, as opposed to the "best" entries */
  35. "bytm:lyricsCacheEntryAdded": { type: "best" | "penalized", entry: LyricsCacheEntry };
  36. // additionally all events from SiteEventsMap in `src/siteEvents.ts`
  37. // are emitted in this format: "bytm:siteEvent:nameOfSiteEvent"
  38. };
  39. const globalFuncs = {
  40. addSelectorListener,
  41. getResourceUrl,
  42. getSessionId,
  43. getVideoTime,
  44. setLocale,
  45. getLocale,
  46. hasKey,
  47. hasKeyFor,
  48. t,
  49. tp,
  50. getFeatures: getFeaturesInterface,
  51. saveFeatures,
  52. fetchLyricsUrlTop,
  53. getLyricsCacheEntry,
  54. sanitizeArtists,
  55. sanitizeSong,
  56. };
  57. /** Initializes the BYTM interface */
  58. export function initInterface() {
  59. const props = {
  60. mode,
  61. branch,
  62. host,
  63. buildNumber,
  64. compressionFormat,
  65. ...scriptInfo,
  66. ...globalFuncs,
  67. UserUtils,
  68. NanoEmitter,
  69. BytmDialog,
  70. createHotkeyInput,
  71. createToggleInput,
  72. };
  73. for(const [key, value] of Object.entries(props))
  74. setGlobalProp(key, value);
  75. log("Initialized BYTM interface");
  76. }
  77. /** Sets a global property on the unsafeWindow.BYTM object */
  78. export function setGlobalProp<
  79. TKey extends keyof Window["BYTM"],
  80. TValue = Window["BYTM"][TKey],
  81. > (
  82. key: TKey | (string & {}),
  83. value: TValue,
  84. ) {
  85. // use unsafeWindow so the properties are available to plugins outside of the userscript's scope
  86. const win = getUnsafeWindow();
  87. if(typeof win.BYTM !== "object")
  88. win.BYTM = {} as typeof window.BYTM;
  89. win.BYTM[key] = value;
  90. }
  91. /** Emits an event on the BYTM interface */
  92. export function emitInterface<
  93. TEvt extends keyof InterfaceEvents,
  94. TDetail extends InterfaceEvents[TEvt],
  95. >(
  96. type: TEvt | `bytm:siteEvent:${keyof SiteEventsMap}`,
  97. ...data: (TDetail extends undefined ? [undefined?] : [TDetail])
  98. ) {
  99. getUnsafeWindow().dispatchEvent(new CustomEvent(type, { detail: data[0] }));
  100. }
  101. //#MARKER proxy functions
  102. /** Returns the current feature config, with sensitive values replaced by `undefined` */
  103. export function getFeaturesInterface() {
  104. const features = getFeatures();
  105. for(const ftKey of Object.keys(features)) {
  106. const info = featInfo[ftKey as keyof typeof featInfo] as FeatureInfo[keyof FeatureInfo];
  107. if(info && info.valueHidden) // @ts-ignore
  108. features[ftKey as keyof typeof features] = undefined;
  109. }
  110. return features as FeatureConfig;
  111. }