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);