refactor caching to be better , uhh changed some things to v2,,,, other things?

This commit is contained in:
2026-01-12 10:06:40 -05:00
parent d00d5bb313
commit 3b8aeff340
7 changed files with 112 additions and 141 deletions

View File

@@ -5,13 +5,13 @@ module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('profile') .setName('profile')
.setDescription('Get your Torn profile') .setDescription('Get your Torn profile')
.addIntegerOption(option => .addIntegerOption(option =>
option.setName('id') option.setName('id')
.setDescription('User ID')), .setDescription('User ID')),
async execute(interaction) { async execute(interaction) {
let id let id
if (!interaction.options.getInteger('id')) { if (!interaction.options.getInteger('id')) {
id = await torn.self.id() id = torn.self.player_id
console.log(`Profile: Looking up "${id}"`) console.log(`Profile: Looking up "${id}"`)
} else { } else {
id = interaction.options.getInteger('id'); id = interaction.options.getInteger('id');

View File

@@ -98,7 +98,7 @@ module.exports = {
let companyFemales = 0; let companyFemales = 0;
let companyTotal = 0; let companyTotal = 0;
const companyFemalePromises = Object.entries(company.employees).map(([user]) => { const companyFemalePromises = Object.entries(company.employees).map(([user]) => {
return torn.cache.user(user).then(data => { return torn.user.basic(user).then(data => {
companyTotal++; companyTotal++;
if (data.gender === "Female") { if (data.gender === "Female") {
companyFemales++; companyFemales++;
@@ -110,7 +110,7 @@ module.exports = {
let factionTotal = 0; let factionTotal = 0;
const factionMembers = await torn.faction.members(KZNKing.faction.faction_id); const factionMembers = await torn.faction.members(KZNKing.faction.faction_id);
const factionFemalePromises = factionMembers.map((user) => { const factionFemalePromises = factionMembers.map((user) => {
return torn.cache.user(user.id).then(data => { return torn.user.basic(user.id).then(data => {
factionTotal++; factionTotal++;
if (data.gender === "Female") { if (data.gender === "Female") {
factionFemales++; factionFemales++;

View File

@@ -55,7 +55,7 @@ module.exports = {
? rewards.items.find(i => (i.id == itemId || i.ID == itemId)).quantity ? rewards.items.find(i => (i.id == itemId || i.ID == itemId)).quantity
: rewards.items[itemId]; : rewards.items[itemId];
const itemData = await torn.item(itemId); const itemData = await torn.item(itemId, true);
if (itemData && itemData.value && itemData.value.market_price) { if (itemData && itemData.value && itemData.value.market_price) {
calculatedTotal += itemData.value.market_price * qt; calculatedTotal += itemData.value.market_price * qt;
} }

View File

@@ -44,7 +44,7 @@ const client = new Client({
}); });
client.once(Events.ClientReady, readyClient => { client.once(Events.ClientReady, readyClient => {
console.log(`Discord: Connected as ${readyClient.user.tag}`); console.log(`Discord: Connected as ${readyClient.user.tag}`);
torn.readyCheck(config.torn); torn.readyCheck();
}); });
client.login(config.token); client.login(config.token);
client.commands = new Collection(); client.commands = new Collection();

View File

@@ -17,7 +17,7 @@ module.exports = async (client, torn, config) => {
if (slot.item_requirement) { if (slot.item_requirement) {
if (slot.item_requirement.is_available === false) { if (slot.item_requirement.is_available === false) {
const username = (await torn.user.profile(slot.user.id)).name; const username = (await torn.user.profile(slot.user.id)).name;
const itemname = (await torn.cache.item(slot.item_requirement.id)).name; const itemname = (await torn.item(slot.item_requirement.id)).name;
console.debug(`noItemOC: Found crime with unavailable item: ${crime.name}: ${slot.user.id}`); console.debug(`noItemOC: Found crime with unavailable item: ${crime.name}: ${slot.user.id}`);
message += `[${username}](https://www.torn.com/profiles.php?XID=${slot.user.id}) needs [${itemname}](https://www.torn.com/page.php?sid=ItemMarket#/market/view=search&itemID=${slot.item_requirement.id}) for [${crime.name}](https://www.torn.com/factions.php?step=your&type=1#/tab=crimes&crimeId=${crime.id})\n`; message += `[${username}](https://www.torn.com/profiles.php?XID=${slot.user.id}) needs [${itemname}](https://www.torn.com/page.php?sid=ItemMarket#/market/view=search&itemID=${slot.item_requirement.id}) for [${crime.name}](https://www.torn.com/factions.php?step=your&type=1#/tab=crimes&crimeId=${crime.id})\n`;
itemsneeded++; itemsneeded++;

View File

@@ -22,7 +22,7 @@ module.exports = async (client, torn, config) => {
.setURL(`https://www.torn.com/factions.php?step=your&type=7#/tab=crimes&crimeId=${crime.id}`); .setURL(`https://www.torn.com/factions.php?step=your&type=7#/tab=crimes&crimeId=${crime.id}`);
if (crime.rewards.money === 0) { if (crime.rewards.money === 0) {
const itemPromises = crime.rewards.items.map(item => const itemPromises = crime.rewards.items.map(item =>
torn.cache.item(item.id).then(itemData => ({ torn.item(item.id, true).then(itemData => ({
quantity: item.quantity, quantity: item.quantity,
name: itemData.name, name: itemData.name,
value: itemData.value.market_price value: itemData.value.market_price

235
torn.js
View File

@@ -26,9 +26,14 @@ try {
} }
// Constants // Constants
const TIME_12H = 12 * 60 * 60 * 1000; const HOURS = 60 * 60 * 1000;
const TIME_7D = 7 * 24 * 60 * 60 * 1000; const TTL = {
const TIME_30D = 30 * 24 * 60 * 60 * 1000; USER: 12 * HOURS,
FACTION: 12 * HOURS,
COMPANY: 12 * HOURS,
ITEM: 7 * 24 * HOURS,
ITEM_LOOKUP: 30 * 24 * HOURS
};
// Helper to save cache // Helper to save cache
function saveCache() { function saveCache() {
@@ -40,8 +45,12 @@ function saveCache() {
} }
// Generic Caching Helper // Generic Caching Helper
async function getCached(collectionName, id, fetchFn, ttl) { async function getCached(collectionName, id, fetchFn, ttl, force = false) {
const now = new Date().getTime(); const now = new Date().getTime();
// Ensure nested object exists
if (!cache[collectionName]) cache[collectionName] = {};
const item = cache[collectionName][id]; const item = cache[collectionName][id];
let lastUpdated = 0; let lastUpdated = 0;
@@ -53,37 +62,23 @@ async function getCached(collectionName, id, fetchFn, ttl) {
} }
} }
if (item && (now - lastUpdated < ttl)) { if (!force && item && (now - lastUpdated < ttl)) {
console.debug(`Cache: Hit for ${collectionName} ${item.name || id}`); console.debug(`Cache: Hit for ${collectionName} ${item.name || id}`);
return item; return item;
} else { } else {
console.debug(`Cache: Miss for ${collectionName} ${id || 'unknown'}`); if (force) console.debug(`Cache: Force refresh for ${collectionName} ${id || 'unknown'}`);
else console.debug(`Cache: Miss for ${collectionName} ${id || 'unknown'}`);
try { try {
// The fetchFn is expected to update the cache and return the data, or we can structure it differently.
// Based on the refactor code below, the fetchFn calls saveCache() and returns the data.
// But wait, the original logic for checking cache was inside the 'cache' object functions,
// calling the specific fetcher which updated the cache.
// In the refactored 'api.cache.user' below, I call 'api.user.basic(user)'.
// 'api.user.basic' updates the cache and returns data.
// So this helper just needs to return that result.
// BUT, I need to make sure I return the logical object.
const result = await fetchFn(); const result = await fetchFn();
console.debug(`Cache: Resolved ${collectionName} ${id}`); console.debug(`Cache: Resolved ${collectionName} ${result.name || result.title || id}`);
// If the fetchFn updated the cache, we can return the cached item to be consistent // Update cache with full result
// or just the result. The original returned the cached item in the cache wrapper. cache[collectionName][id] = {
// Let's return the result from fetchFn which is usually the data. ...result,
// However, the original cache wrappers returned `cache.users[user]`. updated: new Date().toISOString()
// Let's see if there is a difference. };
// `api.user.basic` returns `data`. `cache.users[user]` is a subset of `data`? saveCache();
// Original:
// `cache.users[user] = { name, player_id, level, ... }`
// `return(data)` (full api response)
// But `module.exports.cache.user` returned `cache.users[user]`.
// So the CACHE wrapper returned the CACHED OBJECT (subset), while the FETCH function returned the FULL API response.
// This is a subtle difference.
// If I want to maintain compatibility, `getCached` should return the cached item from `cache` after fetching.
return cache[collectionName][id]; return cache[collectionName][id];
} catch (e) { } catch (e) {
@@ -100,16 +95,24 @@ async function fetchApi(path) {
const data = await response.json(); const data = await response.json();
if (data.error) { if (data.error) {
console.error(`Torn API Error on ${path}:`, JSON.stringify(data.error)); console.error(`Torn API Error on ${path}:`, JSON.stringify(data.error));
throw new Error(data.error.error || "Torn API Error");
} }
return data; return data;
} }
const api = { const api = {
self: {}, // Will be populated by readyCheck
readyCheck: async (key) => { readyCheck: async (key) => {
const url = `https://api.torn.com/user/?selections=basic&key=${key}`; try {
const response = await fetch(url); // Fetch own 'basic' data using V2 (which returns profile object)
const data = await response.json(); // By passing null/undefined as user, api.user.basic defaults to 'self' cache key
console.log(`Torn: Connected as ${data.name} [${data.player_id}]`); const data = await api.user.basic(null, true);
api.self = data;
console.log(`Torn: Connected as ${data.name} [${data.player_id}]`);
} catch (e) {
console.error("Torn: Critical error during startup check", e);
}
}, },
test: async () => { test: async () => {
@@ -124,47 +127,22 @@ const api = {
return response.json(); return response.json();
}, },
cache: {
async user(user) {
return getCached('users', user, async () => await api.user.basic(user), TIME_12H);
},
async faction(faction) {
return getCached('factions', faction, async () => await api.faction.basic(faction), TIME_12H);
},
async company(company) {
return getCached('companies', company, async () => await api.company(company), TIME_12H);
},
async item(item) {
return getCached('items', item, async () => await api.item(item), TIME_7D);
}
},
user: { user: {
async basic(user) { async basic(user, force = false) {
const data = await fetchApi(`https://api.torn.com/user/${user}?selections=basic`); const endpoint = user ? `https://api.torn.com/v2/user/${user}/basic` : `https://api.torn.com/v2/user/basic`;
const now = new Date(); return getCached('users', user || 'self', async () => {
cache.users[user] = { const data = await fetchApi(endpoint);
name: data.name, if (data.profile) data.profile.player_id = data.profile.id; // Shim for V1 compatibility
player_id: data.player_id, return data.profile; // V2 wraps in 'profile'
level: data.level, }, TTL.USER, force);
gender: data.gender,
updated: now.toISOString()
};
saveCache();
return data;
}, },
async profile(user) { async profile(user, force = false) {
const data = await fetchApi(`https://api.torn.com/user/${user}?selections=profile`); const endpoint = user ? `https://api.torn.com/v2/user/${user}/profile` : `https://api.torn.com/v2/user/profile`;
const now = new Date(); return getCached('users', user || 'self', async () => {
cache.users[user] = { const data = await fetchApi(endpoint);
name: data.name, if (data.profile) data.profile.player_id = data.profile.id; // Shim for V1 compatibility
player_id: data.player_id, return data.profile; // V2 wraps in 'profile'
level: data.level, }, TTL.USER, force);
gender: data.gender,
updated: now.toISOString()
};
saveCache();
return data;
}, },
async stats(user, category, statName) { async stats(user, category, statName) {
let url = `https://api.torn.com/v2/user`; let url = `https://api.torn.com/v2/user`;
@@ -173,28 +151,36 @@ const api = {
if (statName) { url += `&stat=${statName}`; } if (statName) { url += `&stat=${statName}`; }
return fetchApi(url); return fetchApi(url);
}, },
// Added lookup to maintain feature parity if it was ever needed, though not in original user object
}, },
faction: { faction: {
async basic(faction) { async basic(faction, force = false) {
const endpoint = faction ? `https://api.torn.com/v2/faction/${faction}/basic` : `https://api.torn.com/v2/faction/basic`; // If faction is null, we can't key by ID easily until we fetch.
const response = await fetchApi(endpoint); // For now, let's assume if faction is provided we use it as key.
// v2 return structure: { basic: { ... } } // If not provided, we might be fetching our own faction.
const data = response.basic; // We can key it by "own" or similar if needed, but let's see.
// If faction is missing, we fetch own faction, resulting data has ID.
const now = new Date(); // Special handling: if faction is undefined, we can't check cache by ID easily without knowing ID.
// Store by ID. If faction is null (own faction), we rely on data.id // However, we can use a special key like 'own' or skip cache check pre-fetch?
cache.factions[data.id] = { // Better: If no ID provided, we just fetch to be safe, OR we assume config.factionID if we had it.
name: data.name, // Let's implement transparent fetching without ID -> fetch -> cache by ID.
leader_id: data.leader_id,
capacity: data.capacity, if (!faction) {
rank: data.rank, const endpoint = `https://api.torn.com/v2/faction/basic`;
best_chain: data.best_chain, const response = await fetchApi(endpoint);
updated: now.toISOString() const data = response.basic;
}; // We can update cache here manually
saveCache(); cache.factions[data.id] = { ...data, updated: new Date().toISOString() };
return data; saveCache();
return data;
}
return getCached('factions', faction, async () => {
const endpoint = `https://api.torn.com/v2/faction/${faction}/basic`;
const response = await fetchApi(endpoint);
return response.basic;
}, TTL.FACTION, force);
}, },
async members(faction) { async members(faction) {
const endpoint = faction ? `https://api.torn.com/v2/faction/${faction}/members?striptags=true` : `https://api.torn.com/v2/faction/members?striptags=true`; const endpoint = faction ? `https://api.torn.com/v2/faction/${faction}/members?striptags=true` : `https://api.torn.com/v2/faction/members?striptags=true`;
@@ -249,37 +235,35 @@ const api = {
} }
}, },
// company was a top-level function in export, but also used as property company: async (company, force = false) => {
// Original: module.exports.company = async ... if (!company) {
// So api.company should be a function const endpoint = `https://api.torn.com/company/?selections=profile`;
company: async (company) => { const data = await fetchApi(endpoint);
const endpoint = company ? `https://api.torn.com/company/${company}?selections=profile` : `https://api.torn.com/company/?selections=profile`; // ID is data.company.ID
const data = await fetchApi(endpoint); // Torn API v1/v2 difference? URL says /company/? so likely v1 standard structure
const now = new Date(); // Let's assume data.company exists.
// company ID is data.company.ID if (data.company) {
cache.companies[data.company.ID] = { cache.companies[data.company.ID] = { ...data.company, updated: new Date().toISOString() };
name: data.company.name, saveCache();
id: data.company.ID, return data.company;
company_type: data.company.company_type, }
director_id: data.company.director, return data;
rating: data.company.rating, }
updated: now.toISOString()
}; return getCached('companies', company, async () => {
saveCache(); const endpoint = `https://api.torn.com/company/${company}?selections=profile`;
return data.company; const data = await fetchApi(endpoint);
return data.company;
}, TTL.COMPANY, force);
}, },
// item was a function with a .lookup property // item was a function with a .lookup property
item: Object.assign( item: Object.assign(
async (item) => { async (item, force = false) => {
const data = await fetchApi(`https://api.torn.com/v2/torn/${item}/items?sort=ASC`); return getCached('items', item, async () => {
const now = new Date(); const data = await fetchApi(`https://api.torn.com/v2/torn/${item}/items?sort=ASC`);
cache.items[item] = data.items[0]; // Assuming item is ID return data.items[0];
if (cache.items[item]) { }, TTL.ITEM, force);
cache.items[item].updated = now.toISOString();
}
saveCache();
return data.items[0];
}, },
{ {
lookup: async (itemName) => { lookup: async (itemName) => {
@@ -292,7 +276,7 @@ const api = {
let last = 0; let last = 0;
try { last = new Date(cache.items[itemId].updated).getTime(); } catch (e) { } try { last = new Date(cache.items[itemId].updated).getTime(); } catch (e) { }
if (now - last < TIME_30D) { if (now - last < TTL.ITEM_LOOKUP) {
console.debug(`Cache: Hit for item ${cache.items[itemId].name}`); console.debug(`Cache: Hit for item ${cache.items[itemId].name}`);
return cache.items[itemId]; return cache.items[itemId];
} }
@@ -321,20 +305,7 @@ const api = {
} }
), ),
self: {
async id() {
if (!config.tornid) {
const url = `https://api.torn.com/user/?selections=basic&key=${config.torn}`;
const response = await fetch(url);
const data = await response.json();
config.tornid = data.player_id;
console.log(`Torn: Retrieved default ID as "${data.player_id}"`);
return data.player_id;
} else {
return config.tornid;
}
}
}
}; };
module.exports = api; module.exports = api;