mcpField
G
gpc-sdk-usage

gpc-sdk-usage

github.com/yasserstudio开发137 次安装1 周前更新
代码仓库

关于 gpc-sdk-usage

gpc-sdk-usage 是一项面向 TypeScript 开发者的技能,介绍如何单独使用 @gpc-cli/api 和 @gpc-cli/auth 以编程方式访问 Google Play。内容涵盖认证、API 客户端及常见发布和运营操作。

主要功能

  1. 01配置服务账号认证
  2. 02创建类型化 API 客户端
  3. 03管理应用编辑与发布
  4. 04处理评论、订阅和购买
  5. 05分页查询与速率限制

适用场景

  1. 01从 TypeScript 服务管理 Google Play 发布
  2. 02在脚本中查询并回复应用评论
  3. 03在后端验证应用内购买
  4. 04将 Play 操作集成到现有应用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yasserstudio/gpc-skills --skill gpc-sdk-usage

安装后在 Claude Code 中输入/gpc-sdk-usage确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
api-reference.md
auth-patterns.md
scripts/
detect_gpc.mjs

详细介绍

gpc-sdk-usage

将 @gpc-cli/api 和 @gpc-cli/auth 用作独立的 TypeScript SDK 包,以编程方式访问 Google Play。

适用场景

  • 构建与 Google Play 交互的后端服务
  • 创建自定义仪表板或自动化脚本
  • 使用 TypeScript/JavaScript 以编程方式管理发布
  • 直接使用类型化 API 客户端(而非通过 CLI)
  • 将 Google Play 操作集成到更大型的应用程序中

所需输入

  • Node.js 20+ 和 TypeScript 5+
  • @gpc-cli/api 和 @gpc-cli/auth 包
  • 服务账号密钥——JSON 文件或原始 JSON 字符串

操作步骤

0. 安装包
npm install @gpc-cli/api @gpc-cli/auth

这些是独立的包,无需安装完整的 CLI。

1. 身份验证
import { resolveAuth } from "@gpc-cli/auth";

// From file path
const auth = await resolveAuth({
  serviceAccountPath: "/path/to/key.json",
});

// From JSON string (e.g., from environment variable)
const auth = await resolveAuth({
  serviceAccountJson: process.env.PLAY_SA_KEY,
});

// From environment (GPC_SERVICE_ACCOUNT or GOOGLE_APPLICATION_CREDENTIALS)
const auth = await resolveAuth();

Read: references/auth-patterns.md,了解高级身份验证模式和令牌缓存。

2. 创建 API 客户端
import { createApiClient } from "@gpc-cli/api";

const client = createApiClient({
  auth,
  maxRetries: 3,
  timeout: 30_000,
});

该客户端提供对 234 个 Google Play Developer API 端点的类型化访问,涵盖 Android Publisher v3、Play Developer Reporting v1beta1、Play Custom App Publishing v1(v0.9.56+)和 Games Configuration v1configuration(v0.9.86+)API。

2a. 创建 Enterprise 客户端(v0.9.56+)

若要通过 Play Custom App Publishing API 发布私有应用,请使用单独的工厂函数:

import { createEnterpriseClient, type CustomApp } from "@gpc-cli/api";

const enterprise = createEnterpriseClient({ auth });

const app: CustomApp = await enterprise.apps.create(
  "1234567890",              // developer account ID (int64, from Play Console URL)
  "./app.aab",                // bundle path
  {
    title: "My Private App",
    languageCode: "en_US",
    organizations: [{ organizationId: "customer-org-id" }],
  },
);

console.log("Assigned package name:", app.packageName);
// com.google.customapp.A1B2C3D4E5 (Google-assigned, you cannot influence)

