LoginSignup
1
1

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

Last updated at Posted at 2023-12-21

はじめに

がちもとさんアドベントカレンダー21日目の記事です。

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

アドベントカレンダーを諦めかけたので、スクショを取得し、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

20231221-165556-9750a165.png

20231221-165642-6f36fdac.png

3.ロールの設定
20231221-165458-bd136835.png

4.S3の設定

20231221-165808-9bcdbadd.png

バケットポリシー

{
    "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

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

お疲れさまでした。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1