This commit is contained in:
+560
-1
@@ -5,6 +5,9 @@ const els = {
|
||||
steamStatus: document.querySelector('#steamStatus'),
|
||||
gcStatus: document.querySelector('#gcStatus'),
|
||||
loginPanel: document.querySelector('#loginPanel'),
|
||||
notificationPanel: document.querySelector('#notificationPanel'),
|
||||
telegramPanel: document.querySelector('#telegramPanel'),
|
||||
lobbyPanel: document.querySelector('#lobbyPanel'),
|
||||
credentialsPanel: document.querySelector('#credentialsPanel'),
|
||||
guardPanel: document.querySelector('#guardPanel'),
|
||||
tokenPanel: document.querySelector('#tokenPanel'),
|
||||
@@ -25,10 +28,70 @@ const els = {
|
||||
tokenUpdated: document.querySelector('#tokenUpdated'),
|
||||
tokenExpires: document.querySelector('#tokenExpires'),
|
||||
clearTokenBtn: document.querySelector('#clearTokenBtn'),
|
||||
enableNotificationsBtn: document.querySelector('#enableNotificationsBtn'),
|
||||
disableNotificationsBtn: document.querySelector('#disableNotificationsBtn'),
|
||||
testNotificationBtn: document.querySelector('#testNotificationBtn'),
|
||||
notificationStatus: document.querySelector('#notificationStatus'),
|
||||
telegramToken: document.querySelector('#telegramToken'),
|
||||
telegramRecipientIds: document.querySelector('#telegramRecipientIds'),
|
||||
saveTelegramBtn: document.querySelector('#saveTelegramBtn'),
|
||||
testTelegramBtn: document.querySelector('#testTelegramBtn'),
|
||||
disableTelegramBtn: document.querySelector('#disableTelegramBtn'),
|
||||
telegramStatus: document.querySelector('#telegramStatus'),
|
||||
savedSteamAccount: document.querySelector('#savedSteamAccount'),
|
||||
activateAccountBtn: document.querySelector('#activateAccountBtn'),
|
||||
lobbyName: document.querySelector('#lobbyName'),
|
||||
lobbyRegion: document.querySelector('#lobbyRegion'),
|
||||
customGameId: document.querySelector('#customGameId'),
|
||||
customGameMode: document.querySelector('#customGameMode'),
|
||||
customMapName: document.querySelector('#customMapName'),
|
||||
lobbyPassKey: document.querySelector('#lobbyPassKey'),
|
||||
customMinPlayers: document.querySelector('#customMinPlayers'),
|
||||
customMaxPlayers: document.querySelector('#customMaxPlayers'),
|
||||
notifyAtPlayers: document.querySelector('#notifyAtPlayers'),
|
||||
leaveAtPlayers: document.querySelector('#leaveAtPlayers'),
|
||||
adminNames: document.querySelector('#adminNames'),
|
||||
customGameCrc: document.querySelector('#customGameCrc'),
|
||||
customGameTimestamp: document.querySelector('#customGameTimestamp'),
|
||||
allowSpectating: document.querySelector('#allowSpectating'),
|
||||
saveLobbySettingsBtn: document.querySelector('#saveLobbySettingsBtn'),
|
||||
createLobbyBtn: document.querySelector('#createLobbyBtn'),
|
||||
stopLobbyBtn: document.querySelector('#stopLobbyBtn'),
|
||||
automationPhase: document.querySelector('#automationPhase'),
|
||||
activeLobbyId: document.querySelector('#activeLobbyId'),
|
||||
activePlayerCount: document.querySelector('#activePlayerCount'),
|
||||
publicationStatus: document.querySelector('#publicationStatus'),
|
||||
arcadePublicationStatus: document.querySelector('#arcadePublicationStatus'),
|
||||
lobbyProtocolStatus: document.querySelector('#lobbyProtocolStatus'),
|
||||
dotaClientVersion: document.querySelector('#dotaClientVersion'),
|
||||
leaveReason: document.querySelector('#leaveReason'),
|
||||
lobbyMembers: document.querySelector('#lobbyMembers'),
|
||||
};
|
||||
|
||||
let adminPassword = sessionStorage.getItem('dokaAdminPassword') || '';
|
||||
let credentialsDirty = false;
|
||||
let lobbySettingsDirty = false;
|
||||
let telegramDirty = false;
|
||||
let regionsReady = false;
|
||||
let lastAdminState = null;
|
||||
|
||||
function readPendingLobbyTemplate() {
|
||||
try {
|
||||
const saved = sessionStorage.getItem('dokaLobbyTemplate');
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed && typeof parsed === 'object') return parsed;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to the query-string compatibility path.
|
||||
}
|
||||
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
return query.get('template') === 'lobby' ? Object.fromEntries(query.entries()) : null;
|
||||
}
|
||||
|
||||
const pendingLobbyTemplate = readPendingLobbyTemplate();
|
||||
let lobbyTemplateApplied = false;
|
||||
|
||||
const dateTimeFormat = new Intl.DateTimeFormat('en-GB', {
|
||||
day: '2-digit',
|
||||
@@ -81,7 +144,270 @@ function unlocked() {
|
||||
return Boolean(adminPassword);
|
||||
}
|
||||
|
||||
function initRegions(regions) {
|
||||
if (regionsReady || !regions) return;
|
||||
for (const [id, name] of Object.entries(regions).sort(([a], [b]) => Number(a) - Number(b))) {
|
||||
const option = document.createElement('option');
|
||||
option.value = id;
|
||||
option.textContent = `${name} (${id})`;
|
||||
els.lobbyRegion.append(option);
|
||||
}
|
||||
regionsReady = true;
|
||||
}
|
||||
|
||||
function setLobbyInputs(settings) {
|
||||
els.lobbyName.value = settings.lobbyName || '';
|
||||
els.lobbyRegion.value = String(settings.serverRegion ?? 3);
|
||||
els.customGameId.value = settings.customGameId || '';
|
||||
els.customGameMode.value = settings.customGameMode || '';
|
||||
els.customMapName.value = settings.customMapName || '';
|
||||
els.lobbyPassKey.value = settings.passKey || '';
|
||||
els.customMinPlayers.value = String(settings.customMinPlayers ?? 2);
|
||||
els.customMaxPlayers.value = String(settings.customMaxPlayers ?? 10);
|
||||
els.notifyAtPlayers.value = String(settings.notifyAtPlayers ?? 8);
|
||||
els.leaveAtPlayers.value = String(settings.leaveAtPlayers ?? 9);
|
||||
els.adminNames.value = (settings.adminNames || []).join(', ');
|
||||
els.customGameCrc.value = settings.customGameCrc || '';
|
||||
els.customGameTimestamp.value = settings.customGameTimestamp || '';
|
||||
els.allowSpectating.checked = settings.allowSpectating !== false;
|
||||
lobbySettingsDirty = false;
|
||||
}
|
||||
|
||||
async function resolveMissingTemplateMetadata() {
|
||||
if (els.customGameMode.value || !pendingLobbyTemplate?.sourceLobbyId) return;
|
||||
const controller = new AbortController();
|
||||
let timedOut = false;
|
||||
const timeout = setTimeout(() => {
|
||||
timedOut = true;
|
||||
controller.abort();
|
||||
}, 15000);
|
||||
try {
|
||||
showMessage('Resolving the internal addon name (local Workshop first, Dota GC fallback; up to 15 seconds)...');
|
||||
const data = await adminFetch('/api/admin/lobby-template', {
|
||||
method: 'POST',
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify({
|
||||
lobbyId: pendingLobbyTemplate.sourceLobbyId,
|
||||
serverRegion: Number(pendingLobbyTemplate.serverRegion || 0),
|
||||
customGameId: pendingLobbyTemplate.customGameId,
|
||||
}),
|
||||
});
|
||||
els.customGameMode.value = data.templateMetadata?.customGameMode || '';
|
||||
if (!els.customGameTimestamp.value && data.templateMetadata?.customGameTimestamp) {
|
||||
els.customGameTimestamp.value = data.templateMetadata.customGameTimestamp;
|
||||
}
|
||||
if (!els.customGameMode.value) throw new Error('The source lobby has no internal addon name.');
|
||||
lobbySettingsDirty = true;
|
||||
showError('');
|
||||
showMessage(`Lobby template is complete: Workshop ${els.customGameId.value}, addon ${els.customGameMode.value}, map ${els.customMapName.value}. Review the thresholds, then create it.`);
|
||||
} catch (err) {
|
||||
showError(`Could not complete lobby template: ${timedOut ? 'metadata lookup timed out after 15 seconds' : err.message}`);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function applyLobbyTemplate() {
|
||||
if (!pendingLobbyTemplate || lobbyTemplateApplied) return;
|
||||
|
||||
const setFromTemplate = (element, key) => {
|
||||
const value = pendingLobbyTemplate[key];
|
||||
if (value !== undefined && value !== null) element.value = String(value);
|
||||
};
|
||||
setFromTemplate(els.lobbyName, 'lobbyName');
|
||||
setFromTemplate(els.lobbyRegion, 'serverRegion');
|
||||
setFromTemplate(els.customGameId, 'customGameId');
|
||||
setFromTemplate(els.customGameMode, 'customGameMode');
|
||||
setFromTemplate(els.customMapName, 'customMapName');
|
||||
setFromTemplate(els.customMinPlayers, 'customMinPlayers');
|
||||
setFromTemplate(els.customMaxPlayers, 'customMaxPlayers');
|
||||
setFromTemplate(els.customGameCrc, 'customGameCrc');
|
||||
setFromTemplate(els.customGameTimestamp, 'customGameTimestamp');
|
||||
|
||||
// The source feed never exposes the lobby password. Clear any stale value.
|
||||
els.lobbyPassKey.value = '';
|
||||
|
||||
const maxPlayers = Number(els.customMaxPlayers.value);
|
||||
if (Number.isInteger(maxPlayers) && maxPlayers > 0) {
|
||||
const highestSafeThreshold = Math.max(1, maxPlayers - 1);
|
||||
const leaveAt = Math.min(Number(els.leaveAtPlayers.value) || highestSafeThreshold, highestSafeThreshold);
|
||||
els.leaveAtPlayers.value = String(leaveAt);
|
||||
els.notifyAtPlayers.value = String(Math.min(Number(els.notifyAtPlayers.value) || leaveAt, leaveAt));
|
||||
}
|
||||
|
||||
lobbyTemplateApplied = true;
|
||||
lobbySettingsDirty = true;
|
||||
try {
|
||||
sessionStorage.removeItem('dokaLobbyTemplate');
|
||||
} catch {
|
||||
// The query-string fallback is still removed below.
|
||||
}
|
||||
window.history.replaceState({}, '', '/admin');
|
||||
showMessage(`Lobby template loaded: Workshop ${els.customGameId.value}, map ${els.customMapName.value}. Review the notification and leave thresholds, then create it.`);
|
||||
if (!els.customGameMode.value) {
|
||||
showError('The GC did not return the internal addon name for this row. Refresh the lobby list and create the template again; an incomplete template cannot be published.');
|
||||
resolveMissingTemplateMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
function lobbyPayload() {
|
||||
return {
|
||||
lobbyName: els.lobbyName.value,
|
||||
serverRegion: Number(els.lobbyRegion.value),
|
||||
customGameId: els.customGameId.value,
|
||||
customGameMode: els.customGameMode.value,
|
||||
customMapName: els.customMapName.value,
|
||||
passKey: els.lobbyPassKey.value,
|
||||
customMinPlayers: Number(els.customMinPlayers.value),
|
||||
customMaxPlayers: Number(els.customMaxPlayers.value),
|
||||
notifyAtPlayers: Number(els.notifyAtPlayers.value),
|
||||
leaveAtPlayers: Number(els.leaveAtPlayers.value),
|
||||
adminNames: els.adminNames.value,
|
||||
customGameCrc: els.customGameCrc.value,
|
||||
customGameTimestamp: els.customGameTimestamp.value,
|
||||
allowSpectating: els.allowSpectating.checked,
|
||||
};
|
||||
}
|
||||
|
||||
function renderLobbyAutomation(automation) {
|
||||
const status = automation.status || {};
|
||||
const settings = automation.settings || {};
|
||||
const busy = ['creating', 'leaving'].includes(status.phase);
|
||||
const hasLobby = Boolean(status.lobbyId);
|
||||
|
||||
if (!lobbySettingsDirty && !busy) setLobbyInputs(settings);
|
||||
if (unlocked() && regionsReady && automation.settings && !busy) applyLobbyTemplate();
|
||||
els.automationPhase.textContent = status.phase || 'idle';
|
||||
els.activeLobbyId.textContent = status.lobbyId || '-';
|
||||
els.activePlayerCount.textContent = String(status.humanCount ?? 0);
|
||||
els.publicationStatus.textContent = status.practicePublished === true
|
||||
? 'listed'
|
||||
: status.practicePublished === false
|
||||
? 'not listed'
|
||||
: status.armed
|
||||
? 'checking'
|
||||
: '-';
|
||||
els.arcadePublicationStatus.textContent = status.arcadePublished === true
|
||||
? 'listed'
|
||||
: status.arcadePublished === false
|
||||
? 'not listed'
|
||||
: status.armed
|
||||
? 'checking'
|
||||
: '-';
|
||||
const protocol = status.lobbyProtocol;
|
||||
els.lobbyProtocolStatus.textContent = protocol
|
||||
? `type=${protocol.lobbyType}, visibility=${protocol.visibility}, auto=${protocol.autoCreated ? 'yes' : 'no'}, bot team=${protocol.botTeam ?? '-'}`
|
||||
: '-';
|
||||
els.leaveReason.textContent = status.leaveReason || '-';
|
||||
els.createLobbyBtn.disabled = busy || hasLobby || Boolean(status.armed);
|
||||
els.stopLobbyBtn.disabled = busy || (!hasLobby && !status.armed);
|
||||
els.saveLobbySettingsBtn.disabled = busy;
|
||||
|
||||
const members = status.members || [];
|
||||
if (members.length) {
|
||||
els.lobbyMembers.textContent = members
|
||||
.map((member) => member.name ? `${member.name} (${member.steamId})` : member.steamId)
|
||||
.join(', ');
|
||||
} else {
|
||||
els.lobbyMembers.textContent = 'No real players in the active lobby.';
|
||||
}
|
||||
|
||||
if (status.lastError) showError(status.lastError);
|
||||
}
|
||||
|
||||
function pushSupported() {
|
||||
return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
|
||||
}
|
||||
|
||||
async function currentPushSubscription() {
|
||||
if (!pushSupported()) return null;
|
||||
const registration = await navigator.serviceWorker.register('/sw.js');
|
||||
return registration.pushManager.getSubscription();
|
||||
}
|
||||
|
||||
async function updateNotificationStatus() {
|
||||
if (!pushSupported()) {
|
||||
els.notificationStatus.textContent = 'not supported';
|
||||
els.enableNotificationsBtn.disabled = true;
|
||||
els.disableNotificationsBtn.disabled = true;
|
||||
els.testNotificationBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const subscription = await currentPushSubscription();
|
||||
els.notificationStatus.textContent = subscription && Notification.permission === 'granted'
|
||||
? 'enabled on this browser'
|
||||
: Notification.permission;
|
||||
els.disableNotificationsBtn.disabled = !subscription;
|
||||
els.testNotificationBtn.disabled = !subscription;
|
||||
} catch (err) {
|
||||
els.notificationStatus.textContent = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
function urlBase64ToUint8Array(value) {
|
||||
const padding = '='.repeat((4 - (value.length % 4)) % 4);
|
||||
const base64 = (value + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||
const raw = atob(base64);
|
||||
return Uint8Array.from([...raw].map((char) => char.charCodeAt(0)));
|
||||
}
|
||||
|
||||
function renderSavedAccounts(credentials) {
|
||||
const accounts = Array.isArray(credentials.savedAccounts) ? credentials.savedAccounts : [];
|
||||
const currentSelection = els.savedSteamAccount.value;
|
||||
els.savedSteamAccount.replaceChildren();
|
||||
if (!accounts.length) {
|
||||
const option = document.createElement('option');
|
||||
option.value = '';
|
||||
option.textContent = 'No saved accounts';
|
||||
els.savedSteamAccount.append(option);
|
||||
els.savedSteamAccount.disabled = true;
|
||||
els.activateAccountBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const account of accounts) {
|
||||
const option = document.createElement('option');
|
||||
option.value = account.accountName;
|
||||
option.textContent = account.accountName === credentials.activeAccountName
|
||||
? `${account.accountName} (active)`
|
||||
: account.accountName;
|
||||
els.savedSteamAccount.append(option);
|
||||
}
|
||||
els.savedSteamAccount.disabled = false;
|
||||
els.activateAccountBtn.disabled = false;
|
||||
const preferred = accounts.some((account) => account.accountName === currentSelection)
|
||||
? currentSelection
|
||||
: credentials.activeAccountName;
|
||||
els.savedSteamAccount.value = preferred || accounts[0].accountName;
|
||||
}
|
||||
|
||||
function renderTelegram(telegram = {}) {
|
||||
const bot = telegram.botUsername ? `@${telegram.botUsername}` : 'bot';
|
||||
if (telegram.configured) {
|
||||
els.telegramStatus.textContent = `${bot}, ${telegram.recipientCount || 0} recipient(s)${telegram.lastError ? `; error: ${telegram.lastError}` : ''}`;
|
||||
} else {
|
||||
els.telegramStatus.textContent = 'not configured';
|
||||
}
|
||||
els.testTelegramBtn.disabled = !telegram.configured;
|
||||
els.disableTelegramBtn.disabled = !telegram.configured;
|
||||
|
||||
const editing = document.activeElement === els.telegramToken
|
||||
|| document.activeElement === els.telegramRecipientIds
|
||||
|| telegramDirty;
|
||||
if (!editing) {
|
||||
els.telegramToken.value = '';
|
||||
els.telegramToken.placeholder = telegram.configured
|
||||
? 'configured; leave blank to keep saved token'
|
||||
: 'paste token from @BotFather';
|
||||
els.telegramRecipientIds.value = (telegram.recipientIds || []).join(', ');
|
||||
telegramDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
function render(data, options = {}) {
|
||||
lastAdminState = data;
|
||||
const preserveEditedInputs = options.preserveEditedInputs !== false;
|
||||
const connection = data.connection || {};
|
||||
const credentials = data.credentials || {};
|
||||
@@ -89,6 +415,9 @@ function render(data, options = {}) {
|
||||
const steamGuard = connection.steamGuard || {};
|
||||
|
||||
els.loginPanel.hidden = unlocked();
|
||||
els.notificationPanel.hidden = !unlocked();
|
||||
els.telegramPanel.hidden = !unlocked();
|
||||
els.lobbyPanel.hidden = !unlocked();
|
||||
els.credentialsPanel.hidden = !unlocked();
|
||||
els.tokenPanel.hidden = !unlocked();
|
||||
els.guardPanel.hidden = !unlocked() || !steamGuard.required;
|
||||
@@ -98,6 +427,13 @@ function render(data, options = {}) {
|
||||
els.tokenStatus.textContent = token.exists ? 'saved' : 'missing';
|
||||
els.steamStatus.textContent = connection.steam || '-';
|
||||
els.gcStatus.textContent = connection.gc || '-';
|
||||
els.dotaClientVersion.textContent = connection.dotaClientVersion
|
||||
? `${connection.dotaClientVersion}${connection.dotaClientVersionSource ? ` (${connection.dotaClientVersionSource})` : ''}`
|
||||
: 'not detected; set DOTA_CLIENT_VERSION';
|
||||
initRegions(data.regions);
|
||||
renderLobbyAutomation(data.lobbyAutomation || {});
|
||||
renderTelegram(data.lobbyAutomation?.telegram || {});
|
||||
renderSavedAccounts(credentials);
|
||||
|
||||
const editingCredentials = document.activeElement === els.steamAccount
|
||||
|| document.activeElement === els.steamPassword
|
||||
@@ -105,7 +441,10 @@ function render(data, options = {}) {
|
||||
|
||||
if (!preserveEditedInputs || !editingCredentials) {
|
||||
els.steamAccount.value = credentials.accountName || '';
|
||||
els.steamPassword.value = credentials.password || '';
|
||||
els.steamPassword.value = '';
|
||||
els.steamPassword.placeholder = credentials.passwordSaved
|
||||
? 'saved; leave blank to keep the saved password'
|
||||
: 'password required for a new account';
|
||||
credentialsDirty = false;
|
||||
}
|
||||
|
||||
@@ -190,6 +529,178 @@ async function clearRefreshToken() {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveLobbySettings() {
|
||||
try {
|
||||
showError('');
|
||||
showMessage('');
|
||||
const data = await adminFetch('/api/admin/lobby-settings', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(lobbyPayload()),
|
||||
});
|
||||
lobbySettingsDirty = false;
|
||||
render(data);
|
||||
showMessage('Lobby settings saved.');
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function createLobby() {
|
||||
try {
|
||||
showError('');
|
||||
showMessage('Creating lobby and waiting for the Dota GC snapshot...');
|
||||
els.createLobbyBtn.disabled = true;
|
||||
const data = await adminFetch('/api/admin/lobby', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(lobbyPayload()),
|
||||
});
|
||||
lobbySettingsDirty = false;
|
||||
render(data);
|
||||
showMessage('Lobby created. Player monitoring and safe auto-leave are active.');
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
showMessage('');
|
||||
} finally {
|
||||
if (!lastAdminState?.lobbyAutomation?.status?.armed) els.createLobbyBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function stopLobby() {
|
||||
try {
|
||||
showError('');
|
||||
showMessage('Leaving lobby...');
|
||||
els.stopLobbyBtn.disabled = true;
|
||||
const data = await adminFetch('/api/admin/lobby', {
|
||||
method: 'DELETE',
|
||||
body: '{}',
|
||||
});
|
||||
render(data);
|
||||
showMessage('The bot left the lobby.');
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
showMessage('');
|
||||
}
|
||||
}
|
||||
|
||||
async function enableNotifications() {
|
||||
try {
|
||||
showError('');
|
||||
if (!pushSupported()) throw new Error('Push notifications are not supported by this browser.');
|
||||
const permission = await Notification.requestPermission();
|
||||
if (permission !== 'granted') throw new Error(`Notification permission is ${permission}.`);
|
||||
const registration = await navigator.serviceWorker.register('/sw.js');
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
if (!subscription) {
|
||||
const publicKey = lastAdminState?.lobbyAutomation?.push?.publicKey;
|
||||
if (!publicKey) throw new Error('Web Push public key is unavailable.');
|
||||
subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(publicKey),
|
||||
});
|
||||
}
|
||||
const data = await adminFetch('/api/admin/push-subscription', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ subscription: subscription.toJSON() }),
|
||||
});
|
||||
render(data);
|
||||
await updateNotificationStatus();
|
||||
showMessage('Browser push notifications enabled.');
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function disableNotifications() {
|
||||
try {
|
||||
showError('');
|
||||
const subscription = await currentPushSubscription();
|
||||
if (subscription) {
|
||||
await adminFetch('/api/admin/push-subscription', {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({ endpoint: subscription.endpoint }),
|
||||
});
|
||||
await subscription.unsubscribe();
|
||||
}
|
||||
await updateNotificationStatus();
|
||||
showMessage('Browser push notifications disabled on this browser.');
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testNotification() {
|
||||
try {
|
||||
showError('');
|
||||
const data = await adminFetch('/api/admin/test-notification', { method: 'POST', body: '{}' });
|
||||
const result = data.notificationResult || {};
|
||||
showMessage(`Test notification sent: ${result.delivered || 0} delivered, ${result.failed || 0} failed.`);
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTelegram() {
|
||||
try {
|
||||
showError('');
|
||||
showMessage('Checking the Telegram bot and saving recipients...');
|
||||
const data = await adminFetch('/api/admin/telegram', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
token: els.telegramToken.value,
|
||||
recipientIds: els.telegramRecipientIds.value,
|
||||
}),
|
||||
});
|
||||
telegramDirty = false;
|
||||
render(data);
|
||||
showMessage('Telegram notifications saved. Use Send test to verify every recipient.');
|
||||
} catch (err) {
|
||||
showMessage('');
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function disableTelegram() {
|
||||
try {
|
||||
showError('');
|
||||
const data = await adminFetch('/api/admin/telegram', { method: 'DELETE', body: '{}' });
|
||||
telegramDirty = false;
|
||||
render(data);
|
||||
showMessage('Telegram notifications disabled and the saved bot token removed.');
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testTelegram() {
|
||||
try {
|
||||
showError('');
|
||||
const data = await adminFetch('/api/admin/test-telegram', { method: 'POST', body: '{}' });
|
||||
const result = data.telegramResult || {};
|
||||
render(data);
|
||||
const errors = result.errors?.length ? ` ${result.errors.join('; ')}` : '';
|
||||
showMessage(`Telegram test: ${result.delivered || 0} delivered, ${result.failed || 0} failed.${errors}`);
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function activateSavedAccount() {
|
||||
try {
|
||||
showError('');
|
||||
showMessage('Switching Steam account and reconnecting Dota GC...');
|
||||
const data = await adminFetch('/api/admin/steam-account/activate', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ accountName: els.savedSteamAccount.value }),
|
||||
});
|
||||
credentialsDirty = false;
|
||||
render(data);
|
||||
showMessage('Steam account activated. Reconnection continues in the background.');
|
||||
} catch (err) {
|
||||
showMessage('');
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
els.unlockBtn.addEventListener('click', () => {
|
||||
adminPassword = els.adminPassword.value;
|
||||
sessionStorage.setItem('dokaAdminPassword', adminPassword);
|
||||
@@ -207,6 +718,16 @@ els.reloadBtn.addEventListener('click', () => {
|
||||
els.saveBtn.addEventListener('click', saveCredentials);
|
||||
els.submitGuardBtn.addEventListener('click', submitSteamGuard);
|
||||
els.clearTokenBtn.addEventListener('click', clearRefreshToken);
|
||||
els.saveLobbySettingsBtn.addEventListener('click', saveLobbySettings);
|
||||
els.createLobbyBtn.addEventListener('click', createLobby);
|
||||
els.stopLobbyBtn.addEventListener('click', stopLobby);
|
||||
els.enableNotificationsBtn.addEventListener('click', enableNotifications);
|
||||
els.disableNotificationsBtn.addEventListener('click', disableNotifications);
|
||||
els.testNotificationBtn.addEventListener('click', testNotification);
|
||||
els.saveTelegramBtn.addEventListener('click', saveTelegram);
|
||||
els.testTelegramBtn.addEventListener('click', testTelegram);
|
||||
els.disableTelegramBtn.addEventListener('click', disableTelegram);
|
||||
els.activateAccountBtn.addEventListener('click', activateSavedAccount);
|
||||
|
||||
els.steamAccount.addEventListener('input', () => {
|
||||
credentialsDirty = true;
|
||||
@@ -216,6 +737,43 @@ els.steamPassword.addEventListener('input', () => {
|
||||
credentialsDirty = true;
|
||||
});
|
||||
|
||||
els.savedSteamAccount.addEventListener('change', () => {
|
||||
if (!els.savedSteamAccount.value) return;
|
||||
els.steamAccount.value = els.savedSteamAccount.value;
|
||||
els.steamPassword.value = '';
|
||||
credentialsDirty = true;
|
||||
});
|
||||
|
||||
[els.telegramToken, els.telegramRecipientIds].forEach((element) => {
|
||||
element.addEventListener('input', () => {
|
||||
telegramDirty = true;
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
els.lobbyName,
|
||||
els.lobbyRegion,
|
||||
els.customGameId,
|
||||
els.customGameMode,
|
||||
els.customMapName,
|
||||
els.lobbyPassKey,
|
||||
els.customMinPlayers,
|
||||
els.customMaxPlayers,
|
||||
els.notifyAtPlayers,
|
||||
els.leaveAtPlayers,
|
||||
els.adminNames,
|
||||
els.customGameCrc,
|
||||
els.customGameTimestamp,
|
||||
els.allowSpectating,
|
||||
].forEach((element) => {
|
||||
element.addEventListener('input', () => {
|
||||
lobbySettingsDirty = true;
|
||||
});
|
||||
element.addEventListener('change', () => {
|
||||
lobbySettingsDirty = true;
|
||||
});
|
||||
});
|
||||
|
||||
els.steamGuardCode.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') els.submitGuardBtn.click();
|
||||
});
|
||||
@@ -225,4 +783,5 @@ if (adminPassword) {
|
||||
}
|
||||
|
||||
loadAuth({ preserveEditedInputs: false });
|
||||
updateNotificationStatus();
|
||||
setInterval(loadAuth, 5000);
|
||||
|
||||
Reference in New Issue
Block a user