注意:

  • 私有应用将永久保持私有。创建后,无法将其设为公开。
  • 创建后,后续操作(版本上传、轨道、商品详情)需使用返回的 packageName,通过常规的 createApiClient() 执行。
  • 你的服务账号需要在 Play Console 中拥有“创建并发布私有应用”权限。
  • 底层的 HttpClient.uploadCustomApp<T>(path, filePath, metadata, contentType) 方法会处理可恢复的分段上传,并在初始会话发起 POST 请求中携带 JSON 元数据。有关将此模式与其他 Google API 配合使用的方法,请参阅 ResumableUploadOptions.initialMetadata。

请参阅 gpc-enterprise 技能,了解 CLI 的对应操作方式和完整设置步骤。

Read: references/api-reference.md,查看包含所有命名空间和方法的完整客户端 API。

3. 编辑生命周期

大多数 Google Play 操作都需要编辑会话:

const APP = "com.example.app";

// 1. Create an edit
const edit = await client.edits.insert(APP);

// 2. Make changes within the edit
const tracks = await client.tracks.list(APP, edit.id);
const details = await client.details.get(APP, edit.id);

// 3. Validate before committing
await client.edits.validate(APP, edit.id);

// 4. Commit the edit (applies all changes)
await client.edits.commit(APP, edit.id);

// Optional: commit with options (v0.9.51+)
await client.edits.commit(APP, edit.id, {
  changesNotSentForReview: true,
  changesInReviewBehavior: "HALT_REVIEW",
});

重要: 同一时间只能打开一个编辑。务必提交或删除编辑。

4. 常见模式
创建自定义封闭式测试轨道(v0.9.79+)
// Create a custom closed testing track before uploading to it
const track = await client.edits.tracks.create(packageName, "my-custom-track");

必须先创建自定义轨道,才能向其分配发布。创建后,使用标准的 client.tracks.update() 调用,将发布推送到新轨道。

上传发布
const edit = await client.edits.insert(APP);

// Upload the bundle
const bundle = await client.bundles.upload(APP, edit.id, "app-release.aab");

// Upload with device tier config (v0.9.51+)
const bundle2 = await client.bundles.upload(APP, edit.id, "app-release.aab", {
  deviceTierConfigId: "my-tier-config",
});

// Set the track
await client.tracks.update(APP, edit.id, "beta", {
  track: "beta",
  releases: [{
    versionCodes: [bundle.versionCode],
    status: "completed",
    releaseNotes: [
      { language: "en-US", text: "Bug fixes and improvements" },
    ],
  }],
});

// Commit
await client.edits.validate(APP, edit.id);
await client.edits.commit(APP, edit.id);
列出评价并回复
// No edit needed for reviews
const reviews = await client.reviews.list(APP, {
  maxResults: 50,
  translationLanguage: "en",
  startIndex: 0,  // pagination offset (v0.9.51+)
});

for (const review of reviews.reviews ?? []) {
  if (review.comments?.[0]?.userComment?.starRating <= 2) {
    await client.reviews.reply(APP, review.reviewId, "Thanks for the feedback!");
  }
}
管理订阅
// No edit needed for subscriptions
const subs = await client.subscriptions.list(APP);

// Get a specific subscription
const sub = await client.subscriptions.get(APP, "premium_monthly");

// Update with mutation options (v0.9.51+)
await client.subscriptions.update(APP, "premium_monthly", data, "price", {
  allowMissing: true,
  latencyTolerance: "PRODUCT_UPDATE_LATENCY_TOLERANCE_LATENCY_TOLERANT",
});

// Activate a base plan
await client.subscriptions.activateBasePlan(APP, "premium_monthly", "monthly");
验证购买
// No edit needed for purchases
const purchase = await client.purchases.getProduct(APP, "coins_100", purchaseToken);

if (purchase.purchaseState === 0 && purchase.acknowledgementState === 0) {
  await client.purchases.acknowledgeProduct(APP, "coins_100", purchaseToken);
}
上传反混淆文件(v0.9.51+)
// Upload ProGuard mapping
await client.deobfuscation.upload(APP, edit.id, versionCode, "mapping.txt", "proguard");

