mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat: auto-send of tg bot
This commit is contained in:
@@ -91,3 +91,5 @@ Thumbs.db
|
|||||||
.cache/
|
.cache/
|
||||||
|
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
|
|
||||||
|
dump.rdb
|
||||||
Binary file not shown.
@@ -22,7 +22,8 @@
|
|||||||
"@memohome/platform": "workspace:*",
|
"@memohome/platform": "workspace:*",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"ioredis": "^5.9.1",
|
"ioredis": "^5.9.1",
|
||||||
"telegraf": "^4.16.3"
|
"telegraf": "^4.16.3",
|
||||||
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.5"
|
"@types/node": "^22.10.5"
|
||||||
|
|||||||
@@ -8,17 +8,12 @@ import { getTokenStorage } from './storage'
|
|||||||
* Usage: /login username password
|
* Usage: /login username password
|
||||||
*/
|
*/
|
||||||
export async function handleLogin(ctx: Context) {
|
export async function handleLogin(ctx: Context) {
|
||||||
const telegramUserId = ctx.from?.id.toString()
|
|
||||||
if (!telegramUserId) {
|
|
||||||
await ctx.reply('❌ Unable to identify user')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse command arguments
|
// Parse command arguments
|
||||||
const args = ctx.message && 'text' in ctx.message
|
const args = ctx.message && 'text' in ctx.message
|
||||||
? ctx.message.text.split(' ').slice(1)
|
? ctx.message.text.split(' ').slice(1)
|
||||||
: []
|
: []
|
||||||
|
|
||||||
if (args.length !== 2) {
|
if (args.length !== 2) {
|
||||||
await ctx.reply(
|
await ctx.reply(
|
||||||
'❌ Invalid format\n\n' +
|
'❌ Invalid format\n\n' +
|
||||||
@@ -30,23 +25,24 @@ export async function handleLogin(ctx: Context) {
|
|||||||
|
|
||||||
const [username, password] = args
|
const [username, password] = args
|
||||||
|
|
||||||
const storage = await getTokenStorage(telegramUserId)
|
const storage = await getTokenStorage(ctx)
|
||||||
|
|
||||||
// Attempt login
|
// Attempt login
|
||||||
const result = await login({ username, password }, { storage })
|
const result = await login({ username, password }, { storage })
|
||||||
|
|
||||||
if (result.success && result.user) {
|
if (result.success && result.user) {
|
||||||
|
storage.setUserId(result.user.id)
|
||||||
|
|
||||||
await ctx.reply(
|
await ctx.reply(
|
||||||
'✅ Login successful!\n\n' +
|
'✅ Login successful!\n\n' +
|
||||||
`👤 Username: ${result.user.username}\n` +
|
`👤 Username: ${result.user.username}\n` +
|
||||||
`🎭 Role: ${result.user.role}\n` +
|
`🎭 Role: ${result.user.role}\n` +
|
||||||
`🔑 User ID: ${result.user.id}\n\n` +
|
`🔑 User ID: ${result.user.id}\n\n` +
|
||||||
'You can now use the bot to interact with MemoHome.'
|
'You can now use the bot to interact with MemoHome.'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
await ctx.reply('❌ Login failed: Invalid response from server')
|
await ctx.reply('❌ Login failed: Invalid response from server')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,14 +50,8 @@ export async function handleLogin(ctx: Context) {
|
|||||||
* Usage: /logout
|
* Usage: /logout
|
||||||
*/
|
*/
|
||||||
export async function handleLogout(ctx: Context) {
|
export async function handleLogout(ctx: Context) {
|
||||||
const telegramUserId = ctx.from?.id.toString()
|
|
||||||
if (!telegramUserId) {
|
|
||||||
await ctx.reply('❌ Unable to identify user')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storage = await getTokenStorage(telegramUserId)
|
const storage = await getTokenStorage(ctx)
|
||||||
|
|
||||||
await logout({ storage })
|
await logout({ storage })
|
||||||
await ctx.reply('✅ Logged out successfully')
|
await ctx.reply('✅ Logged out successfully')
|
||||||
@@ -76,17 +66,11 @@ export async function handleLogout(ctx: Context) {
|
|||||||
* Usage: /whoami
|
* Usage: /whoami
|
||||||
*/
|
*/
|
||||||
export async function handleWhoami(ctx: Context) {
|
export async function handleWhoami(ctx: Context) {
|
||||||
const telegramUserId = ctx.from?.id.toString()
|
|
||||||
if (!telegramUserId) {
|
|
||||||
await ctx.reply('❌ Unable to identify user')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storage = await getTokenStorage(telegramUserId)
|
const storage = await getTokenStorage(ctx)
|
||||||
|
|
||||||
const isLogged = await isLoggedIn({ storage })
|
const isLogged = await isLoggedIn({ storage })
|
||||||
|
|
||||||
if (!isLogged) {
|
if (!isLogged) {
|
||||||
await ctx.reply(
|
await ctx.reply(
|
||||||
'❌ You are not logged in\n\n' +
|
'❌ You are not logged in\n\n' +
|
||||||
@@ -102,7 +86,7 @@ export async function handleWhoami(ctx: Context) {
|
|||||||
`Username: ${user.username}\n` +
|
`Username: ${user.username}\n` +
|
||||||
`Role: ${user.role}\n` +
|
`Role: ${user.role}\n` +
|
||||||
`User ID: ${user.id}\n` +
|
`User ID: ${user.id}\n` +
|
||||||
`Telegram ID: ${telegramUserId}`
|
`Telegram ID: ${storage.getChatId()}`
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
@@ -116,16 +100,10 @@ export async function handleWhoami(ctx: Context) {
|
|||||||
*/
|
*/
|
||||||
export function requireAuth() {
|
export function requireAuth() {
|
||||||
return async (ctx: Context, next: () => Promise<void>) => {
|
return async (ctx: Context, next: () => Promise<void>) => {
|
||||||
const telegramUserId = ctx.from?.id.toString()
|
const storage = await getTokenStorage(ctx)
|
||||||
if (!telegramUserId) {
|
|
||||||
await ctx.reply('❌ Unable to identify user')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const storage = await getTokenStorage(telegramUserId)
|
|
||||||
|
|
||||||
const isLogged = await isLoggedIn({ storage })
|
const isLogged = await isLoggedIn({ storage })
|
||||||
|
|
||||||
if (!isLogged) {
|
if (!isLogged) {
|
||||||
await ctx.reply(
|
await ctx.reply(
|
||||||
'❌ You need to login first\n\n' +
|
'❌ You need to login first\n\n' +
|
||||||
|
|||||||
@@ -25,10 +25,8 @@ async function main() {
|
|||||||
const platform = new TelegramPlatform()
|
const platform = new TelegramPlatform()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await platform.start({
|
platform.serve({
|
||||||
botToken,
|
botToken,
|
||||||
redisUrl,
|
|
||||||
apiUrl,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('✅ Bot is running...')
|
console.log('✅ Bot is running...')
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { Telegraf, type Context } from 'telegraf'
|
import { Telegraf, type Context } from 'telegraf'
|
||||||
import { BasePlatform } from '@memohome/platform'
|
import { BasePlatform, SendSchema } from '@memohome/platform'
|
||||||
import { handleLogin, handleLogout, handleWhoami, requireAuth } from './auth'
|
import { handleLogin, handleLogout, handleWhoami, requireAuth } from './auth'
|
||||||
import { chatStreamAsync, type StreamEvent } from '@memohome/client'
|
import { chatStreamAsync, type StreamEvent } from '@memohome/client'
|
||||||
|
import { getTokenStorage } from './storage'
|
||||||
|
import z from 'zod'
|
||||||
|
import Redis from 'ioredis'
|
||||||
|
|
||||||
export interface TelegramPlatformConfig {
|
export interface TelegramPlatformConfig {
|
||||||
botToken: string
|
botToken: string
|
||||||
@@ -12,8 +15,12 @@ export interface TelegramPlatformConfig {
|
|||||||
export class TelegramPlatform extends BasePlatform {
|
export class TelegramPlatform extends BasePlatform {
|
||||||
name = 'telegram'
|
name = 'telegram'
|
||||||
description = 'Telegram Bot platform for MemoHome'
|
description = 'Telegram Bot platform for MemoHome'
|
||||||
|
config = z.object({
|
||||||
|
botToken: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
private bot?: Telegraf
|
private bot?: Telegraf
|
||||||
|
redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379')
|
||||||
// private storage?: TelegramRedisStorage
|
// private storage?: TelegramRedisStorage
|
||||||
|
|
||||||
async start(config: Record<string, unknown>): Promise<void> {
|
async start(config: Record<string, unknown>): Promise<void> {
|
||||||
@@ -51,6 +58,42 @@ export class TelegramPlatform extends BasePlatform {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async send({ userId, message }: z.infer<typeof SendSchema>): Promise<void> {
|
||||||
|
const pattern = 'memohome:telegram:*:userId'
|
||||||
|
let cursor = '0'
|
||||||
|
let telegramUserId: string | null = null
|
||||||
|
|
||||||
|
do {
|
||||||
|
const [nextCursor, keys] = await this.redis.scan(
|
||||||
|
cursor,
|
||||||
|
'MATCH',
|
||||||
|
pattern,
|
||||||
|
'COUNT',
|
||||||
|
100
|
||||||
|
)
|
||||||
|
cursor = nextCursor
|
||||||
|
|
||||||
|
// 检查每个 key 的值是否匹配目标 userId
|
||||||
|
for (const key of keys) {
|
||||||
|
const storedUserId = await this.redis.get(key)
|
||||||
|
if (storedUserId === userId) {
|
||||||
|
// 从 key 中提取 telegramUserId: memohome:telegram:{telegramUserId}:userId
|
||||||
|
const match = key.match(/^memohome:telegram:(.+):userId$/)
|
||||||
|
if (match) {
|
||||||
|
telegramUserId = match[1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (cursor !== '0')
|
||||||
|
if (telegramUserId) {
|
||||||
|
const chatId = await this.redis.get(`memohome:telegram:${telegramUserId}:chatId`)
|
||||||
|
if (chatId && this.bot) {
|
||||||
|
await this.bot.telegram.sendMessage(chatId, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private registerCommands(): void {
|
private registerCommands(): void {
|
||||||
if (!this.bot) {
|
if (!this.bot) {
|
||||||
throw new Error('Bot or storage not initialized')
|
throw new Error('Bot or storage not initialized')
|
||||||
@@ -123,6 +166,7 @@ export class TelegramPlatform extends BasePlatform {
|
|||||||
try {
|
try {
|
||||||
// Send typing indicator
|
// Send typing indicator
|
||||||
await ctx.sendChatAction('typing')
|
await ctx.sendChatAction('typing')
|
||||||
|
await getTokenStorage(ctx)
|
||||||
|
|
||||||
let responseText = ''
|
let responseText = ''
|
||||||
let lastUpdateTime = Date.now()
|
let lastUpdateTime = Date.now()
|
||||||
|
|||||||
@@ -1,19 +1,86 @@
|
|||||||
import type { TokenStorage } from '@memohome/client'
|
import type { TokenStorage } from '@memohome/client'
|
||||||
import Redis from 'ioredis'
|
import Redis from 'ioredis'
|
||||||
|
import { Context } from 'telegraf'
|
||||||
|
|
||||||
export const getTokenStorage = async (telegramUserId: string): Promise<TokenStorage> => {
|
export interface TelegramTokenStorage extends TokenStorage {
|
||||||
|
getUserId: () => string | null
|
||||||
|
setUserId: (userId: string) => void
|
||||||
|
getChatId: () => string | null
|
||||||
|
setChatId: (chatId: string) => void
|
||||||
|
getTelegramIdByUserId: (userId: string) => Promise<string | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTokenStorage = async (ctx: Context): Promise<TelegramTokenStorage> => {
|
||||||
|
const telegramUserId = ctx.from?.id.toString()
|
||||||
|
if (!telegramUserId) {
|
||||||
|
throw new Error('Unable to identify user')
|
||||||
|
}
|
||||||
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379')
|
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379')
|
||||||
const isExists = await redis.exists(`memohome:telegram:${telegramUserId}:token`)
|
const isTokenExists = await redis.exists(`memohome:telegram:${telegramUserId}:token`)
|
||||||
const token = isExists ? await redis.get(`memohome:telegram:${telegramUserId}:token`) : null
|
const token = isTokenExists ? await redis.get(`memohome:telegram:${telegramUserId}:token`) : null
|
||||||
|
const isUserIdExists = await redis.exists(`memohome:telegram:${telegramUserId}:userId`)
|
||||||
|
const userId = isUserIdExists ? await redis.get(`memohome:telegram:${telegramUserId}:userId`) : null
|
||||||
|
const chatId = ctx.chat?.id.toString() ?? null
|
||||||
|
if (chatId) await redis.set(`memohome:telegram:${telegramUserId}:chatId`, chatId)
|
||||||
return {
|
return {
|
||||||
|
getChatId: () => chatId,
|
||||||
|
setChatId: (chatId: string) => {
|
||||||
|
redis.set(`memohome:telegram:${telegramUserId}:chatId`, chatId)
|
||||||
|
.then(() => {
|
||||||
|
redis.save()
|
||||||
|
})
|
||||||
|
},
|
||||||
getApiUrl: () => process.env.API_URL || 'http://localhost:7002',
|
getApiUrl: () => process.env.API_URL || 'http://localhost:7002',
|
||||||
setApiUrl: () => {},
|
setApiUrl: () => {},
|
||||||
getToken: () => token,
|
getToken: () => token,
|
||||||
setToken: (token: string) => {
|
setToken: (token: string) => {
|
||||||
redis.set(`memohome:telegram:${telegramUserId}:token`, token)
|
redis.set(`memohome:telegram:${telegramUserId}:token`, token)
|
||||||
|
.then(() => {
|
||||||
|
redis.save()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
clearToken: () => {
|
clearToken: () => {
|
||||||
redis.del(`memohome:telegram:${telegramUserId}:token`)
|
redis.del(`memohome:telegram:${telegramUserId}:token`)
|
||||||
|
.then(() => {
|
||||||
|
redis.save()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getUserId: () => userId,
|
||||||
|
setUserId: (userId: string) => {
|
||||||
|
redis.set(`memohome:telegram:${telegramUserId}:userId`, userId)
|
||||||
|
.then(() => {
|
||||||
|
redis.save()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getTelegramIdByUserId: async (userId: string) => {
|
||||||
|
// 扫描所有 memohome:telegram:*:userId 的 key
|
||||||
|
const pattern = 'memohome:telegram:*:userId'
|
||||||
|
let cursor = '0'
|
||||||
|
|
||||||
|
do {
|
||||||
|
const [nextCursor, keys] = await redis.scan(
|
||||||
|
cursor,
|
||||||
|
'MATCH',
|
||||||
|
pattern,
|
||||||
|
'COUNT',
|
||||||
|
100
|
||||||
|
)
|
||||||
|
cursor = nextCursor
|
||||||
|
|
||||||
|
// 检查每个 key 的值是否匹配目标 userId
|
||||||
|
for (const key of keys) {
|
||||||
|
const storedUserId = await redis.get(key)
|
||||||
|
if (storedUserId === userId) {
|
||||||
|
// 从 key 中提取 telegramUserId: memohome:telegram:{telegramUserId}:userId
|
||||||
|
const match = key.match(/^memohome:telegram:(.+):userId$/)
|
||||||
|
if (match) {
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (cursor !== '0')
|
||||||
|
|
||||||
|
return null
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { describe, expect, it } from "vitest"
|
||||||
|
|
||||||
|
describe('Telegram Platform', () => {
|
||||||
|
it('should send a message to a user', async () => {
|
||||||
|
const response = await fetch('http://localhost:7003/send', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
userId: '66392e42-333a-4ee9-9276-1b216d3400b1',
|
||||||
|
message: 'Hello, world!',
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log(await response.json())
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"packageManager": "pnpm@10.27.0",
|
"packageManager": "pnpm@10.27.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"elysia": "^1.4.21"
|
"@elysiajs/cors": "^1.4.1",
|
||||||
|
"elysia": "^1.4.21",
|
||||||
|
"zod": "^4.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,70 @@
|
|||||||
import { Elysia } from 'elysia'
|
import { Elysia } from 'elysia'
|
||||||
|
import { cors } from '@elysiajs/cors'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const SendSchema = z.object({
|
||||||
|
message: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
export class BasePlatform {
|
export class BasePlatform {
|
||||||
name: string = 'base'
|
name: string = 'base'
|
||||||
description: string = 'Base platform'
|
description: string = 'Base platform'
|
||||||
|
started: boolean = false
|
||||||
|
port: number = 7003
|
||||||
|
|
||||||
|
config = z.object()
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
async start(config: Record<string, unknown>): Promise<void> {}
|
async start(config: z.infer<typeof this.config>): Promise<void> {}
|
||||||
|
|
||||||
async stop(): Promise<void> {}
|
async stop(): Promise<void> {}
|
||||||
|
|
||||||
async send(): Promise<void> {}
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
async send(data: z.infer<typeof SendSchema>): Promise<void> {}
|
||||||
|
|
||||||
// serve(): void {
|
serve<T extends z.infer<typeof this.config>>(config?: T): void {
|
||||||
// const app = new Elysia()
|
new Elysia()
|
||||||
// }
|
.use(cors({
|
||||||
|
origin: '*',
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||||
|
credentials: true,
|
||||||
|
}))
|
||||||
|
.onStart(() => {
|
||||||
|
if (config) {
|
||||||
|
this.started = true
|
||||||
|
this.start(config)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post('/start', async ({ body }) => {
|
||||||
|
if (!this.started) {
|
||||||
|
this.start(body)
|
||||||
|
this.started = true
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
body: this.config,
|
||||||
|
})
|
||||||
|
.post('/stop', async () => {
|
||||||
|
if (this.started) {
|
||||||
|
this.stop()
|
||||||
|
this.started = false
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post('/send', async ({ body }) => {
|
||||||
|
await this.send(body)
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
body: SendSchema,
|
||||||
|
})
|
||||||
|
.listen(this.port)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Generated
+22
-3
@@ -237,9 +237,15 @@ importers:
|
|||||||
|
|
||||||
packages/platform:
|
packages/platform:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@elysiajs/cors':
|
||||||
|
specifier: ^1.4.1
|
||||||
|
version: 1.4.1(elysia@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))
|
||||||
elysia:
|
elysia:
|
||||||
specifier: ^1.4.21
|
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)
|
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)
|
||||||
|
zod:
|
||||||
|
specifier: ^4.3.5
|
||||||
|
version: 4.3.5
|
||||||
|
|
||||||
packages/platform-telegram:
|
packages/platform-telegram:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -249,15 +255,22 @@ importers:
|
|||||||
'@memohome/platform':
|
'@memohome/platform':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../platform
|
version: link:../platform
|
||||||
elysia:
|
dotenv:
|
||||||
specifier: ^1.4.21
|
specifier: ^16.4.7
|
||||||
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)
|
version: 16.6.1
|
||||||
ioredis:
|
ioredis:
|
||||||
specifier: ^5.9.1
|
specifier: ^5.9.1
|
||||||
version: 5.9.1
|
version: 5.9.1
|
||||||
telegraf:
|
telegraf:
|
||||||
specifier: ^4.16.3
|
specifier: ^4.16.3
|
||||||
version: 4.16.3(encoding@0.1.13)
|
version: 4.16.3(encoding@0.1.13)
|
||||||
|
zod:
|
||||||
|
specifier: ^4.3.5
|
||||||
|
version: 4.3.5
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.10.5
|
||||||
|
version: 22.19.5
|
||||||
|
|
||||||
packages/shared: {}
|
packages/shared: {}
|
||||||
|
|
||||||
@@ -2541,6 +2554,10 @@ packages:
|
|||||||
digest-fetch@1.3.0:
|
digest-fetch@1.3.0:
|
||||||
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
|
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
|
||||||
|
|
||||||
|
dotenv@16.6.1:
|
||||||
|
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
dotenv@17.2.3:
|
dotenv@17.2.3:
|
||||||
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
|
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -6856,6 +6873,8 @@ snapshots:
|
|||||||
base-64: 0.1.0
|
base-64: 0.1.0
|
||||||
md5: 2.3.0
|
md5: 2.3.0
|
||||||
|
|
||||||
|
dotenv@16.6.1: {}
|
||||||
|
|
||||||
dotenv@17.2.3: {}
|
dotenv@17.2.3: {}
|
||||||
|
|
||||||
drizzle-kit@0.31.8:
|
drizzle-kit@0.31.8:
|
||||||
|
|||||||
Reference in New Issue
Block a user