Jump to content
McKay Development

Recommended Posts

Posted (edited)

Hey everyone,

I'm developing a discord bot announcing changes in apps (kinda like steamdb).
Everything works perfectly fine for the first few hours, but then appUpdate no longer fires.
I haven't seen any messages thrown by steamCommunity "sessionExpired" event, client "disconnected" and "error" events.
This scenario happened every time, but yesterday after running bot for about a week, I got an app update notification
which is strange for me, because it was always completely silent after a few hours.

I was searching through github issues and forum as well, but found nothing.

I've tried implementing reconnecting to steamcommunity, but can't go past "Must not be anonymous user to use webLogOn (check to see you passed in valid credentials to logOn)".
I think that is because it's logging via refreshToken.
I don't even know if appUpdate is managed by steamcommunity.

steam-client.ts

    steamClient: SteamUser;
    steamCommunity: SteamCommunity;
    listener: SteamListener;

    private RECONNECT_INTERVAL = 10000;

    private issuedRefreshToken: string | null;

    private useAnonymous: boolean;
    private refreshToken: string | null;
    private accountName: string | null;
    private password: string | null;
    private machineName: string | null;

    private shouldReconnect = false;

    constructor() {
        this.useAnonymous = Config.default.steam.anonymous;
        this.refreshToken = Config.default.steam.refresh_token;
        this.accountName = Config.default.steam.account_name;
        this.password = Config.default.steam.password;
        this.machineName = Config.default.steam.machine_name;

        this.issuedRefreshToken = null;
        this.listener = new SteamListener();
        this.steamCommunity = new SteamCommunity();
        this.steamClient = new SteamUser({
            "enablePicsCache": true,
            "autoRelogin": true,
            "changelistUpdateInterval": 15000,
            "renewRefreshTokens": true,
        });

        this.steamClient.on("webSession", (_, cookies) => {
            console.log(chalk.green("Steam reconnected."));
            this.steamCommunity.setCookies(cookies);
            this.steamCommunity.startConfirmationChecker(10000, '=');
            this.shouldReconnect = false;
        })

        this.steamCommunity.on("sessionExpired", () => {
            console.log(chalk.red("Steam session expired."));
            this.shouldReconnect = true;
        });
        
        this.steamClient.on("disconnected", (eresult, msg) => {
            console.log(chalk.red(`Disconnected from steam: [ EResult: ${eresult} ] ${msg}`));
        });

        this.steamClient.on("error", (error) => {
            console.log(chalk.red(error));
        });
    }

    private attemptToReconnectTick() {
        console.log("b")
        if(this.shouldReconnect) {
            if(this.steamClient.steamID) {
                this.steamClient.webLogOn();
            }

            this.steamClient.logOn();
        }

        setTimeout(this.attemptToReconnectTick.bind(this), this.RECONNECT_INTERVAL);
    }

    async connect() : Promise<void> {
        console.log("[/] Connecting to Steam...");

        if(this.useAnonymous) {
            return await this.loginAsAnonymous();
        }

        if(this.refreshToken != null && this.refreshToken != "") {
            await this.loginWithRefreshToken();

            // TEST - start reconnect task
            this.shouldReconnect = true;
            return this.attemptToReconnectTick();
        }

        await this.loginWithUsernameAndPassword();
    }

    async loginAsAnonymous() {
        this.steamClient.logOn({
            "anonymous": true,
            "machineName": this.machineName ? this.machineName : undefined
        });

        await this.waitForLogin();
        this.listener.registerListeners(this);
        console.log(chalk.green(`Steam Client connected as anonymous.`));
    }

    private async loginWithUsernameAndPassword() {
        if(this.accountName == null || this.password == null
            || this.accountName.includes(" ") || this.password.includes(" ")
            || this.accountName.replace(" ", "") == "" || this.password.replace(" ", "") == ""
        ) {
            console.log(chalk.bgRed("ERROR") + " " + chalk.red("This steam authentication method requires username and password."))
            process.exit(0);
        }

        this.steamClient.logOn({
            "accountName": this.accountName!,
            "password": this.password!,
            "machineName": this.machineName ? this.machineName : undefined
        });

        await this.waitForLogin();
        this.listener.registerListeners(this);
        console.log(chalk.green(`Steam Client connected using username/password as ${this.steamClient.accountInfo?.name}.`));

        if(this.issuedRefreshToken != null && this.issuedRefreshToken != ""
        ) {
            console.log(chalk.blue("Steam uses refresh tokens when logging in."));
            console.log(chalk.blue("Your refresh token is: ") + chalk.bgBlue(this.issuedRefreshToken));
            console.log(chalk.blue("It's recommended to use it instead of username/password. You can change that in the config file."));
        }
    }

    private async loginWithRefreshToken() {
        if(
            this.refreshToken == null || this.refreshToken == "" || this.refreshToken.includes(" ")
        ) {
            console.log(chalk.bgRed("ERROR") + " " + chalk.red("This steam authentication method requires a refresh token."))
            process.exit(0);
        }

        this.steamClient.logOn({
            "accountName": this.accountName!,
            "password": this.password!,
            "machineName": this.machineName ? this.machineName : undefined
        });
        
        await this.waitForLogin();
        this.listener.registerListeners(this);
        console.log(chalk.green(`Steam Client connected using refresh token as ${this.steamClient.accountInfo?.name}.`));
    }

    // library provides any type, so we need to use it
    // deno-lint-ignore no-explicit-any
    private waitForLogin() : Promise<Record<string, any>> {
        return new Promise((resolve) => {
            this.steamClient.once("loggedOn", (loginInfo) => {
                resolve(loginInfo);
            });
    
            this.steamClient.once("refreshToken", (token) => {
                this.issuedRefreshToken = token;
            });

            this.steamClient.once("error", (error) => {
                throw error;
            });
        })
    }

    getApplicationInfo(appId: number) : Promise<AppInfo | null> {
        return new Promise((resolve) => {
            this.steamClient.getProductInfo([appId], [], false, (_err, apps) => {
                resolve(apps[appId]);
            })
        });
    }

