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

@@ -11,7 +11,7 @@ module.exports = {
async execute(interaction) {
let id
if (!interaction.options.getInteger('id')) {
id = await torn.self.id()
id = torn.self.player_id
console.log(`Profile: Looking up "${id}"`)
} else {
id = interaction.options.getInteger('id');

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ module.exports = async (client, torn, config) => {
if (slot.item_requirement) {
if (slot.item_requirement.is_available === false) {
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}`);
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++;

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}`);
if (crime.rewards.money === 0) {
const itemPromises = crime.rewards.items.map(item =>
torn.cache.item(item.id).then(itemData => ({
torn.item(item.id, true).then(itemData => ({
quantity: item.quantity,
name: itemData.name,
value: itemData.value.market_price

219
torn.js
View File

@@ -26,9 +26,14 @@ try {
}
// Constants
const TIME_12H = 12 * 60 * 60 * 1000;
const TIME_7D = 7 * 24 * 60 * 60 * 1000;
const TIME_30D = 30 * 24 * 60 * 60 * 1000;
const HOURS = 60 * 60 * 1000;
const TTL = {
USER: 12 * HOURS,
FACTION: 12 * HOURS,
COMPANY: 12 * HOURS,
ITEM: 7 * 24 * HOURS,
ITEM_LOOKUP: 30 * 24 * HOURS
};
// Helper to save cache
function saveCache() {
@@ -40,8 +45,12 @@ function saveCache() {
}
// Generic Caching Helper
async function getCached(collectionName, id, fetchFn, ttl) {
async function getCached(collectionName, id, fetchFn, ttl, force = false) {
const now = new Date().getTime();
// Ensure nested object exists
if (!cache[collectionName]) cache[collectionName] = {};
const item = cache[collectionName][id];
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}`);
return item;
} 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 {
// 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();
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
// or just the result. The original returned the cached item in the cache wrapper.
// Let's return the result from fetchFn which is usually the data.
// However, the original cache wrappers returned `cache.users[user]`.
// Let's see if there is a difference.
// `api.user.basic` returns `data`. `cache.users[user]` is a subset of `data`?
// 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.
// Update cache with full result
cache[collectionName][id] = {
...result,
updated: new Date().toISOString()
};
saveCache();
return cache[collectionName][id];
} catch (e) {
@@ -100,16 +95,24 @@ async function fetchApi(path) {
const data = await response.json();
if (data.error) {
console.error(`Torn API Error on ${path}:`, JSON.stringify(data.error));
throw new Error(data.error.error || "Torn API Error");
}
return data;
}
const api = {
self: {}, // Will be populated by readyCheck
readyCheck: async (key) => {
const url = `https://api.torn.com/user/?selections=basic&key=${key}`;
const response = await fetch(url);
const data = await response.json();
try {
// Fetch own 'basic' data using V2 (which returns profile object)
// By passing null/undefined as user, api.user.basic defaults to 'self' cache key
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 () => {
@@ -124,47 +127,22 @@ const api = {
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: {
async basic(user) {
const data = await fetchApi(`https://api.torn.com/user/${user}?selections=basic`);
const now = new Date();
cache.users[user] = {
name: data.name,
player_id: data.player_id,
level: data.level,
gender: data.gender,
updated: now.toISOString()
};
saveCache();
return data;
async basic(user, force = false) {
const endpoint = user ? `https://api.torn.com/v2/user/${user}/basic` : `https://api.torn.com/v2/user/basic`;
return getCached('users', user || 'self', async () => {
const data = await fetchApi(endpoint);
if (data.profile) data.profile.player_id = data.profile.id; // Shim for V1 compatibility
return data.profile; // V2 wraps in 'profile'
}, TTL.USER, force);
},
async profile(user) {
const data = await fetchApi(`https://api.torn.com/user/${user}?selections=profile`);
const now = new Date();
cache.users[user] = {
name: data.name,
player_id: data.player_id,
level: data.level,
gender: data.gender,
updated: now.toISOString()
};
saveCache();
return data;
async profile(user, force = false) {
const endpoint = user ? `https://api.torn.com/v2/user/${user}/profile` : `https://api.torn.com/v2/user/profile`;
return getCached('users', user || 'self', async () => {
const data = await fetchApi(endpoint);
if (data.profile) data.profile.player_id = data.profile.id; // Shim for V1 compatibility
return data.profile; // V2 wraps in 'profile'
}, TTL.USER, force);
},
async stats(user, category, statName) {
let url = `https://api.torn.com/v2/user`;
@@ -173,28 +151,36 @@ const api = {
if (statName) { url += `&stat=${statName}`; }
return fetchApi(url);
},
// Added lookup to maintain feature parity if it was ever needed, though not in original user object
},
faction: {
async basic(faction) {
const endpoint = faction ? `https://api.torn.com/v2/faction/${faction}/basic` : `https://api.torn.com/v2/faction/basic`;
const response = await fetchApi(endpoint);
// v2 return structure: { basic: { ... } }
const data = response.basic;
async basic(faction, force = false) {
// If faction is null, we can't key by ID easily until we fetch.
// For now, let's assume if faction is provided we use it as key.
// If not provided, we might be fetching our own faction.
// 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();
// Store by ID. If faction is null (own faction), we rely on data.id
cache.factions[data.id] = {
name: data.name,
leader_id: data.leader_id,
capacity: data.capacity,
rank: data.rank,
best_chain: data.best_chain,
updated: now.toISOString()
};
// Special handling: if faction is undefined, we can't check cache by ID easily without knowing ID.
// However, we can use a special key like 'own' or skip cache check pre-fetch?
// Better: If no ID provided, we just fetch to be safe, OR we assume config.factionID if we had it.
// Let's implement transparent fetching without ID -> fetch -> cache by ID.
if (!faction) {
const endpoint = `https://api.torn.com/v2/faction/basic`;
const response = await fetchApi(endpoint);
const data = response.basic;
// We can update cache here manually
cache.factions[data.id] = { ...data, updated: new Date().toISOString() };
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) {
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
// Original: module.exports.company = async ...
// So api.company should be a function
company: async (company) => {
const endpoint = company ? `https://api.torn.com/company/${company}?selections=profile` : `https://api.torn.com/company/?selections=profile`;
company: async (company, force = false) => {
if (!company) {
const endpoint = `https://api.torn.com/company/?selections=profile`;
const data = await fetchApi(endpoint);
const now = new Date();
// company ID is data.company.ID
cache.companies[data.company.ID] = {
name: data.company.name,
id: data.company.ID,
company_type: data.company.company_type,
director_id: data.company.director,
rating: data.company.rating,
updated: now.toISOString()
};
// ID is data.company.ID
// Torn API v1/v2 difference? URL says /company/? so likely v1 standard structure
// Let's assume data.company exists.
if (data.company) {
cache.companies[data.company.ID] = { ...data.company, updated: new Date().toISOString() };
saveCache();
return data.company;
}
return data;
}
return getCached('companies', company, async () => {
const endpoint = `https://api.torn.com/company/${company}?selections=profile`;
const data = await fetchApi(endpoint);
return data.company;
}, TTL.COMPANY, force);
},
// item was a function with a .lookup property
item: Object.assign(
async (item) => {
async (item, force = false) => {
return getCached('items', item, async () => {
const data = await fetchApi(`https://api.torn.com/v2/torn/${item}/items?sort=ASC`);
const now = new Date();
cache.items[item] = data.items[0]; // Assuming item is ID
if (cache.items[item]) {
cache.items[item].updated = now.toISOString();
}
saveCache();
return data.items[0];
}, TTL.ITEM, force);
},
{
lookup: async (itemName) => {
@@ -292,7 +276,7 @@ const api = {
let last = 0;
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}`);
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;