// Upload native debug symbols
await client.deobfuscation.upload(APP, edit.id, versionCode, "symbols.zip", "nativeCode");
管理扩展文件(v0.9.51+)
// Get expansion file info
const obb = await client.expansionFiles.get(APP, edit.id, versionCode, "main");

// Upload a new expansion file
const uploaded = await client.expansionFiles.upload(APP, edit.id, versionCode, "main", "main.obb");

// Patch expansion file references
await client.expansionFiles.patch(APP, edit.id, versionCode, "main", {
  referencesVersion: 10,
});
分页列出一次性商品(v0.9.51+)
const products = await client.oneTimeProducts.list(APP, {
  pageSize: 25,
  pageToken: nextToken,
});
5. 分页

使用内置分页工具处理大型结果集:

import { paginate, paginateAll } from "@gpc-cli/api";

// Async generator (stream results)
for await (const page of paginate(
  (token) => client.subscriptions.list(APP, { pageToken: token }),
  { limit: 100 },
)) {
  for (const sub of page) {
    console.log(sub.productId);
  }
}

// Collect all results
const all = await paginateAll(
  (token) => client.subscriptions.list(APP, { pageToken: token }),
);

v0.9.83 新增: paginateAll 现在会返回真实的续页令牌,因此 --limit + --next-page 可以在评价、用户、购买、IAP 和订阅列表中正确续页。所有 CLI 列表命令也统一使用 { <key>, nextPageToken, meta.count, message? } JSON 信封格式(v0.9.87 起扩展至 grants、testers 和 tracks)。从列表命令读取裸数组的脚本将无法运行,请更新为解构带键的字段。

v0.9.98: client.users.list() 始终会发送 pageSize=-1,这是 Google 唯一接受的值(“目前不支持分页”)。调用方提供的 pageSize 会被忽略,并在类型中标记为弃用;整个列表会在一次响应中返回。

6. 速率限制

从 v0.9.47 开始,createApiClient() 会使用 Google 的六桶模型(每桶 3,000 次查询/分钟)自动对所有 API 调用应用速率限制。无需手动配置:

// Rate limiting is automatic — all calls are throttled by resource type
const client = createApiClient({ auth });
// Buckets: edits, purchases, reviews, reporting, monetization, default

若要自定义速率限制(例如,在多个进程间共享配额):

import { createRateLimiter, RATE_LIMIT_BUCKETS } from "@gpc-cli/api";

// Override specific buckets
const limiter = createRateLimiter([
  { ...RATE_LIMIT_BUCKETS.edits, maxTokens: 1500 },     // Half of default
  { ...RATE_LIMIT_BUCKETS.purchases, maxTokens: 1500 },
]);

const client = createApiClient({ auth, rateLimiter: limiter });

resolveBucket(path) 函数会自动将 API 路径映射到对应的桶:

  • /edits/ 路径 → edits 桶
  • /purchases/、/orders → purchases 桶
  • /reviews → reviews 桶
  • Reporting API → reporting 桶
  • /subscriptions、/oneTimeProducts、/inappproducts → monetization 桶
  • 其他所有路径 → default 桶
7. 错误处理
import { PlayApiError } from "@gpc-cli/api";
import { AuthError } from "@gpc-cli/auth";

try {
  await client.edits.insert(APP);
} catch (error) {
  if (error instanceof AuthError) {
    console.error(`Auth failed: ${error.code}`);
  } else if (error instanceof PlayApiError) {
    console.error(`API error ${error.status}: ${error.code}`);
    console.error(`Suggestion: ${error.suggestion}`);
  }
}
更新日志生成(v0.9.62+)

gpc changelog generate 的更新日志流水线以独立的 @gpc-cli/core 导出项形式提供,可直接获取经过聚类和检查的数据结构,适用于 CI 工具。

import {
  generateChangelog,
  resolveLocales,
  renderPlayStore,
  PLAY_STORE_LIMIT,     // 500
  type LocaleBundle,
  type GeneratedChangelog,
} from "@gpc-cli/core";

