Files
XboxAutoRegister/XboxAutoRegister-main/calc_us_price.js
2026-04-02 21:19:42 +08:00

209 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 终极破甲器:手动追踪跳转,继承 Cookie半路截取游戏 ID
async function resolveGameId(startUrl) {
let currentUrl = startUrl;
let cookies = {};
for (let i = 0; i < 7; i++) { // 最多追踪 7 层跳转
try {
// 拼接继承的 Cookie
const cookieHeader = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ');
const response = await fetch(currentUrl, {
redirect: 'manual', // 关键:关闭自动跳转,改为我们手动一步步跟
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Cookie': cookieHeader
}
});
// 继承服务器发下来的 Cookie伪装得更像真人
const setCookieHeader = response.headers.get('set-cookie');
if (setCookieHeader) {
const parts = setCookieHeader.split(/,(?=\s*[a-zA-Z0-9_-]+\s*=)/);
for (const part of parts) {
const cookiePair = part.split(';')[0];
const [key, ...val] = cookiePair.split('=');
if (key && val) cookies[key.trim()] = val.join('=').trim();
}
}
if (response.status >= 300 && response.status < 400) {
const location = response.headers.get('location');
if (!location) break;
const nextUrl = location.startsWith('http') ? location : new URL(location, currentUrl).href;
// 解密 URL剥开追踪网的层层包装
let decodedUrl = nextUrl;
try { decodedUrl = decodeURIComponent(decodedUrl); } catch(e){}
try { decodedUrl = decodeURIComponent(decodedUrl); } catch(e){}
// ⭐️ 半路截胡:只要在跳转链接里发现了 9 开头的 12 位代码,直接带走,不再往下跳!
const idMatch = decodedUrl.match(/(?:\/|id=|ProductId=|bigIds=)([9][A-Za-z0-9]{11})(?:[\/?#&'"]|$)/i);
if (idMatch) return idMatch[1].toUpperCase();
currentUrl = nextUrl;
} else if (response.status === 200) {
const htmlText = await response.text();
// 搜刮网页源码,防备 JS 动态跳转
const htmlMatch = htmlText.match(/(?:\/|id=|ProductId=|bigIds=)([9][A-Za-z0-9]{11})(?:[\/?#&'"]|$)/i);
if (htmlMatch) return htmlMatch[1].toUpperCase();
// 检查 Meta Refresh 自动跳转
const metaRefresh = htmlText.match(/<meta[^>]*http-equiv=["']refresh["'][^>]*content=["']\d+;\s*url=([^"']+)["']/i);
if (metaRefresh) {
currentUrl = metaRefresh[1].replace(/&amp;/g, '&');
if (!currentUrl.startsWith('http')) currentUrl = new URL(currentUrl, startUrl).href;
continue;
}
// 检查 JS 自动跳转
const jsRedirect = htmlText.match(/(?:window\.)?location(?:\.href)?\s*=\s*['"]([^'"]+)['"]/i);
if (jsRedirect) {
currentUrl = jsRedirect[1];
if (!currentUrl.startsWith('http')) currentUrl = new URL(currentUrl, startUrl).href;
continue;
}
break;
} else {
break;
}
} catch (e) {
break;
}
}
return null;
}
// 核心查询逻辑
async function getUSGameData(startUrl) {
try {
const urlObj = new URL(startUrl);
urlObj.searchParams.set('r', 'en-us');
let bigId = await resolveGameId(urlObj.toString());
if (!bigId) {
return { success: false, reason: "防爬虫拦截,未能从底层剥离出 12 位游戏代码" };
}
const apiUrl = `https://displaycatalog.mp.microsoft.com/v7.0/products?bigIds=${bigId}&market=US&languages=en-us&MS-CV=DUMMY.1`;
const apiResponse = await fetch(apiUrl);
const data = await apiResponse.json();
if (!data.Products || data.Products.length === 0) {
return { success: false, reason: `成功获取 ID (${bigId}),但美区查无此游戏数据` };
}
const product = data.Products[0];
const gameName = product.LocalizedProperties?.[0]?.ProductTitle || "未知游戏";
let finalPrice = null;
if (!product.DisplaySkuAvailabilities || product.DisplaySkuAvailabilities.length === 0) {
return { success: false, name: gameName, reason: "该游戏没有销售规格 (无法购买)" };
}
// 智能找买断价
for (const skuObj of product.DisplaySkuAvailabilities) {
if (skuObj.Sku && (skuObj.Sku.SkuType === 'full' || skuObj.Sku.SkuType === 'dlc' || skuObj.Sku.SkuType === 'consumable')) {
for (const avail of skuObj.Availabilities || []) {
if (avail.Actions && avail.Actions.includes('Purchase') && avail.OrderManagementData?.Price !== undefined) {
finalPrice = avail.OrderManagementData.Price.ListPrice;
break;
}
}
}
if (finalPrice !== null) break;
}
if (finalPrice === null) {
for (const skuObj of product.DisplaySkuAvailabilities) {
for (const avail of skuObj.Availabilities || []) {
if (avail.Actions && avail.Actions.includes('Purchase') && avail.OrderManagementData?.Price !== undefined) {
finalPrice = avail.OrderManagementData.Price.ListPrice;
break;
}
}
if (finalPrice !== null) break;
}
}
if (finalPrice === null) {
return { success: false, name: gameName, reason: "只有 XGP 订阅试玩或捆绑包专属,无单买价格" };
}
return { success: true, name: gameName, price: finalPrice };
} catch (e) {
return { success: false, reason: `发生异常: ${e.message}` };
}
}
const inputUrls = [];
console.log('🎮 请粘贴你的 Xbox 链接串 (支持包含回车的多行文本)。');
console.log('💡 提示:粘贴完成后,请在【新的一行】按一次回车开始计算:\n');
rl.on('line', (line) => {
const trimmedLine = line.trim();
if (trimmedLine === '') {
if (inputUrls.length > 0) {
rl.close();
processUrls(inputUrls);
}
return;
}
const splitUrls = trimmedLine.split(/(?=https?:\/\/)/).filter(u => u.startsWith('http'));
inputUrls.push(...splitUrls);
});
async function processUrls(urls) {
console.log(`\n✅ 成功读取到 ${urls.length} 个链接,开始逐个查询美区价格...\n`);
let totalPrice = 0;
let successCount = 0;
const failedDetails = [];
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
process.stdout.write(`[${i + 1}/${urls.length}] 正在查询... `);
const result = await getUSGameData(url);
if (result.success) {
console.log(`✅ 成功 | ${result.name} | 现价: $${result.price}`);
totalPrice += result.price;
successCount++;
} else {
const namePart = result.name ? `[${result.name}] ` : "";
console.log(`❌ 失败 | ${namePart}原因: ${result.reason}`);
failedDetails.push({ url, reason: result.reason, name: result.name });
}
}
console.log("\n================ 结算单 ================");
console.log(`总计识别: ${urls.length} 个游戏`);
console.log(`成功查询: ${successCount} 个游戏`);
console.log(`美元总价: $${totalPrice.toFixed(2)}`);
console.log("========================================\n");
if (failedDetails.length > 0) {
console.log("⚠️ 以下链接需要手动核查:");
failedDetails.forEach((f, idx) => {
const nameStr = f.name ? `游戏: ${f.name}\n ` : "";
console.log(`${idx + 1}. ${nameStr}原因: ${f.reason}\n 链接: ${f.url}`);
});
}
}