From 3b8aeff340accb8f5dbebede06d814938801d54b Mon Sep 17 00:00:00 2001 From: Kira Date: Mon, 12 Jan 2026 10:06:40 -0500 Subject: [PATCH] refactor caching to be better , uhh changed some things to v2,,,, other things? --- commands/api/profile.js | 6 +- commands/stupid/howiskzn.js | 4 +- commands/utility/calcpayout.js | 2 +- index.js | 2 +- tasks/noItemOC.js | 2 +- tasks/unpaidOC.js | 2 +- torn.js | 235 +++++++++++++++------------------ 7 files changed, 112 insertions(+), 141 deletions(-) diff --git a/commands/api/profile.js b/commands/api/profile.js index 111838b..fd2876d 100644 --- a/commands/api/profile.js +++ b/commands/api/profile.js @@ -5,13 +5,13 @@ module.exports = { data: new SlashCommandBuilder() .setName('profile') .setDescription('Get your Torn profile') - .addIntegerOption(option => + .addIntegerOption(option => option.setName('id') - .setDescription('User ID')), + .setDescription('User ID')), 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'); diff --git a/commands/stupid/howiskzn.js b/commands/stupid/howiskzn.js index 3e95e62..34ddb6d 100644 --- a/commands/stupid/howiskzn.js +++ b/commands/stupid/howiskzn.js @@ -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++; diff --git a/commands/utility/calcpayout.js b/commands/utility/calcpayout.js index 25b8105..3bd1897 100644 --- a/commands/utility/calcpayout.js +++ b/commands/utility/calcpayout.js @@ -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; } diff --git a/index.js b/index.js index 5051e9c..1d00fa1 100644 --- a/index.js +++ b/index.js @@ -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(); diff --git a/tasks/noItemOC.js b/tasks/noItemOC.js index 4bbc24f..8f80da2 100644 --- a/tasks/noItemOC.js +++ b/tasks/noItemOC.js @@ -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++; diff --git a/tasks/unpaidOC.js b/tasks/unpaidOC.js index f604ed0..2155df8 100644 --- a/tasks/unpaidOC.js +++ b/tasks/unpaidOC.js @@ -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 diff --git a/torn.js b/torn.js index 81dccae..3e5655e 100644 --- a/torn.js +++ b/torn.js @@ -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(); - console.log(`Torn: Connected as ${data.name} [${data.player_id}]`); + 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() - }; - saveCache(); - return data; + // 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`; - 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() - }; - saveCache(); - return data.company; + company: async (company, force = false) => { + if (!company) { + const endpoint = `https://api.torn.com/company/?selections=profile`; + const data = await fetchApi(endpoint); + // 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) => { - 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]; + async (item, force = false) => { + return getCached('items', item, async () => { + const data = await fetchApi(`https://api.torn.com/v2/torn/${item}/items?sort=ASC`); + 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; \ No newline at end of file