Q2TG/src/services/ConfigService.ts

326 lines
12 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 Telegram from '../client/Telegram';
import { Friend, FriendInfo, Group, GroupInfo } from 'icqq';
import { Button } from 'telegram/tl/custom/button';
import { getLogger, Logger } from 'log4js';
import { getAvatar } from '../utils/urls';
import { CustomFile } from 'telegram/client/uploads';
import db from '../models/db';
import { Api, utils } from 'telegram';
import OicqClient from '../client/OicqClient';
import { md5 } from '../utils/hashing';
import TelegramChat from '../client/TelegramChat';
import Instance from '../models/Instance';
import getAboutText from '../utils/getAboutText';
import random from '../utils/random';
const DEFAULT_FILTER_ID = 114; // 514
export default class ConfigService {
private owner: Promise<TelegramChat>;
private readonly log: Logger;
constructor(private readonly instance: Instance,
private readonly tgBot: Telegram,
private readonly oicq: OicqClient) {
this.log = getLogger(`ConfigService - ${instance.id}`);
this.owner = tgBot.getChat(this.instance.owner);
}
private getAssociateLink(roomId: number) {
return `https://t.me/${this.tgBot.me.username}?startgroup=${roomId}`;
}
// region 打开添加关联的菜单
// 开始添加转发群组流程
public async addGroup() {
const qGroups = Array.from(this.oicq.gl).map(e => e[1])
.filter(it => !this.instance.forwardPairs.find(-it.group_id));
const buttons = qGroups.map(e =>
this.instance.workMode === 'personal' ?
[Button.inline(
`${e.group_name} (${e.group_id})`,
this.tgBot.registerCallback(() => this.onSelectChatPersonal(e)),
)] :
[Button.url(
`${e.group_name} (${e.group_id})`,
this.getAssociateLink(-e.group_id),
)]);
await (await this.owner).createPaginatedInlineSelector(
'选择 QQ 群组' + (this.instance.workMode === 'group' ? '\n然后选择在 TG 中的群组' : ''), buttons);
}
// 只可能是 personal 运行模式
public async addFriend() {
const classes = Array.from(this.oicq.classes);
const friends = Array.from(this.oicq.fl).map(e => e[1]);
classes.sort((a, b) => {
if (a[1] < b[1]) {
return -1;
}
else if (a[1] == b[1]) {
return 0;
}
else {
return 1;
}
});
await (await this.owner).createPaginatedInlineSelector('选择分组', classes.map(e => [
Button.inline(e[1], this.tgBot.registerCallback(
() => this.openFriendSelection(friends.filter(f => f.class_id === e[0]), e[1]),
)),
]));
}
private async openFriendSelection(clazz: FriendInfo[], name: string) {
clazz = clazz.filter(them => !this.instance.forwardPairs.find(them.user_id));
await (await this.owner).createPaginatedInlineSelector(`选择 QQ 好友\n分组${name}`, clazz.map(e => [
Button.inline(`${e.remark || e.nickname} (${e.user_id})`, this.tgBot.registerCallback(
() => this.onSelectChatPersonal(e),
)),
]));
}
private async onSelectChatPersonal(info: FriendInfo | GroupInfo) {
const roomId = 'user_id' in info ? info.user_id : -info.group_id;
const name = 'user_id' in info ? info.remark || info.nickname : info.group_name;
const entity = this.oicq.getChat(roomId);
const avatar = await getAvatar(roomId);
const message = await (await this.owner).sendMessage({
message: await getAboutText(entity, true),
buttons: [
[Button.inline('自动创建群组', this.tgBot.registerCallback(
async () => {
await message.delete({ revoke: true });
this.createGroupAndLink(roomId, name);
}))],
[Button.url('手动选择现有群组', this.getAssociateLink(roomId))],
],
file: new CustomFile('avatar.png', avatar.length, '', avatar),
});
}
public async addExact(gin: number) {
const group = this.oicq.gl.get(gin);
let avatar: Buffer;
try {
avatar = await getAvatar(-group.group_id);
}
catch (e) {
avatar = null;
this.log.error(`加载 ${group.group_name} (${gin}) 的头像失败`, e);
}
const message = `${group.group_name}\n${group.group_id}\n${group.member_count} 名成员`;
await (await this.owner).sendMessage({
message,
file: avatar ? new CustomFile('avatar.png', avatar.length, '', avatar) : undefined,
buttons: Button.url('关联 Telegram 群组', this.getAssociateLink(-group.group_id)),
});
}
// endregion
/**
*
* @param room
* @param title
* @param status 传入 false 的话就不显示状态信息,可以传入一条已有消息覆盖
* @param chat
*/
public async createGroupAndLink(room: number | Friend | Group, title?: string, status: boolean | Api.Message = true, chat?: TelegramChat) {
this.log.info(`创建群组并关联:${room}`);
if (typeof room === 'number') {
room = this.oicq.getChat(room);
}
if (!title) {
// TS 这边不太智能
if (room instanceof Friend) {
title = room.remark || room.nickname;
}
else {
title = room.name;
}
}
let isFinish = false;
try {
let errorMessage = '';
// 状态信息
if (status === true) {
const avatar = await getAvatar(room);
const statusReceiver = chat ? await this.tgBot.getChat(chat.id) : await this.owner;
status = await statusReceiver.sendMessage({
message: '正在创建 Telegram 群…',
file: new CustomFile('avatar.png', avatar.length, '', avatar),
});
}
else if (status instanceof Api.Message) {
await status.edit({ text: '正在创建 Telegram 群…', buttons: Button.clear() });
}
/* if (!chat) {
// 创建群聊,拿到的是 user 的 chat
chat = await this.tgUser.createChat(title, await getAboutText(room, false));
// 添加机器人
status && await status.edit({ text: '正在添加机器人…' });
await chat.inviteMember(this.tgBot.me.id);
} */
// 设置管理员
status && await status.edit({ text: '正在设置管理员…' });
await chat.setAdmin(this.tgBot.me.username);
// 添加到 Filter
/* try {
status && await status.edit({ text: '正在将群添加到文件夹…' });
const dialogFilters = await this.tgUser.getDialogFilters() as Api.DialogFilter[];
const filter = dialogFilters.find(e => e.id === DEFAULT_FILTER_ID);
if (filter) {
filter.includePeers.push(utils.getInputPeer(chat));
await this.tgUser.updateDialogFilter({
id: DEFAULT_FILTER_ID,
filter,
});
}
}
catch (e) {
errorMessage += `\n添加到文件夹失败${e.message}`;
} */
// 关闭【添加成员】快捷条
try {
status && await status.edit({ text: '正在关闭【添加成员】快捷条…' });
await chat.hidePeerSettingsBar();
}
catch (e) {
errorMessage += `\n关闭【添加成员】快捷条失败${e.message}`;
}
// 关联写入数据库
const chatForBot = await this.tgBot.getChat(chat.id);
status && await status.edit({ text: '正在写数据库…' });
const dbPair = await this.instance.forwardPairs.add(room, chatForBot, chat);
isFinish = true;
// 更新头像
try {
status && await status.edit({ text: '正在更新头像…' });
const avatar = await getAvatar(room);
const avatarHash = md5(avatar);
await chatForBot.setProfilePhoto(avatar);
await db.avatarCache.create({
data: { forwardPairId: dbPair.id, hash: avatarHash },
});
}
catch (e) {
errorMessage += `\n更新头像失败${e.message}`;
}
// 完成
if (status) {
await status.edit({ text: '正在获取链接…' });
const { link } = await chat.getInviteLink() as Api.ChatInviteExported;
await status.edit({
text: '创建完成!' + (errorMessage ? '但发生以下错误' + errorMessage : ''),
buttons: Button.url('打开', link),
});
}
}
catch (e) {
this.log.error('创建群组并关联失败', e);
await (await this.owner).sendMessage(`创建群组并关联${isFinish ? '成功了但没完全成功' : '失败'}\n<code>${e}</code>`);
}
}
public async promptNewQqChat(chat: Group | Friend) {
const message = await (await this.owner).sendMessage({
message: '你' +
(chat instanceof Group ? '加入了一个新的群' : '增加了一' + random.pick('位', '个', '只', '头') + '好友') +
'\n' +
await getAboutText(chat, true) + '\n' +
'要创建关联群吗',
buttons: Button.inline('创建', this.tgBot.registerCallback(async () => {
await message.delete({ revoke: true });
this.createGroupAndLink(chat, chat instanceof Group ? chat.name : chat.remark || chat.nickname);
})),
});
return message;
}
public async createLinkGroup(qqRoomId: number, tgChatId: number) {
if (this.instance.workMode === 'group') {
try {
const qGroup = this.oicq.getChat(qqRoomId) as Group;
const tgChat = await this.tgBot.getChat(tgChatId);
const tgUserChat = await this.tgUser.getChat(tgChatId);
await this.instance.forwardPairs.add(qGroup, tgChat, tgUserChat);
await tgChat.sendMessage(`QQ群${qGroup.name} (<code>${qGroup.group_id}</code>)已与 ` +
`Telegram 群 ${(tgChat.entity as Api.Channel).title} (<code>${tgChatId}</code>)关联`);
if (!(tgChat.entity instanceof Api.Channel)) {
// TODO 添加一个转换为超级群组的方法链接
await tgChat.sendMessage({
message: '请注意,这个群不是超级群组。一些功能,比如说同步撤回,可能会工作不正常。建议将此群组转换为超级群组',
linkPreview: false,
});
}
}
catch (e) {
this.log.error(e);
await (await this.owner).sendMessage(`错误:<code>${e}</code>`);
}
}
else {
await (await this.owner).sendMessage(`跳过`);
/* const chat = await this.tgUser.getChat(tgChatId);
await this.createGroupAndLink(qqRoomId, undefined, true, chat); */
}
}
// 创建 QQ 群组的文件夹
/* public async setupFilter() {
const result = await this.tgUser.getDialogFilters() as Api.DialogFilter[];
let filter = result.find(e => e.id === DEFAULT_FILTER_ID);
if (!filter) {
this.log.info('创建 TG 文件夹');
// 要自己计算新的 id随意 id 也是可以的
// https://github.com/morethanwords/tweb/blob/7d646bc9a87d943426d831f30b69d61b743f51e0/src/lib/storages/filters.ts#L251
// 创建
filter = new Api.DialogFilter({
id: DEFAULT_FILTER_ID,
title: 'QQ',
pinnedPeers: [
(await this.tgUser.getChat(this.tgBot.me.username)).inputPeer,
],
includePeers: [],
excludePeers: [],
emoticon: '🐧',
});
let errorText = '设置文件夹失败';
try {
const isSuccess = await this.tgUser.updateDialogFilter({
id: DEFAULT_FILTER_ID,
filter,
});
if (!isSuccess) {
this.log.error(errorText);
await (await this.owner).sendMessage(errorText);
}
}
catch (e) {
this.log.error(errorText, e);
await (await this.owner).sendMessage(errorText + `\n<code>${e}</code>`);
}
}
} */
/* public async migrateAllChats() {
const dbPairs = await db.forwardPair.findMany();
for (const forwardPair of dbPairs) {
const chatForUser = await this.tgUser.getChat(Number(forwardPair.tgChatId));
if (chatForUser.entity instanceof Api.Chat) {
this.log.info('升级群组 ', chatForUser.id);
await chatForUser.migrate();
}
}
} */
}