Initial scaffold: GyanDoot v2.0 in Rust
Full Cargo workspace with 11 crates: - gyandoot-core: Shared types, config, error types - gyandoot-agent: Agent loop, tool execution, context compression - gyandoot-providers: LLM provider abstraction (OpenAI, Anthropic, Ollama) - gyandoot-memory: MemPalace persistent memory system - gyandoot-platform: Platform adapters (Telegram, Discord, Webhook, CLI) - gyandoot-security: Credential vault, sanitizer, threat detector - gyandoot-kanban: Task decomposition, swarm orchestration, diagnostics - gyandoot-subagents: Sub-agent spawning and orchestration - gyandoot-skills: Skills discovery, loading, preprocessing, bundles - gyandoot-profile: Multi-agent profile manager - gyandoot-gateway: Gateway sessions, routing, access control Plus: CLI entry point with clap, cross-platform installer script, KANBAN.md, .gnpl/AGENTS.md Workspace compiles clean: cargo check passes with 0 errors.
This commit is contained in:
138
crates/gyandoot-platform/src/telegram.rs
Normal file
138
crates/gyandoot-platform/src/telegram.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
//! Telegram bot platform adapter.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use gyandoot_core::PlatformResult;
|
||||
use reqwest::Client;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
use crate::{IncomingMessage, OutgoingMessage, Platform};
|
||||
|
||||
/// Adapter for the Telegram Bot API.
|
||||
pub struct TelegramPlatform {
|
||||
bot_token: String,
|
||||
client: Client,
|
||||
allowed_users: Vec<i64>,
|
||||
}
|
||||
|
||||
impl TelegramPlatform {
|
||||
/// Create a new Telegram platform adapter.
|
||||
pub fn new(bot_token: String, allowed_users: Vec<i64>) -> Self {
|
||||
Self {
|
||||
bot_token,
|
||||
client: Client::new(),
|
||||
allowed_users,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the Telegram API URL for a given method.
|
||||
fn api_url(&self, method: &str) -> String {
|
||||
format!("https://api.telegram.org/bot{}/{}", self.bot_token, method)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Platform for TelegramPlatform {
|
||||
fn name(&self) -> &str {
|
||||
"telegram"
|
||||
}
|
||||
|
||||
async fn start(&self) -> PlatformResult<tokio::sync::mpsc::Receiver<IncomingMessage>> {
|
||||
info!("Starting Telegram polling...");
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(100);
|
||||
|
||||
let bot_token = self.bot_token.clone();
|
||||
let client = self.client.clone();
|
||||
let allowed_users = self.allowed_users.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut offset: i64 = 0;
|
||||
loop {
|
||||
let url = format!(
|
||||
"https://api.telegram.org/bot{}/getUpdates?offset={}&timeout=30",
|
||||
bot_token, offset
|
||||
);
|
||||
match client.get(&url).send().await {
|
||||
Ok(response) => {
|
||||
if let Ok(data) = response.json::<serde_json::Value>().await {
|
||||
if let Some(results) = data["result"].as_array() {
|
||||
for update in results {
|
||||
let update_id = update["update_id"].as_i64().unwrap_or(0);
|
||||
offset = update_id + 1;
|
||||
if let Some(msg) = update.get("message") {
|
||||
let chat_id = msg["chat"]["id"].as_i64().unwrap_or(0);
|
||||
let text = msg["text"].as_str().unwrap_or("").to_string();
|
||||
let user_id = msg["from"]["id"].as_i64().unwrap_or(0).to_string();
|
||||
let username = msg["from"]["username"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
|
||||
if !allowed_users.is_empty() && !allowed_users.contains(&chat_id) {
|
||||
debug!("Ignoring message from non-allowed user {}", chat_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let incoming = IncomingMessage {
|
||||
platform: "telegram".to_string(),
|
||||
channel_id: chat_id.to_string(),
|
||||
user_id,
|
||||
username,
|
||||
text,
|
||||
message_id: update_id.to_string(),
|
||||
};
|
||||
|
||||
if tx.send(incoming).await.is_err() {
|
||||
error!("Telegram channel closed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Telegram poll error: {}", e);
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
async fn send(&self, message: OutgoingMessage) -> PlatformResult<()> {
|
||||
let chat_id: i64 = message.channel_id.parse().unwrap_or(0);
|
||||
let body = serde_json::json!({
|
||||
"chat_id": chat_id,
|
||||
"text": message.text,
|
||||
"parse_mode": "Markdown",
|
||||
});
|
||||
|
||||
let url = self.api_url("sendMessage");
|
||||
let response = self.client.post(&url).json(&body).send().await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
error!("Telegram send failed: {}", response.status());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_typing(&self, channel_id: &str) -> PlatformResult<()> {
|
||||
let chat_id: i64 = channel_id.parse().unwrap_or(0);
|
||||
let body = serde_json::json!({
|
||||
"chat_id": chat_id,
|
||||
"action": "typing",
|
||||
});
|
||||
|
||||
let url = self.api_url("sendChatAction");
|
||||
self.client.post(&url).json(&body).send().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> PlatformResult<()> {
|
||||
info!("Telegram platform shutting down");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user