newbies.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. const sinceInput = document.querySelector('#since') as HTMLInputElement;
  2. const newbies = document.querySelector('textarea#newbies') as HTMLTextAreaElement;
  3. {
  4. const twoDaysAgo = new Date();
  5. twoDaysAgo.setUTCDate(twoDaysAgo.getUTCDate() - 2);
  6. twoDaysAgo.setUTCHours(0, 0, 0, 0);
  7. sinceInput.value = twoDaysAgo.toISOString().slice(0, 10);
  8. }
  9. let planets: string[];
  10. export function setupNewbies(setPlanets: string[]): void {
  11. planets = setPlanets;
  12. render();
  13. }
  14. sinceInput.addEventListener('change', () => void render());
  15. async function render(): Promise<void> {
  16. const loader = document.querySelector('#loader') as HTMLElement;
  17. loader.style.display = 'block';
  18. try {
  19. await _render();
  20. } catch (e) {
  21. console.error(e);
  22. }
  23. loader.style.display = 'none';
  24. }
  25. async function _render(): Promise<void> {
  26. if (sinceInput.valueAsDate === null) {
  27. console.warn(sinceInput.value, 'is not a valid date');
  28. newbies.value = '';
  29. return;
  30. }
  31. newbies.style.display = 'none';
  32. const joined: Map<string, Join> = new Map();
  33. for (const planet of planets) {
  34. const messages: Message[] = await fetchJSON('https://api.fnar.net/chat/messages?' +
  35. new URLSearchParams({'channel_names': planet + ' Global Site Owners'}).toString());
  36. for (const message of messages)
  37. if (message.Type == 'JOINED') {
  38. const oldJoin = joined.get(message.SenderUserName)
  39. if (oldJoin === undefined || oldJoin.timestamp > message.MessageTimestamp)
  40. joined.set(message.SenderUserName, {timestamp: message.MessageTimestamp, planet});
  41. }
  42. }
  43. const threshold = sinceInput.valueAsDate!.getTime();
  44. const sortedJoined = Array.from(joined.entries().filter(([_, join]) => join.timestamp >= threshold))
  45. .sort((a, b) => a[1].timestamp - b[1].timestamp);
  46. const reputation = await getReputations(sortedJoined.map(([username, _]) => username));
  47. const lines: string[] = [];
  48. for (const [username, join] of sortedJoined) {
  49. const dtStr = new Date(join.timestamp).toISOString().replace('T', ' ').replace('Z', '');
  50. lines.push(`${username}\t${dtStr}\t${join.planet}\t${reputation.get(username) ?? ''}`);
  51. }
  52. newbies.value = lines.join('\n');
  53. newbies.style.display = 'block';
  54. }
  55. async function getReputations(usernames: string[]): Promise<Map<string, number>> {
  56. const relevantCos = new Set<string>();
  57. for (const username of usernames) {
  58. const response = await fetch('https://rest.fnar.net/user/' + encodeURIComponent(username));
  59. if (response.status !== 204) {
  60. const companyCode = (await response.json()).CompanyCode;
  61. relevantCos.add(companyCode);
  62. }
  63. }
  64. const reputation = new Map<string, number>();
  65. const companyCodes = Array.from(relevantCos);
  66. for (let start = 0; start < companyCodes.length; start += 10) {
  67. const batch = companyCodes.slice(start, start + 10);
  68. const companies: Company[] = await fetchJSON('https://api.fnar.net/company/lookup?' +
  69. batch.map((companyCode) => 'company=' + encodeURIComponent(companyCode)).join('&'));
  70. for (const company of companies)
  71. if (company !== null && company.UserName !== null)
  72. reputation.set(company.UserName.toLowerCase(), company.Reputation);
  73. }
  74. return reputation;
  75. }
  76. document.querySelector('#copy')!.addEventListener('click', () => {
  77. navigator.clipboard.writeText(newbies.value);
  78. });
  79. async function fetchJSON(url: string, options: RequestInit = {}): Promise<any> {
  80. const controller = new AbortController();
  81. const timeoutId = setTimeout(() => controller.abort(), 5000);
  82. const doc = await fetch(url, {...options, signal: controller.signal}).then((r) => r.json());
  83. clearTimeout(timeoutId);
  84. return doc;
  85. }
  86. interface Message {
  87. Type: string;
  88. MessageTimestamp: number;
  89. SenderUserName: string;
  90. }
  91. interface Join {
  92. timestamp: number;
  93. planet: string;
  94. }
  95. interface Company {
  96. UserName: string | null;
  97. CompanyCode: string;
  98. Reputation: number;
  99. }