@mrtimeey/everybodycodes-data 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tim Kruse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # @mrtimeey/everybodycodes-data
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@mrtimeey/everybodycodes-data?color=brightgreen&label=npm)](https://www.npmjs.com/package/@mrtimeey/everybodycodes-data)
4
+ [![npm provenance](https://provenancebadge.vercel.app/npm/@mrtimeey/everybodycodes-data)](https://provenancebadge.vercel.app)
5
+ [![build](https://github.com/mrtimeey/everybodycodes-data/actions/workflows/ci.yml/badge.svg)](https://github.com/mrtimeey/everybodycodes-data/actions/workflows/ci.yml)
6
+ [![release](https://github.com/mrtimeey/everybodycodes-data/actions/workflows/release.yml/badge.svg)](https://github.com/mrtimeey/everybodycodes-data/actions/workflows/release.yml)
7
+ [![license](https://img.shields.io/github/license/mrtimeey/everybodycodes-data?color=blue)](LICENSE)
8
+
9
+ > 🧩 A lightweight Node.js client for fetching and decrypting **Everybody Codes** inputs automatically.
10
+
11
+ ---
12
+
13
+ ## ✨ Features
14
+
15
+ - Fetch and decrypt puzzle input data from [everybody.codes](https://everybody.codes/)
16
+ - Fully typed API (TypeScript)
17
+ - Built-in test mocks via `undici.MockAgent`
18
+ - Automatically validated examples and README code blocks in CI
19
+ - API-stable and documented via [API Extractor](https://api-extractor.com/)
20
+
21
+ ---
22
+
23
+ ## 🚀 Installation
24
+
25
+ ```bash
26
+ npm install @mrtimeey/everybodycodes-data
27
+ # or
28
+ pnpm add @mrtimeey/everybodycodes-data
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 📘 Usage
34
+
35
+ ```ts
36
+ import { EverybodyCodesClient } from "@mrtimeey/everybodycodes-data";
37
+
38
+ const client = new EverybodyCodesClient("your-everybody-codes-session-cookie");
39
+
40
+ // Fetch and decrypt full quest data
41
+ const data = await client.getEventData("2025", 1);
42
+ console.log(data); // { 1: "input text part 1", 2: "...", 3: "..." }
43
+
44
+ // Fetch only a single part
45
+ const part1 = await client.getEventPartData("2025", 1, 1);
46
+ console.log(part1);
47
+ ```
48
+
49
+ > 💡 Your `session-cookie` must match the `everybody-codes` cookie from your logged-in browser.
50
+
51
+ ---
52
+
53
+ ## 🧩 API Reference
54
+
55
+ The public API surface is automatically tracked by [API Extractor](https://api-extractor.com/).
56
+ See [`etc/everybodycodes-data.api.md`](./etc/everybodycodes-data.api.md) for the latest exported types.
57
+
58
+ The public API is also documented through [Typedoc on GH-Pages](https://mrtimeey.github.io/everybodycodes-data/).
59
+
60
+ ---
61
+
62
+ ## 🧱 Development
63
+
64
+ ### Prerequisites
65
+
66
+ - Node.js ≥ 18.17
67
+ - npm ≥ 9 or pnpm ≥ 8
68
+
69
+ ### Setup
70
+
71
+ ```bash
72
+ npm ci
73
+ ```
74
+
75
+ ### Run all checks
76
+
77
+ ```bash
78
+ npm test
79
+ npm run build
80
+ ```
81
+
82
+ ### Lint & Format
83
+
84
+ ```bash
85
+ npm run lint
86
+ npm run format
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 🔄 Release Workflow
92
+
93
+ This repository uses [release-please](https://github.com/google-github-actions/release-please-action):
94
+
95
+ 1. Conventional commits (`fix:`, `feat:`) update changelog and version bump via PR.
96
+ 2. Merge the release PR → Git tag is created.
97
+ 3. GitHub Actions publish the package to npm with provenance.
98
+
99
+ ---
100
+
101
+ ## 🛡️ Security
102
+
103
+ See [SECURITY.md](./SECURITY.md) for responsible disclosure guidelines.
104
+
105
+ ---
106
+
107
+ ## 🤝 Contributing
108
+
109
+ Contributions, issues, and pull requests are welcome!
110
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
111
+
112
+ ---
113
+
114
+ ## 📄 License
115
+
116
+ MIT © [Tim Kruse](https://github.com/mrtimeey)
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Client for request against the EverybodyCodes API.
3
+ *
4
+ * @public
5
+ */
6
+ export declare class EverybodyCodesClient {
7
+ private readonly cookie;
8
+ private seed?;
9
+ /**
10
+ * Creates a new EverybodyCodesClient.
11
+ * @param sessionCookie - Value of the `everybody-codes` cookie.
12
+ */
13
+ constructor(sessionCookie: string);
14
+ private makeUrl;
15
+ private cookieHeader;
16
+ private getSeed;
17
+ private getEncryptedInputs;
18
+ private getDataInternal;
19
+ private getKeys;
20
+ /**
21
+ * Fetches data for a specific event and quest.
22
+ * @param event - Event ID
23
+ * @param quest - Quest ID
24
+ * @returns Data for the event and quest
25
+ */
26
+ getEventData(event: string | number, quest: string | number): Promise<Partial<Record<2 | 1 | 3, string>>>;
27
+ /**
28
+ * Fetches data for a specific story and quest.
29
+ * @param story - Story ID
30
+ * @param quest - Quest ID
31
+ * @returns Data for the story and quest
32
+ */
33
+ getStoryData(story: string | number, quest: string | number): Promise<Partial<Record<2 | 1 | 3, string>>>;
34
+ /**
35
+ * Fetches data for a specific event, quest, and part.
36
+ * @param event - Event ID
37
+ * @param quest - Quest ID
38
+ * @param part - Part number (1, 2, or 3)
39
+ * @returns Data for the event, quest, and part
40
+ */
41
+ getEventPartData(event: string | number, quest: string | number, part: PartNumber): Promise<string | undefined>;
42
+ /**
43
+ * Fetches data for a specific story, quest, and part.
44
+ * @param story - Story ID
45
+ * @param quest - Quest ID
46
+ * @param part - Part number (1, 2, or 3)
47
+ * @returns Data for the story, quest, and part
48
+ */
49
+ getStoryPartData(story: string | number, quest: string | number, part: PartNumber): Promise<string | undefined>;
50
+ }
51
+
52
+ /**
53
+ * Part number for quests.
54
+ * @public
55
+ */
56
+ export declare type PartNumber = 1 | 2 | 3;
57
+
58
+ export { }
package/dist/index.cjs ADDED
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
8
+
9
+ // src/helper/http.ts
10
+ async function http(url, init) {
11
+ const res = await fetch(url, {
12
+ ...init,
13
+ headers: {
14
+ accept: "application/json",
15
+ "user-agent": "everybodycodes-data/0.1 (+github.com/you/repo)",
16
+ ...init?.headers ?? {}
17
+ }
18
+ });
19
+ if (!res.ok) {
20
+ const body = await res.text().catch(() => "");
21
+ throw new Error(
22
+ `HTTP ${res.status} ${res.statusText} :: ${url} :: ${body}`
23
+ );
24
+ }
25
+ const ct = res.headers.get("content-type") || "";
26
+ if (ct.includes("application/json")) {
27
+ return await res.json();
28
+ }
29
+ return await res.text();
30
+ }
31
+ function aesDecryptHexWithKey(key, hexCipher) {
32
+ const keyBytes = Buffer.from(key, "utf8");
33
+ const iv = Buffer.from(key.substring(0, 16), "utf8");
34
+ const cipherBytes = Buffer.from(hexCipher, "hex");
35
+ const algo = keyBytes.length === 16 ? "aes-128-cbc" : keyBytes.length === 24 ? "aes-192-cbc" : keyBytes.length === 32 ? "aes-256-cbc" : "aes-256-cbc";
36
+ const dec = crypto__default.default.createDecipheriv(algo, keyBytes, iv);
37
+ const out = Buffer.concat([dec.update(cipherBytes), dec.final()]);
38
+ return out.toString("utf8");
39
+ }
40
+
41
+ // src/client.ts
42
+ var API_BASE = "https://everybody.codes/api";
43
+ var CDN_BASE = "https://everybody-codes.b-cdn.net/assets";
44
+ var EverybodyCodesClient = class {
45
+ cookie;
46
+ seed;
47
+ /**
48
+ * Creates a new EverybodyCodesClient.
49
+ * @param sessionCookie - Value of the `everybody-codes` cookie.
50
+ */
51
+ constructor(sessionCookie) {
52
+ if (!sessionCookie || !sessionCookie.trim()) {
53
+ throw new Error("EverybodyCodesClient: sessionCookie is required.");
54
+ }
55
+ this.cookie = sessionCookie.trim();
56
+ }
57
+ makeUrl(type, ...parts) {
58
+ return [
59
+ API_BASE,
60
+ type,
61
+ ...parts.map((p) => encodeURIComponent(String(p)))
62
+ ].join("/");
63
+ }
64
+ cookieHeader() {
65
+ return { Cookie: `everybody-codes=${this.cookie}` };
66
+ }
67
+ async getSeed() {
68
+ if (this.seed) return this.seed;
69
+ const url = `${API_BASE}/user/me`;
70
+ const data = await http(url, {
71
+ headers: this.cookieHeader()
72
+ });
73
+ const raw = data?.seed;
74
+ const num = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() !== "" ? Number(raw) : NaN;
75
+ if (!Number.isFinite(num) || num <= 0) {
76
+ throw new Error(
77
+ "Invalid seed value (0). This usually means your session has expired or the 'everybody-codes' cookie is invalid. Log in again at https://everybody.codes, copy the cookie value exactly (no spaces or quotes), and try again."
78
+ );
79
+ }
80
+ this.seed = String(num);
81
+ return this.seed;
82
+ }
83
+ async getEncryptedInputs(event, quest, seed) {
84
+ const url = `${CDN_BASE}/${encodeURIComponent(String(event))}/${encodeURIComponent(
85
+ String(quest)
86
+ )}/input/${encodeURIComponent(seed)}.json`;
87
+ return await http(url, {
88
+ headers: this.cookieHeader()
89
+ });
90
+ }
91
+ async getDataInternal(type, event, quest) {
92
+ const seed = await this.getSeed();
93
+ const encrypted = await this.getEncryptedInputs(event, quest, seed);
94
+ const keys = await this.getKeys(type, event, quest);
95
+ const out = {};
96
+ ["1", "2", "3"].forEach((p) => {
97
+ const hex = encrypted[p];
98
+ const key = keys[p];
99
+ if (hex && key)
100
+ out[Number(p)] = aesDecryptHexWithKey(key, hex);
101
+ });
102
+ return out;
103
+ }
104
+ async getKeys(type, event, quest) {
105
+ const url = this.makeUrl(type, event, "quest", quest);
106
+ const data = await http(url, { headers: this.cookieHeader() });
107
+ const out = {};
108
+ for (const p of ["1", "2", "3"]) {
109
+ const flat = data?.[`key${p}`];
110
+ if (flat) out[p] = String(flat);
111
+ }
112
+ return out;
113
+ }
114
+ /**
115
+ * Fetches data for a specific event and quest.
116
+ * @param event - Event ID
117
+ * @param quest - Quest ID
118
+ * @returns Data for the event and quest
119
+ */
120
+ async getEventData(event, quest) {
121
+ return this.getDataInternal("event" /* Event */, event, quest);
122
+ }
123
+ /**
124
+ * Fetches data for a specific story and quest.
125
+ * @param story - Story ID
126
+ * @param quest - Quest ID
127
+ * @returns Data for the story and quest
128
+ */
129
+ async getStoryData(story, quest) {
130
+ return this.getDataInternal("story" /* Story */, story, quest);
131
+ }
132
+ /**
133
+ * Fetches data for a specific event, quest, and part.
134
+ * @param event - Event ID
135
+ * @param quest - Quest ID
136
+ * @param part - Part number (1, 2, or 3)
137
+ * @returns Data for the event, quest, and part
138
+ */
139
+ async getEventPartData(event, quest, part) {
140
+ const data = await this.getDataInternal("event" /* Event */, event, quest);
141
+ return data[part];
142
+ }
143
+ /**
144
+ * Fetches data for a specific story, quest, and part.
145
+ * @param story - Story ID
146
+ * @param quest - Quest ID
147
+ * @param part - Part number (1, 2, or 3)
148
+ * @returns Data for the story, quest, and part
149
+ */
150
+ async getStoryPartData(story, quest, part) {
151
+ const data = await this.getDataInternal("story" /* Story */, story, quest);
152
+ return data[part];
153
+ }
154
+ };
155
+
156
+ exports.EverybodyCodesClient = EverybodyCodesClient;
157
+ //# sourceMappingURL=index.cjs.map
158
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helper/http.ts","../src/crypto.ts","../src/client.ts"],"names":["crypto"],"mappings":"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","file":"index.cjs","sourcesContent":["export async function http<T = unknown>(\n url: string,\n init?: RequestInit,\n): Promise<T> {\n const res = await fetch(url, {\n ...init,\n headers: {\n accept: \"application/json\",\n \"user-agent\": \"everybodycodes-data/0.1 (+github.com/you/repo)\",\n ...(init?.headers ?? {}),\n },\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(\n `HTTP ${res.status} ${res.statusText} :: ${url} :: ${body}`,\n );\n }\n const ct = res.headers.get(\"content-type\") || \"\";\n if (ct.includes(\"application/json\")) {\n return (await res.json()) as T;\n }\n return (await res.text()) as unknown as T;\n}\n","import crypto from \"node:crypto\";\n\nexport function aesDecryptHexWithKey(key: string, hexCipher: string): string {\n const keyBytes = Buffer.from(key, \"utf8\");\n const iv = Buffer.from(key.substring(0, 16), \"utf8\");\n const cipherBytes = Buffer.from(hexCipher, \"hex\");\n\n const algo =\n keyBytes.length === 16\n ? \"aes-128-cbc\"\n : keyBytes.length === 24\n ? \"aes-192-cbc\"\n : keyBytes.length === 32\n ? \"aes-256-cbc\"\n : \"aes-256-cbc\";\n\n const dec = crypto.createDecipheriv(algo, keyBytes, iv);\n const out = Buffer.concat([dec.update(cipherBytes), dec.final()]);\n return out.toString(\"utf8\");\n}\n","import { http } from \"./helper/http\";\nimport { aesDecryptHexWithKey } from \"./crypto.js\";\n\nconst API_BASE = \"https://everybody.codes/api\";\nconst CDN_BASE = \"https://everybody-codes.b-cdn.net/assets\";\n\nenum ChallengeType {\n Event = \"event\",\n Story = \"story\",\n}\n\n/**\n * Part number for quests.\n * @public\n */\nexport type PartNumber = 1 | 2 | 3;\n\n/**\n * Client for request against the EverybodyCodes API.\n *\n * @public\n */\nexport class EverybodyCodesClient {\n private readonly cookie: string;\n private seed?: string;\n\n /**\n * Creates a new EverybodyCodesClient.\n * @param sessionCookie - Value of the `everybody-codes` cookie.\n */\n constructor(sessionCookie: string) {\n if (!sessionCookie || !sessionCookie.trim()) {\n throw new Error(\"EverybodyCodesClient: sessionCookie is required.\");\n }\n this.cookie = sessionCookie.trim();\n }\n\n private makeUrl(type: ChallengeType, ...parts: (string | number)[]): string {\n return [\n API_BASE,\n type,\n ...parts.map((p) => encodeURIComponent(String(p))),\n ].join(\"/\");\n }\n\n private cookieHeader(): HeadersInit {\n return { Cookie: `everybody-codes=${this.cookie}` };\n }\n\n private async getSeed(): Promise<string> {\n if (this.seed) return this.seed;\n\n const url = `${API_BASE}/user/me`;\n const data = await http<{ seed?: number | string }>(url, {\n headers: this.cookieHeader(),\n });\n\n const raw = data?.seed;\n const num =\n typeof raw === \"number\"\n ? raw\n : typeof raw === \"string\" && raw.trim() !== \"\"\n ? Number(raw)\n : NaN;\n\n if (!Number.isFinite(num) || num <= 0) {\n throw new Error(\n \"Invalid seed value (0). This usually means your session has expired or the 'everybody-codes' cookie is invalid. \" +\n \"Log in again at https://everybody.codes, copy the cookie value exactly (no spaces or quotes), and try again.\",\n );\n }\n\n this.seed = String(num);\n return this.seed;\n }\n\n private async getEncryptedInputs(\n event: string | number,\n quest: string | number,\n seed: string,\n ) {\n const url = `${CDN_BASE}/${encodeURIComponent(String(event))}/${encodeURIComponent(\n String(quest),\n )}/input/${encodeURIComponent(seed)}.json`;\n return await http<Record<\"1\" | \"2\" | \"3\", string>>(url, {\n headers: this.cookieHeader(),\n });\n }\n\n private async getDataInternal(\n type: ChallengeType,\n event: string | number,\n quest: string | number,\n ) {\n const seed = await this.getSeed();\n const encrypted = await this.getEncryptedInputs(event, quest, seed);\n const keys = await this.getKeys(type, event, quest);\n\n const out: Partial<Record<1 | 2 | 3, string>> = {};\n ([\"1\", \"2\", \"3\"] as const).forEach((p) => {\n const hex = encrypted[p];\n const key = keys[p];\n if (hex && key)\n out[Number(p) as 1 | 2 | 3] = aesDecryptHexWithKey(key, hex);\n });\n return out;\n }\n\n private async getKeys(\n type: ChallengeType,\n event: string | number,\n quest: string | number,\n ) {\n const url = this.makeUrl(type, event, \"quest\", quest);\n\n type KeyResponse = {\n key1?: string;\n key2?: string;\n key3?: string;\n };\n\n const data = await http<KeyResponse>(url, { headers: this.cookieHeader() });\n\n const out: Partial<Record<\"1\" | \"2\" | \"3\", string>> = {};\n for (const p of [\"1\", \"2\", \"3\"] as const) {\n const flat = data?.[`key${p}`];\n if (flat) out[p] = String(flat);\n }\n return out;\n }\n\n /**\n * Fetches data for a specific event and quest.\n * @param event - Event ID\n * @param quest - Quest ID\n * @returns Data for the event and quest\n */\n async getEventData(event: string | number, quest: string | number) {\n return this.getDataInternal(ChallengeType.Event, event, quest);\n }\n\n /**\n * Fetches data for a specific story and quest.\n * @param story - Story ID\n * @param quest - Quest ID\n * @returns Data for the story and quest\n */\n async getStoryData(story: string | number, quest: string | number) {\n return this.getDataInternal(ChallengeType.Story, story, quest);\n }\n\n /**\n * Fetches data for a specific event, quest, and part.\n * @param event - Event ID\n * @param quest - Quest ID\n * @param part - Part number (1, 2, or 3)\n * @returns Data for the event, quest, and part\n */\n async getEventPartData(\n event: string | number,\n quest: string | number,\n part: PartNumber,\n ) {\n const data = await this.getDataInternal(ChallengeType.Event, event, quest);\n return data[part];\n }\n\n /**\n * Fetches data for a specific story, quest, and part.\n * @param story - Story ID\n * @param quest - Quest ID\n * @param part - Part number (1, 2, or 3)\n * @returns Data for the story, quest, and part\n */\n async getStoryPartData(\n story: string | number,\n quest: string | number,\n part: PartNumber,\n ) {\n const data = await this.getDataInternal(ChallengeType.Story, story, quest);\n return data[part];\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Part number for quests.
3
+ * @public
4
+ */
5
+ type PartNumber = 1 | 2 | 3;
6
+ /**
7
+ * Client for request against the EverybodyCodes API.
8
+ *
9
+ * @public
10
+ */
11
+ declare class EverybodyCodesClient {
12
+ private readonly cookie;
13
+ private seed?;
14
+ /**
15
+ * Creates a new EverybodyCodesClient.
16
+ * @param sessionCookie - Value of the `everybody-codes` cookie.
17
+ */
18
+ constructor(sessionCookie: string);
19
+ private makeUrl;
20
+ private cookieHeader;
21
+ private getSeed;
22
+ private getEncryptedInputs;
23
+ private getDataInternal;
24
+ private getKeys;
25
+ /**
26
+ * Fetches data for a specific event and quest.
27
+ * @param event - Event ID
28
+ * @param quest - Quest ID
29
+ * @returns Data for the event and quest
30
+ */
31
+ getEventData(event: string | number, quest: string | number): Promise<Partial<Record<2 | 1 | 3, string>>>;
32
+ /**
33
+ * Fetches data for a specific story and quest.
34
+ * @param story - Story ID
35
+ * @param quest - Quest ID
36
+ * @returns Data for the story and quest
37
+ */
38
+ getStoryData(story: string | number, quest: string | number): Promise<Partial<Record<2 | 1 | 3, string>>>;
39
+ /**
40
+ * Fetches data for a specific event, quest, and part.
41
+ * @param event - Event ID
42
+ * @param quest - Quest ID
43
+ * @param part - Part number (1, 2, or 3)
44
+ * @returns Data for the event, quest, and part
45
+ */
46
+ getEventPartData(event: string | number, quest: string | number, part: PartNumber): Promise<string | undefined>;
47
+ /**
48
+ * Fetches data for a specific story, quest, and part.
49
+ * @param story - Story ID
50
+ * @param quest - Quest ID
51
+ * @param part - Part number (1, 2, or 3)
52
+ * @returns Data for the story, quest, and part
53
+ */
54
+ getStoryPartData(story: string | number, quest: string | number, part: PartNumber): Promise<string | undefined>;
55
+ }
56
+
57
+ export { EverybodyCodesClient, type PartNumber };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Part number for quests.
3
+ * @public
4
+ */
5
+ type PartNumber = 1 | 2 | 3;
6
+ /**
7
+ * Client for request against the EverybodyCodes API.
8
+ *
9
+ * @public
10
+ */
11
+ declare class EverybodyCodesClient {
12
+ private readonly cookie;
13
+ private seed?;
14
+ /**
15
+ * Creates a new EverybodyCodesClient.
16
+ * @param sessionCookie - Value of the `everybody-codes` cookie.
17
+ */
18
+ constructor(sessionCookie: string);
19
+ private makeUrl;
20
+ private cookieHeader;
21
+ private getSeed;
22
+ private getEncryptedInputs;
23
+ private getDataInternal;
24
+ private getKeys;
25
+ /**
26
+ * Fetches data for a specific event and quest.
27
+ * @param event - Event ID
28
+ * @param quest - Quest ID
29
+ * @returns Data for the event and quest
30
+ */
31
+ getEventData(event: string | number, quest: string | number): Promise<Partial<Record<2 | 1 | 3, string>>>;
32
+ /**
33
+ * Fetches data for a specific story and quest.
34
+ * @param story - Story ID
35
+ * @param quest - Quest ID
36
+ * @returns Data for the story and quest
37
+ */
38
+ getStoryData(story: string | number, quest: string | number): Promise<Partial<Record<2 | 1 | 3, string>>>;
39
+ /**
40
+ * Fetches data for a specific event, quest, and part.
41
+ * @param event - Event ID
42
+ * @param quest - Quest ID
43
+ * @param part - Part number (1, 2, or 3)
44
+ * @returns Data for the event, quest, and part
45
+ */
46
+ getEventPartData(event: string | number, quest: string | number, part: PartNumber): Promise<string | undefined>;
47
+ /**
48
+ * Fetches data for a specific story, quest, and part.
49
+ * @param story - Story ID
50
+ * @param quest - Quest ID
51
+ * @param part - Part number (1, 2, or 3)
52
+ * @returns Data for the story, quest, and part
53
+ */
54
+ getStoryPartData(story: string | number, quest: string | number, part: PartNumber): Promise<string | undefined>;
55
+ }
56
+
57
+ export { EverybodyCodesClient, type PartNumber };
package/dist/index.js ADDED
@@ -0,0 +1,152 @@
1
+ import crypto from 'crypto';
2
+
3
+ // src/helper/http.ts
4
+ async function http(url, init) {
5
+ const res = await fetch(url, {
6
+ ...init,
7
+ headers: {
8
+ accept: "application/json",
9
+ "user-agent": "everybodycodes-data/0.1 (+github.com/you/repo)",
10
+ ...init?.headers ?? {}
11
+ }
12
+ });
13
+ if (!res.ok) {
14
+ const body = await res.text().catch(() => "");
15
+ throw new Error(
16
+ `HTTP ${res.status} ${res.statusText} :: ${url} :: ${body}`
17
+ );
18
+ }
19
+ const ct = res.headers.get("content-type") || "";
20
+ if (ct.includes("application/json")) {
21
+ return await res.json();
22
+ }
23
+ return await res.text();
24
+ }
25
+ function aesDecryptHexWithKey(key, hexCipher) {
26
+ const keyBytes = Buffer.from(key, "utf8");
27
+ const iv = Buffer.from(key.substring(0, 16), "utf8");
28
+ const cipherBytes = Buffer.from(hexCipher, "hex");
29
+ const algo = keyBytes.length === 16 ? "aes-128-cbc" : keyBytes.length === 24 ? "aes-192-cbc" : keyBytes.length === 32 ? "aes-256-cbc" : "aes-256-cbc";
30
+ const dec = crypto.createDecipheriv(algo, keyBytes, iv);
31
+ const out = Buffer.concat([dec.update(cipherBytes), dec.final()]);
32
+ return out.toString("utf8");
33
+ }
34
+
35
+ // src/client.ts
36
+ var API_BASE = "https://everybody.codes/api";
37
+ var CDN_BASE = "https://everybody-codes.b-cdn.net/assets";
38
+ var EverybodyCodesClient = class {
39
+ cookie;
40
+ seed;
41
+ /**
42
+ * Creates a new EverybodyCodesClient.
43
+ * @param sessionCookie - Value of the `everybody-codes` cookie.
44
+ */
45
+ constructor(sessionCookie) {
46
+ if (!sessionCookie || !sessionCookie.trim()) {
47
+ throw new Error("EverybodyCodesClient: sessionCookie is required.");
48
+ }
49
+ this.cookie = sessionCookie.trim();
50
+ }
51
+ makeUrl(type, ...parts) {
52
+ return [
53
+ API_BASE,
54
+ type,
55
+ ...parts.map((p) => encodeURIComponent(String(p)))
56
+ ].join("/");
57
+ }
58
+ cookieHeader() {
59
+ return { Cookie: `everybody-codes=${this.cookie}` };
60
+ }
61
+ async getSeed() {
62
+ if (this.seed) return this.seed;
63
+ const url = `${API_BASE}/user/me`;
64
+ const data = await http(url, {
65
+ headers: this.cookieHeader()
66
+ });
67
+ const raw = data?.seed;
68
+ const num = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() !== "" ? Number(raw) : NaN;
69
+ if (!Number.isFinite(num) || num <= 0) {
70
+ throw new Error(
71
+ "Invalid seed value (0). This usually means your session has expired or the 'everybody-codes' cookie is invalid. Log in again at https://everybody.codes, copy the cookie value exactly (no spaces or quotes), and try again."
72
+ );
73
+ }
74
+ this.seed = String(num);
75
+ return this.seed;
76
+ }
77
+ async getEncryptedInputs(event, quest, seed) {
78
+ const url = `${CDN_BASE}/${encodeURIComponent(String(event))}/${encodeURIComponent(
79
+ String(quest)
80
+ )}/input/${encodeURIComponent(seed)}.json`;
81
+ return await http(url, {
82
+ headers: this.cookieHeader()
83
+ });
84
+ }
85
+ async getDataInternal(type, event, quest) {
86
+ const seed = await this.getSeed();
87
+ const encrypted = await this.getEncryptedInputs(event, quest, seed);
88
+ const keys = await this.getKeys(type, event, quest);
89
+ const out = {};
90
+ ["1", "2", "3"].forEach((p) => {
91
+ const hex = encrypted[p];
92
+ const key = keys[p];
93
+ if (hex && key)
94
+ out[Number(p)] = aesDecryptHexWithKey(key, hex);
95
+ });
96
+ return out;
97
+ }
98
+ async getKeys(type, event, quest) {
99
+ const url = this.makeUrl(type, event, "quest", quest);
100
+ const data = await http(url, { headers: this.cookieHeader() });
101
+ const out = {};
102
+ for (const p of ["1", "2", "3"]) {
103
+ const flat = data?.[`key${p}`];
104
+ if (flat) out[p] = String(flat);
105
+ }
106
+ return out;
107
+ }
108
+ /**
109
+ * Fetches data for a specific event and quest.
110
+ * @param event - Event ID
111
+ * @param quest - Quest ID
112
+ * @returns Data for the event and quest
113
+ */
114
+ async getEventData(event, quest) {
115
+ return this.getDataInternal("event" /* Event */, event, quest);
116
+ }
117
+ /**
118
+ * Fetches data for a specific story and quest.
119
+ * @param story - Story ID
120
+ * @param quest - Quest ID
121
+ * @returns Data for the story and quest
122
+ */
123
+ async getStoryData(story, quest) {
124
+ return this.getDataInternal("story" /* Story */, story, quest);
125
+ }
126
+ /**
127
+ * Fetches data for a specific event, quest, and part.
128
+ * @param event - Event ID
129
+ * @param quest - Quest ID
130
+ * @param part - Part number (1, 2, or 3)
131
+ * @returns Data for the event, quest, and part
132
+ */
133
+ async getEventPartData(event, quest, part) {
134
+ const data = await this.getDataInternal("event" /* Event */, event, quest);
135
+ return data[part];
136
+ }
137
+ /**
138
+ * Fetches data for a specific story, quest, and part.
139
+ * @param story - Story ID
140
+ * @param quest - Quest ID
141
+ * @param part - Part number (1, 2, or 3)
142
+ * @returns Data for the story, quest, and part
143
+ */
144
+ async getStoryPartData(story, quest, part) {
145
+ const data = await this.getDataInternal("story" /* Story */, story, quest);
146
+ return data[part];
147
+ }
148
+ };
149
+
150
+ export { EverybodyCodesClient };
151
+ //# sourceMappingURL=index.js.map
152
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helper/http.ts","../src/crypto.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export async function http<T = unknown>(\n url: string,\n init?: RequestInit,\n): Promise<T> {\n const res = await fetch(url, {\n ...init,\n headers: {\n accept: \"application/json\",\n \"user-agent\": \"everybodycodes-data/0.1 (+github.com/you/repo)\",\n ...(init?.headers ?? {}),\n },\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(\n `HTTP ${res.status} ${res.statusText} :: ${url} :: ${body}`,\n );\n }\n const ct = res.headers.get(\"content-type\") || \"\";\n if (ct.includes(\"application/json\")) {\n return (await res.json()) as T;\n }\n return (await res.text()) as unknown as T;\n}\n","import crypto from \"node:crypto\";\n\nexport function aesDecryptHexWithKey(key: string, hexCipher: string): string {\n const keyBytes = Buffer.from(key, \"utf8\");\n const iv = Buffer.from(key.substring(0, 16), \"utf8\");\n const cipherBytes = Buffer.from(hexCipher, \"hex\");\n\n const algo =\n keyBytes.length === 16\n ? \"aes-128-cbc\"\n : keyBytes.length === 24\n ? \"aes-192-cbc\"\n : keyBytes.length === 32\n ? \"aes-256-cbc\"\n : \"aes-256-cbc\";\n\n const dec = crypto.createDecipheriv(algo, keyBytes, iv);\n const out = Buffer.concat([dec.update(cipherBytes), dec.final()]);\n return out.toString(\"utf8\");\n}\n","import { http } from \"./helper/http\";\nimport { aesDecryptHexWithKey } from \"./crypto.js\";\n\nconst API_BASE = \"https://everybody.codes/api\";\nconst CDN_BASE = \"https://everybody-codes.b-cdn.net/assets\";\n\nenum ChallengeType {\n Event = \"event\",\n Story = \"story\",\n}\n\n/**\n * Part number for quests.\n * @public\n */\nexport type PartNumber = 1 | 2 | 3;\n\n/**\n * Client for request against the EverybodyCodes API.\n *\n * @public\n */\nexport class EverybodyCodesClient {\n private readonly cookie: string;\n private seed?: string;\n\n /**\n * Creates a new EverybodyCodesClient.\n * @param sessionCookie - Value of the `everybody-codes` cookie.\n */\n constructor(sessionCookie: string) {\n if (!sessionCookie || !sessionCookie.trim()) {\n throw new Error(\"EverybodyCodesClient: sessionCookie is required.\");\n }\n this.cookie = sessionCookie.trim();\n }\n\n private makeUrl(type: ChallengeType, ...parts: (string | number)[]): string {\n return [\n API_BASE,\n type,\n ...parts.map((p) => encodeURIComponent(String(p))),\n ].join(\"/\");\n }\n\n private cookieHeader(): HeadersInit {\n return { Cookie: `everybody-codes=${this.cookie}` };\n }\n\n private async getSeed(): Promise<string> {\n if (this.seed) return this.seed;\n\n const url = `${API_BASE}/user/me`;\n const data = await http<{ seed?: number | string }>(url, {\n headers: this.cookieHeader(),\n });\n\n const raw = data?.seed;\n const num =\n typeof raw === \"number\"\n ? raw\n : typeof raw === \"string\" && raw.trim() !== \"\"\n ? Number(raw)\n : NaN;\n\n if (!Number.isFinite(num) || num <= 0) {\n throw new Error(\n \"Invalid seed value (0). This usually means your session has expired or the 'everybody-codes' cookie is invalid. \" +\n \"Log in again at https://everybody.codes, copy the cookie value exactly (no spaces or quotes), and try again.\",\n );\n }\n\n this.seed = String(num);\n return this.seed;\n }\n\n private async getEncryptedInputs(\n event: string | number,\n quest: string | number,\n seed: string,\n ) {\n const url = `${CDN_BASE}/${encodeURIComponent(String(event))}/${encodeURIComponent(\n String(quest),\n )}/input/${encodeURIComponent(seed)}.json`;\n return await http<Record<\"1\" | \"2\" | \"3\", string>>(url, {\n headers: this.cookieHeader(),\n });\n }\n\n private async getDataInternal(\n type: ChallengeType,\n event: string | number,\n quest: string | number,\n ) {\n const seed = await this.getSeed();\n const encrypted = await this.getEncryptedInputs(event, quest, seed);\n const keys = await this.getKeys(type, event, quest);\n\n const out: Partial<Record<1 | 2 | 3, string>> = {};\n ([\"1\", \"2\", \"3\"] as const).forEach((p) => {\n const hex = encrypted[p];\n const key = keys[p];\n if (hex && key)\n out[Number(p) as 1 | 2 | 3] = aesDecryptHexWithKey(key, hex);\n });\n return out;\n }\n\n private async getKeys(\n type: ChallengeType,\n event: string | number,\n quest: string | number,\n ) {\n const url = this.makeUrl(type, event, \"quest\", quest);\n\n type KeyResponse = {\n key1?: string;\n key2?: string;\n key3?: string;\n };\n\n const data = await http<KeyResponse>(url, { headers: this.cookieHeader() });\n\n const out: Partial<Record<\"1\" | \"2\" | \"3\", string>> = {};\n for (const p of [\"1\", \"2\", \"3\"] as const) {\n const flat = data?.[`key${p}`];\n if (flat) out[p] = String(flat);\n }\n return out;\n }\n\n /**\n * Fetches data for a specific event and quest.\n * @param event - Event ID\n * @param quest - Quest ID\n * @returns Data for the event and quest\n */\n async getEventData(event: string | number, quest: string | number) {\n return this.getDataInternal(ChallengeType.Event, event, quest);\n }\n\n /**\n * Fetches data for a specific story and quest.\n * @param story - Story ID\n * @param quest - Quest ID\n * @returns Data for the story and quest\n */\n async getStoryData(story: string | number, quest: string | number) {\n return this.getDataInternal(ChallengeType.Story, story, quest);\n }\n\n /**\n * Fetches data for a specific event, quest, and part.\n * @param event - Event ID\n * @param quest - Quest ID\n * @param part - Part number (1, 2, or 3)\n * @returns Data for the event, quest, and part\n */\n async getEventPartData(\n event: string | number,\n quest: string | number,\n part: PartNumber,\n ) {\n const data = await this.getDataInternal(ChallengeType.Event, event, quest);\n return data[part];\n }\n\n /**\n * Fetches data for a specific story, quest, and part.\n * @param story - Story ID\n * @param quest - Quest ID\n * @param part - Part number (1, 2, or 3)\n * @returns Data for the story, quest, and part\n */\n async getStoryPartData(\n story: string | number,\n quest: string | number,\n part: PartNumber,\n ) {\n const data = await this.getDataInternal(ChallengeType.Story, story, quest);\n return data[part];\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@mrtimeey/everybodycodes-data",
3
+ "version": "1.2.0",
4
+ "description": "Simple client for fetching data and submitting answers",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "private": false,
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "scripts": {
31
+ "clean": "rimraf dist",
32
+ "docs": "typedoc",
33
+ "docs:clean": "rimraf docs",
34
+ "format": "prettier --write .",
35
+ "format:check": "prettier --check .",
36
+ "lint": "eslint . --max-warnings=0",
37
+ "test": "vitest run",
38
+ "dev:test": "vitest",
39
+ "prepublishOnly": "npm run build && npm run docs",
40
+ "api:extract": "npm run build && api-extractor run --local",
41
+ "build": "tsup",
42
+ "postbuild": "api-extractor run --verbose"
43
+ },
44
+ "devDependencies": {
45
+ "@eslint/js": "^9.39.1",
46
+ "@microsoft/api-extractor": "^7.54.0",
47
+ "@microsoft/tsdoc": "^0.15.1",
48
+ "@types/node": "^24.10.0",
49
+ "eslint": "^9.39.1",
50
+ "eslint-config-prettier": "^10.1.8",
51
+ "globals": "^16.5.0",
52
+ "prettier": "^3.6.2",
53
+ "prettier-plugin-organize-imports": "^4.3.0",
54
+ "rimraf": "^6.0.1",
55
+ "ts-node": "^10.9.2",
56
+ "tsup": "^8.3.0",
57
+ "typedoc": "^0.28.14",
58
+ "typescript": "^5.9.3",
59
+ "typescript-eslint": "^8.46.3",
60
+ "undici": "^7.16.0",
61
+ "vitest": "^4.0.8"
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/MrTimeey/everybodycodes-data.git"
66
+ },
67
+ "keywords": [
68
+ "everybody-codes"
69
+ ],
70
+ "author": "MrTimeey",
71
+ "license": "MIT",
72
+ "bugs": {
73
+ "url": "https://github.com/MrTimeey/everybodycodes-data/issues"
74
+ },
75
+ "homepage": "https://github.com/MrTimeey/everybodycodes-data#readme"
76
+ }