Greasy Fork is available in English.

AnimeBytes Music Artists Helper

AnimeBytes Music Artists Helper Script

// ==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();
})();