feat: add --json-full flag to include raw API response in _raw field

Add a new --json-full flag to tweet-fetching commands that outputs JSON
with an additional _raw field containing the full GraphQL response.

This provides power users access to additional data not exposed in the
curated TweetData interface (media, entities, hashtags, cards, etc.)
while maintaining backward compatibility with --json.

Supported commands:
- read, replies, thread (TweetDetail API)
- search, mentions (SearchTimeline API)
- bookmarks (Bookmarks API)
- likes (Likes API)

Not supported: following, followers (uses mixed GraphQL/REST fallback
with different response structures)

Implementation:
- Add _raw?: GraphqlTweetResult optional field to TweetData type
- Add includeRaw option to mapTweetResult() and parseTweetsFromInstructions()
- Add TweetFetchOptions, SearchFetchOptions, TimelineFetchOptions interfaces
- Update client methods to accept options parameter
- Add --json-full flag to CLI commands
- Update help text to document the new flag
- Add comprehensive test coverage (16 tests)

Co-authored-by: Christian Catalan <[email protected]>
This commit is contained in:
cc-vps
2026-01-03 06:54:36 +01:00
committed by Peter Steinberger
co-authored by Christian Catalan
parent 42abae9c15
commit 3815ebfa03
13 changed files with 658 additions and 48 deletions
+18 -9
View File
@@ -9,7 +9,8 @@ export function registerReadCommands(program: Command, ctx: CliContext): void {
.description('Read/fetch a tweet by ID or URL')
.argument('<tweet-id-or-url>', 'Tweet ID or URL to read')
.option('--json', 'Output as JSON')
.action(async (tweetIdOrUrl: string, cmdOpts: { json?: boolean }) => {
.option('--json-full', 'Output as JSON with full raw API response in _raw field')
.action(async (tweetIdOrUrl: string, cmdOpts: { json?: boolean; jsonFull?: boolean }) => {
const opts = program.opts();
const timeoutMs = ctx.resolveTimeoutFromOptions(opts);
const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts);
@@ -28,10 +29,11 @@ export function registerReadCommands(program: Command, ctx: CliContext): void {
}
const client = new TwitterClient({ cookies, timeoutMs, quoteDepth });
const result = await client.getTweet(tweetId);
const includeRaw = cmdOpts.jsonFull ?? false;
const result = await client.getTweet(tweetId, { includeRaw });
if (result.success && result.tweet) {
if (cmdOpts.json) {
if (cmdOpts.json || cmdOpts.jsonFull) {
console.log(JSON.stringify(result.tweet, null, 2));
} else {
console.log(`@${result.tweet.author.username} (${result.tweet.author.name}):`);
@@ -52,7 +54,8 @@ export function registerReadCommands(program: Command, ctx: CliContext): void {
.description('List replies to a tweet (by ID or URL)')
.argument('<tweet-id-or-url>', 'Tweet ID or URL')
.option('--json', 'Output as JSON')
.action(async (tweetIdOrUrl: string, cmdOpts: { json?: boolean }) => {
.option('--json-full', 'Output as JSON with full raw API response in _raw field')
.action(async (tweetIdOrUrl: string, cmdOpts: { json?: boolean; jsonFull?: boolean }) => {
const opts = program.opts();
const timeoutMs = ctx.resolveTimeoutFromOptions(opts);
const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts);
@@ -70,10 +73,11 @@ export function registerReadCommands(program: Command, ctx: CliContext): void {
}
const client = new TwitterClient({ cookies, timeoutMs, quoteDepth });
const result = await client.getReplies(tweetId);
const includeRaw = cmdOpts.jsonFull ?? false;
const result = await client.getReplies(tweetId, { includeRaw });
if (result.success && result.tweets) {
ctx.printTweets(result.tweets, { json: cmdOpts.json, emptyMessage: 'No replies found.' });
ctx.printTweets(result.tweets, { json: cmdOpts.json || cmdOpts.jsonFull, emptyMessage: 'No replies found.' });
} else {
console.error(`${ctx.p('err')}Failed to fetch replies: ${result.error}`);
process.exit(1);
@@ -85,7 +89,8 @@ export function registerReadCommands(program: Command, ctx: CliContext): void {
.description('Show the full conversation thread containing the tweet')
.argument('<tweet-id-or-url>', 'Tweet ID or URL')
.option('--json', 'Output as JSON')
.action(async (tweetIdOrUrl: string, cmdOpts: { json?: boolean }) => {
.option('--json-full', 'Output as JSON with full raw API response in _raw field')
.action(async (tweetIdOrUrl: string, cmdOpts: { json?: boolean; jsonFull?: boolean }) => {
const opts = program.opts();
const timeoutMs = ctx.resolveTimeoutFromOptions(opts);
const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts);
@@ -103,10 +108,14 @@ export function registerReadCommands(program: Command, ctx: CliContext): void {
}
const client = new TwitterClient({ cookies, timeoutMs, quoteDepth });
const result = await client.getThread(tweetId);
const includeRaw = cmdOpts.jsonFull ?? false;
const result = await client.getThread(tweetId, { includeRaw });
if (result.success && result.tweets) {
ctx.printTweets(result.tweets, { json: cmdOpts.json, emptyMessage: 'No thread tweets found.' });
ctx.printTweets(result.tweets, {
json: cmdOpts.json || cmdOpts.jsonFull,
emptyMessage: 'No thread tweets found.',
});
} else {
console.error(`${ctx.p('err')}Failed to fetch thread: ${result.error}`);
process.exit(1);