// ==UserScript==
// @name ThePosterDB - Easy Links
// @version 2.2.0
// @description Makes it easier to copy data from ThePosterDB
// @author Journey Over
// @license MIT
// @match *://theposterdb.com/*
// @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
// @grant GM_setClipboard
// @grant GM_addStyle
// @icon https://www.google.com/s2/favicons?sz=64&domain=theposterdb.com
// @homepageURL https://github.com/StylusThemes/Userscripts
// @downloadURL https://github.com/StylusThemes/Userscripts/raw/main/userscripts/theposterdb-easy-links.user.js
// @updateURL https://github.com/StylusThemes/Userscripts/raw/main/userscripts/theposterdb-easy-links.user.js
// ==/UserScript==
(function() {
'use strict';
const CONFIG = {
prefix: 'tpdb',
selectors: {
gridPosters: '.col-6 .hovereffect',
copyLinkBtn: '.copy_poster_link',
titleText: 'p.p-0.mb-1.text-break',
overlay: 'div.overlay'
},
attributes: {
posterId: 'data-poster-id',
clipboardText: 'data-clipboard-text'
},
urls: {
apiBase: 'https://theposterdb.com/api/assets'
},
notifications: {
duration: 3000,
messages: {
link: 'Link copied to clipboard',
id: 'ID copied to clipboard',
metadata: 'Metadata copied to clipboard'
}
}
};
const STYLES = `.${CONFIG.prefix}-notification{position:fixed;top:10px;right:10px;padding:10px;background-color:#4caf50;color:white;z-index:10000;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,0.5);transition:opacity 0.3s ease-in-out;}.${CONFIG.prefix}-button-container{display:flex;justify-content:space-between;gap:5px;margin-top:5px;}.${CONFIG.prefix}-button{flex:1;text-align:center;cursor:pointer;padding:5px 10px;border-radius:5px;font-size:1rem;color:white;border:1px solid;transition:all 0.3s ease;}.${CONFIG.prefix}-button:hover{transform:scale(1.05);}.${CONFIG.prefix}-button-link{background-color:#28965a;border-color:#219150;}.${CONFIG.prefix}-button-link:hover{background-color:#1e7948;}.${CONFIG.prefix}-button-id{background-color:#007bff;border-color:#0056b3;}.${CONFIG.prefix}-button-id:hover{background-color:#0056b3;}.${CONFIG.prefix}-metadata-button{cursor:pointer !important;color:white;background:transparent;padding:3px 8px;border-radius:4px;font-size:0.9rem;text-decoration:none;display:inline-block;border:1px solid #5a6268;transition:all 0.3s ease;}.${CONFIG.prefix}-metadata-button:hover{background-color:#5a6268;transform:scale(1.05);}`;
const Utilities = {
async fadeOut(elementToFade, duration) {
elementToFade.style.opacity = '0';
await new Promise(resolve => setTimeout(resolve, duration));
elementToFade.remove();
},
createUrl(posterId) {
return `${CONFIG.urls.apiBase}/${posterId}`;
},
isValidPosterId(posterId) {
return posterId && /^\d+$/.test(posterId);
},
};
const NotificationManager = {
show(message, duration = CONFIG.notifications.duration) {
const notificationElement = document.createElement('div');
notificationElement.className = 'tpdb-notification';
notificationElement.textContent = message;
document.body.appendChild(notificationElement);
setTimeout(() => {
Utilities.fadeOut(notificationElement, 300);
}, duration);
},
};
class PosterData {
constructor(posterElement) {
this.element = posterElement;
this.posterId = this.extractPosterId();
this.title = this.extractTitle();
this.year = this.extractYear();
}
extractPosterId() {
const overlayElement = this.element.querySelector(CONFIG.selectors.overlay);
return overlayElement?.getAttribute(CONFIG.attributes.posterId);
}
// Extract title and remove year from parentheses for cleaner metadata
extractTitle() {
const titleElement = this.element.querySelector(CONFIG.selectors.titleText);
return titleElement?.textContent.trim().replace(/\(\d{4}\)/, '').trim() || '';
}
extractYear() {
const titleElement = this.element.querySelector(CONFIG.selectors.titleText);
const yearMatch = titleElement?.textContent.match(/\((\d{4})\)/);
return yearMatch ? parseInt(yearMatch[1], 10) : null;
}
get apiUrl() {
return Utilities.createUrl(this.posterId);
}
// Format for YAML-style metadata compatible with media servers
toMetadata() {
return ` "${this.title}":\n match:\n year: ${this.year || 'Unknown'}\n url_poster: "${this.apiUrl}"`;
}
}
class UIManager {
constructor() {
this.setupStyles();
this.initializeUI();
}
setupStyles() {
GM_addStyle(STYLES);
}
createButton(text, className, clickHandler) {
const buttonElement = document.createElement('button');
buttonElement.className = `tpdb-button ${className}`;
buttonElement.textContent = text;
buttonElement.addEventListener('click', clickHandler);
return buttonElement;
}
createButtonContainer(posterId) {
const buttonContainer = document.createElement('div');
buttonContainer.className = 'tpdb-button-container';
const copyLinkButton = this.createButton('Copy Link', 'tpdb-button-link', () => {
GM_setClipboard(Utilities.createUrl(posterId));
NotificationManager.show(CONFIG.notifications.messages.link);
});
const copyIdButton = this.createButton('Copy ID', 'tpdb-button-id', () => {
GM_setClipboard(posterId);
NotificationManager.show(CONFIG.notifications.messages.id);
});
buttonContainer.append(copyLinkButton, copyIdButton);
return buttonContainer;
}
// Enhance main poster page with additional copy buttons alongside existing ones
setupMainPosterButtons() {
const existingCopyLinkButton = document.querySelector(CONFIG.selectors.copyLinkBtn);
if (!existingCopyLinkButton) return;
const posterId = existingCopyLinkButton.getAttribute(CONFIG.attributes.posterId);
if (!Utilities.isValidPosterId(posterId)) return;
existingCopyLinkButton.setAttribute(CONFIG.attributes.clipboardText, Utilities.createUrl(posterId));
existingCopyLinkButton.addEventListener('click', () => {
NotificationManager.show(CONFIG.notifications.messages.link);
});
const copyIdButton = document.createElement('button');
copyIdButton.className = 'btn btn-outline-warning clipboard';
copyIdButton.setAttribute(CONFIG.attributes.clipboardText, posterId);
copyIdButton.setAttribute('data-toggle', 'tooltip');
copyIdButton.setAttribute('data-placement', 'top');
copyIdButton.setAttribute('title', 'Copy Poster ID');
copyIdButton.innerHTML = 'Copy ID ';
copyIdButton.addEventListener('click', () => {
NotificationManager.show(CONFIG.notifications.messages.id);
});
existingCopyLinkButton.parentNode.insertBefore(copyIdButton, existingCopyLinkButton.nextSibling);
if (window.ClipboardJS) {
new ClipboardJS(copyIdButton);
}
}
setupGridPosters() {
for (const posterElement of document.querySelectorAll(CONFIG.selectors.gridPosters)) {
const posterData = new PosterData(posterElement);
if (!Utilities.isValidPosterId(posterData.posterId)) continue;
const buttonContainer = this.createButtonContainer(posterData.posterId);
posterElement.parentElement.appendChild(buttonContainer);
}
}
setupMetadataButton() {
const posterDataList = [...document.querySelectorAll(CONFIG.selectors.gridPosters)]
.map(posterElement => new PosterData(posterElement))
.filter(poster => Utilities.isValidPosterId(poster.posterId));
if (posterDataList.length === 0) return;
const metadataButton = document.createElement('button');
metadataButton.className = 'tpdb-metadata-button';
metadataButton.textContent = 'Copy Metadata';
metadataButton.addEventListener('click', () => {
const metadataString = `metadata:\n\n${posterDataList.map(poster => poster.toMetadata()).join('\n\n')}`;
GM_setClipboard(metadataString);
NotificationManager.show(CONFIG.notifications.messages.metadata);
});
document.querySelector('div')?.appendChild(metadataButton);
}
initializeUI() {
this.setupMainPosterButtons();
this.setupGridPosters();
this.setupMetadataButton();
}
}
$(document).ready(() => {
new UIManager();
});
})();