config.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import { ConfigManager, compress, type ConfigMigrationsDict, 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: ConfigMigrationsDict = {
  12. // 1 -> 2
  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
  23. 3: (oldData: Record<string, unknown>) => ({
  24. ...oldData,
  25. removeShareTrackingParam: getFeatureDefault("removeShareTrackingParam"),
  26. numKeysSkipToTime: getFeatureDefault("numKeysSkipToTime"),
  27. fixSpacing: getFeatureDefault("fixSpacing"),
  28. scrollToActiveSongBtn: getFeatureDefault("scrollToActiveSongBtn"),
  29. logLevel: getFeatureDefault("logLevel"),
  30. }),
  31. // 3 -> 4
  32. 4: (oldData: Record<string, unknown>) => {
  33. const oldSwitchSitesHotkey = oldData.switchSitesHotkey as Record<string, unknown>;
  34. return {
  35. ...oldData,
  36. rememberSongTime: getFeatureDefault("rememberSongTime"),
  37. rememberSongTimeSites: getFeatureDefault("rememberSongTimeSites"),
  38. arrowKeySkipBy: 10,
  39. switchSitesHotkey: {
  40. code: oldSwitchSitesHotkey.key ?? "F9",
  41. shift: Boolean(oldSwitchSitesHotkey.shift ?? false),
  42. ctrl: Boolean(oldSwitchSitesHotkey.ctrl ?? false),
  43. alt: Boolean(oldSwitchSitesHotkey.meta ?? false),
  44. },
  45. listButtonsPlacement: "queueOnly",
  46. volumeSliderScrollStep: getFeatureDefault("volumeSliderScrollStep"),
  47. locale: getFeatureDefault("locale"),
  48. versionCheck: getFeatureDefault("versionCheck"),
  49. };
  50. },
  51. // 4 -> 5
  52. 5: (oldData: FeatureConfig) => {
  53. return {
  54. ...oldData,
  55. geniUrlBase: getFeatureDefault("geniUrlBase"),
  56. geniUrlToken: getFeatureDefault("geniUrlToken"),
  57. lyricsCacheMaxSize: getFeatureDefault("lyricsCacheMaxSize"),
  58. lyricsCacheTTL: getFeatureDefault("lyricsCacheTTL"),
  59. clearLyricsCache: getFeatureDefault("clearLyricsCache"),
  60. advancedMode: getFeatureDefault("advancedMode"),
  61. lockVolume: getFeatureDefault("lockVolume"),
  62. lockVolumeLevel: getFeatureDefault("lockVolumeLevel"),
  63. } satisfies FeatureConfig;
  64. },
  65. };
  66. function getFeatureDefault<TKey extends keyof typeof featInfo>(key: TKey): typeof featInfo[TKey]["default"] {
  67. return featInfo[key].default;
  68. }
  69. export const defaultConfig = (Object.keys(featInfo) as (keyof typeof featInfo)[])
  70. .reduce<Partial<FeatureConfig>>((acc, key) => {
  71. acc[key] = featInfo[key].default as unknown as undefined;
  72. return acc;
  73. }, {}) as FeatureConfig;
  74. let canCompress = true;
  75. const cfgMgr = new ConfigManager({
  76. id: "bytm-config",
  77. formatVersion,
  78. defaultConfig,
  79. migrations,
  80. encodeData: (data) => canCompress ? compress(data, compressionFormat, "string") : data,
  81. decodeData: (data) => canCompress ? decompress(data, compressionFormat, "string") : data,
  82. });
  83. /** Initializes the ConfigManager instance and loads persistent data into memory */
  84. export async function initConfig() {
  85. canCompress = await compressionSupported();
  86. const oldFmtVer = Number(await GM.getValue(`_uucfgver-${cfgMgr.id}`, NaN));
  87. const data = await cfgMgr.loadData();
  88. log(`Initialized ConfigManager (format version = ${cfgMgr.formatVersion})`);
  89. if(isNaN(oldFmtVer))
  90. info("Config data initialized with default values");
  91. else if(oldFmtVer !== cfgMgr.formatVersion)
  92. info(`Config data migrated from version ${oldFmtVer} to ${cfgMgr.formatVersion}`);
  93. emitInterface("bytm:configReady", getFeaturesInterface());
  94. return { ...data };
  95. }
  96. /** Returns the current feature config from the in-memory cache as a copy */
  97. export function getFeatures() {
  98. return cfgMgr.getData();
  99. }
  100. /** Saves the feature config synchronously to the in-memory cache and asynchronously to the persistent storage */
  101. export function saveFeatures(featureConf: FeatureConfig) {
  102. const res = cfgMgr.setData(featureConf);
  103. emitSiteEvent("configChanged", cfgMgr.getData());
  104. info("Saved new feature config:", featureConf);
  105. return res;
  106. }
  107. /** Saves the default feature config synchronously to the in-memory cache and asynchronously to persistent storage */
  108. export function setDefaultFeatures() {
  109. const res = cfgMgr.saveDefaultData();
  110. emitSiteEvent("configChanged", cfgMgr.getData());
  111. info("Reset feature config to its default values");
  112. return res;
  113. }
  114. /** 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 */
  115. export async function clearConfig() {
  116. await cfgMgr.deleteConfig();
  117. info("Deleted config from persistent storage");
  118. }