const generated: GeneratedChangelog = await generateChangelog({
  from: "v0.9.61",
  to: "HEAD",
});

// GitHub target: three renderers exposed as RENDERERS["md" | "json" | "prompt"]
// Play Store target: resolveLocales + renderPlayStore
const locales = await resolveLocales("en-US,fr-FR,de-DE");
const { output, bundle } = renderPlayStore(generated, {
  locales,
  format: "json",
});

for (const entry of bundle.locales) {
  console.log(`${entry.language}: ${entry.chars}/${entry.limit} (${entry.status})`);
}

对于 --locales auto,将 { client, packageName } 作为第二个参数传给 resolveLocales——它会调用 client.listings.list,从你当前的 Play Store 列表推断语言区域集合。

将发布说明应用到草稿(v0.9.64+)
import {
  applyReleaseNotes,
  validateBundleForApply,
  bundleToReleaseNotes,
  waitForBundleProcessing,
} from "@gpc-cli/core";

// Convert a LocaleBundle to the API shape
const releaseNotes = bundleToReleaseNotes(bundle);

// Validate (returns blocked locale errors, if any)
const errors = validateBundleForApply(bundle);
if (errors.length > 0) throw new Error(errors.join(", "));

// Write into the latest draft on a track
await applyReleaseNotes(client, "com.example.app", "production", releaseNotes);

// waitForBundleProcessing (v0.9.64+, extended v0.9.77): polls bundles.list
// after AAB upload with Fibonacci backoff (2s, 3s, 5s, 8s, 13s, 21s, 34s ~86s)
// until the uploaded versionCode appears. Fixes large-AAB race.
// v0.9.77 also adds multi-retry guard on validate/commit (15s, 30s, 45s).
await waitForBundleProcessing(client, "com.example.app", editId, versionCode);
配置类型中的 VitalsThresholds(v0.9.82+)

VitalsThresholds 现在属于 @gpc-cli/config 提供的类型化配置接口:

import type { GpcConfig } from "@gpc-cli/config";

const config: GpcConfig = {
  vitals: {
    thresholds: { crashRate: 2.0 },
  },
};

ResolvedConfig(完全合并后的运行时结构)中也包含 VitalsThresholds。在构建以编程方式读取或写入 GPC 配置文件的工具时,可以使用它。

Orders 上的 OfferPhaseDetails(v0.9.79+)

Orders 上的扁平 offerPhase 字符串字段已弃用。请改为读取 offerPhaseDetails:

const order = await client.purchases.orders.get(APP, orderId);
// Deprecated: order.offerPhase
// Preferred:
const phase = order.offerPhaseDetails; // OfferPhaseDetails — phase type, cycle counts, pricing
download() 指数退避(v0.9.80+)

client.download()(用于下载 APK/AAB 二进制文件)现在会自动使用指数退避重试,与 request() 的重试行为一致。无需更改代码——临时的 5xx 错误和网络超时都会自动重试。

从 Play GCS 存储桶获取批量报告(v0.9.93+)

Play 的月度批量报告是存储在与开发者账号关联的 Cloud Storage 存储桶中的 CSV 对象,而不是 Publisher API 资源。**v0.9.93 中的破坏性变更:**已从 @gpc-cli/api 移除不可用的 client.reports.list,并从 @gpc-cli/core 移除旧的 downloadReport 导出项。请改用三个新的 @gpc-cli/core 导出项。

import { resolveAuth, DEFAULT_SCOPES, STORAGE_READ_ONLY_SCOPE } from "@gpc-cli/auth";
import {
  listReports,
  downloadStatsReport,
  downloadFinancialReport,
  resolveReportsBucket,
  parseMonth,
  type ListReportsResult,
} from "@gpc-cli/core";

// The reports path needs one extra scope. Request it only here: storage-scoped tokens are
// cached under a separate key, so other commands' tokens never carry storage access.
const auth = await resolveAuth({
  serviceAccountPath: "key.json",
  scopes: [...DEFAULT_SCOPES, STORAGE_READ_ONLY_SCOPE],
});

