Greasy Fork

Greasy Fork is available in English.

Dreadcast Development Kit

13/11/2023 02:55:01

当前为 2024-09-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Dreadcast Development Kit
// @namespace   Violentmonkey Scripts
// @match       https://www.dreadcast.net/Main
// @version     1.0.11
// @author      Pelagia/Isilin
// @description 13/11/2023 02:55:01
// @license     http://creativecommons.org/licenses/by-nc-nd/4.0/
// @connect     docs.google.com
// @connect     googleusercontent.com
// @connect     sheets.googleapis.com
// @connect     raw.githubusercontent.com
// @grant       GM_xmlhttpRequest
// @grant       GM_addStyle
// ==/UserScript==

// TODO add guards in each function to check Game/EDC/Forum
// TODO add function to add deck command
console.log('DDK - Loading ...');

// ===== JQuery utilities =====

$.fn.insertAt = function (index, element) {
  var lastIndex = this.children().size();
  if (index < 0) {
    index = Math.max(0, lastIndex + 1 + index);
  }
  this.append(element);
  if (index < lastIndex) {
    this.children().eq(index).before(this.children().last());
  }
  return this;
};

// ===== Lib =====

const Util = {
  guard: (condition, message) => {
    if (!condition) throw new Error(message);
    return;
  },

  deprecate: (name, replacement) => {
    console.warn(
      name +
        ': this function has been deprecated and should not be used anymore.' +
        (replacement && replacement !== ''
          ? 'Prefer: ' + replacement + '.'
          : ''),
    );
  },

  isArray: (o, optional = false) =>
    $.type(o) === 'array' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isString: (o, optional = false) =>
    $.type(o) === 'string' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isBoolean: (o, optional = false) =>
    $.type(o) === 'boolean' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isNumber: (o, optional = false) =>
    $.type(o) === 'number' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isFunction: (o, optional = false) =>
    $.type(o) === 'function' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isDate: (o, optional = false) =>
    $.type(o) === 'date' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isError: (o, optional = false) =>
    $.type(o) === 'error' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isRegex: (o, optional = false) =>
    $.type(o) === 'regexp' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isObject: (o, optional = false) =>
    $.type(o) === 'object' ||
    (optional && ($.type(o) === 'undefined' || $.type(o) === 'null')),

  isColor: (o, optional = false) => {
    if (optional && ($.type(o) === 'undefined' || $.type(o) === 'null'))
      return true;
    else {
      const colors = ['rouge', 'bleu', 'vert', 'jaune'];
      return (
        $.type(o) === 'string' &&
        (colors.includes(o) ||
          o.match(/^[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3}$/gi))
      );
    }
  },

  isGame: () => window.location.href.includes('https://www.dreadcast.net/Main'),

  isForum: () =>
    window.location.href.includes('https://www.dreadcast.net/Forum'),

  isEDC: () => window.location.href.includes('https://www.dreadcast.net/EDC'),

  isWiki: () => window.location.href.includes('http://wiki.dreadcast.eu/wiki'),

  getContext: () => {
    return Util.isGame()
      ? 'game'
      : Util.isForum()
      ? 'forum'
      : Util.isEDC()
      ? 'edc'
      : 'wiki';
  },
};

// ===== Overwrite DC functions =====

if (Util.isGame() && MenuChat.prototype.originalSend === undefined) {
  MenuChat.prototype.originalSend = MenuChat.prototype.send;
  MenuChat.prototype.sendCallbacks = [];
  MenuChat.prototype.afterSendCallbacks = [];
  MenuChat.prototype.send = function () {
    const $nextFn = () => true;
    const $abortFn = () => false;
    const $message = $('#chatForm .text_chat').val();
    const $res = this.sendCallbacks.every((callback) =>
      callback($message, $nextFn, $abortFn),
    );
    if (!$res) {
      throw new Error('MenuChat.prototype.send: Error on sending message.');
    }

    this.originalSend();

    this.afterSendCallbacks.every((callback) => callback($message));
  };
  MenuChat.prototype.onSend = (callback) => {
    MenuChat.prototype.sendCallbacks.push(callback);
  };
  MenuChat.prototype.onAfterSend = (callback) => {
    MenuChat.prototype.afterSendCallbacks.push(callback);
  };
}

// ============================

const DC = {};

DC.LocalMemory = {
  init: (label, defaultValue) => {
    const $currentVal = GM_getValue(label);
    if ($currentVal === undefined) {
      GM_setValue(label, defaultValue);
      return defaultValue;
    } else {
      return $currentVal;
    }
  },

  set: (label, value) => GM_setValue(label, value),

  get: (label) => GM_getValue(label),

  delete: (label) => GM_deleteValue(label),

  list: () => GM_listValues(),
};

