refactor(telegram-bot): better structure

This commit is contained in:
Neko Ayaka
2025-03-25 16:30:42 +08:00
parent 7a295ed6a4
commit ae4f1b9898
7 changed files with 201 additions and 156 deletions
@@ -15,8 +15,8 @@ export function createAttentionHandler(bot: BotSelf, config: AttentionConfig) {
// Private utility functions
const calculateNewResponseRate = () => {
const timeSinceLastInteraction = (Date.now() - state.stats.lastInteractionTime) / 60000
const decayFactor = Math.max(0, 1 - timeSinceLastInteraction * config.decayRatePerMinute)
const minutesSinceLastInteraction = (Date.now() - state.stats.lastInteractionTime) / 60000
const decayFactor = Math.max(0, 1 - minutesSinceLastInteraction * config.decayRatePerMinute)
// Reset to max if at minimum and new interactions occurred
if (state.currentResponseRate <= config.responseRateMin
@@ -25,8 +25,8 @@ export function createAttentionHandler(bot: BotSelf, config: AttentionConfig) {
}
let newRate = state.currentResponseRate
newRate += state.stats.mentionCount * 0.2 // Mention multiplier
newRate += state.stats.triggerWordCount * 0.2 // Trigger word multiplier
newRate += state.stats.mentionCount * 100 // Mention multiplier
newRate += state.stats.triggerWordCount * 50 // Trigger word multiplier
newRate *= decayFactor
return Math.min(Math.max(newRate, config.responseRateMin), config.responseRateMax)
@@ -11,8 +11,8 @@ import { Bot } from 'grammy'
import { imagineAnAction } from '../../llm/actions'
import { interpretPhotos } from '../../llm/photo'
import { interpretSticker } from '../../llm/sticker'
import { recordMessage } from '../../models'
import { listJoinedChats, recordJoinedChat } from '../../models/chats'
import { createAttentionHandler } from './attention-handler'
import { readMessage } from './loop/read-message'
import { sendMayStructuredMessage } from './utils/message'
@@ -22,50 +22,65 @@ async function isChatIdBotAdmin(chatId: number) {
}
async function handleLoop(state: BotSelf, msgs?: LLMMessage[], forGroupId?: string) {
state.logger.log('handleLoop')
// Create a new abort controller for this loop execution
if (state.currentAbortController) {
state.currentAbortController.abort()
}
state.currentAbortController = new AbortController()
const currentController = state.currentAbortController // Store reference to current controller
if (msgs == null) {
msgs = []
}
try {
try {
const action = await imagineAnAction(state.unreadMessages, state.currentAbortController, msgs)
const action = await imagineAnAction(state.unreadMessages, currentController, msgs)
switch (action.action) {
case 'readMessages':
// eslint-disable-next-line no-case-declarations
let unreadMessagesForThisChat: Message[] | undefined = state.unreadMessages[action.groupId]
if (forGroupId && forGroupId === action.groupId.toString()
&& state.unreadMessages[action.groupId]
&& state.unreadMessages[action.groupId].length > 0) {
&& unreadMessagesForThisChat
&& unreadMessagesForThisChat.length > 0) {
state.logger.log(`Interrupting message processing for group ${action.groupId} - new messages arrived`)
return handleLoop(state)
}
if (Object.keys(state.unreadMessages).length === 0) {
state.logger.log('No unread messages - deleting all unread messages')
state.unreadMessages = {}
break
}
if (action.groupId == null) {
state.logger.log('No group ID - deleting all unread messages')
state.unreadMessages = {}
break
}
if (state.unreadMessages[action.groupId].length === 0) {
if (!Array.isArray(unreadMessagesForThisChat)) {
state.logger.log(`Unread messages for group ${action.groupId} is not an array - converting to array`)
unreadMessagesForThisChat = []
}
if (unreadMessagesForThisChat.length === 0) {
state.logger.log(`No unread messages for group ${action.groupId} - deleting`)
delete state.unreadMessages[action.groupId]
break
}
// eslint-disable-next-line no-case-declarations
const unreadMessages = state.unreadMessages[action.groupId] as Message[]
// // Add attention check before processing action
// // eslint-disable-next-line no-case-declarations
// const shouldRespond = await state.attentionHandler.shouldRespond(forGroupId, unreadMessagesForThisChat)
// Add attention check before processing action
// eslint-disable-next-line no-case-declarations
const shouldRespond = await state.attentionHandler.shouldRespond(forGroupId, unreadMessages)
// if (!shouldRespond.shouldAct) {
// state.logger.withField('reason', shouldRespond.reason).withField('responseRate', shouldRespond.responseRate).log('Skipping message due to attention check')
// state.unreadMessages[action.groupId] = unreadMessagesForThisChat.shift()
// return { break: true }
// }
if (!shouldRespond.shouldAct) {
state.logger.withField('reason', shouldRespond.reason)
.withField('responseRate', shouldRespond.responseRate)
.log('Skipping message due to attention check')
return { break: true }
}
await readMessage(state, action, unreadMessages)
await readMessage(state, action, unreadMessagesForThisChat, currentController)
break
case 'listChats':
msgs.push(message.user(`List of chats:${(await listJoinedChats()).map(chat => `ID:${chat.chat_id}, Name:${chat.chat_name}`).join('\n')}`))
@@ -74,6 +89,10 @@ async function handleLoop(state: BotSelf, msgs?: LLMMessage[], forGroupId?: stri
case 'sendMessage':
await sendMayStructuredMessage(state, action.content, action.groupId)
break
default:
msgs.push(message.user(`The action you sent ${action.action} haven't implemented yet by developer.`))
await handleLoop(state, msgs)
break
}
}
catch (err) {
@@ -81,35 +100,33 @@ async function handleLoop(state: BotSelf, msgs?: LLMMessage[], forGroupId?: stri
}
}
catch (err) {
// Check if this is an abort error, which we can safely ignore
if (err.name === 'AbortError') {
state.logger.log('Operation was aborted due to interruption')
return
}
state.logger.withError(err).log('Error occurred')
}
finally {
// Clean up the abort controller
state.currentAbortController = null
// Only clean up if this is still the current controller
if (state.currentAbortController === currentController) {
state.currentAbortController = null
}
}
}
function loop(state: BotSelf) {
setTimeout(() => {
handleLoop(state)
.then(() => loop(state))
.then(() => {})
.catch((err) => {
if (err.name === 'AbortError') {
// This is expected when we interrupt processing
state.logger.log('Main loop was aborted - restarting loop')
}
else {
state.logger.withError(err).log('Error in main loop')
}
// Always continue the loop
loop(state)
if (err.name === 'AbortError')
state.logger.log('main loop was aborted - restarting loop')
else
state.logger.withError(err).log('error in main loop')
})
}, 5000)
.finally(() => loop(state))
}, 5 * 60 * 1000)
}
function newBotSelf(bot: Bot, logger: Logg): BotSelf {
@@ -125,16 +142,16 @@ function newBotSelf(bot: Bot, logger: Logg): BotSelf {
attentionHandler: undefined,
}
botSelf.attentionHandler = createAttentionHandler(botSelf, {
initialResponseRate: 0.3,
responseRateMin: 0.1,
responseRateMax: 0.8,
cooldownMs: 30000, // 30 seconds
triggerWords: ['hey bot', 'hello bot'],
ignoreWords: ['ignore me'],
decayRatePerMinute: 0.1,
decayCheckIntervalMs: 20000,
})
// botSelf.attentionHandler = createAttentionHandler(botSelf, {
// initialResponseRate: 0.3,
// responseRateMin: 0.2,
// responseRateMax: 1,
// cooldownMs: 5000, // 30 seconds
// triggerWords: ['ReLU', 'relu', 'RELU', 'Relu', '热卤'],
// ignoreWords: ['ignore me'],
// decayRatePerMinute: 0.05,
// decayCheckIntervalMs: 20000,
// })
return botSelf
}
@@ -167,24 +184,29 @@ async function processMessageQueue(state: BotSelf) {
if (nextMsg.status === 'ready') {
await recordJoinedChat(nextMsg.message.chat.id.toString(), nextMsg.message.chat.title)
if (state.unreadMessages[nextMsg.message.chat.id] == null) {
state.unreadMessages[nextMsg.message.chat.id] = []
await recordMessage(state.bot.botInfo, nextMsg.message)
let unreadMessagesForThisChat = state.unreadMessages[nextMsg.message.chat.id]
if (unreadMessagesForThisChat == null) {
state.logger.withField('chatId', nextMsg.message.chat.id).log('unread messages for this chat is null - creating empty array')
unreadMessagesForThisChat = []
}
if (!Array.isArray(unreadMessagesForThisChat)) {
state.logger.withField('chatId', nextMsg.message.chat.id).log('unread messages for this chat is not an array - converting to array')
unreadMessagesForThisChat = []
}
state.unreadMessages[nextMsg.message.chat.id].push(nextMsg.message)
if (state.unreadMessages[nextMsg.message.chat.id].length > 20) {
state.unreadMessages[nextMsg.message.chat.id] = state.unreadMessages[nextMsg.message.chat.id].slice(-20)
unreadMessagesForThisChat.push(nextMsg.message)
if (unreadMessagesForThisChat.length > 20) {
unreadMessagesForThisChat = unreadMessagesForThisChat.slice(-20)
}
// Check if we're currently processing this chat group
if (state.currentAbortController
&& state.currentTask
&& state.unreadMessages[nextMsg.message.chat.id].length > 0) {
// Interrupt the current processing
state.currentAbortController.abort()
state.logger.log(`Interrupting due to new message in chat ${nextMsg.message.chat.id}`)
}
state.unreadMessages[nextMsg.message.chat.id] = unreadMessagesForThisChat
// Trigger immediate processing when messages are ready
handleLoop(state, [], nextMsg.message.chat.id.toString())
state.messageQueue.shift()
}
}
@@ -261,11 +283,9 @@ export async function startTelegramBot() {
}
await bot.init()
log.withField('bot_username', bot.botInfo.username).log('Authorized bot')
log.withField('bot_username', bot.botInfo.username).log('bot initialized')
bot.start({
drop_pending_updates: true,
})
bot.start()
try {
loop(state)
@@ -12,14 +12,19 @@ import { chatMessageToOneLine, telegramMessageToOneLine } from '../../../models/
import { systemPrompt } from '../../../prompts/system-v1'
import { sendMayStructuredMessage } from '../utils/message'
export async function readMessage(state: BotSelf, action: ReadMessagesAction, unreadMessages: Message[]): Promise<{
loop?: boolean
break?: boolean
}> {
export async function readMessage(
state: BotSelf,
action: ReadMessagesAction,
unreadMessages: Message[],
abortController: AbortController,
): Promise<{
loop?: boolean
break?: boolean
}> {
const logger = useLogg('readMessage').useGlobalConfig()
const lastNMessages = await findLastNMessages(action.groupId, 30)
const lastNMessagesOneliner = lastNMessages.map(msg => chatMessageToOneLine(msg)).join('\n')
const lastNMessagesOneliner = lastNMessages.map(msg => chatMessageToOneLine(state.bot, msg)).join('\n')
logger.withField('number_of_last_n_messages', lastNMessages.length).log('Successfully found last N messages')
@@ -31,7 +36,7 @@ export async function readMessage(state: BotSelf, action: ReadMessagesAction, un
apiKey: env.EMBEDDING_API_KEY!,
model: env.EMBEDDING_MODEL!,
input: msg.text || msg.caption || '',
abortSignal: state.currentAbortController.signal,
abortSignal: abortController.signal,
})
return {
@@ -48,7 +53,7 @@ export async function readMessage(state: BotSelf, action: ReadMessagesAction, un
const unreadHistoryMessageOneliner = unreadHistoryMessages.join('\n')
state.unreadMessages[action.groupId] = []
const relevantChatMessages = await findRelevantMessages(unreadHistoryMessagesEmbedding)
const relevantChatMessages = await findRelevantMessages(state.bot, unreadHistoryMessagesEmbedding)
const relevantChatMessagesOneliner = (await Promise.all(relevantChatMessages.map(async msgs => msgs.join('\n')))).join('\n')
logger.withField('number_of_relevant_chat_messages', relevantChatMessages.length).log('Successfully composed relevant chat messages')
@@ -62,13 +67,18 @@ export async function readMessage(state: BotSelf, action: ReadMessagesAction, un
+ 'Last 30 messages:\n'
+ `${lastNMessagesOneliner || 'No messages'}`
+ '\n'
+ 'I helped you searched these relevant chat messages may help you recall the memories:'
+ 'I helped you searched these relevant chat messages may help you recall the memories:\n'
+ `${relevantChatMessagesOneliner || 'No relevant messages'}`
+ '\n'
+ 'All the messages you requested to read:'
+ 'All the messages you requested to read:\n'
+ `${unreadHistoryMessageOneliner || 'No messages'}`
+ '\n'
+ 'Choose your action. Would you like to say something? Or ignore?',
+ 'Based on your personalities, imaging you have your own choice and interest over the world, '
+ 'giving the above context and chat history, would you like to participate in the conversation '
+ 'about the topic? Or will you aggressively diss or piss off about the opinions of others?\n'
+ 'Feel free to ignore by just sending an empty array (i.e. []).'
+ '\n'
+ 'Choose your action.',
),
)
@@ -80,7 +90,7 @@ export async function readMessage(state: BotSelf, action: ReadMessagesAction, un
baseURL: env.LLM_API_BASE_URL!,
model: env.LLM_MODEL!,
messages,
abortSignal: state.currentAbortController.signal,
abortSignal: abortController.signal,
})
response.text = response.text
@@ -25,7 +25,7 @@ export function parseMayStructuredMessage(responseText: string) {
return
}
return array
return array.filter(Boolean)
}
return [responseText]
@@ -39,6 +39,7 @@ export async function sendMayStructuredMessage(
const chat = (await listJoinedChats()).find((chat) => {
return chat.chat_id === groupId
})
state.logger.withField('chat', chat).log('Chat found')
if (!chat) {
state.logger.withField('groupId', groupId).log('Chat not found')
return
+70 -68
View File
@@ -14,76 +14,78 @@ export async function imagineAnAction(unreadMessages: Record<string, Message[]>,
const logger = useLogg('imagineAnAction').useGlobalConfig()
if (agentMessages == null) {
agentMessages = message.messages(
message.system(''
+ `${systemPrompt()}`
+ '\n'
+ 'I am one of your system component, called Ticking system, which is responsible to keep track of the time, and '
+ 'help you schedule, retain focus, and keep eyes on different tasks, and ideas you have.'
+ '\n'
+ 'Please remember, I am not a human, I am a system that part of you. You should return system/machine readable '
+ 'messages to me, so I can understand and process them correctly.'
+ '\n'
+ 'Now, please, based on the following context, choose a right action from the listing of the tools you want to '
+ 'take next:',
),
message.system(
[
{
description: 'List all available chats, best to do before you want to send a message to a chat.',
example: { action: 'listChats' },
},
{
description: 'Send a message to a specific chat group. If you want to express anything to anyone or your friends in group, you can use this action.',
example: { action: 'sendMessage', content: '<content>', groupId: 'id of chat to send to' },
},
{
description: 'Read unread messages from a specific chat group. If you want to read the unread messages from a specific chat group, you can use this action.',
example: { action: 'readMessages', groupId: 'id of chat to send to' },
},
{
description: 'Continue the current task, which means to keep your current state unchanged, I\'ll ask you again in next tick.',
example: { action: 'continue' },
},
{
description: 'Take a break, which means to clear out ongoing tasks, but keep the short-term memory, and I\'ll ask you again in next tick.',
example: { action: 'break' },
},
{
description: 'Sleep, which means to clear out ongoing tasks, and clear out the working memory, and I\'ll ask you again in next tick.',
example: { action: 'sleep' },
},
{
description: 'By giving references to contexts, come up ideas to record in long-term memory.',
example: { action: 'comeUpIdeas', ideas: ['I want to tell everyone a story of myself', 'I want to google how to make a AI like me'] },
},
{
description: 'By giving references to contexts, come up goals with deadline and priority to record in long-term memory.',
example: { action: 'comeUpGoals', goals: [{ text: 'Learn to play Minecraft', deadline: '2025-05-01 23:59:59', priority: 6 }, { text: 'Learn anime of this season', deadline: '2025-01-08 23:59:59', priority: 9 }] },
},
// { example: { action: 'lookupShortTermMemory', query: '', category: 'chat or self' }, description: 'Look up the short-term, which means to recall the short-term memory from memory component.' },
// { example: { action: 'lookupLongTermMemory', query: '', category: 'chat or self' }, description: 'Look up the long-term, which means to recall the long-term memory from memory component.' },
// { example: { action: 'memorizeShortMemory', content: '<content>', tags: ['keyword tag'] }, description: 'Memorize to short-term memory, which means to append things the short-term memory which will be included for a while, but will be eventually forgot.' },
// { example: { action: 'memorizeLongMemory', content: '<content>', tags: ['keyword tag'] }, description: 'Memorize to long-term memory, which means to append things the long-term memory which will be included for a long time, and hard to forget.' },
// { example: { action: 'forgetShortTermMemory', where: { id: '<id of memory>' } }, description: 'Remove specific short-term memory entry from the memory component.' },
// { example: { action: 'forgetLongTermMemory', where: { id: '<id of memory>' } }, description: 'Remove specific long-term memory entry from the memory component.' },
// { example: { action: 'searchGoogle', query: '<query>' }, description: 'Search Google with the query.' },
]
.map((item, index) => `${index}: ${JSON.stringify(item.example)}: ${item.description}`)
.join('\n'),
),
message.system(''
+ `Now the time is: ${new Date().toLocaleString()}. `
+ `You have total ${Object.values(unreadMessages).reduce((acc, cur) => acc + cur.length, 0)} unread messages.`
+ '\n'
+ 'Unread messages count are:\n'
+ `${Object.entries(unreadMessages).map(([key, value]) => `ID:${key}, Unread message count:${value.length}`).join('\n')}`
+ '',
),
message.user('What do you want to do? Respond with the action and parameters you choose in JSON only, without any explanation and markups'),
)
agentMessages = []
}
agentMessages.push(
message.system(''
+ `${systemPrompt()}`
+ '\n'
+ 'I am one of your system component, called Ticking system, which is responsible to keep track of the time, and '
+ 'help you schedule, retain focus, and keep eyes on different tasks, and ideas you have.'
+ '\n'
+ 'Please remember, I am not a human, I am a system that part of you. You should return system/machine readable '
+ 'messages to me, so I can understand and process them correctly.'
+ '\n'
+ 'Now, please, based on the following context, choose a right action from the listing of the tools you want to '
+ 'take next:',
),
message.system(
[
{
description: 'List all available chats, best to do before you want to send a message to a chat.',
example: { action: 'listChats' },
},
{
description: 'Send a message to a specific chat group. If you want to express anything to anyone or your friends in group, you can use this action.',
example: { action: 'sendMessage', content: '<content>', groupId: 'id of chat to send to' },
},
{
description: 'Read unread messages from a specific chat group. If you want to read the unread messages from a specific chat group, you can use this action.',
example: { action: 'readMessages', groupId: 'id of chat to send to' },
},
{
description: 'Continue the current task, which means to keep your current state unchanged, I\'ll ask you again in next tick.',
example: { action: 'continue' },
},
{
description: 'Take a break, which means to clear out ongoing tasks, but keep the short-term memory, and I\'ll ask you again in next tick.',
example: { action: 'break' },
},
{
description: 'Sleep, which means to clear out ongoing tasks, and clear out the working memory, and I\'ll ask you again in next tick.',
example: { action: 'sleep' },
},
{
description: 'By giving references to contexts, come up ideas to record in long-term memory.',
example: { action: 'comeUpIdeas', ideas: ['I want to tell everyone a story of myself', 'I want to google how to make a AI like me'] },
},
{
description: 'By giving references to contexts, come up goals with deadline and priority to record in long-term memory.',
example: { action: 'comeUpGoals', goals: [{ text: 'Learn to play Minecraft', deadline: '2025-05-01 23:59:59', priority: 6 }, { text: 'Learn anime of this season', deadline: '2025-01-08 23:59:59', priority: 9 }] },
},
// { example: { action: 'lookupShortTermMemory', query: '', category: 'chat or self' }, description: 'Look up the short-term, which means to recall the short-term memory from memory component.' },
// { example: { action: 'lookupLongTermMemory', query: '', category: 'chat or self' }, description: 'Look up the long-term, which means to recall the long-term memory from memory component.' },
// { example: { action: 'memorizeShortMemory', content: '<content>', tags: ['keyword tag'] }, description: 'Memorize to short-term memory, which means to append things the short-term memory which will be included for a while, but will be eventually forgot.' },
// { example: { action: 'memorizeLongMemory', content: '<content>', tags: ['keyword tag'] }, description: 'Memorize to long-term memory, which means to append things the long-term memory which will be included for a long time, and hard to forget.' },
// { example: { action: 'forgetShortTermMemory', where: { id: '<id of memory>' } }, description: 'Remove specific short-term memory entry from the memory component.' },
// { example: { action: 'forgetLongTermMemory', where: { id: '<id of memory>' } }, description: 'Remove specific long-term memory entry from the memory component.' },
// { example: { action: 'searchGoogle', query: '<query>' }, description: 'Search Google with the query.' },
]
.map((item, index) => `${index}: ${JSON.stringify(item.example)}: ${item.description}`)
.join('\n'),
),
message.system(''
+ `Now the time is: ${new Date().toLocaleString()}. `
+ `You have total ${Object.values(unreadMessages).reduce((acc, cur) => acc + cur.length, 0)} unread messages.`
+ '\n'
+ 'Unread messages count are:\n'
+ `${Object.entries(unreadMessages).map(([key, value]) => `ID:${key}, Unread message count:${value.length}`).join('\n')}`
+ '',
),
message.user('What do you want to do? Respond with the action and parameters you choose in JSON only, without any explanation and markups'),
)
const res = await generateText({
apiKey: env.LLM_API_KEY!,
baseURL: env.LLM_API_BASE_URL!,
@@ -1,5 +1,6 @@
import type { EmbedResult } from '@xsai/embed'
import type { SQL } from 'drizzle-orm'
import type { Bot } from 'grammy'
import type { Message, UserFromGetMe } from 'grammy/types'
import { env } from 'node:process'
@@ -71,15 +72,17 @@ export async function recordMessage(botInfo: UserFromGetMe, message: Message) {
}
export async function findLastNMessages(chatId: string, n: number) {
return await useDrizzle()
const res = await useDrizzle()
.select()
.from(chatMessagesTable)
.where(eq(chatMessagesTable.in_chat_id, chatId))
.orderBy(desc(chatMessagesTable.created_at))
.limit(n)
return res.reverse()
}
export async function findRelevantMessages(unreadHistoryMessagesEmbedding: { embedding: number[], message: Message }[]) {
export async function findRelevantMessages(bot: Bot, unreadHistoryMessagesEmbedding: { embedding: number[], message: Message }[]) {
const db = useDrizzle()
const contextWindowSize = 5 // Number of messages to include before and after
const logger = useLogg('findRelevantMessages').useGlobalConfig()
@@ -180,7 +183,7 @@ export async function findRelevantMessages(unreadHistoryMessagesEmbedding: { emb
logger.withField('number_of_context_messages', contextMessages.length).log('Combined context messages')
const contextMessagesOneliner = (await Promise.all(contextMessages.map(m => chatMessageToOneLine(m))))
const contextMessagesOneliner = (await Promise.all(contextMessages.map(m => chatMessageToOneLine(bot, m))))
return `One of the relevant message along with the context:\n${contextMessagesOneliner}`
}),
)
+19 -10
View File
@@ -5,12 +5,18 @@ import type { chatMessagesTable } from '../db/schema'
import { findPhotoDescription } from './photos'
import { findStickerDescription } from './stickers'
export function chatMessageToOneLine(message: Omit<typeof chatMessagesTable.$inferSelect, 'content_vector_1536' | 'content_vector_768' | 'content_vector_1024'>) {
if (message.is_reply) {
return `${new Date(message.created_at).toLocaleString()} User ${message.from_name} replied to ${message.reply_to_name} in same group said: ${message.content}`
export function chatMessageToOneLine(bot: Bot, message: Omit<typeof chatMessagesTable.$inferSelect, 'content_vector_1536' | 'content_vector_768' | 'content_vector_1024'>) {
let userDisplayName = `User [${message.from_name}]`
if (bot.botInfo.id.toString() === message.from_id) {
userDisplayName = 'Yourself'
}
return `${new Date(message.created_at).toLocaleString()} User ${message.from_name} sent in same group said: ${message.content}`
if (message.is_reply) {
return `${new Date(message.created_at).toLocaleString()} ${userDisplayName} replied to ${message.reply_to_name} in same group said: ${message.content}`
}
return `${new Date(message.created_at).toLocaleString()} ${userDisplayName} sent in same group said: ${message.content}`
}
export async function telegramMessageToOneLine(bot: Bot, message: Message) {
@@ -18,24 +24,27 @@ export async function telegramMessageToOneLine(bot: Bot, message: Message) {
return ''
}
const userDisplayName = `${message.from.first_name} ${message.from.last_name} (${message.from.username})`
let userDisplayName = `User [${message.from.first_name} ${message.from.last_name} (${message.from.username})]`
if (bot.botInfo.id.toString() === message.from.id.toString()) {
userDisplayName = 'Yourself'
}
if (message.sticker != null) {
const description = await findStickerDescription(message.sticker.file_id)
return `${new Date(message.date * 1000).toLocaleString()} User [${userDisplayName}] sent in Group [${message.chat.title}] a sticker, and description of the sticker is ${description}`
return `${new Date(message.date * 1000).toLocaleString()} ${userDisplayName} sent in Group [${message.chat.title}] a sticker, and description of the sticker is ${description}`
}
if (message.photo != null) {
const description = await findPhotoDescription(message.photo[0].file_id)
return `${new Date(message.date * 1000).toLocaleString()} User [${userDisplayName}] sent in Group [${message.chat.title}] a photo, and description of the photo is ${description}`
return `${new Date(message.date * 1000).toLocaleString()} ${userDisplayName} sent in Group [${message.chat.title}] a photo, and description of the photo is ${description}`
}
if (message.reply_to_message != null) {
if (bot.botInfo.username === message.reply_to_message.from.username) {
return `${new Date(message.date * 1000).toLocaleString()} User [${userDisplayName}] replied to your previous message [${message.reply_to_message.text}] in Group [${message.chat.title}] said: ${message.text}`
return `${new Date(message.date * 1000).toLocaleString()} ${userDisplayName} replied to your previous message ${message.reply_to_message.text || message.caption} in Group [${message.chat.title}] said: ${message.text}`
}
else {
return `${new Date(message.date * 1000).toLocaleString()} User [${userDisplayName}] replied to [${message.reply_to_message.from.first_name}] in Group [${message.chat.title}] said: ${message.text}`
return `${new Date(message.date * 1000).toLocaleString()} ${userDisplayName} replied to User [${message.reply_to_message.from.first_name} ${message.reply_to_message.from.last_name} (${message.reply_to_message.from.username})] in Group [${message.chat.title}] said: ${message.text}`
}
}
return `${new Date(message.date * 1000).toLocaleString()} User [${userDisplayName}] sent in Group [${message.chat.title}] said: ${message.text}`
return `${new Date(message.date * 1000).toLocaleString()} ${userDisplayName} sent in Group [${message.chat.title}] said: ${message.text}`
}