Teloxy-rs 如何在同一机器人的不同实例之间共享状态?

问题描述 投票:0回答:1

我正在使用 teloxy 一个协调游戏玩家之间信息的机器人。作为一个简化的示例,我希望玩家 A 将 task_1 标记为完成,然后我希望玩家 B 能够询问机器人 task_1 是否已完成,并让机器人做出适当的响应。

到目前为止,我已经尝试了内存存储和Redis存储,两者似乎都存储独立的状态。例如,玩家 A 可以将任务标记为完成,但当玩家 B 询问该任务的状态时,它仍然未完成。

我的机器上还没有安装 sqlite,所以还没有尝试过,但我不认为它的设置与 redis 不同(?)。

理论上我可以将机器人的状态写入磁盘上的文件,然后每次更新内部状态,但这对于我希望 teloxy 已经内置的东西来说似乎很绕?

有什么方法可以让多个用户改变机器人的相同内部状态吗?

这是代码:

这是我正在使用的代码。这是从

db_remember.rs
示例中稍微调整的,以便我可以获取一个数字,重置该数字,并使用
\set XXX

将数字设置为新的值

src/main.rs

use teloxide::{
    dispatching::dialogue::{
        serializer::Bincode,
        ErasedStorage, RedisStorage, Storage,
    },
    prelude::*,
    utils::command::BotCommands,
};
use serde::{self, Deserialize, Serialize};

type MyDialogue = Dialogue<State, ErasedStorage<State>>;
type MyStorage = std::sync::Arc<ErasedStorage<State>>;
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;

#[derive(Clone, Default, Serialize, Deserialize)]
pub enum State {
    #[default]
    Start,
    GotNumber(i32),
}

#[derive(Clone, BotCommands)]
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
pub enum Command {
    #[command(description = "set your number.")]
    Set { num: i32 },
    #[command(description = "get your number.")]
    Get,
    #[command(description = "reset your number.")]
    Reset,
}

#[tokio::main]
async fn main() {
    pretty_env_logger::init();
    log::info!("Starting DB remember bot...");

    let bot = Bot::from_env();

    let storage: MyStorage = RedisStorage::open("redis://127.0.0.1:6379", Bincode).await.unwrap().erase();

    let handler = Update::filter_message()
        .enter_dialogue::<Message, ErasedStorage<State>, State>()
        .branch(dptree::case![State::Start].endpoint(start))
        .branch(
            dptree::case![State::GotNumber(n)]
                .branch(dptree::entry().filter_command::<Command>().endpoint(got_number))
                .branch(dptree::endpoint(invalid_command)),
        );

    Dispatcher::builder(bot, handler)
        .dependencies(dptree::deps![storage])
        .enable_ctrlc_handler()
        .build()
        .dispatch()
        .await;
}

async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
    match msg.text().map(|text| text.parse::<i32>()) {
        Some(Ok(n)) => {
            log::info!("[{:?}] Number set to {n}", msg.chat.username());
            dialogue.update(State::GotNumber(n)).await?;
            bot.send_message(
                msg.chat.id,
                format!("Remembered number {n}. Now use /get or /reset."),
            )
            .await?;
        }
        _ => {
            bot.send_message(msg.chat.id, "Please, send me a number.").await?;
        }
    }

    Ok(())
}

async fn got_number(
    bot: Bot,
    dialogue: MyDialogue,
    num: i32, // Available from `State::GotNumber`.
    msg: Message,
    cmd: Command,
) -> HandlerResult {
    let old_num = num;
    match cmd {
        Command::Set { num } => {
            log::info!("[{:?}] Number changed from {} to {}",  msg.chat.username(), old_num, num);
            dialogue.update(State::GotNumber(num)).await?;
            bot.send_message(msg.chat.id, format!("Set your number from {} to {}", old_num, num)).await?;
        }
        Command::Get => {
            bot.send_message(msg.chat.id, format!("Here is your number: {num}.")).await?;
        }
        Command::Reset => {
            dialogue.reset().await?;
            bot.send_message(msg.chat.id, "Number reset.").await?;
        }
    }
    Ok(())
}

async fn invalid_command(bot: Bot, msg: Message) -> HandlerResult {
    bot.send_message(msg.chat.id, "Please, send /get or /reset.").await?;
    Ok(())
}

Cargo.toml

[package]
name = "tmp"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
teloxide = { version = "0.12", features = ["macros", "redis-storage", "bincode-serializer"] }
log = "0.4"
pretty_env_logger = "0.4"
tokio = { version =  "1.8", features = ["rt-multi-thread", "macros"] }
serde_yaml = "0.9.17"
serde = "1.0.152"
rust persistence telegram-bot
1个回答
0
投票

这是由 WaffleLapkinTeloxy github 讨论页面上回答的:

teloxy 中的对话存储存储每个聊天的状态,如果您需要其他内容,则需要直接使用数据库。

所以我最终使用 Redis 数据库来存储游戏状态,并使用常规内存来存储 teloxy 的状态。比如:

// Cargo.toml: redis = { version = "*" }
extern crate redis;
use redis::{Commands, Connection};

/// Establish a connection to the redis database, 
/// used for sharing Game state across
/// different instances of the bot
fn connect_to_redis() -> Result<Connection, Box<dyn std::error::Error + Sync + Send>> {
    let client = redis::Client::open("redis://127.0.0.1/")?;
    let conn = client.get_connection()?;
    // Cargo.toml: log = "0.4"
    log::info!("Redis connection established");
    Ok(conn)
}

但是你也不能直接将 Rust 结构存储到 Redis 中,所以我使用

serde_yaml
来序列化游戏状态:

fn set_game_state(
    conn: &mut Connection,
    game: Game,
) -> Result<(), Box<dyn Error + Send + Sync>> {
    let game_key = "game_state";
    conn.set::<&str, String, ()>(game_key, serde_yaml::to_string(&game)?)?;
    Ok(())
}

然后将其全部作为一个长字符串存储在数据库中的单个键中。

检索游戏状态是类似的操作,但我还添加了一些逻辑,以便从现有的“模板”YAML 文件创建游戏

game.yaml
(如果密钥不存在):

fn get_game_state(conn: &mut Connection) -> Result<Game, Box<dyn Error + Send + Sync>> {
    let game_state_key = "game_state";
    let game_state: Game = if conn.exists(game_state_key)? {
        log::debug!("Getting game state");
        conn.get::<&str, String>(game_state_key)
            .and_then(|game_state| Ok(serde_yaml::from_str::<Game>(&game_state).unwrap().into()))?
    } else {
        log::debug!("Creating game state");
        let s = Game::new("challenges.yaml")?;
        let serialised = serde_yaml::to_string(&s)?;
        conn.set::<&str, String, ()>(game_state_key, serialised)?;
        s
    };
    Ok(game_state)
}

有点矫枉过正,但确实有效。

© www.soinside.com 2019 - 2024. All rights reserved.