全国オンライン対応受付 平日 9:00-18:00
お問い合わせ

開発記録 / AiInterview制作記 / Vol.21 AiInterview制作記 Vol.1

記事 03

S3への直接PUT・雑誌レイアウト・号編成——地域メディア配信システムの全貌

記事ができた。確認した。さて、どこに出すか。

2026-06-07 公開

記事ができた。確認した。さて、どこに出すか。

Web・LINE・PDF。地域情報メディアの配信手段は複数あっていい。それぞれの入口から届く人が違う。AiInterviewは3つの配信手段を持つ。


GASからS3に直接PUT

記事を承認すると、管理画面の「Web公開」ボタンでS3に記事HTMLをアップロードする。

通常はAWS SDKを使う。しかしGASはNode.jsではなく独自のJavaScriptランタイムで動く。npmパッケージが使えない。SDKは使えない。

AWS Signature V4(SigV4)を自前で実装した。Utilities.computeHmacSignature()でHMACハッシュを計算し、AuthorizationヘッダーをURLFetchAppでS3にPUTリクエストとして送る。

function putToS3(key, content, contentType) {
  const region = 'ap-northeast-1';
  const bucket = 'aiinterview-articles';
  const host = `${bucket}.s3.amazonaws.com`;
  const now = new Date();
  // SigV4の計算...
  const response = UrlFetchApp.fetch(
    `https://${host}/${key}`,
    {
      method: 'PUT',
      headers: {
        'Authorization': authHeader,
        'x-amz-date': amzDate,
        'x-amz-content-sha256': contentHash,
        'Content-Type': contentType,
      },
      payload: content,
      muteHttpExceptions: true,
    }
  );
}

実装で詰まった点はbyte[]の扱いだ。Utilities.computeHmacSignature()はbyte[]を返す。次のHMAC計算にはbyte[]のまま渡す必要がある。文字列に変換してから再変換すると正しくない結果になる。GASのbyte[]型はJavaScriptの配列としては扱えるが、型の素性を意識しないとハマる。


雑誌レイアウト

月号分の記事をA4サイズの雑誌レイアウトで表示して、ブラウザの印刷機能でPDFに書き出す機能を作った。

2カラム構成だ。左カラム(広め)に「今週の人」と本文・グルメ・新店情報。右カラム(60mm)に「今週のイベント」とニュース・お知らせ。

カラーパレットは紺(#1a3a5c)・ゴールド(#b8801a)・赤(#aa2218)。フォントは見出しにNoto Serif JP・本文にNoto Sans JP。地域情報誌らしい、落ち着いた雑誌感を目指した。

縦書きも検討した。日本語の紙媒体らしさが出るからだ。しかし断念した。ブラウザによってページ分割の動作が変わる。印刷時に安定しない。横書き2カラムの方が、実際に印刷して配れる品質になった。


号編成

記事を「号」にまとめる管理機能を実装した。

管理者は承認済み記事を号に追加して、セクションを割り当てる。「今週の人」「グルメ」「イベント」など。号ができたらMagazine.htmlで表示して印刷する。

スプレッドシートの構造はシンプルだ。Issuesシートが号ごとの情報(タイトル・ステータス・公開日)を持つ。IssueArticlesシートが号と記事の紐付け(セクション・表示順)を持つ。


LINE読者配信

LINE公式アカウントを友だち追加したユーザーが読者として登録される。

function handleFollow(userId) {
  registerReader(userId);
  pushMessage(userId, 'LINE友だち追加ありがとうございます!地域人の最新情報をお届けします。');
}

号を公開するタイミングで、全ACTIVEリーダーに記事タイトル一覧をLINEで送る。各記事の詳細はWebサイトのURLで誘導する。

大量のpushMessageを連続で送るとLINEのAPIレートリミットに引っかかる。50ミリ秒のスリープを1送信ごとに入れた。


S3ホスティングとLIFF

Register.html(事業者登録フォーム)はS3+CloudFrontでホスティングしている。

GASのHTMLサービスで配信する方法もある。しかしLIFFのエンドポイントURLとしてgoogleusercontent.comドメインのURLを使うと、LINEアプリ内での表示に問題が出ることがある。S3+CloudFrontの独自ドメインURLの方が安定して動作する。

S3からGASを呼び出すとき、google.script.run(GASのクライアントライブラリ)は使えない。S3からGASのWebアプリURLに直接fetch()でPOSTするように実装を変えた。

fetch('https://script.google.com/macros/s/.../exec', {
  method: 'POST',
  body: JSON.stringify({ action: 'register', ...formData }),
  headers: { 'Content-Type': 'application/json' },
});

ホスティング先が変わると、GASとの通信方法を変える必要がある。前提を確認してから実装する順番が重要だ。


このシステムが解決したもの

コンテンツを人が取材して書く場合、1記事あたり数時間かかる。AiInterviewでは事業者がLINEで答えるだけで素材が集まり、Geminiが記事として整形する。人間の作業は確認と修正だけになる。

地域メディアが続くかどうかは「コンテンツを供給し続けられるか」にかかっている。供給コストを下げることが、持続可能なローカルメディアを作る技術的な解答だと思っている。


*シンプルシステム株式会社 代表 伊藤勝彦*