feat: 使用本地服务渲染 richHeader

This commit is contained in:
Clansty 2024-02-11 13:03:33 +08:00
parent 557c72e790
commit 5934d465c9
No known key found for this signature in database
GPG Key ID: 3A6BE8BAF2EDE134
11 changed files with 186 additions and 23 deletions

View File

@ -0,0 +1,26 @@
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta property="og:image" content="https://q1.qlogo.cn/g?b=qq&nk=<%= userId %>&s=0"/>
<% if (!!title) { %>
<meta property="og:site_name" content="「<%= title %>」"/>
<% }else { %>
<meta property="og:site_name" content="「<%= role %>」"/>
<% } %>
<meta property="og:title" content="<%= name %>"/>
<title>群成员:<%= name %></title>
<style>
html, body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<img style="width: 100%" src="https://q1.qlogo.cn/g?b=qq&nk=<%= userId %>&s=0" alt="头像">
<p><%= role %></p>
<p><%= name %></p>
<p><%= userId %></p>
</body>
</html>

View File

@ -16,6 +16,7 @@
"@types/cli-progress": "^3.11.5",
"@types/date-and-time": "^3.0.3",
"@types/dockerode": "^3.3.23",
"@types/ejs": "^3.1.5",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.17",
@ -32,6 +33,7 @@
"date-and-time": "^3.1.1",
"dockerode": "^4.0.2",
"dotenv": "^16.4.1",
"ejs": "^3.1.9",
"eviltransform": "^0.2.2",
"fastify": "^4.26.0",
"file-type": "^19.0.0",

View File

