AP社 SNS・クチコミ連携 — 全体構成図
株式会社Amuseum Parks/アートアクアリウム美術館 GINZA
時点 2026-09-16(TikTok開通後)
典拠 10_SNS連携/README.md ・20_デプロイ/README.md ・99_預かり/04_繋ぎ込みガイド.md ・gas/Code.gs ・run.sh ・lib/record.py / 件数は 2026-09-16 に台帳・Graph API・日次収集から数え直した実測
/ READMEに書かれていない箇所は 未確認 と明記しています
① ストッパー:いまは出さない(納品は自動送信まで)
納品するシステムは、承認済みの返信を自動で出すところまで。 出し方はAPIではなくCDPでのブラウザ操作
(API投稿はアカウント停止のリスクがあり、止まると拾う側まで一緒に死ぬため)。
いまは出さない。 reply/post.py の _send() は必ず例外を投げ、
ALLOW_SEND=False は環境変数から変えられない。動くのは到達確認だけ。
⚠️2026-09-17 訂正。 ここには「仮の措置ではない/承認を挟む構成は省略できない」と書いていたが、
一時的なストッパーを恒久的な設計として書いてしまっていた 。返信テンプレート第5章は
「送信前の承認は必須」 であって、送信を作るなとは言っていない。コンソールでの承認を必ず通す形で満たす。
外す条件 :①UI/UXの修正が終わる ②AP社からの明示的な合意の記録 ③自動で出してよい範囲の線引き
(InstagramのDMは受信から24時間の制約があり、超えた分は人が送る扱い)。
② 担当の境界:互いに触らない
小島=SNS側 (/sns/ ・/opt/aquacut-sns )、
空(小林)さん=スタジオ側 (/console/ ・/opt/aquacut ・/opt/aquacut-console )。
空さん側のデプロイは rsync --delete なので、内側に置くと消える(実際に /console/studio.html が消えた事故あり)。
deploy_sns.sh は送り先がその2つの内側だったら実行前に止まる。
③ 人が判断する回:下書きを作らない
飼育環境への指摘(型D) とセンシティブ(型G) は自動生成しない。
Dは★1〜2で本文がある回の最大の塊で、定型で返すと炎上を招く。返信には施設側の事実(水質と健康管理の頻度、専門家の監修、個体の休養運用、飼育方針の文書、公開可否)が要る。
SNS側も施設の判断(型S) は下書きを作らない。
先に押さえること — 媒体ごとに性質が違う。件数だけで比べない
2026-09-16 に同じ物差しで数え直した数字。
典拠は Google=data/google_reviews.jsonl /Instagram=Graph API の comments_count 全件(投稿1,234本・2アカウント)/TikTok=日次収集の data/tt_organic/video_list_clean.json (動画123本・9/15 08:07取得)/X=data/x_mentions.jsonl 。
※TikTokの90日は「動画の投稿日 が90日以内」の14本ぶんだけ。古い動画に後から付くコメントは数えられていない(実際より少なく出る)。
※Xの全期間は取り込みを始めた8/14以降しか無い。メンションタブが遡れる範囲までしか見えない。
受信箱に出している行 ── 167件 = Google 96 / Instagram 24 / TikTok 38 / X 9
上の表とは一致しない。 受信箱は「個別に返信するもの」だけを載せる作業台で、台帳(全件)とは別物
Instagramの台帳は296件あるが、うち272件は7/31の浴衣応募 。_inbox:false を付けて受信箱から外し、台帳には残している(感想が混ざっているため捨てない)
Googleの台帳5,663件のうち5,567件は8/25時点の種 (seeded:true )。既存の「MEO口コミ返信 オペレーション管理表」に載っているのでスプシには出さない
TikTokの台帳は413件あるが、受信箱に出すのは90日以内かつ未返信の38件 。古い回と返信済みは台帳に残して外す
GAS経由で読んだ実数・台帳の非種レコード・data/pushed_keys.txt の3つが一致(2026-09-16)
2026-09-15 訂正。前版の「返信の実務量はほぼGoogleクチコミ」は誤りだった。
Instagramを「90日で22件」と書いていたが、これは
--days 90 × --limit-media 60 × キャンペーン除外 という
こちらが勝手に狭めた取得条件の結果 であって、媒体の実態ではない。実際は283件。TikTokも「無い」と書いたが、
440件ある (取り込む仕組みが無いだけ)。測り方を揃えずに結論を出していた。
揃えて測ると Google > Instagram > TikTok ≫ X。 ただし件数の中身は媒体ごとに違うので、総量の大小をそのまま実務量として読まないこと。
Google ── 377件すべてが個別返信の対象。ここは件数=実務量
Instagram ── 総量は多いが大半がキャンペーン応募。個別返信の対象は台帳ベースで7/24〜9/14の24件 (月13件前後)
TikTok ── 440件の中身をまだ読んでいない。返信が要る割合は不明。 取り込んで初めて分かる
X ── 9件すべて返信対象外。他人同士の会話に当アカウントのハンドルが入っているだけ
全体の流れ
拾う Mac常駐 1日1回・手で叩く./run.sh all =4媒体
起動 chrome/launch.sh start
常用プロファイル ~/.dev-tokyo/chrome_profile (kojima@dev-tokyo.jp)を ~/.dev-aa-sns/chrome_profile へコピーしてから 使う
CDP 9610 でオフスクリーン起動。再ログインは絶対にさせない (別プロファイルで入り直すと2段階認証がAP社に飛ぶ)
GBPの管理権限は kojima@dev-tokyo.jp が持つ。lin@amuseumparks.com では「0件のビジネス」になる(2026-09-10確認)
常時ポーリングはしない。1日1回の手動実行
①a Google クチコミ CDP + Chrome
google/fetch_reviews.py
GBPコンソールを新しい順に読み、既知の鍵に当たったら止める (差分)
鍵は投稿者の uid(u:<uid> )。取れない回だけ名前+本文ハッシュ。相対表記は鍵に使わない
全件は 5,567件・112ページ・604秒(8/25実績)→ 差分なら 17秒/4ページで新着76件(9/10実績)
①b Instagram コメント Graph API
instagram/fetch_comments.py
System Userトークン(~/.dev-aa-meta/token.txt ・無期限)。Chromeもcookieも要らない ので落ちにくい
cookieリプレイは使わない。429で止まりやすく、日次収集のIGは9日間止まっていた
キャンペーン投稿(1投稿30件超)は既定で見送る。7/31の浴衣応募が1投稿266件あり、混ぜると「返信が要るもの」を探せなくなる
取れないもの(Metaアプリ側の権限が要る)
投稿者名・返信ツリー ── instagram_manage_comments
DM ── instagram_manage_messages 。DMの取り込みは未実装
①c X メンション CDP + Chrome
chrome/launch_x.sh start → x/fetch_mentions.py →(終わったら停止)
DOMを削らず、画面が裏で叩く GraphQL(NotificationsTimeline)の応答を横取り する。DOMはXの改修で壊れるが応答の形は変わりにくい
解析は x/parse.py に分けてあり、保存した応答で Chrome なしに確認できる
Googleとは別のChromeが要る(プロファイルが違う)。~/.dev-aa-x/chrome_profile (Profile 12)をコピー、CDP 9612
但し書き:メンションタブは「自分への返信」ではない
実測9件のうち @artaquarium_jp への直接のリプライは0件。 残りは他人同士の会話にこちらのハンドルが入っているだけで、割り込んで返信するのは不自然
そのまま流すと受信箱が返信すべきでないもので埋まるため、種別を判定して直接リプライだけを未着手 にする
裏取りで to:artaquarium_jp を検索したところ、他人からの返信は2025-01〜2026-01の4件だけ (最新でも8か月前)。「存在しない」のではなく「稀」。メンションタブが直近を拾う以上、0件で妥当
①d TikTok コメント CDP + Chrome
chrome/launch_tt.sh start → tiktok/fetch_comments.py →(終わったら停止)
APIを2つ使い分ける。動画一覧は TikTok Studio (creator/manage/item_list )、コメントは公開API (api/comment/list/ )
Studioのコメント管理画面(tiktokstudio/api/web/commentsV2 )は、期間を2022年まで広げても直近60日・27件しか返らない (2026-09-16確認)。画面の保持期間の制限で全期間は取れない
公開APIはクエリを増やすと空レスポンスになる 。aweme_id/count/cursor/aid だけで叩く
Xと同じ ~/.dev-aa-x/chrome_profile (Profile 12)をコピー、CDP 9613
返信ツリーの中も辿る。自社が既に返している回は作業対象から外し、第三者の返信は1件として拾う
但し書き:TikTokは日本語が半分しかない
413件のうち日本語226件・それ以外187件 (英語37・フランス語3・スペイン語2ほか)。Instagramには無かった前提
飼育環境への批判が英語で来る。 「Animal abuse museum」「Esto no es arte ! Esto es MALTRATO」「Too many fish in small tanks」。日本語の辞書では1語も引っかからなかったので、多言語のカテゴリ辞書を lib/classify.py に足した
ハンドルは @artaquarium_tm_official 。Xの @artaquarium_jp とは別物
件数は動画一覧の comment_count (440)と一致しない(取得413)。
折りたたみコメント(TikTokがスパム判定したもの)は返らず、fold_comment_cursor を付けても増えない。
自社の返信ぶんも comment_count に含まれる。削除済みが残っている動画もある
(7476392197907025170 は一覧4件に対し公開APIの total が0)。取れない分があること自体は隠さない。
追記のみ(上書きしない)
台帳 Mac ローカル
data/*.jsonl 追記専用 / 一覧の唯一の定義は lib/record.py の LEDGERS
②〜④(push_all.py ・draft.py ・push_drafts.py )はすべてここを見る。媒体を足すときの追加点は1箇所
google_reviews.jsonl 8/25時点の全件5,567件を種 として投入済み(seeded:true )。種はスプシに出さない ── 既存の「MEO口コミ返信 オペレーション管理表」に載っているため
x_mentions.jsonl 同上。対象外にしたものもUGC候補として備考から辿れる
data/pushed_keys.txt で二度貼りを防ぐ
ネガポジ・カテゴリ・区分をつける
判定 と下書き ルールベース
判定 lib/classify.py
ネガポジ(K列)/カテゴリ(L列)/区分=返信型(M列)を決める
③ 下書き reply/draft.py ・ reply/draft_sns.py → data/drafts.jsonl
典拠は aa-meo/返信テンプレート_v1.md (2026-08-24、直近350件の実測)
書き出しは3通り用意し、鍵のハッシュで回す(同じ2行を並べると「定型の連投」になる)
SNSは1〜2文。クチコミの3〜5文だとコメント欄で浮く
② push_all.py が媒体をまたいで未送出分を最終行の次へ追記/④ reply/push_drafts.py が K:M・O・P・X列を更新(確定文がある行と人が決めた状態の行は行単位で 現状を残す。現状の読み取りはGAS経由の1回のHTTPで、変化した行が無ければ何も書かない )
受信箱 唯一の真実
Googleスプレッドシート「AP社 SNS・クチコミ 受信箱(自動連携)」/ タブ 受信箱 24列
人もここを直接触れる。 スプシを直接直しても同じ列を書くので壊れない
APIではなくブラウザ操作 (pbcopy+Cmd+V)で触っている ── この案件にサービスアカウントが無く、作るにはGCP側の作業と権限付与が要るため。API に差し替えられるよう lib/sheet.py に閉じ込めてある
原則、追記しかしない。 既存行の上に貼ると人が入れた確定文・確認者・送信日が消える。空文字を書くと値も書式も消える
Mac側とGAS側で書く列を分けてある (同じセルを両方が触ると壊れるため)
GET で読む/POST で書く(Content-Type: text/plain 。GASはOPTIONSを扱えないのでプリフライトを起こさない)
窓口 GAS Web App
「AP社 受信箱 窓口」 gas/Code.gs (スプシに紐づいたプロジェクト)
doGet ── 読む
受信箱の全行を JSON で返す。投稿日の新しい順
読みにもトークンを要求する (AI下書き・確定文・内部区分が入るため)
doPost ── 書く
書いてよい列は 状態 / 確定文 / 確認者 / 確認日時 の4つだけ 。拾ってきた列は絶対に書かない
状態を動かすと確認者・確認日時が自動で入る
知らない状態は skip 。書けない列は無視
変更は履歴シートに前の値つき で残る(自動作成)
書き込みは LockService で直列化。同時編集は後勝ち
アクセスは「全員」。 (2026-09-12変更)自分のみ や Googleアカウントを持つ全員 だと、
fetch がGoogleのログインリダイレクトを跨ぐことになり、別オリジンからは CORS で読めない (実際に踏んだ)。
公開範囲を広げるかわりに守りはトークン で、素の curl(Googleセッション無し)でトークン無しなら {"ok":false,"error":"token"} が返ることを確認済み。
トークンは ~/.dev-aa-sns/inbox_token (600)にあり、それを載せる config.js は Basic認証の内側にしか置かない・gitに置かない 。⚠️2026-09-17 差し替えた。 同じトークンが public_old/ ・引き渡し用の控え・その ZIP にも入っており、ZIPは空さんに渡していた。GASの rotate_token() を実行して差し替え、旧トークンが弾かれることを確認したうえで、控えとZIPから取り除いた。あわせて doGet/doPost の シート指定(sheet 引数)を廃止 した(以前はトークンさえあれば履歴シートも読めた)。
「全員」にするとURLの形も /a/macros/<domain>/s/ → /macros/s/ に変わる。
静的HTMLから fetch(同一ドメイン・Basic認証の内側)
承認 人がやる
管理画面 https://aquacut-studio.duckdns.org/sns/inbox.html 本番稼働中
2026-09-15 に /sns/ は畳んだ。 正本は 20_デプロイ/console/ → VPSの /opt/aquacut-console/ (/console/ )。/sns/ は /console/inbox.html への案内ページだけが残っている。20_デプロイ/public_old/ は当時の控え
2026-09-12 開通。 空さんが snsdeploy を作成 → デプロイ → Caddyfile 5行追加 → 開通(バックアップ /etc/caddy/Caddyfile.bak_20260912_233320 )。@nocache に /sns/*.html /sns/assets/* も追加済み
静的HTML。 サーバープロセスは持たない。Caddy の Basic認証がブロック全体に効くので、認証もHTTPSも作らなくてよい
⚠️2026-09-17 追記:画面の作り物を落とした。 履歴が全件に固定日時「2026-09-05 09:22 承認(AP社 広報)」を出していたので、GASに履歴の取り出し(action=history )を足して実物を引くようにした。外国語コメントを「原文」と「日本語に自動翻訳」の2段で出していたが、訳文が付くのはGoogleだけで、TikTok等は同じ文が2度並んでいた。D・L・Nは下書きを作らない区分なのに返信文を書く欄が無かった ので、空欄と承認導線を足した。
画面がやるのは「この文で承認する」まで 。送らないので「承認して送信する」とは書かない。⚠️2026-09-17 訂正: ここに「状態を『送信済』にもしない」と書いていたが誤りだった。実際の画面には「次の送信バッチで自動送信されます」「いま送信する」「送信済みです」が残っていて、押すと送っていないのに状態が『送信済』になった。 文言を「この文を各媒体の画面から投稿してください」「投稿したので記録する」「投稿したと記録されています」に直し、人が投稿した事実を記録する 意味に変えた(2026-09-17)
検証は「200が返るか」で見ない。 変更前から /sns/inbox.html は200を返していた(既存の reverse_proxy が全部拾い、Hono の SPA フォールバックが index.html を返すため)。
配信物と実体の md5 突合+存在しないパスの404 で見る。既存4経路は変更前と同じ200であることも確認済み。
人に回る回(下書きを作らない・作っても骨組みだけ)
D 飼育環境への指摘 (★1〜2)→ 保留。返信テンプレート第4章の5点が埋まるまで作らない
G センシティブ → エスカレーション。定型は火に油
E 価格や期待 (★1〜2)→ 要確認。{事実の補足} には確認できた事実しか書けないので、推測で埋めずプレースホルダで人に渡す
SNS側は S 施設の判断 (撮影許可・取材・返金)と G を作らない。Q 問い合わせ は骨組みだけ(営業時間も料金もこちらは持っていない)
⑤ 状態=承認 かつ 確定文あり の行だけ
出す Mac側
reply/post.py ── 到達確認まで。送信は実装していない
スプシから「状態=承認 かつ 確定文あり」の行を読む
GBPコンソールを新しい順にたどり、ID の uid でその回を見つける
返信欄(返信ボタン)に到達できるかだけ確かめる。クリックも入力もしない
結果を V列(送信結果)に dryrun_ok として記録する
_send() は必ず例外を投げる。ALLOW_SEND は環境変数から変えられない
受信箱シートの24列 — 誰が書くか
APコンソール assets/data.js のレコード(id / ch / kind / date / star / lang / author / body / orig / cats / senti / kubun / state / draft)を包含する形。
A ID 拾う
B 媒体 拾う
C 種別 拾う
D 投稿日 拾う
E 取得日時 拾う
F 評価 拾う
G 言語 拾う
H 投稿者 拾う
I 本文 拾う
J 原文 拾う
K ネガポジ AI
L カテゴリ AI
M 区分 AI
N 既存返信 拾う
O 状態 AI→人
P AI下書き AI
Q 確定文 人
R 確認者 人
S 確認日時 人
T 送信予定 人/自動
U 送信日時 出す
V 送信結果 出す
W permalink 拾う
X 備考 拾う+AI
拾う=Mac側の取り込みが書く
AI=判定・下書きが書く
人=管理画面(GAS経由)またはスプシ直接
出す=post.py が書く(いまは dryrun の記録のみ)
状態の値 (APコンソール側に合わせる):未着手 / 下書き済 / 確認中 / 承認 / 送信済 / 対象外 ── 本文なしの星だけの回は自動で「対象外」。
GAS側は加えて 要確認 / 保留 / エスカレーション / 反映確認 も受け付ける。
未確認 W permalink は列だけあり、GBPコンソールから個別URLが取れるかは未確認(READMEの「まだ無いもの」)。
X 備考 は拾った情報に加えて、判定根拠と返信型を push_drafts.py が書く。
返信型 — 下書きを作るもの/人に回すもの
SNSコメント(星が無いので別の軸)
D・L・N は 2026-09-16 に足した。 それまでは判定に本文の言語もカテゴリもネガポジも渡しておらず、
当てはまらない回はすべて「A 好意的」に落ちていた。TikTokの実データを通したら
「Animal abuse museum」「Too many fish in small tanks」「展示中は糞をしないように餌を一切与えず…」に
「ありがとうございます。またのお越しをお待ちしております。」が作られた。
Instagramだけを見ていたときは好意的なコメントしか無く、穴に気づけなかった。
下書きを作らない型が増えたのは後退ではなく、作ってはいけない回を作らなくなったということ。
Instagram の受信箱24件の内訳(2026-09-16):来館後の報告10 / 来館前の予告7 / それ以外の好意6 / ハッシュタグのみ1。
TikTok の受信箱38件は様子が違う :M13(絵文字だけ)/ L10(日本語以外) / A5 / D5(飼育環境) / N3 / Q2。
20件が人に回る。 自動で下書きが付くのは5件だけ。
X は上の但し書きのとおり、返信型に載る前に種別判定で落ちる。
受信箱の現状 (2026-09-16・167件。GAS経由で読んだスプシの実数):
下書き済101 / 保留16 / 要確認21 / エスカレーション6 / 対象外23 = A55 B36 C8 D21 E4 F2 G1 L10 M23 N5 Q2。
27件が人の手に回る (Google 5 / Instagram 2 / TikTok 20)。
下書きを作ってはいけない区分(D・G・L・N・S)に下書きが入っている行は0件。
担当の境界 — 同じVPSの上で分けている
https://aquacut-studio.duckdns.org/ (Caddy・Basic認証がブロック全体に効く)
/console/* 既存のコンソール(静的HTML)
/ 既存の編集画面(React)
/api/* 既存のサーバー(Hono・127.0.0.1:3210)
/files/* 素材・動画の配信
/sns/* ← SNS受信箱(静的HTML)。ここだけが小島の担当
小島 ── SNS側
URL:/sns/* 置き場所:/opt/aquacut-sns/
反映は deploy_sns.sh (rsync)。送り先が空さん側の2つの内側なら実行前に止まる
sudo は使わない。 /opt/aquacut-sns を専用ユーザー snsdeploy の所有にして、そのユーザーで rsync を流す。Caddyは別ユーザーなので755で読める。この鍵でできることは「/opt/aquacut-sns 配下の書き換え」だけ (当初のubuntu+sudoは実質rootを渡すことになると空さん側から指摘を受けて変更)
Caddyfile への追加は5行。既存の reverse_proxy 127.0.0.1:3210 より前 に置く(後ろだと /sns/* がHonoに流れて404)。basic_auth は既存が効くので書かない(二重にしない)
空(小林)さん ── AQUACUTスタジオ側
動画の制作 → 承認 → 公開 。Delivery.state は draft → queued → rendering → preview → approved → published
URL:/console/* ・/api/* ・/ ・/files/*
置き場所:/opt/aquacut/ (deploy.sh が --delete で同期)//opt/aquacut-console/ (console_src 反映が --delete で同期)
こちらは触らない。 Honoサーバー・systemd・既存の basic_auth / reverse_proxy、および @nocache の既存分(/console/* ・/ ・/index.html )。/sns/*.html /sns/assets/* は2026-09-12にこちらの依頼で追加してもらった分
両者に共通の触ってはいけないもの (繋ぎ込みガイド §5):Caddyfile の header_up X-Auth-Email の行(外すと誰でも管理者になれる)/
/opt/aquacut/ の中に自分のファイルを置くこと/アプリを 0.0.0.0 で待ち受けさせること(Caddyを迂回されて認証が無意味になる)/
/var/lib/aquacut/ の直接書き換え(承認記録・楽観ロックが壊れる。必ずAPIを通す)/
pkill -f "tsx watch" でのプロセス停止(同じコマンドラインの別アプリを巻き添えにする)。
繋がっていないこと :SNS受信箱と AQUACUTスタジオの間に、いまデータのやり取りは無い。両者は同じドメインの別パスに同居しているだけ。
繋ぐなら GET /api/console/deliveries で approved を拾い、投稿が通ったあとに POST /api/console/deliveries/:id/publish を叩くのが素直な形(繋ぎ込みガイド §3)。
ただし audio:'manual' の回(Instagramのトレンド音源)は仕様上 API から投稿できない。動画の承認→投稿は server/pick.py が拾う側だけある段階。
残っているもの
業務フロー図 | 資料の一覧へ
根拠:10_SNS連携/README.md /20_デプロイ/README.md /99_預かり/04_繋ぎ込みガイド.md /10_SNS連携/gas/Code.gs ・run.sh ・lib/record.py ・reply/post.py 。
数字はREADMEに記録された実測値をそのまま引いている。