This commit is contained in:
@@ -27,6 +27,7 @@ import { migratePlayerScoreTotal } from './player-score-total.js';
|
||||
import { migrateBank } from './bank.js';
|
||||
import { migrateAfk } from './afk.js';
|
||||
import { migrateDiscord } from '../discord/schema.js';
|
||||
import { migrateDiscordPublicationDefaults } from '../discord/publication-defaults.js';
|
||||
import { migrateBankCredentials } from './bank-credentials.js';
|
||||
import { migrateTemperatures } from '../hosts/temperatures.js';
|
||||
import {
|
||||
@@ -212,7 +213,12 @@ export const migrations: readonly Migration[] = [
|
||||
mode: 'foreign-keys-off',
|
||||
run: migrateModerationCaseStates
|
||||
},
|
||||
{ version: 37, name: 'Discord boards and availability subscriptions', run: migrateDiscord }
|
||||
{ version: 37, name: 'Discord boards and availability subscriptions', run: migrateDiscord },
|
||||
{
|
||||
version: 38,
|
||||
name: 'Discord default server publication',
|
||||
run: migrateDiscordPublicationDefaults
|
||||
}
|
||||
];
|
||||
|
||||
export const databaseVersion = migrations.length;
|
||||
|
||||
@@ -14,6 +14,8 @@ type SourceServer = FleetServer & {
|
||||
network?: { publicAddress: string | null; publicPort: number | null } | null;
|
||||
};
|
||||
|
||||
export const DEFAULT_DISCORD_LOBBY_CAPACITY = 4;
|
||||
|
||||
/** IDs stay short enough for Discord components, even for long registry names. */
|
||||
export function discordServerKey(name: string): string {
|
||||
return createHash('sha256').update(name).digest('hex').slice(0, 20);
|
||||
@@ -69,19 +71,24 @@ export function projectFleet(
|
||||
settings: DiscordSettings,
|
||||
now = Date.now()
|
||||
): DiscordPublicServer[] {
|
||||
const published = new Set(settings.publishedServers);
|
||||
const excluded = new Set(settings.excludedServers);
|
||||
return servers
|
||||
.flatMap((server): DiscordPublicServer[] => {
|
||||
if (!published.has(server.name)) return [];
|
||||
if (excluded.has(server.name)) return [];
|
||||
const identity = fleetIdentity(server);
|
||||
if (identity.game !== 't5' && identity.game !== 't6') return [];
|
||||
const game = identity.game;
|
||||
const fresh = freshServer(server, now);
|
||||
const fresh =
|
||||
freshServer(server, now) &&
|
||||
(!server.telemetry_after ||
|
||||
Date.parse(server.last_seen_at ?? '') > Date.parse(server.telemetry_after));
|
||||
const players =
|
||||
fresh && Number.isSafeInteger(server.last_players) && server.last_players! >= 0
|
||||
? server.last_players
|
||||
: null;
|
||||
const capacity = players !== null ? currentCapacity(server, now) : null;
|
||||
// These lobbies have four slots when the live status omits the limit.
|
||||
// Occupancy still needs fresh, current-process evidence below.
|
||||
const capacity = currentCapacity(server, now) ?? DEFAULT_DISCORD_LOBBY_CAPACITY;
|
||||
const state: DiscordPublicServer['state'] =
|
||||
server.health === 'starting' || server.health === 'provisioning'
|
||||
? 'starting'
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
|
||||
/** Replace the old opt-in default while preserving an existing nonempty selection. */
|
||||
export function migrateDiscordPublicationDefaults(handle: Database.Database): void {
|
||||
const row = handle.prepare('SELECT value FROM discord_configuration WHERE id=1').get() as
|
||||
{ value: string } | undefined;
|
||||
if (!row) return;
|
||||
const settings = JSON.parse(row.value) as Record<string, unknown>;
|
||||
if ('excludedServers' in settings) return;
|
||||
const published = new Set(settings.publishedServers as string[]);
|
||||
settings.excludedServers = published.size
|
||||
? (
|
||||
handle
|
||||
.prepare('SELECT name FROM servers WHERE destroyed_at IS NULL ORDER BY name')
|
||||
.all() as { name: string }[]
|
||||
)
|
||||
.map((server) => server.name)
|
||||
.filter((name) => !published.has(name))
|
||||
: [];
|
||||
delete settings.publishedServers;
|
||||
handle
|
||||
.prepare('UPDATE discord_configuration SET value=? WHERE id=1')
|
||||
.run(JSON.stringify(settings));
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '../../leaderboard-efficiency.js';
|
||||
import { MIN_RECORDED_ROUND } from '../../match-history.js';
|
||||
import { gameTitle } from '../../ui/server-labels.js';
|
||||
import { discordServerKey, discordText } from './projection.js';
|
||||
import { DEFAULT_DISCORD_LOBBY_CAPACITY, discordServerKey, discordText } from './projection.js';
|
||||
|
||||
export const BOARD_PAGE_SIZE = 8;
|
||||
export const BROWSER_PAGE_SIZE = 5;
|
||||
@@ -65,7 +65,7 @@ function summary(server: DiscordPublicServer): string {
|
||||
const count =
|
||||
server.players === null
|
||||
? 'Players unknown'
|
||||
: `${server.players}/${server.capacity ?? '?'} players`;
|
||||
: `${server.players}/${server.capacity ?? DEFAULT_DISCORD_LOBBY_CAPACITY} players`;
|
||||
return `${count}${server.round === null ? '' : ` · Round ${server.round}`}\n${states[server.state]}`;
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ export function renderWatchMaps(
|
||||
export function renderWatchChoice(server: DiscordPublicServer): DiscordMessage {
|
||||
return discordMessage([
|
||||
text(
|
||||
`## Watch ${discordText(server.map)}\nEmpty means zero observed players. Space means fewer players than the current observed capacity. Neither guarantees that a match accepts new players.\nOne DM · Expires in 24 hours`
|
||||
`## Watch ${discordText(server.map)}\nEmpty means zero observed players. Space means fewer players than the lobby capacity. Neither guarantees that a match accepts new players.\nOne DM · Expires in 24 hours`
|
||||
),
|
||||
row(
|
||||
button('Empty lobby', `ec:w:empty:${discordServerKey(server.mapKey)}`),
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
DiscordSettings
|
||||
} from '../../discord.js';
|
||||
import { requireDiscordCredentials } from './config.js';
|
||||
import { discordText, publicFleet } from './projection.js';
|
||||
import { DEFAULT_DISCORD_LOBBY_CAPACITY, discordText, publicFleet } from './projection.js';
|
||||
import { renderBoard } from './render.js';
|
||||
import { DiscordStore, discordStore, type DiscordJob, type DiscordSubscription } from './store.js';
|
||||
import { DiscordDeliveryError, DiscordRest, type DiscordTransport } from './transport.js';
|
||||
@@ -373,7 +373,7 @@ function matchingServer(
|
||||
function alertMessage(watch: DiscordSubscription, server: DiscordPublicServer): DiscordMessage {
|
||||
const safe = (value: string) => discordText(value, 100);
|
||||
return {
|
||||
content: `🍒 **${safe(server.map)}** ${watch.mode === 'empty' ? 'has an empty lobby' : 'has an open player slot'}: **${safe(server.label)}** (${server.players}/${server.capacity ?? '?'}).\nThis describes occupancy; joining an in-progress game may still be restricted.\nYour one-time map watch is complete. Use /servers to browse or /watch to watch again.`,
|
||||
content: `🍒 **${safe(server.map)}** ${watch.mode === 'empty' ? 'has an empty lobby' : 'has an open player slot'}: **${safe(server.label)}** (${server.players}/${server.capacity ?? DEFAULT_DISCORD_LOBBY_CAPACITY}).\nThis describes occupancy; joining an in-progress game may still be restricted.\nYour one-time map watch is complete. Use /servers to browse or /watch to watch again.`,
|
||||
allowed_mentions: { parse: [] }
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user