feat: add multi-tab fetching to news command for 5x more headlines
Completely rewrites the news command to fetch from multiple Explore tabs (For You, News, Sports, Entertainment) using GenericTimelineById GraphQL API instead of just the ExplorePage initialTimeline. This delivers 15+ AI-curated headlines instead of 3, a 5x improvement in content discovery. Key Changes: - Add GenericTimelineById query ID support with auto-refresh capability - Implement multi-tab fetching with cross-tab headline deduplication - Add CLI flags for granular tab filtering (--for-you, --news-only, --sports, --entertainment, --trending-only) - Fetch from 4 tabs by default (excludes trending to reduce noise) - Add early stopping optimization when count is reached - Handle tab-level errors gracefully without failing entire request Implementation: - Added TIMELINE_IDS constant with base64 timeline identifiers - Created fetchTimelineTab() method for GenericTimelineById requests - Created parseTimelineTabItems() for new response structure - Removed old parseNewsItems() and extractNewsItemsFromInstructions() - Updated all 7 tests to mock GenericTimelineById responses API Changes: - NewsFetchOptions: Added `tabs?: ExploreTab[]` option - ExploreTab type exported for library consumers - Backward compatible - existing code continues to work Documentation: - Added comprehensive "News & Trending" section with examples - Updated command reference with all new flags - Added library usage examples with tab filtering - Updated JSON schema documentation Testing: - All 268 tests passing - Real-world verified: fetches 15+ AI headlines across tabs - Tab filtering verified: --sports, --entertainment, etc all work Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
committed by
Peter Steinberger
co-authored by
Claude Sonnet 4.5
parent
57d1498f1d
commit
fb092f5cec
+30
-3
@@ -1,7 +1,7 @@
|
||||
import type { Command } from 'commander';
|
||||
import type { CliContext } from '../cli/shared.js';
|
||||
import { TwitterClient } from '../lib/twitter-client.js';
|
||||
import type { NewsItem } from '../lib/twitter-client-news.js';
|
||||
import type { ExploreTab, NewsItem } from '../lib/twitter-client-news.js';
|
||||
|
||||
function formatPostCount(count: number): string {
|
||||
if (count >= 1_000_000) {
|
||||
@@ -69,11 +69,16 @@ export function registerNewsCommand(program: Command, ctx: CliContext): void {
|
||||
program
|
||||
.command('news')
|
||||
.alias('trending')
|
||||
.description("Fetch today's news and trending topics")
|
||||
.description("Fetch AI-curated news and trending topics from Explore tabs")
|
||||
.option('-n, --count <number>', 'Number of items to fetch', '10')
|
||||
.option('--ai-only', 'Show only AI-curated news items')
|
||||
.option('--with-tweets', 'Also fetch related tweets for each news item')
|
||||
.option('--tweets-per-item <number>', 'Number of tweets to fetch per news item (default: 5)', '5')
|
||||
.option('--for-you', 'Fetch only from For You tab')
|
||||
.option('--news-only', 'Fetch only from News tab')
|
||||
.option('--sports', 'Fetch only from Sports tab')
|
||||
.option('--entertainment', 'Fetch only from Entertainment tab')
|
||||
.option('--trending-only', 'Fetch only from Trending tab')
|
||||
.option('--json', 'Output as JSON')
|
||||
.option('--json-full', 'Output as JSON with full raw API response in _raw field')
|
||||
.action(
|
||||
@@ -82,6 +87,11 @@ export function registerNewsCommand(program: Command, ctx: CliContext): void {
|
||||
aiOnly?: boolean;
|
||||
withTweets?: boolean;
|
||||
tweetsPerItem?: string;
|
||||
forYou?: boolean;
|
||||
newsOnly?: boolean;
|
||||
sports?: boolean;
|
||||
entertainment?: boolean;
|
||||
trendingOnly?: boolean;
|
||||
json?: boolean;
|
||||
jsonFull?: boolean;
|
||||
}) => {
|
||||
@@ -112,12 +122,29 @@ export function registerNewsCommand(program: Command, ctx: CliContext): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Determine which tabs to fetch from
|
||||
const tabs: ExploreTab[] = [];
|
||||
if (cmdOpts.forYou) tabs.push('forYou');
|
||||
if (cmdOpts.newsOnly) tabs.push('news');
|
||||
if (cmdOpts.sports) tabs.push('sports');
|
||||
if (cmdOpts.entertainment) tabs.push('entertainment');
|
||||
if (cmdOpts.trendingOnly) tabs.push('trending');
|
||||
|
||||
// If no specific tabs selected, use defaults (all tabs except trending)
|
||||
const tabsToFetch = tabs.length > 0 ? tabs : undefined;
|
||||
|
||||
const client = new TwitterClient({ cookies, timeoutMs, quoteDepth });
|
||||
const includeRaw = cmdOpts.jsonFull ?? false;
|
||||
const withTweets = cmdOpts.withTweets ?? false;
|
||||
const aiOnly = cmdOpts.aiOnly ?? false;
|
||||
|
||||
const result = await client.getNews(count, { includeRaw, withTweets, tweetsPerItem, aiOnly });
|
||||
const result = await client.getNews(count, {
|
||||
includeRaw,
|
||||
withTweets,
|
||||
tweetsPerItem,
|
||||
aiOnly,
|
||||
tabs: tabsToFetch,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
printNewsItems(result.items, ctx, {
|
||||
|
||||
Reference in New Issue
Block a user