优化连接过期验证与戒色信息-1
This commit is contained in:
@@ -25,6 +25,14 @@ TENCENT_BROWSER_PREWARM=true
|
|||||||
TENCENT_BROWSER_KEEP_ALIVE=true
|
TENCENT_BROWSER_KEEP_ALIVE=true
|
||||||
TENCENT_BROWSER_SLOW_MO=0
|
TENCENT_BROWSER_SLOW_MO=0
|
||||||
TENCENT_SESSION_DEBUG=false
|
TENCENT_SESSION_DEBUG=false
|
||||||
|
CLOUDTENTACLES_BIND_URL_TTL_SECONDS=300
|
||||||
|
CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS=5000
|
||||||
|
CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT=https://comm.ams.game.qq.com/ide/
|
||||||
|
CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID=323794
|
||||||
|
CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID=323794
|
||||||
|
CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN=z90Syo
|
||||||
|
CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL=http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F
|
||||||
|
CLOUDTENTACLES_BIND_URL_PROBE_REFERER=https://gp.qq.com/
|
||||||
|
|
||||||
ADMIN_SESSION_SECRET=xm4BDpbdHJvU4RIlpGqD0nvQTZtIidvQMk6LSP1d
|
ADMIN_SESSION_SECRET=xm4BDpbdHJvU4RIlpGqD0nvQTZtIidvQMk6LSP1d
|
||||||
ADMIN_SESSION_TTL_HOURS=12
|
ADMIN_SESSION_TTL_HOURS=12
|
||||||
|
|||||||
@@ -99,6 +99,16 @@ module.exports = {
|
|||||||
vnBindUrlPath: '/vn/bind_url',
|
vnBindUrlPath: '/vn/bind_url',
|
||||||
vnBindInfoPath: '/vn/bind_info',
|
vnBindInfoPath: '/vn/bind_info',
|
||||||
vnBackPath: '/vn/back',
|
vnBackPath: '/vn/back',
|
||||||
|
bindUrlTtlSeconds: 300,
|
||||||
|
bindUrlProbeTimeoutMs: 5000,
|
||||||
|
bindUrlProbeUserAgent: 'Mozilla/5.0 (Linux; Android 12; M2012K11AC Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/98.0.4758.102 MQQBrowser/6.2 TBS/046141 Mobile Safari/537.36 V1_AND_SQ_8.8.85_2674_YYB_D QQ/8.8.85.7685 NetType/WIFI WebP/0.3.0 Pixel/1080 StatusBarHeight/108 SimpleUISwitch/0 QQTheme/1000',
|
||||||
|
bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/',
|
||||||
|
bindUrlProbeChartId: '323794',
|
||||||
|
bindUrlProbeSubChartId: '323794',
|
||||||
|
bindUrlProbeIdeToken: 'z90Syo',
|
||||||
|
bindUrlProbeActivityUrl: 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F',
|
||||||
|
bindUrlProbeReferer: 'https://gp.qq.com/',
|
||||||
|
bindUrlProbeExtraCookie: '',
|
||||||
clientSource: 'ct-client',
|
clientSource: 'ct-client',
|
||||||
deviceId: '-',
|
deviceId: '-',
|
||||||
deviceType: 0,
|
deviceType: 0,
|
||||||
|
|||||||
@@ -355,6 +355,66 @@ function applyEnvOverrides(baseConfig) {
|
|||||||
nextConfig.platforms.cloudtentacles.vnBindUrlPath = cloudtentaclesVnBindUrlPath
|
nextConfig.platforms.cloudtentacles.vnBindUrlPath = cloudtentaclesVnBindUrlPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesVnBindInfoPath = String(process.env.CLOUDTENTACLES_VN_BIND_INFO_PATH || '').trim()
|
||||||
|
if (cloudtentaclesVnBindInfoPath) {
|
||||||
|
nextConfig.platforms.cloudtentacles.vnBindInfoPath = cloudtentaclesVnBindInfoPath
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesVnBackPath = String(process.env.CLOUDTENTACLES_VN_BACK_PATH || '').trim()
|
||||||
|
if (cloudtentaclesVnBackPath) {
|
||||||
|
nextConfig.platforms.cloudtentacles.vnBackPath = cloudtentaclesVnBackPath
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlTtlSeconds = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_TTL_SECONDS)
|
||||||
|
if (cloudtentaclesBindUrlTtlSeconds !== null) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlTtlSeconds = cloudtentaclesBindUrlTtlSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeTimeoutMs = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS)
|
||||||
|
if (cloudtentaclesBindUrlProbeTimeoutMs !== null) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeTimeoutMs = cloudtentaclesBindUrlProbeTimeoutMs
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeUserAgent = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_USER_AGENT || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeUserAgent) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeUserAgent = cloudtentaclesBindUrlProbeUserAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeEndpoint = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeEndpoint) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeEndpoint = cloudtentaclesBindUrlProbeEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeChartId = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeChartId) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeChartId = cloudtentaclesBindUrlProbeChartId
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeSubChartId = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeSubChartId) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeSubChartId = cloudtentaclesBindUrlProbeSubChartId
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeIdeToken = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeIdeToken) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeIdeToken = cloudtentaclesBindUrlProbeIdeToken
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeActivityUrl = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeActivityUrl) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeActivityUrl = cloudtentaclesBindUrlProbeActivityUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeReferer = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_REFERER || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeReferer) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeReferer = cloudtentaclesBindUrlProbeReferer
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudtentaclesBindUrlProbeExtraCookie = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_EXTRA_COOKIE || '').trim()
|
||||||
|
if (cloudtentaclesBindUrlProbeExtraCookie) {
|
||||||
|
nextConfig.platforms.cloudtentacles.bindUrlProbeExtraCookie = cloudtentaclesBindUrlProbeExtraCookie
|
||||||
|
}
|
||||||
|
|
||||||
const cloudtentaclesPublicKeyPem = String(process.env.CLOUDTENTACLES_PUBLIC_KEY_PEM || '').trim()
|
const cloudtentaclesPublicKeyPem = String(process.env.CLOUDTENTACLES_PUBLIC_KEY_PEM || '').trim()
|
||||||
if (cloudtentaclesPublicKeyPem) {
|
if (cloudtentaclesPublicKeyPem) {
|
||||||
nextConfig.platforms.cloudtentacles.publicKeyPem = cloudtentaclesPublicKeyPem
|
nextConfig.platforms.cloudtentacles.publicKeyPem = cloudtentaclesPublicKeyPem
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ export function mapKuaishouCloudFulfillmentContext(value) {
|
|||||||
vnPhone: String(binding.vnPhone || '').trim(),
|
vnPhone: String(binding.vnPhone || '').trim(),
|
||||||
bindUrl: String(binding.bindUrl || '').trim(),
|
bindUrl: String(binding.bindUrl || '').trim(),
|
||||||
bindPreparedAt: binding.bindPreparedAt || null,
|
bindPreparedAt: binding.bindPreparedAt || null,
|
||||||
|
bindExpiresAt: binding.bindExpiresAt || null,
|
||||||
|
bindProbeAt: binding.bindProbeAt || null,
|
||||||
|
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||||
|
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
status: String(role.status || 'pending').trim() || 'pending',
|
status: String(role.status || 'pending').trim() || 'pending',
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
normalizeKuaishouCloudFlow,
|
normalizeKuaishouCloudFlow,
|
||||||
normalizeKuaishouCloudRoleInfo,
|
normalizeKuaishouCloudRoleInfo,
|
||||||
prepareKuaishouCloudBindResourceWithFallback,
|
prepareKuaishouCloudBindResourceWithFallback,
|
||||||
|
resolveKuaishouCloudBindUrlExpiresAt,
|
||||||
resolveKuaishouCloudBindingResources,
|
resolveKuaishouCloudBindingResources,
|
||||||
resolveKuaishouCloudVnKeyCandidates,
|
resolveKuaishouCloudVnKeyCandidates,
|
||||||
resolvePersistedCloudtentaclesContext,
|
resolvePersistedCloudtentaclesContext,
|
||||||
@@ -169,6 +170,10 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
|
|||||||
vnPhone,
|
vnPhone,
|
||||||
bindUrl: preparedBinding.bindUrl,
|
bindUrl: preparedBinding.bindUrl,
|
||||||
bindPreparedAt: now,
|
bindPreparedAt: now,
|
||||||
|
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
|
||||||
|
bindProbeAt: null,
|
||||||
|
bindProbeStatus: 'pending',
|
||||||
|
bindProbeMessage: '',
|
||||||
roleName: '',
|
roleName: '',
|
||||||
roleId: '',
|
roleId: '',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { normalizeProductName } from '../../order/product-match-service.js'
|
import { normalizeProductName } from '../../order/product-match-service.js'
|
||||||
import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js'
|
import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js'
|
||||||
import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js'
|
import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js'
|
||||||
|
import { resolveCloudtentaclesConfig } from '../../platforms/cloudtentacles/shared.js'
|
||||||
import {
|
import {
|
||||||
appointCloudtentaclesVirtualNumber,
|
appointCloudtentaclesVirtualNumber,
|
||||||
backCloudtentaclesVirtualNumber,
|
backCloudtentaclesVirtualNumber,
|
||||||
@@ -48,6 +49,7 @@ export function normalizeKuaishouCloudFlow(value) {
|
|||||||
const returnNumber = source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
const returnNumber = source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
||||||
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
||||||
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
||||||
|
const bindPreparedAt = binding.bindPreparedAt || null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...source,
|
...source,
|
||||||
@@ -74,7 +76,11 @@ export function normalizeKuaishouCloudFlow(value) {
|
|||||||
vnId: Number(binding.vnId || 0) || 0,
|
vnId: Number(binding.vnId || 0) || 0,
|
||||||
vnPhone: String(binding.vnPhone || '').trim(),
|
vnPhone: String(binding.vnPhone || '').trim(),
|
||||||
bindUrl: String(binding.bindUrl || '').trim(),
|
bindUrl: String(binding.bindUrl || '').trim(),
|
||||||
bindPreparedAt: binding.bindPreparedAt || null,
|
bindPreparedAt,
|
||||||
|
bindExpiresAt: binding.bindExpiresAt || resolveKuaishouCloudBindUrlExpiresAt(bindPreparedAt),
|
||||||
|
bindProbeAt: binding.bindProbeAt || null,
|
||||||
|
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||||
|
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||||
roleName: String(binding.roleName || '').trim(),
|
roleName: String(binding.roleName || '').trim(),
|
||||||
roleId: String(binding.roleId || '').trim(),
|
roleId: String(binding.roleId || '').trim(),
|
||||||
},
|
},
|
||||||
@@ -121,14 +127,27 @@ export function normalizeKuaishouCloudFlow(value) {
|
|||||||
|
|
||||||
export function normalizeKuaishouCloudRoleInfo(value) {
|
export function normalizeKuaishouCloudRoleInfo(value) {
|
||||||
const rawInfo = value && typeof value === 'object' ? value : null
|
const rawInfo = value && typeof value === 'object' ? value : null
|
||||||
|
const nestedBindInfo = rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
|
||||||
|
const source = nestedBindInfo || rawInfo
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: String(rawInfo?.name || rawInfo?.roleName || rawInfo?.nickname || '').trim(),
|
name: String(source?.name || source?.roleName || source?.nickname || source?.sRoleName || '').trim(),
|
||||||
rid: String(rawInfo?.rid || rawInfo?.roleId || rawInfo?.uid || '').trim(),
|
rid: String(source?.rid || source?.roleId || source?.uid || source?.sRoleId || source?.sUserId || '').trim(),
|
||||||
rawInfo,
|
rawInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
|
||||||
|
const preparedTime = Date.parse(String(preparedAt || ''))
|
||||||
|
if (!Number.isFinite(preparedTime)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = resolveCloudtentaclesConfig()
|
||||||
|
const ttlSeconds = Number(config.bindUrlTtlSeconds || 300)
|
||||||
|
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
|
export function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
|
||||||
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
||||||
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export async function confirmKuaishouCloudClaimRole(token) {
|
|||||||
source: 'claim_page_role_confirm',
|
source: 'claim_page_role_confirm',
|
||||||
actor: { source: 'claim_page' },
|
actor: { source: 'claim_page' },
|
||||||
recordEvent: false,
|
recordEvent: false,
|
||||||
|
forceProbe: true,
|
||||||
})
|
})
|
||||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(refreshed.task).kuaishouCloudFulfillment)
|
const flow = normalizeKuaishouCloudFlow(parseTaskContext(refreshed.task).kuaishouCloudFulfillment)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
normalizeKuaishouCloudFlow,
|
normalizeKuaishouCloudFlow,
|
||||||
|
probeKuaishouCloudTaskBindUrl,
|
||||||
refreshKuaishouCloudTaskRoleInfo,
|
refreshKuaishouCloudTaskRoleInfo,
|
||||||
} from '../fulfillment/kuaishou-cloud-task-service.js'
|
} from '../fulfillment/kuaishou-cloud-task-service.js'
|
||||||
|
|
||||||
@@ -10,11 +11,13 @@ export async function syncKuaishouCloudRoleInfo(task) {
|
|||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flow.binding.roleName && flow.binding.roleId) {
|
|
||||||
return task
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const probed = await probeKuaishouCloudTaskBindUrl(task, {
|
||||||
|
source: 'claim_page_polling_bind_url_probe',
|
||||||
|
actor: { source: 'system' },
|
||||||
|
})
|
||||||
|
task = probed.task
|
||||||
|
|
||||||
const result = await refreshKuaishouCloudTaskRoleInfo(task, {
|
const result = await refreshKuaishouCloudTaskRoleInfo(task, {
|
||||||
source: 'claim_page_polling',
|
source: 'claim_page_polling',
|
||||||
actor: { source: 'system' },
|
actor: { source: 'system' },
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ export function mapClaimKuaishouCloudFulfillment(task, order) {
|
|||||||
vnId: Number(binding.vnId || 0) || 0,
|
vnId: Number(binding.vnId || 0) || 0,
|
||||||
bindUrl: String(binding.bindUrl || '').trim(),
|
bindUrl: String(binding.bindUrl || '').trim(),
|
||||||
bindPreparedAt: binding.bindPreparedAt || null,
|
bindPreparedAt: binding.bindPreparedAt || null,
|
||||||
|
bindExpiresAt: binding.bindExpiresAt || null,
|
||||||
|
bindProbeAt: binding.bindProbeAt || null,
|
||||||
|
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||||
|
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||||
vnPhone: String(binding.vnPhone || '').trim(),
|
vnPhone: String(binding.vnPhone || '').trim(),
|
||||||
roleName: String(binding.roleName || '').trim(),
|
roleName: String(binding.roleName || '').trim(),
|
||||||
roleId: String(binding.roleId || '').trim(),
|
roleId: String(binding.roleId || '').trim(),
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import {
|
|||||||
generateCloudtentaclesLoginCode,
|
generateCloudtentaclesLoginCode,
|
||||||
getCloudtentaclesBindInfo,
|
getCloudtentaclesBindInfo,
|
||||||
getCloudtentaclesBindUrl,
|
getCloudtentaclesBindUrl,
|
||||||
|
probeCloudtentaclesBindUrl,
|
||||||
verifyCloudtentaclesLoginCode,
|
verifyCloudtentaclesLoginCode,
|
||||||
} from '../platforms/cloudtentacles/virtual-number-service.js'
|
} from '../platforms/cloudtentacles/virtual-number-service.js'
|
||||||
|
import { resolveCloudtentaclesConfig } from '../platforms/cloudtentacles/shared.js'
|
||||||
import { consumeKuaishouEticket } from '../platforms/kuaishou-eticket/consume-service.js'
|
import { consumeKuaishouEticket } from '../platforms/kuaishou-eticket/consume-service.js'
|
||||||
import {
|
import {
|
||||||
getKuaishouEticketSourceConfig,
|
getKuaishouEticketSourceConfig,
|
||||||
@@ -49,6 +51,8 @@ export function normalizeKuaishouCloudFlow(value) {
|
|||||||
|
|
||||||
const roleName = String(role.name || binding.roleName || '').trim()
|
const roleName = String(role.name || binding.roleName || '').trim()
|
||||||
const roleId = String(role.rid || binding.roleId || '').trim()
|
const roleId = String(role.rid || binding.roleId || '').trim()
|
||||||
|
const bindPreparedAt = binding.bindPreparedAt || null
|
||||||
|
const bindExpiresAt = binding.bindExpiresAt || resolveKuaishouCloudBindUrlExpiresAt(bindPreparedAt)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...source,
|
...source,
|
||||||
@@ -75,7 +79,11 @@ export function normalizeKuaishouCloudFlow(value) {
|
|||||||
vnId: Number(binding.vnId || 0) || 0,
|
vnId: Number(binding.vnId || 0) || 0,
|
||||||
vnPhone: String(binding.vnPhone || '').trim(),
|
vnPhone: String(binding.vnPhone || '').trim(),
|
||||||
bindUrl: String(binding.bindUrl || '').trim(),
|
bindUrl: String(binding.bindUrl || '').trim(),
|
||||||
bindPreparedAt: binding.bindPreparedAt || null,
|
bindPreparedAt,
|
||||||
|
bindExpiresAt,
|
||||||
|
bindProbeAt: binding.bindProbeAt || null,
|
||||||
|
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||||
|
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||||
roleName,
|
roleName,
|
||||||
roleId,
|
roleId,
|
||||||
},
|
},
|
||||||
@@ -123,10 +131,12 @@ export function normalizeKuaishouCloudFlow(value) {
|
|||||||
|
|
||||||
export function normalizeKuaishouCloudRoleInfo(value) {
|
export function normalizeKuaishouCloudRoleInfo(value) {
|
||||||
const rawInfo = value && typeof value === 'object' ? value : null
|
const rawInfo = value && typeof value === 'object' ? value : null
|
||||||
|
const nestedBindInfo = rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
|
||||||
|
const source = nestedBindInfo || rawInfo
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: String(rawInfo?.name || rawInfo?.roleName || rawInfo?.nickname || '').trim(),
|
name: String(source?.name || source?.roleName || source?.nickname || source?.sRoleName || '').trim(),
|
||||||
rid: String(rawInfo?.rid || rawInfo?.roleId || rawInfo?.uid || '').trim(),
|
rid: String(source?.rid || source?.roleId || source?.uid || source?.sRoleId || source?.sUserId || '').trim(),
|
||||||
rawInfo,
|
rawInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,6 +198,14 @@ export async function prepareKuaishouCloudFulfillmentTask(task, options = {}) {
|
|||||||
const claimLinkState = await ensureTaskClaimLink(task)
|
const claimLinkState = await ensureTaskClaimLink(task)
|
||||||
|
|
||||||
if (!force && flow.binding.prepareStatus === 'ready' && flow.binding.vnId > 0 && flow.binding.vnPhone && flow.binding.bindUrl) {
|
if (!force && flow.binding.prepareStatus === 'ready' && flow.binding.vnId > 0 && flow.binding.vnPhone && flow.binding.bindUrl) {
|
||||||
|
if (!isKuaishouCloudBindUrlFresh(flow)) {
|
||||||
|
return refreshKuaishouCloudTaskBindUrl(task, {
|
||||||
|
source: options.source || 'system_refresh_expired_bind_url',
|
||||||
|
actor,
|
||||||
|
claimLinkState,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let readyTask = task
|
let readyTask = task
|
||||||
|
|
||||||
if (String(task.task_status || '').trim() !== 'waiting_binding') {
|
if (String(task.task_status || '').trim() !== 'waiting_binding') {
|
||||||
@@ -300,6 +318,10 @@ export async function prepareKuaishouCloudFulfillmentTask(task, options = {}) {
|
|||||||
vnPhone: preparedBinding.vnPhone,
|
vnPhone: preparedBinding.vnPhone,
|
||||||
bindUrl: preparedBinding.bindUrl,
|
bindUrl: preparedBinding.bindUrl,
|
||||||
bindPreparedAt: now,
|
bindPreparedAt: now,
|
||||||
|
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
|
||||||
|
bindProbeAt: null,
|
||||||
|
bindProbeStatus: 'pending',
|
||||||
|
bindProbeMessage: '',
|
||||||
roleName: '',
|
roleName: '',
|
||||||
roleId: '',
|
roleId: '',
|
||||||
},
|
},
|
||||||
@@ -356,6 +378,180 @@ export async function prepareKuaishouCloudFulfillmentTask(task, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function refreshKuaishouCloudTaskBindUrl(task, options = {}) {
|
||||||
|
if (!isKuaishouCloudTask(task)) {
|
||||||
|
throw createHttpError('当前任务不是快手 Cloud 履约任务', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'kuaishou_cloud_task_invalid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = nowIso()
|
||||||
|
const actor = normalizeActor(options.actor)
|
||||||
|
const taskContext = parseTaskContext(task)
|
||||||
|
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
||||||
|
|
||||||
|
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
||||||
|
throw createHttpError('当前任务缺少可刷新绑定链接的虚拟号信息', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'kuaishou_cloud_missing_bind_url_context',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudContext = resolvePersistedCloudtentaclesContext()
|
||||||
|
const bindUrlResult = await getCloudtentaclesBindUrl({
|
||||||
|
...cloudContext,
|
||||||
|
key: flow.binding.vnKey,
|
||||||
|
id: flow.binding.vnId,
|
||||||
|
})
|
||||||
|
const bindUrl = String(bindUrlResult.bindUrl || '').trim()
|
||||||
|
|
||||||
|
if (!bindUrl) {
|
||||||
|
throw createHttpError('cloudtentacles 未返回新的绑定链接', {
|
||||||
|
statusCode: 502,
|
||||||
|
errorCode: 'kuaishou_cloud_empty_bind_url',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextContext = {
|
||||||
|
...taskContext,
|
||||||
|
kuaishouCloudFulfillment: {
|
||||||
|
...flow,
|
||||||
|
binding: {
|
||||||
|
...flow.binding,
|
||||||
|
prepareStatus: 'ready',
|
||||||
|
bindUrl,
|
||||||
|
bindPreparedAt: now,
|
||||||
|
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
|
||||||
|
bindProbeAt: null,
|
||||||
|
bindProbeStatus: 'pending',
|
||||||
|
bindProbeMessage: '',
|
||||||
|
roleName: String(task.task_status || '').trim() === 'role_confirmed' ? flow.binding.roleName : '',
|
||||||
|
roleId: String(task.task_status || '').trim() === 'role_confirmed' ? flow.binding.roleId : '',
|
||||||
|
},
|
||||||
|
role: String(task.task_status || '').trim() === 'role_confirmed'
|
||||||
|
? flow.role
|
||||||
|
: {
|
||||||
|
status: 'pending',
|
||||||
|
name: '',
|
||||||
|
rid: '',
|
||||||
|
refreshedAt: null,
|
||||||
|
errorMessage: '',
|
||||||
|
rawInfo: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimLinkState = options.claimLinkState || await ensureTaskClaimLink(task)
|
||||||
|
const updatedTask = await updateTask(task.id, {
|
||||||
|
task_status: String(task.task_status || '').trim() === 'role_confirmed' ? task.task_status : 'waiting_binding',
|
||||||
|
claim_token: claimLinkState.token || task.claim_token || '',
|
||||||
|
claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task),
|
||||||
|
role_id: String(task.task_status || '').trim() === 'role_confirmed' ? task.role_id : '',
|
||||||
|
role_name: String(task.task_status || '').trim() === 'role_confirmed' ? task.role_name : '',
|
||||||
|
last_error: '',
|
||||||
|
context_json: JSON.stringify(nextContext),
|
||||||
|
updated_at: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
await createTaskEvent(task.id, 'kuaishou_cloud_bind_url_refreshed', {
|
||||||
|
source: String(options.source || 'system').trim() || 'system',
|
||||||
|
vnId: flow.binding.vnId,
|
||||||
|
vnPhoneMasked: maskPhone(flow.binding.vnPhone),
|
||||||
|
actor,
|
||||||
|
}, now)
|
||||||
|
|
||||||
|
return {
|
||||||
|
task: updatedTask,
|
||||||
|
claimUrl: claimLinkState.claimUrl,
|
||||||
|
token: claimLinkState.token,
|
||||||
|
flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function probeKuaishouCloudTaskBindUrl(task, options = {}) {
|
||||||
|
if (!isKuaishouCloudTask(task)) {
|
||||||
|
throw createHttpError('当前任务不是快手 Cloud 履约任务', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'kuaishou_cloud_task_invalid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = nowIso()
|
||||||
|
const taskContext = parseTaskContext(task)
|
||||||
|
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
||||||
|
|
||||||
|
if (!flow.binding.bindUrl) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
flow,
|
||||||
|
probe: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.force && flow.binding.bindProbeAt && Date.now() - Date.parse(flow.binding.bindProbeAt) < 15000) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
flow,
|
||||||
|
probe: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const probe = await probeCloudtentaclesBindUrl({
|
||||||
|
bindUrl: flow.binding.bindUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (probe.expired || !isKuaishouCloudBindUrlFresh(flow)) {
|
||||||
|
const refreshed = await refreshKuaishouCloudTaskBindUrl(task, {
|
||||||
|
source: options.source || 'claim_page_bind_url_expired',
|
||||||
|
actor: options.actor || { source: 'system' },
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...refreshed,
|
||||||
|
probe,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const probeRoleInfo = normalizeKuaishouCloudRoleInfo(probe.roleInfo)
|
||||||
|
const hasRoleInfo = Boolean(probeRoleInfo.name || probeRoleInfo.rid)
|
||||||
|
const nextContext = {
|
||||||
|
...taskContext,
|
||||||
|
kuaishouCloudFulfillment: {
|
||||||
|
...flow,
|
||||||
|
binding: {
|
||||||
|
...flow.binding,
|
||||||
|
bindProbeAt: now,
|
||||||
|
bindProbeStatus: probe.valid ? 'valid' : 'invalid',
|
||||||
|
bindProbeMessage: String(probe.message || probe.reason || '').trim(),
|
||||||
|
roleName: hasRoleInfo ? probeRoleInfo.name : flow.binding.roleName,
|
||||||
|
roleId: hasRoleInfo ? probeRoleInfo.rid : flow.binding.roleId,
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
...flow.role,
|
||||||
|
status: hasRoleInfo ? 'ready' : flow.role.status,
|
||||||
|
name: hasRoleInfo ? probeRoleInfo.name : flow.role.name,
|
||||||
|
rid: hasRoleInfo ? probeRoleInfo.rid : flow.role.rid,
|
||||||
|
refreshedAt: hasRoleInfo ? now : flow.role.refreshedAt,
|
||||||
|
errorMessage: hasRoleInfo ? '' : flow.role.errorMessage,
|
||||||
|
rawInfo: hasRoleInfo ? probeRoleInfo.rawInfo : flow.role.rawInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedTask = await updateTask(task.id, {
|
||||||
|
role_id: hasRoleInfo ? probeRoleInfo.rid : task.role_id,
|
||||||
|
role_name: hasRoleInfo ? probeRoleInfo.name : task.role_name,
|
||||||
|
context_json: JSON.stringify(nextContext),
|
||||||
|
updated_at: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
task: updatedTask,
|
||||||
|
flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
|
||||||
|
probe,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function refreshKuaishouCloudTaskRoleInfo(task, options = {}) {
|
export async function refreshKuaishouCloudTaskRoleInfo(task, options = {}) {
|
||||||
if (!isKuaishouCloudTask(task)) {
|
if (!isKuaishouCloudTask(task)) {
|
||||||
throw createHttpError('当前任务不是快手 Cloud 履约任务', {
|
throw createHttpError('当前任务不是快手 Cloud 履约任务', {
|
||||||
@@ -376,6 +572,34 @@ export async function refreshKuaishouCloudTaskRoleInfo(task, options = {}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flow.binding.bindUrl) {
|
||||||
|
const probed = await probeKuaishouCloudTaskBindUrl(task, {
|
||||||
|
source: options.source || 'system_role_refresh_bind_url_probe',
|
||||||
|
actor,
|
||||||
|
force: options.forceProbe === true,
|
||||||
|
})
|
||||||
|
const probedFlow = normalizeKuaishouCloudFlow(parseTaskContext(probed.task).kuaishouCloudFulfillment)
|
||||||
|
if (probed.probe?.expired) {
|
||||||
|
return {
|
||||||
|
task: probed.task,
|
||||||
|
roleInfo: normalizeKuaishouCloudRoleInfo(null),
|
||||||
|
flow: probedFlow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((probed.probe === null || probed.probe?.valid) && (probedFlow.binding.roleName || probedFlow.binding.roleId)) {
|
||||||
|
return {
|
||||||
|
task: probed.task,
|
||||||
|
roleInfo: {
|
||||||
|
name: probedFlow.binding.roleName,
|
||||||
|
rid: probedFlow.binding.roleId,
|
||||||
|
rawInfo: probedFlow.role.rawInfo,
|
||||||
|
},
|
||||||
|
flow: probedFlow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const cloudContext = resolvePersistedCloudtentaclesContext()
|
const cloudContext = resolvePersistedCloudtentaclesContext()
|
||||||
const bindInfoResult = await getCloudtentaclesBindInfo({
|
const bindInfoResult = await getCloudtentaclesBindInfo({
|
||||||
...cloudContext,
|
...cloudContext,
|
||||||
@@ -701,6 +925,36 @@ export function maskCode(value) {
|
|||||||
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
|
||||||
|
const preparedTime = Date.parse(String(preparedAt || ''))
|
||||||
|
if (!Number.isFinite(preparedTime)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = resolveCloudtentaclesConfig()
|
||||||
|
const ttlSeconds = Number(config.bindUrlTtlSeconds || 300)
|
||||||
|
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isKuaishouCloudBindUrlFresh(flow, now = new Date()) {
|
||||||
|
const normalizedFlow = normalizeKuaishouCloudFlow(flow)
|
||||||
|
if (!normalizedFlow.binding.bindUrl) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiresAt = normalizedFlow.binding.bindExpiresAt
|
||||||
|
if (!expiresAt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiresTime = Date.parse(String(expiresAt || ''))
|
||||||
|
if (!Number.isFinite(expiresTime)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return expiresTime > now.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
|
function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
|
||||||
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
||||||
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ export async function syncDeliveryTasksForOrder(order, orderItems) {
|
|||||||
vnPhone: '',
|
vnPhone: '',
|
||||||
bindUrl: '',
|
bindUrl: '',
|
||||||
bindPreparedAt: null,
|
bindPreparedAt: null,
|
||||||
|
bindExpiresAt: null,
|
||||||
|
bindProbeAt: null,
|
||||||
|
bindProbeStatus: '',
|
||||||
|
bindProbeMessage: '',
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
|
|||||||
vnBindUrlPath: '/vn/bind_url',
|
vnBindUrlPath: '/vn/bind_url',
|
||||||
vnBindInfoPath: '/vn/bind_info',
|
vnBindInfoPath: '/vn/bind_info',
|
||||||
vnBackPath: '/vn/back',
|
vnBackPath: '/vn/back',
|
||||||
|
bindUrlTtlSeconds: 300,
|
||||||
|
bindUrlProbeTimeoutMs: 5000,
|
||||||
|
bindUrlProbeUserAgent: '',
|
||||||
|
bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/',
|
||||||
|
bindUrlProbeChartId: '323794',
|
||||||
|
bindUrlProbeSubChartId: '323794',
|
||||||
|
bindUrlProbeIdeToken: 'z90Syo',
|
||||||
|
bindUrlProbeActivityUrl: 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F',
|
||||||
|
bindUrlProbeReferer: 'https://gp.qq.com/',
|
||||||
|
bindUrlProbeExtraCookie: '',
|
||||||
publicKeyPem: '',
|
publicKeyPem: '',
|
||||||
clientSource: 'ct-client',
|
clientSource: 'ct-client',
|
||||||
deviceId: '-',
|
deviceId: '-',
|
||||||
@@ -60,6 +70,16 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
|
|||||||
vnBindUrlPath: normalizePath(overrides.vnBindUrlPath || baseConfig.vnBindUrlPath, '/vn/bind_url'),
|
vnBindUrlPath: normalizePath(overrides.vnBindUrlPath || baseConfig.vnBindUrlPath, '/vn/bind_url'),
|
||||||
vnBindInfoPath: normalizePath(overrides.vnBindInfoPath || baseConfig.vnBindInfoPath, '/vn/bind_info'),
|
vnBindInfoPath: normalizePath(overrides.vnBindInfoPath || baseConfig.vnBindInfoPath, '/vn/bind_info'),
|
||||||
vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'),
|
vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'),
|
||||||
|
bindUrlTtlSeconds: normalizePositiveInteger(overrides.bindUrlTtlSeconds || baseConfig.bindUrlTtlSeconds, 300),
|
||||||
|
bindUrlProbeTimeoutMs: normalizePositiveInteger(overrides.bindUrlProbeTimeoutMs || baseConfig.bindUrlProbeTimeoutMs, 5000),
|
||||||
|
bindUrlProbeUserAgent: String(overrides.bindUrlProbeUserAgent || baseConfig.bindUrlProbeUserAgent || '').trim(),
|
||||||
|
bindUrlProbeEndpoint: String(overrides.bindUrlProbeEndpoint || baseConfig.bindUrlProbeEndpoint || 'https://comm.ams.game.qq.com/ide/').trim(),
|
||||||
|
bindUrlProbeChartId: String(overrides.bindUrlProbeChartId || baseConfig.bindUrlProbeChartId || '323794').trim(),
|
||||||
|
bindUrlProbeSubChartId: String(overrides.bindUrlProbeSubChartId || baseConfig.bindUrlProbeSubChartId || '323794').trim(),
|
||||||
|
bindUrlProbeIdeToken: String(overrides.bindUrlProbeIdeToken || baseConfig.bindUrlProbeIdeToken || 'z90Syo').trim(),
|
||||||
|
bindUrlProbeActivityUrl: String(overrides.bindUrlProbeActivityUrl || baseConfig.bindUrlProbeActivityUrl || 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F').trim(),
|
||||||
|
bindUrlProbeReferer: String(overrides.bindUrlProbeReferer || baseConfig.bindUrlProbeReferer || 'https://gp.qq.com/').trim(),
|
||||||
|
bindUrlProbeExtraCookie: String(overrides.bindUrlProbeExtraCookie || baseConfig.bindUrlProbeExtraCookie || '').trim(),
|
||||||
publicKeyPem: normalizePem(overrides.publicKeyPem || baseConfig.publicKeyPem),
|
publicKeyPem: normalizePem(overrides.publicKeyPem || baseConfig.publicKeyPem),
|
||||||
clientSource: String(overrides.clientSource || baseConfig.clientSource || 'ct-client').trim() || 'ct-client',
|
clientSource: String(overrides.clientSource || baseConfig.clientSource || 'ct-client').trim() || 'ct-client',
|
||||||
deviceId: String(overrides.deviceId ?? baseConfig.deviceId ?? '-').trim() || '-',
|
deviceId: String(overrides.deviceId ?? baseConfig.deviceId ?? '-').trim() || '-',
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
import http from 'node:http'
|
||||||
|
import https from 'node:https'
|
||||||
|
|
||||||
import { createHttpError } from '../../../utils/http.js'
|
import { createHttpError } from '../../../utils/http.js'
|
||||||
import { cloudtentaclesRequest } from './http-client.js'
|
import { cloudtentaclesRequest } from './http-client.js'
|
||||||
import { resolveCloudtentaclesConfig } from './shared.js'
|
import { resolveCloudtentaclesConfig } from './shared.js'
|
||||||
|
|
||||||
|
const BIND_URL_PROBE_MAX_BODY_BYTES = 64 * 1024
|
||||||
|
const AMS_SIGNATURE_EXPIRED_CODE = '99998'
|
||||||
|
|
||||||
export async function listCloudtentaclesVirtualNumbers(payload = {}) {
|
export async function listCloudtentaclesVirtualNumbers(payload = {}) {
|
||||||
const token = requireToken(payload.token, 'cloudtentacles 虚拟号列表缺少 token', 'cloudtentacles_vn_list_missing_token')
|
const token = requireToken(payload.token, 'cloudtentacles 虚拟号列表缺少 token', 'cloudtentacles_vn_list_missing_token')
|
||||||
const key = requireKey(payload.key, 'cloudtentacles 虚拟号列表缺少 key', 'cloudtentacles_vn_list_missing_key')
|
const key = requireKey(payload.key, 'cloudtentacles 虚拟号列表缺少 key', 'cloudtentacles_vn_list_missing_key')
|
||||||
@@ -159,6 +165,80 @@ export async function getCloudtentaclesBindUrl(payload = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function probeCloudtentaclesBindUrl(payload = {}) {
|
||||||
|
const bindUrl = String(payload.bindUrl || '').trim()
|
||||||
|
if (!bindUrl) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
status: 0,
|
||||||
|
finalUrl: '',
|
||||||
|
reason: 'empty_url',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = resolveCloudtentaclesConfig(payload)
|
||||||
|
const signatureParams = extractBindUrlSignatureParams(bindUrl)
|
||||||
|
if (!signatureParams) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
expired: false,
|
||||||
|
status: 0,
|
||||||
|
finalUrl: bindUrl,
|
||||||
|
reason: 'missing_signature_params',
|
||||||
|
roleInfo: normalizeAmsBindRoleInfo(null),
|
||||||
|
raw: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutMs = Number(payload.timeoutMs || config.bindUrlProbeTimeoutMs || 5000)
|
||||||
|
const userAgent = String(payload.userAgent || config.bindUrlProbeUserAgent || '').trim()
|
||||||
|
const endpoint = String(payload.endpoint || config.bindUrlProbeEndpoint || 'https://comm.ams.game.qq.com/ide/').trim()
|
||||||
|
const startedAt = Date.now()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await requestAmsIdeProbe(endpoint, {
|
||||||
|
timeoutMs,
|
||||||
|
body: buildAmsIdeProbeBody(signatureParams, config, payload),
|
||||||
|
headers: buildAmsIdeProbeHeaders(signatureParams, config, {
|
||||||
|
userAgent,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
const raw = tryParseJson(response.bodyText)
|
||||||
|
const expired = isAmsSignatureExpired(raw)
|
||||||
|
const bindInfo = extractAmsBindInfo(raw)
|
||||||
|
const roleInfo = normalizeAmsBindRoleInfo(bindInfo)
|
||||||
|
const ok = response.status >= 200
|
||||||
|
&& response.status < 300
|
||||||
|
&& !expired
|
||||||
|
&& isAmsBusinessOk(raw)
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: ok,
|
||||||
|
expired,
|
||||||
|
status: response.status,
|
||||||
|
finalUrl: endpoint,
|
||||||
|
ret: String(raw?.ret ?? raw?.iRet ?? ''),
|
||||||
|
iRet: String(raw?.iRet ?? raw?.jData?.iRet ?? ''),
|
||||||
|
message: String(raw?.sMsg || raw?.jData?.sMsg || ''),
|
||||||
|
reason: expired ? 'signature_expired' : (ok ? 'ok' : 'ams_business_not_ok'),
|
||||||
|
roleInfo,
|
||||||
|
raw,
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
expired: false,
|
||||||
|
status: 0,
|
||||||
|
finalUrl: bindUrl,
|
||||||
|
reason: error instanceof Error ? error.message : String(error || 'probe_failed'),
|
||||||
|
roleInfo: normalizeAmsBindRoleInfo(null),
|
||||||
|
raw: null,
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getCloudtentaclesBindInfo(payload = {}) {
|
export async function getCloudtentaclesBindInfo(payload = {}) {
|
||||||
const token = requireToken(payload.token, 'cloudtentacles 获取绑定信息缺少 token', 'cloudtentacles_vn_bind_info_missing_token')
|
const token = requireToken(payload.token, 'cloudtentacles 获取绑定信息缺少 token', 'cloudtentacles_vn_bind_info_missing_token')
|
||||||
const key = requireKey(payload.key, 'cloudtentacles 获取绑定信息缺少 key', 'cloudtentacles_vn_bind_info_missing_key')
|
const key = requireKey(payload.key, 'cloudtentacles 获取绑定信息缺少 key', 'cloudtentacles_vn_bind_info_missing_key')
|
||||||
@@ -275,6 +355,242 @@ function parseBindInfo(value) {
|
|||||||
return tryParseJson(bindInfoText) || bindInfoText || null
|
return tryParseJson(bindInfoText) || bindInfoText || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function requestAmsIdeProbe(endpoint, options = {}) {
|
||||||
|
const url = new URL(endpoint)
|
||||||
|
if (!['http:', 'https:'].includes(url.protocol)) {
|
||||||
|
throw new Error('unsupported_ams_probe_protocol')
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutMs = Number(options.timeoutMs || 5000)
|
||||||
|
const isHttps = url.protocol === 'https:'
|
||||||
|
const transport = isHttps ? https : http
|
||||||
|
const body = options.body instanceof URLSearchParams ? options.body.toString() : String(options.body || '')
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = transport.request(url, {
|
||||||
|
method: 'POST',
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
headers: {
|
||||||
|
...normalizeHeaderMap(options.headers),
|
||||||
|
'content-length': Buffer.byteLength(body),
|
||||||
|
'accept-encoding': 'identity',
|
||||||
|
},
|
||||||
|
}, (response) => {
|
||||||
|
const chunks = []
|
||||||
|
let receivedBytes = 0
|
||||||
|
|
||||||
|
response.on('data', (chunk) => {
|
||||||
|
if (receivedBytes >= BIND_URL_PROBE_MAX_BODY_BYTES) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
||||||
|
const remainingBytes = BIND_URL_PROBE_MAX_BODY_BYTES - receivedBytes
|
||||||
|
chunks.push(buffer.length > remainingBytes ? buffer.subarray(0, remainingBytes) : buffer)
|
||||||
|
receivedBytes += Math.min(buffer.length, remainingBytes)
|
||||||
|
})
|
||||||
|
|
||||||
|
response.on('end', () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
resolve({
|
||||||
|
status: Number(response.statusCode || 0),
|
||||||
|
headers: createHeaderAdapter(response.headers),
|
||||||
|
bodyText: Buffer.concat(chunks).toString('utf8'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
request.destroy(new Error('bind_url_probe_timeout'))
|
||||||
|
}, timeoutMs)
|
||||||
|
|
||||||
|
request.on('error', (error) => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
request.write(body)
|
||||||
|
request.end()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHeaderAdapter(headers) {
|
||||||
|
const normalized = new Map()
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(headers || {})) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
normalized.set(String(key || '').toLowerCase(), value.join(', '))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
normalized.set(String(key || '').toLowerCase(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get(name) {
|
||||||
|
return normalized.get(String(name || '').toLowerCase()) || null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAmsIdeProbeBody(signatureParams, config, payload = {}) {
|
||||||
|
const body = new URLSearchParams()
|
||||||
|
const chartId = String(payload.chartId || config.bindUrlProbeChartId || '323794').trim()
|
||||||
|
const subChartId = String(payload.subChartId || config.bindUrlProbeSubChartId || chartId).trim()
|
||||||
|
|
||||||
|
body.set('iChartId', chartId)
|
||||||
|
body.set('iSubChartId', subChartId)
|
||||||
|
body.set('sIdeToken', String(payload.ideToken || config.bindUrlProbeIdeToken || 'z90Syo').trim())
|
||||||
|
body.set('e_code', '0')
|
||||||
|
body.set('g_code', '0')
|
||||||
|
body.set('eas_url', String(payload.easUrl || config.bindUrlProbeActivityUrl || 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F').trim())
|
||||||
|
body.set('eas_refer', String(payload.easRefer || `http%3A%2F%2Fnoreferrer%2F%3Freqid%3D${Date.now()}%26version%3D27`).trim())
|
||||||
|
body.set('sMiloTag', String(payload.miloTag || `AMS-gp-${Date.now()}`).trim())
|
||||||
|
body.set('userId', signatureParams.userId)
|
||||||
|
body.set('timestamp', signatureParams.timestamp)
|
||||||
|
body.set('nonce', signatureParams.nonce)
|
||||||
|
body.set('sign', signatureParams.sign)
|
||||||
|
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAmsIdeProbeHeaders(signatureParams, config, options = {}) {
|
||||||
|
const userAgent = String(options.userAgent || '').trim()
|
||||||
|
const referer = String(config.bindUrlProbeReferer || 'https://gp.qq.com/').trim()
|
||||||
|
|
||||||
|
return {
|
||||||
|
accept: 'application/json, text/plain, */*',
|
||||||
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
|
'accept-language': 'zh-CN,zh;q=0.9,fr;q=0.8,de;q=0.7,en;q=0.6',
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
origin: 'https://gp.qq.com',
|
||||||
|
pragma: 'no-cache',
|
||||||
|
referer,
|
||||||
|
'sec-ch-ua': '""',
|
||||||
|
'sec-ch-ua-mobile': '?1',
|
||||||
|
'sec-ch-ua-platform': '""',
|
||||||
|
'sec-fetch-dest': 'empty',
|
||||||
|
'sec-fetch-mode': 'cors',
|
||||||
|
'sec-fetch-site': 'same-site',
|
||||||
|
...(userAgent ? { 'user-agent': userAgent } : {}),
|
||||||
|
cookie: buildAmsIdeProbeCookie(signatureParams, config),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAmsIdeProbeCookie(signatureParams, config) {
|
||||||
|
const tokenParams = `?userId=${signatureParams.userId}×tamp=${signatureParams.timestamp}&nonce=${signatureParams.nonce}&sign=${signatureParams.sign}`
|
||||||
|
const cookies = [
|
||||||
|
`tokenParams=${encodeURIComponent(tokenParams)}`,
|
||||||
|
'gpqqcomrouteLine=a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc',
|
||||||
|
String(config.bindUrlProbeExtraCookie || '').trim(),
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
|
return cookies.join('; ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractBindUrlSignatureParams(bindUrl) {
|
||||||
|
try {
|
||||||
|
const url = new URL(bindUrl)
|
||||||
|
const params = new URLSearchParams(url.search)
|
||||||
|
const hashQueryIndex = String(url.hash || '').indexOf('?')
|
||||||
|
if (hashQueryIndex >= 0) {
|
||||||
|
const hashParams = new URLSearchParams(String(url.hash || '').slice(hashQueryIndex + 1))
|
||||||
|
for (const [key, value] of hashParams.entries()) {
|
||||||
|
if (!params.has(key)) {
|
||||||
|
params.set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tokenParams = String(params.get('tokenParams') || '').trim()
|
||||||
|
if (tokenParams) {
|
||||||
|
const nested = new URLSearchParams(tokenParams.startsWith('?') ? tokenParams.slice(1) : tokenParams)
|
||||||
|
for (const [key, value] of nested.entries()) {
|
||||||
|
if (!params.has(key)) {
|
||||||
|
params.set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = String(params.get('userId') || '').trim()
|
||||||
|
const timestamp = String(params.get('timestamp') || '').trim()
|
||||||
|
const nonce = String(params.get('nonce') || '').trim()
|
||||||
|
const sign = String(params.get('sign') || '').trim()
|
||||||
|
|
||||||
|
if (!userId || !timestamp || !nonce || !sign) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
userId,
|
||||||
|
timestamp,
|
||||||
|
nonce,
|
||||||
|
sign,
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAmsBusinessOk(raw) {
|
||||||
|
const ret = String(raw?.ret ?? '').trim()
|
||||||
|
const iRet = String(raw?.iRet ?? raw?.jData?.iRet ?? '').trim()
|
||||||
|
return ret === '0' || iRet === '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAmsSignatureExpired(raw) {
|
||||||
|
const codes = [
|
||||||
|
raw?.ret,
|
||||||
|
raw?.iRet,
|
||||||
|
raw?.jData?.ret,
|
||||||
|
raw?.jData?.iRet,
|
||||||
|
raw?.jData?.jData?.iRet,
|
||||||
|
raw?.arrErrNodeInfo?.errorCode,
|
||||||
|
raw?.jData?.arrErrNodeInfo?.errorCode,
|
||||||
|
].map((value) => String(value ?? '').trim())
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
raw?.sMsg,
|
||||||
|
raw?.jData?.sMsg,
|
||||||
|
raw?.jData?.jData?.sMsg,
|
||||||
|
].map((value) => String(value ?? '').trim())
|
||||||
|
|
||||||
|
return codes.includes(AMS_SIGNATURE_EXPIRED_CODE) || messages.some((message) => message.includes('签名已过期'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractAmsBindInfo(raw) {
|
||||||
|
const candidates = [
|
||||||
|
raw?.jData?.sBindInfo,
|
||||||
|
raw?.jData?.jData?.sBindInfo,
|
||||||
|
raw?.sBindInfo,
|
||||||
|
]
|
||||||
|
|
||||||
|
return candidates.find((item) => isPlainObject(item)) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAmsBindRoleInfo(bindInfo) {
|
||||||
|
const source = isPlainObject(bindInfo) ? bindInfo : {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: String(source.sRoleName || source.roleName || source.name || '').trim(),
|
||||||
|
rid: String(source.sRoleId || source.roleId || source.rid || '').trim(),
|
||||||
|
rawInfo: isPlainObject(bindInfo) ? bindInfo : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHeaderMap(headers) {
|
||||||
|
if (!headers || typeof headers !== 'object') {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(headers)
|
||||||
|
.map(([key, value]) => [String(key || '').trim().toLowerCase(), String(value || '').trim()])
|
||||||
|
.filter(([key, value]) => key && value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function isPlainObject(value) {
|
function isPlainObject(value) {
|
||||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ export {}
|
|||||||
* vnBindUrlPath: string
|
* vnBindUrlPath: string
|
||||||
* vnBindInfoPath: string
|
* vnBindInfoPath: string
|
||||||
* vnBackPath: string
|
* vnBackPath: string
|
||||||
|
* bindUrlTtlSeconds: number
|
||||||
|
* bindUrlProbeTimeoutMs: number
|
||||||
|
* bindUrlProbeUserAgent: string
|
||||||
|
* bindUrlProbeEndpoint: string
|
||||||
|
* bindUrlProbeChartId: string
|
||||||
|
* bindUrlProbeSubChartId: string
|
||||||
|
* bindUrlProbeIdeToken: string
|
||||||
|
* bindUrlProbeActivityUrl: string
|
||||||
|
* bindUrlProbeReferer: string
|
||||||
|
* bindUrlProbeExtraCookie: string
|
||||||
* publicKeyPem: string
|
* publicKeyPem: string
|
||||||
* clientSource: string
|
* clientSource: string
|
||||||
* deviceId: string
|
* deviceId: string
|
||||||
|
|||||||
@@ -671,6 +671,10 @@ export interface AdminTaskDetail {
|
|||||||
vnPhone: string
|
vnPhone: string
|
||||||
bindUrl: string
|
bindUrl: string
|
||||||
bindPreparedAt: string | null
|
bindPreparedAt: string | null
|
||||||
|
bindExpiresAt: string | null
|
||||||
|
bindProbeAt: string | null
|
||||||
|
bindProbeStatus: string
|
||||||
|
bindProbeMessage: string
|
||||||
}
|
}
|
||||||
role: {
|
role: {
|
||||||
status: string
|
status: string
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ export interface ClaimKuaishouCloudFlowInfo {
|
|||||||
vnId: number
|
vnId: number
|
||||||
bindUrl: string
|
bindUrl: string
|
||||||
bindPreparedAt: string | null
|
bindPreparedAt: string | null
|
||||||
|
bindExpiresAt: string | null
|
||||||
|
bindProbeAt: string | null
|
||||||
|
bindProbeStatus: string
|
||||||
|
bindProbeMessage: string
|
||||||
vnPhone: string
|
vnPhone: string
|
||||||
roleName: string
|
roleName: string
|
||||||
roleId: string
|
roleId: string
|
||||||
|
|||||||
@@ -33,10 +33,23 @@ const order = computed(() => detail.value?.order || null)
|
|||||||
const orderItem = computed(() => detail.value?.orderItem || null)
|
const orderItem = computed(() => detail.value?.orderItem || null)
|
||||||
const task = computed(() => detail.value?.task || null)
|
const task = computed(() => detail.value?.task || null)
|
||||||
|
|
||||||
const roleName = computed(() => flow.value?.binding.roleName || flow.value?.role.name || '')
|
const roleName = computed(() => flow.value?.role.name || flow.value?.binding.roleName || '')
|
||||||
const roleId = computed(() => flow.value?.binding.roleId || flow.value?.role.rid || '')
|
const roleId = computed(() => flow.value?.role.rid || flow.value?.binding.roleId || '')
|
||||||
const isTicketVerified = computed(() => flow.value?.ticket.status === 'verified')
|
const isTicketVerified = computed(() => flow.value?.ticket.status === 'verified')
|
||||||
const isBindingPrepared = computed(() => flow.value?.binding.prepareStatus === 'ready' && Boolean(String(flow.value?.binding.bindUrl || '').trim()))
|
const isBindUrlExpired = computed(() => {
|
||||||
|
const expiresAt = String(flow.value?.binding.bindExpiresAt || '').trim()
|
||||||
|
if (!expiresAt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiresTime = Date.parse(expiresAt)
|
||||||
|
return Number.isFinite(expiresTime) && expiresTime <= Date.now()
|
||||||
|
})
|
||||||
|
const isBindingPrepared = computed(() => (
|
||||||
|
flow.value?.binding.prepareStatus === 'ready'
|
||||||
|
&& Boolean(String(flow.value?.binding.bindUrl || '').trim())
|
||||||
|
&& !isBindUrlExpired.value
|
||||||
|
))
|
||||||
const isBindingPreparing = computed(() => flow.value?.binding.prepareStatus === 'pending')
|
const isBindingPreparing = computed(() => flow.value?.binding.prepareStatus === 'pending')
|
||||||
const canEnterBindingStep = computed(() => isTicketVerified.value && isBindingPrepared.value)
|
const canEnterBindingStep = computed(() => isTicketVerified.value && isBindingPrepared.value)
|
||||||
const isRoleReady = computed(() => Boolean(roleName.value || roleId.value))
|
const isRoleReady = computed(() => Boolean(roleName.value || roleId.value))
|
||||||
@@ -408,7 +421,10 @@ onBeforeUnmount(() => {
|
|||||||
如果当前角色不正确,请重新打开绑定页完成重新绑定,然后刷新角色信息。
|
如果当前角色不正确,请重新打开绑定页完成重新绑定,然后刷新角色信息。
|
||||||
</p>
|
</p>
|
||||||
<p class="muted meta-line">
|
<p class="muted meta-line">
|
||||||
最近刷新:{{ formatDateTime(flow.role.refreshedAt) }}
|
最近刷新:{{ formatDateTime(flow.role.refreshedAt) }};链接有效期:{{ formatDateTime(flow.binding.bindExpiresAt) }}
|
||||||
|
</p>
|
||||||
|
<p v-if="flow.binding.bindProbeMessage" class="muted meta-line">
|
||||||
|
链接检测:{{ flow.binding.bindProbeStatus || '-' }} / {{ flow.binding.bindProbeMessage }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
@@ -425,7 +441,7 @@ onBeforeUnmount(() => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
:loading="confirmingRole"
|
:loading="confirmingRole"
|
||||||
:disabled="!isBindingPrepared"
|
:disabled="!isBindingPrepared || !isRoleReady"
|
||||||
@click="confirmRole"
|
@click="confirmRole"
|
||||||
>
|
>
|
||||||
我已完成绑定,下一步
|
我已完成绑定,下一步
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
curl -X POST 'https://comm.ams.game.qq.com/ide/' -H 'User-Agent: Mozilla/5.0 (Linux; Android 12; M2012K11AC Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/98.0.4758.102 MQQBrowser/6.2 TBS/046141 Mobile Safari/537.36 V1_AND_SQ_8.8.85_2674_YYB_D QQ/8.8.85.7685 NetType/WIFI WebP/0.3.0 Pixel/1080 StatusBarHeight/108 SimpleUISwitch/0 QQTheme/1000' -H 'Accept: application/json, text/plain, */*' -H 'Content-Type: application/x-www-form-urlencoded' -H 'accept-language: zh-CN,zh;q=0.9,fr;q=0.8,de;q=0.7,en;q=0.6' -H 'cache-control: no-cache' -H 'origin: https://gp.qq.com' -H 'pragma: no-cache' -H 'priority: u=1, i' -H 'referer: https://gp.qq.com/' -H 'sec-ch-ua: ""' -H 'sec-ch-ua-mobile: ?1' -H 'sec-ch-ua-platform: ""' -H 'sec-fetch-dest: empty' -H 'sec-fetch-mode: cors' -H 'sec-fetch-site: same-site' -H 'Cookie: RK=uMT6S1KV0W; ptcz=c5241ee891d01510c81130023879f9d68088e87c75cb94e6dcc439a0d0078bab; pgv_pvid=5513916356; eas_sid=a1t7G7I644L7p7O3w0S3Y7W1f0; pgv_info=ssid=s2668768060; tokenParams=%3FuserId%3D14987713343%26timestamp%3D1778739347%26nonce%3Dd593e045%26sign%3D43ed7de057afb340fc766de4404817f8396ec727; gpqqcomrouteLine=a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc' --data-urlencode 'iChartId=323794' --data-urlencode 'iSubChartId=323794' --data-urlencode 'sIdeToken=z90Syo' --data-urlencode 'e_code=0' --data-urlencode 'g_code=0' --data-urlencode 'eas_url=http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F' --data-urlencode 'eas_refer=http%3A%2F%2Fnoreferrer%2F%3Freqid%3Da29ad787-5500-414c-8773-7723877f52c5%26version%3D27' --data-urlencode 'sMiloTag=AMS-gp-0514141601-VuK8ts-666311-1067699' --data-urlencode 'userId=14987713343' --data-urlencode 'timestamp=1778739347' --data-urlencode 'nonce=d593e045' --data-urlencode 'sign=43ed7de057afb340fc766de4404817f8396ec727'
|
||||||
|
|
||||||
|
正常响应
|
||||||
|
{
|
||||||
|
"ret": 0,
|
||||||
|
"iRet": 0,
|
||||||
|
"sMsg": "ok",
|
||||||
|
"jData": {
|
||||||
|
"iRet": "0",
|
||||||
|
"sMsg": "ok",
|
||||||
|
"sBindInfo": {
|
||||||
|
"id": "213252",
|
||||||
|
"sUserId": "14987713343",
|
||||||
|
"sOpenId": "osewR0kiZgLUM7CTh2bIFsds9Yyo",
|
||||||
|
"iAreaId": "1",
|
||||||
|
"iPlatId": "1",
|
||||||
|
"sRoleId": "3177794957",
|
||||||
|
"sRoleName": "是海也是牛",
|
||||||
|
"iStatus": "1",
|
||||||
|
"iDate": "20260321",
|
||||||
|
"dtCreateTime": "2026-02-20 12:13:01",
|
||||||
|
"dtUpdateTime": "2026-03-21 10:47:03",
|
||||||
|
"hasBind": 1
|
||||||
|
},
|
||||||
|
"iStatus": "0"
|
||||||
|
},
|
||||||
|
"sAmsSerial": "AMS-GP-0514141709-1UrrnM-666311-323794"
|
||||||
|
}
|
||||||
|
|
||||||
|
签名过期 响应
|
||||||
|
{
|
||||||
|
"ret": 99998,
|
||||||
|
"iRet": 99998,
|
||||||
|
"sMsg": "签名已过期",
|
||||||
|
"jData": {
|
||||||
|
"ret": "99998",
|
||||||
|
"iRet": "99998",
|
||||||
|
"sMsg": "签名已过期",
|
||||||
|
"jData": {
|
||||||
|
"iRet": "99998",
|
||||||
|
"sMsg": "签名已过期"
|
||||||
|
},
|
||||||
|
"arrErrNodeInfo": {
|
||||||
|
"nodeId": "nodeEnd",
|
||||||
|
"nodeType": "nodeEnd",
|
||||||
|
"errorCode": "99998"
|
||||||
|
},
|
||||||
|
"sAmsSerial": "AMS-GP-0514141817-eN55LW-666311-323794",
|
||||||
|
"subChartAmsSerial": "AMS-GP-0514141817-eN55LW-666311-323794",
|
||||||
|
"timecost": 17
|
||||||
|
},
|
||||||
|
"arrErrNodeInfo": {
|
||||||
|
"nodeId": "4",
|
||||||
|
"nodeType": "nodeSubChart",
|
||||||
|
"errorCode": "99998"
|
||||||
|
},
|
||||||
|
"sAmsSerial": "AMS-GP-0514141817-cZJfjw-666311-323794"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user