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):
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
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
# 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
}
}
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
Start with defaults
Use default weights to understand baseline behavior:
Analyze engagement patterns
Inspect scores with --json to see which metrics dominate: twitter feed --filter --json | jq '.[].score'
Tune weights iteratively
Adjust weights based on your quality definition: weights :
bookmarks : 10.0 # Prioritize saved content
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
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
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