次の方法で共有


チュートリアル: チャット アプリでインライン 画像のサポートを有効にする

Chat SDK は、Microsoft Teamsをシームレスに操作するように設計されています。 具体的には、Chat SDK は、インライン イメージを受信し、Microsoft Teamsからユーザーにインライン イメージを送信するソリューションを提供します。

このチュートリアルでは、Azure Communication Services Chat SDK for JavaScript を使用してインライン イメージのサポートを有効にする方法について説明します。

インライン イメージは、Teams クライアントの送信ボックスに直接コピーして貼り付ける画像です。 [このデバイスからアップロード] メニューまたはドラッグ アンド ドロップを使用してアップロードされた画像 (Teams の送信ボックスに直接ドラッグされた画像など) の場合は、このチュートリアルをファイル共有機能の一部として参照する必要があります。 (「画像の添付ファイルを処理する」セクションを参照してください)。)

イメージをコピーするには、Teams ユーザーには次の 2 つのオプションがあります。

  • オペレーティング システムのコンテキスト メニューを使用してイメージ ファイルをコピーし、Teams クライアントの送信ボックスに貼り付けます。
  • キーボード ショートカットを使用します。

このチュートリアルでは、次の場合に実行する必要がある操作について説明します。

現在、インライン イメージを送信する機能は、パブリック プレビューで利用できます。 JavaScript でのみ使用できます。 インラインイメージの受信は、現在一般に利用可能です。 Teams 相互運用性チャットでは、JavaScript と C# の両方で使用できます。

[前提条件]

サンプル コード

このチュートリアルの最終的なコードは GitHub にあります。

受信したインライン イメージを新しいメッセージ イベントで処理する

このセクションでは、新しいメッセージ受信イベントのメッセージ コンテンツに埋め込まれたインライン イメージをレンダリングする方法について説明します。

クイック スタートでは、chatMessageReceived イベントのイベント ハンドラーを作成しました。これは、Teams ユーザーから新しいメッセージを受信したときにトリガーされます。 また、次のように、messageContainerからchatMessageReceived イベントを受信したときに、受信メッセージの内容を直接chatClientに追加します。

chatClient.on("chatMessageReceived", (e) => {
   console.log("Notification chatMessageReceived!");

   // Check whether the notification is intended for the current thread
   if (threadIdInput.value != e.threadId) {
      return;
   }

   if (e.sender.communicationUserId != userId) {
      renderReceivedMessage(e.message);
   }
   else {
      renderSentMessage(e.message);
   }
});
   
async function renderReceivedMessage(message) {
   messages += '<div class="container lighter">' + message + '</div>';
   messagesContainer.innerHTML = messages;
}

ChatMessageReceivedEvent型の受信イベントから、attachments という名前のプロパティにインライン イメージに関する情報が含まれます。 UI でインライン イメージをレンダリングする必要があるのは、これですべてです。

export interface ChatMessageReceivedEvent extends BaseChatMessageEvent {
  /**
   * Content of the message.
   */
  message: string;

  /**
   * Metadata of the message.
   */
  metadata: Record<string, string>;

  /**
   * Chat message attachment.
   */
  attachments?: ChatAttachment[];
}

export interface ChatAttachment {
  /** Id of the attachment */
  id: string;
  /** The type of attachment. */
  attachmentType: ChatAttachmentType;
  /** The name of the attachment content. */
  name?: string;
  /** The URL where the attachment can be downloaded */
  url?: string;
  /** The URL where the preview of attachment can be downloaded */
  previewUrl?: string;
}

export type ChatAttachmentType = "image" | "unknown";

次に、前のコードに戻り、次のコード スニペットのような追加のロジックを追加します。

chatClient.on("chatMessageReceived", (e) => {
  console.log("Notification chatMessageReceived!");
  // Check whether the notification is intended for the current thread
  if (threadIdInput.value != e.threadId) {
     return;
  }
   
  const isMyMessage = e.sender.communicationUserId === userId;
  renderReceivedMessage(e, isMyMessage);
});

