LINEボットからクリップボードへコピーできるアクションを使ってみた

LINEヤフー社からのニュースリリースにより、テキストをクリップボードへコピーするアクションが Messaging API に追加されたので試してみました。途中、所帯じみたスクショがありますがスルー頂けると幸いです。
2024.02.14

こんにちは、高崎@アノテーション です。

はじめに

LINEヤフー社より、先日以下のニュースリリースがされました。

Messaging APIにおいて、ユーザーがクリップボードにテキストを簡単にコピーできるアクションが追加されました

今まで、ボットや公式アカウントのトークからですと、テキストをコピーする際に吹き出しを長押しして「コピー」をタップする必要がありました(下図参照)。

そのためクリップボードへのコピーをしたければ2回アクションを行う必要がありましたが、今回の対応でワンアクションでクリップボードへのコピーが出来るようになりました。

そこで、ちょっと手持ちの LINE ボット環境に組み込んでみたいと思います。

ベース環境

今回も例によって例のごとく、以下のブログで作成した環境を使用したいと思います。

なお、変更前ソースの GitHub は下記です。

ソースへの組み込み

考え方

前述のニュースリリースに記載されているコマンド例によると、

ニュースリリースより抜粋

curl -v -X POST https://api.line.me/v2/bot/message/push \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
    "to": "U4af4980629...",
     "messages":[
      {
        "type": "template",
        "altText": "クーポンコードをお送りします。",
        "template": {
          "type": "buttons",
          "thumbnailImageUrl": "{your coupon image}",
          "imageAspectRatio": "rectangle",
          "imageSize": "cover",
          "imageBackgroundColor": "#FFFFFF",
          "title": "限定クーポン配布中!",
          "text": "有効期限:2024年2月末日\nクーポンコード(3B48740B)を下記のボタンからコピーしてお使いください。",
          "actions": [
            {
              "type": "clipboard",
              "label": "コピー",
              "clipboardText": "3B48740B"  // クリップボードにコピーするテキストを指定する
            }
          ]
        }
      }
    ]
}'

ハイライトしていますがactionsセクションのtypeclipboardという定義が追加になりました。

今回は、オウム返しする時にテキストメッセージに、この例と同じようにtemplateを追加して返信するようにします。

実装してみる

前述の環境に対して下記の箇所を対応します。

src/lambda/use-case/line-bot-use-case/use-case.ts

import { WebhookRequestBody, WebhookEvent } from "@line/bot-sdk";
import { LineBot } from "../../domain/support/line-bot/line-bot";
import { MemoStoreRepository } from "../../domain/model/memoStore/memoStore-repository";
import { ImageCraftRepository } from "../../domain/model/imageCraft/imageCraft-repository";
import { execRegisterCommand, execListCommand, execDeleteCommand, execAskCommand, ReplyMessages } from "./dispatchCommand";

export class InvalidSignatureError extends Error {}
export class InvalidRequestError extends Error {}
export class UnexpectedError extends Error {}
export type LineBotUseCaseResult = 
  | void
  | InvalidSignatureError
  | InvalidRequestError
  | UnexpectedError;
export type ExecWebhookEventResult =
  | void
  | UnexpectedError;

/**
 * event ディスパッチ処理
 * @param webhookEvent : event インスタンス
 * @param lineBotClient : LINE の Messaging API 等実行インスタンス
 * @param memoStoreRepository : memoStore データリポジトリ
 * @param imageCraftRepository : imageCraft データリポジトリ
 * @returns ExecWebhookEventResult 戻り値(エラーインスタンス、エラー無しの場合は undefined)
 */
