mirror of https://github.com/Nofated095/Q2TG.git
refactor: 使用实例管理 Controller 生命周期,并将配置存储在数据库。实例可并行运行
This commit is contained in:
parent
415cba38f5
commit
315a2cd544
|
@ -11,40 +11,55 @@ datasource db {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String @unique
|
name String @unique
|
||||||
dcId Int?
|
dcId Int?
|
||||||
port Int?
|
port Int?
|
||||||
serverAddress String?
|
serverAddress String?
|
||||||
authKey Bytes?
|
authKey Bytes?
|
||||||
entities Entity[]
|
entities Entity[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Entity {
|
model Entity {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
// 源代码里面大概支持 string 和 BigInteger,不如先全都存 String
|
// 源代码里面大概支持 string 和 BigInteger,不如先全都存 String
|
||||||
entityId String
|
entityId String
|
||||||
sessionId Int
|
sessionId Int
|
||||||
session Session @relation(fields: [sessionId], references: [id])
|
session Session @relation(fields: [sessionId], references: [id])
|
||||||
hash String?
|
hash String?
|
||||||
username String?
|
username String?
|
||||||
phone String?
|
phone String?
|
||||||
name String?
|
name String?
|
||||||
|
|
||||||
@@unique([entityId, sessionId])
|
@@unique([entityId, sessionId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Instance {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
owner BigInt @default(0)
|
||||||
|
qqUin BigInt @default(0)
|
||||||
|
qqPassword String @default("")
|
||||||
|
qqPlatform Int @default(0)
|
||||||
|
workMode String @default("")
|
||||||
|
isSetup Boolean @default(false)
|
||||||
|
botToken String?
|
||||||
|
Message Message[]
|
||||||
|
ForwardPair ForwardPair[]
|
||||||
|
}
|
||||||
|
|
||||||
model Message {
|
model Message {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
qqRoomId BigInt @db.BigInt
|
qqRoomId BigInt @db.BigInt
|
||||||
qqSenderId BigInt @db.BigInt
|
qqSenderId BigInt @db.BigInt
|
||||||
time Int
|
time Int
|
||||||
brief String?
|
brief String?
|
||||||
seq Int
|
seq Int
|
||||||
rand Int
|
rand Int
|
||||||
pktnum Int
|
pktnum Int
|
||||||
tgChatId BigInt @db.BigInt
|
tgChatId BigInt @db.BigInt
|
||||||
tgMsgId Int
|
tgMsgId Int
|
||||||
|
instanceId Int @default(0)
|
||||||
|
instance Instance @relation(fields: [instanceId], references: [id])
|
||||||
|
|
||||||
@@unique([qqRoomId, qqSenderId, seq, rand, pktnum, time])
|
@@unique([qqRoomId, qqSenderId, seq, rand, pktnum, time])
|
||||||
@@unique([tgChatId, tgMsgId])
|
@@unique([tgChatId, tgMsgId])
|
||||||
|
@ -52,29 +67,31 @@ model Message {
|
||||||
|
|
||||||
model ForwardPair {
|
model ForwardPair {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
qqRoomId BigInt @unique @db.BigInt
|
qqRoomId BigInt @unique @db.BigInt
|
||||||
tgChatId BigInt @unique @db.BigInt
|
tgChatId BigInt @unique @db.BigInt
|
||||||
avatarCache AvatarCache[]
|
avatarCache AvatarCache[]
|
||||||
|
instanceId Int @default(0)
|
||||||
|
instance Instance @relation(fields: [instanceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model File {
|
model File {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
roomId BigInt @db.BigInt
|
roomId BigInt @db.BigInt
|
||||||
fileId String
|
fileId String
|
||||||
info String
|
info String
|
||||||
|
|
||||||
@@unique([roomId, fileId])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model FlashPhoto {
|
model FlashPhoto {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
photoMd5 String
|
photoMd5 String
|
||||||
|
views FlashPhotoView[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model FlashPhotoView {
|
model FlashPhotoView {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
flashPhotoId Int
|
flashPhotoId Int
|
||||||
viewerId BigInt @db.BigInt
|
flashPhoto FlashPhoto @relation(fields: [flashPhotoId], references: [id])
|
||||||
|
viewerId BigInt @db.BigInt
|
||||||
|
|
||||||
@@unique([flashPhotoId, viewerId])
|
@@unique([flashPhotoId, viewerId])
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import CallbackQueryHelper from '../helpers/CallbackQueryHelper';
|
||||||
import { CallbackQuery } from 'telegram/events/CallbackQuery';
|
import { CallbackQuery } from 'telegram/events/CallbackQuery';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import TelegramChat from './TelegramChat';
|
import TelegramChat from './TelegramChat';
|
||||||
import TelegramSession from './TelegramSession';
|
import TelegramSession from '../models/TelegramSession';
|
||||||
import { LogLevel } from 'telegram/extensions/Logger';
|
import { LogLevel } from 'telegram/extensions/Logger';
|
||||||
|
|
||||||
type MessageHandler = (message: Api.Message) => Promise<boolean | void>;
|
type MessageHandler = (message: Api.Message) => Promise<boolean | void>;
|
||||||
|
|
|
@ -2,29 +2,30 @@ import { Api } from 'telegram';
|
||||||
import Telegram from '../client/Telegram';
|
import Telegram from '../client/Telegram';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import ConfigService from '../services/ConfigService';
|
import ConfigService from '../services/ConfigService';
|
||||||
import { config } from '../providers/userConfig';
|
|
||||||
import regExps from '../constants/regExps';
|
import regExps from '../constants/regExps';
|
||||||
import forwardPairs from '../providers/forwardPairs';
|
import forwardPairs from '../models/forwardPairs';
|
||||||
import { GroupMessageEvent, MemberIncreaseEvent, PrivateMessageEvent } from 'oicq';
|
import { GroupMessageEvent, MemberIncreaseEvent, PrivateMessageEvent } from 'oicq';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
export default class ConfigController {
|
export default class ConfigController {
|
||||||
private readonly configService: ConfigService;
|
private readonly configService: ConfigService;
|
||||||
private readonly createPrivateMessageGroupBlockList = new Map<number, Promise<void>>();
|
private readonly createPrivateMessageGroupBlockList = new Map<number, Promise<void>>();
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram,
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
private readonly tgUser: Telegram,
|
private readonly tgUser: Telegram,
|
||||||
private readonly oicq: OicqClient) {
|
private readonly oicq: OicqClient) {
|
||||||
this.configService = new ConfigService(tgBot, tgUser, oicq);
|
this.configService = new ConfigService(this.instance, tgBot, tgUser, oicq);
|
||||||
tgBot.addNewMessageEventHandler(this.handleMessage);
|
tgBot.addNewMessageEventHandler(this.handleMessage);
|
||||||
tgBot.addNewServiceMessageEventHandler(this.handleServiceMessage);
|
tgBot.addNewServiceMessageEventHandler(this.handleServiceMessage);
|
||||||
oicq.addNewMessageEventHandler(this.handleQqMessage);
|
oicq.addNewMessageEventHandler(this.handleQqMessage);
|
||||||
config.workMode === 'personal' && oicq.on('notice.group.increase', this.handleMemberIncrease);
|
this.instance.workMode === 'personal' && oicq.on('notice.group.increase', this.handleMemberIncrease);
|
||||||
this.configService.configCommands();
|
this.configService.configCommands();
|
||||||
config.workMode === 'personal' && this.configService.setupFilter();
|
this.instance.workMode === 'personal' && this.configService.setupFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleMessage = async (message: Api.Message) => {
|
private handleMessage = async (message: Api.Message) => {
|
||||||
if (!message.sender.id.eq(config.owner)) {
|
if (!message.sender.id.eq(this.instance.owner)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const messageSplit = message.message.split(' ');
|
const messageSplit = message.message.split(' ');
|
||||||
|
@ -37,7 +38,7 @@ export default class ConfigController {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (message.isPrivate) {
|
else if (message.isPrivate) {
|
||||||
if (config.workMode === 'personal') {
|
if (this.instance.workMode === 'personal') {
|
||||||
switch (messageSplit[0]) {
|
switch (messageSplit[0]) {
|
||||||
case '/addfriend':
|
case '/addfriend':
|
||||||
await this.configService.addFriend();
|
await this.configService.addFriend();
|
||||||
|
@ -77,7 +78,7 @@ export default class ConfigController {
|
||||||
// 会自动写入数据库
|
// 会自动写入数据库
|
||||||
pair.tg = await this.tgBot.getChat(message.action.channelId);
|
pair.tg = await this.tgBot.getChat(message.action.channelId);
|
||||||
// 升级之后 bot 的管理权限可能没了,需要修复一下
|
// 升级之后 bot 的管理权限可能没了,需要修复一下
|
||||||
if (config.workMode === 'personal') {
|
if (this.instance.workMode === 'personal') {
|
||||||
const chatForUser = await this.tgUser.getChat(message.action.channelId);
|
const chatForUser = await this.tgUser.getChat(message.action.channelId);
|
||||||
await chatForUser.setAdmin(this.tgBot.me.username);
|
await chatForUser.setAdmin(this.tgBot.me.username);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,20 @@ import { getLogger } from 'log4js';
|
||||||
import Telegram from '../client/Telegram';
|
import Telegram from '../client/Telegram';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import { Api } from 'telegram';
|
import { Api } from 'telegram';
|
||||||
import forwardPairs from '../providers/forwardPairs';
|
import forwardPairs from '../models/forwardPairs';
|
||||||
import { FriendRecallEvent, GroupRecallEvent } from 'oicq';
|
import { FriendRecallEvent, GroupRecallEvent } from 'oicq';
|
||||||
import { DeletedMessageEvent } from 'telegram/events/DeletedMessage';
|
import { DeletedMessageEvent } from 'telegram/events/DeletedMessage';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
export default class DeleteMessageController {
|
export default class DeleteMessageController {
|
||||||
private readonly deleteMessageService: DeleteMessageService;
|
private readonly deleteMessageService: DeleteMessageService;
|
||||||
private readonly log = getLogger('DeleteMessageController');
|
private readonly log = getLogger('DeleteMessageController');
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram,
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
private readonly tgUser: Telegram,
|
private readonly tgUser: Telegram,
|
||||||
private readonly oicq: OicqClient) {
|
private readonly oicq: OicqClient) {
|
||||||
this.deleteMessageService = new DeleteMessageService(tgBot, oicq);
|
this.deleteMessageService = new DeleteMessageService(this.instance, tgBot, oicq);
|
||||||
tgBot.addNewMessageEventHandler(this.onTelegramMessage);
|
tgBot.addNewMessageEventHandler(this.onTelegramMessage);
|
||||||
tgBot.addEditedMessageEventHandler(this.onTelegramEditMessage);
|
tgBot.addEditedMessageEventHandler(this.onTelegramEditMessage);
|
||||||
tgUser.addDeletedMessageEventHandler(this.onTgDeletedMessage);
|
tgUser.addDeletedMessageEventHandler(this.onTgDeletedMessage);
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import Telegram from '../client/Telegram';
|
import Telegram from '../client/Telegram';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import { Api } from 'telegram';
|
import { Api } from 'telegram';
|
||||||
import db from '../providers/db';
|
import db from '../models/db';
|
||||||
import { Button } from 'telegram/tl/custom/button';
|
import { Button } from 'telegram/tl/custom/button';
|
||||||
import { getLogger } from 'log4js';
|
import { getLogger } from 'log4js';
|
||||||
import { CustomFile } from 'telegram/client/uploads';
|
import { CustomFile } from 'telegram/client/uploads';
|
||||||
import { fetchFile, getImageUrlByMd5 } from '../utils/urls';
|
import { fetchFile, getImageUrlByMd5 } from '../utils/urls';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
const REGEX = /^\/start (file|flash)-(\d+)$/;
|
const REGEX = /^\/start (file|flash)-(\d+)$/;
|
||||||
|
|
||||||
export default class FileAndFlashPhotoController {
|
export default class FileAndFlashPhotoController {
|
||||||
private readonly log = getLogger('FileController');
|
private readonly log = getLogger('FileController');
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram,
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
private readonly oicq: OicqClient) {
|
private readonly oicq: OicqClient) {
|
||||||
tgBot.addNewMessageEventHandler(this.onTelegramMessage);
|
tgBot.addNewMessageEventHandler(this.onTelegramMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import Telegram from '../client/Telegram';
|
import Telegram from '../client/Telegram';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import ForwardService from '../services/ForwardService';
|
import ForwardService from '../services/ForwardService';
|
||||||
import forwardPairs from '../providers/forwardPairs';
|
import forwardPairs from '../models/forwardPairs';
|
||||||
import { GroupMessageEvent, PrivateMessageEvent } from 'oicq';
|
import { GroupMessageEvent, PrivateMessageEvent } from 'oicq';
|
||||||
import db from '../providers/db';
|
import db from '../models/db';
|
||||||
import { Api } from 'telegram';
|
import { Api } from 'telegram';
|
||||||
import { getLogger } from 'log4js';
|
import { getLogger } from 'log4js';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
export default class ForwardController {
|
export default class ForwardController {
|
||||||
private readonly forwardService: ForwardService;
|
private readonly forwardService: ForwardService;
|
||||||
private readonly log = getLogger('ForwardController');
|
private readonly log = getLogger('ForwardController');
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram,
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
private readonly tgUser: Telegram,
|
private readonly tgUser: Telegram,
|
||||||
private readonly oicq: OicqClient) {
|
private readonly oicq: OicqClient) {
|
||||||
this.forwardService = new ForwardService(tgBot, oicq);
|
this.forwardService = new ForwardService(this.instance, tgBot, oicq);
|
||||||
forwardPairs.init(oicq, tgBot)
|
forwardPairs.init(oicq, tgBot)
|
||||||
.then(() => oicq.addNewMessageEventHandler(this.onQqMessage))
|
.then(() => oicq.addNewMessageEventHandler(this.onQqMessage))
|
||||||
.then(() => tgBot.addNewMessageEventHandler(this.onTelegramMessage))
|
.then(() => tgBot.addNewMessageEventHandler(this.onTelegramMessage))
|
||||||
|
|
|
@ -9,6 +9,7 @@ import commands from '../constants/commands';
|
||||||
import { WorkMode } from '../types/definitions';
|
import { WorkMode } from '../types/definitions';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import { md5Hex } from '../utils/hashing';
|
import { md5Hex } from '../utils/hashing';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
export default class SetupController {
|
export default class SetupController {
|
||||||
private readonly setupService: SetupService;
|
private readonly setupService: SetupService;
|
||||||
|
@ -19,8 +20,9 @@ export default class SetupController {
|
||||||
private tgUser: Telegram;
|
private tgUser: Telegram;
|
||||||
private oicq: OicqClient;
|
private oicq: OicqClient;
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram) {
|
constructor(private readonly instance: Instance,
|
||||||
this.setupService = new SetupService(tgBot);
|
private readonly tgBot: Telegram) {
|
||||||
|
this.setupService = new SetupService(this.instance, tgBot);
|
||||||
tgBot.addNewMessageEventHandler(this.handleMessage);
|
tgBot.addNewMessageEventHandler(this.handleMessage);
|
||||||
tgBot.setCommands(commands.preSetupCommands, new Api.BotCommandScopeUsers());
|
tgBot.setCommands(commands.preSetupCommands, new Api.BotCommandScopeUsers());
|
||||||
}
|
}
|
||||||
|
|
42
src/index.ts
42
src/index.ts
|
@ -1,12 +1,5 @@
|
||||||
import Telegram from './client/Telegram';
|
|
||||||
import { config } from './providers/userConfig';
|
|
||||||
import { configure, getLogger } from 'log4js';
|
import { configure, getLogger } from 'log4js';
|
||||||
import SetupController from './controllers/SetupController';
|
import Instance from './models/Instance';
|
||||||
import OicqClient from './client/OicqClient';
|
|
||||||
import ConfigController from './controllers/ConfigController';
|
|
||||||
import ForwardController from './controllers/ForwardController';
|
|
||||||
import FileAndFlashPhotoController from './controllers/FileAndFlashPhotoController';
|
|
||||||
import DeleteMessageController from './controllers/DeleteMessageController';
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
configure({
|
configure({
|
||||||
|
@ -21,36 +14,5 @@ import DeleteMessageController from './controllers/DeleteMessageController';
|
||||||
process.on('unhandledRejection', error => {
|
process.on('unhandledRejection', error => {
|
||||||
log.error('UnhandledException: ', error);
|
log.error('UnhandledException: ', error);
|
||||||
});
|
});
|
||||||
|
await Instance.start(0);
|
||||||
log.debug('正在登录 TG Bot');
|
|
||||||
const tgBot = await Telegram.create({
|
|
||||||
botAuthToken: process.env.TG_BOT_TOKEN,
|
|
||||||
}, 'bot');
|
|
||||||
|
|
||||||
let tgUser: Telegram, oicq: OicqClient;
|
|
||||||
log.debug('TG Bot 登录完成');
|
|
||||||
if (!config.isSetup) {
|
|
||||||
log.info('当前服务器未配置,请向 Bot 发送 /setup 来设置');
|
|
||||||
const setupController = new SetupController(tgBot);
|
|
||||||
({ tgUser, oicq } = await setupController.waitForFinish());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug('正在登录 TG UserBot');
|
|
||||||
tgUser = await Telegram.connect('user');
|
|
||||||
log.debug('TG UserBot 登录完成');
|
|
||||||
log.debug('正在登录 OICQ');
|
|
||||||
oicq = await OicqClient.create({
|
|
||||||
uin: config.qqUin,
|
|
||||||
password: config.qqPassword,
|
|
||||||
platform: config.qqPlatform,
|
|
||||||
onVerifyDevice: () => null,
|
|
||||||
onVerifySlider: () => null,
|
|
||||||
onQrCode: () => null,
|
|
||||||
});
|
|
||||||
log.debug('OICQ 登录完成');
|
|
||||||
}
|
|
||||||
new ConfigController(tgBot, tgUser, oicq);
|
|
||||||
new DeleteMessageController(tgBot, tgUser, oicq);
|
|
||||||
new ForwardController(tgBot, tgUser, oicq);
|
|
||||||
new FileAndFlashPhotoController(tgBot, oicq);
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
import { WorkMode } from '../types/definitions';
|
||||||
|
import db from './db';
|
||||||
|
import { Platform } from 'oicq';
|
||||||
|
import ConfigController from '../controllers/ConfigController';
|
||||||
|
import SetupController from '../controllers/SetupController';
|
||||||
|
import ForwardController from '../controllers/ForwardController';
|
||||||
|
import DeleteMessageController from '../controllers/DeleteMessageController';
|
||||||
|
import FileAndFlashPhotoController from '../controllers/FileAndFlashPhotoController';
|
||||||
|
import Telegram from '../client/Telegram';
|
||||||
|
import OicqClient from '../client/OicqClient';
|
||||||
|
import { getLogger, Logger } from 'log4js';
|
||||||
|
|
||||||
|
export default class Instance {
|
||||||
|
private _owner = 0;
|
||||||
|
private _qqUin = 0;
|
||||||
|
private _qqPassword = '';
|
||||||
|
private _qqPlatform = 0;
|
||||||
|
private _isSetup = false;
|
||||||
|
private _workMode = '';
|
||||||
|
|
||||||
|
private readonly log: Logger;
|
||||||
|
|
||||||
|
private setupController: SetupController;
|
||||||
|
private configController: ConfigController;
|
||||||
|
private deleteMessageController: DeleteMessageController;
|
||||||
|
private forwardController: ForwardController;
|
||||||
|
private fileAndFlashPhotoController: FileAndFlashPhotoController;
|
||||||
|
|
||||||
|
private constructor(public readonly id: number) {
|
||||||
|
this.log = getLogger(`Instance ${this.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async load() {
|
||||||
|
const dbEntry = await db.instance.findFirst({
|
||||||
|
where: { id: this.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dbEntry) {
|
||||||
|
if (this.id === 0) {
|
||||||
|
// 创建零号实例
|
||||||
|
await db.instance.create({
|
||||||
|
data: { id: 0 },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Error('Instance not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._owner = Number(dbEntry.owner);
|
||||||
|
this._qqUin = Number(dbEntry.qqUin);
|
||||||
|
this._qqPassword = dbEntry.qqPassword;
|
||||||
|
this._qqPlatform = dbEntry.qqPlatform;
|
||||||
|
this._isSetup = dbEntry.isSetup;
|
||||||
|
this._workMode = dbEntry.workMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init() {
|
||||||
|
this.log.debug('正在登录 TG Bot');
|
||||||
|
const tgBot = await Telegram.create({
|
||||||
|
botAuthToken: process.env.TG_BOT_TOKEN,
|
||||||
|
}, 'bot');
|
||||||
|
|
||||||
|
let tgUser: Telegram, oicq: OicqClient;
|
||||||
|
this.log.debug('TG Bot 登录完成');
|
||||||
|
if (!this.isSetup) {
|
||||||
|
this.log.info('当前服务器未配置,请向 Bot 发送 /setup 来设置');
|
||||||
|
this.setupController = new SetupController(this, tgBot);
|
||||||
|
({ tgUser, oicq } = await this.setupController.waitForFinish());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.log.debug('正在登录 TG UserBot');
|
||||||
|
tgUser = await Telegram.connect('user');
|
||||||
|
this.log.debug('TG UserBot 登录完成');
|
||||||
|
this.log.debug('正在登录 OICQ');
|
||||||
|
oicq = await OicqClient.create({
|
||||||
|
uin: this.qqUin,
|
||||||
|
password: this.qqPassword,
|
||||||
|
platform: this.qqPlatform,
|
||||||
|
onVerifyDevice: () => null,
|
||||||
|
onVerifySlider: () => null,
|
||||||
|
onQrCode: () => null,
|
||||||
|
});
|
||||||
|
this.log.debug('OICQ 登录完成');
|
||||||
|
}
|
||||||
|
this.configController = new ConfigController(this, tgBot, tgUser, oicq);
|
||||||
|
this.deleteMessageController = new DeleteMessageController(this, tgBot, tgUser, oicq);
|
||||||
|
this.forwardController = new ForwardController(this, tgBot, tgUser, oicq);
|
||||||
|
this.fileAndFlashPhotoController = new FileAndFlashPhotoController(this, tgBot, oicq);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async start(instanceId: number) {
|
||||||
|
const instance = new this(instanceId);
|
||||||
|
await instance.load();
|
||||||
|
await instance.init();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
get owner() {
|
||||||
|
return this._owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
get qqUin() {
|
||||||
|
return this._qqUin;
|
||||||
|
}
|
||||||
|
|
||||||
|
get qqPassword() {
|
||||||
|
return this._qqPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
get qqPlatform() {
|
||||||
|
return this._qqPlatform as Platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSetup() {
|
||||||
|
return this._isSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
get workMode() {
|
||||||
|
return this._workMode as WorkMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
set owner(owner: number) {
|
||||||
|
this._owner = owner;
|
||||||
|
db.instance.update({
|
||||||
|
data: { owner },
|
||||||
|
where: { id: this.id },
|
||||||
|
})
|
||||||
|
.then(() => this.log.trace(owner));
|
||||||
|
}
|
||||||
|
|
||||||
|
set qqUin(qqUin: number) {
|
||||||
|
this._qqUin = qqUin;
|
||||||
|
db.instance.update({
|
||||||
|
data: { qqUin },
|
||||||
|
where: { id: this.id },
|
||||||
|
})
|
||||||
|
.then(() => this.log.trace(qqUin));
|
||||||
|
}
|
||||||
|
|
||||||
|
set qqPassword(qqPassword: string) {
|
||||||
|
this._qqPassword = qqPassword;
|
||||||
|
db.instance.update({
|
||||||
|
data: { qqPassword },
|
||||||
|
where: { id: this.id },
|
||||||
|
})
|
||||||
|
.then(() => this.log.trace(qqPassword));
|
||||||
|
}
|
||||||
|
|
||||||
|
set qqPlatform(qqPlatform: Platform) {
|
||||||
|
this._qqPlatform = qqPlatform;
|
||||||
|
db.instance.update({
|
||||||
|
data: { qqPlatform },
|
||||||
|
where: { id: this.id },
|
||||||
|
})
|
||||||
|
.then(() => this.log.trace(qqPlatform));
|
||||||
|
}
|
||||||
|
|
||||||
|
set isSetup(isSetup: boolean) {
|
||||||
|
this._isSetup = isSetup;
|
||||||
|
db.instance.update({
|
||||||
|
data: { isSetup },
|
||||||
|
where: { id: this.id },
|
||||||
|
})
|
||||||
|
.then(() => this.log.trace(isSetup));
|
||||||
|
}
|
||||||
|
|
||||||
|
set workMode(workMode: WorkMode) {
|
||||||
|
this._workMode = workMode;
|
||||||
|
db.instance.update({
|
||||||
|
data: { workMode },
|
||||||
|
where: { id: this.id },
|
||||||
|
})
|
||||||
|
.then(() => this.log.trace(workMode));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { MemorySession } from 'telegram/sessions';
|
import { MemorySession } from 'telegram/sessions';
|
||||||
import db from '../providers/db';
|
import db from './db';
|
||||||
import { AuthKey } from 'telegram/crypto/AuthKey';
|
import { AuthKey } from 'telegram/crypto/AuthKey';
|
||||||
import { getLogger } from 'log4js';
|
import { getLogger } from 'log4js';
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import fs from 'fs';
|
|
||||||
import fsP from 'fs/promises';
|
|
||||||
import { WorkMode } from '../types/definitions';
|
|
||||||
|
|
||||||
type UserConfig = {
|
|
||||||
owner: number
|
|
||||||
qqUin: number;
|
|
||||||
qqPassword: string;
|
|
||||||
qqPlatform: number
|
|
||||||
isSetup: boolean;
|
|
||||||
workMode?: WorkMode
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONFIG_PATH = './data/config.json';
|
|
||||||
|
|
||||||
const defaultConfig: UserConfig = {
|
|
||||||
owner: 0,
|
|
||||||
qqUin: 0,
|
|
||||||
qqPassword: '',
|
|
||||||
qqPlatform: 0,
|
|
||||||
isSetup: false,
|
|
||||||
workMode: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const config: UserConfig = fs.existsSync(CONFIG_PATH) ?
|
|
||||||
JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) :
|
|
||||||
defaultConfig;
|
|
||||||
|
|
||||||
export const saveConfig = async () => {
|
|
||||||
await fsP.writeFile(CONFIG_PATH, JSON.stringify(config, null, 0), 'utf8');
|
|
||||||
};
|
|
|
@ -1,18 +1,17 @@
|
||||||
import Telegram from '../client/Telegram';
|
import Telegram from '../client/Telegram';
|
||||||
import { Friend, FriendInfo, Group } from 'oicq';
|
import { Friend, FriendInfo, Group } from 'oicq';
|
||||||
import { config } from '../providers/userConfig';
|
|
||||||
import { Button } from 'telegram/tl/custom/button';
|
import { Button } from 'telegram/tl/custom/button';
|
||||||
import { getLogger } from 'log4js';
|
import { getLogger } from 'log4js';
|
||||||
import { getAvatar } from '../utils/urls';
|
import { getAvatar } from '../utils/urls';
|
||||||
import { CustomFile } from 'telegram/client/uploads';
|
import { CustomFile } from 'telegram/client/uploads';
|
||||||
import db from '../providers/db';
|
import db from '../models/db';
|
||||||
import { Api, utils } from 'telegram';
|
import { Api, utils } from 'telegram';
|
||||||
import commands from '../constants/commands';
|
import commands from '../constants/commands';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import { md5 } from '../utils/hashing';
|
import { md5 } from '../utils/hashing';
|
||||||
import TelegramChat from '../client/TelegramChat';
|
import TelegramChat from '../client/TelegramChat';
|
||||||
import forwardPairs from '../providers/forwardPairs';
|
import forwardPairs from '../models/forwardPairs';
|
||||||
import bigInt from 'big-integer';
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
const DEFAULT_FILTER_ID = 114; // 514
|
const DEFAULT_FILTER_ID = 114; // 514
|
||||||
|
|
||||||
|
@ -20,10 +19,11 @@ export default class ConfigService {
|
||||||
private owner: Promise<TelegramChat>;
|
private owner: Promise<TelegramChat>;
|
||||||
private log = getLogger('ConfigService');
|
private log = getLogger('ConfigService');
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram,
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
private readonly tgUser: Telegram,
|
private readonly tgUser: Telegram,
|
||||||
private readonly oicq: OicqClient) {
|
private readonly oicq: OicqClient) {
|
||||||
this.owner = tgBot.getChat(config.owner);
|
this.owner = tgBot.getChat(this.instance.owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAssociateLink(roomId: number) {
|
private getAssociateLink(roomId: number) {
|
||||||
|
@ -33,7 +33,7 @@ export default class ConfigService {
|
||||||
public async configCommands() {
|
public async configCommands() {
|
||||||
await this.tgBot.setCommands([], new Api.BotCommandScopeUsers());
|
await this.tgBot.setCommands([], new Api.BotCommandScopeUsers());
|
||||||
await this.tgBot.setCommands(
|
await this.tgBot.setCommands(
|
||||||
config.workMode === 'personal' ? commands.personalPrivateCommands : commands.groupPrivateCommands,
|
this.instance.workMode === 'personal' ? commands.personalPrivateCommands : commands.groupPrivateCommands,
|
||||||
new Api.BotCommandScopePeer({
|
new Api.BotCommandScopePeer({
|
||||||
peer: (await this.owner).inputPeer,
|
peer: (await this.owner).inputPeer,
|
||||||
}),
|
}),
|
||||||
|
@ -47,7 +47,7 @@ export default class ConfigService {
|
||||||
const qGroups = Array.from(this.oicq.gl).map(e => e[1])
|
const qGroups = Array.from(this.oicq.gl).map(e => e[1])
|
||||||
.filter(it => !forwardPairs.find(-it.group_id));
|
.filter(it => !forwardPairs.find(-it.group_id));
|
||||||
const buttons = qGroups.map(e =>
|
const buttons = qGroups.map(e =>
|
||||||
config.workMode === 'personal' ?
|
this.instance.workMode === 'personal' ?
|
||||||
[Button.inline(
|
[Button.inline(
|
||||||
`${e.group_name} (${e.group_id})`,
|
`${e.group_name} (${e.group_id})`,
|
||||||
this.tgBot.registerCallback(() => this.createGroupAndLink(-e.group_id, e.group_name)),
|
this.tgBot.registerCallback(() => this.createGroupAndLink(-e.group_id, e.group_name)),
|
||||||
|
@ -57,7 +57,7 @@ export default class ConfigService {
|
||||||
this.getAssociateLink(-e.group_id),
|
this.getAssociateLink(-e.group_id),
|
||||||
)]);
|
)]);
|
||||||
await (await this.owner).createPaginatedInlineSelector(
|
await (await this.owner).createPaginatedInlineSelector(
|
||||||
'选择 QQ 群组' + (config.workMode === 'group' ? '\n然后选择在 TG 中的群组' : ''), buttons);
|
'选择 QQ 群组' + (this.instance.workMode === 'group' ? '\n然后选择在 TG 中的群组' : ''), buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只可能是 personal 运行模式
|
// 只可能是 personal 运行模式
|
||||||
|
|
|
@ -2,15 +2,16 @@ import Telegram from '../client/Telegram';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import { getLogger } from 'log4js';
|
import { getLogger } from 'log4js';
|
||||||
import { Api } from 'telegram';
|
import { Api } from 'telegram';
|
||||||
import { Pair } from '../providers/forwardPairs';
|
import { Pair } from '../models/forwardPairs';
|
||||||
import { config } from '../providers/userConfig';
|
import db from '../models/db';
|
||||||
import db from '../providers/db';
|
|
||||||
import { Friend, FriendRecallEvent, GroupRecallEvent } from 'oicq';
|
import { Friend, FriendRecallEvent, GroupRecallEvent } from 'oicq';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
export default class DeleteMessageService {
|
export default class DeleteMessageService {
|
||||||
private log = getLogger('DeleteMessageService');
|
private log = getLogger('DeleteMessageService');
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram,
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
private readonly oicq: OicqClient) {
|
private readonly oicq: OicqClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,12 +37,12 @@ export default class DeleteMessageService {
|
||||||
console.log(123);
|
console.log(123);
|
||||||
const tipMsg = await pair.tg.sendMessage({
|
const tipMsg = await pair.tg.sendMessage({
|
||||||
message: '撤回 QQ 中对应的消息失败' +
|
message: '撤回 QQ 中对应的消息失败' +
|
||||||
(config.workMode === 'group' ? ',QQ Bot 需要是管理员' : '') +
|
(this.instance.workMode === 'group' ? ',QQ Bot 需要是管理员' : '') +
|
||||||
(isOthersMsg ? ',而且无法撤回其他管理员的消息' : '') +
|
(isOthersMsg ? ',而且无法撤回其他管理员的消息' : '') +
|
||||||
(e.message ? '\n' + e.message : ''),
|
(e.message ? '\n' + e.message : ''),
|
||||||
silent: true,
|
silent: true,
|
||||||
});
|
});
|
||||||
config.workMode === 'group' && setTimeout(async () => await tipMsg.delete({ revoke: true }), 5000);
|
this.instance.workMode === 'group' && setTimeout(async () => await tipMsg.delete({ revoke: true }), 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +60,7 @@ export default class DeleteMessageService {
|
||||||
const replyMessage = await message.getReplyMessage();
|
const replyMessage = await message.getReplyMessage();
|
||||||
if (replyMessage instanceof Api.Message) {
|
if (replyMessage instanceof Api.Message) {
|
||||||
// 检查权限并撤回被回复的消息
|
// 检查权限并撤回被回复的消息
|
||||||
let hasPermission = config.workMode === 'personal' || replyMessage.senderId?.eq(message.senderId);
|
let hasPermission = this.instance.workMode === 'personal' || replyMessage.senderId?.eq(message.senderId);
|
||||||
if (!hasPermission && message.chat instanceof Api.Channel) {
|
if (!hasPermission && message.chat instanceof Api.Channel) {
|
||||||
// 可能是超级群
|
// 可能是超级群
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Telegram from '../client/Telegram';
|
import Telegram from '../client/Telegram';
|
||||||
import OicqClient from '../client/OicqClient';
|
import OicqClient from '../client/OicqClient';
|
||||||
import { Group, GroupMessageEvent, PrivateMessageEvent, Quotable, segment, Sendable } from 'oicq';
|
import { Group, GroupMessageEvent, PrivateMessageEvent, Quotable, segment, Sendable } from 'oicq';
|
||||||
import { Pair } from '../providers/forwardPairs';
|
import { Pair } from '../models/forwardPairs';
|
||||||
import { fetchFile, getBigFaceUrl, getImageUrlByMd5 } from '../utils/urls';
|
import { fetchFile, getBigFaceUrl, getImageUrlByMd5 } from '../utils/urls';
|
||||||
import { FileLike, MarkupLike } from 'telegram/define';
|
import { FileLike, MarkupLike } from 'telegram/define';
|
||||||
import { CustomFile } from 'telegram/client/uploads';
|
import { CustomFile } from 'telegram/client/uploads';
|
||||||
|
@ -9,11 +9,10 @@ import { getLogger } from 'log4js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import exts from '../constants/exts';
|
import exts from '../constants/exts';
|
||||||
import helper from '../helpers/forwardHelper';
|
import helper from '../helpers/forwardHelper';
|
||||||
import db from '../providers/db';
|
import db from '../models/db';
|
||||||
import { Button } from 'telegram/tl/custom/button';
|
import { Button } from 'telegram/tl/custom/button';
|
||||||
import { SendMessageParams } from 'telegram/client/messages';
|
import { SendMessageParams } from 'telegram/client/messages';
|
||||||
import { Api } from 'telegram';
|
import { Api } from 'telegram';
|
||||||
import { config } from '../providers/userConfig';
|
|
||||||
import { file as createTempFile, FileResult } from 'tmp-promise';
|
import { file as createTempFile, FileResult } from 'tmp-promise';
|
||||||
import fsP from 'fs/promises';
|
import fsP from 'fs/promises';
|
||||||
import eviltransform from 'eviltransform';
|
import eviltransform from 'eviltransform';
|
||||||
|
@ -22,12 +21,14 @@ import fs from 'fs';
|
||||||
import tgsToGif from '../encoding/tgsToGif';
|
import tgsToGif from '../encoding/tgsToGif';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { md5Hex } from '../utils/hashing';
|
import { md5Hex } from '../utils/hashing';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
// noinspection FallThroughInSwitchStatementJS
|
// noinspection FallThroughInSwitchStatementJS
|
||||||
export default class ForwardService {
|
export default class ForwardService {
|
||||||
private log = getLogger('ForwardService');
|
private log = getLogger('ForwardService');
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram,
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
private readonly oicq: OicqClient) {
|
private readonly oicq: OicqClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ export default class ForwardService {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'flash': {
|
case 'flash': {
|
||||||
message += `[闪照]\n${config.workMode === 'group' ? '每人' : ''}只能查看一次`;
|
message += `[闪照]\n${this.instance.workMode === 'group' ? '每人' : ''}只能查看一次`;
|
||||||
const dbEntry = await db.flashPhoto.create({
|
const dbEntry = await db.flashPhoto.create({
|
||||||
data: { photoMd5: (elem.file as string).substring(0, 32) },
|
data: { photoMd5: (elem.file as string).substring(0, 32) },
|
||||||
});
|
});
|
||||||
|
@ -228,7 +229,7 @@ export default class ForwardService {
|
||||||
const chain: Sendable = [];
|
const chain: Sendable = [];
|
||||||
// 这条消息在 tg 中被回复的时候显示的
|
// 这条消息在 tg 中被回复的时候显示的
|
||||||
let brief = '';
|
let brief = '';
|
||||||
config.workMode === 'group' && chain.push(helper.getUserDisplayName(message.sender) +
|
this.instance.workMode === 'group' && chain.push(helper.getUserDisplayName(message.sender) +
|
||||||
(message.forward ? ' Forwarded from ' + helper.getUserDisplayName(message.forward.chat || message.forward.sender) : '') +
|
(message.forward ? ' Forwarded from ' + helper.getUserDisplayName(message.forward.chat || message.forward.sender) : '') +
|
||||||
': \n');
|
': \n');
|
||||||
if (message.photo instanceof Api.Photo ||
|
if (message.photo instanceof Api.Photo ||
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import Telegram from '../client/Telegram';
|
import Telegram from '../client/Telegram';
|
||||||
import { config, saveConfig } from '../providers/userConfig';
|
|
||||||
import { getLogger } from 'log4js';
|
import { getLogger } from 'log4js';
|
||||||
import { BigInteger } from 'big-integer';
|
import { BigInteger } from 'big-integer';
|
||||||
import { Platform } from 'oicq';
|
import { Platform } from 'oicq';
|
||||||
|
@ -9,16 +8,18 @@ import { Button } from 'telegram/tl/custom/button';
|
||||||
import { CustomFile } from 'telegram/client/uploads';
|
import { CustomFile } from 'telegram/client/uploads';
|
||||||
import { WorkMode } from '../types/definitions';
|
import { WorkMode } from '../types/definitions';
|
||||||
import TelegramChat from '../client/TelegramChat';
|
import TelegramChat from '../client/TelegramChat';
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
|
||||||
export default class SetupService {
|
export default class SetupService {
|
||||||
private owner: TelegramChat;
|
private owner: TelegramChat;
|
||||||
private log = getLogger('SetupService');
|
private log = getLogger('SetupService');
|
||||||
|
|
||||||
constructor(private readonly tgBot: Telegram) {
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public setWorkMode(mode: WorkMode) {
|
public setWorkMode(mode: WorkMode) {
|
||||||
config.workMode = mode;
|
this.instance.workMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +30,7 @@ export default class SetupService {
|
||||||
public async claimOwner(userId: number | BigInteger) {
|
public async claimOwner(userId: number | BigInteger) {
|
||||||
userId = Number(userId);
|
userId = Number(userId);
|
||||||
if (!this.owner) {
|
if (!this.owner) {
|
||||||
config.owner = userId;
|
this.instance.owner = userId;
|
||||||
await this.setupOwner();
|
await this.setupOwner();
|
||||||
this.log.info(`用户 ID: ${userId} 成为了 Bot 主人`);
|
this.log.info(`用户 ID: ${userId} 成为了 Bot 主人`);
|
||||||
return true;
|
return true;
|
||||||
|
@ -38,8 +39,8 @@ export default class SetupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setupOwner() {
|
private async setupOwner() {
|
||||||
if (!this.owner && config.owner) {
|
if (!this.owner && this.instance.owner) {
|
||||||
this.owner = await this.tgBot.getChat(config.owner);
|
this.owner = await this.tgBot.getChat(this.instance.owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,13 +108,12 @@ export default class SetupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveOicqLoginInfo(uin: number, password: string, platform: Platform) {
|
public saveOicqLoginInfo(uin: number, password: string, platform: Platform) {
|
||||||
config.qqUin = uin;
|
this.instance.qqUin = uin;
|
||||||
config.qqPassword = password;
|
this.instance.qqPassword = password;
|
||||||
config.qqPlatform = platform;
|
this.instance.qqPlatform = platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async finishConfig() {
|
public async finishConfig() {
|
||||||
config.isSetup = true;
|
this.instance.isSetup = true;
|
||||||
await saveConfig();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue