如何使用 OAuthCard 基于登录用户在 BotFramework-WebChat 中动态设置用户头像
How to set the User avatar dynamically in BotFramework-WebChat based on logged in user using OAuthCard
我使用 Microsoft Bot Framework V4 开发了一个聊天机器人,并使用 BotFramework-WebChat 为用户提供使用 DirectLine 令牌从网站聊天的功能,
我可以通过分配静态 public 图像 URL 来设置机器人头像和用户头像。问题是我想使用以下步骤在 WebChat 中动态设置用户头像
- OAuthCard 登录后API使用 Microsoft graph 获取用户图标
- 在 Webchat styleSetOptions 中动态设置登录用户图像。
我已经按照提供的示例完成了设置机器人框架服务器和网络聊天的演示
机器人服务器 == https://github.com/Microsoft/BotBuilder-Samples
网络聊天 == https://github.com/Microsoft/BotFramework-WebChat
但是没有关于如何在用户登录后使用已签名的用户对象设置用户图像的适当示例或文档。
请问任何一点正确的方向如何实现。
提前致谢
您可以通过将 Graph API 调用和结果包装到 AAD 登录过程的结果中来实现此目的。以下代码基于 BotBuilder-Samples 24.bot-authentication-msgraph
sample and BotFramework-WebChat 17.chat-send-history
sample 使用 React.Component.
(请注意,目前位于 master 分支中的 Graph 示例不包括获取 AAD 登录用户的照片。我有一个 PR 将此功能添加到示例中,但是我也将其包含在此处.我使用 WebChat 示例作为构建以下内容的参考。)
网络聊天
您将需要示例 #17 中的这些文件,然后是需要更改的 App.js 文件:
- public [文件夹]
- favicon.ico
- index.html
- manifest.json
- src [文件夹]
- App.js
- index.css
- index.js
- .env
- package.json
App.js:
注意:我在一个单独的项目中本地生成直线令牌。这假设 AAD 配置文件有照片。
import React from 'react';
import ReactWebChat, { createDirectLine, createStore} from 'botframework-webchat';
export default class extends React.Component {
constructor(props) {
super(props);
this.state = {
directLine: null,
avatarState: false, // Sets value to false; Is updated to true after login
// Sets the default styleOptions used during rendering
styleOptions: {
botAvatarImage: 'https://docs.microsoft.com/en-us/azure/bot-service/v4sdk/media/logo_bot.svg?view=azure-bot-service-4.0',
botAvatarInitials: 'BF',
userAvatarImage: 'https://github.com/compulim.png?size=64',
userAvatarInitials: 'WC'
}
};
// Creates the listener filtering for a new avatar image and applies to styleOptions
this.store = createStore(
{},
() => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
}
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'
&& action.payload.activity.attachments
&& 0 in action.payload.activity.attachments
&& this.state.avatarState === false) {
let attachments = action.payload.activity.attachments;
if ('content' in attachments[0] && 'images' in attachments[0].content) {
this.setState(() => ({
styleOptions: {
userAvatarImage: attachments[0].content.images[0].contentUrl
},
avatarState: true
}));
}
}
return next(action);
}
)
}
componentDidMount() {
this.fetchToken();
}
async fetchToken() {
const res = await fetch('http://localhost:3979/directline/token', { method: 'POST' });
const { token } = await res.json();
this.setState(() => ({
directLine: createDirectLine({ token })
}));
}
render() {
return (
this.state.directLine ?
<ReactWebChat
className="chat"
directLine={ this.state.directLine }
styleOptions={ this.state.styleOptions }
store={ this.store }
{ ...this.props }
/>
:
<div>Connecting to bot…</div>
);
}
}
package.json
{
"name": "change-avatar",
"version": "0.1.0",
"private": true,
"homepage": "",
"dependencies": {
"botframework-webchat": ">= 0.0.0-0",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-scripts": "2.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
MS 图表机器人
更新示例 #24 中的以下文件:
bot.js:
将async processStep
替换为:
async processStep(step) {
// We do not need to store the token in the bot. When we need the token we can
// send another prompt. If the token is valid the user will not need to log back in.
// The token will be available in the Result property of the task.
const tokenResponse = step.result;
// If the user is authenticated the bot can use the token to make API calls.
if (tokenResponse !== undefined) {
let parts = await this.commandState.get(step.context);
if (!parts) {
parts = step.context.activity.text;
}
const command = parts.split(' ')[0].toLowerCase();
if (command === 'me') {
await OAuthHelpers.listMe(step.context, tokenResponse);
} else if (command === 'send') {
await OAuthHelpers.sendMail(step.context, tokenResponse, parts.split(' ')[1].toLowerCase());
} else if (command === 'recent') {
await OAuthHelpers.listRecentMail(step.context, tokenResponse);
} else {
let photoResponse = await OAuthHelpers.loginData(step.context, tokenResponse);
const card = CardFactory.heroCard(
`Welcome ${ photoResponse.displayName }, you are now logged in.`,
[photoResponse],
[]
);
const reply = ({ type: ActivityTypes.Message });
reply.attachments = [card];
await step.context.sendActivity(reply);
}
} else {
// Ask the user to try logging in later as they are not logged in.
await step.context.sendActivity(`We couldn't log you in. Please try again later.`);
}
return await step.endDialog();
};
oauth-helpers.js:
添加static async loginData
:
/**
* Displays information about the user in the bot.
* @param {TurnContext} turnContext A TurnContext instance containing all the data needed for processing this conversation turn.
* @param {TokenResponse} tokenResponse A response that includes a user token.
*/
static async loginData(turnContext, tokenResponse) {
if (!turnContext) {
throw new Error('OAuthHelpers.loginData(): `turnContext` cannot be undefined.');
}
if (!tokenResponse) {
throw new Error('OAuthHelpers.loginData(): `tokenResponse` cannot be undefined.');
}
try {
// Pull in the data from Microsoft Graph.
const client = new SimpleGraphClient(tokenResponse.token);
const me = await client.getMe();
const photoResponse = await client.getPhoto();
// Attaches user's profile photo to the reply activity.
if (photoResponse != null) {
let replyAttachment;
const base64 = Buffer.from(photoResponse, 'binary').toString('base64');
replyAttachment = {
contentType: 'image/jpeg',
contentUrl: `data:image/jpeg;base64,${ base64 }`
};
replyAttachment.displayName = me.displayName;
return (replyAttachment);
}
} catch (error) {
throw error;
}
}
简单图-client.js:
添加async getPhoto
:
/**
* Collects the user's photo.
*/
async getPhoto() {
return await this.graphClient
.api('/me/photo/$value')
.responseType('ArrayBuffer')
.version('beta')
.get()
.then((res) => {
return res;
})
.catch((err) => {
console.log(err);
});
}
package.json:
请确保 @microsoft/microsoft-graph-client
安装版本 1.0.0,因为在后续版本中围绕 AAD 'displayName' 获取进行了重大更改。
执行完上述代码后,我就可以登录了,登录成功后,立即更新了用户头像。
希望得到帮助!
我使用 Microsoft Bot Framework V4 开发了一个聊天机器人,并使用 BotFramework-WebChat 为用户提供使用 DirectLine 令牌从网站聊天的功能,
我可以通过分配静态 public 图像 URL 来设置机器人头像和用户头像。问题是我想使用以下步骤在 WebChat 中动态设置用户头像
- OAuthCard 登录后API使用 Microsoft graph 获取用户图标
- 在 Webchat styleSetOptions 中动态设置登录用户图像。
我已经按照提供的示例完成了设置机器人框架服务器和网络聊天的演示
机器人服务器 == https://github.com/Microsoft/BotBuilder-Samples
网络聊天 == https://github.com/Microsoft/BotFramework-WebChat
但是没有关于如何在用户登录后使用已签名的用户对象设置用户图像的适当示例或文档。
请问任何一点正确的方向如何实现。
提前致谢
您可以通过将 Graph API 调用和结果包装到 AAD 登录过程的结果中来实现此目的。以下代码基于 BotBuilder-Samples 24.bot-authentication-msgraph
sample and BotFramework-WebChat 17.chat-send-history
sample 使用 React.Component.
(请注意,目前位于 master 分支中的 Graph 示例不包括获取 AAD 登录用户的照片。我有一个 PR 将此功能添加到示例中,但是我也将其包含在此处.我使用 WebChat 示例作为构建以下内容的参考。)
网络聊天
您将需要示例 #17 中的这些文件,然后是需要更改的 App.js 文件:
- public [文件夹]
- favicon.ico
- index.html
- manifest.json
- src [文件夹]
- App.js
- index.css
- index.js
- .env
- package.json
App.js:
注意:我在一个单独的项目中本地生成直线令牌。这假设 AAD 配置文件有照片。
import React from 'react';
import ReactWebChat, { createDirectLine, createStore} from 'botframework-webchat';
export default class extends React.Component {
constructor(props) {
super(props);
this.state = {
directLine: null,
avatarState: false, // Sets value to false; Is updated to true after login
// Sets the default styleOptions used during rendering
styleOptions: {
botAvatarImage: 'https://docs.microsoft.com/en-us/azure/bot-service/v4sdk/media/logo_bot.svg?view=azure-bot-service-4.0',
botAvatarInitials: 'BF',
userAvatarImage: 'https://github.com/compulim.png?size=64',
userAvatarInitials: 'WC'
}
};
// Creates the listener filtering for a new avatar image and applies to styleOptions
this.store = createStore(
{},
() => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
}
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'
&& action.payload.activity.attachments
&& 0 in action.payload.activity.attachments
&& this.state.avatarState === false) {
let attachments = action.payload.activity.attachments;
if ('content' in attachments[0] && 'images' in attachments[0].content) {
this.setState(() => ({
styleOptions: {
userAvatarImage: attachments[0].content.images[0].contentUrl
},
avatarState: true
}));
}
}
return next(action);
}
)
}
componentDidMount() {
this.fetchToken();
}
async fetchToken() {
const res = await fetch('http://localhost:3979/directline/token', { method: 'POST' });
const { token } = await res.json();
this.setState(() => ({
directLine: createDirectLine({ token })
}));
}
render() {
return (
this.state.directLine ?
<ReactWebChat
className="chat"
directLine={ this.state.directLine }
styleOptions={ this.state.styleOptions }
store={ this.store }
{ ...this.props }
/>
:
<div>Connecting to bot…</div>
);
}
}
package.json
{
"name": "change-avatar",
"version": "0.1.0",
"private": true,
"homepage": "",
"dependencies": {
"botframework-webchat": ">= 0.0.0-0",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-scripts": "2.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
MS 图表机器人
更新示例 #24 中的以下文件:
bot.js:
将async processStep
替换为:
async processStep(step) {
// We do not need to store the token in the bot. When we need the token we can
// send another prompt. If the token is valid the user will not need to log back in.
// The token will be available in the Result property of the task.
const tokenResponse = step.result;
// If the user is authenticated the bot can use the token to make API calls.
if (tokenResponse !== undefined) {
let parts = await this.commandState.get(step.context);
if (!parts) {
parts = step.context.activity.text;
}
const command = parts.split(' ')[0].toLowerCase();
if (command === 'me') {
await OAuthHelpers.listMe(step.context, tokenResponse);
} else if (command === 'send') {
await OAuthHelpers.sendMail(step.context, tokenResponse, parts.split(' ')[1].toLowerCase());
} else if (command === 'recent') {
await OAuthHelpers.listRecentMail(step.context, tokenResponse);
} else {
let photoResponse = await OAuthHelpers.loginData(step.context, tokenResponse);
const card = CardFactory.heroCard(
`Welcome ${ photoResponse.displayName }, you are now logged in.`,
[photoResponse],
[]
);
const reply = ({ type: ActivityTypes.Message });
reply.attachments = [card];
await step.context.sendActivity(reply);
}
} else {
// Ask the user to try logging in later as they are not logged in.
await step.context.sendActivity(`We couldn't log you in. Please try again later.`);
}
return await step.endDialog();
};
oauth-helpers.js:
添加static async loginData
:
/**
* Displays information about the user in the bot.
* @param {TurnContext} turnContext A TurnContext instance containing all the data needed for processing this conversation turn.
* @param {TokenResponse} tokenResponse A response that includes a user token.
*/
static async loginData(turnContext, tokenResponse) {
if (!turnContext) {
throw new Error('OAuthHelpers.loginData(): `turnContext` cannot be undefined.');
}
if (!tokenResponse) {
throw new Error('OAuthHelpers.loginData(): `tokenResponse` cannot be undefined.');
}
try {
// Pull in the data from Microsoft Graph.
const client = new SimpleGraphClient(tokenResponse.token);
const me = await client.getMe();
const photoResponse = await client.getPhoto();
// Attaches user's profile photo to the reply activity.
if (photoResponse != null) {
let replyAttachment;
const base64 = Buffer.from(photoResponse, 'binary').toString('base64');
replyAttachment = {
contentType: 'image/jpeg',
contentUrl: `data:image/jpeg;base64,${ base64 }`
};
replyAttachment.displayName = me.displayName;
return (replyAttachment);
}
} catch (error) {
throw error;
}
}
简单图-client.js:
添加async getPhoto
:
/**
* Collects the user's photo.
*/
async getPhoto() {
return await this.graphClient
.api('/me/photo/$value')
.responseType('ArrayBuffer')
.version('beta')
.get()
.then((res) => {
return res;
})
.catch((err) => {
console.log(err);
});
}
package.json:
请确保 @microsoft/microsoft-graph-client
安装版本 1.0.0,因为在后续版本中围绕 AAD 'displayName' 获取进行了重大更改。
执行完上述代码后,我就可以登录了,登录成功后,立即更新了用户头像。
希望得到帮助!