Letterboxd to P-Stream

Add P-Stream links to Letterboxd film pages

// ==UserScript==
// @name         Letterboxd to P-Stream
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Add P-Stream links to Letterboxd film pages
// @author       archiivv
// @match        https://letterboxd.com/film/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Wait for page to load
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    function init() {
        // Wait a bit for dynamic content to load
        setTimeout(addPstreamButton, 1000);
    }

    function addPstreamButton() {
        // Get film data from the page
        let filmTitle, filmYear;

        // Try to get from filmData global variable (if available)
        if (typeof window.filmData !== 'undefined') {
            filmTitle = window.filmData.name;
            filmYear = window.filmData.releaseYear;
        } else {
            // Fallback: scrape from page elements
            const titleElement = document.querySelector('h1.headline-1');
            const yearElement = document.querySelector('.number a');

            if (titleElement) {
                filmTitle = titleElement.textContent.trim();
            }
            if (yearElement) {
                filmYear = yearElement.textContent.trim();
            }
        }

        if (!filmTitle) {
            console.log('Could not find film title');
            return;
        }

        console.log('Film:', filmTitle, filmYear);

        // Look for TMDB link on the page
        const tmdbLink = document.querySelector('a[data-track-action="TMDb"], a[href*="themoviedb.org"]');

        if (tmdbLink) {
            const tmdbMatch = tmdbLink.href.match(/themoviedb\.org\/movie\/(\d+)/);
            if (tmdbMatch) {
                const tmdbId = tmdbMatch[1];
                console.log('Found TMDB ID:', tmdbId);
                createPstreamButton(tmdbId, filmTitle);
                return;
            }
        }

        // If no TMDB link found, search via API
        searchTMDBForId(filmTitle, filmYear);
    }

    function searchTMDBForId(title, year) {
        // Free TMDB API - you need to get a key from https://www.themoviedb.org/settings/api
        const apiKey = 'YOUR_API_KEY_HERE'; // Replace with your actual API key

        if (apiKey === 'YOUR_API_KEY_HERE') {
            console.log('Please add your TMDB API key to the script');
            // For now, let's try to construct URL without API
            createPstreamButtonFallback(title, year);
            return;
        }

        const searchUrl = `https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&query=${encodeURIComponent(title)}&year=${year}`;

        GM_xmlhttpRequest({
            method: 'GET',
            url: searchUrl,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.results && data.results.length > 0) {
                        const tmdbId = data.results[0].id;
                        console.log('Found TMDB ID via API:', tmdbId);
                        createPstreamButton(tmdbId, title);
                    } else {
                        console.log('No TMDB results found');
                        createPstreamButtonFallback(title, year);
                    }
                } catch (error) {
                    console.error('Error parsing TMDB response:', error);
                    createPstreamButtonFallback(title, year);
                }
            },
            onerror: function(error) {
                console.error('Error fetching TMDB data:', error);
                createPstreamButtonFallback(title, year);
            }
        });
    }

    function createPstreamButton(tmdbId, title) {
        // Create slug from title
        const slug = title.toLowerCase()
            .replace(/[^a-z0-9\s-]/g, '')
            .replace(/\s+/g, '-')
            .replace(/-+/g, '-')
            .replace(/^-|-$/g, '');

        const pstreamUrl = `https://pstream.mov/media/tmdb-movie-${tmdbId}-${slug}`;

        addButtonToPage(pstreamUrl, 'Watch on P-Stream');
    }

    function createPstreamButtonFallback(title, year) {
        // Create a generic search URL or best guess
        const slug = title.toLowerCase()
            .replace(/[^a-z0-9\s-]/g, '')
            .replace(/\s+/g, '-')
            .replace(/-+/g, '-')
            .replace(/^-|-$/g, '');

        const pstreamUrl = `https://pstream.mov/search?q=${encodeURIComponent(title + ' ' + year)}`;

        addButtonToPage(pstreamUrl, 'Search on P-Stream');
    }

    function addButtonToPage(url, text) {
        // Find the actions panel ul
        const actionsList = document.querySelector('.actions-panel ul, .js-actions-panel ul');

        if (!actionsList) {
            console.log('Could not find actions panel list');
            return;
        }

        // Create li element to match Letterboxd structure
        const listItem = document.createElement('li');
        listItem.className = 'pstream-action';

        // Create the link
        const link = document.createElement('a');
        link.href = url;
        link.target = '_blank';
        link.textContent = text;

        // Add the link to the list item
        listItem.appendChild(link);

        // Add to the actions list
        actionsList.appendChild(listItem);

        console.log('Added P-Stream button:', url);
    }

    // Add CSS to match Letterboxd's design
    GM_addStyle(`
        .pstream-action {
            color: var(--content-color) !important;
            background-color: #456 !important;
            text-align: center !important;
            border-bottom: 1px solid #2C3440 !important;
            padding: 10px 0 !important;
            box-sizing: border-box !important;
        }

        .pstream-action a {
            color: var(--content-color) !important;
            text-decoration: none !important;
            display: block !important;
            width: 100% !important;
            height: 100% !important;
        }

        .pstream-action a:hover {
            color: var(--content-color) !important;
        }
    `);

})();