[html]
<div class="aquests">
<!-- Сюда вставится таблица -->
</div>
<script>
// ============================================
// СИСТЕМА ЗАДАНИЙ V2: Парсинг HTML + API
// ============================================
// Конфигурация
const QUEST_SYSTEM = {
forumUrl: 'https://warframe.f-rpg.me/viewforum.php?id=18', // Форум с заданиями
apiBase: 'https://warframe.f-rpg.me/api.php',
currentUserId: null, // Будет определён автоматически
containerSelector: '.aquests', // Куда вставлять таблицу
debug: true // Поставь false, чтобы убрать логи в консоль
};
// Главная функция инициализации
async function initQuestSystem() {
if (QUEST_SYSTEM.debug) console.log('[Quests V2] Инициализация системы...');
// 1. Пытаемся определить ID текущего пользователя
await determineCurrentUserId();
if (!QUEST_SYSTEM.currentUserId) {
showError('Не удалось определить ID пользователя. Войдите на форум.');
return;
}
if (QUEST_SYSTEM.debug) console.log('[Quests V2] ID пользователя:', QUEST_SYSTEM.currentUserId);
// 2. Загружаем и парсим страницу форума с заданиями
const quests = await fetchAndParseForum();
if (quests.length === 0) {
showMessage('Нет активных заданий.');
return;
}
// 3. Для каждого задания проверяем участие пользователя через API
const questsWithStatus = await checkUserParticipation(quests);
// 4. Генерируем и вставляем таблицу
renderQuestsTable(questsWithStatus);
}
// Функция 1: Определяем ID текущего пользователя
async function determineCurrentUserId() {
try {
// Сначала проверяем глобальную переменную (если она есть от старого скрипта)
if (window.currentWOTVUser && window.currentWOTVUser.user_id) {
QUEST_SYSTEM.currentUserId = window.currentWOTVUser.user_id;
return;
}
// Пробуем найти ID в ссылках на аватар в текущей странице
const avatarLink = document.querySelector('a[href*="profile.php?id="]');
if (avatarLink) {
const match = avatarLink.href.match(/profile\.php\?id=(\d+)/);
if (match) {
QUEST_SYSTEM.currentUserId = match[1];
return;
}
}
// Последний вариант: делаем тестовый запрос к API для получения своего ID
const testResponse = await fetch(`${QUEST_SYSTEM.apiBase}?method=users.get&fields=user_id&limit=1`);
if (testResponse.ok) {
const data = await testResponse.json();
if (data.response && data.response.length > 0) {
QUEST_SYSTEM.currentUserId = data.response[0].user_id;
}
}
} catch (error) {
if (QUEST_SYSTEM.debug) console.warn('[Quests V2] Не удалось определить ID пользователя:', error);
}
}
// Функция 2: Загружаем и парсим страницу форума
async function fetchAndParseForum() {
try {
const response = await fetch(QUEST_SYSTEM.forumUrl);
const html = await response.text();
// Создаём временный элемент для парсинга
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Ищем таблицу с темами
const topicsTable = doc.querySelector('table[summary*="Список тем"] tbody');
if (!topicsTable) {
if (QUEST_SYSTEM.debug) console.log('[Quests V2] Таблица тем не найдена');
return [];
}
const quests = [];
const topicRows = topicsTable.querySelectorAll('tr');
topicRows.forEach(row => {
const topicLink = row.querySelector('a[href*="viewtopic.php?id="]');
if (!topicLink) return;
// Извлекаем ID темы из ссылки
const topicIdMatch = topicLink.href.match(/viewtopic\.php\?id=(\d+)/);
if (!topicIdMatch) return;
const topicId = topicIdMatch[1];
const topicTitle = topicLink.textContent.trim();
// Парсим название для определения фракции
const parsedTitle = parseQuestTitle(topicTitle);
if (!parsedTitle) return;
quests.push({
id: topicId,
title: topicTitle,
url: topicLink.href,
...parsedTitle,
createdDate: new Date(), // Можно было бы получить из таблицы, но пока текущая дата
status: 'unknown',
userPost: null
});
});
if (QUEST_SYSTEM.debug) console.log(`[Quests V2] Найдено заданий: ${quests.length}`, quests);
return quests;
} catch (error) {
if (QUEST_SYSTEM.debug) console.error('[Quests V2] Ошибка парсинга форума:', error);
showError('Ошибка загрузки списка заданий.');
return [];
}
}
// Функция 3: Проверяем участие пользователя в каждом задании
async function checkUserParticipation(quests) {
const checkPromises = quests.map(async (quest) => {
try {
// Делаем запрос к API для получения сообщений в теме
const apiUrl = `${QUEST_SYSTEM.apiBase}?method=post.get&topic_id=${quest.id}&fields=user_id,id,posted&limit=200`;
const response = await fetch(apiUrl);
if (!response.ok) {
quest.status = 'error';
return quest;
}
const data = await response.json();
// Ищем сообщения текущего пользователя в этой теме
const userPosts = data.response?.filter(post =>
post.user_id === QUEST_SYSTEM.currentUserId
) || [];
// Определяем статус задания
if (userPosts.length > 0) {
quest.status = 'completed';
quest.userPost = userPosts[0];
quest.completedAt = userPosts[0].posted;
} else {
// Проверяем срок выполнения (7 дней)
const daysSinceCreation = Math.floor((new Date() - quest.createdDate) / (1000 * 60 * 60 * 24));
quest.status = daysSinceCreation > 7 ? 'failed' : 'active';
}
return quest;
} catch (error) {
if (QUEST_SYSTEM.debug) console.error(`[Quests V2] Ошибка проверки темы ${quest.id}:`, error);
quest.status = 'error';
return quest;
}
});
return await Promise.all(checkPromises);
}
// Функция 4: Рендерим таблицу с заданиями
function renderQuestsTable(quests) {
const container = document.querySelector(QUEST_SYSTEM.containerSelector);
if (!container) {
if (QUEST_SYSTEM.debug) console.error(`[Quests V2] Контейнер ${QUEST_SYSTEM.containerSelector} не найден`);
return;
}
// Создаём таблицу
const table = document.createElement('table');
table.className = 'quests-table';
table.innerHTML = `
<thead>
<tr>
<th>Фракция</th>
<th>Задание</th>
<th>Статус</th>
<th>Срок</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
${quests.map(quest => `
<tr class="quest-row quest-status-${quest.status} quest-faction-${quest.factionCode.toLowerCase()}">
<td class="faction-cell">
<span class="faction-badge" style="background-color: ${quest.factionColor}">
${quest.factionIcon} ${quest.faction}
</span>
</td>
<td class="title-cell">
<strong>${quest.agent}:</strong> ${quest.description}
<div class="quest-meta">
<small>ID: ${quest.id} | Ответов: ?</small>
</div>
</td>
<td class="status-cell">
<span class="status-badge status-${quest.status}">
${getStatusText(quest.status)}
</span>
${quest.completedAt ? `<br><small>Выполнено: ${formatDate(quest.completedAt)}</small>` : ''}
</td>
<td class="deadline-cell">
${getDeadlineInfo(quest.createdDate, quest.status)}
</td>
<td class="actions-cell">
<a href="${quest.url}" target="_blank" class="quest-link">Перейти</a>
<button onclick="checkSingleQuest(${quest.id})" class="check-btn">Проверить</button>
</td>
</tr>
`).join('')}
</tbody>
`;
// Очищаем контейнер и добавляем таблицу
container.innerHTML = '';
container.appendChild(table);
// Добавляем немного стилей
addQuestStyles();
if (QUEST_SYSTEM.debug) console.log('[Quests V2] Таблица заданий отрисована');
}
// ============================================
// Вспомогательные функции
// ============================================
// Парсинг названия задания (та же функция, что и раньше)
function parseQuestTitle(title) {
const match = title.match(/^\[([^\]]+)\]\s*(.+?):\s*(.+)$/);
if (!match) return null;
const factions = {
'Ополчение': { code: 'FM', color: '#2ecc71', icon: '🛡️' },
'Скальдра': { code: 'FS', color: '#e74c3c', icon: '⚔️' },
'Аутсайдеры': { code: 'FO', color: '#f39c12', icon: '🏴' },
'Ночная волна': { code: 'LEXI', color: '#9b59b6', icon: '🎧' }
};
const factionName = match[1].trim();
const faction = factions[factionName] || { code: 'UNK', color: '#95a5a6', icon: '❓' };
return {
faction: factionName,
factionCode: faction.code,
factionColor: faction.color,
factionIcon: faction.icon,
agent: match[2].trim(),
description: match[3].trim()
};
}
// Текстовое описание статуса
function getStatusText(status) {
const statusMap = {
active: '🟢 Активно',
completed: '✅ Выполнено',
failed: '❌ Провалено',
expired: '⏰ Просрочено',
unavailable: '⚪ Недоступно',
error: '⚠️ Ошибка',
unknown: '❓ Неизвестно'
};
return statusMap[status] || status;
}
// Информация о сроке выполнения
function getDeadlineInfo(createdDate, status) {
if (status === 'completed') return '—';
const now = new Date();
const created = new Date(createdDate);
const deadline = new Date(created.getTime() + 7 * 24 * 60 * 60 * 1000);
const diff = deadline - now;
if (diff <= 0) return 'Истёк';
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
return `Осталось: ${days}д ${hours}ч`;
}
// Форматирование даты
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit'
});
}
// Функции для отображения сообщений
function showError(message) {
const container = document.querySelector(QUEST_SYSTEM.containerSelector);
if (container) {
container.innerHTML = `<div class="quest-error">${message}</div>`;
}
console.error('[Quests V2] Ошибка:', message);
}
function showMessage(message) {
const container = document.querySelector(QUEST_SYSTEM.containerSelector);
if (container) {
container.innerHTML = `<div class="quest-message">${message}</div>`;
}
}
// Добавление стилей
function addQuestStyles() {
const styleId = 'quest-system-styles';
if (document.getElementById(styleId)) return;
const styles = `
.quests-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 14px;
}
.quests-table th {
background: #2c3e50;
color: white;
padding: 12px;
text-align: left;
border-bottom: 3px solid #34495e;
}
.quests-table td {
padding: 12px;
border-bottom: 1px solid #eee;
vertical-align: top;
}
.quests-table tr:hover {
background: #f9f9f9;
}
.faction-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
color: white;
font-size: 12px;
font-weight: bold;
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.status-active { background: #d4edda; color: #155724; }
.status-completed { background: #d1ecf1; color: #0c5460; }
.status-failed { background: #f8d7da; color: #721c24; }
.status-expired { background: #e2e3e5; color: #383d41; }
.quest-link, .check-btn {
display: inline-block;
padding: 6px 12px;
margin: 2px;
border-radius: 4px;
text-decoration: none;
font-size: 12px;
cursor: pointer;
}
.quest-link {
background: #3498db;
color: white;
}
.check-btn {
background: #95a5a6;
color: white;
border: none;
}
.quest-error {
padding: 20px;
background: #f8d7da;
color: #721c24;
border-radius: 8px;
border: 1px solid #f5c6cb;
}
.quest-message {
padding: 20px;
background: #d4edda;
color: #155724;
border-radius: 8px;
border: 1px solid #c3e6cb;
}
.quest-meta {
margin-top: 5px;
color: #7f8c8d;
font-size: 12px;
}
`;
const styleEl = document.createElement('style');
styleEl.id = styleId;
styleEl.textContent = styles;
document.head.appendChild(styleEl);
}
// Глобальные функции для ручной проверки
window.checkSingleQuest = async function(topicId) {
if (!QUEST_SYSTEM.currentUserId) return;
try {
const apiUrl = `${QUEST_SYSTEM.apiBase}?method=post.get&topic_id=${topicId}&fields=user_id,id,posted&limit=200`;
const response = await fetch(apiUrl);
const data = await response.json();
const userPosts = data.response?.filter(post =>
post.user_id === QUEST_SYSTEM.currentUserId
) || [];
if (userPosts.length > 0) {
alert(`✅ Вы выполнили это задание!\nДата: ${formatDate(userPosts[0].posted)}`);
} else {
alert('❌ Вы ещё не участвовали в этом задании.');
}
} catch (error) {
alert('⚠️ Ошибка проверки задания');
}
};
window.refreshQuests = function() {
initQuestSystem();
};
// ============================================
// Запуск системы
// ============================================
// Запускаем после загрузки страницы
document.addEventListener('DOMContentLoaded', function() {
// Ждём немного, чтобы другие скрипты успели инициализироваться
setTimeout(initQuestSystem, 1000);
});
// Также можно запустить вручную: window.refreshQuests()
</script>
[/html]