242 lines
6.7 KiB
JavaScript
242 lines
6.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import https from 'https';
|
|
|
|
/**
|
|
* Blizzard API client for fetching item data
|
|
* To use this, you'll need to:
|
|
* 1. Create an app at https://develop.battle.net/
|
|
* 2. Get your client ID and client secret
|
|
* 3. Set environment variables: BLIZZARD_CLIENT_ID and BLIZZARD_CLIENT_SECRET
|
|
*/
|
|
|
|
let accessToken = null;
|
|
let tokenExpiry = null;
|
|
|
|
/**
|
|
* Gets an access token from Blizzard API
|
|
* @returns {Promise<string|null>} Access token or null if failed
|
|
*/
|
|
async function getAccessToken() {
|
|
const clientId = process.env.BLIZZARD_CLIENT_ID;
|
|
const clientSecret = process.env.BLIZZARD_CLIENT_SECRET;
|
|
|
|
if (!clientId || !clientSecret) {
|
|
console.error('Blizzard API credentials not found. Please set BLIZZARD_CLIENT_ID and BLIZZARD_CLIENT_SECRET environment variables.');
|
|
return null;
|
|
}
|
|
|
|
// Check if we have a valid token
|
|
if (accessToken && tokenExpiry && Date.now() < tokenExpiry) {
|
|
return accessToken;
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
const postData = 'grant_type=client_credentials';
|
|
|
|
const options = {
|
|
hostname: 'oauth.battle.net',
|
|
port: 443,
|
|
path: '/token',
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Basic ${auth}`,
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'Content-Length': Buffer.byteLength(postData)
|
|
}
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
const response = JSON.parse(data);
|
|
if (response.access_token) {
|
|
accessToken = response.access_token;
|
|
tokenExpiry = Date.now() + (response.expires_in * 1000);
|
|
resolve(accessToken);
|
|
} else {
|
|
console.error('Failed to get access token:', response);
|
|
resolve(null);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing token response:', error.message);
|
|
resolve(null);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
console.error('Error getting access token:', error.message);
|
|
resolve(null);
|
|
});
|
|
|
|
req.write(postData);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetches item data from Blizzard API
|
|
* @param {number} itemId - The item ID to fetch
|
|
* @param {string} region - The region (default: 'us')
|
|
* @returns {Promise<Object|null>} Item data or null if failed
|
|
*/
|
|
async function fetchFromBlizzard(itemId, region = 'us') {
|
|
const token = await getAccessToken();
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const options = {
|
|
hostname: `${region}.api.blizzard.com`,
|
|
port: 443,
|
|
path: `/data/wow/item/${itemId}?namespace=static-${region}&locale=en_US&access_token=${token}`,
|
|
method: 'GET'
|
|
};
|
|
|
|
https.get(options, (res) => {
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
const response = JSON.parse(data);
|
|
|
|
if (response.name) {
|
|
const itemData = {
|
|
id: itemId,
|
|
name: response.name,
|
|
slot: mapSlotFromBlizzard(response.inventory_type?.type),
|
|
quality: response.quality?.name?.toLowerCase(),
|
|
icon: response.media ? `blizzard-${response.media.id}` : null,
|
|
source: 'blizzard',
|
|
blizzardData: {
|
|
itemClass: response.item_class?.name,
|
|
itemSubclass: response.item_subclass?.name,
|
|
inventoryType: response.inventory_type?.name,
|
|
bindType: response.bind_type?.name,
|
|
level: response.level,
|
|
requiredLevel: response.required_level
|
|
}
|
|
};
|
|
resolve(itemData);
|
|
} else {
|
|
console.log(`No data found for item ${itemId} on Blizzard API`);
|
|
resolve(null);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error parsing Blizzard data for item ${itemId}:`, error.message);
|
|
resolve(null);
|
|
}
|
|
});
|
|
}).on('error', (error) => {
|
|
console.error(`Error fetching from Blizzard for item ${itemId}:`, error.message);
|
|
resolve(null);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Maps Blizzard inventory types to our slot names
|
|
* @param {string} inventoryType - Blizzard inventory type
|
|
* @returns {string|null} Our slot name or null
|
|
*/
|
|
function mapSlotFromBlizzard(inventoryType) {
|
|
const typeMap = {
|
|
'HEAD': 'head',
|
|
'NECK': 'neck',
|
|
'SHOULDER': 'shoulders',
|
|
'BODY': 'chest',
|
|
'CHEST': 'chest',
|
|
'WAIST': 'waist',
|
|
'LEGS': 'legs',
|
|
'FEET': 'feet',
|
|
'WRIST': 'wrist',
|
|
'HAND': 'hands',
|
|
'FINGER': 'finger',
|
|
'TRINKET': 'trinket',
|
|
'WEAPON': '1h-weapon',
|
|
'SHIELD': 'off-hand',
|
|
'RANGED': '2h-weapon',
|
|
'CLOAK': 'back',
|
|
'WEAPON_2H': '2h-weapon',
|
|
'WEAPON_MAIN_HAND': '1h-weapon',
|
|
'WEAPON_OFF_HAND': 'off-hand',
|
|
'HOLDABLE': 'off-hand',
|
|
'AMMO': null,
|
|
'THROWN': '1h-weapon',
|
|
'RANGED_RIGHT': '2h-weapon',
|
|
'QUIVER': null,
|
|
'RELIC': 'trinket'
|
|
};
|
|
|
|
return typeMap[inventoryType] || null;
|
|
}
|
|
|
|
/**
|
|
* Fetches item media (icon) from Blizzard API
|
|
* @param {number} itemId - The item ID
|
|
* @param {string} region - The region (default: 'us')
|
|
* @returns {Promise<string|null>} Icon URL or null if failed
|
|
*/
|
|
async function fetchItemMedia(itemId, region = 'us') {
|
|
const token = await getAccessToken();
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const options = {
|
|
hostname: `${region}.api.blizzard.com`,
|
|
port: 443,
|
|
path: `/data/wow/media/item/${itemId}?namespace=static-${region}&locale=en_US&access_token=${token}`,
|
|
method: 'GET'
|
|
};
|
|
|
|
https.get(options, (res) => {
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
const response = JSON.parse(data);
|
|
|
|
if (response.assets && response.assets.length > 0) {
|
|
const iconAsset = response.assets.find(asset => asset.key === 'icon');
|
|
resolve(iconAsset ? iconAsset.value : null);
|
|
} else {
|
|
resolve(null);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error parsing media data for item ${itemId}:`, error.message);
|
|
resolve(null);
|
|
}
|
|
});
|
|
}).on('error', (error) => {
|
|
console.error(`Error fetching media for item ${itemId}:`, error.message);
|
|
resolve(null);
|
|
});
|
|
});
|
|
}
|
|
|
|
export {
|
|
fetchFromBlizzard,
|
|
fetchItemMedia,
|
|
getAccessToken,
|
|
mapSlotFromBlizzard
|
|
};
|