Suche auf BS

try to take over the world!

// ==UserScript==
// @name         Suche auf BS
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  try to take over the world!
// @author       You
// @match        https://bs.to/andere-serien
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Get the search term from the URL hash
    const hash = location.hash;
    if (hash !== undefined && hash.length > 0) {
        let searchTerm = JSON.parse(decodeURIComponent(hash.substr(1))).search;
        searchTerm = removeSeasons(searchTerm);
        // Initialize an array to hold the search results
        const searchResults = [];

        // Select and format the link elements from the page
        const searchList = collectLinkElements();

        // Perform a fuzzy search on each search term
        searchResults.push(...go(searchTerm, searchList));

        // Find the best search result
        const bestMatch = findBestMatch(searchResults);

        // If there are any results, open the highest scoring result in a new tab
        if (bestMatch) {
            window.open(bestMatch.obj.href, "_self", "", true);
        } else {
            // If there are no results, set the search input to the search term and search
            document.getElementById("serInput").value = searchTerm.join(" ");
            searchSeries();
        }
    }
})();

function findBestMatch(searchResults) {
    let bestMatch = {
        score: 0,
        probability: 0,
        searchTerm: '',
        target: {},
        obj: {}
    };
    if(searchResults.length == 1){
        bestMatch = {
            score: searchResults[0].score,
            probability: 1,
            searchTerm: searchResults[0].searchTerm,
            target: searchResults[0].target,
            obj: searchResults[0].obj
        };
        return bestMatch;
    }
    for (const result of searchResults) {
        const probability = result.score * (1 - (result.target.length / result.searchTerm.length));
        // Calculate the probability of this result being the correct match
        if(result.score == 1){
            if(bestMatch.score != 1){
                bestMatch = {
                    score: result.score,
                    probability: 1,
                    searchTerm: result.searchTerm,
                    target: result.target,
                    obj: result.obj
                };
                continue;
            }else{
                if(result.target.length > bestMatch.target.length){
                    bestMatch = {
                        score: result.score,
                        probability: 1,
                        searchTerm: result.searchTerm,
                        target: result.target,
                        obj: result.obj
                    };
                    continue;
                }
            }
        }
        if (probability >= bestMatch.probability) {
            bestMatch = {
                score: result.score,
                probability: probability,
                searchTerm: result.searchTerm,
                target: result.target,
                obj: result.obj
            };
        }
    }

    return bestMatch;
}

function removeSeasons(arr) {
  // First, we will use a regular expression to remove the " Xth Season" or " Season X" portion from the strings
  const processedArr = arr.map(str => str.replace(/\s(Season|Season)\s\d+|\s\d+(th|st|rd|nd)\sSeason/i, ''));

  // Next, we will use another regular expression to remove the "Part X" portion from the strings
  const intermediateArr = processedArr.map(str => str.replace(/\sPart\s\d+/i, ''));

  // Use a regular expression to remove Roman numerals from the strings
  const intermediateTwoArr = intermediateArr.map(str => str.replace(/\bM{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})\b/g, ''));

  // Split the strings on the ':' character and create new entries for each part
  const splitArr = intermediateTwoArr.flatMap(str => {
    const splitStrings = str.split(':');
    return [...splitStrings, str];
  });

  // Remove any entries that are shorter than 4 letters
  const filteredArr = splitArr.filter(str => str.length >= 4);

  // Finally, we will use another regular expression to remove any numbers from the end of the strings, and we will use the Set object to remove any duplicates from the array
  return [...new Set(filteredArr.map(str => str.replace(/\s\d+$/, '').trim()))];
}






// Helper function to search multiple keys
function go(search, targets) {
    // Perform a fuzzy search on each search term
    const searchResults = search.flatMap(term => {
        return fuzzySearch(term, targets);
    });

    // Remove duplicates from the search results
    const filteredResults = [...new Set(searchResults)];

    return filteredResults;
}


