您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Restyles and adds new functionality to Soundgasm --- dark mode/keyboard shortcuts/quick download/and more
// ==UserScript== // @name Soundgasm Improvements // @namespace V.L // @version 1.0 // @description Restyles and adds new functionality to Soundgasm --- dark mode/keyboard shortcuts/quick download/and more // @author Valerio Lyndon // @homepageURL https://github.com/ValerioLyndon/Soundgasm-Improvements // @supportURL https://github.com/ValerioLyndon/Soundgasm-Improvements/issues // @license GPL-3.0-only // @match https://soundgasm.net/* // @run-at document-start // @grant GM_getValue // @grant GM_setValue // ==/UserScript== 'use strict'; document.addEventListener("DOMContentLoaded", domLoaded); // Dark or Light mode const theme = GM_getValue('theme', 'dark'); document.documentElement.classList.add(theme); // CSS const css = document.createElement('style'); css.textContent = ` html { font-size: 1px; --icons: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAGQCAYAAADr8i+wAAAACXBIWXMAAAsTAAALEwEAmpwYAAAMjmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmI2Mjc5ZTQ3LTNiYzAtM2Q0ZC1iNzUyLWRiNmM4OGU5MDliYyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5N2EzMzA5ZC03NWE4LWRhNDktYjQyNi04NmU1ZDBlM2ZiZTgiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0iOTc4MTgzNEJDNEFBMTFCODE1QzQzMjUyQkJGQkIwNUEiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0iIiB0aWZmOkltYWdlV2lkdGg9IjIwMCIgdGlmZjpJbWFnZUxlbmd0aD0iNDAwIiB0aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb249IjIiIHRpZmY6U2FtcGxlc1BlclBpeGVsPSIzIiB0aWZmOlhSZXNvbHV0aW9uPSIxMDAvMSIgdGlmZjpZUmVzb2x1dGlvbj0iMTAwLzEiIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjEiIGV4aWY6RXhpZlZlcnNpb249IjAyMjEiIGV4aWY6Q29sb3JTcGFjZT0iNjU1MzUiIGV4aWY6UGl4ZWxYRGltZW5zaW9uPSIyMDAiIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI0MDAiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAyLTA4VDIwOjExOjU5LTA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMi0wOFQyMTowMDoyMS0wODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0wMi0wOFQyMTowMDoyMS0wODowMCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjM5ZmM5YWM1LTg2NWMtZjU0Mi05MDM5LTUyNTRmMDg3OTA3MSIgc3RFdnQ6d2hlbj0iMjAyMi0wMi0wOFQyMDoxNzoyNS0wODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGltYWdlL2pwZWcgdG8gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iZGVyaXZlZCIgc3RFdnQ6cGFyYW1ldGVycz0iY29udmVydGVkIGZyb20gaW1hZ2UvanBlZyB0byBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ZWU4ZTcwNS1kYTBjLTI4NDMtYTQ1My05Y2U4ZDdiMjk0ZTIiIHN0RXZ0OndoZW49IjIwMjItMDItMDhUMjA6MTc6MjUtMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjk4ZTlmMDE1LTg3OGMtODc0Yi04MjBkLWE0NjdjZjEzMTZkOCIgc3RFdnQ6d2hlbj0iMjAyMi0wMi0wOFQyMTowMDoyMS0wODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvcG5nIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJkZXJpdmVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJjb252ZXJ0ZWQgZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6OTdhMzMwOWQtNzVhOC1kYTQ5LWI0MjYtODZlNWQwZTNmYmU4IiBzdEV2dDp3aGVuPSIyMDIyLTAyLTA4VDIxOjAwOjIxLTA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRm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); } html.dark { --background: hsl(0, 0%, 6.5%); --foreground-1: hsl(0, 0%, 12%); --foreground-bar-2: hsl(0, 0%, 15%); --foreground-bar: hsl(0, 0%, 27%); --foreground-2: hsl(0, 0%, 17.6%); --border: var(--foreground-bar-2); --text-low: hsl(0, 0%, 65%); --text-medium: hsl(0, 0%, 80%); --text-high: hsl(0, 0%, 98%); --accent: hsl(310, 30%, 30%); } html.light { --background: hsl(0, 0%, 96%); --foreground-1: hsl(0, 0%, 100%); --foreground-bar-2: hsl(0, 0%, 13.3%); --foreground-bar: hsl(0, 0%, 9%); --foreground-2: hsl(0, 0%, 94%); --border: hsl(0, 0%, 94%); --text-low: hsl(0, 0%, 25%); --text-medium: hsl(0, 0%, 7%); --text-high: hsl(0, 0%, 0%); --accent: hsl(310, 30%, 70%); } html body { min-width: 424rem; max-width: 1200rem; padding: 40rem 20rem; margin: 0 auto; background: var(--background); font-size: 12rem; color: var(--text-low); } a { color: var(--text-medium) !important; text-decoration: none; } a:hover { color: var(--text-high) !important; } html *::selection { background-color: var(--accent); } body input, body textarea { background: var(--foreground-2); border: 1px solid var(--border); color: var(--text-medium); resize: vertical; } body input[type="submit"]:hover, body input[type="submit"]:active { cursor: pointer; border-color: var(--accent); } /* Header */ body header { min-height: 20rem; padding-bottom: 40rem; text-align: center; } nav a { display: inline-block; } body .logo { display: none; } nav a[href="https://soundgasm.net/logout"] { font-size: 0; } nav a[href="https://soundgasm.net/logout"]::before { content: "Logout"; font-size: 16px; } /* Multiple-page rules */ body #container, .vl-container, body .sound-details, #jp_container_1, body .uploadform, body .contactform, body .loginform, body .signupform, body .passwordresetform, .vl-sidebar { background: var(--foreground-1); box-shadow: 0 2rem 4rem var(--background), 0 4rem 10rem hsla(0,0%,0%,10%); border-color: var(--border); margin: 0 auto; } #container h1, body h1 { border-color: var(--border); color: var(--text-low); } /* Generic Containers */ body p.footer { border-color: var(--border); } body #container { max-width: 800rem; } .vl-container { max-width: 770rem; padding: 15rem; border-size: 1rem; margin: 30rem auto; } .vl-container-header { padding-bottom: 10rem; border-bottom: 1px solid var(--border); margin: 0 0 14rem; font-size: 16px; font-weight: normal; } .vl-column { display: flex; flex-flow: column nowrap; gap: 5rem 10rem; margin-bottom: 5rem; align-items: start; } .vl-link { color: var(--text-medium); } .vl-link:hover { color: var(--text-high); text-decoration: underline; } .vl-paragraph { margin: 0 0 5rem; font-size: 12rem; line-height: 1.35; } /* Home Page */ .vl-user-list { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5rem; justify-items: start; align-items: start; } /* User Page */ body .sound-details { display: grid; width: calc(100% - 22rem); padding: 10rem; border-radius: 4rem; margin: 0 auto 12rem; grid-template-columns: 1fr 70rem; grid-template-rows: auto auto; grid-template-areas: "title plays" "description description"; grid-auto-flow: column; } .sound-details > a { grid-area: title; justify-self: start; font-size: 16rem; font-weight: bold; white-space: normal; } .playCount { grid-area: plays; max-width: 70rem; margin-left: auto; text-align: right; } .playCount::before { content: ""; display: inline-block; border-color: transparent; border-left-color: var(--text-low); border-style: solid; border-width: .45em .65em; margin-right: -0.4em; vertical-align: middle; } .soundDescription { grid-area: description; order: 3; width: 100%; margin-top: 6rem; } /* Split Content + Sidebar */ .vl-split { display: grid; max-width: calc(640rem + 20rem + 240rem); gap: 20rem; margin: 0 auto; } .vl-directory { display: grid; width: 100vw; max-width: 620rem; grid-auto-flow: row; margin: 0 auto; } .vl-sidebar { padding: 10rem; border-radius: 4rem; overflow-y: auto; } @media (max-width: 941px) { .vl-split { grid-auto-flow: row; } .vl-sidebar { max-width: 600rem; max-height: 80vh; border: 1px solid var(--border); } } @media (min-width: 940px) { .vl-split { grid-auto-flow: column; align-items: start; } .vl-sidebar { position: sticky; top: 20rem; width: 220rem; max-height: calc(100vh - 160rem); order: 1; } } /* Filters */ .vl-hidden, .vl-hidden-by-search, .vl-hidden-by-tag { display: none !important; } .vl-filters { font-size: 12rem; } .vl-filter-section { margin-bottom: 15rem; } .vl-filter-header { border-bottom: 1rem solid var(--border); margin: 0 0 10rem; font-size: 14rem; line-height: 1.25em; font-weight: bold; } .vl-sort-btn::before { content: "• "; } .vl-sort-btn.is-active { font-weight: bold; } .vl-sort-btn.is-active::after { content: attr(data-direction); color: var(--text-low); font-size: 10rem; margin-left: 5rem; } .vl-search { padding: 4rem; border-radius: 3px; margin: 0 0 5rem; } .vl-tag-list { width: 100%; text-align: left; } .vl-tag-list th { font-size: 12rem; } .vl-tag-list tr > *:last-child { text-align: right; } .vl-tag-btn { display: inline; padding: 2rem 4rem; background: none; border: none; border-radius: 2.5rem; color: var(--text-medium); font-size: 11rem; text-align: left; text-transform: capitalize; cursor: pointer; } .vl-tag-btn.is-active, .vl-tag-btn:hover { background: var(--foreground-2); color: var(--text-high); } .vl-tag-btn.is-active { font-weight: bold; } .vl-tag-count { padding: 1rem; font-size: 12rem; } /* Player Page */ div[style="margin:10px 0"] { margin: 0 0 25rem !important; font-size: 18rem; text-align: center; } #jp_container_1, .jp-audio .jp-audio-stream, .jp-audio .jp-video { border: 2rem solid var(--border); color: var(--text-low); } #jp_container_1 { width: 420rem; } .jp-interface { background: var(--foreground-bar); } .jp-audio .jp-details { background: var(--foreground-bar-2); } .jp-details .jp-title { font-size: 12rem; } .light .jp-details .jp-title { color: var(--background); } .jp-description { padding: 0 10rem; font-size: 12rem; } /* Player */ .jp-state-muted .jp-unmute { background: url("../image/jplayer.blue.monday.jpg") -60px -170px no-repeat; } .jp-state-muted .jp-unmute:focus { background: url("../image/jplayer.blue.monday.jpg") -79px -170px no-repeat; } #jp_container_1 button, .jp-gui .jp-seek-bar, .jp-gui .jp-play-bar, .jp-gui .jp-volume-bar, .jp-gui .jp-volume-bar-value { background-image: var(--icons); } .jp-gui .jp-progress { background: none; border-radius: 2.5rem; } .jp-progress .jp-seeking-bg { background: var(--icons) 0 -202px repeat-x; animation: seeking .8s ease-in-out infinite alternate; } @keyframes seeking { 0% { opacity: 1; } 100% { opacity: 0.3; } } .jp-gui .jp-volume-controls { width: 0; } .dark .jp-current-time, .dark .jp-duration { color: var(--text-medium); } .light .jp-current-time, .light .jp-duration { color: var(--background); } /* Description */ .vl-desc-container { margin: 12rem 0 0; } .sound-details .vl-desc-container { display: inline; margin: 0; } .vl-desc-new, .vl-desc-raw { margin: 12rem 0; } .sound-details .vl-desc-new, .sound-details .vl-desc-raw:not([style*="none"]) { display: inline !important; white-space: normal; } .vl-tag { display: inline-block; padding: 2rem 4rem; background: var(--foreground-2); border-radius: 2.5rem; margin: 0 4rem 4rem 0; color: var(--text-medium); font-size: 11rem; text-transform: capitalize; } .vl-showraw { display: inline-block; opacity: 0.5; } .vl-showraw:hover { opacity: 1; } .jp-audio .vl-showraw { margin-bottom: 12rem; } .sound-details .vl-showraw { float: right; } /* Contact page */ header + ul { width: 414rem; padding-left: 16rem; margin: 12rem auto; word-break: break-word; } /* Footer */ .vl-footer { width: 420rem; margin: 0 auto; text-align: center; padding-top: 30rem; } .vl-footer a { padding: 0 15rem; } /* fixes */ .patreon-widget { width: 300px !important; height: 36px !important; } `; document.documentElement.appendChild(css); // Functions & Classes function paragraph( text ){ let p = document.createElement('p'); p.className = 'vl-paragraph'; p.textContent = text; return p; } var users = new class UserDatabase { constructor( ){ let storage = GM_getValue('knownUsernames', '[]'); this.names = JSON.parse(storage); } generateUserList( ){ } add( name ){ let index = this.names.indexOf(name); if( index === -1 ){ this.names.push(name); this.save(); } } remove( name ){ let index = this.names.indexOf(name); if( index > -1 ){ this.names.splice(index, 1); this.save(); } } save( ){ console.log('saving', JSON.stringify(this.names)); GM_setValue('knownUsernames', JSON.stringify(this.names)); } } class AudioListing { constructor({ element, titleSelector, descriptionSelector, playCountSelector = false, order = 0 }){ // Process description let titleDestination = element.querySelector(titleSelector); let descDestination = element.querySelector(descriptionSelector); let desc = descDestination.textContent; let title = titleDestination.textContent; let originalTitle = title; let originalDesc = desc; let processedTitleDiv = document.createElement('span'); let rawTitleDiv = document.createElement('span'); let processedDescDiv = document.createElement('div'); let rawDescDiv = document.createElement('p'); let tagsDiv = document.createElement('div'); let descDiv = document.createElement('p'); let tags = new Set(); // match all words inside brackets [] {}. also matches parentheses () but only for one-word sections to try and avoid false positives const extractTagsRegex = /[\[\{](.*?)[\]\}]|\(([^\s]+)\)/g; // same as the extraction regex but with extra whitespace matching rules const removeTagsRegex = /\s*(?:[\[\{].*?[\]\}]|\([^\s]+\))\s*/g; rawTitleDiv.textContent = title; rawTitleDiv.style.display = 'none'; processedDescDiv.classList.add('vl-desc-container'); tagsDiv.classList.add('vl-tags'); descDiv.classList.add('vl-desc-new'); processedDescDiv.appendChild(tagsDiv); processedDescDiv.appendChild(descDiv); rawDescDiv.classList.add('vl-desc-raw'); rawDescDiv.textContent = desc; rawDescDiv.style.display = 'none'; let combined = title + desc; var tagMatches = combined.matchAll(extractTagsRegex); for( let match of tagMatches ){ let tag = match[1] === undefined ? match[2] : match[1]; if( tag.length > 0 ){ tags.add(tag); } } // remove tags from text title = title.replaceAll(removeTagsRegex, '') desc = desc.replaceAll(removeTagsRegex, '') // sort the tags by length tags = Array.from(tags); tags.sort( (a, b) => { return a.length - b.length; } ); // create the element for( let i = 0; i < tags.length; i++ ){ var tagSpan = document.createElement('span'); tagSpan.classList.add('vl-tag'); tagSpan.textContent = tags[i]; tagsDiv.appendChild(tagSpan); } // Create "view raw" button var viewRawBtn = document.createElement('a'); viewRawBtn.href = 'javascript:void(0);'; viewRawBtn.classList.add('vl-showraw'); viewRawBtn.textContent = 'Show raw.' viewRawBtn.onclick = ()=>{ if( processedDescDiv.style.display === 'none' ){ processedTitleDiv.style.display = 'inline'; rawTitleDiv.style.display = 'none'; processedDescDiv.style.display = 'block'; rawDescDiv.style.display = 'none'; viewRawBtn.textContent = 'Show raw.'; } else { processedTitleDiv.style.display = 'none'; rawTitleDiv.style.display = 'inline'; processedDescDiv.style.display = 'none'; rawDescDiv.style.display = 'block'; viewRawBtn.textContent = 'Show processed.'; } } // finish up with tags & description descDiv.textContent = desc.trim(); processedTitleDiv.textContent = title.trim(); // Add everything back to DOM unless it is identical if( title !== originalTitle || desc !== originalDesc ){ titleDestination.replaceChildren(processedTitleDiv, rawTitleDiv); descDestination.replaceChildren(processedDescDiv, rawDescDiv, viewRawBtn); } // parse play count and change html let plays = false; if( playCountSelector ){ // this if else and element.append are place here due to weird HTML bugs on the default website let playElement = element.querySelector(playCountSelector); if( !playElement ){ plays = 0; playElement = document.createElement('span'); playElement.className = 'playCount'; playElement.textContent = 'unknown'; } else { plays = playElement.textContent.split(': ')[1]; let playText = String(plays); if( plays.length > 3 ) { playElement.title = `played ${plays} times`; playText = playText.substring(0, plays.length - 3) + 'k'; } playElement.textContent = playText; } element.append(playElement); } // Assign variables for use in AudioDirectory classes this.element = element; this.title = title; this.description = desc; this.plays = plays; this.order = order; this.tags = tags; } } class AudioDirectory { constructor({ elements, titleSelector, descriptionSelector, playCountSelector = false, filterElement = false }){ this.audios = []; // used to iterate through audio info this.tags = {}; // used as a reference for which items have which tags this.selectedTags = []; // used to keep track of current filters this.availableElements = []; // used to update tag counts in the sidebar this.tagButtons = []; // used to update tag counts in the sidebar // process all audios for( let index = 0; index < elements.length; index++ ){ let audio = new AudioListing({ element: elements[index], titleSelector: titleSelector, descriptionSelector: descriptionSelector, playCountSelector: playCountSelector, order: index }); this.audios.push(audio); for( let tag of audio.tags ){ let tagName = tag.toLowerCase(); if(!( tagName in this.tags )){ this.tags[tagName] = []; } this.tags[tagName].push(audio.element); } } // intialise filters and sorting if( filterElement ){ // set up DOM this.filterElement = filterElement; this.filterElement.classList.add('vl-filters'); this.sortElement = document.createElement('div'); this.sortElement.className = 'vl-filter-section'; this.searchElement = document.createElement('div'); this.searchElement.className = 'vl-filter-section'; this.tagElement = document.createElement('div'); this.tagElement.className = 'vl-filter-section'; let sortHeader = document.createElement('h6'); sortHeader.className = 'vl-filter-header'; sortHeader.textContent = 'Sort by...'; this.sortElement.append(sortHeader); let searchHeader = document.createElement('h6'); searchHeader.className = 'vl-filter-header'; searchHeader.textContent = 'Search...'; this.searchElement.append(searchHeader); let tagHeader = document.createElement('h6'); tagHeader.className = 'vl-filter-header'; tagHeader.textContent = 'Filter by tag...'; this.tagElement.append(tagHeader); this.sortList = document.createElement('div'); this.sortList.className = 'vl-column'; this.sortElement.append(this.sortList); this.tagList = document.createElement('table'); this.tagList.className = 'vl-tag-list'; this.tagList.insertAdjacentHTML("afterbegin", ` <tr><th>Name</th><th>Count</th></tr> `); this.tagElement.append(this.tagList); // set up sorting this.calculatedSorts = {}; this.sortButtons = {}; this.createSortButton('Title', 'title', 'ascending'); this.createSortButton('Play Count', 'plays', 'descending'); this.createSortButton('Date', 'order', 'descending'); this.sort(); // set up search this.searchBar = document.createElement('input'); this.searchBar.type = 'search'; this.searchBar.className = 'vl-search'; this.searchBar.placeholder = 'Search here.'; this.searchElement.append(this.searchBar); this.searchElement.append(paragraph(`Search supports some basic operators. For example: "phrase" to require an exact string or word and -word to excluse a word. Un-quoted words are treated as OR.`)); this.searchBar.addEventListener('input', ()=>{ this.search() }); this.searchTimeout = setTimeout(null, 0); // set up tag filters let sortedTags = Object.keys(this.tags).sort((first,second)=>{ // sort primarily by number of elements that match the tag with a secondary alphabetical sort let tagCount = this.tags[second].length - this.tags[first].length let tagName = first < second ? -1 : first > second ? 1 : 0; return tagCount === 0 ? tagName : tagCount; }); for( let tag of sortedTags ){ this.createTagButton(tag); } // Append all workspace items to DOM this.filterElement.append(this.sortElement, this.searchElement, this.tagElement); } if( Object.keys(this.tags).length === 0 ){ this.tagElement.remove(); } } createSortButton( title, column, direction ){ let button = document.createElement('a'); button.href = 'javascript:void(0);'; button.textContent = title; button.className = 'vl-sort-btn'; button.dataset.column = column; button.dataset.direction = direction; button.addEventListener('click', ()=>{ this.sort(column, direction); }); this.sortList.append(button); this.sortButtons[column] = button; } createTagButton( tag ){ let row = document.createElement('tr'); let cell1 = document.createElement('td'); let cell2 = document.createElement('td'); let button = document.createElement('button'); button.type = 'button'; button.textContent = tag; button.className = 'vl-tag-btn'; cell1.append(button) let count = this.tags[tag].length; let countElement = document.createElement('span'); countElement.className = 'vl-tag-count'; countElement.textContent = count; cell2.append(countElement); row.append(cell1, cell2); this.tagList.append(row); button.addEventListener('click', ()=>{ let selected = this.selectedTags.indexOf(tag); if( selected > -1 ){ button.classList.remove('is-active'); this.selectedTags.splice(selected, 1); } else { button.classList.add('is-active'); this.selectedTags.push(tag); } this.applySelectedTags(); }); this.tagButtons.push({ 'element': row, 'tag': tag, 'countElement': countElement }); } sort( column = 'order', direction = 'descending' ){ // flip direction if already sorting this way if( this?.sorted?.column === column && this?.sorted?.direction === direction ){ direction = direction === 'descending' ? 'ascending' : 'descending'; } this.sorted = { column, direction }; for( let button of Object.values(this.sortButtons) ){ if( button.dataset.column === column ){ button.classList.add('is-active'); button.dataset.direction = direction; } else { button.classList.remove('is-active'); } } // if list was already sorted once, just re-use the previous sort let array = []; if( `${column}-${direction}` in this.calculatedSorts ){ array = this.calculatedSorts[`${column}-${direction}`]; } // if not sorted yet, choose correct function and sort else { // 'order' column gets sorted in reverse due to being front-facingly labelled as date let sortFunction = () => { throw new Error('unknown sort'); }; if( column === 'plays' && direction === 'ascending' || column === 'order' && direction === 'descending' ){ sortFunction = (first, second) => { return first['value'] - second['value']; }; } else if( column === 'plays' && direction === 'descending' || column === 'order' && direction === 'ascending' ){ sortFunction = (first, second) => { return second['value'] - first['value']; }; } else if( column === 'title' && direction === 'ascending' ){ sortFunction = (first, second) => { let a = first['value']; let b = second['value']; return (a < b) ? -1 : (a > b) ? 1 : 0; }; } else if( column === 'title' && direction === 'descending' ){ sortFunction = (first, second) => { let a = first['value'].toLowerCase(); let b = second['value'].toLowerCase(); return (b < a) ? -1 : (b > a) ? 1 : 0; }; } for( let audio of this.audios ){ array.push({'element': audio.element, 'value': audio[column]}); } array.sort(sortFunction); this.calculatedSorts[`${column}-${direction}`] = array; } // apply sort to items using CSS 'order' values for( let i = 0; i < array.length; i++ ){ array[i]['element'].style.order = i; } } search( ){ clearTimeout(this.searchTimeout); // parse query const exclusionRegex = /(?:^|\s)+-(\w+)/g; const phraseRegex = /"([^"]+)"/g; let query = this.searchBar.value.toLowerCase(); let failMatches = query.matchAll(exclusionRegex); let exclusions = Array.from(failMatches).map( match => match[1] ); query = query.replaceAll(exclusionRegex, '').trim(); let phraseMatches = query.matchAll(phraseRegex); let phrases = Array.from(phraseMatches).map( match => match[1] ); query = query.replaceAll(phraseRegex, '').trim(); let words = query.split(' '); this.searchTimeout = setTimeout(()=>{ for( let audio of this.audios ){ if( this.passesSearch(audio, phrases, words, exclusions) ){ audio.element.classList.remove('vl-hidden-by-search'); this.updateAvailableElements(audio.element); } else { audio.element.classList.add('vl-hidden-by-search'); this.updateAvailableElements(audio.element); } } this.updateTagCounts(); }, 350); } passesSearch( audioListing, phrases, words, exclusions ){ console.log(phrases, words, exclusions); const title = audioListing.title.toLowerCase(); const tags = audioListing.tags.join(' ').toLowerCase(); const any = title + tags; // cannot match any exclusions for( let str of exclusions ){ if( any.includes(str) ){ return false; } } // must match all phrases for( let phrase of phrases ){ if(! any.includes(phrase) ){ return false; } } // can match any word for( let word of words ){ if( any.includes(word) ){ return true; } } } applySelectedTags( ){ if( this.selectedTags.length === 0 ){ for( let audio of this.audios ){ audio.element.classList.remove('vl-hidden-by-tag'); this.updateAvailableElements( audio.element ); } this.updateTagCounts(); return; } for( let audio of this.audios ){ let passesAllTags = true; for( let tag of this.selectedTags ){ if(! this.tags[tag].includes(audio.element) ){ passesAllTags = false; break; } } if( passesAllTags ){ audio.element.classList.remove('vl-hidden-by-tag'); this.updateAvailableElements( audio.element ); } else { audio.element.classList.add('vl-hidden-by-tag'); this.updateAvailableElements( audio.element ); } } this.updateTagCounts(); } updateAvailableElements( element ){ let index = this.availableElements.indexOf(element); let hidden = element.classList.contains('vl-hidden-by-tag') | element.classList.contains('vl-hidden-by-search') if( hidden && index > -1 ){ this.availableElements.splice(index, 1); } else if( !hidden && index === -1 ){ this.availableElements.push(element); } } updateTagCounts( ){ for( let data of this.tagButtons ){ let tag = data.tag; let availableCount = 0; for( let element of this.availableElements ){ if( this.tags[tag].includes(element) ){ availableCount++; } } if( availableCount > 0 ){ data.countElement.textContent = availableCount; data.element.classList.remove('vl-hidden'); } else { data.element.classList.add('vl-hidden'); } } } } // Begin modifying page function domLoaded() { // If content is blank let content = document.querySelector('body > div'); if( content === null ) { let blank = document.createElement('div'); blank.id = 'container'; blank.innerHTML = `<div id="body"><p>There's nothing here.</p></div>`; document.body.appendChild(blank); } // Add footer let footer = document.createElement('footer'); footer.classList.add('vl-footer'); // Theme switcher let themeSwitcher = document.createElement('a'); themeSwitcher.textContent = 'Theme'; themeSwitcher.href = 'javascript:void(0);'; themeSwitcher.onclick = function() { if(GM_getValue('theme', 'dark') === 'dark') { GM_setValue('theme', 'light'); document.documentElement.classList.add('light'); document.documentElement.classList.remove('dark'); } else { GM_setValue('theme', 'dark'); document.documentElement.classList.add('dark'); document.documentElement.classList.remove('light'); } }; footer.appendChild(themeSwitcher); document.body.appendChild(footer); var path = window.location.pathname; // Per-page sections // Any page with a user as long as it has content if( content && path.startsWith('/u/') && path.split('/').length > 2 ){ let username = path.split('/')[2]; users.add(username); } // Homepage if( path === '/' ){ document.querySelector('h1').textContent = 'Welcome to Soundgasm.net, improved!'; let container = document.createElement('div'); container.className = 'vl-container'; container.style.marginBottom = '0'; let header = document.createElement('h3'); header.className = 'vl-container-header'; header.textContent = 'Known user list.'; container.append(header); if( users.names.length === 0 ){ container.append(paragraph(`The script will remember usernames from the audio and user pages you visit. Once you've opened a few, you can always come back here to find them all!`)); } else { let userList = document.createElement('div'); userList.classList.add('vl-user-list'); userList.style.fontSize = '14rem'; for( let username of users.names.sort() ){ let link = document.createElement('a'); link.href = `/u/${username}`; link.textContent = `• ${username}`; link.className = 'vl-link'; userList.append(link); } container.append(userList); } footer.insertAdjacentElement('beforebegin', container); } // User pages if( content && path.startsWith('/u/') && path.split('/').length < 4 ){ // Prep DOM for filters let container = document.createElement('div'); container.className = 'vl-split'; let directory = document.createElement('main'); directory.className = 'vl-directory'; let sidebar = document.createElement('aside'); sidebar.className = 'vl-sidebar'; let frag = new DocumentFragment(); let items = document.querySelectorAll('.sound-details'); for( let item of items ){ frag.append(item); } directory.append(frag); container.append(sidebar, directory); document.getElementsByTagName('footer')[0].insertAdjacentElement('beforebegin', container); // catch any oddities such as patron links and other things from descriptions let patreon = document.querySelector('.soundDescription .patreon-widget'); if( patreon ){ let container = document.createElement('div'); container.className = 'vl-container'; container.style.marginTop = '0'; container.style.width = 'calc(100% - 30rem)'; let header = document.createElement('h3'); header.className = 'vl-container-header'; header.textContent = 'Extra user info.'; container.append(header, patreon); directory.style.order = '-1'; directory.prepend(container); } // Process audio listings new AudioDirectory({ elements: items, titleSelector: 'a', descriptionSelector: '.soundDescription', playCountSelector: '.playCount', filterElement: sidebar }); } // Player page if( path.startsWith('/u/') && path.split('/').length > 3 ){ // Add custom descriptions new AudioListing({ element: document.querySelector('.jp-type-single'), titleSelector: '.jp-title', descriptionSelector: '.jp-description' }); let stop = document.querySelector('.jp-stop'); let title = document.querySelector('.jp-title'); let author = document.querySelector('div[style="margin:10px 0"] a'); let audio = document.querySelector('audio'); // Keypress handler function setKeybinds() { window.addEventListener('keydown', (e) => { let k = e.key.toLowerCase(); if(e.key === ' ') { e.preventDefault(); } }); window.addEventListener('keyup', (e) => { let k = e.key.toLowerCase(); let ctrl = e.ctrlKey; let time = 5.0; if(ctrl){ time = 15.0; } if(k === 'p' || k === 'k' || k === ' ') { if(!audio.paused) { audio.pause(); } else { audio.play(); } } else if(k === 's') { stop.click(); } else if(k === 'd') { document.querySelector('.dl').click(); } else if(k === 'arrowleft') { audio.currentTime -= time; } else if(k === 'arrowright') { audio.currentTime += time; } else if(k === 'arrowup') { let newVol = audio.volume + 0.1; if(newVol > 1) { newVol = 1.0; } audio.volume = newVol; } else if(k === 'arrowdown') { let newVol = audio.volume - 0.1; if(newVol < 0) { newVol = 0.0; } audio.volume = newVol; } else if(k === '0') { audio.currentTime = 0.0; } else if(k === '1') { audio.currentTime = audio.duration / 10; } else if(k === '2') { audio.currentTime = audio.duration / 10 * 2; } else if(k === '3') { audio.currentTime = audio.duration / 10 * 3; } else if(k === '4') { audio.currentTime = audio.duration / 10 * 4; } else if(k === '5') { audio.currentTime = audio.duration / 10 * 5; } else if(k === '6') { audio.currentTime = audio.duration / 10 * 6; } else if(k === '7') { audio.currentTime = audio.duration / 10 * 7; } else if(k === '8') { audio.currentTime = audio.duration / 10 * 8; } else if(k === '9') { audio.currentTime = audio.duration / 10 * 9; } }); } // Download button function addDownload() { let audio = document.querySelector('audio'); let src = audio.getAttribute('src'); let ext = src.split('.').pop(); let dl = document.createElement('a'); dl.classList.add('dl'); footer.appendChild(dl); dl.href = src; dl.setAttribute("download", title.innerText + ' by ' + author.innerText + '.' + ext); dl.setAttribute("target", "_blank"); dl.textContent = 'Download this audio'; } function audioLoaded() { audio = document.querySelector('audio'); if(audio !== null && audio.getAttribute('src') !== null) { // observer.disconnect(); addDownload(); setKeybinds(); } else { setTimeout(audioLoaded, 100); } } // Wait for audio to load if(audio !== null && audio.getAttribute('src') !== null) { addDownload(); } else { audioLoaded(); } } // signup page if(window.location.pathname.startsWith('/signup')) { let h1 = document.querySelector('h1'); let form = document.querySelector('.signupform'); form.prepend(h1); } }