NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Indieheads/Hiphopheads Streaming Links // @namespace https://www.reddit.com/r/indieheads // @version 1.0 // @description Adds links to various streaming services (from https://song.link) to the comments page for all [FRESH*] posts in /r/indieheads and /r/hiphopheads. // @author jeffm24 // @license MIT // @match https://www.reddit.com/r/indieheads/comments/* // @match https://www.reddit.com/r/hiphopheads/comments/* // @grant GM.xmlHttpRequest // @grant GM.addStyle // ==/UserScript== /* jshint esnext: false */ /* jshint esversion: 6 */ GM.addStyle(` #streaming-links-toggle { font-size: 12px; margin: 0px 10px; white-space: nowrap; } #streaming-links-toggle.none-found { color: red; } #streaming-links-popup { position: absolute; z-index: 9999; padding: 10px; max-width: 300px; box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); background-color: white; font-family: Helvetica, Ariel; } #streaming-links-popup.hidden { display: none; } #streaming-links-popup > .header { margin: -10px -10px 10px; padding: 8px 10px; background-color: slategrey; color: white; text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); } #streaming-links-popup > ul { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 15px 10px; text-align: center; margin: 15px 5px; } #streaming-links-popup > ul > li { white-space: nowrap; } #streaming-links-popup > ul > li svg { display: block; margin: 0px auto 5px; } #streaming-links-popup > .footer { background-color: ghostwhite; color: white; padding: 7px 10px; border-top: 1px solid lightgrey; margin: 0px -10px -10px; } `); // Promisified xmlHttpRequest method GM.xmlHttpRequestAsync = (method, url) => { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method, url, onload(res) { if (res.status >= 200 && res.status < 300) { try { resolve(JSON.parse(res.response)); } catch (error) { resolve(res.response); } } else { reject({ status: res.status, statusText: res.statusText }); } }, onerror (res) { reject({ status: res.status, statusText: res.statusText }); } }); }); }; // Get the current x and y position of the given element relative to viewport function getPosition(element) { let xPosition = 0, yPosition = 0; while (element) { xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft); yPosition += (element.offsetTop - element.scrollTop + element.clientTop); element = element.offsetParent; } return { x: xPosition, y: yPosition }; } async function processTitle(postTitle) { const songPrefixMatch = /^\[FRESH( VIDEO)?\] /i, albumPrefixMatch = /^\[FRESH (ALBUM|EP)\] /i; let songLink, regexMatch = false; // Check if post is for a song or album and get songLink based on that if (songPrefixMatch.test(postTitle)) { regexMatch = true; const formattedQuery = postTitle.replace(songPrefixMatch, '').replace(/ - | /g, '+'), response = await GM.xmlHttpRequestAsync('GET', `https://song.link/search?q=${formattedQuery}`); if (response.songs.length) { songLink = response.songs[0].links.songlink; } } else if (albumPrefixMatch.test(postTitle)) { regexMatch = true; const formattedQuery = postTitle.replace(albumPrefixMatch, '').replace(/ - | /g, '+'), response = await GM.xmlHttpRequestAsync('GET', `https://song.link/search?q=${formattedQuery}`); if (response.albums.length) { songLink = response.albums[0].links.songlink; } } if (songLink) { // Load link page html into custom div so it can be queried const linkPage = Object.assign(document.createElement('div'), { innerHTML: await GM.xmlHttpRequestAsync('GET', songLink) }); const popup = Object.assign(document.createElement('div'), { id: 'streaming-links-popup', className: 'hidden', innerHTML: '<h2 class="header">All Streaming Links</h2>', onclick(e) { e.stopImmediatePropagation(); } }); const servicesUL = document.createElement('ul'); // Scrape streaming links from universal link page and add them to popup html linkPage.querySelectorAll('[data-nemo^="listen"] > a').forEach((streamingLinkNode) => { streamingLinkNode.target = '_blank'; servicesUL.appendChild(Object.assign(document.createElement('li'), { innerHTML: streamingLinkNode.outerHTML })); }); popup.appendChild(servicesUL); // Add footer div with universal share link at the bottom of the popup popup.appendChild(Object.assign(document.createElement('div'), { className: 'footer', innerHTML: `<a href="${songLink}" target="_blank">Universal Link</a>` })); document.body.appendChild(popup); // Create popup toggle link const popupToggle = Object.assign(document.createElement('a'), { id: 'streaming-links-toggle', href: '', innerText: 'All Streaming Links', onclick(e) { e.preventDefault(); e.stopImmediatePropagation(); const pos = getPosition(e.currentTarget); popup.style.top = `${pos.y}px`; popup.style.left = `${pos.x}px`; popup.classList.remove('hidden'); } }); let prevWindowWidth = document.body.clientWidth; // Reposition streaming links popup on window width resize window.addEventListener('resize', () => { if (document.body.clientWidth !== prevWindowWidth) { prevWindowWidth = document.body.clientWidth; const pos = getPosition(popupToggle); popup.style.top = `${pos.y}px`; popup.style.left = `${pos.x}px`; } }); // Hide streaming links popup on body click document.body.addEventListener('click', () => { popup.classList.add('hidden'); }); // Add popup toggle link to post title document.querySelector('.top-matter > .title').appendChild(popupToggle); } else if (regexMatch) { // Show not found message if regex matched post title document.querySelector('.top-matter > .title').appendChild( Object.assign(document.createElement('span'), { id: 'streaming-links-toggle', className: 'none-found', innerText: 'No Streaming Links Found :(' }) ); } } processTitle(document.querySelector('.top-matter > .title > a.title').text);