Furaffinity-Custom-Settings

Helper Script to create Custom settings on Furaffinitiy

目前为 2023-09-12 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/475041/1249288/Furaffinity-Custom-Settings.js

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

QingJ © 2025

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