function renderReceivedMessage(e, isMyMessage) {
  const messageContent = e.message;

  const card = document.createElement('div');
  card.className = isMyMessage ? "container darker" : "container lighter";
  card.innerHTML = messageContent;
  
  messagesContainer.appendChild(card);
  
  // Filter out inline images from attachments
  const imageAttachments = e.attachments.filter((e) =>
    e.attachmentType.toLowerCase() === 'image');
  
  // Fetch and render preview images
  fetchPreviewImages(imageAttachments);
  
  // Set up onclick event handler to fetch full-scale image
  setImgHandler(card, imageAttachments);
}

function setImgHandler(element, imageAttachments) {
  // Do nothing if there are no image attachments
  if (!imageAttachments.length > 0) {
    return;
  }
  const imgs = element.getElementsByTagName('img');
  for (const img of imgs) {
    img.addEventListener('click', (e) => {
      // Fetch full-scale image upon click
      fetchFullScaleImage(e, imageAttachments);
    });
  }
}

async function fetchPreviewImages(attachments) {
  if (!attachments.length > 0) {
    return;
  }
  // Since each message could contain more than one inline image
  // we need to fetch them individually 
  const result = await Promise.all(
      attachments.map(async (attachment) => {
        // Fetch preview image from its 'previewURL'
        const response = await fetch(attachment.previewUrl, {
          method: 'GET',
          headers: {
            // The token here should be the same one from chat initialization
            'Authorization': 'Bearer ' + tokenString,
          },
        });
        // The response would be in an image blob, so we can render it directly
        return {
          id: attachment.id,
          content: await response.blob(),
        };
      }),
  );
  result.forEach((imageResult) => {
    const urlCreator = window.URL || window.webkitURL;
    const url = urlCreator.createObjectURL(imageResult.content);
    // Look up the image ID and replace its 'src' with object URL
    document.getElementById(imageResult.id).src = url;
  });
}

この例では、 fetchPreviewImagessetImgHandlerの 2 つのヘルパー関数を作成しました。 最初のものは、認証ヘッダーを持つ各previewURLオブジェクトで提供されるChatAttachmentからプレビュー画像を直接取得します。 同様に、関数onclick内の各イメージに対してsetImgHandler イベントを設定します。 イベントハンドラーでは、認証ヘッダーを使用してurlオブジェクト内のプロパティChatAttachmentからフルスケールのイメージを取得します。

認証ヘッダーを作成する必要があるため、トークンをグローバル レベルで公開する必要があります。 次のコードを変更する必要があります。

// New variable for token string
var tokenString = '';

async function init() {

   ....
   
   let tokenResponse = await identityClient.getToken(identityResponse, [
      "voip",
      "chat"
	]);
	const { token, expiresOn } = tokenResponse;
   
   // Save to token string
   tokenString = token;
   
   ...
}

オーバーレイでフル スケールのイメージを表示するには、新しいコンポーネントを追加する必要もあります。


<div class="overlay" id="overlay-container">
   <div class="content">
      <img id="full-scale-image" src="" alt="" />
   </div>
</div>

一部の CSS の場合:


/* let's make chat popup scrollable */
.chat-popup {

   ...
   
   max-height: 650px;
   overflow-y: scroll;
}

 .overlay {
    position: fixed; 
    width: 100%; 
    height: 100%;
    background: rgba(0, 0, 0, .7);
    top: 0;
    left: 0;
    z-index: 100;
 }

.overlay .content {
   position: fixed; 
   width: 100%;
   height: 100%;
   text-align: center;
   overflow: hidden;
   z-index: 100;
   margin: auto;
   background-color: rgba(0, 0, 0, .7);
}

.overlay img {
   position: absolute;
   display: block;
   max-height: 90%;
   max-width: 90%;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
}

#overlay-container {
   display: none
}

オーバーレイを設定したら、ロジックを使用してフル スケールのイメージをレンダリングします。 関数onClickを呼び出すfetchFullScaleImage イベント ハンドラーを作成したことを思い出してください。


const overlayContainer = document.getElementById('overlay-container');
const loadingImageOverlay = document.getElementById('full-scale-image');

