feat: 声网插件webhhok处理与消息通知方式

顺便修复了一些小的样式问题
pull/64/head
moonrailgun 2 years ago
parent 58dba494a0
commit bd3f2e129c

@ -979,12 +979,14 @@ importers:
'@types/styled-components': ^5.1.26
agora-rtc-react: ^1.1.3
ahooks: ^3.7.4
lodash: ^4.17.21
react: 18.2.0
styled-components: ^5.3.6
zustand: ^4.1.5
dependencies:
agora-rtc-react: 1.1.3_react@18.2.0
ahooks: 3.7.4_react@18.2.0
lodash: 4.17.21
devDependencies:
'@types/styled-components': 5.1.26
react: 18.2.0
@ -13538,7 +13540,7 @@ packages:
babel-plugin-syntax-jsx: 6.18.0
lodash: 4.17.21
picomatch: 2.3.1
styled-components: 5.3.6_7i5myeigehqah43i5u7wbekgba
styled-components: 5.3.6_react@18.2.0
/babel-plugin-syntax-jsx/6.18.0:
resolution: {integrity: sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==}
@ -32885,6 +32887,7 @@ packages:
react-is: 18.2.0
shallowequal: 1.1.0
supports-color: 5.5.0
dev: false
/styled-components/5.3.6_mdz3marskokvq6744hhidi3r5a:
resolution: {integrity: sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==}
@ -32930,7 +32933,6 @@ packages:
react: 18.2.0
shallowequal: 1.1.0
supports-color: 5.5.0
dev: true
/styled-system/5.1.5:
resolution: {integrity: sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==}

@ -24,6 +24,7 @@
"k82f5a5d4": "Password incorrect",
"k89bf46fc": "Unable to recall messages from {{minutes}} minutes ago",
"k986040de": "No group found",
"ka3eb52f8": "Call ended, duration: {{num}} minutes",
"ka8b712f7": "Email already exists!",
"kb143afe": "This data is not allowed to be modified",
"kb32d3d62": "Anonymous",
@ -47,5 +48,6 @@
"ke82b4383": "You cannot send messages because you are banned",
"ke99cd649": "No access to converse information permission",
"ke9fabda8": "Token Invalid",
"kea5b4254": "This channel has opened a call",
"kef3676e1": "The invitation code has expired"
}

@ -24,6 +24,7 @@
"k82f5a5d4": "密码不正确",
"k89bf46fc": "无法撤回 {{minutes}} 分钟前的消息",
"k986040de": "没有找到群组",
"ka3eb52f8": "通话已结束, 时长: {{num}}分钟",
"ka8b712f7": "邮箱已存在!",
"kb143afe": "该数据不允许修改",
"kb32d3d62": "匿名用户",
@ -47,5 +48,6 @@
"ke82b4383": "您因为被禁言无法发送消息",
"ke99cd649": "没有获取会话信息权限",
"ke9fabda8": "Token不合规",
"kea5b4254": "本频道开启了通话",
"kef3676e1": "该邀请码已过期"
}

