NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name CW-smell
// @namespace http://tampermonkey.net/
// @version 1.4.6
// @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
// ==/UserScript==
(function() {
'use strict';
const STORAGE_KEY = 'CUSTOM_ODORS_DATA';
const OLD_ODOR = 'odoroj/403.png';
const OLD_ODOR_NAME = OLD_ODOR.split('/').pop();
const DEFAULT_PHRASES_TO_IMAGES = [
['Последователь-идальго', 'https://i.yapx.ru/cEnIO.png'],
];
const loadData = async () => {
const storedData = await GM_getValue(STORAGE_KEY);
return storedData ? JSON.parse(storedData) : DEFAULT_PHRASES_TO_IMAGES;
};
const saveData = async (data) => {
await GM_setValue(STORAGE_KEY, JSON.stringify(data));
alert("Запахи сохранены!");
};
const getCustomOdorNames = (phrasesToImages) => {
const names = new Set(
phrasesToImages.map(([, img]) => img.split('/').pop())
);
names.add(OLD_ODOR_NAME);
return names;
};
const getTokens = t => t.toLowerCase().match(/[\p{L}\p{N}]+/gu) || [];
const phraseInText = (tokens, phrase) => {
const phraseTokens = getTokens(phrase);
const pLen = phraseTokens.length;
const tLen = tokens.length;
for (let i = 0; i <= tLen - pLen; i++) {
let match = true;
for (let j = 0; j < pLen; j++) {
if (tokens[i + j] !== phraseTokens[j]) {
match = false;
break;
}
}
if (match && tokens[i + pLen - 1] === phraseTokens[pLen - 1]) return true;
}
return false;
};
const getMatchingImage = (text, phrasesToImages) => {
const tokens = getTokens(text);
for (const [phrase, img] of phrasesToImages) {
if (phraseInText(tokens, phrase)) return img;
}
return null;
};
const runOdorLogic = async () => {
const phrasesToImages = await loadData();
const customOdorNames = getCustomOdorNames(phrasesToImages);
const update = () => {
document.querySelectorAll('.cage').forEach(cage => {
const newImgSrc = getMatchingImage(cage.textContent, phrasesToImages);
const img = cage.querySelector('img[src*="odoroj/"]');
if (img) {
const currentImgSrcFileName = img.src.split('/').pop();
const isCustomOdor = customOdorNames.has(currentImgSrcFileName);
const targetSrc = newImgSrc
? newImgSrc
: (isCustomOdor ? OLD_ODOR : null);
if (targetSrc && targetSrc.split('/').pop() !== currentImgSrcFileName) {
img.src = targetSrc;
}
}
});
};
update();
new MutationObserver(update).observe(
document.body,
{ childList: true, subtree: true, characterData: true, attributes: true }
);
};
const createSettingsInterface = async () => {
const currentData = await 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 = `
#odor-settings-panel {
max-width: 800px; margin: 20px auto; padding: 15px; border: 1px solid #444;
color: #ddd; background: rgba(34, 34, 34, 0.70); border-radius: 20px;
}
#odor-settings-panel h3 { color: #fff; border-bottom: 1px solid #555; padding-bottom: 5px; }
#odor-settings-panel .rule-item { display: flex; gap: 10px; margin-bottom: 8px; align-items: center; }
#odor-settings-panel .column-headers {
display: flex; gap: 10px; margin-bottom: 5px; padding: 0 5px; font-weight: bold; color: #aaa;
}
#odor-settings-panel .column-headers div:first-child { flex-grow: 1; text-align: left; }
#odor-settings-panel .column-headers div:nth-child(2) { width: 290px; text-align: left; }
#odor-settings-panel .column-headers div:last-child { width: 90px; }
#odor-settings-panel input { padding: 5px; border: 1px solid #555; background: #333; color: #eee; }
#odor-settings-panel button { padding: 8px 15px; cursor: pointer; border: none; color: white; margin-right: 10px; }
#odor-settings-panel button#save-settings-btn { background: #5b6f5b; }
#odor-settings-panel button.remove { background: #5d3b3b; }
#odor-settings-panel button.add { background: #40404857; }
`;
document.head.appendChild(style);
const panel = document.createElement('div');
panel.id = 'odor-settings-panel';
panel.innerHTML = `
<h3>Настройка Запахов</h3>
<div class="column-headers"><div>Должность</div><div>Запах</div><div></div></div>
<div id="rule-list"></div>
<button id="add-rule-btn" class="add">Добавить запах</button>
<hr style="margin-top: 15px;">
<button id="save-settings-btn">Сохранить</button>
<button id="reset-settings-btn" class="remove">Сбросить</button>
<p style="font-size: 0.8em; margin-top: 10px;">
* Чем больше фраза - тем выше поставьте её.
<br>* Можно вписывать и свои имена/статусы. Реагирует на весь текст в плашке кота.</p>
`;
const targetElement = document.querySelector('a[href="del"]');
if (targetElement) {
targetElement.insertAdjacentElement('afterend', panel);
} else {
settingsContainer.appendChild(panel);
}
const ruleList = panel.querySelector('#rule-list');
const saveBtn = panel.querySelector('#save-settings-btn');
const resetBtn = panel.querySelector('#reset-settings-btn');
const addBtn = panel.querySelector('#add-rule-btn');
const renderRules = (data) => {
ruleList.innerHTML = '';
data.forEach(([phrase, image]) => {
const item = document.createElement('div');
item.className = 'rule-item';
item.innerHTML = `
<input type="text" class="phrase" value="${phrase}" placeholder="Название должности" style="flex-grow: 2;">
<input type="text" class="image" value="${image}" placeholder="Ссылка на картинку" style="flex-grow: 1;">
<button class="remove">Удалить</button>
`;
ruleList.appendChild(item);
});
};
const collectData = () => {
const data = [];
panel.querySelectorAll('.rule-item').forEach(item => {
const phrase = item.querySelector('.phrase').value.trim();
const image = item.querySelector('.image').value.trim();
if (phrase && image) data.push([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.closest('.rule-item').remove();
}
};
saveBtn.onclick = () => {
const dataToSave = collectData();
if (dataToSave.length > 0) {
saveData(dataToSave);
} else if (confirm("Список пуст. Сбросить настройки на дефолтные?")) {
GM_deleteValue(STORAGE_KEY).then(() => {
renderRules(DEFAULT_PHRASES_TO_IMAGES);
alert("Настройки сброшены!");
});
} else {
alert("Сохранение отменено.");
}
};
resetBtn.onclick = () => {
if (confirm("Сбросить настройки на дефолтные?")) {
GM_deleteValue(STORAGE_KEY).then(() => {
renderRules(DEFAULT_PHRASES_TO_IMAGES);
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')) {
runOdorLogic();
}
})();