CW smell

Меняет запахи по исходному запаху + по имени/статусу/должности.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         CW smell
// @namespace    http://tampermonkey.net/
// @version      1.0.10
// @description  Меняет запахи по исходному запаху + по имени/статусу/должности.
// @author       achterstem
// @match        http*://*.catwar.net/*
// @match        http*://*.catwar.su/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=catwar.su
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @run-at       document-idle
// @homepageURL  https://greasyfork.org/ru/scripts/555607-cw-smell
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_KEY = 'CUSTOM_SMELLS_DATA';

    const gmGetValueSync = (key, defaultValue) => {
        if (typeof GM_getValue === 'function') {
            try {
                return GM_getValue(key, defaultValue);
            } catch (e) {
                console.error("Возврат дефолта.", e);
            }
        }
        return defaultValue;
    };

    const gmSetValueSync = (key, value) => {
        if (typeof GM_setValue === 'function') {
            try {
                GM_setValue(key, value);
            } catch (e) {
                console.error("Сохранение не выполнено.", e);
            }
        }
    };

    const gmDeleteValueSync = (key) => {
        if (typeof GM_deleteValue === 'function') {
            try {
                GM_deleteValue(key);
            } catch (e) {
                console.error("Удаление не выполнено.", e);
            }
        }
    };

    const DEFAULT_RULES = [
        // ЗАСТЫВШАЯ ЭПОХА
        ['odoroj/232.png', 'Провидец Времени', 'odoroj/432.png'],
        ['odoroj/232.png', 'Провидица Времени', 'odoroj/432.png'],
        ['odoroj/232.png', 'Наследник', 'odoroj/432.png'],
        ['odoroj/232.png', 'Наследница', 'odoroj/432.png'],
        ['odoroj/232.png', 'Верховодец', 'odoroj/432.png'],
        ['odoroj/232.png', 'Егерь', 'odoroj/432.png'],
        ['odoroj/232.png', 'Старший Отец', 'odoroj/432.png'],
        ['odoroj/232.png', 'Старшая Матерь', 'odoroj/432.png'],
        ['odoroj/232.png', 'Знахарь', 'odoroj/432.png'],
        ['odoroj/232.png', 'Последователь Знахаря', 'odoroj/432.png'],
        ['odoroj/232.png', 'Последовательница Знахаря', 'odoroj/432.png'],
        ['odoroj/232.png', 'Вояка', 'odoroj/432.png'],
        ['odoroj/232.png', 'Добытчик', 'odoroj/432.png'],
        ['odoroj/232.png', 'Добытчица', 'odoroj/432.png'],
        ['odoroj/232.png', 'Заботливый Отец', 'odoroj/432.png'],
        ['odoroj/232.png', 'Заботливая Матерь', 'odoroj/432.png'],
        ['odoroj/232.png', 'Последователь', 'odoroj/432.png'],
        ['odoroj/232.png', 'Последовательница', 'odoroj/432.png'],
        ['odoroj/232.png', 'Постигающий', 'odoroj/432.png'],
        ['odoroj/232.png', 'Постигающая', 'odoroj/432.png'],
        ['odoroj/232.png', 'Новорожденное Дитя', 'odoroj/432.png'],
        ['odoroj/232.png', 'Старец', 'odoroj/432.png'],
        ['odoroj/232.png', 'Старица', 'odoroj/432.png'],

        // ПЛЕМЯ ТУМАННОГО ЗАЛИВА
        ['odoroj/232.png', 'возрождённый', 'odoroj/177.png'],
        ['odoroj/232.png', 'возрождённая', 'odoroj/177.png'],
        ['odoroj/232.png', 'воспитанник', 'odoroj/177.png'],
        ['odoroj/232.png', 'воспитанница', 'odoroj/177.png'],
        ['odoroj/232.png', 'преданный Моря', 'odoroj/177.png'],
        ['odoroj/232.png', 'преданная Моря', 'odoroj/177.png'],
        ['odoroj/232.png', 'старший преданный Моря', 'odoroj/177.png'],
        ['odoroj/232.png', 'старшая преданная Моря', 'odoroj/177.png'],
        ['odoroj/232.png', 'поверенный', 'odoroj/177.png'],
        ['odoroj/232.png', 'поверенная', 'odoroj/177.png'],
        ['odoroj/232.png', 'приближённый', 'odoroj/177.png'],
        ['odoroj/232.png', 'приближённая', 'odoroj/177.png'],
        ['odoroj/232.png', 'советник', 'odoroj/177.png'],
        ['odoroj/232.png', 'советница', 'odoroj/177.png'],
        ['odoroj/232.png', 'служитель Моря', 'odoroj/177.png'],
        ['odoroj/232.png', 'служительница Моря', 'odoroj/177.png'],
        ['odoroj/232.png', 'душа Моря', 'odoroj/177.png'],
        ['odoroj/232.png', 'неприкасаемый', 'odoroj/177.png'],
        ['odoroj/232.png', 'неприкасаемая', 'odoroj/177.png'],
        ['odoroj/232.png', 'сухопутный', 'odoroj/177.png'],
        ['odoroj/232.png', 'сухопутная', 'odoroj/177.png'],
        ['odoroj/232.png', 'очистившийся', 'odoroj/177.png'],
        ['odoroj/232.png', 'очистившаяся', 'odoroj/177.png'],
        ['odoroj/232.png', 'Помнящий', 'odoroj/177.png'],
        ['odoroj/232.png', 'Помнящая', 'odoroj/177.png'],
        ['odoroj/232.png', 'преемник Помнящего', 'odoroj/177.png'],
        ['odoroj/232.png', 'преемница Помнящего', 'odoroj/177.png'],
        ['odoroj/232.png', 'житель пещер', 'odoroj/177.png'],
        ['odoroj/232.png', 'жительница пещер', 'odoroj/177.png'],
        ['odoroj/232.png', 'представитель жителей пещер', 'odoroj/177.png'],
        ['odoroj/232.png', 'представительница жителей пещер', 'odoroj/177.png'],
        ['odoroj/232.png', 'житель скал', 'odoroj/177.png'],
        ['odoroj/232.png', 'жительница скал', 'odoroj/177.png'],
        ['odoroj/232.png', 'вождь жителей скал', 'odoroj/177.png'],

        // СОШЕДШИЕ
        ['odoroj/232.png', 'Путешественник', 'odoroj/400.png'],
        ['odoroj/232.png', 'Путешественница', 'odoroj/400.png'],
        ['odoroj/232.png', 'Искатель приключений', 'odoroj/400.png'],
        ['odoroj/232.png', 'Искательница приключений', 'odoroj/400.png'],
        ['odoroj/232.png', 'Старший искатель приключений', 'odoroj/400.png'],
        ['odoroj/232.png', 'Старшая искательница приключений', 'odoroj/400.png'],
        ['odoroj/232.png', 'Рыцарь', 'odoroj/400.png'],
        ['odoroj/232.png', 'Цисин', 'odoroj/400.png'],
        ['odoroj/232.png', 'Издатель', 'odoroj/400.png'],
        ['odoroj/232.png', 'Мудрец', 'odoroj/400.png'],
        ['odoroj/232.png', 'Эйдолон', 'odoroj/400.png'],
        ['odoroj/232.png', 'Гладиатор', 'odoroj/400.png'],
        ['odoroj/232.png', 'Предвестник', 'odoroj/400.png'],
        ['odoroj/232.png', 'Анемо Архонт', 'odoroj/400.png'],
        ['odoroj/232.png', 'Гео Архонт', 'odoroj/400.png'],
        ['odoroj/232.png', 'Электро Архонт', 'odoroj/400.png'],
        ['odoroj/232.png', 'Дендро Архонт', 'odoroj/400.png'],
        ['odoroj/232.png', 'Гидро Архонт', 'odoroj/400.png'],
        ['odoroj/232.png', 'Пиро Архонт', 'odoroj/400.png'],
        ['odoroj/232.png', 'Крио Архонт', 'odoroj/400.png'],
        ['odoroj/232.png', 'Избранник небес', 'odoroj/400.png'],
        ['odoroj/232.png', 'Хранитель небесного порядка', 'odoroj/400.png'],
        ['odoroj/232.png', 'Маленькая океанида', 'odoroj/400.png'],
        ['odoroj/232.png', 'Таинственный натурфилософ', 'odoroj/400.png'],
        ['odoroj/232.png', 'Дракон рассвета и заката', 'odoroj/400.png'],
        ['odoroj/232.png', 'Дракон рассвета и заката', 'odoroj/400.png'],
        ['odoroj/232.png', 'Рыцарь Вечности', 'odoroj/400.png'],
        ['odoroj/232.png', 'Нечто из иного мира', 'odoroj/400.png'],
        ['odoroj/232.png', 'Странствующий звездочёт', 'odoroj/400.png'],

        // ВЕЛИКОКНЯЖЕСТВО
        ['odoroj/232.png', 'Чадо', 'odoroj/421.png'],
        ['odoroj/232.png', 'Отрок', 'odoroj/421.png'],
        ['odoroj/232.png', 'Пахоликъ', 'odoroj/421.png'],
        ['odoroj/232.png', 'Кормилица', 'odoroj/421.png'],
        ['odoroj/232.png', 'Лежебока', 'odoroj/421.png'],
        ['odoroj/232.png', 'Положникъ', 'odoroj/421.png'],
        ['odoroj/232.png', 'Казатель', 'odoroj/421.png'],
        ['odoroj/232.png', 'Усердник', 'odoroj/421.png'],
        ['odoroj/232.png', 'Радетель', 'odoroj/421.png'],
        ['odoroj/232.png', 'Храбрецъ', 'odoroj/421.png'],
        ['odoroj/232.png', 'Борецъ', 'odoroj/421.png'],
        ['odoroj/232.png', 'Гридъ', 'odoroj/421.png'],
        ['odoroj/232.png', 'Боярин', 'odoroj/421.png'],
        ['odoroj/232.png', 'Ученик Знахаря', 'odoroj/421.png'],
        ['odoroj/232.png', 'Княжич', 'odoroj/421.png'],
        ['odoroj/232.png', 'Князь', 'odoroj/421.png'],

        // АКАДЕМИЯ ХАУККА
        ['odoroj/232.png', 'Ректор', 'odoroj/466.png'],
        ['odoroj/232.png', 'Проректор', 'odoroj/466.png'],
        ['odoroj/232.png', 'Декан', 'odoroj/466.png'],
        ['odoroj/232.png', 'Социальный педагог', 'odoroj/466.png'],
        ['odoroj/232.png', 'Медбрат', 'odoroj/466.png'],
        ['odoroj/232.png', 'Медсестра', 'odoroj/466.png'],
        ['odoroj/232.png', 'Профессор', 'odoroj/466.png'],
        ['odoroj/232.png', 'Преподаватель', 'odoroj/466.png'],
        ['odoroj/232.png', 'Куратор', 'odoroj/466.png'],
        ['odoroj/232.png', 'Выпускник', 'odoroj/466.png'],
        ['odoroj/232.png', 'Студент', 'odoroj/466.png'],
        ['odoroj/232.png', 'Староста', 'odoroj/466.png'],
        ['odoroj/232.png', 'Астроном', 'odoroj/466.png'],
        ['odoroj/232.png', 'Большая шишка', 'odoroj/466.png'],
        ['odoroj/232.png', 'Гражданин', 'odoroj/466.png'],

        // ОЗХ
        ['odoroj/150.png', 'Вождь', 'odoroj/452.png'],
        ['odoroj/150.png', 'Преемник', 'odoroj/452.png'],
        ['odoroj/150.png', 'Преемница', 'odoroj/452.png'],
        ['odoroj/150.png', 'Голос Мару', 'odoroj/452.png'],
        ['odoroj/150.png', 'Шаман', 'odoroj/452.png'],
        ['odoroj/150.png', 'Голос Калао', 'odoroj/452.png'],
        ['odoroj/150.png', 'Шёпот Мару', 'odoroj/452.png'],
        ['odoroj/150.png', 'Шёпот Исины', 'odoroj/452.png'],
        ['odoroj/150.png', 'Шёпот Калао', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословлённый Мару', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословлённый Исиной', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословлённый Калао', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословлённая Мару', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословлённая Исиной', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословлённая Калао', 'odoroj/452.png'],
        ['odoroj/150.png', 'Последователь Мару', 'odoroj/452.png'],
        ['odoroj/150.png', 'Последователь Исины', 'odoroj/452.png'],
        ['odoroj/150.png', 'Последователь Калао', 'odoroj/452.png'],
        ['odoroj/150.png', 'Последовательница Мару', 'odoroj/452.png'],
        ['odoroj/150.png', 'Последовательница Исины', 'odoroj/452.png'],
        ['odoroj/150.png', 'Последовательница Калао', 'odoroj/452.png'],
        ['odoroj/150.png', 'Познающий Пути', 'odoroj/452.png'],
        ['odoroj/150.png', 'Познающая Пути', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословенный Шакти', 'odoroj/452.png'],
        ['odoroj/150.png', 'Благословенная Шакти', 'odoroj/452.png'],
        ['odoroj/150.png', 'Завершивший Путь', 'odoroj/452.png'],
        ['odoroj/150.png', 'Завершившая Путь', 'odoroj/452.png'],
        ['odoroj/150.png', 'Малютка', 'odoroj/452.png'],
        ['odoroj/150.png', 'Чужеземец', 'odoroj/452.png'],
        ['odoroj/150.png', 'Чужеземка', 'odoroj/452.png'],
        ['odoroj/150.png', 'Искатель стези', 'odoroj/452.png'],
        ['odoroj/150.png', 'Искательница стези', 'odoroj/452.png'],

        // КАССИОПЕЯ
        ['odoroj/403.png', 'Созидатель звёзд', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранник Нави', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранник Шедар', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранник Каф', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранник Сегин', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранник Рукбах', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенный Нави', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенный Шедар', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенный Сегин', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенный Рукбах', 'odoroj/456.png'],
        ['odoroj/403.png', 'Матушка-Плеяда', 'odoroj/456.png'],
        ['odoroj/403.png', 'Гордость звездных предков', 'odoroj/456.png'],
        ['odoroj/403.png', 'Сияние Нави', 'odoroj/456.png'],
        ['odoroj/403.png', 'Сияние Шедар', 'odoroj/456.png'],
        ['odoroj/403.png', 'Сияние Каф', 'odoroj/456.png'],
        ['odoroj/403.png', 'Сияние Сегин', 'odoroj/456.png'],
        ['odoroj/403.png', 'Сияние Рукбах', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающий Нави', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающий Шедар', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающий Каф', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающий Сегин', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающий Рукбах', 'odoroj/456.png'],
        ['odoroj/403.png', 'Искорка', 'odoroj/456.png'],
        ['odoroj/403.png', 'Созидательница звёзд', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранница Нави', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранница Шедар', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранница Каф', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранница Сегин', 'odoroj/456.png'],
        ['odoroj/403.png', 'Избранница Рукбах', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенная Нави', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенная Шедар', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенная Сегин', 'odoroj/456.png'],
        ['odoroj/403.png', 'Приближенная Рукбах', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающая Нави', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающая Шедар', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающая Каф', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающая Сегин', 'odoroj/456.png'],
        ['odoroj/403.png', 'Мерцающая Рукбах', 'odoroj/456.png'],

        // НЕБОЖИТЕЛИ
        ['odoroj/403.png', 'Небожитель', 'odoroj/448.png'],
        ['odoroj/403.png', 'Даочжан', 'odoroj/448.png'],
        ['odoroj/403.png', 'Владыка', 'odoroj/448.png'],
        ['odoroj/403.png', 'Этот Достопочтенный', 'odoroj/448.png'],
        ['odoroj/403.png', 'Несущий Бедствия', 'odoroj/448.png'],
        ['odoroj/403.png', 'Наступающий На Бессмертных', 'odoroj/448.png'],
        ['odoroj/403.png', 'Бог Войны Юго-Востока', 'odoroj/448.png'],
        ['odoroj/403.png', 'Дух Созвездия Хуагай', 'odoroj/448.png'],
        ['odoroj/403.png', 'Забытая Госпожа', 'odoroj/448.png'],
        ['odoroj/403.png', 'Повелитель Воды', 'odoroj/448.png'],
        ['odoroj/403.png', 'Молодой Господин Проливший Вино', 'odoroj/448.png'],
        ['odoroj/403.png', 'Змей-Лун', 'odoroj/448.png'],
        ['odoroj/403.png', 'Танцующий Среди Ликорисов', 'odoroj/448.png'],
        ['odoroj/403.png', 'Скрывающийся в Сумерках Охотник', 'odoroj/448.png'],
        ['odoroj/403.png', 'Дух поветрия', 'odoroj/448.png'],
        ['odoroj/403.png', 'Междумирец', 'odoroj/448.png'],
        ['odoroj/403.png', 'Созерцающий пустоту', 'odoroj/448.png'],
        ['odoroj/403.png', 'Упрямый Юноша', 'odoroj/448.png'],
        ['odoroj/403.png', 'Дитя пустоты', 'odoroj/448.png'],
        ['odoroj/403.png', 'Посмешище трех миров', 'odoroj/448.png'],

        // ХРАМ ЯО-ХУ
        ['odoroj/403.png', 'Постигший Души', 'odoroj/352.png'],
        ['odoroj/403.png', 'Постигшая Души', 'odoroj/352.png'],
        ['odoroj/403.png', 'Изучающий Души', 'odoroj/352.png'],
        ['odoroj/403.png', 'Изучающая Души', 'odoroj/352.png'],
        ['odoroj/403.png', 'Глава пика Фэн', 'odoroj/352.png'],
        ['odoroj/403.png', 'Глава пика Цао', 'odoroj/352.png'],
        ['odoroj/403.png', 'Глава пика Пэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Глава пика Вэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первый ученик Фэн', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первая ученица Фэн', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первый ученик Цао', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первая ученица Цао', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первый ученик Пэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первая ученица Пэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первый ученик Вэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Первая ученица Вэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Лаоши', 'odoroj/352.png'],
        ['odoroj/403.png', 'Адепт Фэн', 'odoroj/352.png'],
        ['odoroj/403.png', 'Адепт Цао', 'odoroj/352.png'],
        ['odoroj/403.png', 'Адепт Пэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Адепт Вэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Старший ученик Фэн', 'odoroj/352.png'],
        ['odoroj/403.png', 'Старшая ученица Фэн', 'odoroj/352.png'],
        ['odoroj/403.png', 'Старший ученик Пэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Старшая ученица Пэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Старший ученик Вэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Старшая ученица Вэй', 'odoroj/352.png'],
        ['odoroj/403.png', 'Младший ученик', 'odoroj/352.png'],
        ['odoroj/403.png', 'Младшая ученица', 'odoroj/352.png'],
        ['odoroj/403.png', 'Создание Яо-ху', 'odoroj/352.png'],

        // мои тесты
        ['odoroj/403.png', 'Гильотина', 'https://e.radikal.host/2025/11/11/ZPK27fc936d42ab4cef.png'],
        ['odoroj/403.png', 'Чёрт, Фраудхарт', 'https://e.radikal.host/2025/11/11/ZPK27fc936d42ab4cef.png']
    ];

    let ALL_ORIGINAL_SMELLS = [];
    let CUSTOM_TO_BASE_SMELL_MAP = new Map();

    const loadData = () => {
        let storedData = gmGetValueSync(STORAGE_KEY, null);

        let rules;
        try {
            rules = storedData ? JSON.parse(storedData) : DEFAULT_RULES;

        } catch (e) {
            console.error("Возврат дефолта.", e);
            rules = DEFAULT_RULES;
        }

        const originalSmellsSet = new Set();
        CUSTOM_TO_BASE_SMELL_MAP.clear();

        rules.forEach(([oldSmell, phrase, newSmell]) => {
            if (oldSmell && newSmell) {
                const canonicalBaseSmell = oldSmell.split('/').slice(-2).join('/');
                originalSmellsSet.add(canonicalBaseSmell);

                CUSTOM_TO_BASE_SMELL_MAP.set(newSmell, oldSmell);
            }
        });

        ALL_ORIGINAL_SMELLS = Array.from(originalSmellsSet);
        return rules;
    };

    const saveData = (data) => {
        if (confirm("Сохранить запахи?")) {
            gmSetValueSync(STORAGE_KEY, JSON.stringify(data));
        }
    };

    const resetData = () => {
        gmDeleteValueSync(STORAGE_KEY);
    };


    const phraseInText = (text, phrase) => {
        const normalizedText = text.toLowerCase().replace(/\s+/g, ' ').trim();
        const normalizedPhrase = phrase.toLowerCase().trim();

        return normalizedText.includes(normalizedPhrase);
    };

    const getMatchingImage = (text, canonicalBaseSmell, rules) => {
        for (const [oldSmell, phrase, newImg] of rules) {
            const ruleCanonicalSmell = oldSmell.split('/').slice(-2).join('/');

            if (canonicalBaseSmell.endsWith(ruleCanonicalSmell.split('/').pop())) {
                if (phraseInText(text, phrase)) {
                    return { newSmell: newImg, originalBase: oldSmell };
                }
            }
        }
        return { newSmell: null, originalBase: null };
    };

    const ORIGINAL_SRC_ATTRIBUTE = 'data-original-smell';
    const CURRENT_CAT_ID_ATTRIBUTE = 'data-cat-id';

    const applySmellsToCage = (cage, rules) => {
        const img = cage.querySelector('img[src*="odoroj/"], img[data-original-smell]');
        const catNameElement = cage.querySelector('a');

        if (!img || !catNameElement) return;

        let currentFullRelativeSrc = img.getAttribute('src');
        const textContent = (cage.querySelector('span') || { innerText: '' }).innerText + ' ' + catNameElement.textContent.trim();

        const catUrl = catNameElement.href;
        const match = catUrl.match(/cat(\d+)/);
        const currentCatId = match ? match[1] : null;
        const cachedCatId = img.getAttribute(CURRENT_CAT_ID_ATTRIBUTE);

        const hasCustomSrc = CUSTOM_TO_BASE_SMELL_MAP.has(currentFullRelativeSrc);


        let originalSrc = img.getAttribute(ORIGINAL_SRC_ATTRIBUTE);

        if (!originalSrc) {
             originalSrc = currentFullRelativeSrc;
             img.setAttribute(ORIGINAL_SRC_ATTRIBUTE, originalSrc);
             img.setAttribute(CURRENT_CAT_ID_ATTRIBUTE, currentCatId);

        } else if (currentCatId !== cachedCatId) {
             if (hasCustomSrc) {
                 img.src = originalSrc;
                 currentFullRelativeSrc = originalSrc;
             }

             img.removeAttribute(ORIGINAL_SRC_ATTRIBUTE);

             img.setAttribute(CURRENT_CAT_ID_ATTRIBUTE, currentCatId);
             return applySmellsToCage(cage, rules);

        } else if (hasCustomSrc) {
             const baseSmellForCustom = CUSTOM_TO_BASE_SMELL_MAP.get(currentFullRelativeSrc);
             const matchingResult = getMatchingImage(textContent, baseSmellForCustom, rules);

             if (!matchingResult.newSmell || matchingResult.newSmell !== currentFullRelativeSrc) {
                 img.src = originalSrc;
                 currentFullRelativeSrc = originalSrc;
             }
        }

        originalSrc = img.getAttribute(ORIGINAL_SRC_ATTRIBUTE) || currentFullRelativeSrc;

        const originalSrcPart = originalSrc.split('/').slice(-2).join('/');

        const matchingResult = getMatchingImage(textContent, originalSrcPart, rules);

        let targetSrc = originalSrc;

        if (matchingResult.newSmell) {
            targetSrc = matchingResult.newSmell;
        }

        if (targetSrc && targetSrc !== img.src) {
            img.src = targetSrc;
        }
    };

    const initSmellObservers = () => {
        const rules = loadData();

        const mapContainer = document.querySelector('#ist, #cages_div');
        if (!mapContainer) {
             return;
        }

        const observers = [];

        const setupCageObserver = (cage) => {
            const img = cage.querySelector('img[src*="odoroj/"]');
            const catNameElement = cage.querySelector('a');
            const targetSpan = cage.querySelector('span');

            if (!img || !catNameElement || !targetSpan) return;

            applySmellsToCage(cage, rules);

            const cageObserver = new MutationObserver((mutationsList) => {
                let shouldApply = false;

                for (const mutation of mutationsList) {
                    if (mutation.type === 'childList' || mutation.type === 'characterData') {
                        shouldApply = true;
                        break;
                    }
                    if (mutation.type === 'attributes' && mutation.attributeName === 'href') {
                        shouldApply = true;
                        break;
                    }
                }

                if (shouldApply) {
                    applySmellsToCage(cage, rules);
                }
            });

            cageObserver.observe(targetSpan, {
                childList: true,
                subtree: true,
                characterData: true,
                attributes: true
            });

            cageObserver.observe(catNameElement, {
                attributes: true,
                attributeFilter: ['href']
            });

            observers.push(cageObserver);
        };

        document.querySelectorAll('.cage').forEach(setupCageObserver);

        const mapChangeObserver = new MutationObserver((mutationsList) => {
            mutationsList.forEach(mutation => {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === 1 && node.classList.contains('cage')) {
                            setupCageObserver(node);
                        }
                    });

                    document.querySelectorAll('.cage').forEach(cage => {
                        applySmellsToCage(cage, rules);
                    });
                }
            });
        });

        mapChangeObserver.observe(mapContainer, {
            childList: true,
            subtree: true
        });

        window.smellObservers = observers;
    };


    // --- НАСТРОЙКИ ---
    const createSettingsInterface = () => {
        const currentData = loadData();
        const siteTable = document.querySelector("#site_table");
        if (!siteTable) return;
        const settingsContainer = siteTable.getAttribute("data-mobile") === "0"
            ? document.querySelector("#branch")
            : siteTable;
        if (!settingsContainer) return;
        const style = document.createElement('style');
        style.innerHTML = `
            #smell-settings-panel {
                max-width: 800px; margin: 20px auto; padding: 15px; border: 1px solid #000000;
                color: #c9c9c9; background: rgb(35 33 33 / 83%); border-radius: 20px;
            }
            #smell-settings-panel h3 { color: #ffffff; border-bottom: 1px solid #ffffff; padding-bottom: 5px; }
            #smell-settings-panel #toggle-rules-btn {
                background: #232020;
                padding: 5px 10px;
                margin-top: 5px;
                margin-bottom: 5px;
                border-radius: 10px;
                font-size: 0.9em;
            }
            #smell-settings-panel .rule-item { display: flex; gap: 10px; margin-bottom: 8px; align-items: center; }
            #smell-settings-panel .column-headers {
                display: flex; gap: 10px; margin-bottom: 5px; padding: 0 5px; font-weight: bold; color: #afafaf;
            }
            #smell-settings-panel .column-headers div:first-child { width: 150px; text-align: left; }
            #smell-settings-panel .column-headers div:nth-child(2) { flex-grow: 1; text-align: left; }
            #smell-settings-panel .column-headers div:nth-child(3) { width: 290px; text-align: left; }
            #smell-settings-panel .column-headers div:last-child { width: 90px; }
            #smell-settings-panel input { padding: 5px; border: 1px solid #000000; background: #1a1818bf; color: #e3e3e3; }
            #smell-settings-panel button { padding: 6px 8px; cursor: pointer; border: none; color: white; margin-right: 10px; border-radius: 20px; }
            #smell-settings-panel button#save-settings-btn { background: #646464; }
            #smell-settings-panel button.remove { background: #613737; }
            #smell-settings-panel button#delete-all-btn { background: #7c3d3d; }
            #smell-settings-panel button.add { background: #646464; }
            #smell-list-content.hidden {
                display: none;
            }
        `;
        document.head.appendChild(style);
        const panel = document.createElement('div');
        panel.id = 'smell-settings-panel';
        panel.innerHTML = `
            <h3>Настройка Запахов</h3>
            <button id="toggle-rules-btn">Развернуть</button>
            <div id="smell-list-content" class="hidden">
                <div class="column-headers">
                    <div>Исходный Запах</div>
                    <div>Должность</div>
                    <div>Нужный запах</div>
                    <div></div>
                </div>
                <div id="rule-list"></div>
                <button id="add-rule-btn" class="add">Добавить запах</button>
            </div>
            <hr style="margin-top: 15px;">
            <button id="save-settings-btn">Сохранить</button>
            <button id="reset-settings-btn" class="remove">Сбросить</button>
            <button id="delete-all-btn" class="remove">Удалить все запахи</button>
            <p style="font-size: 0.8em; margin-top: 10px;">
                * Можно вводить как и должности, так и имена.
            </p>
        `;
        const targetElement = document.querySelector('a[href="del"]');
        if (targetElement) {
            targetElement.insertAdjacentElement('afterend', panel);
        } else {
            settingsContainer.appendChild(panel);
        }
        const toggleBtn = panel.querySelector('#toggle-rules-btn');
        const listContent = panel.querySelector('#smell-list-content');
        const ruleList = panel.querySelector('#rule-list');
        const saveBtn = panel.querySelector('#save-settings-btn');
        const resetBtn = panel.querySelector('#reset-settings-btn');
        const deleteAllBtn = panel.querySelector('#delete-all-btn');
        const addBtn = panel.querySelector('#add-rule-btn');
        toggleBtn.onclick = () => {
            listContent.classList.toggle('hidden');
            if (listContent.classList.contains('hidden')) {
                toggleBtn.textContent = 'Развернуть';
            } else {
                toggleBtn.textContent = 'Свернуть';
            }
        };
        const renderRules = (data) => {
            ruleList.innerHTML = '';
            data.forEach(([oldSmell, phrase, image]) => {
                const item = document.createElement('div');
                item.className = 'rule-item';
                item.innerHTML = `
                    <input type="text" class="old-smell" value="${oldSmell}" placeholder="odoroj/403.png" style="width: 150px;">
                    <input type="text" class="phrase" value="${phrase}" placeholder="Название должности" style="flex-grow: 1;">
                    <input type="text" class="image" value="${image}" placeholder="Ссылка на картинку" style="width: 290px;">
                    <button class="remove">Удалить</button>
                `;
                ruleList.appendChild(item);
            });
        };
        const collectData = () => {
            const data = [];
            panel.querySelectorAll('.rule-item').forEach(item => {
                const oldSmell = item.querySelector('.old-smell').value.trim();
                const phrase = item.querySelector('.phrase').value.trim();
                const image = item.querySelector('.image').value.trim();
                if (oldSmell && phrase && image) data.push([oldSmell, phrase, image]);
            });
            return data;
        };
        addBtn.onclick = () => {
            const newData = collectData();
            newData.push(["", "", ""]);
            renderRules(newData);
        };
        ruleList.onclick = (e) => {
            if (e.target.classList.contains('remove') && e.target.id !== 'reset-settings-btn' && e.target.id !== 'delete-all-btn') {
                e.target.closest('.rule-item').remove();
            }
        };
        saveBtn.onclick = () => {
            const dataToSave = collectData();
            if (dataToSave.length > 0) {
                saveData(dataToSave);
                alert("Запахи сохранены!");
            } else if (confirm("Список пуст. Сбросить запахи на дефолтные?")) {
                resetData();
                renderRules(DEFAULT_RULES);
                alert("Запахи сброшены.");
            } else {
                alert("Сохранение отменено.");
            }
        };
        resetBtn.onclick = () => {
            if (confirm("Сбросить запахи на дефолтные?")) {
                resetData();
                renderRules(DEFAULT_RULES);
                alert("Запахи сброшены.");
            }
        };
        deleteAllBtn.onclick = () => {
            if (confirm("Удалить все запахи?")) {
                renderRules([]);
                gmSetValueSync(STORAGE_KEY, "[]");
                alert("Все запахи удалены.");
            }
        };
        renderRules(currentData);
    };

    const waitForElement = (selector) => new Promise(resolve => {
        const element = document.querySelector(selector);
        if (element) return resolve(element);
        const observer = new MutationObserver((_, obs) => {
            const el = document.querySelector(selector);
            if (el) {
                obs.disconnect();
                resolve(el);
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    });

    if (window.location.pathname.endsWith('/settings')) {
        waitForElement('#site_table').then(createSettingsInterface);
    } else if (document.querySelector('#main_table')) {
        waitForElement('#ist, #cages_div').then(initSmellObservers);
    }
})();