From df75033876182036bb204fdf5974648580d58b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=9E=97=E4=BC=9F?= Date: Fri, 24 Apr 2026 10:46:23 +0800 Subject: [PATCH 1/2] feat: support --account and explicit credentials for get-context-token --- src/cli.rs | 30 ++++++- src/commands/get_context_token.rs | 127 +++++++++++++++++++++++++++--- src/main.rs | 8 +- 3 files changed, 153 insertions(+), 12 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 6a13afc..3887c9b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -77,10 +77,36 @@ pub struct AccountDeleteArgs { } #[derive(Debug, Args)] +#[command(after_help = "Authentication Modes: + 1. Saved Account Mode: + --account + + 2. Explicit Credentials Mode: + --bot-token --user-id [--route-tag ] + +Usage Rules: + - Omit all auth params to use the first saved account. + - Or use exactly one of the modes above. + - --account cannot be combined with --bot-token or --user-id. + - In Explicit mode, both --bot-token and --user-id are required.")] pub struct GetContextTokenArgs { - /// Saved account user ID. If omitted, the first saved account is used. - #[arg(long)] + #[arg( + long, + help = "Saved account index from `wechat-cli account list`. Required for Saved Account Mode." + )] + pub account: Option, + #[arg(long, help = "Target user ID. Required in Explicit Credentials Mode.")] pub user_id: Option, + #[arg( + long, + help = "Explicit bot token. Required in Explicit Credentials Mode." + )] + pub bot_token: Option, + #[arg( + long, + help = "Optional route tag used only in Explicit Credentials Mode." + )] + pub route_tag: Option, } #[derive(Debug, Args)] diff --git a/src/commands/get_context_token.rs b/src/commands/get_context_token.rs index 3a24eb8..afeeb57 100644 --- a/src/commands/get_context_token.rs +++ b/src/commands/get_context_token.rs @@ -1,18 +1,24 @@ -use anyhow::{Context, Result, bail}; +use anyhow::{Context, Result, anyhow, bail}; use crate::{ commands::account::{build_client, resolve_user_id}, - storage, - wechat::api::is_session_expired, + storage::{self, load_account}, + wechat::api::{WeixinApiClient, is_session_expired}, wechat::models::InboundMessage, }; -pub async fn run(user_id: Option<&str>) -> Result<()> { - let resolved_id = resolve_user_id(user_id)?; - let session = storage::get_account_data(&resolved_id) - .with_context(|| format!("failed to load account data for `{resolved_id}`"))?; - let client = build_client(&session); - let user_id = session.user_id; +pub async fn run( + account: Option, + user_id: Option<&str>, + bot_token: Option<&str>, + route_tag: Option<&str>, +) -> Result<()> { + let target = resolve_target(account, user_id, bot_token, route_tag)?; + let (user_id, client) = match target { + Target::Saved { user_id, client } => (user_id, client), + Target::Explicit { user_id, client } => (user_id, client), + }; + let mut consecutive_errors = 0u32; eprintln!("waiting for the bound user to send a message for `{user_id}`; press Ctrl+C to stop"); @@ -55,6 +61,55 @@ pub async fn run(user_id: Option<&str>) -> Result<()> { } } +#[derive(Debug)] +enum Target { + Saved { user_id: String, client: WeixinApiClient }, + Explicit { user_id: String, client: WeixinApiClient }, +} + +fn resolve_target( + account: Option, + user_id: Option<&str>, + bot_token: Option<&str>, + route_tag: Option<&str>, +) -> Result { + let using_explicit = user_id.is_some() || bot_token.is_some(); + + if using_explicit { + if account.is_some() { + bail!("`--account` cannot be used with `--bot-token` / `--user-id`"); + } + let user_id = user_id + .ok_or_else(|| anyhow!("`--user-id` is required in explicit credential mode"))? + .to_string(); + let bot_token = bot_token + .ok_or_else(|| anyhow!("`--bot-token` is required in explicit credential mode"))? + .to_string(); + + return Ok(Target::Explicit { + user_id, + client: WeixinApiClient::new(&bot_token, route_tag.map(str::to_string)), + }); + } + + if let Some(idx) = account { + let data = load_account(idx)?; + let user_id = data.user_id.clone(); + return Ok(Target::Saved { + user_id, + client: build_client(&data), + }); + } + + let resolved_id = resolve_user_id(None)?; + let session = storage::get_account_data(&resolved_id) + .with_context(|| format!("failed to load account data for `{resolved_id}`"))?; + Ok(Target::Saved { + user_id: resolved_id, + client: build_client(&session), + }) +} + fn is_timeout_error(err: &anyhow::Error) -> bool { err.chain() .find_map(|cause| cause.downcast_ref::()) @@ -72,3 +127,57 @@ fn extract_context_token(user_id: &str, message: &InboundMessage) -> Option AnyResult<()> { AccountCommand::Delete(args) => commands::account::delete_account(args.account)?, }, Command::GetContextToken(args) => { - commands::get_context_token::run(args.user_id.as_deref()).await?; + commands::get_context_token::run( + args.account, + args.user_id.as_deref(), + args.bot_token.as_deref(), + args.route_tag.as_deref(), + ) + .await?; } Command::Send(args) => { commands::send::run( From 608f49f4bba84b428a38a91db58d029dbe4e3459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=9E=97=E4=BC=9F?= Date: Fri, 24 Apr 2026 11:06:35 +0800 Subject: [PATCH 2/2] refactor: reuse SendTarget and resolve_send_target in get-context-token --- src/commands/get_context_token.rs | 92 +++++++++---------------------- src/commands/send.rs | 4 +- 2 files changed, 29 insertions(+), 67 deletions(-) diff --git a/src/commands/get_context_token.rs b/src/commands/get_context_token.rs index afeeb57..d9d9b18 100644 --- a/src/commands/get_context_token.rs +++ b/src/commands/get_context_token.rs @@ -1,9 +1,10 @@ -use anyhow::{Context, Result, anyhow, bail}; +use anyhow::{Context, Result, bail}; use crate::{ commands::account::{build_client, resolve_user_id}, - storage::{self, load_account}, - wechat::api::{WeixinApiClient, is_session_expired}, + commands::send::{SendTarget, resolve_send_target}, + storage::{self}, + wechat::api::is_session_expired, wechat::models::InboundMessage, }; @@ -13,10 +14,21 @@ pub async fn run( bot_token: Option<&str>, route_tag: Option<&str>, ) -> Result<()> { - let target = resolve_target(account, user_id, bot_token, route_tag)?; + let target = if account.is_none() && user_id.is_none() && bot_token.is_none() { + let resolved_id = resolve_user_id(None)?; + let session = storage::get_account_data(&resolved_id) + .with_context(|| format!("failed to load account data for `{resolved_id}`"))?; + SendTarget::Saved { + user_id: resolved_id, + client: build_client(&session), + } + } else { + resolve_send_target(account, user_id, bot_token, route_tag)? + }; + let (user_id, client) = match target { - Target::Saved { user_id, client } => (user_id, client), - Target::Explicit { user_id, client } => (user_id, client), + SendTarget::Saved { user_id, client } => (user_id, client), + SendTarget::Explicit { user_id, client } => (user_id, client), }; let mut consecutive_errors = 0u32; @@ -61,55 +73,6 @@ pub async fn run( } } -#[derive(Debug)] -enum Target { - Saved { user_id: String, client: WeixinApiClient }, - Explicit { user_id: String, client: WeixinApiClient }, -} - -fn resolve_target( - account: Option, - user_id: Option<&str>, - bot_token: Option<&str>, - route_tag: Option<&str>, -) -> Result { - let using_explicit = user_id.is_some() || bot_token.is_some(); - - if using_explicit { - if account.is_some() { - bail!("`--account` cannot be used with `--bot-token` / `--user-id`"); - } - let user_id = user_id - .ok_or_else(|| anyhow!("`--user-id` is required in explicit credential mode"))? - .to_string(); - let bot_token = bot_token - .ok_or_else(|| anyhow!("`--bot-token` is required in explicit credential mode"))? - .to_string(); - - return Ok(Target::Explicit { - user_id, - client: WeixinApiClient::new(&bot_token, route_tag.map(str::to_string)), - }); - } - - if let Some(idx) = account { - let data = load_account(idx)?; - let user_id = data.user_id.clone(); - return Ok(Target::Saved { - user_id, - client: build_client(&data), - }); - } - - let resolved_id = resolve_user_id(None)?; - let session = storage::get_account_data(&resolved_id) - .with_context(|| format!("failed to load account data for `{resolved_id}`"))?; - Ok(Target::Saved { - user_id: resolved_id, - client: build_client(&session), - }) -} - fn is_timeout_error(err: &anyhow::Error) -> bool { err.chain() .find_map(|cause| cause.downcast_ref::()) @@ -130,11 +93,11 @@ fn extract_context_token(user_id: &str, message: &InboundMessage) -> Option, user_id: Option<&str>, bot_token: Option<&str>,