且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何基于使用OAuthCard登录的用户在BotFramework-WebChat中动态设置用户头像

更新时间:2023-02-25 12:25:08

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

You can achieve this by wrapping the Graph API call and result into the result of the AAD login process. The following code is based off of the BotBuilder-Samples 24.bot-authentication-msgraph sample and BotFramework-WebChat 17.chat-send-history sample using React.Component.

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

(Please be aware that the Graph sample currently located in the master branch does not include obtaining the AAD login user's photo. I have a PR which adds this feature into the sample, however I have included it here, as well. I used the WebChat sample as a reference for building the below.)

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

You will need these files from sample #17, followed by the App.js file that needs altering:

  • 公开 [文件夹]
    • favicon.ico
    • index.html
    • manifest.json
    • public [folder]
      • favicon.ico
      • index.html
      • manifest.json
      • App.js
      • index.css
      • index.js

      注意:我在一个单独的项目中本地生成了直接令牌.假设AAD个人资料中有照片.

      Note: I generate the direct line token locally in a separate project. This assumes an AAD profile has a photo.

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

      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 Graph Bot

      更新示例#24中的以下文件:

      MS Graph Bot

      Update the following files in sample #24:

      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;
          }
      }
      

      simple-graph-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由于在后续版本中获取AAD'displayName'所引起的重大更改而安装了1.0.0版本.

      package.json:

      Be sure the @microsoft/microsoft-graph-client installs version 1.0.0 due to breaking changes around AAD 'displayName' acquisition in subsequent versions.

      实施上述代码后,我便能够登录,成功登录后,便会立即更新用户头像.

      Once the above code was implemented, I was able to login which, upon success, immediately updated the user avatar.

      希望有帮助!