[hideprofile]
[html]<div class="gemini_topic">
<div class="gemini_topic_title">
<div class="gemini_topic_humanized">удаления</div>
<div class="gemini_topic_directive">G:\accounts_deletion</div>
</div>
<div class="gemini_topic_main">
<p>дорогой пользователь, для того чтобы твой аккаунт оставался в активной базе данных, и ты всегда мог получать актуальную информацию о проекте, необходимо регулярно чистить чаты твоих запросов, переносить их в архив, а также следить за статусом твоей подписки plus pro — ее активация дает тебе доступ к расширенным возможностям на проекте.</p>
<p>для сохранения подписки активной настоятельно советуем освежить в памяти пользовательское соглашение и убедиться, что ты выполняешь все его условия. если что-то осталось непонятным — смело пиши gemini team.</p>
<div class="gemini_topic_minititle">обновление внешностей</div>
<p>минимальная активность для сохранения внешности:</p>
<p class="point">одно сообщение <b>в две недели</b> в общедоступных темах, а также в личных эпизодах.</p>
<p>напоминаем, если ты остаешься неактивным две недели, другой пользователь может оформить запрос на дубль твоей внешности. если ты остаешься неактивным, а запрос на дубль не поступает, то твоя внешность автоматически снимается в чистку внешностей, но профиль остается за тобой.</p>
<p>также мы уведомляем, что при попадании в список освободившихся внешностей твой профиль переносится в другую группу — деактивированных: это означает, что тебе необходимо определиться с выбором внешности (старой все еще свободной или новой).</p>
<p>если ты решаешь внести правки в анкету или переписать персонажа для новой внешности необходимо подать свежую анкету. в ином случае предупреди gemini team, что хочешь оставить старую анкету и отметить старую внешность заново, если та все еще является свободной.</p>
<p class="point">обновление каталога внешностей и публикация списка аккаунтов, попадающих под удаление, происходит <b>первого числа</b> месяца.</p>
<p class="point">список уже освободившихся внешностей выкладывается для информирования. если ты снова получаешь доступ к своему аккаунту, ты всегда можешь заново занять свою внешность, если ее не забрали. при условии дубля — внешность остается за тем, у кого этот дубль был.</p>
<p>помни, что залог сохранения внешности — активность по сообщениям раз в две недели.</p>
<div class="gemini_topic_minititle">удаление профилей</div>
<p>напоминаем, что минимальная игровая активность для сохранения аккаунта — <b>один пост в два месяца.</b> если активация не прошла успешно, и аккаунт попал в список на удаление, действуют следующие пункты:</p>
<p class="point">после информирования по списку у тебя есть <b>семь дней,</b> чтобы написать пост и отметиться у gemini team, тогда твой аккаунт будет сохранен.</p>
<p class="point">если у тебя нет возможности написать пост, ты можешь воспользоваться <u>ваучером,</u> оставить (в зоне флуда) <u>двести сообщений</u> или <u>тридцать снс.</u> однако это действует <b>разово,</b> то есть в следующее удаление аккаунтов ты обязуешься написать пост.</p>
<p>настоятельно рекомендуем самостоятельно следить за своей активностью и сроками по внешностям и постам. один пост в два месяца — твой профиль сохранен, но при этом следи за сообщениями, чтобы сохранить и внешность. не дочитать пользовательское соглашение — твой выбор. в случае непредвиденных ситуаций ты всегда можешь обратиться к gemini team.</p>
</div>
</div>[/html]
[html]<script>
(function() {
// Запуск только для администратора (GroupID == 1)
if (typeof GroupID !== 'undefined' && GroupID !== 1) {
return;
}
const TARGET_FORUMS = [6, 7, 22];
const decoder = new TextDecoder("windows-1251");
function parseDate(str) {
if (!str || str === '—' || str === '-' || /никогда/i.test(str)) return null;
const now = new Date();
const t = str.trim();
if (/сегодня/i.test(t)) {
const m = t.match(/(\d{1,2})
\d{2})/);
const h = m ? parseInt(m[1], 10) : 0;
const min = m ? parseInt(m[2], 10) : 0;
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), h, min).getTime();
}
if (/вчера/i.test(t)) {
const m = t.match(/(\d{1,2})
\d{2})/);
const h = m ? parseInt(m[1], 10) : 0;
const min = m ? parseInt(m[2], 10) : 0;
return new Date(now.getTime() - 86400000).setHours(h, min, 0, 0);
}
const ym = t.match(/(\d{4})[.\/-](\d{1,2})[.\/-](\d{1,2})(?:\s+(\d{1,2})
\d{2}))?/);
if (ym) {
return new Date(ym[1], ym[2] - 1, ym[3], ym[4] || 0, ym[5] || 0).getTime();
}
const dm = t.match(/(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})(?:\s+(\d{1,2})
\d{2}))?/);
if (dm) {
return new Date(dm[3], dm[2] - 1, dm[1], dm[4] || 0, dm[5] || 0).getTime();
}
const p = Date.parse(t);
return isNaN(p) ? null : p;
}
// 1. Ищем ВСЕ посты со списками чистки на текущей странице и берем САМЫЙ ПОСЛЕДНИЙ (актуальный)
const allCleanPosts = Array.from(document.querySelectorAll('.blockpost, .post')).filter(p => {
return p.querySelector('.gemini_topic') || p.innerText.includes('удаление профилей') || p.innerText.includes('G:\\deleting_users');
});
if (allCleanPosts.length === 0) {
return; // На этой странице нет списков чистки
}
const latestPost = allCleanPosts[allCleanPosts.length - 1];
// 2. Встраиваем панель строго в конец актуального поста
const panel = document.createElement('div');
panel.id = 'clean-updater-panel';
panel.style.cssText = 'margin: 15px 0 5px 0; padding: 10px 14px; background: rgba(0,0,0,0.06); border: 1px dashed #3182ce; border-radius: 6px; font-family: inherit; font-size: 13px; clear: both;';
panel.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap;">
<span style="font-weight: bold; color: #2b6cb0;">Актуализация текущего списка чистки:</span>
<button id="run-auto-strike-btn" type="button" style="background: #3182ce; color: #fff; border: none; padding: 6px 14px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 12px;">
Проверить и вычеркнуть
</button>
</div>
<div id="auto-strike-status" style="margin-top: 6px; font-size: 12px; color: #666;">
Будут проверены игроки именно этого сообщения на новые посты в #6, #7, #22 или смену группы.
</div>
`;
// Вставляем панель внутрь блока сообщения (.post-content или сам .post)
const contentBox = latestPost.querySelector('.post-content, .post-box, .container') || latestPost;
contentBox.appendChild(panel);
const btn = panel.querySelector('#run-auto-strike-btn');
const statusEl = panel.querySelector('#auto-strike-status');
function setStatus(text, color = '#666') {
if (statusEl) {
statusEl.innerHTML = text;
statusEl.style.color = color;
}
}
// Сбор только тех тем, где дата ответа >= postTime
async function getFreshTopics(forumId, sinceMs) {
const topics = [];
let page = 1;
let keepGoing = true;
while (keepGoing) {
const res = await fetch(`/viewforum.php?id=${forumId}&p=${page}`);
const buffer = await res.arrayBuffer();
const doc = new DOMParser().parseFromString(decoder.decode(buffer), 'text/html');
const rows = Array.from(doc.querySelectorAll('tr')).filter(r => r.querySelector('a[href*="viewtopic.php?id="]'));
if (rows.length === 0) break;
let allRowsOlder = true;
for (const row of rows) {
const tLink = row.querySelector('.tcl a[href*="viewtopic.php?id="], a[href*="viewtopic.php?id="]');
if (!tLink) continue;
const m = tLink.getAttribute('href').match(/viewtopic\.php\?id=(\d+)/);
if (!m) continue;
const tid = m[1];
const lastPostCell = row.querySelector('.tcr, td:last-child');
const lastPostText = lastPostCell ? (lastPostCell.innerText || '').trim() : '';
const dm = lastPostText.match(/\d{4}-\d{2}-\d{2}/) || lastPostText.match(/\d{2}\.\d{2}\.\d{4}/) || lastPostText.match(/(?:сегодня|вчера)/i);
let lastPostTime = null;
if (dm) {
lastPostTime = parseDate(dm[0]);
}
if (!lastPostTime || lastPostTime >= sinceMs) {
allRowsOlder = false;
if (!topics.includes(tid)) {
topics.push(tid);
}
}
}
if (allRowsOlder && page > 1) {
keepGoing = false;
break;
}
const hasNextPage = Array.from(doc.querySelectorAll('a[href*="viewforum.php"]')).some(a => {
const href = a.getAttribute('href') || '';
return href.includes(`p=${page + 1}`);
});
if (!hasNextPage) {
keepGoing = false;
} else {
page++;
}
}
return topics;
}
// Поиск авторов постов после публикации актуального списка
async function findAuthorsSince(topicId, sinceMs, activeIdsSet) {
const firstRes = await fetch(`/viewtopic.php?id=${topicId}&p=1`);
const firstBuf = await firstRes.arrayBuffer();
const firstDoc = new DOMParser().parseFromString(decoder.decode(firstBuf), 'text/html');
let totalPages = 1;
firstDoc.querySelectorAll('.pagelink a, .pagination a, .pages a').forEach(a => {
const num = parseInt(a.textContent.trim(), 10);
if (!isNaN(num) && num > totalPages) totalPages = num;
});
for (let p = totalPages; p >= 1; p--) {
let doc = firstDoc;
if (p !== 1) {
const res = await fetch(`/viewtopic.php?id=${topicId}&p=${p}`);
const buf = await res.arrayBuffer();
doc = new DOMParser().parseFromString(decoder.decode(buf), 'text/html');
}
const posts = Array.from(doc.querySelectorAll('.blockpost, .post'));
let foundOlderInPage = false;
for (let i = posts.length - 1; i >= 0; i--) {
if (p === 1 && i === 0) continue; // Пропускаем шапку темы
const post = posts[i];
let postTime = null;
const dataPosted = post.getAttribute('data-posted');
if (dataPosted) {
postTime = parseInt(dataPosted, 10) * 1000;
}
if (!postTime) {
const plink = post.querySelector('.permalink, .post-date');
postTime = parseDate(plink?.innerText || '');
}
if (postTime) {
if (postTime >= sinceMs) {
let uid = post.getAttribute('data-user-id');
if (!uid || uid === '0') {
const authLink = post.querySelector('.post-author a[href*="profile.php?id="], a[href*="profile.php?id="]');
if (authLink) {
const m = authLink.getAttribute('href').match(/profile\.php\?id=(\d+)/);
if (m) uid = m[1];
}
}
if (uid && uid !== '1' && uid !== '0') {
activeIdsSet.add(String(uid));
}
} else {
foundOlderInPage = true;
}
}
}
if (foundOlderInPage) break;
}
}
btn.addEventListener('click', async function() {
btn.disabled = true;
setStatus('Подготовка к актуализации актуального сообщения...');
try {
// ID именно актуального поста
const postIdMatch = (latestPost.id || '').match(/\d+/) || (latestPost.querySelector('a[href*="pid="]')?.href || '').match(/pid=(\d+)/);
if (!postIdMatch) throw new Error('Не удалось определить ID сообщения.');
const postId = postIdMatch[1] || postIdMatch[0];
// Дата публикации актуального сообщения
let postTime = null;
const dataPosted = latestPost.getAttribute('data-posted');
if (dataPosted) {
postTime = parseInt(dataPosted, 10) * 1000;
} else {
const plink = latestPost.querySelector('.permalink, .post-date');
postTime = parseDate(plink?.innerText || '');
}
if (!postTime) {
postTime = Date.now() - (7 * 86400000);
}
// Загружаем форму редактирования именно этого поста
setStatus(`Загрузка формы редактирования поста #${postId}...`);
const editResp = await fetch(`/edit.php?id=${postId}`);
const editBuf = await editResp.arrayBuffer();
const editDoc = new DOMParser().parseFromString(decoder.decode(editBuf), 'text/html');
const reqForm = editDoc.querySelector('form[action*="edit.php"]');
const textarea = editDoc.querySelector('textarea[name="req_message"]');
if (!textarea || !reqForm) throw new Error('Не удалось открыть форму редактирования поста.');
let messageText = textarea.value;
// Ищем пользователей в формате шаблона
const mentionRegex = /<a class="post-mention" href="\/profile\.php\?id=(\d+)">([^<]+)<\/a>/gi;
const foundUsers = [];
let m;
while ((m = mentionRegex.exec(messageText)) !== null) {
foundUsers.push({
fullMatch: m[0],
id: m[1],
name: m[2],
index: m.index
});
}
if (foundUsers.length === 0) {
setStatus('В этом посте не найдено пользователей в формате чистки.', '#d69e2e');
btn.disabled = false;
return;
}
const idxAnketa = messageText.indexOf('не подана анкета');
const idxActivity = messageText.indexOf('нет активности');
const idxPosts = messageText.indexOf('нет постов');
// Кандидаты группы 5
const g5Candidates = foundUsers.filter(u => {
if (u.index <= idxPosts) return false;
const checkWindow = messageText.substring(Math.max(0, u.index - 30), Math.min(messageText.length, u.index + u.fullMatch.length + 30));
return !/<del>|<s>|\[s\]/i.test(checkWindow);
});
// Сканирование подфорумов 6, 7, 22
const authorsWithNewPosts = new Set();
if (g5Candidates.length > 0) {
setStatus('Сбор свежих тем в #6, #7, #22...');
let freshTopics = [];
for (const fid of TARGET_FORUMS) {
const tList = await getFreshTopics(fid, postTime);
freshTopics = freshTopics.concat(tList);
}
for (let i = 0; i < freshTopics.length; i++) {
setStatus(`Проверка тем (${i + 1}/${freshTopics.length}) на новые посты...`);
await findAuthorsSince(freshTopics[i], postTime, authorsWithNewPosts);
}
}
let struckCount = 0;
// Проверка каждого пользователя
for (let i = 0; i < foundUsers.length; i++) {
const u = foundUsers[i];
const checkWindow = messageText.substring(Math.max(0, u.index - 30), Math.min(messageText.length, u.index + u.fullMatch.length + 30));
if (/<del>|<s>|\[s\]/i.test(checkWindow)) {
continue; // Уже вычеркнут
}
let shouldStrike = false;
if (u.index > idxPosts) {
// Группа 5: написал пост в #6, #7, #22
if (authorsWithNewPosts.has(String(u.id))) {
shouldStrike = true;
}
} else {
// Группы 4 и 8: проверка смены группы
setStatus(`Проверка профиля ${u.name}...`);
let userGroup = null;
try {
const profResp = await fetch(`/profile.php?id=${u.id}`);
if (profResp.ok) {
const profBuf = await profResp.arrayBuffer();
const profDoc = new DOMParser().parseFromString(decoder.decode(profBuf), 'text/html');
profDoc.querySelectorAll('script').forEach(s => s.remove());
const items = profDoc.querySelectorAll('li, tr, dl, div');
for (const el of items) {
const txt = el.innerText || el.textContent;
if (/пользовательские данные/i.test(txt)) continue;
if (/группа/i.test(txt) && !userGroup) {
const dd = el.querySelector('dd, td, span:last-child');
userGroup = (dd ? dd.innerText : txt).replace(/.*группа:?/i, '').trim();
break;
}
}
}
} catch (e) {
console.warn(`Ошибка проверки профиля ${u.name}:`, e);
}
if (u.index > idxAnketa && u.index < idxActivity) {
if (userGroup && !/не принят|нет анкеты|гость/i.test(userGroup)) {
shouldStrike = true;
}
} else if (u.index > idxActivity && u.index < idxPosts) {
if (userGroup && !/деактив|пассив/i.test(userGroup)) {
shouldStrike = true;
}
}
}
if (shouldStrike) {
const replacement = `<del style="opacity:0.45;">${u.fullMatch}</del>`;
messageText = messageText.replace(u.fullMatch, replacement);
struckCount++;
}
}
// Сохранение обновленного поста
if (struckCount > 0) {
setStatus(`Найдено исправившихся: ${struckCount}. Сохранение...`, '#2f855a');
const formData = new FormData(reqForm);
formData.set('req_message', messageText);
const saveResp = await fetch(reqForm.getAttribute('action') || `/edit.php?id=${postId}`, {
method: 'POST',
body: formData
});
if (saveResp.ok) {
setStatus(`Готово! Вычеркнуто: ${struckCount}. Обновляю страницу...`, '#276749');
setTimeout(() => {
window.location.reload();
}, 1200);
} else {
throw new Error('Не удалось сохранить изменения.');
}
} else {
setStatus('Проверка завершена. Никто из этого списка пока не исправился.', '#2b6cb0');
btn.disabled = false;
}
} catch (err) {
console.error(err);
setStatus(`Ошибка: ${err.message}`, '#c53030');
btn.disabled = false;
}
});
})();
</script>[/html]























