自動Qiita投稿システムを作ってみた
今日は、勉強メモをQiitaに自動投稿するシステムを作った話をシェアします。
動機
AWSの資格勉強をしている中で、分からないところのメモを取っていました。このメモをなんとかアウトプットとして活用できないかと考え、Qiitaへの投稿を自動化しようと思い立ちました。
ワークフロー🔗
以下の流れで自動投稿システムを作りました。
- LINEbotにメモを送信
- API Gatewayで受け取りLambdaに流す
- LambdaでメモをBeadrockに渡し、Markdownの記事を作成
- Qiita APIで投稿
コード
LINEbotからメモを受け取るLambda
LINEBotにメモを送信するとAPIGatewayのエンドポイントに送信するようMessageAPIを設定します。
トークンはSecretManagerに格納しておき、LambdaでLINEからのメッセージを受け取ります。
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
// --- 型定義 ---
interface QiitaPostResponse {
url: string;
title: string;
id: string;
[key: string]: unknown;
}
interface Secrets {
LINE_CHANNEL_ACCESS_TOKEN: string;
QIITA_ACCESS_TOKEN: string;
BEDROCK_API_KEY: string;
}
// --- Lambda Handler ---
export const handler = async (event: any) => {
const secretsClient = new SecretsManagerClient({ region: "XXX" });
const parsedEvent = typeof event.body === "string"
? JSON.parse(event.body)
: event;
try {
// Secrets Manager からキーを取得
const secretRes = await secretsClient.send(
new GetSecretValueCommand({
SecretId: process.env.SECRET_NAME
})
);
if (!secretRes.SecretString) {
throw new Error("Secrets not found");
}
const secrets: Secrets = JSON.parse(secretRes.SecretString);
if (!parsedEvent.events || !Array.isArray(parsedEvent.events) || parsedEvent.events.length === 0) {
return { statusCode: 200, body: "非対応イベント" };
}
const lineEvent = parsedEvent.events[0];
if (!lineEvent || lineEvent.type !== "message" || !lineEvent.message?.text) {
return { statusCode: 200, body: "非対応イベント" };
}
const userMessage = lineEvent.message.text;
BeadrockでMarkdown記事を作成するLambda
受け取ったメッセージをあらかじめ用意したプロンプトに織り交ぜ生成AIに投げます。
今回はNovaというモデルを採用しています。
// --- Bedrock Claude に送信 ---
const bedrockClient = new BedrockRuntimeClient({ region: "xxx" });
const prompt = `あなたはQiitaで記事を書くエンジニアです。
ユーザーから与えられたメモをもとに、Qiita用のMarkdown記事を文章を作成してください。
# 要件
- 見出し・箇条書き・コード例・注意点を含める
- 未検証部分は「補足: 未検証」と記載
- 動機セクションを記載
- まとめセクションを最後に追加
- タイトルは内容を要約したもの
=== メモ:\n\n${userMessage}`;
const input = {
modelId: "XXXnovaXXX",
contentType: "application/json",
accept: "application/json",
body: JSON.stringify({
messages: [
{
role: "user",
content: [{ text: prompt }],
},
],
inferenceConfig: {
maxTokens: 1024,
temperature: 0.7,
},
}),
};
const bedrockRes = await bedrockClient.send(new InvokeModelCommand(input));
const responseBody = await bedrockRes.body.transformToString();
const claudeData = JSON.parse(responseBody);
const articleBody = claudeData.output?.message?.content?.[0]?.text || "";
Qiita APIで投稿するLambda
QiitaのAPIを介してQiitaに記事を投稿します。
// --- Qiita 下書き作成 ---
const qiitaRes = await fetch("https://qiita.com/api/v2/items", {
method: "POST",
headers: {
Authorization: `Bearer ${secrets.QIITA_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
body: articleBody,
private: false,
draft: true,
tags: [{ name: "自動投稿", versions: [] }],
title: "Lambdaからの自動投稿",
}),
});
if (!qiitaRes.ok) {
const text = await qiitaRes.text();
throw new Error(`Qiita API Error: ${qiitaRes.status} ${text}`);
}
const qiitaResult: QiitaPostResponse = await qiitaRes.json() as QiitaPostResponse;
実装の上で苦労した点
- Qiita APIの制約: QiitaのAPIでは下書きに直接投稿できないため、投稿し、コピペし、投稿を削除し、下書きを新しく手動で作るという手間が生じています。
- APIのレスポンス処理: 各APIのレスポンスのエラー処理に一番時間を取られました。CloudWatch様様です。地道にデバッグしました。
改善点として
QiitaのAPIの使用上、直接下書き保存ができないので、下書きに直接投げれるような仕組みを思案中です。
まとめ🏁
今日は、勉強メモを自動でQiitaに投稿するシステムを作った話をしました。
この記事含め、学習記録の記事はこのシステムを活用して投稿してます。
何か質問やフィードバックがあれば、ぜひコメント欄にご意見ください!💬