mirror of
https://github.com/fiatjaf/vnak.git
synced 2026-07-22 07:48:24 +00:00
383 lines
10 KiB
Go
383 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"fiatjaf.com/nostr"
|
|
"fiatjaf.com/nostr/nip05"
|
|
"fiatjaf.com/nostr/nip19"
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
qt "github.com/mappu/miqt/qt6"
|
|
"github.com/mappu/miqt/qt6/mainthread"
|
|
)
|
|
|
|
type pasteVars struct {
|
|
inputEdit *qt.QTextEdit
|
|
outputVBox *qt.QVBoxLayout
|
|
|
|
nip05ctxCancel context.CancelFunc
|
|
nip05ctxAbort error
|
|
}
|
|
|
|
var paste = &pasteVars{
|
|
nip05ctxAbort: errors.New("aborted"),
|
|
}
|
|
|
|
func setupPasteTab() *qt.QWidget {
|
|
tab := qt.NewQWidget(window.QWidget)
|
|
layout := qt.NewQVBoxLayout2()
|
|
tab.SetLayout(layout.QLayout)
|
|
|
|
// input
|
|
inputLabel := qt.NewQLabel2()
|
|
inputLabel.SetText("paste an event, nevent, npub, nip05, filter, naddr or other things:")
|
|
layout.AddWidget(inputLabel.QWidget)
|
|
paste.inputEdit = qt.NewQTextEdit(tab)
|
|
layout.AddWidget(paste.inputEdit.QWidget)
|
|
paste.inputEdit.OnTextChanged(paste.updatePaste)
|
|
|
|
// output
|
|
paste.outputVBox = qt.NewQVBoxLayout2()
|
|
layout.AddLayout(paste.outputVBox.QLayout)
|
|
|
|
return tab
|
|
}
|
|
|
|
func deleteLayoutRecursively(layout *qt.QLayout) {
|
|
for layout.Count() > 0 {
|
|
layoutItem := layout.ItemAt(0)
|
|
if w := layoutItem.Widget(); w != nil {
|
|
w.DeleteLater()
|
|
} else if subLayout := layoutItem.Layout(); subLayout != nil {
|
|
deleteLayoutRecursively(subLayout)
|
|
}
|
|
layout.RemoveItem(layoutItem)
|
|
}
|
|
layout.DeleteLater()
|
|
}
|
|
|
|
func (paste *pasteVars) updatePaste() {
|
|
// clear previous output
|
|
for paste.outputVBox.Count() > 0 {
|
|
item := paste.outputVBox.ItemAt(0)
|
|
if widget := item.Widget(); widget != nil {
|
|
widget.DeleteLater()
|
|
} else if layout := item.Layout(); layout != nil {
|
|
deleteLayoutRecursively(layout)
|
|
}
|
|
paste.outputVBox.RemoveItem(item)
|
|
}
|
|
|
|
// process current
|
|
text := strings.TrimSpace(paste.inputEdit.ToPlainText())
|
|
if text == "" {
|
|
return
|
|
}
|
|
|
|
// try nip19 decode
|
|
if prefix, decoded, err := nip19.Decode(text); err == nil {
|
|
paste.displayNip19Decoded(prefix, decoded)
|
|
return
|
|
}
|
|
|
|
// try nip05
|
|
if nip05.IsValidIdentifier(text) {
|
|
debounced.Call(func() {
|
|
if nip05.IsValidIdentifier(text) {
|
|
paste.displayNip05(text)
|
|
}
|
|
})
|
|
return
|
|
}
|
|
|
|
// try JSON event
|
|
var event nostr.Event
|
|
if err := json.Unmarshal([]byte(text), &event); err == nil && (event.ID != nostr.ZeroID || event.Kind != 0 || event.CreatedAt != 0 || event.Content != "" || event.Tags != nil || event.PubKey != nostr.ZeroPK) {
|
|
paste.displayEventButton(event)
|
|
return
|
|
}
|
|
|
|
// try JSON filter
|
|
var filter nostr.Filter
|
|
if err := json.Unmarshal([]byte(text), &filter); err == nil {
|
|
paste.displayFilterButton(filter)
|
|
return
|
|
}
|
|
|
|
// if nothing worked, show error
|
|
errorLabel := qt.NewQLabel2()
|
|
errorLabel.SetText("could not decode input")
|
|
paste.outputVBox.AddWidget(errorLabel.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayNip19Decoded(prefix string, decoded interface{}) {
|
|
switch prefix {
|
|
case "nsec":
|
|
if sk, ok := decoded.(nostr.SecretKey); ok {
|
|
p.displayNsec(sk)
|
|
}
|
|
case "npub":
|
|
if pk, ok := decoded.(nostr.PubKey); ok {
|
|
p.displayPubKey(pk)
|
|
}
|
|
case "note":
|
|
if id, ok := decoded.(nostr.ID); ok {
|
|
p.displayEventID(id)
|
|
}
|
|
case "nevent":
|
|
if ep, ok := decoded.(nostr.EventPointer); ok {
|
|
p.displayEventPointer(ep)
|
|
p.displayPointerTag(ep)
|
|
}
|
|
case "nprofile":
|
|
if pp, ok := decoded.(nostr.ProfilePointer); ok {
|
|
p.displayProfilePointer(pp)
|
|
p.displayPointerTag(pp)
|
|
}
|
|
case "naddr":
|
|
if ap, ok := decoded.(nostr.EntityPointer); ok {
|
|
p.displayAddressPointer(ap)
|
|
p.displayPointerTag(ap)
|
|
}
|
|
default:
|
|
label := qt.NewQLabel2()
|
|
label.SetText(fmt.Sprintf("decoded %s: %v", prefix, decoded))
|
|
p.outputVBox.AddWidget(label.QWidget)
|
|
}
|
|
}
|
|
|
|
func (p *pasteVars) displayNsec(sk nostr.SecretKey) {
|
|
// nsec
|
|
nsecLabel := qt.NewQLabel2()
|
|
nsecLabel.SetText("nsec:")
|
|
p.outputVBox.AddWidget(nsecLabel.QWidget)
|
|
nsec := nip19.EncodeNsec(sk)
|
|
nsecEdit := qt.NewQLineEdit(window.QWidget)
|
|
nsecEdit.SetText(nsec)
|
|
nsecEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(nsecEdit.QWidget)
|
|
|
|
// hex
|
|
hexLabel := qt.NewQLabel2()
|
|
hexLabel.SetText("hex:")
|
|
p.outputVBox.AddWidget(hexLabel.QWidget)
|
|
hexEdit := qt.NewQLineEdit(window.QWidget)
|
|
hexEdit.SetText(sk.Hex())
|
|
hexEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(hexEdit.QWidget)
|
|
|
|
// npub
|
|
npubLabel := qt.NewQLabel2()
|
|
npubLabel.SetText("corresponding npub:")
|
|
p.outputVBox.AddWidget(npubLabel.QWidget)
|
|
_, pub := btcec.PrivKeyFromBytes(sk[:])
|
|
pk := nostr.PubKey(pub.SerializeCompressed()[1:])
|
|
npub := nip19.EncodeNpub(pk)
|
|
npubEdit := qt.NewQLineEdit(window.QWidget)
|
|
npubEdit.SetText(npub)
|
|
npubEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(npubEdit.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayPubKey(pk nostr.PubKey) {
|
|
npubLabel := qt.NewQLabel2()
|
|
npubLabel.SetText("npub:")
|
|
p.outputVBox.AddWidget(npubLabel.QWidget)
|
|
npub := nip19.EncodeNpub(pk)
|
|
npubEdit := qt.NewQLineEdit(window.QWidget)
|
|
npubEdit.SetText(npub)
|
|
npubEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(npubEdit.QWidget)
|
|
|
|
hexLabel := qt.NewQLabel2()
|
|
hexLabel.SetText("hex:")
|
|
p.outputVBox.AddWidget(hexLabel.QWidget)
|
|
hexEdit := qt.NewQLineEdit(window.QWidget)
|
|
hexEdit.SetText(pk.Hex())
|
|
hexEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(hexEdit.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayEventID(id nostr.ID) {
|
|
hexLabel := qt.NewQLabel2()
|
|
hexLabel.SetText("id (hex):")
|
|
p.outputVBox.AddWidget(hexLabel.QWidget)
|
|
hexEdit := qt.NewQLineEdit(window.QWidget)
|
|
hexEdit.SetText(id.Hex())
|
|
hexEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(hexEdit.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayPointerTag(pointer nostr.Pointer) {
|
|
tagLabel := qt.NewQLabel2()
|
|
tagLabel.SetText("tag reference:")
|
|
p.outputVBox.AddWidget(tagLabel.QWidget)
|
|
tagEdit := qt.NewQLineEdit(window.QWidget)
|
|
tagj, _ := json.Marshal(pointer.AsTag())
|
|
tagEdit.SetText(string(tagj))
|
|
tagEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(tagEdit.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayEventPointer(ep nostr.EventPointer) {
|
|
p.displayEventID(ep.ID)
|
|
if ep.Author != nostr.ZeroPK {
|
|
authorLabel := qt.NewQLabel2()
|
|
authorLabel.SetText("author:")
|
|
p.outputVBox.AddWidget(authorLabel.QWidget)
|
|
authorEdit := qt.NewQLineEdit(window.QWidget)
|
|
authorEdit.SetText(ep.Author.Hex())
|
|
authorEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(authorEdit.QWidget)
|
|
}
|
|
p.displayRelays(ep.Relays)
|
|
|
|
button := qt.NewQPushButton5("filter "+ARROW, window.QWidget)
|
|
button.OnClicked(func() {
|
|
filter := nostr.Filter{
|
|
IDs: []nostr.ID{ep.ID},
|
|
}
|
|
if ep.Author != nostr.ZeroPK {
|
|
filter.Authors = []nostr.PubKey{ep.Author}
|
|
}
|
|
req.populate(filter, ep.Relays)
|
|
tabWidget.SetCurrentIndex(tabs.req)
|
|
})
|
|
p.outputVBox.AddWidget(button.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayProfilePointer(pp nostr.ProfilePointer) {
|
|
p.displayPubKey(pp.PublicKey)
|
|
p.displayRelays(pp.Relays)
|
|
|
|
button := qt.NewQPushButton5("filter "+ARROW, window.QWidget)
|
|
button.OnClicked(func() {
|
|
filter := nostr.Filter{
|
|
Authors: []nostr.PubKey{pp.PublicKey},
|
|
}
|
|
req.populate(filter, pp.Relays)
|
|
tabWidget.SetCurrentIndex(tabs.req)
|
|
})
|
|
p.outputVBox.AddWidget(button.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayRelays(relays []string) {
|
|
if len(relays) > 0 {
|
|
relaysLabel := qt.NewQLabel2()
|
|
relaysLabel.SetText("relays:")
|
|
p.outputVBox.AddWidget(relaysLabel.QWidget)
|
|
relaysVBox := qt.NewQVBoxLayout2()
|
|
for i := 0; i < len(relays); i += 5 {
|
|
rowHBox := qt.NewQHBoxLayout2()
|
|
for j := 0; j < 5 && i+j < len(relays); j++ {
|
|
relayEdit := qt.NewQLineEdit(window.QWidget)
|
|
relayEdit.SetText(relays[i+j])
|
|
relayEdit.SetReadOnly(true)
|
|
rowHBox.AddWidget(relayEdit.QWidget)
|
|
}
|
|
relaysVBox.AddLayout(rowHBox.QLayout)
|
|
}
|
|
p.outputVBox.AddLayout(relaysVBox.QLayout)
|
|
}
|
|
}
|
|
|
|
func (p *pasteVars) displayAddressPointer(ap nostr.EntityPointer) {
|
|
kindLabel := qt.NewQLabel2()
|
|
kindLabel.SetText("kind:")
|
|
p.outputVBox.AddWidget(kindLabel.QWidget)
|
|
kindEdit := qt.NewQLineEdit(window.QWidget)
|
|
kindEdit.SetText(fmt.Sprintf("%d", ap.Kind))
|
|
kindEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(kindEdit.QWidget)
|
|
|
|
p.displayPubKey(ap.PublicKey)
|
|
|
|
identifierLabel := qt.NewQLabel2()
|
|
identifierLabel.SetText("identifier:")
|
|
p.outputVBox.AddWidget(identifierLabel.QWidget)
|
|
identifierEdit := qt.NewQLineEdit(window.QWidget)
|
|
identifierEdit.SetText(ap.Identifier)
|
|
identifierEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(identifierEdit.QWidget)
|
|
|
|
p.displayRelays(ap.Relays)
|
|
|
|
button := qt.NewQPushButton5("filter "+ARROW, window.QWidget)
|
|
button.OnClicked(func() {
|
|
filter := nostr.Filter{
|
|
Kinds: []nostr.Kind{ap.Kind},
|
|
Authors: []nostr.PubKey{ap.PublicKey},
|
|
Tags: map[string][]string{"d": {ap.Identifier}},
|
|
}
|
|
req.populate(filter, ap.Relays)
|
|
tabWidget.SetCurrentIndex(tabs.req)
|
|
})
|
|
p.outputVBox.AddWidget(button.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayNip05(identifier string) {
|
|
mainthread.Wait(func() {
|
|
label := qt.NewQLabel2()
|
|
label.SetText("nip05: " + identifier)
|
|
p.outputVBox.AddWidget(label.QWidget)
|
|
})
|
|
|
|
// try to query
|
|
if paste.nip05ctxCancel != nil {
|
|
paste.nip05ctxCancel()
|
|
}
|
|
nip05ctx, cancel := context.WithTimeoutCause(ctx, time.Second*3, paste.nip05ctxAbort)
|
|
paste.nip05ctxCancel = cancel
|
|
defer cancel()
|
|
pp, err := nip05.QueryIdentifier(nip05ctx, identifier)
|
|
if err != nil {
|
|
if err == paste.nip05ctxAbort || context.Cause(nip05ctx) == paste.nip05ctxAbort {
|
|
return
|
|
}
|
|
mainthread.Wait(func() {
|
|
errorLabel := qt.NewQLabel2()
|
|
errorLabel.SetText("failed to query nip05: " + err.Error())
|
|
p.outputVBox.AddWidget(errorLabel.QWidget)
|
|
})
|
|
return
|
|
}
|
|
|
|
mainthread.Wait(func() {
|
|
nprofileLabel := qt.NewQLabel2()
|
|
nprofileLabel.SetText("nprofile")
|
|
p.outputVBox.AddWidget(nprofileLabel.QWidget)
|
|
nprofileEdit := qt.NewQLineEdit(window.QWidget)
|
|
nprofileEdit.SetText(nip19.EncodeNprofile(pp.PublicKey, pp.Relays))
|
|
nprofileEdit.SetReadOnly(true)
|
|
p.outputVBox.AddWidget(nprofileEdit.QWidget)
|
|
|
|
p.displayProfilePointer(*pp)
|
|
p.displayPointerTag(*pp)
|
|
})
|
|
}
|
|
|
|
func (p *pasteVars) displayEventButton(evt nostr.Event) {
|
|
button := qt.NewQPushButton5("event "+ARROW, window.QWidget)
|
|
button.OnClicked(func() {
|
|
event.populate(evt)
|
|
// switch to event tab
|
|
tabWidget.SetCurrentIndex(tabs.event)
|
|
})
|
|
p.outputVBox.AddWidget(button.QWidget)
|
|
}
|
|
|
|
func (p *pasteVars) displayFilterButton(filter nostr.Filter) {
|
|
button := qt.NewQPushButton5("filter "+ARROW, window.QWidget)
|
|
button.OnClicked(func() {
|
|
req.populate(filter, []string{})
|
|
// switch to req tab
|
|
tabWidget.SetCurrentIndex(tabs.req)
|
|
})
|
|
p.outputVBox.AddWidget(button.QWidget)
|
|
}
|