old.myshows.me

С 1 мая 2024 года отключают old.myshows.me. Под ручку с ChatGPT попытался починить нужные мне места.

Od 26.04.2024.. Pogledajte najnovija verzija.

// ==UserScript==
// @name         old.myshows.me
// @namespace    http://tampermonkey.net/
// @version      2024-04-26-1
// @description  С 1 мая 2024 года отключают old.myshows.me. Под ручку с ChatGPT попытался починить нужные мне места.
// @             Желательно использовать вместе с внешним видом от другого энтузиаста: https://userstyles.world/style/15722/old-myshows-me (инструкцию ищите там же)
// @author       SanBest93
// @match        https://myshows.me/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=myshows.me
// @grant        none
// @license      MIT
// ==/UserScript==

'use strict';

const originalTitleIsNeeded = true; // Всегда выводить оригинальные названия на странице 'https://myshows.me/profile/next/'. Не надо — замените на false
const postfix = '1080'; // Текст после s01e01. Мне так удобнее на торрентах искать. Не надо — замените на ''

let myMap = new Map(); // Сюда будем складывать соответствие showId — titleOriginal
let changed = 0; // Для проверки количества внесённых изменений
let changeIsNeeded = true; // Так мне просто понятнее
let STYLE;

// Запоминаем userName
let userName;
const headerLogin = document.querySelector('div.HeaderLogin__username');
if (headerLogin) userName = headerLogin.textContent;

// Создаем объект для хранения данных о шоу
class ShowData {
    constructor(index, showTitle, episodeInfo, innerHTML) {
        this.index = index;
        this.showTitle = showTitle;
        this.episodeInfo = episodeInfo;
        this.innerHTML = innerHTML;
    }
}

// Получение содержимого <script> и преобразование его в объект JavaScript
function parseScriptData() {
    const scriptElement = document.getElementById('__NUXT_DATA__');
    if (!scriptElement) return null;
    const dataString = scriptElement.textContent;
    return JSON.parse(dataString);
}

// Обработчик события загрузки всего DOM
window.addEventListener('load', onPageLoad());

// Функция, которая будет выполняться после загрузки всего DOM
function onPageLoad() {
    // Создаём новый экземпляр MutationObserver
    var observer = new MutationObserver(function(mutations) {
        // При каждом изменении DOM вызываем функцию checkPage
        checkPage();
    });
    // Настраиваем наблюдение за изменениями DOM
    observer.observe(document.body, {
        subtree: true,
        childList: true
    });
}

// Функция, которая будет выполняться при изменении DOM
function checkPage() {
    // Для /profile/next/ ---->
    // Проверяем, является ли текущий URL страницей 'https://myshows.me/profile/next/'
    if (!linksAreSimilar(window.location.href, 'https://myshows.me/profile/next/') && changeIsNeeded !== true) {
        changeIsNeeded = true;
        changed = 0;
    }
    if (linksAreSimilar(window.location.href, 'https://myshows.me/profile/next/') && changeIsNeeded === true) {
        // Не знаю, какими стандартными функциями получаются данные.
        if (originalTitleIsNeeded === true) {
            // Создаём свою карту данных
            createMap();
        }
        if (myMap.size > 0 || originalTitleIsNeeded === false) {
            fixWatchSoonElements();
        }
        changeIsNeeded = changed === 0;
    }
    // <---- Для /profile/next/

    // Для /username/ ---->
    if (linksAreSimilar(window.location.href, 'https://myshows.me/' + userName)) {
        fixProfileNumbers();
    }
    // <---- Для /username/

    // Меняем стили через CSS
    initStyle();
}

