`;
botsList.appendChild(card);
});
// Helper function to generate a color from a string
function generateColorFromString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
// Use the hash to create a hue in the purple range (260-290)
const hue = ((hash % 30) + 260) % 360;
return `hsl(${hue}, 70%, 60%)`;
}
}
window.publishBotProfile = async function(botId) {
try {
const token = localStorage.getItem('authToken');
const response = await fetch(`${API_ENDPOINT}/api/bots/${botId}/profile/publish`, {
method: 'POST',
headers: {
'Authorization': token
}
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
throw new Error(errData.error || 'Failed to publish profile');
}
const data = await response.json();
// Check if event data with NIP-19 encoding is available
if (data.event) {
// Create a modal to show the encoded event info
const modalId = 'eventInfoModal';
// Remove any existing modal with the same ID
const existingModal = document.getElementById(modalId);
if (existingModal) {
existingModal.remove();
}
const eventModal = document.createElement('div');
eventModal.className = 'modal fade';
eventModal.id = modalId;
eventModal.setAttribute('tabindex', '-1');
eventModal.innerHTML = `
Profile Published Successfully
`;
// Add the modal to the document
document.body.appendChild(eventModal);
// Show the modal using Bootstrap
const bsModal = new bootstrap.Modal(document.getElementById(modalId));
bsModal.show();
}
alert('Profile published successfully!');
} catch (err) {
console.error('Error publishing profile:', err);
alert(`Error publishing profile: ${err.message}`);
}
};
/* ----------------------------------------------------
* Show Create Bot Modal
* -------------------------------------------------- */
function showCreateBotModal() {
// Reset form
const createBotForm = document.getElementById('create-bot-form');
if (createBotForm) createBotForm.reset();
// Default: keyOption = "generate" → hide nsecKeyInput
if (keyOption) {
keyOption.value = 'generate';
}
if (nsecKeyInput) {
nsecKeyInput.style.display = 'none';
}
// Show modal
if (createBotModal) {
createBotModal.show();
}
}
/* ----------------------------------------------------
* Create Bot (merged logic)
* -------------------------------------------------- */
async function createBot() {
console.clear();
console.log('Creating new bot...');
// Get form values
const name = document.getElementById('botName').value.trim();
const displayName = document.getElementById('botDisplayName').value.trim();
const bio = document.getElementById('botBio').value.trim();
const nip05 = document.getElementById('botNip05').value.trim();
const keyChoice = keyOption ? keyOption.value : 'generate'; // fallback
// Validate form
if (!name) {
alert('Bot name is required.');
return;
}
// Build request data
const requestData = {
name: name,
display_name: displayName,
bio: bio,
nip05: nip05
};
// If user selected "import", grab the NSEC key
if (keyChoice === 'import') {
const nsecKey = document.getElementById('botNsecKey').value.trim();
if (!nsecKey) {
alert('NSEC key is required when importing an existing key.');
return;
}
requestData.encrypted_privkey = nsecKey;
console.log('Using imported NSEC key (starts with):', nsecKey.substring(0, 4) + '...');
} else {
console.log('Using auto-generated keypair');
}
console.log('Sending request to create bot...');
try {
const token = localStorage.getItem('authToken');
const response = await fetch(`${API_ENDPOINT}/api/bots`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify(requestData)
});
console.log('Response status:', response.status);
const rawResponse = await response.text();
console.log('Response text:', rawResponse.substring(0, 200) + '...');
let jsonResponse;
try {
jsonResponse = JSON.parse(rawResponse);
} catch (e) {
console.error('Failed to parse JSON response:', e);
}
if (response.ok) {
console.log('Bot created successfully!');
// Hide modal
if (typeof bootstrap !== 'undefined') {
const modal = bootstrap.Modal.getInstance(createBotModalEl);
if (modal) modal.hide();
}
// Refresh bot list
fetchBots();
} else {
const errorMsg = jsonResponse && jsonResponse.error
? jsonResponse.error
: `Failed with status ${response.status}`;
alert('Failed to create bot: ' + errorMsg);
}
} catch (error) {
console.error('Error creating bot:', error);
alert('Error creating bot: ' + error.message);
}
}
/* ----------------------------------------------------
* Enable Bot
* -------------------------------------------------- */
window.enableBot = async function (botId) {
try {
const token = localStorage.getItem('authToken');
const response = await fetch(`${API_ENDPOINT}/api/bots/${botId}/enable`, {
method: 'POST',
headers: {
'Authorization': token
}
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
throw new Error(errData.error || 'Failed to enable bot');
}
alert('Bot enabled successfully!');
// Reload bots
fetchBots();
} catch (err) {
console.error('Error enabling bot:', err);
alert(`Error enabling bot: ${err.message}`);
}
};
/* ----------------------------------------------------
* Disable Bot
* -------------------------------------------------- */
window.disableBot = async function (botId) {
try {
const token = localStorage.getItem('authToken');
const response = await fetch(`${API_ENDPOINT}/api/bots/${botId}/disable`, {
method: 'POST',
headers: {
'Authorization': token,
}
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
throw new Error(errData.error || 'Failed to disable bot');
}
alert('Bot paused/stopped successfully!');
// Refresh the list so it shows "Inactive"
fetchBots();
} catch (err) {
console.error('Error disabling bot:', err);
alert(`Error disabling bot: ${err.message}`);
}
};
/* ----------------------------------------------------
* Settings Window
* -------------------------------------------------- */
window.openBotSettings = async function (botId) {
try {
const token = localStorage.getItem('authToken');
const response = await fetch(`${API_ENDPOINT}/api/bots/${botId}`, {
headers: { 'Authorization': token }
});
if (!response.ok) {
throw new Error('Failed to load bot data');
}
const bot = await response.json();
// Fill form fields
document.getElementById('botSettingsName').value = bot.name || '';
document.getElementById('botSettingsDisplayName').value = bot.display_name || '';
document.getElementById('botSettingsBio').value = bot.bio || '';
document.getElementById('botSettingsNip05').value = bot.nip05 || '';
document.getElementById('botSettingsZap').value = bot.zap_address || '';
// If post_config is present
if (bot.post_config) {
document.getElementById('botSettingsInterval').value = bot.post_config.interval_minutes || 60;
// hashtags is stored as a JSON string
const hashtagsJson = bot.post_config.hashtags || '[]';
const tagsArr = JSON.parse(hashtagsJson);
document.getElementById('botSettingsHashtags').value = tagsArr.join(', ');
}
// Show the modal
const modalEl = document.getElementById('botSettingsModal');
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
modal.show();
// Store bot ID so we know which bot to save
document.getElementById('botSettingsSaveBtn').setAttribute('data-bot-id', botId);
} catch (err) {
console.error('Error loading bot data:', err);
alert('Error loading bot: ' + err.message);
}
};
window.saveBotSettings = async function () {
const botId = document.getElementById('botSettingsSaveBtn').getAttribute('data-bot-id');
if (!botId) {
return alert('No bot ID found');
}
// Basic info
const name = document.getElementById('botSettingsName').value.trim();
const displayName = document.getElementById('botSettingsDisplayName').value.trim();
const bio = document.getElementById('botSettingsBio').value.trim();
const nip05 = document.getElementById('botSettingsNip05').value.trim();
const zap = document.getElementById('botSettingsZap').value.trim();
// 1) Update the basic fields (PUT /api/bots/:id)
try {
const token = localStorage.getItem('authToken');
const updateResp = await fetch(`${API_ENDPOINT}/api/bots/${botId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify({
name,
display_name: displayName,
bio,
nip05,
zap_address: zap
})
});
if (!updateResp.ok) {
const errData = await updateResp.json().catch(() => ({}));
throw new Error(errData.error || 'Failed to update bot info');
}
} catch (err) {
console.error('Failed to update bot info:', err);
alert('Error updating bot info: ' + err.message);
return;
}
// 2) Update the post config (PUT /api/bots/:id/config)
const intervalValue = parseInt(document.getElementById('botSettingsInterval').value, 10) || 60;
const hashtagsStr = document.getElementById('botSettingsHashtags').value.trim();
const hashtagsArr = hashtagsStr.length ? hashtagsStr.split(',').map(s => s.trim()) : [];
const configPayload = {
post_config: {
interval_minutes: intervalValue,
hashtags: JSON.stringify(hashtagsArr)
// We do not override 'enabled' here, so it remains as is
}
};
try {
const token = localStorage.getItem('authToken');
const configResp = await fetch(`${API_ENDPOINT}/api/bots/${botId}/config`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify(configPayload)
});
if (!configResp.ok) {
const errData = await configResp.json().catch(() => ({}));
throw new Error(errData.error || 'Failed to update post config');
}
} catch (err) {
console.error('Failed to update post config:', err);
alert('Error updating post config: ' + err.message);
return;
}
alert('Bot settings updated!');
// Hide modal
const modalEl = document.getElementById('botSettingsModal');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
// Reload bots
fetchBots();
};
/* ----------------------------------------------------
* Nuke bot
* -------------------------------------------------- */
window.deleteBot = async function (botId) {
// Safety check: prompt user to confirm
if (!confirm('Are you sure you want to delete this bot? This cannot be undone.')) {
return;
}
try {
const token = localStorage.getItem('authToken');
const response = await fetch(`${API_ENDPOINT}/api/bots/${botId}`, {
method: 'DELETE',
headers: {
'Authorization': token
}
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
throw new Error(errData.error || `Failed to delete bot (status: ${response.status})`);
}
alert('Bot deleted successfully!');
// Reload the bots so it disappears from the list
fetchBots();
} catch (err) {
console.error('Error deleting bot:', err);
alert(`Error deleting bot: ${err.message}`);
}
};
/* ----------------------------------------------------
* Content managment - Exposed as global functions
* -------------------------------------------------- */
// Called by content.html after we confirm the user is logged in
window.loadBotChoices = async function() {
try {
const token = localStorage.getItem('authToken');
if (!token) return;
const res = await fetch(`${API_ENDPOINT}/api/bots`, {
headers: { 'Authorization': token }
});
if (!res.ok) {
throw new Error('Failed to fetch bots');
}
const bots = await res.json();
const botSelect = document.getElementById('botSelect');
if (!botSelect) return;
botSelect.innerHTML = ``;
bots.forEach(bot => {
botSelect.innerHTML += ``;
});
} catch (err) {
console.error('Error loading bot choices:', err);
}
};
// Called when user picks a bot from the dropdown and clicks "Load Content"
window.loadBotContent = async function() {
const botSelect = document.getElementById('botSelect');
if (!botSelect) return;
const botId = botSelect.value;
if (!botId) {
alert('Please select a bot first!');
return;
}
try {
const token = localStorage.getItem('authToken');
const res = await fetch(`${API_ENDPOINT}/api/content/${botId}`, {
headers: { 'Authorization': token }
});
if (!res.ok) {
throw new Error('Failed to list content');
}
const files = await res.json();
renderBotContent(botId, files);
} catch (err) {
console.error('Error loading content:', err);
alert('Error loading content: ' + err.message);
}
};
function renderBotContent(botId, files) {
const container = document.getElementById('contentArea');
if (!container) return;
// Clear existing
container.innerHTML = `