diff --git a/.gitignore b/.gitignore index d3cfc9e..506e2e5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ foo* newrelic_agent.log data.json +strippedData.json +differences.json diff --git a/src/api-wrapper/api.ts b/src/api-wrapper/api.ts index 0726433..93289cf 100644 --- a/src/api-wrapper/api.ts +++ b/src/api-wrapper/api.ts @@ -8,6 +8,8 @@ import { MergedPlanetEventData, ApiData, WarDifferences, + Assignment, + NewsFeedItem, } from './types'; import {getFactionName, getPlanetEventType, getPlanetName} from './mapping'; import {existsSync, mkdirSync, writeFileSync} from 'fs'; @@ -65,6 +67,8 @@ export let data: ApiData = { Terminids: 0, }, UTCOffset: 0, + Assignment: [], + NewsFeed: [], }; export async function getData() { @@ -85,12 +89,7 @@ export async function getData() { }) ).data; const warInfo = warInfoApi as WarInfo; - // const warInfoPath = path.join( - // 'api_responses', - // String(season), - // `${fileTimestamp}_WarInfo.json` - // ); - // await writeGzipJson(warInfoPath + '.gz', warInfoApi); + const statusApi = await ( await axios.get(`${API_URL}/WarSeason/${season}/Status`, { headers: { @@ -101,12 +100,26 @@ export async function getData() { const status = statusApi as Status; status.timeUtc = Date.now(); - // const statusPath = path.join( - // 'api_responses', - // String(season), - // `${fileTimestamp}_Status.json` - // ); - // await writeGzipJson(statusPath + '.gz', statusApi); + // https://api.live.prod.thehelldiversgame.com/api/v2/Assignment/War/801 + + const assignmentApi = await ( + await axios.get(`${API_URL}/v2/Assignment/War/${season}`, { + headers: { + 'Accept-Language': 'en-us', + }, + }) + ).data; + const assignment = assignmentApi as Assignment[]; + + //https://api.live.prod.thehelldiversgame.com/api/NewsFeed/801 + const newsFeedApi = await ( + await axios.get(`${API_URL}/NewsFeed/${season}`, { + headers: { + 'Accept-Language': 'en-us', + }, + }) + ).data; + const newsFeed = newsFeedApi as NewsFeedItem[]; const planets: MergedPlanetData[] = []; const players = { @@ -123,7 +136,10 @@ export async function getData() { const planetStatus = status.planetStatus.find(p => p.index === index); if (planetStatus) { const {regenPerSecond} = planetStatus; - const liberation = (1 - planetStatus.health / planet.maxHealth) * 100; + const liberation = +( + (1 - planetStatus.health / planet.maxHealth) * + 100 + ).toFixed(4); const lossPercPerHour = ((regenPerSecond * 3600) / planet.maxHealth) * 100; const playerPerc = (planetStatus.players / players['Total']) * 100; @@ -165,6 +181,8 @@ export async function getData() { data = { WarInfo: warInfo, Status: status, + Assignment: assignment, + NewsFeed: newsFeed, Planets: planets, Campaigns: campaigns, PlanetEvents: planetEvents, diff --git a/src/api-wrapper/assignment.ts b/src/api-wrapper/assignment.ts new file mode 100644 index 0000000..8490f9a --- /dev/null +++ b/src/api-wrapper/assignment.ts @@ -0,0 +1,5 @@ +import {data} from './api'; + +export function getLatestAssignment() { + return data.Assignment[0]; +} diff --git a/src/api-wrapper/index.ts b/src/api-wrapper/index.ts index cc5aee5..2dd49f5 100644 --- a/src/api-wrapper/index.ts +++ b/src/api-wrapper/index.ts @@ -1,4 +1,6 @@ +export * from './mapping'; export * from './api'; +export * from './assignment'; export * from './campaign'; export * from './events'; // export * from './info'; diff --git a/src/api-wrapper/mapping/currency.json b/src/api-wrapper/mapping/currency.json new file mode 100644 index 0000000..941800b --- /dev/null +++ b/src/api-wrapper/mapping/currency.json @@ -0,0 +1,3 @@ +{ + "1": "Medals" +} \ No newline at end of file diff --git a/src/api-wrapper/mapping/index.ts b/src/api-wrapper/mapping/index.ts index 42dd269..238be5b 100644 --- a/src/api-wrapper/mapping/index.ts +++ b/src/api-wrapper/mapping/index.ts @@ -1,4 +1,5 @@ -import {Faction, PlanetEventType} from '../types'; +import {Currency, Faction, PlanetEventType} from '../types'; +import currency from './currency.json'; import factions from './factions.json'; import planetEvents from './planetEvents.json'; import planets from './planets.json'; @@ -8,6 +9,10 @@ interface JsonFile { [key: string]: string; } +export function getCurrencyName(id: number): Currency { + return (currency as JsonFile)[id] as Currency; +} + export function getFactionName(id: number): Faction { return (factions as JsonFile)[id] as Faction; } diff --git a/src/api-wrapper/types.ts b/src/api-wrapper/types.ts index 2c78491..33cb28e 100644 --- a/src/api-wrapper/types.ts +++ b/src/api-wrapper/types.ts @@ -3,6 +3,7 @@ export type Position = { y: number; }; +export type Currency = 'Medals'; export type Faction = 'Humans' | 'Total' | 'Automaton' | 'Terminids'; export type PlanetEventType = 'Defend'; export type CampaignType = 'Defend' | 'Liberation'; @@ -172,6 +173,39 @@ export type Status = { superEarthWarResults: any[]; }; +// /api/v2/Assignment/War/{war_id} +export type Assignment = { + id32: number; + progress: number[]; + expiresIn: number; + setting: { + type: number; + overrideTitle: string; + overrideBrief: string; + taskDescription: string; + tasks: { + type: number; + values: number[]; + valueTypes: number[]; + }[]; + reward: { + type: number; + id32: number; + amount: number; + }; + flags: number; + }; +}; + +// /api/NewsFeed/{war_id} +export type NewsFeedItem = { + id: number; + published: number; + type: number; + tagIds: number[]; + message: string; +}; + export type WarOverview = { warId: number; startDate: number; @@ -185,6 +219,8 @@ export type WarOverview = { export type ApiData = { WarInfo: WarInfo; Status: Status; + Assignment: Assignment[]; + NewsFeed: NewsFeedItem[]; Planets: MergedPlanetData[]; Campaigns: MergedCampaignData[]; PlanetEvents: MergedPlanetEventData[]; @@ -200,6 +236,8 @@ export type ApiData = { export type StrippedApiData = { WarInfo: Omit; Status: Omit; + Assignment: Assignment[]; + NewsFeed: NewsFeedItem[]; Campaigns: MergedCampaignData[]; PlanetEvents: MergedPlanetEventData[]; ActivePlanets: MergedPlanetData[]; @@ -214,7 +252,7 @@ export type StrippedApiData = { export type WarDifferences = { NewCampaigns: MergedCampaignData[]; NewEvents: GlobalEvent[]; - NewMajorOrder?: GlobalEvent; + NewMajorOrder?: Assignment; WonPlanets: MergedCampaignData[]; LostPlanets: MergedCampaignData[]; Players: { diff --git a/src/handlers/cron/compareData.ts b/src/handlers/cron/compareData.ts index 06b7283..9b203ca 100644 --- a/src/handlers/cron/compareData.ts +++ b/src/handlers/cron/compareData.ts @@ -43,6 +43,8 @@ export async function compareData(): Promise { globalEvents: data.Status.globalEvents, superEarthWarResults: data.Status.superEarthWarResults, }, + Assignment: data.Assignment, + NewsFeed: data.NewsFeed, Campaigns: data.Campaigns, PlanetEvents: data.PlanetEvents, ActivePlanets: data.ActivePlanets, @@ -106,6 +108,7 @@ export async function compareData(): Promise { // compare old api snapshot to the new one, check for changes // eg. new campaign, planet owner change, new event, new major order etc. + // TODO: compare major order // compare old and new campaigns for (const campaign of newData.Campaigns) { const {planetName} = campaign; @@ -174,7 +177,6 @@ export async function compareData(): Promise { for (const event of newData.Events) { const oldEvent = oldData.Events.find(e => e.eventId === event.eventId); if (!oldEvent) { - if (event.flag === 0) differences.NewMajorOrder = event; differences.NewEvents.push(event); logger.info(`New event: ${event.title}`, {type: 'info'}); newEventUpdate(event, channelIds); diff --git a/src/handlers/cron/dbData.ts b/src/handlers/cron/dbData.ts index 0d88124..94e6d2c 100644 --- a/src/handlers/cron/dbData.ts +++ b/src/handlers/cron/dbData.ts @@ -30,6 +30,8 @@ export async function dbData() { globalEvents: data.Status.globalEvents, superEarthWarResults: data.Status.superEarthWarResults, }, + Assignment: data.Assignment, + NewsFeed: data.NewsFeed, Campaigns: data.Campaigns, PlanetEvents: data.PlanetEvents, ActivePlanets: data.ActivePlanets, diff --git a/src/handlers/cron/deliverUpdates.ts b/src/handlers/cron/deliverUpdates.ts index e8a0db2..2cdff84 100644 --- a/src/handlers/cron/deliverUpdates.ts +++ b/src/handlers/cron/deliverUpdates.ts @@ -4,11 +4,13 @@ import { Faction, MergedCampaignData, GlobalEvent, + Assignment, } from '../../api-wrapper'; import {FACTION_COLOUR} from '../../commands/_components'; import {config, helldiversConfig} from '../../config'; import {planetNameTransform} from '../custom'; import {validateChannelArr} from '../discord'; +import {majorOrderEmbed} from '../embed'; const {SUBSCRIBE_FOOTER} = config; const {factionSprites, altSprites} = helldiversConfig; @@ -214,9 +216,22 @@ export async function newEventUpdate(event: GlobalEvent, channelIds: string[]) { } // TODO: use new endpoint to get this // export async function newMajorOrdeUpdater(order: ??, channels: (TextChannel | PublicThreadChannel)[]) {} +export async function newMajorOrderUpdater( + assignment: Assignment, + channelIds: string[] +) { + const channels = await validateChannelArr(channelIds); + + const embeds = [majorOrderEmbed(assignment)]; + + // send new updates to subscribed channels + const promises: Promise[] = []; + for (const channel of channels) promises.push(channel.send({embeds: embeds})); + await Promise.all(promises); + return; +} // TODO: use new endpoint to get this // export async function newMessageUpdate(message: ??, channels: (TextChannel | PublicThreadChannel)[]) {} - // LostPlanets // NewCampaigns // NewEvents diff --git a/src/handlers/embed.ts b/src/handlers/embed.ts index ec97950..4fba698 100644 --- a/src/handlers/embed.ts +++ b/src/handlers/embed.ts @@ -1,7 +1,6 @@ import { ColorResolvable, CommandInteraction, - Embed, EmbedBuilder, ModalSubmitInteraction, PublicThreadChannel, @@ -10,18 +9,21 @@ import { import {config, helldiversConfig} from '../config'; import {client, planetNameTransform} from '.'; import { + Assignment, Faction, MergedCampaignData, MergedPlanetEventData, getAllCampaigns, getAllPlayers, getCampaignByPlanetName, - getLatestEvent, + getCurrencyName, + getLatestAssignment, + getPlanetName, } from '../api-wrapper'; import {FACTION_COLOUR} from '../commands/_components'; const {SUBSCRIBE_FOOTER, FOOTER_MESSAGE, EMBED_COLOUR} = config; -const {factionSprites} = helldiversConfig; +const {factionSprites, altSprites} = helldiversConfig; export function commandErrorEmbed( interaction: CommandInteraction | ModalSubmitInteraction @@ -146,10 +148,99 @@ export function subscribeNotifEmbed(type: string): EmbedBuilder[] { return embeds; } +export function majorOrderEmbed(assignment: Assignment) { + const {expiresIn, id32, progress, setting} = assignment; + const { + type: settingsType, + overrideTitle, + overrideBrief, + taskDescription, + tasks, + reward, + } = setting; + const {type, amount} = reward; + + const expiresInUtcS = Math.floor((Date.now() + expiresIn * 1000) / 1000); + const expiresInDays = Math.floor(expiresIn / 86400); + const expiresInHours = Math.floor((expiresIn % 86400) / 3600); + + // 0 means incomplete, 1 means complete + const completed = progress.filter(value => value === 1).length; + + const campaigns = getAllCampaigns(); + + const embedTitle = overrideTitle || 'Major Order'; + const embedDescription = overrideBrief || 'No briefing provided.'; + const embedTaskDescription = + taskDescription || 'No task description provided.'; + const embedFields: {name: string; value: string; inline?: boolean}[] = []; + + embedFields.push( + { + name: 'Objective', + value: embedTaskDescription, + inline: false, + }, + { + name: 'Progress', + value: `${completed} / ${tasks.length}`, + inline: true, + }, + { + name: 'Expires In', + value: ` (${expiresInDays}d ${expiresInHours}h)`, + inline: true, + }, + { + name: 'Reward', + value: `${amount}x ${getCurrencyName(type)}`, + inline: true, + } + ); + + if (settingsType === 4) { + const tasksDisplay = tasks.map((t, i) => ({ + completed: progress[i] === 1, + planetName: getPlanetName(t.values[2]), + progress: + campaigns.find(c => c.planetName === getPlanetName(t.values[2])) + ?.planetData.liberation || 100, + })); + let taskString = ''; + for (const task of tasksDisplay) + taskString += `${task.completed ? '✅' : '❌'}: ${task.planetName}\n`; + + embedFields.push( + ...tasksDisplay.map(task => ({ + name: task.planetName, + value: + task.progress === 100 + ? '**COMPLETE**' + : `${task.progress.toFixed(2)}%`, + inline: true, + })) + ); + } + + const embed = new EmbedBuilder() + .setThumbnail(factionSprites['Humans']) + .setColor(FACTION_COLOUR.Humans) + .setAuthor({ + name: 'Super Earth Command Dispatch', + iconURL: altSprites['Humans'], + }) + .setTitle(embedTitle) + .setDescription(embedDescription) + .setFields(embedFields) + .setFooter({text: SUBSCRIBE_FOOTER}); + + return embed; +} + export function warStatusEmbeds() { const campaigns = getAllCampaigns(); const players = getAllPlayers(); - const latestEvent = getLatestEvent(); + const majorOrder = getLatestAssignment(); const status: Record = { Terminids: [], @@ -192,17 +283,7 @@ export function warStatusEmbeds() { const embeds = [automatonEmbed, terminidEmbed]; - if (latestEvent) { - const eventEmbed = new EmbedBuilder() - .setThumbnail(factionSprites['Humans']) - .setColor(FACTION_COLOUR.Humans) - .setAuthor({ - name: 'Super Earth Command Dispatch', - }); - if (latestEvent.title) eventEmbed.setTitle(latestEvent.title); - if (latestEvent.message) eventEmbed.setDescription(latestEvent.message); - embeds.push(eventEmbed); - } + if (majorOrder) embeds.push(majorOrderEmbed(majorOrder)); return embeds; } @@ -274,7 +355,7 @@ export async function campaignEmbeds(planet_name?: string) { 'Controlled By': owner, Attackers: race, Defence: `${defence}%`, - 'Time Left': `${Math.floor((expireTime - Date.now()) / 1000)}s`, + 'Time Left': `${Math.floor(expireTime - Date.now())}s`, 'Total Squad Impact': `${squadImpact.toLocaleString()} / ${maxHealth.toLocaleString()}`, }; for (const [key, val] of Object.entries(display)) {