// ==UserScript==
// @name old.myshows.me
// @namespace http://tampermonkey.net/
// @version 2024-v29
// @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==
(function () {
'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;
}
}
// Проверка, что ссылки одинаковые
function linksAreSimilar(link1, link2) {
// Удаляем последний слеш, если он есть
const link1mod = link1.endsWith('/') ? link1.slice(0, -1) : link1;
const link2mod = link2.endsWith('/') ? link2.slice(0, -1) : link2;
// Сравниваем ссылки
return link1mod === link2mod;
}
// Общая функция для поиска ключа в массиве данных
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 += 1) {
// Проверяем, является ли элемент объектом и содержит ли указанный ключ
if (typeof data[i] === 'object' && !!data[i] && key in data[i]) {
// Если да, выводим значение ключа и прерываем цикл
result = data[i][key];
break;
}
}
return result;
}
// Замена названия на оригинальное
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;
}
}
// Добавление префикса 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;
}
// Получаем из строки "1 x 1 - название эпизода" данные {se: 's01e01', name: 'название эпизода'}
function fixEpisodeInfo(episodeText) {
const [season,, 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
};
}
// Исправляем текст, который стал в несколько строк с последним обновлением сайта.
// И сортируем строки
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 prevWatchSoonLeft = ''; // Для проверки, что текст сменился
// Заполняем массив объектами на основе данных на странице
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 WatchSoonLeft = parent.querySelector('.WatchSoon-left').textContent.trim();
if (WatchSoonLeft !== prevWatchSoonLeft) {
prevWatchSoonLeft = WatchSoonLeft;
index += 1;
}
const showTitle = originalTitleIsNeeded === true ? fixTitle(showLink) : showLink.textContent;
const episodeInfo = fixEpisodeInfo(episodeLink.textContent);
const innerHTML = `<a href="${showLink.href}" target="_blank">${showTitle}</a><span> — ${episodeInfo.se} — </span><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 += 1;
}
});
// Меняем стили через CSS
initStyle();
}
// Получение содержимого <script> и преобразование его в объект JavaScript
function parseScriptData() {
const scriptElement = document.getElementById('__NUXT_DATA__');
if (!scriptElement) return null;
const dataString = scriptElement.textContent;
return JSON.parse(dataString);
}
// Создание своей карты соответствия данных
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);
}
});
}
}
// Смена чисел профиля с "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;
});
}
}
// Функция, чтобы оставить ссылку только в русском названии шоу на странице myshows.me/profile/
function modifyShowTitleLink() {
let showTitles = document.querySelectorAll('a.Unwatched-showTitle');
if (!showTitles) return;
let newElement;
showTitles.forEach(function(title) {
// Получаем значение href из элемента с классом 'Unwatched-showTitle'
const hrefValue = title.getAttribute('href');
// Создаем новый элемент <div>
newElement = document.createElement('div');
newElement.className = 'Unwatched-showTitle';
// Перебираем все дочерние элементы элемента <a>
while (title.firstChild) {
// Перемещаем каждый дочерний элемент из <a> в новый <div>
newElement.appendChild(title.firstChild);
}
// Заменяем элемент <a> на новый элемент <div>
title.parentNode.replaceChild(newElement, title);
// Ищем внутри нового элемента элементы с классом "Unwatched-showTitle-title" и заменяем их на ссылки
let titleElements = newElement.querySelectorAll('span.Unwatched-showTitle-title');
if (!titleElements) return;
titleElements.forEach(function(titleElement) {
newElement = document.createElement('a');
newElement.href = hrefValue;
newElement.className = 'Unwatched-showTitle-title';
newElement.innerHTML = titleElement.innerHTML;
// Заменяем элемент <span> на новый элемент <a>
titleElement.parentNode.replaceChild(newElement, titleElement);
changed += 1;
});
});
}
// Функция замены "5 эпизодов с e1" на "5 эпизодов с e01" на странице myshows.me/profile/
function modifyShowSeasonMeta() {
// Находим все элементы <div> с классом "Unwatched-showSeasonMeta"
const elements = document.querySelectorAll('div.Unwatched-showSeasonMeta');
const regex = / с e(0(?=$)|[1-9]\d*$)/;
// Перебираем каждый элемент
elements.forEach(function(element) {
// Получаем текстовое содержимое элемента
let text = element.textContent;
// Ищем подстроку " с e" и последующей цифрой
const match = text.match(regex);
// Если подстрока найдена и цифра меньше 10
if (match && parseInt(match[1], 10) < 10) {
// Заменяем найденную цифру на "0" + цифра
const newText = ` с e0${match[1]}`;
// Устанавливаем новый текстовый контент элемента
element.textContent = text.replace(match[0], newText);
}
});
}
// Внешний вид стилей попробуем менять через 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*/ `
/*===============================================================================================*/
/*https://myshows.me/userName*/
.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}
}
/*===============================================================================================*/
/*https://myshows.me/profile/next*/
${hideWatchSoon}
.OldMyShowsClass {
font-size: 14px;
}
.WatchSoon-episodes .Row {
height: 30px;
padding: 0 10px 0 10px;
}
.WatchSoon-date {
max-width: 43px;
font-weight: 500;
font-size: 13px;
}
.WatchSoon-date a {
display: flex;
flex-wrap: wrap;
gap: 0 4px;
}
.WatchSoon-date div:first-child, .WatchSoon-show {
font-size: 14px;
}
.WatchSoon-date a div:first-child::after {
content: ',';
}
/*===============================================================================================*/
/*https://myshows.me/profile*/
.Unwatched-showTitle-inline {
display: inline-flex;
}
.Unwatched-showTitle-subTitle {
display: none;
}
.Unwatched-showTitle-title {
align-self: auto;
padding-right: 10px;
}
.Unwatched-season ~ div .UnwatchedEpisodeItem {
height: 30px;
}
.UnwatchedEpisodeItem__info {
display: contents;
}
`.replace(/;/g, '!important;');
}
// Функция, которая будет выполняться при изменении 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/
// Для /profile/ ---->
if (linksAreSimilar(window.location.href, 'https://myshows.me/profile')) {
//modifyShowTitleLink();
modifyShowSeasonMeta();
}
// <---- Для /profile/
// Меняем стили через CSS
initStyle();
}
// Функция, которая будет выполняться после загрузки всего DOM
function onPageLoad() {
// Создаём новый экземпляр MutationObserver
var observer = new MutationObserver(function() {
// При каждом изменении DOM вызываем функцию checkPage
checkPage();
});
// Настраиваем наблюдение за изменениями DOM
observer.observe(document.body, {
subtree: true,
childList: true
});
// Меняем стили через CSS
initStyle();
}
// Обработчик события загрузки всего DOM
window.addEventListener('load', onPageLoad());
})();