You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为Steam Dota2机器人配置Sentry文件以实现免重复登录?

如何为Steam Dota2机器人配置Sentry文件以实现免重复登录?

看起来你在折腾Steam + Dota2机器人的免登配置时卡壳了,尤其是登录密钥生成和Sentry文件这块,官方文档又没说清楚——太懂这种抓瞎的感觉了!我结合你给出的代码和遇到的问题,一步步给你拆解解决方案。

一、为什么你的login方法没生成登录密钥?

首先,SteamClient的login方法默认不会自动保存持久化的登录密钥,而且你得确保参数和时机都对,才会拿到可复用的会话凭证。

关键修复:登录时必须加remember=True

Steam的登录密钥(login_key)是持久化会话的核心,你需要在调用login时加上remember=True参数,这样Steam才会返回可复用的登录密钥,而不是临时会话。

修改你的登录代码:

self.client.login(
    username=os.getenv("STEAM_USERNAME"),
    password=os.getenv("STEAM_PASSWORD"),
    remember=True  # 没加这个的话,Steam只会给临时会话,不会生成持久化login_key
)

确认登录密钥的生成时机

登录密钥只有在**首次成功登录(且验证过2FA/邮箱验证码)**后才会返回。如果你的账号之前在其他设备登录过,可能需要先在Steam网页端的「账户设置-安全」里撤销旧的登录密钥,再重新用机器人登录。

你可以在_on_logged_on里加日志确认login_key是否存在:

def _on_logged_on(self):
    if self.client.login_key:
        logger.info(f"成功获取登录密钥: {self.client.login_key[:10]}...")  # 只打前10位,避免泄露
        # 把密钥存到Sentry路径下,用用户名区分(多实例时不会冲突)
        sentry_file = os.path.join(self.client.credential_location, f"{self.client.username.lower()}_login.key")
        with open(sentry_file, 'w') as f:
            f.write(self.client.login_key)
    else:
        logger.warning("未获取到登录密钥!请检查登录参数或账号安全设置")
    self.dota.launch()

二、Sentry文件到底是什么?怎么配置?

你提到的「Sentry binaries」其实是SteamClient用来存储持久化登录凭证的文件集合,这些文件不需要你手动写内容——只要你正确配置了存储路径,SteamClient会自动在登录成功后生成和维护它们

你的代码里的Sentry路径配置是对的,但要注意这几点:

  1. 确保Sentry路径有读写权限:你的代码里os.makedirs(sentry_path, exist_ok=True)已经做了这一步,没问题。
  2. 多实例时不用改路径:每个账号的凭证会自动用用户名区分存储,共用一个路径也不会冲突。
  3. 别手动改Sentry下的文件:这些是SteamClient内部用的二进制/文本凭证,手动修改会导致登录失败,交给库自己处理就行。

三、复用登录密钥实现免密码登录

当你成功存储了登录密钥后,下次登录就可以不用输入密码和验证码,直接用密钥登录:

修改你的run方法,先检查是否有已存储的登录密钥:

def run(self):
    try:
        username = os.getenv("STEAM_USERNAME")
        sentry_file = os.path.join(self.client.credential_location, f"{username.lower()}_login.key")
        
        if os.path.exists(sentry_file):
            with open(sentry_file, 'r') as f:
                login_key = f.read().strip()
            logger.info(f"使用存储的登录密钥登录账号: {username}")
            self.client.login(
                username=username,
                login_key=login_key
            )
        else:
            logger.info(f"首次登录账号: {username},请准备验证码")
            self.client.login(
                username=username,
                password=os.getenv("STEAM_PASSWORD"),
                remember=True
            )
        self.client.run_forever()
    except Exception as e:
        logger.error(f"登录失败: {e}")
        self.client.logout()

四、多实例+Flask结合的注意事项

你要同时跑Flask和多机器人实例,最容易踩的坑就是阻塞主线程线程安全问题,给你两个核心建议:

1. 把机器人的运行逻辑放到独立线程/进程

client.run_forever()是阻塞方法,直接放在主线程会卡住Flask的请求处理。建议给每个BotInstance加个任务队列,用独立线程处理机器人操作:

from queue import Queue
import threading

class BotInstance:
    def __init__(self):
        # ... 其他初始化代码 ...
        self.task_queue = Queue()
        self.running = True
        # 启动任务处理线程(守护线程,随主线程退出)
        threading.Thread(target=self._process_tasks, daemon=True).start()
    
    def _process_tasks(self):
        while self.running:
            if not self.task_queue.empty():
                task = self.task_queue.get()
                try:
                    if task['action'] == 'create_lobby':
                        result = self.create_and_go(
                            name=task['name'],
                            password=task['password'],
                            game_mode=task['game_mode'],
                            steam_ids=task['steam_ids']
                        )
                        # 把结果返回给Flask(可以用回调函数)
                        task['callback'](result)
                except Exception as e:
                    logger.error(f"处理任务失败: {e}")
                    task['callback']({'launch': 500, 'message': str(e)})
            time.sleep(0.1)
    
    # Flask接口调用这个方法提交创建房间的任务
    def submit_create_lobby_task(self, name, password, game_mode, steam_ids, callback):
        self.task_queue.put({
            'action': 'create_lobby',
            'name': name,
            'password': password,
            'game_mode': game_mode,
            'steam_ids': steam_ids,
            'callback': callback
        })

