// ==UserScript==
// @name RYM Descriptor Organizer
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Organize descriptors automatically on RateYourMusic
// @author https://greasyfork.org/users/1320826-polachek
// @match *://rateyourmusic.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const categories = {
Lyrics: [
'theme', 'abstract', 'alienation', 'conscious', 'crime', 'death', 'suicide',
'drugs', 'alcohol', 'educational', 'fantasy', 'folklore', 'hedonistic',
'history', 'holiday', 'Christmas', 'Halloween', 'ideology', 'anti-religious',
'pagan', 'political', 'anarchism', 'nationalism', 'propaganda', 'protest',
'religious', 'Christian', 'Islamic', 'satanic', 'introspective', 'LGBT',
'love', 'breakup', 'misanthropic', 'mythology', 'nature', 'occult',
'paranormal', 'patriotic', 'philosophical', 'existential', 'nihilistic',
'science fiction', 'self-hatred', 'sexual', 'sports', 'violence', 'war',
'tone', 'apathetic', 'boastful', 'cryptic', 'deadpan', 'hateful',
'humorous', 'optimistic', 'pessimistic', 'poetic', 'rebellious',
'sarcastic', 'satirical', 'serious', 'vulgar'
],
Atmosphere: [
'apocalyptic', 'cold', 'dark', 'funereal', 'infernal', 'ominous', 'scary',
'epic', 'ethereal', 'futuristic', 'hypnotic', 'martial', 'mechanical',
'medieval', 'mysterious', 'natural', 'aquatic', 'desert', 'forest',
'rain', 'tropical', 'nocturnal', 'party', 'pastoral', 'peaceful',
'psychedelic', 'ritualistic', 'seasonal', 'autumn', 'spring', 'summer',
'winter', 'space', 'spiritual', 'surreal', 'suspenseful', 'tribal',
'urban', 'warm'
],
Mood: [
'angry', 'aggressive', 'anxious', 'bittersweet', 'calm', 'meditative',
'disturbing', 'energetic', 'manic', 'happy', 'playful', 'lethargic',
'longing', 'mellow', 'soothing', 'passionate', 'quirky', 'romantic',
'sad', 'depressive', 'lonely', 'melancholic', 'sombre', 'sensual',
'sentimental', 'uplifting', 'triumphant'
],
Style: [
'anthemic', 'atmospheric', 'atonal', 'avant-garde', 'chaotic', 'complex',
'dense', 'dissonant', 'eclectic', 'heavy', 'lush', 'melodic', 'microtonal',
'minimalistic', 'noisy', 'polyphonic', 'progressive', 'raw', 'repetitive',
'rhythmic', 'soft', 'sparse', 'technical'
],
Form: [
'ballad', 'carol', "children's music", 'fairy tale', 'lullaby',
'nursery rhyme', 'concept album', 'rock opera', 'concerto', 'ensemble',
'a cappella', 'acoustic', 'androgynous vocals', 'chamber music',
'string quartet', 'choral', 'female vocalist', 'instrumental',
'male vocalist', 'non-binary vocalist', 'orchestral', 'vocal group',
'hymn', 'jingle', 'madrigal', 'mashup', 'medley', 'monologue',
'novelty', 'opera', 'oratorio', 'parody', 'poem', 'section',
'interlude', 'intro', 'movement', 'outro', 'reprise', 'silence',
'skit', 'sonata', 'stem', 'suite', 'symphony', 'tone poem', 'waltz'
],
Technique: [
'composition', 'aleatory', 'generative music', 'improvisation',
'uncommon time signatures', 'production', 'lobit', 'lo-fi', 'sampling',
'Wall of Sound'
]
};
function capitalizeDescriptor(descriptor) {
if (descriptor.toLowerCase() === "lgbt") {
return "LGBT";
}
return descriptor.toLowerCase().replace(/\b\w/g, char => char.toUpperCase());
}
function getReleaseType() {
const typeRow = Array.from(document.querySelectorAll('tr'))
.find(row => row.querySelector('th')?.innerText.trim() === 'Type');
return typeRow ? typeRow.querySelector('td').innerText.trim().toLowerCase() : null;
}
function formatDescriptor(descriptor) {
return descriptor.toLowerCase().replace(/\s+/g, '-');
}
function organizeDescriptors() {
const descriptorsContainer = document.querySelector('.release_pri_descriptors');
if (!descriptorsContainer) return;
const descriptors = descriptorsContainer.innerText.split(', ').map(d => d.trim());
const organized = {};
for (const category in categories) {
organized[category] = descriptors.filter(desc => categories[category].includes(desc))
.map(desc => capitalizeDescriptor(desc));
}
const releaseType = getReleaseType();
const isAlbum = releaseType === 'album';
const isSingle = releaseType === 'single';
const isMixtape = releaseType === 'mixtape';
const isEP = releaseType === 'ep';
const isDJMix = releaseType === 'dj mix';
const isMusicVideo = releaseType === 'music video';
const isVideo = releaseType === 'video';
const isCompilation = releaseType === 'compilation';
const isBootleg = releaseType === 'bootleg / unauthorized';
const isAdditionalRelease = releaseType === 'additional release';
const typePrefix = isAlbum ? 'album'
: isSingle ? 'single'
: isMixtape ? 'mixtape'
: isEP ? 'ep'
: isDJMix ? 'djmix'
: isMusicVideo ? 'musicvideo'
: isVideo ? 'video'
: isCompilation ? 'comp'
: isBootleg ? 'unauth'
: isAdditionalRelease ? 'additional'
: null;
const newContainer = document.createElement('div');
newContainer.style.marginTop = '20px';
newContainer.style.fontSize = '0.9em';
const flexContainer = document.createElement('div');
flexContainer.style.marginTop = '10px';
flexContainer.style.marginBottom = '10px';
flexContainer.style.display = 'flex';
flexContainer.style.flexWrap = 'wrap';
flexContainer.style.gap = '20px';
flexContainer.style.fontSize = '0.9em';
for (const category in organized) {
if (organized[category].length > 0) {
const categoryDiv = document.createElement('div');
categoryDiv.style.flex = '1';
categoryDiv.style.minWidth = '120px';
const categoryTitle = document.createElement('p');
categoryTitle.innerText = category;
categoryTitle.style.fontWeight = 'bold';
categoryDiv.appendChild(categoryTitle);
const categoryList = document.createElement('ul');
categoryList.style.listStyleType = 'none';
organized[category].forEach(desc => {
const listItem = document.createElement('li');
const link = document.createElement('a');
const formattedDesc = formatDescriptor(desc);
link.href = `https://rateyourmusic.com/charts/top/${typePrefix}/all-time/d:${formattedDesc}`;
link.target = '_blank';
link.innerText = desc;
listItem.style.marginTop = '10px';
listItem.appendChild(link);
categoryList.appendChild(listItem);
});
categoryDiv.appendChild(categoryList);
flexContainer.appendChild(categoryDiv);
}
}
descriptorsContainer.parentNode.insertBefore(flexContainer, descriptorsContainer.nextSibling);
descriptorsContainer.style.display = 'none';
}
organizeDescriptors();
})();