const sinceInput = document.querySelector('#since') as HTMLInputElement; const newbies = document.querySelector('textarea#newbies') as HTMLTextAreaElement; { const twoDaysAgo = new Date(); twoDaysAgo.setUTCDate(twoDaysAgo.getUTCDate() - 2); twoDaysAgo.setUTCHours(0, 0, 0, 0); sinceInput.value = twoDaysAgo.toISOString().slice(0, 10); } let planets: string[]; export function setupNewbies(setPlanets: string[]): void { planets = setPlanets; render(); } sinceInput.addEventListener('change', () => void render()); async function render(): Promise { const loader = document.querySelector('#loader') as HTMLElement; loader.style.display = 'block'; try { await _render(); } catch (e) { console.error(e); } loader.style.display = 'none'; } async function _render(): Promise { if (sinceInput.valueAsDate === null) { console.warn(sinceInput.value, 'is not a valid date'); newbies.value = ''; return; } newbies.style.display = 'none'; const joined: Map = new Map(); for (const planet of planets) { const messages: Message[] = await fetchJSON('https://api.fnar.net/chat/messages?' + new URLSearchParams({'channel_names': planet + ' Global Site Owners'}).toString()); for (const message of messages) if (message.Type == 'JOINED') { const oldJoin = joined.get(message.SenderUserName) if (oldJoin === undefined || oldJoin.timestamp > message.MessageTimestamp) joined.set(message.SenderUserName, {timestamp: message.MessageTimestamp, planet}); } } const threshold = sinceInput.valueAsDate!.getTime(); const sortedJoined = Array.from(joined.entries().filter(([_, join]) => join.timestamp >= threshold)) .sort((a, b) => a[1].timestamp - b[1].timestamp); const reputation = await getReputations(sortedJoined.map(([username, _]) => username)); const lines: string[] = []; for (const [username, join] of sortedJoined) { const dtStr = new Date(join.timestamp).toISOString().replace('T', ' ').replace('Z', ''); lines.push(`${username}\t${dtStr}\t${join.planet}\t${reputation.get(username) ?? ''}`); } newbies.value = lines.join('\n'); newbies.style.display = 'block'; } async function getReputations(usernames: string[]): Promise> { const relevantCos = new Set(); for (const username of usernames) { const response = await fetch('https://rest.fnar.net/user/' + encodeURIComponent(username)); if (response.status !== 204) { const companyCode = (await response.json()).CompanyCode; relevantCos.add(companyCode); } } const reputation = new Map(); const companyCodes = Array.from(relevantCos); for (let start = 0; start < companyCodes.length; start += 10) { const batch = companyCodes.slice(start, start + 10); const companies: Company[] = await fetchJSON('https://api.fnar.net/company/lookup?' + batch.map((companyCode) => 'company=' + encodeURIComponent(companyCode)).join('&')); for (const company of companies) if (company !== null && company.UserName !== null) reputation.set(company.UserName.toLowerCase(), company.Reputation); } return reputation; } document.querySelector('#copy')!.addEventListener('click', () => { navigator.clipboard.writeText(newbies.value); }); async function fetchJSON(url: string, options: RequestInit = {}): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const doc = await fetch(url, {...options, signal: controller.signal}).then((r) => r.json()); clearTimeout(timeoutId); return doc; } interface Message { Type: string; MessageTimestamp: number; SenderUserName: string; } interface Join { timestamp: number; planet: string; } interface Company { UserName: string | null; CompanyCode: string; Reputation: number; }