const dispatchEvent = async({
  webhookEvent,
  lineBotClient,
  memoStoreRepository,
  imageCraftRepository,
}: {
  webhookEvent: WebhookEvent,
  lineBotClient: LineBot,
  memoStoreRepository: MemoStoreRepository,
  imageCraftRepository: ImageCraftRepository,
}): Promise<ExecWebhookEventResult> => {
  console.log("LINE Bot use case start.", webhookEvent);
  if (webhookEvent.type !== "message" || webhookEvent.message.type !== "text") {
    console.error("メッセージがテキストではない");
    return new UnexpectedError();
  }
  const requestText: string = webhookEvent.message.text;
  const lineUserId: string = webhookEvent.source.userId || "";
  const quoteToken = webhookEvent.message.quoteToken;
  const replyToken = webhookEvent.replyToken;
  const commandResult: ReplyMessages = [];

  try {
    if (requestText.startsWith("regist:")) {
      // データ登録機能
      const resultRegisterCommand = await execRegisterCommand({
        memoStoreRepository,
        lineUserId,
        memoText: requestText.replace("regist:", ""),
        quoteToken,
      });
      console.log("result : ", resultRegisterCommand);
      resultRegisterCommand.map((item) => commandResult.push(item));
    } else if (requestText.startsWith("list")) {
      // データ一覧表示機能
      const resultListCommand = await execListCommand({
        memoStoreRepository,
        lineUserId,
        quoteToken,
        maxListNumber: Number(process.env.TABLE_MAXIMUM_NUMBER_OF_RECORD) || 5,
      });
      console.log("result : ", resultListCommand);
      resultListCommand.map((item) => commandResult.push(item));
    } else if (requestText.startsWith("delete:")) {
      // データ削除機能
      const resultDeleteCommand = await execDeleteCommand({
        memoStoreRepository,
        lineUserId,
        messageId: Number(requestText.replace("delete:", "")),
        quoteToken,
      });
      console.log("result : ", resultDeleteCommand);
      resultDeleteCommand.map((item) => commandResult.push(item));
    } else if (requestText.startsWith("ask:")) {
      // 生成 AI へ画像生成依頼機能
      const resultAskCommand = await execAskCommand({
        imageCraftRepository,
        orderedText: requestText.replace("ask:", ""),
        quoteToken,
      });
      console.log("result : ", resultAskCommand);
      resultAskCommand.map((item) => commandResult.push(item));
    } else {
      // オウム返し
      commandResult.push({
        type: "text",
        text: webhookEvent.message.text,
        quoteToken: quoteToken,
      });
      // クリップボードアクションを使ったテンプレートを送信
      commandResult.push({
        type: "template",
        altText: "文字をオウム返しします",
        template: {
          type: "buttons",
          title: "オウム返しボットテスト",
          text: "オウム返しテキストをクリップボードへコピーします",
          actions: [{
            type: "clipboard",
            label: "コピー",
            clipboardText: webhookEvent.message.text,
          },],
        }
      });
    }
    console.log("commandResult : ", commandResult);
    // LINE リプライ実行
    await lineBotClient.replyMessage({
      replyToken,
      messages: commandResult,
    });
  } catch (e) {
    console.error(e);
    return new UnexpectedError();
  }
  return undefined;
}
(以下略)

動作確認してみた

ビルド、デプロイは前回と同様です。

npm run build
cdk deploy

ボットでの実行画面です。

コピーを押すと、クリップボードへテキストがコピーされました。

ソース環境も置いておきます。1

おわりに

今回はクリップボードをテキストにコピーできるアクションについてお試しで使ってみました。

この対応で、メッセージ送信されたテキストそのものを見せることなくワンアクションでクリップボードへコピーが出来るようになります。

これによりそれぞれの「友だち」に個別で特有のテキスト(たとえば、個別で発行するクーポンコードといったもの)を送って公式サイトにてそのテキストを入力、といった局面で使えるかと思います。

今月は他にも利便性向上を図れる新しい施策がリリースされていますので、また紹介していきたいと思います。

アノテーション株式会社について

アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。
サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。


  1. ソースは他にsrc/lambda/use-case/line-bot-use-case/dispatchCommand.tsも変更しています。use-case 層に 3rd パーティーのライブラリを import するのはパターン的に微妙と思いましたが、対象が LINE ボットのライブラリを LINE ボットのユースケースに入れる、ということで import しました。