steam-listener.ts
 

    registerListeners(steamClient: SteamClient) {
        steamClient.steamClient.on("appUpdate", this.onAppUpdateReceived.bind(this));
    }

    /**
     * Called when an app is updated.
     * @param appid The id of the app that was updated.
     * @param data The new app info.
     */
    async onAppUpdateReceived(appId: number, data: AppInfo): Promise<void> {
        if(await UserApplicationsController.isApplicationPresentAnywhere(appId) == false &&
            await ServerApplicationsController.isApplicationPresentAnywhere(appId) == false) {
            return;
        }

        const cachedApp: ICachedApplications | null = await CachedApplicationsController.getCachedApplication(appId);
        const newAppInfo: AppInfo | null = await SteamClientInstance.getApplicationInfo(appId);

        console.log(`[*] Received update for application ${appId}.`);

        if(cachedApp != null && cachedApp.changenumber == data.changenumber) {
            console.log(`[*] No changes detected.`);
            return;
        }

        if(cachedApp == null || newAppInfo == null) {
            return CachedApplicationsController.cacheApplication(newAppInfo);
        }

        // don't announce if only the changenumber changed
        const changes = ApplicationDataComparator.compareApplicationData(cachedApp, newAppInfo);
        if(!this.isChangeOnlyChangeNumber(changes)) {
            await AnnouncementsManager.announceApplicationChangelist(data.changenumber, appId, changes);
        }

        // finally save the new app info
        await CachedApplicationsController.cacheApplication(newAppInfo);
    }

    private isChangeOnlyChangeNumber(changes: ApplicationChanges): boolean {
        let isOnlyChangeNumber = true;
        keys<ApplicationChanges>(changes).forEach(key => {
            const change = changes[key];

            // changeNumber changes every time a change is detected
            // so we don't need to check if changeNumber actually changed
            // we are skipping branches because they are handled separately
            if(key == "branches" || key == "changenumber") {
                return;
            }

            if(this.didChange(change)) {
                isOnlyChangeNumber = false;
            }
        });

        // check for branch changes
        Object.keys(changes.branches).forEach(branchName => {
            const branchChanges = changes.branches[branchName];

            keys<BranchChanges>(branchChanges).forEach(key => {
                const change = changes.branches[branchName][key];
                if(this.didChange(change)) {
                    isOnlyChangeNumber = false;
                }
            });
        });

        return isOnlyChangeNumber;
    }

    private didChange(change: ChangeFromTo): boolean {
        if((change.from == null || change.from == "") && (change.to != null && change.to != "")) {
            return true;
        }

        if((change.to == null || change.to == "") && (change.from != null && change.from != "")) {
            return true;
        }

        if(change.from != change.to) {
            return true;
        }

        return false;
    }

    private getCurrentDateAsString() : string {
        const options: Intl.DateTimeFormatOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
        return new Date().toLocaleDateString("pl-PL", options);
    }

 

Edited by Krifix
Posted
3 minutes ago, Krifix said:

Also I am using deno.js. Is it possible deno causes that?

Certainly is. I can't really support your setup if you aren't using Node.js, since my modules are specifically for Node.js.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...