DC.Style = {
  apply: (css) => {
    Util.guard(
      Util.isString(css, true),
      "DC.Style.apply: 'css' parameter should be a string.",
    );

    if (typeof GM_addStyle !== 'undefined') {
      GM_addStyle(css);
    } else {
      let $styleNode = document.createElement('style');
      $styleNode.appendChild(document.createTextNode(css));
      (document.querySelector('head') || document.documentElement).appendChild(
        $styleNode,
      );
    }
  },
};

DC.TopMenu = {
  get: () => {
    return $('.menus');
  },

  add: (element, index = 0) => {
    Util.guard(
      Util.isNumber(index),
      "DC.TopMenu.add: 'index' parameter should be a number.",
    );

    const $dom = DC.TopMenu.get();
    if (index === 0) {
      $dom.prepend(element);
    } else {
      $dom.insertAt(index, element);
    }
  },
};

DC.UI = {
  Separator: () => $('<li class="separator" />'),

  Menu: (label, fn) => {
    Util.guard(
      Util.isString(label),
      "DC.UI.Menu: 'label' parameter should be a string.",
    );
    Util.guard(
      Util.isFunction(fn),
      "DC.UI.Menu: 'fn' parameter should be a function.",
    );

    return $(`<li id="${label}" class="couleur5">${label}</li>`).bind(
      'click',
      fn,
    );
  },

  SubMenu: (label, fn, separatorBefore = false) => {
    Util.guard(
      Util.isString(label),
      "DC.UI.SubMenu: 'label' parameter should be a string.",
    );
    Util.guard(
      Util.isFunction(fn),
      "DC.UI.SubMenu: 'fn' parameter should be a function.",
    );
    Util.guard(
      Util.isBoolean(separatorBefore),
      "DC.UI.SubMenu: 'separatorBefore' parameter should be a boolean.",
    );

    return $(
      `<li class="link couleur2 ${
        separatorBefore ? 'separator' : ''
      }">${label}</li>`,
    ).bind('click', fn);
  },

  DropMenu: (label, submenu) => {
    Util.guard(
      Util.isString(label),
      "DC.UI.DropMenu: 'label' parameter should be a string.",
    );

    const $label = label + '▾';

    const $list = $('<ul></ul>');
    if (!Array.isArray(submenu)) {
      throw new Error("'submenu' should be an array in DC.UI.DropMenu !");
    }
    submenu.forEach(($submenu) => {
      $($list).append($submenu);
    });

    return $(
      `<li id="${label}" class="parametres couleur5 right hover" onclick="$(this).find('ul').slideDown();">${$label}</li>`,
    ).append($list);
  },

  addSubMenuTo: (name, element, index = 0) => {
    Util.guard(
      Util.isString(name),
      "DC.UI.addSubMenuTo: 'name' parameter should be a string.",
    );
    Util.guard(
      Util.isNumber(index),
      "DC.UI.addSubMenuTo: 'index' parameter should be a string.",
    );

    const $menu = $(`.menus li:contains("${name}") ul`);

    if (index === 0) {
      $menu.prepend(element);
    } else {
      $menu.insertAt(index, element);
    }
  },

  TextButton: (id, label, fn) => {
    Util.guard(
      Util.isString(id),
      "DC.UI.TextButton: 'id' parameter should be a string.",
    );
    Util.guard(
      Util.isString(label),
      "DC.UI.TextButton: 'label' parameter should be a string.",
    );
    Util.guard(
      Util.isFunction(fn),
      "DC.UI.TextButton: 'fn' parameter should be a function.",
    );

    return $(`<div id="${id}" class="btnTxt">${label}</div>`).bind('click', fn);
  },

  Button: (id, label, fn) => {
    Util.guard(
      Util.isString(id),
      "DC.UI.Button: 'id' parameter should be a string.",
    );
    Util.guard(
      Util.isString(label),
      "DC.UI.Button: 'label' parameter should be a string.",
    );
    Util.guard(
      Util.isFunction(fn),
      "DC.UI.Button: 'fn' parameter should be a function.",
    );

    return $(
      `<div id="${id}" class="btn add link infoAide"><div class="gridCenter">${label}</div></div>`,
    ).bind('click', fn);
  },

  Tooltip: (text, content) => {
    DC.Style.apply(`
        .tooltip {
          position: relative;
          display: inline-block;
        }
        .tooltip .tooltiptext {
          visibility: hidden;
          background-color: rgba(24,24,24,0.95);
          color: #fff;
          text-align: center;
          padding: 5px;
          border-radius: 6px;
          position: absolute;
          z-index: 1;
          font-size: 1rem;
        }
        .tooltip:hover .tooltiptext {
          visibility: visible;
        }
      `);

    return $(`<div class="tooltip">
        <span class="tooltiptext">${text}</span>
        </div>`).prepend(content);
  },

  Checkbox: (id, defaultEnable = true, onAfterClick) => {
    Util.guard(
      Util.isString(id),
      "DC.UI.Checkbox: 'id' parameter should be a string.",
    );
    Util.guard(
      Util.isBoolean(defaultEnable),
      "DC.UI.Checkbox: 'defaultEnable' parameter should be a boolean.",
    );
    Util.guard(
      Util.isFunction(onAfterClick, true),
      "DC.UI.Checkbox: 'onAfterClick' optional parameter should be a function.",
    );

    DC.Style.apply(`
        .dc_ui_checkbox {
          cursor: pointer;
          width: 30px;
          height: 18px;
          background: url(../../../images/fr/design/boutons/b_0.png) 0 0 no-repeat;
        }

        .dc_ui_checkbox_on {
          background: url(../../../images/fr/design/boutons/b_1.png) 0 0 no-repeat;
        }
      `);

    return $(
      `<div id="${id}" class="dc_ui_checkbox ${
        defaultEnable ? 'dc_ui_checkbox_on' : ''
      }" />`,
    ).bind('click', () => {
      $(`#${id}`).toggleClass('dc_ui_checkbox_on');
      onAfterClick?.($(`#${id}`).hasClass('dc_ui_checkbox_on'));
    });
  },

  PopUp: (id, title, content) => {
    Util.guard(
      Util.isString(id),
      "DC.UI.PopUp: 'id' parameter should be a string.",
    );
    Util.guard(
      Util.isString(title),
      "DC.UI.PopUp: 'title' parameter should be a string.",
    );

    $('#loader').fadeIn('fast');

    const html = `
        <div id="${id}" class="dataBox"  onClick="engine.switchDataBox(this)" style="display: block; z-index: 5; left: 764px; top: 16px;">
          <relative>
            <div class="head" ondblclick="$('#${id}').toggleClass('reduced');">
            <div title="Fermer la fenêtre (Q)" class="info1 link close transition3s" onClick="engine.closeDataBox($(this).parent().parent().parent().attr('id'));" alt="$('${id}').removeClass('active')">
              <i class="fas fa-times"></i>
            </div>
            <div title="Reduire/Agrandir la fenêtre" class="info1 link reduce transition3s" onClick="$('#${id}').toggleClass('reduced');">
              <span>-</span>
            </div>
            <div class="title">${title}</div>
          </div>
          <div class="dbloader"></div>
          <div class="content" style="max-width: 800px; max-height: 600px; overflow-y: auto; overflow-x: hidden;">
          </div>
        </relative>
      </div>`;

    engine.displayDataBox(html);
    $(`#${id} .content`).append(content);

    $('#loader').hide();
  },

  SideMenu: (id, label, content) => {
    Util.guard(
      Util.isString(id),
      "DC.UI.SideMenu: 'id' parameter should be a string.",
    );
    Util.guard(
      Util.isString(label),
      "DC.UI.SideMenu: 'label' parameter should be a string.",
    );
    Util.guard(
      Util.isString(content),
      "DC.UI.SideMenu: 'content' parameter should be a string.",
    );

    const idContainer = id + '_container';
    const idButton = id + '_button';
    const idContent = id + '_content';

    if ($('div#zone_sidemenu').length === 0) {
      $('body').append('<div id="zone_sidemenu"></div>');
    }
    $('#zone_sidemenu').append(
      `<div id="${idContainer}" class="sidemenu_container"></div>`,
    );

    $(`#${idContainer}`).append(
      DC.UI.TextButton(
        idButton,
        '<i class="fas fa-chevron-left"></i>' + label,
        () => {
          const isOpen = $(`#${idButton}`).html().includes('fa-chevron-right');
          if (isOpen) {
            $(`#${idButton}`)
              .empty()
              .append('<i class="fas fa-chevron-left"></i>' + label);
            $(`#${idContainer}`).css('right', '-220px');
          } else {
            $(`#${idButton}`)
              .empty()
              .append('<i class="fas fa-chevron-right"></i>' + label);
            $(`#${idContainer}`).css('right', '0px');
          }
        },
      ),
    );

    $(`#${idContainer}`).append(
      `<div id="${idContent}" class="sidemenu_content">${content}</div>`,
    );

    DC.Style.apply(`
        #zone_sidemenu {
          display: flex;
          flex-direction: column;
          position: absolute;
          right: 0px;
          top: 80px;
          z-index: 999999;
        }

        .sidemenu_container {
          display: flex;
          right: -220px;
        }

        #zone_sidemenu .btnTxt {
          margin: 0 auto;
          min-width: 100px;
          max-width: 100px;
          font-size: 1rem;
          padding: 1%;
          display: grid;
          height: 100%;
          box-sizing: border-box;
          grid-template-columns: 10% 1fr;
          align-items: center;
          text-transform: uppercase;
          font-family: Arial !important;
          line-height: normal !important;
        }

        #zone_sidemenu .btnTxt:hover {
          background: #0b9bcb;
          color: #fff;
        }

        .sidemenu_content {
          background-color: #000;
          color: #fff !important;
          box-shadow: 0 0 15px -5px inset #a2e4fc !important;
          padding: 10px;
          width: 200px;
        }
      `);
  },
};

