Letterboxd++

Add customized search links with favicons to Letterboxd movie pages

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name        Letterboxd++
// @namespace   https://github.com/shmup/lbpp
// @description Add customized search links with favicons to Letterboxd movie pages
// @author      Jared Miller (shmup)
// @license     GPL-3.0-or-later
// @match       https://letterboxd.com/film/*
// @grant       none
// @run-at      document-end
// @version     1.76
// ==/UserScript==

(function () {
  "use strict";

  const styles = `
  .link-container {
    margin-top: 10px;
    padding: 10px;
    display: inline-block;
  }
  .links-flex {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }
  .custom-link {
    align-items: center;
    background-color: #445566;
    border-radius: 3px;
    color: #bcd;
    display: flex;
    font-weight: bold;
    padding: 5px 10px;
    text-decoration: none;
    transition: background-color 0.2s, color 0.2s;
  }
  .custom-link:hover {
    color: #fff;
    background-color: #506070;
  }
  .custom-link:active {
    color: #bcd;
    background-color: #3a4a5a;
  }
  .custom-link img {
    width: 16px;
    height: 16px;
    margin-right: 5px;
  }
`;

  function getMovieTitle() {
    // Updated selector to match actual HTML structure
    const titleElement = document.querySelector(
      ".headline-1.primaryname .name",
    );
    return titleElement
      ? titleElement.textContent.trim().replace(/\s+/g, " ")
      : null;
  }

  function getReleaseYear() {
    // Updated selector to match actual HTML structure
    const yearElement = document.querySelector(".releasedate a");
    return yearElement ? yearElement.textContent.trim() : null;
  }

  function createLink(href, text, icon) {
    const domain = new URL(href).hostname;
    const faviconUrl =
      icon || `https://www.google.com/s2/favicons?domain=${domain}`;

    const link = document.createElement("a");
    link.href = href;
    link.target = "_blank";
    link.classList.add("custom-link");
    link.title = `Search on ${domain}`;

    const img = document.createElement("img");
    img.src = faviconUrl;
    img.onerror = () => {
      link.textContent = `🔍 ${text}`;
    };

    link.appendChild(img);

    const span = document.createElement("span");
    span.textContent = text;

    link.appendChild(span);

    return link;
  }

  function addLinks(links) {
    // Updated to find a more reliable insertion point
    const productionInfo = document.querySelector(".productioninfo");
    if (!productionInfo) {
      console.error("Could not find productioninfo element");
      return;
    }

    const linkContainer = document.createElement("div");
    linkContainer.classList.add("link-container");

    const flexContainer = document.createElement("div");
    flexContainer.classList.add("links-flex");

    links.forEach(({ href, text, icon = "" }) => {
      const link = createLink(href, text, icon);
      flexContainer.appendChild(link);
    });

    linkContainer.appendChild(flexContainer);
    productionInfo.insertAdjacentElement("afterend", linkContainer);
  }

  const movieTitle = getMovieTitle();
  const releaseYear = getReleaseYear();

  console.debug("Movie title:", movieTitle);
  console.debug("Release year:", releaseYear);

  if (!movieTitle || !releaseYear) {
    console.error("Could not find movie title or release year");
    return;
  }

  const q = `${movieTitle} ${releaseYear}`;
  const searchQueries = {
    DEFAULT: q,
    WIKI: `${movieTitle} (${releaseYear} film)`,
    YT: `${q} trailer`,
    IA: `title:(${movieTitle}) AND mediatype:(movies)`,
  };

  const links = [
    {
      text: "YT",
      href: `https://www.youtube.com/results?search_query=${encodeURIComponent(searchQueries.YT)}`,
    },
    {
      text: "IA",
      href: `https://archive.org/search.php?query=${encodeURIComponent(searchQueries.IA)}`,
    },
    {
      text: "WIKI",
      href: `https://en.wikipedia.org/w/index.php?search=${encodeURIComponent(searchQueries.WIKI)}&ns0=1`,
    },
    {
      text: "TD",
      icon: "https://www.torrentday.com/favicon.ico",
      href: `https://www.torrentday.com/t?q=${encodeURIComponent(searchQueries.DEFAULT)};o=seeders&qf=#torrents`,
    },
    {
      text: "TPB",
      href: `https://thepiratebay.org/search.php?q=${encodeURIComponent(searchQueries.DEFAULT)}&video=on&search=Pirate+Search&page=0&orderby=`,
    },
    {
      text: "BTDIG",
      href: `https://btdig.com/search?q=${encodeURIComponent(searchQueries.DEFAULT)}`,
    },
    {
      text: "BITSEARCH",
      href: `https://bitsearch.to/search?q=${encodeURIComponent(searchQueries.DEFAULT)}`,
    },
  ];

  document.head.appendChild(
    Object.assign(document.createElement("style"), { textContent: styles }),
  );
  addLinks(links);
})();