interface.ts 2.7 KB

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