aplaceforallmystuff/gumroad-library-tools
GitHub: aplaceforallmystuff/gumroad-library-tools
通过读取 Apple Silicon Mac 上 Gumroad iOS 应用的本地 NSURLCache 缓存提取认证令牌,绕过网页端 Cloudflare 防护,调用移动端 API 实现已购资源的列出、原始质量下载和研究档案构建。
Stars: 0 | Forks: 0
# gumroad-library-tools
用于提取您已购买的 Gumroad 资源库的工具——可列出资源、以原始质量下载文件,并为每个产品的描述和目录构建研究档案。
通过读取由 **Gumroad iOS-on-Mac app**(在 Apple Silicon Mac 上原生运行的 iPhone/iPad 应用)缓存在磁盘上的 auth token 来工作。无需登录流程,无需抓取 cookie,也无需绕过 Cloudflare——只要您在该应用中处于登录状态,这些工具就能直接使用。
## 为什么会有这个工具
Gumroad 的网页下载页面位于 Cloudflare 的机器人验证之后,这使得使用 cookie 进行抓取非常脆弱(`cf_clearance` cookie 与 TLS 指纹绑定,而不仅仅是 user-agent)。位于 `api.gumroad.com/mobile/` 的移动端 API 则完全没有这些问题——它是一个通过稳定 token 进行身份验证的干净 JSON API。
网页下载 URL 也不总是提供原始质量的视频;流媒体端点会将其转码为约 1080p 的 HLS 格式。移动端 API 暴露了一个 `/mobile/url_redirects/download/` 端点,该端点会返回创作者上传的原始文件。
## 要求
- Apple Silicon Mac (M1/M2/M3/M4)
- 通过 Mac App Store 安装并登录您账户的 [**Gumroad** iOS app](https://apps.apple.com/app/gumroad/id1067678944)
- Python 3.9+(仅使用标准库——无 pip 依赖)
安装应用后,**打开它一次并加载您的资源库页面**,以便它缓存已认证的请求。这是唯一的设置步骤。
## 安装
克隆并运行——无需安装任何东西:
```
git clone https://github.com//gumroad-library-tools
cd gumroad-library-tools
```
## 使用方法
### 列出您的资源库
```
python3 list_library.py
```
打印一个按内容陈旧程度排序的汇总表(多年未更新的创作者会排在最前面)。添加 `--md inv.md` 或 `--json inv.json` 也可以写入结构化输出。
### 下载产品
```
# 按 Gumroad 永久链接(gumroad.com/l/ 之后的部分)
python3 download_product.py --permalink abc123
# 按名称子字符串(不区分大小写)
python3 download_product.py --name "Some Course Title"
# 批量处理
python3 download_product.py --name "First Product" --name "Second Product"
# 自定义输出文件夹
python3 download_product.py --permalink abc123 --out ~/Videos/my-course
```
文件按每个产品写入为 `NN - .`,并附带一个 `_metadata.json` 用于记录描述、创作者、购买日期和 Gumroad URL。
对于没有 Gumroad 托管文件的产品(通过外部 URL 交付的创作者),脚本会写入一个 `README.md`,其中包含描述和 gumroad.com/d/ 下载页面 URL——在浏览器中打开该页面即可查看实际的交付说明。
### 构建研究档案
```
python3 build_dossier.py
# 写入 ./dossier.md
```
这是一个单一的 Markdown 文档,包含每个产品的完整描述(由 HTML 转换为 Markdown)和文件级别的目录。对于浏览您的资源库、寻找哪些旧课程正在积灰,或从您的旧目录中挖掘内容创意非常有用。
## 工作原理
1. 通过读取每个容器的 `.com.apple.containermanagerd.metadata.plist` 并匹配 bundle identifier `com.GRD.Gumroad`,在 `~/Library/Containers//Data/` 下找到 Gumroad 应用的数据容器。
2. 以只读模式打开位于 `Library/Caches/com.GRD.Gumroad/Cache.db` 的 NSURLCache SQLite 数据库。
3. 遍历缓存的请求 URL,直到找到带有 `?mobile_token=...` 查询参数的 URL。这就是您的 auth token。
4. 直接调用移动端 API 以获取购买记录、产品属性或下载 URL。
这些工具从不修改应用的数据库,从不向 Gumroad 自身 API 以外的任何内容触发网络请求,也从不将您的 token 持久化到磁盘。
## 逆向工程 iOS-on-Mac 应用——通用技术
同样的方法可推广到**任何您通过 Mac App Store 安装在 Apple Silicon 上的 iOS 或 iPadOS 应用**。iOS-on-Mac 应用是沙盒化的,但它们在磁盘上的状态是普通的 SQLite、plist 或 JSON 格式——无需越狱即可使用标准工具读取。
如果您想像这些工具挖掘 Gumroad 那样挖掘其他应用的 API,这里有一个秘诀:
### 1. 找到应用的数据容器
iOS-on-Mac 应用位于 `~/Library/Containers//Data/`。UUID 是按安装生成的,并不固定,因此需通过读取 metadata plist 来发现它:
```
# 列出每个 iOS-on-Mac 容器及其 bundle ID
for dir in ~/Library/Containers/*/; do
meta="$dir/.com.apple.containermanagerd.metadata.plist"
[ -f "$meta" ] || continue
bundle=$(/usr/libexec/PlistBuddy -c "Print :MCMMetadataIdentifier" "$meta" 2>/dev/null)
echo "$bundle → $dir"
done | sort
```
匹配您正在寻找的 bundle ID(例如,`com.GRD.Gumroad`)。
### 2. 查看应用缓存了什么
使用 `URLSession`(标准 Apple 网络栈)的应用会免费获得 NSURLCache。它将最近的 HTTP 响应按请求 URL 作为键存储在一个 SQLite 数据库中:
```
CONTAINER=~/Library/Containers//Data
sqlite3 "$CONTAINER/Library/Caches//Cache.db" \
"SELECT request_key FROM cfurl_cache_response;"
```
对于较小的 payload,响应正文存储在同一个数据库中;对于较大的 payload,它们会溢出到同一 Caches 目录下的 `fsCachedData/` 文件中。
**在缓存中要寻找什么:**
- **URL 中的 Auth token**——许多移动端 API 使用 `?token=...` 查询参数。遍历每个缓存的请求键并解析出认证信息。
- **API 契约**——缓存的 URL 揭示了 endpoint 模式、路径参数和查询结构。
- **响应 payload**——对于较短的响应,正文在 `cfurl_cache_receiver_data` 中;对于较长的响应,它是一个磁盘上的 blob,其 UUID 作为正文存储。
### 3. 对于 Expo / React Native 应用,grep JS bundle
如果应用的 `Wrapper/.app/` 目录包含 `main.jsbundle`(有时是压缩过的),它会在一个文件中包含整个客户端代码库:
```
# 查找 API endpoint 模式
grep -aoE '/[a-z_]+/[a-z_/]+' main.jsbundle | sort -u | head -50
# 查找 auth 或 download 关键词
grep -aoE '[a-zA-Z_]{3,20}(download|stream|token|auth)[a-zA-Z_]{0,30}' main.jsbundle \
| sort -u
```
您通常会发现**同一 endpoint 的两种风格**——一种是流媒体(转码后的,质量较低),另一种是下载(原始文件)。应用默认使用流媒体进行播放;而下载路径才是您用于存档的目标。
### 4. 对于原生 Swift/Obj-C 应用,grep 二进制文件
同样的技巧也适用于原生二进制文件——它们以纯字符串形式泄露主机名和路径模板:
```
strings -a /Applications/.app/Contents/MacOS/ \
| grep -E '^https?://|^/api/|^/v[0-9]+/'
```
虽然比 JS bundle 暴露的攻击面小,但通常足以映射出 API。
### 5. 观察应用在运行时的行为
对于任何无法从磁盘推断出的内容,可以在 `lsof` 下运行应用以查看它接触了哪些文件,或者使用绕过证书固定的 `Charles` / `mitmproxy`(随 iOS 发布的应用会积极固定证书——如果已固定,这会变得更难,但大多数实用工具应用不会这么做)。
### 此方法的局限性
- **不适用于不缓存响应的应用**(某些应用禁用 NSURLCache 或自行其是进行加密)。
- **Token 可能是短期的**——通过在应用内触发新的已认证请求来刷新它们。
- **API 可能已进行证书固定**,在这种情况下,代理拦截会失败,您只能局限于缓存和二进制文件所揭示的内容。
- **应用更新可能会更改 API**——内部 endpoint 没有稳定性契约。
Gumroad 的情况异常简单,因为: 应用将其 token 作为纯文本 `?mobile_token=` 查询参数存储在缓存的 URL 中, React Native bundle 暴露了每个 endpoint 名称,以及 Gumroad 的移动端 API 既能干净地进行身份验证,又能提供原始文件。并非每个应用都会如此配合,但有足够多的应用是这样的,因此在求助于 mitm proxies 或其他重型工具之前,这值得一试。
## 隐私
- **您的 token 不会被记录。** 脚本将其读入内存并通过 `urllib` 传递。它不会出现在任何输出、文件或 stderr 消息中。
- **您的资源库数据只会存入您指定的磁盘路径。** 无遥测。无外部服务。
- **auth token 可以被轮换**,只需退出 Gumroad 应用即可——如果您在某个地方分享了调试日志,这会很有用。
## 这不是什么
- **不是盗版工具。** 仅限于您自己的购买——API 会拒绝您未拥有产品的请求。下载 endpoint 与应用使用的相同;您只是从不同的客户端调用它们。
- **不是 Gumroad 创作者工具。** 这是供买家提取其资源库的。如果您是希望管理产品的创作者,请使用官方的 [Gumroad API](https://app.gumroad.com/api)。
- **不受 Gumroad 官方支持。** 移动端 API 是未公开的;当 Gumroad 发布新应用版本时,预计会出现损坏情况。
## 限制
- 仅限 macOS。iOS 应用的容器路径是整个机制的核心。
- 仅限 Apple Silicon。iOS-on-Mac 应用无法在 Intel Mac 上运行。
- 要求应用在您登录后至少加载过一次已认证的请求。
- 某些产品使用**外部交付**——创作者将文件托管在 Gumroad 之外,仅将 Gumroad 用于购买和重定向。对于这些产品,清单中文件数为零;脚本会写入一个 README,指引您访问 gumroad.com/d/ 页面。
## 许可证
MIT。见 `LICENSE`。
## 致谢
这起初是为了提取作者想用来挖掘内容创意的一门旧课程购买记录而进行的一次偶然探索。从“抓取网页下载页面”到“移动应用已经有了数据和干净的 API,只需读取其缓存”的转变,事实证明具有很好的通用性。
标签:Apple Silicon, Gumroad, HTTP工具, iOS-on-Mac, JSON API, Mac M1 M2 M3 M4, Mac工具, NSURLCache, Python, URL抓取, 令牌提取, 原始质量, 媒体下载, 数字内容提取, 数字取证, 数字版权管理, 数据抓取, 文件下载, 无依赖, 无后门, 移动API, 绕过Cloudflare, 自动化脚本, 资料归档, 逆向分析