Oak

Check-in [dec188c782]
Login

Check-in [dec188c782]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:nit(ui): Use npubs in Nostr bot UI
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: dec188c782826881ad0b5e29708bd5feeb65ccb6ae279ce14514cb1a6abdb208
User & Date: carlos 2023-09-20 18:59:03
Context
2023-09-21
20:06
fix(nwc): Include client pubkey tag in NWC Response check-in: f19b421d5c user: carlos tags: trunk
2023-09-20
18:59
nit(ui): Use npubs in Nostr bot UI check-in: dec188c782 user: carlos tags: trunk
2023-09-17
19:26
ref(deps): Bump dependency versions check-in: d26486bd53 user: carlos tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/bot/nostr.rs.

1
2
3
4
5
6
7
8
9
10
11
12
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, OnceLock};
use std::time::Duration;

use ::hex::ToHex;
use anyhow::{anyhow, Result};
use ln_sdk::client::lnd::{LndClient, LndConfig};
use ln_sdk::HttpConfig;
use nostr::{
    message::relay::RelayMessage,
    prelude::{decrypt, ToBech32},
    Event, EventBuilder, Filter, Keys, Profile, Url,




<







1
2
3
4

5
6
7
8
9
10
11
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, OnceLock};
use std::time::Duration;


use anyhow::{anyhow, Result};
use ln_sdk::client::lnd::{LndClient, LndConfig};
use ln_sdk::HttpConfig;
use nostr::{
    message::relay::RelayMessage,
    prelude::{decrypt, ToBech32},
    Event, EventBuilder, Filter, Keys, Profile, Url,
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
            .iter()
            .map(|o| o.metadata(composite_params_opt))
            .collect(),
    );

    // Bot keys are known and valid
    info!("nostr_bot pubkey: {}", nostr_bot_keys.public_key());
    nostr_bot_status.set_bot_pubkey(nostr_bot_keys.public_key().serialize().encode_hex());

    // Expect owner keys
    nostr_bot_status.to_state_pending_owner_key_pairing()?;

    // Retrieve bot owner profile
    match composite_params_opt
        .try_get_bot_owner_nprofile_with_nip65_relays()
        .await?
    {
        None => {
            warn!("Bot owner profile not configured");
            Ok(None)
        }
        Some(owner_profile) => {
            // Owner key is known and valid
            info!("nostr_bot_owner profile: {owner_profile:?}");
            info!("nostr_bot_owner nprofile: {}", owner_profile.to_bech32()?);
            nostr_bot_status.set_owner_pubkey(owner_profile.public_key.serialize().encode_hex());

            // Collect all relays we want to connect to
            let mut relevant_relays: HashSet<String> = HashSet::new();
            for relay in &owner_profile.relays {
                relevant_relays.insert(relay.clone());
            }
            if composite_params_opt.ons4.enabled {







|

















|







183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
            .iter()
            .map(|o| o.metadata(composite_params_opt))
            .collect(),
    );

    // Bot keys are known and valid
    info!("nostr_bot pubkey: {}", nostr_bot_keys.public_key());
    nostr_bot_status.set_bot_pk(nostr_bot_keys.public_key());

    // Expect owner keys
    nostr_bot_status.to_state_pending_owner_key_pairing()?;

    // Retrieve bot owner profile
    match composite_params_opt
        .try_get_bot_owner_nprofile_with_nip65_relays()
        .await?
    {
        None => {
            warn!("Bot owner profile not configured");
            Ok(None)
        }
        Some(owner_profile) => {
            // Owner key is known and valid
            info!("nostr_bot_owner profile: {owner_profile:?}");
            info!("nostr_bot_owner nprofile: {}", owner_profile.to_bech32()?);
            nostr_bot_status.set_owner_pk(owner_profile.public_key);

            // Collect all relays we want to connect to
            let mut relevant_relays: HashSet<String> = HashSet::new();
            for relay in &owner_profile.relays {
                relevant_relays.insert(relay.clone());
            }
            if composite_params_opt.ons4.enabled {

Changes to src/bot/ons/ons1.rs.

255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
}

/// True, if the bot already sent a greeting message to this owner pubkey. False otherwise.
///
/// If the owner changes pubkeys, the new one will also receive a greeting when its paired.
async fn was_last_greeting_sent_this_pubkey(
    db: &mut PoolConnection<Sqlite>,
    owner_pubkey: &str,
) -> bool {
    db::config::config_read_optional(db, KEY_GREETING_SENT)
        .await
        .map_err(|e| error!("Error when trying to read config from DB: {:?}", e))
        .map(|greeted_key| match greeted_key {
            None => false,
            Some(k) => k.eq(owner_pubkey),
        })
        .unwrap_or(false)
}

async fn set_last_greeting_sent_this_pubkey(db: &mut PoolConnection<Sqlite>, owner_pubkey: &str) {
    info!("Marking owner pubkey as greeted");
    db::config::config_write_or_update(db, KEY_GREETING_SENT, owner_pubkey)
        .await
        .map_err(|e| error!("Error when trying to write config to DB: {:?}", e))
        .unwrap();
}

async fn maybe_greet_user(db: &mut PoolConnection<Sqlite>, ctx: &mut NostrContext) {
    let owner_pubkey: String = ctx
        .nostr_bot_owner_nprofile
        .public_key
        .serialize()
        .encode_hex();
    if !was_last_greeting_sent_this_pubkey(db, &owner_pubkey).await {
        info!("Sending greeting to pubkey {}", owner_pubkey);

        ctx.send(GREETING.to_string()).await;

        set_last_greeting_sent_this_pubkey(db, &owner_pubkey).await;
    }
}







|






|




|

|






|




|
|



|


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
}

/// True, if the bot already sent a greeting message to this owner pubkey. False otherwise.
///
/// If the owner changes pubkeys, the new one will also receive a greeting when its paired.
async fn was_last_greeting_sent_this_pubkey(
    db: &mut PoolConnection<Sqlite>,
    owner_pk_hex: &str,
) -> bool {
    db::config::config_read_optional(db, KEY_GREETING_SENT)
        .await
        .map_err(|e| error!("Error when trying to read config from DB: {:?}", e))
        .map(|greeted_key| match greeted_key {
            None => false,
            Some(k) => k.eq(owner_pk_hex),
        })
        .unwrap_or(false)
}

async fn set_last_greeting_sent_this_pubkey(db: &mut PoolConnection<Sqlite>, owner_pk_hex: &str) {
    info!("Marking owner pubkey as greeted");
    db::config::config_write_or_update(db, KEY_GREETING_SENT, owner_pk_hex)
        .await
        .map_err(|e| error!("Error when trying to write config to DB: {:?}", e))
        .unwrap();
}

async fn maybe_greet_user(db: &mut PoolConnection<Sqlite>, ctx: &mut NostrContext) {
    let owner_pk_hex: String = ctx
        .nostr_bot_owner_nprofile
        .public_key
        .serialize()
        .encode_hex();
    if !was_last_greeting_sent_this_pubkey(db, &owner_pk_hex).await {
        info!("Sending greeting to pubkey {owner_pk_hex}");

        ctx.send(GREETING.to_string()).await;

        set_last_greeting_sent_this_pubkey(db, &owner_pk_hex).await;
    }
}

Changes to src/model/bot/nostr.rs.

1
2
3
4



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19






20
21
22
23
24
25
26
use crate::model::api::ApiError::GenericError;
use crate::model::api::ApiErrorLogged;
use crate::model::bot::nostr::NostrBotState::*;
use anyhow::{anyhow, Result};



use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use utoipa::ToSchema;

/// Current status of the deltachat bot submodule
#[derive(Clone, Serialize, ToSchema)]
pub struct NostrBotStatus {
    #[schema(value_type = NostrBotState)]
    pub(crate) state: Arc<Mutex<NostrBotState>>,

    #[schema(value_type = String)]
    pub(crate) bot_pubkey: Arc<Mutex<Option<String>>>,

    #[schema(value_type = String)]
    pub(crate) owner_pubkey: Arc<Mutex<Option<String>>>,







    #[schema(value_type = OnsMetadata)]
    pub(crate) ons: Arc<Mutex<Vec<OnsMetadata>>>,
}

impl NostrBotStatus {
    pub(crate) fn ensure_nip47_module_enabled(&self) -> Result<(), ApiErrorLogged> {




>
>
>











|


|
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use crate::model::api::ApiError::GenericError;
use crate::model::api::ApiErrorLogged;
use crate::model::bot::nostr::NostrBotState::*;
use anyhow::{anyhow, Result};
use hex::ToHex;
use nostr::secp256k1::XOnlyPublicKey;
use nostr::ToBech32;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use utoipa::ToSchema;

/// Current status of the deltachat bot submodule
#[derive(Clone, Serialize, ToSchema)]
pub struct NostrBotStatus {
    #[schema(value_type = NostrBotState)]
    pub(crate) state: Arc<Mutex<NostrBotState>>,

    #[schema(value_type = String)]
    pub(crate) bot_pk_hex: Arc<Mutex<Option<String>>>,

    #[schema(value_type = String)]
    pub(crate) bot_pk_npub: Arc<Mutex<Option<String>>>,

    #[schema(value_type = String)]
    pub(crate) owner_pk_hex: Arc<Mutex<Option<String>>>,

    #[schema(value_type = String)]
    pub(crate) owner_pk_npub: Arc<Mutex<Option<String>>>,

    #[schema(value_type = OnsMetadata)]
    pub(crate) ons: Arc<Mutex<Vec<OnsMetadata>>>,
}

impl NostrBotStatus {
    pub(crate) fn ensure_nip47_module_enabled(&self) -> Result<(), ApiErrorLogged> {
52
53
54
55
56
57
58
59
60


61
62
63
64
65
66
67
68
69
70
71
72
73


74

75
76
77
78


79

80
81
82
83
84
85
86
87
    pub enabled: bool,
}

impl Default for NostrBotStatus {
    fn default() -> NostrBotStatus {
        NostrBotStatus {
            state: Arc::new(Mutex::new(PendingBotKeySetup)),
            bot_pubkey: Arc::new(Mutex::new(None)),
            owner_pubkey: Arc::new(Mutex::new(None)),


            ons: Arc::new(Mutex::new(vec![])),
        }
    }
}

pub trait NostrBotStatusOps {
    fn set_bot_pubkey(&self, bot_pubkey: String);
    fn set_owner_pubkey(&self, owner_pubkey: String);
    fn set_ons(&self, ons: Vec<OnsMetadata>);
}

impl NostrBotStatusOps for NostrBotStatus {
    fn set_bot_pubkey(&self, bot_pubkey: String) {


        let mut mutex_bot_pubkey = self.bot_pubkey.lock().unwrap();

        *mutex_bot_pubkey = Some(bot_pubkey);
    }

    fn set_owner_pubkey(&self, owner_pubkey: String) {


        let mut mutex_owner_pubkey = self.owner_pubkey.lock().unwrap();

        *mutex_owner_pubkey = Some(owner_pubkey);
    }

    fn set_ons(&self, ons: Vec<OnsMetadata>) {
        let mut mutex_ons = self.ons.lock().unwrap();
        *mutex_ons = ons;
    }
}







|
|
>
>






|
|




|
>
>
|
>
|


|
>
>
|
>
|







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
    pub enabled: bool,
}

impl Default for NostrBotStatus {
    fn default() -> NostrBotStatus {
        NostrBotStatus {
            state: Arc::new(Mutex::new(PendingBotKeySetup)),
            bot_pk_hex: Arc::new(Mutex::new(None)),
            bot_pk_npub: Arc::new(Mutex::new(None)),
            owner_pk_hex: Arc::new(Mutex::new(None)),
            owner_pk_npub: Arc::new(Mutex::new(None)),
            ons: Arc::new(Mutex::new(vec![])),
        }
    }
}

pub trait NostrBotStatusOps {
    fn set_bot_pk(&self, bot_pk: XOnlyPublicKey);
    fn set_owner_pk(&self, owner_pk: XOnlyPublicKey);
    fn set_ons(&self, ons: Vec<OnsMetadata>);
}

impl NostrBotStatusOps for NostrBotStatus {
    fn set_bot_pk(&self, bot_pk: XOnlyPublicKey) {
        let mut locked_bot_pk_hex = self.bot_pk_hex.lock().unwrap();
        *locked_bot_pk_hex = Some(bot_pk.serialize().encode_hex());

        let mut locked_bot_pk_npub = self.bot_pk_npub.lock().unwrap();
        *locked_bot_pk_npub = Some(bot_pk.to_bech32().unwrap());
    }

    fn set_owner_pk(&self, owner_pk: XOnlyPublicKey) {
        let mut locked_owner_pk_hex = self.owner_pk_hex.lock().unwrap();
        *locked_owner_pk_hex = Some(owner_pk.serialize().encode_hex());

        let mut locked_owner_pk_npub = self.owner_pk_npub.lock().unwrap();
        *locked_owner_pk_npub = Some(owner_pk.to_bech32().unwrap());
    }

    fn set_ons(&self, ons: Vec<OnsMetadata>) {
        let mut mutex_ons = self.ons.lock().unwrap();
        *mutex_ons = ons;
    }
}

Changes to www/templates/bot-nostr-nip47.html.tera.

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

  }

  function show_nostr_connect_qr(nwc_secret) {

    $.ajax( "/status" )
      .done(function(data) {
        let bot_pk = data.nostr_bot.bot_pubkey;
        let relay_best = data.relay_best;
        let nostr_connect_string = "nostr+walletconnect://" + bot_pk + "?relay=" + relay_best + "&secret=" + nwc_secret;

        console.log("Showing QR for NWC connection string: " + nostr_connect_string);
        show_qr(nostr_connect_string);
      });
  }







|







123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

  }

  function show_nostr_connect_qr(nwc_secret) {

    $.ajax( "/status" )
      .done(function(data) {
        let bot_pk = data.nostr_bot.bot_pk_hex;
        let relay_best = data.relay_best;
        let nostr_connect_string = "nostr+walletconnect://" + bot_pk + "?relay=" + relay_best + "&secret=" + nwc_secret;

        console.log("Showing QR for NWC connection string: " + nostr_connect_string);
        show_qr(nostr_connect_string);
      });
  }

Changes to www/templates/bot-nostr.html.tera.

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
              <i class="bi bi-check-circle-fill"></i>
              The bot is up and running
            </div>
          </div>

          <div>
            Your pubkey:
            <p class="card-text font-monospace fs-6 py-2" id="owner-pubkey"></p>

            Bot pubkey:
            <p class="card-text font-monospace fs-6 py-2" id="bot-pubkey"></p>

            <div class="card-text">
              <button type="button" class="btn btn-primary btn-sm ms-auto" data-bs-toggle="modal" data-bs-target="#modal-setup-owner-pk-relay">Update pairing</button>
            </div>
          </div>

        </div>







|


|







55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
              <i class="bi bi-check-circle-fill"></i>
              The bot is up and running
            </div>
          </div>

          <div>
            Your pubkey:
            <p class="card-text font-monospace fs-6 py-2" id="owner-pk-npub"></p>

            Bot pubkey:
            <p class="card-text font-monospace fs-6 py-2" id="bot-pk-npub"></p>

            <div class="card-text">
              <button type="button" class="btn btn-primary btn-sm ms-auto" data-bs-toggle="modal" data-bs-target="#modal-setup-owner-pk-relay">Update pairing</button>
            </div>
          </div>

        </div>
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
        <div class="modal-header">
          <h5 class="modal-title">Setup Bot Pairing</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <form>
            <div class="mb-3">
              <label for="owner-pubkey-input" class="col-form-label">Your Nostr Pubkey:</label>
              <input type="text" class="form-control" id="owner-pubkey-input" placeholder="npub.." autofocus>
            </div>
            <div class="mb-3">
              <label for="owner-relay-input" class="col-form-label">One relay where you post:</label>
              <input type="text" class="form-control" id="owner-relay-input" placeholder="wss://...">
            </div>
          </form>
        </div>







|
|







178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
        <div class="modal-header">
          <h5 class="modal-title">Setup Bot Pairing</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <form>
            <div class="mb-3">
              <label for="owner-pk-input" class="col-form-label">Your Nostr Pubkey:</label>
              <input type="text" class="form-control" id="owner-pk-input" placeholder="npub.." autofocus>
            </div>
            <div class="mb-3">
              <label for="owner-relay-input" class="col-form-label">One relay where you post:</label>
              <input type="text" class="form-control" id="owner-relay-input" placeholder="wss://...">
            </div>
          </form>
        </div>
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
          if (currentBotState == "StartupError") {
            alert(currentBotState);
            $('#card-StartupError').show();
          }
          else if (currentBotState == "Running") {
            $('#card-Running').show();

            $("#bot-pubkey").text(responseJsonObj.nostr_bot.bot_pubkey);
            $("#owner-pubkey").text(responseJsonObj.nostr_bot.owner_pubkey);

            var relay_data = [];
            for (var r in responseJsonObj.relays) {
              relay_data.push(
                {
                  'id': r,
                  'status': responseJsonObj.relays[r]







|
|







216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
          if (currentBotState == "StartupError") {
            alert(currentBotState);
            $('#card-StartupError').show();
          }
          else if (currentBotState == "Running") {
            $('#card-Running').show();

            $("#bot-pk-npub").text(responseJsonObj.nostr_bot.bot_pk_npub);
            $("#owner-pk-npub").text(responseJsonObj.nostr_bot.owner_pk_npub);

            var relay_data = [];
            for (var r in responseJsonObj.relays) {
              relay_data.push(
                {
                  'id': r,
                  'status': responseJsonObj.relays[r]
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268



    function persist_pk_relay() {
      var addModal = document.getElementById('modal-setup-owner-pk-relay');

      let postObj = {
          pk_hex_or_npub: addModal.querySelector('#owner-pubkey-input').value,
          relay: addModal.querySelector('#owner-relay-input').value
      }

      $.ajax({
        type: "post",
        url: "/bot/nostr",
        data: JSON.stringify(postObj),







|







254
255
256
257
258
259
260
261
262
263
264
265
266
267
268



    function persist_pk_relay() {
      var addModal = document.getElementById('modal-setup-owner-pk-relay');

      let postObj = {
          pk_hex_or_npub: addModal.querySelector('#owner-pk-input').value,
          relay: addModal.querySelector('#owner-relay-input').value
      }

      $.ajax({
        type: "post",
        url: "/bot/nostr",
        data: JSON.stringify(postObj),