内容
# @udondan/duolingo
一款使用TypeScript编写的软件包,提供 **Duolingo API客户端库** 和 **MCP服务器**,适用于LLM代理(如Claude)。
该软件包是针对非官方的Duolingo REST API使用TypeScript原生构建的 - 无需第三方的Duolingo库依赖。
## 目录
- [获取您的JWT令牌](#获取您的JWT令牌)
- [MCP服务器](#mcp服务器)
- [安装](#mcp安装)
- [Claude桌面版](#claude桌面版)
- [Claude代码](#claude代码)
- [可用工具](#可用工具)
- [库](#库)
- [安装](#库安装)
- [快速入门](#快速入门)
- [API参考](#api参考)
- [开发](#开发)
---
## 获取您的JWT令牌
MCP服务器和库都需要Duolingo JWT令牌进行身份验证。
1. 在浏览器中登录 [duolingo.com](https://www.duolingo.com)。
2. 打开浏览器开发者控制台(F12 → 控制台选项卡)。
3. 运行:
```js
document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11)
```
4. 复制输出结果 - 这是您的JWT令牌。
> **注意**:JWT令牌会过期。如果您遇到身份验证错误,请重复上述步骤。
---
## MCP服务器
### MCP安装
**选项A - 直接使用npx运行(无需安装):**
```bash
DUOLINGO_USERNAME=您的用户名 DUOLINGO_JWT=您的jwt令牌 npx @udondan/duolingo
```
**选项B - 全局安装:**
```bash
npm install -g @udondan/duolingo
DUOLINGO_USERNAME=您的用户名 DUOLINGO_JWT=您的jwt令牌 duolingo-mcp
```
**选项C - 从源代码克隆并构建:**
```bash
git clone https://github.com/udondan/duolingo.git
cd duolingo
npm install && npm run build
node dist/server.js
```
### Claude桌面版
将以下内容添加到 `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"duolingo": {
"command": "npx",
"args": ["-y", "@udondan/duolingo"],
"env": {
"DUOLINGO_USERNAME": "您的用户名",
"DUOLINGO_JWT": "您的jwt令牌"
}
}
}
}
```
### Claude代码
```bash
claude mcp add duolingo -- npx -y @udondan/duolingo
```
在启动Claude代码之前设置环境变量:
```bash
export DUOLINGO_USERNAME="您的用户名"
export DUOLINGO_JWT="您的jwt令牌"
```
### 可用工具
所有工具都是 **只读** 的 - 服务器永远不会修改您的Duolingo帐户。
#### 帐户
| 工具 | 描述 |
|------|-------------|
| `duolingo_get_user_info` | 配置文件:用户名,姓名,简介,位置,头像,关注者,学习语言 |
| `duolingo_get_settings` | 通知和帐户设置 |
| `duolingo_get_streak_info` | 当前连胜,最长连胜,每日目标,当日延长 |
| `duolingo_get_daily_xp_progress` | XP目标,当日赚取XP,当日完成课程 |
| `duolingo_get_languages` | 学习中的语言(全名或缩写) |
| `duolingo_get_courses` | 所有课程,包括数学,象棋和音乐,每个课程的XP |
| `duolingo_get_friends` | 用户关注的用户,带有总XP |
| `duolingo_get_calendar` | 当前课程的近期活动日历(约2周) |
| `duolingo_get_leaderboard` | 按XP排序的已认证用户朋友(按周或月) |
| `duolingo_get_shop_items` | 完整的商店目录,带有价格和物品类型 |
| `duolingo_get_health` | 当前心数,最大心数,补充时间 |
| `duolingo_get_currencies` | 宝石和玲珑余额 |
| `duolingo_get_streak_goal` | 当前连胜目标,带有即将到来的检查点 |
#### 语言
| 工具 | 描述 |
|------|-------------|
| `duolingo_get_language_details` | 特定语言的等级,积分,连胜 |
| `duolingo_get_language_progress` | 详细进度:等级%,到下一等级的积分,流利度 |
| `duolingo_get_known_topics` | 已学习的主题/技能名称 |
| `duolingo_get_unknown_topics` | 尚未学习的主题 |
| `duolingo_get_golden_topics` | 完全掌握的主题(强度=1.0) |
| `duolingo_get_reviewable_topics` | 已学习但未完全掌握的主题 |
| `duolingo_get_known_words` | 语言的已知单词集 |
| `duolingo_get_learned_skills` | 按学习顺序排列的完整技能对象 |
| `duolingo_get_language_voices` | 语言的可用TTS语音名称 |
| `duolingo_get_audio_url` | 单词的发音音频URL |
#### 实用工具
| 工具 | 描述 |
|------|-------------|
| `duolingo_get_language_from_abbr` | 将语言缩写转换为全名(例如`fr`→`French`) |
| `duolingo_get_abbreviation_of` | 将完整语言名称转换为缩写(例如`French`→`fr`) |
---
## 库
### 库安装
```bash
npm install @udondan/duolingo
```
### 快速入门
```typescript
import { DuolingoClient } from '@udondan/duolingo';
const client = new DuolingoClient('您的用户名', '您的jwt令牌');
// 获取所有课程,包括数学,象棋和音乐
const userData = await client.getUserData();
const userId = userData.id;
const v2 = await client.getUserDataV2(userId);
for (const course of v2.courses) {
console.log(`${course.subject}: ${course.xp} XP`);
}
// 获取连胜信息
const streak = v2.streak;
const longestStreak = v2.streakData.longestStreak?.length;
// 获取朋友
const friends = await client.getFollowing(userId);
for (const friend of friends) {
console.log(`${friend.username}: ${friend.totalXp} XP`);
}
// 获取商店物品
const items = await client.getShopItems();
const streakFreeze = items.find(i => i.id === 'streak_freeze');
// 获取心数
const health = await client.getHealth();
console.log(`心数: ${health.hearts}/${health.maxHearts}`);
```
### API参考
#### `new DuolingoClient(username, jwt)`
创建新的客户端实例。结果按实例缓存。
```typescript
const client = new DuolingoClient('您的用户名', '您的jwt令牌');
```
#### 用户数据
```typescript
// 旧版API - 返回带有技能,日历等的language_data。
// 需要:语言详细信息,主题,已知单词,已学习技能
const userData = await client.getUserData(username?);
// 2023-05-23 API - 返回所有课程(语言+数学/象棋/音乐),
const userId = userData.id;
const v2 = await client.getUserDataV2(userId);
// 解析用户名为数字用户ID
const id = await client.getUserIdByUsername('someuser');
```
#### 课程(所有科目)
```typescript
const v2 = await client.getUserDataV2(userId);
// 所有课程
v2.courses; // DuolingoCourse[]
// 按科目过滤
const langCourses = v2.courses.filter(c => c.subject === 'language');
const mathCourses = v2.courses.filter(c => c.subject === 'math');
const chessCourses = v2.courses.filter(c => c.subject === 'chess');
const musicCourses = v2.courses.filter(c => c.subject === 'music');
```
每个课程都有:`id`, `subject`, `topic`, `xp`, `fromLanguage`。
语言课程还具有:`learningLanguage`, `title`, `authorId`。
#### 每日XP进度
```typescript
const progress = await client.getUserDataById(userId, ['xpGoal', 'xpGains', 'streakData']);
// progress.xpGoal - 每日XP目标
// progress.xpGains - 最近课程的{xp, skillId, time}数组
// progress.streakData.updatedTimestamp - 最后一次连胜更新
```
#### 朋友与社交
```typescript
// 此用户关注的人(= 朋友)
const following = await client.getFollowing(userId);
// following[0].username, .totalXp, .userScore?.score(每周XP), .isFollowedBy
// 关注此用户的人
const followers = await client.getFollowers(userId);
```
#### 商店,健康与货币
```typescript
// 完整的商店目录(只读)
const items = await client.getShopItems();
// items[0].id, .name, .type, .price, .currencyType, .lastUsedDate
// 心数/健康(仅限认证用户)
const health = await client.getHealth();
// health.hearts, .maxHearts, .eligibleForFreeRefill, .secondsUntilNextHeartSegment
// 宝石和玲珑余额(仅限认证用户)
const { gems, lingots } = await client.getCurrencies();
```
#### 连胜目标
```typescript
// 当前连胜目标,带有检查点
const goal = await client.getStreakGoalCurrent();
// goal.hasActiveGoal, goal.streakGoal.lastCompleteGoal, .checkpoints, .nextSelectedGoal
// 可用的下一个目标选项
const options = await client.getStreakGoalNextOptions();
```
#### TTS音频
```typescript
// 发现语言的可用语音
const voices = await client.getLanguageVoices('es'); // ['beaes', 'juniores', ...]
// 构建音频URL
const url = await client.buildAudioUrl('hola', 'es');
const urlWithVoice = await client.buildAudioUrl('hola', 'es', 'beaes');
```
#### 错误处理
```typescript
import {
DuolingoClient,
DuolingoAuthError,
DuolingoNotFoundError,
DuolingoCaptchaError,
DuolingoClientError,
} from '@udondan/duolingo';
try {
const data = await client.getUserData('someuser');
} catch (err) {
if (err instanceof DuolingoAuthError) {
// JWT过期 - 从浏览器中提取新的JWT
} else if (err instanceof DuolingoNotFoundError) {
// 用户不存在
} else if (err instanceof DuolingoCaptchaError) {
// Duolingo阻止了请求 - 稍后重试
} else if (err instanceof DuolingoClientError) {
// 其他API错误
}
}
```
---
## 开发
```bash
git clone https://github.com/udondan/duolingo.git
cd duolingo
npm install
# 构建
npm run build
# 运行测试(单元测试+集成测试)
npm test
# 集成测试需要凭证:
export DUOLINGO_USERNAME="您的用户名"
export DUOLINGO_JWT="您的jwt令牌"
npm test
# 类型检查,无需构建
npm run typecheck
# 以开发模式运行MCP服务器(无需构建步骤)
npm run dev
```
### 项目结构
```
src/
├── index.ts # 库入口点 - 导出客户端,类型,错误
├── server.ts # MCP服务器入口点(stdio传输)
├── client/
│ ├── duolingo.ts # DuolingoClient - 所有API方法
│ ├── types.ts # 所有API响应的TypeScript接口
│ └── errors.ts # 自定义错误类
└── tools/
├── account.ts # 帐户工具(13)
├── language.ts # 语言工具(11)
├── shop.ts # 实用工具(2)
└── helpers.ts # 共享实用程序(错误处理,Zod模式)
```
### 使用的API版本
| 端点 | API版本 | 用途 |
|----------|-------------|----------|
| `/users/<username>` | 旧版 | 用户配置文件,语言数据,技能,日历 |
| `/2023-05-23/users/{id}` | 当前 | 课程(所有科目),连胜,健康,宝石 |
| `/2023-05-23/friends/users/{id}/following` | 当前 | 朋友,排行榜 |
| `/2023-05-23/shop-items` | 当前 | 商店目录 |
| `/users/{id}/streak-goal-current` | 当前 | 连胜目标 |
| `/2017-06-30/sessions` | 当前 | TTS语音发现 |
---
## 许可证
MIT - 请参阅 [LICENSE](LICENSE)