Greasy Fork

来自缓存

Greasy Fork is available in English.

AnimeBytes Music Artists Helper

AnimeBytes Music Artists Helper Script

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        AnimeBytes Music Artists Helper
// @description AnimeBytes Music Artists Helper Script
// @match       https://animebytes.tv/upload.php
// @grant       none
// @version     1.0.3
// @author      Abyraea
// @namespace   Abyraea
// @license     GNU GPLv3
// #created     2024-12-09
// #updated     2024-12-09
// ==/UserScript==

(function() {
  'use strict';

  // ---------- CONFIG -------------------
  const CONFIG = Object.freeze({
    secondaryCollapsiblesClosed: true,
    textareaMinHeight: '6em',
  });
  // ---------- CONFIG -------------------


  // ---------- ADVANCED CONFIG -------------------
  // DO NOT EDIT UNLESS YOU KNOW WHAT YOU ARE DOING
  const ADVANCED_CONFIG = Object.freeze({
    importances: Object.freeze(['main', 'guest']),
    roles: Object.freeze(['performer', 'composer', 'arranger']),
    splitBy: Object.freeze(['\n', ';']),
    replaceText: Object.freeze({
      'ā': 'aa',
      'ē': 'ee',
      'ī': 'ii',
      'ō': 'ou',
      'ū': 'uu'
    }),
  });
  // ---------- END ADVANCED CONFIG ---------------


  // ---------- INTERNAL CONFIG ---------------------------------
  // DO NOT EDIT UNLESS YOU REALLY REALLY KNOW WHAT YOU ARE DOING
  // ---------- Selectors
  const ELEMENTS_IDS_PREFIX = 'custom-animebytes-artists-helper-';
  const INTERNAL_CONFIG = Object.freeze({
    selectors: Object.freeze({
      groupForm: '#music_form > #group_information',
      formFieldSetsWrapper: '#artist_names_music',
    }),
    ids: Object.freeze({
      reverseNamesCheckbox: `${ELEMENTS_IDS_PREFIX}reverse-names-checkbox`,
    }),
    misc: Object.freeze({
      splitRegex: new RegExp(ADVANCED_CONFIG.splitBy.join('|')),
    }),
    classes: Object.freeze({
      header: 'head colhead_dark strong',
      body: 'box pad',
      fieldSet: 'nameFieldSet',
      fieldSetInput: 'artist_name standard_fields',
      rowBodyCollapsed: `${ELEMENTS_IDS_PREFIX}rowbody-collapsed`,
    }),
    labels: Object.freeze({
      header: 'Artists Helper',
      collapsibleShow: '(show)',
      collapsibleHide: '(hide)',
      reverseNamesCheckbox: 'Reverse Names',
      reverseNamesCheckboxDescription: 'Check this box to reverse two words lines (only newly added)',
    }),
    styles: Object.freeze({
      capitalize: 'text-transform: capitalize;',
      body: 'margin-bottom: 0;',
      textareaWrapper: 'flex-grow: 1; display: flex; flex-direction: column;',
      textarea: `min-height: ${CONFIG.textareaMinHeight};`,
      artistRowBody: 'display: flex; gap: 8px;',
      flexColumn: 'display: flex; flex-direction: column;',
      artistHeaderCollapsibleAction: 'cursor: pointer; text-decoration: underline;',
      rowBodyCollapsed: 'height: 10px; overflow: hidden; visibility: hidden;',
      rowBodyExpanded: '',
    }),
  });
  let PREVIOUS_ARTISTS = [];
  // ---------- END INTERNAL CONFIG -----------------------------


  function init() {
    const groupFormEl = document.querySelector(INTERNAL_CONFIG.selectors.groupForm);
    groupFormEl.prepend(getHeader(), getBody());
  };


  function getHeader() {
    const headerEl = document.createElement('div');
    headerEl.innerText = INTERNAL_CONFIG.labels.header;
    headerEl.className = INTERNAL_CONFIG.classes.header;
    return headerEl;
  }


  function getBody() {
    function getTextarea(importance, role) {
      const textareaWrapperEl = document.createElement('div');
      textareaWrapperEl.style = INTERNAL_CONFIG.styles.textareaWrapper;

      const textareaHeaderEl = document.createElement('span');
      textareaHeaderEl.style = INTERNAL_CONFIG.styles.capitalize;
      textareaHeaderEl.innerText = role;

      const textareaEl = document.createElement('textarea');
      textareaEl.style = INTERNAL_CONFIG.styles.textarea;
      textareaEl.onkeyup = updateArtistsForm;
      textareaEl.id = `${ELEMENTS_IDS_PREFIX}${importance}-${role}`;

      textareaWrapperEl.append(textareaHeaderEl, textareaEl);
      return textareaWrapperEl;
    }

    const bodyEl = document.createElement('dl');
    bodyEl.className = INTERNAL_CONFIG.classes.body;
    bodyEl.style = INTERNAL_CONFIG.styles.body;

    const artistsRowsEls = ADVANCED_CONFIG.importances.flatMap((importance, index) => {
      const rowBodyEl = document.createElement('div');
      rowBodyEl.style = INTERNAL_CONFIG.styles.artistRowBody;
      rowBodyEl.append(...ADVANCED_CONFIG.roles.map(role => getTextarea(importance, role)));
      return getRow(importance, rowBodyEl, index > 0);
    })

    const checkboxRowBodyEl = document.createElement('div');
    const checkboxEl = document.createElement('input');
    checkboxEl.type = 'checkbox';
    checkboxEl.id = INTERNAL_CONFIG.ids.reverseNamesCheckbox;
    const checkboxLabelEl = document.createElement('span');
    checkboxLabelEl.innerText = INTERNAL_CONFIG.labels.reverseNamesCheckboxDescription;
    checkboxRowBodyEl.append(checkboxEl, getSpacingEl(1), checkboxLabelEl);
    const checkBoxRowEls = getRow(INTERNAL_CONFIG.labels.reverseNamesCheckbox, checkboxRowBodyEl);

    bodyEl.append(...checkBoxRowEls, ...artistsRowsEls);

    return bodyEl;
  }


  function getRow(name, bodyEl, collapsible){
    const rowBodyEl = document.createElement('dd');
    const rowBodyCollapsibleEl = document.createElement('div');
    rowBodyCollapsibleEl.append(bodyEl);
    rowBodyEl.append(rowBodyCollapsibleEl);

    const rowHeaderEl = document.createElement('dt');
    rowHeaderEl.innerText = name;
    rowHeaderEl.style = INTERNAL_CONFIG.styles.capitalize;

    if (collapsible) {
      const rowHeaderWrapperEl = document.createElement('div');
      rowHeaderWrapperEl.style = INTERNAL_CONFIG.styles.flexColumn;
      const rowHeaderTitleEl = document.createElement('span');
      rowHeaderTitleEl.innerText = name;
      const rowHeaderActionEl = document.createElement('span');
      rowHeaderActionEl.innerText = CONFIG.secondaryCollapsiblesClosed
        ? INTERNAL_CONFIG.labels.collapsibleShow
        : INTERNAL_CONFIG.labels.collapsibleHide;
      rowHeaderActionEl.onclick = () => {
        const isCollapsed = rowBodyCollapsibleEl.classList.contains(INTERNAL_CONFIG.classes.rowBodyCollapsed);
        rowBodyCollapsibleEl.classList.toggle(INTERNAL_CONFIG.classes.rowBodyCollapsed);
        if (isCollapsed) {
          rowBodyCollapsibleEl.style = INTERNAL_CONFIG.styles.rowBodyExpanded;
          rowHeaderActionEl.innerText = INTERNAL_CONFIG.labels.collapsibleHide;
        } else {
          rowBodyCollapsibleEl.style = INTERNAL_CONFIG.styles.rowBodyCollapsed;
          rowHeaderActionEl.innerText = INTERNAL_CONFIG.labels.collapsibleShow;
        }
      };
      rowHeaderActionEl.style = INTERNAL_CONFIG.styles.artistHeaderCollapsibleAction;
      rowHeaderWrapperEl.append(rowHeaderTitleEl, rowHeaderActionEl);
      rowHeaderEl.innerHTML = '';
      rowHeaderEl.append(rowHeaderWrapperEl);

      rowBodyCollapsibleEl.style = CONFIG.secondaryCollapsiblesClosed
        ? INTERNAL_CONFIG.styles.rowBodyCollapsed
        : INTERNAL_CONFIG.styles.rowBodyExpanded;
      rowBodyCollapsibleEl.className = CONFIG.secondaryCollapsiblesClosed
        ? INTERNAL_CONFIG.classes.rowBodyCollapsed
        : '';
    }

    return [rowHeaderEl, rowBodyEl];
  }


  function updateArtistsForm(){
    const formFieldSetsWrapperEl = document.querySelector(INTERNAL_CONFIG.selectors.formFieldSetsWrapper);

    const reverseNames = document.querySelector(`#${INTERNAL_CONFIG.ids.reverseNamesCheckbox}`).checked;

    const artists = ADVANCED_CONFIG.importances.flatMap((importance) => {
      return ADVANCED_CONFIG.roles.flatMap((role) => {
        const textareaValue = document.querySelector(`#${ELEMENTS_IDS_PREFIX}${importance}-${role}`).value;
        const splittedValues = textareaValue.split(INTERNAL_CONFIG.misc.splitRegex);
        return [...new Set(splittedValues)].filter((name) => !!name).map((name) => ({
          name: name.trim(),
          importance,
          role,
        }));
      });
    });

    PREVIOUS_ARTISTS = PREVIOUS_ARTISTS.filter(({ name }) => !artists.includes(name));
    const formattedArtists = artists.map((artist) => formatArtist(artist, reverseNames));
    PREVIOUS_ARTISTS = formattedArtists;

    const formFieldSetsEls = artists.length === 0
      ? [getFormFieldSet('', ADVANCED_CONFIG.importances[0], ADVANCED_CONFIG.roles[0], 0)]
      : formattedArtists.map(({ formattedName, importance, role }, index) => getFormFieldSet(formattedName, importance, role, index));

    formFieldSetsWrapperEl.innerHTML = '';
    formFieldSetsWrapperEl.append(...formFieldSetsEls);
  }


  function getFormFieldSet(name, importance, role, index) {
    function getSelect(field, options, value){
      const selectEl = document.createElement('select');
      selectEl.className = `artist_${field}`;
      selectEl.name = `artist_${field}[]`;
      const optionsEls = options.map((option) => {
        const optionEl = document.createElement('option');
        optionEl.value = option;
        optionEl.innerText = capitalizeText(option);
        return optionEl;
      });
      selectEl.append(...optionsEls);
      selectEl.value = value;
      return selectEl;
    }

    const fieldSetEl = document.createElement('div');
    fieldSetEl.className = INTERNAL_CONFIG.classes.fieldSet;

    const inputEl = document.createElement('input');
    inputEl.type = 'text';
    inputEl.value = name;
    inputEl.className = INTERNAL_CONFIG.classes.fieldSetInput;
    inputEl.id = `artist_music_${index}`;
    inputEl.name = 'artist_name[]';
    inputEl.size = '35';

    const importanceSelect = getSelect('importance', ADVANCED_CONFIG.importances, importance);
    const roleSelect = getSelect('role', ADVANCED_CONFIG.roles, role);
    fieldSetEl.append(inputEl, getSpacingEl(1), importanceSelect, getSpacingEl(4), roleSelect);

    return fieldSetEl;
  }


  function formatArtist(artist, reverse){
    const formattedName = Object.entries(ADVANCED_CONFIG.replaceText).reduce((text, [search, replace]) => {
      return text.replace(search, replace);
    }, artist.name);
    const splittedName = formattedName.split(' ');

    const previousArtist = PREVIOUS_ARTISTS.find(({name, importance, role}) => artist.name === name && artist.importance === importance && artist.role === role);

    const formattedArtist = {
      ...artist,
      formattedName: previousArtist?.formattedName ?? formattedName,
      reverse: previousArtist?.reverse ?? false,
    };

    if (!reverse || previousArtist?.reverse === false || splittedName.length !== 2) {
      return formattedArtist;
    }

    formattedArtist.formattedName = splittedName.reverse().join(' ');
    formattedArtist.reverse = true;
    return formattedArtist;
  }


  function getSpacingEl(spaces){
    const spacingEl = document.createElement('span');
    spacingEl.innerHTML = ' '.repeat(spaces || 1);
    return spacingEl;
  }


  function capitalizeText(text){
    return `${text.substring(0, 1).toUpperCase()}${text.substring(1)}`;
  }


  init();
})();