mirror of https://github.com/Nofated095/Q2TG.git
feat: 创建 Telegram UserBot
This commit is contained in:
parent
ad5d2f0a67
commit
6c5f84bc63
|
@ -16,6 +16,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^3.9.2",
|
||||
"log4js": "^6.4.1",
|
||||
"oicq": "^2.2.0",
|
||||
"telegram": "^2.5.0"
|
||||
}
|
||||
|
|
|
@ -7,11 +7,14 @@ import { DeletedMessage, DeletedMessageEvent } from 'telegram/events/DeletedMess
|
|||
import { Entity, EntityLike } from 'telegram/define';
|
||||
import { SendMessageParams } from 'telegram/client/messages';
|
||||
import { CustomFile } from 'telegram/client/uploads';
|
||||
import WaitForInputHelper from '../helpers/WaitForInputHelper';
|
||||
import WaitForMessageHelper from '../helpers/WaitForMessageHelper';
|
||||
|
||||
type MessageHandler = (message: Api.Message) => Promise<boolean>;
|
||||
|
||||
export class Telegram {
|
||||
private readonly client: TelegramClient;
|
||||
private waitForInputHelper: WaitForInputHelper;
|
||||
private waitForMessageHelper: WaitForMessageHelper;
|
||||
private readonly onMessageHandlers: Array<MessageHandler> = [];
|
||||
|
||||
private constructor(stringSession = '') {
|
||||
this.client = new TelegramClient(
|
||||
|
@ -32,7 +35,8 @@ export class Telegram {
|
|||
public static async create(startArgs: UserAuthParams | BotAuthParams, stringSession = '') {
|
||||
const bot = new this(stringSession);
|
||||
await bot.client.start(startArgs);
|
||||
bot.waitForInputHelper = new WaitForInputHelper(bot);
|
||||
bot.waitForMessageHelper = new WaitForMessageHelper(bot);
|
||||
bot.client.addEventHandler(bot.onMessage, new NewMessage({}));
|
||||
return bot;
|
||||
}
|
||||
|
||||
|
@ -42,9 +46,24 @@ export class Telegram {
|
|||
return bot;
|
||||
}
|
||||
|
||||
public addNewMessageEventHandler(handler: (event: Api.Message) => any) {
|
||||
private onMessage = async (event: NewMessageEvent) => {
|
||||
// 能用的东西基本都在 message 里面,直接调用 event 里的会 undefined
|
||||
this.client.addEventHandler(event => handler(event.message), new NewMessage({}));
|
||||
for (const handler of this.onMessageHandlers) {
|
||||
const res = await handler(event.message);
|
||||
if (res) return;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 注册消息处理器
|
||||
* @param handler 此方法返回 true 可以阻断下面的处理器
|
||||
*/
|
||||
public addNewMessageEventHandler(handler: MessageHandler) {
|
||||
this.onMessageHandlers.push(handler);
|
||||
}
|
||||
|
||||
public removeNewMessageEventHandler(handler: MessageHandler) {
|
||||
this.onMessageHandlers.includes(handler) && this.onMessageHandlers.splice(this.onMessageHandlers.indexOf(handler), 1);
|
||||
}
|
||||
|
||||
public addEditedMessageEventHandler(handler: (event: EditedMessageEvent) => any) {
|
||||
|
@ -56,14 +75,14 @@ export class Telegram {
|
|||
}
|
||||
|
||||
public async getChat(entity: EntityLike) {
|
||||
return new TelegramChat(this.client, await this.client.getEntity(entity), this.waitForInputHelper);
|
||||
return new TelegramChat(this.client, await this.client.getEntity(entity), this.waitForMessageHelper);
|
||||
}
|
||||
}
|
||||
|
||||
export class TelegramChat {
|
||||
constructor(private client: TelegramClient,
|
||||
private entity: Entity,
|
||||
private waitForInputHelper: WaitForInputHelper) {
|
||||
constructor(private readonly client: TelegramClient,
|
||||
private readonly entity: Entity,
|
||||
private readonly waitForInputHelper: WaitForMessageHelper) {
|
||||
}
|
||||
|
||||
public async sendMessage(params: SendMessageParams) {
|
||||
|
@ -83,6 +102,6 @@ export class TelegramChat {
|
|||
}
|
||||
|
||||
public async waitForInput() {
|
||||
return this.waitForInputHelper.waitForInput(this.entity.id);
|
||||
return this.waitForInputHelper.waitForMessage(this.entity.id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { Telegram } from '../client/Telegram';
|
||||
import SetupService from '../services/SetupService';
|
||||
import { Api } from 'telegram';
|
||||
import { getLogger } from 'log4js';
|
||||
|
||||
export default class SetupController {
|
||||
private readonly setupService: SetupService;
|
||||
private log = getLogger('SetupController');
|
||||
private isInProgress = false;
|
||||
// 创建的 UserBot
|
||||
private tgUser: Telegram;
|
||||
|
||||
constructor(tgBot: Telegram) {
|
||||
this.setupService = new SetupService(tgBot);
|
||||
tgBot.addNewMessageEventHandler(this.handleMessage);
|
||||
}
|
||||
|
||||
private handleMessage = async (message: Api.Message) => {
|
||||
if (this.isInProgress) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.text === '/setup') {
|
||||
this.isInProgress = true;
|
||||
try {
|
||||
const result = await this.setupService.claimOwner(message.sender.id);
|
||||
if (!result) return true;
|
||||
}
|
||||
catch (e) {
|
||||
this.log.error('Claim Owner 失败', e);
|
||||
}
|
||||
await this.setupService.informOwner('创建 Telegram UserBot,请输入你的手机号码(需要带国家区号,例如:+86)');
|
||||
try {
|
||||
const phoneNumber = await this.setupService.waitForOwnerInput();
|
||||
await this.setupService.informOwner('正在登录,请稍候…');
|
||||
this.tgUser = await this.setupService.createUserBot(phoneNumber);
|
||||
await this.setupService.informOwner(`登录成功`);
|
||||
}
|
||||
catch (e) {
|
||||
this.log.error('创建 UserBot 失败', e);
|
||||
}
|
||||
this.isInProgress = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
|
@ -2,24 +2,25 @@ import { Telegram } from '../client/Telegram';
|
|||
import { BigInteger } from 'big-integer';
|
||||
import { Api } from 'telegram';
|
||||
|
||||
export default class WaitForInputHelper {
|
||||
export default class WaitForMessageHelper {
|
||||
// BugInteger 好像不能用 === 判断,Telegram 的 ID 还没有超过 number
|
||||
private map = new Map<number, (event: Api.Message) => any>();
|
||||
|
||||
constructor(private tg: Telegram) {
|
||||
tg.addNewMessageEventHandler(e => {
|
||||
tg.addNewMessageEventHandler(async e => {
|
||||
const handler = this.map.get(Number(e.chat.id));
|
||||
if (handler) {
|
||||
this.map.delete(Number(e.chat.id));
|
||||
handler(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public waitForInput(chatId: BigInteger | number) {
|
||||
public waitForMessage(chatId: BigInteger | number) {
|
||||
return new Promise<Api.Message>(resolve => {
|
||||
chatId = Number(chatId);
|
||||
console.log(chatId);
|
||||
this.map.set(chatId, resolve);
|
||||
});
|
||||
}
|
26
src/index.ts
26
src/index.ts
|
@ -1,15 +1,25 @@
|
|||
import { Telegram } from './client/Telegram';
|
||||
import { config } from './providers/userConfig';
|
||||
import { getLogger, configure } from 'log4js';
|
||||
import SetupController from './controllers/SetupController';
|
||||
|
||||
(async () => {
|
||||
configure({
|
||||
appenders: {
|
||||
console: { type: 'console' },
|
||||
},
|
||||
categories: {
|
||||
default: { level: 'debug', appenders: ['console'] },
|
||||
},
|
||||
});
|
||||
const log = getLogger('Main');
|
||||
log.debug('正在登录 TG Bot');
|
||||
const bot = await Telegram.create({
|
||||
botAuthToken: process.env.TG_BOT_TOKEN,
|
||||
});
|
||||
const me = await bot.getChat('@Clansty');
|
||||
const a = await me.waitForInput();
|
||||
console.log(a);
|
||||
const b = await me.waitForInput();
|
||||
console.log(b);
|
||||
await me.sendMessage({
|
||||
message: a.message + b.message,
|
||||
});
|
||||
log.debug('TG Bot 登录完成');
|
||||
if (!config.isSetup) {
|
||||
log.info('当前服务器未配置,请向 Bot 发送 /setup 来设置');
|
||||
const setupController = new SetupController(bot);
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import fs from 'fs';
|
||||
import fsP from 'fs/promises'
|
||||
|
||||
type UserConfig = {
|
||||
owner: number
|
||||
userBotSession: string;
|
||||
qqUin: number;
|
||||
qqPassword: string;
|
||||
|
@ -10,6 +12,7 @@ type UserConfig = {
|
|||
const CONFIG_PATH = './data/config.json';
|
||||
|
||||
const defaultConfig: UserConfig = {
|
||||
owner: 0,
|
||||
userBotSession: '',
|
||||
qqUin: 0,
|
||||
qqPassword: '',
|
||||
|
@ -20,6 +23,6 @@ export const config: UserConfig = fs.existsSync(CONFIG_PATH) ?
|
|||
JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) :
|
||||
defaultConfig;
|
||||
|
||||
export const saveConfig = () => {
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 0), 'utf8');
|
||||
export const saveConfig = async () => {
|
||||
await fsP.writeFile(CONFIG_PATH, JSON.stringify(config, null, 0), 'utf8');
|
||||
};
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import { Telegram, TelegramChat } from '../client/Telegram';
|
||||
import { config, saveConfig } from '../providers/userConfig';
|
||||
import { getLogger } from 'log4js';
|
||||
import { BigInteger } from 'big-integer';
|
||||
|
||||
export default class SetupService {
|
||||
private owner: TelegramChat;
|
||||
private log = getLogger('SetupService');
|
||||
|
||||
constructor(private readonly tgBot: Telegram) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在设置阶段,第一个 start bot 的用户成为 bot 主人
|
||||
* @param userId 申请成为主人的用户 ID
|
||||
* @return {boolean} 是否成功,false 的话就是被占用了
|
||||
*/
|
||||
public async claimOwner(userId: number | BigInteger) {
|
||||
userId = Number(userId);
|
||||
if (!this.owner) {
|
||||
config.owner = userId;
|
||||
await saveConfig();
|
||||
await this.setupOwner();
|
||||
this.log.info(`用户 ID: ${userId} 成为了 Bot 主人`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async setupOwner() {
|
||||
if (!this.owner && config.owner) {
|
||||
this.owner = await this.tgBot.getChat(config.owner);
|
||||
}
|
||||
}
|
||||
|
||||
public async informOwner(message: string) {
|
||||
if (!this.owner) {
|
||||
throw new Error('应该不会运行到这里');
|
||||
}
|
||||
await this.owner.sendMessage({ message });
|
||||
}
|
||||
|
||||
public async waitForOwnerInput() {
|
||||
if (!this.owner) {
|
||||
throw new Error('应该不会运行到这里');
|
||||
}
|
||||
const { message } = await this.owner.waitForInput();
|
||||
return message;
|
||||
}
|
||||
|
||||
public async createUserBot(phoneNumber: string) {
|
||||
if (!this.owner) {
|
||||
throw new Error('应该不会运行到这里');
|
||||
}
|
||||
const bot = await Telegram.create({
|
||||
phoneNumber,
|
||||
password: async (hint?: string) => {
|
||||
await this.owner.sendMessage({
|
||||
message: `请输入你的二步验证密码${hint ? '\n密码提示:' + hint : ''}`,
|
||||
});
|
||||
return await this.waitForOwnerInput();
|
||||
},
|
||||
phoneCode: async (isCodeViaApp?: boolean) => {
|
||||
await this.owner.sendMessage({
|
||||
message: `请输入你${isCodeViaApp ? ' Telegram APP 中' : '手机上'}收到的验证码`,
|
||||
});
|
||||
return await this.waitForOwnerInput();
|
||||
},
|
||||
onError: (err) => this.log.error(err),
|
||||
});
|
||||
return bot;
|
||||
}
|
||||
}
|
|
@ -1072,7 +1072,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"log4js@npm:^6.3.0":
|
||||
"log4js@npm:^6.3.0, log4js@npm:^6.4.1":
|
||||
version: 6.4.1
|
||||
resolution: "log4js@npm:6.4.1"
|
||||
dependencies:
|
||||
|
@ -1494,6 +1494,7 @@ __metadata:
|
|||
dependencies:
|
||||
"@prisma/client": ^3.9.2
|
||||
"@types/node": ^17.0.18
|
||||
log4js: ^6.4.1
|
||||
oicq: ^2.2.0
|
||||
prisma: ^3.9.2
|
||||
telegram: ^2.5.0
|
||||
|
|
Loading…
Reference in New Issue