|
// 补填 Age Ratings(Social Media)和 inflight 版本的 Contact Information |
|
// 用法:浏览器登录 appstoreconnect.apple.com 后,F12 控制台粘贴执行 |
|
// 执行前先改 FILL 里的信息,建议先设 ONLY_APP 用一个 app 试跑 |
|
|
|
const API = 'https://appstoreconnect.apple.com/iris/v1'; |
|
const H = { 'Accept': 'application/json', 'Content-Type': 'application/json' }; |
|
|
|
// 改成你自己的信息。 |
|
// socialMedia / socialMediaAgeRestricted:按 app 实际功能,工具类一般选 NO(false) |
|
const FILL = { |
|
socialMedia: false, |
|
socialMediaAgeRestricted: false, |
|
contactFirstName: '你的名', |
|
contactLastName: '你的姓', |
|
contactPhone: '你的电话', |
|
contactEmail: '你的邮箱', |
|
}; |
|
|
|
// 试跑:只处理这一个 app,跑通后改成 null |
|
const ONLY_APP = null; // 例:'URL Redirector' |
|
|
|
const INFLIGHT = new Set([ |
|
'PREPARE_FOR_SUBMISSION', 'WAITING_FOR_REVIEW', 'IN_REVIEW', |
|
'REJECTED', 'DEVELOPER_REJECTED', 'ACCEPTED', 'READY_FOR_REVIEW', |
|
]); |
|
|
|
async function get(path) { |
|
const res = await fetch(API + path, { credentials: 'include', headers: H }); |
|
if (!res.ok) throw new Error(res.status + ' ' + path); |
|
return res.json(); |
|
} |
|
|
|
async function send(method, path, body) { |
|
const res = await fetch(API + path, { |
|
method, |
|
credentials: 'include', |
|
headers: H, |
|
body: body ? JSON.stringify(body) : undefined, |
|
}); |
|
return res; |
|
} |
|
|
|
// 版本号 +1 patch:0.19.3 -> 0.19.4 |
|
function bump(v) { |
|
const parts = String(v).split('.'); |
|
parts[parts.length - 1] = String((parseInt(parts[parts.length - 1]) || 0) + 1); |
|
return parts.join('.'); |
|
} |
|
|
|
(async () => { |
|
const { data: apps } = await get('/apps?limit=200'); |
|
const targets = apps.filter(a => !a.attributes.removed && (!ONLY_APP || a.attributes.name === ONLY_APP)); |
|
|
|
for (const app of targets) { |
|
console.log('\n== ' + app.attributes.name + ' [' + app.id + ']'); |
|
|
|
// 1. Age rating:PATCH 编辑中 appInfo 的 ard |
|
try { |
|
const ai = await get('/apps/' + app.id + '/appInfos?include=ageRatingDeclaration'); |
|
const editable = (ai.data || []).find(i => INFLIGHT.has(i.attributes.state)); |
|
const rel = editable && editable.relationships.ageRatingDeclaration.data; |
|
if (!rel) { |
|
console.log(' age rating: 没有编辑中的 appInfo,跳过'); |
|
} else { |
|
const res = await send('PATCH', '/ageRatingDeclarations/' + rel.id, { |
|
data: { |
|
type: 'ageRatingDeclarations', |
|
id: rel.id, |
|
attributes: { |
|
socialMedia: FILL.socialMedia, |
|
socialMediaAgeRestricted: FILL.socialMediaAgeRestricted, |
|
}, |
|
}, |
|
}); |
|
console.log(' age rating: ' + (res.ok ? 'ok' : res.status)); |
|
} |
|
} catch (e) { |
|
console.log(' age rating 出错: ' + e.message); |
|
} |
|
|
|
// 2. 找 inflight 版本;没有就按平台建 patch 版本 |
|
const vs = await get('/apps/' + app.id + '/appStoreVersions?limit=20'); |
|
const inflight = (vs.data || []).filter(v => INFLIGHT.has(v.attributes.appVersionState || v.attributes.appStoreState)); |
|
|
|
if (inflight.length === 0) { |
|
const latest = {}; |
|
for (const v of vs.data || []) { |
|
if (!latest[v.attributes.platform]) latest[v.attributes.platform] = v.attributes.versionString; |
|
} |
|
for (const [platform, ver] of Object.entries(latest)) { |
|
const versionString = bump(ver); |
|
const res = await send('POST', '/appStoreVersions', { |
|
data: { |
|
type: 'appStoreVersions', |
|
attributes: { platform, versionString }, |
|
relationships: { app: { data: { type: 'apps', id: app.id } } }, |
|
}, |
|
}); |
|
if (res.ok) { |
|
const j = await res.json(); |
|
inflight.push({ id: j.data.id, attributes: { platform, versionString, appVersionState: 'PREPARE_FOR_SUBMISSION' } }); |
|
console.log(' 新建版本 ' + platform + ' ' + versionString); |
|
} else { |
|
console.log(' 新建版本 ' + platform + ' ' + versionString + ' 失败: ' + res.status); |
|
} |
|
} |
|
} |
|
|
|
// 3. 每个 inflight 版本补 contact |
|
const contact = { |
|
contactFirstName: FILL.contactFirstName, |
|
contactLastName: FILL.contactLastName, |
|
contactPhone: FILL.contactPhone, |
|
contactEmail: FILL.contactEmail, |
|
}; |
|
for (const v of inflight) { |
|
const tag = v.attributes.platform + ' ' + v.attributes.versionString; |
|
try { |
|
const rdRes = await fetch(API + '/appStoreVersions/' + v.id + '/appStoreReviewDetail', { credentials: 'include', headers: H }); |
|
let method, path, body; |
|
if (rdRes.status === 404) { |
|
// 从没建过 review detail:POST 建一个 |
|
method = 'POST'; |
|
path = '/appStoreReviewDetails'; |
|
body = { |
|
data: { |
|
type: 'appStoreReviewDetails', |
|
attributes: contact, |
|
relationships: { appStoreVersion: { data: { type: 'appStoreVersions', id: v.id } } }, |
|
}, |
|
}; |
|
} else { |
|
const j = await rdRes.json(); |
|
const id = j.data && j.data.id; |
|
if (!id) { |
|
console.log(' ' + tag + ' contact: 没有 review detail,跳过'); |
|
continue; |
|
} |
|
method = 'PATCH'; |
|
path = '/appStoreReviewDetails/' + id; |
|
body = { data: { type: 'appStoreReviewDetails', id, attributes: contact } }; |
|
} |
|
const res = await send(method, path, body); |
|
console.log(' ' + tag + ' contact: ' + (res.ok ? 'ok' : res.status)); |
|
} catch (e) { |
|
console.log(' ' + tag + ' contact 出错: ' + e.message); |
|
} |
|
} |
|
} |
|
console.log('\n完成。跑一遍 verify.js 复查。'); |
|
})(); |