65.9K
CodeProject is changing. Read more.
Home

Creating a File Upload Bot

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1 vote)

Feb 14, 2022

CPOL

4 min read

viewsIcon

4680

How to create a Spring Boot web app in Java that replicates the functionality of the “Teams File Upload” sample app on GitHub

Chatbots provide a convenient platform when working with file upload. Chat platforms like Microsoft Teams already expose a familiar interface for attaching files to messages, as well as taking care of hosting any message attachments. This relieves us of having to code the rather mundane, but still tricky logic that would otherwise be required to handle file uploads ourselves.

In this post, we’ll create a bot that downloads and processes file attachments in chat messages.

Prerequisites

To follow along in this article, you’ll need a Java 11 JDK, Apache Maven, Node.js and npm, an Azure subscription to deploy the final application, and the Azure CLI.

I’ve also used a scaffolding tool, Yeoman, to simplify setup. Install it with the following command:

npm install -g yo

The chatbot template is provided by the generator-botbuilder-java package, which you’ll need to install with this command:

npm install -g generator-botbuilder-java

You now have everything you need to create your sample chatbot application.

Example Source Code

You can follow along by examining this project’s source code on its GitHub page.

Build the Base Application

Refer to the previous article in this series for the process of building and updating the base application. Follow the instructions under the headings “Creating the Sample Application” and “Updating the Sample Application” to create a Spring Boot project using Yeoman.

Add New Dependencies

In addition to the updated dependencies mentioned in the previous article, you also need to add Maven dependencies to support your file upload bot.

Add the following dependencies to the pom.xml file:

<dependencies>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.11.0</version>
    </dependency>
    …
</dependencies>

Build the Upload Bot

Your bot is defined in the UploadBot class and extends the ActivityHandler class:

public class UploadBot extends ActivityHandler {

The onMembersAdded method displays a message to any new chat members informing them that the bot will download files from message attachments:

  @Override
  protected CompletableFuture<Void> onMembersAdded(
      final List<ChannelAccount> membersAdded,
      final TurnContext turnContext
  ) {
    return membersAdded.stream()
        .filter(
            member -> !StringUtils
                .equals(member.getId(), turnContext.getActivity().getRecipient().getId())
        ).map(channel -> turnContext.sendActivity(
            MessageFactory.text
            ("Welcome! Post a message with an attachment and I'll download it!")))
        .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null);
  }

The onMessageActivity method inspects any new messages to determine if they have attachments:

  @Override
  protected CompletableFuture<Void> onMessageActivity(final TurnContext turnContext) {
    if (messageWithDownload(turnContext.getActivity())) {
      final Attachment attachment = turnContext.getActivity().getAttachments().get(0);

If an attachment was found, the file is downloaded to a local temporary location:

      return downloadAttachment(attachment)

If there were any issues with the file download, a message is posted to the chat:

          .thenCompose(result -> !result.result()
              ? turnContext.sendActivityBlind(
              MessageFactory.text("Failed to download the attachment"))

If the attachment was successfully downloaded, it is inspected, and the file length and type are posted to the chat:

              : turnContext.sendActivityBlind(
                  MessageFactory.text(
                      "Downloaded file " + attachment.getName() + ". It was "
                          + getFileSize(result.getRight())
                          + " bytes long and appears to be of type "
                          + getFileType(result.getRight())))
          );
    }

If no attachments were found, the bot posts a reminder to the chat informing users that it will download any attachments:

    return turnContext.sendActivity(
        MessageFactory.text("Post a message with an attachment and I'll download it!")
    ).thenApply(sendResult -> null);
  }

To determine if a message has an attachment, the messageWithDownload method checks the attachments collection and verifies the content type of the first attachment:

  private boolean messageWithDownload(final Activity activity) {
    return activity.getAttachments() != null
        && activity.getAttachments().size() > 0
        && StringUtils.equalsIgnoreCase(
        activity.getAttachments().get(0).getContentType(),
        FileDownloadInfo.CONTENT_TYPE);
  }

Downloading an attachment is similar to downloading any file from an HTTP server. The downloadAttachment method downloads attachments to a temporary location:

  private CompletableFuture<ResultPair<String>> downloadAttachment(final Attachment attachment) {

Any shared variable accessed from within lambda methods must be final, or effectively final, which simply means their value does not change once it has been set.

So, the result of your file download is captured in a ResultPair, which is wrapped in a final AtomicReference. This allows you to return the results of your file download from within the lambda methods created next:

    final AtomicReference<ResultPair<String>> result = new AtomicReference<>();

The app creates a temporary file, then uses the Apache Commons library to download the attachment to it:

    return CompletableFuture.runAsync(() -> {
          try {
            final FileDownloadInfo fileDownload = Serialization
                .getAs(attachment.getContent(), FileDownloadInfo.class);
            final File filePath = Files.createTempFile(
                FilenameUtils.getBaseName(attachment.getName()),
                "." + FilenameUtils.getExtension(attachment.getName())).toFile();

            FileUtils.copyURLToFile(
                new URL(fileDownload.getDownloadUrl()),
                filePath,
                30000,
                30000);

If everything went well, you return true and the path to the temporary file by setting the value wrapped by the AtomicReference:

            result.set(new ResultPair<>(true, filePath.getAbsolutePath()));
          } catch (Throwable t) {

In the event of an error, you return false and the error message:

            result.set(new ResultPair<>(false, t.getLocalizedMessage()));
          }
        })

The wrapped ResultPair is then returned by the CompletableFuture:

        .thenApply(aVoid -> result.get());
  }

To demonstrate that the bot has successfully downloaded the attachment, the getFileSize method returns the file’s size:

  private long getFileSize(final String path) {
    try {
      return Files.size(Paths.get(path));
    } catch (IOException e) {
      return -1;
    }
  }

You also inspect the file’s type with the getFileType method:

  private String getFileType(final String path) {
    try {
      final String type = Files.probeContentType(Paths.get(path));
      return type == null ? "unknown" : type;
    } catch (IOException e) {
      return "unknown";
    }
  }
}

Test the Bot

Refer to the instructions in the first article in this series, in the “Deploy the Bot” and “Link to Teams” sections, to deploy the catering bot and integrate it with Teams.

Then, post a message with an attachment. The bot will detect the attachment, download it, and report back with the file size and type:

Title: Inserting image...

Conclusion

Handling files with a chatbot is straightforward, requiring only the ability to download HTTP files from regular attachments in messages.

In this article, you created a simple chatbot that downloaded any message attachments and reported back the file size to indicate that it has successfully downloaded the attachment.

This example is easy to extend with any additional logic that your teams may require, allowing users to upload files through the familiar Teams chat interface. It can be used as the basis for tools such as file conversion, and can support workflows or application deployments. Simply clone the source code from GitHub and add your own custom logic to the UploadBot class to build a bot that suits your needs.

To learn more about building your own bot for Microsoft Teams, check out Building great bots for Microsoft Teams with Azure Bot Framework Composer.