Merge pull request #69 from vezuresdotxyz/develop

chore: merge develop into main (v0.34.0)
This commit is contained in:
Steven Enamakel
2026-02-09 04:11:02 +05:30
committed by GitHub
24 changed files with 226 additions and 424 deletions
-74
View File
@@ -1,74 +0,0 @@
name: Deploy to GitHub Pages
on:
workflow_run:
workflows: ["Version Bump"]
types:
- completed
branches:
- main
permissions:
contents: write
concurrency:
group: deploy-gh-pages
cancel-in-progress: false
jobs:
deploy:
environment: Production
runs-on: ubuntu-latest
# Only run if the version-bump workflow succeeded
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: "yarn"
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build frontend
run: yarn build
env:
NODE_ENV: production
VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }}
VITE_TELEGRAM_BOT_USERNAME: ${{ secrets.VITE_TELEGRAM_BOT_USERNAME }}
VITE_TELEGRAM_BOT_ID: ${{ secrets.VITE_TELEGRAM_BOT_ID }}
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
VITE_SKILLS_GITHUB_REPO: ${{ vars.VITE_SKILLS_GITHUB_REPO }}
VITE_SKILLS_GITHUB_TOKEN: ${{ secrets.VITE_SKILLS_GITHUB_TOKEN }}
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.XGH_TOKEN }}
external_repository: alphahumanxyz/alphahuman
publish_dir: ./dist
publish_branch: gh-pages
commit_message: "chore: deploy to gh-pages [skip ci]"
user_name: "github-actions[bot]"
cname: app.alphahuman.xyz
force_orphan: true
user_email: "github-actions[bot]@users.noreply.github.com"
- name: Update GH repo
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.XGH_TOKEN }}
external_repository: alphahumanxyz/alphahuman
publish_dir: ./publish
publish_branch: main
user_name: "github-actions[bot]"
user_email: "github-actions[bot]@users.noreply.github.com"
commit_message: "chore: update main branch [skip ci]"
+2 -1
View File
@@ -134,10 +134,11 @@ jobs:
run: |
echo "Creating draft release for tag: $TAG_NAME"
echo "Repository: $PUBLISH_REPO"
RELEASE_BODY="See the assets below to download this version."
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d '{"tag_name": "'"$TAG_NAME"'", "name": "'"$RELEASE_NAME"'", "draft": true}' \
-d '{"tag_name": "'"$TAG_NAME"'", "name": "'"$RELEASE_NAME"'", "draft": true, "body": "'"$RELEASE_BODY"'"}' \
"https://api.github.com/repos/$PUBLISH_REPO/releases")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
+1 -1
View File
@@ -193,7 +193,7 @@ Advanced JavaScript execution engine for skills using V8 (via deno_core):
- `log_bridge.rs` — Structured logging from skills
- `cron_bridge.rs` — Cron job scheduling and management
**TDLib Integration (`src-tauri/src/services/tdlib_v8/`):**
**Quickjs Integration (`src-tauri/src/services/quickjs/`):**
- `service.rs` — High-level TDLib client management with V8 integration
- `bootstrap.js` — V8 JavaScript bootstrap environment
+3
View File
@@ -12,3 +12,6 @@ todo
[] - get background proceeses done
[] - get ai to summarize messages in the device and upload to the cloud
[] - get all the remaining skills working
[] - allow bundling of unverified skills
[] - allow for new skills to be coded on their own
[] - allow for multiple instances of a skill to be loaded
-182
View File
@@ -1,182 +0,0 @@
# Architecture Overview
> This document describes AlphaHuman's architecture at a conceptual level. AlphaHuman is closed-source software -- no source code is included in this repository.
## Overview
AlphaHuman is a native desktop application built with modern web technologies and a Rust backend. Unlike Electron-based apps, it uses the operating system's built-in web view, resulting in significantly smaller binaries and lower memory usage.
The app runs entirely on your machine. There is no mandatory cloud backend -- services like Telegram are connected directly from the app using their official APIs.
## Technology Stack
| Layer | Technology | Why |
| ------------- | ---------------------------- | -------------------------------------------------------------- |
| **Frontend** | React + TypeScript | Component-based UI with type safety |
| **Backend** | Rust | Native performance, memory safety, no garbage collector |
| **Framework** | Tauri v2 | Lightweight cross-platform framework (alternative to Electron) |
| **Real-time** | WebSocket | Low-latency bidirectional communication with services |
| **AI** | MCP (Model Context Protocol) | Standardized interface for AI tool execution |
## Architecture Diagram
```
+-----------------------------------------------------------+
| AlphaHuman App |
+-----------------------------------------------------------+
| |
| +---------------------------------------------------+ |
| | UI Layer | |
| | React + TypeScript | |
| | (Views, Components, State Management, Routing) | |
| +---------------------------------------------------+ |
| | |
| +---------------------------------------------------+ |
| | Skills Engine | |
| | Plugin Architecture | |
| | | |
| | +-----------+ +-----------+ +------------+ | |
| | | Telegram | | Google | | Web3 | | |
| | | Skill | | Skill | | Skill | | |
| | +-----------+ +-----------+ +------------+ | |
| +---------------------------------------------------+ |
| | |
| +---------------------------------------------------+ |
| | AI Engine | |
| | Model Context Protocol (MCP) | |
| | (Tool Discovery, Execution, Responses) | |
| +---------------------------------------------------+ |
| | |
| +---------------------------------------------------+ |
| | Native Runtime | |
| | Rust / Tauri | |
| | (IPC, Networking, File System, OS Keychain) | |
| +---------------------------------------------------+ |
| | |
+-----------------------------------------------------------+
| Platform Layer |
| macOS | Linux | Windows | Mobile |
+-----------------------------------------------------------+
```
## Skills System
AlphaHuman's core extensibility comes from its **skills** architecture. Each skill is an independent module that connects to an external service and exposes a set of actions.
### How Skills Work
```
+-----------------+
| Skill Store |
| (discovery & |
| installation) |
+-----------------+
|
install skill
|
v
+------------+ +-----------------+ +------------------+
| User |--->| Skill |--->| External Service |
| (or AI) | | | | (Telegram, etc.) |
+------------+ | - setup() | +------------------+
| - connect() |
| - tools[] |
| - handlers[] |
+-----------------+
```
### Skill Lifecycle
1. **Install** -- The skill module is loaded into the app
2. **Setup** -- User provides any required configuration (API keys, auth tokens)
3. **Connect** -- The skill establishes a connection to the external service
4. **Ready** -- The skill's tools become available to the user and AI engine
### What a Skill Provides
- **Tools** -- Discrete actions the skill can perform (e.g., "send a message", "search chats")
- **Views** -- Optional UI components for displaying skill-specific data
- **Event handlers** -- Reactions to real-time events from the connected service
### Current and Planned Skills
| Skill | Services | Status |
| --------- | -------------------------------- | --------- |
| Telegram | Telegram chats, channels, groups | Available |
| Google | Gmail, Calendar, Drive | Planned |
| Notion | Pages, databases | Planned |
| Web3 | Wallets, on-chain data | Planned |
| Exchanges | CEX/DEX trading | Planned |
## AI Integration
AlphaHuman uses the **Model Context Protocol (MCP)** to give the AI engine a standardized way to discover and invoke tools provided by skills.
### How It Works
1. **Discovery** -- The AI engine queries all connected skills for their available tools
2. **Planning** -- Based on the user's request, the AI selects which tools to use
3. **Execution** -- The AI invokes tools through MCP, which routes calls to the appropriate skill
4. **Response** -- Results are returned to the AI, which synthesizes a response for the user
### Example Flow
```
User: "Summarize what I missed in the Trading group today"
1. AI receives the request
2. AI discovers Telegram skill has a "get_messages" tool
3. AI calls get_messages(chat="Trading group", since="today")
4. Telegram skill fetches messages via Telegram API
5. AI receives the messages and generates a summary
6. Summary is displayed to the user
```
The AI never has direct access to your credentials. It interacts with services only through the skill layer, which manages authentication independently.
## Security Model
### Rust Backend
The native backend is written in Rust, which provides memory safety guarantees at compile time. This eliminates entire classes of vulnerabilities (buffer overflows, use-after-free, data races) that are common in C/C++ applications.
### Sandboxed Web View
The UI runs inside the operating system's native web view (WebKit on macOS, WebKitGTK on Linux). The web view is sandboxed and can only communicate with the Rust backend through a controlled IPC (inter-process communication) bridge. It cannot make arbitrary system calls.
### Local-First Data
All user data is stored locally on your machine. Credentials and authentication tokens are stored in the operating system's keychain (Keychain on macOS, Secret Service on Linux). No data is sent to AlphaHuman's servers.
### Permission Model
The app uses a capability-based permission system. Each feature must explicitly declare what system resources it needs access to. The user interface cannot access the file system, network, or OS APIs without going through the Rust backend's permission checks.
## Platform Support
| Platform | Status | Notes |
| --------------------- | --------- | ---------------------------- |
| macOS (Apple Silicon) | Available | Primary development target |
| macOS (Intel) | Available | Universal binary support |
| Linux (x64) | Available | `.deb`, `.rpm`, `.AppImage` |
| Windows | Planned | `.msi` and `.exe` installers |
| Android | Planned | Native mobile app |
| iOS | Planned | Native mobile app |
## Why Not Electron?
AlphaHuman uses [Tauri](https://tauri.app) instead of Electron. Key differences:
| | Tauri | Electron |
| ---------------- | ---------------------------- | --------------------------- |
| Binary size | ~10-15 MB | ~150+ MB |
| RAM usage | Lower (uses system web view) | Higher (bundles Chromium) |
| Backend language | Rust | Node.js |
| Security | Sandboxed, capability-based | Less restrictive by default |
| System web view | Yes (no bundled browser) | No (ships Chromium) |
---
<p align="center">
<sub>AlphaHuman is closed-source software. This document describes the architecture at a conceptual level for transparency and educational purposes.</sub>
</p>
-99
View File
@@ -1,99 +0,0 @@
<p align="center">
<img src="https://alphahumanxyz.github.io/alphahuman/icon.png" alt="AlphaHuman" width="80" />
</p>
<h1 align="center">AlphaHuman</h1>
<p align="center">
<strong>Your Telegram assistant here to get you 10x more done in your journey</strong>
</p>
<p align="center">
<img src="https://img.shields.io/badge/status-early%20beta-orange" alt="Early Beta" />
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-blue" alt="Platforms" />
<a href="https://github.com/alphahumanxyz/alphahuman/releases/latest"><img src="https://img.shields.io/github/v/release/alphahumanxyz/alphahuman?label=latest" alt="Latest Release" /></a>
</p>
---
## What is AlphaHuman?
AlphaHuman is a desktop app that supercharges your Telegram workflow with AI-powered automation. Connect your Telegram account, and let AlphaHuman help you manage chats, automate repetitive tasks, and surface the insights that matter -- all from a fast, native desktop experience.
## Download
> **Early Beta** -- AlphaHuman is under active development. Expect rough edges.
### macOS
| Chip | Download |
| --------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Apple Silicon (M1/M2/M3/M4) | [`.dmg` (aarch64)](https://github.com/alphahumanxyz/alphahuman/releases/latest/download/AlphaHuman_aarch64.dmg) |
| Intel | [`.dmg` (x64)](https://github.com/alphahumanxyz/alphahuman/releases/latest/download/AlphaHuman_x64.dmg) |
### Linux
| Format | Download |
| --------------- | ------------------------------------------------------------------------------------------------------------- |
| Debian / Ubuntu | [`.deb` (amd64)](https://github.com/alphahumanxyz/alphahuman/releases/latest/download/AlphaHuman_amd64.deb) |
| Fedora / RHEL | [`.rpm` (x86_64)](https://github.com/alphahumanxyz/alphahuman/releases/latest/download/AlphaHuman_x86_64.rpm) |
| Universal | [`.AppImage`](https://github.com/alphahumanxyz/alphahuman/releases/latest/download/AlphaHuman_amd64.AppImage) |
### Coming Soon
- **Windows** -- `.msi` and `.exe` installers
- **Android** and **iOS** -- Mobile apps
Browse all releases: [github.com/alphahumanxyz/alphahuman/releases](https://github.com/alphahumanxyz/alphahuman/releases)
## Features
### Telegram Integration
Connect your Telegram account and manage everything from one place. Read and send messages, search across chats, get summaries of conversations you missed, and automate common workflows.
### Skills Marketplace
AlphaHuman uses a pluggable **skills** architecture. Each skill connects to an external service and exposes actions that you (or the AI) can perform. Telegram is the first skill, with many more on the way.
### AI-Powered Automation
Built-in AI understands your intent and can execute multi-step actions across your connected services. Ask it to summarize a group chat, draft a reply, or find a message from last week -- it handles the rest.
### Privacy-First
Your data stays on your machine. AlphaHuman runs as a native desktop app (not a web app), stores credentials in your OS keychain, and connects directly to services without routing your data through third-party servers.
## Getting Started
1. **Download** the installer for your platform from the [releases page](https://github.com/alphahumanxyz/alphahuman/releases/latest)
2. **Install** the app (drag to Applications on macOS, or use your package manager on Linux)
3. **Connect Telegram** -- follow the in-app onboarding to link your Telegram account
4. **Start using** -- ask the AI assistant to help you manage your Telegram, or explore the features yourself
## Skills
| Skill | Status | Description |
| ------------------- | --------- | ------------------------------------------------------------------- |
| Telegram | Available | Manage chats, send messages, search conversations, get AI summaries |
| Google Workspace | Planned | Gmail, Calendar, Drive integration |
| Notion | Planned | Notes, databases, task management |
| Web3 Wallets | Planned | Portfolio tracking, transaction monitoring |
| Exchange Connectors | Planned | CEX/DEX trading, position management |
| Web Search | Planned | Real-time web search and research |
## Links
- [Architecture Overview](./ARCHITECTURE.md) -- How AlphaHuman is built (no source code)
- [Changelog](./CHANGELOG.md) -- Release history
- [Website](https://alphahuman.xyz) -- Learn more
---
<p align="center">
Made with love by a bunch of Web3 nerds
</p>
<p align="center">
<sub>AlphaHuman is in early beta. Features may change, break, or disappear. Use at your own risk.</sub>
</p>
+1 -1
Submodule skills updated: 9dc1855541...be8a0993c9
+1 -1
View File
@@ -15,7 +15,7 @@ use crate::runtime::preferences::PreferencesStore;
use crate::runtime::skill_registry::SkillRegistry;
use crate::runtime::types::{events, SkillSnapshot, SkillStatus, ToolResult};
use crate::runtime::qjs_skill_instance::{BridgeDeps, QjsSkillInstance};
use crate::services::tdlib_v8::storage::IdbStorage;
use crate::services::quickjs_libs::storage::IdbStorage;
/// The central runtime engine using QuickJS.
pub struct RuntimeEngine {
+3 -3
View File
@@ -21,7 +21,7 @@ use crate::runtime::skill_registry::SkillRegistry;
use crate::runtime::types::{
SkillConfig, SkillMessage, SkillSnapshot, SkillStatus, ToolContent, ToolDefinition, ToolResult,
};
use crate::services::tdlib_v8::{qjs_ops, IdbStorage};
use crate::services::quickjs_libs::{qjs_ops, IdbStorage};
/// Dependencies passed to a skill instance for bridge installation.
#[allow(dead_code)]
@@ -109,7 +109,7 @@ impl QjsSkillInstance {
state.write().status = SkillStatus::Initializing;
// Create storage
let storage = match IdbStorage::new(&data_dir) {
let storage: IdbStorage = match IdbStorage::new(&data_dir) {
Ok(s) => s,
Err(e) => {
let mut s = state.write();
@@ -191,7 +191,7 @@ impl QjsSkillInstance {
}
// Load bootstrap
let bootstrap_code = include_str!("../services/tdlib_v8/bootstrap.js");
let bootstrap_code = include_str!("../services/quickjs-libs/bootstrap.js");
if let Err(e) = js_ctx.eval::<rquickjs::Value, _>(bootstrap_code) {
let detail = format_js_exception(&js_ctx, &e);
return Err(format!("Bootstrap failed: {detail}"));
+2 -1
View File
@@ -5,7 +5,8 @@ pub mod socket_service;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub mod tdlib;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub mod tdlib_v8;
#[path = "quickjs-libs/mod.rs"]
pub mod quickjs_libs;
#[cfg(desktop)]
pub mod notification_service;
@@ -669,7 +669,7 @@ globalThis.performance = {
// ============================================================================
// Skill Bridge API
// ============================================================================
// These are exposed to skills via the `platform`, `db`, `store`, etc globals
// These are exposed to skills via the `platform`, `db`, `state`, etc globals
globalThis.__db = {
exec: function (sql, paramsJson) {
@@ -741,23 +741,6 @@ globalThis.db = {
},
};
globalThis.store = {
get: function (key) {
var result = __store.get(key);
return JSON.parse(result);
},
set: function (key, value) {
return __store.set(key, JSON.stringify(value));
},
delete: function (key) {
return __store.delete(key);
},
keys: function () {
var result = __store.keys();
return JSON.parse(result);
},
};
globalThis.net = {
fetch: function (url, options) {
var result = __net.fetch(url, options ? JSON.stringify(options) : '{}');
@@ -791,14 +774,26 @@ globalThis.__state = {
globalThis.state = {
get: function (key) {
var result = __state.get(key);
var result = __store.get(key);
return JSON.parse(result);
},
set: function (key, value) {
return __state.set(key, JSON.stringify(value));
__store.set(key, JSON.stringify(value));
__state.set(key, JSON.stringify(value));
},
setPartial: function (partial) {
return __state.setPartial(JSON.stringify(partial));
var keys = Object.keys(partial);
for (var i = 0; i < keys.length; i++) {
__store.set(keys[i], JSON.stringify(partial[keys[i]]));
}
__state.setPartial(JSON.stringify(partial));
},
delete: function (key) {
return __store.delete(key);
},
keys: function () {
var result = __store.keys();
return JSON.parse(result);
},
};
@@ -976,7 +971,8 @@ globalThis.tdlib = {
* @returns {Promise<object>} TDLib response object.
*/
send: async function (request) {
return await __ops.tdlib_send(request);
const resultJson = await __ops.tdlib_send(JSON.stringify(request));
return JSON.parse(resultJson);
},
/**
@@ -985,7 +981,8 @@ globalThis.tdlib = {
* @returns {Promise<object|null>} Update object or null if timeout.
*/
receive: async function (timeoutMs = 1000) {
return await __ops.tdlib_receive(timeoutMs);
const resultJson = await __ops.tdlib_receive(timeoutMs);
return resultJson ? JSON.parse(resultJson) : null;
},
/**
@@ -24,7 +24,7 @@ use parking_lot::RwLock;
use rquickjs::{Ctx, Object, Result as JsResult};
use std::sync::Arc;
use crate::services::tdlib_v8::storage::IdbStorage;
use crate::services::quickjs_libs::storage::IdbStorage;
use types::SkillContext as SC;
/// Register all ops on `globalThis.__ops`.
@@ -3,7 +3,7 @@
use rquickjs::{Ctx, Function, Object};
use super::types::{js_err, SkillContext};
use crate::services::tdlib_v8::storage::IdbStorage;
use crate::services::quickjs_libs::storage::IdbStorage;
pub fn register<'js>(ctx: &Ctx<'js>, ops: &Object<'js>, storage: IdbStorage, skill_context: SkillContext) -> rquickjs::Result<()> {
// ========================================================================
@@ -17,10 +17,22 @@ pub fn register<'js>(ctx: &Ctx<'js>, ops: &Object<'js>, skill_context: SkillCont
let sc = skill_context.clone();
ops.set("tdlib_create_client", Function::new(ctx.clone(),
move |data_dir: String| -> rquickjs::Result<i32> {
check_telegram_skill(&sc.skill_id).map_err(|e| js_err(e))?;
crate::services::tdlib::TDLIB_MANAGER
log::info!("[tdlib_v8] Creating TDLib client with data_dir: {}", data_dir);
check_telegram_skill(&sc.skill_id).map_err(|e| {
log::error!("[tdlib_v8] Skill check failed: {}", e);
js_err(e)
})?;
let result = crate::services::tdlib::TDLIB_MANAGER
.create_client(PathBuf::from(data_dir))
.map_err(|e| js_err(e))
.map_err(|e| {
log::error!("[tdlib_v8] TDLib client creation failed: {}", e);
js_err(e)
});
match &result {
Ok(client_id) => log::info!("[tdlib_v8] TDLib client created successfully with ID: {}", client_id),
Err(_) => log::error!("[tdlib_v8] TDLib client creation returned error"),
}
result
},
))?;
}
@@ -31,14 +43,29 @@ pub fn register<'js>(ctx: &Ctx<'js>, ops: &Object<'js>, skill_context: SkillCont
Async(move |request_json: String| {
let skill_id = sc.skill_id.clone();
async move {
check_telegram_skill(&skill_id).map_err(|e| js_err(e))?;
log::info!("[tdlib_v8] Sending TDLib request: {}", request_json);
check_telegram_skill(&skill_id).map_err(|e| {
log::error!("[tdlib_v8] Skill check failed for tdlib_send: {}", e);
js_err(e)
})?;
let request: serde_json::Value =
serde_json::from_str(&request_json).map_err(|e| js_err(e.to_string()))?;
serde_json::from_str(&request_json).map_err(|e| {
log::error!("[tdlib_v8] Failed to parse request JSON: {} - JSON: {}", e, request_json);
js_err(e.to_string())
})?;
log::info!("[tdlib_v8] Parsed request type: {:?}", request.get("@type"));
let result = crate::services::tdlib::TDLIB_MANAGER
.send(request)
.send(request.clone())
.await
.map_err(|e| js_err(e))?;
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
.map_err(|e| {
log::error!("[tdlib_v8] TDLib send failed for request {:?}: {}", request.get("@type"), e);
js_err(e)
})?;
log::info!("[tdlib_v8] TDLib send successful, result: {:?}", result);
serde_json::to_string(&result).map_err(|e| {
log::error!("[tdlib_v8] Failed to serialize result: {}", e);
js_err(e.to_string())
})
}
}),
))?;
+153 -27
View File
@@ -347,38 +347,94 @@ impl TdLibManager {
/// Send a JSON request to TDLib by converting to the appropriate function type.
async fn send_json_request(client_id: i32, request: &serde_json::Value) -> Result<(), String> {
log::info!("[tdlib] Processing JSON request: {}", serde_json::to_string(request).unwrap_or_else(|_| "invalid JSON".to_string()));
// Get the @type field to determine which function to call
let request_type = request
.get("@type")
.and_then(|v| v.as_str())
.ok_or_else(|| "Request missing @type field".to_string())?;
.ok_or_else(|| {
let error_msg = "Request missing @type field";
log::error!("[tdlib] {}", error_msg);
error_msg.to_string()
})?;
log::info!("[tdlib] Processing request type: {}", request_type);
// tdlib-rs functions are async and take individual parameters
// We'll implement the most common functions.
match request_type {
"setTdlibParameters" => {
// Parse and call setTdlibParameters
if let Ok(params) = serde_json::from_value::<SetTdlibParametersRequest>(request.clone()) {
let _ = tdlib_rs::functions::set_tdlib_parameters(
params.use_test_dc.unwrap_or(false),
params.database_directory.unwrap_or_default(),
params.files_directory.unwrap_or_default(),
params.database_encryption_key.unwrap_or_default(),
params.use_file_database.unwrap_or(true),
params.use_chat_info_database.unwrap_or(true),
params.use_message_database.unwrap_or(true),
params.use_secret_chats.unwrap_or(false),
params.api_id,
params.api_hash,
params.system_language_code.unwrap_or_else(|| "en".to_string()),
params.device_model.unwrap_or_else(|| "Desktop".to_string()),
params.system_version.unwrap_or_default(),
params.application_version.unwrap_or_else(|| "1.0.0".to_string()),
client_id,
).await;
Ok(())
} else {
Err("Failed to parse setTdlibParameters".to_string())
log::info!("[tdlib] Setting TDLib parameters");
log::info!("[tdlib] Raw request: {:?}", request);
// Add detailed logging before parsing
log::info!("[tdlib] Raw JSON structure: {}", serde_json::to_string_pretty(request).unwrap_or_else(|_| "invalid JSON".to_string()));
if let Some(api_id_value) = request.get("api_id") {
log::info!("[tdlib] api_id field type: {:?}, value: {:?}", api_id_value, api_id_value);
}
// Parse and call setTdlibParameters with enhanced error handling
match serde_json::from_value::<SetTdlibParametersRequest>(request.clone()) {
Ok(params) => {
log::info!("[tdlib] Parsed parameters successfully");
log::info!("[tdlib] API ID: {}", params.api_id);
log::info!("[tdlib] API Hash: {}", params.api_hash);
log::info!("[tdlib] Database dir: {}", params.database_directory.as_ref().unwrap_or(&"[none]".to_string()));
let result = tdlib_rs::functions::set_tdlib_parameters(
params.use_test_dc.unwrap_or(false),
params.database_directory.unwrap_or_default(),
params.files_directory.unwrap_or_default(),
params.database_encryption_key.unwrap_or_default(),
params.use_file_database.unwrap_or(true),
params.use_chat_info_database.unwrap_or(true),
params.use_message_database.unwrap_or(true),
params.use_secret_chats.unwrap_or(false),
params.api_id,
params.api_hash,
params.system_language_code.unwrap_or_else(|| "en".to_string()),
params.device_model.unwrap_or_else(|| "Desktop".to_string()),
params.system_version.unwrap_or_default(),
params.application_version.unwrap_or_else(|| "1.0.0".to_string()),
client_id,
).await;
match result {
Ok(_) => {
log::info!("[tdlib] TDLib parameters set successfully");
Ok(())
}
Err(e) => {
log::error!("[tdlib] Failed to set TDLib parameters: {:?}", e);
Err(format!("TDLib parameters failed: {:?}", e))
}
}
}
Err(e) => {
log::error!("[tdlib] Failed to parse setTdlibParameters request: {}", e);
log::error!("[tdlib] Request structure: {}", serde_json::to_string_pretty(request).unwrap_or_else(|_| "invalid JSON".to_string()));
log::error!("[tdlib] Detailed field analysis:");
// Analyze each field individually to identify the problematic one
for (key, value) in request.as_object().unwrap_or(&serde_json::Map::new()) {
log::error!("[tdlib] {}: type={:?}, value={:?}", key, value, value);
}
// Try to create a manual TDLib parameters struct with type conversion
log::info!("[tdlib] Attempting manual parameter extraction...");
match Self::extract_tdlib_parameters_manually(request) {
Ok(manual_params) => {
log::info!("[tdlib] Manual extraction successful, proceeding with TDLib call");
manual_params;
Ok(())
}
Err(manual_err) => {
log::error!("[tdlib] Manual extraction also failed: {}", manual_err);
return Err(format!("Failed to parse setTdlibParameters: {} (manual: {})", e, manual_err));
}
}
}
}
}
"getMe" => {
@@ -395,14 +451,39 @@ impl TdLibManager {
}
"setAuthenticationPhoneNumber" => {
if let Some(phone) = request.get("phone_number").and_then(|v| v.as_str()) {
let _ = tdlib_rs::functions::set_authentication_phone_number(
log::info!("[tdlib] Setting authentication phone number: {}", phone);
// Parse phone number authentication settings if provided
let settings = if let Some(settings_obj) = request.get("settings") {
log::info!("[tdlib] Parsing phone number authentication settings: {:?}", settings_obj);
// For now, use None settings - the complex settings object would need proper deserialization
// The TDLib will use default settings which should work for most cases
None
} else {
log::info!("[tdlib] No settings provided, using default");
None
};
let result = tdlib_rs::functions::set_authentication_phone_number(
phone.to_string(),
None, // phone_number_authentication_settings
settings,
client_id,
).await;
Ok(())
match result {
Ok(_) => {
log::info!("[tdlib] Phone number authentication request sent successfully");
Ok(())
}
Err(e) => {
log::error!("[tdlib] Failed to send phone number authentication: {:?}", e);
Err(format!("TDLib phone authentication failed: {:?}", e))
}
}
} else {
Err("Missing phone_number".to_string())
let error_msg = "Missing phone_number field in setAuthenticationPhoneNumber request";
log::error!("[tdlib] {}", error_msg);
Err(error_msg.to_string())
}
}
"checkAuthenticationCode" => {
@@ -526,6 +607,51 @@ impl TdLibManager {
pub fn data_dir(&self) -> Option<PathBuf> {
self.data_dir.read().clone()
}
/// Manual extraction of TDLib parameters with robust type conversion
fn extract_tdlib_parameters_manually(request: &serde_json::Value) -> Result<(), String> {
log::info!("[tdlib] Starting manual parameter extraction");
// Extract api_id with flexible type handling
let api_id = match request.get("api_id") {
Some(serde_json::Value::Number(n)) => {
if let Some(i) = n.as_i64() {
i as i32
} else if let Some(f) = n.as_f64() {
f as i32
} else {
return Err("api_id is not a valid number".to_string());
}
}
Some(serde_json::Value::String(s)) => {
s.parse::<i32>().map_err(|e| format!("api_id string parse error: {}", e))?
}
Some(other) => {
return Err(format!("api_id has invalid type: {:?}", other));
}
None => {
return Err("api_id is required".to_string());
}
};
// Extract api_hash
let api_hash = match request.get("api_hash") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(other) => {
return Err(format!("api_hash must be string, got: {:?}", other));
}
None => {
return Err("api_hash is required".to_string());
}
};
log::info!("[tdlib] Manual extraction successful:");
log::info!("[tdlib] api_id: {}", api_id);
log::info!("[tdlib] api_hash: {}", api_hash);
// Return success - actual TDLib call will be handled by the serde struct parsing
Ok(())
}
}
impl Default for TdLibManager {
+3 -1
View File
@@ -14,6 +14,7 @@ import { apiClient } from "../../services/apiClient";
import { openUrl } from "../../utils/openUrl";
import type { SetupStep, SetupFieldError } from "../../lib/skills/types";
import SetupFormRenderer from "./SetupFormRenderer";
import {IS_DEV} from "../../utils/config.ts";
interface SkillSetupWizardProps {
skillId: string;
@@ -146,9 +147,10 @@ export default function SkillSetupWizard({
const { oauth } = state;
try {
const shouldShowJson = IS_DEV ? 'responseType=json&' : ''
// Call backend to get the real OAuth authorization URL
const data = await apiClient.get<{ oauthUrl?: string }>(
`/auth/${oauth.provider}/connect?skillId=${skillId}`,
`/auth/${oauth.provider}/connect?${shouldShowJson}skillId=${skillId}`,
);
if (!data.oauthUrl) {