swapped to a new db locally hosted

This commit is contained in:
2025-10-06 00:25:29 -04:00
parent 097583ca0a
commit ca23c0ab8c
40 changed files with 2244 additions and 556 deletions

166
discord-bot/api.js Normal file
View File

@@ -0,0 +1,166 @@
const fetch = require('node-fetch');
// Resolve backend candidates (env or common local addresses). We'll try them in order
// for each request so the bot can still reach the backend even when it binds to
// a specific non-loopback IP.
const envBase = process.env.BACKEND_BASE ? process.env.BACKEND_BASE.replace(/\/$/, '') : null;
const host = process.env.BACKEND_HOST || process.env.HOST || '127.0.0.1';
const port = process.env.BACKEND_PORT || process.env.PORT || '3002';
const CANDIDATES = [envBase, `http://${host}:${port}`, `http://localhost:${port}`, `http://127.0.0.1:${port}`].filter(Boolean);
async function tryFetch(url, opts = {}) {
// Try each candidate base until one responds successfully
for (const base of CANDIDATES) {
const target = `${base.replace(/\/$/, '')}${url}`;
try {
const res = await fetch(target, opts);
if (res && (res.ok || res.status === 204)) {
return res;
}
// if this candidate returned a non-ok status, log and continue trying others
console.error(`Candidate ${base} returned ${res.status} ${res.statusText} for ${target}`);
} catch (e) {
// network error for this candidate; try next
// console.debug(`Candidate ${base} failed:`, e && e.message ? e.message : e);
}
}
// none of the candidates succeeded
return null;
}
async function safeFetchJsonPath(path, opts = {}) {
const res = await tryFetch(path, opts);
if (!res) return null;
try {
return await res.json();
} catch (e) {
console.error('Failed to parse JSON from backend response:', e && e.message ? e.message : e);
return null;
}
}
async function getServerSettings(guildId) {
const path = `/api/servers/${guildId}/settings`;
const json = await safeFetchJsonPath(path);
return json || {};
}
async function upsertServerSettings(guildId, settings) {
const path = `/api/servers/${guildId}/settings`;
try {
const res = await tryFetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings),
});
return res && res.ok;
} catch (e) {
console.error(`Failed to upsert settings for ${guildId}:`, e && e.message ? e.message : e);
return false;
}
}
async function getCommands(guildId) {
const path = `/api/servers/${guildId}/commands`;
const json = await safeFetchJsonPath(path);
return json || [];
}
async function toggleCommand(guildId, cmdName, enabled) {
const path = `/api/servers/${guildId}/commands/${cmdName}/toggle`;
try {
const res = await tryFetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled }),
});
return res && res.ok;
} catch (e) {
console.error(`Failed to toggle command ${cmdName} for ${guildId}:`, e && e.message ? e.message : e);
return false;
}
}
async function listInvites(guildId) {
const path = `/api/servers/${guildId}/invites`;
const json = await safeFetchJsonPath(path);
return json || [];
}
async function addInvite(guildId, invite) {
const path = `/api/servers/${guildId}/invites`;
try {
const res = await tryFetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(invite),
});
return res && res.ok;
} catch (e) {
console.error(`Failed to add invite for ${guildId}:`, e && e.message ? e.message : e);
return false;
}
}
async function deleteInvite(guildId, code) {
const path = `/api/servers/${guildId}/invites/${code}`;
try {
const res = await tryFetch(path, { method: 'DELETE' });
return res && res.ok;
} catch (e) {
console.error(`Failed to delete invite ${code} for ${guildId}:`, e && e.message ? e.message : e);
return false;
}
}
module.exports = { getServerSettings, upsertServerSettings, getCommands, toggleCommand, listInvites, addInvite, deleteInvite };
// Twitch users helpers
async function getTwitchUsers(guildId) {
const path = `/api/servers/${guildId}/twitch-users`;
const json = await safeFetchJsonPath(path);
return json || [];
}
async function addTwitchUser(guildId, username) {
const path = `/api/servers/${guildId}/twitch-users`;
try {
const res = await tryFetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username }),
});
return res && res.ok;
} catch (e) {
console.error(`Failed to add twitch user ${username} for ${guildId}:`, e && e.message ? e.message : e);
return false;
}
}
async function deleteTwitchUser(guildId, username) {
const path = `/api/servers/${guildId}/twitch-users/${encodeURIComponent(username)}`;
try {
const res = await tryFetch(path, { method: 'DELETE' });
return res && res.ok;
} catch (e) {
console.error(`Failed to delete twitch user ${username} for ${guildId}:`, e && e.message ? e.message : e);
return false;
}
}
// Fetch stream status via backend proxy endpoint /api/twitch/streams?users=a,b,c
async function tryFetchTwitchStreams(usersCsv) {
const path = `/api/twitch/streams?users=${encodeURIComponent(usersCsv || '')}`;
const json = await safeFetchJsonPath(path);
return json || [];
}
// Raw direct call helper (not used in most environments) — kept for legacy watcher
async function _rawGetTwitchStreams(usersCsv) {
// Try direct backend candidate first
const path = `/api/twitch/streams?users=${encodeURIComponent(usersCsv || '')}`;
const res = await tryFetch(path);
if (!res) return [];
try { return await res.json(); } catch (e) { return []; }
}
module.exports = { getServerSettings, upsertServerSettings, getCommands, toggleCommand, listInvites, addInvite, deleteInvite, getTwitchUsers, addTwitchUser, deleteTwitchUser };