Dreadcast Script Manager

Centralize all dreadcast scripts in one single source, integrated to the game.

当前为 2025-01-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Dreadcast Script Manager
  3. // @namespace Dreadcast
  4. // @match https://www.dreadcast.net/Main
  5. // @match https://www.dreadcast.net/Forum
  6. // @match https://www.dreadcast.net/Forum/*
  7. // @match https://www.dreadcast.net/EDC
  8. // @match https://www.dreadcast.net/EDC/*
  9. // @version 1.3.1
  10. // @author Pelagia/Isilin
  11. // @description Centralize all dreadcast scripts in one single source, integrated to the game.
  12. // @license https://github.com/Isilin/dreadcast-scripts?tab=GPL-3.0-1-ov-file
  13. // @require https://update.gf.qytechs.cn/scripts/507382/Dreadcast%20Development%20Kit.user.js
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_deleteValue
  17. // @grant GM_listValues
  18. // @grant GM_xmlhttpRequest
  19. // @grant GM_addStyle
  20. // @connect update.gf.qytechs.cn
  21. // @connect docs.google.com
  22. // @connect googleusercontent.com
  23. // @connect sheets.googleapis.com
  24. // @connect raw.githubusercontent.com
  25. // ==/UserScript==
  26.  
  27. // TODO remplacer petit à petit les scripts par les versions locales nettoyées.
  28. // TODO use a recent jquery with noConflict
  29. // TODO Reset button in Com'Back reset all the settings from DCSM (including scripts then).
  30.  
  31. $(() => {
  32. // To check if a script is used in a DSM context.
  33. Util.isDSM = () => true;
  34.  
  35. const LIST_TAG = 'dcsm_list';
  36. const ALL_DISABLED_TAG = 'dcsm_all_disabled';
  37. const INTRO_TAG = 'dcsm_intro_disabled';
  38. const DEV_MODE_TAG = 'dcsm_dev_mode';
  39.  
  40. let settings, allDisabled, introDisabled, devMode;
  41. let newSettings, newAllDisabled, newDevMode;
  42.  
  43. // ===== CORE =====
  44.  
  45. const initPersistence = () => {
  46. // Init persistent memory if needed.
  47. DC.LocalMemory.init(LIST_TAG, {});
  48. DC.LocalMemory.init(ALL_DISABLED_TAG, false);
  49. DC.LocalMemory.init(INTRO_TAG, false);
  50. DC.LocalMemory.init(DEV_MODE_TAG, false);
  51.  
  52. // TODO to delete at next major version.
  53. if (DC.LocalMemory.get('dcm_list') !== undefined) {
  54. DC.LocalMemory.set(LIST_TAG, DC.LocalMemory.get('dcm_list'));
  55. DC.LocalMemory.delete('dcm_list');
  56. }
  57. if (DC.LocalMemory.get('dcm_all_disabled') !== undefined) {
  58. DC.LocalMemory.set(
  59. ALL_DISABLED_TAG,
  60. DC.LocalMemory.get('dcm_all_disabled'),
  61. );
  62. DC.LocalMemory.delete('dcm_all_disabled');
  63. }
  64.  
  65. // Load the current settings.
  66. settings = DC.LocalMemory.get(LIST_TAG);
  67. allDisabled = DC.LocalMemory.get(ALL_DISABLED_TAG);
  68. introDisabled = DC.LocalMemory.get(INTRO_TAG);
  69. devMode = DC.LocalMemory.get(DEV_MODE_TAG);
  70. };
  71.  
  72. const synchronizeSettings = (settings, scripts) => {
  73. let tmp = settings;
  74.  
  75. scripts.forEach((script) => {
  76. if (!Object.hasOwn(tmp, script.id)) {
  77. // Update the settings, if there is new scripts.
  78. tmp[script.id] = false;
  79. }
  80. });
  81.  
  82. // Remove in settings, scripts that doesn't exist anymore.
  83. tmp = Object.keys(tmp)
  84. .filter(
  85. (key) => scripts.find((script) => script.id === key) !== undefined,
  86. )
  87. .reduce((obj, key) => {
  88. obj[key] = tmp[key];
  89. return obj;
  90. }, {});
  91.  
  92. // Save the new settings in persistent memory.
  93. DC.LocalMemory.set(LIST_TAG, tmp);
  94.  
  95. return tmp;
  96. };
  97.  
  98. const createScriptLine = (script, index) => {
  99. const line = $(`
  100. <tr style="border-top: 1px solid white; border-left: 1px solid white; border-right: 1px solid white;">
  101. <td style="padding: 5px 0 0 5px" rowspan="2">${index}</td>
  102. ${
  103. script.icon && script.icon !== ''
  104. ? `<td style="padding: 5px" rowspan="2"><img src="${script.icon}" width="48" height="48" /></td>`
  105. : '<td class="short" style="width: 58px;" rowspan="2" />'
  106. }
  107. <td style="padding: 5px 0; min-width: 120px; text-align: left;">${
  108. script.experimental ? '<span style="color: red;">[DEV]</span>' : ''
  109. } ${script.name || ''}</td>
  110. <td style="padding: 5px 0; min-width: 120px; text-align: left;"><small>${
  111. script.authors || ''
  112. }</small></td>
  113. <td class="enabled_cell" style="padding: 5px 0; display: flex; justify-content: center;"></td>
  114. <td class="setting_cell" style="padding: 5px 5px 0 0;"></td>
  115. <td class="doc_cell" style="padding: 5px 5px 0 0;"></td>
  116. <td class="rp_cell" style="padding: 5px 5px 0 0;"></td>
  117. <td class="contact_cell" style="padding: 5px 5px 0 0;"></td>
  118. </tr>
  119. <tr style="border-bottom: 1px solid white; border-left: 1px solid white; border-right: 1px solid white;">
  120. <td colspan="7" style="padding: 0 5px 5px 5px; text-align: left;"><small><em class="couleur5">${
  121. script.description || ''
  122. }</em></small></td>
  123. </tr>
  124. `);
  125. $('.enabled_cell', line).append(
  126. DC.UI.Tooltip(
  127. 'Activer/Désactiver le script ne perdra pas sa configuration.',
  128. DC.UI.Checkbox(
  129. `${script.id}_check`,
  130. newSettings[script.id],
  131. () => (newSettings[script.id] = !newSettings[script.id]),
  132. ),
  133. ),
  134. );
  135. if (script.settings) {
  136. $('.setting_cell', line).append(
  137. DC.UI.Tooltip(
  138. 'Settings',
  139. DC.UI.Button(
  140. `${script.id}_setting`,
  141. '<i class="fas fa-cog"></i>',
  142. () => {},
  143. ),
  144. ),
  145. );
  146. }
  147. if (script.doc && script.doc !== '') {
  148. $('.doc_cell', line).append(
  149. DC.UI.Tooltip(
  150. 'Documentation',
  151. DC.UI.Button(`${script.id}_doc`, '<i class="fas fa-book"></i>', () =>
  152. window.open(script.doc, '_blank'),
  153. ),
  154. ),
  155. );
  156. }
  157. if (script.rp && script.rp !== '') {
  158. $('.rp_cell', line).append(
  159. DC.UI.Tooltip(
  160. 'Topic RP',
  161. DC.UI.Button(
  162. `${script.id}_rp`,
  163. '<div class=""gridCenter>RP</div>',
  164. () => window.open(script.doc, '_blank'),
  165. ),
  166. ),
  167. );
  168. }
  169. if (script.contact && script.contact !== '') {
  170. $('.contact_cell', line).append(
  171. DC.UI.Tooltip(
  172. 'Contact',
  173. DC.UI.Button(
  174. `${script.id}_rp`,
  175. '<i class="fas fa-envelope"></i>',
  176. () => nav.getMessagerie().newMessage(script.contact),
  177. ),
  178. ),
  179. );
  180. }
  181.  
  182. return line;
  183. };
  184.  
  185. const createIntro = () => {
  186. if (Util.isGame() && !introDisabled) {
  187. introDisabled = true;
  188. DC.LocalMemory.set(INTRO_TAG, introDisabled);
  189. DC.UI.PopUp(
  190. 'dcsm_intro',
  191. 'Bienvenue sur le Dreadcast Script Manager !',
  192. $(`
  193. <div style="color: white;">
  194. <h3>Merci d'avoir installé le DreaCast Script Manager (DCSM).</h3><br />
  195. <p>Cet utilitaire va vous permettre de gérer vos scripts directement en jeu. Pensez à désactiver/désinstaller dans votre GreaseMonkey/TamperMonkey (ou équivalent), les scripts que vous activerez dans le DCSM, pour éviter des doublons.</p><br />
  196. <p>La suite se passe dans Paramètres > Scripts & Skins.</p><br />
  197. <p>Vous pourrez obtenir des réponses à vos questions sur le <a href="https://github.com/Isilin/dreadcast-scripts/wiki">wiki</a>, sur le forum, ou en me contactant directement par Com' HRP : (<em>JD Pelagia</em>).</p><br />
  198. <p>Bon jeu ! (Vous ne verrez plus cette fenêtre d'information par la suite).</p>
  199. </div>
  200. `),
  201. );
  202. }
  203. };
  204.  
  205. const createUI = (scripts, settings) => {
  206. DC.UI.addSubMenuTo(
  207. 'Paramètres ▾',
  208. DC.UI.SubMenu(
  209. 'Scripts & Skins',
  210. () => {
  211. // On récupère une config temporaire qu'on appliquera uniquement si sauvegardée.
  212. newSettings = settings;
  213. newAllDisabled = allDisabled;
  214. newDevMode = devMode;
  215.  
  216. const sections = [
  217. { id: 'all', label: 'Tous' },
  218. { id: 'game', label: 'Jeu' },
  219. { id: 'forum', label: 'Forum' },
  220. { id: 'edc', label: 'EDC' },
  221. ];
  222.  
  223. const categories = [
  224. { id: 'all', label: 'Tous' },
  225. { id: 'mailing', label: 'Messagerie' },
  226. { id: 'chat', label: 'Chat' },
  227. { id: 'silhouette', label: 'Silhouette' },
  228. { id: 'ui', label: 'UI' },
  229. { id: 'mech', label: 'Mécaniques' },
  230. { id: 'fix', label: 'Correctifs' },
  231. { id: 'misc', label: 'Autres' },
  232. ];
  233.  
  234. const content = $(`<div style="color: white;">
  235. <div id="developper_mode_switch" style="display: flex; justify-content: flex-begin;gap: 1rem;margin-bottom: 1rem;">
  236. <p>Mode développeur</p>
  237. </div>
  238. <div style="display: flex; justify-content: space-between">
  239. <div id="scripts_all_switch" style="display: flex;gap: 1rem;margin-bottom: 1rem;">
  240. <p>Tout désactiver</p>
  241. </div>
  242. <div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
  243. <label for="search_script">Recherche</label>
  244. <input id="search_script" name="search_script" type="text" size="50" style="color: white;" />
  245. </div>
  246. </div>
  247. <div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
  248. <legend style="margin-right: 1rem; min-width: 60px;">Filtrer :</legend>
  249. <div style="display: flex; gap: 5%; flex-wrap: wrap; width: 100%;">
  250. ${sections
  251. .map(
  252. (section, index) => `
  253. <div>
  254. <input type="radio" id="${
  255. section.id
  256. }_section" name ="section" value ="${section.id}" ${
  257. index === 0 ? 'checked' : ''
  258. } />
  259. <label for="${section.id}_section">${section.label}</label>
  260. </div>
  261. `,
  262. )
  263. .join('')}
  264. </div>
  265. </div>
  266. <div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
  267. <legend style="margin-right: 1rem; min-width: 60px;">Filtrer :</legend>
  268. <div style="display: flex; gap: 5%; flex-wrap: wrap; width: 100%;">
  269. ${categories
  270. .map(
  271. (category, index) => `
  272. <div>
  273. <input type="radio" id="${
  274. category.id
  275. }_category" name ="category" value ="${category.id}" ${
  276. index === 0 ? 'checked' : ''
  277. } />
  278. <label for="${category.id}_category">${
  279. category.label
  280. }</label>
  281. </div>
  282. `,
  283. )
  284. .join('')}
  285. </div>
  286. </div>
  287. <div style="overflow-y: scroll; overflow-x: hidden; max-height: 350px;">
  288. <table style="border-collapse: collapse; width: 100%; border: 1px solid white; padding: 5px; font-size: 15px; text-align: center;">
  289. <thead>
  290. <th style="padding: 5px 0 5px 5px" scope="col">#</th>
  291. <th class="short" style="width:58px;" />
  292. <th style="padding: 5px 0 5px 0" scope="col">Nom</th>
  293. <th style="padding: 5px 0 5px 0" scope="col">Auteurs</th>
  294. <th style="padding: 5px 0 5px 0" scope="col">Actif</th>
  295. <th class="short" style="width: 40px;" />
  296. <th class="short" style="width: 40px;" />
  297. <th class="short" style="width: 40px;" />
  298. <th class="short" style="width: 40px;" />
  299. </thead>
  300. <tbody></tbody>
  301. </table>
  302. </div>
  303. </div>`);
  304.  
  305. $(document).on('change', "input[name='category']", (e) => {
  306. const category = e.target.value;
  307. const section = $("input[name='section']:checked").val();
  308. const search = $("input[name='search_script']").val().toLowerCase();
  309.  
  310. // Empty the table
  311. $('tbody', content).empty();
  312. // Add filtered lines
  313. scripts
  314. .filter(
  315. (script) =>
  316. (script.section.includes(section) || section === 'all') &&
  317. (script.category.includes(category) || category === 'all') &&
  318. (script.name.toLowerCase().includes(search) ||
  319. script.description.toLowerCase().includes(search)),
  320. )
  321. .forEach((script, index) => {
  322. const line = createScriptLine(script, index);
  323. $('tbody', content).append(line);
  324. });
  325. });
  326.  
  327. $(document).on('change', "input[name='section']", (e) => {
  328. const section = e.target.value;
  329. const category = $("input[name='category']:checked").val();
  330. const search = $("input[name='search_script']").val().toLowerCase();
  331.  
  332. // Empty the table
  333. $('tbody', content).empty();
  334. // Add filtered lines
  335. scripts
  336. .filter(
  337. (script) =>
  338. (script.section.includes(section) || section === 'all') &&
  339. (script.category.includes(category) || category === 'all') &&
  340. (script.name.toLowerCase().includes(search) ||
  341. script.description.toLowerCase().includes(search)),
  342. )
  343. .forEach((script, index) => {
  344. const line = createScriptLine(script, index);
  345. $('tbody', content).append(line);
  346. });
  347. });
  348.  
  349. $(document).on('input', "input[name='search_script']", (e) => {
  350. const search = e.target.value.toLowerCase();
  351. const category = $("input[name='category']:checked").val();
  352. const section = $("input[name='section']:checked").val();
  353.  
  354. // Empty the table
  355. $('tbody', content).empty();
  356. // Add filtered lines
  357. scripts
  358. .filter(
  359. (script) =>
  360. (script.section.includes(section) || section === 'all') &&
  361. (script.category.includes(category) || category === 'all') &&
  362. (script.name.toLowerCase().includes(search) ||
  363. script.description.toLowerCase().includes(search)),
  364. )
  365. .forEach((script, index) => {
  366. const line = createScriptLine(script, index);
  367. $('tbody', content).append(line);
  368. });
  369. });
  370.  
  371. // Sauvegarder les paramètres.
  372. content.append(
  373. DC.UI.TextButton('scripts_refresh', 'Sauvegarder', () => {
  374. settings = newSettings;
  375. allDisabled = newAllDisabled;
  376. devMode = newDevMode;
  377. DC.LocalMemory.set(LIST_TAG, settings);
  378. DC.LocalMemory.set(ALL_DISABLED_TAG, allDisabled);
  379. DC.LocalMemory.set(DEV_MODE_TAG, devMode);
  380. location.replace('https://www.dreadcast.net/Main'); // Better than reload() with Chrome.
  381. }),
  382. );
  383. content.append(
  384. $(
  385. `<p><em class="couleur5">⚠ Sauvegarder votre configuration va raffraichir la page.<br />
  386. Pensez à sauvegarder votre travail en cours avant.</em></p>`,
  387. ),
  388. );
  389.  
  390. const resetConfig = () => {
  391. const list = DC.LocalMemory.list();
  392. list.forEach((key) => {
  393. DC.LocalMemory.delete(key);
  394. });
  395. };
  396.  
  397. // Import/Export
  398. content.append(
  399. $(
  400. '<div id="config_buttons" style="display: flex; justify-content: end; gap: 1rem; margin-bottom: 1rem;"></div>',
  401. ),
  402. );
  403. $('#config_buttons', content).append(
  404. DC.UI.TextButton(
  405. 'config_reset',
  406. '<i class="fas fa-undo"></i> Réinitialiser',
  407. () => {
  408. resetConfig();
  409. location.replace('https://www.dreadcast.net/Main');
  410. },
  411. ),
  412. );
  413. $('#config_buttons', content).append(
  414. DC.UI.TextButton(
  415. 'config_import',
  416. '<i class="fas fa-upload"></i> Importer la configuration',
  417. () => {
  418. resetConfig();
  419.  
  420. const anchor = document.createElement('input');
  421. anchor.style.display = 'none';
  422. anchor.type = 'file';
  423. anchor.accept = 'application.json';
  424. anchor.onchange = (e) => {
  425. var reader = new FileReader();
  426. reader.onload = (e) => {
  427. const data = JSON.parse(e.target.result);
  428. Object.keys(data).forEach((key) => {
  429. DC.LocalMemory.set(key, data[key]);
  430. });
  431. };
  432. reader.readAsText(e.target.files[0]);
  433. document.body.removeChild(anchor);
  434. location.replace('https://www.dreadcast.net/Main');
  435. };
  436. document.body.appendChild(anchor);
  437. anchor.click();
  438. },
  439. ),
  440. );
  441. $('#config_buttons', content).append(
  442. DC.UI.TextButton(
  443. 'config_export',
  444. '<i class="fas fa-download"></i> Exporter la configuration',
  445. function () {
  446. const list = DC.LocalMemory.list();
  447. let data = {};
  448. list.forEach((key) => {
  449. data[key] = DC.LocalMemory.get(key);
  450. });
  451.  
  452. const anchor = document.createElement('a');
  453. anchor.style.display = 'none';
  454. anchor.href = URL.createObjectURL(
  455. new Blob([JSON.stringify(data)], {
  456. type: 'application/json',
  457. }),
  458. );
  459. anchor.download = 'dcsm_config.json';
  460. document.body.appendChild(anchor);
  461. anchor.click();
  462. document.body.removeChild(anchor);
  463. },
  464. ),
  465. );
  466.  
  467. // Switch button pour désactiver tous les scripts.
  468. $('#scripts_all_switch', content).append(
  469. DC.UI.Checkbox(
  470. 'scripts_all_check',
  471. newAllDisabled,
  472. () => (newAllDisabled = !newAllDisabled),
  473. ),
  474. );
  475.  
  476. // Switch button pour le développeur mode.
  477. $('#developper_mode_switch', content).append(
  478. DC.UI.Tooltip(
  479. 'Attention, ces scripts sont encore en développement !',
  480. DC.UI.Checkbox(
  481. 'developper_mode_check',
  482. newDevMode,
  483. () => (newDevMode = !newDevMode),
  484. ),
  485. ),
  486. );
  487.  
  488. scripts
  489. .filter((script) => devMode || !script.experimental)
  490. .forEach((script, index) => {
  491. const line = createScriptLine(script, index);
  492. $('tbody', content).append(line);
  493. });
  494.  
  495. return DC.UI.PopUp('scripts_modal', 'Scripts & Skins', content);
  496. },
  497. true,
  498. ),
  499. 5,
  500. );
  501. };
  502.  
  503. // ===============
  504. $(document).ready(() => {
  505. initPersistence();
  506. createIntro();
  507.  
  508. // Load list of scripts
  509. DC.Network.loadJson(
  510. 'https://raw.githubusercontent.com/Isilin/dreadcast-scripts/main/data/scripts.json',
  511. )
  512. .then((scripts) => {
  513. settings = synchronizeSettings(settings, scripts);
  514.  
  515. // Create the interface.
  516. if (Util.isGame()) {
  517. createUI(scripts, settings);
  518. }
  519.  
  520. // Load the scripts
  521. if (!allDisabled) {
  522. const context = Util.getContext();
  523.  
  524. scripts
  525. .filter((script) => script.section.includes(context))
  526. .filter((script) => devMode || !script.experimental)
  527. .forEach((script) => {
  528. if (settings[script.id]) {
  529. DC.Network.loadScript(script.url)
  530. .then(() => {
  531. console.info(
  532. `DCSM - '${script.name}' script has been loaded successfully.`,
  533. );
  534. })
  535. .catch((err) => {
  536. console.error(
  537. `DCSM - Error loading '${script.name}' script: ` + err,
  538. );
  539. });
  540. }
  541. });
  542. }
  543. })
  544. .catch((err) => {
  545. console.error('DCSM - Error loading the list of scripts :' + err);
  546. });
  547. });
  548. });

QingJ © 2025

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