config.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { DataStore, compress, type DataMigrationsDict, decompress } from "@sv443-network/userutils";
  2. import { featInfo } from "./features/index";
  3. import { compressionSupported, info, log } from "./utils";
  4. import { emitSiteEvent } from "./siteEvents";
  5. import { compressionFormat } from "./constants";
  6. import type { FeatureConfig } from "./types";
  7. import { emitInterface, getFeaturesInterface } from "./interface";
  8. /** If this number is incremented, the features object data will be migrated to the new format */
  9. export const formatVersion = 5;
  10. /** Config data format migration dictionary */
  11. export const migrations: DataMigrationsDict = {
  12. // 1 -> 2 (v1.0)
  13. 2: (oldData: Record<string, unknown>) => {
  14. const queueBtnsEnabled = Boolean(oldData.queueButtons);
  15. delete oldData.queueButtons;
  16. return {
  17. ...oldData,
  18. deleteFromQueueButton: queueBtnsEnabled,
  19. lyricsQueueButton: queueBtnsEnabled,
  20. };
  21. },
  22. // 2 -> 3 (v1.0)
  23. 3: (oldData: FeatureConfig) => useDefaultConfig([
  24. "removeShareTrackingParam", "numKeysSkipToTime",
  25. "fixSpacing", "scrollToActiveSongBtn", "logLevel",
  26. ], oldData),
  27. // 3 -> 4 (v1.1)
  28. 4: (oldData: FeatureConfig) => {
  29. const oldSwitchSitesHotkey = oldData.switchSitesHotkey as Record<string, unknown>;
  30. return {
  31. ...useDefaultConfig([
  32. "rememberSongTime", "rememberSongTimeSites",
  33. "volumeSliderScrollStep", "locale", "versionCheck",
  34. ], oldData),
  35. arrowKeySkipBy: 10,
  36. switchSitesHotkey: {
  37. code: oldSwitchSitesHotkey.key ?? "F9",
  38. shift: Boolean(oldSwitchSitesHotkey.shift ?? false),
  39. ctrl: Boolean(oldSwitchSitesHotkey.ctrl ?? false),
  40. alt: Boolean(oldSwitchSitesHotkey.meta ?? false),
  41. },
  42. listButtonsPlacement: "queueOnly",
  43. };
  44. },
  45. // 4 -> 5 (v1.2)
  46. 5: (oldData: FeatureConfig) => ({
  47. ...useDefaultConfig([
  48. "geniUrlBase", "geniUrlToken",
  49. "lyricsCacheMaxSize", "lyricsCacheTTL",
  50. "clearLyricsCache", "advancedMode",
  51. "checkVersionNow", "advancedLyricsFilter",
  52. "rememberSongTimeDuration", "rememberSongTimeReduction",
  53. "rememberSongTimeMinPlayTime", "volumeSharedBetweenTabs",
  54. "setInitialTabVolume", "initialTabVolumeLevel",
  55. "thumbnailOverlayBehavior", "thumbnailOverlayToggleBtnShown",
  56. "thumbnailOverlayShowIndicator", "thumbnailOverlayImageFit",
  57. "removeShareTrackingParamSites",
  58. ], oldData),
  59. }),
  60. // TODO: once advanced filtering is fully implemented, clear cache on migration to fv6
  61. // 5 -> 6 (v1.3)
  62. // 6: (oldData: FeatureConfig) =>
  63. } as const satisfies DataMigrationsDict;
  64. export const defaultData = (Object.keys(featInfo) as (keyof typeof featInfo)[])
  65. .reduce<Partial<FeatureConfig>>((acc, key) => {
  66. acc[key] = featInfo[key].default as unknown as undefined;
  67. return acc;
  68. }, {}) as FeatureConfig;
  69. /** Uses the default config as the base, then overwrites all values with the passed {@linkcode baseData}, then sets all passed {@linkcode resetKeys} to their default values */
  70. function useDefaultConfig(resetKeys: (keyof typeof featInfo)[], baseData?: FeatureConfig): Partial<FeatureConfig> {
  71. const newData = { ...defaultData, ...(baseData ?? {}) };
  72. for(const key of resetKeys)
  73. newData[key] = featInfo[key].default as never; // typescript funny moments
  74. return newData;
  75. }
  76. let canCompress = true;
  77. const bytmCfgStore = new DataStore({
  78. id: "bytm-config",
  79. formatVersion,
  80. defaultData,
  81. migrations,
  82. encodeData: (data) => canCompress ? compress(data, compressionFormat, "string") : data,
  83. decodeData: (data) => canCompress ? decompress(data, compressionFormat, "string") : data,
  84. });
  85. /** Initializes the DataStore instance and loads persistent data into memory */
  86. export async function initConfig() {
  87. canCompress = await compressionSupported();
  88. const oldFmtVer = Number(await GM.getValue(`_uucfgver-${bytmCfgStore.id}`, NaN));
  89. const data = await bytmCfgStore.loadData();
  90. log(`Initialized feature config DataStore (formatVersion = ${bytmCfgStore.formatVersion})`);
  91. if(isNaN(oldFmtVer))
  92. info(" !- Config data was initialized with default values");
  93. else if(oldFmtVer !== bytmCfgStore.formatVersion)
  94. info(` !- Config data was migrated from version ${oldFmtVer} to ${bytmCfgStore.formatVersion}`);
  95. emitInterface("bytm:configReady", getFeaturesInterface());
  96. return { ...data };
  97. }
  98. /** Returns the current feature config from the in-memory cache as a copy */
  99. export function getFeatures() {
  100. return bytmCfgStore.getData();
  101. }
  102. /** Saves the feature config synchronously to the in-memory cache and asynchronously to the persistent storage */
  103. export function setFeatures(featureConf: FeatureConfig) {
  104. const res = bytmCfgStore.setData(featureConf);
  105. emitSiteEvent("configChanged", bytmCfgStore.getData());
  106. info("Saved new feature config:", featureConf);
  107. return res;
  108. }
  109. /** Saves the default feature config synchronously to the in-memory cache and asynchronously to persistent storage */
  110. export function setDefaultFeatures() {
  111. const res = bytmCfgStore.saveDefaultData();
  112. emitSiteEvent("configChanged", bytmCfgStore.getData());
  113. info("Reset feature config to its default values");
  114. return res;
  115. }
  116. /** Clears the feature config from the persistent storage - since the cache will be out of whack, this should only be run before a site re-/unload */
  117. export async function clearConfig() {
  118. await bytmCfgStore.deleteData();
  119. info("Deleted config from persistent storage");
  120. }