DC.Network = {
  fetch: (args) => {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest(
        Object.assign({}, args, {
          onload: (e) => resolve(e.response),
          onerror: reject,
          ontimeout: reject,
        }),
      );
    });
  },

  loadSpreadsheet: async (sheetId, tabName, range, apiKey, onLoad) => {
    Util.guard(
      Util.isString(sheetId),
      "DC.Network.loadSpreadsheet: 'sheetId' parameter should be a string.",
    );
    Util.guard(
      Util.isString(tabName),
      "DC.Network.loadSpreadsheet: 'tabName' parameter should be a string.",
    );
    Util.guard(
      Util.isString(range),
      "DC.Network.loadSpreadsheet: 'range' parameter should be a string.",
    );
    Util.guard(
      Util.isString(apiKey),
      "DC.Network.loadSpreadsheet: 'apiKey' parameter should be a string.",
    );
    Util.guard(
      Util.isFunction(onLoad),
      "DC.Network.loadSpreadsheet: 'onLoad' parameter should be a function.",
    );

    const urlGoogleSheetDatabase = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${tabName}!${range}?key=${apiKey}`;
    const result = await DC.Network.fetch({
      method: 'GET',
      url: urlGoogleSheetDatabase,
      headers: {
        'Content-Type': 'application/json',
      },
      responseType: 'json',
    });
    onLoad(result.values);
  },

  loadScript: async (url, onAfterLoad) => {
    Util.guard(
      Util.isString(url),
      "DC.Network.loadScript: 'url' parameter should be a string.",
    );
    Util.guard(
      Util.isFunction(onAfterLoad, true),
      "DC.Network.loadScript: 'onAfterLoad' optional parameter should be a function.",
    );

    // TODO we should check that url is from a valid and secure source.
    const result = await DC.Network.fetch({
      method: 'GET',
      url,
      headers: {
        'Content-Type': 'text/javascript',
      },
    });
    // TODO we have to secure more this call
    eval(result);

    onAfterLoad?.();
  },

  loadJson: async (url) => {
    Util.guard(
      Util.isString(url),
      "DC.Network.loadJson: 'url' parameter should be a string.",
    );

    const result = await DC.Network.fetch({
      method: 'GET',
      url,
      headers: {
        'Content-Type': 'application/json',
      },
      responseType: 'json',
    });
    return result;
  },
};

DC.Chat = {
  sendMessage: (message) => {
    Util.guard(
      Util.isString(message),
      "DC.Chat.sendMessage: 'message' parameter should be a string.",
    );

    $('#chatForm .text_chat').val(message);
    $('#chatForm .text_valider').click();
  },

  t: (message, decoration) => {
    Util.guard(
      Util.isString(message, true),
      "DC.Chat.t: 'message' parameter should be a string.",
    );
    Util.guard(
      Util.isBoolean(decoration.bold, true),
      "DC.Chat.t: 'bold' optional parameter should be a boolean.",
    );
    Util.guard(
      Util.isBoolean(decoration.italic, true),
      "DC.Chat.t: 'italic' optional parameter should be a boolean.",
    );
    Util.guard(
      Util.isColor(decoration.color, true),
      "DC.Chat.t: 'color' optional parameter should be a color string.",
    );

    var prefix = '';
    var suffix = '';

    if (decoration.bold) {
      prefix += '[b]';
      suffix += '[b]';
    }

    if (decoration.italic) {
      prefix += '[i]';
      suffix = '[/i]' + suffix;
    }

    if (decoration.color && decoration.color !== '') {
      prefix += '[c=' + decoration.color + ']';
      suffix = '[/c]' + suffix;
    }

    return prefix + message + suffix;
  },

  addCommand: (label, fn) => {
    Util.guard(
      Util.isString(label),
      "DC.Chat.addCommand: 'label' parameter should be a string.",
    );
    Util.guard(
      Util.isFunction(fn),
      "DC.Chat.addCommand: 'fn' parameter should be a function.",
    );

    nav.getChat().onSend((message, next, abort) => {
      const forbiden = ['me', 'y', 'ye', 'yme', 'w', 'we', 'wme', 'roll', ''];

      const labelUsed = message.split(' ')[0].substr(1);
      if (
        message[0] !== '/' ||
        labelUsed !== label ||
        forbiden.includes(labelUsed)
      ) {
        return next();
      }

      const content = message.substr(labelUsed.length + 1);

      if (fn(labelUsed, content)) {
        return next();
      } else {
        return abort();
      }
    });
  },
};