// pubsite_prod_<developerId>, or an explicit reports.bucket value
const bucket = resolveReportsBucket({ developerId: "1234567890" });
const month = parseMonth("2026-02");

const { reports, nextPageToken }: ListReportsResult = await listReports(
  auth,
  bucket,
  "installs",
  { packageName: "com.example.app", month, maxResults: 50 },
);

// Stats: per app, one CSV per dimension (reviews reports have no dimension)
const stats = await downloadStatsReport(
  auth, bucket, "com.example.app", "installs", month, "country",
);
console.log(stats.objectName, stats.csv); // gunzipped, UTF-16 decoded to UTF-8

// Financial: account-level, ZIP archives
const fin = await downloadFinancialReport(auth, bucket, "earnings", month);
if (fin.kind === "csv") {
  console.log(fin.text);
} else {
  for (const entry of fin.entries) console.log(entry.name, entry.csv.length);
}

auth 只需满足 ReportsAuth({ getAccessToken(): Promise<string> })接口,因此任何令牌来源都可使用。失败时会抛出带有 REPORT_* 代码的 GpcError(参见 gpc-troubleshooting);首次运行时会抛出 REPORT_ACCESS_DENIED,直到在 Play Console 中为服务账号授予“查看应用信息并下载批量报告(只读)”权限。

API 正确性历史(近期)
  • v0.9.57:apprecovery.cancel/deploy URL 现在使用复数路径 /appRecoveries/。dataSafety.update 使用 POST,而非 PUT。已移除不存在的 dataSafety.get。新增 onetimeproducts.offers.activateOffer / deactivateOffer。新增 getVitalsErrorCount 函数。
  • **v0.9.58 / v0.9.59:**Vitals LMK 指标集为 lmkRateMetricSet,包含指标 userPerceivedLmkRate、userPerceivedLmkRate7dUserWeighted、userPerceivedLmkRate28dUserWeighted、distinctUsers。(v0.9.58 使用了错误的资源名称;v0.9.59 为修正后的版本。)

验证

  • resolveAuth() 返回有效的认证客户端
  • createApiClient({ auth }) 创建可用的客户端
  • client.edits.insert(APP) 成功打开编辑
  • API 调用返回类型化响应
  • 错误处理能够捕获 PlayApiError 和 AuthError

故障模式/调试

症状可能原因修复方法
AUTH_NO_CREDENTIALS未找到身份验证来源传入 serviceAccountPath 或设置 GPC_SERVICE_ACCOUNT
AUTH_INVALID_KEY密钥文件中的 JSON 无效从 Google Cloud Console 重新下载
编辑插入操作失败,返回 403服务账号没有 API 访问权限在 GCP 中启用 Google Play Developer API
并发编辑冲突另一个编辑操作仍处于打开状态先提交或删除现有编辑操作
PlayApiError,状态码为 429请求受限使用 createRateLimiter() 并设置合适的分桶
类型无法解析TypeScript 配置错误确保 moduleResolution: "bundler" 或 "node16"
client.reports.list 不是函数在 v0.9.93 中已移除(此前从未正常工作)使用 @gpc-cli/core 中的 listReports / downloadStatsReport / downloadFinancialReport
报表调用返回 REPORT_ACCESS_DENIED服务账号缺少批量报表授权在 Play Console 中启用“查看应用信息并下载批量报表(只读)”,然后重试
已授权,但报表调用仍返回 403创建令牌时未包含 devstorage.read_only将 scopes: [...DEFAULT_SCOPES, STORAGE_READ_ONLY_SCOPE] 传给 resolveAuth

相关技能

  • gpc-setup — 创建服务账号并配置身份验证
  • gpc-plugin-development — 构建内部使用 SDK 的插件
  • gpc-troubleshooting — 解读 API 错误代码

评论

支持 Markdown