ClientX/_opt/scExecHangarStatus.js
2025-04-25 21:27:00 -04:00

335 lines
12 KiB
JavaScript

// _opt/schangar.js
import { SlashCommandBuilder } from 'discord.js';
// Export commands array for the centralized handler
export const commands = [
{
data: new SlashCommandBuilder()
.setName('hangarsync')
.setDescription('Mark the moment all five lights turn green, for use with hangarstatus')
.addStringOption(option =>
option.setName('timestamp')
.setDescription('Custom timestamp (Unix time in seconds or ISO 8601 format). Leave empty for current time.')
.setRequired(false)),
execute: async (interaction, client) => {
const customTimestamp = interaction.options.getString('timestamp');
let syncEpoch;
// Attempt to validate custom timestamp
if (customTimestamp) {
try {
if (/^\d+$/.test(customTimestamp)) {
const timestampInSeconds = parseInt(customTimestamp);
if (timestampInSeconds < 0 || timestampInSeconds > Math.floor(Date.now() / 1000)) {
return interaction.reply({
content: 'Invalid timestamp. Please provide a Unix time in seconds that is not in the future.',
ephemeral: true
});
}
syncEpoch = timestampInSeconds * 1000;
} else {
const date = new Date(customTimestamp);
syncEpoch = date.getTime();
if (isNaN(syncEpoch) || syncEpoch < 0) {
return interaction.reply({
content: 'Invalid timestamp format. Please use Unix time in seconds or a valid ISO 8601 string.',
ephemeral: true
});
}
}
} catch (error) {
client.logger.error(`Failed to parse timestamp in hangarsync command: ${error.message}`);
return interaction.reply({
content: 'Failed to parse timestamp. Please use Unix time in seconds or a valid ISO 8601 string.',
ephemeral: true
});
}
} else {
syncEpoch = Date.now();
}
// Check PocketBase connection status
if (!isPocketBaseConnected(client)) {
client.logger.error('PocketBase not connected when executing hangarsync command');
// Try to reconnect if available
if (typeof client.pb.ensureConnection === 'function') {
await client.pb.ensureConnection();
// Check if reconnection worked
if (!isPocketBaseConnected(client)) {
return interaction.reply({
content: 'Database connection unavailable. Please try again later.',
ephemeral: true
});
}
} else {
return interaction.reply({
content: 'Database connection unavailable. Please try again later.',
ephemeral: true
});
}
}
// Create or update timestamp for guild
try {
let record = null;
try {
// First try the enhanced method if available
if (typeof client.pb.getFirst === 'function') {
record = await client.pb.getFirst('command_hangarsync', `guildId = "${interaction.guildId}"`);
} else {
// Fall back to standard PocketBase method
const records = await client.pb.collection('command_hangarsync').getList(1, 1, {
filter: `guildId = "${interaction.guildId}"`
});
if (records.items.length > 0) {
record = records.items[0];
}
}
} catch (error) {
// Handle case where collection might not exist
client.logger.warn(`Error retrieving hangarsync record: ${error.message}`);
}
if (record) {
// Update existing record
if (typeof client.pb.updateOne === 'function') {
await client.pb.updateOne('command_hangarsync', record.id, {
userId: `${interaction.user.id}`,
epoch: `${syncEpoch}`,
});
} else {
await client.pb.collection('command_hangarsync').update(record.id, {
userId: `${interaction.user.id}`,
epoch: `${syncEpoch}`,
});
}
client.logger.info(`Updated hangarsync for guild ${interaction.guildId} by user ${interaction.user.id}`);
} else {
// Create new record
if (typeof client.pb.createOne === 'function') {
await client.pb.createOne('command_hangarsync', {
guildId: `${interaction.guildId}`,
userId: `${interaction.user.id}`,
epoch: `${syncEpoch}`,
});
} else {
await client.pb.collection('command_hangarsync').create({
guildId: `${interaction.guildId}`,
userId: `${interaction.user.id}`,
epoch: `${syncEpoch}`,
});
}
client.logger.info(`Created new hangarsync for guild ${interaction.guildId} by user ${interaction.user.id}`);
}
await interaction.reply(`Executive hangar status has been synced: <t:${Math.ceil(syncEpoch / 1000)}>`);
} catch (error) {
client.logger.error(`Error in hangarsync command: ${error.message}`);
await interaction.reply({
content: `Error syncing hangar status. Please try again later.`,
ephemeral: true
});
}
}
},
{
data: new SlashCommandBuilder()
.setName('hangarstatus')
.setDescription('Check the status of contested zone executive hangars')
.addBooleanOption(option =>
option.setName('verbose')
.setDescription('Extra output, mainly for debugging.')
.setRequired(false)),
execute: async (interaction, client) => {
const verbose = interaction.options.getBoolean('verbose');
// Check PocketBase connection status
if (!isPocketBaseConnected(client)) {
client.logger.error('PocketBase not connected when executing hangarstatus command');
// Try to reconnect if available
if (typeof client.pb.ensureConnection === 'function') {
await client.pb.ensureConnection();
// Check if reconnection worked
if (!isPocketBaseConnected(client)) {
return interaction.reply({
content: 'Database connection unavailable. Please try again later.',
ephemeral: true
});
}
} else {
return interaction.reply({
content: 'Database connection unavailable. Please try again later.',
ephemeral: true
});
}
}
try {
// Get hangarsync data for guild
let hangarSync = null;
try {
// First try the enhanced method if available
if (typeof client.pb.getFirst === 'function') {
hangarSync = await client.pb.getFirst('command_hangarsync', `guildId = "${interaction.guildId}"`);
} else {
// Fall back to standard PocketBase methods
try {
hangarSync = await client.pb.collection('command_hangarsync').getFirstListItem(`guildId = "${interaction.guildId}"`);
} catch (error) {
// getFirstListItem throws if no items found
if (error.status !== 404) throw error;
}
}
if (!hangarSync) {
client.logger.info(`No sync data found for guild ${interaction.guildId}`);
return interaction.reply({
content: 'No sync data found. Please use `/hangarsync` first to establish a reference point.',
ephemeral: true
});
}
} catch (error) {
client.logger.info(`Error retrieving sync data for guild ${interaction.guildId}: ${error.message}`);
return interaction.reply({
content: 'No sync data found. Please use `/hangarsync` first to establish a reference point.',
ephemeral: true
});
}
const currentTime = Date.now();
// 5 minutes (all off) + 5*24 minutes (turning green) + 5*12 minutes (turning off) = 185 minutes
const cycleDuration = 5 + (5 * 24) + (5 * 12);
// Key positions in the cycle
const allOffDuration = 5;
const turningGreenDuration = 5 * 24;
const turningOffDuration = 5 * 12;
// Calculate how much time has passed since the epoch
const timeSinceEpoch = (currentTime - hangarSync.epoch) / (60 * 1000);
// Calculate where we are in the full-cycle relative to the epoch
const cyclePosition = ((timeSinceEpoch % cycleDuration) + cycleDuration) % cycleDuration;
// Initialize stuff and things
const lights = [":black_circle:", ":black_circle:", ":black_circle:", ":black_circle:", ":black_circle:"];
let minutesUntilNextPhase = 0;
let currentPhase = "";
// If the epoch is now, we should be at the all-green position.
// From there, we need to determine where we are in the cycle.
// Case 1: We're in the unlocked phase, right after epoch
if (cyclePosition < turningOffDuration) {
currentPhase = "Unlocked";
// All lights start as green
lights.fill(":green_circle:");
// Calculate how many lights have turned off
const offLights = Math.floor(cyclePosition / 12);
// Set the appropriate number of lights to off
for (let i = 0; i < offLights; i++) {
lights[i] = ":black_circle:";
}
// Calculate time until next light turns off
const timeUntilNextLight = 12 - (cyclePosition % 12);
minutesUntilNextPhase = timeUntilNextLight;
}
// Case 2: We're in the reset phase
else if (cyclePosition < turningOffDuration + allOffDuration) {
currentPhase = "Resetting";
// Lights are initialized "off", so do nothing with them
// Calculate time until all lights turn red
const timeIntoPhase = cyclePosition - turningOffDuration;
minutesUntilNextPhase = allOffDuration - timeIntoPhase;
}
// Case 3: We're in the locked phase
else {
currentPhase = "Locked";
// All lights start as red
lights.fill(":red_circle:");
// Calculate how many lights have turned green
const timeIntoPhase = cyclePosition - (turningOffDuration + allOffDuration);
const greenLights = Math.floor(timeIntoPhase / 24);
// Set the appropriate number of lights to green
for (let i = 0; i < greenLights; i++) {
lights[i] = ":green_circle:";
}
// Calculate time until next light turns green
const timeUntilNextLight = 24 - (timeIntoPhase % 24);
minutesUntilNextPhase = timeUntilNextLight;
}
// Calculate a timestamp for Discord's formatting and reply
const expiration = Math.ceil((Date.now() / 1000) + (minutesUntilNextPhase * 60));
await interaction.reply(`### ${lights[0]} ${lights[1]} ${lights[2]} ${lights[3]} ${lights[4]}`);
if (verbose) {
await interaction.followUp(`- **Phase**: ${currentPhase}\n- **Status Expiration**: <t:${expiration}:R>\n- **Epoch**: <t:${Math.ceil(hangarSync.epoch / 1000)}:R>\n- **Sync**: <t:${Math.floor(new Date(hangarSync.updated).getTime() / 1000)}:R> by <@${hangarSync.userId}>`);
// Add additional debug info to logs
client.logger.debug(`Hangarstatus for guild ${interaction.guildId}: Phase=${currentPhase}, CyclePosition=${cyclePosition}, TimeSinceEpoch=${timeSinceEpoch}`);
}
} catch (error) {
client.logger.error(`Error in hangarstatus command: ${error.message}`);
await interaction.reply({
content: `Error retrieving hangar status. Please try again later.`,
ephemeral: true
});
}
}
}
];
// Function to check PocketBase connection status
function isPocketBaseConnected(client) {
// Check multiple possible status indicators to be safe
return client.pb && (
// Check status object (original code style)
(client.pb.status && client.pb.status.connected) ||
// Check isConnected property (pbutils module style)
client.pb.isConnected === true ||
// Last resort: check if authStore is valid
client.pb.authStore?.isValid === true
);
}
// Initialize module
export const init = async (client, config) => {
client.logger.info('Initializing Star Citizen Hangar Status module');
// Check PocketBase connection
if (!isPocketBaseConnected(client)) {
client.logger.warn('PocketBase not connected at initialization');
// Try to reconnect if available
if (typeof client.pb.ensureConnection === 'function') {
await client.pb.ensureConnection();
}
} else {
client.logger.info('PocketBase connection confirmed');
}
client.logger.info('Star Citizen Hangar Status module initialized');
};