NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @namespace https://openuserjs.org/users/SB100
// @name PTP Show Profile Film in Forums
// @description Show a user's profile film when you click on their display picture
// @updateURL https://openuserjs.org/meta/SB100/PTP_Show_Profile_Film_in_Forums.meta.js
// @version 1.1.3
// @author SB100
// @copyright 2021, SB100 (https://openuserjs.org/users/SB100)
// @license MIT
// @match https://passthepopcorn.me/forums.php?*action=viewthread*
// @match https://passthepopcorn.me/torrents.php?*id=*
// @match https://passthepopcorn.me/requests.php?*action=view*id=*
// @match https://passthepopcorn.me/comments.php?*action=requests_b*
// @match https://passthepopcorn.me/comments.php?*action=my_torrents*
// @grant GM_xmlhttpRequest
// ==/UserScript==
// ==OpenUserJS==
// @author SB100
// ==/OpenUserJS==
/* jshint esversion: 6 */
/**
* Turn a HTML string into a HTML element so that we can run querySelector calls against it
*/
function htmlToElement(html) {
var template = document.createElement('template');
html = html.trim();
template.innerHTML = html;
return template.content;
}
/**
* Traverse a node until we find a parent who matches the selector
*/
function getParent(elem, selector) {
if (elem === document.documentElement) {
return null;
}
if (elem.matches(selector)) {
return elem;
}
return getParent(elem.parentNode, selector);
}
/**
* Adds styling to div to show it nicely as an overlay
*/
function addStylingToLoadingDiv(div, height = '100%') {
div.style.position = 'absolute';
div.style.left = '5px';
div.style.top = '5px';
div.style.width = 'calc(100% - 9px)';
div.style.height = height.toString().includes('%') ? `calc(${height} - 5px)` : `${height}px`;
div.style.background = 'rgba(0, 0, 0, 0.5)';
div.style.display = 'flex';
div.style.alignItems = 'center';
div.style.justifyContent = 'center';
div.classList.add('forum-post__avatar-profile-film');
}
/**
* Load a user profile and return traversable nodes
*/
function loadUserProfile(url, avatarParent, height) {
let resolver;
let rejecter;
const p = new Promise((resolveFn, rejectFn) => {
resolver = resolveFn;
rejecter = rejectFn;
});
const div = document.createElement('div');
GM_xmlhttpRequest({
method: 'get',
url: url,
timeout: 10000,
onloadstart: () => {
addStylingToLoadingDiv(div, height);
div.innerText = 'Loading Profile Film ...';
avatarParent.appendChild(div);
},
onload: (response) => {
if (response.status !== 200) {
div.innerText = "Couldn't load User Profile =(";
div.onclick = () => {
div.remove()
}
rejecter(new Error('Not OK'));
return;
}
div.remove();
resolver(htmlToElement(response.response));
},
onerror: (response) => {
div.innerText = "Unknown Error =(";
div.onclick = () => {
div.remove()
}
rejecter(response)
},
ontimeout: (response) => {
div.innerText = "Timed out =(";
div.onclick = () => {
div.remove()
}
rejecter(response)
}
});
return p;
}
/**
* Find a user profile link by traversing from their avatar image
*/
function getUserProfileLink(avatarElem) {
const forumPost = getParent(avatarElem, '.forum-post');
if (!forumPost) {
return;
}
const usernameElem = forumPost.querySelector('a.username');
if (!usernameElem) {
return;
}
return usernameElem.href;
}
/**
* Find the profile film image on the user page
*/
function findProfileFilmFromUserPage(userPageDoc) {
const profileFilmHeader = Array.from(userPageDoc.querySelectorAll('.panel__heading__title')).filter(title => title.innerText === 'Profile Film');
if (!profileFilmHeader || profileFilmHeader.length !== 1) {
return;
}
const panel = getParent(profileFilmHeader[0], '.panel');
if (!panel) {
return;
}
return panel.querySelector('img');
}
/**
* Swap the avatar and the profile film
*/
function swapImage(parentNode) {
parentNode.querySelector('.forum-post__avatar__image').classList.toggle('hidden');
parentNode.querySelector('.sidebar-cover-image').classList.toggle('hidden');
}
/**
* Main runner to do all the things
*/
function showProfileFilm(event) {
// only interested in forum avatars
if (event.target.classList.contains('forum-post__avatar__image') === false && event.target.classList.contains('sidebar-cover-image') === false) {
return;
}
const avatarParent = getParent(event.target, '.forum-post__avatar');
// if we've already fetched the profile film, just swap the images
if (avatarParent.childElementCount === 2) {
swapImage(avatarParent);
return;
}
// find height of avatar image so we don;t experience jank on an image swap
const avatarHeight = event.target.height;
// make relative to we can add a loading spinner to the image
// set height to reduce jank
avatarParent.style.position = 'relative';
avatarParent.style.minHeight = `${avatarHeight + 10}px`;
avatarParent.parentNode.style.minHeight = `${avatarHeight + 10}px`;
// find the user link on the page
const userProfileLink = getUserProfileLink(event.target);
if (!userProfileLink) {
return;
}
// load it, find the profile film, add it to the current page, and show it
return loadUserProfile(userProfileLink, avatarParent, avatarHeight)
.then((doc) => findProfileFilmFromUserPage(doc))
.then((profileImg) => {
if (!profileImg) {
const div = document.createElement('div');
addStylingToLoadingDiv(div, avatarHeight);
div.innerText = 'No Profile Film Found';
div.onclick = () => {
div.remove()
}
avatarParent.appendChild(div);
return;
}
profileImg.style.border = '2px dashed #C8AF4B';
avatarParent.appendChild(profileImg);
if (profileImg.height > 100 && profileImg.height < avatarHeight) {
avatarParent.style.minHeight = `${profileImg.height + 10}px`;
avatarParent.parentNode.style.minHeight = `${profileImg.height + 10}px`;
}
event.target.classList.add('hidden');
});
}
/**
* Event listener to run the main runner. Even works with infinite scrolling!
*/
function attachEventListeners() {
document.querySelector('.thin').addEventListener('dblclick', showProfileFilm);
}
/**
* Run the script
*/
(function () {
'use strict';
attachEventListeners();
})();