NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Метрическая книга Ветра
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Ведёт запись в тетрадь смерти
// @author Светокамень I Великая
// @license MIT
// @match *://catwar.net/*
// @match *://catwar.su/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=catwar.net
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Память
let catsMemory = JSON.parse(sessionStorage.getItem('cw-cats-memory') || '{}');
let expectedCats = JSON.parse(sessionStorage.getItem('cw-expected-cats') || '{}');
// Глобальные стили
const globalStyles = document.createElement('style');
globalStyles.textContent = `
.cw-copy-color-btn {
height: 40px;
cursor: pointer;
border-radius: 4px;
transition: transform 0.2s, box-shadow 0.2s;
background: rgba(255,255,255,0.1);
}
.cw-copy-color-btn:hover {
transform: scale(1.1);
box-shadow: 0 0 8px rgba(255,255,255,0.5);
}
.cw-copy-row-btn {
cursor: pointer;
font-size: 16px;
transition: transform 0.1s;
display: inline-block;
}
.cw-copy-row-btn:hover {
transform: scale(1.2);
}
.cw-profile-btn {
text-decoration: none;
font-size: 16px;
margin-left: 8px;
transition: transform 0.1s;
display: inline-block;
}
.cw-profile-btn:hover {
transform: scale(1.2);
}
#cw-cats-table-overlay {
position: fixed;
top: 50px;
right: 50px;
background: rgba(30, 30, 30, 0.95);
color: #fff;
padding: 15px;
border-radius: 8px;
z-index: 99999;
max-height: 80vh;
overflow-y: auto;
font-family: sans-serif;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
border: 1px solid #555;
min-width: 480px;
}
.cw-row-gray { background-color: rgba(150, 150, 150, 0.25); }
.cw-row-red { background-color: rgba(220, 53, 69, 0.4); }
`;
document.head.appendChild(globalStyles);
// ==========================================
// 1. ФУНКЦИЯ ДЛЯ ПРОФИЛЯ (Копирование даты)
// ==========================================
function checkProfileAndAddButton() {
if (document.getElementById('cw-copy-date-btn')) return;
// Ищем иконки (сначала перерождение, если нет - обычную)
const age2Icon = document.getElementById('age2_icon');
const ageIcon = document.getElementById('age_icon');
const targetIcon = age2Icon || ageIcon;
if (!targetIcon) return;
const targetTd = targetIcon.closest('td').nextElementSibling;
if (targetTd) {
const copyBtn = document.createElement('span');
copyBtn.id = 'cw-copy-date-btn';
copyBtn.textContent = '📋';
copyBtn.title = 'Скопировать дату (скрипт сам нажмет на луны)';
copyBtn.style.cssText = `
cursor: pointer;
margin-left: 10px;
font-size: 16px;
transition: transform 0.1s;
display: inline-block;
vertical-align: middle;
`;
copyBtn.addEventListener('mouseenter', () => copyBtn.style.transform = 'scale(1.2)');
copyBtn.addEventListener('mouseleave', () => copyBtn.style.transform = 'scale(1)');
copyBtn.addEventListener('click', (e) => {
e.stopPropagation();
const originalIcon = '📋';
copyBtn.textContent = '⏳'; // Показываем часики, пока ждем ответа от сервера игры
// 1. Программно кликаем по самой картинке лун
targetIcon.click();
// 2. Запускаем таймер, который будет искать дату каждые 100мс
let attempts = 0;
const checkInterval = setInterval(() => {
attempts++;
const infoBlock = document.getElementById('info');
let finalDate = "";
// Если блок инфо видим и в нем есть текст, ищем дату
if (infoBlock && infoBlock.style.display !== 'none') {
const match = infoBlock.innerHTML.match(/(\d{4}-\d{2}-\d{2})/);
if (match) {
finalDate = match[1];
}
}
// Если нашли дату
if (finalDate) {
clearInterval(checkInterval); // Останавливаем проверки
navigator.clipboard.writeText(finalDate).then(() => {
copyBtn.textContent = '✅';
setTimeout(() => { copyBtn.textContent = originalIcon; }, 1500);
});
} else if (attempts > 30) {
// Если прошло 3 секунды (30 попыток), а даты нет - сдаемся
clearInterval(checkInterval);
copyBtn.textContent = originalIcon;
alert('Не удалось получить дату. Возможно, сервер игры слишком долго отвечает.');
}
}, 100);
});
targetTd.appendChild(copyBtn);
}
}
// ==========================================
// 2. ФУНКЦИЯ ДЛЯ ЛОКАЦИИ (Сбор котов)
// ==========================================
function scanCats() {
const tooltips = document.querySelectorAll('.cat_tooltip');
if (tooltips.length === 0) return;
let isChanged = false;
tooltips.forEach(tooltip => {
try {
const hasSpecificOdor = tooltip.querySelector('img[src*="odoroj/2.png"]');
if (!hasSpecificOdor) return;
const link = tooltip.querySelector('u a');
if (!link) return;
const name = link.textContent.trim();
const href = link.getAttribute('href');
const id = href ? href.replace('/cat', '') : 'Нет ID';
if (id === 'Нет ID') return;
const roleElements = tooltip.querySelectorAll('small i');
let role = '-';
if (roleElements.length > 0) {
role = roleElements[roleElements.length - 1].textContent.trim();
}
let colorUrl = 'Нет окраса';
const catContainer = tooltip.closest('.cat');
if (catContainer) {
const firstDiv = catContainer.querySelector('.first');
if (firstDiv && firstDiv.style.backgroundImage) {
const match = firstDiv.style.backgroundImage.match(/url\(["']?(.*?)["']?\)/);
if (match && match[1]) {
const rawUrl = match[1];
colorUrl = rawUrl.startsWith('http') ? rawUrl : 'https://catwar.net' + (rawUrl.startsWith('/') ? '' : '/') + rawUrl;
}
}
}
const existing = catsMemory[id];
if (!existing || existing.name !== name || existing.role !== role || existing.colorUrl !== colorUrl) {
catsMemory[id] = { id, name, role, colorUrl };
isChanged = true;
}
} catch (e) {
console.error("Ошибка при чтении данных кота:", e);
}
});
if (isChanged) {
sessionStorage.setItem('cw-cats-memory', JSON.stringify(catsMemory));
if (document.getElementById('cw-cats-table-content')) {
renderTableContent();
}
}
}
// ==========================================
// 3. ОТРИСОВКА И ЛОГИКА ТАБЛИЦЫ
// ==========================================
function processPastedData() {
const textArea = document.getElementById('cw-expected-data');
if (!textArea) return;
const lines = textArea.value.trim().split('\n');
const newExpected = {};
let count = 0;
lines.forEach(line => {
const cols = line.split('\t');
if (cols.length >= 7) {
const id = cols[0].trim();
if (/^\d+$/.test(id)) {
newExpected[id] = {
name: cols[1].trim(),
role: cols[3].trim(),
colorUrl: cols[6].trim()
};
count++;
}
}
});
expectedCats = newExpected;
sessionStorage.setItem('cw-expected-cats', JSON.stringify(expectedCats));
textArea.value = '';
textArea.placeholder = `Таблица загружена! Котов в базе: ${count}`;
renderTableContent();
}
function renderTableContent() {
const wrapper = document.getElementById('cw-cats-table-content');
const missingWrapper = document.getElementById('cw-missing-cats-content');
if (!wrapper) return;
const cats = Object.values(catsMemory);
const hasExpectedData = Object.keys(expectedCats).length > 0;
let html = '';
if (cats.length === 0) {
html += '<p style="text-align:center; color:#aaa; margin: 10px 0;">Замеченных котов нет. Ждем появления...</p>';
} else {
html += '<h3 style="margin-top:15px; border-bottom: 1px solid #555; padding-bottom: 10px;">Замеченные коты (' + cats.length + ')</h3>';
html += '<table style="width: 100%; border-collapse: collapse; text-align: left;">';
html += '<tr style="border-bottom: 2px solid #777;"><th style="padding: 5px; width: 60px;"></th><th style="padding: 5px;">ID</th><th style="padding: 5px;">ИМЯ</th><th style="padding: 5px;">ДОЛЖНОСТЬ</th><th style="padding: 5px; text-align: center;">ОКРАС</th></tr>';
cats.forEach(cat => {
let colorHtml = '-';
if (cat.colorUrl !== 'Нет окраса') {
colorHtml = `<img src="${cat.colorUrl}" class="cw-copy-color-btn" data-url="${cat.colorUrl}" title="Нажмите, чтобы скопировать ссылку" alt="Окрас">`;
}
const rowCopyData = `${cat.id}\t${cat.name}\t\t${cat.role}\t\t\t${cat.colorUrl}`;
let rowClass = '';
let titleAttr = '';
if (hasExpectedData) {
const exp = expectedCats[cat.id];
if (!exp) {
rowClass = 'cw-row-gray';
titleAttr = 'Этого кота нет в вашей Google Таблице';
} else {
const isNameDiff = cat.name !== exp.name;
const isRoleDiff = cat.role !== exp.role;
const isColorDiff = cat.colorUrl !== exp.colorUrl;
if (isNameDiff || isRoleDiff || isColorDiff) {
rowClass = 'cw-row-red';
let diffs = [];
if (isNameDiff) diffs.push(`Имя (Ожидалось: ${exp.name})`);
if (isRoleDiff) diffs.push(`Должность (Ожидалось: ${exp.role})`);
if (isColorDiff) diffs.push(`Окрас (Ожидался другой)`);
titleAttr = 'Несовпадение: ' + diffs.join(', ');
}
}
}
html += `<tr class="${rowClass}" title="${titleAttr}">
<td style="padding: 5px; border-bottom: 1px solid #444; text-align: center; white-space: nowrap;">
<span class="cw-copy-row-btn" data-copy="${rowCopyData}" title="Скопировать строку">📋</span>
<a href="https://catwar.net/cat${cat.id}" target="_blank" class="cw-profile-btn" title="Перейти в профиль">🐱</a>
</td>
<td style="padding: 5px; border-bottom: 1px solid #444; color: #a8d5ff;">${cat.id}</td>
<td style="padding: 5px; border-bottom: 1px solid #444; font-weight: bold;">${cat.name}</td>
<td style="padding: 5px; border-bottom: 1px solid #444; color: #ffcc80;">${cat.role}</td>
<td style="padding: 5px; border-bottom: 1px solid #444; text-align: center;">${colorHtml}</td>
</tr>`;
});
html += '</table>';
}
wrapper.innerHTML = html;
const copyColorBtns = wrapper.querySelectorAll('.cw-copy-color-btn');
copyColorBtns.forEach(btn => {
btn.addEventListener('click', function() {
const url = this.getAttribute('data-url');
navigator.clipboard.writeText(url).then(() => {
this.style.boxShadow = '0 0 10px #4caf50';
this.style.border = '2px solid #4caf50';
setTimeout(() => {
this.style.boxShadow = 'none';
this.style.border = 'none';
}, 600);
});
});
});
const copyRowBtns = wrapper.querySelectorAll('.cw-copy-row-btn');
copyRowBtns.forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const dataToCopy = this.getAttribute('data-copy');
navigator.clipboard.writeText(dataToCopy).then(() => {
const originalIcon = this.textContent;
this.textContent = '✅';
setTimeout(() => {
this.textContent = originalIcon;
}, 1000);
});
});
});
if (missingWrapper) {
let missingHtml = '';
if (hasExpectedData) {
const missingCats = [];
for (const id in expectedCats) {
if (!catsMemory[id]) {
missingCats.push({ id: id, name: expectedCats[id].name });
}
}
if (missingCats.length > 0) {
missingHtml += `<h3 style="margin-top: 20px; border-bottom: 1px solid #555; padding-bottom: 10px; color: #ff8a80;">Не удалось найти:</h3>`;
missingHtml += `<ul style="margin: 10px 0 0 0; padding-left: 20px; color: #ccc;">`;
missingCats.forEach(cat => {
missingHtml += `<li style="margin-bottom: 5px;">
<span style="font-weight: bold;">${cat.name}</span> —
<a href="https://catwar.net/cat${cat.id}" target="_blank" style="color: #b39ddb; text-decoration: none;">Профиль</a>
</li>`;
});
missingHtml += `</ul>`;
} else if (cats.length > 0) {
missingHtml += `<p style="margin-top: 20px; color: #4caf50; font-weight: bold; text-align: center;">Все ожидаемые коты из таблицы найдены!</p>`;
}
}
missingWrapper.innerHTML = missingHtml;
}
}
function toggleCatsList() {
let container = document.getElementById('cw-cats-table-overlay');
if (container) {
container.remove();
} else {
container = document.createElement('div');
container.id = 'cw-cats-table-overlay';
let html = `
<div style="background: rgba(0,0,0,0.3); padding: 10px; border-radius: 5px; border: 1px solid #444;">
<textarea id="cw-expected-data" style="width: 100%; height: 50px; background: #222; color: #ccc; border: 1px solid #555; border-radius: 4px; padding: 5px; font-size: 12px; resize: vertical;" placeholder="Вставьте скопированные строки из Google Таблицы... (ID - Окрас)"></textarea>
<button id="cw-apply-data" style="margin-top: 5px; width: 100%; padding: 6px; background: #2196f3; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Загрузить / Сверить</button>
</div>
<div id="cw-cats-table-content"></div>
<button id="cw-clear-btn" style="margin-top: 15px; padding: 8px 12px; cursor: pointer; background: #757575; color: white; border: none; border-radius: 4px; width: 100%; font-weight: bold;">Очистить список замеченных</button>
<div id="cw-missing-cats-content"></div>
`;
container.innerHTML = html;
document.body.appendChild(container);
document.getElementById('cw-apply-data').addEventListener('click', processPastedData);
document.getElementById('cw-clear-btn').addEventListener('click', () => {
catsMemory = {};
sessionStorage.removeItem('cw-cats-memory');
renderTableContent();
});
const hasExpectedData = Object.keys(expectedCats).length > 0;
if (hasExpectedData) {
document.getElementById('cw-expected-data').placeholder = `Таблица загружена! Котов в базе: ${Object.keys(expectedCats).length}`;
}
renderTableContent();
}
}
function addTriggerButton() {
if (document.getElementById('cw-trigger-btn')) return;
if (document.getElementById('age_icon') || document.getElementById('age2_icon')) return;
const btn = document.createElement('button');
btn.id = 'cw-trigger-btn';
btn.textContent = '📋 Список котов';
btn.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 10px 15px;
background: #4caf50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
z-index: 99999;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
`;
btn.addEventListener('click', toggleCatsList);
document.body.appendChild(btn);
}
setInterval(() => {
scanCats();
checkProfileAndAddButton();
}, 1500);
setTimeout(addTriggerButton, 2000);
})();