@ -13,6 +13,12 @@ export class AgoraMeeting extends TimeStamps implements db.Base {
@prop()
converseId: string;
@prop()
groupId?: string;
@prop()
messageId: string;
@prop()
channelName: string;

@ -1,4 +1,9 @@
import { DataNotFoundError, TcContext } from 'tailchat-server-sdk';
import {
DataNotFoundError,
MessageStruct,
TcContext,
TcPureContext,
} from 'tailchat-server-sdk';
import { TcService, TcDbService, db } from 'tailchat-server-sdk';
import type {
AgoraMeetingDocument,
@ -26,6 +31,8 @@ interface ChannelUserListRet {
};
}
const noticeCacheKey = 'agora:notice:';
/**
*
*
@ -35,6 +42,8 @@ interface AgoraService
extends TcService,
TcDbService<AgoraMeetingDocument, AgoraMeetingModel> {}
class AgoraService extends TcService {
botUserId: string | undefined;
get serviceName() {
return 'plugin:com.msgbyte.agora';
}
@ -104,6 +113,25 @@ class AgoraService extends TcService {
this.registerAuthWhitelist(['/webhook']);
}
protected onInited(): void {
// 确保机器人用户存在, 并记录机器人用户id
this.waitForServices(['user']).then(async () => {
try {
const botUserId = await this.broker.call('user.ensurePluginBot', {
botId: 'agora-meeting',
nickname: 'Agora Bot',
avatar: '{BACKEND}/plugins/com.msgbyte.agora/assets/icon.png',
});
this.logger.info('Agora Meeting Bot Id:', botUserId);
this.botUserId = String(botUserId);
} catch (e) {
this.logger.error(e);
}
});
}
generateJoinInfo(
ctx: TcContext<{
channelName: string;
@ -166,6 +194,7 @@ class AgoraService extends TcService {
/**
* agora
* NOTICE: (header)
* Reference: https://docs.agora.io/cn/live-streaming-premium-legacy/rtc_channel_event?platform=RESTful#101-channel-create
*/
async webhook(
@ -177,9 +206,16 @@ class AgoraService extends TcService {
payload: any;
}>
) {
const { eventType, payload } = ctx.params;
const { eventType, payload, noticeId } = ctx.params;
const { t } = ctx.meta;
const channelName = payload.channelName;
const valid = await this.checkNoticeValid(noticeId);
if (!valid) {
this.logger.debug('agora notice has been handled');
return true;
}
this.logger.info('webhook received', { eventType, payload });
if (channelName === 'test_webhook') {
// 连通性检查
@ -190,42 +226,74 @@ class AgoraService extends TcService {
// 频道被创建
const ts = payload.ts;
const converseId = this.getConverseIdFromChannelName(channelName);
const [converseId, groupId] = this.getSourceFromChannelName(channelName);
const existedMeeting =
await this.adapter.model.findLastestMeetingByConverseId(converseId);
if (existedMeeting) {
// 已经创建了,则跳过
return;
}
const message = await this.sendPluginBotMessage(ctx, {
converseId,
groupId,
content: t('本频道开启了通话'),
});
const messageId = String(message._id);
const meeting = await this.adapter.model.create({
channelName,
converseId,
groupId,
messageId,
active: true,
createdAt: new Date(ts),
createdAt: new Date(ts * 1000),
});
this.roomcastNotify(ctx, converseId, 'agoraChannelCreate', {
converseId,
groupId,
meetingId: String(meeting._id),
});
} else if (eventType === 102) {
// 频道被销毁
const ts = payload.ts;
const converseId = this.getConverseIdFromChannelName(channelName);
const [converseId, groupId] = this.getSourceFromChannelName(channelName);
const meeting = await this.adapter.model.findLastestMeetingByConverseId(
converseId
);
if (!meeting) {
// 会议不存在,直接跳过
return;
}
meeting.active = false;
meeting.endAt = new Date(ts);
meeting.endAt = new Date(ts * 1000);
await meeting.save();
this.roomcastNotify(ctx, converseId, 'agoraChannelDestroy', {
converseId,
groupId,
meetingId: String(meeting._id),
});
const duration =
new Date(meeting.endAt).valueOf() -
new Date(meeting.createdAt).valueOf();
this.sendPluginBotMessage(ctx, {
converseId,
groupId,
content: t('通话已结束, 时长: {{num}}分钟', {
num: Math.round(duration / 1000 / 60),
}),
});
} else if (eventType === 103) {
// 用户加入
const { channelName, uid: userId } = payload;
const converseId = this.getConverseIdFromChannelName(channelName);
const [converseId, groupId] = this.getSourceFromChannelName(channelName);
const meeting = await this.adapter.model.findLastestMeetingByConverseId(
converseId
@ -238,13 +306,14 @@ class AgoraService extends TcService {
this.roomcastNotify(ctx, converseId, 'agoraBroadcasterJoin', {
converseId,
groupId,
meetingId: String(meeting._id),
userId,
});
} else if (eventType === 104) {
// 用户离开
const { channelName, uid } = payload;
const converseId = this.getConverseIdFromChannelName(channelName);
const [converseId, groupId] = this.getSourceFromChannelName(channelName);
const meeting = await this.adapter.model.findLastestMeetingByConverseId(
converseId
@ -255,6 +324,7 @@ class AgoraService extends TcService {
this.roomcastNotify(ctx, converseId, 'agoraBroadcasterLeave', {
converseId,
groupId,
meetingId: String(meeting._id),
userId: uid,
});
@ -278,7 +348,9 @@ class AgoraService extends TcService {
/**
* NOTICE: ChannelName使
*/
private getConverseIdFromChannelName(channelName: string): string {
private getSourceFromChannelName(
channelName: string
): [string, string | undefined] {
if (!channelName) {
this.logger.error('channel name invalid', channelName);
throw new Error('channel name invalid');
@ -290,7 +362,57 @@ class AgoraService extends TcService {
throw new Error('converseId invalid');
}
return converseId;
if (groupId === 'personal') {
return [converseId, undefined];
} else {
if (!db.Types.ObjectId.isValid(groupId)) {
this.logger.error('groupId invalid:', groupId);
throw new Error('groupId invalid');
}
return [converseId, groupId];
}
}
private async checkNoticeValid(noticeId: string) {
const key = noticeCacheKey + noticeId;
const v = await this.broker.cacher.get(key);
if (v) {
return false;
}
await this.broker.cacher.set(key, '1', 60 * 10); // 1分钟
return true;
}
private async sendPluginBotMessage(
ctx: TcPureContext<any>,
messagePayload: {
converseId: string;
groupId?: string;
content: string;
meta?: any;
}
): Promise<MessageStruct> {
if (!this.botUserId) {
this.logger.warn('机器人尚未初始化,无法发送插件消息');
return;
}
const res = await ctx.call(
'chat.message.sendMessage',
{
...messagePayload,
},
{
meta: {
userId: this.botUserId,
},
}
);
return res as MessageStruct;
}
}

@ -9,7 +9,8 @@
},
"dependencies": {
"agora-rtc-react": "^1.1.3",
"ahooks": "^3.7.4"
"ahooks": "^3.7.4",
"lodash": "^4.17.21"
},
"devDependencies": {
"@types/styled-components": "^5.1.26",

@ -9,6 +9,8 @@ import { request } from '../request';
import styled from 'styled-components';
import { useMeetingStore } from './store';
import { NetworkStats } from './NetworkStats';
import _once from 'lodash/once';
import type { IAgoraRTCClient } from 'agora-rtc-react';
const Root = styled.div`
.body {
@ -25,6 +27,10 @@ const Root = styled.div`
}
`;
const enableDualStream = _once((client: IAgoraRTCClient) => {
return client.enableDualStream();
});
export interface MeetingViewProps {
meetingId: string;
onClose: () => void;
@ -83,7 +89,7 @@ export const MeetingView: React.FC<MeetingViewProps> = React.memo((props) => {
const { appId, token } = data ?? {};
await client.join(appId, channelName, token, _id);
await client.enableDualStream();
await enableDualStream(client);
client.enableAudioVolumeIndicator();
setStart(true);
} catch (err) {

@ -5,6 +5,7 @@ import { OwnVideoView, VideoView } from './VideoView';
const Root = styled.div`
height: 70vh;
overflow: hidden;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(440px, 1fr));
`;

@ -19,8 +19,7 @@ regPluginPanelAction({
title: '发起通话',
content: '是否通过声网插件在当前会话开启音视频通讯?',
onConfirm: async () => {
// startFastMeeting(`${groupId}|${panelId}`);
startFastMeeting('123456'); // for test
startFastMeeting(`${groupId}|${panelId}`);
},
});
},

Loading…
Cancel
Save