| name | get-tweet |
| description | Fetch a public X/Twitter post as JSON via the get-tweet CLI. Use when the user shares an X/Twitter post URL or tweet ID and needs the post body, author, media (photo/video), quoted/parent tweets, or link-card metadata. |
get-tweet
get-tweet CLI で X (Twitter) の公開ポストを Syndication API から JSON 取得する。
いつ使うか
- ユーザーが X/Twitter の post URL (
https://x.com/<user>/status/<id> など) または数値のツイート ID を提示したとき
- 本文・投稿者・添付メディア (画像/動画)・引用元/返信元・リンクカード・投稿日時を構造化データとして扱いたいとき
基本コマンド
get-tweet <URL or tweet ID>
get-tweet <URL or tweet ID> --pretty
実行方針 (重要)
- まず素で叩く。パイプで
jq を挟まない。
- スキーマを確認してから
jq で必要フィールドだけ抜く。 コンテキスト節約は 2 手目。
- jq が失敗したら、パイプをやめてフル JSON を一度取り、キー構造を見てから jq を組み直す。
レスポンスのスキーマ (投稿種別による差分)
Syndication API は種別ごとに以下のキーが出たり消えたりする。分岐して読むこと。
常にあるフィールド
| path | 意味 |
|---|
.id_str | ツイート ID |
.text | 本文 (t.co 短縮 URL 含む) |
.created_at | ISO8601 |
.lang | 言語 |
.favorite_count | いいね数 |
.conversation_count | 返信数 |
.user.screen_name / .user.name / .user.id_str | 投稿者 |
.user.is_blue_verified | 認証マーク |
.entities.urls[]? | 本文中のリンク (expanded_url, url(=t.co), display_url) |
.entities.media[]? | 本文中のメディア短縮 URL (実データは .mediaDetails 側) |
メディア (画像・動画・GIF)
.mediaDetails[] に出る (画像のみのときは .photos[] にも同じものが入る)。
.mediaDetails[].type は "photo" / "video" / "animated_gif"。
- 動画は さらに トップレベルに
.video があり、variants[] に mp4/HLS の URL が並ぶ。
.mediaDetails[] | select(.type=="video") | .video_info.variants[] にも同じものがある。
- MP4 の最高ビットレートを取りたいなら
select(.content_type=="video/mp4") | sort_by(-.bitrate) | .[0].url。
- 画像 URL は
.mediaDetails[].media_url_https (動画のときはサムネ画像)。
返信
.in_reply_to_status_id_str / .in_reply_to_screen_name / .in_reply_to_user_id_str が付き、親ポストが .parent に埋め込まれる (.parent.text, .parent.user.screen_name など)。
引用リツイート
.quoted_tweet に引用元がフルで入る (.quoted_tweet.text, .quoted_tweet.user.screen_name, .quoted_tweet.id_str …)。引用元自身もメディア/返信を持ちうるので同じスキーマで再帰的に読める。
リンクカード (OGP プレビュー)
.card が付く (.card.name = summary_large_image など)。実データは .card.binding_values.<key>.string_value / .image_value.url に潜っている:
.card.binding_values.title.string_value
.card.binding_values.description.string_value
.card.binding_values.domain.string_value
.card.binding_values.thumbnail_image_original.image_value.url
.card.url (= t.co 短縮 URL)
汎用の正規化 jq
種別差分を吸収して 1 つの形に落とすレシピ。まず全種別で通ることを確認済み (動画のみ / 返信+動画+画像 / 返信+画像複数 / 引用RT / リンクカード)。
{
id: .id_str,
url: ("https://x.com/" + .user.screen_name + "/status/" + .id_str),
created_at, lang, text,
favorite_count,
reply_count: .conversation_count,
author: {
id: .user.id_str,
screen_name: .user.screen_name,
name: .user.name,
verified: (.user.is_blue_verified // false)
},
reply_to: (
if .in_reply_to_status_id_str then {
status_id: .in_reply_to_status_id_str,
screen_name: .in_reply_to_screen_name,
user_id: .in_reply_to_user_id_str,
parent_text: (.parent.text // null)
} else null end
),
quoted: (
if .quoted_tweet then {
id: .quoted_tweet.id_str,
screen_name: .quoted_tweet.user.screen_name,
text: .quoted_tweet.text,
created_at: .quoted_tweet.created_at
} else null end
),
media: [
(.mediaDetails // [])[] | {
type: .type,
thumbnail: .media_url_https,
expanded_url: .expanded_url,
video: (
if .video_info then {
duration_ms: .video_info.duration_millis,
aspect_ratio: .video_info.aspect_ratio,
variants: [
.video_info.variants[]
| select(.content_type == "video/mp4")
| {bitrate, url}
] | sort_by(-(.bitrate // 0))
} else null end
)
}
],
urls: [
(.entities.urls // [])[] | {url: .expanded_url, t_co: .url, display: .display_url}
],
card: (
if .card then {
name: .card.name,
title: (.card.binding_values.title.string_value // null),
description: (.card.binding_values.description.string_value // null),
domain: (.card.binding_values.domain.string_value // null),
image: (.card.binding_values.thumbnail_image_original.image_value.url
// .card.binding_values.summary_photo_image_original.image_value.url
// null),
url: .card.url
} else null end
)
}
使い方:
get-tweet <url> > /tmp/tw.json
jq -f /path/to/normalize.jq /tmp/tw.json
ユースケース別のワンライナー
get-tweet <url> | jq '{text, user: .user.screen_name, created_at}'
get-tweet <url> | jq -r '[.mediaDetails[]?.video_info.variants[]? | select(.content_type=="video/mp4")] | sort_by(-.bitrate) | .[0].url'
get-tweet <url> | jq -r '[.mediaDetails[]? | select(.type=="photo") | .media_url_https][]'
get-tweet <url> | jq '{reply_to: .in_reply_to_status_id_str, parent: .parent.text}'
get-tweet <url> | jq '{quoted_text: .quoted_tweet.text, quoted_user: .quoted_tweet.user.screen_name}'
get-tweet <url> | jq '{title: .card.binding_values.title.string_value, desc: .card.binding_values.description.string_value, image: .card.binding_values.thumbnail_image_original.image_value.url}'
終了コード
| code | 意味 |
|---|
| 0 | 成功 |
| 1 | 引数エラー・URL 解決失敗 |
| 2 | ツイートが存在しない / 削除済み |
| 3 | Syndication API のエラー |
| 4 | 内部エラー |
注意
- 認証不要 (公開 Syndication エンドポイントのみ)。非公開・保護アカウントのポストは取得できない。
- スキーマは Twitter 側の都合で変わりうる。必ず一度フル JSON を確認してから jq を書く。
--pretty は人間確認用。エージェントからパースするだけならデフォルトの raw JSON で良い。