mirror of https://github.com/Nofated095/Q2TG.git
Compare commits
9 Commits
61f163d07f
...
d7f4da7d0e
Author | SHA1 | Date |
---|---|---|
Nofated095 | d7f4da7d0e | |
Nofated095 | 4d9d5abc41 | |
Clansty | 17669d5a60 | |
Clansty | f3a0a583de | |
Clansty | 00a8e11e35 | |
Clansty | 6b1676365e | |
Clansty | e76c4b4db9 | |
Clansty | c0cadee504 | |
Clansty | b64c6f7a4d |
|
@ -217,15 +217,15 @@ export default class OicqClient extends Client {
|
||||||
await contact.uploadImages(imgs);
|
await contact.uploadImages(imgs);
|
||||||
const compressed = await gzip(pb.encode({
|
const compressed = await gzip(pb.encode({
|
||||||
1: nodes,
|
1: nodes,
|
||||||
2: {
|
2: [{
|
||||||
1: 'MultiMsg',
|
1: 'MultiMsg',
|
||||||
2: {
|
2: {
|
||||||
1: nodes,
|
1: nodes,
|
||||||
},
|
},
|
||||||
},
|
}],
|
||||||
}));
|
}));
|
||||||
const _uploadMultiMsg = Reflect.get(contact, '_uploadMultiMsg') as Function;
|
const _uploadMultiMsg = Reflect.get(contact, '_uploadMultiMsg') as Function;
|
||||||
const resid = await _uploadMultiMsg.apply(contact, compressed);
|
const resid = await _uploadMultiMsg.apply(contact, [compressed]);
|
||||||
return {
|
return {
|
||||||
tSum: nodes.length,
|
tSum: nodes.length,
|
||||||
resid,
|
resid,
|
||||||
|
|
|
@ -2,4 +2,9 @@ import random from '../utils/random';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
picture: () => random.pick('🎆', '🌃', '🌇', '🎇', '🌌', '🌠', '🌅', '🌉', '🏞', '🌆', '🌄', '🖼', '🗾', '🎑', '🏙', '🌁'),
|
picture: () => random.pick('🎆', '🌃', '🌇', '🎇', '🌌', '🌠', '🌅', '🌉', '🏞', '🌆', '🌄', '🖼', '🗾', '🎑', '🏙', '🌁'),
|
||||||
|
color(index: number) {
|
||||||
|
const arr = [...new Intl.Segmenter().segment('🔴🟠🟡🟢🔵🟣⚫️⚪️🟤')].map(x => x.segment);
|
||||||
|
index = index % arr.length;
|
||||||
|
return arr[index];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ enum flags {
|
||||||
DISABLE_POKE = 1 << 3,
|
DISABLE_POKE = 1 << 3,
|
||||||
NO_DELETE_MESSAGE = 1 << 4,
|
NO_DELETE_MESSAGE = 1 << 4,
|
||||||
NO_AUTO_CREATE_PM = 1 << 5,
|
NO_AUTO_CREATE_PM = 1 << 5,
|
||||||
|
COLOR_EMOJI_PREFIX = 1 << 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default flags;
|
export default flags;
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import Instance from '../models/Instance';
|
||||||
|
import Telegram from '../client/Telegram';
|
||||||
|
import OicqClient from '../client/OicqClient';
|
||||||
|
import { Api } from 'telegram';
|
||||||
|
|
||||||
|
export default class AliveCheckController {
|
||||||
|
constructor(private readonly instance: Instance,
|
||||||
|
private readonly tgBot: Telegram,
|
||||||
|
private readonly tgUser: Telegram,
|
||||||
|
private readonly oicq: OicqClient) {
|
||||||
|
tgBot.addNewMessageEventHandler(this.handleMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMessage = async (message: Api.Message) => {
|
||||||
|
if (!message.sender.id.eq(this.instance.owner) || !message.isPrivate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!['似了吗', '/alive'].includes(message.message)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.reply({
|
||||||
|
message: this.genMessage(this.instance.id === 0 ? Instance.instances : [this.instance]),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private genMessage(instances: Instance[]): string {
|
||||||
|
const boolToStr = (value: boolean) => {
|
||||||
|
return value ? '好' : '坏';
|
||||||
|
};
|
||||||
|
const messageParts: string[] = [];
|
||||||
|
|
||||||
|
for (const instance of instances) {
|
||||||
|
const oicq = instance.oicq;
|
||||||
|
const tgBot = instance.tgBot;
|
||||||
|
const tgUser = instance.tgUser;
|
||||||
|
|
||||||
|
const tgUserName = (tgUser.me.username || tgUser.me.usernames.length) ?
|
||||||
|
'@' + (tgUser.me.username || tgUser.me.usernames[0].username) : tgUser.me.firstName;
|
||||||
|
messageParts.push([
|
||||||
|
`Instance #${instance.id}`,
|
||||||
|
|
||||||
|
`QQ <code>${instance.qqUin}</code>\t` +
|
||||||
|
`${boolToStr(oicq.isOnline())}\t${oicq.stat.msg_cnt_per_min} msg/min`,
|
||||||
|
|
||||||
|
`TG @${tgBot.me.username}\t${boolToStr(tgBot.isOnline)}`,
|
||||||
|
|
||||||
|
`TG User ${tgUserName}\t${boolToStr(tgBot.isOnline)}`,
|
||||||
|
].join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageParts.join('\n\n');
|
||||||
|
};
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ export default {
|
||||||
const aspectRatio = dimensions.width / dimensions.height;
|
const aspectRatio = dimensions.width / dimensions.height;
|
||||||
if (aspectRatio > 20 || aspectRatio < 1 / 20
|
if (aspectRatio > 20 || aspectRatio < 1 / 20
|
||||||
|| dimensions.width + dimensions.height > 10000
|
|| dimensions.width + dimensions.height > 10000
|
||||||
|| file.length > 1024 * 1024 * 10
|
|
||||||
) {
|
) {
|
||||||
// 让 Telegram 服务器下载
|
// 让 Telegram 服务器下载
|
||||||
return url
|
return url
|
||||||
|
|
10
src/index.ts
10
src/index.ts
|
@ -12,18 +12,22 @@ import db from './models/db';
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const log = getLogger('Main');
|
const log = getLogger('Main');
|
||||||
|
|
||||||
|
if (!process.versions.node.startsWith('18.')) {
|
||||||
|
log.warn('当前正在使用的 Node.JS 版本为', process.versions.node, ',未经测试');
|
||||||
|
}
|
||||||
|
|
||||||
process.on('unhandledRejection', error => {
|
process.on('unhandledRejection', error => {
|
||||||
log.error('UnhandledException: ', error);
|
log.error('UnhandledException: ', error);
|
||||||
});
|
});
|
||||||
const instanceEntries = await db.instance.findMany();
|
const instanceEntries = await db.instance.findMany();
|
||||||
|
|
||||||
const instances = [] as Instance[];
|
|
||||||
if (!instanceEntries.length) {
|
if (!instanceEntries.length) {
|
||||||
instances.push(await Instance.start(0));
|
await Instance.start(0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (const instanceEntry of instanceEntries) {
|
for (const instanceEntry of instanceEntries) {
|
||||||
instances.push(await Instance.start(instanceEntry.id));
|
await Instance.start(instanceEntry.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,11 @@ import HugController from '../controllers/HugController';
|
||||||
import QuotLyController from '../controllers/QuotLyController';
|
import QuotLyController from '../controllers/QuotLyController';
|
||||||
import MiraiSkipFilterController from '../controllers/MiraiSkipFilterController';
|
import MiraiSkipFilterController from '../controllers/MiraiSkipFilterController';
|
||||||
import env from './env';
|
import env from './env';
|
||||||
|
import AliveCheckController from '../controllers/AliveCheckController';
|
||||||
|
|
||||||
export default class Instance {
|
export default class Instance {
|
||||||
|
public static readonly instances: Instance[] = [];
|
||||||
|
|
||||||
private _owner = 0;
|
private _owner = 0;
|
||||||
private _isSetup = false;
|
private _isSetup = false;
|
||||||
private _workMode = '';
|
private _workMode = '';
|
||||||
|
@ -57,6 +60,7 @@ export default class Instance {
|
||||||
private hugController: HugController;
|
private hugController: HugController;
|
||||||
private quotLyController: QuotLyController;
|
private quotLyController: QuotLyController;
|
||||||
private miraiSkipFilterController: MiraiSkipFilterController;
|
private miraiSkipFilterController: MiraiSkipFilterController;
|
||||||
|
private aliveCheckController: AliveCheckController;
|
||||||
|
|
||||||
private constructor(public readonly id: number) {
|
private constructor(public readonly id: number) {
|
||||||
this.log = getLogger(`Instance - ${this.id}`);
|
this.log = getLogger(`Instance - ${this.id}`);
|
||||||
|
@ -150,6 +154,7 @@ export default class Instance {
|
||||||
this.oicqErrorNotifyController = new OicqErrorNotifyController(this, this.oicq);
|
this.oicqErrorNotifyController = new OicqErrorNotifyController(this, this.oicq);
|
||||||
this.requestController = new RequestController(this, this.tgBot, this.oicq);
|
this.requestController = new RequestController(this, this.tgBot, this.oicq);
|
||||||
this.configController = new ConfigController(this, this.tgBot, this.oicq);
|
this.configController = new ConfigController(this, this.tgBot, this.oicq);
|
||||||
|
this.aliveCheckController = new AliveCheckController(this, this.tgBot, this.oicq);
|
||||||
this.deleteMessageController = new DeleteMessageController(this, this.tgBot, this.oicq);
|
this.deleteMessageController = new DeleteMessageController(this, this.tgBot, this.oicq);
|
||||||
this.miraiSkipFilterController = new MiraiSkipFilterController(this, this.tgBot, this.oicq);
|
this.miraiSkipFilterController = new MiraiSkipFilterController(this, this.tgBot, this.oicq);
|
||||||
this.inChatCommandsController = new InChatCommandsController(this, this.tgBot, this.oicq);
|
this.inChatCommandsController = new InChatCommandsController(this, this.tgBot, this.oicq);
|
||||||
|
@ -170,6 +175,7 @@ export default class Instance {
|
||||||
|
|
||||||
public static async start(instanceId: number, botToken?: string) {
|
public static async start(instanceId: number, botToken?: string) {
|
||||||
const instance = new this(instanceId);
|
const instance = new this(instanceId);
|
||||||
|
Instance.instances.push(instance);
|
||||||
await instance.login(botToken);
|
await instance.login(botToken);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ import { escapeXml } from 'icqq/lib/common';
|
||||||
import Docker from 'dockerode';
|
import Docker from 'dockerode';
|
||||||
import ReplyKeyboardHide = Api.ReplyKeyboardHide;
|
import ReplyKeyboardHide = Api.ReplyKeyboardHide;
|
||||||
import env from '../models/env';
|
import env from '../models/env';
|
||||||
|
import { CustomFile } from 'telegram/client/uploads';
|
||||||
|
import flags from '../constants/flags';
|
||||||
|
|
||||||
const NOT_CHAINABLE_ELEMENTS = ['flash', 'record', 'video', 'location', 'share', 'json', 'xml', 'poke'];
|
const NOT_CHAINABLE_ELEMENTS = ['flash', 'record', 'video', 'location', 'share', 'json', 'xml', 'poke'];
|
||||||
|
|
||||||
|
@ -93,7 +95,11 @@ export default class ForwardService {
|
||||||
public async forwardFromQq(event: PrivateMessageEvent | GroupMessageEvent, pair: Pair) {
|
public async forwardFromQq(event: PrivateMessageEvent | GroupMessageEvent, pair: Pair) {
|
||||||
try {
|
try {
|
||||||
const tempFiles: FileResult[] = [];
|
const tempFiles: FileResult[] = [];
|
||||||
let message = '', files: FileLike[] = [], buttons: ButtonLike[] = [], replyTo = 0;
|
let message = '',
|
||||||
|
files: FileLike[] = [],
|
||||||
|
buttons: ButtonLike[] = [],
|
||||||
|
replyTo = 0,
|
||||||
|
forceDocument = false;
|
||||||
let messageHeader = '', sender = '';
|
let messageHeader = '', sender = '';
|
||||||
if (event.message_type === 'group') {
|
if (event.message_type === 'group') {
|
||||||
// 产生头部,这和工作模式没有关系
|
// 产生头部,这和工作模式没有关系
|
||||||
|
@ -101,7 +107,10 @@ export default class ForwardService {
|
||||||
if (event.anonymous) {
|
if (event.anonymous) {
|
||||||
sender = `[${sender}]${event.anonymous.name}`;
|
sender = `[${sender}]${event.anonymous.name}`;
|
||||||
}
|
}
|
||||||
messageHeader = `<b>${helper.htmlEscape(sender)}</b>: `;
|
if ((pair.flags | this.instance.flags) & flags.COLOR_EMOJI_PREFIX) {
|
||||||
|
messageHeader += emoji.color(event.sender.user_id);
|
||||||
|
}
|
||||||
|
messageHeader += `<b>${helper.htmlEscape(sender)}</b>: `;
|
||||||
}
|
}
|
||||||
const useSticker = (file: FileLike) => {
|
const useSticker = (file: FileLike) => {
|
||||||
files.push(file);
|
files.push(file);
|
||||||
|
@ -111,7 +120,7 @@ export default class ForwardService {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const useForward = async (resId: string) => {
|
const useForward = async (resId: string) => {
|
||||||
if(env.CRV_API) {
|
if (env.CRV_API) {
|
||||||
try {
|
try {
|
||||||
const messages = await pair.qq.getForwardMsg(resId);
|
const messages = await pair.qq.getForwardMsg(resId);
|
||||||
message = helper.generateForwardBrief(messages);
|
message = helper.generateForwardBrief(messages);
|
||||||
|
@ -186,7 +195,12 @@ export default class ForwardService {
|
||||||
useSticker(await convert.webp(elem.file as string, () => fetchFile(elem.url)));
|
useSticker(await convert.webp(elem.file as string, () => fetchFile(elem.url)));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
files.push(await helper.downloadToCustomFile(url, !(message || messageHeader)));
|
const file = await helper.downloadToCustomFile(url, !(message || messageHeader));
|
||||||
|
files.push(file);
|
||||||
|
if (file instanceof CustomFile && elem.type === 'image' && file.size > 10 * 1024 * 1024) {
|
||||||
|
this.log.info('强制使用文件发送');
|
||||||
|
forceDocument = true;
|
||||||
|
}
|
||||||
buttons.push(Button.url(`${emoji.picture()} 查看原图`, url));
|
buttons.push(Button.url(`${emoji.picture()} 查看原图`, url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,7 +230,12 @@ export default class ForwardService {
|
||||||
}
|
}
|
||||||
this.log.info('正在发送媒体,长度', helper.hSize(elem.size));
|
this.log.info('正在发送媒体,长度', helper.hSize(elem.size));
|
||||||
try {
|
try {
|
||||||
files.push(await helper.downloadToCustomFile(url, !(message || messageHeader), elem.name));
|
const file = await helper.downloadToCustomFile(url, !(message || messageHeader), elem.name);
|
||||||
|
if (file instanceof CustomFile && file.size > 10 * 1024 * 1024) {
|
||||||
|
this.log.info('强制使用文件发送');
|
||||||
|
forceDocument = true;
|
||||||
|
}
|
||||||
|
files.push(file);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
this.log.error('下载媒体失败', e);
|
this.log.error('下载媒体失败', e);
|
||||||
|
@ -353,7 +372,9 @@ export default class ForwardService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
const messageToSend: SendMessageParams = {};
|
const messageToSend: SendMessageParams = {
|
||||||
|
forceDocument: forceDocument as any, // 恼
|
||||||
|
};
|
||||||
message && (messageToSend.message = message);
|
message && (messageToSend.message = message);
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
messageToSend.file = files[0];
|
messageToSend.file = files[0];
|
||||||
|
@ -398,6 +419,9 @@ export default class ForwardService {
|
||||||
helper.getUserDisplayName(await message.forward.getChat() || await message.forward.getSender())) :
|
helper.getUserDisplayName(await message.forward.getChat() || await message.forward.getSender())) :
|
||||||
'') +
|
'') +
|
||||||
': \n';
|
': \n';
|
||||||
|
if ((pair.flags | this.instance.flags) & flags.COLOR_EMOJI_PREFIX) {
|
||||||
|
messageHeader = emoji.color(message.senderId.toJSNumber()) + messageHeader;
|
||||||
|
}
|
||||||
if (message.photo instanceof Api.Photo ||
|
if (message.photo instanceof Api.Photo ||
|
||||||
// stickers 和以文件发送的图片都是这个
|
// stickers 和以文件发送的图片都是这个
|
||||||
message.document?.mimeType?.startsWith('image/')) {
|
message.document?.mimeType?.startsWith('image/')) {
|
||||||
|
|
Loading…
Reference in New Issue