function fetchFullScaleImage(e, imageAttachments) {
  // Get the image ID from the clicked image element
  const link = imageAttachments.filter((attachment) =>
    attachment.id === e.target.id)[0].url;
  loadingImageOverlay.src = '';
  
  // Fetch the image
  fetch(link, {
    method: 'GET',
    headers: {'Authorization': 'Bearer ' + tokenString},
  }).then(async (result) => {
   
    // Now we set image blob to our overlay element
    const content = await result.blob();
    const urlCreator = window.URL || window.webkitURL;
    const url = urlCreator.createObjectURL(content);
    loadingImageOverlay.src = url;
  });
  // Show overlay
  overlayContainer.style.display = 'block';
}

最後に追加したいのは、画像がクリックされたときにオーバーレイを閉じる機能です。

loadingImageOverlay.addEventListener('click', () => {
  overlayContainer.style.display = 'none';
});

これで、リアルタイム通知から送信されるメッセージのインライン 画像をレンダリングするために必要なすべての変更を行いました。

コードを実行する

Webpack ユーザーは、 webpack-dev-server を使用してアプリをビルドして実行できます。 ローカルの Web サーバーにアプリケーション ホストをバンドルするには、次のコマンドを実行します。

npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map

デモ

自分のブラウザーを開き、http://localhost:8080/ にアクセスします。 会議 URL とスレッド ID を入力します。 Teams クライアントからインライン イメージを送信します。

次のようなメッセージが送信された Teams クライアントを示すスクリーンショット。アイデアをいくつか紹介します。ご自分の考えを教えてください。メッセージには、部屋の内部モックアップの 2 つのインライン画像も含まれています。

その後、プレビュー 画像と共に新しいメッセージがレンダリングされます。

インライン 画像を含む受信メッセージを含むサンプル アプリを示すスクリーンショット。

Azure Communication Services ユーザーがプレビュー イメージを選択すると、Teams ユーザーによって送信された本格的なイメージと共にオーバーレイが表示されます。

本格的なイメージのオーバーレイを含むサンプル アプリを示すスクリーンショット。

新しいメッセージ要求でのインライン イメージの送信を処理する

重要

Azure Communication Services のこの機能は、現在プレビュー段階にあります。 プレビュー段階の機能は一般公開されており、新規および既存の Microsoft のすべてのお客様が使用できます。

プレビューの API と SDK は、サービス レベル アグリーメントなしに提供されます。 運用環境のワークロードには使用しないことをお勧めします。 特定の機能がサポートされていないか、機能が制約されている可能性があります。

詳しくは、Microsoft Azure プレビューの追加使用条件に関するページをご覧ください。

Chat SDK for JavaScript では、インライン イメージを使用してメッセージを処理するだけでなく、通信ユーザーが相互運用性チャットでMicrosoft Teams ユーザーにインライン イメージを送信できるようにするソリューションも提供されます。

ChatThreadClientから新しい API を確認します。

var imageAttachment = await chatThreadClient.uploadImage(blob, file.name, {
  "onUploadProgress": reportProgressCallback
});

API は、画像 BLOB、ファイル名文字列、およびアップロードの進行状況を報告する関数コールバックを受け取ります。

他のチャット参加者に画像を送信するには、次の操作を行う必要があります。

  1. uploadImageから ChatThreadClient API を使用してイメージをアップロードし、返されたオブジェクトを保存します。
  2. メッセージの内容を作成し、前の手順で保存した返されたオブジェクトに添付ファイルを設定します。
  3. sendMessageから ChatThreadClient API 経由で新しいメッセージを送信します。

イメージを受け入れる新しいファイル ピッカーを作成します。

<label for="myfile">Attach images:</label>
<input id="upload" type="file" id="myfile" name="myfile" accept="image/*" multiple>
<input style="display: none;" id="upload-result"></input>

次に、状態の変化がある場合のイベント リスナーを設定します。

document.getElementById("upload").addEventListener("change", uploadImages);

状態が変わるときの新しい関数を作成する必要があります。

