← back to douglaz__nostrweet

Function bodies 355 total

All specs Real LLM only Function bodies
select_media_url function · rust · L816-L829 (14 LOC)
crates/nostrweet-cli/src/main.rs
fn select_media_url(media: &Media) -> Option<(HttpUrl, Option<String>)> {
    if let Some(url) = &media.url {
        return Some((url.clone(), None));
    }

    if matches!(media.kind, MediaKind::Video | MediaKind::AnimatedGif)
        && let Some(variant) = select_media_variant(media)
    {
        return Some((variant.url.clone(), Some(variant.content_type)));
    }

    media.preview_image_url.clone().map(|url| (url, None))
}
media_downloads_for_tweet function · rust · L830-L836 (7 LOC)
crates/nostrweet-cli/src/main.rs
fn media_downloads_for_tweet(tweet: &Tweet) -> Vec<MediaDownload> {
    let mut downloads = Vec::new();
    let mut seen = HashSet::new();
    collect_media_downloads(tweet, &mut downloads, &mut seen);
    downloads
}
collect_media_downloads function · rust · L837-L895 (59 LOC)
crates/nostrweet-cli/src/main.rs
fn collect_media_downloads(
    tweet: &Tweet,
    downloads: &mut Vec<MediaDownload>,
    seen: &mut HashSet<String>,
) {
    if let Some(includes) = &tweet.includes {
        for media in &includes.media {
            let key = media.media_key.as_str().to_string();
            if !seen.insert(key.clone()) {
                continue;
            }
            let Some((url, variant_content_type)) = select_media_url(media) else {
                continue;
            };

            let media_key_clean = media.media_key.cleaned();
            let username = if tweet.author.username.as_str().is_empty() {
                "unknown"
            } else {
                tweet.author.username.as_str()
            };

            let extension = match media.kind {
                MediaKind::Photo => Some("jpg".to_string()),
                MediaKind::Video | MediaKind::AnimatedGif => Some("mp4".to_string()),
                MediaKind::Other(_) => extension_from_url(&url),
            }
     
fetch_media_assets function · rust · L896-L933 (38 LOC)
crates/nostrweet-cli/src/main.rs
async fn fetch_media_assets(data_dir: &Path, tweet: &Tweet) -> Result<Vec<MediaAsset>> {
    let mut assets = Vec::new();
    for download in media_downloads_for_tweet(tweet) {
        let path = data_dir.join(&download.filename);
        let bytes = if path.exists() {
            std::fs::read(&path).with_context(|| {
                format!("Failed to read cached media file at {}", path.display())
            })?
        } else {
            let response = reqwest::get(download.url.as_str())
                .await
                .with_context(|| format!("Failed to download media {}", download.url.as_str()))?;
            let status = response.status();
            if !status.is_success() {
                anyhow::bail!(
                    "Media download failed with HTTP {status} for {}",
                    download.url.as_str()
                );
            }
            let bytes = response
                .bytes()
                .await
                .context("Failed to rea
build_blossom_client function · rust · L934-L949 (16 LOC)
crates/nostrweet-cli/src/main.rs
async fn build_blossom_client(servers: &[String]) -> Result<Option<BlossomClient>> {
    if servers.is_empty() {
        return Ok(None);
    }
    let mut parsed = Vec::new();
    for server in servers {
        parsed.push(
            nostrweet_core::BlossomUrl::parse(server)
                .with_context(|| format!("Invalid Blossom server URL: {server}"))?,
        );
    }
    // FIXME: generate NIP-98 auth header and call .with_auth_header() so uploads
    // work on Blossom servers that require authentication.
    Ok(Some(BlossomClient::new(parsed)?))
}
parse_username function · rust · L950-L953 (4 LOC)
crates/nostrweet-cli/src/main.rs
fn parse_username(input: &str) -> Result<Username> {
    Username::parse(input).with_context(|| format!("Invalid username: {input}"))
}
derive_keys_for_user function · rust · L954-L958 (5 LOC)
crates/nostrweet-cli/src/main.rs
fn derive_keys_for_user(user_id: &UserId, mnemonic: &MnemonicPhrase) -> Result<Keys> {
    let secret = derive_nostr_secret_key(user_id, mnemonic, None)?;
    Keys::parse(&secret.to_hex()).context("Failed to derive Nostr keys")
}
Repobility · MCP-ready · https://repobility.com
fetch_profile function · rust · L959-L967 (9 LOC)
crates/nostrweet-cli/src/main.rs
async fn fetch_profile(data_dir: &Path, bearer_token: &str, username: &str) -> Result<()> {
    let username = parse_username(username)?;
    let twitter = TwitterClient::new(bearer_token)?;
    let storage = FileStorage::new(data_dir)?;
    let user = twitter.fetch_user_profile(&username).await?;
    storage.save_user_profile(&user).await?;
    Ok(())
}
load_or_fetch_tweet function · rust · L968-L981 (14 LOC)
crates/nostrweet-cli/src/main.rs
async fn load_or_fetch_tweet(
    data_dir: &Path,
    bearer_token: Option<&str>,
    tweet_id: &TweetId,
) -> Result<Tweet> {
    let storage = FileStorage::new(data_dir)?;
    let twitter = if let Some(token) = bearer_token {
        Some(TwitterClient::new(token)?)
    } else {
        None
    };
    load_or_fetch_tweet_with_ports(&storage, twitter.as_ref(), tweet_id).await
}
load_or_fetch_tweet_with_ports function · rust · L982-L1014 (33 LOC)
crates/nostrweet-cli/src/main.rs
async fn load_or_fetch_tweet_with_ports<S: StoragePort, T: TwitterPort>(
    storage: &S,
    twitter: Option<&T>,
    tweet_id: &TweetId,
) -> Result<Tweet> {
    if storage.is_tweet_not_found(tweet_id).await? {
        bail!(
            "Tweet {} was previously marked as not found",
            tweet_id.as_str()
        );
    }

    if let Some(tweet) = storage.load_tweet(tweet_id).await? {
        return Ok(tweet);
    }

    let twitter = twitter.context("Twitter bearer token required to fetch tweet from API")?;
    match twitter.fetch_tweet(tweet_id).await {
        Ok(tweet) => {
            storage.save_tweet(&tweet).await?;
            Ok(tweet)
        }
        Err(err) => {
            if let Some(TwitterAdapterError::TweetNotFound { .. }) =
                err.downcast_ref::<TwitterAdapterError>()
            {
                storage.mark_tweet_not_found(tweet_id).await?;
            }
            Err(err)
        }
    }
}
fetch_tweet function · rust · L1015-L1037 (23 LOC)
crates/nostrweet-cli/src/main.rs
async fn fetch_tweet(
    data_dir: &Path,
    bearer_token: &str,
    tweet_url_or_id: &str,
    skip_profiles: bool,
) -> Result<()> {
    let tweet_id = TweetId::parse(tweet_url_or_id)
        .with_context(|| format!("Failed to parse tweet ID from {tweet_url_or_id}"))?;
    let twitter = TwitterClient::new(bearer_token)?;
    let storage = FileStorage::new(data_dir)?;

    let mut tweet = load_or_fetch_tweet(data_dir, Some(bearer_token), &tweet_id).await?;
    twitter.enrich_referenced_tweets(&mut tweet).await?;
    storage.save_tweet(&tweet).await?;
    let _ = fetch_media_assets(data_dir, &tweet).await?;

    if !skip_profiles {
        download_profiles_for_tweet(&twitter, &storage, &tweet).await?;
    }

    Ok(())
}
user_tweets function · rust · L1038-L1069 (32 LOC)
crates/nostrweet-cli/src/main.rs
async fn user_tweets(
    data_dir: &Path,
    bearer_token: &str,
    username: &str,
    count: u32,
    days: Option<u32>,
    skip_profiles: bool,
) -> Result<()> {
    let username = parse_username(username)?;
    let twitter = TwitterClient::new(bearer_token)?;
    let storage = FileStorage::new(data_dir)?;
    let query = UserTweetsQuery {
        count,
        days,
        since_id: None,
    };
    let mut tweets = twitter.fetch_user_tweets(&username, query).await?;
    let mut processed = Vec::new();
    for tweet in &mut tweets {
        twitter.enrich_referenced_tweets(tweet).await?;
        storage.save_tweet(tweet).await?;
        let _ = fetch_media_assets(data_dir, tweet).await?;
        processed.push(tweet.clone());
    }

    if !skip_profiles {
        download_profiles_for_tweets(&twitter, &storage, &processed).await?;
    }

    Ok(())
}
download_profiles_for_tweets function · rust · L1070-L1081 (12 LOC)
crates/nostrweet-cli/src/main.rs
async fn download_profiles_for_tweets(
    twitter: &TwitterClient,
    storage: &FileStorage<nostrweet_storage::SystemClock>,
    tweets: &[Tweet],
) -> Result<()> {
    let mut usernames = HashSet::new();
    for tweet in tweets {
        usernames.extend(collect_usernames_from_tweet(tweet));
    }
    download_profiles_for_usernames(twitter, storage, usernames).await
}
download_profiles_for_tweet function · rust · L1082-L1090 (9 LOC)
crates/nostrweet-cli/src/main.rs
async fn download_profiles_for_tweet(
    twitter: &TwitterClient,
    storage: &FileStorage<nostrweet_storage::SystemClock>,
    tweet: &Tweet,
) -> Result<()> {
    let usernames = collect_usernames_from_tweet(tweet);
    download_profiles_for_usernames(twitter, storage, usernames).await
}
download_profiles_for_usernames function · rust · L1091-L1106 (16 LOC)
crates/nostrweet-cli/src/main.rs
async fn download_profiles_for_usernames(
    twitter: &TwitterClient,
    storage: &FileStorage<nostrweet_storage::SystemClock>,
    usernames: HashSet<String>,
) -> Result<()> {
    for username in usernames {
        let Ok(username) = Username::parse(&username) else {
            continue;
        };
        if let Ok(user) = twitter.fetch_user_profile(&username).await {
            let _ = storage.save_user_profile(&user).await?;
        }
    }
    Ok(())
}
Repobility · code-quality intelligence platform · https://repobility.com
collect_usernames_from_tweet function · rust · L1107-L1131 (25 LOC)
crates/nostrweet-cli/src/main.rs
fn collect_usernames_from_tweet(tweet: &Tweet) -> HashSet<String> {
    let mut usernames = HashSet::new();
    if !tweet.author.username.as_str().is_empty() {
        usernames.insert(tweet.author.username.as_str().to_string());
    }
    if let Some(entities) = &tweet.entities {
        for mention in &entities.mentions {
            usernames.insert(mention.username.as_str().to_string());
        }
    }
    for reference in &tweet.referenced_tweets {
        if let Some(data) = &reference.data {
            if !data.author.username.as_str().is_empty() {
                usernames.insert(data.author.username.as_str().to_string());
            }
            if let Some(entities) = &data.entities {
                for mention in &entities.mentions {
                    usernames.insert(mention.username.as_str().to_string());
                }
            }
        }
    }
    usernames
}
new function · rust · L1149-L1156 (8 LOC)
crates/nostrweet-cli/src/main.rs
    fn new(data_dir: Option<PathBuf>, mnemonic: Option<MnemonicPhrase>) -> Self {
        Self {
            username_to_pubkey: HashMap::new(),
            user_id_to_pubkey: HashMap::new(),
            data_dir,
            mnemonic,
        }
    }
add_known_user function · rust · L1157-L1177 (21 LOC)
crates/nostrweet-cli/src/main.rs
    fn add_known_user(&mut self, username: &Username, user_id: &UserId) -> Result<()> {
        let Some(mnemonic) = &self.mnemonic else {
            return Ok(());
        };
        if self.user_id_to_pubkey.contains_key(user_id.as_str()) {
            if let Some(pubkey) = self.user_id_to_pubkey.get(user_id.as_str()) {
                self.username_to_pubkey
                    .insert(username.as_str().to_string(), *pubkey);
            }
            return Ok(());
        }

        let keys = derive_keys_for_user(user_id, mnemonic)?;
        let pubkey = keys.public_key();
        self.user_id_to_pubkey
            .insert(user_id.as_str().to_string(), pubkey);
        self.username_to_pubkey
            .insert(username.as_str().to_string(), pubkey);
        Ok(())
    }
resolve_username function · rust · L1178-L1204 (27 LOC)
crates/nostrweet-cli/src/main.rs
    async fn resolve_username(&mut self, username: &str) -> Result<Option<nostr_sdk::PublicKey>> {
        if let Some(pubkey) = self.username_to_pubkey.get(username) {
            return Ok(Some(*pubkey));
        }
        let Some(mnemonic) = &self.mnemonic else {
            return Ok(None);
        };
        let Some(data_dir) = &self.data_dir else {
            return Ok(None);
        };

        let storage = FileStorage::new(data_dir)?;
        let Ok(username_parsed) = Username::parse(username) else {
            return Ok(None);
        };
        let user = storage.load_latest_user_profile(&username_parsed).await?;
        let Some(user) = user else {
            return Ok(None);
        };
        let keys = derive_keys_for_user(&user.id, mnemonic)?;
        let pubkey = keys.public_key();
        self.user_id_to_pubkey
            .insert(user.id.as_str().to_string(), pubkey);
        self.username_to_pubkey.insert(username.to_string(), pubkey);
        Ok(Some(pubkey))
extract_additional_mentions function · rust · L1206-L1223 (18 LOC)
crates/nostrweet-cli/src/main.rs
fn extract_additional_mentions(text: &str, already_processed: &HashSet<String>) -> Vec<String> {
    let mut mentions = Vec::new();
    for word in text.split_whitespace() {
        if !word.starts_with('@') || word.len() <= 1 {
            continue;
        }
        let username = word[1..].trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_');
        if username.is_empty() || username.len() > 15 {
            continue;
        }
        if already_processed.contains(username) {
            continue;
        }
        mentions.push(username.to_string());
    }
    mentions
}
pubkey_to_bech32 function · rust · L1224-L1230 (7 LOC)
crates/nostrweet-cli/src/main.rs
fn pubkey_to_bech32(pubkey: &nostr_sdk::PublicKey) -> String {
    match pubkey.to_bech32() {
        Ok(npub) => npub,
        Err(err) => match err {},
    }
}
process_mentions_in_text function · rust · L1231-L1269 (39 LOC)
crates/nostrweet-cli/src/main.rs
async fn process_mentions_in_text(
    text: &str,
    entities: Option<&nostrweet_core::Entities>,
    resolver: &mut NostrLinkResolver,
) -> Result<(String, Vec<nostr_sdk::PublicKey>)> {
    let mut result = text.to_string();
    let mut mentioned_pubkeys = Vec::new();
    let mut processed = HashSet::new();

    if let Some(entities) = entities {
        for mention in &entities.mentions {
            let username = mention.username.as_str();
            if processed.contains(username) {
                continue;
            }
            processed.insert(username.to_string());
            if let Some(pubkey) = resolver.resolve_username(username).await? {
                let npub = pubkey_to_bech32(&pubkey);
                let old = format!("@{username}");
                let new = format!("nostr:{npub}");
                result = result.replace(&old, &new);
                mentioned_pubkeys.push(pubkey);
            }
        }
    }

    for username in extract_additional_mentio
format_tweet_text_with_mentions function · rust · L1270-L1291 (22 LOC)
crates/nostrweet-cli/src/main.rs
async fn format_tweet_text_with_mentions(
    tweet: &Tweet,
    media_urls: &[HttpUrl],
    resolver: &mut NostrLinkResolver,
) -> Result<FormattedContent> {
    resolver.add_known_user(&tweet.author.username, &tweet.author.id)?;
    let raw_text = tweet
        .note_tweet
        .as_ref()
        .map(|note| note.text.as_str())
        .unwrap_or_else(|| tweet.text.as_str());
    let decoded = decode_html_entities(raw_text);
    let expanded = expand_urls_in_text(&decoded, tweet.entities.as_ref(), media_urls, tweet);
    let (text_with_mentions, mentioned_pubkeys) =
        process_mentions_in_text(&expanded.text, tweet.entities.as_ref(), resolver).await?;
    Ok(FormattedContent {
        text: text_with_mentions,
        used_media_urls: expanded.used_media_urls,
        mentioned_pubkeys,
    })
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
is_simple_retweet function · rust · L1292-L1619 (328 LOC)
crates/nostrweet-cli/src/main.rs
fn is_simple_retweet(tweet: &Tweet) -> (bool, Option<String>) {
    if !tweet
        .referenced_tweets
        .iter()
        .any(|rt| matches!(rt.kind, nostrweet_core::ReferenceKind::Retweeted))
    {
        return (false, None);
    }
    let raw_text = tweet
        .note_tweet
        .as_ref()
        .map(|note| note.text.as_str())
        .unwrap_or_else(|| tweet.text.as_str());
    let is_simple = raw_text.starts_with("RT @")
        && raw_text.contains(':')
        && !raw_text.contains('\n')
        && !raw_text.contains(" // ");
    let username = if is_simple {
        raw_text.find(':').and_then(|end_idx| {
            raw_text
                .find('@')
                .map(|start_idx| raw_text[(start_idx + 1)..end_idx].trim().to_string())
        })
    } else {
        None
    };
    (is_simple, username)
}

async fn format_reply_section(
    content: &mut String,
    ref_tweet: &nostrweet_core::ReferencedTweet,
    resolver: &mut NostrLinkResolver,
) -> Result<
format_reply_section function · rust · L1321-L1362 (42 LOC)
crates/nostrweet-cli/src/main.rs
async fn format_reply_section(
    content: &mut String,
    ref_tweet: &nostrweet_core::ReferencedTweet,
    resolver: &mut NostrLinkResolver,
) -> Result<Vec<nostr_sdk::PublicKey>> {
    let mut mentioned_pubkeys = Vec::new();
    let tweet_url = build_twitter_status_url(&ref_tweet.id);
    if let Some(data) = &ref_tweet.data {
        resolver.add_known_user(&data.author.username, &data.author.id)?;
        let author = if let Some(pubkey) = resolver
            .resolve_username(data.author.username.as_str())
            .await?
        {
            mentioned_pubkeys.push(pubkey);
            format!("nostr:{}", pubkey_to_bech32(&pubkey))
        } else {
            format!("@{}", data.author.username.as_str())
        };

        content.push_str(&format!("↩️ Reply to {author}:\n"));

        let media_urls = extract_media_urls(data);
        let formatted = format_tweet_text_with_mentions(data, &media_urls, resolver).await?;
        mentioned_pubkeys.extend(formatted.mentioned
format_quote_section function · rust · L1363-L1403 (41 LOC)
crates/nostrweet-cli/src/main.rs
async fn format_quote_section(
    content: &mut String,
    ref_tweet: &nostrweet_core::ReferencedTweet,
    resolver: &mut NostrLinkResolver,
) -> Result<Vec<nostr_sdk::PublicKey>> {
    let mut mentioned_pubkeys = Vec::new();
    let tweet_url = build_twitter_status_url(&ref_tweet.id);
    if let Some(data) = &ref_tweet.data {
        resolver.add_known_user(&data.author.username, &data.author.id)?;
        let author = if let Some(pubkey) = resolver
            .resolve_username(data.author.username.as_str())
            .await?
        {
            mentioned_pubkeys.push(pubkey);
            format!("nostr:{}", pubkey_to_bech32(&pubkey))
        } else {
            format!("@{}", data.author.username.as_str())
        };

        content.push_str(&format!("💬 Quote of {author}:\n"));
        let media_urls = extract_media_urls(data);
        let formatted = format_tweet_text_with_mentions(data, &media_urls, resolver).await?;
        mentioned_pubkeys.extend(formatted.mentioned_p
format_retweet_section function · rust · L1404-L1444 (41 LOC)
crates/nostrweet-cli/src/main.rs
async fn format_retweet_section(
    content: &mut String,
    ref_tweet: &nostrweet_core::ReferencedTweet,
    retweeter: &str,
    resolver: &mut NostrLinkResolver,
) -> Result<Vec<nostr_sdk::PublicKey>> {
    let mut mentioned_pubkeys = Vec::new();
    let tweet_url = build_twitter_status_url(&ref_tweet.id);
    if let Some(data) = &ref_tweet.data {
        resolver.add_known_user(&data.author.username, &data.author.id)?;
        let author = if let Some(pubkey) = resolver
            .resolve_username(data.author.username.as_str())
            .await?
        {
            mentioned_pubkeys.push(pubkey);
            format!("nostr:{}", pubkey_to_bech32(&pubkey))
        } else {
            format!("@{}", data.author.username.as_str())
        };

        content.push_str(&format!("🔁 @{retweeter} retweeted {author}:\n"));
        let media_urls = extract_media_urls(data);
        let formatted = format_tweet_text_with_mentions(data, &media_urls, resolver).await?;
        mentioned
format_tweet_as_nostr_content_with_mentions function · rust · L1445-L1505 (61 LOC)
crates/nostrweet-cli/src/main.rs
async fn format_tweet_as_nostr_content_with_mentions(
    tweet: &Tweet,
    media_urls: &[HttpUrl],
    resolver: &mut NostrLinkResolver,
) -> Result<(String, Vec<nostr_sdk::PublicKey>)> {
    let mut content = String::new();
    let mut mentioned_pubkeys = Vec::new();
    let (is_simple_retweet, rt_username) = is_simple_retweet(tweet);

    if !is_simple_retweet {
        if !tweet.author.username.as_str().is_empty() {
            content.push_str(&format!("🐦 @{}: ", tweet.author.username.as_str()));
        } else if let Some(author_id) = tweet.author_id.as_ref() {
            content.push_str(&format!("🐦 User {}: ", author_id.as_str()));
        } else {
            content.push_str("🐦 Tweet: ");
        }

        let formatted = format_tweet_text_with_mentions(tweet, media_urls, resolver).await?;
        mentioned_pubkeys.extend(formatted.mentioned_pubkeys);
        content.push_str(&formatted.text);
        content.push('\n');

        for url in media_urls {
            if !fo
build_nostr_event_tags function · rust · L1506-L1535 (30 LOC)
crates/nostrweet-cli/src/main.rs
fn build_nostr_event_tags(
    tweet_id: &TweetId,
    original_media_urls: &[HttpUrl],
    blossom_urls: &[HttpUrl],
    mentioned_pubkeys: &[nostr_sdk::PublicKey],
) -> Result<Vec<NostrTag>> {
    let mut tags = Vec::new();
    let original_url = HttpUrl::parse(&build_twitter_status_url(tweet_id))?;
    tags.push(NostrTag::r(&original_url));

    for pubkey in mentioned_pubkeys {
        let nostr_pubkey = NostrPubkey::parse(&pubkey.to_hex())?;
        tags.push(NostrTag::p(&nostr_pubkey));
    }

    if blossom_urls.is_empty() {
        for url in original_media_urls {
            tags.push(NostrTag::media(url));
        }
    } else {
        for (source, media) in original_media_urls.iter().zip(blossom_urls.iter()) {
            tags.push(NostrTag::source(source));
            tags.push(NostrTag::media(media));
        }
    }

    tags.push(NostrTag::client());
    Ok(tags)
}
build_event_from_draft function · rust · L1536-L1553 (18 LOC)
crates/nostrweet-cli/src/main.rs
async fn build_event_from_draft(draft: &NostrEventDraft, keys: &Keys) -> Result<nostr_sdk::Event> {
    let mut builder = EventBuilder::new(Kind::TextNote, draft.content.clone());
    if let Some(created_at) = draft.created_at {
        builder = builder.custom_created_at(Timestamp::from(created_at.value()));
    }
    for tag in &draft.tags {
        let mut values = Vec::with_capacity(1 + tag.values.len());
        values.push(tag.name.clone());
        values.extend(tag.values.clone());
        builder = builder.tag(Tag::parse(values)?);
    }
    let event = builder
        .sign(keys)
        .await
        .context("Failed to sign Nostr event")?;
    Ok(event)
}
build_nostr_client function · rust · L1554-L1565 (12 LOC)
crates/nostrweet-cli/src/main.rs
async fn build_nostr_client(keys: &Keys, relays: &[String]) -> Result<NostrClient> {
    let client = NostrClient::new(keys.clone());
    for relay in relays {
        client
            .add_relay(relay)
            .await
            .with_context(|| format!("Failed to add relay: {relay}"))?;
    }
    client.connect().await;
    Ok(client)
}
All rows scored by the Repobility analyzer (https://repobility.com)
find_existing_event function · rust · L1566-L1587 (22 LOC)
crates/nostrweet-cli/src/main.rs
async fn find_existing_event(
    client: &NostrClient,
    tweet_id: &TweetId,
    keys: &Keys,
) -> Result<Option<nostr_sdk::Event>> {
    let pubkey = keys.public_key();
    let twitter_url = build_twitter_status_url(tweet_id);
    let filter = Filter::new()
        .author(pubkey)
        .kind(Kind::TextNote)
        .custom_tag(SingleLetterTag::lowercase(Alphabet::R), twitter_url)
        .limit(10);

    match client.fetch_events(filter, Duration::from_secs(10)).await {
        Ok(events) => Ok(events.into_iter().next()),
        Err(err) => {
            warn!("Failed to check existing events for {tweet_id}: {err}");
            Ok(None)
        }
    }
}
save_nostr_event_json function · rust · L1588-L1602 (15 LOC)
crates/nostrweet-cli/src/main.rs
fn save_nostr_event_json(data_dir: &Path, event: &nostr_sdk::Event) -> Result<()> {
    let dir = data_dir.join("nostr_events");
    std::fs::create_dir_all(&dir).with_context(|| {
        format!(
            "Failed to create nostr_events directory at {}",
            dir.display()
        )
    })?;
    let path = dir.join(format!("{}.json", event.id.to_hex()));
    let json = serde_json::to_string_pretty(event).context("Failed to serialize Nostr event")?;
    std::fs::write(&path, json)
        .with_context(|| format!("Failed to write Nostr event to {}", path.display()))?;
    Ok(())
}
parse_relay_urls function · rust · L1603-L1612 (10 LOC)
crates/nostrweet-cli/src/main.rs
fn parse_relay_urls(relays: &[String]) -> Result<Vec<nostrweet_core::RelayUrl>> {
    relays
        .iter()
        .map(|relay| {
            nostrweet_core::RelayUrl::parse(relay)
                .with_context(|| format!("Invalid relay URL: {relay}"))
        })
        .collect()
}
build_profile_metadata function · rust · L1620-L1647 (28 LOC)
crates/nostrweet-cli/src/main.rs
fn build_profile_metadata(user: &User, username: &Username) -> Metadata {
    let mut metadata = Metadata::new();

    if let Some(name) = &user.name {
        metadata = metadata.name(name);
    }

    let disclaimer = profile_disclaimer(username);
    let about = match &user.description {
        Some(desc) => format!("{desc}{disclaimer}"),
        None => disclaimer,
    };
    metadata = metadata.about(&about);

    if let Some(url) = &user.profile_image_url
        && let Ok(parsed) = url.as_str().parse()
    {
        metadata = metadata.picture(parsed);
    }
    if let Some(url) = &user.url
        && let Ok(parsed) = url.as_str().parse()
    {
        metadata = metadata.website(parsed);
    }

    metadata
}
post_single_profile function · rust · L1648-L1674 (27 LOC)
crates/nostrweet-cli/src/main.rs
async fn post_single_profile(
    username: &Username,
    adapter: &impl NostrAdapter,
    data_dir: &Path,
    mnemonic: &MnemonicPhrase,
) -> Result<nostr_sdk::EventId> {
    let storage = FileStorage::new(data_dir)?;
    let Some(user) = storage.load_latest_user_profile(username).await? else {
        bail!(
            "No profile found for user '@{username}'",
            username = username.as_str()
        );
    };

    let keys = derive_keys_for_user(&user.id, mnemonic)?;
    let metadata = build_profile_metadata(&user, username);
    adapter
        .publish_profile(metadata, &keys)
        .await
        .with_context(|| {
            format!(
                "Failed to publish profile for @{username}",
                username = username.as_str()
            )
        })
}
post_relay_list_for_user function · rust · L1675-L1694 (20 LOC)
crates/nostrweet-cli/src/main.rs
async fn post_relay_list_for_user(
    username: &Username,
    adapter: &impl NostrAdapter,
    data_dir: &Path,
    mnemonic: &MnemonicPhrase,
    relays: &[String],
) -> Result<()> {
    let storage = FileStorage::new(data_dir)?;
    let Some(user) = storage.load_latest_user_profile(username).await? else {
        bail!(
            "No profile found for user '@{username}'",
            username = username.as_str()
        );
    };

    let keys = derive_keys_for_user(&user.id, mnemonic)?;
    adapter.update_relay_list(relays, &keys).await?;
    Ok(())
}
post_user_profile_with_relay_list function · rust · L1695-L1710 (16 LOC)
crates/nostrweet-cli/src/main.rs
async fn post_user_profile_with_relay_list(
    username: &Username,
    adapter: &impl NostrAdapter,
    data_dir: &Path,
    mnemonic: &MnemonicPhrase,
    relays: &[String],
) -> Result<()> {
    let event_id = post_single_profile(username, adapter, data_dir, mnemonic).await?;
    debug!(
        "Posted profile for @{username} with event ID {event_id}",
        username = username.as_str()
    );
    post_relay_list_for_user(username, adapter, data_dir, mnemonic, relays).await?;
    Ok(())
}
check_profile_exists function · rust · L1711-L1725 (15 LOC)
crates/nostrweet-cli/src/main.rs
async fn check_profile_exists(
    username: &Username,
    adapter: &impl NostrAdapter,
    data_dir: &Path,
    mnemonic: &MnemonicPhrase,
) -> Result<bool> {
    let storage = FileStorage::new(data_dir)?;
    let Some(user) = storage.load_latest_user_profile(username).await? else {
        return Ok(false);
    };

    let keys = derive_keys_for_user(&user.id, mnemonic)?;
    adapter.profile_exists(&keys.public_key()).await
}
Repobility · MCP-ready · https://repobility.com
filter_profiles_to_post function · rust · L1726-L1761 (36 LOC)
crates/nostrweet-cli/src/main.rs
async fn filter_profiles_to_post(
    usernames: HashSet<String>,
    adapter: &impl NostrAdapter,
    data_dir: &Path,
    force: bool,
    mnemonic: &MnemonicPhrase,
) -> Result<HashSet<String>> {
    if force {
        return Ok(usernames);
    }

    let storage = FileStorage::new(data_dir)?;
    let mut profiles_to_post = HashSet::new();

    for username in usernames {
        let Ok(username_parsed) = Username::parse(&username) else {
            continue;
        };
        let Some(user) = storage.load_latest_user_profile(&username_parsed).await? else {
            continue;
        };

        let keys = derive_keys_for_user(&user.id, mnemonic)?;
        let has_existing_profile = adapter
            .profile_exists(&keys.public_key())
            .await
            .unwrap_or(false);

        if !has_existing_profile {
            profiles_to_post.insert(username);
        }
    }

    Ok(profiles_to_post)
}
post_referenced_profiles function · rust · L1762-L1794 (33 LOC)
crates/nostrweet-cli/src/main.rs
async fn post_referenced_profiles(
    usernames: &HashSet<String>,
    adapter: &impl NostrAdapter,
    data_dir: &Path,
    mnemonic: &MnemonicPhrase,
) -> Result<usize> {
    if usernames.is_empty() {
        return Ok(0);
    }

    let mut posted_count = 0;
    let mut failed_count = 0;

    for username in usernames {
        let Ok(username_parsed) = Username::parse(username) else {
            continue;
        };
        match post_single_profile(&username_parsed, adapter, data_dir, mnemonic).await {
            Ok(_) => posted_count += 1,
            Err(err) => {
                debug!("Failed to post profile for @{username}: {err}");
                failed_count += 1;
            }
        }
    }

    if failed_count > 0 {
        debug!("Posted {posted_count} profiles, {failed_count} failed");
    }

    Ok(posted_count)
}
post_tweet_to_nostr function · rust · L1797-L1936 (140 LOC)
crates/nostrweet-cli/src/main.rs
async fn post_tweet_to_nostr(
    data_dir: &Path,
    bearer_token: Option<&str>,
    mnemonic: &str,
    tweet_url_or_id: &str,
    relays: &[String],
    blossom_servers: &[String],
    force: bool,
    skip_profiles: bool,
) -> Result<()> {
    let tweet_id = TweetId::parse(tweet_url_or_id)
        .with_context(|| format!("Failed to parse tweet ID from {tweet_url_or_id}"))?;
    let storage = FileStorage::new(data_dir)?;

    if let Some(_existing) = storage.load_nostr_event_info(&tweet_id).await?
        && !force
    {
        return Ok(());
    }

    let twitter = if let Some(token) = bearer_token {
        Some(TwitterClient::new(token)?)
    } else {
        None
    };
    let mut tweet = load_or_fetch_tweet_with_ports(&storage, twitter.as_ref(), &tweet_id).await?;
    if let Some(twitter) = twitter.as_ref() {
        twitter.enrich_referenced_tweets(&mut tweet).await?;
        if !skip_profiles {
            download_profiles_for_tweet(twitter, &storage, &tweet).await?;
  
post_user_to_nostr function · rust · L1937-L1994 (58 LOC)
crates/nostrweet-cli/src/main.rs
async fn post_user_to_nostr(
    data_dir: &Path,
    mnemonic: &str,
    username: &str,
    relays: &[String],
    blossom_servers: &[String],
    force: bool,
    skip_profiles: bool,
) -> Result<()> {
    let username = parse_username(username)?;
    let storage = FileStorage::new(data_dir)?;
    let summaries = storage.list_tweets().await?;
    let mut referenced_users = HashSet::new();
    let mut matched = 0usize;

    for summary in summaries {
        let tweet = summary.tweet;
        if tweet.author.username.normalized() != username.normalized() {
            continue;
        }
        matched += 1;
        if !skip_profiles {
            referenced_users.extend(collect_usernames_from_tweet(&tweet));
        }
        post_tweet_to_nostr(
            data_dir,
            None,
            mnemonic,
            tweet.id.as_str(),
            relays,
            blossom_servers,
            force,
            true,
        )
        .await?;
    }

    ensure!(
        matc
post_profile_to_nostr function · rust · L1995-L2017 (23 LOC)
crates/nostrweet-cli/src/main.rs
async fn post_profile_to_nostr(
    data_dir: &Path,
    mnemonic: &str,
    username: &str,
    relays: &[String],
) -> Result<()> {
    let username = parse_username(username)?;
    let storage = FileStorage::new(data_dir)?;
    let Some(user) = storage.load_latest_user_profile(&username).await? else {
        bail!(
            "No cached profile found for @{username}",
            username = username.as_str()
        );
    };

    let mnemonic = MnemonicPhrase::parse(mnemonic)?;
    let keys = derive_keys_for_user(&user.id, &mnemonic)?;
    let adapter = NostrSdkAdapter::new(&keys, relays, data_dir).await?;
    let metadata = build_profile_metadata(&user, &username);
    let _ = adapter.publish_profile(metadata, &keys).await?;
    Ok(())
}
update_relay_list function · rust · L2018-L2026 (9 LOC)
crates/nostrweet-cli/src/main.rs
async fn update_relay_list(mnemonic: &str, relays: &[String]) -> Result<()> {
    let mnemonic = MnemonicPhrase::parse(mnemonic)?;
    let user_id = UserId::parse("0")?;
    let keys = derive_keys_for_user(&user_id, &mnemonic)?;
    let adapter = NostrSdkAdapter::new(&keys, relays, Path::new(".")).await?;
    adapter.update_relay_list(relays, &keys).await?;
    Ok(())
}
show_tweet function · rust · L2027-L2083 (57 LOC)
crates/nostrweet-cli/src/main.rs
async fn show_tweet(
    data_dir: &Path,
    bearer_token: Option<&str>,
    mnemonic: Option<&str>,
    cmd: ShowTweetCommand,
) -> Result<()> {
    let tweet_id = TweetId::parse(&cmd.tweet)
        .with_context(|| format!("Failed to parse tweet ID from {}", cmd.tweet))?;
    let tweet = load_or_fetch_tweet(data_dir, bearer_token, &tweet_id).await?;
    let media_urls = extract_media_urls(&tweet);

    let mnemonic = mnemonic.map(MnemonicPhrase::parse).transpose()?;
    let mut resolver = NostrLinkResolver::new(Some(data_dir.to_path_buf()), mnemonic);
    let (content, mentioned_pubkeys) =
        format_tweet_as_nostr_content_with_mentions(&tweet, &media_urls, &mut resolver).await?;

    let tags = build_nostr_event_tags(&tweet.id, &media_urls, &[], &mentioned_pubkeys)?;
    let created_at = tweet.created_at.unix_timestamp().ok();
    let keys = Keys::generate();
    let draft = NostrEventDraft {
        content: content.clone(),
        tags,
        created_at: created_at.map(|t
new function · rust · L2108-L2119 (12 LOC)
crates/nostrweet-cli/src/main.rs
    fn new() -> Self {
        Self {
            last_poll_time: None,
            last_success_time: None,
            last_profile_post_time: None,
            profile_posted: false,
            consecutive_failures: 0,
            total_tweets_downloaded: 0,
            total_tweets_posted: 0,
            is_processing: false,
        }
    }
Repobility · code-quality intelligence platform · https://repobility.com
next_poll_delay function · rust · L2120-L2128 (9 LOC)
crates/nostrweet-cli/src/main.rs
    fn next_poll_delay(&self, base_interval: u64) -> Duration {
        if self.consecutive_failures > 0 {
            let backoff_seconds = base_interval * 2_u64.pow(self.consecutive_failures.min(5));
            return Duration::from_secs(backoff_seconds.min(3600));
        }

        Duration::from_secs(base_interval)
    }
new function · rust · L2158-L2164 (7 LOC)
crates/nostrweet-cli/src/main.rs
    fn new(requests_per_window: u32, window_seconds: u64) -> Self {
        Self {
            requests_per_window,
            window_duration: Duration::from_secs(window_seconds),
            request_times: std::collections::VecDeque::new(),
        }
    }
wait_if_needed function · rust · L2165-L2188 (24 LOC)
crates/nostrweet-cli/src/main.rs
    async fn wait_if_needed(&mut self) {
        let cutoff = Instant::now() - self.window_duration;
        while let Some(&front) = self.request_times.front() {
            if front < cutoff {
                self.request_times.pop_front();
            } else {
                break;
            }
        }

        if self.request_times.len() >= self.requests_per_window as usize
            && let Some(&oldest) = self.request_times.front()
        {
            let wait_until = oldest + self.window_duration;
            let wait_duration = wait_until.saturating_duration_since(Instant::now());
            if wait_duration > Duration::ZERO {
                info!("Rate limit reached, waiting {wait_duration:?}");
                time::sleep(wait_duration).await;
            }
        }

        self.request_times.push_back(Instant::now());
    }
‹ prevpage 2 / 8next ›