From 7254ae5aeac6776d2cab3f6b64b86b0875598113 Mon Sep 17 00:00:00 2001 From: rachel-mp4 Date: Fri, 26 Sep 2025 23:35:49 -0400 Subject: [PATCH] beepin and borpin --- README.md | 4 + diff.go | 233 ++++++++ go.mod | 37 +- go.sum | 131 ++++- lex/lexicons_cbor.go | 1207 ++++++++++++++++++++++++++++++++++++++++++ lex/types.go | 71 +++ main.go | 632 ++++++++++++++++++++-- 7 files changed, 2272 insertions(+), 43 deletions(-) create mode 100644 README.md create mode 100644 diff.go create mode 100644 lex/lexicons_cbor.go create mode 100644 lex/types.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..491ac33 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# tty!xcvr + +talk to you! transceiver is everyone's favorite ATP application, brought to +the teletypewriter! diff --git a/diff.go b/diff.go new file mode 100644 index 0000000..8e8a317 --- /dev/null +++ b/diff.go @@ -0,0 +1,233 @@ +package main + +import () + +type EditType = int + +const ( + EditAdd EditType = iota + EditKeep + EditDel + EditNil +) + +type Edit struct { + EditType EditType + Utf16Text []uint16 +} + +type Editstring struct { + EditType EditType + Text string +} + +type EditSegment struct { + weight int + aidx int + bidx int + parent *EditSegment +} + +type coordinate struct { + a int + b int +} + +type SegmentHeap struct { + segments []*EditSegment + searched map[coordinate]bool +} + +func NewSegmentHeap() SegmentHeap { + segments := make([]*EditSegment, 0, 10) + searched := make(map[coordinate]bool) + return SegmentHeap{segments, searched} +} + +func (h *SegmentHeap) Add(seg *EditSegment) { + searched := h.searched[coordinate{seg.aidx, seg.bidx}] + if searched { + return + } + h.segments = append(h.segments, seg) + h.searched[coordinate{seg.aidx, seg.bidx}] = true + h.siftUp(len(h.segments) - 1) +} + +func (h *SegmentHeap) PopFront() *EditSegment { + if len(h.segments) == 0 { + return nil + } + front := h.segments[0] + if len(h.segments) == 1 { + h.segments = nil + return front + } + h.segments[0] = h.segments[len(h.segments)-1] + h.segments = h.segments[:len(h.segments)-1] + h.siftDown(0) + return front +} + +func (h *SegmentHeap) siftUp(idx int) { + if idx == 0 { + return + } + loweridx := idx + upperidx := (idx - 1) / 2 + lower := h.segments[loweridx] + upper := h.segments[upperidx] + if lower.lighter(upper) { + h.segments[upperidx] = lower + h.segments[loweridx] = upper + h.siftUp(upperidx) + } +} + +func (h *SegmentHeap) siftDown(idx int) { + upperidx := idx + var swap *EditSegment + loweridx := idx*2 + 1 + lower2idx := idx*2 + 2 + if loweridx < len(h.segments) && h.segments[loweridx].lighter(h.segments[upperidx]) { + swap = h.segments[upperidx] + h.segments[upperidx] = h.segments[loweridx] + h.segments[loweridx] = swap + h.siftDown(loweridx) + return + } + if lower2idx < len(h.segments) && h.segments[lower2idx].lighter(h.segments[upperidx]) { + swap = h.segments[upperidx] + h.segments[upperidx] = h.segments[lower2idx] + h.segments[lower2idx] = swap + h.siftDown(lower2idx) + return + } + +} + +func (seg *EditSegment) lighter(A *EditSegment) bool { + if seg.weight < A.weight { + return true + } else if seg.weight > A.weight { + return false + } else { + return seg.aidx+seg.bidx > A.aidx+A.bidx + } +} + +// Diff calculates the diff between wordA and wordB as a miniaml slice of +// edits that you have to make to wordA so that you end up with wordB +func Diff(wordA []uint16, wordB []uint16) []Edit { + heap := NewSegmentHeap() + head := EditSegment{0, 0, 0, nil} + heap.Add(&head) + segment := heap.PopFront() + for !(segment.aidx == len(wordA) && segment.bidx == len(wordB)) { + if segment.aidx != len(wordA) && + segment.bidx != len(wordB) && + wordA[segment.aidx] == wordB[segment.bidx] { + newSegment := EditSegment{segment.weight, segment.aidx + 1, segment.bidx + 1, segment} + heap.Add(&newSegment) + } + if segment.aidx != len(wordA) { + newSegment := EditSegment{segment.weight + 1, segment.aidx + 1, segment.bidx, segment} + heap.Add(&newSegment) + } + if segment.bidx != len(wordB) { + newSegment := EditSegment{segment.weight + 1, segment.aidx, segment.bidx + 1, segment} + heap.Add(&newSegment) + } + segment = heap.PopFront() + } + prevSegment := segment.parent + edits := make([]Edit, 0) + currentEdit := Edit{EditNil, nil} + for prevSegment != nil { + diffA := prevSegment.aidx != segment.aidx + diffB := prevSegment.bidx != segment.bidx + var et EditType + var char uint16 + if diffA && diffB { + et = EditKeep + char = wordA[prevSegment.aidx] + } else if diffA { + et = EditDel + char = wordA[prevSegment.aidx] + } else if diffB { + et = EditAdd + char = wordB[prevSegment.bidx] + } else { + et = EditNil + } + if currentEdit.EditType != et { + if currentEdit.EditType != EditNil { + edits = append([]Edit{currentEdit}, edits...) + } + currentEdit = Edit{et, []uint16{char}} + } else { + currentEdit.Utf16Text = append([]uint16{char}, currentEdit.Utf16Text...) + } + segment = prevSegment + prevSegment = segment.parent + } + edits = append([]Edit{currentEdit}, edits...) + return edits +} + +func Diffs(wordA string, wordB string) []Editstring { + heap := NewSegmentHeap() + head := EditSegment{0, 0, 0, nil} + heap.Add(&head) + segment := heap.PopFront() + for !(segment.aidx == len(wordA) && segment.bidx == len(wordB)) { + if segment.aidx != len(wordA) && + segment.bidx != len(wordB) && + wordA[segment.aidx] == wordB[segment.bidx] { + newSegment := EditSegment{segment.weight, segment.aidx + 1, segment.bidx + 1, segment} + heap.Add(&newSegment) + } + if segment.aidx != len(wordA) { + newSegment := EditSegment{segment.weight + 1, segment.aidx + 1, segment.bidx, segment} + heap.Add(&newSegment) + } + if segment.bidx != len(wordB) { + newSegment := EditSegment{segment.weight + 1, segment.aidx, segment.bidx + 1, segment} + heap.Add(&newSegment) + } + segment = heap.PopFront() + } + prevSegment := segment.parent + edits := make([]Editstring, 0) + currentEdit := Editstring{EditNil, ""} + for prevSegment != nil { + diffA := prevSegment.aidx != segment.aidx + diffB := prevSegment.bidx != segment.bidx + var et EditType + var char string + if diffA && diffB { + et = EditKeep + char = string(wordA[prevSegment.aidx]) + } else if diffA { + et = EditDel + char = string(wordA[prevSegment.aidx]) + } else if diffB { + et = EditAdd + char = string(wordB[prevSegment.bidx]) + } else { + et = EditNil + } + if currentEdit.EditType != et { + if currentEdit.EditType != EditNil { + edits = append([]Editstring{currentEdit}, edits...) + } + currentEdit = Editstring{et, char} + } else { + currentEdit.Text = char + currentEdit.Text + } + segment = prevSegment + prevSegment = segment.parent + } + edits = append([]Editstring{currentEdit}, edits...) + return edits +} diff --git a/go.mod b/go.mod index 635eea4..6d93a93 100644 --- a/go.mod +++ b/go.mod @@ -3,32 +3,59 @@ module github.com/rachel-mp4/ttyxcvr go 1.24.2 require ( + github.com/bluesky-social/indigo v0.0.0-20250925152209-0f22fe8b39a8 github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/lipgloss v1.1.0 + github.com/gorilla/websocket v1.5.3 + github.com/ipfs/go-cid v0.4.1 + github.com/rachel-mp4/lrcproto v0.0.0-20250905154858-2ddb78e31d0c + github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 + google.golang.org/protobuf v1.36.6 ) require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/carlmjohnson/versioninfo v0.22.5 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect - github.com/rachel-mp4/lrcproto v0.0.0-20250905154858-2ddb78e31d0c // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.3.8 // indirect - google.golang.org/protobuf v1.36.6 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + lukechampine.com/blake3 v1.2.1 // indirect ) diff --git a/go.sum b/go.sum index 60f7c5b..2587e9c 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,14 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bluesky-social/indigo v0.0.0-20250925152209-0f22fe8b39a8 h1:+YD75cZtvj0uFVOjb8XglqJ4Ev4UFKyAvzR3TOMREW0= +github.com/bluesky-social/indigo v0.0.0-20250925152209-0f22fe8b39a8/go.mod h1:n6QE1NDPFoi7PRbMUZmc2y7FibCqiVU4ePpsvhHUBR8= +github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= +github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= @@ -20,10 +28,60 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payR github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= +github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= +github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= +github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= +github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= +github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= +github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= +github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo= +github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= +github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= +github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs= +github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk= +github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= +github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= +github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= +github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= +github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -34,12 +92,42 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= +github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rachel-mp4/lrcproto v0.0.0-20250905154858-2ddb78e31d0c h1:t33xVlfSwvB80nj1jroRXUaq/RTgjWwD4l7p/ISatUQ= github.com/rachel-mp4/lrcproto v0.0.0-20250905154858-2ddb78e31d0c/go.mod h1:hQzO36tQELGbkmRnUtKeM6NMU34t79ZcTlhM+MO7pHw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -47,15 +135,50 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= +github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= +gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= +gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= +gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/lex/lexicons_cbor.go b/lex/lexicons_cbor.go new file mode 100644 index 0000000..a628c6f --- /dev/null +++ b/lex/lexicons_cbor.go @@ -0,0 +1,1207 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package lex + +import ( + "fmt" + "io" + "math" + "sort" + + util "github.com/bluesky-social/indigo/lex/util" + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +func (t *ProfileRecord) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + fieldCount := 6 + + if t.DisplayName == nil { + fieldCount-- + } + + if t.DefaultNick == nil { + fieldCount-- + } + + if t.Status == nil { + fieldCount-- + } + + if t.Avatar == nil { + fieldCount-- + } + + if t.Color == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + return err + } + + // t.LexiconTypeID (string) (string) + if len("$type") > 8192 { + return xerrors.Errorf("Value in field \"$type\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil { + return err + } + if _, err := cw.WriteString(string("$type")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("org.xcvr.actor.profile"))); err != nil { + return err + } + if _, err := cw.WriteString(string("org.xcvr.actor.profile")); err != nil { + return err + } + + // t.Color (uint64) (uint64) + if t.Color != nil { + + if len("color") > 8192 { + return xerrors.Errorf("Value in field \"color\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("color"))); err != nil { + return err + } + if _, err := cw.WriteString(string("color")); err != nil { + return err + } + + if t.Color == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.Color)); err != nil { + return err + } + } + + } + + // t.Avatar (util.LexBlob) (struct) + if t.Avatar != nil { + + if len("avatar") > 8192 { + return xerrors.Errorf("Value in field \"avatar\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("avatar"))); err != nil { + return err + } + if _, err := cw.WriteString(string("avatar")); err != nil { + return err + } + + if err := t.Avatar.MarshalCBOR(cw); err != nil { + return err + } + } + + // t.Status (string) (string) + if t.Status != nil { + + if len("status") > 8192 { + return xerrors.Errorf("Value in field \"status\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("status"))); err != nil { + return err + } + if _, err := cw.WriteString(string("status")); err != nil { + return err + } + + if t.Status == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Status) > 8192 { + return xerrors.Errorf("Value in field t.Status was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Status))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Status)); err != nil { + return err + } + } + } + + // t.DefaultNick (string) (string) + if t.DefaultNick != nil { + + if len("defaultNick") > 8192 { + return xerrors.Errorf("Value in field \"defaultNick\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("defaultNick"))); err != nil { + return err + } + if _, err := cw.WriteString(string("defaultNick")); err != nil { + return err + } + + if t.DefaultNick == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.DefaultNick) > 8192 { + return xerrors.Errorf("Value in field t.DefaultNick was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.DefaultNick))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.DefaultNick)); err != nil { + return err + } + } + } + + // t.DisplayName (string) (string) + if t.DisplayName != nil { + + if len("displayName") > 8192 { + return xerrors.Errorf("Value in field \"displayName\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("displayName"))); err != nil { + return err + } + if _, err := cw.WriteString(string("displayName")); err != nil { + return err + } + + if t.DisplayName == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.DisplayName) > 8192 { + return xerrors.Errorf("Value in field t.DisplayName was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.DisplayName))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.DisplayName)); err != nil { + return err + } + } + } + return nil +} + +func (t *ProfileRecord) UnmarshalCBOR(r io.Reader) (err error) { + *t = ProfileRecord{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("ProfileRecord: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 11) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.LexiconTypeID (string) (string) + case "$type": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.LexiconTypeID = string(sval) + } + // t.Color (uint64) (uint64) + case "color": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + typed := uint64(extra) + t.Color = &typed + } + + } + // t.Avatar (util.LexBlob) (struct) + case "avatar": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.Avatar = new(util.LexBlob) + if err := t.Avatar.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Avatar pointer: %w", err) + } + } + + } + // t.Status (string) (string) + case "status": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Status = (*string)(&sval) + } + } + // t.DefaultNick (string) (string) + case "defaultNick": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.DefaultNick = (*string)(&sval) + } + } + // t.DisplayName (string) (string) + case "displayName": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.DisplayName = (*string)(&sval) + } + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *ChannelRecord) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + fieldCount := 5 + + if t.Topic == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + return err + } + + // t.Host (string) (string) + if len("host") > 8192 { + return xerrors.Errorf("Value in field \"host\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("host"))); err != nil { + return err + } + if _, err := cw.WriteString(string("host")); err != nil { + return err + } + + if len(t.Host) > 8192 { + return xerrors.Errorf("Value in field t.Host was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Host))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.Host)); err != nil { + return err + } + + // t.LexiconTypeID (string) (string) + if len("$type") > 8192 { + return xerrors.Errorf("Value in field \"$type\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil { + return err + } + if _, err := cw.WriteString(string("$type")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("org.xcvr.feed.channel"))); err != nil { + return err + } + if _, err := cw.WriteString(string("org.xcvr.feed.channel")); err != nil { + return err + } + + // t.Title (string) (string) + if len("title") > 8192 { + return xerrors.Errorf("Value in field \"title\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("title"))); err != nil { + return err + } + if _, err := cw.WriteString(string("title")); err != nil { + return err + } + + if len(t.Title) > 8192 { + return xerrors.Errorf("Value in field t.Title was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Title))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.Title)); err != nil { + return err + } + + // t.Topic (string) (string) + if t.Topic != nil { + + if len("topic") > 8192 { + return xerrors.Errorf("Value in field \"topic\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("topic"))); err != nil { + return err + } + if _, err := cw.WriteString(string("topic")); err != nil { + return err + } + + if t.Topic == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Topic) > 8192 { + return xerrors.Errorf("Value in field t.Topic was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Topic))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Topic)); err != nil { + return err + } + } + } + + // t.CreatedAt (string) (string) + if len("createdAt") > 8192 { + return xerrors.Errorf("Value in field \"createdAt\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil { + return err + } + if _, err := cw.WriteString(string("createdAt")); err != nil { + return err + } + + if len(t.CreatedAt) > 8192 { + return xerrors.Errorf("Value in field t.CreatedAt was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.CreatedAt)); err != nil { + return err + } + return nil +} + +func (t *ChannelRecord) UnmarshalCBOR(r io.Reader) (err error) { + *t = ChannelRecord{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("ChannelRecord: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 9) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Host (string) (string) + case "host": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Host = string(sval) + } + // t.LexiconTypeID (string) (string) + case "$type": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.LexiconTypeID = string(sval) + } + // t.Title (string) (string) + case "title": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Title = string(sval) + } + // t.Topic (string) (string) + case "topic": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Topic = (*string)(&sval) + } + } + // t.CreatedAt (string) (string) + case "createdAt": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.CreatedAt = string(sval) + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *MessageRecord) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + fieldCount := 6 + + if t.Nick == nil { + fieldCount-- + } + + if t.Color == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + return err + } + + // t.Body (string) (string) + if len("body") > 8192 { + return xerrors.Errorf("Value in field \"body\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("body"))); err != nil { + return err + } + if _, err := cw.WriteString(string("body")); err != nil { + return err + } + + if len(t.Body) > 8192 { + return xerrors.Errorf("Value in field t.Body was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Body))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.Body)); err != nil { + return err + } + + // t.Nick (string) (string) + if t.Nick != nil { + + if len("nick") > 8192 { + return xerrors.Errorf("Value in field \"nick\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("nick"))); err != nil { + return err + } + if _, err := cw.WriteString(string("nick")); err != nil { + return err + } + + if t.Nick == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Nick) > 8192 { + return xerrors.Errorf("Value in field t.Nick was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Nick))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Nick)); err != nil { + return err + } + } + } + + // t.LexiconTypeID (string) (string) + if len("$type") > 8192 { + return xerrors.Errorf("Value in field \"$type\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil { + return err + } + if _, err := cw.WriteString(string("$type")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("org.xcvr.lrc.message"))); err != nil { + return err + } + if _, err := cw.WriteString(string("org.xcvr.lrc.message")); err != nil { + return err + } + + // t.Color (uint64) (uint64) + if t.Color != nil { + + if len("color") > 8192 { + return xerrors.Errorf("Value in field \"color\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("color"))); err != nil { + return err + } + if _, err := cw.WriteString(string("color")); err != nil { + return err + } + + if t.Color == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.Color)); err != nil { + return err + } + } + + } + + // t.PostedAt (string) (string) + if len("postedAt") > 8192 { + return xerrors.Errorf("Value in field \"postedAt\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("postedAt"))); err != nil { + return err + } + if _, err := cw.WriteString(string("postedAt")); err != nil { + return err + } + + if len(t.PostedAt) > 8192 { + return xerrors.Errorf("Value in field t.PostedAt was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.PostedAt))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.PostedAt)); err != nil { + return err + } + + // t.SignetURI (string) (string) + if len("signetURI") > 8192 { + return xerrors.Errorf("Value in field \"signetURI\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("signetURI"))); err != nil { + return err + } + if _, err := cw.WriteString(string("signetURI")); err != nil { + return err + } + + if len(t.SignetURI) > 8192 { + return xerrors.Errorf("Value in field t.SignetURI was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.SignetURI))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.SignetURI)); err != nil { + return err + } + return nil +} + +func (t *MessageRecord) UnmarshalCBOR(r io.Reader) (err error) { + *t = MessageRecord{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("MessageRecord: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 9) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Body (string) (string) + case "body": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Body = string(sval) + } + // t.Nick (string) (string) + case "nick": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Nick = (*string)(&sval) + } + } + // t.LexiconTypeID (string) (string) + case "$type": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.LexiconTypeID = string(sval) + } + // t.Color (uint64) (uint64) + case "color": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + typed := uint64(extra) + t.Color = &typed + } + + } + // t.PostedAt (string) (string) + case "postedAt": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.PostedAt = string(sval) + } + // t.SignetURI (string) (string) + case "signetURI": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.SignetURI = string(sval) + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *SignetRecord) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + fieldCount := 5 + + if t.StartedAt == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + return err + } + + // t.Author (string) (string) + if len("nick") > 8192 { + return xerrors.Errorf("Value in field \"nick\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("nick"))); err != nil { + return err + } + if _, err := cw.WriteString(string("nick")); err != nil { + return err + } + + if len(t.AuthorHandle) > 8192 { + return xerrors.Errorf("Value in field t.Author was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.AuthorHandle))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.AuthorHandle)); err != nil { + return err + } + + // t.LexiconTypeID (string) (string) + if len("$type") > 8192 { + return xerrors.Errorf("Value in field \"$type\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil { + return err + } + if _, err := cw.WriteString(string("$type")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("org.xcvr.lrc.signet"))); err != nil { + return err + } + if _, err := cw.WriteString(string("org.xcvr.lrc.signet")); err != nil { + return err + } + + // t.LRCID (uint64) (uint64) + if len("lrcID") > 8192 { + return xerrors.Errorf("Value in field \"lrcID\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("lrcID"))); err != nil { + return err + } + if _, err := cw.WriteString(string("lrcID")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.LRCID)); err != nil { + return err + } + + // t.StartedAt (string) (string) + if t.StartedAt != nil { + + if len("startedAt") > 8192 { + return xerrors.Errorf("Value in field \"startedAt\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("startedAt"))); err != nil { + return err + } + if _, err := cw.WriteString(string("startedAt")); err != nil { + return err + } + + if t.StartedAt == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.StartedAt) > 8192 { + return xerrors.Errorf("Value in field t.StartedAt was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.StartedAt))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.StartedAt)); err != nil { + return err + } + } + } + + // t.ChannelURI (string) (string) + if len("channelURI") > 8192 { + return xerrors.Errorf("Value in field \"channelURI\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("channelURI"))); err != nil { + return err + } + if _, err := cw.WriteString(string("channelURI")); err != nil { + return err + } + + if len(t.ChannelURI) > 8192 { + return xerrors.Errorf("Value in field t.ChannelURI was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.ChannelURI))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.ChannelURI)); err != nil { + return err + } + return nil +} + +func (t *SignetRecord) UnmarshalCBOR(r io.Reader) (err error) { + *t = SignetRecord{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SignetRecord: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 10) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Author (string) (string) + case "authorHandle": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.AuthorHandle = string(sval) + } + // t.LexiconTypeID (string) (string) + case "$type": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.LexiconTypeID = string(sval) + } + // t.LRCID (uint64) (uint64) + case "lrcID": + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.LRCID = uint64(extra) + + } + // t.StartedAt (string) (string) + case "startedAt": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.StartedAt = (*string)(&sval) + } + } + // t.ChannelURI (string) (string) + case "channelURI": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.ChannelURI = string(sval) + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} diff --git a/lex/types.go b/lex/types.go new file mode 100644 index 0000000..7f26fd0 --- /dev/null +++ b/lex/types.go @@ -0,0 +1,71 @@ +package lex + +import ( + "github.com/bluesky-social/indigo/lex/util" +) + +func init() { + util.RegisterType("org.xcvr.actor.profile", &ProfileRecord{}) + util.RegisterType("org.xcvr.feed.channel", &ChannelRecord{}) + util.RegisterType("org.xcvr.lrc.message", &MessageRecord{}) + util.RegisterType("org.xcvr.lrc.signet", &SignetRecord{}) +} + +type ProfileRecord struct { + LexiconTypeID string `json:"$type,const=org.xcvr.actor.profile" cborgen:"$type,const=org.xcvr.actor.profile"` + DisplayName *string `json:"displayName,omitempty" cborgen:"displayName,omitempty"` + DefaultNick *string `json:"defaultNick,omitempty" cborgen:"defaultNick,omitempty"` + Status *string `json:"status,omitempty" cborgen:"status,omitempty"` + Avatar *util.LexBlob `json:"avatar,omitempty" cborgen:"avatar,omitempty"` + Color *uint64 `json:"color,omitempty" cborgen:"color,omitempty"` +} + +type ChannelRecord struct { + LexiconTypeID string `json:"$type,const=org.xcvr.feed.channel" cborgen:"$type,const=org.xcvr.feed.channel"` + Title string `json:"title" cborgen:"title"` + Topic *string `json:"topic,omitempty" cborgen:"topic,omitempty"` + CreatedAt string `json:"createdAt" cborgen:"createdAt"` + Host string `json:"host" cborgen:"host"` +} + +type MessageRecord struct { + LexiconTypeID string `json:"$type,const=org.xcvr.lrc.message" cborgen:"$type,const=org.xcvr.lrc.message"` + SignetURI string `json:"signetURI" cborgen:"signetURI"` + Body string `json:"body" cborgen:"body"` + Nick *string `json:"nick,omitempty" cborgen:"nick,omitempty"` + Color *uint64 `json:"color,omitempty" cborgen:"color,omitempty"` + PostedAt string `json:"postedAt" cborgen:"postedAt"` +} + +type SignetRecord struct { + LexiconTypeID string `json:"$type,const=org.xcvr.lrc.signet" cborgen:"$type,const=org.xcvr.lrc.signet"` + ChannelURI string `json:"channelURI" cborgen:"channelURI"` + LRCID uint64 `json:"lrcID" cborgen:"lrcID"` + AuthorHandle string `json:"authorHandle" cborgen:"authorHandle"` + StartedAt *string `json:"startedAt,omitempty" cborgen:"startedAt,omitempty"` +} + +type MediaRecord struct { + LexiconTypeID string `json:"$type,const=org.xcvr.lrc.media" cborgen:"$type,const=org.xcvr.lrc.media"` + SignetURI string `json:"signetURI" cborgen:"signetURI"` + Media Media `json:"media" cborgen:"media"` + Nick *string `json:"nick,omitempty" cborgen:"nick,omitempty"` + Color *uint64 `json:"color,omitempty" cborgen:"color,omitempty"` + PostedAt string `json:"postedAt" cborgen:"postedAt"` +} + +type Media struct { + Image *Image +} + +type Image struct { + LexiconTypeID string `json:"$type,const=org.xcvr.lrc.image" cborgen:"$type,const=org.xcvr.lrc.image"` + Alt string `json:"alt" cborgen:"alt"` + AspectRatio *AspectRatio `json:"aspectRatio,omitempty" cborgen:"aspectRatio,omitempty"` + Image *util.BlobSchema `json:"image,omitempty" cborgen:"image,omitempty"` +} + +type AspectRatio struct { + Height int64 `json:"height" cborgen:"height"` + Width int64 `json:"width" cborgen:"width"` +} diff --git a/main.go b/main.go index 4a7ca65..ba72064 100644 --- a/main.go +++ b/main.go @@ -8,16 +8,24 @@ import ( "io" "net/http" "os" + "strconv" "strings" "time" "unicode/utf16" + "github.com/bluesky-social/indigo/api/atproto" + "github.com/bluesky-social/indigo/atproto/client" + "github.com/bluesky-social/indigo/atproto/identity" + "github.com/bluesky-social/indigo/atproto/syntax" + "github.com/bluesky-social/indigo/lex/util" "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/gorilla/websocket" "github.com/rachel-mp4/lrcproto/gen/go" + "github.com/rachel-mp4/ttyxcvr/lex" "google.golang.org/protobuf/proto" ) @@ -60,20 +68,27 @@ type model struct { width int height int error *error - command *string + prompt textinput.Model + draft *textinput.Model + sentmsg *string channels *[]Channel list *list.Model curchannel *Channel wsurl *string lrcconn *websocket.Conn + lexconn *websocket.Conn + evtchan chan []byte cancel func() vp *viewport.Model msgs map[uint32]*Message + myid *uint32 renders []*string topic *string color *uint32 nick *string handle *string + signeturi *string + xrpc *PasswordClient } type Message struct { @@ -140,36 +155,48 @@ func (d ChannelItemDelegate) Render(w io.Writer, m list.Model, index int, item l var desc string var uri string var host string + var color *uint32 + var author string if i, ok := item.(ChannelItem); ok { title = i.Title() desc = i.Description() - host = subduedStyle.Render(fmt.Sprintf("(hosted by %s)", i.Host())) + author = fmt.Sprintf("(%s)", renderName(i.channel.Creator.DisplayName, i.channel.Creator.Handle)) + host = subduedStyle.Render(fmt.Sprintf("(hosted on %s)", i.Host())) if desc == "" { desc = subduedStyle.Render("no provided description") } uri = i.URI() + color = i.channel.Creator.Color } else { return } if index == m.Index() { - greenStyle := lipgloss.NewStyle().Foreground(Green) - title = fmt.Sprintf("│%s %s", greenStyle.Render(title), host) + greenStyle := lipgloss.NewStyle().Foreground(ColorFromInt(color)) + title = fmt.Sprintf("│%s %s", greenStyle.Render(title), author) desc = fmt.Sprintf("│%s", desc) uri = fmt.Sprintf("└%s", strings.Repeat("─", m.Width()-1)) } else { s := lipgloss.NewStyle() s = s.Foreground(subduedColor) uri = s.Render(uri) + host = subduedStyle.Render(author) } fmt.Fprintf(w, "%s %s\n%s\n%s", title, host, desc, uri) } func initialModel() model { + prompt := textinput.New() + prompt.Prompt = ":" + nick := "wanderer" + color := uint32(33096) return model{ state: Splash, mode: Normal, + prompt: prompt, width: 30, height: 20, + nick: &nick, + color: &color, } } func (m model) Init() tea.Cmd { @@ -213,15 +240,111 @@ type channelsMsg struct{ channels []Channel } type errMsg struct{ err error } +func login(handle string, secret string) tea.Cmd { + return func() tea.Msg { + hdl, err := syntax.ParseHandle(handle) + if err != nil { + err = errors.New("handle failed to parse: " + err.Error()) + return errMsg{err} + } + id, err := identity.DefaultDirectory().LookupHandle(context.Background(), hdl) + if err != nil { + err = errors.New("handle failed to loopup: " + err.Error()) + return errMsg{err} + } + xrpc := NewPasswordClient(id.DID.String(), id.PDSEndpoint()) + err = xrpc.CreateSession(context.Background(), handle, secret) + if err != nil { + return errMsg{err} + } + return loggedInMsg{xrpc} + } +} + +type loggedInMsg struct { + xrpc *PasswordClient +} + func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case errMsg: m.state = Error m.error = &msg.err + return m, nil + case svMsg: + if m.myid != nil && msg.signetView.LrcId == *m.myid { + m.signeturi = &msg.signetView.URI + return m, nil + } + + case loginMsg: + if len(msg.value) == 2 { + return m, login(msg.value[0], msg.value[1]) + } + case loggedInMsg: + m.xrpc = msg.xrpc + return m, nil + + case setMsg: + key, val, found := strings.Cut(msg.value, "=") + if !found { + return m, nil + } + switch key { + case "color", "c": + i, err := strconv.Atoi(val) + if err != nil { + return m, nil + } + b := uint32(i) + m.color = &b + if m.draft != nil { + m.draft.PromptStyle = lipgloss.NewStyle().Foreground(ColorFromInt(&b)) + } + err = sendSet(m.evtchan, m.nick, m.handle, m.color) + if err != nil { + send(errMsg{err}) + } + return m, nil + case "nick", "name", "n": + m.nick = &val + if m.draft != nil { + m.draft.Prompt = renderName(m.nick, m.handle) + " " + } + err := sendSet(m.evtchan, m.nick, m.handle, m.color) + if err != nil { + send(errMsg{err}) + } + return m, nil + case "handle", "h", "at", "@": + m.handle = &val + if m.draft != nil { + m.draft.Prompt = renderName(m.nick, m.handle) + " " + } + err := sendSet(m.evtchan, m.nick, m.handle, m.color) + if err != nil { + send(errMsg{err}) + } + return m, nil + } case tea.WindowSizeMsg: m.height = msg.Height m.width = msg.Width + if m.vp != nil { + m.vp.Width = msg.Width + m.vp.Height = msg.Height - 2 + } + if m.renders != nil { + for _, message := range m.msgs { + message.renderMessage(msg.Width) + } + m.vp.SetContent(JoinDeref(m.renders, "")) + } + if m.list != nil { + m.list.SetSize(msg.Width, msg.Height) + } + return m, nil case tea.KeyMsg: switch msg.String() { @@ -257,6 +380,7 @@ func (m model) updateConnected(msg tea.Msg) (tea.Model, tea.Cmd) { m.error = &err return m, nil } + id := msg.e.Id switch msg := msg.e.Msg.(type) { case *lrcpb.Event_Ping: return m, nil @@ -269,6 +393,9 @@ func (m model) updateConnected(msg tea.Msg) (tea.Model, tea.Cmd) { m.error = &err return m, nil } + if msg.Init.Echoed != nil && *msg.Init.Echoed { + m.myid = msg.Init.Id + } m.vp.SetContent(JoinDeref(m.renders, "")) return m, nil case *lrcpb.Event_Pub: @@ -310,13 +437,235 @@ func (m model) updateConnected(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, nil case *lrcpb.Event_Editbatch: + if id == nil { + return m, nil + } + err := editMessage(*id, msg.Editbatch.Edits, m.msgs, &m.renders, m.width) + if err != nil { + m.state = Error + m.error = &err + return m, nil + } + m.vp.SetContent(JoinDeref(m.renders, "")) return m, nil + } + case tea.KeyMsg: + switch m.mode { + case Normal: + switch msg.String() { + case "i", "a": + m.mode = Insert + return m, m.draft.Focus() + case ":": + m.mode = Command + return m, m.prompt.Focus() + } + case Insert: + switch msg.String() { + case "esc": + m.mode = Normal + m.draft.Blur() + return m, nil + case "enter": + if m.sentmsg != nil { + if m.xrpc != nil && m.signeturi != nil { + var color64 *uint64 + if m.color != nil { + c64 := uint64(*m.color) + color64 = &c64 + } + lmr := lex.MessageRecord{ + SignetURI: *m.signeturi, + Body: *m.sentmsg, + Nick: m.nick, + Color: color64, + PostedAt: syntax.DatetimeNow().String(), + } + m.draft.SetValue("") + m.sentmsg = nil + m.myid = nil + m.signeturi = nil + return m, tea.Batch(sendPub(m.lrcconn), createMSGCmd(m.xrpc, &lmr)) + } + m.draft.SetValue("") + m.sentmsg = nil + return m, sendPub(m.lrcconn) + } + return m, nil + } + case Command: + switch msg.String() { + case "esc": + m.mode = Normal + m.prompt.Blur() + m.prompt.SetValue("") + return m, nil + case "enter": + m.mode = Normal + m.prompt.Blur() + v := m.prompt.Value() + m.prompt.SetValue("") + return m, evaluateCommand(v) + default: + } + } + } + switch m.mode { + case Normal: + vp, cmd := m.vp.Update(msg) + m.vp = &vp + return m, cmd + case Command: + prompt, cmd := m.prompt.Update(msg) + m.prompt = prompt + return m, cmd + case Insert: + draft, cmd := m.draft.Update(msg) + if m.sentmsg == nil && draft.Value() != "" { + nv := draft.Value() + m.sentmsg = &nv + m.draft = &draft + return m, tea.Batch(cmd, sendInsert(m.lrcconn, nv, 0, true)) + } + if m.sentmsg != nil && *m.sentmsg != draft.Value() { + draftutf16 := utf16.Encode([]rune(draft.Value())) + sentutf16 := utf16.Encode([]rune(*m.sentmsg)) + edits := Diff(sentutf16, draftutf16) + m.draft = &draft + sentmsg := draft.Value() + m.sentmsg = &sentmsg + return m, tea.Batch(cmd, sendEditBatch(m.evtchan, edits)) + } + m.draft = &draft + return m, cmd + } + return m, nil +} +func createMSGCmd(xrpc *PasswordClient, lmr *lex.MessageRecord) tea.Cmd { + return func() tea.Msg { + _, _, err := xrpc.CreateXCVRMessage(lmr, context.Background()) + if err != nil { + return errMsg{err} } + return nil } - vp, cmd := m.vp.Update(msg) - m.vp = &vp - return m, cmd +} + +func sendEditBatch(datachan chan []byte, edits []Edit) tea.Cmd { + return func() tea.Msg { + idx := 0 + batch := make([]*lrcpb.Edit, 0) + for _, edit := range edits { + switch edit.EditType { + case EditDel: + idx2 := idx + len(edit.Utf16Text) + evt := makeDelete(uint32(idx), uint32(idx2)) + edit := lrcpb.Edit{Edit: &lrcpb.Edit_Delete{Delete: evt.GetDelete()}} + batch = append(batch, &edit) + case EditKeep: + idx = idx + len(edit.Utf16Text) + case EditAdd: + evt := makeInsert(string(utf16.Decode(edit.Utf16Text)), uint32(idx)) + idx = idx + len(edit.Utf16Text) + edit := lrcpb.Edit{Edit: &lrcpb.Edit_Insert{Insert: evt.GetInsert()}} + batch = append(batch, &edit) + } + } + evt := lrcpb.Event{Msg: &lrcpb.Event_Editbatch{Editbatch: &lrcpb.EditBatch{Edits: batch}}} + data, err := proto.Marshal(&evt) + if err != nil { + return errMsg{err} + } + datachan <- data + return nil + } +} + +func sendPub(conn *websocket.Conn) tea.Cmd { + return func() tea.Msg { + evt := &lrcpb.Event{Msg: &lrcpb.Event_Pub{Pub: &lrcpb.Pub{}}} + data, err := proto.Marshal(evt) + if err != nil { + return errMsg{err} + } + err = conn.WriteMessage(websocket.BinaryMessage, data) + if err != nil { + return errMsg{err} + } + return nil + } +} + +func makeDelete(start uint32, end uint32) *lrcpb.Event { + evt := &lrcpb.Event{Msg: &lrcpb.Event_Delete{Delete: &lrcpb.Delete{Utf16Start: start, Utf16End: end}}} + return evt +} + +func makeInsert(body string, idx uint32) *lrcpb.Event { + evt := &lrcpb.Event{Msg: &lrcpb.Event_Insert{Insert: &lrcpb.Insert{Body: body, Utf16Index: idx}}} + return evt +} + +func sendInsert(conn *websocket.Conn, body string, utf16idx uint32, init bool) tea.Cmd { + return func() tea.Msg { + if init { + evt := &lrcpb.Event{Msg: &lrcpb.Event_Init{Init: &lrcpb.Init{}}} + data, err := proto.Marshal(evt) + if err != nil { + return errMsg{err} + } + if conn == nil { + return nil + } + err = conn.WriteMessage(websocket.BinaryMessage, data) + if err != nil { + return errMsg{err} + } + } + evt := &lrcpb.Event{Msg: &lrcpb.Event_Insert{Insert: &lrcpb.Insert{Body: body, Utf16Index: utf16idx}}} + data, err := proto.Marshal(evt) + if err != nil { + return errMsg{err} + } + err = conn.WriteMessage(websocket.BinaryMessage, data) + if err != nil { + return errMsg{err} + } + return nil + } +} + +func evaluateCommand(command string) tea.Cmd { + return func() tea.Msg { + parts := strings.Split(command, " ") + if parts == nil { + return nil + } + switch parts[0] { + case "q": + return tea.QuitMsg{} + case "se", "set": + if len(parts) != 1 { + return setMsg{parts[1]} + } + case "resize": + return tea.WindowSize() + case "login": + if len(parts) != 1 { + return loginMsg{parts[1:]} + } + } + return nil + } +} + +type loginMsg struct { + value []string +} + +type setMsg struct { + value string } // i think that the type of renders is a bit awkward, but i want deletemessage + friends to just modify the rendered @@ -374,6 +723,28 @@ func deleteBtwnUTF16Indices(base string, start uint32, end uint32) string { return string(resultRunes) } +func editMessage(id uint32, edits []*lrcpb.Edit, msgmap map[uint32]*Message, renders *[]*string, width int) error { + for _, edit := range edits { + switch e := edit.Edit.(type) { + case *lrcpb.Edit_Insert: + ins := e.Insert + ins.Id = &id + err := insertMessage(ins, msgmap, renders, width) + if err != nil { + return err + } + case *lrcpb.Edit_Delete: + del := e.Delete + del.Id = &id + err := deleteMessage(del, msgmap, renders, width) + if err != nil { + return err + } + } + } + return nil +} + func insertMessage(msg *lrcpb.Insert, msgmap map[uint32]*Message, renders *[]*string, width int) error { if msg == nil { return errors.New("no insert") @@ -474,20 +845,12 @@ func (m *Message) renderMessage(width int) { return } stylem := lipgloss.NewStyle().Width(width).Align(lipgloss.Left) - styleh := stylem.Foreground(Green) + styleh := stylem.Foreground(ColorFromInt(m.color)) if m.active { styleh = styleh.Reverse(true) stylem = styleh } - var nick string - if m.nick != nil { - nick = *m.nick - } - var handle string - if m.handle != nil && *m.handle != "" { - handle = fmt.Sprintf("@%s", *m.handle) - } - header := styleh.Render(fmt.Sprintf("%s%s", nick, handle)) + header := styleh.Render(renderName(m.nick, m.handle)) body := stylem.Render(m.text) *m.rendered = fmt.Sprintf("%s\n%s\n", header, body) } @@ -498,28 +861,72 @@ func (m model) updateConnectingToChannel(msg tea.Msg) (tea.Model, tea.Cmd) { m.state = Connected m.cancel = msg.cancel m.msgs = make(map[uint32]*Message) - vp := viewport.New(m.width, m.height-1) + vp := viewport.New(m.width, m.height-2) m.vp = &vp - go startLRCHandlers(msg.conn) + draft := textinput.New() + draft.Prompt = renderName(m.nick, m.handle) + " " + draft.PromptStyle = lipgloss.NewStyle().Foreground(ColorFromInt(m.color)) + draft.Placeholder = "press i to start typing" + draft.Width = m.width + m.draft = &draft + go startLRCHandlers(msg.conn, msg.lexconn, m.nick, m.handle, m.color) + m.lrcconn = msg.conn + m.lexconn = msg.lexconn + m.evtchan = make(chan []byte) + go LRCWriter(m.lrcconn, m.evtchan) return m, nil } return m, nil } -func startLRCHandlers(conn *websocket.Conn) { +func LRCWriter(conn *websocket.Conn, datachan chan []byte) { + for data := range datachan { + err := conn.WriteMessage(websocket.BinaryMessage, data) + if err != nil { + send(errMsg{err}) + return + } + } +} + +func renderName(nick *string, handle *string) string { + var n string + if nick != nil { + n = *nick + } + var h string + if handle != nil { + h = fmt.Sprintf("@%s", *handle) + } + return fmt.Sprintf("%s%s", n, h) +} + +func sendSet(datachan chan []byte, nick *string, handle *string, color *uint32) error { + evt := &lrcpb.Event{Msg: &lrcpb.Event_Set{Set: &lrcpb.Set{Nick: nick, ExternalID: handle, Color: color}}} + data, err := proto.Marshal(evt) + if err != nil { + return err + } + datachan <- data + return nil + +} + +func startLRCHandlers(conn *websocket.Conn, lexconn *websocket.Conn, nick *string, handle *string, color *uint32) { if conn == nil { send(errMsg{errors.New("provided nil conn")}) return } - nick := "wanderer" - evt := &lrcpb.Event{Msg: &lrcpb.Event_Set{Set: &lrcpb.Set{Nick: &nick}}} + evt := &lrcpb.Event{Msg: &lrcpb.Event_Set{Set: &lrcpb.Set{Nick: nick, ExternalID: handle, Color: color}}} data, err := proto.Marshal(evt) if err != nil { send(errMsg{errors.New("failed to marshal: " + err.Error())}) return } + conn.WriteMessage(websocket.BinaryMessage, data) - evt = &lrcpb.Event{Msg: &lrcpb.Event_Get{Get: &lrcpb.Get{Topic: &nick}}} + bep := "bep" + evt = &lrcpb.Event{Msg: &lrcpb.Event_Get{Get: &lrcpb.Get{Topic: &bep}}} data, err = proto.Marshal(evt) if err != nil { send(errMsg{errors.New("failed to marshal: " + err.Error())}) @@ -527,6 +934,52 @@ func startLRCHandlers(conn *websocket.Conn) { } conn.WriteMessage(websocket.BinaryMessage, data) go listenToConn(conn) + go listenToLexConn(lexconn) +} + +type typedJSON struct { + Type string `json:"$type"` +} + +func listenToLexConn(conn *websocket.Conn) { + for { + var rawMsg json.RawMessage + err := conn.ReadJSON(&rawMsg) + if err != nil { + send(errMsg{err}) + return + } + var typed typedJSON + err = json.Unmarshal(rawMsg, &typed) + if err != nil { + send(errMsg{err}) + return + } + switch typed.Type { + case "org.xcvr.lrc.defs#signetView": + var sv SignetView + err = json.Unmarshal(rawMsg, &sv) + if err != nil { + send(errMsg{err}) + return + } + send(svMsg{&sv}) + } + } +} + +type svMsg struct { + signetView *SignetView +} + +type SignetView struct { + Type string `json:"$type,const=org.xcvr.lrc.defs#signetView"` + URI string `json:"uri"` + IssuerHandle string `json:"issuerHandle"` + ChannelURI string `json:"channelURI"` + LrcId uint32 `json:"lrcID"` + AuthorHandle string `json:"authorHandle"` + StartedAt time.Time `json:"startedAt"` } func listenToConn(conn *websocket.Conn) { @@ -568,13 +1021,20 @@ func (m model) connectToChannel(ctx context.Context, cancel func()) tea.Cmd { if err != nil { return errMsg{err} } - return connMsg{conn, cancel} + + dialer = websocket.DefaultDialer + lexconn, _, err := dialer.DialContext(ctx, fmt.Sprintf("wss://xcvr.org/xrpc/org.xcvr.lrc.subscribeLexStream?uri=%s", m.curchannel.URI), http.Header{}) + if err != nil { + return errMsg{err} + } + return connMsg{conn, lexconn, cancel} } } type connMsg struct { - conn *websocket.Conn - cancel func() + conn *websocket.Conn + lexconn *websocket.Conn + cancel func() } const ( @@ -707,16 +1167,26 @@ func (m model) connectedView() string { } remainingspace := m.width - len(address) - len(topic) var footertext string - if remainingspace < 1 { - footertext = address + if m.mode == Command { + footertext = m.prompt.View() + } else if remainingspace < 1 { + footertext = fmt.Sprintf("%s%s", address, strings.Repeat(" ", m.width-len(address))) } else { footertext = fmt.Sprintf("%s%s%s", address, strings.Repeat(" ", remainingspace), topic) } insert := m.mode == Insert - footerstyle := lipgloss.NewStyle().Foreground(Green).Reverse(insert) + footerstyle := lipgloss.NewStyle().Reverse(insert) + if m.mode != Command { + footerstyle = footerstyle.Foreground(ColorFromInt(m.color)) + } footer := footerstyle.Render(footertext) - return fmt.Sprintf("%s\n%s", vpt, footer) + var draftText string + if m.draft != nil { + draftText = m.draft.View() + } + return fmt.Sprintf("%s\n%s\n%s", vpt, draftText, footer) } + func (m model) connectingView() string { blip := m.wsurl if blip == nil { @@ -731,7 +1201,11 @@ func (m model) channelListView() string { func (m model) splashView() string { style := lipgloss.NewStyle().Foreground(Green) - part1 := "\n %%%%%%% " + part00 := "\n ⣰⡀ ⢀⣀ ⡇ ⡇⡠ ⣰⡀ ⢀⡀ ⡀⢀ ⢀⡀ ⡀⢀ ⡇" + part01 := "\n ⠘⠤ ⠣⠼ ⠣ ⠏⠢ ⠘⠤ ⠣⠜ ⣑⡺ ⠣⠜ ⠣⠼ ⠅" + part02 := "\n ⣰⡀ ⡀⣀ ⢀⣀ ⣀⡀ ⢀⣀ ⢀⣀ ⢀⡀ ⠄ ⡀⢀ ⢀⡀ ⡀⣀" + part03 := "\n ⠘⠤ ⠏ ⠣⠼ ⠇⠸ ⠭⠕ ⠣⠤ ⠣⠭ ⠇ ⠱⠃ ⠣⠭ ⠏" + part1 := "\n\n %%%%%%% " text1 := "tty!xcvr\n" part2 := ` %%%%%%%%%% %%% % % %%%%%%%% %%%%%%%%%%%%%%%%% %%%% %%%%%%%%%%%%%%%%%% @@ -753,9 +1227,9 @@ func (m model) splashView() string { press a key to start! ` - s := fmt.Sprintf("%s%s%s%s%s%s%s%s%s", style.Render(part1), text1, style.Render(part2), style.Render(part25), text2, style.Render(part3), text3, style.Render(part4), text4) - - return s + s := fmt.Sprintf("\n\n\n\n%s%s%s%s%s%s%s%s%s%s%s%s%s", style.Render(part00), style.Render(part01), style.Render(part02), style.Render(part03), style.Render(part1), text1, style.Render(part2), style.Render(part25), text2, style.Render(part3), text3, style.Render(part4), text4) + offset := lipgloss.NewStyle().MarginLeft((m.width - 58) / 2) + return offset.Render(s) } var send func(msg tea.Msg) @@ -899,3 +1373,93 @@ func JoinDeref(elems []*string, sep string) string { } return b.String() } + +type PasswordClient struct { + xrpc *client.APIClient + accessjwt *string + refreshjwt *string + did *string +} + +func NewPasswordClient(did string, host string) *PasswordClient { + return &PasswordClient{ + xrpc: client.NewAPIClient(host), + did: &did, + } +} + +func (c *PasswordClient) CreateSession(ctx context.Context, identity string, secret string) error { + input := atproto.ServerCreateSession_Input{ + Identifier: identity, + Password: secret, + } + var out atproto.ServerCreateSession_Output + err := c.xrpc.LexDo(ctx, "POST", "application/json", "com.atproto.server.createSession", nil, input, &out) + if err != nil { + return errors.New("I couldn't create a session: " + err.Error()) + } + c.accessjwt = &out.AccessJwt + c.refreshjwt = &out.RefreshJwt + return nil +} + +func (c *PasswordClient) RefreshSession(ctx context.Context) error { + c.xrpc.Headers.Set("Authorization", fmt.Sprintf("Bearer %s", *c.refreshjwt)) + var out atproto.ServerRefreshSession_Output + err := c.xrpc.LexDo(ctx, "POST", "application/json", "com.atproto.server.refreshSession", nil, nil, &out) + if err != nil { + return errors.New("failed to refresh session! " + err.Error()) + } + c.accessjwt = &out.AccessJwt + c.refreshjwt = &out.RefreshJwt + return nil +} + +func (c *PasswordClient) CreateXCVRMessage(message *lex.MessageRecord, ctx context.Context) (cid string, uri string, err error) { + input := atproto.RepoCreateRecord_Input{ + Collection: "org.xcvr.lrc.message", + Repo: *c.did, + Record: &util.LexiconTypeDecoder{Val: message}, + } + return c.createMyRecord(input, ctx) +} + +func (c *PasswordClient) createMyRecord(input atproto.RepoCreateRecord_Input, ctx context.Context) (cid string, uri string, err error) { + if c.accessjwt == nil { + err = errors.New("must create a session first") + return + } + c.xrpc.Headers.Set("Authorization", fmt.Sprintf("Bearer %s", *c.accessjwt)) + var out atproto.RepoCreateRecord_Output + err = c.xrpc.LexDo(ctx, "POST", "application/json", "com.atproto.repo.createRecord", nil, input, &out) + if err != nil { + err1 := err.Error() + err = c.RefreshSession(ctx) + if err != nil { + err = errors.New(fmt.Sprintf("failed to refresh session while creating %s! first %s then %s", input.Collection, err1, err.Error())) + return + } + c.xrpc.Headers.Set("Authorization", fmt.Sprintf("Bearer %s", *c.accessjwt)) + out = atproto.RepoCreateRecord_Output{} + err = c.xrpc.LexDo(ctx, "POST", "application/json", "com.atproto.repo.createRecord", nil, input, &out) + if err != nil { + err = errors.New(fmt.Sprintf("not good, failed to create %s after failing then refreshing session! first %s then %s", input.Collection, err1, err.Error())) + return + } + cid = out.Cid + uri = out.Uri + return + } + cid = out.Cid + uri = out.Uri + return +} + +func ColorFromInt(c *uint32) lipgloss.Color { + if c == nil { + return Green + } + ui := *c + guess := fmt.Sprintf("#%06x", ui) + return lipgloss.Color(guess[0:7]) +} -- 2.51.2