// Helper function to collect and format the link elements
function collectLinkElements() {
    const linkElements = document.querySelectorAll("#seriesContainer ul li a");
    const searchList = [];
    linkElements.forEach(element => {
        // Get the title from the href
        const searchHref = element.href.split('/').pop().replace(/-/g, ' ');
        if (element.title.includes('|')) {
            // Split the title at the '|' character and create an entry for each part
            const titles = element.title.split('|');
            titles.forEach(title => {
                searchList.push({
                    'title': title.trim(),
                    'href': element.href,
                    'searchHref': searchHref
                });
            });
        } else if (element.title.includes(':')) {
            // Split the title at the ':' character and create new entries for each part
            const titles = element.title.split(':');
            titles.forEach(title => {
                searchList.push({
                    'title': title.trim(),
                    'href': element.href,
                    'searchHref': searchHref
                });
            });
            searchList.push({
                'title': element.title,
                'href': element.href,
                'searchHref': searchHref
            });
        } else if (element.title.includes('-')) {
            let titletwo = element.title.replace('-', '');
             titletwo = titletwo.replace('  ', ' ');
                searchList.push({
                    'title': titletwo.trim(),
                    'href': element.href,
                    'searchHref': searchHref
                });
            searchList.push({
                'title': element.title,
                'href': element.href,
                'searchHref': searchHref
            });
        } else {
            searchList.push({
                'title': element.title,
                'href': element.href,
                'searchHref': searchHref
            });
        }
    });
    return searchList;
}

function fuzzySearch(searchTerm, targets) {
    const searchResults = [];

    for (const target of targets) {
        let targetArray = { Name: '', length:0}
        // Calculate the Levenshtein distance between the search term and the title
        let distance = levenshteinDistance(searchTerm, target.title);
        targetArray.Name = target.title;
        targetArray.length =  targetArray.Name.length;
        // Calculate the score for this search result
        let score = 1 - distance / Math.max(searchTerm.length, target.title.length);

        // If the title does not contain the search term or includes "...", search the searchHref
        if ((score === 0 || target.title.includes("...")) && target.searchHref.includes(searchTerm)) {
            // Calculate the Levenshtein distance between the search term and the searchHref
            distance = levenshteinDistance(searchTerm, target.searchHref);
            targetArray.Name = target.searchHref;
            targetArray.length =  targetArray.Name.length;
            // Calculate the score for this search result
            score = 1 - distance / Math.max(searchTerm.length, target.searchHref.length);
        }

        // Check if this search term is longer than any previous search terms that matched this target object
        const previousMatch = searchResults.find(result => result.obj === target);
        if (previousMatch) {
            if (searchTerm.length > previousMatch.searchTerm.length) {
                // If the search term is longer, replace the previous match with this one
                previousMatch.score = score;
                previousMatch.searchTerm = searchTerm;
            }
        } else if (score > 0.5) {
            // If the score is high enough, add the search result to the array
            searchResults.push({
                score: score,
                searchTerm: searchTerm,
                target: targetArray,
                obj: target
            });
        }
    }

    return searchResults;
}





function levenshteinDistance(a, b) {
    // Create a matrix with rows representing the characters in a and columns representing the characters in b
    const matrix = [];
    for (let i = 0; i <= a.length; i++) {
        matrix[i] = [i];
    }
    for (let j = 0; j <= b.length; j++) {
        matrix[0][j] = j;
    }

    // Loop through the characters in a and b and calculate the distances
    for (let i = 1; i <= a.length; i++) {
        for (let j = 1; j <= b.length; j++) {
            if (a.charAt(i - 1) === b.charAt(j - 1)) {
                matrix[i][j] = matrix[i - 1][j - 1];
            } else {
                matrix[i][j] = Math.min(
                    matrix[i - 1][j] + 1,
                    matrix[i][j - 1] + 1,
                    matrix[i - 1][j - 1] + 1
                );
            }
        }
    }

    // Return the distance
    return matrix[a.length][b.length];
}