// ==UserScript== // @name Trakt.tv Universal Search (Anime and Non-Anime) // @namespace http://tampermonkey.net/ // @version 1.0 // @description Search for anime on hianime.to and non-anime content on 1flix.to from Trakt.tv // @author konvar // @match https://trakt.tv/* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect hianime.to // @connect 1flix.to // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; const HIANIME_BASE_URL = 'https://hianime.to'; const FLIX_BASE_URL = 'https://1flix.to'; const TOP_RESULTS = 10; const SIMILARITY_THRESHOLD = 0.4; const EPISODE_TITLE_SIMILARITY_THRESHOLD = 0.8; const MAX_SEARCH_PAGES = 1; GM_addStyle(` .trakt-universal-search-button { display: flex; align-items: center; justify-content: center; margin-bottom: 10px; /* Remove default button styles */ background: none; border: none; padding: 0; cursor: pointer; /* Maintain pointer cursor */ } .trakt-universal-search-button:hover { /* Prevent white glow on hover */ box-shadow: none; } .trakt-universal-search-button img { max-height: 30px; width: auto; } `); class ContentInfo { constructor(title, year, isAnime, season, episode, episodeTitle, alternativeTitles, contentType, absoluteEpisode) { this.title = title; this.year = year; this.isAnime = isAnime; this.season = season; this.episode = episode; this.episodeTitle = episodeTitle; this.alternativeTitles = alternativeTitles; this.contentType = contentType; this.absoluteEpisode = absoluteEpisode; } static fromDOM() { let titleElement, yearElement; if (window.location.pathname.startsWith('/movies/')) { const movieTitleElement = document.querySelector('h1'); if (movieTitleElement) { titleElement = movieTitleElement.childNodes[0]; yearElement = movieTitleElement.querySelector('.year'); } } else { titleElement = document.querySelector('h2 a[data-safe="true"]'); } const episodeElement = document.querySelector('h1.episode .main-title-sxe'); const episodeTitleElement = document.querySelector('h1.episode .main-title'); const episodeAbsElement = document.querySelector('h1.episode .main-title-abs'); const genreElements = document.querySelectorAll('.genres .btn'); const additionalStats = document.querySelector('ul.additional-stats'); const alternativeTitleElement = document.querySelector('.additional-stats .meta-data[data-type="alternative_titles"]'); if (titleElement) { const title = titleElement.textContent.trim().replace(/[:.,!?]+$/, ''); const episodeInfo = episodeElement ? episodeElement.textContent.trim().split('x') : null; const season = episodeInfo ? parseInt(episodeInfo[0]) : null; const episode = episodeInfo ? parseInt(episodeInfo[1]) : null; const episodeTitle = episodeTitleElement ? episodeTitleElement.textContent.trim() : null; const absoluteEpisode = episodeAbsElement ? parseInt(episodeAbsElement.textContent.trim().replace(/[\(\)]/g, '')) : null; const genres = Array.from(genreElements).map(el => el.textContent.trim().toLowerCase()); const isAnime = genres.includes('anime') || (additionalStats && additionalStats.textContent.toLowerCase().includes('anime')) || document.querySelector('.poster img[src*="anime"]') !== null; let year; if (yearElement) { year = yearElement.textContent.trim(); } else if (additionalStats) { const yearMatch = additionalStats.textContent.match(/(\d{4})/); year = yearMatch ? yearMatch[1] : null; } const alternativeTitles = alternativeTitleElement ? alternativeTitleElement.textContent.split(',').map(t => t.trim()) : []; const contentType = window.location.pathname.startsWith('/movies/') ? 'movie' : 'tv'; return new ContentInfo(title, year, isAnime, season, episode, episodeTitle, alternativeTitles, contentType, absoluteEpisode); } return null; } } class SearchButton { constructor(contentInfo) { this.contentInfo = contentInfo; this.button = this.createButton(); } createButton() { const button = document.createElement('button'); // Changed from to