Skip to main content
twitter-cli includes an optional engagement scoring system to rank and filter tweets by quality.
Filtering is opt-in. Use the --filter flag to enable scoring. Without --filter, tweets appear in chronological order.

Scoring Algorithm

Each tweet receives a numeric score based on engagement metrics:
score = w_likes × likes
      + w_retweets × retweets
      + w_replies × replies
      + w_bookmarks × bookmarks
      + w_views_log × log10(max(views, 1))
Source: twitter_cli/filter.py:23-45

Default Weights

DEFAULT_WEIGHTS = {
    "likes": 1.0,
    "retweets": 3.0,
    "replies": 2.0,
    "bookmarks": 5.0,
    "views_log": 0.5,
}
Source: twitter_cli/filter.py:14-20

Why Log Scale for Views?

Views can range from hundreds to millions, creating extreme variance. Using log10(views) normalizes this:
  • 1,000 views → log10(1000) = 3.0
  • 100,000 views → log10(100000) = 5.0
  • 10,000,000 views → log10(10000000) = 7.0
This prevents viral tweets from dominating scores solely by view count.

Filter Modes

Configure filtering behavior via config.yaml:

topN Mode (Default)

Keeps the top N highest-scoring tweets:
filter:
  mode: "topN"
  topN: 20  # Return top 20 tweets
Source: twitter_cli/filter.py:81-83 Use case: “Show me the 20 best tweets from this feed”

score Mode

Keeps all tweets with score >= minScore:
filter:
  mode: "score"
  minScore: 100  # Only tweets with score ≥ 100
Source: twitter_cli/filter.py:84-86 Use case: “Show me tweets with at least 100 engagement points”

all Mode

Returns all tweets, sorted by score (no filtering):
filter:
  mode: "all"
Source: twitter_cli/filter.py:87 Use case: “Show me everything, ranked by engagement”

Customizing Weights

Adjust weights in config.yaml to prioritize different engagement types:

Prioritize Bookmarks

Bookmarks indicate high-quality content:
filter:
  weights:
    likes: 0.5
    retweets: 2.0
    replies: 1.0
    bookmarks: 10.0  # Heavily weight bookmarks
    views_log: 0.2

Prioritize Discussion

Emphasize tweets with active conversation:
filter:
  weights:
    likes: 1.0
    retweets: 2.0
    replies: 8.0  # Weight replies heavily
    bookmarks: 3.0
    views_log: 0.1

Viral Content Only

Focus on high retweet/like counts:
filter:
  weights:
    likes: 3.0
    retweets: 10.0  # Prioritize viral spread
    replies: 1.0
    bookmarks: 2.0
    views_log: 1.0

Additional Filters

Language Filter

Restrict to specific languages using ISO 639-1 codes:
filter:
  lang: ["en"]  # English only
filter:
  lang: ["en", "ja", "zh"]  # English, Japanese, Chinese
Source: twitter_cli/filter.py:63-66

Exclude Retweets

Filter out retweets, keeping only original content:
filter:
  excludeRetweets: true
Source: twitter_cli/filter.py:69-70

Implementation Details

Filter Pipeline

def filter_tweets(tweets, config) -> List[Tweet]:
    filtered = list(tweets)

    # 1. Language filter
    lang_filter = config.get("lang", [])
    if lang_filter:
        lang_set = {str(lang) for lang in lang_filter}
        filtered = [tweet for tweet in filtered if tweet.lang in lang_set]

    # 2. Exclude retweets
    if config.get("excludeRetweets", False):
        filtered = [tweet for tweet in filtered if not tweet.is_retweet]

    # 3. Score all tweets
    weights = _build_weights(config.get("weights", {}))
    scored = [replace(tweet, score=round(score_tweet(tweet, weights), 1)) 
              for tweet in filtered]

    # 4. Sort by score (descending)
    scored.sort(key=lambda tweet: tweet.score, reverse=True)

    # 5. Apply filter mode
    mode = str(config.get("mode", "topN"))
    if mode == "topN":
        return scored[:config.get("topN", 20)]
    elif mode == "score":
        return [t for t in scored if t.score >= config.get("minScore", 50)]
    return scored
Source: twitter_cli/filter.py:48-87

Score Calculation

def score_tweet(tweet, weights=None) -> float:
    w = weights or DEFAULT_WEIGHTS
    m = tweet.metrics
    return (
        w.get("likes", 1.0) * m.likes
        + w.get("retweets", 3.0) * m.retweets
        + w.get("replies", 2.0) * m.replies
        + w.get("bookmarks", 5.0) * m.bookmarks
        + w.get("views_log", 0.5) * math.log10(max(m.views, 1))
    )
Source: twitter_cli/filter.py:23-45

Usage Examples

Enable Filtering

Filtering is disabled by default. Enable with --filter:
# Without filter: chronological order
twitter feed

# With filter: ranked by engagement
twitter feed --filter

Top 10 Tweets

# config.yaml
filter:
  mode: "topN"
  topN: 10
twitter feed --filter --max 50
# Fetches 50, returns top 10 by score

High-Quality Threshold

# config.yaml
filter:
  mode: "score"
  minScore: 200
twitter search "AI agents" --filter
# Only tweets with score ≥ 200

Language-Specific Feed

# config.yaml
filter:
  mode: "topN"
  topN: 20
  lang: ["ja"]
  weights:
    bookmarks: 8.0
twitter feed --filter
# Top 20 Japanese tweets by engagement

Debugging Scores

Use --json to inspect calculated scores:
twitter feed --filter --json | jq '.[] | {text: .text, score: .score, metrics: .metrics}'
Example output:
{
  "text": "Amazing AI breakthrough!",
  "score": 287.3,
  "metrics": {
    "likes": 42,
    "retweets": 18,
    "replies": 7,
    "bookmarks": 31,
    "views": 15420
  }
}

Performance Notes

  • Scoring happens after fetching, not during API calls
  • Filters apply client-side, so fetch.count affects raw data volume
  • Use rateLimit.maxCount to cap total fetched items
Example:
fetch:
  count: 50  # Fetch 50 per page

filter:
  mode: "topN"
  topN: 10  # Return top 10

rateLimit:
  maxCount: 200  # Stop after 200 total fetched
This fetches up to 200 tweets (4 pages × 50), then returns the top 10.

Best Practices

1

Start with defaults

Use default weights to understand baseline behavior:
twitter feed --filter
2

Analyze engagement patterns

Inspect scores with --json to see which metrics dominate:
twitter feed --filter --json | jq '.[].score'
3

Tune weights iteratively

Adjust weights based on your quality definition:
weights:
  bookmarks: 10.0  # Prioritize saved content
4

Test with different modes

Compare topN vs score modes for your use case

Troubleshooting

Filter not applying

Cause: Forgot to pass --filter flag. Solution:
twitter feed --filter  # Enable filtering

All tweets have low scores

Cause: Fetching from low-engagement feed or weights are too conservative. Solution:
  • Increase fetch.count to fetch more candidates
  • Lower filter.minScore threshold
  • Adjust weights to match typical engagement levels

No tweets returned

Cause: filter.minScore too high or language filter too restrictive. Solution:
filter:
  mode: "topN"  # Fallback to topN mode
  topN: 10

Unexpected ranking

Cause: Default weights may not match your quality definition. Solution:
  • Use --json to inspect scores and metrics
  • Adjust weights to prioritize desired engagement types
  • Set views_log to 0.0 if views skew results

Next Steps

Config File

Full config.yaml schema and examples

Commands

Apply filtering to timeline commands

Build docs developers (and LLMs) love