From dbebbc54e66172653006a002c03b8932cb9d8ed2 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Fri, 19 May 2023 15:52:21 +0800 Subject: [PATCH] feat: add welcome plugin for user which can send welcome message when user join group welcome text support rich syntax and spec tag --- .../components/modals/GroupDetail/Config.tsx | 2 +- client/web/src/plugin/common/reg.ts | 2 +- client/web/src/styles/antd/dark.less | 7 + client/web/tailchat.d.ts | 24 +- package.json | 2 +- pnpm-lock.yaml | 25 + server/packages/sdk/src/services/lib/call.ts | 17 +- server/packages/sdk/src/structs/group.ts | 2 + server/packages/sdk/src/structs/user.ts | 4 +- .../com.msgbyte.welcome/.ministarrc.js | 14 + .../plugins/com.msgbyte.welcome/package.json | 20 + .../services/welcome.service.ts | 95 +++ .../plugins/com.msgbyte.welcome/manifest.json | 11 + .../plugins/com.msgbyte.welcome/package.json | 16 + .../plugins/com.msgbyte.welcome/src/index.tsx | 37 ++ .../com.msgbyte.welcome/src/translate.ts | 15 + .../plugins/com.msgbyte.welcome/tsconfig.json | 7 + .../com.msgbyte.welcome/types/tailchat.d.ts | 556 ++++++++++++++++++ 18 files changed, 847 insertions(+), 9 deletions(-) create mode 100644 server/plugins/com.msgbyte.welcome/.ministarrc.js create mode 100644 server/plugins/com.msgbyte.welcome/package.json create mode 100644 server/plugins/com.msgbyte.welcome/services/welcome.service.ts create mode 100644 server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/manifest.json create mode 100644 server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/package.json create mode 100644 server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/src/index.tsx create mode 100644 server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/src/translate.ts create mode 100644 server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/tsconfig.json create mode 100644 server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/types/tailchat.d.ts diff --git a/client/web/src/components/modals/GroupDetail/Config.tsx b/client/web/src/components/modals/GroupDetail/Config.tsx index 4555deb1..fc6bfa7e 100644 --- a/client/web/src/components/modals/GroupDetail/Config.tsx +++ b/client/web/src/components/modals/GroupDetail/Config.tsx @@ -51,7 +51,7 @@ export const GroupConfig: React.FC<{ key={name} title={item.title} tip={item.tip} - content={item.render({ + content={React.createElement(item.component, { value: config[name], onChange: (val: any) => handleModifyConfig(name, val), loading, diff --git a/client/web/src/plugin/common/reg.ts b/client/web/src/plugin/common/reg.ts index 15ecc379..fe9d4f63 100644 --- a/client/web/src/plugin/common/reg.ts +++ b/client/web/src/plugin/common/reg.ts @@ -307,7 +307,7 @@ export const [pluginGroupConfigItems, regPluginGroupConfigItem] = buildRegList<{ name: string; title: string; tip?: string; - render: (props: { + component: (props: { value: any; onChange: (val: unknown) => void; loading: boolean; diff --git a/client/web/src/styles/antd/dark.less b/client/web/src/styles/antd/dark.less index e0d03f61..45b4bee1 100644 --- a/client/web/src/styles/antd/dark.less +++ b/client/web/src/styles/antd/dark.less @@ -149,6 +149,13 @@ border-color: rgba(255, 255, 255, 0.12); } + // 文本框 + .ant-input-textarea{ + &.ant-input-textarea-show-count:after { + color: rgba(255, 255, 255, 0.45); + } + } + // 复选框 .ant-checkbox-wrapper { color: white; diff --git a/client/web/tailchat.d.ts b/client/web/tailchat.d.ts index dcc8499f..1442f7e4 100644 --- a/client/web/tailchat.d.ts +++ b/client/web/tailchat.d.ts @@ -147,6 +147,8 @@ declare module '@capital/common' { deps?: React.DependencyList ) => [{ loading: boolean; value?: any }, T]; + export const useEvent: any; + export const uploadFile: any; export const showToasts: ( @@ -170,6 +172,8 @@ declare module '@capital/common' { export const joinArray: any; + export const useConverseMessageContext: any; + export const navigate: any; export const useLocation: any; @@ -241,6 +245,10 @@ declare module '@capital/common' { export const regChatInputAction: any; + export const pluginChatInputButtons: any; + + export const regChatInputButton: any; + export const regSocketEventListener: (item: { eventName: string; eventFn: (...args: any[]) => void; @@ -328,6 +336,10 @@ declare module '@capital/common' { export const regPluginInboxItemMap: any; + export const pluginGroupConfigItems: any; + + export const regPluginGroupConfigItem: any; + export const useGroupIdContext: () => string; export const useGroupPanelContext: () => { @@ -376,6 +388,10 @@ declare module '@capital/component' { }> >; + export const Popover: any; + + export const Tag: any; + export const TextArea: any; export const Avatar: any; @@ -410,6 +426,10 @@ declare module '@capital/component' { export const MessageAckContainer: any; + export const BaseChatInputButton: any; + + export const useChatInputActionContext: any; + export const GroupExtraDataPanel: any; export const Image: any; @@ -431,8 +451,6 @@ declare module '@capital/component' { export const PillTabPane: any; - export const LoadingSpinner: React.FC<{ tip?: string }>; - export const FullModalField: any; export const DefaultFullModalInputEditorRender: any; @@ -480,6 +498,8 @@ declare module '@capital/component' { children?: React.ReactNode; }>; + export const LoadingSpinner: React.FC<{ tip?: string }>; + export const LoadingOnFirst: React.FC<{ spinning: boolean; className?: string; diff --git a/package.json b/package.json index 2e210c37..362c003f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "start:admin-next": "cd server/admin-next && pnpm start", "build": "concurrently npm:build:web npm:build:server npm:build:admin && cp -r client/web/dist/* server/dist/public && cp -r client/web/dist/* server/dist/public", "build:web": "cd client/web && pnpm build", - "build:server": "cd server && pnpm build && echo \"Install server side plugin:\" && pnpm run plugin:install com.msgbyte.tasks com.msgbyte.linkmeta com.msgbyte.github com.msgbyte.simplenotify com.msgbyte.topic com.msgbyte.agora com.msgbyte.wxpusher && mkdir -p ./dist/public && cp -r ./public/plugins ./dist/public && cp ./public/registry-be.json ./dist/public", + "build:server": "cd server && pnpm build && echo \"Install server side plugin:\" && pnpm run plugin:install com.msgbyte.tasks com.msgbyte.linkmeta com.msgbyte.github com.msgbyte.simplenotify com.msgbyte.topic com.msgbyte.agora com.msgbyte.wxpusher com.msgbyte.welcome && mkdir -p ./dist/public && cp -r ./public/plugins ./dist/public && cp ./public/registry-be.json ./dist/public", "build:admin": "cd server/admin && pnpm build", "build:admin-next": "cd server/admin-next && pnpm build", "check:type": "concurrently npm:check:type:client npm:check:type:server", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eff6f229..e0753a8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1998,6 +1998,31 @@ importers: specifier: ^4.3.6 version: 4.3.6(immer@9.0.15)(react@18.2.0) + server/plugins/com.msgbyte.welcome: + dependencies: + tailchat-server-sdk: + specifier: '*' + version: link:../../packages/sdk + devDependencies: + '@types/react': + specifier: 18.0.20 + version: 18.0.20 + mini-star: + specifier: '*' + version: 1.3.1 + + server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome: + devDependencies: + '@types/styled-components': + specifier: ^5.1.26 + version: 5.1.26 + react: + specifier: 18.2.0 + version: 18.2.0 + styled-components: + specifier: ^5.3.6 + version: 5.3.6(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) + server/plugins/com.msgbyte.wxpusher: dependencies: got: diff --git a/server/packages/sdk/src/services/lib/call.ts b/server/packages/sdk/src/services/lib/call.ts index 57e2ad25..4a60e557 100644 --- a/server/packages/sdk/src/services/lib/call.ts +++ b/server/packages/sdk/src/services/lib/call.ts @@ -41,13 +41,24 @@ export function call(ctx: TcPureContext) { } ); }, + /** + * 获取群组大厅会话的id + */ + async getGroupLobbyConverseId(groupId: string): Promise { + const lobbyConverseId: string = await ctx.call( + 'group.getGroupLobbyConverseId', + { + groupId, + } + ); + + return lobbyConverseId; + }, /** * 添加群组系统信息 */ async addGroupSystemMessage(groupId: string, message: string) { - const lobbyConverseId = await ctx.call('group.getGroupLobbyConverseId', { - groupId, - }); + const lobbyConverseId = await call(ctx).getGroupLobbyConverseId(groupId); if (!lobbyConverseId) { // 如果没有文本频道则跳过 diff --git a/server/packages/sdk/src/structs/group.ts b/server/packages/sdk/src/structs/group.ts index fd66578e..9c97b257 100644 --- a/server/packages/sdk/src/structs/group.ts +++ b/server/packages/sdk/src/structs/group.ts @@ -52,4 +52,6 @@ export interface GroupStruct { panels: GroupPanelStruct[]; roles?: GroupRoleStruct[]; + + config: Record; } diff --git a/server/packages/sdk/src/structs/user.ts b/server/packages/sdk/src/structs/user.ts index af351a8f..4417dfda 100644 --- a/server/packages/sdk/src/structs/user.ts +++ b/server/packages/sdk/src/structs/user.ts @@ -1,7 +1,9 @@ export const userType = ['normalUser', 'pluginBot', 'openapiBot'] as const; -export type UserType = typeof userType[number]; +export type UserType = (typeof userType)[number]; export interface UserStruct { + _id: string; + /** * 用户名 不可被修改 * 与email必有一个 diff --git a/server/plugins/com.msgbyte.welcome/.ministarrc.js b/server/plugins/com.msgbyte.welcome/.ministarrc.js new file mode 100644 index 00000000..3c4db179 --- /dev/null +++ b/server/plugins/com.msgbyte.welcome/.ministarrc.js @@ -0,0 +1,14 @@ +const path = require('path'); + +module.exports = { + externalDeps: [ + 'react', + 'react-router', + 'axios', + 'styled-components', + 'zustand', + 'zustand/middleware/immer', + ], + pluginRoot: path.resolve(__dirname, './web'), + outDir: path.resolve(__dirname, '../../public'), +}; diff --git a/server/plugins/com.msgbyte.welcome/package.json b/server/plugins/com.msgbyte.welcome/package.json new file mode 100644 index 00000000..8173d25d --- /dev/null +++ b/server/plugins/com.msgbyte.welcome/package.json @@ -0,0 +1,20 @@ +{ + "name": "tailchat-plugin-welcome", + "version": "1.0.0", + "main": "index.js", + "author": "moonrailgun", + "description": "加入群组时发送欢迎消息", + "license": "MIT", + "private": true, + "scripts": { + "build:web": "ministar buildPlugin all", + "build:web:watch": "ministar watchPlugin all" + }, + "devDependencies": { + "@types/react": "18.0.20", + "mini-star": "*" + }, + "dependencies": { + "tailchat-server-sdk": "*" + } +} diff --git a/server/plugins/com.msgbyte.welcome/services/welcome.service.ts b/server/plugins/com.msgbyte.welcome/services/welcome.service.ts new file mode 100644 index 00000000..6c4d690e --- /dev/null +++ b/server/plugins/com.msgbyte.welcome/services/welcome.service.ts @@ -0,0 +1,95 @@ +import { call, TcContext } from 'tailchat-server-sdk'; +import { TcService } from 'tailchat-server-sdk'; + +/** + * 入群欢迎 + * + * 加入群组时发送欢迎消息 + */ +class WelcomeService extends TcService { + get serviceName() { + return 'plugin:com.msgbyte.welcome'; + } + + onInit() { + this.registryAfterActionHook('group.joinGroup', 'joinGroupCallback'); // not work + + this.registerAction('joinGroupCallback', this.joinGroupCallback, { + params: { + groupId: 'string', + }, + visibility: 'public', + }); + } + + async joinGroupCallback( + ctx: TcContext<{ + groupId: string; + }> + ) { + const { groupId } = ctx.params; + + const groupInfo = await call(ctx).getGroupInfo(groupId); + + if (groupInfo.config['plugin:groupWelcomeText']) { + // 有欢迎词 + + const lobbyConverseId = await call(ctx).getGroupLobbyConverseId(groupId); + + if (!lobbyConverseId) { + // 如果没有文本频道则跳过 + return; + } + + const [welcomeText, meta] = await parseGroupWelcomeText( + ctx, + groupInfo.config['plugin:groupWelcomeText'] + ); + + await ctx.call( + 'chat.message.sendMessage', + { + converseId: lobbyConverseId, + groupId: groupId, + content: welcomeText, + meta, + }, + { + meta: { + ...ctx.meta, + userId: groupInfo.owner, // 以群组owner的名义 + }, + } + ); + } + } +} + +export default WelcomeService; + +async function parseGroupWelcomeText( + ctx: TcContext, + welcomeText: string +): Promise<[string, {}]> { + const meta: Record = {}; + if ( + welcomeText.includes('{nickname}') || + welcomeText.includes('{@nickname}') + ) { + const memberInfo = await call(ctx).getUserInfo(ctx.meta.userId); + const nickname = memberInfo.nickname; + const userId = String(memberInfo._id); + + welcomeText = welcomeText.replaceAll('{nickname}', nickname); + + if (welcomeText.includes('{@nickname}')) { + welcomeText = welcomeText.replaceAll( + '{@nickname}', + `[at=${userId}]${nickname}[/at]` + ); + meta.mentions = [String(userId)]; + } + } + + return [welcomeText, meta]; +} diff --git a/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/manifest.json b/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/manifest.json new file mode 100644 index 00000000..d4356400 --- /dev/null +++ b/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/manifest.json @@ -0,0 +1,11 @@ +{ + "label": "Group Welcome", + "label.zh-CN": "入群欢迎", + "name": "com.msgbyte.welcome", + "url": "{BACKEND}/plugins/com.msgbyte.welcome/index.js", + "version": "0.0.0", + "author": "moonrailgun", + "description": "Send a welcome message when joining a group", + "description.zh-CN": "加入群组时发送欢迎消息", + "requireRestart": true +} diff --git a/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/package.json b/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/package.json new file mode 100644 index 00000000..4ec4442b --- /dev/null +++ b/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/package.json @@ -0,0 +1,16 @@ +{ + "name": "@plugins/com.msgbyte.welcome", + "main": "src/index.tsx", + "version": "0.0.0", + "description": "加入群组时发送欢迎消息", + "private": true, + "scripts": { + "sync:declaration": "tailchat declaration github" + }, + "dependencies": {}, + "devDependencies": { + "@types/styled-components": "^5.1.26", + "react": "18.2.0", + "styled-components": "^5.3.6" + } +} diff --git a/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/src/index.tsx b/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/src/index.tsx new file mode 100644 index 00000000..023fe501 --- /dev/null +++ b/server/plugins/com.msgbyte.welcome/web/plugins/com.msgbyte.welcome/src/index.tsx @@ -0,0 +1,37 @@ +import { regPluginGroupConfigItem } from '@capital/common'; +import { TextArea } from '@capital/component'; +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { Translate } from './translate'; + +console.log('Plugin Group Welcome is loaded'); + +const Desc = styled.div` + color: rgba(#999, 0.8); + font-size: 9px; + margin-top: 2px; +`; + +regPluginGroupConfigItem({ + name: 'groupWelcomeText', + title: Translate.welcomeText, + tip: Translate.welcomeTip, + component: ({ value, onChange, loading }) => { + const [text, setText] = useState(value ?? ''); + + return ( + <> +