Skip to content

Commit 5a6a3b1

Browse files
authored
Initial typescript adoption (#43)
1 parent 0fa80c6 commit 5a6a3b1

File tree

11 files changed

+2551
-314
lines changed

11 files changed

+2551
-314
lines changed

.eslintrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"plugins": ["@typescript-eslint"],
3+
"rules": {
4+
"@typescript-eslint/no-deprecated": "warn"
5+
},
6+
"parser": "@typescript-eslint/parser",
7+
"parserOptions": {
8+
"project": "./tsconfig.json"
9+
}
10+
}

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
*.pluto linguist-language=Lua
2+
3+
typestripped/ linguist-generated

.github/workflows/check.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Check
2+
3+
on:
4+
pull_request:
5+
push:
6+
7+
jobs:
8+
check:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-node@v4
13+
with:
14+
node-version: 20
15+
cache: npm
16+
- run: npm ci
17+
- run: npm exec tsc
18+
- name: Fail if there are uncommitted changes
19+
run: |
20+
if [[ -n "$(git status --porcelain)" ]]; then
21+
echo "Uncommitted changes detected:"
22+
git status
23+
git --no-pager diff
24+
exit 1
25+
fi

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
node_modules/
12
dynstat.php
23

34
# Symlinks

arbys.php

Lines changed: 17 additions & 314 deletions
Large diffs are not rendered by default.