// Создание своей карты соответствия данных
function createMap() {
    const dataObject = parseScriptData();
    if (!dataObject) return;
    let showIDs; // Массив сериалов
    let iShowIDs; // Адрес массива сериалов
    // Получаем нужные данные из объекта
    if (dataObject.length > 0) {
        // Тут будут нужные данные, если изначально была открыта страница 'https://myshows.me/profile/next/'
        iShowIDs = findKeyInArray(dataObject, 'list', 30);
        if (!iShowIDs) {
            // Тут будут нужные данные, если изначально была открыта страница 'https://myshows.me/<имя профиля>'
            iShowIDs = findKeyInArray(dataObject, 'userShows', 30);
        }
        // Проверяем заполненность. Если пусто, то
        if (!iShowIDs) {
            // Перезагружаем страницу.
            // Тогда точно попадутся правильные данные в '__NUXT_DATA__'
            location.reload();
            return;
        }
        // Если нашли адрес, получаем список сериалов
        showIDs = dataObject[iShowIDs];
    }

    // Перебираем полученный массив сериалов, заполняем карту.
    // На всякий случай с попыткой
    if (showIDs && typeof showIDs.forEach === 'function') {
        showIDs.forEach(element => {
            try {
                const show = dataObject[element].show;
                myMap.set(dataObject[dataObject[show].id].toString().trim(), dataObject[dataObject[show].titleOriginal].trim());
            } catch (error) {
                console.error('[скрипт old.myshows.me] Ошибка в createMap():', error);
            }
        });
    }
}

// Исправляем текст, который стал в несколько строк с последним обновлением сайта.
// И сортируем строки
function fixWatchSoonElements() {
    const watchSoonElements = document.querySelectorAll('.WatchSoon__title-wrapper');
    if (!watchSoonElements) return;

    //const dataMap = createDataMap(parseScriptData());
    //if (!dataMap.size) return;

    const showsData = []; // Массив объектов для сортировки данных о шоу и эпизодах
    let index = -1; // Индекс для сортировки по дням
    let prevWatchSoon_left = ''; // Для проверки, что текст сменился

    // Заполняем массив объектами на основе данных на странице
    watchSoonElements.forEach(element => {
        const showLink = element.querySelector('.WatchSoon-show');
        const episodeLink = element.querySelector('.WatchSoon-episode');
        if (!showLink || !episodeLink || !episodeLink.textContent.includes(' - ')) return;

        // Находим родительский элемент с классом ".WatchSoon-left"
        let parent = element;
        while (parent) {
            if (parent.querySelector('.WatchSoon-left')) break;
            parent = parent.parentElement;
        }
        if (!parent) return; // Если не найден родительский элемент, выходим

        // Добавим признак группировки из правой колонки (да, я вижу, что в коде она называется left)
        const WatchSoon_left = parent.querySelector('.WatchSoon-left').textContent.trim();
        if (WatchSoon_left !== prevWatchSoon_left) {
            prevWatchSoon_left = WatchSoon_left;
            index++;
        }

        const showTitle = originalTitleIsNeeded === true ? fixTitle(showLink) : showLink.textContent;
        const episodeInfo = fixEpisodeInfo(episodeLink.textContent);
        const innerHTML = `<a href="${showLink.href}" target="_blank">${showTitle}</a> — ${episodeInfo.se} — <a href="${episodeLink.href}" target="_blank">${episodeInfo.name}</a>`;

        // Добавляем данные в массив объектов
        showsData.push(new ShowData(index, showTitle, episodeInfo.se, innerHTML));
    });

    // Сортируем массив по index, затем по showText, затем по episodeInfo
    showsData.sort((a, b) => {
        if (a.index !== b.index) return a.index - b.index;
        if (a.showTitle !== b.showTitle) return a.showTitle.localeCompare(b.showTitle);
        return a.episodeInfo.localeCompare(b.episodeInfo);
    });

    // Вставляем элементы на основе отсортированных данных
    showsData.forEach((data, index) => {
        const newElement = document.createElement('div');
        newElement.innerHTML = data.innerHTML;
        newElement.classList.add('OldMyShowsClass');
        // (описание классов см. в initStyle())

        const parent = watchSoonElements[index];
        if (parent) {
            parent.parentNode.insertBefore(newElement, parent.nextSibling);
            changed++;
        }
    });
}

