refactor caching to be better , uhh changed some things to v2,,,, other things?
This commit is contained in:
@@ -11,7 +11,7 @@ module.exports = {
|
|||||||
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');
|
||||||
|
|||||||
@@ -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++;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
2
index.js
2
index.js
@@ -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();
|
||||||
|
|||||||
@@ -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++;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
219
torn.js
219
torn.js
@@ -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
|
||||||
|
const data = await api.user.basic(null, true);
|
||||||
|
api.self = data;
|
||||||
console.log(`Torn: Connected as ${data.name} [${data.player_id}]`);
|
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
|
||||||
|
cache.factions[data.id] = { ...data, updated: new Date().toISOString() };
|
||||||
saveCache();
|
saveCache();
|
||||||
return data;
|
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 endpoint = company ? `https://api.torn.com/company/${company}?selections=profile` : `https://api.torn.com/company/?selections=profile`;
|
|
||||||
const data = await fetchApi(endpoint);
|
const data = await fetchApi(endpoint);
|
||||||
const now = new Date();
|
// ID is data.company.ID
|
||||||
// company ID is data.company.ID
|
// Torn API v1/v2 difference? URL says /company/? so likely v1 standard structure
|
||||||
cache.companies[data.company.ID] = {
|
// Let's assume data.company exists.
|
||||||
name: data.company.name,
|
if (data.company) {
|
||||||
id: data.company.ID,
|
cache.companies[data.company.ID] = { ...data.company, updated: new Date().toISOString() };
|
||||||
company_type: data.company.company_type,
|
|
||||||
director_id: data.company.director,
|
|
||||||
rating: data.company.rating,
|
|
||||||
updated: now.toISOString()
|
|
||||||
};
|
|
||||||
saveCache();
|
saveCache();
|
||||||
return data.company;
|
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 was a function with a .lookup property
|
||||||
item: Object.assign(
|
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 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];
|
return data.items[0];
|
||||||
|
}, TTL.ITEM, force);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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;
|
||||||
Reference in New Issue
Block a user