Files
2026-04-15 08:00:15 +03:00

296 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
<h2>События Twitch</h2>
<p>Настройте цепочку действий, которые будут выполняться при наступлении события (подписка, рейд, награда и т.д.)</p>
<div class="card">
<label for="event-select">Выберите событие:</label>
<select id="event-select">
<option value="follow">Подписка на канал (follow)</option>
<option value="subscribe">Подписка (subscribe)</option>
<option value="gift_sub">Подарочная подписка (gift_sub)</option>
<option value="raid">Рейд (raid)</option>
<option value="reward_redemption">Награда за баллы (reward_redemption)</option>
</select>
<button id="load-actions">Загрузить действия</button>
</div>
<div class="card" id="actions-editor" style="display: none;">
<h3>Цепочка действий</h3>
<div id="actions-list"></div>
<button id="add-action"> Добавить действие</button>
<button id="save-actions">💾 Сохранить</button>
</div>
<!-- Модальное окно для создания/редактирования действия -->
<div id="action-modal" class="modal">
<div class="modal-content">
<h3 id="modal-title">Действие</h3>
<form id="action-form">
<input type="hidden" id="action-index" value="-1">
<label>Тип действия:</label>
<select id="action-type">
<option value="send_message">Отправить сообщение</option>
<option value="play_sound">Воспроизвести звук</option>
<option value="press_hotkey">Нажать горячую клавишу</option>
<option value="http_request">HTTP запрос (GET)</option>
<option value="run_program">Запустить программу</option>
<option value="send_alert">Отправить уведомление (alert)</option>
</select>
<div id="fields-container"></div>
<div style="margin-top: 15px;">
<button type="submit">Сохранить</button>
<button type="button" id="close-action-modal">Отмена</button>
</div>
</form>
</div>
</div>
<script>
let currentPlatform = "twitch";
let currentEvent = "";
let actions = [];
let editingIndex = -1;
// Списки для выпадающих полей
let soundFiles = [];
let imageFiles = [];
const eventSelect = document.getElementById('event-select');
const loadBtn = document.getElementById('load-actions');
const actionsDiv = document.getElementById('actions-list');
const editorDiv = document.getElementById('actions-editor');
const addBtn = document.getElementById('add-action');
const saveBtn = document.getElementById('save-actions');
const actionModal = document.getElementById('action-modal');
const actionTypeSelect = document.getElementById('action-type');
const fieldsContainer = document.getElementById('fields-container');
const actionForm = document.getElementById('action-form');
const actionIndexInput = document.getElementById('action-index');
const closeModalBtn = document.getElementById('close-action-modal');
// Загрузка списков звуков и изображений
async function loadSoundList() {
const res = await fetch('/api/sounds/list');
if (res.ok) {
soundFiles = await res.json();
} else {
soundFiles = [];
}
}
async function loadImageList() {
const res = await fetch('/api/images/list');
if (res.ok) {
imageFiles = await res.json();
} else {
imageFiles = [];
}
}
// Функция отображения полей в зависимости от типа
function renderFieldsForType(type, data = {}) {
let html = '';
switch (type) {
case 'send_message':
html = `<label>Текст сообщения:</label><textarea id="action-text" rows="3" style="width:100%;">${escapeHtml(data.text || '')}</textarea>
<small>Доступны плейсхолдеры: {{.username}}, {{.reward_title}}, {{.tier}}, {{.viewers}} и др.</small>`;
break;
case 'play_sound':
html = `<label>Звуковой файл:</label>
<select id="action-sound-file" style="width:100%;">
<option value="">— без звука —</option>
${soundFiles.map(f => `<option value="data/sounds/${f}" ${data.sound_file === `data/sounds/${f}` ? 'selected' : ''}>${f}</option>`).join('')}
</select>`;
break;
case 'press_hotkey':
html = `<label>Комбинация клавиш:</label><input type="text" id="action-keys" style="width:100%;" value="${escapeHtml(data.keys || '')}" placeholder="CTRL+ALT+Q">
<small>Поддерживаемые модификаторы: CTRL, ALT, SHIFT, WIN. Основные клавиши: A-Z, 0-9, F1-F12, SPACE, ENTER и др.</small>`;
break;
case 'http_request':
html = `<label>URL (GET):</label><input type="text" id="action-url" style="width:100%;" value="${escapeHtml(data.url || '')}">`;
break;
case 'run_program':
html = `<label>Полный путь к исполняемому файлу:</label><input type="text" id="action-executable" style="width:100%;" value="${escapeHtml(data.executable || '')}">
<label>Аргументы (через пробел):</label><input type="text" id="action-args" style="width:100%;" value="${escapeHtml(data.args || '')}">`;
break;
case 'send_alert':
html = `<label>Заголовок:</label><input type="text" id="action-title" style="width:100%;" value="${escapeHtml(data.title || '')}">
<label>Текст:</label><textarea id="action-alert-text" rows="2" style="width:100%;">${escapeHtml(data.alert_text || '')}</textarea>
<label>Изображение:</label>
<select id="action-image" style="width:100%;">
<option value="">— без изображения —</option>
${imageFiles.map(f => `<option value="/static/${f}" ${data.image === `/static/${f}` ? 'selected' : ''}>${f}</option>`).join('')}
</select>
<label>Звук:</label>
<select id="action-sound" style="width:100%;">
<option value="">— без звука —</option>
${soundFiles.map(f => `<option value="/sounds/${f}" ${data.sound_file === `/sounds/${f}` ? 'selected' : ''}>${f}</option>`).join('')}
</select>
<label>Длительность (сек):</label><input type="number" id="action-duration" value="${data.duration || 5}" min="1" max="30">
<label>Целевой веб-сервис (ID):</label><input type="number" id="action-target-id" value="${data.target_web_service_id || 0}" min="0">
<small>0 = все alert-сервисы. Узнать ID можно на странице "Веб-сервисы".</small>`;
break;
default:
html = '<p>Неизвестный тип действия</p>';
}
fieldsContainer.innerHTML = html;
}
// Открыть модалку для создания/редактирования
async function openActionModal(index) {
editingIndex = index;
actionIndexInput.value = index;
let data = {};
if (index >= 0 && index < actions.length) {
data = actions[index];
actionTypeSelect.value = data.type;
} else {
actionTypeSelect.value = 'send_message';
}
// Загружаем списки, если ещё не загружены
if (soundFiles.length === 0) await loadSoundList();
if (imageFiles.length === 0) await loadImageList();
renderFieldsForType(actionTypeSelect.value, data);
actionModal.style.display = 'flex';
document.getElementById('modal-title').innerText = index >= 0 ? 'Редактировать действие' : 'Новое действие';
}
// Собрать данные из формы
function collectActionData() {
const type = actionTypeSelect.value;
const base = { type: type };
switch (type) {
case 'send_message':
base.text = document.getElementById('action-text')?.value || '';
break;
case 'play_sound':
base.sound_file = document.getElementById('action-sound-file')?.value || '';
break;
case 'press_hotkey':
base.keys = document.getElementById('action-keys')?.value || '';
break;
case 'http_request':
base.url = document.getElementById('action-url')?.value || '';
break;
case 'run_program':
base.executable = document.getElementById('action-executable')?.value || '';
base.args = document.getElementById('action-args')?.value || '';
break;
case 'send_alert':
base.title = document.getElementById('action-title')?.value || '';
base.alert_text = document.getElementById('action-alert-text')?.value || '';
base.image = document.getElementById('action-image')?.value || '';
base.sound_file = document.getElementById('action-sound')?.value || '';
base.duration = parseInt(document.getElementById('action-duration')?.value) || 5;
base.target_web_service_id = parseInt(document.getElementById('action-target-id')?.value) || 0;
break;
}
return base;
}
// Отобразить список действий
function renderActions() {
actionsDiv.innerHTML = '';
if (actions.length === 0) {
actionsDiv.innerHTML = '<p>Нет действий. Нажмите "Добавить действие".</p>';
return;
}
actions.forEach((act, idx) => {
const div = document.createElement('div');
div.className = 'action-item';
let typeName = '';
switch (act.type) {
case 'send_message': typeName = '📨 Отправить сообщение'; break;
case 'play_sound': typeName = '🔊 Воспроизвести звук'; break;
case 'press_hotkey': typeName = '⌨️ Нажать клавиши'; break;
case 'http_request': typeName = '🌐 HTTP запрос'; break;
case 'run_program': typeName = '⚙️ Запустить программу'; break;
case 'send_alert': typeName = '🔔 Уведомление (alert)'; break;
default: typeName = act.type;
}
let summary = '';
if (act.type === 'send_message') summary = act.text;
else if (act.type === 'play_sound') summary = act.sound_file;
else if (act.type === 'press_hotkey') summary = act.keys;
else if (act.type === 'http_request') summary = act.url;
else if (act.type === 'run_program') summary = act.executable + (act.args ? ' ' + act.args : '');
else if (act.type === 'send_alert') summary = act.title + ' / ' + act.alert_text;
div.innerHTML = `
<strong>${typeName}</strong><br>
<small>${escapeHtml(summary)}</small><br>
<button class="edit-action" data-idx="${idx}">✏️ Редактировать</button>
<button class="remove-action" data-idx="${idx}">🗑️ Удалить</button>
`;
actionsDiv.appendChild(div);
});
document.querySelectorAll('.edit-action').forEach(btn => {
btn.addEventListener('click', (e) => openActionModal(parseInt(btn.dataset.idx)));
});
document.querySelectorAll('.remove-action').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = parseInt(btn.dataset.idx);
actions.splice(idx, 1);
renderActions();
});
});
}
// Загрузка действий с сервера
async function loadEventActions() {
currentEvent = eventSelect.value;
const res = await fetch(`/api/events?platform=${currentPlatform}&event=${currentEvent}`);
if (res.ok) {
actions = await res.json();
if (!Array.isArray(actions)) actions = [];
renderActions();
editorDiv.style.display = 'block';
} else {
alert('Ошибка загрузки действий');
}
}
// Сохранение действий
async function saveEventActions() {
const res = await fetch(`/api/events?platform=${currentPlatform}&event=${currentEvent}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(actions)
});
if (res.ok) {
alert('Действия сохранены!');
} else {
alert('Ошибка сохранения');
}
}
// Обработчики UI
loadBtn.addEventListener('click', loadEventActions);
addBtn.addEventListener('click', () => openActionModal(-1));
saveBtn.addEventListener('click', saveEventActions);
actionForm.addEventListener('submit', (e) => {
e.preventDefault();
const newAction = collectActionData();
if (editingIndex >= 0 && editingIndex < actions.length) {
actions[editingIndex] = newAction;
} else {
actions.push(newAction);
}
renderActions();
actionModal.style.display = 'none';
});
actionTypeSelect.addEventListener('change', () => {
const data = editingIndex >= 0 ? actions[editingIndex] : {};
renderFieldsForType(actionTypeSelect.value, data);
});
closeModalBtn.addEventListener('click', () => actionModal.style.display = 'none');
window.addEventListener('click', (e) => { if (e.target === actionModal) actionModal.style.display = 'none'; });
function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&amp;'; if (m === '<') return '&lt;'; if (m === '>') return '&gt;'; return m; }); }
</script>
{{end}}