2024-04-04 05:47:26 +00:00
|
|
|
import "dotenv/config";
|
|
|
|
|
2024-04-04 07:16:33 +00:00
|
|
|
const AUTH_KEY = Bun.env.TBA_AUTH_KEY as string;
|
2024-04-04 06:56:41 +00:00
|
|
|
if (!AUTH_KEY) {
|
2024-04-04 07:16:33 +00:00
|
|
|
process.exit(1);
|
2024-04-04 06:56:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const authHeaders = new Headers({
|
|
|
|
"X-TBA-Auth-Key": AUTH_KEY,
|
|
|
|
});
|
|
|
|
|
2024-04-04 05:47:26 +00:00
|
|
|
const BASE_URL = "https://www.thebluealliance.com/api/v3";
|
|
|
|
|
2024-04-04 06:56:41 +00:00
|
|
|
interface CacheEntry {
|
|
|
|
etag: string;
|
|
|
|
data: string;
|
|
|
|
staleTime: number;
|
|
|
|
}
|
|
|
|
|
2024-04-04 07:16:33 +00:00
|
|
|
const CACHE_PATH = Bun.env.CACHE_PATH as string;
|
|
|
|
if (!CACHE_PATH) {
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function initializeCache() {
|
|
|
|
let data = {};
|
|
|
|
|
|
|
|
const cacheFile = Bun.file(".cache");
|
|
|
|
if (cacheFile.size > 0) {
|
|
|
|
data = JSON.parse(await cacheFile.text());
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tbaCache: { [name: string]: CacheEntry } = await initializeCache();
|
|
|
|
|
|
|
|
async function writeCache() {
|
|
|
|
Bun.write(CACHE_PATH, JSON.stringify(tbaCache));
|
|
|
|
}
|
2024-04-04 06:56:41 +00:00
|
|
|
|
|
|
|
interface TBAResponse {
|
|
|
|
data: string;
|
|
|
|
status: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateCache(
|
|
|
|
endpoint: string,
|
|
|
|
etag: string | null,
|
|
|
|
data: string,
|
|
|
|
maxAge: number,
|
|
|
|
) {
|
|
|
|
if (etag) {
|
2024-04-04 07:16:33 +00:00
|
|
|
tbaCache[endpoint] = { etag, data, staleTime: Date.now() + maxAge * 1000 };
|
2024-04-04 06:56:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function fetchCache(endpoint: string) {
|
|
|
|
return tbaCache[endpoint];
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getTBAEndpoint(endpoint: string): Promise<TBAResponse> {
|
|
|
|
const time = Date.now();
|
|
|
|
if (tbaCache[endpoint] && tbaCache[endpoint].staleTime > time) {
|
2024-04-04 08:13:55 +00:00
|
|
|
console.error("Cache hit... " + endpoint);
|
2024-04-04 06:56:41 +00:00
|
|
|
return { data: fetchCache(endpoint).data, status: 200 };
|
|
|
|
}
|
|
|
|
|
2024-04-04 08:13:55 +00:00
|
|
|
console.error("Fetching... " + endpoint);
|
2024-04-04 06:56:41 +00:00
|
|
|
const headers = new Headers(authHeaders);
|
2024-04-04 07:53:15 +00:00
|
|
|
if (tbaCache[endpoint]) {
|
|
|
|
headers.append("If-None-Match", tbaCache[endpoint].etag);
|
|
|
|
}
|
2024-04-04 06:56:41 +00:00
|
|
|
const response = await fetch(BASE_URL + endpoint, { headers });
|
|
|
|
|
|
|
|
let data;
|
|
|
|
if (response.status === 200) {
|
|
|
|
data = await response.text();
|
|
|
|
const maxAge = parseInt(
|
|
|
|
response.headers
|
|
|
|
.get("cache-control")
|
|
|
|
?.split(",")
|
|
|
|
.filter((d) => d.includes("max-age"))[0]
|
|
|
|
.trim()
|
|
|
|
.split("=")[1] as string,
|
|
|
|
);
|
|
|
|
updateCache(endpoint, response.headers.get("ETag"), data, maxAge);
|
|
|
|
} else if (response.status === 304) {
|
|
|
|
// Safety: a 304 response indicates out cache is valid based on ETag
|
|
|
|
data = fetchCache(endpoint).data as string;
|
|
|
|
|
2024-04-04 08:13:55 +00:00
|
|
|
console.error("Unchanged... " + endpoint);
|
2024-04-04 06:56:41 +00:00
|
|
|
} else {
|
2024-04-04 07:22:04 +00:00
|
|
|
data = await response.text();
|
2024-04-04 06:56:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
data,
|
|
|
|
status: response.status,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-04-04 07:22:04 +00:00
|
|
|
const response = await getTBAEndpoint(
|
|
|
|
"/team/frc4043/event/2024pncmp/matches/simple",
|
|
|
|
);
|
|
|
|
|
2024-04-04 08:13:55 +00:00
|
|
|
const data: any = JSON.parse(response.data);
|
|
|
|
|
|
|
|
const matchAlliances = data.map((match: any) => match.alliances);
|
|
|
|
|
|
|
|
function filterNerdHerd(teamKeys: string[]) {
|
|
|
|
return teamKeys.filter((team) => !team.includes("4043"));
|
|
|
|
}
|
|
|
|
|
|
|
|
const matchups = matchAlliances.map((match: any) => {
|
|
|
|
const blueAlliance = filterNerdHerd(match.blue["team_keys"]);
|
|
|
|
const redAlliance = filterNerdHerd(match.red["team_keys"]);
|
|
|
|
|
|
|
|
if (blueAlliance.length > redAlliance.length) {
|
|
|
|
return {
|
|
|
|
with: redAlliance,
|
|
|
|
against: blueAlliance,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
with: blueAlliance,
|
|
|
|
against: redAlliance,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}) as { with: string[]; against: string[] }[];
|
|
|
|
|
|
|
|
const withOccurences = matchups.reduce(
|
|
|
|
(acc, matchup) => {
|
|
|
|
for (const team of matchup.with) {
|
|
|
|
acc[team] = acc[team] + 1 || 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
},
|
|
|
|
{} as { [team: string]: number },
|
|
|
|
);
|
|
|
|
|
|
|
|
const againstOccurences = matchups.reduce(
|
|
|
|
(acc, matchup) => {
|
|
|
|
for (const team of matchup.against) {
|
|
|
|
acc[team] = acc[team] + 1 || 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
},
|
|
|
|
{} as { [team: string]: number },
|
|
|
|
);
|
|
|
|
|
|
|
|
console.log("With:");
|
|
|
|
for (const team of Object.entries(withOccurences).toSorted(
|
|
|
|
(a, b) => b[1] - a[1],
|
|
|
|
)) {
|
|
|
|
console.log(team);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("\nAgainst:");
|
|
|
|
for (const team of Object.entries(againstOccurences).toSorted(
|
|
|
|
(a, b) => b[1] - a[1],
|
|
|
|
)) {
|
|
|
|
console.log(team);
|
|
|
|
}
|
2024-04-04 06:56:41 +00:00
|
|
|
|
2024-04-04 07:16:33 +00:00
|
|
|
writeCache();
|
2024-04-04 05:47:26 +00:00
|
|
|
|