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]>
101 lines
3.9 KiB
TypeScript
101 lines
3.9 KiB
TypeScript
import type { Command } from 'commander';
|
|
import type { CliContext } from '../cli/shared.js';
|
|
import { mentionsQueryFromUserOption, normalizeHandle } from '../lib/normalize-handle.js';
|
|
import { TwitterClient } from '../lib/twitter-client.js';
|
|
|
|
export function registerSearchCommands(program: Command, ctx: CliContext): void {
|
|
program
|
|
.command('search')
|
|
.description('Search for tweets')
|
|
.argument('<query>', 'Search query (e.g., "@clawdbot" or "from:clawdbot")')
|
|
.option('-n, --count <number>', 'Number of tweets to fetch', '10')
|
|
.option('--json', 'Output as JSON')
|
|
.option('--json-full', 'Output as JSON with full raw API response in _raw field')
|
|
.action(async (query: string, cmdOpts: { count?: string; json?: boolean; jsonFull?: boolean }) => {
|
|
const opts = program.opts();
|
|
const timeoutMs = ctx.resolveTimeoutFromOptions(opts);
|
|
const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts);
|
|
const count = Number.parseInt(cmdOpts.count || '10', 10);
|
|
|
|
const { cookies, warnings } = await ctx.resolveCredentialsFromOptions(opts);
|
|
|
|
for (const warning of warnings) {
|
|
console.error(`${ctx.p('warn')}${warning}`);
|
|
}
|
|
|
|
if (!cookies.authToken || !cookies.ct0) {
|
|
console.error(`${ctx.p('err')}Missing required credentials`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const client = new TwitterClient({ cookies, timeoutMs, quoteDepth });
|
|
const includeRaw = cmdOpts.jsonFull ?? false;
|
|
const result = await client.search(query, count, { includeRaw });
|
|
|
|
if (result.success && result.tweets) {
|
|
ctx.printTweets(result.tweets, { json: cmdOpts.json || cmdOpts.jsonFull, emptyMessage: 'No tweets found.' });
|
|
} else {
|
|
console.error(`${ctx.p('err')}Search failed: ${result.error}`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
program
|
|
.command('mentions')
|
|
.description('Find tweets mentioning a user (defaults to current user)')
|
|
.option('-u, --user <handle>', 'User handle (e.g. @steipete)')
|
|
.option('-n, --count <number>', 'Number of tweets to fetch', '10')
|
|
.option('--json', 'Output as JSON')
|
|
.option('--json-full', 'Output as JSON with full raw API response in _raw field')
|
|
.action(async (cmdOpts: { user?: string; count?: string; json?: boolean; jsonFull?: boolean }) => {
|
|
const opts = program.opts();
|
|
const timeoutMs = ctx.resolveTimeoutFromOptions(opts);
|
|
const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts);
|
|
const count = Number.parseInt(cmdOpts.count || '10', 10);
|
|
|
|
const fromUserOpt = mentionsQueryFromUserOption(cmdOpts.user);
|
|
if (fromUserOpt.error) {
|
|
console.error(`${ctx.p('err')}${fromUserOpt.error}`);
|
|
process.exit(2);
|
|
}
|
|
|
|
let query: string | null = fromUserOpt.query;
|
|
|
|
const { cookies, warnings } = await ctx.resolveCredentialsFromOptions(opts);
|
|
|
|
for (const warning of warnings) {
|
|
console.error(`${ctx.p('warn')}${warning}`);
|
|
}
|
|
|
|
if (!cookies.authToken || !cookies.ct0) {
|
|
console.error(`${ctx.p('err')}Missing required credentials`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const client = new TwitterClient({ cookies, timeoutMs, quoteDepth });
|
|
|
|
if (!query) {
|
|
const who = await client.getCurrentUser();
|
|
const handle = normalizeHandle(who.user?.username);
|
|
if (handle) {
|
|
query = `@${handle}`;
|
|
} else {
|
|
console.error(
|
|
`${ctx.p('err')}Could not determine current user (${who.error ?? 'Unknown error'}). Use --user <handle>.`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
const includeRaw = cmdOpts.jsonFull ?? false;
|
|
const result = await client.search(query, count, { includeRaw });
|
|
|
|
if (result.success && result.tweets) {
|
|
ctx.printTweets(result.tweets, { json: cmdOpts.json || cmdOpts.jsonFull, emptyMessage: 'No mentions found.' });
|
|
} else {
|
|
console.error(`${ctx.p('err')}Failed to fetch mentions: ${result.error}`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
}
|