// Замена названия на оригинальное
function fixTitle(show) {
    if (myMap.size > 0) {
        const parts = show.pathname.split("/");
        const showId = parts[parts.length - 2];
        const titleOriginal = myMap.get(showId);
        if (titleOriginal && titleOriginal !== '') return titleOriginal;
    }
}

// Получаем из строки "1 x 1 - название эпизода" данные {se: 's01e01', name: 'название эпизода'}
function fixEpisodeInfo(episodeText) {
    const [season, x, episode, ...rest] = episodeText.split(' ').map(str => str.trim());
    const s = addPrefix(season, 's');
    const e = `${addPrefix(episode, 'e')}${(postfix === '' ? '' : ` ${postfix}`)}`;
    let name = rest.join(' ').trim();
    if (name.startsWith('-')) {
        name = name.replace('-', '').trim();
    }
    return {
        se: `${s}${e}`,
        name: name
    };
}

// Добавление префикса S или E к номеру сезона или серии
function addPrefix(text, prefix) {
    if (!isNaN(text) && !text.toLowerCase().startsWith(prefix)) {
        const num = +text;
        text = (num < 10 ? `${prefix}0` : prefix) + text;
    }
    return text;
}


// Общая функция для поиска ключа в массиве данных
function findKeyInArray(data, key, N) {
    const end = N === undefined ? data.length : Math.min(N, data.length); // Количество первых
    let result;
    for (let i = 0; i < end; i++) {
        // Проверяем, является ли элемент объектом и содержит ли указанный ключ
        if (typeof data[i] === 'object' && !!data[i] && key in data[i]) {
            // Если да, выводим значение ключа и прерываем цикл
            result = data[i][key];
            break;
        }
    }
    return result;
}


// Проверка, что ссылки одинаковые
function linksAreSimilar(link1, link2) {
    // Удаляем последний слеш, если он есть
    link1 = link1.endsWith('/') ? link1.slice(0, -1) : link1;
    link2 = link2.endsWith('/') ? link2.slice(0, -1) : link2;
    // Сравниваем ссылки
    return link1 === link2;
}


// Смена чисел профиля с "1к" на "1 000"
function fixProfileNumbers() {
    // Выбираем все div с классом UserHeader__stats-value на странице
    const statsValues = document.querySelectorAll('div.UserHeader__stats-value');
    if (statsValues && typeof statsValues.forEach === 'function' && statsValues.lenght !== 0) {
        // Перебираем коллекцию элементов и меняем их содержимое
        statsValues.forEach(element => {
            element.textContent = element.title;
        });
    }
}


// Внешний вид стилей попробуем менять через CSS
function initStyle() {
    // Получить существующий элемент <style>
    STYLE = document.querySelector('style');
    if (!STYLE) {
        // Создать новый элемент <style> и установить его содержимое
        STYLE = document.createElement('style');
        // Вставить новый элемент <style> в <head>
        document.head.appendChild(STYLE);
    }
    const statsRowColor = 'white';
    const hideWatchSoon = changed === 0 ? '' : '.WatchSoon__title-wrapper {display: none;}';

    STYLE.textContent += /*CSS*/ `
${hideWatchSoon}

.OldMyShowsClass {
    font-size: 14px;
}

.UserHeader__stats-row {
    text-shadow:
            -1px -1px 0 black,
             1px -1px 0 black,
            -1px  1px 0 black,
             1px  1px 0 black;
    color: ${statsRowColor};
}

.UserHeader__stats-title {
    color: ${statsRowColor}
}

.Unwatched-season ~ div .UnwatchedEpisodeItem {
    height: 30px;
}

.WatchSoon-episodes .Row {
    height: 30px;
}

`.replace(/;/g, '!important;');
}