Skip to content

Commit

Permalink
implement last seen (and integrate into /townless)
Browse files Browse the repository at this point in the history
  • Loading branch information
Owen3H committed Jul 25, 2024
1 parent 8363627 commit bb99520
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 52 deletions.
18 changes: 18 additions & 0 deletions aurora/commands/nation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
type Client, type Message,
EmbedBuilder, Colors
} from "discord.js"

export default {
name: "nation",
description: "Displays info for a nation.",
slashCommand: true,
aliases: ["n"],
run: async (_client: Client, message: Message, _args: string[]) => {
return message.reply({embeds: [new EmbedBuilder()
.setTitle("Command Deprecated")
.setTitle("This command no longer exists. Use the **nation** slash commands instead.")
.setColor(Colors.Orange)
]}).then(m => setTimeout(() => m.delete(), 5000)).catch(() => {})
}
}
18 changes: 18 additions & 0 deletions aurora/commands/town.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
type Client, type Message,
EmbedBuilder, Colors
} from "discord.js"

export default {
name: "town",
description: "Displays info for a town.",
slashCommand: true,
aliases: ["t"],
run: async (_client: Client, message: Message, _args: string[]) => {
return message.reply({embeds: [new EmbedBuilder()
.setTitle("Command Deprecated")
.setTitle("This command no longer exists. Use the **town** slash commands instead.")
.setColor(Colors.Orange)
]}).then(m => setTimeout(() => m.delete(), 5000)).catch(() => {})
}
}
2 changes: 1 addition & 1 deletion aurora/common/resident.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class ResidentHelper extends BaseHelper {
const statusStr = this.status == "Offline" ? ":red_circle: Offline" : ":green_circle: Online"
this.addField("Status", statusStr, true)

if (lastOnlineTs != 0)
if (lastOnlineTs != 0 && this.status == "Offline")
this.addField("Last Online", `<t:${secToMs(lastOnlineTs)}:R>`, true)

if (registeredTs != 0)
Expand Down
92 changes: 67 additions & 25 deletions aurora/slashcommands/townless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {
} from "discord.js"

import { Aurora } from 'earthmc'
import { fetchError, paginatorInteraction } from '../../bot/utils/fn.js'
import { paginatorInteraction } from '../../bot/utils/fn.js'
import { lastSeenPlayers } from "../../bot/constants.js"

const embed = (len: number, desc: string, footer?: { text: string, iconURL: string }) => {
const embed = (len: number, desc: string, footer?: { text: string, iconURL?: string }) => {
const builder = new EmbedBuilder()
.setColor(Colors.DarkPurple)
.setTitle(`Townless Players [${len}]`)
Expand All @@ -18,43 +19,84 @@ const embed = (len: number, desc: string, footer?: { text: string, iconURL: stri
return builder
}

const townlessLastSeen = async () => {
//#region Cache these
// TODO
const residents = await Aurora.Residents.all()
if (!residents) {
console.warn(`[AURORA] Error getting townless, could not get residents!`)
return null
}

const residentNames = new Set<string>(residents.reduce((out: string[], cur) => {
out.push(cur.name)
return out
}, []))
//#endregion

return [...lastSeenPlayers.values()].filter(p => !residentNames.has(p.name))
}

export default {
name: "townless",
description: "Lists all online players without a town.",
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
const townlessPlayers = await Aurora.Players.townless()
if (!townlessPlayers) return await interaction.reply({ embeds: [fetchError], ephemeral: true })
run: async (_: Client, interaction: ChatInputCommandInteraction) => {
// const townlessPlayers = await Aurora.Players.townless()
// if (!townlessPlayers) return await interaction.reply({ embeds: [fetchError], ephemeral: true })

const townlessLen = townlessPlayers.length
const allData = townlessPlayers.map(p => p.name).join('\n').match(/(?:^.*$\n?){1,10}/mg)
const len = allData.length

const page = 0
const botEmbed: EmbedBuilder[] = []
const townless = await townlessLastSeen()
const townlessAmt = townless.length

if (townlessAmt < 1) {
// Try emc.Townless()

if (townlessLen < 1) {
// Definitely no townless online, send appropriate msg.
return interaction.reply({
embeds: [embed(0, "There are currently no townless players.")],
ephemeral: true
})
}

if (len <= 1) { // If only one page, don't create paginator.
return interaction.reply({embeds: [
embed(townlessLen, "```" + townlessPlayers[0].name + "\n" + allData.toString() + "```")
]})

// const onlineTownless: SeenPlayer[] = []
// const offlineTownless: SeenPlayer[] = []

// // Single pass [O(n)] to avoid slight overhead of two filter/map calls. [O(2n)]
// for (let i = 0; i < townlessAmt; i++) {
// const player = townless[i]

// if (player.online) onlineTownless.push(player)
// else offlineTownless.push(player)
// }

const allData = townless.sort((a, b) => (a.online === b.online) ? 0 : a.online ? -1 : 1).map(p => {
if (p.online) return `${p.name}`

const minSinceSeen = ((Date.now() - p.timestamp) / 60000)
if (minSinceSeen >= 1) return `(Seen: ${minSinceSeen.toFixed(0)}m ago) ${p.name}`

const secSinceSeen = ((Date.now() - p.timestamp) / 1000)
return `(Seen: ${secSinceSeen.toFixed(0)}s ago) ${p.name}`
}).join('\n').match(/(?:^.*$\n?){1,15}/mg)

// console.log("---- Online Townless ----", onlineTownlessData)
// console.log("----------------------------")
// console.log("---- Offline Townless ----", offlineTownlessData)

const len = allData.length
if (len <= 1) {
// If only one page, don't create paginator.
const desc = "```" + `${townless[0].name}\n${allData}` + "```"
return interaction.reply({ embeds: [embed(len, desc)] })
}

for (let i = 0; i < len; i++) {
botEmbed[i] = embed(
townlessLen,
"```" + townlessPlayers[0].name + "\n" + allData[i] + "```",
{ text: `Page ${i+1}/${len}`, iconURL: client.user.avatarURL() }
)
const botEmbed: EmbedBuilder[] = []
for (let page = 0; page < len; page++) {
const desc = "```" + `${townless[0].name}\n${allData[page]}` + "```"
botEmbed[page] = embed(townlessAmt, desc, { text: `Page ${page+1}/${len}` })
}

await interaction.reply({ embeds: [botEmbed[page]] })
.then(() => paginatorInteraction(interaction, botEmbed, page))
await interaction.reply({ embeds: [botEmbed[0]] })
.then(() => paginatorInteraction(interaction, botEmbed, 0))
.catch(console.log)
}, data: new SlashCommandBuilder()
.setName("townless")
Expand Down
4 changes: 3 additions & 1 deletion bot/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Aurora } from "earthmc"

import type { Client } from "discord.js"
import type { Firestore } from "firebase-admin/firestore"
import type { MapInstance } from "./types.js"
import type { MapInstance, SeenPlayer } from "./types.js"

import {
type DocReference,
Expand Down Expand Up @@ -42,6 +42,8 @@ const AURORA: MapInstance = {
// We update this every x seconds, so expiry isn't needed.
const cache = new TTLCache<string, any>({ ttl: Infinity })

export const lastSeenPlayers = new Map<string, SeenPlayer>()

export {
prod, setProduction,
client, setClient,
Expand Down
15 changes: 6 additions & 9 deletions bot/events/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ const rdyEvent: DJSEvent = {
'admins sleep', 'alliances forming', 'the queue grow'
]

await initUpdates(prod)

if (prod) {
console.log("Production enabled, initializing data updates..")
await initUpdates()

queueSubbedChannels.get().then(doc => {
const { channelIDs } = doc.data()
fn.setQueueSubbedChannels(channelIDs)
Expand Down Expand Up @@ -103,12 +102,10 @@ async function registerCommands(client: ExtendedClient) {
client.slashCommands.set(command.name, command)

if (command.data) data.push(command.data.toJSON())
else {
data.push({
name: command.name,
description: command.description
})
}
else data.push({
name: command.name,
description: command.description
})
}

const linkAction = new ContextMenuCommandBuilder().setName("Link User").setType(2)
Expand Down
22 changes: 20 additions & 2 deletions bot/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
Collection
} from "discord.js"

import type { Nation, Squaremap, SquaremapNation, SquaremapTown, Town } from "earthmc"
import type { Nation, Squaremap, SquaremapNation, SquaremapOnlinePlayer, SquaremapTown, Town } from "earthmc"
import type { Timestamp, WriteResult } from "firebase-admin/firestore"

export type ErrorWithCode = Error & { code: number }
Expand Down Expand Up @@ -228,4 +228,22 @@ export interface V3Player extends Entity {
nationRanks?: string[]
}
friends: Entity[]
}
}

export interface SeenPlayer extends SquaremapOnlinePlayer {
online: boolean
timestamp: number
}

// type OfflineEstimateType = typeof OFFLINE_ESTIMATE
// export type OfflineEstimate = OfflineEstimateType[keyof OfflineEstimateType]

// export const OFFLINE_ESTIMATE = {
// Possibly: 5,
// Likely: 10,
// VeryLikely: 20,
// Certain: 40,
// Definitely: 80
// } as const

// export const OFFLINE_ESTIMATES = Object.values(OFFLINE_ESTIMATE)
51 changes: 37 additions & 14 deletions bot/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import * as database from "../bot/utils/database.js"
import * as fn from "../bot/utils/fn.js"

import {
formatString
formatString,
type SquaremapOnlinePlayer
} from "earthmc"

import {
prod, client,
AURORA //NOVA
client,
AURORA, //NOVA
lastSeenPlayers
} from "./constants.js"

import {
Expand All @@ -23,18 +25,24 @@ import {
Colors, EmbedBuilder
} from "discord.js"

import type {
MapInstance, ResidentRank,
DBAlliance, DBResident, DBPlayer
import {
type MapInstance, type ResidentRank,
type DBAlliance, type DBResident, type DBPlayer,
type SeenPlayer
} from "./types.js"

import { devsFooter } from "../bot/utils/fn.js"
//#endregion

//#region Call Updates
const oneMinute = 60 * 1000

async function initUpdates() {
export async function initUpdates(prod = false) {
await updateLastSeen()

if (prod) {
console.log("Production enabled, initializing data updates..")

await updateAurora(true)
await updateAlliances(AURORA)

Expand All @@ -46,15 +54,16 @@ async function initUpdates() {
await updateNews()
}

setInterval(() => updateAurora(), 1.5 * oneMinute)
setInterval(updateLastSeen, 0.15 * oneMinute)
setInterval(updateAurora, 1.5 * oneMinute)

setInterval(async () => {
await updateAlliances(AURORA)
await api.sendAuroraAlliances()
}, 2 * oneMinute)

// Send news to API (for both maps).
setInterval(() => updateNews(), 10 * oneMinute)
setInterval(updateNews, 10 * oneMinute)

// setInterval(async () => {
// await updateFallenTowns(AURORA)
Expand Down Expand Up @@ -266,6 +275,24 @@ async function updateMapData(map: MapInstance) {
map.db.setNations(nationsArray)
//#endregion
}

async function updateLastSeen() {
const ops = await AURORA.emc.Players.online() as SquaremapOnlinePlayer[]
if (!ops) return console.warn(`[AURORA] Error updating last seen, bad response getting online players!`)

const now = Date.now()

ops.forEach(op => {
op['timestamp'] = now
lastSeenPlayers.set(op.name, op as SeenPlayer)
})

lastSeenPlayers.forEach(v => {
v.online = ops.some(op => op.name == v.name)
})

console.log(`[AURORA] Updated last seen. Length: ${lastSeenPlayers.size}`)
}
//#endregion

//#region Live Stuff
Expand Down Expand Up @@ -600,8 +627,4 @@ async function _purgeInactive(players: DBPlayer[]) {

return players
}
//#endregion

export {
initUpdates
}
//#endregion

0 comments on commit bb99520

Please sign in to comment.