spell: store spells locally and add stdin spell to history.

This commit is contained in:
fiatjaf
2025-12-22 11:59:17 -03:00
parent 3be80c29df
commit b95665d986

View File

@@ -65,38 +65,8 @@ var spell = &cli.Command{
if spell.Kind != 777 { if spell.Kind != 777 {
return fmt.Errorf("event is not a spell (expected kind 777, got %d)", spell.Kind) return fmt.Errorf("event is not a spell (expected kind 777, got %d)", spell.Kind)
} }
// parse spell tags to build REQ filter
spellFilter, err := buildSpellReq(ctx, c, spell.Tags)
if err != nil {
return fmt.Errorf("failed to parse spell tags: %w", err)
}
// determine relays to query
var spellRelays []string
var outbox bool
relaysTag := spell.Tags.Find("relays")
if relaysTag == nil {
// if this tag doesn't exist assume $outbox
relaysTag = nostr.Tag{"relays", "$outbox"}
}
for i := 1; i < len(relaysTag); i++ {
switch relaysTag[i] {
case "$outbox":
outbox = true
default:
spellRelays = append(spellRelays, relaysTag[i])
}
}
stream := !spell.Tags.Has("close-on-eose") return runSpell(ctx, c, historyPath, history, nostr.EventPointer{ID: spell.ID}, spell)
logverbose("executing spell from stdin: %s relays=%v outbox=%v stream=%v\n",
spellFilter, spellRelays, outbox, stream)
// execute without adding to history
logSpellDetails(spell)
performReq(ctx, spellFilter, spellRelays, stream, outbox, c.Uint("outbox-relays-per-pubkey"), false, 0, "nak-spell")
return nil
} }
// no stdin input, show recent spells // no stdin input, show recent spells
@@ -123,13 +93,14 @@ var spell = &cli.Command{
} }
lastUsed := entry.LastUsed.Format("2006-01-02 15:04") lastUsed := entry.LastUsed.Format("2006-01-02 15:04")
stdout(fmt.Sprintf(" %s %s%s - %s\n", stdout(fmt.Sprintf(" %s %s%s - %s",
color.BlueString(entry.Identifier), color.BlueString(entry.Identifier),
displayName, displayName,
color.YellowString(lastUsed), color.YellowString(lastUsed),
desc, desc,
)) ))
} }
return nil return nil
} }
@@ -156,23 +127,48 @@ var spell = &cli.Command{
return fmt.Errorf("invalid spell reference") return fmt.Errorf("invalid spell reference")
} }
// fetch spell // first try to fetch spell from sys.Store
relays := pointer.Relays var spell nostr.Event
found := false
for evt := range sys.Store.QueryEvents(nostr.Filter{IDs: []nostr.ID{pointer.ID}}, 1) {
spell = evt
found = true
break
}
var relays []string
if !found {
// if not found in store, fetch from external relays
relays = pointer.Relays
if pointer.Author != nostr.ZeroPK { if pointer.Author != nostr.ZeroPK {
for _, url := range relays { for _, url := range relays {
sys.Hints.Save(pointer.Author, nostr.NormalizeURL(url), hints.LastInHint, nostr.Now()) sys.Hints.Save(pointer.Author, nostr.NormalizeURL(url), hints.LastInHint, nostr.Now())
} }
relays = append(relays, sys.FetchOutboxRelays(ctx, pointer.Author, 3)...) relays = append(relays, sys.FetchOutboxRelays(ctx, pointer.Author, 3)...)
} }
spell := sys.Pool.QuerySingle(ctx, relays, nostr.Filter{IDs: []nostr.ID{pointer.ID}}, result := sys.Pool.QuerySingle(ctx, relays, nostr.Filter{IDs: []nostr.ID{pointer.ID}},
nostr.SubscriptionOptions{Label: "nak-spell-f"}) nostr.SubscriptionOptions{Label: "nak-spell-f"})
if spell == nil { if result == nil {
return fmt.Errorf("spell event not found") return fmt.Errorf("spell event not found")
} }
spell = result.Event
}
if spell.Kind != 777 { if spell.Kind != 777 {
return fmt.Errorf("event is not a spell (expected kind 777, got %d)", spell.Kind) return fmt.Errorf("event is not a spell (expected kind 777, got %d)", spell.Kind)
} }
return runSpell(ctx, c, historyPath, history, pointer, spell)
},
}
func runSpell(
ctx context.Context,
c *cli.Command,
historyPath string,
history []SpellHistoryEntry,
pointer nostr.EventPointer,
spell nostr.Event,
) error {
// parse spell tags to build REQ filter // parse spell tags to build REQ filter
spellFilter, err := buildSpellReq(ctx, c, spell.Tags) spellFilter, err := buildSpellReq(ctx, c, spell.Tags)
if err != nil { if err != nil {
@@ -182,7 +178,7 @@ var spell = &cli.Command{
// determine relays to query // determine relays to query
var spellRelays []string var spellRelays []string
var outbox bool var outbox bool
relaysTag := spell.Event.Tags.Find("relays") relaysTag := spell.Tags.Find("relays")
if relaysTag == nil { if relaysTag == nil {
// if this tag doesn't exist assume $outbox // if this tag doesn't exist assume $outbox
relaysTag = nostr.Tag{"relays", "$outbox"} relaysTag = nostr.Tag{"relays", "$outbox"}
@@ -192,7 +188,7 @@ var spell = &cli.Command{
case "$outbox": case "$outbox":
outbox = true outbox = true
default: default:
relays = append(relays, relaysTag[i]) spellRelays = append(spellRelays, relaysTag[i])
} }
} }
@@ -201,10 +197,15 @@ var spell = &cli.Command{
// fill in the author if we didn't have it // fill in the author if we didn't have it
pointer.Author = spell.PubKey pointer.Author = spell.PubKey
// save spell to sys.Store
if err := sys.Store.SaveEvent(spell); err != nil {
logverbose("failed to save spell to store: %v\n", err)
}
// add to history before execution // add to history before execution
{ {
idStr := nip19.EncodeNevent(spell.ID, nil, nostr.ZeroPK) idStr := nip19.EncodeNevent(spell.ID, nil, nostr.ZeroPK)
identifier = "spell" + idStr[len(idStr)-7:] identifier := "spell" + idStr[len(idStr)-7:]
nameTag := spell.Tags.Find("name") nameTag := spell.Tags.Find("name")
var name string var name string
if nameTag != nil { if nameTag != nil {
@@ -228,14 +229,18 @@ var spell = &cli.Command{
file.Write(data) file.Write(data)
file.Write([]byte{'\n'}) file.Write([]byte{'\n'})
for i, entry := range history { for i, entry := range history {
// limit history size (keep last 100) if entry.Identifier == identifier {
if i == 100 { continue
break
} }
data, _ := json.Marshal(entry) data, _ := json.Marshal(entry)
file.Write(data) file.Write(data)
file.Write([]byte{'\n'}) file.Write([]byte{'\n'})
// limit history size (keep last 100)
if i == 100 {
break
}
} }
file.Close() file.Close()
@@ -244,11 +249,10 @@ var spell = &cli.Command{
} }
// execute // execute
logSpellDetails(spell.Event) logSpellDetails(spell)
performReq(ctx, spellFilter, spellRelays, stream, outbox, c.Uint("outbox-relays-per-pubkey"), false, 0, "nak-spell") performReq(ctx, spellFilter, spellRelays, stream, outbox, c.Uint("outbox-relays-per-pubkey"), false, 0, "nak-spell")
return nil return nil
},
} }
func buildSpellReq(ctx context.Context, c *cli.Command, tags nostr.Tags) (nostr.Filter, error) { func buildSpellReq(ctx context.Context, c *cli.Command, tags nostr.Tags) (nostr.Filter, error) {