view: basic req functionality.

This commit is contained in:
fiatjaf
2025-11-27 07:06:37 -03:00
parent eb6fdfdd39
commit 789c6a3884
3 changed files with 357 additions and 2 deletions

View File

@@ -1,7 +1,6 @@
package main
import (
"context"
"encoding/json"
"slices"
"strings"
@@ -63,7 +62,7 @@ func updateEvent() {
if currentKeyer != nil {
signAndFinalize := func() {
if currentKeyer != nil {
if err := currentKeyer.SignEvent(context.Background(), &result); err == nil {
if err := currentKeyer.SignEvent(ctx, &result); err == nil {
finalize()
} else {
statusLabel.SetText("failed to sign: " + err.Error())

View File

@@ -1,6 +1,7 @@
package main
import (
"context"
"encoding/hex"
"os"
"strings"
@@ -10,6 +11,7 @@ import (
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip19"
"fiatjaf.com/nostr/nip49"
"fiatjaf.com/nostr/sdk"
"github.com/therecipe/qt/widgets"
)
@@ -18,6 +20,8 @@ var (
currentKeyer nostr.Keyer
statusLabel *widgets.QLabel
debounced = debouncer.New(800 * time.Millisecond)
sys = sdk.NewSystem()
ctx = context.Background()
)
func main() {

View File

@@ -1 +1,353 @@
package main
import (
"encoding/json"
"strconv"
"strings"
"fiatjaf.com/nostr"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/widgets"
)
type reqVars struct {
authorsEdits []*widgets.QLineEdit
idsEdits []*widgets.QLineEdit
kindsEdits []*widgets.QLineEdit
kindsLabels []*widgets.QLabel
relaysEdits []*widgets.QLineEdit
sinceEdit *widgets.QDateTimeEdit
untilEdit *widgets.QDateTimeEdit
limitSpin *widgets.QSpinBox
filter nostr.Filter
outputEdit *widgets.QTextEdit
resultsEdit *widgets.QTextEdit
}
var req = reqVars{}
func setupReqTab() *widgets.QWidget {
tab := widgets.NewQWidget(nil, 0)
layout := widgets.NewQVBoxLayout()
tab.SetLayout(layout)
// authors
authorsLabel := widgets.NewQLabel2("authors:", nil, 0)
layout.AddWidget(authorsLabel, 0, 0)
authorsVBox := widgets.NewQVBoxLayout()
layout.AddLayout(authorsVBox, 0)
req.authorsEdits = []*widgets.QLineEdit{}
var addAuthorEdit func()
addAuthorEdit = func() {
edit := widgets.NewQLineEdit(nil)
req.authorsEdits = append(req.authorsEdits, edit)
authorsVBox.AddWidget(edit, 0, 0)
edit.ConnectTextChanged(func(text string) {
if strings.TrimSpace(text) != "" {
if edit == req.authorsEdits[len(req.authorsEdits)-1] {
addAuthorEdit()
}
} else {
n := len(req.authorsEdits)
if n >= 2 && strings.TrimSpace(req.authorsEdits[n-1].Text()) == "" && strings.TrimSpace(req.authorsEdits[n-2].Text()) == "" {
authorsVBox.Layout().RemoveWidget(req.authorsEdits[n-1])
req.authorsEdits[n-1].DeleteLater()
req.authorsEdits = req.authorsEdits[0 : n-1]
}
}
updateReq()
})
}
addAuthorEdit()
// ids
idsLabel := widgets.NewQLabel2("ids:", nil, 0)
layout.AddWidget(idsLabel, 0, 0)
idsVBox := widgets.NewQVBoxLayout()
layout.AddLayout(idsVBox, 0)
req.idsEdits = []*widgets.QLineEdit{}
var addIdEdit func()
addIdEdit = func() {
edit := widgets.NewQLineEdit(nil)
req.idsEdits = append(req.idsEdits, edit)
idsVBox.AddWidget(edit, 0, 0)
edit.ConnectTextChanged(func(text string) {
if strings.TrimSpace(text) != "" {
if edit == req.idsEdits[len(req.idsEdits)-1] {
addIdEdit()
}
} else {
n := len(req.idsEdits)
if n >= 2 && strings.TrimSpace(req.idsEdits[n-1].Text()) == "" && strings.TrimSpace(req.idsEdits[n-2].Text()) == "" {
idsVBox.Layout().RemoveWidget(req.idsEdits[n-1])
req.idsEdits[n-1].DeleteLater()
req.idsEdits = req.idsEdits[0 : n-1]
}
}
updateReq()
})
}
addIdEdit()
// kinds
kindsLabel := widgets.NewQLabel2("kinds:", nil, 0)
layout.AddWidget(kindsLabel, 0, 0)
kindsVBox := widgets.NewQVBoxLayout()
layout.AddLayout(kindsVBox, 0)
req.kindsEdits = []*widgets.QLineEdit{}
req.kindsLabels = []*widgets.QLabel{}
var addKindEdit func()
addKindEdit = func() {
hbox := widgets.NewQHBoxLayout()
kindsVBox.AddLayout(hbox, 0)
edit := widgets.NewQLineEdit(nil)
req.kindsEdits = append(req.kindsEdits, edit)
hbox.AddWidget(edit, 0, 0)
label := widgets.NewQLabel2("", nil, 0)
req.kindsLabels = append(req.kindsLabels, label)
hbox.AddWidget(label, 0, 0)
edit.ConnectTextChanged(func(text string) {
if strings.TrimSpace(text) != "" {
if edit == req.kindsEdits[len(req.kindsEdits)-1] {
addKindEdit()
}
} else {
n := len(req.kindsEdits)
if n >= 2 && strings.TrimSpace(req.kindsEdits[n-1].Text()) == "" && strings.TrimSpace(req.kindsEdits[n-2].Text()) == "" {
lastItem := kindsVBox.ItemAt(kindsVBox.Count() - 1)
kindsVBox.RemoveItem(lastItem)
lastHBox := lastItem.Layout()
lastHBox.RemoveWidget(req.kindsEdits[n-1])
lastHBox.RemoveWidget(req.kindsLabels[n-1])
req.kindsEdits[n-1].DeleteLater()
req.kindsLabels[n-1].DeleteLater()
lastHBox.DeleteLater()
req.kindsEdits = req.kindsEdits[0 : n-1]
req.kindsLabels = req.kindsLabels[0 : n-1]
}
}
updateReq()
})
}
addKindEdit()
// since
sinceHBox := widgets.NewQHBoxLayout()
layout.AddLayout(sinceHBox, 0)
sinceLabel := widgets.NewQLabel2("since:", nil, 0)
sinceHBox.AddWidget(sinceLabel, 0, 0)
req.sinceEdit = widgets.NewQDateTimeEdit(nil)
{
time := core.NewQDateTime3(core.NewQDate3(0, 0, 0), core.NewQTime3(0, 0, 0, 0), 0)
time.SetMSecsSinceEpoch(0)
req.sinceEdit.SetDateTime(time)
}
sinceHBox.AddWidget(req.sinceEdit, 0, 0)
req.sinceEdit.ConnectDateTimeChanged(func(*core.QDateTime) {
updateReq()
})
// until
untilHBox := widgets.NewQHBoxLayout()
layout.AddLayout(untilHBox, 0)
untilLabel := widgets.NewQLabel2("until:", nil, 0)
untilHBox.AddWidget(untilLabel, 0, 0)
req.untilEdit = widgets.NewQDateTimeEdit(nil)
{
time := core.NewQDateTime3(core.NewQDate3(0, 0, 0), core.NewQTime3(0, 0, 0, 0), 0)
time.SetMSecsSinceEpoch(0)
req.untilEdit.SetDateTime(time)
}
untilHBox.AddWidget(req.untilEdit, 0, 0)
req.untilEdit.ConnectDateTimeChanged(func(*core.QDateTime) {
updateReq()
})
// limit
limitHBox := widgets.NewQHBoxLayout()
layout.AddLayout(limitHBox, 0)
limitLabel := widgets.NewQLabel2("limit:", nil, 0)
limitHBox.AddWidget(limitLabel, 0, 0)
req.limitSpin = widgets.NewQSpinBox(nil)
req.limitSpin.SetMinimum(0)
req.limitSpin.SetMaximum(1000)
limitHBox.AddWidget(req.limitSpin, 0, 0)
req.limitSpin.ConnectValueChanged(func(int) {
updateReq()
})
// output
outputLabel := widgets.NewQLabel2("filter:", nil, 0)
layout.AddWidget(outputLabel, 0, 0)
req.outputEdit = widgets.NewQTextEdit(nil)
req.outputEdit.SetReadOnly(true)
layout.AddWidget(req.outputEdit, 0, 0)
// relays
relaysLabel := widgets.NewQLabel2("relays:", nil, 0)
layout.AddWidget(relaysLabel, 0, 0)
relaysVBox := widgets.NewQVBoxLayout()
layout.AddLayout(relaysVBox, 0)
req.relaysEdits = []*widgets.QLineEdit{}
var addRelayEdit func()
addRelayEdit = func() {
edit := widgets.NewQLineEdit(nil)
req.relaysEdits = append(req.relaysEdits, edit)
relaysVBox.AddWidget(edit, 0, 0)
edit.ConnectTextChanged(func(text string) {
if strings.TrimSpace(text) != "" {
if edit == req.relaysEdits[len(req.relaysEdits)-1] {
addRelayEdit()
}
} else {
n := len(req.relaysEdits)
if n >= 2 && strings.TrimSpace(req.relaysEdits[n-1].Text()) == "" && strings.TrimSpace(req.relaysEdits[n-2].Text()) == "" {
relaysVBox.Layout().RemoveWidget(req.relaysEdits[n-1])
req.relaysEdits[n-1].DeleteLater()
req.relaysEdits = req.relaysEdits[0 : n-1]
}
}
})
}
addRelayEdit()
// send button
buttonHBox := widgets.NewQHBoxLayout()
layout.AddLayout(buttonHBox, 0)
sendButton := widgets.NewQPushButton2("send request", nil)
buttonHBox.AddWidget(sendButton, 0, 0)
buttonHBox.AddStretch(1)
// results
resultsLabel := widgets.NewQLabel2("results:", nil, 0)
layout.AddWidget(resultsLabel, 0, 0)
req.resultsEdit = widgets.NewQTextEdit(nil)
req.resultsEdit.SetReadOnly(true)
layout.AddWidget(req.resultsEdit, 0, 0)
sendButton.ConnectClicked(func(checked bool) {
req.subscribe()
})
return tab
}
func updateReq() {
req.filter = nostr.Filter{}
// collect authors
authors := []nostr.PubKey{}
for _, edit := range req.authorsEdits {
if pk, err := nostr.PubKeyFromHex(strings.TrimSpace(edit.Text())); err == nil {
authors = append(authors, pk)
}
}
if len(authors) > 0 {
req.filter.Authors = authors
}
// collect ids
ids := []nostr.ID{}
for _, edit := range req.idsEdits {
if id, err := nostr.IDFromHex(strings.TrimSpace(edit.Text())); err == nil {
ids = append(ids, id)
}
}
if len(ids) > 0 {
req.filter.IDs = ids
}
// collect kinds
kinds := []nostr.Kind{}
for _, edit := range req.kindsEdits {
text := strings.TrimSpace(edit.Text())
if k, err := strconv.Atoi(text); err == nil {
kinds = append(kinds, nostr.Kind(k))
}
}
if len(kinds) > 0 {
req.filter.Kinds = kinds
}
// update kind labels
for i, kind := range kinds {
if i < len(req.kindsLabels) {
name := kind.Name()
if name != "unknown" {
req.kindsLabels[i].SetText(name)
} else {
req.kindsLabels[i].SetText("")
}
}
}
for i := len(kinds); i < len(req.kindsLabels); i++ {
req.kindsLabels[i].SetText("")
}
// since
if req.sinceEdit.DateTime().IsValid() {
ts := nostr.Timestamp(req.sinceEdit.DateTime().ToMSecsSinceEpoch() / 1000)
req.filter.Since = ts
}
// until
if req.untilEdit.DateTime().IsValid() {
ts := nostr.Timestamp(req.untilEdit.DateTime().ToMSecsSinceEpoch() / 1000)
req.filter.Until = ts
}
// limit
if req.limitSpin.Value() > 0 {
req.filter.Limit = req.limitSpin.Value()
}
jsonBytes, _ := json.Marshal(req.filter)
req.outputEdit.SetPlainText(string(jsonBytes))
}
func (req reqVars) subscribe() {
// collect relays
relays := []string{}
for _, edit := range req.relaysEdits {
url := strings.TrimSpace(edit.Text())
if url != "" {
relays = append(relays, url)
}
}
if len(relays) == 0 {
statusLabel.SetText("no relays specified")
return
}
// subscribe
statusLabel.SetText("subscribed to " + strings.Join(relays, " "))
eoseChan := make(chan struct{})
eventsChan := sys.Pool.SubscribeManyNotifyEOSE(ctx, relays, req.filter, eoseChan, nostr.SubscriptionOptions{
Label: "nakv-req",
})
// collect events
go func() {
eosed := false
for {
select {
case ie, ok := <-eventsChan:
if !ok {
statusLabel.SetText("subscription ended")
return
}
jsonBytes, _ := json.Marshal(ie.Event)
if eosed {
req.resultsEdit.SetPlainText(string(jsonBytes) + "\n" + req.resultsEdit.ToPlainText())
} else {
req.resultsEdit.InsertPlainText("\n" + string(jsonBytes))
}
case <-eoseChan:
eosed = true
}
}
}()
}