Skip to content

Commit 5466167

Browse files
authored
Merge pull request #21 from Mirasaki/feat/next-restart
Feat: next restart in ... functionality
2 parents 168981e + f1bb69f commit 5466167

File tree

4 files changed

+87
-2
lines changed

4 files changed

+87
-2
lines changed

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ services:
1515
PLAYER_COUNT_PROVIDER: ${PLAYER_COUNT_PROVIDER}
1616
DISCORD_PUBLISHER_MESSAGE_FORMAT: ${DISCORD_PUBLISHER_MESSAGE_FORMAT}
1717
DISCORD_PUBLISHER_MESSAGE_QUEUED_FORMAT: ${DISCORD_PUBLISHER_MESSAGE_QUEUED_FORMAT}
18+
DISCORD_PUBLISHER_MESSAGE_NEXT_RESTART_FORMAT: ${DISCORD_PUBLISHER_MESSAGE_NEXT_RESTART_FORMAT}
1819
STEAM_API_TOKEN: ${STEAM_API_TOKEN}
1920
GAME_ADDRESS: ${GAME_ADDRESS}
2021
BM_ACCESS_TOKEN: ${BM_ACCESS_TOKEN}

example.env

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ DISCORD_TOKEN=
4141
# count provider, the game and the used game server.
4242
DISCORD_MESSAGE_CHANNEL_ID=
4343

44+
# Next Restart Configuration - Restart times in LOCAL hours (local = time of the machine this app will be hosted on)
45+
# PLEASE NOTE: "Next restart in..." is NOT powered by an API (nor does it restart your servers), it instead requires
46+
# an absolute restart schedule. If you change your restart schedule, you WILL
47+
# need to change this to prevent feeding misinformation to your players.
48+
# Format: Comma separated list (eg. "0,6,12,18" for every 6 hours)
49+
ABSOLUTE_RESTART_TIMES=0,4,8,12,16,20
4450

4551
# Message formats
4652
# You can define your own message formats for the player count display in the user list in Discord.
@@ -49,6 +55,9 @@ DISCORD_MESSAGE_CHANNEL_ID=
4955
# - maxPlayers: The maximum number of players that can be on the server
5056
# - queuedPlayersMessage: The message as defined in DISCORD_PUBLISHER_MESSAGE_QUEUED_FORMAT (only makes sense when the server supports a player queue
5157
# - queuedPlayers: The number of players currently in the queue
58+
# - nextRestartMessage: The message as defined in DISCORD_PUBLISHER_MESSAGE_NEXT_RESTART_FORMAT (only used/parsed when ABSOLUTE_RESTART_TIMES is provided and valid)
59+
# - nextRestartRelative: Relative time until next server restart
5260
# Example (uncomment if editing):
53-
# DISCORD_PUBLISHER_MESSAGE_FORMAT='${playerCount}/${maxPlayers} ${queuedPlayersMessage}'
61+
# DISCORD_PUBLISHER_MESSAGE_FORMAT='${playerCount}/${maxPlayers} ${queuedPlayersMessage} ${nextRestartMessage}'
5462
# DISCORD_PUBLISHER_MESSAGE_QUEUED_FORMAT='(+${queuedPlayers})'
63+
# DISCORD_PUBLISHER_MESSAGE_NEXT_RESTART_FORMAT='⌛ Restart: ${nextRestartRelative}'

src/adapter/discord/discord-publisher.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,69 @@ import {MapsRepository} from '../../domain/maps-repository.js';
88
export interface MessageFormats {
99
playerCount: string,
1010
queuedPlayers: string,
11+
nextRestart: string,
1112
}
1213

