залил

This commit is contained in:
2026-04-15 08:00:15 +03:00
commit 5549b3545e
51 changed files with 8073 additions and 0 deletions
+434
View File
@@ -0,0 +1,434 @@
{{define "content"}}
<h2>Команды чата</h2>
<div class="card">
<h3>Добавить / редактировать команду</h3>
<form id="command-form">
<input type="hidden" id="command-id" value="0">
<label>Триггер (без !):</label>
<input type="text" id="trigger" required placeholder="ping">
<label>Шаблон ответа:</label>
<textarea id="template" rows="5" required placeholder="Pong! &lt;USERNAME/&gt;"></textarea>
<!-- Панель кнопок для вставки тегов -->
<div style="margin: 8px 0; display: flex; flex-wrap: wrap; gap: 6px;">
<button type="button" class="tag-btn" data-tag="&lt;USERNAME/&gt;">👤 USERNAME</button>
<button type="button" class="tag-btn" data-tag="&lt;ARG/&gt;">📝 ARG</button>
<button type="button" class="tag-btn" data-tag="&lt;AI/&gt;">🤖 AI</button>
<button type="button" class="tag-btn" data-tag="&lt;RANDOMUSER/&gt;">🎲 RANDOMUSER</button>
<button type="button" id="random-btn">🔢 Случайное число</button>
<button type="button" id="song-btn">🎵 Вставить звук</button>
<button type="button" id="group-btn">📦 Обертка group</button>
<button type="button" id="timeout-btn">⏱ Отстранение</button>
</div>
<div>
<button type="button" id="test-btn">🧪 Тестировать</button>
</div>
<label>
<input type="checkbox" id="enabled" checked> Включена
</label>
<label>Кулдаун (секунды):</label>
<input type="number" id="cooldown" value="0" min="0">
<label>Права доступа:</label>
<select id="permission">
<option value="everyone">Все</option>
<option value="moderator">Модераторы</option>
<option value="subscriber">Подписчики</option>
<option value="vip">VIP</option>
<option value="broadcaster">Стример</option>
</select>
<button type="submit">Сохранить</button>
<button type="button" id="cancel-edit" style="background: #6c757d;">Отмена</button>
</form>
</div>
<div class="card">
<h3>Список команд</h3>
<table style="width:100%; border-collapse: collapse;">
<thead>
<tr><th>Триггер</th><th>Шаблон</th><th>Кулдаун</th><th>Права</th><th>Статус</th><th>Действия</th></tr>
</thead>
<tbody id="commands-table">
<tr><td colspan="6">Загрузка...</td></tr>
</tbody>
</table>
</div>
<!-- Модальное окно для тестирования -->
<div id="test-modal" class="modal">
<div class="modal-content">
<h3>Результат тестирования</h3>
<p><strong>Текст для чата:</strong></p>
<pre id="test-result" style="background:#f0f0f0; padding:10px; border-radius:4px; white-space:pre-wrap;"></pre>
<p><strong>Звуковые файлы:</strong> <span id="test-sounds"></span></p>
<button id="close-modal" style="margin-top:10px;">Закрыть</button>
</div>
</div>
<!-- Модальное окно для случайного числа -->
<div id="random-modal" class="modal">
<div class="modal-content">
<h3>Вставить случайное число</h3>
<label>Минимум:</label>
<input type="number" id="random-min" value="1">
<label>Максимум:</label>
<input type="number" id="random-max" value="100">
<div style="margin-top:15px;">
<button id="insert-random-btn">Вставить</button>
<button id="cancel-random-btn">Отмена</button>
</div>
</div>
</div>
<!-- Модальное окно для звука -->
<div id="song-modal" class="modal">
<div class="modal-content">
<h3>Вставить звук</h3>
<label>Выберите звуковой файл:</label>
<select id="song-select" style="width:100%;">
<option value="">-- нет звуков --</option>
</select>
<div style="margin-top:15px;">
<button id="insert-song-btn">Вставить</button>
<button id="cancel-song-btn">Отмена</button>
</div>
</div>
</div>
<!-- Модальное окно для группы -->
<div id="group-modal" class="modal">
<div class="modal-content">
<h3>Создать группу вариантов</h3>
<div id="group-variants">
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 1" style="width:80%;">
</div>
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 2" style="width:80%;">
</div>
</div>
<button type="button" id="add-variant-btn">+ Добавить вариант</button>
<button type="button" id="remove-variant-btn" style="margin-left:10px;"> Удалить последний</button>
<div style="margin-top:15px;">
<button id="insert-group-btn">Вставить</button>
<button id="cancel-group-btn">Отмена</button>
</div>
</div>
</div>
<!-- Модальное окно для отстранения (timeout) -->
<div id="timeout-modal" class="modal">
<div class="modal-content">
<h3>Отстранение (таймаут)</h3>
<label>Количество минут:</label>
<input type="number" id="timeout-minutes" value="5" min="1" max="1440">
<div style="margin-top:15px;">
<button id="insert-timeout-btn">Вставить</button>
<button id="cancel-timeout-btn">Отмена</button>
</div>
</div>
</div>
<script>
let commands = [];
let soundList = [];
async function loadSoundList() {
try {
const res = await fetch('/api/sounds/list');
if (res.ok) {
soundList = await res.json();
const select = document.getElementById('song-select');
select.innerHTML = '<option value="">-- выберите звук --</option>';
soundList.forEach(sound => {
const option = document.createElement('option');
option.value = sound;
option.textContent = sound;
select.appendChild(option);
});
}
} catch(e) { console.error('Failed to load sounds', e); }
}
function insertAtCursor(textareaId, text) {
const textarea = document.getElementById(textareaId);
if (!textarea) return;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
textarea.value = value.slice(0, start) + text + value.slice(end);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
textarea.focus();
}
// Простые теги
document.querySelectorAll('.tag-btn').forEach(btn => {
btn.addEventListener('click', () => {
insertAtCursor('template', btn.dataset.tag);
});
});
// Random
document.getElementById('random-btn').addEventListener('click', () => {
document.getElementById('random-modal').style.display = 'flex';
});
document.getElementById('insert-random-btn').addEventListener('click', () => {
const min = document.getElementById('random-min').value;
const max = document.getElementById('random-max').value;
const tag = `<random s=${min} e=${max}/>`;
insertAtCursor('template', tag);
document.getElementById('random-modal').style.display = 'none';
});
document.getElementById('cancel-random-btn').addEventListener('click', () => {
document.getElementById('random-modal').style.display = 'none';
});
// Song
document.getElementById('song-btn').addEventListener('click', async () => {
await loadSoundList();
document.getElementById('song-modal').style.display = 'flex';
});
document.getElementById('insert-song-btn').addEventListener('click', () => {
const selected = document.getElementById('song-select').value;
if (!selected) {
alert('Выберите звуковой файл');
return;
}
const tag = `<song f="data/sounds/${selected}"/>`;
insertAtCursor('template', tag);
document.getElementById('song-modal').style.display = 'none';
});
document.getElementById('cancel-song-btn').addEventListener('click', () => {
document.getElementById('song-modal').style.display = 'none';
});
// Group
document.getElementById('group-btn').addEventListener('click', () => {
const container = document.getElementById('group-variants');
container.innerHTML = `
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 1" style="width:80%;">
</div>
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 2" style="width:80%;">
</div>
`;
document.getElementById('group-modal').style.display = 'flex';
});
document.getElementById('add-variant-btn').addEventListener('click', () => {
const container = document.getElementById('group-variants');
const newDiv = document.createElement('div');
newDiv.className = 'group-variant';
newDiv.style.marginBottom = '8px';
newDiv.innerHTML = `<input type="text" class="variant-input" placeholder="Новый вариант" style="width:80%;">`;
container.appendChild(newDiv);
});
document.getElementById('remove-variant-btn').addEventListener('click', () => {
const container = document.getElementById('group-variants');
if (container.children.length > 2) {
container.removeChild(container.lastChild);
} else {
alert('Должно быть хотя бы два варианта');
}
});
document.getElementById('insert-group-btn').addEventListener('click', () => {
const inputs = document.querySelectorAll('#group-variants .variant-input');
let variants = [];
inputs.forEach(inp => {
let val = inp.value.trim();
if (val !== '') variants.push(val);
});
if (variants.length < 2) {
alert('Введите хотя бы два непустых варианта');
return;
}
let groupHtml = '<group>\n';
variants.forEach(v => {
groupHtml += ` <g>${escapeHtmlForGroup(v)}</g>\n`;
});
groupHtml += '</group>';
insertAtCursor('template', groupHtml);
document.getElementById('group-modal').style.display = 'none';
});
document.getElementById('cancel-group-btn').addEventListener('click', () => {
document.getElementById('group-modal').style.display = 'none';
});
// Timeout
document.getElementById('timeout-btn').addEventListener('click', () => {
document.getElementById('timeout-modal').style.display = 'flex';
});
document.getElementById('insert-timeout-btn').addEventListener('click', () => {
const minutes = document.getElementById('timeout-minutes').value;
const tag = `<timeout minutes="${minutes}"/>`;
insertAtCursor('template', tag);
document.getElementById('timeout-modal').style.display = 'none';
});
document.getElementById('cancel-timeout-btn').addEventListener('click', () => {
document.getElementById('timeout-modal').style.display = 'none';
});
function escapeHtmlForGroup(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
// Закрытие модалок по клику вне области
window.addEventListener('click', (e) => {
const modals = ['random-modal', 'song-modal', 'group-modal', 'test-modal', 'timeout-modal'];
modals.forEach(id => {
const modal = document.getElementById(id);
if (e.target === modal) modal.style.display = 'none';
});
});
// --- Остальной код команд (без изменений) ---
async function loadCommands() {
const res = await fetch('/api/commands');
commands = await res.json();
renderTable();
}
function renderTable() {
const tbody = document.getElementById('commands-table');
if (!commands.length) {
tbody.innerHTML = '<tr><td colspan="6">Нет команд. Добавьте первую!</td></tr>';
return;
}
tbody.innerHTML = commands.map(cmd => `
<tr>
<td>!${escapeHtml(cmd.Trigger)}</td>
<td style="max-width: 300px; overflow-x: auto;">${escapeHtml(cmd.Template)}</td>
<td>${cmd.CooldownSec}</td>
<td>${cmd.Permission}</td>
<td>${cmd.Enabled ? '✅ Вкл' : '❌ Выкл'}</td>
<td>
<button onclick="editCommand(${cmd.ID})">✏️</button>
<button onclick="deleteCommand(${cmd.ID})">🗑️</button>
</td>
</tr>
`).join('');
}
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;
});
}
function editCommand(id) {
const cmd = commands.find(c => c.ID === id);
if (!cmd) return;
document.getElementById('command-id').value = cmd.ID;
document.getElementById('trigger').value = cmd.Trigger;
document.getElementById('template').value = cmd.Template;
document.getElementById('enabled').checked = cmd.Enabled;
document.getElementById('cooldown').value = cmd.CooldownSec;
document.getElementById('permission').value = cmd.Permission;
document.querySelector('.card').scrollIntoView({ behavior: 'smooth' });
}
async function deleteCommand(id) {
if (!confirm('Удалить команду?')) return;
await fetch(`/api/commands?id=${id}`, { method: 'DELETE' });
loadCommands();
if (document.getElementById('command-id').value == id) resetForm();
}
function resetForm() {
document.getElementById('command-id').value = 0;
document.getElementById('trigger').value = '';
document.getElementById('template').value = '';
document.getElementById('enabled').checked = true;
document.getElementById('cooldown').value = 0;
document.getElementById('permission').value = 'everyone';
}
document.getElementById('cancel-edit').addEventListener('click', resetForm);
document.getElementById('command-form').addEventListener('submit', async (e) => {
e.preventDefault();
const id = parseInt(document.getElementById('command-id').value);
const trigger = document.getElementById('trigger').value.trim();
const template = document.getElementById('template').value;
const enabled = document.getElementById('enabled').checked;
const cooldown = parseInt(document.getElementById('cooldown').value);
const permission = document.getElementById('permission').value;
if (!trigger || !template) {
alert('Заполните триггер и шаблон');
return;
}
const method = id === 0 ? 'POST' : 'PUT';
const url = '/api/commands';
const body = JSON.stringify({ ID: id, Trigger: trigger, Template: template, Enabled: enabled, CooldownSec: cooldown, Permission: permission });
try {
const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body });
if (res.ok) {
loadCommands();
resetForm();
} else {
const err = await res.text();
alert('Ошибка: ' + err);
}
} catch (err) {
alert('Ошибка соединения: ' + err.message);
}
});
document.getElementById('test-btn').addEventListener('click', async () => {
const template = document.getElementById('template').value;
if (!template) {
alert('Введите шаблон для тестирования');
return;
}
const username = prompt('Введите имя пользователя для подстановки (без @):', 'TestUser');
if (!username) return;
try {
const res = await fetch('/api/commands/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ template, username })
});
if (!res.ok) {
const err = await res.text();
alert('Ошибка тестирования: ' + err);
return;
}
const data = await res.json();
document.getElementById('test-result').textContent = data.result;
const soundsSpan = document.getElementById('test-sounds');
if (data.soundFiles && data.soundFiles.length) {
soundsSpan.innerHTML = data.soundFiles.join('<br>');
} else {
soundsSpan.textContent = 'нет';
}
document.getElementById('test-modal').style.display = 'flex';
} catch (err) {
alert('Ошибка: ' + err.message);
}
});
document.getElementById('close-modal').addEventListener('click', () => {
document.getElementById('test-modal').style.display = 'none';
});
loadCommands();
loadSoundList();
</script>
{{end}}