arbys.ts

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
import type { IFaction, IRegion, TFaction } from "warframe-public-export-plus";
2+
3+
// common.js
4+
declare let onLanguageUpdate: () => void;
5+
declare function getDictPromise(): Promise<Record<string, string>>;
6+
declare function toTitleCase(str: string): string;
7+
8+
// arbyTiers.js
9+
declare const arbyTiers: Record<string, string>;
10+
11+
// fetch
12+
declare let dict: Record<string, string>;
13+
declare let ExportFactions: Record<TFaction, IFaction>;
14+
declare let ExportRegions: Record<string, IRegion>;
15+
declare let arbys: [number, string][];
16+
17+
// state
18+
declare let currentHour: number;
19+
20+
const days = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ];
21+
const months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];
22+
23+
function loc(key)
24+
{
25+
return dict[key] ?? key;
26+
}
27+
28+
function totwo(num)
29+
{
30+
if (num < 10)
31+
{
32+
return "0" + num;
33+
}
34+
return num;
35+
}
36+
37+
function formattz(offset)
38+
{
39+
if (offset == 0)
40+
{
41+
return "UTC+0";
42+
}
43+
offset /= 60;
44+
if (offset < 0)
45+
{
46+
return "UTC+" + (offset * -1);
47+
}
48+
return "UTC-" + offset;
49+
}
50+
document.getElementById("local-time-option").textContent += " (" + formattz(new Date().getTimezoneOffset()) + ")";
51+
52+
function formathour(hour)
53+
{
54+
switch ((document.getElementById("select-hourfmt") as HTMLSelectElement).value)
55+
{
56+
case "mil": default: // This is the default because it indicates when zulu time is used, making it easier to parse screenshots of the schedule.
57+
return totwo(hour) + "00" + ((document.getElementById("select-tz") as HTMLSelectElement).value == "zulu" ? "Z" : "");
58+
59+
case "24":
60+
return totwo(hour) + ":00";
61+
62+
case "12":
63+
return ((hour % 12) == 0 ? "12" : (hour % 12)) + (hour >= 12 ? "pm" : "am");
64+
}
65+
}
66+
67+
const params = new URLSearchParams(location.hash.replace("#", ""));
68+
if (params.has("days"))
69+
{
70+
(document.getElementById("select-days") as HTMLSelectElement).value = params.get("days");
71+
}
72+
else if ("userAgentData" in navigator && (navigator.userAgentData as { mobile?: boolean }).mobile)
73+
{
74+
(document.getElementById("select-days") as HTMLSelectElement).value = "1";
75+
}
76+
if (params.has("tz"))
77+
{
78+
(document.getElementById("select-tz") as HTMLSelectElement).value = params.get("tz");
79+
}
80+
if (params.has("hourfmt"))
81+
{
82+
(document.getElementById("select-hourfmt") as HTMLSelectElement).value = params.get("hourfmt");
83+
}
84+
if (params.has("exclude"))
85+
{
86+
params.get("exclude").split(".").forEach(opt =>
87+
{
88+
const checkbox = document.getElementById("filter-" + opt) as HTMLInputElement | null;
89+
if (checkbox)
90+
{
91+
checkbox.checked = false;
92+
}
93+
});
94+
}
95+
96+
Promise.all([
97+
getDictPromise(),
98+
fetch("https://browse.wf/warframe-public-export-plus/ExportFactions.json").then(res => res.json()),
99+
fetch("https://browse.wf/warframe-public-export-plus/ExportRegions.json").then(res => res.json()),
100+
fetch("https://browse.wf/arbys.txt").then(res => res.text())
101+
]).then(([ dict, ExportFactions, ExportRegions, arbys ]) => {
102+
(window as any).dict = dict;
103+
(window as any).ExportFactions = ExportFactions;
104+
(window as any).ExportRegions = ExportRegions;
105+
(window as any).arbys = arbys.split("\n").map(line => line.split(",")).filter(arr => arr.length == 2).map(arr => [ parseInt(arr[0]), arr[1] ]);
106+
onLanguageUpdate = function()
107+
{
108+
updateLog();
109+
updateFilterNamesForLocale();
110+
};
111+
onLanguageUpdate();
112+
});
113+
114+
function updateFilterNamesForLocale()
115+
{
116+
document.querySelector("label[for=filter-MT_SURVIVAL]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Survival"]);
117+
document.querySelector("label[for=filter-MT_DEFENSE]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Defense"]);
118+
document.querySelector("label[for=filter-MT_TERRITORY]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Territory"]);
119+
document.querySelector("label[for=filter-MT_EXCAVATE]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Excavation"]);
120+
document.querySelector("label[for=filter-MT_PURIFY]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Purify"]);
121+
document.querySelector("label[for=filter-MT_EVACUATION]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Evacuation"]);
122+
document.querySelector("label[for=filter-MT_ARTIFACT]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Artifact"]);
123+
document.querySelector("label[for=filter-MT_CORRUPTION]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Corruption"]);
124+
document.querySelector("label[for=filter-MT_VOID_CASCADE]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_VoidCascade"]);
125+
document.querySelector("label[for=filter-MT_ARMAGEDDON]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Armageddon"]);
126+
document.querySelector("label[for=filter-MT_ALCHEMY]").textContent = toTitleCase(dict["/Lotus/Language/Missions/MissionName_Alchemy"]);
127+
128+
document.querySelector("label[for=filter-FC_GRINEER]").textContent = dict["/Lotus/Language/Game/Faction_GrineerUC"];
129+
document.querySelector("label[for=filter-FC_CORPUS]").textContent = dict["/Lotus/Language/Game/Faction_CorpusUC"];
130+
document.querySelector("label[for=filter-FC_INFESTATION]").textContent = dict["/Lotus/Language/Game/Faction_InfestationUC"];
131+
document.querySelector("label[for=filter-FC_OROKIN]").textContent = dict["/Lotus/Language/Game/Faction_OrokinUC"];
132+
document.querySelector("label[for=filter-FC_MITW]").textContent = dict["/Lotus/Language/Game/Faction_MITW"];
133+
}
134+
135+
function updateLog()
136+
{
137+
console.time("updateLog");
138+
139+
const zulu = ((document.getElementById("select-tz") as HTMLSelectElement).value == "zulu");
140+
141+
currentHour = Math.trunc(Date.now() / 3600000) * 3600;
142+
143+
const epochHour = arbys[0][0];
144+
const currentHourIndex = (currentHour - epochHour) / 3600;
145+
146+
// Update log
147+
const currentYear = zulu ? new Date().getUTCFullYear() : new Date().getFullYear();
148+
let remainingArbys = parseInt((document.getElementById("select-days") as HTMLSelectElement).value) * 24;
149+
let lastArbyDay = -1;
150+
document.getElementById("log").innerHTML = "";
151+
for (let i = currentHourIndex; i != arbys.length && remainingArbys-- > 0; ++i)
152+
{
153+
const arr = arbys[i];
154+
155+
const thisArbyGrade = (arbyTiers[arr[1]] ?? "F");
156+
if (!(document.getElementById("filter-tier-" + thisArbyGrade) as HTMLInputElement).checked)
157+
{
158+
continue;
159+
}
160+
161+
const node = ExportRegions[arr[1]];
162+
if (!(document.getElementById("filter-" + node.missionType) as HTMLInputElement).checked
163+
|| !(document.getElementById("filter-" + node.faction) as HTMLInputElement).checked
164+
)
165+
{
166+
continue;
167+
}
168+
169+
const date = new Date(arr[0] * 1000);
170+
const thisArbyHour = zulu ? date.getUTCHours() : date.getHours();
171+
const thisArbyDay = zulu ? date.getUTCDate() : date.getDate();
172+
173+
if (thisArbyDay != lastArbyDay)
174+
{
175+
lastArbyDay = thisArbyDay;
176+
const thisArbyWeekDay = zulu ? date.getUTCDay() : date.getDay();
177+
const thisArbyMonth = zulu ? date.getUTCMonth() : date.getMonth();
178+
const thisArbyYear = zulu ? date.getUTCFullYear() : date.getFullYear();
179+
let h3 = document.createElement("h3");
180+
h3.textContent = days[thisArbyWeekDay] + ", " + months[thisArbyMonth] + " " + thisArbyDay;
181+
if (thisArbyYear != currentYear)
182+
{
183+
h3.textContent += ", " + thisArbyYear;
184+
}
185+
document.getElementById("log").appendChild(h3);
186+
}
187+
188+
let span = document.createElement(arr[0] == currentHour ? "b" : "span");
189+
span.setAttribute("data-timestamp", arr[0].toString());
190+
span.textContent = formathour(thisArbyHour) + " • " + toTitleCase(loc(node.missionName)) + " - " + dict[ExportFactions[node.faction].name] + " @ " + loc(node.name) + ", " + loc(node.systemName) + " (" + thisArbyGrade + " tier";
191+
if ("darkSectorData" in node)
192+
{
193+
span.textContent += ", " + (node.darkSectorData.resourceBonus * 100).toFixed(0) + "% resource bonus";
194+
}
195+
span.textContent += ")";
196+
document.getElementById("log").appendChild(span);
197+
}
198+
if (document.getElementById("log").children.length == 0)
199+
{
200+
let span = document.createElement("span");
201+
span.textContent = "I've looked through " + (arbys.length - currentHourIndex) + " arbitrations but not a one matches your filters. :/";
202+
document.getElementById("log").appendChild(span);
203+
}
204+
205+
// Update table
206+
document.querySelectorAll("table tbody tr").forEach(tr => {
207+
tr.setAttribute("data-starved", "true");
208+
tr.children[1].innerHTML = "N/A";
209+
tr.children[2].innerHTML = "N/A";
210+
});
211+
for (let i = currentHourIndex; i != arbys.length && document.querySelector("[data-starved]"); ++i)
212+
{
213+
const arr = arbys[i];
214+
215+
const date = new Date(arr[0] * 1000);
216+
const node = ExportRegions[arr[1]];
217+
218+
const thisArbyHour = zulu ? date.getUTCHours() : date.getHours();
219+
const thisArbyDay = zulu ? date.getUTCDate() : date.getDate();
220+
const thisArbyWeekDay = zulu ? date.getUTCDay() : date.getDay();
221+
const thisArbyMonth = zulu ? date.getUTCMonth() : date.getMonth();
222+
223+
const thisArbyGrade = (arbyTiers[arr[1]] ?? "F");
224+
225+
{
226+
const tr = document.getElementById("next-tier-" + thisArbyGrade);
227+
if (tr.children[1].innerHTML == "N/A")
228+
{
229+
tr.removeAttribute("data-starved");
230+
tr.children[1].setAttribute("data-timestamp", arr[0].toString());
231+
tr.children[1].textContent = days[thisArbyWeekDay] + ", " + months[thisArbyMonth] + " " + thisArbyDay + ", " + formathour(thisArbyHour);
232+
tr.children[2].textContent = toTitleCase(loc(node.missionName)) + " - " + dict[ExportFactions[node.faction].name] + " @ " + loc(node.name) + ", " + loc(node.systemName);
233+
if ("darkSectorData" in node)
234+
{
235+
tr.children[2].textContent += " (" + (node.darkSectorData.resourceBonus * 100).toFixed(0) + "% resource bonus)";
236+
}
237+
}
238+
}
239+
{
240+
const tr = document.getElementById("next-" + node.missionType);
241+
if (tr.children[1].innerHTML == "N/A")
242+
{
243+
tr.removeAttribute("data-starved");
244+
tr.children[1].setAttribute("data-timestamp", arr[0].toString());
245+
tr.children[1].textContent = days[thisArbyWeekDay] + ", " + months[thisArbyMonth] + " " + thisArbyDay + ", " + formathour(thisArbyHour);
246+
tr.children[2].textContent = dict[ExportFactions[node.faction].name] + " @ " + loc(node.name) + ", " + loc(node.systemName);
247+
if ("darkSectorData" in node)
248+
{
249+
tr.children[2].textContent += " (" + (node.darkSectorData.resourceBonus * 100).toFixed(0) + "% resource bonus)";
250+
}
251+
}
252+
}
253+
{
254+
const tr = document.getElementById("next-" + node.faction);
255+
if (tr.children[1].innerHTML == "N/A")
256+
{
257+
tr.removeAttribute("data-starved");
258+
tr.children[1].setAttribute("data-timestamp", arr[0].toString());
259+
tr.children[1].textContent = days[thisArbyWeekDay] + ", " + months[thisArbyMonth] + " " + thisArbyDay + ", " + formathour(thisArbyHour);
260+
tr.children[2].textContent = toTitleCase(loc(node.missionName)) + " - " + loc(node.name) + ", " + loc(node.systemName);
261+
if ("darkSectorData" in node)
262+
{
263+
tr.children[2].textContent += " (" + (node.darkSectorData.resourceBonus * 100).toFixed(0) + "% resource bonus)";
264+
}
265+
}
266+
}
267+
}
268+
269+
// Ensure data stays up-to-date
270+
if (!("updater" in window))
271+
{
272+
(window as any).updater = setInterval(function()
273+
{
274+
if (currentHour != (Math.trunc((Date.now() / 1000) / 3600) * 3600))
275+
{
276+
updateLog();
277+
}
278+
}, 1000);
279+
}
280+
281+
console.timeEnd("updateLog");
282+
}
283+
284+
function saveSettings()
285+
{
286+
let hash = "days=" + encodeURIComponent((document.getElementById("select-days") as HTMLSelectElement).value)
287+
+ "&tz=" + encodeURIComponent((document.getElementById("select-tz") as HTMLSelectElement).value)
288+
+ "&hourfmt=" + encodeURIComponent((document.getElementById("select-hourfmt") as HTMLSelectElement).value)
289+
;
290+
291+
const filtered_away = [];
292+
document.querySelectorAll<HTMLInputElement>("input[type=checkbox]").forEach(elm =>
293+
{
294+
if (!elm.checked)
295+
{
296+
filtered_away.push(elm.id.substring(7)); // "filter-"
297+
}
298+
});
299+
if (filtered_away.length != 0)
300+
{
301+
hash += "&exclude=" + encodeURIComponent(filtered_away.join("."));
302+
}
303+
304+
location.hash = hash;
305+
}
306+
307+
document.querySelectorAll<HTMLSelectElement | HTMLInputElement>("select, input[type=checkbox]").forEach(elm =>
308+
{
309+
elm.onchange = function()
310+
{
311+
if ("arbys" in window)
312+
{
313+
updateLog();
314+
}
315+
saveSettings();
316+
};
317+
});

0 commit comments

Comments
 (0)