// ==UserScript==
// @name old.myshows.me
// @namespace http://tampermonkey.net/
// @version 2025-v33
// @description С 1 мая 2024 года обещали отключить old.myshows.me. Под ручку с нейросетями попытался починить нужные мне места.
// @ Желательно использовать вместе с внешним видом от другого энтузиаста: 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';
// Добавлены настройки на страницу, собсно, «Настройки» (https://myshows.me/profile/edit/)
// Сохраняются в локальное хранение. Удаляются при очистке кэша.
// Вписывайте ниже жёстко, если это критично
const CONFIG = {
OriginalTitleIsNeeded: getItem('OriginalTitleIsNeeded') // Всегда выводить оригинальные названия на странице 'https://myshows.me/profile/next/'
, ModifyS01E01: getItem('ModifyS01E01') // Замена "1 x 1" на "s01e01" (и ещё по мелочи) на странице 'https://myshows.me/profile/next/'
, s01e01Postfix: getItem('s01e01PostfixValue') // Текст после s01e01 на странице 'https://myshows.me/profile/next/'. Мне так удобнее на торрентах искать
, ModifyProfileNumbers: getItem('ModifyProfileNumbers') // Вывод полных чисел в шапке профиля
, ModifyShowSeasonMeta: getItem('ModifyShowSeasonMeta') // Замена "5 эпизодов с e1" на "5 эпизодов с e01" на странице 'https://myshows.me/profile/'
, ModifyShowTitleLink: getItem('ModifyShowTitleLink') // Ссылка только в русском названии шоу на странице 'https://myshows.me/profile/'
};
// Массив флагов выполнения функций
let MODS = {
profileNumbersModified: false
, showSeasonMetaModified: false
, showTitleLinkModified: false
, S01E01Modified: false
, watchSoonElementsModified: false
}
let myMap = new Map(); // Сюда будем складывать соответствие showId — titleOriginal
let changed = 0; // Для проверки количества внесённых изменений
let changeIsNeeded = true; // Так мне просто понятнее
let STYLE;
let lastUrl = location.href;
let observers = new Map(); // Глобальный объект для хранения наблюдателей
// Запоминаем userName
const userName = document.querySelector('div.HeaderLogin__username')?.textContent;
// Создаём объект для хранения данных о шоу
class ShowData {
constructor(index, showTitle, episodeInfo, innerHTML) {
this.index = index;
this.showTitle = showTitle;
this.episodeInfo = episodeInfo;
this.innerHTML = innerHTML;
}
}
// Получение настроек со страницы настроек по id
function getItem (id) {
const value = localStorage.getItem(id);
return value === 'true' ? true : value === 'false' ? false : value;
}
// Сброс флага по имена
function resetFlag(flagName) {
if (flagName in MODS) {
MODS[flagName] = false;
}
}
// Проверка смены страницы
function checkUrlChange() {
const currentUrl = location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
Object.keys(MODS).forEach(flagName => resetFlag(flagName));
}
}
// Функция для создания флажка
function createCheckbox(parent, id, labelText, addTextContent) {
// Создаём элемент метки (label)
const label = document.createElement('label');
label.className = 'oldMyshowsSettings-label'; // Устанавливаем класс метки
// Создаём элемент флажка (input)
const checkbox = document.createElement('input');
checkbox.className = 'oldMyshowsSettings-checkbox';
checkbox.type = 'checkbox';
checkbox.id = id;
checkbox.checked = getItem(id); // Устанавливаем состояние флажка на основе значения из localStorage
checkbox.addEventListener('change', function() { // Добавляем обработчик события изменения состояния флажка
localStorage.setItem(id, this.checked); // Сохраняем состояние флажка в localStorage
/*const element = document.getElementById(id);
element.checked = this.checked;*/
});
label.appendChild(checkbox); // Добавляем флажок в метку
// Создаём элемент span для текстового содержимого метки
const span = document.createElement('span');
span.innerText = labelText;
label.appendChild(span); // Добавляем span в метку
// Если требуется добавить дополнительное текстовое содержимое
if (addTextContent === true) {
const idValue = `${id}Value`; // Генерируем id для дополнительного текстового поля
const textContent = document.createElement('input');
textContent.className = 'oldMyshowsSettings-checkbox-textContent';
textContent.type = 'text';
textContent.id = idValue;
textContent.value = getItem(idValue); // Устанавливаем значение текстового поля на основе значения из localStorage
textContent.addEventListener('change', function() { // Добавляем обработчик события изменения значения текстового поля
localStorage.setItem(idValue, this.value); // Сохраняем значение текстового поля в localStorage
});
label.appendChild(textContent); // Добавляем текстовое поле в метку
}
parent.appendChild(label); // Добавляем метку в указанный родительский элемент
return label; // Возвращаем созданную метку
}
// Создание флажков на странице настроек
function createCheckboxes() {
const groupTitleTextContent = 'Настройки скрипта old.myshows.me';
// Проверяем, существует ли уже наша группа.
// Если уже существует, прекращаем выполнение функции
if (document.querySelector('.oldMyshowsSettings')) return;
const parentElement = document.querySelector('.mb-5'); // Элемент, в который мы хотим добавить новую группу
const thelastChild = parentElement?.lastChild; // Его последний дочерний элемент
if (!parentElement || !thelastChild) return;
// Создаём группу
let sectionAccordion = document.createElement('div');
sectionAccordion.classList.add('SectionAccordion');
// Создаём заголовок для группы
let sectionAccordionTitle = document.createElement('div');
sectionAccordionTitle.textContent = groupTitleTextContent;
sectionAccordionTitle.classList.add('SectionAccordion-title');
// Создаём контейнер для флажков
let checkboxesContainer = document.createElement('div');
checkboxesContainer.classList.add('oldMyshowsSettings');
// Создаём контейнер для стиля
let checkboxesStyle = document.createElement('div');
checkboxesStyle.classList.add('oldMyshowsSettings-style');
// Создаём флажки
createCheckbox(checkboxesStyle,
'ModifyShowTitleLink',
'«Мои сериалы»: оставить ссылку только в русском названии');
createCheckbox(checkboxesStyle,
'ModifyShowSeasonMeta',
'«Мои сериалы»: замена "N эпизодов с e1" на "N эпизодов с e01"');
createCheckbox(checkboxesStyle,
'ModifyProfileNumbers',
'«Профиль»: вывод полных чисел в шапке');
createCheckbox(checkboxesStyle,
'OriginalTitleIsNeeded',
'«Календарь»: всегда выводить оригинальные названия');
createCheckbox(checkboxesStyle,
'ModifyS01E01',
'«Календарь»: замена "1 x 1" на "s01e01" (и ещё по мелочи)');
createCheckbox(checkboxesStyle,
's01e01Postfix',
'«Календарь»: текст после s01e01',
true);
// Добавляем флажки в контейнер
checkboxesContainer.appendChild(checkboxesStyle);
checkboxesContainer.classList.toggle('hidden');
// Добавляем заголовок и контейнер с флажками в сворачиваемую группу
sectionAccordion.appendChild(sectionAccordionTitle);
sectionAccordion.appendChild(checkboxesContainer);
// Добавляем обработчик события клика на заголовок для переключения видимости контейнера с флажками
sectionAccordionTitle.addEventListener('click', function() {
checkboxesContainer.classList.toggle('hidden');
});
// Вставляем новую группу перед последним дочерним элементом
parentElement.insertBefore(sectionAccordion, thelastChild);
}
// Проверка применения настроек для /profile/edit/
function ensureSettingsPage() {
const currentUrl = window.location.href;
if (!linksAreSimilar(currentUrl, 'https://myshows.me/profile/edit')) return;
if (!document.querySelector('.oldMyshowsSettings')) {
createCheckboxes();
}
}
// Проверка, что ссылки одинаковые
function linksAreSimilar(link1, link2) {
// Сравниваем ссылки
return link1.replace(/\/$/, '') === link2.replace(/\/$/, '');
}
// Общая функция для поиска ключа в массиве данных
function findKeyInArray(data, key, N = data.length) {
for (let i = 0; i < Math.min(N, data.length); i += 1) {
// Проверяем, является ли элемент объектом и содержит ли указанный ключ
if (typeof data[i] === 'object' && !!data[i] && key in data[i]) {
// Если да, выводим значение ключа
return data[i][key];
}
}
return undefined;
}
// Функция для поиска значений в __NUXT_DATA__ по пути
function findValueByPath(data, path) {
// Разделяем путь на компоненты
let keys = path.split('.');
let index = 0;
let currentData = data[index];
// Проходимся по каждому компоненту пути
for (let key of keys) {
if (Array.isArray(currentData) && currentData.length > 0) {
// Если текущие данные являются массивом, то
// на 2024.06.18 структура такая, что вид ['Reactive', число]
let indexReactive = currentData[0].indexOf('Reactive') + 1; // Это число
if (!isNaN(indexReactive) && indexReactive > 0 && indexReactive < currentData.length) {
index = currentData[indexReactive];
currentData = data[index];
} else {
return undefined; // Если индекс некорректный или за пределами массива
}
}
if (typeof currentData === 'object' && currentData !== null) {
// Если текущие данные являются объектом
if (key in currentData) {
index = currentData[key];
currentData = data[index];
} else {
return undefined; // Если ключ не найден в текущем объекте
}
} else {
return undefined; // Если текущие данные не являются ни объектом, ни массивом
}
}
return currentData;
}
// Замена названия на оригинальное
// function fixTitle(show) {
// if (myMap.size > 0) {
// const showId = show.pathname.split("/").slice(-2)[0];
// return myMap.get(showId) || '';
// }
// return '';
// }
let lastRetryTime = 0;
function fixTitle(show, retry = true) {
if (myMap.size > 0) {
const showId = show.pathname.split("/").slice(-2)[0];
const title = myMap.get(showId) || '';
if (title === '' && retry && Date.now() - lastRetryTime > 5000) { // Пауза 5 секунд между попытками
console.log('[old.myshows.me] Title not found for showId:', showId, 'Retrying...');
lastRetryTime = Date.now();
// Пересоздаём myMap и повторяем попытку
createMap(() => {
const retryTitle = myMap.get(showId) || '';
console.log('[old.myshows.me] Retry result for showId:', showId, 'Title:', retryTitle);
return retryTitle;
}, 1); // Одна попытка для пересоздания
return myMap.get(showId) || ''; // Возвращаем результат после пересоздания
}
return title;
}
return '';
}
// Добавление префикса S или E к номеру сезона или серии
function addPrefix(text, prefix) {
const num = parseInt(text, 10);
return isNaN(num) ? text : `${prefix}${num < 10 ? '0' : ''}${num}`;
}
// Получаем из строки "1 x 1 - название эпизода" данные {se: 's01e01', name: 'название эпизода'}
function fixEpisodeInfo(episodeText) {
let se = '';
let name = '';
if (!episodeText) return { se, name };
if (CONFIG.ModifyS01E01) {
const parts = episodeText.split(' ');
if (parts.length >= 3) {
const season = parts[0];
const episode = parts[2];
const s = addPrefix(season, 's');
const e = `${addPrefix(episode, 'e')}${CONFIG.s01e01Postfix ? ` ${CONFIG.s01e01Postfix}` : ''}`;
se = `${s}${e}`;
name = parts.slice(3).join(' ').replace(/^-\s*/, '').trim();
}
} else {
const parts = episodeText.split(' - ');
if (parts.length >= 2) {
se = parts[0].trim();
name = parts.slice(1).join(' - ').trim();
} else {
se = episodeText.trim();
}
}
return { se, name };
}
// Исправляем текст, который стал в несколько строк с последним обновлением сайта.
// И сортируем строки
function fixWatchSoonElements() {
if (MODS.watchSoonElementsModified) return;
const watchSoonElements = document.querySelectorAll('.WatchSoon__title-wrapper');
if (!watchSoonElements) return;
// Проверяем, есть ли уже элементы OldMyShowsClass
const existingCustomElements = document.querySelectorAll('.OldMyShowsClass');
if (!!watchSoonElements.length && existingCustomElements.length >= watchSoonElements.length) {
MODS.watchSoonElementsModified = true; // Если элементы уже есть, считаем задачу выполненной
return;
}
const showsData = []; // Массив объектов для сортировки данных о шоу и эпизодах
let index = -1; // Индекс для сортировки по дням
let prevWatchSoonLeft = ''; // Для проверки, что текст сменился
let changes = 0;
// Заполняем массив объектами на основе данных на странице
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;
while (parent && !parent.querySelector('.WatchSoon-left')) {
parent = parent.parentElement;
}
if (!parent) return; // Если не найден родительский элемент, выходим
// Добавим признак группировки из правой колонки (да, я вижу, что в коде она называется left)
const watchSoonLeft = parent.querySelector('.WatchSoon-left').textContent.trim();
if (watchSoonLeft !== prevWatchSoonLeft) {
prevWatchSoonLeft = watchSoonLeft;
index += 1;
}
const showTitle = CONFIG.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));
// Скрываем исходный элемент
if (!element.classList.contains('hidden')) {
element.classList.add('hidden'); // Добавляем hidden только если его нет
}
});
// Сортируем массив по 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);
});
existingCustomElements.forEach(el => el.remove());
// Вставляем элементы на основе отсортированных данных
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);
changes += 1;
}
});
// Меняем стили через CSS
initStyle();
MODS.watchSoonElementsModified = changes !== 0;
changed = changes;
}
// Проверка применения настроек для /profile/next/
function ensureWatchSoonElements() {
const currentUrl = window.location.href;
if (!linksAreSimilar(currentUrl, 'https://myshows.me/profile/next/')) return;
const hasCustomElements = document.querySelectorAll('.OldMyShowsClass').length > 0;
const hasOriginalElements = document.querySelectorAll('.WatchSoon__title-wrapper:not(.hidden)').length > 0;
if (!hasCustomElements && !hasOriginalElements) {
resetFlag('watchSoonElementsModified');
fixWatchSoonElements();
}
}
// Получение содержимого <script> и преобразование его в объект JavaScript
function parseScriptData() {
const scriptElement = document.getElementById('__NUXT_DATA__');
return scriptElement ? JSON.parse(scriptElement.textContent) : null;
}
// Создание своей карты соответствия данных
function createMap(callback, attempts = 5) {
console.log('[old.myshows.me] Creating map, attempts left:', attempts, 'myMap size:', myMap.size);
if (attempts <= 0) {
console.warn('[old.myshows.me] Gave up waiting for __NUXT_DATA__');
if (callback) callback();
return;
}
const dataObject = parseScriptData();
if (!dataObject) {
setTimeout(() => createMap(callback, attempts - 1), 500);
return;
}
// Вариант 1: Структура с list или userShows
let iShowIDs = findKeyInArray(dataObject, 'list', 30) || findKeyInArray(dataObject, 'userShows', 30);// || findKeyInArray(dataObject, 'unwatched', 30);
if (iShowIDs) {
const showIDs = dataObject?.[iShowIDs];
if (Array.isArray(showIDs) && !!showIDs.length) {
showIDs.forEach(element => {
try {
const show = dataObject?.[element]?.show;
if (show && dataObject?.[show]?.id && dataObject?.[show]?.titleOriginal) {
myMap.set(
dataObject[dataObject[show].id].toString().trim(),
dataObject[dataObject[show].titleOriginal].trim()
);
}
} catch (error) {
console.error('[old.myshows.me] Ошибка создания карты:', error);
}
});
console.log('[old.myshows.me] Карта создана:', Array.from(myMap.entries()));
if (callback) callback();
return;
}
}
const profileShowsIdx = findKeyInArray(dataObject, 'profileShows');
if (profileShowsIdx) {
const profileShows = dataObject[profileShowsIdx];
const showFiltersIdx = profileShows?.showFilters;
if (showFiltersIdx && Array.isArray(dataObject[showFiltersIdx])) {
const m1 = dataObject[showFiltersIdx];
m1.forEach(m1Idx => {
const m1Data = dataObject[m1Idx];
if (!m1Data || !m1Data.shows) return;
const showsIdx = m1Data.shows;
if (showsIdx && Array.isArray(dataObject[showsIdx])) {
const m2 = dataObject[showsIdx];
m2.forEach(m2Idx => {
const show = dataObject[m2Idx];
if (show && show.id && show.titleOriginal) {
try {
myMap.set(
dataObject[show.id].toString().trim(),
dataObject[show.titleOriginal].trim()
);
} catch (error) {
console.error('[old.myshows.me] Error in showFilters structure mapping:', error);
}
}
});
}
});
console.log('[old.myshows.me] Map created (showFilters structure):', Array.from(myMap.entries()));
if (callback) callback();
return;
}
}
// Если ни одна структура не подошла, повторяем попытку
console.log('[old.myshows.me] No suitable structure found in __NUXT_DATA__:', dataObject);
setTimeout(() => createMap(callback, attempts - 1), 500);
}
// Смена чисел профиля с "1к" на "1 000"
function modifyProfileNumbers() {
if (MODS.profileNumbersModified) return;
// Выбираем все div с классом UserHeader__stats-row на странице
const statsRows = document.querySelectorAll('div.UserHeader__stats-row');
if (!statsRows.length) return;
const dataObject = parseScriptData();
const path1 = 'data.User.profile.stats.watchedEpisodes';
const path2 = 'data.User.profile.statsMovies.watchedMovies';
const path3 = 'data.User.profile.statsTotal.watchedHours';
// Ищем значения в __NUXT_DATA__. Могут быть в разных местах в зависимости от открытой страницы
let value1 = findValueByPath(dataObject, path1);
if (!value1) value1 = dataObject?.[findKeyInArray(dataObject, path1.split('.').pop())] ?? undefined;
if (!value1) return;
let value2 = findValueByPath(dataObject, path2);
if (!value2) value2 = dataObject?.[findKeyInArray(dataObject, path2.split('.').pop())] ?? undefined;
if (!value2) return;
let value3 = findValueByPath(dataObject, path3);
if (!value3) value3 = dataObject?.[dataObject?.[findKeyInArray(dataObject, 'statsTotal')]?.watchedHours] ?? undefined;
if (!value3) return;
let value4 = Math.ceil(value3 / 24);
// Сохраняем значения по ключам
const valueMap = {
э: value1 // эпизодов
, ф: value2 // фильмов
, ч: value3 // часов
, д: value4 // дней
};
let changes = 0;
// Перебираем коллекцию элементов и меняем их содержимое
statsRows.forEach(element => {
const valueElement = element.querySelector('.UserHeader__stats-value');
const titleElement = element.querySelector('.UserHeader__stats-title');
if (!valueElement || !titleElement) return;
// Получаем первую букву подписи
const key = titleElement.textContent.trim().charAt(0);
// Ищем такую в сохранённых
const value = valueMap[key];
// Если не пустая - присваиваем (без всяких привязок к классам и стилям, может потом)
if (value !== undefined) {
valueElement.textContent = Math.round(value).toLocaleString();
changes += 1;
}
});
MODS.profileNumbersModified = changes !== 0;
}
// Проверка применения настроек для /username/
function ensureProfileNumbers() {
const currentUrl = window.location.href;
if (!linksAreSimilar(currentUrl, `https://myshows.me/${userName}`)) return;
if (CONFIG.ModifyProfileNumbers && !MODS.profileNumbersModified) {
modifyProfileNumbers();
}
}
// Функция, чтобы оставить ссылку только в русском названии шоу на странице myshows.me/profile/
function modifyShowTitleLink() {
if (!CONFIG.ModifyShowTitleLink || MODS.showTitleLinkModified) return;
let showTitles = document.querySelectorAll('a.Unwatched-showTitle');
if (!showTitles) return;
let changes = 0;
showTitles.forEach(title => {
// Получаем значение href из элемента с классом 'Unwatched-showTitle'
const hrefValue = title.getAttribute('href');
// Создаём новый элемент <div>
const 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(titleElement => {
const newLink = document.createElement('a');
newLink.href = hrefValue;
newLink.className = 'Unwatched-showTitle-title';
newLink.innerHTML = titleElement.innerHTML;
// Заменяем элемент <span> на новый элемент <a>
titleElement.parentNode.replaceChild(newLink, titleElement);
changes += 1;
});
});
MODS.showTitleLinkModified = changes !== 0;
}
// Функция замены "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*$)/;
let changes = 0;
// Перебираем каждый элемент
elements.forEach(element => {
// Получаем текстовое содержимое элемента
const text = element.textContent;
// Ищем подстроку " с e" и последующей цифрой
const match = text.match(regex);
// Если подстрока найдена и цифра меньше 10
if (match && parseInt(match[1], 10) < 10) {
// Заменяем найденную цифру на "0" + цифра.
// Устанавливаем новый текстовый контент элемента
element.textContent = text.replace(match[0], ` с e0${match[1]}`);
changes += 1;
}
});
MODS.showSeasonMetaModified = changes !== 0;
}
// Проверка применения настроек для /profile/
function ensureProfileMods() {
const currentUrl = window.location.href;
if (!linksAreSimilar(currentUrl, 'https://myshows.me/profile')) return;
if (CONFIG.ModifyShowTitleLink && !MODS.showTitleLinkModified) {
modifyShowTitleLink();
}
if (CONFIG.ModifyShowSeasonMeta && !MODS.showSeasonMetaModified) {
modifyShowSeasonMeta();
}
}
// Внешний вид стилей попробуем менять через CSS
function initStyle() {
// Получить существующий / создать новый элемент <style>
STYLE = document.querySelector('style') || document.createElement('style');
if (!STYLE.parentNode) {
// Вставить новый элемент <style> в <head>
document.head.appendChild(STYLE);
}
const statsRowColor = 'white';
const hideWatchSoon = changed === 0 ? '' : '.WatchSoon__title-wrapper hidden';
// Не будем много раз добавлять одно и то же
const startPhrase = '/* old.myshows.me.start */';
const endPhrase = '/* old.myshows.me.end */';
// Находим индексы начала и конца текста между фразами
let styleContent = STYLE.textContent;
const startIndex = styleContent.indexOf(startPhrase);
const endIndex = styleContent.indexOf(endPhrase) + endPhrase.length;
if (startIndex !== -1 && endIndex !== -1) {
// Удаляем существующий текст между startPhrase и endPhrase
STYLE.textContent = (styleContent.substring(0, startIndex) + styleContent.substring(endIndex)).trim();
}
STYLE.textContent += /*CSS*/ `
${startPhrase}
.hidden {
display: none;
}
/* ============================================================================================= */
/* 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 */
.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 a div:first-child::after {
content: ',';
}
.WatchSoon-show {
font-size: 14px;
}
/* ============================================================================================= */
/* 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;
}
/* ============================================================================================= */
/* https://myshows.me/profile/edit */
.oldMyshowsSettings-style {
display: grid;
}
.oldMyshowsSettings-label {
display: inline-flex;
margin-top: 20px;
}
.oldMyshowsSettings-label>* {
margin-right: 7px; /* Увеличение отступа между полем флажка и текстом */
}
.oldMyshowsSettings-checkbox input[type="checkbox"] {
width: 10px; /* Ширина поля флажка */
height: 10px; /* Высота поля флажка */
}
${endPhrase}
`.replace(/;/g, '!important;');
}
// Функция, которая будет выполняться при изменении DOM
function checkPage() {
// console.log('[old.myshows.me] Checking page:', window.location.href);
const currentUrl = window.location.href;
// Сбрасываем все флаги при смене страницы
checkUrlChange();
// Для /profile/next/ ---->
if (linksAreSimilar(currentUrl, 'https://myshows.me/profile/next/')) {
if (CONFIG.OriginalTitleIsNeeded === true && myMap.size === 0) {
createMap(() => {
fixWatchSoonElements();
ensureWatchSoonElements();
setupObserver('.WatchSoon__title-wrapper', 'OriginalTitleIsNeeded', 'watchSoonElementsModified', fixWatchSoonElements);
});
} else {
fixWatchSoonElements();
ensureWatchSoonElements();
setupObserver('.WatchSoon__title-wrapper', 'OriginalTitleIsNeeded', 'watchSoonElementsModified', fixWatchSoonElements);
}
}
// <---- Для /profile/next/
// Для /username/ ---->
if (linksAreSimilar(currentUrl, `https://myshows.me/${userName}`)) {
if (CONFIG.ModifyProfileNumbers === true) {
modifyProfileNumbers();
ensureProfileNumbers();
}
setupObserver('div.UserHeader__stats-row', 'ModifyProfileNumbers', 'profileNumbersModified', modifyProfileNumbers);
}
// <---- Для /username/
// Для /profile/ ---->
if (linksAreSimilar(currentUrl, 'https://myshows.me/profile')) {
if (CONFIG.ModifyShowTitleLink === true) {
modifyShowTitleLink();
}
setupObserver('a.Unwatched-showTitle', 'ModifyShowTitleLink', 'showTitleLinkModified', modifyShowTitleLink);
if (CONFIG.ModifyShowSeasonMeta === true) {
modifyShowSeasonMeta();
}
setupObserver('div.Unwatched-showSeasonMeta', 'ModifyShowSeasonMeta', 'showSeasonMetaModified', modifyShowSeasonMeta);
}
// <---- Для /profile/
// Для /profile/edit/ ---->
if (linksAreSimilar(currentUrl, 'https://myshows.me/profile/edit')) {
createCheckboxes();
ensureSettingsPage();
}
// <---- Для /profile/edit/
// Меняем стили через CSS
initStyle();
}
// Универсальная функция для настройки наблюдателя
function setupObserver(targetName, configName, flagName, procedure) {
// Удаляем старые наблюдатели для этого targetName
if (observers.has(targetName)) {
observers.get(targetName).disconnect();
observers.delete(targetName);
}
const targetElements = document.querySelectorAll(targetName);
if (!targetElements.length) return;
const observer = new MutationObserver((mutations) => {
let shouldRun = false;
mutations.forEach(mutation => {
if (mutation.target.classList?.contains('OldMyShowsClass') ||
mutation.oldValue?.includes('hidden') ||
mutation.target.classList?.contains('hidden')) {
return;
}
shouldRun = true;
});
if (shouldRun && CONFIG[configName] === true) {
resetFlag(flagName);
procedure();
}
});
targetElements.forEach(element => {
observer.observe(element, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeOldValue: true
});
});
// Сохраняем наблюдатель
observers.set(targetName, observer);
}
// Настройка глобального наблюдателя и отслеживания URL
function setupObserverGlobal() {
const observer = new MutationObserver(() => {
checkUrlChange();
checkPage();
ensureWatchSoonElements();
ensureProfileNumbers();
ensureProfileMods();
ensureSettingsPage();
});
observer.observe(document.body, {
subtree: true,
childList: true,
attributes: true
});
// Отслеживание изменений URL через popstate
window.addEventListener('popstate', () => {
checkUrlChange();
setTimeout(checkPage, 500); // Задержка для SPA
});
// Перехват pushState и replaceState
const originalPushState = history.pushState;
history.pushState = function () {
originalPushState.apply(this, arguments);
checkUrlChange();
setTimeout(checkPage, 500); // Задержка для асинхронной загрузки
};
const originalReplaceState = history.replaceState;
history.replaceState = function () {
originalReplaceState.apply(this, arguments);
checkUrlChange();
setTimeout(checkPage, 500); // Задержка для асинхронной загрузки
};
}
// Запуск после загрузки страницы
function onPageLoad() {
setupObserverGlobal();
setTimeout(checkPage, 500); // Первоначальная проверка с задержкой
initStyle();
}
window.addEventListener('load', onPageLoad);
})();