1

我使用 Microsoft Bot Framework V4 开发了一个聊天机器人,并使用 BotFramework-WebChat 为用户提供使用 DirectLine 令牌从网站聊天,

我可以通过分配静态公共图像 URL 来设置机器人头像和用户头像。问题是我想使用以下步骤在 WebChat 中动态设置用户头像

  1. OAuthCard 登录后使用 Microsoft 图形 API 获取用户图标
  2. 动态设置 Webchat styleSetOptions 中的登录用户图像。

我已经按照提供的示例完成了为机器人设置机器人框架服务器和网络聊天的演示

机器人服务器 == https://github.com/Microsoft/BotBuilder-Samples

网络聊天 == https://github.com/Microsoft/BotFramework-WebChat

但是没有关于如何在用户登录后设置用户图像的适当示例或文档。使用已签名的用户对象。

可任何一点上正确的方向就如何实现。

提前致谢

4

1 回答 1

3

您可以通过将 Graph API 调用和结果包装到 AAD 登录过程的结果中来实现此目的。以下代码基于使用 React.Component 的 BotBuilder-Samples24.bot-authentication-msgraph 示例和 BotFramework-WebChat17.chat-send-history 示例

(请注意,当前位于 master 分支中的 Graph 示例不包括获取 AAD 登录用户的照片。我有一个 PR 将这个功能添加到示例中,但是我也将其包含在此处。我使用了 WebChat示例作为构建以下内容的参考。)

网络聊天

您将需要示例 #17 中的这些文件,然后是需要更改的 App.js 文件:

  • 公共[文件夹]
    • favicon.ico
    • 索引.html
    • 清单.json
  • src [文件夹]
    • 应用程序.js
    • 索引.css
    • index.js
  • .env
  • 包.json

应用程序.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&hellip;</div>
    );
  }
}

包.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);
        });
}

包.json:

由于后续版本中围绕 AAD 'displayName' 获取的重大更改,请确保@microsoft/microsoft-graph-client安装版本 1.0.0。

实现上述代码后,我可以登录,成功后立即更新用户头像。

希望有帮助!

于 2019-04-24T08:50:31.147 回答