@ -90,6 +90,7 @@ model ForwardPair {
instanceId Int @default(0)
instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
flags Int @default(0)
apiKey String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
@@unique([qqRoomId, instanceId])
@@unique([tgChatId, instanceId])

View File

@ -2,6 +2,7 @@ import { getLogger } from 'log4js';
import Fastify from 'fastify';
import FastifyProxy from '@fastify/http-proxy';
import env from '../models/env';
import richHeader from './richHeader';
const log = getLogger('Web Api');
const fastify = Fastify();
@ -10,6 +11,8 @@ fastify.get('/', async (request, reply) => {
return { hello: 'Q2TG' };
});
fastify.register(richHeader, { prefix: '/richHeader' });
if (env.UI_PROXY) {
fastify.register(FastifyProxy, {
upstream: env.UI_PROXY,

View File

@ -0,0 +1,36 @@
import { FastifyPluginCallback } from 'fastify';
import { Pair } from '../models/Pair';
import ejs from 'ejs';
import fs from 'fs';
import { Group } from 'icqq';
const template = ejs.compile(fs.readFileSync('./assets/richHeader.ejs', 'utf-8'));
export default ((fastify, opts, done) => {
fastify.get<{
Params: { apiKey: string, userId: string }
}>('/:apiKey/:userId', async (request, reply) => {
const pair = Pair.getByApiKey(request.params.apiKey);
if (!pair) {
reply.code(404);
return 'Group not found';
}
const group = pair.qq as Group;
const members = await group.getMemberMap();
const member = members.get(Number(request.params.userId));
if (!member) {
reply.code(404);
return 'Member not found';
}
reply.type('text/html');
return template({
userId: request.params.userId,
title: member.title,
name: member.card || member.nickname,
role: member.role,
});
});
done();
}) as FastifyPluginCallback;

View File

@ -3,7 +3,7 @@ import OicqClient from '../client/OicqClient';
import ForwardService from '../services/ForwardService';
import {
Friend,
FriendPokeEvent,
FriendPokeEvent, Group,
GroupMessageEvent,
GroupPokeEvent,
MemberIncreaseEvent,

View File

@ -28,7 +28,7 @@ export default class ForwardPairs {
const tg = await tgBot.getChat(Number(i.tgChatId));
const tgUserChat = await tgUser.getChat(Number(i.tgChatId));
if (qq && tg && tgUserChat) {
this.pairs.push(new Pair(qq, tg, tgUserChat, i.id, i.flags));
this.pairs.push(new Pair(qq, tg, tgUserChat, i.id, i.flags, i.apiKey));
}
}
catch (e) {
@ -51,7 +51,7 @@ export default class ForwardPairs {
instanceId: this.instanceId,
},
});
this.pairs.push(new Pair(qq, tg, tgUser, dbEntry.id, dbEntry.flags));
this.pairs.push(new Pair(qq, tg, tgUser, dbEntry.id, dbEntry.flags, dbEntry.apiKey));
return dbEntry;
}

View File

@ -9,6 +9,12 @@ import db from './db';
const log = getLogger('ForwardPair');
export class Pair {
private static readonly apiKeyMap = new Map<string, Pair>();
public static getByApiKey(key: string) {
return this.apiKeyMap.get(key);
}
// 群成员的 tg 账号对应它对应的 QQ 账号获取到的 Group 对象
// 只有群组模式有效
public readonly instanceMapForTg = {} as { [tgUserId: string]: Group };
@ -19,7 +25,11 @@ export class Pair {
public readonly tgUser: TelegramChat,
public dbId: number,
private _flags: number,
public readonly apiKey: string,
) {
if (apiKey) {
Pair.apiKeyMap.set(apiKey, this);
}
}
// 更新 TG 群组的头像和简介

View File

@ -32,6 +32,7 @@ const configParsed = z.object({
LISTEN_PORT: z.string().regex(/^\d+$/).transform(Number).default('8080'),
UI_PATH: z.string().optional(),
UI_PROXY: z.string().url().optional(),
WEB_ENDPOINT: z.string().url().optional(),
}).safeParse(process.env);
if (!configParsed.success) {

View File

@ -26,7 +26,7 @@ import fsP from 'fs/promises';
import eviltransform from 'eviltransform';
import silk from '../encoding/silk';
import axios from 'axios';
import { md5Hex } from '../utils/hashing';
import { md5B64, md5Hex } from '../utils/hashing';
import Instance from '../models/Instance';
import { Pair } from '../models/Pair';
import OicqClient from '../client/OicqClient';
@ -385,11 +385,19 @@ export default class ForwardService {
else if (event.message_type === 'group' && (pair.flags | this.instance.flags) & flags.RICH_HEADER) {
// 没有文件时才能显示链接预览
richHeaderUsed = true;
const url = new URL('https://q2tg-header.clansty.workers.dev');
url.searchParams.set('name', sender);
url.searchParams.set('title', 'title' in event.sender ? event.sender.title : '');
url.searchParams.set('role', 'role' in event.sender ? event.sender.role : '');
url.searchParams.set('id', event.sender.user_id.toString());
let url: URL;
if (env.WEB_ENDPOINT) {
url = new URL(`${env.WEB_ENDPOINT}/richHeader/${pair.apiKey}/${event.sender.user_id}`);
// 防止群名片刷新慢
url.searchParams.set('hash', md5B64(messageHeader).substring(0, 10));
}
else {
url = new URL('https://q2tg-header.clansty.workers.dev');
url.searchParams.set('name', sender);
url.searchParams.set('title', 'title' in event.sender ? event.sender.title : '');
url.searchParams.set('role', 'role' in event.sender ? event.sender.role : '');
url.searchParams.set('id', event.sender.user_id.toString());
}
// https://github.com/tdlib/td/blob/437c2d0c6e0ad104022d5ad86ddc8aedc41cb7a8/td/telegram/MessageContent.cpp#L2575
// https://github.com/tdlib/td/blob/437c2d0c6e0ad104022d5ad86ddc8aedc41cb7a8/td/generate/scheme/telegram_api.tl#L1841
// https://github.com/gram-js/gramjs/pull/633

View File

@ -41,6 +41,9 @@ importers:
dotenv:
specifier: ^16.4.1
version: 16.4.1
ejs:
specifier: ^3.1.9
version: 3.1.9
eviltransform:
specifier: ^0.2.2
version: 0.2.2
@ -85,7 +88,7 @@ importers:
version: 0.2.2
telegram:
specifier: https://github.com/clansty/gramjs/releases/download/2.19.10%2Brevert_media/telegram-2.19.10.tgz
version: '@github.com/clansty/gramjs/releases/download/2.19.10%2525252525252Brevert_media/telegram-2.19.10.tgz'
version: '@github.com/clansty/gramjs/releases/download/2.19.10%25252525252525252Brevert_media/telegram-2.19.10.tgz'
tmp-promise:
specifier: ^3.0.3
version: 3.0.3
@ -108,6 +111,9 @@ importers:
'@types/dockerode':
specifier: ^3.3.23
version: 3.3.23
'@types/ejs':
specifier: ^3.1.5
version: 3.1.5
'@types/fluent-ffmpeg':
specifier: ^2.1.24
version: 2.1.24
@ -140,10 +146,10 @@ importers:
version: 5.1.1(sass@1.70.0)
vue:
specifier: ^3.4.18
version: 3.4.18
version: 3.4.18(typescript@5.3.3)
vue-tg:
specifier: ^0.3.0
version: 0.3.0
version: 0.3.0(typescript@5.3.3)
packages:
@ -470,7 +476,7 @@ packages:
peerDependencies:
vue: ^3.0.11
dependencies:
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
dev: true
/@emnapi/runtime@0.45.0:
@ -1517,6 +1523,10 @@ packages:
'@types/node': 20.11.17
dev: true
/@types/ejs@3.1.5:
resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==}
dev: true
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
@ -1585,7 +1595,7 @@ packages:
'@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.9)
'@vue/babel-plugin-jsx': 1.2.1(@babel/core@7.23.9)
vite: 5.1.1(sass@1.70.0)
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
transitivePeerDependencies:
- supports-color
dev: true
@ -1697,7 +1707,7 @@ packages:
dependencies:
'@vue/compiler-ssr': 3.4.18
'@vue/shared': 3.4.18
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
dev: true
/@vue/shared@3.4.18:
@ -1805,6 +1815,13 @@ packages:
dependencies:
color-convert: 1.9.3
/ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: false
/any-base@1.1.0:
resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==}
dev: false
@ -1965,6 +1982,12 @@ packages:
concat-map: 0.0.1
dev: false
/brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
dependencies:
balanced-match: 1.0.2
dev: false
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
@ -2087,6 +2110,14 @@ packages:
escape-string-regexp: 1.0.5
supports-color: 5.5.0
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: false
/chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@ -2506,6 +2537,14 @@ packages:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
/ejs@3.1.9:
resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==}
engines: {node: '>=0.10.0'}
hasBin: true
dependencies:
jake: 10.8.7
dev: false
/electron-to-chromium@1.4.665:
resolution: {integrity: sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==}
dev: true
@ -2774,6 +2813,12 @@ packages:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
dev: false
/filelist@1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
dependencies:
minimatch: 5.1.6
dev: false
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@ -3022,6 +3067,11 @@ packages:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
dev: false
/has-property-descriptors@1.0.1:
resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
dependencies:
@ -3301,6 +3351,17 @@ packages:
resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
dev: false
/jake@10.8.7:
resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==}
engines: {node: '>=10'}
hasBin: true
dependencies:
async: 3.2.5
chalk: 4.1.2
filelist: 1.0.4
minimatch: 3.1.2
dev: false
/jimp@0.22.10:
resolution: {integrity: sha512-lCaHIJAgTOsplyJzC1w/laxSxrbSsEBw4byKwXgUdMmh+ayPsnidTblenQm+IvhIs44Gcuvlb6pd2LQ0wcKaKg==}
dependencies:
@ -3674,6 +3735,13 @@ packages:
brace-expansion: 1.1.11
dev: false
/minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: false
/minimist@0.0.8:
resolution: {integrity: sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==}
requiresBuild: true
@ -3781,7 +3849,7 @@ packages:
treemate: 0.3.11
vdirs: 0.1.8(vue@3.4.18)
vooks: 0.2.12(vue@3.4.18)
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
vueuc: 0.4.58(vue@3.4.18)
dev: true
@ -4685,6 +4753,13 @@ packages:
dependencies:
has-flag: 3.0.0
/supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
dev: false
/svg-tags@1.0.0:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
dev: true
@ -5022,7 +5097,7 @@ packages:
vue: ^3.0.11
dependencies:
evtd: 0.2.4
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
dev: true
/verror@1.10.0:
@ -5076,19 +5151,19 @@ packages:
vue: ^3.0.0
dependencies:
evtd: 0.2.4
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
dev: true
/vue-tg@0.3.0:
/vue-tg@0.3.0(typescript@5.3.3):
resolution: {integrity: sha512-DZ5wt9ZY1Ux3yFH+KWeYyVo4Fd3R4UDoq07XWhA6EqpyfxII9zxT7xPKjTjrpDbvjHRzeuukZBBi4EIiQxtDaQ==}
dependencies:
'@types/telegram-web-app': 7.0.0
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
transitivePeerDependencies:
- typescript
dev: true
/vue@3.4.18:
/vue@3.4.18(typescript@5.3.3):
resolution: {integrity: sha512-0zLRYamFRe0wF4q2L3O24KQzLyLpL64ye1RUToOgOxuWZsb/FhaNRdGmeozdtVYLz6tl94OXLaK7/WQIrVCw1A==}
peerDependencies:
typescript: '*'
@ -5101,6 +5176,7 @@ packages:
'@vue/runtime-dom': 3.4.18
'@vue/server-renderer': 3.4.18(vue@3.4.18)
'@vue/shared': 3.4.18
typescript: 5.3.3
dev: true
/vueuc@0.4.58(vue@3.4.18):
@ -5115,7 +5191,7 @@ packages:
seemly: 0.3.8
vdirs: 0.1.8(vue@3.4.18)
vooks: 0.2.12(vue@3.4.18)
vue: 3.4.18
vue: 3.4.18(typescript@5.3.3)
dev: true
/w3c-hr-time@1.0.2:
@ -5342,7 +5418,7 @@ packages:
- utf-8-validate
dev: false
'@github.com/clansty/gramjs/releases/download/2.19.10%2525252525252Brevert_media/telegram-2.19.10.tgz':
'@github.com/clansty/gramjs/releases/download/2.19.10%25252525252525252Brevert_media/telegram-2.19.10.tgz':
resolution: {tarball: https://github.com/clansty/gramjs/releases/download/2.19.10%2Brevert_media/telegram-2.19.10.tgz}
name: telegram
version: 2.19.10