Furaffinity-Custom-Settings

Helper Script to create Custom settings on Furaffinitiy

当前为 2023-09-12 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/475041/1249083/Furaffinity-Custom-Settings.js

  1. // ==UserScript==
  2. // @name Furaffinity-Custom-Settings
  3. // @namespace Violentmonkey Scripts
  4. // @grant none
  5. // @version 3.3.0
  6. // @author Midori Dragon
  7. // @description Helper Script to create Custom settings on Furaffinitiy
  8. // @icon https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2
  9. // @homepageURL https://gf.qytechs.cn/de/scripts/475041-furaffinity-custom-settings
  10. // @supportURL https://gf.qytechs.cn/de/scripts/475041-furaffinity-custom-settings/feedback
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. // jshint esversion: 8
  15.  
  16. //#region Local Access
  17. class Settings {
  18. constructor() {
  19. this._name = "Extension Settings";
  20. this._provider = "Custom Furaffinity Settings";
  21. this.HeaderName = "Extension Settings";
  22. this.Settings = [];
  23.  
  24. nameId = makeIdCompatible(this._name);
  25. providerId = makeIdCompatible(this._provider);
  26. }
  27.  
  28. set Name(value) {
  29. this._name = value;
  30. nameId = makeIdCompatible(this._name);
  31. }
  32. get Name() {
  33. return this._name;
  34. }
  35.  
  36. set Provider(value) {
  37. this._provider = value;
  38. providerId = makeIdCompatible(this._provider);
  39. }
  40. get Provider() {
  41. return this._provider;
  42. }
  43.  
  44. async loadSettings() {
  45. try {
  46. await readSettings();
  47. addExSettings();
  48. if (window.location.toString().includes("controls/settings")) {
  49. addExSettingsSidebar();
  50. if (window.location.toString().includes("?extension=" + providerId)) loadSettings();
  51. }
  52. } catch (e) {
  53. console.error(e);
  54. }
  55. }
  56.  
  57. toString() {
  58. let settingsString = "(";
  59. for (const setting of CustomSettings.Settings) {
  60. if (setting.type !== SettingTypes.Action) settingsString += `"${setting.toString()}", `;
  61. }
  62. settingsString = settingsString.slice(0, -2);
  63. settingsString += ")";
  64. return settingsString;
  65. }
  66. }
  67.  
  68. class LocalSetting {
  69. constructor() {
  70. this.id;
  71. this.name;
  72. this.type;
  73. this.document;
  74. this.action;
  75. this._value;
  76. this.defaultValue;
  77. }
  78.  
  79. set value(newValue) {
  80. this._value = newValue;
  81. if (newValue == this.defaultValue) localStorage.removeItem(this.id);
  82. else localStorage.setItem(this.id, newValue);
  83. }
  84. get value() {
  85. return this._value;
  86. }
  87.  
  88. toString() {
  89. return `${this.name} = ${this.value}`;
  90. }
  91. }
  92.  
  93. let nameId;
  94. let providerId;
  95. let bodyContainer;
  96. //#endregion
  97.  
  98. //#region Global Access
  99. class Setting {
  100. constructor(name, description, type, typeDescription, defaultValue, action) {
  101. this._id;
  102. this.name = name;
  103. this.description = description;
  104. this.type = type;
  105. this.typeDescription = typeDescription;
  106. this.defaultValue = defaultValue;
  107. this.action = action;
  108. this._idFirstSet = true;
  109.  
  110. addSetting(this);
  111. }
  112.  
  113. set id(newValue) {
  114. if (this._idFirstSet) {
  115. this._id = newValue;
  116. this._idFirstSet = false;
  117. } else throw new Error("Can't set Id of a Setting that was already been set.");
  118. }
  119. get id() {
  120. return this._id;
  121. }
  122.  
  123. set value(newValue) {
  124. getLocalSettingById(this._id).value = newValue;
  125. }
  126. get value() {
  127. const setting = getLocalSettingById(this._id);
  128. setting.value = readSettingValue(setting);
  129. return setting.value;
  130. }
  131. }
  132.  
  133. const CustomSettings = new Settings();
  134. const SettingTypes = Object.freeze({
  135. Number: Symbol("Number"),
  136. Boolean: Symbol("Boolean"),
  137. Action: Symbol("Action"),
  138. Text: Symbol("Text"),
  139. });
  140. //#endregion
  141.  
  142. function addSetting(newSetting) {
  143. const setting = new LocalSetting();
  144. if (newSetting.id) setting.id = newSetting.id;
  145. else {
  146. setting.id = providerId + "_" + makeIdCompatible(newSetting.name);
  147. newSetting.id = setting.id;
  148. }
  149. setting.name = newSetting.name;
  150. setting.type = newSetting.type;
  151. setting.defaultValue = newSetting.defaultValue;
  152. const savedValue = localStorage.getItem(setting.id);
  153. if (savedValue == null || savedValue == undefined) setting.value = setting.defaultValue;
  154. else setting.value = convertStringToValue(savedValue);
  155. setting.document = createSetting(setting.id, newSetting.name, newSetting.description, newSetting.type, newSetting.typeDescription, (target) => {
  156. let value;
  157. switch (setting.type) {
  158. case SettingTypes.Number:
  159. value = +target.value;
  160. setting.value = value;
  161. newSetting.value = value;
  162. if (value == setting.defaultValue) localStorage.removeItem(setting.id);
  163. else localStorage.setItem(setting.id, value);
  164. break;
  165. case SettingTypes.Text:
  166. value = target.value;
  167. setting.value = value;
  168. newSetting.value = value;
  169. if (value == setting.defaultValue) localStorage.removeItem(setting.id);
  170. else localStorage.setItem(setting.id, value);
  171. break;
  172. case SettingTypes.Boolean:
  173. value = target.checked;
  174. setting.value = value;
  175. newSetting.value = value;
  176. if (value == setting.defaultValue) localStorage.removeItem(setting.id);
  177. else localStorage.setItem(setting.id, value);
  178. break;
  179. }
  180. if (setting.action) setting.action(target);
  181. });
  182. setting.action = newSetting.action;
  183. CustomSettings.Settings.push(setting);
  184. }
  185.  
  186. async function addExSettings() {
  187. const settings = document.querySelector('ul[class="navhideonmobile"]').querySelector('a[href="/controls/settings/"]').parentNode;
  188.  
  189. if (!document.getElementById(nameId)) {
  190. const exSettingsHeader = document.createElement("h3");
  191. exSettingsHeader.id = nameId;
  192. exSettingsHeader.textContent = CustomSettings.Name;
  193. settings.appendChild(exSettingsHeader);
  194. }
  195.  
  196. if (!document.getElementById(providerId)) {
  197. const currExSettings = document.createElement("a");
  198. currExSettings.id = providerId;
  199. currExSettings.textContent = CustomSettings.Provider;
  200. currExSettings.href = "/controls/settings?extension=" + providerId;
  201. currExSettings.style.cursor = "pointer";
  202. settings.appendChild(currExSettings);
  203. }
  204. }
  205.  
  206. async function addExSettingsSidebar() {
  207. const settings = document.getElementById("controlpanelnav");
  208.  
  209. if (!document.getElementById(nameId + "_side")) {
  210. const exSettingsHeader = document.createElement("h3");
  211. exSettingsHeader.id = nameId + "_side";
  212. exSettingsHeader.textContent = CustomSettings.Name;
  213. settings.appendChild(exSettingsHeader);
  214. }
  215.  
  216. if (!document.getElementById(providerId + "_side")) {
  217. const currExSettings = document.createElement("a");
  218. currExSettings.id = providerId + "_side";
  219. currExSettings.textContent = CustomSettings.Provider;
  220. currExSettings.href = "/controls/settings?extension=" + providerId;
  221. currExSettings.style.cursor = "pointer";
  222. settings.appendChild(currExSettings);
  223. }
  224. }
  225.  
  226. async function readSettings() {
  227. for (const setting of CustomSettings.Settings) {
  228. setting.value = readSettingValue(setting);
  229. }
  230. }
  231.  
  232. function readSettingValue(setting) {
  233. const value = localStorage.getItem(setting.id);
  234. if (value == null || value == undefined) return setting.defaultValue;
  235. return convertStringToValue(value);
  236. }
  237.  
  238. async function loadSettings() {
  239. if (!CustomSettings || !CustomSettings.Settings || CustomSettings.Settings.length === 0) return;
  240.  
  241. const columnPage = document.getElementById("columnpage");
  242. const content = columnPage.querySelector('div[class="content"]');
  243.  
  244. for (const section of content.querySelectorAll('section:not([class="exsettings"])')) {
  245. section.parentNode.removeChild(section);
  246. }
  247.  
  248. const section = document.createElement("section");
  249. section.className = "exsettings";
  250. const headerContainer = document.createElement("div");
  251. headerContainer.className = "section-header";
  252. const header = document.createElement("h2");
  253. header.textContent = CustomSettings.HeaderName;
  254. headerContainer.appendChild(header);
  255. section.appendChild(headerContainer);
  256. bodyContainer = document.createElement("div");
  257. bodyContainer.className = "section-body";
  258.  
  259. for (const setting of CustomSettings.Settings) {
  260. const settingElem = setting.document.querySelector(`[id="${setting.id}"]`);
  261. switch (setting.type) {
  262. case SettingTypes.Number:
  263. settingElem.value = setting.value;
  264. break;
  265. case SettingTypes.Boolean:
  266. settingElem.checked = setting.value;
  267. break;
  268. }
  269. bodyContainer.appendChild(setting.document);
  270. }
  271.  
  272. section.appendChild(bodyContainer);
  273. content.appendChild(section);
  274. }
  275.  
  276. function createSetting(id, name, description, type, typeDescription, action) {
  277. const settingContainer = document.createElement("div");
  278. settingContainer.className = "control-panel-item-container";
  279.  
  280. const settingName = document.createElement("div");
  281. settingName.className = "control-panel-item-name";
  282. const settingNameText = document.createElement("h4");
  283. settingNameText.textContent = name;
  284. settingName.appendChild(settingNameText);
  285. settingContainer.appendChild(settingName);
  286.  
  287. const settingDesc = document.createElement("div");
  288. settingDesc.className = "control-panel-item-description";
  289. const settingDescText = document.createTextNode(description);
  290. settingDesc.appendChild(settingDescText);
  291. settingContainer.appendChild(settingDesc);
  292.  
  293. const settingOption = document.createElement("div");
  294. settingOption.className = "control-panel-item-options";
  295.  
  296. switch (type) {
  297. case SettingTypes.Number:
  298. settingOption.appendChild(createSettingNumber(id, action));
  299. break;
  300. case SettingTypes.Boolean:
  301. settingOption.appendChild(createSettingBoolean(id, typeDescription, action));
  302. break;
  303. case SettingTypes.Action:
  304. settingOption.appendChild(createSettingAction(id, typeDescription, action));
  305. break;
  306. case SettingTypes.Text:
  307. settingOption.appendChild(createSettingText(id, action));
  308. break;
  309. }
  310.  
  311. settingContainer.appendChild(settingOption);
  312. return settingContainer;
  313. }
  314.  
  315. function createSettingNumber(id, action) {
  316. const setting = document.createElement("input");
  317. setting.id = id;
  318. setting.type = "text";
  319. setting.className = "textbox";
  320. setting.addEventListener("keydown", (event) => {
  321. const currentValue = parseInt(setting.value) || 0;
  322. if (event.key === "ArrowUp") {
  323. setting.value = (currentValue + 1).toString();
  324. action(setting);
  325. } else if (event.key === "ArrowDown") {
  326. if (currentValue != 0) setting.value = (currentValue - 1).toString();
  327. action(setting);
  328. }
  329. });
  330. setting.addEventListener("input", () => {
  331. setting.value = setting.value.replace(/[^0-9]/g, "");
  332. if (setting.value < 0) setting.value = 0;
  333. });
  334. setting.addEventListener("input", () => action(setting));
  335. return setting;
  336. }
  337.  
  338. function createSettingText(id, action) {
  339. const setting = document.createElement("input");
  340. setting.id = id;
  341. setting.type = "text";
  342. setting.className = "textbox";
  343. setting.addEventListener("keydown", (event) => action(setting));
  344. setting.addEventListener("input", () => action(setting));
  345. return setting;
  346. }
  347.  
  348. function createSettingBoolean(id, typeDescription, action) {
  349. const container = document.createElement("div");
  350. const setting = document.createElement("input");
  351. setting.id = id;
  352. setting.type = "checkbox";
  353. setting.style.cursor = "pointer";
  354. setting.style.marginRight = "4px";
  355. setting.addEventListener("change", () => action(setting));
  356. container.appendChild(setting);
  357. const settingLabel = document.createElement("label");
  358. settingLabel.textContent = typeDescription;
  359. settingLabel.style.cursor = "pointer";
  360. settingLabel.style.userSelect = "none";
  361. settingLabel.addEventListener("click", () => {
  362. setting.checked = !setting.checked;
  363. action(setting);
  364. });
  365. container.appendChild(settingLabel);
  366. return container;
  367. }
  368.  
  369. function createSettingAction(id, typeDescription, action) {
  370. const setting = document.createElement("button");
  371. setting.id = id;
  372. setting.type = "button";
  373. setting.className = "button standard mobile-fix";
  374. setting.textContent = typeDescription;
  375. setting.addEventListener("click", () => action(setting));
  376. return setting;
  377. }
  378.  
  379. function makeIdCompatible(inputString) {
  380. const sanitizedString = inputString
  381. .replace(/[^a-zA-Z0-9-_\.]/g, "-")
  382. .replace(/^-+|-+$/g, "")
  383. .replace(/^-*(?=\d)/, "id-");
  384. return /^[0-9]/.test(sanitizedString) ? "id-" + sanitizedString : sanitizedString;
  385. }
  386.  
  387. function getLocalSettingById(id) {
  388. return CustomSettings.Settings.find((setting) => setting.id === id);
  389. }
  390.  
  391. function convertStringToValue(value) {
  392. if (value === "true" || value === "false") {
  393. return value === "true";
  394. }
  395.  
  396. const parsedNumber = parseFloat(value);
  397. if (!isNaN(parsedNumber)) {
  398. return parsedNumber;
  399. }
  400. return value;
  401. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址