Skip to content

Commit

Permalink
🔀 Merge pull request #1603 from TF2Autobot/revert-remove-auto-check
Browse files Browse the repository at this point in the history
Revert removing auto-check for missing listings
  • Loading branch information
idinium96 authored Jul 18, 2023
2 parents 460dbb3 + b322f36 commit 6ce2aec
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 6 deletions.
231 changes: 225 additions & 6 deletions src/classes/Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { EResult, EPersonaState } from 'steam-user';
import TradeOfferManager, { CustomError, EconItem } from '@tf2autobot/tradeoffer-manager';
import SteamCommunity from '@tf2autobot/steamcommunity';
import SteamTotp from 'steam-totp';
import ListingManager from '@tf2autobot/bptf-listings';
import ListingManager, { Listing } from '@tf2autobot/bptf-listings';
import SchemaManager, { Effect, StrangeParts } from '@tf2autobot/tf2-schema';
import BptfLogin from '@tf2autobot/bptf-login';
import TF2 from '@tf2autobot/tf2';
import dayjs, { Dayjs } from 'dayjs';
import async from 'async';
import semver from 'semver';
import axios, { AxiosError } from 'axios';
import pluralize from 'pluralize';
import * as timersPromises from 'timers/promises';
import fs from 'fs';
import path from 'path';
Expand All @@ -25,7 +26,7 @@ import DiscordBot from './DiscordBot';
import { Message as DiscordMessage } from 'discord.js';