var uploadedImageModels = [];

async function uploadImages(e) {
  const files = e.target.files;
  if (files.length === 0) {
    return;
  }
  for (let key in files) {
    if (files.hasOwnProperty(key)) {
        await uploadImage(files[key]);
    }
}
}

async function uploadImage(file) {
  const buffer = await file.arrayBuffer();
  const blob = new Blob([new Uint8Array(buffer)], {type: file.type });
  const url = window.URL.createObjectURL(blob);
  document.getElementById("upload-result").innerHTML += `<img src="${url}" height="auto" width="100" />`;
  let uploadedImageModel = await chatThreadClient.uploadImage(blob, file.name, {
    imageBytesLength: file.size
  });
  uploadedImageModels.push(uploadedImageModel);
}

この例では、各イメージをFileReaderエンコードされたイメージとして読み取るbase64を作成し、ChatSDK API を呼び出してアップロードする前にBlobを作成しました。 チャット SDK からアップロードされた画像のデータ モデルを保存するためのグローバル uploadedImageModels を作成しました。

最後に、前に作成した sendMessageButton イベント リスナーを変更して、アップロードしたイメージをアタッチする必要があります。

sendMessageButton.addEventListener("click", async () => {
  let message = messagebox.value;
  let attachments = uploadedImageModels;

    // Inject image tags for images we have selected
  // so they can be treated as inline images
  // Alternatively, we can use some third-party libraries 
  // to have a rich text editor with inline image support
  message += attachments.map((attachment) => `<img id="${attachment.id}" />`).join("");

  let sendMessageRequest = {
    content: message,
    attachments: attachments,
  };

  let sendMessageOptions = {
    senderDisplayName: "Jack",
    type: "html"
  };

  let sendChatMessageResult = await chatThreadClient.sendMessage(
    sendMessageRequest,
    sendMessageOptions
  );
  let messageId = sendChatMessageResult.id;
  uploadedImageModels = [];

  messagebox.value = "";
  document.getElementById("upload").value = "";
  console.log(`Message sent!, message id:${messageId}`);
});

それです。 次に、コードを実行して動作を確認します。

コードを実行する

Webpack ユーザーは、 webpack-dev-server を使用してアプリをビルドして実行できます。 ローカルの Web サーバーにアプリケーション ホストをバンドルするには、次のコマンドを実行します。

npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map

デモ

自分のブラウザーを開き、http://localhost:8080/ にアクセスします。 画像を添付するための新しいセクションが送信ボックスに追加されました。

画像を添付するためのセクションが新しく追加されたサンプル アプリを示すスクリーンショット。

次に、添付するイメージを選択できます。

ユーザーがメッセージに添付できるイメージの一覧を含むファイル ピッカーを示すスクリーンショット。

2 つの画像が添付されたサンプル アプリを示すスクリーンショット。

Teams ユーザーは、[ 送信] を選択したときに送信した画像を受け取る必要があります。

2 つの埋め込み画像を含む送信済みメッセージを含むサンプル アプリを示すスクリーンショット。

2 つの埋め込み画像を含む受信メッセージを含む Teams クライアントを示すスクリーンショット。

このチュートリアルでは、C# 用 Azure Communication Services Chat SDK を使用してインライン イメージのサポートを有効にする方法について説明します。

このチュートリアルでは、以下の内容を学習します。

  • 新しいメッセージのインライン イメージを処理します。

[前提条件]

目標

  • インライン画像添付ファイルの previewUri プロパティを取得します。

新しいメッセージのインライン イメージを処理する

クイック スタートでは、メッセージをポーリングし、新しいメッセージを messageList プロパティに追加します。 この機能をさらに発展させて、インライン画像の解析と取得を含めます。

  CommunicationUserIdentifier currentUser = new(user_Id_);
  AsyncPageable<ChatMessage> allMessages = chatThreadClient.GetMessagesAsync();
  SortedDictionary<long, string> messageList = [];
  int textMessages = 0;
  await foreach (ChatMessage message in allMessages)
  {
      if (message.Type == ChatMessageType.Html || message.Type == ChatMessageType.Text)
      {
          textMessages++;
          var userPrefix = message.Sender.Equals(currentUser) ? "[you]:" : "";
          var strippedMessage = StripHtml(message.Content.Message);
          messageList.Add(long.Parse(message.SequenceId), $"{userPrefix}{strippedMessage}");
      }
  }

