tail-fin-twitter 0.5.1

Twitter/X adapter for tail-fin: timeline, search, profile, bookmarks, likes, thread, post, like, follow, block, bookmark, reply, trending, lists, article, download, notifications
Documentation
use std::collections::HashSet;

use serde_json::Value;

use crate::types::TwitterUser;

pub fn parse_user_list_response(data: &Value) -> Vec<TwitterUser> {
    let instructions = data
        .pointer("/data/user/result/timeline/timeline/instructions")
        .and_then(|v| v.as_array());

    let instructions = match instructions {
        Some(i) => i,
        None => return vec![],
    };

    let mut users = Vec::new();
    let mut seen = HashSet::new();

    for instruction in instructions {
        let entries = match instruction.get("entries").and_then(|e| e.as_array()) {
            Some(e) => e,
            None => continue,
        };

        for entry in entries {
            let entry_id = entry.get("entryId").and_then(|v| v.as_str()).unwrap_or("");

            if !entry_id.starts_with("user-") {
                continue;
            }

            if let Some(user) = entry.pointer("/content/itemContent/user_results/result") {
                let legacy = user.get("legacy").unwrap_or(&Value::Null);
                let core = user.get("core").unwrap_or(&Value::Null);

                let screen_name = core
                    .get("screen_name")
                    .or_else(|| legacy.get("screen_name"))
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();

                if screen_name.is_empty() || !seen.insert(screen_name.clone()) {
                    continue;
                }

                users.push(TwitterUser {
                    screen_name,
                    name: core
                        .get("name")
                        .or_else(|| legacy.get("name"))
                        .and_then(|v| v.as_str())
                        .unwrap_or("")
                        .to_string(),
                    bio: legacy
                        .get("description")
                        .and_then(|v| v.as_str())
                        .unwrap_or("")
                        .to_string(),
                    followers: legacy
                        .get("followers_count")
                        .and_then(|v| v.as_u64())
                        .unwrap_or(0),
                });
            }
        }
    }

    users
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_user_list_response_one_user() {
        let data = serde_json::json!({
            "data": {
                "user": {
                    "result": {
                        "timeline": {
                            "timeline": {
                                "instructions": [{
                                    "entries": [{
                                        "entryId": "user-123",
                                        "content": {
                                            "itemContent": {
                                                "user_results": {
                                                    "result": {
                                                        "legacy": {
                                                            "screen_name": "charlie",
                                                            "name": "Charlie",
                                                            "description": "Hi there",
                                                            "followers_count": 42
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }]
                                }]
                            }
                        }
                    }
                }
            }
        });

        let users = parse_user_list_response(&data);
        assert_eq!(users.len(), 1);
        assert_eq!(users[0].screen_name, "charlie");
        assert_eq!(users[0].name, "Charlie");
        assert_eq!(users[0].bio, "Hi there");
        assert_eq!(users[0].followers, 42);
    }

    #[test]
    fn test_parse_user_list_response_empty() {
        let data = serde_json::json!({
            "data": {
                "user": {
                    "result": {
                        "timeline": {
                            "timeline": {
                                "instructions": [{
                                    "entries": []
                                }]
                            }
                        }
                    }
                }
            }
        });

        let users = parse_user_list_response(&data);
        assert!(users.is_empty());
    }
}