スクショを取得し、S3にアップロード、マークダウン形式のリンクを発行するやーつ（Vue3、AWS Lambda Python 3.12）

# はじめに
[がちもとさんアドベントカレンダー](https://qiita.com/advent-calendar/2023/gachimoto3)21日目の記事です。

Qiitaは、毎月100MBまでという画像アップロード制限があります。

https://x.com/sotongshi/status/1737334533572513862?s=20

アドベントカレンダーを諦めかけたので、スクショを取得し、S3にアップロード、マークダウン形式のリンクを発行するやつを作りました。

# 開発環境
- Windows 11 PC
- Vue3
- AWS Lambda（Python 3.12）

# 導入

1．画像をS3にアップロードするAPI（Lambda関数）を作成

~~~lambda_function.py
import json
import boto3
import base64
import uuid
from datetime import datetime
import os

# 環境変数からAPIキーを取得
api_key = os.environ.get('API_KEY')

# AWSリソースのセットアップ
s3 = boto3.client('s3')
 
def lambda_handler(event, context):

    try:
        # リクエストからAPIキーを取得
        provided_api_key = event['headers']['x-api-key']
        print(provided_api_key)

        # 提供されたAPIキーが設定されたAPIキーと一致するかを確認
        if provided_api_key != api_key:
            return {
                'statusCode': 401,
                'body': json.dumps({'message': 'APIキーが無効です。'})
            }
            
        # イベントからデータを取得
        event_body = json.loads(event['body'])  # JSON文字列をPython辞書に変換
        image_data_base64 = event_body.get('imageData')
        bucket_name = 'gachimoto-qiita-storage'
        
        # 日付とUUIDを使用してファイル名を生成
        timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
        unique_id = str(uuid.uuid4())[:8]  # ランダムな8文字のUUID
        file_name = f'{timestamp}-{unique_id}.png'  # S3に保存するファイル名

        # Base64形式のデータをバイナリにデコード
        image_data_binary = base64.b64decode(image_data_base64)
 
        # S3にバイナリデータを直接アップロード
        s3.put_object(
            Bucket=bucket_name,
            Key=file_name,
            Body=image_data_binary,
            ContentType='image/png'  # ContentTypeを指定
        )

        # アップロードされた画像のURLを生成
        image_url = f'https://{bucket_name}.s3.amazonaws.com/{file_name}'

        return {
            'statusCode': 200,
            'body': json.dumps({'message': f"![{file_name}]({image_url})"})
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({'message': str(e)})
        }
~~~

2．Lambda関数の設定

![20231221-165416-fa8f067d.png](https://gachimoto-qiita-storage.s3.amazonaws.com/20231221-165416-fa8f067d.png)

![20231221-165556-9750a165.png](https://gachimoto-qiita-storage.s3.amazonaws.com/20231221-165556-9750a165.png)

![20231221-165642-6f36fdac.png](https://gachimoto-qiita-storage.s3.amazonaws.com/20231221-165642-6f36fdac.png)

3．ロールの設定
![20231221-165458-bd136835.png](https://gachimoto-qiita-storage.s3.amazonaws.com/20231221-165458-bd136835.png)

4．S3の設定

![20231221-165808-9bcdbadd.png](https://gachimoto-qiita-storage.s3.amazonaws.com/20231221-165808-9bcdbadd.png)

バケットポリシー
~~~json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::gachimoto-qiita-storage/*"
        }
    ]
}
~~~

ACLの有効化
![20231221-170003-a8aa3df0.png](https://gachimoto-qiita-storage.s3.amazonaws.com/20231221-170003-a8aa3df0.png)

5．Vue3のアプリ作成

~~~App.vue
<template>
  <div>
    <button @click="captureScreenshot">スクリーンショットを取得</button>
    <br>
    <br>
    <img v-if="imageURL" :src="imageURL" alt="クリップボードの画像" style="max-width: 200px; height: auto;">
    <p v-if="successMessage">{{ successMessage }}</p>
    <p v-if="imageUrl">{{ imageUrl }}</p>
    <button @click="copyImageUrlToClipboard" v-if="imageUrl">画像リンクをコピー</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      imageURL: null,
      successMessage: '',
      imageUrl: '',
    };
  },
  methods: {
    async captureScreenshot() {
      try {
        const clipboardItems = await navigator.clipboard.read();
        for (const clipboardItem of clipboardItems) {
          if (clipboardItem.types.includes('image/png')) {
            const blob = await clipboardItem.getType('image/png');
            const reader = new FileReader();

            reader.onloadend = () => {
              const image_data_base64 = reader.result.split(',')[1];
              this.imageURL = 'data:image/png;base64,' + image_data_base64;

              // Lambda関数のエンドポイントURLを設定
              const lambda_endpoint_url = 'https://xxxx.lambda-url.ap-northeast-1.on.aws/';

              // Lambda関数に渡すデータ
              const lambda_data = {
                'imageData': image_data_base64,
              };

              const apiKey = process.env.VUE_APP_API_KEY;
              // Lambda関数にHTTP POSTリクエストを送信
              fetch(lambda_endpoint_url, {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json',
                  'x-api-key': apiKey
                },
                body: JSON.stringify(lambda_data),
              })
                .then((response) => {
                  if (response.status === 200) {
                    return response.json();
                  } else {
                    throw new Error(`HTTPリクエストエラー: ステータスコード ${response.status}`);
                  }
                })
                .then((responseData) => {
                  console.log('Lambdaからのレスポンス:', responseData);
                  this.successMessage = '画像がアップロードされました。';
                  this.imageUrl = responseData.message;
                })
                .catch((error) => {
                  console.error('エラー:', error);
                  this.successMessage = '画像のアップロードに失敗しました。';
                });
            };

            reader.readAsDataURL(blob);
          }
        }
      } catch (error) {
        console.error('クリップボードからの画像取得エラー', error);
        this.successMessage = '画像のアップロードに失敗しました。';
      }
    },
    copyImageUrlToClipboard() {
      // imageUrlをクリップボードにコピーする処理を実装
      const textArea = document.createElement("textarea");
      textArea.value = this.imageUrl;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand('copy');
      document.body.removeChild(textArea);
    },
  },
};
</script>
~~~

~~~.env
VUE_APP_API_KEY=xxxx
~~~


# 実行結果
![20231221-162115-06354714.png](https://gachimoto-qiita-storage.s3.amazonaws.com/20231221-162115-06354714.png)

お疲れさまでした。