inactive command to show people not participating in OC

This commit is contained in:
2026-01-12 16:46:00 -05:00
parent 3b8aeff340
commit ee6dbc1df2
7 changed files with 428 additions and 21 deletions

View File

@@ -7,14 +7,17 @@ module.exports = {
.setDescription('How is KZNKing doing'),
async execute(interaction) {
const kznID = 3392710
const KZNKing = await torn.user.profile(kznID);
let message = `${KZNKing.name} has ${KZNKing.friends} friends and ${KZNKing.enemies} enemies. `;
(KZNKing.married.duration) ? message += `He has been married to [${KZNKing.married.spouse_name}](https://www.torn.com/profiles.php?XID=${KZNKing.married.spouse_id}) for ${KZNKing.married.duration} days. ` : message += `He is not married. `;
(KZNKing.property === "Private Island") ? message += `He has a Private Island. ` : message += `He does not have a Private Island. `;
(KZNKing.job.position === "Director") ? message += `He is director of ${KZNKing.job.company_name}. ` : message += `He is not director of his company. `;
(KZNKing.faction.position === "Leader") ? message += `He is leader of ${KZNKing.faction.faction_name}. ` : message += `He is not leader of his faction. `;
const data = await torn.user.get(kznID, ['profile', 'job', 'faction']);
const company = (await torn.company(KZNKing.job.company_id));
const KZNKing = { ...data.profile, job: data.job, faction: data.faction };
let message = `${KZNKing.name} has ${KZNKing.friends} friends and ${KZNKing.enemies} enemies. `;
(KZNKing.spouse && KZNKing.spouse.days_married) ? message += `He has been married to [${KZNKing.spouse.name}](https://www.torn.com/profiles.php?XID=${KZNKing.spouse.id}) for ${KZNKing.spouse.days_married} days. ` : message += `He is not married. `;
(KZNKing.property === "Private Island") ? message += `He has a Private Island. ` : message += `He does not have a Private Island. `;
(KZNKing.job.position === "Director") ? message += `He is director of ${KZNKing.job.name}. ` : message += `He is not director of his company. `;
(KZNKing.faction.position === "Leader") ? message += `He is leader of ${KZNKing.faction.name}. ` : message += `He is not leader of his faction. `;
const company = (await torn.company(KZNKing.job.id));
const embeds = [];
if (KZNKing.job.position === "Director") {
const jobEmbed = new EmbedBuilder()
@@ -60,35 +63,35 @@ module.exports = {
embeds.push(jobEmbed);
}
const faction = await torn.faction.basic(KZNKing.faction.faction_id)
const factionBasic = await torn.faction.basic(KZNKing.faction.id)
if (KZNKing.faction.position === "Leader") {
const facEmbed = new EmbedBuilder()
.setTitle(faction.name)
.setURL(`https://www.torn.com/factions.php?step=profile&ID=${faction.id}`)
.setTitle(factionBasic.name)
.setURL(`https://www.torn.com/factions.php?step=profile&ID=${factionBasic.id}`)
.addFields(
{
name: "Members",
value: `${faction.members}/${faction.capacity}`,
value: `${factionBasic.members}/${factionBasic.capacity}`,
inline: true
},
{
name: "Rank",
value: `${faction.rank.name} ${faction.rank.division}`,
value: `${factionBasic.rank.name} ${factionBasic.rank.division}`,
inline: true
},
{
name: "Respect",
value: `${faction.respect.toLocaleString()}`,
value: `${factionBasic.respect.toLocaleString()}`,
inline: true
},
{
name: "Age",
value: `${faction.days_old}`,
value: `${factionBasic.days_old}`,
inline: true
},
{
name: "Wars Won",
value: `${faction.rank.wins}`,
value: `${factionBasic.rank.wins}`,
inline: true
},
);
@@ -108,7 +111,7 @@ module.exports = {
let factionFemales = 0;
let factionTotal = 0;
const factionMembers = await torn.faction.members(KZNKing.faction.faction_id);
const factionMembers = await torn.faction.members(KZNKing.faction.id);
const factionFemalePromises = factionMembers.map((user) => {
return torn.user.basic(user.id).then(data => {
factionTotal++;

View File

@@ -0,0 +1,112 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const fs = require('fs');
const path = require('path');
const torn = require('../../torn.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('inactive')
.setDescription('Shows users who haven\'t participated in an OC recently.')
.addIntegerOption(option =>
option.setName('days')
.setDescription('Number of days of inactivity (default 3)')
.setMinValue(1)
),
async execute(interaction) {
await interaction.deferReply();
const days = interaction.options.getInteger('days') || 3;
const cutoffTime = Date.now() - (days * 24 * 60 * 60 * 1000);
const statsPath = path.join(__dirname, '../../data/ocStats.json');
// Load tracked stats
let stats = {};
if (fs.existsSync(statsPath)) {
try {
stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
} catch (e) {
console.error("inactive: Failed to load ocStats.json", e);
}
}
let members = [];
try {
// Fetch own faction members
members = await torn.faction.members();
} catch (e) {
console.error("inactive: Failed to fetch members", e);
return interaction.editReply('Failed to fetch faction members from API.');
}
const inactiveUsers = [];
// Check each member
for (const member of members) {
const userId = member.id;
const userName = member.name;
const userStat = stats[userId];
if (!userStat) {
// Never seen in tracking
inactiveUsers.push({
id: userId,
name: userName,
lastSeen: null,
daysInactive: -1
});
} else {
if (userStat.lastSeen < cutoffTime) {
inactiveUsers.push({
id: userId,
name: userName,
lastSeen: new Date(userStat.lastSeen),
daysInactive: Math.floor((Date.now() - userStat.lastSeen) / (24 * 60 * 60 * 1000))
});
}
}
}
// Sort: Never seen first, then by longest inactivity
inactiveUsers.sort((a, b) => {
if (a.lastSeen === null && b.lastSeen === null) return 0;
if (a.lastSeen === null) return -1; // a comes first
if (b.lastSeen === null) return 1; // b comes first
return a.lastSeen - b.lastSeen; // Older timestamp (smaller) comes first
});
const embed = new EmbedBuilder()
.setTitle(`Inactive OC Members (> ${days} days)`)
.setColor(0xFF0000)
.setTimestamp();
if (inactiveUsers.length === 0) {
embed.setDescription(`Everyone has participated in an OC in the last ${days} days!`);
embed.setColor(0x00FF00);
} else {
const limit = 25;
const shownUsers = inactiveUsers.slice(0, limit);
shownUsers.forEach(user => {
let value = "";
if (user.lastSeen === null) {
value = "Never seen in OCs (since tracking started)";
} else {
const ts = Math.floor(user.lastSeen.getTime() / 1000);
value = `Last crime: <t:${ts}:d>\n(<t:${ts}:R>)`;
}
embed.addFields({
name: `${user.name} [${user.id}]`,
value: value,
inline: true
});
});
if (inactiveUsers.length > limit) {
embed.setFooter({ text: `...and ${inactiveUsers.length - limit} more.` });
}
}
await interaction.editReply({ embeds: [embed] });
},
};

View File

@@ -0,0 +1,85 @@
const { SlashCommandBuilder } = require('discord.js');
const fs = require('fs');
const path = require('path');
const { getMemberMap, processCrimes } = require('../../utils/ocLogic');
const torn = require('../../torn.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('scanoc')
.setDescription('Scans historical OCs to populate participation stats.')
.addIntegerOption(option =>
option.setName('days')
.setDescription('How many days back to scan (default 30)')
.setMinValue(1)
.setMaxValue(365)
),
async execute(interaction) {
await interaction.deferReply();
const days = interaction.options.getInteger('days') || 30;
const now = Date.now();
const fromTimestamp = Math.floor((now - (days * 24 * 60 * 60 * 1000)) / 1000);
const statsPath = path.join(__dirname, '../../data/ocStats.json');
// Load existing stats
let stats = {};
if (fs.existsSync(statsPath)) {
try {
stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
} catch (e) {
console.error("scanOC: Failed to load ocStats.json", e);
}
}
// Fetch faction members
const memberMap = await getMemberMap(torn);
await interaction.editReply(`Scanning OCs from the last ${days} days...`);
let crimesList = [];
const categories = ['recruiting', 'planned', 'active', 'successful', 'failed'];
for (const cat of categories) {
try {
// Fetch with a higher limit since we are scanning back further
const crimes = await torn.faction.crimes({
from: fromTimestamp,
sort: 'ASC',
category: cat,
limit: 300 // Reasonable batch size?
});
if (crimes && Array.isArray(crimes)) {
crimesList = crimesList.concat(crimes);
}
} catch (e) {
console.error(`scanOC: Failed to fetch crimes for category '${cat}'`, e);
}
}
if (crimesList.length === 0) {
return interaction.editReply(`Scan complete. No OCs found in the last ${days} days.`);
}
// Process with utility
const updates = await processCrimes(crimesList, stats, memberMap, torn);
// Save
if (updates > 0) {
try {
const dir = path.dirname(statsPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(statsPath, JSON.stringify(stats, null, 4));
await interaction.editReply(`Scan complete. Processed ${crimesList.length} crimes. Updated stats for ${updates} users.`);
} catch (e) {
console.error("scanOC: Failed to save stats", e);
await interaction.editReply(`Scan complete, but failed to save stats: ${e.message}`);
}
} else {
await interaction.editReply(`Scan complete. Processed ${crimesList.length} crimes. No new updates needed.`);
}
},
};