mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 06:32:24 +00:00
Merge pull request #69 from vezuresdotxyz/develop
chore: merge develop into main (v0.34.0)
This commit is contained in:
@@ -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]"
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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}"));
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
+20
-23
@@ -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;
|
||||
},
|
||||
|
||||
/**
|
||||
+1
-1
@@ -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`.
|
||||
+1
-1
@@ -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<()> {
|
||||
// ========================================================================
|
||||
+35
-8
@@ -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())
|
||||
})
|
||||
}
|
||||
}),
|
||||
))?;
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user