1314
export class DiscordPublisher implements GameStatusPublisher {
1415
private messageId: string | undefined;
1516
private channel: TextChannel | undefined;
17+
private nextRestartTimes: number[] | undefined;
18+
19+
public msUntilNextRestart(): number | null {
20+
if (!this.nextRestartTimes || this.nextRestartTimes.length === 0) {
21+
return null;
22+
}
23+
const now = new Date();
24+
const currentHour = now.getHours();
25+
const nextRestartHour = this.nextRestartTimes.find((t) => t > currentHour);
26+
if (nextRestartHour === undefined) {
27+
return null;
28+
}
29+
const nextRestartDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), nextRestartHour, 0, 0);
30+
if (nextRestartDate < now) {
31+
nextRestartDate.setDate(nextRestartDate.getDate() + 1);
32+
}
33+
return nextRestartDate.getTime() - now.getTime();
34+
}
35+
36+
public humanReadableMs(ms: number): string {
37+
const seconds = Math.floor(ms / 1000);
38+
const minutes = Math.floor(seconds / 60);
39+
const hours = Math.floor(minutes / 60);
40+
41+
const pluralize = (value: number, unit: string): string => {
42+
return `${value} ${unit}${value !== 1 ? 's' : ''}`;
43+
};
44+
45+
const parts: string[] = [];
46+
if (hours > 0) {
47+
parts.push(pluralize(hours, 'hour'));
48+
}
49+
if (minutes % 60 > 0) {
50+
parts.push(pluralize(minutes % 60, 'minute'));
51+
}
52+
if (parts.length === 0 && seconds % 60 > 0) {
53+
parts.push(pluralize(seconds % 60, 'second'));
54+
}
55+
if (parts.length === 0) {
56+
return 'less than a second';
57+
}
58+
59+
return parts.join(', ');
60+
}
1661

1762
constructor(private readonly client: Client, private readonly maps: MapsRepository, private readonly formats: MessageFormats) {
1863
if (!process.env.DISCORD_MESSAGE_CHANNEL_ID) {
1964
return
2065
}
66+
if (process.env.ABSOLUTE_RESTART_TIMES?.length) {
67+
const times = process.env.ABSOLUTE_RESTART_TIMES.split(',').map((t) => parseInt(t.trim(), 10));
68+
if (times.some((t) => isNaN(t) || t < 0 || t > 23)) {
69+
throw new Error('Invalid ABSOLUTE_RESTART_TIMES provided, must be a comma separated list of integers between (inclusive) 0 and 23.');
70+
}
71+
console.log('Using absolute restart times: ' + times.join(', '));
72+
this.nextRestartTimes = times;
73+
}
2174
this.updateCreateStatusMessage().then((c) => {
2275
console.log('Created or updates initial status message...');
2376
this.messageId = c.messageId;
@@ -103,6 +156,27 @@ export class DiscordPublisher implements GameStatusPublisher {
103156
.replace('${queuedPlayersMessage}', '')
104157
.replace('${queuedPlayers}', '');
105158
}
159+
const hasNextRestartMessage = message.indexOf('${nextRestartMessage}') !== -1;
160+
const hasNextRestartRelative = message.indexOf('${nextRestartRelative}') !== -1;
161+
if (hasNextRestartMessage || hasNextRestartRelative) {
162+
const nextRestart = this.msUntilNextRestart();
163+
if (nextRestart !== null) {
164+
const humanReadableNextRestart = this.humanReadableMs(nextRestart);
165+
if (hasNextRestartMessage) {
166+
message = message.replace(
167+
'${nextRestartMessage}',
168+
this.formats.nextRestart.replace('${nextRestartRelative}', humanReadableNextRestart)
169+
);
170+
}
171+
if (hasNextRestartRelative) {
172+
message = message.replace('${nextRestartRelative}', humanReadableNextRestart);
173+
}
174+
} else {
175+
message = message
176+
.replace('${nextRestartMessage}', '')
177+
.replace('${nextRestartRelative}', '');
178+
}
179+
}
106180
this.client.user?.setPresence({
107181
status: 'online',
108182
activities: [{

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ class App {
2222
this.client = await this.createDiscordClient();
2323
try {
2424
const publisher = new DiscordPublisher(this.client, new FileBackedMapRepository(), {
25-
playerCount: process.env.DISCORD_PUBLISHER_MESSAGE_FORMAT || '${playerCount}/${maxPlayers} $queuedPlayersMessage',
25+
playerCount: process.env.DISCORD_PUBLISHER_MESSAGE_FORMAT || '${playerCount}/${maxPlayers} $queuedPlayersMessage ${nextRestartMessage}',
2626
queuedPlayers: process.env.DISCORD_PUBLISHER_MESSAGE_QUEUED_FORMAT || '(+${queuedPlayers})',
27+
nextRestart: process.env.DISCORD_PUBLISHER_MESSAGE_NEXT_RESTART_FORMAT || '⌛ Restart: ${nextRestartRelative}',
2728
});
2829
this.useCase = new ProvideGameStatus(providerFactory().build(), publisher);
2930
} catch (e) {

0 commit comments

Comments
 (0)