diff --git a/server/plugins/com.msgbyte.prettyinvite/package.json b/server/plugins/com.msgbyte.prettyinvite/package.json new file mode 100644 index 00000000..bde21943 --- /dev/null +++ b/server/plugins/com.msgbyte.prettyinvite/package.json @@ -0,0 +1,14 @@ +{ + "name": "tailchat-plugin-prettyinvite", + "version": "1.0.0", + "main": "index.js", + "author": "moonrailgun", + "description": "邀请链接美化", + "license": "MIT", + "private": true, + "scripts": {}, + "devDependencies": {}, + "dependencies": { + "tailchat-server-sdk": "*" + } +} diff --git a/server/plugins/com.msgbyte.prettyinvite/services/prettyinvite.service.ts b/server/plugins/com.msgbyte.prettyinvite/services/prettyinvite.service.ts new file mode 100644 index 00000000..aa6a7658 --- /dev/null +++ b/server/plugins/com.msgbyte.prettyinvite/services/prettyinvite.service.ts @@ -0,0 +1,121 @@ +import type { GroupBaseInfo, TcPureContext } from 'tailchat-server-sdk'; +import { TcService } from 'tailchat-server-sdk'; + +const badgeTemplate = (title: string, text: string) => { + // 这个6.75计算的会不准、正确应该是5.5但是svg压缩时有误差 + const titleWidth = calcWordWidth(title) * 6.75 + 10; + const iconWidth = 20; + const textWidth = calcWordWidth(text) * 6.75 + 20; + const textOffset = 60; + + // Fork from https://shields.io/ + return ` + + ${title}: ${text} + + + + + + + + + + + + + + + ${title} + + ${text} + + + + + +`; +}; + +/** + * 邀请链接美化 + */ +class PrettyinviteService extends TcService { + get serviceName() { + return 'plugin:com.msgbyte.prettyinvite'; + } + + onInit() { + this.registerAction('badge', this.badge, { + params: { + inviteCode: 'string', + }, + }); + + this.registerAuthWhitelist(['/plugin:com.msgbyte.prettyinvite/badge']); + } + + async badge( + ctx: TcPureContext<{ + inviteCode: string; + }> + ) { + const inviteCode = ctx.params.inviteCode; + + const inviteInfo: any = await ctx.call('group.invite.findInviteByCode', { + code: inviteCode, + }); + + if (!inviteInfo) { + return { + __raw: true, + header: { + 'content-type': 'image/svg+xml; charset=utf-8', + }, + html: badgeTemplate('Not Found', 'NaN'), + }; + } + + const groupId = inviteInfo.groupId; + const group: GroupBaseInfo = await ctx.call('group.getGroupBasicInfo', { + groupId: String(groupId), + }); + + return { + __raw: true, + header: { + 'content-type': 'image/svg+xml; charset=utf-8', + }, + html: badgeTemplate(group.name, String(group.memberCount)), + }; + } +} + +export default PrettyinviteService; + +/** + * 计算文本占据宽度 + * 单位为字符宽度 + */ +function calcWordWidth(text: string): number { + return text + .split('') + .map((char) => (char.charCodeAt(0) < 255 ? 1 : 2)) // 判断是否为ascii码 + .reduce((prev, curr) => prev + curr, 0); +} diff --git a/server/services/core/gateway.service.ts b/server/services/core/gateway.service.ts index b3cac0c8..e35c0526 100644 --- a/server/services/core/gateway.service.ts +++ b/server/services/core/gateway.service.ts @@ -168,6 +168,20 @@ export default class ApiService extends TcService { ) { // Async function which return with Promise res.setHeader('X-Node-ID', ctx.nodeID); + + if (data['__raw']) { + if (data['header']) { + Object.entries(data['header']).forEach(([key, value]) => { + res.setHeader(key, String(value)); + }); + } + + res.write(data['html'] ?? ''); + + res.end(); + return; + } + return { code: res.statusCode, data }; }, @@ -308,6 +322,7 @@ export default class ApiService extends TcService { send(req, './public/index.html', { root: process.cwd() }).pipe(res); } }, + whitelist: [], autoAliases: false, }, ];