Files
boltun-ai/src/services/chat-member-service.ts
yunogasai e812f1b1e2 Упрощение: общие хелперы, команды и упоминания по entities, фото только для моделей со зрением
- Одна formatUtc и один displayName(profile) вместо копий в разных слоях
- commandOf/mentionsUser/textWithoutMention работают по entities для текста и подписей;
  убраны askInCaption, regex-поиск упоминаний и отдельный composer.command("ask")
- Фото скачиваются только если модель их видит; пустой вопрос с картинкой формулирует промпт
- Поддержка фото задаётся явно для моделей по умолчанию, для своей AI_MODEL — через AI_IMAGE_INPUT
- Активность участников считается по сохранённой истории, а не отдельными счётчиками;
  один хендлер для message и edited_message, упоминания не пишутся дважды
- SQLite: synchronous = NORMAL для WAL
- Кнопки не-админов отклоняются через bot.drop(isAdmin)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 20:10:56 +02:00

78 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { ChatMemberRepository } from "../repositories/chat-member-repository.js";
import type { ChatMember, MemberProfile } from "../types.js";
interface ChatMemberServiceDeps {
chatMemberRepository: ChatMemberRepository;
}
export interface MentionQuery {
/** Ники из `@username` без собаки. */
usernames: string[];
/** ID людей, упомянутых по имени-ссылке (у них может не быть ника). */
userIds: number[];
}
export interface ResolvedMentions {
members: ChatMember[];
/** Ники, которых бот в этом чате не встречал. */
unknownUsernames: string[];
}
/** Регистр и «ё» не важны: «Алёна» найдётся по «алена». */
const normalize = (value: string): string => value.toLocaleLowerCase("ru").replaceAll("ё", "е").trim();
const words = (member: ChatMember): string[] =>
[member.firstName, member.lastName ?? "", member.username ?? ""].flatMap((part) => normalize(part).split(/\s+/));
const usernameIs = (username: string) => (member: ChatMember) =>
member.username !== null && normalize(member.username) === normalize(username);
/** Каждое слово запроса — начало одного из слов имени или ника: «петр» найдёт «Петров». */
const nameMatches = (query: string) => (member: ChatMember) => {
const memberWords = words(member);
return normalize(query)
.split(/\s+/)
.filter(Boolean)
.every((token) => memberWords.some((word) => word.startsWith(token)));
};
export const createChatMemberService = ({ chatMemberRepository }: ChatMemberServiceDeps) => {
/** Запоминаем актуальные имя и ник: автор сообщения или человек, упомянутый по ссылке. */
const remember = (chatId: number, profile: MemberProfile): void => chatMemberRepository.upsert(chatId, profile);
/** Участники с активностью по сохранённой истории; сначала — недавно писавшие. */
const list = (chatId: number): ChatMember[] => chatMemberRepository.listByChat(chatId);
/** Поиск по нику (с @ или без) или по имени/фамилии. */
const search = (chatId: number, query: string): ChatMember[] => {
const cleaned = query.trim().replace(/^@/, "");
if (!cleaned) return [];
const members = list(chatId);
const byUsername = members.filter(usernameIs(cleaned));
return byUsername.length > 0 ? byUsername : members.filter(nameMatches(cleaned));
};
/** Превращает упоминания из вопроса в известных участников чата. */
const resolveMentions = (chatId: number, { usernames, userIds }: MentionQuery): ResolvedMentions => {
if (usernames.length === 0 && userIds.length === 0) return { members: [], unknownUsernames: [] };
const members = list(chatId);
const unknownUsernames = usernames.filter((username) => !members.some(usernameIs(username)));
const mentioned = members.filter(
(member) => userIds.includes(member.userId) || usernames.some((username) => usernameIs(username)(member)),
);
return { members: mentioned, unknownUsernames };
};
return { remember, list, search, resolveMentions };
};
export type ChatMemberService = ReturnType<typeof createChatMemberService>;