2. 多实例要对应不同Steam账号

每个BotInstance必须绑定独立的Steam账号,不能用同一个账号跑多个实例——Steam会检测到异地/多设备登录,直接踢掉会话。

最后,给你的完整修改代码(关键部分整合)

from steam.client import SteamClient
from dota2.client import Dota2Client
from dota2.enums import DOTA_GC_TEAM, DOTA_GameState
import time
import os
from dotenv import load_dotenv
import logging
import threading
from queue import Queue

logger = logging.getLogger(__name__)

class BotInstance:
    def __init__(self):
        load_dotenv()
        self.client = SteamClient()
        
        # 配置Sentry存储路径(自动生成凭证文件)
        sentry_path = os.path.abspath('./sentry')
        os.makedirs(sentry_path, exist_ok=True)
        self.client.set_credential_location(sentry_path)
        
        self.dota = Dota2Client(self.client)
        self.in_lobby = False
        self.task_queue = Queue()
        self.running = True
        threading.Thread(target=self._process_tasks, daemon=True).start()
        self.setup_handlers()

    def setup_handlers(self):
        @self.client.on(self.client.EVENT_AUTH_CODE_REQUIRED)
        def _on_auth_code(is_2fa, code_mismatch):
            # 线上环境可以改成从Flask接口获取验证码,不用控制台输入
            if is_2fa:
                code = input("输入2FA验证码: ")
            else:
                code = input("输入邮箱验证码: ")
            # 重新登录时也要加remember=True
            self.client.login(
                username=os.getenv("STEAM_USERNAME"),
                password=os.getenv("STEAM_PASSWORD"),
                remember=True,
                two_factor_code=code if is_2fa else None,
                auth_code=code if not is_2fa else None
            )

        self.client.on('logged_on', self._on_logged_on)
        self.client.on("error", lambda e: logger.error(f"Steam客户端错误: {e}"))

        self.dota.on('ready', self._on_dota_ready)
        self.dota.on("lobby_new", self._on_lobby_created)
        self.dota.on("lobby_changed", self._on_lobby_changed)
        self.dota.on("lobby_removed", self._on_lobby_removed)

    def _on_logged_on(self):
        if self.client.login_key:
            logger.info(f"成功获取登录密钥: {self.client.login_key[:10]}...")
            sentry_file = os.path.join(self.client.credential_location, f"{self.client.username.lower()}_login.key")
            with open(sentry_file, 'w') as f:
                f.write(self.client.login_key)
        else:
            logger.warning("未获取到登录密钥!请检查登录参数或账号安全设置")
        self.dota.launch()

    # ... 你的原有方法(_on_dota_ready、check_players_in_lobby、create_and_go等)保持不变 ...

    def _process_tasks(self):
        while self.running:
            if not self.task_queue.empty():
                task = self.task_queue.get()
                try:
                    if task['action'] == 'create_lobby':
                        result = self.create_and_go(
                            name=task['name'],
                            password=task['password'],
                            game_mode=task['game_mode'],
                            steam_ids=task['steam_ids']
                        )
                        task['callback'](result)
                except Exception as e:
                    logger.error(f"处理任务失败: {e}")
                    task['callback']({'launch': 500, 'message': str(e)})
            time.sleep(0.1)

    def submit_create_lobby_task(self, name, password, game_mode, steam_ids, callback):
        self.task_queue.put({
            'action': 'create_lobby',
            'name': name,
            'password': password,
            'game_mode': game_mode,
            'steam_ids': steam_ids,
            'callback': callback
        })

    def run(self):
        try:
            username = os.getenv("STEAM_USERNAME")
            sentry_file = os.path.join(self.client.credential_location, f"{username.lower()}_login.key")
            
            if os.path.exists(sentry_file):
                with open(sentry_file, 'r') as f:
                    login_key = f.read().strip()
                logger.info(f"使用存储的登录密钥登录账号: {username}")
                self.client.login(
                    username=username,
                    login_key=login_key
                )
            else:
                logger.info(f"首次登录账号: {username},请准备验证码")
                self.client.login(
                    username=username,
                    password=os.getenv("STEAM_PASSWORD"),
                    remember=True
                )
            self.client.run_forever()
        except Exception as e:
            logger.error(f"登录失败: {e}")
            self.client.logout()
            self.running = False

    def shutdown(self):
        self.running = False
        if self.dota.lobby:
            self.dota.leave_practice_lobby()
            self.in_lobby = False
        self.client.logout()
        self.client.disconnect()

备注:内容来源于stack exchange,提问作者Shork

火山引擎 最新活动