From 0b730405a6bccf13f2256c5bcfa4a4bfc4f974a2 Mon Sep 17 00:00:00 2001 From: caadiq Date: Sun, 7 Jun 2026 09:30:59 +0900 Subject: [PATCH] =?UTF-8?q?fix(x-bot):=20=EB=A7=8C=EB=A3=8C=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=ED=8A=B8=EC=9C=84=ED=84=B0=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A5=BC=20=EC=9B=90=EB=B3=B8=20OG?= =?UTF-8?q?=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nitter가 주는 pbs.twimg.com/card_img 이미지는 시간이 지나면 404로 만료됨. resolveCard에서 카드 이미지가 없거나 해당 만료성 URL이면 원본 URL의 OG 이미지(i.ytimg 등 안정적)로 대체. Co-Authored-By: Claude Opus 4.7 --- backend/src/services/x/index.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/backend/src/services/x/index.js b/backend/src/services/x/index.js index c50b637..32c65cc 100644 --- a/backend/src/services/x/index.js +++ b/backend/src/services/x/index.js @@ -59,22 +59,27 @@ async function xBotPlugin(fastify, opts) { */ async function resolveCard(tweet) { const nitter = tweet.card && (tweet.card.title || tweet.card.image) ? tweet.card : null; - // Nitter 카드에 이미지가 있으면 그대로 사용 - if (nitter && nitter.image) return nitter; - // 이미지가 없으면 OG로 보강 (YouTube 등 Nitter가 카드 이미지를 안 주는 경우 복구) - const url = (nitter && nitter.url) || extractFirstUrl(tweet.text); - if (url) { - try { - const og = await fetchOgCard(url); - if (og) { - // Nitter 카드가 있으면 제목/설명은 유지하되 OG 이미지로 보강 - return nitter - ? { ...nitter, image: og.image || null, description: nitter.description || og.description } - : og; + // pbs.twimg.com/card_img 이미지는 시간이 지나면 만료(404)되므로 안정적인 원본 OG 이미지로 대체. + // 이미지가 없거나 만료성 트위터 카드 이미지면 OG 보강 시도. + const isEphemeral = nitter?.image && /pbs\.twimg\.com\/card_img\//.test(nitter.image); + const needsImage = !nitter || !nitter.image || isEphemeral; + + if (needsImage) { + const url = (nitter && nitter.url) || extractFirstUrl(tweet.text); + if (url) { + try { + const og = await fetchOgCard(url); + if (og && og.image) { + return nitter + ? { ...nitter, image: og.image, description: nitter.description || og.description } + : og; + } + // OG 이미지가 없고 Nitter 카드도 없으면 OG 카드라도 반환 + if (og && !nitter) return og; + } catch { + // noop } - } catch { - // noop } } return nitter;