deduplicate task parsing and masking helpers
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import { getAgisoShopConfig } from '../platforms/agiso/shop-config-service.js'
|
import { getAgisoShopConfig } from '../platforms/agiso/shop-config-service.js'
|
||||||
import { safeParseJson } from './admin-query-utils.js'
|
import { safeParseJson } from './admin-query-utils.js'
|
||||||
import { normalizeAdminRole } from './admin-auth-service.js'
|
import { normalizeAdminRole } from './admin-auth-service.js'
|
||||||
|
import {
|
||||||
|
parseTaskContext as parseTaskContextValue,
|
||||||
|
parseTaskState as parseTaskStateValue,
|
||||||
|
} from '../../utils/task-json.js'
|
||||||
|
|
||||||
import type { AdminViewerSessionInput } from '../../types/admin-read-inputs.js'
|
import type { AdminViewerSessionInput } from '../../types/admin-read-inputs.js'
|
||||||
import type { OrderItemRow, TaskRow } from '../../types/repository-rows.js'
|
import type { OrderItemRow, TaskRow } from '../../types/repository-rows.js'
|
||||||
@@ -227,39 +231,11 @@ export function resolveDisplayShopName(provider: unknown, shopId: unknown, shopN
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseTaskContext(task: TaskLike | null | undefined): JsonRecord {
|
export function parseTaskContext(task: TaskLike | null | undefined): JsonRecord {
|
||||||
const value = task?.context_json
|
return parseTaskContextValue(task)
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(String(value || '{}'))
|
|
||||||
} catch {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseTaskState(task: TaskLike | null | undefined): JsonRecord {
|
export function parseTaskState(task: TaskLike | null | undefined): JsonRecord {
|
||||||
const value = task?.state_json
|
return parseTaskStateValue(task)
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(String(value || '{}'))
|
|
||||||
} catch {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveOrderItemTitle(item: Partial<OrderItemRow> | null | undefined): string {
|
export function resolveOrderItemTitle(item: Partial<OrderItemRow> | null | undefined): string {
|
||||||
|
|||||||
@@ -1,29 +1,16 @@
|
|||||||
|
import {
|
||||||
|
maskPhone as maskPhoneValue,
|
||||||
|
maskSecret as maskSecretValue,
|
||||||
|
} from "../../../utils/masking.js";
|
||||||
|
|
||||||
type JsonObject = Record<string, any>
|
type JsonObject = Record<string, any>
|
||||||
|
|
||||||
export function maskSecret(value: unknown) {
|
export function maskSecret(value: unknown) {
|
||||||
const normalized = String(value || "").trim();
|
return maskSecretValue(value);
|
||||||
if (!normalized) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalized.length <= 10) {
|
|
||||||
return `${normalized.slice(0, 2)}****${normalized.slice(-2)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${normalized.slice(0, 6)}****${normalized.slice(-6)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maskPhone(value: unknown) {
|
export function maskPhone(value: unknown) {
|
||||||
const normalized = String(value || "").trim();
|
return maskPhoneValue(value, { maskShort: false });
|
||||||
if (!normalized) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalized.length < 7) {
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${normalized.slice(0, 3)}****${normalized.slice(-4)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapAdminKuaishouEticketShopItem(item: JsonObject = {}) {
|
export function mapAdminKuaishouEticketShopItem(item: JsonObject = {}) {
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import { normalizeProductName } from '../../order/product-match-service.js'
|
|
||||||
import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js'
|
|
||||||
import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js'
|
|
||||||
import { resolveCloudtentaclesConfig } from '../../platforms/cloudtentacles/shared.js'
|
|
||||||
import {
|
|
||||||
appointCloudtentaclesVirtualNumber,
|
|
||||||
backCloudtentaclesVirtualNumber,
|
|
||||||
fetchCloudtentaclesVirtualNumberCode,
|
|
||||||
generateCloudtentaclesLoginCode,
|
|
||||||
getCloudtentaclesBindUrl,
|
|
||||||
verifyCloudtentaclesLoginCode,
|
|
||||||
} from '../../platforms/cloudtentacles/virtual-number-service.js'
|
|
||||||
import { createHttpError } from '../../../utils/http.js'
|
import { createHttpError } from '../../../utils/http.js'
|
||||||
|
import type { HttpErrorLike } from '../../../utils/http.js'
|
||||||
|
import {
|
||||||
|
resolvePersistedCloudtentaclesContext as resolveFulfillmentPersistedCloudtentaclesContext,
|
||||||
|
} from '../../fulfillment/kuaishou-cloud/cloudtentacles-context.js'
|
||||||
|
|
||||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
|
export {
|
||||||
|
KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||||
export type JsonObject = Record<string, any>
|
isKuaishouCloudTask,
|
||||||
|
normalizeKuaishouCloudFlow,
|
||||||
|
normalizeKuaishouCloudRoleInfo,
|
||||||
|
resolveKuaishouCloudBindUrlExpiresAt,
|
||||||
|
type JsonObject,
|
||||||
|
} from '../../fulfillment/kuaishou-cloud/domain.js'
|
||||||
|
export {
|
||||||
|
prepareKuaishouCloudBindResourceWithFallback,
|
||||||
|
resolveKuaishouCloudBindingResources,
|
||||||
|
resolveKuaishouCloudVnKeyCandidates,
|
||||||
|
} from '../../fulfillment/kuaishou-cloud/binding-resources.js'
|
||||||
|
|
||||||
export type CloudtentaclesContext = {
|
export type CloudtentaclesContext = {
|
||||||
baseUrl: string
|
baseUrl: string
|
||||||
@@ -31,17 +33,17 @@ export type CloudSkuLikeItem = {
|
|||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KuaishouCloudFlow = JsonObject & {
|
export type KuaishouCloudFlow = Record<string, any> & {
|
||||||
configId: string
|
configId: string
|
||||||
internalSkuCode: string
|
internalSkuCode: string
|
||||||
internalSkuName: string
|
internalSkuName: string
|
||||||
ticket: JsonObject
|
ticket: Record<string, any>
|
||||||
binding: JsonObject
|
binding: Record<string, any>
|
||||||
role: JsonObject
|
role: Record<string, any>
|
||||||
purchase: JsonObject
|
purchase: Record<string, any>
|
||||||
dispatch: JsonObject
|
dispatch: Record<string, any>
|
||||||
returnNumber: JsonObject
|
returnNumber: Record<string, any>
|
||||||
consume: JsonObject
|
consume: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KuaishouCloudBindingResources = {
|
export type KuaishouCloudBindingResources = {
|
||||||
@@ -60,332 +62,23 @@ export type PreparedKuaishouCloudBindResource = {
|
|||||||
bindUrl: string
|
bindUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isKuaishouCloudTask(task: unknown): boolean {
|
|
||||||
const record: JsonObject = task && typeof task === 'object' ? task : {}
|
|
||||||
return String(record.executor_key || '').trim() === 'kuaishou_ct_assisted'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolvePersistedCloudtentaclesContext(): CloudtentaclesContext {
|
export function resolvePersistedCloudtentaclesContext(): CloudtentaclesContext {
|
||||||
const source = getCloudtentaclesSourceConfig()
|
try {
|
||||||
const session = getCloudtentaclesSessionState()
|
return resolveFulfillmentPersistedCloudtentaclesContext()
|
||||||
const token = String(session.token || '').trim()
|
} catch (error) {
|
||||||
|
if (isMissingCloudtentaclesTokenError(error)) {
|
||||||
if (!token) {
|
|
||||||
throw createHttpError('当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验', {
|
throw createHttpError('当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验', {
|
||||||
statusCode: 409,
|
statusCode: 409,
|
||||||
errorCode: 'admin_task_kuaishou_cloud_missing_cloud_token',
|
errorCode: 'admin_task_kuaishou_cloud_missing_cloud_token',
|
||||||
|
cause: error,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
baseUrl: String(session.baseUrl || source.baseUrl || '').trim() || 'https://123.207.217.176',
|
|
||||||
token,
|
|
||||||
deviceId: String(session.deviceId || source.deviceId || '-').trim() || '-',
|
|
||||||
deviceType: Number(session.deviceType ?? source.deviceType ?? 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeKuaishouCloudFlow(value: unknown): KuaishouCloudFlow {
|
|
||||||
const source: JsonObject = value && typeof value === 'object' ? value : {}
|
|
||||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
|
||||||
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
|
||||||
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
|
||||||
const dispatch = source.dispatch && typeof source.dispatch === 'object' ? source.dispatch : {}
|
|
||||||
const returnNumber = source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
|
||||||
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
|
||||||
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
|
||||||
const bindPreparedAt = binding.bindPreparedAt || null
|
|
||||||
|
|
||||||
return {
|
|
||||||
...source,
|
|
||||||
configId: String(source.configId || '').trim(),
|
|
||||||
internalSkuCode: String(source.internalSkuCode || '').trim(),
|
|
||||||
internalSkuName: String(source.internalSkuName || '').trim(),
|
|
||||||
ticket: {
|
|
||||||
code: String(ticket.code || '').trim(),
|
|
||||||
status: String(ticket.status || 'pending').trim() || 'pending',
|
|
||||||
capturedAt: ticket.capturedAt || null,
|
|
||||||
capturedBy: ticket.capturedBy || null,
|
|
||||||
verifiedAt: ticket.verifiedAt || null,
|
|
||||||
oid: String(ticket.oid || '').trim(),
|
|
||||||
formToken: String(ticket.formToken || '').trim(),
|
|
||||||
leftCount: Number(ticket.leftCount || 0) || 0,
|
|
||||||
goodsTitle: String(ticket.goodsTitle || '').trim(),
|
|
||||||
},
|
|
||||||
binding: {
|
|
||||||
prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending',
|
|
||||||
cloudSourceKey: String(binding.cloudSourceKey || 'default').trim() || 'default',
|
|
||||||
skuId: Number(binding.skuId || 0) || 0,
|
|
||||||
skuName: String(binding.skuName || '').trim(),
|
|
||||||
vnKey: String(binding.vnKey || '').trim(),
|
|
||||||
vnId: Number(binding.vnId || 0) || 0,
|
|
||||||
vnPhone: String(binding.vnPhone || '').trim(),
|
|
||||||
bindUrl: String(binding.bindUrl || '').trim(),
|
|
||||||
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(),
|
|
||||||
roleId: String(binding.roleId || '').trim(),
|
|
||||||
},
|
|
||||||
role: {
|
|
||||||
status: String(role.status || 'pending').trim() || 'pending',
|
|
||||||
name: String(role.name || '').trim(),
|
|
||||||
rid: String(role.rid || '').trim(),
|
|
||||||
refreshedAt: role.refreshedAt || null,
|
|
||||||
errorMessage: String(role.errorMessage || '').trim(),
|
|
||||||
rawInfo: role.rawInfo && typeof role.rawInfo === 'object' ? role.rawInfo : null,
|
|
||||||
},
|
|
||||||
purchase: {
|
|
||||||
autoBuyEnabled: purchase.autoBuyEnabled !== false,
|
|
||||||
minAssetReserve: Number(purchase.minAssetReserve || 0) || 0,
|
|
||||||
usedKnapsack: purchase.usedKnapsack === true,
|
|
||||||
purchaseTriggered: purchase.purchaseTriggered === true,
|
|
||||||
assetBefore: Number(purchase.assetBefore || 0) || 0,
|
|
||||||
assetAfter: Number(purchase.assetAfter || 0) || 0,
|
|
||||||
purchaseAt: purchase.purchaseAt || null,
|
|
||||||
},
|
|
||||||
dispatch: {
|
|
||||||
status: String(dispatch.status || 'pending').trim() || 'pending',
|
|
||||||
dispatchAt: dispatch.dispatchAt || null,
|
|
||||||
dispatchBy: dispatch.dispatchBy || null,
|
|
||||||
sendType: Number(dispatch.sendType || 0) || 0,
|
|
||||||
note: String(dispatch.note || '').trim(),
|
|
||||||
},
|
|
||||||
returnNumber: {
|
|
||||||
status: String(returnNumber.status || 'pending').trim() || 'pending',
|
|
||||||
returnedAt: returnNumber.returnedAt || null,
|
|
||||||
returnedBy: returnNumber.returnedBy || null,
|
|
||||||
autoReturnEnabled: returnNumber.autoReturnEnabled === true,
|
|
||||||
},
|
|
||||||
consume: {
|
|
||||||
status: String(consume.status || 'pending').trim() || 'pending',
|
|
||||||
shopId: String(consume.shopId || '').trim(),
|
|
||||||
shopName: String(consume.shopName || '').trim(),
|
|
||||||
autoConsumeEnabled: consume.autoConsumeEnabled === true,
|
|
||||||
consumedAt: consume.consumedAt || null,
|
|
||||||
errorMessage: String(consume.errorMessage || '').trim(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeKuaishouCloudRoleInfo(value: unknown): { name: string, rid: string, rawInfo: JsonObject | null } {
|
|
||||||
const rawInfo: JsonObject | null = value && typeof value === 'object' ? value : null
|
|
||||||
const nestedBindInfo = rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
|
|
||||||
const source = nestedBindInfo || rawInfo
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: String(source?.name || source?.roleName || source?.nickname || source?.sRoleName || '').trim(),
|
|
||||||
rid: String(source?.rid || source?.roleId || source?.uid || source?.sRoleId || source?.sUserId || '').trim(),
|
|
||||||
rawInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt: unknown): string | null {
|
|
||||||
const preparedTime = Date.parse(String(preparedAt || ''))
|
|
||||||
if (!Number.isFinite(preparedTime)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = resolveCloudtentaclesConfig()
|
|
||||||
const ttlSeconds = Number(config.bindUrlTtlSeconds || 600)
|
|
||||||
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveKuaishouCloudBindingResources(
|
|
||||||
flow: KuaishouCloudFlow,
|
|
||||||
{ skuItems = [], knapsackItems = [] }: { skuItems?: unknown[], knapsackItems?: unknown[] } = {},
|
|
||||||
): KuaishouCloudBindingResources {
|
|
||||||
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
|
||||||
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
|
||||||
const currentSkuId = Number(flow?.binding?.skuId || 0) || 0
|
|
||||||
const currentSkuName = String(flow?.binding?.skuName || '').trim()
|
|
||||||
const nameCandidates = collectKuaishouCloudNameCandidates(flow)
|
|
||||||
|
|
||||||
const skuItemById = currentSkuId > 0
|
|
||||||
? normalizedSkuItems.find((item) => Number(item.id || 0) === currentSkuId) || null
|
|
||||||
: null
|
|
||||||
const knapsackItemById = currentSkuId > 0
|
|
||||||
? normalizedKnapsackItems.find((item) => Number(item.id || 0) === currentSkuId) || null
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (skuItemById || knapsackItemById) {
|
|
||||||
const matchedItem = skuItemById || knapsackItemById
|
|
||||||
return {
|
|
||||||
skuId: Number(matchedItem?.id || 0) || 0,
|
|
||||||
skuName: String(currentSkuName || matchedItem?.name || '').trim(),
|
|
||||||
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
|
|
||||||
skuItem: skuItemById,
|
|
||||||
knapsackItem: knapsackItemById,
|
|
||||||
resolvedByName: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchedSkuItem = findCloudItemByNames(normalizedSkuItems, nameCandidates)
|
|
||||||
const matchedKnapsackItem = findCloudItemByNames(
|
|
||||||
normalizedKnapsackItems,
|
|
||||||
nameCandidates,
|
|
||||||
matchedSkuItem ? Number(matchedSkuItem.id || 0) : 0,
|
|
||||||
)
|
|
||||||
const matchedItem = matchedSkuItem || matchedKnapsackItem
|
|
||||||
|
|
||||||
return {
|
|
||||||
skuId: Number(matchedItem?.id || 0) || 0,
|
|
||||||
skuName: String(currentSkuName || matchedItem?.name || '').trim(),
|
|
||||||
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
|
|
||||||
skuItem: matchedSkuItem,
|
|
||||||
knapsackItem: matchedKnapsackItem,
|
|
||||||
resolvedByName: Boolean(matchedItem),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveKuaishouCloudVnKeyCandidates(input: { flow?: KuaishouCloudFlow, binding?: KuaishouCloudBindingResources } = {}): string[] {
|
|
||||||
return [KUAISHOU_CLOUD_FIXED_VN_KEY]
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function prepareKuaishouCloudBindResourceWithFallback(input: {
|
|
||||||
cloudContext?: CloudtentaclesContext
|
|
||||||
vnKeyCandidates?: string[]
|
|
||||||
} = {}): Promise<PreparedKuaishouCloudBindResource> {
|
|
||||||
const { cloudContext = {}, vnKeyCandidates = [] } = input
|
|
||||||
const candidates = Array.isArray(vnKeyCandidates) ? vnKeyCandidates : []
|
|
||||||
let lastError: unknown = null
|
|
||||||
|
|
||||||
for (const vnKey of candidates) {
|
|
||||||
let vnId = 0
|
|
||||||
let vnPhone = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
const appointed = await appointCloudtentaclesVirtualNumber({
|
|
||||||
...cloudContext,
|
|
||||||
key: vnKey,
|
|
||||||
})
|
|
||||||
vnId = Number(appointed.item?.id || 0)
|
|
||||||
vnPhone = String(appointed.item?.phone || '').trim()
|
|
||||||
|
|
||||||
if (!vnId || !vnPhone) {
|
|
||||||
throw createHttpError('申请虚拟号成功但返回数据不完整', {
|
|
||||||
statusCode: 502,
|
|
||||||
errorCode: 'admin_task_kuaishou_cloud_invalid_vn',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await generateCloudtentaclesLoginCode({
|
|
||||||
...cloudContext,
|
|
||||||
key: vnKey,
|
|
||||||
id: vnId,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchedCode = await fetchCloudtentaclesVirtualNumberCode({
|
|
||||||
...cloudContext,
|
|
||||||
key: vnKey,
|
|
||||||
phone: vnPhone,
|
|
||||||
})
|
|
||||||
|
|
||||||
await verifyCloudtentaclesLoginCode({
|
|
||||||
...cloudContext,
|
|
||||||
key: vnKey,
|
|
||||||
id: vnId,
|
|
||||||
code: fetchedCode.code,
|
|
||||||
})
|
|
||||||
|
|
||||||
const bindUrlResult = await getCloudtentaclesBindUrl({
|
|
||||||
...cloudContext,
|
|
||||||
key: vnKey,
|
|
||||||
id: vnId,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
vnKey,
|
|
||||||
vnId,
|
|
||||||
vnPhone,
|
|
||||||
bindUrl: String(bindUrlResult.bindUrl || '').trim(),
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
lastError = error
|
|
||||||
|
|
||||||
if (vnId > 0) {
|
|
||||||
try {
|
|
||||||
await backCloudtentaclesVirtualNumber({
|
|
||||||
...cloudContext,
|
|
||||||
key: vnKey,
|
|
||||||
id: vnId,
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
// 兜底退号失败时保留原始错误,避免吞掉主链路异常
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRecoverableKuaishouCloudVnKeyError(error)) {
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
throw lastError || createHttpError('没有找到可用的 VN Key', {
|
function isMissingCloudtentaclesTokenError(error: unknown): boolean {
|
||||||
statusCode: 409,
|
const current = error as HttpErrorLike | null
|
||||||
errorCode: 'admin_task_kuaishou_cloud_missing_binding_config',
|
return String(current?.errorCode || '').trim() === 'kuaishou_cloud_missing_cloud_token'
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectKuaishouCloudNameCandidates(flow: KuaishouCloudFlow): string[] {
|
|
||||||
return Array.from(new Set([
|
|
||||||
String(flow?.binding?.skuName || '').trim(),
|
|
||||||
String(flow?.internalSkuName || '').trim(),
|
|
||||||
String(flow?.internalSkuCode || '').trim(),
|
|
||||||
].filter(Boolean)))
|
|
||||||
}
|
|
||||||
|
|
||||||
function findCloudItemByNames(items: CloudSkuLikeItem[], nameCandidates: string[], preferredId = 0): CloudSkuLikeItem | null {
|
|
||||||
const normalizedItems = Array.isArray(items) ? items : []
|
|
||||||
const normalizedNames = nameCandidates
|
|
||||||
.map((item) => ({
|
|
||||||
raw: String(item || '').trim(),
|
|
||||||
normalized: normalizeProductName(item),
|
|
||||||
}))
|
|
||||||
.filter((item) => item.raw && item.normalized)
|
|
||||||
|
|
||||||
if (normalizedNames.length === 0 || normalizedItems.length === 0) {
|
|
||||||
return preferredId > 0
|
|
||||||
? normalizedItems.find((item) => Number(item.id || 0) === preferredId) || null
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferredId > 0) {
|
|
||||||
const preferred = normalizedItems.find((item) => Number(item.id || 0) === preferredId) || null
|
|
||||||
if (preferred) {
|
|
||||||
return preferred
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const exactMatches = normalizedItems.filter((item) => {
|
|
||||||
const itemName = normalizeProductName(item.name)
|
|
||||||
return normalizedNames.some((candidate) => candidate.normalized === itemName)
|
|
||||||
})
|
|
||||||
if (exactMatches.length > 0) {
|
|
||||||
return exactMatches[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
const partialMatches = normalizedItems.filter((item) => {
|
|
||||||
const itemName = normalizeProductName(item.name)
|
|
||||||
return normalizedNames.some((candidate) => itemName.includes(candidate.normalized) || candidate.normalized.includes(itemName))
|
|
||||||
})
|
|
||||||
if (partialMatches.length > 0) {
|
|
||||||
return partialMatches.sort((left, right) => String(left.name || '').length - String(right.name || '').length)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function isCloudSkuLikeItem(item: unknown): item is CloudSkuLikeItem {
|
|
||||||
const record: JsonObject = item && typeof item === 'object' ? item : {}
|
|
||||||
return Number(record.id || 0) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRecoverableKuaishouCloudVnKeyError(error: any): boolean {
|
|
||||||
const errorCode = String(error?.errorCode || error?.code || '').trim()
|
|
||||||
const errorMessage = String(error?.message || '').trim()
|
|
||||||
return errorCode === 'cloudtentacles_vn_bind_url_failed'
|
|
||||||
&& errorMessage.includes('不支持的游戏类型')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { buildClaimUrl, createTaskClaimToken } from '../../claim/claim-service.js'
|
import { buildClaimUrl, createTaskClaimToken } from '../../claim/claim-service.js'
|
||||||
import { createHttpError } from '../../../utils/http.js'
|
import { createHttpError } from '../../../utils/http.js'
|
||||||
|
import {
|
||||||
|
maskCode as maskCodeValue,
|
||||||
|
maskPhone as maskPhoneValue,
|
||||||
|
} from '../../../utils/masking.js'
|
||||||
import {
|
import {
|
||||||
canViewerConfirmAssistedRole,
|
canViewerConfirmAssistedRole,
|
||||||
canViewerRedeemAssistedTask,
|
canViewerRedeemAssistedTask,
|
||||||
@@ -24,29 +28,11 @@ type ErrorLike = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function maskPhone(value: unknown): string {
|
export function maskPhone(value: unknown): string {
|
||||||
const text = String(value || '').trim()
|
return maskPhoneValue(value)
|
||||||
if (!text) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.length <= 7) {
|
|
||||||
return `${text.slice(0, 2)}***${text.slice(-2)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${text.slice(0, 3)}****${text.slice(-4)}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maskCode(value: unknown): string {
|
export function maskCode(value: unknown): string {
|
||||||
const text = String(value || '').trim()
|
return maskCodeValue(value)
|
||||||
if (!text) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.length <= 8) {
|
|
||||||
return `${text.slice(0, 2)}***${text.slice(-2)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveTaskInventoryGroupCodes(task: TaskRow): string[] | null {
|
export function resolveTaskInventoryGroupCodes(task: TaskRow): string[] | null {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { buildClaimUrl } from '../claim-service.js'
|
import { buildClaimUrl } from '../claim-service.js'
|
||||||
import { createHttpError } from '../../../utils/http.js'
|
import { createHttpError } from '../../../utils/http.js'
|
||||||
|
import { maskCode as maskCodeValue } from '../../../utils/masking.js'
|
||||||
import { formatFenToAmount, normalizeFen } from '../../../utils/money.js'
|
import { formatFenToAmount, normalizeFen } from '../../../utils/money.js'
|
||||||
|
import {
|
||||||
|
parseTaskContext as parseTaskContextValue,
|
||||||
|
parseTaskState as parseTaskStateValue,
|
||||||
|
} from '../../../utils/task-json.js'
|
||||||
|
|
||||||
export const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed'])
|
export const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed'])
|
||||||
export const REDEEM_REPLACEMENT_LIMIT = 10
|
export const REDEEM_REPLACEMENT_LIMIT = 10
|
||||||
@@ -178,21 +183,7 @@ export function mergeTaskContext(task, patch = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseTaskState(task) {
|
export function parseTaskState(task) {
|
||||||
const value = task?.state_json
|
return parseTaskStateValue(task)
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(String(value || '{}'))
|
|
||||||
} catch {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAssistedClaimTask(task) {
|
export function isAssistedClaimTask(task) {
|
||||||
@@ -200,21 +191,7 @@ export function isAssistedClaimTask(task) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseTaskContext(task) {
|
export function parseTaskContext(task) {
|
||||||
const value = task?.context_json
|
return parseTaskContextValue(task)
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(String(value || '{}'))
|
|
||||||
} catch {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeClaimLoginType(loginType) {
|
export function normalizeClaimLoginType(loginType) {
|
||||||
@@ -227,15 +204,5 @@ export function isRecoverableSessionError(error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function maskCode(value) {
|
export function maskCode(value) {
|
||||||
const text = String(value || '').trim()
|
return maskCodeValue(value, { shortMask: '****' })
|
||||||
|
|
||||||
if (!text) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.length <= 8) {
|
|
||||||
return `${text.slice(0, 2)}****${text.slice(-2)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { resolveCloudtentaclesConfig } from "../../platforms/cloudtentacles/shared.js";
|
import { resolveCloudtentaclesConfig } from "../../platforms/cloudtentacles/shared.js";
|
||||||
|
import {
|
||||||
|
maskCode as maskCodeValue,
|
||||||
|
maskPhone as maskPhoneValue,
|
||||||
|
} from "../../../utils/masking.js";
|
||||||
|
|
||||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = "1";
|
export const KUAISHOU_CLOUD_FIXED_VN_KEY = "1";
|
||||||
|
|
||||||
@@ -154,29 +158,11 @@ export function normalizeKuaishouCloudRoleInfo(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function maskPhone(value) {
|
export function maskPhone(value) {
|
||||||
const text = String(value || "").trim();
|
return maskPhoneValue(value);
|
||||||
if (!text) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.length <= 7) {
|
|
||||||
return `${text.slice(0, 2)}***${text.slice(-2)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${text.slice(0, 3)}****${text.slice(-4)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maskCode(value) {
|
export function maskCode(value) {
|
||||||
const text = String(value || "").trim();
|
return maskCodeValue(value);
|
||||||
if (!text) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.length <= 8) {
|
|
||||||
return `${text.slice(0, 2)}***${text.slice(-2)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${text.slice(0, 4)}****${text.slice(-4)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
|
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { parseTaskContext as parseTaskContextValue } from "../../../utils/task-json.js";
|
||||||
|
|
||||||
export function normalizeActor(actor) {
|
export function normalizeActor(actor) {
|
||||||
if (!actor || typeof actor !== "object") {
|
if (!actor || typeof actor !== "object") {
|
||||||
return null;
|
return null;
|
||||||
@@ -21,21 +23,7 @@ export function normalizeActor(actor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseTaskContext(task) {
|
export function parseTaskContext(task) {
|
||||||
const value = task?.context_json;
|
return parseTaskContextValue(task);
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "object") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(String(value || "{}"));
|
|
||||||
} catch {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTaskClaimExpiresAt(task) {
|
export function getTaskClaimExpiresAt(task) {
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
type MaskPhoneOptions = {
|
||||||
|
maskShort?: boolean
|
||||||
|
shortMask?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MaskCodeOptions = {
|
||||||
|
shortMask?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function maskSecret(value: unknown): string {
|
||||||
|
const normalized = String(value || '').trim()
|
||||||
|
if (!normalized) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized.length <= 10) {
|
||||||
|
return `${normalized.slice(0, 2)}****${normalized.slice(-2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${normalized.slice(0, 6)}****${normalized.slice(-6)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function maskPhone(value: unknown, options: MaskPhoneOptions = {}): string {
|
||||||
|
const text = String(value || '').trim()
|
||||||
|
if (!text) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.length <= 7) {
|
||||||
|
if (options.maskShort === false) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${text.slice(0, 2)}${options.shortMask || '***'}${text.slice(-2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${text.slice(0, 3)}****${text.slice(-4)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function maskCode(value: unknown, options: MaskCodeOptions = {}): string {
|
||||||
|
const text = String(value || '').trim()
|
||||||
|
if (!text) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.length <= 8) {
|
||||||
|
return `${text.slice(0, 2)}${options.shortMask || '***'}${text.slice(-2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
export type JsonRecord = Record<string, any>
|
||||||
|
|
||||||
|
export function parseJsonObject(value: unknown): JsonRecord {
|
||||||
|
if (!value) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||||
|
return value as JsonRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(String(value || '{}'))
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseTaskContext(task: { context_json?: unknown } | null | undefined): JsonRecord {
|
||||||
|
return parseJsonObject(task?.context_json)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseTaskState(task: { state_json?: unknown } | null | undefined): JsonRecord {
|
||||||
|
return parseJsonObject(task?.state_json)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user