Letterboxd DMM Integration

Add Debrid Media Manager buttons to Letterboxd movie pages

// ==UserScript==
// @name         Letterboxd DMM Integration
// @namespace    letterboxd-dmm
// @version      1.1
// @description  Add Debrid Media Manager buttons to Letterboxd movie pages
// @author       You
// @match        https://letterboxd.com/film/*
// @match        https://www.letterboxd.com/film/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    console.log('Letterboxd DMM script loaded');

    // Configuration - Update these URLs to match your DMM setup
    const DMM_BASE_URL = 'https://debridmediamanager.com';
    
    // Function to extract movie information from Letterboxd page
    function getMovieInfo() {
        console.log('Attempting to extract movie info...');
        
        // First, try to find IMDb ID from page links
        let imdbId = '';
        
        // Method 1: Look for IMDb link in the page
        const imdbLink = document.querySelector('a[href*="imdb.com/title/"]');
        if (imdbLink) {
            const imdbMatch = imdbLink.href.match(/\/title\/(tt\d+)/);
            if (imdbMatch) {
                imdbId = imdbMatch[1];
                console.log(`Found IMDb ID from link: ${imdbId}`);
            }
        }
        
        // Method 2: Check for IMDb link in the film details/external links section
        if (!imdbId) {
            const detailsLinks = document.querySelectorAll('a[href*="imdb.com"]');
            for (const link of detailsLinks) {
                const imdbMatch = link.href.match(/\/title\/(tt\d+)/);
                if (imdbMatch) {
                    imdbId = imdbMatch[1];
                    console.log(`Found IMDb ID from details: ${imdbId}`);
                    break;
                }
            }
        }
        
        // Method 3: Check meta tags for IMDb ID
        if (!imdbId) {
            const imdbMeta = document.querySelector('meta[property*="imdb"]');
            if (imdbMeta && imdbMeta.content) {
                const imdbMatch = imdbMeta.content.match(/(tt\d+)/);
                if (imdbMatch) {
                    imdbId = imdbMatch[1];
                    console.log(`Found IMDb ID from meta: ${imdbId}`);
                }
            }
        }
        
        // Get movie title - try multiple selectors
        const titleSelectors = [
            'h1.headline-1.primaryname',  // Updated based on debug output
            'h1.headline-1.filmtitle',
            'h1.filmtitle', 
            'h1.headline-1',
            'h1.js-widont',
            'h1.prettify',
            '.film-title h1',
            'section.film-header h1',
            'h1',  // Last resort - any h1
            '[data-film-name]'  // In case they use data attributes
        ];
        
        let titleElement = null;
        let title = '';
        
        // Debug: Log all h1 elements found
        const allH1s = document.querySelectorAll('h1');
        console.log(`Found ${allH1s.length} h1 elements on page:`);
        allH1s.forEach((h1, index) => {
            console.log(`H1 ${index}: class="${h1.className}", text="${h1.textContent.trim()}"`);
        });
        
        for (const selector of titleSelectors) {
            titleElement = document.querySelector(selector);
            if (titleElement) {
                title = titleElement.textContent.trim();
                console.log(`Found title with selector "${selector}": ${title}`);
                break;
            }
        }
        
        if (!title) {
            console.log('Could not find movie title');
            return null;
        }
        
        // Get year from the URL or page
        const urlMatch = window.location.pathname.match(/\/film\/([^\/]+)/);
        const movieSlug = urlMatch ? urlMatch[1] : '';
        
        // Try to extract year from various sources
        let year = '';
        
        // Method 1: From the release year link
        const yearSelectors = [
            '.releaseyear a',
            '.film-title .releaseyear',
            'small.number a',
            '.film-poster img'
        ];
        
        for (const selector of yearSelectors) {
            const yearElement = document.querySelector(selector);
            if (yearElement) {
                if (selector === '.film-poster img') {
                    // Extract from alt text
                    const altText = yearElement.alt;
                    const yearMatch = altText.match(/\((\d{4})\)/);
                    if (yearMatch) {
                        year = yearMatch[1];
                        console.log(`Found year from poster alt: ${year}`);
                        break;
                    }
                } else {
                    year = yearElement.textContent.trim();
                    console.log(`Found year with selector "${selector}": ${year}`);
                    break;
                }
            }
        }
        
        // Method 2: From meta tags
        if (!year) {
            const metaYear = document.querySelector('meta[property="video:release_date"]');
            if (metaYear) {
                year = new Date(metaYear.content).getFullYear().toString();
                console.log(`Found year from meta: ${year}`);
            }
        }
        
        // Method 3: Extract from page title
        if (!year) {
            const pageTitle = document.title;
            const yearMatch = pageTitle.match(/\((\d{4})\)/);
            if (yearMatch) {
                year = yearMatch[1];
                console.log(`Found year from page title: ${year}`);
            }
        }
        
        console.log(`Extracted movie info: Title="${title}", Year="${year}", Slug="${movieSlug}", IMDb ID="${imdbId}"`);
        return { title, year, slug: movieSlug, imdbId: imdbId };
    }
    
    // Function to create DMM button
    function createDMMButton(movieInfo) {
        const button = document.createElement('a');
        
        // Use IMDb ID format if available, otherwise fall back to title search
        if (movieInfo.imdbId) {
            button.href = `${DMM_BASE_URL}/movie/${movieInfo.imdbId}`;
            button.textContent = '📁 Add to DMM';
        } else {
            // Fallback to hash search if no IMDb ID found
            button.href = `${DMM_BASE_URL}/hash/${encodeURIComponent(movieInfo.title + (movieInfo.year ? ' ' + movieInfo.year : ''))}`;
            button.textContent = '📁 Search DMM';
        }
        
        button.target = '_blank';
        button.rel = 'noopener noreferrer';
        button.className = 'dmm-button';
        
        // Style the button to match Letterboxd's design
        button.style.cssText = `
            display: inline-block;
            background: #00c030;
            color: white !important;
            padding: 8px 12px;
            border-radius: 3px;
            text-decoration: none !important;
            font-weight: 600;
            font-size: 11px;
            margin: 4px 4px 4px 0;
            transition: background-color 0.2s;
            border: none;
            cursor: pointer;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        `;
        
        // Add hover effect
        button.addEventListener('mouseenter', function() {
            this.style.backgroundColor = '#00a028';
        });
        
        button.addEventListener('mouseleave', function() {
            this.style.backgroundColor = '#00c030';
        });
        
        return button;
    }
    
    // Function to create search DMM button
    function createSearchDMMButton(movieInfo) {
        const button = document.createElement('a');
        
        // Use the correct DMM search URL format
        button.href = `${DMM_BASE_URL}/search?query=${encodeURIComponent(movieInfo.title + (movieInfo.year ? ' ' + movieInfo.year : ''))}`;
        button.target = '_blank';
        button.rel = 'noopener noreferrer';
        button.className = 'dmm-search-button';
        button.textContent = '🔍 Search DMM';
        
        // Style the button
        button.style.cssText = `
            display: inline-block;
            background: #0078d4;
            color: white !important;
            padding: 8px 12px;
            border-radius: 3px;
            text-decoration: none !important;
            font-weight: 600;
            font-size: 11px;
            margin: 4px 4px 4px 0;
            transition: background-color 0.2s;
            border: none;
            cursor: pointer;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        `;
        
        // Add hover effect
        button.addEventListener('mouseenter', function() {
            this.style.backgroundColor = '#106ebe';
        });
        
        button.addEventListener('mouseleave', function() {
            this.style.backgroundColor = '#0078d4';
        });
        
        return button;
    }
    
    // Function to insert DMM buttons
    function insertDMMButtons() {
        console.log('insertDMMButtons called');
        
        // Check if buttons already exist
        if (document.querySelector('.dmm-button')) {
            console.log('DMM buttons already exist, skipping');
            return;
        }
        
        const movieInfo = getMovieInfo();
        if (!movieInfo) {
            console.log('Could not extract movie info from Letterboxd page');
            return;
        }
        
        // Find a good location to insert the buttons
        const possibleContainers = [
            'section.film-header',
            '.film-header-group',
            '.film-header',
            '.film-title-wrapper',
            'h1.headline-1.primaryname',  // Updated based on debug output
            'h1.headline-1.filmtitle',
            'h1.filmtitle',
            '.film-title',
            '.primarytext'
        ];
        
        let container = null;
        for (const selector of possibleContainers) {
            container = document.querySelector(selector);
            if (container) {
                console.log(`Found container with selector: ${selector}`);
                break;
            }
        }
        
        if (!container) {
            console.log('Could not find suitable container, trying to insert after title');
            const titleElement = document.querySelector('h1.headline-1.primaryname, h1.headline-1.filmtitle, h1.filmtitle, h1.headline-1');
            if (titleElement) {
                container = titleElement.parentElement;
                console.log('Using title parent as container:', container.tagName, container.className);
            }
        }
        
        if (!container) {
            console.log('Could not find any suitable container for DMM buttons');
            console.log('Available sections:', Array.from(document.querySelectorAll('section')).map(s => s.className));
            console.log('Available divs with classes:', Array.from(document.querySelectorAll('div[class]')).slice(0, 10).map(d => d.className));
            return;
        }
        
        // Create button container
        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'dmm-buttons-container';
        buttonContainer.style.cssText = `
            margin: 12px 0 8px 0;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 4px;
        `;
        
        // Create buttons
        const addButton = createDMMButton(movieInfo);
        const searchButton = createSearchDMMButton(movieInfo);
        
        // Add buttons to container
        buttonContainer.appendChild(addButton);
        buttonContainer.appendChild(searchButton);
        
        // Insert the button container
        if (container.tagName === 'H1') {
            container.parentNode.insertBefore(buttonContainer, container.nextSibling);
        } else if (container.tagName === 'SECTION') {
            // For section containers, append at the end
            container.appendChild(buttonContainer);
        } else {
            // For other containers, try to insert after the title
            const titleInContainer = container.querySelector('h1');
            if (titleInContainer) {
                titleInContainer.parentNode.insertBefore(buttonContainer, titleInContainer.nextSibling);
            } else {
                container.appendChild(buttonContainer);
            }
        }
        
        console.log('DMM buttons added successfully');
    }
    
    // Function to wait for elements and then insert buttons
    function waitAndInsert() {
        let attempts = 0;
        const maxAttempts = 30;
        
        function tryInsert() {
            attempts++;
            console.log(`Attempt ${attempts} to insert DMM buttons`);
            
            // Check for multiple possible title elements
            const possibleTitles = [
                document.querySelector('h1.headline-1.filmtitle'),
                document.querySelector('h1.filmtitle'),
                document.querySelector('h1.headline-1'),
                document.querySelector('h1.js-widont'),
                document.querySelector('h1.prettify'),
                document.querySelector('h1'),
                document.querySelector('[data-film-name]'),
                document.querySelector('.film-title h1')
            ].filter(Boolean);
            
            console.log(`Found ${possibleTitles.length} potential title elements`);
            
            // Also check if the page has loaded enough content
            const hasContent = document.querySelector('.film-poster') || 
                             document.querySelector('.film-header') ||
                             document.querySelector('section') ||
                             document.querySelector('.col-17');
            
            if (possibleTitles.length > 0 && hasContent) {
                console.log('Page appears to be loaded, attempting to insert buttons');
                insertDMMButtons();
            } else if (attempts < maxAttempts) {
                console.log(`Not ready yet (titles: ${possibleTitles.length}, content: ${!!hasContent}), retrying...`);
                setTimeout(tryInsert, 800);
            } else {
                console.log('Max attempts reached. Final debug info:');
                console.log('Page HTML snippet:', document.body.innerHTML.substring(0, 500));
                console.log('All H1 elements:', Array.from(document.querySelectorAll('h1')).map(h1 => ({
                    text: h1.textContent.trim(),
                    class: h1.className,
                    id: h1.id
                })));
            }
        }
        
        tryInsert();
    }
    
    // Initialize the script
    function init() {
        console.log('Initializing DMM script');
        console.log('Current URL:', window.location.href);
        
        // Check if we're on a movie page
        if (!window.location.pathname.startsWith('/film/')) {
            console.log('Not on a film page, skipping');
            return;
        }
        
        // Wait for page to load and try to insert buttons
        waitAndInsert();
    }
    
    // Run when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
    
    // Also run on URL changes (for single page app navigation)
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            console.log('URL changed to:', url);
            setTimeout(init, 1000);
        }
    }).observe(document, { subtree: true, childList: true });

})();