Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert removing auto-check for missing listings #1603

Merged
merged 1 commit into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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