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