mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-23 06:58:51 +00:00
spell: store spells locally and add stdin spell to history.
This commit is contained in:
220
spell.go
220
spell.go
@@ -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,99 +127,132 @@ 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
|
||||||
if pointer.Author != nostr.ZeroPK {
|
found := false
|
||||||
for _, url := range relays {
|
for evt := range sys.Store.QueryEvents(nostr.Filter{IDs: []nostr.ID{pointer.ID}}, 1) {
|
||||||
sys.Hints.Save(pointer.Author, nostr.NormalizeURL(url), hints.LastInHint, nostr.Now())
|
spell = evt
|
||||||
}
|
found = true
|
||||||
relays = append(relays, sys.FetchOutboxRelays(ctx, pointer.Author, 3)...)
|
break
|
||||||
}
|
}
|
||||||
spell := sys.Pool.QuerySingle(ctx, relays, nostr.Filter{IDs: []nostr.ID{pointer.ID}},
|
|
||||||
nostr.SubscriptionOptions{Label: "nak-spell-f"})
|
var relays []string
|
||||||
if spell == nil {
|
if !found {
|
||||||
return fmt.Errorf("spell event not found")
|
// if not found in store, fetch from external relays
|
||||||
|
relays = pointer.Relays
|
||||||
|
if pointer.Author != nostr.ZeroPK {
|
||||||
|
for _, url := range relays {
|
||||||
|
sys.Hints.Save(pointer.Author, nostr.NormalizeURL(url), hints.LastInHint, nostr.Now())
|
||||||
|
}
|
||||||
|
relays = append(relays, sys.FetchOutboxRelays(ctx, pointer.Author, 3)...)
|
||||||
|
}
|
||||||
|
result := sys.Pool.QuerySingle(ctx, relays, nostr.Filter{IDs: []nostr.ID{pointer.ID}},
|
||||||
|
nostr.SubscriptionOptions{Label: "nak-spell-f"})
|
||||||
|
if result == nil {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse spell tags to build REQ filter
|
return runSpell(ctx, c, historyPath, history, pointer, spell)
|
||||||
spellFilter, err := buildSpellReq(ctx, c, spell.Tags)
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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")
|
||||||
|
|
||||||
|
// fill in the author if we didn't have it
|
||||||
|
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
|
||||||
|
{
|
||||||
|
idStr := nip19.EncodeNevent(spell.ID, nil, nostr.ZeroPK)
|
||||||
|
identifier := "spell" + idStr[len(idStr)-7:]
|
||||||
|
nameTag := spell.Tags.Find("name")
|
||||||
|
var name string
|
||||||
|
if nameTag != nil {
|
||||||
|
name = nameTag[1]
|
||||||
|
}
|
||||||
|
if len(history) > 100 {
|
||||||
|
history = history[:100]
|
||||||
|
}
|
||||||
|
// write back to file
|
||||||
|
file, err := os.Create(historyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse spell tags: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
data, _ := json.Marshal(SpellHistoryEntry{
|
||||||
// determine relays to query
|
Identifier: identifier,
|
||||||
var spellRelays []string
|
Name: name,
|
||||||
var outbox bool
|
Content: spell.Content,
|
||||||
relaysTag := spell.Event.Tags.Find("relays")
|
LastUsed: time.Now(),
|
||||||
if relaysTag == nil {
|
Pointer: pointer,
|
||||||
// if this tag doesn't exist assume $outbox
|
})
|
||||||
relaysTag = nostr.Tag{"relays", "$outbox"}
|
file.Write(data)
|
||||||
}
|
file.Write([]byte{'\n'})
|
||||||
for i := 1; i < len(relaysTag); i++ {
|
for i, entry := range history {
|
||||||
switch relaysTag[i] {
|
if entry.Identifier == identifier {
|
||||||
case "$outbox":
|
continue
|
||||||
outbox = true
|
|
||||||
default:
|
|
||||||
relays = append(relays, relaysTag[i])
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
stream := !spell.Tags.Has("close-on-eose")
|
data, _ := json.Marshal(entry)
|
||||||
|
|
||||||
// fill in the author if we didn't have it
|
|
||||||
pointer.Author = spell.PubKey
|
|
||||||
|
|
||||||
// add to history before execution
|
|
||||||
{
|
|
||||||
idStr := nip19.EncodeNevent(spell.ID, nil, nostr.ZeroPK)
|
|
||||||
identifier = "spell" + idStr[len(idStr)-7:]
|
|
||||||
nameTag := spell.Tags.Find("name")
|
|
||||||
var name string
|
|
||||||
if nameTag != nil {
|
|
||||||
name = nameTag[1]
|
|
||||||
}
|
|
||||||
if len(history) > 100 {
|
|
||||||
history = history[:100]
|
|
||||||
}
|
|
||||||
// write back to file
|
|
||||||
file, err := os.Create(historyPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, _ := json.Marshal(SpellHistoryEntry{
|
|
||||||
Identifier: identifier,
|
|
||||||
Name: name,
|
|
||||||
Content: spell.Content,
|
|
||||||
LastUsed: time.Now(),
|
|
||||||
Pointer: pointer,
|
|
||||||
})
|
|
||||||
file.Write(data)
|
file.Write(data)
|
||||||
file.Write([]byte{'\n'})
|
file.Write([]byte{'\n'})
|
||||||
for i, entry := range history {
|
|
||||||
// limit history size (keep last 100)
|
|
||||||
if i == 100 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := json.Marshal(entry)
|
// limit history size (keep last 100)
|
||||||
file.Write(data)
|
if i == 100 {
|
||||||
file.Write([]byte{'\n'})
|
break
|
||||||
}
|
}
|
||||||
file.Close()
|
|
||||||
|
|
||||||
logverbose("executing %s: %s relays=%v outbox=%v stream=%v\n",
|
|
||||||
identifier, spellFilter, spellRelays, outbox, stream)
|
|
||||||
}
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
// execute
|
logverbose("executing %s: %s relays=%v outbox=%v stream=%v\n",
|
||||||
logSpellDetails(spell.Event)
|
identifier, spellFilter, spellRelays, outbox, stream)
|
||||||
performReq(ctx, spellFilter, spellRelays, stream, outbox, c.Uint("outbox-relays-per-pubkey"), false, 0, "nak-spell")
|
}
|
||||||
|
|
||||||
return nil
|
// execute
|
||||||
},
|
logSpellDetails(spell)
|
||||||
|
performReq(ctx, spellFilter, spellRelays, stream, outbox, c.Uint("outbox-relays-per-pubkey"), false, 0, "nak-spell")
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user