caracal 0.3.9

Nostr client for Gemini
use super::route_prelude::*;
use nostr::nips::nip46::NostrConnectURI;
use nostr_connect::prelude::*;
use nostr_sdk::prelude::*;
use std::sync::Arc;

#[derive(Template)]
#[template(path = "connect/index.gmi", escape = "txt")]
pub struct NostrConnectTemplate {}

#[derive(Template)]
#[template(path = "connect/success.gmi", escape = "txt")]
pub struct NostrConnectSuccessTemplate {}

#[derive(Template)]
#[template(path = "connect/failure.gmi", escape = "txt")]
pub struct NostrConnectFailureTemplate {}

#[derive(Template)]
#[template(path = "connect/direct.gmi", escape = "txt")]
pub struct NostrConnectClientInitTemplate<'a> {
    pub uri: &'a NostrConnectURI,
}

pub async fn nostr_connect_index(
    _ctx: RouteContext,
    _user: &'static mut CaracalUser,
) -> Response {
    Response::success(WindTemplate::render(NostrConnectTemplate {}))
}

fn ask_bunker_uri() -> Response {
    Response::input(t!("input_bunker_uri"))
}

/// Bunker connection
/// The bunker URI is passed in the query
pub async fn nostr_connect_bunker(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Ok(client_keys) = user.get_client_keys() else {
        return Response::temporary_failure("Client keys error");
    };

    let config = user.nostr_config();

    match ctx.action() {
        Action::New => {
            if let Some(input_uri) = dec_urlq(&ctx.url) {
                // From query

                if let Ok(uri) = NostrConnectURI::parse(&input_uri) {
                    let connect_string = uri.to_string();

                    if let Ok(signer) = NostrConnect::new(
                        uri.clone(),
                        client_keys,
                        std::time::Duration::from_secs(20),
                        None,
                    ) {
                        let mut nostr_config = user.nostr_config();
                        nostr_config.last_bunker_uri = Some(connect_string);
                        let _ = user.save_nostr_config(&nostr_config);

                        user.signer = Arc::new(signer);
                        user.client.set_signer(user.signer.clone()).await;

                        user.connect_uri = Some(uri.clone());

                        let _ = user.tx.send(0).await;

                        // Redirect to home (we could also show the "success" page)
                        Response::temporary_redirect("/")
                    } else {
                        user.connect_uri = None;

                        let _ = user.client.unset_signer().await;

                        Response::success(WindTemplate::render(
                            NostrConnectFailureTemplate {},
                        ))
                    }
                } else {
                    user.connect_uri = None;
                    Response::input(t!("invalid_input_bunker_uri"))
                }
            } else {
                ask_bunker_uri()
            }
        }
        Action::Reconnect => {
            if let Some(uri) = config.last_bunker_uri {
                // let _ = user.save_nostr_config(&config);

                Response::temporary_redirect(format!(
                    "/connect/bunker/new?{}",
                    encode(&uri)
                ))
            } else {
                ask_bunker_uri()
            }
        }
        _ => ask_bunker_uri(),
    }
}

/// Direct connection, initiated by the client
/// TODO
pub async fn nostr_connect_client_initiated(
    _ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Ok(client_keys) = user.get_client_keys() else {
        return Response::temporary_failure("Client keys error");
    };

    let uri = NostrConnectURI::client(
        client_keys.public_key(),
        vec![RelayUrl::parse("wss://nos.lol").unwrap()],
        "caracal",
    );

    let Ok(signer) = NostrConnect::new(
        uri.clone(),
        client_keys,
        Duration::from_secs(20),
        None,
    ) else {
        return Response::temporary_failure("Failed to build signer");
    };

    user.client.set_signer(signer).await;
    user.connect_uri = Some(uri.clone());

    Response::success(WindTemplate::render(NostrConnectClientInitTemplate {
        uri: &uri,
    }))
}