Flatlogic Bot 3f421e9a53 Good Versi
2026-02-26 19:50:11 +00:00

277 lines
11 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const connectBtn = document.getElementById('connectBtn');
if (!connectBtn) return; // Exit if not logged in
const disconnectBtn = document.getElementById('disconnectBtn');
const tiktokUsernameInput = document.getElementById('tiktokUsername');
const connectionStatus = document.getElementById('connectionStatus');
const commentFeed = document.getElementById('commentFeed');
const emptyFeed = document.getElementById('emptyFeed');
const voiceSelect = document.getElementById('voiceSelect');
const rateRange = document.getElementById('rateRange');
const rateValue = document.getElementById('rateValue');
const autoReplyToggle = document.getElementById('autoReplyToggle');
const simulateBtn = document.getElementById('simulateBtn');
const manualCommentInput = document.getElementById('manualComment');
const historyTableBody = document.getElementById('historyTableBody');
const commentCountBadge = document.getElementById('commentCount');
const toastContainer = document.getElementById('toastContainer');
const aiPersonality = document.getElementById('aiPersonality');
let isConnected = false;
let commentCount = 0;
let synth = window.speechSynthesis;
let voices = [];
let pollInterval = null;
let lastEventId = 0;
// Load voices
function populateVoiceList() {
if (!synth) return;
voices = synth.getVoices().sort(function (a, b) {
const aname = a.name.toUpperCase();
const bname = b.name.toUpperCase();
if (aname < bname) return -1;
else if (aname > bname) return 1;
return 0;
});
if (voiceSelect) {
voiceSelect.innerHTML = '';
voices.forEach((voice, i) => {
const option = document.createElement('option');
option.textContent = `${voice.name} (${voice.lang})`;
if (voice.default) option.textContent += ' -- DEFAULT';
option.setAttribute('data-lang', voice.lang);
option.setAttribute('data-name', voice.name);
voiceSelect.appendChild(option);
});
}
}
populateVoiceList();
if (speechSynthesis.onvoiceschanged !== undefined) {
speechSynthesis.onvoiceschanged = populateVoiceList;
}
if (rateRange) {
rateRange.addEventListener('input', () => {
if (rateValue) rateValue.textContent = rateRange.value;
});
}
if (aiPersonality) {
aiPersonality.addEventListener('change', async () => {
const personality = aiPersonality.value;
try {
const resp = await fetch('api/update_personality.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ personality })
});
const data = await resp.json();
if (data.success) {
showToast('Settings Saved', 'AI personality updated successfully.', 'success');
} else {
showToast('Error', data.error || 'Failed to update personality', 'danger');
}
} catch (err) {
showToast('Network Error', 'Could not reach settings API.', 'danger');
}
});
}
async function startBridge(username) {
try {
const resp = await fetch(`api/bridge_control.php?action=start&username=${username}`);
const data = await resp.json();
if (data.success) {
isConnected = true;
connectBtn.classList.add('d-none');
if (disconnectBtn) disconnectBtn.classList.remove('d-none');
if (connectionStatus) connectionStatus.innerHTML = '<span class="status-dot bg-success me-1 pulse"></span> Live connection active: @' + username;
if (emptyFeed) emptyFeed.classList.add('d-none');
showToast('Connected', `Started listening to @${username}. Looking for comments...`, 'success');
startPolling(username);
} else {
showToast('Bridge Error', data.error || 'Failed to start bridge', 'danger');
}
} catch (err) {
showToast('Network Error', 'Could not reach bridge control.', 'danger');
}
}
async function stopBridge(username) {
try {
await fetch(`api/bridge_control.php?action=stop&username=${username}`);
isConnected = false;
connectBtn.classList.remove('d-none');
if (disconnectBtn) disconnectBtn.classList.add('d-none');
if (connectionStatus) connectionStatus.innerHTML = '<span class="status-dot bg-secondary me-1"></span> Disconnected';
showToast('Disconnected', 'Stopped bridge.', 'warning');
if (pollInterval) clearInterval(pollInterval);
} catch (err) {
console.error(err);
}
}
function startPolling(username) {
if (pollInterval) clearInterval(pollInterval);
pollInterval = setInterval(async () => {
if (!isConnected) return;
try {
const resp = await fetch(`api/get_updates.php?username=${username}`);
const data = await resp.json();
if (data.events && data.events.length > 0) {
data.events.forEach(event => {
// Only add if not already seen
if (event.id > lastEventId) {
addEventToFeed(event);
lastEventId = event.id;
}
});
}
} catch (err) {
console.error('Polling error:', err);
}
}, 3000); // Poll every 3 seconds
}
function addEventToFeed(event) {
commentCount++;
if (commentCountBadge) commentCountBadge.textContent = `${commentCount} events`;
const isSystem = event.comment.includes('sent') && event.comment.includes('x');
const commentItem = document.createElement('div');
commentItem.className = isSystem ? 'comment-item border-start border-4 border-tiktok-red' : 'comment-item';
commentItem.innerHTML = `
<div class="d-flex justify-content-between">
<div>
<span class="comment-author" style="${isSystem ? 'color: var(--tiktok-red)' : ''}">${event.author}:</span>
<span class="comment-text">${event.comment}</span>
</div>
<small class="text-secondary opacity-50" style="font-size: 0.7rem">${new Date(event.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</small>
</div>
<div class="ai-reply-box mt-1">AI: ${event.reply}</div>
`;
if (commentFeed) commentFeed.prepend(commentItem);
// TTS
speak(event.reply);
// Update history table
updateHistoryTable(event);
}
connectBtn.addEventListener('click', () => {
const username = tiktokUsernameInput.value.trim();
if (!username) {
showToast('Error', 'Please enter a TikTok username.', 'danger');
return;
}
startBridge(username);
});
if (disconnectBtn) {
disconnectBtn.addEventListener('click', () => {
const username = tiktokUsernameInput.value.trim();
stopBridge(username);
});
}
if (simulateBtn) {
simulateBtn.addEventListener('click', async () => {
const commentText = manualCommentInput.value.trim();
const username = tiktokUsernameInput.value.trim();
if (!commentText || !username) return;
try {
// Manually insert into DB to test
await fetch('api/process_comment.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ author: 'TestUser', comment: commentText, username })
});
manualCommentInput.value = '';
// Polling will pick it up
} catch (err) {
console.error(err);
}
});
}
function speak(text) {
if (!text || !synth) return;
const utterThis = new SpeechSynthesisUtterance(text);
const selectedOption = voiceSelect?.selectedOptions[0]?.getAttribute('data-name');
if (selectedOption) {
for (let i = 0; i < voices.length; i++) {
if (voices[i].name === selectedOption) {
utterThis.voice = voices[i];
}
}
}
utterThis.pitch = 1;
utterThis.rate = rateRange?.value || 1.0;
synth.speak(utterThis);
}
function updateHistoryTable(event) {
if (!historyTableBody) return;
const row = document.createElement('tr');
row.className = 'border-secondary';
const time = new Date(event.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
row.innerHTML = `
<td class="text-secondary small">${time}</td>
<td><strong>${event.author}</strong></td>
<td class="text-secondary">${event.comment}</td>
<td class="text-tiktok-cyan">${event.reply}</td>
`;
if (historyTableBody.firstChild && historyTableBody.firstChild.tagName === 'TR' && historyTableBody.firstChild.innerText.includes('No history yet')) {
historyTableBody.innerHTML = '';
}
historyTableBody.prepend(row);
if (historyTableBody.children.length > 20) {
historyTableBody.removeChild(historyTableBody.lastChild);
}
}
function showToast(title, message, type = 'info') {
if (!toastContainer) return;
const toastId = 'toast-' + Date.now();
const toastHtml = `
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header bg-dark text-light border-secondary">
<strong class="me-auto ${type === 'danger' ? 'text-danger' : (type === 'success' ? 'text-tiktok-cyan' : '')}">${title}</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
${message}
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
if (toastElement) {
const toast = new bootstrap.Toast(toastElement);
toast.show();
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
}
});