import InventoryManager from './InventoryManager';
import Pricelist, { EntryData, PricesDataObject } from './Pricelist';
import Pricelist, { Entry, EntryData, PricesDataObject } from './Pricelist';
import Friends from './Friends';
import Trades from './Trades';
import Listings from './Listings';
Expand Down Expand Up @@ -157,6 +158,20 @@ export default class Bot {

private halted = false;

public autoRefreshListingsInterval: NodeJS.Timeout;

private alreadyExecutedRefreshlist = false;

set isRecentlyExecuteRefreshlistCommand(setExecuted: boolean) {
this.alreadyExecutedRefreshlist = setExecuted;
}

private executedDelayTime = 30 * 60 * 1000;

set setRefreshlistExecutedDelay(delay: number) {
this.executedDelayTime = delay;
}

public sendStatsInterval: NodeJS.Timeout;

public periodicCheckAdmin: NodeJS.Timeout;
Expand Down Expand Up @@ -386,6 +401,9 @@ export default class Bot {
log.debug('Settings status in Discord to "idle"');
this.discordBot?.halt();

// disable auto-check for missing/mismatching listings
clearInterval(this.autoRefreshListingsInterval);

log.debug('Removing all listings due to halt mode turned on');
await this.listings.removeAll().catch((err: Error) => {
log.warn('Failed to remove all listings on enabling halt mode: ', err);
Expand All @@ -411,6 +429,9 @@ export default class Bot {
log.debug('Settings status in Discord to "online"');
this.discordBot?.unhalt();

// Re-initialize auto-check for missing/mismatching listings
this.startAutoRefreshListings();

return recrateListingsFailed;
}

Expand Down Expand Up @@ -527,10 +548,6 @@ export default class Bot {
});
}

private get sendStatsEnabled(): boolean {
return this.options.statistics.sendStats.enable;
}

private getLatestVersion(
attempt: 'first' | 'retry' = 'first'
): Promise<{ version: string; canUpdateRepo: boolean; updateMessage: string }> {
Expand Down Expand Up @@ -559,6 +576,208 @@ export default class Bot {
});
}

startAutoRefreshListings(): void {
// Automatically check for missing listings every 30 minutes
let pricelistLength = 0;

this.autoRefreshListingsInterval = setInterval(
() => {
const createListingsEnabled = this.options.miscSettings.createListings.enable;

if (this.halted) {
// Make sure not to run if halted
return;
}

if (this.alreadyExecutedRefreshlist || !createListingsEnabled) {
log.debug(
`❌ ${
this.alreadyExecutedRefreshlist
? 'Just recently executed refreshlist command'
: 'miscSettings.createListings.enable is set to false'
}, will not run automatic check for missing listings.`
);

setTimeout(() => {
this.startAutoRefreshListings();
}, this.executedDelayTime);

// reset to default
this.setRefreshlistExecutedDelay = 30 * 60 * 1000;
clearInterval(this.autoRefreshListingsInterval);
return;
}

pricelistLength = 0;
log.debug('Running automatic check for missing/mismatch listings...');

const listings: { [sku: string]: Listing[] } = {};
this.listingManager.getListings(false, async (err: AxiosError) => {
if (err) {
log.warn('Error getting listings on auto-refresh listings operation:', filterAxiosError(err));
setTimeout(() => {
this.startAutoRefreshListings();
}, 30 * 60 * 1000);
clearInterval(this.autoRefreshListingsInterval);
return;
}

const inventoryManager = this.inventoryManager;
const inventory = inventoryManager.getInventory;
const isFilterCantAfford = this.options.pricelist.filterCantAfford.enable;

this.listingManager.listings.forEach(listing => {
let listingSKU = listing.getSKU();
if (listing.intent === 1) {
if (this.options.normalize.painted.our && /;[p][0-9]+/.test(listingSKU)) {
listingSKU = listingSKU.replace(/;[p][0-9]+/, '');
}

if (this.options.normalize.festivized.our && listingSKU.includes(';festive')) {
listingSKU = listingSKU.replace(';festive', '');
}

if (this.options.normalize.strangeAsSecondQuality.our && listingSKU.includes(';strange')) {
listingSKU = listingSKU.replace(';strange', '');
}
} else {
if (/;[p][0-9]+/.test(listingSKU)) {
listingSKU = listingSKU.replace(/;[p][0-9]+/, '');
}
}

let match: Entry | null;
const assetIdPrice = this.pricelist.getPrice({ priceKey: listing.id.slice('440_'.length) });
if (null !== assetIdPrice) {
match = assetIdPrice;
} else {
match = this.pricelist.getPrice({ priceKey: listingSKU });
}

if (isFilterCantAfford && listing.intent === 0 && match !== null) {
const canAffordToBuy = inventoryManager.isCanAffordToBuy(match.buy, inventory);
if (!canAffordToBuy) {
// Listing for buying exist but we can't afford to buy, remove.
log.debug(`Intent buy, removed because can't afford: ${match.sku}`);
listing.remove();
}
}

if (listing.intent === 1 && match !== null && !match.enabled) {
// Listings for selling exist, but the item is currently disabled, remove it.
log.debug(`Intent sell, removed because not selling: ${match.sku}`);
listing.remove();
}

listings[listingSKU] = (listings[listingSKU] ?? []).concat(listing);
});

const pricelist = Object.assign({}, this.pricelist.getPrices);
const keyPrice = this.pricelist.getKeyPrice.metal;

for (const priceKey in pricelist) {
if (!Object.prototype.hasOwnProperty.call(pricelist, priceKey)) {
continue;
}

const entry = pricelist[priceKey];
const _listings = listings[priceKey];

const amountCanBuy = inventoryManager.amountCanTrade({ priceKey, tradeIntent: 'buying' });
const amountAvailable = inventory.getAmount({
priceKey,
includeNonNormalized: false,
tradableOnly: true
});

if (_listings) {
_listings.forEach(listing => {
if (
_listings.length === 1 &&
listing.intent === 0 && // We only check if the only listing exist is buy order
entry.max > 1 &&
amountAvailable > 0 &&
amountAvailable > entry.min
) {
// here we only check if the bot already have that item
log.debug(`Missing sell order listings: ${priceKey}`);
} else if (
listing.intent === 0 &&
listing.currencies.toValue(keyPrice) !== entry.buy.toValue(keyPrice)
) {
// if intent is buy, we check if the buying price is not same
log.debug(`Buying price for ${priceKey} not updated`);
} else if (
listing.intent === 1 &&
listing.currencies.toValue(keyPrice) !== entry.sell.toValue(keyPrice)
) {
// if intent is sell, we check if the selling price is not same
log.debug(`Selling price for ${priceKey} not updated`);
} else {
delete pricelist[priceKey];
}
});

continue;
}

// listing not exist

if (!entry.enabled) {
delete pricelist[priceKey];
log.debug(`${priceKey} disabled, skipping...`);
continue;
}

if (
(amountCanBuy > 0 && inventoryManager.isCanAffordToBuy(entry.buy, inventory)) ||
amountAvailable > 0
) {
// if can amountCanBuy is more than 0 and isCanAffordToBuy is true OR amountAvailable is more than 0
// return this entry
log.debug(
`Missing${isFilterCantAfford ? '/Re-adding can afford' : ' listings'}: ${priceKey}`
);
} else {
delete pricelist[priceKey];
}
}

const priceKeysToCheck = Object.keys(pricelist);
const pricelistCount = priceKeysToCheck.length;

if (pricelistCount > 0) {
log.debug(
'Checking listings for ' +
pluralize('item', pricelistCount, true) +
` [${priceKeysToCheck.join(', ')}]...`
);

await this.listings.recursiveCheckPricelist(
priceKeysToCheck,
pricelist,
true,
pricelistCount > 4000 ? 400 : 200,
true
);

log.debug('✅ Done checking ' + pluralize('item', pricelistCount, true));
} else {
log.debug('❌ Nothing to refresh.');
}

pricelistLength = pricelistCount;
});
},
// set check every 60 minutes if pricelist to check was more than 4000 items
(pricelistLength > 4000 ? 60 : 30) * 60 * 1000
);
}

private get sendStatsEnabled(): boolean {
return this.options.statistics.sendStats.enable;
}

sendStats(): void {
clearInterval(this.sendStatsInterval);

Expand Down
3 changes: 3 additions & 0 deletions src/classes/Commands/sub-classes/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,11 +732,14 @@ export default class ManagerCommands {
'Refreshing listings for ' + pluralize('item', pricelistCount, true) + '...'
);

this.bot.isRecentlyExecuteRefreshlistCommand = true;
this.bot.setRefreshlistExecutedDelay = (this.pricelistCount > 4000 ? 60 : 30) * 60 * 1000;
this.pricelistCount = pricelistCount;
this.executedRefreshList = true;
this.executeRefreshListTimeout = setTimeout(() => {
this.lastExecutedRefreshListTime = null;
this.executedRefreshList = false;
this.bot.isRecentlyExecuteRefreshlistCommand = false;
clearTimeout(this.executeRefreshListTimeout);
}, (this.pricelistCount > 4000 ? 60 : 30) * 60 * 1000);

Expand Down
9 changes: 9 additions & 0 deletions src/classes/MyHandler/MyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ export default class MyHandler extends Handler {
// Initialize send stats
this.bot.sendStats();

// Check for missing listings every 30 minutes, initiate setInterval 5 minutes after start
this.refreshTimeout = setTimeout(() => {
this.bot.startAutoRefreshListings();
}, 5 * 60 * 1000);

this.pollDataInterval = setInterval(this.refreshPollDataPath.bind(this), 24 * 60 * 60 * 1000);

// Send notification to admin/Discord Webhook if there's any item failed to go through updateOldPrices
Expand Down Expand Up @@ -369,6 +374,10 @@ export default class MyHandler extends Handler {
clearInterval(this.bot.sendStatsInterval);
}

if (this.bot.autoRefreshListingsInterval) {
clearInterval(this.bot.autoRefreshListingsInterval);
}

if (this.classWeaponsTimeout) {
clearTimeout(this.classWeaponsTimeout);
}
Expand Down

0 comments on commit 6ce2aec

Please sign in to comment.