mirror of
https://github.com/fiatjaf/vnak.git
synced 2026-07-22 07:48:24 +00:00
build: variadic fields and other small things.
This commit is contained in:
397
build.go
397
build.go
@@ -5,15 +5,18 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/schema"
|
||||
qt "github.com/mappu/miqt/qt6"
|
||||
"github.com/mappu/miqt/qt6/mainthread"
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
type buildVars struct {
|
||||
@@ -33,6 +36,11 @@ type buildVars struct {
|
||||
contentLabel *qt.QLabel
|
||||
}
|
||||
|
||||
type listFuzzySource struct{ *qt.QListWidget }
|
||||
|
||||
func (l listFuzzySource) String(i int) string { return strings.ToLower(l.Item(i).Text()) }
|
||||
func (l listFuzzySource) Len() int { return l.Count() }
|
||||
|
||||
type kindItem struct {
|
||||
kind uint16
|
||||
schema.KindSchema
|
||||
@@ -48,6 +56,7 @@ type tagSection struct {
|
||||
rowsBox *qt.QVBoxLayout
|
||||
rows []*qt.QHBoxLayout
|
||||
edits [][]*qt.QLineEdit
|
||||
bool *qt.QCheckBox
|
||||
}
|
||||
|
||||
var build = &buildVars{}
|
||||
@@ -75,11 +84,23 @@ func setupBuildTab() *qt.QWidget {
|
||||
// filter list items as user types
|
||||
searchInput.OnTextChanged(func(text string) {
|
||||
filterText := strings.ToLower(text)
|
||||
for i := 0; i < build.kindsList.Count(); i++ {
|
||||
item := build.kindsList.Item(i)
|
||||
itemText := strings.ToLower(item.Text())
|
||||
// TODO: instead of strings.Contains() use levenshtein distance fuzz matching
|
||||
build.kindsList.SetRowHidden(i, !strings.Contains(itemText, filterText))
|
||||
matches := fuzzy.FindFrom(filterText, listFuzzySource{build.kindsList})
|
||||
|
||||
if filterText == "" {
|
||||
// show all
|
||||
for i := 0; i < build.kindsList.Count(); i++ {
|
||||
build.kindsList.SetRowHidden(i, false)
|
||||
}
|
||||
} else {
|
||||
// hide all
|
||||
for i := 0; i < build.kindsList.Count(); i++ {
|
||||
build.kindsList.SetRowHidden(i, true)
|
||||
}
|
||||
|
||||
// except the matched
|
||||
for _, match := range matches {
|
||||
build.kindsList.SetRowHidden(match.Index, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -115,16 +136,64 @@ func setupBuildTab() *qt.QWidget {
|
||||
continueButton.OnClicked(func() {
|
||||
eventBuilt := build.buildEvent()
|
||||
|
||||
// TODO: check for missing tags in event
|
||||
// check for missing required tags
|
||||
missing := []string{}
|
||||
if len(missing) > 0 {
|
||||
setStatus(tabs.build, "missing required tags: %s", strings.Join(missing, ", "))
|
||||
return
|
||||
for _, ts := range build.tagSections {
|
||||
if ts.isRequired {
|
||||
tagName := strings.TrimSuffix(
|
||||
strings.SplitN(ts.label.Text(), " ", 2)[0],
|
||||
":",
|
||||
)
|
||||
|
||||
hasValidTag := false
|
||||
if ts.isBoolean {
|
||||
hasValidTag = ts.bool != nil && ts.bool.CheckState() == qt.Checked
|
||||
} else {
|
||||
for _, edits := range ts.edits {
|
||||
hasValidValue := false
|
||||
for _, edit := range edits {
|
||||
if strings.TrimSpace(edit.Text()) != "" {
|
||||
hasValidValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasValidValue {
|
||||
hasValidTag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasValidTag {
|
||||
missing = append(missing, tagName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check each tag to see if it has all the necessary items and they match each of the types
|
||||
// ('constrained', 'url', 'relay', 'id', 'addr', 'pubkey', 'timestamp', 'kind', 'giturl'. 'json', 'hex')
|
||||
// ...
|
||||
if len(missing) > 0 {
|
||||
// show validation failure alert
|
||||
msgBox := qt.NewQMessageBox2()
|
||||
msgBox.SetWindowTitle("validation failed")
|
||||
msgBox.SetText(fmt.Sprintf("missing required tags: %s", strings.Join(missing, ", ")))
|
||||
msgBox.SetInformativeText("you can continue to manual event editing or cancel to fix the issues.")
|
||||
msgBox.SetStandardButtons(qt.QMessageBox__Ok | qt.QMessageBox__Cancel)
|
||||
if msgBox.Exec() == int(qt.QMessageBox__Cancel) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check each tag to see if it has all the necessary items and they match each of the types
|
||||
validationErrors := build.validateTags()
|
||||
if len(validationErrors) > 0 {
|
||||
msgBox := qt.NewQMessageBox2()
|
||||
msgBox.SetWindowTitle("tag validation errors")
|
||||
msgBox.SetText(strings.Join(validationErrors, "\n"))
|
||||
msgBox.SetInformativeText("you can continue to manual event editing or cancel to fix the issues")
|
||||
msgBox.SetStandardButtons(qt.QMessageBox__Ok | qt.QMessageBox__Cancel)
|
||||
if msgBox.Exec() == int(qt.QMessageBox__Cancel) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
event.populate(eventBuilt)
|
||||
tabWidget.SetCurrentIndex(tabs.event)
|
||||
@@ -137,7 +206,7 @@ func setupBuildTab() *qt.QWidget {
|
||||
go func() {
|
||||
sch, err := schema.FetchSchemaFromURL("https://raw.githubusercontent.com/nostr-protocol/registry-of-kinds/refs/heads/master/schema.yaml")
|
||||
if err != nil {
|
||||
fmt.Println("error loading schema:", err)
|
||||
fmt.Fprintf(os.Stderr, "error loading schema:", err)
|
||||
mainthread.Wait(func() {
|
||||
setStatus(tabs.build, "error loading schema: %s", err)
|
||||
})
|
||||
@@ -173,6 +242,17 @@ func setupBuildTab() *qt.QWidget {
|
||||
// show description
|
||||
build.descLabel.SetText(item.Description)
|
||||
|
||||
// all addressable events need a 'd' tag
|
||||
if nostr.Kind(kindItem.kind).IsAddressable() {
|
||||
build.addTag(&schema.TagSpec{
|
||||
Name: "d",
|
||||
Next: &schema.ContentSpec{
|
||||
Type: "free",
|
||||
Required: true,
|
||||
},
|
||||
}, true, false)
|
||||
}
|
||||
|
||||
// generate tag inputs based on schema
|
||||
for _, tagSpec := range item.Tags {
|
||||
isMultiple := slices.Contains(item.Multiple, tagSpec.Name)
|
||||
@@ -227,23 +307,27 @@ func (b *buildVars) addTag(tagSpec *schema.TagSpec, required bool, isMultiple bo
|
||||
vbox := qt.NewQVBoxLayout2()
|
||||
ts.rowsBox = vbox
|
||||
ts.sectionBox.AddLayout(vbox.Layout())
|
||||
build.addTagRow(0, ts, tagSpec)
|
||||
build.addTagRow(ts, tagSpec, 0)
|
||||
} else {
|
||||
// boolean tag - add checkbox
|
||||
ts.bool = qt.NewQCheckBox2()
|
||||
ts.sectionBox.AddWidget(ts.bool.QWidget)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
gitURLRegex = regexp.MustCompile(`^(git@[\w.-]+:[\w./-]+(\.git)?|https?://[\w.-]+/[\w./-]+(\.git)?)$`)
|
||||
gitURLRegex = regexp.MustCompile(`^(git@[\w.-]+:[\w./-]+(\.git)?|(https?|git|ssh)://[\w.-]+/[\w./-]+(\.git)?)$`)
|
||||
addrRegex = regexp.MustCompile(`^\d+:[0-9a-fA-F]{64}:.+$`)
|
||||
dateRegex = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)
|
||||
)
|
||||
|
||||
func validateTagInput(text string, inputType string, either []string) bool {
|
||||
func validateTagInput(text string, spec *schema.ContentSpec) bool {
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return true // empty is always valid (required check is separate)
|
||||
}
|
||||
|
||||
switch inputType {
|
||||
switch spec.Type {
|
||||
case "free":
|
||||
return true
|
||||
|
||||
@@ -252,7 +336,7 @@ func validateTagInput(text string, inputType string, either []string) bool {
|
||||
return err == nil && n >= 0
|
||||
|
||||
case "constrained":
|
||||
return slices.Contains(either, text)
|
||||
return slices.Contains(spec.Either, text)
|
||||
|
||||
case "hex":
|
||||
_, err := hex.DecodeString(text)
|
||||
@@ -297,70 +381,97 @@ func validateTagInput(text string, inputType string, either []string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *buildVars) addTagRow(i int, ts *tagSection, tagSpec *schema.TagSpec) {
|
||||
func (b *buildVars) addTagRow(ts *tagSection, tagSpec *schema.TagSpec, rowIdx int) {
|
||||
hbox := qt.NewQHBoxLayout2()
|
||||
ts.rowsBox.AddLayout(hbox.QLayout)
|
||||
ts.rows = append(ts.rows, hbox)
|
||||
ts.edits = append(ts.edits, make([]*qt.QLineEdit, 0, 3))
|
||||
|
||||
for curr := tagSpec.Next; curr != nil; curr = curr.Next {
|
||||
// input for the tag value
|
||||
edit := qt.NewQLineEdit(b.tab)
|
||||
switch curr.Type {
|
||||
case "constrained":
|
||||
edit.SetPlaceholderText("\"" + strings.Join(curr.Either, "\" or \"") + "\"")
|
||||
default:
|
||||
edit.SetPlaceholderText(curr.Type)
|
||||
if ts.isBoolean {
|
||||
// TODO: in this case only add one field: a checkbox, save it to ts.bool
|
||||
} else {
|
||||
itemIdx := 0
|
||||
for curr := tagSpec.Next; curr != nil; curr = curr.Next {
|
||||
b.addInput(ts, tagSpec, rowIdx, hbox, curr, itemIdx)
|
||||
itemIdx++
|
||||
}
|
||||
hbox.AddWidget(edit.QWidget)
|
||||
ts.edits[i] = append(ts.edits[i], edit)
|
||||
}
|
||||
}
|
||||
|
||||
inputType := curr.Type
|
||||
eitherValues := curr.Either
|
||||
func (b *buildVars) addInput(
|
||||
ts *tagSection,
|
||||
tagSpec *schema.TagSpec,
|
||||
rowIdx int,
|
||||
row *qt.QHBoxLayout,
|
||||
itemSpec *schema.ContentSpec,
|
||||
itemIdx int,
|
||||
) {
|
||||
// input for the tag value
|
||||
edit := qt.NewQLineEdit(b.tab)
|
||||
configureInputForType(edit, itemSpec)
|
||||
|
||||
edit.OnTextChanged(func(text string) {
|
||||
// validation
|
||||
if validateTagInput(text, inputType, eitherValues) {
|
||||
edit.SetStyleSheet("")
|
||||
} else {
|
||||
edit.SetStyleSheet("border: 1px solid red;")
|
||||
row.AddWidget(edit.QWidget)
|
||||
ts.edits[rowIdx] = append(ts.edits[rowIdx], edit)
|
||||
|
||||
edit.OnTextChanged(func(text string) {
|
||||
if !ts.isMultiple {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(text) != "" {
|
||||
// if this is the last row and it has content and this tag accepts multiple values and add a new row
|
||||
if rowIdx == ts.rowsBox.Count()-1 {
|
||||
b.addTagRow(ts, tagSpec, rowIdx+1)
|
||||
}
|
||||
|
||||
if !ts.isMultiple {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(text) != "" {
|
||||
// if this is the last row and it has content and this tag accepts multiple values and add a new row
|
||||
if i == len(ts.rowsBox.Children())-1 {
|
||||
b.addTagRow(i+1, ts, tagSpec)
|
||||
}
|
||||
} else {
|
||||
// now if this is the second to last row and the last row and it is fully empty we remove the last
|
||||
if len(ts.rows) >= 2 && i == len(ts.rows)-2 {
|
||||
shouldDelete := true
|
||||
// if all inputs are empty in the last two rows we will delete
|
||||
deletecheck:
|
||||
for r := len(ts.rows) - 2; r < len(ts.edits); r++ {
|
||||
for _, edit := range ts.edits[r] {
|
||||
if strings.TrimSpace(edit.Text()) != "" {
|
||||
shouldDelete = false
|
||||
break deletecheck
|
||||
}
|
||||
} else {
|
||||
// now if this is the second to last row and the last row and it is fully empty we remove the last
|
||||
if len(ts.rows) >= 2 && rowIdx == len(ts.rows)-2 {
|
||||
shouldDelete := true
|
||||
// if all inputs are empty in the last two rows we will delete
|
||||
deletecheck:
|
||||
for r := len(ts.rows) - 2; r < len(ts.edits); r++ {
|
||||
for _, edit := range ts.edits[r] {
|
||||
if strings.TrimSpace(edit.Text()) != "" {
|
||||
shouldDelete = false
|
||||
break deletecheck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shouldDelete {
|
||||
last := ts.rows[len(ts.rows)-1]
|
||||
for _, edit := range ts.edits[len(ts.rows)-1] {
|
||||
last.RemoveWidget(edit.QWidget)
|
||||
edit.DeleteLater()
|
||||
}
|
||||
ts.edits = ts.edits[0 : len(ts.edits)-1]
|
||||
if shouldDelete {
|
||||
last := ts.rows[len(ts.rows)-1]
|
||||
for _, edit := range ts.edits[len(ts.rows)-1] {
|
||||
last.RemoveWidget(edit.QWidget)
|
||||
edit.DeleteLater()
|
||||
}
|
||||
ts.edits = ts.edits[0 : len(ts.edits)-1]
|
||||
|
||||
ts.rowsBox.RemoveItem(last.QLayoutItem)
|
||||
last.DeleteLater()
|
||||
ts.rows = ts.rows[0 : len(ts.rows)-1]
|
||||
ts.rowsBox.RemoveItem(last.QLayoutItem)
|
||||
last.DeleteLater()
|
||||
ts.rows = ts.rows[0 : len(ts.rows)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if itemSpec.Variadic {
|
||||
// variadic field - add dynamic add/remove functionality
|
||||
edit.OnTextChanged(func(text string) {
|
||||
if strings.TrimSpace(text) != "" {
|
||||
// if this is the last edit and has content, add a new one
|
||||
lastRowIdx := len(ts.edits) - 1
|
||||
lastEditIdx := len(ts.edits[lastRowIdx]) - 1
|
||||
if itemIdx == lastEditIdx {
|
||||
b.addInput(ts, tagSpec, rowIdx, row, itemSpec, itemIdx+1)
|
||||
}
|
||||
} else {
|
||||
// if this is second to last and the last one is empty delete the last
|
||||
if itemIdx == len(ts.edits[rowIdx])-2 {
|
||||
if strings.TrimSpace(ts.edits[rowIdx][itemIdx+1].Text()) == "" {
|
||||
edit := ts.edits[rowIdx][itemIdx+1]
|
||||
row.RemoveWidget(edit.QWidget)
|
||||
edit.DeleteLater()
|
||||
ts.edits[rowIdx] = ts.edits[rowIdx][:itemIdx+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,21 +496,27 @@ func (b *buildVars) buildEvent() nostr.Event {
|
||||
":",
|
||||
)
|
||||
|
||||
for e, edits := range ts.edits {
|
||||
tag := make(nostr.Tag, 1, 4)
|
||||
tag[0] = tagName
|
||||
|
||||
hasValue := false
|
||||
for _, edit := range edits {
|
||||
value := strings.TrimSpace(edit.Text())
|
||||
tag = append(tag, value)
|
||||
if value != "" {
|
||||
hasValue = true
|
||||
}
|
||||
if ts.isBoolean {
|
||||
if ts.bool.CheckState() == qt.Checked {
|
||||
base.Tags = append(base.Tags, nostr.Tag{tagName})
|
||||
}
|
||||
} else {
|
||||
for e, edits := range ts.edits {
|
||||
tag := make(nostr.Tag, 1, 4)
|
||||
tag[0] = tagName
|
||||
|
||||
if ts.isBoolean || (ts.isRequired && e == 0) || hasValue {
|
||||
base.Tags = append(base.Tags, tag)
|
||||
hasValue := false
|
||||
for _, edit := range edits {
|
||||
value := strings.TrimSpace(edit.Text())
|
||||
tag = append(tag, value)
|
||||
if value != "" {
|
||||
hasValue = true
|
||||
}
|
||||
}
|
||||
|
||||
if ts.isBoolean || (ts.isRequired && e == 0) || hasValue {
|
||||
base.Tags = append(base.Tags, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,21 +524,84 @@ func (b *buildVars) buildEvent() nostr.Event {
|
||||
return base
|
||||
}
|
||||
|
||||
func (b *buildVars) validateTags() []string {
|
||||
var errors []string
|
||||
|
||||
for _, ts := range b.tagSections {
|
||||
tagName := strings.TrimSuffix(
|
||||
strings.SplitN(ts.label.Text(), " ", 2)[0],
|
||||
":",
|
||||
)
|
||||
|
||||
if ts.isBoolean {
|
||||
// boolean tags don't need type validation
|
||||
continue
|
||||
}
|
||||
|
||||
for rowIdx, edits := range ts.edits {
|
||||
for itemIdx, edit := range edits {
|
||||
text := strings.TrimSpace(edit.Text())
|
||||
if text == "" {
|
||||
continue // skip empty fields
|
||||
}
|
||||
|
||||
// find the tag spec for this tag
|
||||
for _, tagSpec := range b.selected.Tags {
|
||||
// try only tags with matching names, of course
|
||||
if tagSpec.Name != tagName {
|
||||
continue
|
||||
}
|
||||
|
||||
// navigate to the correct position in the spec
|
||||
curr := tagSpec.Next
|
||||
for range itemIdx {
|
||||
if curr.Variadic {
|
||||
// if we reach a variadic that's the end of it, all edits further on will be of this type
|
||||
break
|
||||
}
|
||||
|
||||
if curr.Next == nil {
|
||||
// this should only happen when we are the last iteration step anyway,
|
||||
// but let's exit here instead of crashing
|
||||
break
|
||||
}
|
||||
|
||||
curr = curr.Next
|
||||
}
|
||||
|
||||
// validate against expected type
|
||||
if !validateTagInput(text, curr) {
|
||||
errors = append(errors, fmt.Sprintf("%s[%d][%d]: invalid value for type '%s'", tagName, rowIdx, itemIdx, curr.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (b *buildVars) clearForm() {
|
||||
// clear all tag layouts and inputs
|
||||
for _, ts := range b.tagSections {
|
||||
ts.sectionBox.RemoveWidget(ts.label.QWidget)
|
||||
ts.label.DeleteLater()
|
||||
|
||||
for r, row := range ts.rows {
|
||||
for _, edit := range ts.edits[r] {
|
||||
row.RemoveWidget(edit.QWidget)
|
||||
edit.DeleteLater()
|
||||
if ts.isBoolean {
|
||||
ts.sectionBox.RemoveWidget(ts.bool.QWidget)
|
||||
ts.bool.DeleteLater()
|
||||
} else {
|
||||
for r, row := range ts.rows {
|
||||
for _, edit := range ts.edits[r] {
|
||||
row.RemoveWidget(edit.QWidget)
|
||||
edit.DeleteLater()
|
||||
}
|
||||
ts.rowsBox.RemoveItem(row.QLayoutItem)
|
||||
row.DeleteLater()
|
||||
}
|
||||
ts.rowsBox.RemoveItem(row.QLayoutItem)
|
||||
row.DeleteLater()
|
||||
ts.sectionBox.RemoveItem(ts.rowsBox.QLayoutItem)
|
||||
ts.rowsBox.DeleteLater()
|
||||
}
|
||||
ts.rowsBox.DeleteLater()
|
||||
|
||||
b.formLayout.RemoveItem(ts.sectionBox.QLayoutItem)
|
||||
ts.sectionBox.DeleteLater()
|
||||
@@ -431,3 +611,42 @@ func (b *buildVars) clearForm() {
|
||||
// but keep the content just in case
|
||||
// b.contentEdit.SetPlainText("")
|
||||
}
|
||||
|
||||
func configureInputForType(edit *qt.QLineEdit, spec *schema.ContentSpec) {
|
||||
switch spec.Type {
|
||||
case "constrained":
|
||||
edit.SetPlaceholderText("\"" + strings.Join(spec.Either, "\" or \"") + "\"")
|
||||
case "pubkey":
|
||||
edit.SetPlaceholderText("pubkey: npub1... (hex or npub)")
|
||||
case "url":
|
||||
edit.SetPlaceholderText("url: https://example.com")
|
||||
case "relay":
|
||||
edit.SetPlaceholderText("relay url: wss://relay.example.com")
|
||||
case "event":
|
||||
edit.SetPlaceholderText("event id: nevent1... (hex or nevent)")
|
||||
case "hex":
|
||||
edit.SetPlaceholderText("hex: 0a1b2c3d...")
|
||||
case "timestamp":
|
||||
edit.SetPlaceholderText("unix timestamp: " + strconv.Itoa(int(nostr.Now())))
|
||||
case "kind":
|
||||
edit.SetPlaceholderText("kind: 1")
|
||||
case "addr":
|
||||
edit.SetPlaceholderText("address: <kind>:<hex-pubkey>:<identifier>")
|
||||
case "date":
|
||||
edit.SetPlaceholderText("date: " + time.Now().Format(time.DateOnly))
|
||||
case "giturl":
|
||||
edit.SetPlaceholderText("git url: https://gitserver.com/repo.git")
|
||||
case "json":
|
||||
edit.SetPlaceholderText("json: {\"key\": \"value\"}")
|
||||
default:
|
||||
edit.SetPlaceholderText(spec.Type)
|
||||
}
|
||||
|
||||
edit.OnTextChanged(func(text string) {
|
||||
if validateTagInput(text, spec) {
|
||||
edit.SetStyleSheet("")
|
||||
} else {
|
||||
edit.SetStyleSheet("border: 1px solid red;")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
6
go.mod
6
go.mod
@@ -4,11 +4,12 @@ go 1.25
|
||||
|
||||
require (
|
||||
fiatjaf.com/lib v0.3.2
|
||||
fiatjaf.com/nostr v0.0.0-20251126120447-7261a4b515ed
|
||||
fiatjaf.com/nostr v0.0.0-20260110125515-a5e52684e880
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||
github.com/mailru/easyjson v0.9.0
|
||||
github.com/mappu/miqt v0.12.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/sahilm/fuzzy v0.1.1
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
)
|
||||
|
||||
@@ -34,6 +35,7 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/liamg/magic v0.0.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -55,5 +57,3 @@ require (
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace fiatjaf.com/nostr => ../nostrlib
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,5 +1,7 @@
|
||||
fiatjaf.com/lib v0.3.2 h1:RBS41z70d8Rp8e2nemQsbPY1NLLnEGShiY2c+Bom3+Q=
|
||||
fiatjaf.com/lib v0.3.2/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8=
|
||||
fiatjaf.com/nostr v0.0.0-20260110125515-a5e52684e880 h1:WJpPdI0/5lKmtWN7EQE+C36wieDrjYSwvtSXYmKPMs8=
|
||||
fiatjaf.com/nostr v0.0.0-20260110125515-a5e52684e880/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
||||
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
||||
github.com/FastFilter/xorfilter v0.2.1/go.mod h1:aumvdkhscz6YBZF9ZA/6O4fIoNod4YR50kIVGGZ7l9I=
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=
|
||||
@@ -103,6 +105,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
|
||||
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
@@ -133,6 +137,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
||||
Reference in New Issue
Block a user