feat: 将 session 存储在数据库中

This commit is contained in:
Clansty 2022-02-24 21:16:19 +08:00
parent f71b04c62b
commit a8b87a49dd
No known key found for this signature in database
GPG Key ID: 05F8479BA63A8E92
7 changed files with 139 additions and 45 deletions

View File

@ -10,12 +10,36 @@ datasource db {
url = env("DATABASE_URL")
}
model Session {
id Int @id @default(autoincrement())
name String @unique
dcId Int?
port Int?
serverAddress String?
authKey Bytes?
entities Entity[]
}
model Entity {
id Int @id @default(autoincrement())
// 源代码里面大概支持 string 和 BigInteger不如先全都存 String
entityId String
sessionId Int
session Session @relation(fields: [sessionId], references: [id])
hash String?
username String?
phone String?
name String?
@@unique([entityId, sessionId])
}
model Message {
id Int @id @default(autoincrement())
qqRoomId Int
qqSenderId Int
time Int
brief String
brief String?
seq Int
rand Int
pktnum Int
@ -30,7 +54,7 @@ model ForwardPair {
id Int @id @default(autoincrement())
qqRoomId Int @unique
tgChatId Int @unique
AvatarCache AvatarCache[]
avatarCache AvatarCache[]
}
model File {

View File

@ -10,6 +10,7 @@ import CallbackQueryHelper from '../helpers/CallbackQueryHelper';
import { CallbackQuery } from 'telegram/events/CallbackQuery';
import os from 'os';
import TelegramChat from './TelegramChat';
import TelegramSession from './TelegramSession';
type MessageHandler = (message: Api.Message) => Promise<boolean>;
@ -20,9 +21,9 @@ export default class Telegram {
private readonly onMessageHandlers: Array<MessageHandler> = [];
public me: Api.User;
private constructor(stringSession = '') {
private constructor(sessionId: string) {
this.client = new TelegramClient(
new StringSession(stringSession),
new TelegramSession(sessionId),
parseInt(process.env.TG_API_ID),
process.env.TG_API_HASH,
{
@ -39,15 +40,15 @@ export default class Telegram {
);
}
public static async create(startArgs: UserAuthParams | BotAuthParams, stringSession = '') {
const bot = new this(stringSession);
public static async create(startArgs: UserAuthParams | BotAuthParams, sessionId: string) {
const bot = new this(sessionId);
await bot.client.start(startArgs);
await bot.config();
return bot;
}
public static async connect(stringSession: string) {
const bot = new this(stringSession);
public static async connect(sessionId: string) {
const bot = new this(sessionId);
await bot.client.connect();
await bot.config();
return bot;
@ -93,11 +94,6 @@ export default class Telegram {
return new TelegramChat(this, this.client, await this.client.getEntity(entity), this.waitForMessageHelper);
}
public getStringSession() {
// 上游定义不好好写
return (this.client.session as StringSession).save();
}
public async setCommands(commands: Api.BotCommand[], scope: Api.TypeBotCommandScope) {
return await this.client.invoke(
new Api.bots.SetBotCommands({

View File

@ -0,0 +1,100 @@
import { MemorySession } from 'telegram/sessions';
import db from '../providers/db';
import { AuthKey } from 'telegram/crypto/AuthKey';
import { returnBigInt } from 'telegram/Helpers';
import { getLogger } from 'log4js';
export default class TelegramSession extends MemorySession {
private dbId: number;
private log = getLogger('TelegramSession');
constructor(private readonly sessionName: string) {
super();
}
async load() {
this.log.trace('load');
const dbEntry = await db.session.findFirst({
where: { name: this.sessionName },
include: { entities: true },
});
if (!dbEntry) {
this.log.debug('Session 不存在,创建');
// 创建并返回
const newDbEntry = await db.session.create({ data: { name: this.sessionName } });
this.dbId = newDbEntry.id;
return;
}
this.dbId = dbEntry.id;
const { authKey, dcId, port, serverAddress } = dbEntry;
if (authKey && typeof authKey === 'object') {
this._authKey = new AuthKey();
await this._authKey.setKey(authKey);
}
if (dcId) {
this._dcId = dcId;
}
if (port) {
this._port = port;
}
if (serverAddress) {
this._serverAddress = serverAddress;
}
// id, hash, username, phone, name
this._entities = new Set(
dbEntry.entities.map(e => [returnBigInt(e.entityId), returnBigInt(e.hash), e.username, e.phone, e.name]));
}
setDC(dcId: number, serverAddress: string, port: number) {
this.log.trace('setDC', dcId, serverAddress, port);
super.setDC(dcId, serverAddress, port);
db.session.update({
where: { id: this.dbId },
data: { dcId, serverAddress, port },
})
.then(e => this.log.trace('DC update result', e));
}
set authKey(value: AuthKey | undefined) {
this.log.trace('authKey', value);
this._authKey = value;
db.session.update({
where: { id: this.dbId },
data: { authKey: value?.getKey() || null },
})
.then(e => this.log.trace('authKey update result', e));
}
processEntities(tlo: any) {
this.log.trace('processEntities');
const entitiesSet = this._entitiesToRows(tlo);
for (const e of entitiesSet) {
this.log.trace('processEntity', e);
this._entities.add(e);
db.entity.upsert({
// id, hash, username, phone, name
where: {
entityId_sessionId: { sessionId: this.dbId, entityId: e[0].toString() },
},
create: {
sessionId: this.dbId,
entityId: e[0] && e[0].toString(),
hash: e[1] && e[1].toString(),
username: e[2] && e[2].toString(),
phone: e[3] && e[3].toString(),
name: e[4] && e[4].toString(),
},
update: {
hash: e[1] && e[1].toString(),
username: e[2] && e[2].toString(),
phone: e[3] && e[3].toString(),
name: e[4] && e[4].toString(),
},
})
.then(e => this.log.trace('Entity update result', e));
}
}
}

View File

@ -99,30 +99,12 @@ export default class SetupController {
this.isInProgress = false;
throw e;
}
let createUserBot: boolean;
if (workMode === 'group') {
const createUserBotChoice = await this.setupService.waitForOwnerInput('是否创建一个 Telegram UserBot\n' +
'将 UserBot 加入转发 Bot 所在的群可以监控原生的【删除消息】操作,方便用户直接删除消息', [
[Button.text('是', true, true)],
[Button.text('否', true, true)],
]);
createUserBot = createUserBotChoice === '是';
}
else {
createUserBot = true;
}
// 登录 tg UserBot
if (!createUserBot) {
this.setupService.saveUserBotSession('');
return;
}
try {
const phoneNumber = await this.setupService.waitForOwnerInput('创建 Telegram UserBot请输入你的手机号码需要带国家区号例如+86');
await this.setupService.informOwner('正在登录,请稍候…');
this.tgUser = await this.setupService.createUserBot(phoneNumber);
await this.setupService.informOwner(`登录成功`);
this.setupService.saveUserBotSession(this.tgUser.getStringSession());
this.log.debug('StringSession 保存成功');
}
catch (e) {
this.log.error('创建 UserBot 失败', e);

View File

@ -12,7 +12,7 @@ import ForwardController from './controllers/ForwardController';
console: { type: 'console' },
},
categories: {
default: { level: 'debug', appenders: ['console'] },
default: { level: 'trace', appenders: ['console'] },
},
});
const log = getLogger('Main');
@ -23,7 +23,7 @@ import ForwardController from './controllers/ForwardController';
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 登录完成');
@ -33,11 +33,9 @@ import ForwardController from './controllers/ForwardController';
({ tgUser, oicq } = await setupController.waitForFinish());
}
else {
if (config.userBotSession) {
log.debug('正在登录 TG UserBot');
tgUser = await Telegram.connect(config.userBotSession);
log.debug('TG UserBot 登录完成');
}
log.debug('正在登录 TG UserBot');
tgUser = await Telegram.connect('user');
log.debug('TG UserBot 登录完成');
log.debug('正在登录 OICQ');
oicq = await OicqClient.create({
uin: config.qqUin,

View File

@ -4,7 +4,6 @@ import { WorkMode } from '../types/definitions';
type UserConfig = {
owner: number
userBotSession: string;
qqUin: number;
qqPassword: string;
qqPlatform: number
@ -16,7 +15,6 @@ const CONFIG_PATH = './data/config.json';
const defaultConfig: UserConfig = {
owner: 0,
userBotSession: '',
qqUin: 0,
qqPassword: '',
qqPlatform: 0,

View File

@ -72,11 +72,7 @@ export default class SetupService {
return await this.waitForOwnerInput(`请输入你${isCodeViaApp ? ' Telegram APP 中' : '手机上'}收到的验证码`);
},
onError: (err) => this.log.error(err),
});
}
public saveUserBotSession(session: string) {
config.userBotSession = session;
}, 'user');
}
public async createOicq(uin: number, password: string, platform: Platform) {