@udondan/duolingo 1.0.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) 2026 Daniel Schroeder
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,373 @@
1
+ # @udondan/duolingo
2
+
3
+ A TypeScript package that provides both a **Duolingo API client library** and an
4
+ **[MCP](https://modelcontextprotocol.io) server** for LLM agents (e.g. Claude).
5
+
6
+ Built natively in TypeScript against the unofficial Duolingo REST API — no third-party
7
+ Duolingo library dependency.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Getting Your JWT Token](#getting-your-jwt-token)
12
+ - [MCP Server](#mcp-server)
13
+ - [Installation](#mcp-installation)
14
+ - [Claude Desktop](#claude-desktop)
15
+ - [Claude Code](#claude-code)
16
+ - [Available Tools](#available-tools)
17
+ - [Library](#library)
18
+ - [Installation](#library-installation)
19
+ - [Quick Start](#quick-start)
20
+ - [API Reference](#api-reference)
21
+ - [Development](#development)
22
+
23
+ ---
24
+
25
+ ## Getting Your JWT Token
26
+
27
+ Both the MCP server and the library require a Duolingo JWT token for authentication.
28
+
29
+ 1. Log in to [duolingo.com](https://www.duolingo.com) in your browser.
30
+ 2. Open the browser developer console (F12 → Console tab).
31
+ 3. Run:
32
+ ```js
33
+ document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11)
34
+ ```
35
+ 4. Copy the output — that is your JWT token.
36
+
37
+ > **Note**: JWT tokens expire. If you get authentication errors, repeat the steps above.
38
+
39
+ ---
40
+
41
+ ## MCP Server
42
+
43
+ ### MCP Installation
44
+
45
+ **Option A — run directly with npx (no install needed):**
46
+
47
+ ```bash
48
+ DUOLINGO_USERNAME=your_username DUOLINGO_JWT=your_jwt npx @udondan/duolingo
49
+ ```
50
+
51
+ **Option B — install globally:**
52
+
53
+ ```bash
54
+ npm install -g @udondan/duolingo
55
+ DUOLINGO_USERNAME=your_username DUOLINGO_JWT=your_jwt duolingo-mcp
56
+ ```
57
+
58
+ **Option C — clone and build from source:**
59
+
60
+ ```bash
61
+ git clone https://github.com/udondan/duolingo.git
62
+ cd duolingo
63
+ npm install && npm run build
64
+ node dist/server.js
65
+ ```
66
+
67
+ ### Claude Desktop
68
+
69
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "duolingo": {
75
+ "command": "npx",
76
+ "args": ["-y", "@udondan/duolingo"],
77
+ "env": {
78
+ "DUOLINGO_USERNAME": "your_username",
79
+ "DUOLINGO_JWT": "your_jwt_token"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Claude Code
87
+
88
+ ```bash
89
+ claude mcp add duolingo -- npx -y @udondan/duolingo
90
+ ```
91
+
92
+ Then set the environment variables before starting Claude Code:
93
+
94
+ ```bash
95
+ export DUOLINGO_USERNAME="your_username"
96
+ export DUOLINGO_JWT="your_jwt_token"
97
+ ```
98
+
99
+ ### Available Tools
100
+
101
+ All tools are **read-only** — the server never modifies your Duolingo account.
102
+
103
+ #### Account
104
+
105
+ | Tool | Description |
106
+ |------|-------------|
107
+ | `duolingo_get_user_info` | Profile: username, name, bio, location, avatar, followers, learning language |
108
+ | `duolingo_get_settings` | Notification and account settings |
109
+ | `duolingo_get_streak_info` | Current streak, longest streak, daily goal, extended today |
110
+ | `duolingo_get_daily_xp_progress` | XP goal, XP earned today, lessons completed today |
111
+ | `duolingo_get_languages` | Languages being learned (full names or abbreviations) |
112
+ | `duolingo_get_courses` | All courses including Math, Chess, and Music with XP per course |
113
+ | `duolingo_get_friends` | Users the authenticated user follows, with total XP |
114
+ | `duolingo_get_calendar` | Recent activity calendar for the current course (~last 2 weeks) |
115
+ | `duolingo_get_leaderboard` | Authenticated user's friends sorted by XP for week or month |
116
+ | `duolingo_get_shop_items` | Full shop catalogue with prices and item types |
117
+ | `duolingo_get_health` | Current hearts count, max hearts, refill timing |
118
+ | `duolingo_get_currencies` | Gem and lingot balances |
119
+ | `duolingo_get_streak_goal` | Current streak goal with upcoming checkpoints |
120
+
121
+ #### Language
122
+
123
+ | Tool | Description |
124
+ |------|-------------|
125
+ | `duolingo_get_language_details` | Level, points, streak for a specific language |
126
+ | `duolingo_get_language_progress` | Detailed progress: level %, points to next level, fluency |
127
+ | `duolingo_get_known_topics` | Learned topic/skill names |
128
+ | `duolingo_get_unknown_topics` | Not-yet-learned topics |
129
+ | `duolingo_get_golden_topics` | Fully mastered topics (strength = 1.0) |
130
+ | `duolingo_get_reviewable_topics` | Learned but not fully mastered topics |
131
+ | `duolingo_get_known_words` | Set of known words for a language |
132
+ | `duolingo_get_learned_skills` | Full skill objects sorted by learning order |
133
+ | `duolingo_get_language_voices` | Available TTS voice names for a language |
134
+ | `duolingo_get_audio_url` | Pronunciation audio URL for a word |
135
+
136
+ #### Utilities
137
+
138
+ | Tool | Description |
139
+ |------|-------------|
140
+ | `duolingo_get_language_from_abbr` | Convert language abbreviation to full name (e.g. `fr` → `French`) |
141
+ | `duolingo_get_abbreviation_of` | Convert full language name to abbreviation (e.g. `French` → `fr`) |
142
+
143
+ ---
144
+
145
+ ## Library
146
+
147
+ ### Library Installation
148
+
149
+ ```bash
150
+ npm install @udondan/duolingo
151
+ ```
152
+
153
+ ### Quick Start
154
+
155
+ ```typescript
156
+ import { DuolingoClient } from '@udondan/duolingo';
157
+
158
+ const client = new DuolingoClient('your_username', 'your_jwt_token');
159
+
160
+ // Get all courses including Math, Chess, and Music
161
+ const userData = await client.getUserData();
162
+ const userId = userData.id;
163
+ const v2 = await client.getUserDataV2(userId);
164
+ for (const course of v2.courses) {
165
+ console.log(`${course.subject}: ${course.xp} XP`);
166
+ }
167
+
168
+ // Get streak info
169
+ const streak = v2.streak;
170
+ const longestStreak = v2.streakData.longestStreak?.length;
171
+
172
+ // Get friends
173
+ const friends = await client.getFollowing(userId);
174
+ for (const friend of friends) {
175
+ console.log(`${friend.username}: ${friend.totalXp} XP`);
176
+ }
177
+
178
+ // Get shop items
179
+ const items = await client.getShopItems();
180
+ const streakFreeze = items.find(i => i.id === 'streak_freeze');
181
+
182
+ // Get hearts
183
+ const health = await client.getHealth();
184
+ console.log(`Hearts: ${health.hearts}/${health.maxHearts}`);
185
+ ```
186
+
187
+ ### API Reference
188
+
189
+ #### `new DuolingoClient(username, jwt)`
190
+
191
+ Creates a new client instance. Results are cached per instance.
192
+
193
+ ```typescript
194
+ const client = new DuolingoClient('your_username', 'your_jwt_token');
195
+ ```
196
+
197
+ #### User Data
198
+
199
+ ```typescript
200
+ // Legacy API — returns language_data with skills, calendar, etc.
201
+ // Required for: language details, topics, known words, learned skills
202
+ const userData = await client.getUserData(username?);
203
+
204
+ // 2023-05-23 API — returns all courses (language + math/chess/music),
205
+ // richer streak data, subscriber level, gems, health
206
+ const userId = userData.id;
207
+ const v2 = await client.getUserDataV2(userId);
208
+
209
+ // Resolve a username to a numeric user ID
210
+ const id = await client.getUserIdByUsername('someuser');
211
+ ```
212
+
213
+ #### Courses (all subjects)
214
+
215
+ ```typescript
216
+ const v2 = await client.getUserDataV2(userId);
217
+
218
+ // All courses
219
+ v2.courses; // DuolingoCourse[]
220
+
221
+ // Filter by subject
222
+ const langCourses = v2.courses.filter(c => c.subject === 'language');
223
+ const mathCourses = v2.courses.filter(c => c.subject === 'math');
224
+ const chessCourses = v2.courses.filter(c => c.subject === 'chess');
225
+ const musicCourses = v2.courses.filter(c => c.subject === 'music');
226
+ ```
227
+
228
+ Each course has: `id`, `subject`, `topic`, `xp`, `fromLanguage`.
229
+ Language courses also have: `learningLanguage`, `title`, `authorId`.
230
+
231
+ #### Daily XP Progress
232
+
233
+ ```typescript
234
+ const progress = await client.getUserDataById(userId, ['xpGoal', 'xpGains', 'streakData']);
235
+ // progress.xpGoal — daily XP goal
236
+ // progress.xpGains — array of { xp, skillId, time } for recent lessons
237
+ // progress.streakData.updatedTimestamp — last streak update
238
+ ```
239
+
240
+ #### Friends & Social
241
+
242
+ ```typescript
243
+ // People this user follows (= friends)
244
+ const following = await client.getFollowing(userId);
245
+ // following[0].username, .totalXp, .userScore?.score (weekly XP), .isFollowedBy
246
+
247
+ // People who follow this user
248
+ const followers = await client.getFollowers(userId);
249
+ ```
250
+
251
+ #### Shop, Health & Currencies
252
+
253
+ ```typescript
254
+ // Full shop catalogue (read-only)
255
+ const items = await client.getShopItems();
256
+ // items[0].id, .name, .type, .price, .currencyType, .lastUsedDate
257
+
258
+ // Hearts / health (authenticated user only)
259
+ const health = await client.getHealth();
260
+ // health.hearts, .maxHearts, .eligibleForFreeRefill, .secondsUntilNextHeartSegment
261
+
262
+ // Gem and lingot balances (authenticated user only)
263
+ const { gems, lingots } = await client.getCurrencies();
264
+ ```
265
+
266
+ #### Streak Goals
267
+
268
+ ```typescript
269
+ // Current streak goal with checkpoints
270
+ const goal = await client.getStreakGoalCurrent();
271
+ // goal.hasActiveGoal, goal.streakGoal.lastCompleteGoal, .checkpoints, .nextSelectedGoal
272
+
273
+ // Available next goal options
274
+ const options = await client.getStreakGoalNextOptions();
275
+ ```
276
+
277
+ #### TTS Audio
278
+
279
+ ```typescript
280
+ // Discover available voices for a language
281
+ const voices = await client.getLanguageVoices('es'); // ['beaes', 'juniores', ...]
282
+
283
+ // Build an audio URL
284
+ const url = await client.buildAudioUrl('hola', 'es');
285
+ const urlWithVoice = await client.buildAudioUrl('hola', 'es', 'beaes');
286
+ ```
287
+
288
+ #### Error Handling
289
+
290
+ ```typescript
291
+ import {
292
+ DuolingoClient,
293
+ DuolingoAuthError,
294
+ DuolingoNotFoundError,
295
+ DuolingoCaptchaError,
296
+ DuolingoClientError,
297
+ } from '@udondan/duolingo';
298
+
299
+ try {
300
+ const data = await client.getUserData('someuser');
301
+ } catch (err) {
302
+ if (err instanceof DuolingoAuthError) {
303
+ // JWT expired — extract a new one from the browser
304
+ } else if (err instanceof DuolingoNotFoundError) {
305
+ // User does not exist
306
+ } else if (err instanceof DuolingoCaptchaError) {
307
+ // Duolingo blocked the request — try again later
308
+ } else if (err instanceof DuolingoClientError) {
309
+ // Other API error
310
+ }
311
+ }
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Development
317
+
318
+ ```bash
319
+ git clone https://github.com/udondan/duolingo.git
320
+ cd duolingo
321
+ npm install
322
+
323
+ # Build
324
+ npm run build
325
+
326
+ # Run tests (unit + integration)
327
+ npm test
328
+
329
+ # Integration tests require credentials:
330
+ export DUOLINGO_USERNAME="your_username"
331
+ export DUOLINGO_JWT="your_jwt_token"
332
+ npm test
333
+
334
+ # Type-check without building
335
+ npm run typecheck
336
+
337
+ # Run MCP server in dev mode (no build step)
338
+ npm run dev
339
+ ```
340
+
341
+ ### Project Structure
342
+
343
+ ```
344
+ src/
345
+ ├── index.ts # Library entry point — exports client, types, errors
346
+ ├── server.ts # MCP server entry point (stdio transport)
347
+ ├── client/
348
+ │ ├── duolingo.ts # DuolingoClient — all API methods
349
+ │ ├── types.ts # TypeScript interfaces for all API responses
350
+ │ └── errors.ts # Custom error classes
351
+ └── tools/
352
+ ├── account.ts # Account tools (13)
353
+ ├── language.ts # Language tools (11)
354
+ ├── shop.ts # Utility tools (2)
355
+ └── helpers.ts # Shared utilities (error handling, Zod schemas)
356
+ ```
357
+
358
+ ### API Versions Used
359
+
360
+ | Endpoint | API Version | Used For |
361
+ |----------|-------------|----------|
362
+ | `/users/<username>` | Legacy | User profile, language data, skills, calendar |
363
+ | `/2023-05-23/users/{id}` | Current | Courses (all subjects), streak, health, gems |
364
+ | `/2023-05-23/friends/users/{id}/following` | Current | Friends, leaderboard |
365
+ | `/2023-05-23/shop-items` | Current | Shop catalogue |
366
+ | `/users/{id}/streak-goal-current` | Current | Streak goals |
367
+ | `/2017-06-30/sessions` | Current | TTS voice discovery |
368
+
369
+ ---
370
+
371
+ ## License
372
+
373
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Native TypeScript Duolingo API client.
3
+ *
4
+ * Calls the unofficial Duolingo REST API directly using axios.
5
+ * No third-party Duolingo library dependency.
6
+ */
7
+ import type { DuolingoUserData, DuolingoDailyProgress, DuolingoLeaderboardData, DuolingoFriendUser, DuolingoUserDataV2, DuolingoShopItem, DuolingoHealth, DuolingoStreakGoalCurrentResponse, DuolingoStreakGoalNextOptionsResponse, DuolingoSessionResponse } from './types.js';
8
+ export declare class DuolingoClient {
9
+ private readonly http;
10
+ private readonly username;
11
+ private readonly jwt;
12
+ /** Cache of user data keyed by username. */
13
+ private readonly userDataCache;
14
+ /** Cache of v2 user data keyed by numeric user ID. */
15
+ private readonly userDataV2Cache;
16
+ /**
17
+ * Cached set of voice names discovered for each language via the session API.
18
+ * lang → Set<voiceName>
19
+ */
20
+ private voiceCache;
21
+ /** Voice URL dictionary: lang → word → Set<url> */
22
+ private voiceUrlDict;
23
+ constructor(username: string, jwt: string);
24
+ /**
25
+ * Fetch user data from /users/<username>.
26
+ * Results are cached per username for the lifetime of this client instance.
27
+ */
28
+ getUserData(username?: string): Promise<DuolingoUserData>;
29
+ /**
30
+ * Invalidate the user data cache for a specific username (or all).
31
+ */
32
+ invalidateCache(username?: string): void;
33
+ /**
34
+ * Fetch daily XP progress data for a user.
35
+ * Uses the 2023-05-23 API which supersedes the 2017-06-30 endpoint.
36
+ */
37
+ getUserDataById(userId: number, fields: string[]): Promise<DuolingoDailyProgress>;
38
+ /**
39
+ * Get the list of users the given user is following.
40
+ * Uses the 2023-05-23 API which supersedes the 2017-06-30 endpoint.
41
+ *
42
+ * The `viewerId` must be the authenticated user's ID (not the target user's ID).
43
+ */
44
+ getFollowing(userId: number): Promise<DuolingoFriendUser[]>;
45
+ /**
46
+ * Get the list of users who follow the given user.
47
+ * Uses the 2023-05-23 API which supersedes the 2017-06-30 endpoint.
48
+ *
49
+ * The `viewerId` must be the authenticated user's ID (not the target user's ID).
50
+ */
51
+ getFollowers(userId: number): Promise<DuolingoFriendUser[]>;
52
+ /**
53
+ * Resolve a username to a numeric user ID using the 2023-05-23 API.
54
+ * Throws DuolingoNotFoundError if the username does not exist.
55
+ */
56
+ getUserIdByUsername(username: string): Promise<number>;
57
+ /**
58
+ * Fetch rich user data from the 2023-05-23 API.
59
+ * Returns all courses including non-language subjects (math, chess, music),
60
+ * plus streak data, subscriber level, and more.
61
+ *
62
+ * Accepts either a numeric user ID or a username string.
63
+ * Results are cached per user ID for the lifetime of this client instance.
64
+ */
65
+ getUserDataV2(userIdOrUsername: number | string): Promise<DuolingoUserDataV2>;
66
+ /**
67
+ * Get the full shop item catalogue from the 2023-05-23 API.
68
+ * Returns all purchasable items with prices, types, and last-used dates.
69
+ * This is a read-only endpoint — it does not purchase anything.
70
+ */
71
+ getShopItems(): Promise<DuolingoShopItem[]>;
72
+ /**
73
+ * Get the authenticated user's current hearts/health status.
74
+ * Returns heart count, max hearts, refill eligibility, and timing.
75
+ */
76
+ getHealth(): Promise<DuolingoHealth>;
77
+ /**
78
+ * Get the authenticated user's gem and lingot balances.
79
+ */
80
+ getCurrencies(): Promise<{
81
+ gems: number;
82
+ lingots: number;
83
+ }>;
84
+ /**
85
+ * Get the authenticated user's current streak goal.
86
+ */
87
+ getStreakGoalCurrent(): Promise<DuolingoStreakGoalCurrentResponse>;
88
+ /**
89
+ * Get the available next streak goal options for the authenticated user.
90
+ */
91
+ getStreakGoalNextOptions(): Promise<DuolingoStreakGoalNextOptionsResponse>;
92
+ /**
93
+ * Get the numeric user ID of the authenticated user.
94
+ * Cached via getUserData().
95
+ */
96
+ private getAuthenticatedUserId;
97
+ /**
98
+ * Get leaderboard data for a time unit.
99
+ * Note: the /friendships/leaderboard_activity endpoint returns an empty ranking
100
+ * for most users. Prefer getFollowing() and sort by weeklyXp/monthlyXp instead.
101
+ */
102
+ getLeaderboard(unit: string, before: string): Promise<DuolingoLeaderboardData>;
103
+ /**
104
+ * Get the TTS base URL for the authenticated user.
105
+ * Falls back to the known CDN URL if not present in user data.
106
+ */
107
+ getTtsBaseUrl(): Promise<string>;
108
+ /**
109
+ * Discover available TTS voice names for a language by making a single
110
+ * GLOBAL_PRACTICE session request and extracting voice names from the
111
+ * TTS CDN URLs returned in the challenges.
112
+ *
113
+ * Voice names are extracted from URLs of the form:
114
+ * https://<cdn>/<voiceName>/<hash>
115
+ *
116
+ * Results are cached per language.
117
+ */
118
+ getLanguageVoices(langAbbr: string): Promise<string[]>;
119
+ /**
120
+ * Build a TTS audio URL for a word using the tts_base_url from user data.
121
+ *
122
+ * URL format: {ttsBaseUrl}tts/{lang}/{voice}/token/{word}
123
+ * Without voice: {ttsBaseUrl}tts/{lang}/token/{word}
124
+ */
125
+ buildAudioUrl(word: string, langAbbr: string, voice?: string): Promise<string>;
126
+ /**
127
+ * Fetch a GLOBAL_PRACTICE session for a language.
128
+ * Used to discover TTS voice names and audio URLs.
129
+ */
130
+ getGlobalPracticeSession(langAbbr: string, fromLanguage: string): Promise<DuolingoSessionResponse | null>;
131
+ /**
132
+ * Fetch a practice session for a skill.
133
+ * Note: SKILL_PRACTICE is no longer supported by the API.
134
+ * Delegates to getGlobalPracticeSession instead.
135
+ */
136
+ getSession(_skillId: string, langAbbr: string): Promise<DuolingoSessionResponse | null>;
137
+ /**
138
+ * Populate the voice URL dictionary for a language by scraping a session.
139
+ * Uses GLOBAL_PRACTICE (the only supported session type in the current API).
140
+ */
141
+ populateVoiceUrlDictionary(langAbbr: string): Promise<void>;
142
+ /**
143
+ * Get the voice URL dictionary for a language, populating it if needed.
144
+ */
145
+ getVoiceUrlDictionary(langAbbr: string): Promise<Map<string, Set<string>>>;
146
+ /**
147
+ * Extract the voice name from a Duolingo TTS CDN URL.
148
+ * Supports two URL formats:
149
+ * Legacy: https://<cdn>/<voice>/<hash>
150
+ * Modern: https://<cdn>/tts/<lang>/<voice>/token/<word>
151
+ */
152
+ private extractVoiceFromTtsUrl;
153
+ /**
154
+ * Normalize a TTS base URL to always end with a slash and use HTTPS.
155
+ */
156
+ private normalizeTtsBaseUrl;
157
+ private addToVoiceUrlDict;
158
+ private addTokenListToVoiceUrlDict;
159
+ private makeRequest;
160
+ }
161
+ /**
162
+ * Get or create the singleton DuolingoClient.
163
+ * Reads DUOLINGO_USERNAME and DUOLINGO_JWT from environment variables.
164
+ */
165
+ export declare function getClient(): DuolingoClient;
166
+ /**
167
+ * Reset the singleton client (useful for testing or credential rotation).
168
+ */
169
+ export declare function resetClient(): void;
170
+ //# sourceMappingURL=duolingo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duolingo.d.ts","sourceRoot":"","sources":["../../src/client/duolingo.ts"],"names":[],"mappings":"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"}