ChatMessageReceivedEvent型の受信イベントから、attachments という名前のプロパティにインライン イメージに関する情報が含まれます。 UI でインライン イメージをレンダリングするために必要な操作はこれですべてです。

public class ChatAttachment
{
    public ChatAttachment(string id, ChatAttachmentType attachmentType)
    public ChatAttachmentType AttachmentType { get }
    public string Id { get }
    public string Name { get }
    public System.Uri PreviewUrl { get }
    public System.Uri Url { get }
}

public struct ChatAttachmentType : System.IEquatable<AttachmentType>
{
    public ChatAttachmentType(string value)
    public static File { get }
    public static Image { get }
}

次の JSON は、画像添付ファイルに対して ChatAttachment がどのように表示されるかの例です。

"attachments": [
    {
        "id": "9d89acb2-c4e4-4cab-b94a-7c12a61afe30",
        "attachmentType": "image",
        "name": "Screenshot.png",
        "url": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/original?api-version=2023-11-03",
        "previewUrl": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/small?api-version=2023-11-03"
      }
]

ここで戻ってコードを置き換えて、画像添付ファイルを解析して取り込むための追加のロジックを追加します。

  CommunicationUserIdentifier currentUser = new(user_Id_);
  AsyncPageable<ChatMessage> allMessages = chatThreadClient.GetMessagesAsync();
  SortedDictionary<long, string> messageList = [];
  int textMessages = 0;
  await foreach (ChatMessage message in allMessages)
  {
      // Get message attachments that are of type 'image'
      IEnumerable<ChatAttachment> imageAttachments = message.Content.Attachments.Where(x => x.AttachmentType == ChatAttachmentType.Image);

      // Fetch image and render
      var chatAttachmentImageUris = new List<Uri>();
      foreach (ChatAttachment imageAttachment in imageAttachments)
      {
          client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", communicationTokenCredential.GetToken().Token);
          var response = await client.GetAsync(imageAttachment.PreviewUri);
          var randomAccessStream = await response.Content.ReadAsStreamAsync();
          await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
          {
              var bitmapImage = new BitmapImage();
              await bitmapImage.SetSourceAsync(randomAccessStream.AsRandomAccessStream());
              InlineImage.Source = bitmapImage;
          });

          chatAttachmentImageUris.Add(imageAttachment.PreviewUri);
      }

      // Build message list
      if (message.Type == ChatMessageType.Html || message.Type == ChatMessageType.Text)
      {
          textMessages++;
          var userPrefix = message.Sender.Equals(currentUser) ? "[you]:" : "";
          var strippedMessage = StripHtml(message.Content.Message);
          var chatAttachments = chatAttachmentImageUris.Count > 0 ? "[Attachments]:\n" + string.Join(",\n", chatAttachmentImageUris) : "";
          messageList.Add(long.Parse(message.SequenceId), $"{userPrefix}{strippedMessage}\n{chatAttachments}");
      }

この例では、Image 型のメッセージからすべての添付ファイルを取得し、それから各画像をダウンロードします。 認証のために、リクエストヘッダーのToken部分でBearerを使用する必要があります。 イメージがダウンロードされたら、ビューの InlineImage 要素に割り当てることができます。

また、テキスト メッセージ リストにメッセージと共に表示する添付ファイル URI の一覧も含めます。

デモ

  • 統合開発環境 (IDE) からアプリケーションを実行します。
  • Teams 会議リンクを入力します。
  • 会議に参加します。
  • Teams 側でユーザーを許可します。
  • Teams 側から画像を含むメッセージを送信します。

メッセージに含まれる URL がメッセージ一覧に表示されます。 最後に受信したイメージは、ウィンドウの下部にレンダリングされます。

次のステップ