// ==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;');
}