はじめに
がちもとさんアドベントカレンダー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関数の設定
4.S3の設定
バケットポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::gachimoto-qiita-storage/*"
}
]
}
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
実行結果
お疲れさまでした。