feat(proxy): add scoped proxy configuration and docs runbooks
- add scope-aware proxy schema and runtime wiring for providers/channels/tools - add agent callable proxy_config tool for fast proxy setup - standardize docs system with index, template, and playbooks
This commit is contained in:
parent
13ee9e6398
commit
ce104bed45
36 changed files with 2025 additions and 323 deletions
|
|
@ -15,7 +15,6 @@ pub struct DingTalkChannel {
|
|||
client_id: String,
|
||||
client_secret: String,
|
||||
allowed_users: Vec<String>,
|
||||
client: reqwest::Client,
|
||||
/// Per-chat session webhooks for sending replies (chatID -> webhook URL).
|
||||
/// DingTalk provides a unique webhook URL with each incoming message.
|
||||
session_webhooks: Arc<RwLock<HashMap<String, String>>>,
|
||||
|
|
@ -34,11 +33,14 @@ impl DingTalkChannel {
|
|||
client_id,
|
||||
client_secret,
|
||||
allowed_users,
|
||||
client: reqwest::Client::new(),
|
||||
session_webhooks: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.dingtalk")
|
||||
}
|
||||
|
||||
fn is_user_allowed(&self, user_id: &str) -> bool {
|
||||
self.allowed_users.iter().any(|u| u == "*" || u == user_id)
|
||||
}
|
||||
|
|
@ -86,7 +88,7 @@ impl DingTalkChannel {
|
|||
});
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post("https://api.dingtalk.com/v1.0/gateway/connections/open")
|
||||
.json(&body)
|
||||
.send()
|
||||
|
|
@ -128,7 +130,12 @@ impl Channel for DingTalkChannel {
|
|||
}
|
||||
});
|
||||
|
||||
let resp = self.client.post(webhook_url).json(&body).send().await?;
|
||||
let resp = self
|
||||
.http_client()
|
||||
.post(webhook_url)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ pub struct DiscordChannel {
|
|||
allowed_users: Vec<String>,
|
||||
listen_to_bots: bool,
|
||||
mention_only: bool,
|
||||
client: reqwest::Client,
|
||||
typing_handle: Mutex<Option<tokio::task::JoinHandle<()>>>,
|
||||
}
|
||||
|
||||
|
|
@ -31,11 +30,14 @@ impl DiscordChannel {
|
|||
allowed_users,
|
||||
listen_to_bots,
|
||||
mention_only,
|
||||
client: reqwest::Client::new(),
|
||||
typing_handle: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.discord")
|
||||
}
|
||||
|
||||
/// Check if a Discord user ID is in the allowlist.
|
||||
/// Empty list means deny everyone until explicitly configured.
|
||||
/// `"*"` means allow everyone.
|
||||
|
|
@ -198,7 +200,7 @@ impl Channel for DiscordChannel {
|
|||
let body = json!({ "content": chunk });
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bot {}", self.bot_token))
|
||||
.json(&body)
|
||||
|
|
@ -229,7 +231,7 @@ impl Channel for DiscordChannel {
|
|||
|
||||
// Get Gateway URL
|
||||
let gw_resp: serde_json::Value = self
|
||||
.client
|
||||
.http_client()
|
||||
.get("https://discord.com/api/v10/gateway/bot")
|
||||
.header("Authorization", format!("Bot {}", self.bot_token))
|
||||
.send()
|
||||
|
|
@ -424,7 +426,7 @@ impl Channel for DiscordChannel {
|
|||
}
|
||||
|
||||
async fn health_check(&self) -> bool {
|
||||
self.client
|
||||
self.http_client()
|
||||
.get("https://discord.com/api/v10/users/@me")
|
||||
.header("Authorization", format!("Bot {}", self.bot_token))
|
||||
.send()
|
||||
|
|
@ -436,7 +438,7 @@ impl Channel for DiscordChannel {
|
|||
async fn start_typing(&self, recipient: &str) -> anyhow::Result<()> {
|
||||
self.stop_typing(recipient).await?;
|
||||
|
||||
let client = self.client.clone();
|
||||
let client = self.http_client();
|
||||
let token = self.bot_token.clone();
|
||||
let channel_id = recipient.to_string();
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ pub struct LarkChannel {
|
|||
use_feishu: bool,
|
||||
/// How to receive events: WebSocket long-connection or HTTP webhook.
|
||||
receive_mode: crate::config::schema::LarkReceiveMode,
|
||||
client: reqwest::Client,
|
||||
/// Cached tenant access token
|
||||
tenant_token: Arc<RwLock<Option<String>>>,
|
||||
/// Dedup set: WS message_ids seen in last ~30 min to prevent double-dispatch
|
||||
|
|
@ -165,7 +164,6 @@ impl LarkChannel {
|
|||
allowed_users,
|
||||
use_feishu: true,
|
||||
receive_mode: crate::config::schema::LarkReceiveMode::default(),
|
||||
client: reqwest::Client::new(),
|
||||
tenant_token: Arc::new(RwLock::new(None)),
|
||||
ws_seen_ids: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
|
|
@ -185,6 +183,10 @@ impl LarkChannel {
|
|||
ch
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.lark")
|
||||
}
|
||||
|
||||
fn api_base(&self) -> &'static str {
|
||||
if self.use_feishu {
|
||||
FEISHU_BASE_URL
|
||||
|
|
@ -212,7 +214,7 @@ impl LarkChannel {
|
|||
/// POST /callback/ws/endpoint → (wss_url, client_config)
|
||||
async fn get_ws_endpoint(&self) -> anyhow::Result<(String, WsClientConfig)> {
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(format!("{}/callback/ws/endpoint", self.ws_base()))
|
||||
.header("locale", if self.use_feishu { "zh" } else { "en" })
|
||||
.json(&serde_json::json!({
|
||||
|
|
@ -488,7 +490,7 @@ impl LarkChannel {
|
|||
"app_secret": self.app_secret,
|
||||
});
|
||||
|
||||
let resp = self.client.post(&url).json(&body).send().await?;
|
||||
let resp = self.http_client().post(&url).json(&body).send().await?;
|
||||
let data: serde_json::Value = resp.json().await?;
|
||||
|
||||
let code = data.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
|
||||
|
|
@ -642,7 +644,7 @@ impl Channel for LarkChannel {
|
|||
});
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {token}"))
|
||||
.header("Content-Type", "application/json; charset=utf-8")
|
||||
|
|
@ -655,7 +657,7 @@ impl Channel for LarkChannel {
|
|||
self.invalidate_token().await;
|
||||
let new_token = self.get_tenant_access_token().await?;
|
||||
let retry_resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {new_token}"))
|
||||
.header("Content-Type", "application/json; charset=utf-8")
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ pub struct MatrixChannel {
|
|||
access_token: String,
|
||||
room_id: String,
|
||||
allowed_users: Vec<String>,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -79,10 +78,13 @@ impl MatrixChannel {
|
|||
access_token,
|
||||
room_id,
|
||||
allowed_users,
|
||||
client: Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> Client {
|
||||
crate::config::build_runtime_proxy_client("channel.matrix")
|
||||
}
|
||||
|
||||
fn is_user_allowed(&self, sender: &str) -> bool {
|
||||
if self.allowed_users.iter().any(|u| u == "*") {
|
||||
return true;
|
||||
|
|
@ -95,7 +97,7 @@ impl MatrixChannel {
|
|||
async fn get_my_user_id(&self) -> anyhow::Result<String> {
|
||||
let url = format!("{}/_matrix/client/v3/account/whoami", self.homeserver);
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {}", self.access_token))
|
||||
.send()
|
||||
|
|
@ -130,7 +132,7 @@ impl Channel for MatrixChannel {
|
|||
});
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.put(&url)
|
||||
.header("Authorization", format!("Bearer {}", self.access_token))
|
||||
.json(&body)
|
||||
|
|
@ -157,7 +159,7 @@ impl Channel for MatrixChannel {
|
|||
);
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {}", self.access_token))
|
||||
.send()
|
||||
|
|
@ -179,7 +181,7 @@ impl Channel for MatrixChannel {
|
|||
);
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {}", self.access_token))
|
||||
.send()
|
||||
|
|
@ -250,7 +252,7 @@ impl Channel for MatrixChannel {
|
|||
async fn health_check(&self) -> bool {
|
||||
let url = format!("{}/_matrix/client/v3/account/whoami", self.homeserver);
|
||||
let Ok(resp) = self
|
||||
.client
|
||||
.http_client()
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {}", self.access_token))
|
||||
.send()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ pub struct MattermostChannel {
|
|||
thread_replies: bool,
|
||||
/// When true, only respond to messages that @-mention the bot.
|
||||
mention_only: bool,
|
||||
client: reqwest::Client,
|
||||
/// Handle for the background typing-indicator loop (aborted on stop_typing).
|
||||
typing_handle: Mutex<Option<tokio::task::JoinHandle<()>>>,
|
||||
}
|
||||
|
|
@ -38,11 +37,14 @@ impl MattermostChannel {
|
|||
allowed_users,
|
||||
thread_replies,
|
||||
mention_only,
|
||||
client: reqwest::Client::new(),
|
||||
typing_handle: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.mattermost")
|
||||
}
|
||||
|
||||
/// Check if a user ID is in the allowlist.
|
||||
/// Empty list means deny everyone. "*" means allow everyone.
|
||||
fn is_user_allowed(&self, user_id: &str) -> bool {
|
||||
|
|
@ -53,7 +55,7 @@ impl MattermostChannel {
|
|||
/// and detect @-mentions by username.
|
||||
async fn get_bot_identity(&self) -> (String, String) {
|
||||
let resp: Option<serde_json::Value> = async {
|
||||
self.client
|
||||
self.http_client()
|
||||
.get(format!("{}/api/v4/users/me", self.base_url))
|
||||
.bearer_auth(&self.bot_token)
|
||||
.send()
|
||||
|
|
@ -109,7 +111,7 @@ impl Channel for MattermostChannel {
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(format!("{}/api/v4/posts", self.base_url))
|
||||
.bearer_auth(&self.bot_token)
|
||||
.json(&body_map)
|
||||
|
|
@ -147,7 +149,7 @@ impl Channel for MattermostChannel {
|
|||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
|
||||
let resp = match self
|
||||
.client
|
||||
.http_client()
|
||||
.get(format!(
|
||||
"{}/api/v4/channels/{}/posts",
|
||||
self.base_url, channel_id
|
||||
|
|
@ -202,7 +204,7 @@ impl Channel for MattermostChannel {
|
|||
}
|
||||
|
||||
async fn health_check(&self) -> bool {
|
||||
self.client
|
||||
self.http_client()
|
||||
.get(format!("{}/api/v4/users/me", self.base_url))
|
||||
.bearer_auth(&self.bot_token)
|
||||
.send()
|
||||
|
|
@ -215,7 +217,7 @@ impl Channel for MattermostChannel {
|
|||
// Cancel any existing typing loop before starting a new one.
|
||||
self.stop_typing(recipient).await?;
|
||||
|
||||
let client = self.client.clone();
|
||||
let client = self.http_client();
|
||||
let token = self.bot_token.clone();
|
||||
let base_url = self.base_url.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ pub struct QQChannel {
|
|||
app_id: String,
|
||||
app_secret: String,
|
||||
allowed_users: Vec<String>,
|
||||
client: reqwest::Client,
|
||||
/// Cached access token + expiry timestamp.
|
||||
token_cache: Arc<RwLock<Option<(String, u64)>>>,
|
||||
/// Message deduplication set.
|
||||
|
|
@ -33,12 +32,15 @@ impl QQChannel {
|
|||
app_id,
|
||||
app_secret,
|
||||
allowed_users,
|
||||
client: reqwest::Client::new(),
|
||||
token_cache: Arc::new(RwLock::new(None)),
|
||||
dedup: Arc::new(RwLock::new(HashSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.qq")
|
||||
}
|
||||
|
||||
fn is_user_allowed(&self, user_id: &str) -> bool {
|
||||
self.allowed_users.iter().any(|u| u == "*" || u == user_id)
|
||||
}
|
||||
|
|
@ -50,7 +52,12 @@ impl QQChannel {
|
|||
"clientSecret": self.app_secret,
|
||||
});
|
||||
|
||||
let resp = self.client.post(QQ_AUTH_URL).json(&body).send().await?;
|
||||
let resp = self
|
||||
.http_client()
|
||||
.post(QQ_AUTH_URL)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
|
|
@ -109,7 +116,7 @@ impl QQChannel {
|
|||
/// Get the WebSocket gateway URL.
|
||||
async fn get_gateway_url(&self, token: &str) -> anyhow::Result<String> {
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.get(format!("{QQ_API_BASE}/gateway"))
|
||||
.header("Authorization", format!("QQBot {token}"))
|
||||
.send()
|
||||
|
|
@ -190,7 +197,7 @@ impl Channel for QQChannel {
|
|||
};
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(&url)
|
||||
.header("Authorization", format!("QQBot {token}"))
|
||||
.json(&body)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ pub struct SignalChannel {
|
|||
allowed_from: Vec<String>,
|
||||
ignore_attachments: bool,
|
||||
ignore_stories: bool,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
// ── signal-cli SSE event JSON shapes ────────────────────────────
|
||||
|
|
@ -81,10 +80,6 @@ impl SignalChannel {
|
|||
ignore_stories: bool,
|
||||
) -> Self {
|
||||
let http_url = http_url.trim_end_matches('/').to_string();
|
||||
let client = Client::builder()
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.expect("Signal HTTP client should build");
|
||||
Self {
|
||||
http_url,
|
||||
account,
|
||||
|
|
@ -92,10 +87,15 @@ impl SignalChannel {
|
|||
allowed_from,
|
||||
ignore_attachments,
|
||||
ignore_stories,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> Client {
|
||||
let builder = Client::builder().connect_timeout(Duration::from_secs(10));
|
||||
let builder = crate::config::apply_runtime_proxy_to_builder(builder, "channel.signal");
|
||||
builder.build().expect("Signal HTTP client should build")
|
||||
}
|
||||
|
||||
/// Effective sender: prefer `sourceNumber` (E.164), fall back to `source`.
|
||||
fn sender(envelope: &Envelope) -> Option<String> {
|
||||
envelope
|
||||
|
|
@ -178,7 +178,7 @@ impl SignalChannel {
|
|||
});
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.header("Content-Type", "application/json")
|
||||
|
|
@ -298,7 +298,7 @@ impl Channel for SignalChannel {
|
|||
|
||||
loop {
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.get(url.clone())
|
||||
.header("Accept", "text/event-stream")
|
||||
.send()
|
||||
|
|
@ -408,7 +408,7 @@ impl Channel for SignalChannel {
|
|||
async fn health_check(&self) -> bool {
|
||||
let url = format!("{}/api/v1/check", self.http_url);
|
||||
let Ok(resp) = self
|
||||
.client
|
||||
.http_client()
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ pub struct SlackChannel {
|
|||
bot_token: String,
|
||||
channel_id: Option<String>,
|
||||
allowed_users: Vec<String>,
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl SlackChannel {
|
||||
|
|
@ -15,10 +14,13 @@ impl SlackChannel {
|
|||
bot_token,
|
||||
channel_id,
|
||||
allowed_users,
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.slack")
|
||||
}
|
||||
|
||||
/// Check if a Slack user ID is in the allowlist.
|
||||
/// Empty list means deny everyone until explicitly configured.
|
||||
/// `"*"` means allow everyone.
|
||||
|
|
@ -29,7 +31,7 @@ impl SlackChannel {
|
|||
/// Get the bot's own user ID so we can ignore our own messages
|
||||
async fn get_bot_user_id(&self) -> Option<String> {
|
||||
let resp: serde_json::Value = self
|
||||
.client
|
||||
.http_client()
|
||||
.get("https://slack.com/api/auth.test")
|
||||
.bearer_auth(&self.bot_token)
|
||||
.send()
|
||||
|
|
@ -58,7 +60,7 @@ impl Channel for SlackChannel {
|
|||
});
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post("https://slack.com/api/chat.postMessage")
|
||||
.bearer_auth(&self.bot_token)
|
||||
.json(&body)
|
||||
|
|
@ -108,7 +110,7 @@ impl Channel for SlackChannel {
|
|||
}
|
||||
|
||||
let resp = match self
|
||||
.client
|
||||
.http_client()
|
||||
.get("https://slack.com/api/conversations.history")
|
||||
.bearer_auth(&self.bot_token)
|
||||
.query(¶ms)
|
||||
|
|
@ -179,7 +181,7 @@ impl Channel for SlackChannel {
|
|||
}
|
||||
|
||||
async fn health_check(&self) -> bool {
|
||||
self.client
|
||||
self.http_client()
|
||||
.get("https://slack.com/api/auth.test")
|
||||
.bearer_auth(&self.bot_token)
|
||||
.send()
|
||||
|
|
|
|||
|
|
@ -357,6 +357,10 @@ impl TelegramChannel {
|
|||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.telegram")
|
||||
}
|
||||
|
||||
fn normalize_identity(value: &str) -> String {
|
||||
value.trim().trim_start_matches('@').to_string()
|
||||
}
|
||||
|
|
@ -448,7 +452,7 @@ impl TelegramChannel {
|
|||
}
|
||||
|
||||
async fn fetch_bot_username(&self) -> anyhow::Result<String> {
|
||||
let resp = self.client.get(self.api_url("getMe")).send().await?;
|
||||
let resp = self.http_client().get(self.api_url("getMe")).send().await?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("Failed to fetch bot info: {}", resp.status());
|
||||
|
|
@ -857,7 +861,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let markdown_resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendMessage"))
|
||||
.json(&markdown_body)
|
||||
.send()
|
||||
|
|
@ -887,7 +891,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
plain_body["message_thread_id"] = serde_json::Value::String(tid.to_string());
|
||||
}
|
||||
let plain_resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendMessage"))
|
||||
.json(&plain_body)
|
||||
.send()
|
||||
|
|
@ -936,7 +940,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url(method))
|
||||
.json(&body)
|
||||
.send()
|
||||
|
|
@ -1029,7 +1033,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendDocument"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
|
|
@ -1068,7 +1072,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendDocument"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
|
|
@ -1112,7 +1116,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendPhoto"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
|
|
@ -1151,7 +1155,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendPhoto"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
|
|
@ -1195,7 +1199,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendVideo"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
|
|
@ -1239,7 +1243,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendAudio"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
|
|
@ -1283,7 +1287,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendVoice"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
|
|
@ -1320,7 +1324,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendDocument"))
|
||||
.json(&body)
|
||||
.send()
|
||||
|
|
@ -1357,7 +1361,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
|||
}
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendPhoto"))
|
||||
.json(&body)
|
||||
.send()
|
||||
|
|
@ -1685,7 +1689,7 @@ impl Channel for TelegramChannel {
|
|||
"allowed_updates": ["message"]
|
||||
});
|
||||
|
||||
let resp = match self.client.post(&url).json(&body).send().await {
|
||||
let resp = match self.http_client().post(&url).json(&body).send().await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
tracing::warn!("Telegram poll error: {e}");
|
||||
|
|
@ -1750,7 +1754,7 @@ Ensure only one `zeroclaw` process is using this bot token."
|
|||
"action": "typing"
|
||||
});
|
||||
let _ = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(self.api_url("sendChatAction"))
|
||||
.json(&typing_body)
|
||||
.send()
|
||||
|
|
@ -1769,7 +1773,7 @@ Ensure only one `zeroclaw` process is using this bot token."
|
|||
|
||||
match tokio::time::timeout(
|
||||
timeout_duration,
|
||||
self.client.get(self.api_url("getMe")).send(),
|
||||
self.http_client().get(self.api_url("getMe")).send(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
|
@ -1788,7 +1792,7 @@ Ensure only one `zeroclaw` process is using this bot token."
|
|||
async fn start_typing(&self, recipient: &str) -> anyhow::Result<()> {
|
||||
self.stop_typing(recipient).await?;
|
||||
|
||||
let client = self.client.clone();
|
||||
let client = self.http_client();
|
||||
let url = self.api_url("sendChatAction");
|
||||
let chat_id = recipient.to_string();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ pub struct WhatsAppChannel {
|
|||
endpoint_id: String,
|
||||
verify_token: String,
|
||||
allowed_numbers: Vec<String>,
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl WhatsAppChannel {
|
||||
|
|
@ -28,10 +27,13 @@ impl WhatsAppChannel {
|
|||
endpoint_id,
|
||||
verify_token,
|
||||
allowed_numbers,
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client(&self) -> reqwest::Client {
|
||||
crate::config::build_runtime_proxy_client("channel.whatsapp")
|
||||
}
|
||||
|
||||
/// Check if a phone number is allowed (E.164 format: +1234567890)
|
||||
fn is_number_allowed(&self, phone: &str) -> bool {
|
||||
self.allowed_numbers.iter().any(|n| n == "*" || n == phone)
|
||||
|
|
@ -164,7 +166,7 @@ impl Channel for WhatsAppChannel {
|
|||
});
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.http_client()
|
||||
.post(&url)
|
||||
.bearer_auth(&self.access_token)
|
||||
.header("Content-Type", "application/json")
|
||||
|
|
@ -201,7 +203,7 @@ impl Channel for WhatsAppChannel {
|
|||
// Check if we can reach the WhatsApp API
|
||||
let url = format!("https://graph.facebook.com/v18.0/{}", self.endpoint_id);
|
||||
|
||||
self.client
|
||||
self.http_client()
|
||||
.get(&url)
|
||||
.bearer_auth(&self.access_token)
|
||||
.send()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue