Сбор id сообщений в теме
// ==UserScript==
// @name Lolz.live comment ID Collector
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Сбор id сообщений в теме
// @author eretly
// @match https://lolz.live/threads/*
// @license MIT
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
// Конфигурация (краткое пояснение, посты - комменты людей, а комменты - комментарии к постам).
const config = {
targetUsername: {
username: "", // Оставьте пустым для сбора всех ID
include: true // true - собирает ТОЛЬКО id постов, где в комментах ответил человек с этим ником, false - ВСЕ id постов КРОМЕ постов, где в комментах ответил человек с этим ником.
},
idFormat: '"${id}":', // ${id} это оставляем и как угодно заворачиваем
copySeparator: "\n", // отступы типо, можно пробел там либо перенос или ваще похуй.
showExampleOnCopy: true, // Это при копировании показывает сбоку в менюшке, как выглядит формат 1 айдишки.
resume: true // НЕ ТРОГАТЬ
};
const panel = document.createElement('div');
panel.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
background: #2c3e50;
padding: 10px;
border-radius: 4px;
color: white;
font-family: Arial, sans-serif;
min-width: 280px;
box-shadow: 0 2px 10px rgba(0,0,0,0.5);
`;
const progressBar = document.createElement('div');
progressBar.style.cssText = `
height: 6px;
background: #34495e;
margin: 8px 0;
border-radius: 3px;
overflow: hidden;
`;
const progressFill = document.createElement('div');
progressFill.style.cssText = `
height: 100%;
background: #2ecc71;
width: 0%;
transition: width 0.3s;
`;
const statsText = document.createElement('div');
statsText.style.cssText = `
font-size: 13px;
margin: 8px 0;
display: flex;
justify-content: space-between;
line-height: 1.4;
`;
const buttonsContainer = document.createElement('div');
buttonsContainer.style.cssText = `
display: flex;
gap: 5px;
margin-top: 8px;
`;
const actionBtn = document.createElement('button');
actionBtn.style.cssText = `
padding: 6px 12px;
background: #27ae60;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
flex: 1;
font-weight: bold;
`;
const copyBtn = document.createElement('button');
copyBtn.textContent = 'Копировать';
copyBtn.style.cssText = `
padding: 6px 12px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
flex: 1;
`;
const resetBtn = document.createElement('button');
resetBtn.textContent = 'Сброс';
resetBtn.style.cssText = `
padding: 6px 12px;
background: #e74c3c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
flex: 1;
`;
progressBar.appendChild(progressFill);
panel.appendChild(progressBar);
panel.appendChild(statsText);
buttonsContainer.appendChild(actionBtn);
buttonsContainer.appendChild(copyBtn);
buttonsContainer.appendChild(resetBtn);
panel.appendChild(buttonsContainer);
document.body.appendChild(panel);
let totalPages = 1;
let foundPostIds = [];
let processedPages = [];
let nextPageToProcess = 1;
if (config.resume) {
const savedState = GM_getValue('scanState', null);
if (savedState) {
foundPostIds = savedState.foundPostIds || [];
processedPages = savedState.processedPages || [];
nextPageToProcess = savedState.nextPageToProcess || 1;
totalPages = savedState.totalPages || 1;
updateUI();
}
}
function saveState() {
GM_setValue('scanState', {
foundPostIds: foundPostIds,
processedPages: processedPages,
nextPageToProcess: nextPageToProcess,
totalPages: totalPages
});
}
function updateUI() {
updateTotalPages();
const currentPage = getCurrentPage();
const isLastPage = currentPage >= totalPages;
if (processedPages.length === 0) {
actionBtn.textContent = 'Начать';
} else if (isLastPage) {
actionBtn.textContent = 'Закончить';
} else {
actionBtn.textContent = 'Продолжить';
}
const progressPercent = Math.min(100, (processedPages.length / totalPages) * 100);
progressFill.style.width = `${progressPercent}%`;
statsText.innerHTML = `
<div>
<div>Обработано: <strong>${processedPages.length}</strong> из <strong>${totalPages}</strong></div>
<div>Найдено: <strong>${foundPostIds.length}</strong> ID</div>
</div>
<div style="text-align:right">
<div>Прогресс: <strong>${Math.round(progressPercent)}%</strong></div>
<div style="font-size:11px;color:#${processedPages.length > 0 ? '2ecc71' : 'bdc3c7'}">
${isLastPage ? '✓ Сканирование завершено' : processedPages.length > 0 ? '✓ Готово к продолжению' : 'Готово к началу'}
</div>
</div>
`;
}
function updateTotalPages() {
const pageNav = document.querySelector('.PageNav');
if (pageNav) {
totalPages = parseInt(pageNav.dataset.last) || 1;
} else {
const lastPageLink = document.querySelector('a.pageNav-jump--last');
if (lastPageLink) {
const match = lastPageLink.href.match(/page-(\d+)/);
totalPages = match ? parseInt(match[1]) : 1;
} else {
totalPages = 1;
}
}
}
function scanCurrentPage() {
const currentPage = getCurrentPage();
const isLastPage = currentPage >= totalPages;
if (processedPages.includes(currentPage)) {
if (!isLastPage) goToNextPage();
return;
}
const posts = document.querySelectorAll('li[id^="post-"]:not([id^="post-comment-"]):not(.firstPost)');
let foundOnThisPage = 0;
posts.forEach(post => {
const postId = post.id.replace('post-', '');
if (!config.targetUsername.username) {
if (!foundPostIds.includes(postId)) {
foundPostIds.push(postId);
foundOnThisPage++;
}
return;
}
const comments = post.querySelectorAll('.comment');
let hasTargetUserInComments = false;
comments.forEach(comment => {
const commentAuthor = comment.querySelector('.username')?.textContent || '';
if (commentAuthor.includes(config.targetUsername.username)) {
hasTargetUserInComments = true;
}
});
if (
(config.targetUsername.include && hasTargetUserInComments) ||
(!config.targetUsername.include && !hasTargetUserInComments)
) {
if (!foundPostIds.includes(postId)) {
foundPostIds.push(postId);
foundOnThisPage++;
}
}
});
if (!processedPages.includes(currentPage)) {
processedPages.push(currentPage);
processedPages.sort((a, b) => a - b);
}
updateNextPageToProcess();
updateUI();
saveState();
if (!isLastPage) {
setTimeout(goToNextPage, 300);
}
}
function getCurrentPage() {
const pageMatch = window.location.href.match(/page-(\d+)/);
return pageMatch ? parseInt(pageMatch[1]) : 1;
}
function updateNextPageToProcess() {
for (let i = 1; i <= totalPages; i++) {
if (!processedPages.includes(i)) {
nextPageToProcess = i;
return;
}
}
nextPageToProcess = totalPages + 1;
}
function goToNextPage() {
if (nextPageToProcess > totalPages) {
return;
}
const threadId = window.location.pathname.split('/')[2];
window.location.href = `https://lolz.live/threads/${threadId}/page-${nextPageToProcess}`;
}
function formatIds(ids) {
return ids.map(id => config.idFormat.replace('${id}', id)).join(config.copySeparator);
}
function copyResults() {
if (foundPostIds.length === 0) {
statsText.innerHTML = `
<div>Нет ID для копирования</div>
`;
return;
}
const formattedIds = formatIds(foundPostIds);
navigator.clipboard.writeText(formattedIds)
.then(() => {
let copyMessage = `<div>Скопировано: <strong>${foundPostIds.length}</strong> ID</div>`;
if (config.showExampleOnCopy) {
copyMessage += `<div style="font-size:11px">Формат: ${formattedIds.split(config.copySeparator)[0]}</div>`;
}
statsText.innerHTML = copyMessage;
})
.catch(err => {
console.error('Ошибка копирования:', err);
statsText.innerHTML = `
<div>Ошибка при копировании</div>
`;
});
}
function resetProgress() {
foundPostIds = [];
processedPages = [];
nextPageToProcess = 1;
saveState();
updateUI();
statsText.innerHTML = `
<div>Прогресс сброшен</div>
<div style="color:#2ecc71">Готово к началу</div>
`;
progressFill.style.width = '0%';
}
actionBtn.addEventListener('click', scanCurrentPage);
copyBtn.addEventListener('click', copyResults);
resetBtn.addEventListener('click', resetProgress);
updateUI();
})();