0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

既定値が、取得できないはずのサイトを出典として名乗っていた

0
Posted at

背景

私が運用している「アニメ 海外の反応」まとめサイトには、話数ごとのページを作る雛形生成スクリプトがあります。
node scaffold-episode.js --slug <作品> --num 9 と打つと、作品の JSON に第9話の空枠を1つ足すだけの、ごく短いスクリプトです。

そのスクリプトが、取得できないことが実測で分かっているサイトを出典として書き込んでいた、というのに今朝気づきました。
自分で書いたコードなので言い訳のしようがないのですが、気づくまでにだいぶ掛かりました…。

何が書き込まれていたか

生成される話数オブジェクトはこういう形です。threadNote は「この反応をどこから取ったか」を読者に見せるための欄で、threadUrl は出典スレッドへのリンクです。

const ep = {
  num,
  title: args.title || `第${num}話`,
  aired: args.aired || todayJST(),
  threadNote:
    args["thread-note"] ||
    "出典: reddit r/anime 該当話ディスカッション + YouTube 海外リアクション動画コメント(投稿者名は匿名)。",
  threadUrl: args["thread-url"] || threadSearchUrl(a.titleEn || slug, num),
  reactions: [],
};

args はコマンドライン引数で、|| の右側が既定値です。
つまり --thread-note を渡さないと、上の文字列がそのまま JSON に入ります。

問題は、Reddit も YouTube のコメントも、この環境からは取得できないことが実測で確定していた点です。
Reddit は www / old / api / .rss のどれも 403 か 429 を返し、YouTube のコメントは動的描画で本文が取れません。
反応の取得元は数か月前に MyAnimeList へ切り替え済みでした。

つまりこのスクリプトは、もう使っていない2つのサイトを、毎回「出典」として名乗っていたわけです。

なぜ表に出なかったのか

実害が出ていなかったのには理由がありました。
実際に反応を取ってくる別のコマンドが、取得成功時に threadNote と threadUrl を上書きするからです。

threadUrl:  https://myanimelist.net/forum/?topicid=2278019
threadNote: MyAnimeList のエピソード討論スレッドに実在する投稿を 2026-09-08 に取得し、12件を要約せず和訳した。

上書きが走った話数は正しい出典になります。
逆に言えば、上書きが走らなかった話数だけが嘘の出典を持ったまま公開される構造でした。
生成はしたが反応が取れなかった、という回はふつうに起きます。

これは静かな壊れ方です。エラーも警告も出ず、ページも普通に表示されます。

直し方は「もっともらしい既定値」をやめること

最初は既定値を「MyAnimeList のスレッド」に差し替えようかと思いました。
でもそれだと同じ形の問題が残ります。取りに行っていないのに出典を名乗るという構造は変わらないからです。

なので、空にしました。

// 出典は取得できたときにだけ名乗る。既定は空で、反応の取得側が埋める。
threadNote: args["thread-note"] || "",
threadUrl: args["thread-url"] || null,

ページを組み立てる側は threadNote が空なら出典の段落自体を出しません。
threadUrl が null のときも同様です。
結果として「取れていない話数は、出典を名乗らない」という表示になります。

threadSearchUrl() という Reddit の検索 URL を組み立てる関数も、呼び出し元が無くなったので関数ごと消しました。

テストは「既定値に何が入っていないか」を見る

直したあとに書いたテストが、たぶんこの件でいちばん実用的な部分です。
threadNote === "" を確認するだけだと、また別の文字列が既定値に入ったときに素通りします。
なので、生成した JSON 全体を文字列にして、禁止したい語が現れないことを見ています。

test("取得できないソースを既定値として書き込まない", () => {
  const { data } = runScaffold();           // 空の作品JSONに1話ぶん生成させる
  const json = JSON.stringify(data);
  assert.ok(!/reddit/i.test(json), "Reddit を出典として書いてはいけない");
  assert.ok(!/youtube/i.test(json), "YouTube を出典として書いてはいけない");
});

runScaffold() は一時ディレクトリに空の作品 JSON を作り、スクリプトを子プロセスで動かして、書き込まれたファイルを読み返すだけのヘルパです。
実際のコマンドをそのまま叩いているので、既定値の変更はここで必ず引っかかります。

before は /reddit/i が2箇所(threadNote と threadUrl)に当たって落ち、after は0件で通ります。

まとめではなく

サイトはこれです。

雛形生成ツールの既定値は、埋めるのが親切だと思って書いていました。
でも出典のように「事実を主張する欄」については、空欄のほうが親切です。
空欄は読者に何も伝えませんが、もっともらしい既定値は間違ったことを伝えるからです。


本記事はAI補助で執筆した、個人開発の紹介記事です。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?