mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat: telegram platform
This commit is contained in:
@@ -0,0 +1,418 @@
|
|||||||
|
# 重构总结文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本次重构将 MemoHome CLI 项目分离成了清晰的两个层次,并实现了 Telegram Bot 的多用户登录鉴权系统。
|
||||||
|
|
||||||
|
## 重构目标
|
||||||
|
|
||||||
|
1. ✅ **分离关注点**: CLI UI 层和核心业务逻辑完全分离
|
||||||
|
2. ✅ **可复用性**: Core 层可被任何平台使用(CLI、Telegram、Web 等)
|
||||||
|
3. ✅ **多存储后端**: 支持不同的存储方式(文件、Redis 等)
|
||||||
|
4. ✅ **多用户支持**: Telegram bot 支持多个 TG 账号绑定多个 MemoHome 账号
|
||||||
|
|
||||||
|
## 架构变化
|
||||||
|
|
||||||
|
### 之前 (单体架构)
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/cli/
|
||||||
|
├── src/
|
||||||
|
│ ├── client.ts # 混合了 API 调用和配置管理
|
||||||
|
│ ├── config.ts # 硬编码文件存储
|
||||||
|
│ ├── utils.ts
|
||||||
|
│ ├── types.ts
|
||||||
|
│ └── commands/ # CLI 命令直接调用 API
|
||||||
|
│ ├── auth.ts
|
||||||
|
│ ├── user.ts
|
||||||
|
│ └── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:
|
||||||
|
- CLI 和业务逻辑耦合
|
||||||
|
- 配置硬编码为文件存储
|
||||||
|
- 无法在其他平台复用
|
||||||
|
- 单用户设计
|
||||||
|
|
||||||
|
### 之后 (分层架构)
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/cli/
|
||||||
|
├── src/
|
||||||
|
│ ├── core/ # 核心业务逻辑(可复用)
|
||||||
|
│ │ ├── context.ts # 上下文管理
|
||||||
|
│ │ ├── storage.ts # 存储接口定义
|
||||||
|
│ │ ├── storage/
|
||||||
|
│ │ │ ├── file.ts # 文件存储实现
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ ├── client.ts # API 客户端(支持多种存储)
|
||||||
|
│ │ ├── auth.ts # 认证逻辑
|
||||||
|
│ │ ├── user.ts # 用户管理
|
||||||
|
│ │ ├── agent.ts # AI 对话
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── cli/ # CLI UI 层
|
||||||
|
│ │ ├── index.ts # CLI 入口
|
||||||
|
│ │ └── commands/ # 命令定义(使用 core)
|
||||||
|
│ │ ├── auth.ts
|
||||||
|
│ │ ├── user.ts
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── types/ # 类型定义
|
||||||
|
│ └── utils/ # 工具函数
|
||||||
|
|
||||||
|
packages/platform-telegram/ # Telegram 平台
|
||||||
|
├── src/
|
||||||
|
│ ├── storage.ts # Redis 存储实现
|
||||||
|
│ ├── auth.ts # Telegram 认证处理
|
||||||
|
│ ├── index.ts # Bot 实现(使用 cli/core)
|
||||||
|
│ └── bot.ts # 独立启动入口
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心改进
|
||||||
|
|
||||||
|
### 1. 存储抽象层
|
||||||
|
|
||||||
|
**接口定义** (`cli/src/core/storage.ts`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface TokenStorage {
|
||||||
|
getApiUrl(): Promise<string> | string
|
||||||
|
setApiUrl(url: string): Promise<void> | void
|
||||||
|
getToken(userId?: string): Promise<string | null> | string | null
|
||||||
|
setToken(token: string, userId?: string): Promise<void> | void
|
||||||
|
clearToken(userId?: string): Promise<void> | void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**实现**:
|
||||||
|
|
||||||
|
1. **FileTokenStorage** (CLI 用)
|
||||||
|
- 存储位置: `~/.memohome/config.json`
|
||||||
|
- 单用户
|
||||||
|
- 同步操作
|
||||||
|
|
||||||
|
2. **TelegramRedisStorage** (Telegram Bot 用)
|
||||||
|
- 存储位置: Redis
|
||||||
|
- 多用户: `memohome:tg:token:{telegram_user_id}`
|
||||||
|
- 异步操作
|
||||||
|
- 30 天过期
|
||||||
|
|
||||||
|
### 2. 上下文管理
|
||||||
|
|
||||||
|
**MemoHomeContext** (`cli/src/core/context.ts`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface MemoHomeContext {
|
||||||
|
storage: TokenStorage // 存储后端
|
||||||
|
currentUserId?: string // 当前用户 ID(用于多用户场景)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用方式**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// CLI 场景(单用户,文件存储)
|
||||||
|
import { setContext, FileTokenStorage } from '@memohome/cli/core'
|
||||||
|
|
||||||
|
setContext({
|
||||||
|
storage: new FileTokenStorage()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Telegram 场景(多用户,Redis 存储)
|
||||||
|
import { createContext } from '@memohome/cli/core'
|
||||||
|
import { TelegramRedisStorage } from '@memohome/platform-telegram'
|
||||||
|
|
||||||
|
const storage = new TelegramRedisStorage()
|
||||||
|
const context = createContext({
|
||||||
|
storage,
|
||||||
|
userId: telegramUserId // 不同的 TG 用户
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 同步/异步 API 支持
|
||||||
|
|
||||||
|
为了同时支持同步存储(文件)和异步存储(Redis),提供了两套 API:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 同步版本(CLI 用)
|
||||||
|
export function login(params: LoginParams, context?: MemoHomeContext)
|
||||||
|
export function isLoggedIn(context?: MemoHomeContext): boolean
|
||||||
|
export function getToken(context?: MemoHomeContext): string | null
|
||||||
|
|
||||||
|
// 异步版本(Telegram Bot 用)
|
||||||
|
export async function loginAsync(params: LoginParams, context?: MemoHomeContext)
|
||||||
|
export async function isLoggedInAsync(context?: MemoHomeContext): Promise<boolean>
|
||||||
|
export async function getTokenAsync(context?: MemoHomeContext): Promise<string | null>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Telegram Bot 多用户架构
|
||||||
|
|
||||||
|
**存储结构**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Redis:
|
||||||
|
memohome:tg:token:123456 -> "token_abc..." (Telegram User 123456)
|
||||||
|
memohome:tg:user:123456 -> { username, role }
|
||||||
|
memohome:tg:token:789012 -> "token_def..." (Telegram User 789012)
|
||||||
|
memohome:tg:user:789012 -> { username, role }
|
||||||
|
```
|
||||||
|
|
||||||
|
**工作流程**:
|
||||||
|
|
||||||
|
1. 用户 A (TG ID: 123456) 发送: `/login userA passwordA`
|
||||||
|
2. Bot 验证 -> 获取 token -> 存储到 Redis: `token:123456`
|
||||||
|
3. 用户 B (TG ID: 789012) 发送: `/login userB passwordB`
|
||||||
|
4. Bot 验证 -> 获取 token -> 存储到 Redis: `token:789012`
|
||||||
|
5. 两个用户独立聊天,使用各自的 token
|
||||||
|
|
||||||
|
**鉴权中间件**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
bot.command('chat', requireAuth(storage), async (ctx) => {
|
||||||
|
// 自动检查用户是否已登录
|
||||||
|
// 获取该用户的 token
|
||||||
|
// 执行命令
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### CLI 使用(不变)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CLI 仍然使用文件存储
|
||||||
|
memohome auth login -u admin -p password
|
||||||
|
memohome agent chat "Hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 作为库使用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { login, chat } from '@memohome/cli'
|
||||||
|
|
||||||
|
// 默认使用文件存储
|
||||||
|
await login({ username: 'admin', password: 'password' })
|
||||||
|
const response = await chat({ message: 'Hello' })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Telegram Bot 使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 配置环境变量
|
||||||
|
export BOT_TOKEN="your_bot_token"
|
||||||
|
export REDIS_URL="redis://localhost:6379"
|
||||||
|
export API_BASE_URL="http://localhost:7002"
|
||||||
|
|
||||||
|
# 启动 bot
|
||||||
|
cd packages/platform-telegram
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
**用户操作**:
|
||||||
|
|
||||||
|
```
|
||||||
|
User A: /login adminA passwordA
|
||||||
|
Bot: ✅ Login successful! Username: adminA
|
||||||
|
|
||||||
|
User B: /login userB passwordB
|
||||||
|
Bot: ✅ Login successful! Username: userB
|
||||||
|
|
||||||
|
User A: 今天天气怎么样?
|
||||||
|
Bot: 🤖 今天天气...
|
||||||
|
|
||||||
|
User B: 讲个笑话
|
||||||
|
Bot: 🤖 好的,听好了...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在其他项目中使用 Core
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
loginAsync,
|
||||||
|
chatStreamAsync
|
||||||
|
} from '@memohome/cli/core'
|
||||||
|
import { TelegramRedisStorage } from '@memohome/platform-telegram'
|
||||||
|
|
||||||
|
// 创建自定义存储
|
||||||
|
const storage = new TelegramRedisStorage({
|
||||||
|
redisUrl: 'redis://localhost:6379'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 为用户创建上下文
|
||||||
|
const userContext = createContext({
|
||||||
|
storage,
|
||||||
|
userId: 'user_12345'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
await loginAsync({
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass'
|
||||||
|
}, userContext)
|
||||||
|
|
||||||
|
// 聊天
|
||||||
|
await chatStreamAsync({
|
||||||
|
message: 'Hello'
|
||||||
|
}, async (event) => {
|
||||||
|
if (event.type === 'text-delta') {
|
||||||
|
console.log(event.text)
|
||||||
|
}
|
||||||
|
}, userContext)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键文件清单
|
||||||
|
|
||||||
|
### CLI 包
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
|------|------|
|
||||||
|
| `src/core/storage.ts` | 存储接口定义 |
|
||||||
|
| `src/core/storage/file.ts` | 文件存储实现(CLI 用) |
|
||||||
|
| `src/core/context.ts` | 上下文管理 |
|
||||||
|
| `src/core/client.ts` | API 客户端(支持上下文) |
|
||||||
|
| `src/core/auth.ts` | 认证逻辑(同步/异步版本) |
|
||||||
|
| `src/core/agent.ts` | AI 对话(同步/异步版本) |
|
||||||
|
| `src/core/index.ts` | 统一导出 |
|
||||||
|
| `src/cli/commands/*` | CLI 命令(使用 core) |
|
||||||
|
|
||||||
|
### Telegram 平台包
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
|------|------|
|
||||||
|
| `src/storage.ts` | Redis 存储实现 |
|
||||||
|
| `src/auth.ts` | Telegram 认证处理器 |
|
||||||
|
| `src/index.ts` | Bot 主逻辑 |
|
||||||
|
| `src/bot.ts` | 独立启动入口 |
|
||||||
|
| `README.md` | 使用文档 |
|
||||||
|
| `SETUP.md` | 设置指南 |
|
||||||
|
|
||||||
|
## 迁移指南
|
||||||
|
|
||||||
|
如果你有旧代码需要迁移:
|
||||||
|
|
||||||
|
### 旧代码
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createClient, getToken } from './client'
|
||||||
|
import { loadConfig } from './config'
|
||||||
|
|
||||||
|
const client = createClient()
|
||||||
|
const token = getToken()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 新代码
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 方式 1: 使用默认上下文(CLI 场景)
|
||||||
|
import { createClient, getToken } from '@memohome/cli/core'
|
||||||
|
|
||||||
|
const client = createClient()
|
||||||
|
const token = getToken()
|
||||||
|
|
||||||
|
// 方式 2: 使用自定义上下文(多用户场景)
|
||||||
|
import { createContext, createClientAsync, getTokenAsync } from '@memohome/cli/core'
|
||||||
|
import { TelegramRedisStorage } from '@memohome/platform-telegram'
|
||||||
|
|
||||||
|
const storage = new TelegramRedisStorage()
|
||||||
|
const context = createContext({ storage, userId: 'user123' })
|
||||||
|
|
||||||
|
const client = await createClientAsync(context)
|
||||||
|
const token = await getTokenAsync(context)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
### CLI 测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/cli
|
||||||
|
pnpm start auth login -u admin -p password
|
||||||
|
pnpm start agent chat "Hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Telegram Bot 测试
|
||||||
|
|
||||||
|
1. 启动 Redis: `redis-server`
|
||||||
|
2. 启动 API: `cd packages/api && pnpm start`
|
||||||
|
3. 启动 Bot: `cd packages/platform-telegram && pnpm start`
|
||||||
|
4. 在 Telegram 中测试:
|
||||||
|
- `/start`
|
||||||
|
- `/login admin password`
|
||||||
|
- `/chat 你好`
|
||||||
|
|
||||||
|
## 优势总结
|
||||||
|
|
||||||
|
### 1. 关注点分离
|
||||||
|
- ✅ Core 层: 纯业务逻辑,无 UI 依赖
|
||||||
|
- ✅ CLI 层: 只负责用户交互
|
||||||
|
- ✅ Platform 层: 平台特定实现
|
||||||
|
|
||||||
|
### 2. 可扩展性
|
||||||
|
- ✅ 轻松添加新的存储后端
|
||||||
|
- ✅ 轻松添加新的平台(Web、Desktop、Discord 等)
|
||||||
|
- ✅ 轻松添加新功能到 core
|
||||||
|
|
||||||
|
### 3. 可测试性
|
||||||
|
- ✅ Core 层可独立测试,无需模拟 UI
|
||||||
|
- ✅ Storage 可以 mock
|
||||||
|
- ✅ Context 可以独立创建
|
||||||
|
|
||||||
|
### 4. 多用户支持
|
||||||
|
- ✅ Telegram bot 支持多个用户同时使用
|
||||||
|
- ✅ 每个用户独立的 session
|
||||||
|
- ✅ Token 自动管理和过期
|
||||||
|
|
||||||
|
### 5. 类型安全
|
||||||
|
- ✅ 完整的 TypeScript 类型定义
|
||||||
|
- ✅ 导出所有类型供外部使用
|
||||||
|
|
||||||
|
## 未来扩展
|
||||||
|
|
||||||
|
基于这个架构,可以轻松添加:
|
||||||
|
|
||||||
|
1. **Discord Bot**
|
||||||
|
```typescript
|
||||||
|
// packages/platform-discord
|
||||||
|
import { createContext, loginAsync } from '@memohome/cli/core'
|
||||||
|
import { DiscordRedisStorage } from './storage'
|
||||||
|
|
||||||
|
const storage = new DiscordRedisStorage()
|
||||||
|
// 类似 Telegram 实现
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Web 应用**
|
||||||
|
```typescript
|
||||||
|
// packages/web
|
||||||
|
import { createContext, loginAsync, chatStreamAsync } from '@memohome/cli/core'
|
||||||
|
import { BrowserStorage } from './storage'
|
||||||
|
|
||||||
|
const storage = new BrowserStorage() // localStorage
|
||||||
|
// 在 React/Vue 中使用
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **移动应用**
|
||||||
|
```typescript
|
||||||
|
// packages/mobile
|
||||||
|
import { createContext } from '@memohome/cli/core'
|
||||||
|
import { SecureStorage } from './storage'
|
||||||
|
|
||||||
|
const storage = new SecureStorage() // React Native AsyncStorage
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
本次重构实现了:
|
||||||
|
|
||||||
|
1. ✅ **完全分离** CLI 和 Core 层
|
||||||
|
2. ✅ **抽象存储** 支持多种后端
|
||||||
|
3. ✅ **多用户支持** Telegram bot 可服务多个用户
|
||||||
|
4. ✅ **同步/异步** API 适配不同场景
|
||||||
|
5. ✅ **可扩展** 易于添加新平台和功能
|
||||||
|
6. ✅ **文档完善** README、SETUP、示例齐全
|
||||||
|
|
||||||
|
现在你可以:
|
||||||
|
- 继续使用 CLI(体验不变)
|
||||||
|
- 启动 Telegram bot 服务多个用户
|
||||||
|
- 在其他项目中复用 Core 功能
|
||||||
|
- 轻松添加新的平台支持
|
||||||
|
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
# ✅ Telegram Bot 完成总结
|
||||||
|
|
||||||
|
## 🎉 完成的功能
|
||||||
|
|
||||||
|
### 1. Core 层重构 ✅
|
||||||
|
|
||||||
|
**位置**: `packages/cli/src/core/`
|
||||||
|
|
||||||
|
- ✅ **存储抽象层** (`storage.ts`): 定义 `TokenStorage` 接口
|
||||||
|
- ✅ **文件存储** (`storage/file.ts`): CLI 使用的文件存储
|
||||||
|
- ✅ **上下文管理** (`context.ts`): 支持多用户场景
|
||||||
|
- ✅ **客户端** (`client.ts`): 同步/异步 API 支持
|
||||||
|
- ✅ **认证** (`auth.ts`): 同步/异步登录、登出
|
||||||
|
- ✅ **AI 对话** (`agent.ts`): 同步/异步流式对话
|
||||||
|
|
||||||
|
### 2. Telegram Bot 实现 ✅
|
||||||
|
|
||||||
|
**位置**: `packages/platform-telegram/`
|
||||||
|
|
||||||
|
- ✅ **Redis 存储** (`src/storage.ts`): 多用户 token 管理
|
||||||
|
- ✅ **认证处理** (`src/auth.ts`): 登录、登出、鉴权中间件
|
||||||
|
- ✅ **Bot 实现** (`src/index.ts`): 完整的 Telegram bot
|
||||||
|
- ✅ **独立启动** (`src/bot.ts`): 可独立运行
|
||||||
|
|
||||||
|
### 3. 多用户支持 ✅
|
||||||
|
|
||||||
|
```
|
||||||
|
Telegram User A (ID: 123456) → Token A → MemoHome User A
|
||||||
|
Telegram User B (ID: 789012) → Token B → MemoHome User B
|
||||||
|
```
|
||||||
|
|
||||||
|
- ✅ 每个 TG 用户独立登录
|
||||||
|
- ✅ 独立的 session 管理
|
||||||
|
- ✅ Token 自动过期(30天)
|
||||||
|
- ✅ 用户信息缓存
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/
|
||||||
|
├── cli/ # CLI 和 Core 层
|
||||||
|
│ └── src/
|
||||||
|
│ ├── core/ # 核心功能(可复用)
|
||||||
|
│ │ ├── storage.ts # 存储接口
|
||||||
|
│ │ ├── storage/
|
||||||
|
│ │ │ ├── file.ts # 文件存储
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ ├── context.ts # 上下文管理
|
||||||
|
│ │ ├── client.ts # API 客户端
|
||||||
|
│ │ ├── auth.ts # 认证逻辑
|
||||||
|
│ │ ├── agent.ts # AI 对话
|
||||||
|
│ │ └── index.ts # 统一导出
|
||||||
|
│ └── cli/ # CLI UI 层
|
||||||
|
│ └── commands/
|
||||||
|
│
|
||||||
|
└── platform-telegram/ # Telegram Bot
|
||||||
|
├── src/
|
||||||
|
│ ├── storage.ts # Redis 存储
|
||||||
|
│ ├── auth.ts # TG 认证处理
|
||||||
|
│ ├── index.ts # Bot 主逻辑
|
||||||
|
│ └── bot.ts # 启动入口
|
||||||
|
├── .env.example # 环境变量示例
|
||||||
|
├── README.md # 完整文档
|
||||||
|
├── SETUP.md # 设置指南
|
||||||
|
└── QUICKSTART.md # 快速开始
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 配置环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/platform-telegram
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
编辑 `.env`:
|
||||||
|
```env
|
||||||
|
BOT_TOKEN=你的_bot_token
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
API_BASE_URL=http://localhost:7002
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1: Redis
|
||||||
|
redis-server
|
||||||
|
|
||||||
|
# Terminal 2: MemoHome API
|
||||||
|
cd packages/api
|
||||||
|
pnpm start
|
||||||
|
|
||||||
|
# Terminal 3: Telegram Bot
|
||||||
|
cd packages/platform-telegram
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 测试 Bot
|
||||||
|
|
||||||
|
在 Telegram 中:
|
||||||
|
|
||||||
|
```
|
||||||
|
/start
|
||||||
|
/login admin password
|
||||||
|
/chat 你好
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 核心特性
|
||||||
|
|
||||||
|
### 存储抽象
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 接口定义
|
||||||
|
interface TokenStorage {
|
||||||
|
getToken(userId?: string): Promise<string | null> | string | null
|
||||||
|
setToken(token: string, userId?: string): Promise<void> | void
|
||||||
|
clearToken(userId?: string): Promise<void> | void
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI 使用文件存储
|
||||||
|
const fileStorage = new FileTokenStorage()
|
||||||
|
|
||||||
|
// Telegram 使用 Redis 存储
|
||||||
|
const redisStorage = new TelegramRedisStorage()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 多用户上下文
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 为不同用户创建独立上下文
|
||||||
|
const userAContext = createContext({
|
||||||
|
storage: redisStorage,
|
||||||
|
userId: 'telegram_123456' // User A
|
||||||
|
})
|
||||||
|
|
||||||
|
const userBContext = createContext({
|
||||||
|
storage: redisStorage,
|
||||||
|
userId: 'telegram_789012' // User B
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用各自的上下文
|
||||||
|
await loginAsync({ username: 'userA', password: 'passA' }, userAContext)
|
||||||
|
await loginAsync({ username: 'userB', password: 'passB' }, userBContext)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 鉴权中间件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 自动检查用户是否登录
|
||||||
|
bot.command('chat', requireAuth(storage), async (ctx) => {
|
||||||
|
// 只有登录用户才能执行
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 流式对话
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await chatStreamAsync({
|
||||||
|
message: '讲个故事',
|
||||||
|
language: 'Chinese'
|
||||||
|
}, async (event) => {
|
||||||
|
if (event.type === 'text-delta') {
|
||||||
|
// 实时更新 Telegram 消息
|
||||||
|
}
|
||||||
|
}, userContext)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Redis 存储结构
|
||||||
|
|
||||||
|
```
|
||||||
|
memohome:tg:token:123456 → "token_abc..." (30天过期)
|
||||||
|
memohome:tg:user:123456 → {
|
||||||
|
"username": "userA",
|
||||||
|
"role": "admin",
|
||||||
|
"userId": "user-id-xxx"
|
||||||
|
}
|
||||||
|
|
||||||
|
memohome:tg:token:789012 → "token_def..."
|
||||||
|
memohome:tg:user:789012 → {
|
||||||
|
"username": "userB",
|
||||||
|
"role": "user",
|
||||||
|
"userId": "user-id-yyy"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Bot 命令
|
||||||
|
|
||||||
|
| 命令 | 描述 | 需要登录 |
|
||||||
|
|------|------|---------|
|
||||||
|
| `/start` | 欢迎消息 | ❌ |
|
||||||
|
| `/help` | 帮助信息 | ❌ |
|
||||||
|
| `/login <username> <password>` | 登录 | ❌ |
|
||||||
|
| `/logout` | 登出 | ✅ |
|
||||||
|
| `/whoami` | 查看当前用户 | ✅ |
|
||||||
|
| `/chat <message>` | AI 对话 | ✅ |
|
||||||
|
| 直接发送消息 | AI 对话 | ✅ |
|
||||||
|
|
||||||
|
## 🎨 使用示例
|
||||||
|
|
||||||
|
### CLI 使用(不受影响)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
memohome auth login -u admin -p password
|
||||||
|
memohome agent chat "Hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Telegram Bot 使用
|
||||||
|
|
||||||
|
```
|
||||||
|
User A: /login adminA passwordA
|
||||||
|
Bot: ✅ Login successful! Username: adminA
|
||||||
|
|
||||||
|
User A: 今天天气怎么样?
|
||||||
|
Bot: 🤖 今天天气...
|
||||||
|
|
||||||
|
User B: /login userB passwordB
|
||||||
|
Bot: ✅ Login successful! Username: userB
|
||||||
|
|
||||||
|
User B: 讲个笑话
|
||||||
|
Bot: 🤖 好的,听好了...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 编程使用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createContext, loginAsync, chatStreamAsync } from '@memohome/cli'
|
||||||
|
import { TelegramRedisStorage } from '@memohome/platform-telegram'
|
||||||
|
|
||||||
|
const storage = new TelegramRedisStorage()
|
||||||
|
const context = createContext({
|
||||||
|
storage,
|
||||||
|
userId: 'telegram_123456'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
await loginAsync({
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass'
|
||||||
|
}, context)
|
||||||
|
|
||||||
|
// 对话
|
||||||
|
await chatStreamAsync({
|
||||||
|
message: 'Hello'
|
||||||
|
}, async (event) => {
|
||||||
|
console.log(event)
|
||||||
|
}, context)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 文档
|
||||||
|
|
||||||
|
- 📖 [README.md](packages/platform-telegram/README.md) - 完整功能文档
|
||||||
|
- 🚀 [QUICKSTART.md](packages/platform-telegram/QUICKSTART.md) - 5分钟快速开始
|
||||||
|
- 🛠 [SETUP.md](packages/platform-telegram/SETUP.md) - 详细设置指南
|
||||||
|
- 🏗 [REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md) - 重构总结
|
||||||
|
- 📐 [ARCHITECTURE.md](packages/cli/ARCHITECTURE.md) - CLI 架构说明
|
||||||
|
|
||||||
|
## ✨ 亮点
|
||||||
|
|
||||||
|
1. **完全分离**: CLI UI 和业务逻辑完全解耦
|
||||||
|
2. **可复用**: Core 层可被任何平台使用
|
||||||
|
3. **多存储**: 支持文件、Redis 等多种存储
|
||||||
|
4. **多用户**: Telegram bot 支持多用户独立 session
|
||||||
|
5. **类型安全**: 完整的 TypeScript 类型
|
||||||
|
6. **文档完善**: README、SETUP、示例齐全
|
||||||
|
7. **易扩展**: 轻松添加新平台(Discord、Web 等)
|
||||||
|
|
||||||
|
## 🔮 未来扩展
|
||||||
|
|
||||||
|
基于这个架构,可以轻松添加:
|
||||||
|
|
||||||
|
- 🎮 Discord Bot
|
||||||
|
- 🌐 Web 应用
|
||||||
|
- 📱 移动应用
|
||||||
|
- 💬 微信 Bot
|
||||||
|
- 🤖 其他平台...
|
||||||
|
|
||||||
|
只需实现对应的 `TokenStorage` 和平台逻辑即可!
|
||||||
|
|
||||||
|
## ✅ 测试清单
|
||||||
|
|
||||||
|
- [x] CLI 登录/登出功能正常
|
||||||
|
- [x] CLI 对话功能正常
|
||||||
|
- [x] Telegram bot 启动成功
|
||||||
|
- [x] Telegram 多用户登录
|
||||||
|
- [x] Telegram 独立 session
|
||||||
|
- [x] Telegram 流式对话
|
||||||
|
- [x] Redis 存储正常
|
||||||
|
- [x] Token 过期管理
|
||||||
|
- [x] 鉴权中间件工作
|
||||||
|
- [x] 错误处理完善
|
||||||
|
- [x] 类型检查通过
|
||||||
|
- [x] 文档完整
|
||||||
|
|
||||||
|
## 🎊 总结
|
||||||
|
|
||||||
|
重构完成!现在你拥有:
|
||||||
|
|
||||||
|
1. ✅ **清晰的架构**: Core 层可复用,CLI 和 Platform 独立
|
||||||
|
2. ✅ **多用户支持**: Telegram bot 支持多个用户同时使用
|
||||||
|
3. ✅ **灵活的存储**: 文件存储(CLI)和 Redis 存储(Telegram)
|
||||||
|
4. ✅ **完善的文档**: 从快速开始到详细设置一应俱全
|
||||||
|
5. ✅ **易于扩展**: 添加新平台只需实现存储接口
|
||||||
|
|
||||||
|
现在可以:
|
||||||
|
- 继续使用 CLI(体验不变)
|
||||||
|
- 启动 Telegram bot 服务多个用户
|
||||||
|
- 在其他项目中复用 Core 功能
|
||||||
|
- 轻松添加新的平台支持
|
||||||
|
|
||||||
|
祝使用愉快!🚀
|
||||||
|
|
||||||
Binary file not shown.
@@ -4,7 +4,12 @@ import { treaty } from '@elysiajs/eden'
|
|||||||
export type ApiClient = typeof app
|
export type ApiClient = typeof app
|
||||||
|
|
||||||
export const createClient = (
|
export const createClient = (
|
||||||
baseUrl: string = process.env.API_BASE_URL ?? 'http://localhost:7002'
|
baseUrl: string = process.env.API_BASE_URL ?? 'http://localhost:7002',
|
||||||
|
token?: string,
|
||||||
) => {
|
) => {
|
||||||
return treaty<ApiClient>(baseUrl)
|
return treaty<ApiClient>(baseUrl, {
|
||||||
|
headers: token ? {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
} : undefined,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.env
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# Telegram Bot 快速开始
|
||||||
|
|
||||||
|
## 5 分钟启动指南
|
||||||
|
|
||||||
|
### 1. 获取 Bot Token
|
||||||
|
|
||||||
|
1. 在 Telegram 搜索 `@BotFather`
|
||||||
|
2. 发送 `/newbot`
|
||||||
|
3. 按提示输入 bot 名称和用户名
|
||||||
|
4. 复制获得的 token(格式类似:`123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)
|
||||||
|
|
||||||
|
### 2. 准备环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
cd packages/platform-telegram
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# 创建配置文件
|
||||||
|
cat > .env << EOF
|
||||||
|
BOT_TOKEN=你的_bot_token
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
API_BASE_URL=http://localhost:7002
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动服务
|
||||||
|
|
||||||
|
**Terminal 1 - 启动 Redis:**
|
||||||
|
```bash
|
||||||
|
redis-server
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 2 - 启动 MemoHome API:**
|
||||||
|
```bash
|
||||||
|
cd packages/api
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 3 - 启动 Telegram Bot:**
|
||||||
|
```bash
|
||||||
|
cd packages/platform-telegram
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 测试 Bot
|
||||||
|
|
||||||
|
在 Telegram 中找到你的 bot,然后:
|
||||||
|
|
||||||
|
```
|
||||||
|
你: /start
|
||||||
|
|
||||||
|
Bot: 👋 Welcome to MemoHome Bot!
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
/login <username> <password> - Login to your account
|
||||||
|
/logout - Logout from your account
|
||||||
|
/whoami - Show current user info
|
||||||
|
/chat <message> - Chat with AI agent
|
||||||
|
/help - Show this help message
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
你: /login admin password
|
||||||
|
|
||||||
|
Bot: ✅ Login successful!
|
||||||
|
|
||||||
|
👤 Username: admin
|
||||||
|
🎭 Role: admin
|
||||||
|
🔑 User ID: xxx
|
||||||
|
|
||||||
|
You can now use the bot to interact with MemoHome.
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
你: /chat 你好,介绍一下你自己
|
||||||
|
|
||||||
|
Bot: 🤖 你好!我是 MemoHome AI 助手...
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
你: 今天天气怎么样?
|
||||||
|
|
||||||
|
Bot: 🤖 很抱歉,我没有实时天气信息...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多用户测试
|
||||||
|
|
||||||
|
用两个不同的 Telegram 账号测试:
|
||||||
|
|
||||||
|
**账号 A:**
|
||||||
|
```
|
||||||
|
A: /login userA passwordA
|
||||||
|
Bot: ✅ Login successful! Username: userA
|
||||||
|
|
||||||
|
A: 我是用户A
|
||||||
|
Bot: 🤖 你好 userA...
|
||||||
|
```
|
||||||
|
|
||||||
|
**账号 B:**
|
||||||
|
```
|
||||||
|
B: /login userB passwordB
|
||||||
|
Bot: ✅ Login successful! Username: userB
|
||||||
|
|
||||||
|
B: 我是用户B
|
||||||
|
Bot: 🤖 你好 userB...
|
||||||
|
```
|
||||||
|
|
||||||
|
两个用户的对话是完全独立的!
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: Bot 没有响应?
|
||||||
|
|
||||||
|
**A:** 检查以下几点:
|
||||||
|
1. Bot token 是否正确
|
||||||
|
2. Bot 是否在运行:`pnpm start`
|
||||||
|
3. 查看控制台是否有错误
|
||||||
|
4. 在 BotFather 中确认 bot 已创建
|
||||||
|
|
||||||
|
### Q: 登录失败?
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
1. 确认 API 是否在运行:`curl http://localhost:7002`
|
||||||
|
2. 检查用户名密码是否正确
|
||||||
|
3. 查看 API 日志
|
||||||
|
|
||||||
|
### Q: Redis 连接错误?
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
1. 确认 Redis 在运行:`redis-cli ping` 应返回 `PONG`
|
||||||
|
2. 检查 `.env` 中的 `REDIS_URL`
|
||||||
|
3. 如果用 Docker:`docker run -d -p 6379:6379 redis`
|
||||||
|
|
||||||
|
### Q: 如何查看存储的数据?
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
```bash
|
||||||
|
# 连接 Redis
|
||||||
|
redis-cli
|
||||||
|
|
||||||
|
# 查看所有 token
|
||||||
|
KEYS memohome:tg:token:*
|
||||||
|
|
||||||
|
# 查看特定用户的 token
|
||||||
|
GET memohome:tg:token:123456789
|
||||||
|
|
||||||
|
# 查看用户信息
|
||||||
|
GET memohome:tg:user:123456789
|
||||||
|
|
||||||
|
# 查看过期时间
|
||||||
|
TTL memohome:tg:token:123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- 📖 阅读 [README.md](./README.md) 了解更多功能
|
||||||
|
- 🛠 查看 [SETUP.md](./SETUP.md) 了解详细配置
|
||||||
|
- 🎯 参考 [example.ts](./example.ts) 学习编程使用
|
||||||
|
- 📚 阅读项目根目录的 [REFACTORING_SUMMARY.md](../../REFACTORING_SUMMARY.md) 了解整体架构
|
||||||
|
|
||||||
|
## 添加自定义命令
|
||||||
|
|
||||||
|
编辑 `src/index.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 添加一个新命令
|
||||||
|
this.bot.command('hello', requireAuth(storage), async (ctx) => {
|
||||||
|
await ctx.reply('Hello! 你好!')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
重启 bot,然后在 Telegram 中:
|
||||||
|
|
||||||
|
```
|
||||||
|
你: /hello
|
||||||
|
|
||||||
|
Bot: Hello! 你好!
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生产部署提示
|
||||||
|
|
||||||
|
1. **使用环境变量** 而不是 `.env` 文件
|
||||||
|
2. **使用 HTTPS** 作为 API endpoint
|
||||||
|
3. **使用 PM2** 或 Docker 保持进程运行
|
||||||
|
4. **添加日志** 监控和调试
|
||||||
|
5. **设置 Webhook** 而不是 polling(更高效)
|
||||||
|
|
||||||
|
示例 PM2 配置:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"apps": [{
|
||||||
|
"name": "memohome-tg-bot",
|
||||||
|
"script": "bun",
|
||||||
|
"args": "run src/bot.ts",
|
||||||
|
"env": {
|
||||||
|
"BOT_TOKEN": "your_token",
|
||||||
|
"REDIS_URL": "redis://localhost:6379",
|
||||||
|
"API_BASE_URL": "https://api.yourdomain.com"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
启动:
|
||||||
|
```bash
|
||||||
|
pm2 start ecosystem.json
|
||||||
|
pm2 logs memohome-tg-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
祝你使用愉快!🎉
|
||||||
|
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# MemoHome Telegram Platform
|
||||||
|
|
||||||
|
Telegram bot platform for MemoHome, supporting multi-user authentication with Redis storage.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🔐 **Multi-user Authentication**: Each Telegram user can login to their own MemoHome account
|
||||||
|
- 💾 **Redis Storage**: Token and user info stored in Redis
|
||||||
|
- 💬 **AI Chat**: Stream responses from MemoHome AI agent
|
||||||
|
- 🔄 **Real-time Updates**: Live message editing during streaming responses
|
||||||
|
- 🛡️ **Auth Middleware**: Protected commands require login
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/platform-telegram
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
1. Create a `.env` file from the example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Configure your environment variables:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Get your bot token from @BotFather on Telegram
|
||||||
|
BOT_TOKEN=your_telegram_bot_token_here
|
||||||
|
|
||||||
|
# Redis connection string
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
|
||||||
|
# MemoHome API URL
|
||||||
|
API_BASE_URL=http://localhost:7002
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Standalone Bot
|
||||||
|
|
||||||
|
Run the bot as a standalone process:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in development mode with auto-reload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### As a Platform Module
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TelegramPlatform } from '@memohome/platform-telegram'
|
||||||
|
|
||||||
|
const platform = new TelegramPlatform()
|
||||||
|
|
||||||
|
await platform.start({
|
||||||
|
botToken: process.env.BOT_TOKEN,
|
||||||
|
redisUrl: process.env.REDIS_URL,
|
||||||
|
apiUrl: process.env.API_BASE_URL,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bot Commands
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
- `/start` - Welcome message and command list
|
||||||
|
- `/login <username> <password>` - Login to your MemoHome account
|
||||||
|
- `/logout` - Logout from your account
|
||||||
|
- `/whoami` - Show current user information
|
||||||
|
|
||||||
|
### Chat
|
||||||
|
|
||||||
|
- `/chat <message>` - Send a message to the AI agent
|
||||||
|
- Or just send a message directly (requires login)
|
||||||
|
|
||||||
|
### Help
|
||||||
|
|
||||||
|
- `/help` - Show all available commands
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Storage Structure
|
||||||
|
|
||||||
|
Redis keys follow this pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
memohome:tg:token:{telegram_user_id} -> token (30 days TTL)
|
||||||
|
memohome:tg:user:{telegram_user_id} -> { username, role, userId } (30 days TTL)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-user Support
|
||||||
|
|
||||||
|
Each Telegram user ID is mapped to their own MemoHome account token:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// User 123456 logs in
|
||||||
|
telegram_user_id: "123456" -> token: "abc..."
|
||||||
|
telegram_user_id: "123456" -> userInfo: { username: "user1", ... }
|
||||||
|
|
||||||
|
// User 789012 logs in
|
||||||
|
telegram_user_id: "789012" -> token: "def..."
|
||||||
|
telegram_user_id: "789012" -> userInfo: { username: "user2", ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Flow
|
||||||
|
|
||||||
|
1. User sends `/login username password`
|
||||||
|
2. Bot validates credentials with MemoHome API
|
||||||
|
3. Token is stored in Redis with Telegram user ID as key
|
||||||
|
4. User info is cached in Redis
|
||||||
|
5. Subsequent messages use the stored token
|
||||||
|
|
||||||
|
### Middleware
|
||||||
|
|
||||||
|
The `requireAuth` middleware checks if the user is logged in before allowing access to protected commands:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
bot.command('chat', requireAuth(storage), async (ctx) => {
|
||||||
|
// User is guaranteed to be logged in
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Custom Commands
|
||||||
|
|
||||||
|
Add new commands to `src/index.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
this.bot.command('mycommand', requireAuth(storage), async (ctx) => {
|
||||||
|
const memoContext = getMemoContext(ctx, storage)
|
||||||
|
// Your command logic here
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Core Functions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { chatStreamAsync, listModels } from '@memohome/cli/core'
|
||||||
|
import { getMemoContext } from './auth'
|
||||||
|
|
||||||
|
// In a command handler
|
||||||
|
const memoContext = getMemoContext(ctx, storage)
|
||||||
|
|
||||||
|
// Chat with AI
|
||||||
|
await chatStreamAsync({
|
||||||
|
message: 'Hello',
|
||||||
|
language: 'Chinese'
|
||||||
|
}, async (event) => {
|
||||||
|
if (event.type === 'text-delta') {
|
||||||
|
// Handle streaming text
|
||||||
|
}
|
||||||
|
}, memoContext)
|
||||||
|
|
||||||
|
// Or use other core functions
|
||||||
|
const models = await listModels()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Login
|
||||||
|
|
||||||
|
```
|
||||||
|
/login admin password
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Chat
|
||||||
|
|
||||||
|
```
|
||||||
|
/chat 你好,今天天气怎么样?
|
||||||
|
```
|
||||||
|
|
||||||
|
Or send a message directly:
|
||||||
|
|
||||||
|
```
|
||||||
|
介绍一下 TypeScript
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Bot not responding
|
||||||
|
|
||||||
|
1. Check if the bot token is correct
|
||||||
|
2. Ensure MemoHome API is running
|
||||||
|
3. Check Redis connection
|
||||||
|
|
||||||
|
### Login failed
|
||||||
|
|
||||||
|
1. Verify the API URL is correct
|
||||||
|
2. Check if the username/password is valid
|
||||||
|
3. Ensure the API server is accessible
|
||||||
|
|
||||||
|
### Redis errors
|
||||||
|
|
||||||
|
1. Make sure Redis is running: `redis-server`
|
||||||
|
2. Check the Redis URL in `.env`
|
||||||
|
3. Test connection: `redis-cli ping`
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- Never commit your `.env` file or bot token
|
||||||
|
- Tokens are stored with 30-day expiration
|
||||||
|
- Use HTTPS for production API endpoints
|
||||||
|
- Consider rate limiting for production use
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM oven/bun:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json ./
|
||||||
|
RUN bun install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["bun", "run", "src/bot.ts"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Set these in your deployment environment:
|
||||||
|
|
||||||
|
- `BOT_TOKEN` - Your Telegram bot token
|
||||||
|
- `REDIS_URL` - Redis connection string
|
||||||
|
- `API_BASE_URL` - MemoHome API URL
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
ISC
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
# Telegram Bot Setup Guide
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Telegram Bot Token**
|
||||||
|
- Open Telegram and search for `@BotFather`
|
||||||
|
- Send `/newbot` and follow instructions
|
||||||
|
- Copy the bot token provided
|
||||||
|
|
||||||
|
2. **Redis Server**
|
||||||
|
```bash
|
||||||
|
# Install Redis (macOS)
|
||||||
|
brew install redis
|
||||||
|
|
||||||
|
# Start Redis
|
||||||
|
redis-server
|
||||||
|
|
||||||
|
# Or with Docker
|
||||||
|
docker run -d -p 6379:6379 redis:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **MemoHome API**
|
||||||
|
- Ensure the API server is running on `http://localhost:7002`
|
||||||
|
- Or update `API_BASE_URL` to your API endpoint
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Create Environment File
|
||||||
|
|
||||||
|
Create `.env` file in `packages/platform-telegram/`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
API_BASE_URL=http://localhost:7002
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/platform-telegram
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start the Bot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
```
|
||||||
|
🚀 Starting Telegram bot...
|
||||||
|
📡 API URL: http://localhost:7002
|
||||||
|
💾 Redis URL: redis://localhost:6379
|
||||||
|
✅ Telegram bot started successfully
|
||||||
|
✅ Bot is running...
|
||||||
|
Press Ctrl+C to stop
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test the Bot
|
||||||
|
|
||||||
|
Open Telegram and find your bot, then:
|
||||||
|
|
||||||
|
1. **Start conversation**
|
||||||
|
```
|
||||||
|
/start
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Login to MemoHome**
|
||||||
|
```
|
||||||
|
/login admin password
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Check your user**
|
||||||
|
```
|
||||||
|
/whoami
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Chat with AI**
|
||||||
|
```
|
||||||
|
/chat 你好,介绍一下你自己
|
||||||
|
```
|
||||||
|
|
||||||
|
Or just send a message directly:
|
||||||
|
```
|
||||||
|
今天天气怎么样?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Storage Model
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ Telegram User 1 │──► telegram_id: 123456
|
||||||
|
│ @user1 │ ├─ token: "abc..."
|
||||||
|
└─────────────────────┘ └─ userInfo: { username: "user1", ... }
|
||||||
|
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ Telegram User 2 │──► telegram_id: 789012
|
||||||
|
│ @user2 │ ├─ token: "def..."
|
||||||
|
└─────────────────────┘ └─ userInfo: { username: "user2", ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User: /login admin password
|
||||||
|
│
|
||||||
|
├──► Bot validates with MemoHome API
|
||||||
|
│
|
||||||
|
├──► API returns token + user info
|
||||||
|
│
|
||||||
|
└──► Redis stores:
|
||||||
|
├─ memohome:tg:token:123456 = "token_abc..."
|
||||||
|
└─ memohome:tg:user:123456 = { username, role, userId }
|
||||||
|
|
||||||
|
2. User: /chat Hello
|
||||||
|
│
|
||||||
|
├──► Middleware checks: is logged in?
|
||||||
|
│
|
||||||
|
├──► Get token from Redis by telegram_id
|
||||||
|
│
|
||||||
|
├──► Call MemoHome API with token
|
||||||
|
│
|
||||||
|
└──► Stream response back to user
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Multiple Users
|
||||||
|
|
||||||
|
Each Telegram user can have their own account:
|
||||||
|
|
||||||
|
```
|
||||||
|
# User A logs in
|
||||||
|
@userA: /login adminA passwordA
|
||||||
|
Bot: ✅ Login successful! Username: adminA
|
||||||
|
|
||||||
|
# User B logs in
|
||||||
|
@userB: /login userB passwordB
|
||||||
|
Bot: ✅ Login successful! Username: userB
|
||||||
|
|
||||||
|
# Both can chat independently
|
||||||
|
@userA: /chat What's the weather?
|
||||||
|
@userB: /chat Tell me a joke
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session Management
|
||||||
|
|
||||||
|
Tokens expire after 30 days. To re-login:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Logout
|
||||||
|
/logout
|
||||||
|
|
||||||
|
# Login again
|
||||||
|
/login username password
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Authentication
|
||||||
|
|
||||||
|
```
|
||||||
|
/whoami
|
||||||
|
|
||||||
|
Response:
|
||||||
|
👤 Current User:
|
||||||
|
|
||||||
|
Username: admin
|
||||||
|
Role: admin
|
||||||
|
User ID: user-id-xxx
|
||||||
|
Telegram ID: 123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/platform-telegram/
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # Main platform class
|
||||||
|
│ ├── bot.ts # Standalone entry point
|
||||||
|
│ ├── auth.ts # Auth handlers & middleware
|
||||||
|
│ └── storage.ts # Redis storage implementation
|
||||||
|
├── package.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Custom Commands
|
||||||
|
|
||||||
|
Edit `src/index.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add a new command
|
||||||
|
this.bot.command('mycommand', requireAuth(storage), async (ctx) => {
|
||||||
|
const memoContext = getMemoContext(ctx, storage)
|
||||||
|
|
||||||
|
// Your logic here
|
||||||
|
await ctx.reply('Command executed!')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Core Functions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
chatStreamAsync,
|
||||||
|
listModels,
|
||||||
|
searchMemory
|
||||||
|
} from '@memohome/cli/core'
|
||||||
|
|
||||||
|
// In command handler
|
||||||
|
const memoContext = getMemoContext(ctx, storage)
|
||||||
|
|
||||||
|
// List models
|
||||||
|
const models = await listModels()
|
||||||
|
|
||||||
|
// Search memory
|
||||||
|
const memories = await searchMemory({
|
||||||
|
query: 'TypeScript',
|
||||||
|
limit: 5
|
||||||
|
})
|
||||||
|
|
||||||
|
// Chat with streaming
|
||||||
|
await chatStreamAsync({
|
||||||
|
message: 'Hello'
|
||||||
|
}, async (event) => {
|
||||||
|
if (event.type === 'text-delta') {
|
||||||
|
console.log(event.text)
|
||||||
|
}
|
||||||
|
}, memoContext)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
### Enable Verbose Logging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DEBUG=telegraf:* pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Redis Data
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect to Redis CLI
|
||||||
|
redis-cli
|
||||||
|
|
||||||
|
# List all keys
|
||||||
|
KEYS memohome:tg:*
|
||||||
|
|
||||||
|
# Get a token
|
||||||
|
GET memohome:tg:token:123456
|
||||||
|
|
||||||
|
# Get user info
|
||||||
|
GET memohome:tg:user:123456
|
||||||
|
|
||||||
|
# Check TTL (time to live)
|
||||||
|
TTL memohome:tg:token:123456
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test API Connection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test if API is accessible
|
||||||
|
curl http://localhost:7002/
|
||||||
|
|
||||||
|
# Test login endpoint
|
||||||
|
curl -X POST http://localhost:7002/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"password"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
### Using Docker Compose
|
||||||
|
|
||||||
|
Create `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
|
||||||
|
telegram-bot:
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
- BOT_TOKEN=${BOT_TOKEN}
|
||||||
|
- REDIS_URL=redis://redis:6379
|
||||||
|
- API_BASE_URL=${API_BASE_URL}
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
||||||
|
```
|
||||||
|
|
||||||
|
Start with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables for Production
|
||||||
|
|
||||||
|
```env
|
||||||
|
BOT_TOKEN=your_production_bot_token
|
||||||
|
REDIS_URL=redis://your-redis-host:6379
|
||||||
|
API_BASE_URL=https://api.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Considerations
|
||||||
|
|
||||||
|
1. **Never commit** `.env` files or bot tokens
|
||||||
|
2. **Use HTTPS** for production API endpoints
|
||||||
|
3. **Rate limiting**: Consider adding rate limits to prevent abuse
|
||||||
|
4. **Token rotation**: Implement token refresh mechanism
|
||||||
|
5. **Monitoring**: Add logging and error tracking (e.g., Sentry)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Bot not responding
|
||||||
|
|
||||||
|
**Issue**: Bot doesn't respond to commands
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Check bot is running: `pnpm start`
|
||||||
|
2. Verify bot token is correct
|
||||||
|
3. Check if bot is blocked by user
|
||||||
|
4. Review logs for errors
|
||||||
|
|
||||||
|
### Authentication failures
|
||||||
|
|
||||||
|
**Issue**: `/login` fails
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Verify API URL is accessible
|
||||||
|
2. Check username/password are correct
|
||||||
|
3. Ensure MemoHome API is running
|
||||||
|
4. Test API endpoint directly with curl
|
||||||
|
|
||||||
|
### Redis connection errors
|
||||||
|
|
||||||
|
**Issue**: Cannot connect to Redis
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Check Redis is running: `redis-cli ping` should return `PONG`
|
||||||
|
2. Verify REDIS_URL in `.env`
|
||||||
|
3. Check firewall settings
|
||||||
|
4. For Docker: ensure containers are on same network
|
||||||
|
|
||||||
|
### Token expiration
|
||||||
|
|
||||||
|
**Issue**: Commands fail after some time
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Tokens expire after 30 days
|
||||||
|
2. User needs to `/logout` and `/login` again
|
||||||
|
3. Check token TTL in Redis: `TTL memohome:tg:token:123456`
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Custom Storage Backend
|
||||||
|
|
||||||
|
You can implement your own storage:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { TokenStorage } from '@memohome/cli/core'
|
||||||
|
|
||||||
|
class MyCustomStorage implements TokenStorage {
|
||||||
|
async getApiUrl(): Promise<string> { /* ... */ }
|
||||||
|
async setApiUrl(url: string): Promise<void> { /* ... */ }
|
||||||
|
async getToken(userId?: string): Promise<string | null> { /* ... */ }
|
||||||
|
async setToken(token: string, userId?: string): Promise<void> { /* ... */ }
|
||||||
|
async clearToken(userId?: string): Promise<void> { /* ... */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use it
|
||||||
|
const storage = new MyCustomStorage()
|
||||||
|
const platform = new TelegramPlatform()
|
||||||
|
// ... modify platform to use custom storage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Webhook Mode (instead of polling)
|
||||||
|
|
||||||
|
For production, use webhooks instead of long polling:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Telegraf } from 'telegraf'
|
||||||
|
|
||||||
|
const bot = new Telegraf(botToken)
|
||||||
|
|
||||||
|
// Set webhook
|
||||||
|
bot.telegram.setWebhook('https://yourdomain.com/bot')
|
||||||
|
|
||||||
|
// Express.js example
|
||||||
|
import express from 'express'
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.use(bot.webhookCallback('/bot'))
|
||||||
|
app.listen(3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions:
|
||||||
|
1. Check the main [README.md](./README.md)
|
||||||
|
2. Review [ARCHITECTURE.md](../../ARCHITECTURE.md) in CLI package
|
||||||
|
3. Open an issue on GitHub
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
ISC
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: Running Telegram Bot
|
||||||
|
*
|
||||||
|
* This example shows how to start the Telegram bot programmatically
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TelegramPlatform } from './src/index'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// Configuration
|
||||||
|
const config = {
|
||||||
|
botToken: process.env.BOT_TOKEN || '',
|
||||||
|
redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
|
||||||
|
apiUrl: process.env.API_BASE_URL || 'http://localhost:7002',
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Starting Telegram bot with config:', {
|
||||||
|
apiUrl: config.apiUrl,
|
||||||
|
redisUrl: config.redisUrl,
|
||||||
|
botToken: config.botToken.substring(0, 10) + '...',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create and start platform
|
||||||
|
const platform = new TelegramPlatform()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await platform.start(config)
|
||||||
|
|
||||||
|
console.log('Bot is running!')
|
||||||
|
console.log('\nAvailable commands:')
|
||||||
|
console.log(' /start - Welcome message')
|
||||||
|
console.log(' /login <username> <password> - Login to MemoHome')
|
||||||
|
console.log(' /whoami - Show current user')
|
||||||
|
console.log(' /chat <message> - Chat with AI')
|
||||||
|
console.log(' /logout - Logout')
|
||||||
|
|
||||||
|
// Handle shutdown
|
||||||
|
process.once('SIGINT', async () => {
|
||||||
|
console.log('\nStopping bot...')
|
||||||
|
await platform.stop()
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to start bot:', error)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only run if executed directly
|
||||||
|
if (import.meta.main) {
|
||||||
|
main()
|
||||||
|
}
|
||||||
|
|
||||||
|
export { main }
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@memohome/platform-telegram",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Telegram platform for MemoHome",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"bin": {
|
||||||
|
"memohome-tg-bot": "./src/bot.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "bun run src/bot.ts",
|
||||||
|
"dev": "bun run --watch src/bot.ts"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"packageManager": "pnpm@10.27.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@memohome/client": "workspace:*",
|
||||||
|
"@memohome/platform": "workspace:*",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"ioredis": "^5.9.1",
|
||||||
|
"telegraf": "^4.16.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import type { Context } from 'telegraf'
|
||||||
|
import { login, logout, isLoggedIn, getCurrentUser } from '@memohome/client'
|
||||||
|
import { getTokenStorage } from './storage'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login command handler for Telegram bot
|
||||||
|
* Usage: /login username password
|
||||||
|
*/
|
||||||
|
export async function handleLogin(ctx: Context) {
|
||||||
|
const telegramUserId = ctx.from?.id.toString()
|
||||||
|
if (!telegramUserId) {
|
||||||
|
await ctx.reply('❌ Unable to identify user')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('telegramUserId', telegramUserId)
|
||||||
|
|
||||||
|
// Parse command arguments
|
||||||
|
const args = ctx.message && 'text' in ctx.message
|
||||||
|
? ctx.message.text.split(' ').slice(1)
|
||||||
|
: []
|
||||||
|
|
||||||
|
if (args.length !== 2) {
|
||||||
|
await ctx.reply(
|
||||||
|
'❌ Invalid format\n\n' +
|
||||||
|
'Usage: /login <username> <password>\n' +
|
||||||
|
'Example: /login admin password'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [username, password] = args
|
||||||
|
|
||||||
|
try {
|
||||||
|
const storage = await getTokenStorage(telegramUserId)
|
||||||
|
|
||||||
|
// Attempt login
|
||||||
|
const result = await login({ username, password }, { storage })
|
||||||
|
|
||||||
|
if (result.success && result.user) {
|
||||||
|
|
||||||
|
await ctx.reply(
|
||||||
|
'✅ Login successful!\n\n' +
|
||||||
|
`👤 Username: ${result.user.username}\n` +
|
||||||
|
`🎭 Role: ${result.user.role}\n` +
|
||||||
|
`🔑 User ID: ${result.user.id}\n\n` +
|
||||||
|
'You can now use the bot to interact with MemoHome.'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await ctx.reply('❌ Login failed: Invalid response from server')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
await ctx.reply(`❌ Login failed: ${message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout command handler for Telegram bot
|
||||||
|
* Usage: /logout
|
||||||
|
*/
|
||||||
|
export async function handleLogout(ctx: Context) {
|
||||||
|
const telegramUserId = ctx.from?.id.toString()
|
||||||
|
if (!telegramUserId) {
|
||||||
|
await ctx.reply('❌ Unable to identify user')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const storage = await getTokenStorage(telegramUserId)
|
||||||
|
|
||||||
|
await logout({ storage })
|
||||||
|
await ctx.reply('✅ Logged out successfully')
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
await ctx.reply(`❌ Logout failed: ${message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whoami command handler - show current logged in user
|
||||||
|
* Usage: /whoami
|
||||||
|
*/
|
||||||
|
export async function handleWhoami(ctx: Context) {
|
||||||
|
const telegramUserId = ctx.from?.id.toString()
|
||||||
|
if (!telegramUserId) {
|
||||||
|
await ctx.reply('❌ Unable to identify user')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const storage = await getTokenStorage(telegramUserId)
|
||||||
|
|
||||||
|
const isLogged = await isLoggedIn({ storage })
|
||||||
|
|
||||||
|
if (!isLogged) {
|
||||||
|
await ctx.reply(
|
||||||
|
'❌ You are not logged in\n\n' +
|
||||||
|
'Use /login <username> <password> to login'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await getCurrentUser({ storage })
|
||||||
|
|
||||||
|
await ctx.reply(
|
||||||
|
'👤 Current User:\n\n' +
|
||||||
|
`Username: ${user.username}\n` +
|
||||||
|
`Role: ${user.role}\n` +
|
||||||
|
`User ID: ${user.id}\n` +
|
||||||
|
`Telegram ID: ${telegramUserId}`
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
await ctx.reply(`❌ Error: ${message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to require authentication
|
||||||
|
* Add this middleware to commands that require login
|
||||||
|
*/
|
||||||
|
export function requireAuth() {
|
||||||
|
return async (ctx: Context, next: () => Promise<void>) => {
|
||||||
|
const telegramUserId = ctx.from?.id.toString()
|
||||||
|
if (!telegramUserId) {
|
||||||
|
await ctx.reply('❌ Unable to identify user')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const storage = await getTokenStorage(telegramUserId)
|
||||||
|
|
||||||
|
const isLogged = await isLoggedIn({ storage })
|
||||||
|
|
||||||
|
if (!isLogged) {
|
||||||
|
await ctx.reply(
|
||||||
|
'❌ You need to login first\n\n' +
|
||||||
|
'Use /login <username> <password> to login'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is authenticated, continue to next handler
|
||||||
|
await next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Telegram Bot Standalone Entry Point
|
||||||
|
*
|
||||||
|
* This file allows running the Telegram bot as a standalone process
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TelegramPlatform } from './index'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const botToken = process.env.BOT_TOKEN
|
||||||
|
const redisUrl = process.env.REDIS_URL
|
||||||
|
const apiUrl = process.env.API_BASE_URL
|
||||||
|
|
||||||
|
if (!botToken) {
|
||||||
|
console.error('❌ BOT_TOKEN environment variable is required')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 Starting Telegram bot...')
|
||||||
|
console.log(`📡 API URL: ${apiUrl || 'http://localhost:7002'}`)
|
||||||
|
console.log(`💾 Redis URL: ${redisUrl || 'redis://localhost:6379'}`)
|
||||||
|
|
||||||
|
const platform = new TelegramPlatform()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await platform.start({
|
||||||
|
botToken,
|
||||||
|
redisUrl,
|
||||||
|
apiUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('✅ Bot is running...')
|
||||||
|
console.log('Press Ctrl+C to stop')
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
process.once('SIGINT', async () => {
|
||||||
|
console.log('\n🛑 Stopping bot...')
|
||||||
|
await platform.stop()
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
process.once('SIGTERM', async () => {
|
||||||
|
console.log('\n🛑 Stopping bot...')
|
||||||
|
await platform.stop()
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to start bot:', error)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import { Telegraf, type Context } from 'telegraf'
|
||||||
|
import { BasePlatform } from '@memohome/platform'
|
||||||
|
import { handleLogin, handleLogout, handleWhoami, requireAuth } from './auth'
|
||||||
|
import { chatStreamAsync, type StreamEvent } from '@memohome/client'
|
||||||
|
|
||||||
|
export interface TelegramPlatformConfig {
|
||||||
|
botToken: string
|
||||||
|
redisUrl?: string
|
||||||
|
apiUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TelegramPlatform extends BasePlatform {
|
||||||
|
name = 'telegram'
|
||||||
|
description = 'Telegram Bot platform for MemoHome'
|
||||||
|
|
||||||
|
private bot?: Telegraf
|
||||||
|
// private storage?: TelegramRedisStorage
|
||||||
|
|
||||||
|
async start(config: Record<string, unknown>): Promise<void> {
|
||||||
|
const botToken = config.botToken as string
|
||||||
|
if (!botToken) {
|
||||||
|
throw new Error('Bot token is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Initialize storage
|
||||||
|
// this.storage = new TelegramRedisStorage({
|
||||||
|
// redisUrl: config.redisUrl as string,
|
||||||
|
// apiUrl: config.apiUrl as string,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Initialize bot
|
||||||
|
this.bot = new Telegraf(botToken)
|
||||||
|
|
||||||
|
// Register commands
|
||||||
|
this.registerCommands()
|
||||||
|
|
||||||
|
// Start bot
|
||||||
|
await this.bot.launch()
|
||||||
|
console.log('✅ Telegram bot started successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
if (this.bot) {
|
||||||
|
this.bot.stop('SIGTERM')
|
||||||
|
console.log('🛑 Telegram bot stopped')
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (this.storage) {
|
||||||
|
// await this.storage.close()
|
||||||
|
// console.log('🛑 Redis connection closed')
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerCommands(): void {
|
||||||
|
if (!this.bot) {
|
||||||
|
throw new Error('Bot or storage not initialized')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start command
|
||||||
|
this.bot.command('start', async (ctx) => {
|
||||||
|
await ctx.reply(
|
||||||
|
'👋 Welcome to MemoHome Bot!\n\n' +
|
||||||
|
'Available commands:\n' +
|
||||||
|
'/login <username> <password> - Login to your account\n' +
|
||||||
|
'/logout - Logout from your account\n' +
|
||||||
|
'/whoami - Show current user info\n' +
|
||||||
|
'/chat <message> - Chat with AI agent\n' +
|
||||||
|
'/help - Show this help message'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Help command
|
||||||
|
this.bot.command('help', async (ctx) => {
|
||||||
|
await ctx.reply(
|
||||||
|
'📚 MemoHome Bot Help\n\n' +
|
||||||
|
'🔐 Authentication:\n' +
|
||||||
|
'/login <username> <password> - Login\n' +
|
||||||
|
'/logout - Logout\n' +
|
||||||
|
'/whoami - Show current user\n\n' +
|
||||||
|
'💬 Chat:\n' +
|
||||||
|
'/chat <message> - Talk to AI\n' +
|
||||||
|
'Or just send a message directly\n\n' +
|
||||||
|
'❓ Help:\n' +
|
||||||
|
'/help - Show this message'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auth commands
|
||||||
|
this.bot.command('login', (ctx) => handleLogin(ctx))
|
||||||
|
this.bot.command('logout', (ctx) => handleLogout(ctx))
|
||||||
|
this.bot.command('whoami', (ctx) => handleWhoami(ctx))
|
||||||
|
|
||||||
|
// Chat command (requires auth)
|
||||||
|
this.bot.command('chat', requireAuth(), async (ctx) => {
|
||||||
|
const args = ctx.message.text.split(' ').slice(1)
|
||||||
|
if (args.length === 0) {
|
||||||
|
await ctx.reply('❌ Please provide a message\n\nUsage: /chat <message>')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = args.join(' ')
|
||||||
|
await this.handleChat(ctx, message)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle direct messages (requires auth)
|
||||||
|
this.bot.on('text', requireAuth(), async (ctx) => {
|
||||||
|
// Skip if it's a command
|
||||||
|
if (ctx.message.text.startsWith('/')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.handleChat(ctx, ctx.message.text)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
this.bot.catch((err, ctx) => {
|
||||||
|
console.error('Bot error:', err)
|
||||||
|
ctx.reply('❌ An error occurred. Please try again.')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleChat(ctx: Context, message: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Send typing indicator
|
||||||
|
await ctx.sendChatAction('typing')
|
||||||
|
|
||||||
|
let responseText = ''
|
||||||
|
let lastUpdateTime = Date.now()
|
||||||
|
let messageId: number | undefined
|
||||||
|
|
||||||
|
await chatStreamAsync(
|
||||||
|
{
|
||||||
|
message,
|
||||||
|
language: 'Chinese',
|
||||||
|
maxContextLoadTime: 60,
|
||||||
|
},
|
||||||
|
async (event: StreamEvent) => {
|
||||||
|
if (event.type === 'text-delta' && event.text) {
|
||||||
|
responseText += event.text
|
||||||
|
|
||||||
|
// Update message every 1 second or when response is complete
|
||||||
|
const now = Date.now()
|
||||||
|
if (now - lastUpdateTime > 1000) {
|
||||||
|
lastUpdateTime = now
|
||||||
|
|
||||||
|
if (messageId && ctx.chat) {
|
||||||
|
// Edit existing message
|
||||||
|
try {
|
||||||
|
await ctx.telegram.editMessageText(
|
||||||
|
ctx.chat.id,
|
||||||
|
messageId,
|
||||||
|
undefined,
|
||||||
|
`🤖 ${responseText}`
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// Ignore if message is not modified
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Send first message
|
||||||
|
const sent = await ctx.reply(`🤖 ${responseText}`)
|
||||||
|
messageId = sent.message_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event.type === 'tool-call') {
|
||||||
|
// Show tool usage
|
||||||
|
if (messageId && ctx.chat) {
|
||||||
|
try {
|
||||||
|
await ctx.telegram.editMessageText(
|
||||||
|
ctx.chat.id,
|
||||||
|
messageId,
|
||||||
|
undefined,
|
||||||
|
`🤖 ${responseText}\n\n🔧 Using tool: ${event.toolName}...`
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event.type === 'error') {
|
||||||
|
await ctx.reply(`❌ Error: ${event.error}`)
|
||||||
|
} else if (event.type === 'done') {
|
||||||
|
// Final update
|
||||||
|
if (messageId && responseText && ctx.chat) {
|
||||||
|
try {
|
||||||
|
await ctx.telegram.editMessageText(
|
||||||
|
ctx.chat.id,
|
||||||
|
messageId,
|
||||||
|
undefined,
|
||||||
|
`🤖 ${responseText}`
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
} else if (!messageId && responseText) {
|
||||||
|
await ctx.reply(`🤖 ${responseText}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
await ctx.reply(`❌ Error: ${errorMessage}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for easy use
|
||||||
|
export { handleLogin, handleLogout, handleWhoami, requireAuth } from './auth'
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { TokenStorage } from '@memohome/client'
|
||||||
|
import Redis from 'ioredis'
|
||||||
|
|
||||||
|
export const getTokenStorage = async (telegramUserId: string): Promise<TokenStorage> => {
|
||||||
|
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379')
|
||||||
|
const token = await redis.get(`memohome:telegram:${telegramUserId}:token`)
|
||||||
|
return {
|
||||||
|
getApiUrl: () => process.env.API_URL || 'http://localhost:7002',
|
||||||
|
setApiUrl: () => {},
|
||||||
|
getToken: () => token,
|
||||||
|
setToken: (token: string) => {
|
||||||
|
redis.set(`memohome:telegram:${telegramUserId}:token`, token)
|
||||||
|
},
|
||||||
|
clearToken: () => {
|
||||||
|
redis.del(`memohome:telegram:${telegramUserId}:token`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# @memohome/platform
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@memohome/platform",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"packageManager": "pnpm@10.27.0",
|
||||||
|
"dependencies": {
|
||||||
|
"elysia": "^1.4.21"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Elysia } from 'elysia'
|
||||||
|
|
||||||
|
export class BasePlatform {
|
||||||
|
name: string = 'base'
|
||||||
|
description: string = 'Base platform'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
async start(config: Record<string, unknown>): Promise<void> {}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {}
|
||||||
|
|
||||||
|
async send(): Promise<void> {}
|
||||||
|
|
||||||
|
// serve(): void {
|
||||||
|
// const app = new Elysia()
|
||||||
|
// }
|
||||||
|
}
|
||||||
Generated
+149
@@ -235,6 +235,30 @@ importers:
|
|||||||
specifier: ^0.4.1
|
specifier: ^0.4.1
|
||||||
version: 0.4.1(zod-to-json-schema@3.25.1(zod@4.3.5))(zod@4.3.5)
|
version: 0.4.1(zod-to-json-schema@3.25.1(zod@4.3.5))(zod@4.3.5)
|
||||||
|
|
||||||
|
packages/platform:
|
||||||
|
dependencies:
|
||||||
|
elysia:
|
||||||
|
specifier: ^1.4.21
|
||||||
|
version: 1.4.21(@sinclair/typebox@0.34.47)(@types/bun@1.3.5)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||||
|
|
||||||
|
packages/platform-telegram:
|
||||||
|
dependencies:
|
||||||
|
'@memohome/client':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../cli
|
||||||
|
'@memohome/platform':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../platform
|
||||||
|
elysia:
|
||||||
|
specifier: ^1.4.21
|
||||||
|
version: 1.4.21(@sinclair/typebox@0.34.47)(@types/bun@1.3.5)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||||
|
ioredis:
|
||||||
|
specifier: ^5.9.1
|
||||||
|
version: 5.9.1
|
||||||
|
telegraf:
|
||||||
|
specifier: ^4.16.3
|
||||||
|
version: 4.16.3(encoding@0.1.13)
|
||||||
|
|
||||||
packages/shared: {}
|
packages/shared: {}
|
||||||
|
|
||||||
packages/ui:
|
packages/ui:
|
||||||
@@ -1328,6 +1352,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==}
|
resolution: {integrity: sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
|
'@ioredis/commands@1.5.0':
|
||||||
|
resolution: {integrity: sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==}
|
||||||
|
|
||||||
'@isaacs/balanced-match@4.0.1':
|
'@isaacs/balanced-match@4.0.1':
|
||||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||||
engines: {node: 20 || >=22}
|
engines: {node: 20 || >=22}
|
||||||
@@ -1750,6 +1777,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^2.7.0 || ^3.0.0
|
vue: ^2.7.0 || ^3.0.0
|
||||||
|
|
||||||
|
'@telegraf/types@7.1.0':
|
||||||
|
resolution: {integrity: sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==}
|
||||||
|
|
||||||
'@tokenizer/inflate@0.4.1':
|
'@tokenizer/inflate@0.4.1':
|
||||||
resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
|
resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -2269,9 +2299,18 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
buffer-alloc-unsafe@1.1.0:
|
||||||
|
resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==}
|
||||||
|
|
||||||
|
buffer-alloc@1.2.0:
|
||||||
|
resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==}
|
||||||
|
|
||||||
buffer-equal-constant-time@1.0.1:
|
buffer-equal-constant-time@1.0.1:
|
||||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||||
|
|
||||||
|
buffer-fill@1.0.0:
|
||||||
|
resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==}
|
||||||
|
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
@@ -2483,6 +2522,10 @@ packages:
|
|||||||
delegates@1.0.0:
|
delegates@1.0.0:
|
||||||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||||
|
|
||||||
|
denque@2.1.0:
|
||||||
|
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
|
||||||
detect-libc@2.1.2:
|
detect-libc@2.1.2:
|
||||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3096,6 +3139,10 @@ packages:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
ioredis@5.9.1:
|
||||||
|
resolution: {integrity: sha512-BXNqFQ66oOsR82g9ajFFsR8ZKrjVvYCLyeML9IvSMAsP56XH2VXBdZjmI11p65nXXJxTEt1hie3J2QeFJVgrtQ==}
|
||||||
|
engines: {node: '>=12.22.0'}
|
||||||
|
|
||||||
ip-address@10.1.0:
|
ip-address@10.1.0:
|
||||||
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
|
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
@@ -3360,9 +3407,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
lodash.defaults@4.2.0:
|
||||||
|
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||||
|
|
||||||
lodash.includes@4.3.0:
|
lodash.includes@4.3.0:
|
||||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||||
|
|
||||||
|
lodash.isarguments@3.1.0:
|
||||||
|
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||||
|
|
||||||
lodash.isboolean@3.0.3:
|
lodash.isboolean@3.0.3:
|
||||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||||
|
|
||||||
@@ -3538,6 +3591,10 @@ packages:
|
|||||||
mlly@1.8.0:
|
mlly@1.8.0:
|
||||||
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
||||||
|
|
||||||
|
mri@1.2.0:
|
||||||
|
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
mrmime@2.0.1:
|
mrmime@2.0.1:
|
||||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3715,6 +3772,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
|
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
p-timeout@4.1.0:
|
||||||
|
resolution: {integrity: sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1:
|
package-json-from-dist@1.0.1:
|
||||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
@@ -3895,6 +3956,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
redis-errors@1.2.0:
|
||||||
|
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
redis-parser@3.0.0:
|
||||||
|
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
redis@4.7.1:
|
redis@4.7.1:
|
||||||
resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==}
|
resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==}
|
||||||
|
|
||||||
@@ -3962,9 +4031,16 @@ packages:
|
|||||||
safe-buffer@5.2.1:
|
safe-buffer@5.2.1:
|
||||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
|
||||||
|
safe-compare@1.1.4:
|
||||||
|
resolution: {integrity: sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==}
|
||||||
|
|
||||||
safer-buffer@2.1.2:
|
safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
|
sandwich-stream@2.0.2:
|
||||||
|
resolution: {integrity: sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
semver@6.3.1:
|
semver@6.3.1:
|
||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -4073,6 +4149,9 @@ packages:
|
|||||||
stackback@0.0.2:
|
stackback@0.0.2:
|
||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
|
standard-as-callback@2.1.0:
|
||||||
|
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
|
||||||
|
|
||||||
std-env@3.10.0:
|
std-env@3.10.0:
|
||||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
@@ -4160,6 +4239,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
telegraf@4.16.3:
|
||||||
|
resolution: {integrity: sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==}
|
||||||
|
engines: {node: ^12.20.0 || >=14.13.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
tinybench@2.9.0:
|
tinybench@2.9.0:
|
||||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
@@ -5472,6 +5556,8 @@ snapshots:
|
|||||||
|
|
||||||
'@intlify/shared@11.2.8': {}
|
'@intlify/shared@11.2.8': {}
|
||||||
|
|
||||||
|
'@ioredis/commands@1.5.0': {}
|
||||||
|
|
||||||
'@isaacs/balanced-match@4.0.1': {}
|
'@isaacs/balanced-match@4.0.1': {}
|
||||||
|
|
||||||
'@isaacs/brace-expansion@5.0.0':
|
'@isaacs/brace-expansion@5.0.0':
|
||||||
@@ -5878,6 +5964,8 @@ snapshots:
|
|||||||
'@tanstack/virtual-core': 3.13.17
|
'@tanstack/virtual-core': 3.13.17
|
||||||
vue: 3.5.26(typescript@5.9.3)
|
vue: 3.5.26(typescript@5.9.3)
|
||||||
|
|
||||||
|
'@telegraf/types@7.1.0': {}
|
||||||
|
|
||||||
'@tokenizer/inflate@0.4.1':
|
'@tokenizer/inflate@0.4.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -6555,8 +6643,17 @@ snapshots:
|
|||||||
node-releases: 2.0.27
|
node-releases: 2.0.27
|
||||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||||
|
|
||||||
|
buffer-alloc-unsafe@1.1.0: {}
|
||||||
|
|
||||||
|
buffer-alloc@1.2.0:
|
||||||
|
dependencies:
|
||||||
|
buffer-alloc-unsafe: 1.1.0
|
||||||
|
buffer-fill: 1.0.0
|
||||||
|
|
||||||
buffer-equal-constant-time@1.0.1: {}
|
buffer-equal-constant-time@1.0.1: {}
|
||||||
|
|
||||||
|
buffer-fill@1.0.0: {}
|
||||||
|
|
||||||
buffer-from@1.1.2: {}
|
buffer-from@1.1.2: {}
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
@@ -6746,6 +6843,8 @@ snapshots:
|
|||||||
delegates@1.0.0:
|
delegates@1.0.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
denque@2.1.0: {}
|
||||||
|
|
||||||
detect-libc@2.1.2: {}
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
diff-sequences@29.6.3: {}
|
diff-sequences@29.6.3: {}
|
||||||
@@ -7388,6 +7487,20 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 22.19.5
|
'@types/node': 22.19.5
|
||||||
|
|
||||||
|
ioredis@5.9.1:
|
||||||
|
dependencies:
|
||||||
|
'@ioredis/commands': 1.5.0
|
||||||
|
cluster-key-slot: 1.1.2
|
||||||
|
debug: 4.4.3
|
||||||
|
denque: 2.1.0
|
||||||
|
lodash.defaults: 4.2.0
|
||||||
|
lodash.isarguments: 3.1.0
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
redis-parser: 3.0.0
|
||||||
|
standard-as-callback: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
ip-address@10.1.0:
|
ip-address@10.1.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -7623,8 +7736,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
|
lodash.defaults@4.2.0: {}
|
||||||
|
|
||||||
lodash.includes@4.3.0: {}
|
lodash.includes@4.3.0: {}
|
||||||
|
|
||||||
|
lodash.isarguments@3.1.0: {}
|
||||||
|
|
||||||
lodash.isboolean@3.0.3: {}
|
lodash.isboolean@3.0.3: {}
|
||||||
|
|
||||||
lodash.isinteger@4.0.4: {}
|
lodash.isinteger@4.0.4: {}
|
||||||
@@ -7815,6 +7932,8 @@ snapshots:
|
|||||||
pkg-types: 1.3.1
|
pkg-types: 1.3.1
|
||||||
ufo: 1.6.2
|
ufo: 1.6.2
|
||||||
|
|
||||||
|
mri@1.2.0: {}
|
||||||
|
|
||||||
mrmime@2.0.1: {}
|
mrmime@2.0.1: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
@@ -8027,6 +8146,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-finally: 1.0.0
|
p-finally: 1.0.0
|
||||||
|
|
||||||
|
p-timeout@4.1.0: {}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1: {}
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
@@ -8201,6 +8322,12 @@ snapshots:
|
|||||||
string_decoder: 1.3.0
|
string_decoder: 1.3.0
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
|
redis-errors@1.2.0: {}
|
||||||
|
|
||||||
|
redis-parser@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
|
||||||
redis@4.7.1:
|
redis@4.7.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@redis/bloom': 1.2.0(@redis/client@1.6.1)
|
'@redis/bloom': 1.2.0(@redis/client@1.6.1)
|
||||||
@@ -8298,8 +8425,14 @@ snapshots:
|
|||||||
|
|
||||||
safe-buffer@5.2.1: {}
|
safe-buffer@5.2.1: {}
|
||||||
|
|
||||||
|
safe-compare@1.1.4:
|
||||||
|
dependencies:
|
||||||
|
buffer-alloc: 1.2.0
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
|
sandwich-stream@2.0.2: {}
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.5.4:
|
semver@7.5.4:
|
||||||
@@ -8406,6 +8539,8 @@ snapshots:
|
|||||||
|
|
||||||
stackback@0.0.2: {}
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
|
standard-as-callback@2.1.0: {}
|
||||||
|
|
||||||
std-env@3.10.0: {}
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
stdin-discarder@0.2.2: {}
|
stdin-discarder@0.2.2: {}
|
||||||
@@ -8502,6 +8637,20 @@ snapshots:
|
|||||||
mkdirp: 1.0.4
|
mkdirp: 1.0.4
|
||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
|
|
||||||
|
telegraf@4.16.3(encoding@0.1.13):
|
||||||
|
dependencies:
|
||||||
|
'@telegraf/types': 7.1.0
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
debug: 4.4.3
|
||||||
|
mri: 1.2.0
|
||||||
|
node-fetch: 2.7.0(encoding@0.1.13)
|
||||||
|
p-timeout: 4.1.0
|
||||||
|
safe-compare: 1.1.4
|
||||||
|
sandwich-stream: 2.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
- supports-color
|
||||||
|
|
||||||
tinybench@2.9.0: {}
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
tinyexec@1.0.2: {}
|
tinyexec@1.0.2: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user