PageSourceSearch

https://easydemonlist.pages.dev/js/content.js

js easydemonlist.pages.dev collected 2026-10-03 05:10:49 UTC 5,592 bytes, 208 lines download raw bytes

1import { round, score } from './score.js';
2
3/**
4 * Path to directory containing `_list.json` and all levels
5 */
6const dir = '/data';
7
8export async function fetchList() {
9  // Load pack definitions once
10  const packs = await fetchPacks(); // returns null or array
11  const levelToPacks = {};
12
13  if (packs) {
14    packs.forEach(pack => {
15      (pack.levels ?? []).forEach(levelId => {
16        (levelToPacks[levelId] ??= []).push({
17          name: pack.name,
18          colour: pack.colour,
19        });
20      });
21    });
22  }
23
24  const listResult = await fetch(`${dir}/_list.json`);
25  try {
26    const list = await listResult.json();
27    return await Promise.all(
28      list.map(async (path, rank) => {
29        const levelResult = await fetch(`${dir}/${path}.json`);
30        try {
31          const level = await levelResult.json();
32          return [
33            {
34              ...level,
35              path,
36              // Inject pack membership for the level (empty if none / packs failed)
37              packs: levelToPacks[path] ?? [],
38              records: (level.records ?? [])
39                .map(({ hz, ...rest }) => rest) // keep EDL behavior
40                .sort((a, b) => b.percent - a.percent),
41            },
42            null,
43          ];
44        } catch {
45          console.error(`Failed to load level #${rank + 1} ${path}.`);
46          return [null, path];
47        }
48      }),
49    );
50  } catch {
51    console.error(`Failed to load list.`);
52    return null;
53  }
54}
55
56export async function fetchEditors() {
57  try {
58    const editorsResults = await fetch(`${dir}/_editors.json`);
59    const editors = await editorsResults.json();
60    return editors;
61  } catch {
62    return null;
63  }
64}
65
66export async function fetchLeaderboard() {
67  const list = await fetchList();
68
69  if (!list) return null;
70
71  const scoreMap = {};
72  const errs = [];
73
74  list.forEach(([level, err], rank) => {
75    if (err) {
76      errs.push(err);
77      return;
78    }
79
80    // Verification
81    const verifier =
82      Object.keys(scoreMap).find((u) => u.toLowerCase() === level.verifier.toLowerCase()) ||
83      level.verifier;
84
85    scoreMap[verifier] ??= { verified: [], completed: [], progressed: [] };
86
87    scoreMap[verifier].verified.push({
88      rank: rank + 1,
89      level: level.name,
90      score: score(rank + 1, 100, level.percentToQualify),
91      link: level.verification,
92    });
93
94    // Records
95    level.records.forEach((record) => {
96      const user =
97        Object.keys(scoreMap).find((u) => u.toLowerCase() === record.user.toLowerCase()) ||
98        record.user;
99
100      scoreMap[user] ??= { verified: [], completed: [], progressed: [] };
101
102      if (record.percent === 100) {
103        scoreMap[user].completed.push({
104          rank: rank + 1,
105          level: level.name,
106          levelPath: level.path, // IMPORTANT for pack completion
107          score: score(rank + 1, 100, level.percentToQualify),
108          link: record.link,
109        });
110        return;
111      }
112
113      scoreMap[user].progressed.push({
114        rank: rank + 1,
115        level: level.name,
116        levelPath: level.path, // optional, but keep consistent
117        percent: record.percent,
118        score: score(rank + 1, record.percent, level.percentToQualify),
119        link: record.link,
120      });
121    });
122  });
123
124  // Wrap in extra Object containing the user and total score
125  const res = Object.entries(scoreMap).map(([user, scores]) => {
126    const { verified, completed, progressed } = scores;
127    const total = [verified, completed, progressed].flat().reduce((prev, cur) => prev + cur.score, 0);
128
129    return {
130      user,
131      total: round(total),
132      packs: [],
133      ...scores,
134    };
135  });
136
137  /* ================= PACK COMPLETION ================= */
138
139  const packs = await fetchPacks(); // uses _packlist.json
140
141  if (packs) {
142    res.forEach((player) => {
143      const completedIds = new Set(
144        (player.completed ?? []).map((l) => l.levelPath).filter(Boolean)
145      );
146
147      player.packs = packs.filter((pack) => {
148        const levels = pack.levels ?? [];
149        if (levels.length === 0) return false;
150        return levels.every((levelId) => completedIds.has(levelId));
151      });
152    });
153  } else {
154    res.forEach((player) => (player.packs = []));
155  }
156
157  /* =================================================== */
158
159  // Sort by total score
160  return [res.sort((a, b) => b.total - a.total), errs];
161}
162
163export async function fetchPacks() {
164  try {
165    const res = await fetch(`${dir}/_packlist.json`);
166    return await res.json(); // array of {name, levels, colour}
167  } catch {
168    return null;
169  }
170}
171
172export async function fetchPackLevels(packName) {
173  try {
174    const packs = await fetchPacks();
175    if (!packs) return null;
176
177    const pack = packs.find((p) => p.name === packName);
178    if (!pack) return null;
179
180    return await Promise.all(
181      (pack.levels ?? []).map(async (path, idx) => {
182        try {
183          const levelRes = await fetch(`${dir}/${path}.json`);
184          const level = await levelRes.json();
185
186          return [
187            {
188              level: {
189                ...level,
190                path,
191                // optional: also inject pack membership for pack-page levels
192                records: (level.records ?? [])
193                  .map(({ hz, ...rest }) => rest) // keep EDL behavior
194                  .sort((a, b) => b.percent - a.percent),
195              },
196            },
197            null,
198          ];
199        } catch {
200          console.error(`Failed to load pack level #${idx + 1}: ${path}.json`);
201          return [null, path];
202        }
203      }),
204    );
205  } catch {
206    return null;
207  }
208}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.