Compare commits
141 Commits
subset_exp
...
gethistory
Author | SHA1 | Date | |
---|---|---|---|
b0b2532976 | |||
0aa616d92b | |||
9cb8b62843 | |||
d6546733aa | |||
77dd75bcd4 | |||
1b7b0463a0 | |||
1d0e31541d | |||
ab4077b115 | |||
d7ef6a62e0 | |||
9ad38fb0e8 | |||
e8294241ec | |||
6508aeb3ea | |||
31ec2a2511 | |||
19b1813685 | |||
34531400f0 | |||
3148b0a632 | |||
45ad6b53cb | |||
935347ffcf | |||
7b78274a52 | |||
d0be016c3f | |||
ac0980a7ab | |||
e6c3a8e993 | |||
6ece486103 | |||
29c23f424b | |||
ea0eb38260 | |||
5fee37e032 | |||
d24a58b81e | |||
8876ce997f | |||
57d905cd72 | |||
4b20caa7cf | |||
5d82c85944 | |||
b0ac8e7a29 | |||
4281f40523 | |||
8a37bfcb61 | |||
e3c70554c6 | |||
758ad8f65a | |||
ec0118f702 | |||
b1544feca0 | |||
230ad41644 | |||
f334c03796 | |||
5099b56ecb | |||
d4263b967f | |||
6a1aa0abda | |||
506ebec7e4 | |||
74b87b904b | |||
8466510fee | |||
32bd924e7e | |||
1bb9e1f7e1 | |||
d0f68086a1 | |||
b714bda988 | |||
29d1927104 | |||
635ebb2a8c | |||
6d2115607b | |||
e0de8ec0d9 | |||
9c959346f1 | |||
1c434193eb | |||
7aa32e24c7 | |||
d2a0c38f68 | |||
81e329b40e | |||
d9839f1d06 | |||
b6fd2e2da5 | |||
5bcdbfa7bd | |||
de6689220e | |||
ebb92aba24 | |||
f40cc793f0 | |||
5cd2cae3ef | |||
518f2cbb12 | |||
623d2d2260 | |||
b7b10cda6f | |||
b492ff07d0 | |||
3a406cb92e | |||
79705cbb9b | |||
2fff3968db | |||
2b349a9851 | |||
3343e21c11 | |||
5391529466 | |||
30587f7ad2 | |||
ea20ddd2ce | |||
ec348d57ee | |||
986cd7ecd2 | |||
2689824126 | |||
08cdc5bede | |||
1e211d894e | |||
ed923abc2f | |||
79630703c2 | |||
d15474ad61 | |||
18667084ad | |||
f54e4c47b0 | |||
c406bb4870 | |||
a72fbc9c08 | |||
bacd954fa0 | |||
6fd27827a0 | |||
9027ebfe84 | |||
887d635683 | |||
61fdb42027 | |||
88c198c0d8 | |||
8620e17810 | |||
0055a1ed65 | |||
1879341499 | |||
0addd8dc47 | |||
ccc4a3371b | |||
615431496b | |||
6c6413c1b4 | |||
a3852d89c9 | |||
a18e8a7866 | |||
aa60a6e136 | |||
386642d6f1 | |||
0016495525 | |||
84add5baac | |||
26e2809c9a | |||
22417e4d82 | |||
03da29e3f9 | |||
a076e4741f | |||
db146446d0 | |||
1eb377d065 | |||
5711efbece | |||
5927b7dfe6 | |||
c4b57ae813 | |||
2fd4c12a95 | |||
8ad65f785d | |||
58d133b5c8 | |||
0548777948 | |||
c06b03ad54 | |||
db948f1f67 | |||
0fbd6f0931 | |||
65d64bbacf | |||
f764e7dfed | |||
028e3a47b5 | |||
961f817a8b | |||
be19fcbfc4 | |||
a7041e31cc | |||
696701e855 | |||
0aeed5578e | |||
ec32643407 | |||
3f2f4d9d59 | |||
ebd2604805 | |||
69f080fe57 | |||
1b2b1db95f | |||
0952ad9220 | |||
37b4a21899 | |||
807bb8700c |
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
@ -0,0 +1,5 @@
|
||||
# 0.1.1
|
||||
|
||||
_26 February 2022_
|
||||
|
||||
- [PR #32](https://git.coopcloud.tech/golgi-ssb/golgi/pulls/32): Fix two filter-related bugs which resulted in `get_about_message_stream(ssb_id)` returning **all** messages authored by `ssb_id`.
|
284
Cargo.lock
generated
284
Cargo.lock
generated
@ -83,9 +83,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b"
|
||||
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
]
|
||||
@ -151,7 +151,17 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"async-stream-impl 0.2.1",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625"
|
||||
dependencies = [
|
||||
"async-stream-impl 0.3.2",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
@ -167,10 +177,21 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.0.3"
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
|
||||
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
@ -180,9 +201,9 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
@ -202,6 +223,15 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.1.0"
|
||||
@ -218,9 +248,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.8.0"
|
||||
version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
|
||||
[[package]]
|
||||
name = "c_linked_list"
|
||||
@ -230,15 +260,15 @@ checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b"
|
||||
|
||||
[[package]]
|
||||
name = "cache-padded"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
|
||||
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@ -262,15 +292,34 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.21"
|
||||
@ -281,6 +330,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
@ -304,24 +363,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
|
||||
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.5.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e"
|
||||
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e"
|
||||
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -334,9 +393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
|
||||
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@ -344,15 +403,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
|
||||
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@ -361,9 +420,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
@ -382,9 +441,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -393,21 +452,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
|
||||
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
|
||||
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -427,6 +486,16 @@ version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "get_if_addrs"
|
||||
version = "0.5.3"
|
||||
@ -451,9 +520,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
@ -462,22 +531,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
|
||||
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "golgi"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-stream 0.3.2",
|
||||
"base64 0.13.0",
|
||||
"futures",
|
||||
"hex",
|
||||
@ -486,6 +555,7 @@ dependencies = [
|
||||
"kuska-ssb",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -511,9 +581,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
@ -530,15 +600,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.55"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
|
||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@ -569,10 +639,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kuska-ssb"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/Kuska-ssb/ssb#fb7062de606e7c9cae8dd4df402a122db46c1b77"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-stream",
|
||||
"async-stream 0.2.1",
|
||||
"base64 0.11.0",
|
||||
"dirs",
|
||||
"futures",
|
||||
@ -605,9 +676,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.108"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "libsodium-sys"
|
||||
@ -639,9 +710,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
@ -649,9 +720,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
@ -661,9 +732,9 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
@ -673,9 +744,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.22"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
|
||||
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
@ -692,18 +763,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -746,9 +817,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@ -761,18 +832,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -781,9 +852,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.72"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@ -792,10 +863,21 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.10"
|
||||
name = "sha2"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
|
||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
@ -818,9 +900,9 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
@ -828,9 +910,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.82"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -857,6 +939,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
@ -875,9 +963,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
@ -904,9 +992,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
@ -914,9 +1002,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
@ -929,9 +1017,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.28"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
|
||||
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
@ -941,9 +1029,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@ -951,9 +1039,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -964,15 +1052,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.55"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
|
||||
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
21
Cargo.toml
21
Cargo.toml
@ -1,18 +1,25 @@
|
||||
[package]
|
||||
name = "golgi"
|
||||
version = "0.1.0"
|
||||
authors = ["glyph <glyph@mycelial.technology>"]
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
authors = ["Max Fowler <max@mfowler.info>", "Andrew Reid <glyph@mycelial.technology>"]
|
||||
readme = "README.md"
|
||||
description = "An asynchronous, experimental Scuttlebutt client library"
|
||||
repository = "https://git.coopcloud.tech/golgi-ssb/golgi"
|
||||
homepage = "http://golgi.mycelial.technology"
|
||||
license = "LGPL-3.0"
|
||||
keywords = ["scuttlebutt", "ssb", "decentralized", "peer-for-peer", "p4p"]
|
||||
exclude = ["git_hooks/", "examples/"]
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.10.0"
|
||||
async-stream = "0.3.2"
|
||||
base64 = "0.13.0"
|
||||
futures = "0.3.18"
|
||||
futures = "0.3.21"
|
||||
hex = "0.4.3"
|
||||
kuska-handshake = { version = "0.2.0", features = ["async_std"] }
|
||||
kuska-sodiumoxide = "0.2.5-0"
|
||||
# waiting for a pr merge upstream
|
||||
kuska-ssb = { path = "../ssb" }
|
||||
# try to replace with miniserde
|
||||
serde = "1"
|
||||
kuska-ssb = { git = "https://github.com/Kuska-ssb/ssb" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "0.10.2"
|
||||
|
165
LICENSE.txt
Normal file
165
LICENSE.txt
Normal file
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
64
README.md
64
README.md
@ -1,18 +1,72 @@
|
||||
# golgi
|
||||
|
||||
_The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins into membrane-bound vesicles inside the cell before the vesicles are sent to their destination._
|
||||
_The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins
|
||||
into membrane-bound vesicles inside the cell before the vesicles are sent
|
||||
to their destination._
|
||||
|
||||
-----
|
||||
|
||||
Golgi is an experimental Scuttlebutt client which uses the [kuska-ssb](https://github.com/Kuska-ssb) libraries and aims to provide a high-level API for interacting with an sbot instance. Development efforts are currently oriented towards [go-sbot](https://github.com/cryptoscope/ssb) interoperability.
|
||||
## Introduction
|
||||
|
||||
Golgi is an asynchronous, experimental Scuttlebutt client that aims to
|
||||
facilitate Scuttlebutt application development. It provides a high-level
|
||||
API for interacting with an sbot instance and uses the
|
||||
[kuska-ssb](https://github.com/Kuska-ssb) libraries to make RPC calls.
|
||||
|
||||
## Features
|
||||
|
||||
Golgi offers the ability to invoke individual RPC methods while also
|
||||
providing a number of convenience methods which may involve multiple RPC
|
||||
calls and / or the processing of data received from those calls. The
|
||||
`Sbot` `struct` is the primary means of interacting with the library.
|
||||
|
||||
Features include the ability to publish messages of various kinds; to
|
||||
retrieve messages (e.g. `about` and `description` messages) and formulate
|
||||
queries; to follow, unfollow, block and unblock a peer; to query the social
|
||||
graph; and to generate pub invite codes.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```rust
|
||||
pub async fn run() -> Result<(), GolgiError> {
|
||||
let mut sbot_client = Sbot::init(None, None).await?;
|
||||
Basic usage is demonstrated below. Visit the [examples directory](https://git.coopcloud.tech/golgi-ssb/golgi/src/branch/main/examples) in the `golgi` repository for
|
||||
more comprehensive examples.
|
||||
|
||||
```rust
|
||||
use golgi::{GolgiError, Sbot, sbot::Keystore};
|
||||
|
||||
pub async fn run() -> Result<(), GolgiError> {
|
||||
// Attempt to initialise a connection to an sbot instance using the
|
||||
// secret file at the Patchwork path and the default IP address, port
|
||||
// and network key (aka. capabilities key).
|
||||
let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
|
||||
// Call the `whoami` RPC method to retrieve the public key for the sbot
|
||||
// identity.
|
||||
let id = sbot_client.whoami().await?;
|
||||
|
||||
// Print the public key (identity) to `stdout`.
|
||||
println!("{}", id);
|
||||
|
||||
// Compose an SSB post message type.
|
||||
let post = SsbMessageContent::Post {
|
||||
text: "Biology, eh?!".to_string(),
|
||||
mentions: None,
|
||||
};
|
||||
|
||||
// Publish the post.
|
||||
let post_msg_reference = sbot_client.publish(post).await?;
|
||||
|
||||
// Print the reference (sigil-link) for the published post.
|
||||
println!("{}", post_msg_reference);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
- [notplants](https://mfowler.info/)
|
||||
- [glyph](https://mycelial.technology/)
|
||||
|
||||
## License
|
||||
|
||||
LGPL-3.0.
|
||||
|
99
examples/basic.rs
Normal file
99
examples/basic.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use std::process;
|
||||
|
||||
use golgi::{messages::SsbMessageContent, sbot::Keystore, GolgiError, Sbot};
|
||||
|
||||
// Golgi is an asynchronous library so we must call it from within an
|
||||
// async function. The `GolgiError` type encapsulates all possible
|
||||
// error variants for the library.
|
||||
async fn run() -> Result<(), GolgiError> {
|
||||
// Attempt to initialise a connection to an sbot instance using the
|
||||
// secret file at the Patchwork path and the default IP address, port
|
||||
// and network key (aka. capabilities key).
|
||||
let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
|
||||
// Alternatively, we could specify a non-standard IP and port.
|
||||
// let ip_port = "127.0.0.1:8021".to_string();
|
||||
// let mut sbot_client = Sbot::init(Some(ip_port), None).await?;
|
||||
|
||||
// Call the `whoami` RPC method to retrieve the public key for the sbot
|
||||
// identity. This is our 'local' public key.
|
||||
let id = sbot_client.whoami().await?;
|
||||
|
||||
// Print the public key (identity) to `stdout`.
|
||||
println!("whoami: {}", id);
|
||||
|
||||
// Compose an SSB `about` message type.
|
||||
// The `SsbMessageContent` type has many variants and allows for a high
|
||||
// degree of control when creating messages.
|
||||
let name = SsbMessageContent::About {
|
||||
about: id.clone(),
|
||||
name: Some("golgi".to_string()),
|
||||
title: None,
|
||||
branch: None,
|
||||
image: None,
|
||||
description: None,
|
||||
location: None,
|
||||
start_datetime: None,
|
||||
};
|
||||
|
||||
// Publish the name message. The `publish` method returns a reference to
|
||||
// the published message.
|
||||
let name_msg_ref = sbot_client.publish(name).await?;
|
||||
|
||||
// Print the message reference to `stdout`.
|
||||
println!("name_msg_ref: {}", name_msg_ref);
|
||||
|
||||
// Compose an SSB `post` message type.
|
||||
let post = SsbMessageContent::Post {
|
||||
text: "golgi go womp womp".to_string(),
|
||||
mentions: None,
|
||||
};
|
||||
|
||||
// Publish the post.
|
||||
let post_msg_ref = sbot_client.publish(post).await?;
|
||||
|
||||
// Print the post reference to `stdout`.
|
||||
println!("post_msg_ref: {}", post_msg_ref);
|
||||
|
||||
// Golgi also exposes convenience methods for some of the most common
|
||||
// message types. Here we see an example of a convenience method for
|
||||
// posting a description message. The description is for the local
|
||||
// identity, ie. we are publishing this about "ourself".
|
||||
let post_msg_ref = sbot_client
|
||||
.publish_description("this is a description")
|
||||
.await?;
|
||||
|
||||
// Print the description message reference to `stdout`.
|
||||
println!("description: {}", post_msg_ref);
|
||||
|
||||
let author: String = id.clone();
|
||||
println!("author: {:?}", author);
|
||||
|
||||
// Retrieve the description for the given public key (identity).
|
||||
let description = sbot_client.get_description(&author).await?;
|
||||
|
||||
// Print the description to `stdout`.
|
||||
println!("found description: {:?}", description);
|
||||
|
||||
// Compose and publish another `post` message type.
|
||||
let post = SsbMessageContent::Post {
|
||||
text: "golgi go womp womp2".to_string(),
|
||||
mentions: None,
|
||||
};
|
||||
|
||||
let post_msg_ref = sbot_client.publish(post).await?;
|
||||
println!("post_msg_ref2: {}", post_msg_ref);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Enable an async main function and execute the `run()` function,
|
||||
// catching any errors and printing them to `stderr` before exiting the
|
||||
// process.
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
if let Err(e) = run().await {
|
||||
eprintln!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
129
examples/friends.rs
Normal file
129
examples/friends.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use std::process;
|
||||
|
||||
use golgi::{
|
||||
api::friends::{FriendsHops, RelationshipQuery},
|
||||
sbot::Keystore,
|
||||
GolgiError, Sbot,
|
||||
};
|
||||
|
||||
// Golgi is an asynchronous library so we must call it from within an
|
||||
// async function. The `GolgiError` type encapsulates all possible
|
||||
// error variants for the library.
|
||||
async fn run() -> Result<(), GolgiError> {
|
||||
// Attempt to initialise a connection to an sbot instance using the
|
||||
// secret file at the Patchwork path and the default IP address, port
|
||||
// and network key (aka. capabilities key).
|
||||
let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
|
||||
// Call the `whoami` RPC method to retrieve the public key for the sbot
|
||||
// identity. This is our 'local' public key.
|
||||
let id = sbot_client.whoami().await?;
|
||||
|
||||
// Print the public key (identity) to `stdout`.
|
||||
println!("whoami: {}", id);
|
||||
|
||||
// Define IDs (public keys) to follow and block.
|
||||
let to_follow = String::from("@5Pt3dKy2HTJ0mWuS78oIiklIX0gBz6BTfEnXsbvke9c=.ed25519");
|
||||
let to_block = String::from("@7Y4nwfQmVtAilEzi5knXdS2gilW7cGKSHXdXoT086LM=.ed25519");
|
||||
|
||||
// Set the relationship of the local identity to the `to_follow` identity.
|
||||
// In this case, the `set_relationship` method publishes a `contact`
|
||||
// message which defines following as `true`.
|
||||
// A message reference is returned for the published `contact` message.
|
||||
let response = sbot_client
|
||||
.set_relationship(&to_follow, Some(true), None)
|
||||
.await?;
|
||||
|
||||
// Print the message reference to `stdout`.
|
||||
println!("follow_response: {:?}", response);
|
||||
|
||||
// Set the relationship of the local identity to the `to_block` identity.
|
||||
// In this case, the `set_relationship` method publishes a `contact`
|
||||
// message which defines blocking as `true`.
|
||||
// A message reference is returned for the published `contact` message.
|
||||
let response = sbot_client
|
||||
.set_relationship(&to_block, None, Some(true))
|
||||
.await?;
|
||||
|
||||
// Print the message reference to `stdout`.
|
||||
println!("follow_response: {:?}", response);
|
||||
|
||||
// Golgi also exposes convenience methods for following and blocking.
|
||||
// Here is an example of a simpler way to follow an identity.
|
||||
let _follow_response = sbot_client.follow(&to_follow).await?;
|
||||
|
||||
// Blocking can be achieved in a similar fashion.
|
||||
let _block_response = sbot_client.block(&to_block).await?;
|
||||
|
||||
// Get a list of peers within 0 hops of the local identity.
|
||||
// This returns a list of peers whom we follow.
|
||||
// If `max` is set to 1, the list will include the peers we follow plus
|
||||
// the peers that they follow.
|
||||
let follows = sbot_client
|
||||
.friends_hops(FriendsHops {
|
||||
max: 0,
|
||||
start: None,
|
||||
// The `reverse` parameter is not currently implemented in `go-sbot`.
|
||||
reverse: Some(false),
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Print the list of peers to `stdout`.
|
||||
println!("follows: {:?}", follows);
|
||||
|
||||
// Determine if an identity (`source`) is following a second identity (`dest`).
|
||||
// This method will return `true` or `false`.
|
||||
let mref = sbot_client
|
||||
.friends_is_following(RelationshipQuery {
|
||||
source: id.clone(),
|
||||
dest: to_follow.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Print the follow status to `stdout`.
|
||||
println!("isfollowingmref: {}", mref);
|
||||
|
||||
// Determine if an identity (`source`) is blocking a second identity (`dest`).
|
||||
let mref = sbot_client
|
||||
.friends_is_blocking(RelationshipQuery {
|
||||
source: id.clone(),
|
||||
dest: to_block.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Print the block status to `stdout`.
|
||||
println!("isblockingmref: {}", mref);
|
||||
|
||||
let mref = sbot_client
|
||||
.friends_is_blocking(RelationshipQuery {
|
||||
source: id.clone(),
|
||||
dest: to_follow,
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Output should be `false`.
|
||||
println!("isblockingmref(should be false): {}", mref);
|
||||
|
||||
let mref = sbot_client
|
||||
.friends_is_following(RelationshipQuery {
|
||||
source: id,
|
||||
dest: to_block.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Output should be `false`.
|
||||
println!("isfollowingmref(should be false): {}", mref);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Enable an async main function and execute the `run()` function,
|
||||
// catching any errors and printing them to `stderr` before exiting the
|
||||
// process.
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
if let Err(e) = run().await {
|
||||
eprintln!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
77
examples/invite.rs
Normal file
77
examples/invite.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::process;
|
||||
|
||||
use kuska_ssb::api::dto::content::PubAddress;
|
||||
|
||||
use golgi::{messages::SsbMessageContent, sbot::Keystore, GolgiError, Sbot};
|
||||
|
||||
// Golgi is an asynchronous library so we must call it from within an
|
||||
// async function. The `GolgiError` type encapsulates all possible
|
||||
// error variants for the library.
|
||||
async fn run() -> Result<(), GolgiError> {
|
||||
// Attempt to initialise a connection to an sbot instance using the
|
||||
// secret file at the Patchwork path and the default IP address, port
|
||||
// and network key (aka. capabilities key).
|
||||
let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
|
||||
// Call the `whoami` RPC method to retrieve the public key for the sbot
|
||||
// identity. This is our 'local' public key.
|
||||
let id = sbot_client.whoami().await?;
|
||||
|
||||
// Print the public key (identity) to `stdout`.
|
||||
println!("whoami: {}", id);
|
||||
|
||||
// Compose a `pub` address type message.
|
||||
let pub_address_msg = SsbMessageContent::Pub {
|
||||
address: Some(PubAddress {
|
||||
// IP address.
|
||||
host: Some("127.0.0.1".to_string()),
|
||||
// Port.
|
||||
port: 8009,
|
||||
// Public key.
|
||||
key: id,
|
||||
}),
|
||||
};
|
||||
|
||||
// Publish the `pub` address message.
|
||||
// This step is required for successful invite code creation.
|
||||
let pub_msg_ref = sbot_client.publish(pub_address_msg).await?;
|
||||
|
||||
// Print the message reference to `stdout`.
|
||||
println!("pub_msg_ref: {}", pub_msg_ref);
|
||||
|
||||
// Generate an invite code that can be used 1 time.
|
||||
let invite_code = sbot_client.invite_create(1).await?;
|
||||
|
||||
// Print the invite code to `stdout`.
|
||||
println!("invite (1 use): {:?}", invite_code);
|
||||
|
||||
// Generate an invite code that can be used 7 times.
|
||||
let invite_code_2 = sbot_client.invite_create(7).await?;
|
||||
|
||||
// Print the invite code to `stdout`.
|
||||
println!("invite (7 uses): {:?}", invite_code_2);
|
||||
|
||||
// Define an invite code.
|
||||
let test_invite =
|
||||
"net:ssbroom2.commoninternet.net:8009~shs:wm8a1zHWjtESv4XSKMWU/rPRhnAoAiSAe4hQSY0UF5A=";
|
||||
|
||||
// Redeem an invite code (initiating a mutual follow between the local
|
||||
// identity and the identity which generated the code (`wm8a1z...`).
|
||||
let mref = sbot_client.invite_use(test_invite).await?;
|
||||
|
||||
// Print the message reference to `stdout`.
|
||||
println!("mref: {:?}", mref);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Enable an async main function and execute the `run()` function,
|
||||
// catching any errors and printing them to `stderr` before exiting the
|
||||
// process.
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
if let Err(e) = run().await {
|
||||
eprintln!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use std::process;
|
||||
|
||||
use kuska_ssb::api::dto::content::{SubsetQuery, TypedMessage};
|
||||
|
||||
use golgi::error::GolgiError;
|
||||
use golgi::sbot::Sbot;
|
||||
|
||||
async fn run() -> Result<(), GolgiError> {
|
||||
let mut sbot_client = Sbot::init(None, None).await?;
|
||||
|
||||
let id = sbot_client.whoami().await?;
|
||||
println!("{}", id);
|
||||
|
||||
/*
|
||||
let name = TypedMessage::About {
|
||||
about: id,
|
||||
name: Some("golgi".to_string()),
|
||||
title: None,
|
||||
branch: None,
|
||||
image: None,
|
||||
description: None,
|
||||
location: None,
|
||||
start_datetime: None,
|
||||
};
|
||||
|
||||
let name_msg_ref = sbot_client.publish(name).await?;
|
||||
println!("{}", name_msg_ref);
|
||||
|
||||
let post = TypedMessage::Post {
|
||||
text: "golgi go womp womp".to_string(),
|
||||
mentions: None,
|
||||
};
|
||||
|
||||
let post_msg_ref = sbot_client.publish(post).await?;
|
||||
println!("{}", post_msg_ref);
|
||||
|
||||
let post_msg_ref = sbot_client
|
||||
.publish_description("this is a description")
|
||||
.await?;
|
||||
println!("description: {}", post_msg_ref);
|
||||
*/
|
||||
|
||||
let query = SubsetQuery::Type {
|
||||
op: "type".to_string(),
|
||||
string: "vote".to_string(),
|
||||
};
|
||||
let query_response = sbot_client.getsubset(query).await?;
|
||||
println!("{}", query_response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
if let Err(e) = run().await {
|
||||
eprintln!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
161
examples/streams.rs
Normal file
161
examples/streams.rs
Normal file
@ -0,0 +1,161 @@
|
||||
use std::process;
|
||||
|
||||
use async_std::stream::StreamExt;
|
||||
use futures::TryStreamExt;
|
||||
|
||||
use golgi::{
|
||||
api::get_subset::{SubsetQuery, SubsetQueryOptions},
|
||||
messages::{SsbMessageContentType, SsbMessageValue},
|
||||
sbot::Keystore,
|
||||
GolgiError, Sbot,
|
||||
};
|
||||
|
||||
// Golgi is an asynchronous library so we must call it from within an
|
||||
// async function. The `GolgiError` type encapsulates all possible
|
||||
// error variants for the library.
|
||||
async fn run() -> Result<(), GolgiError> {
|
||||
// Attempt to initialise a connection to an sbot instance using the
|
||||
// secret file at the Patchwork path and the default IP address, port
|
||||
// and network key (aka. capabilities key).
|
||||
let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
|
||||
// Call the `whoami` RPC method to retrieve the public key for the sbot
|
||||
// identity. This is our 'local' public key.
|
||||
let id = sbot_client.whoami().await?;
|
||||
|
||||
// Print the public key (identity) to `stdout`.
|
||||
println!("whoami: {}", id);
|
||||
|
||||
let author = id.clone();
|
||||
|
||||
/* HISTORY STREAM EXAMPLE */
|
||||
|
||||
// Create an ordered stream of all messages authored by the `author`
|
||||
// identity.
|
||||
let history_stream = sbot_client
|
||||
.create_history_stream(author.to_string())
|
||||
.await?;
|
||||
|
||||
// Pin the stream to the stack to allow polling of the `future`.
|
||||
futures::pin_mut!(history_stream);
|
||||
|
||||
println!("looping through stream");
|
||||
|
||||
// Iterate through each element in the stream and match on the `Result`.
|
||||
// In this case, each element has type `Result<SsbMessageValue, GolgiError>`.
|
||||
while let Some(res) = history_stream.next().await {
|
||||
match res {
|
||||
Ok(value) => {
|
||||
// Print the `SsbMessageValue` of this element to `stdout`.
|
||||
println!("value: {:?}", value);
|
||||
}
|
||||
Err(err) => {
|
||||
// Print the `GolgiError` of this element to `stderr`.
|
||||
eprintln!("err: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("reached end of stream");
|
||||
|
||||
// Create an ordered stream of all messages authored by the `author`
|
||||
// identity.
|
||||
let history_stream = sbot_client
|
||||
.create_history_stream(author.to_string())
|
||||
.await?;
|
||||
|
||||
// Collect the stream elements into a `Vec<SsbMessageValue>` using
|
||||
// `try_collect`. A `GolgiError` will be returned from the `run`
|
||||
// function if any element contains an error.
|
||||
let results: Vec<SsbMessageValue> = history_stream.try_collect().await?;
|
||||
|
||||
// Loop through the `SsbMessageValue` elements, printing each one
|
||||
// to `stdout`.
|
||||
for x in results {
|
||||
println!("x: {:?}", x);
|
||||
}
|
||||
|
||||
// Create an ordered stream of all messages authored by the `author`
|
||||
// identity.
|
||||
let history_stream = sbot_client
|
||||
.create_history_stream(author.to_string())
|
||||
.await?;
|
||||
|
||||
// Iterate through the elements in the stream and use `map` to convert
|
||||
// each `SsbMessageValue` element into a tuple of
|
||||
// `(String, SsbMessageContentType)`. This is an example of stream
|
||||
// conversion.
|
||||
let type_stream = history_stream.map(|msg| match msg {
|
||||
Ok(val) => {
|
||||
let message_type = val.get_message_type()?;
|
||||
let tuple: (String, SsbMessageContentType) = (val.signature, message_type);
|
||||
Ok(tuple)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
});
|
||||
|
||||
// Pin the stream to the stack to allow polling of the `future`.
|
||||
futures::pin_mut!(type_stream);
|
||||
|
||||
println!("looping through type stream");
|
||||
|
||||
// Iterate through each element in the stream and match on the `Result`.
|
||||
// In this case, each element has type
|
||||
// `Result<(String, SsbMessageContentType), GolgiError>`.
|
||||
while let Some(res) = type_stream.next().await {
|
||||
match res {
|
||||
Ok(value) => {
|
||||
println!("value: {:?}", value);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("err: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("reached end of type stream");
|
||||
|
||||
/* SUBSET STREAM EXAMPLE */
|
||||
|
||||
// Compose a subset query for `post` message types.
|
||||
let post_query = SubsetQuery::Type {
|
||||
op: "type".to_string(),
|
||||
string: "post".to_string(),
|
||||
};
|
||||
|
||||
// Define the options for the query.
|
||||
let post_query_opts = SubsetQueryOptions {
|
||||
descending: Some(true),
|
||||
keys: None,
|
||||
page_limit: Some(5),
|
||||
};
|
||||
|
||||
// Return 5 `post` type messages from any author in descending order.
|
||||
let query_stream = sbot_client
|
||||
.get_subset_stream(post_query, Some(post_query_opts))
|
||||
.await?;
|
||||
|
||||
println!("looping through post query stream");
|
||||
|
||||
// Iterate over the stream and pretty-print each returned message
|
||||
// value while ignoring any errors.
|
||||
query_stream.for_each(|msg| match msg {
|
||||
Ok(val) => println!("{:#?}", val),
|
||||
Err(_) => (),
|
||||
});
|
||||
|
||||
println!("reached end of post query stream");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Enable an async main function and execute the `run()` function,
|
||||
// catching any errors and printing them to `stderr` before exiting the
|
||||
// process.
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
if let Err(e) = run().await {
|
||||
eprintln!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
1
git_hooks/pre-commit
Executable file
1
git_hooks/pre-commit
Executable file
@ -0,0 +1 @@
|
||||
cargo fmt
|
387
src/api/about.rs
Normal file
387
src/api/about.rs
Normal file
@ -0,0 +1,387 @@
|
||||
//! Retrieve data about a peer.
|
||||
//!
|
||||
//! Implements the following methods:
|
||||
//!
|
||||
//! - [`Sbot::get_about_info`]
|
||||
//! - [`Sbot::get_about_message_stream`]
|
||||
//! - [`Sbot::get_description`]
|
||||
//! - [`Sbot::get_latest_about_message`]
|
||||
//! - [`Sbot::get_name`]
|
||||
//! - [`Sbot::get_name_and_image`]
|
||||
//! - [`Sbot::get_profile_info`]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_std::stream::{Stream, StreamExt};
|
||||
|
||||
use crate::{
|
||||
api::get_subset::{SubsetQuery, SubsetQueryOptions},
|
||||
error::GolgiError,
|
||||
messages::{SsbMessageContentType, SsbMessageValue},
|
||||
sbot::Sbot,
|
||||
};
|
||||
|
||||
impl Sbot {
|
||||
/// Get all the `about` type messages for a peer in order of recency
|
||||
/// (ie. most recent messages first).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use async_std::stream::{Stream, StreamExt};
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn about_message_stream() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// let about_message_stream = sbot_client.get_about_message_stream(ssb_id).await?;
|
||||
///
|
||||
/// // Make the stream into an iterator.
|
||||
/// futures::pin_mut!(about_message_stream);
|
||||
///
|
||||
/// about_message_stream.for_each(|msg| {
|
||||
/// match msg {
|
||||
/// Ok(val) => println!("msg value: {:?}", val),
|
||||
/// Err(e) => eprintln!("error: {}", e),
|
||||
/// }
|
||||
/// }).await;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_about_message_stream(
|
||||
&mut self,
|
||||
ssb_id: &str,
|
||||
) -> Result<impl Stream<Item = Result<SsbMessageValue, GolgiError>>, GolgiError> {
|
||||
let query = SubsetQuery::Author {
|
||||
op: "author".to_string(),
|
||||
feed: ssb_id.to_string(),
|
||||
};
|
||||
|
||||
// specify that most recent messages should be returned first
|
||||
let query_options = SubsetQueryOptions {
|
||||
descending: Some(true),
|
||||
keys: None,
|
||||
page_limit: None,
|
||||
};
|
||||
|
||||
let get_subset_stream = self.get_subset_stream(query, Some(query_options)).await?;
|
||||
|
||||
// TODO: after fixing sbot regression,
|
||||
// change this subset query to filter by type about in addition to author
|
||||
// and remove this filter section
|
||||
// filter down to about messages
|
||||
let about_message_stream = get_subset_stream.filter(|msg| match msg {
|
||||
Ok(val) => val.is_message_type(SsbMessageContentType::About),
|
||||
Err(_err) => false,
|
||||
});
|
||||
|
||||
// return about message stream
|
||||
Ok(about_message_stream)
|
||||
}
|
||||
|
||||
/// Get the value of the latest `about` type message, containing the given
|
||||
/// `key`, for a peer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn name_info() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
/// let key = "name";
|
||||
///
|
||||
/// let name_info = sbot_client.get_latest_about_message(ssb_id, key).await?;
|
||||
///
|
||||
/// match name_info {
|
||||
/// Some(name) => println!("peer {} is named {}", ssb_id, name),
|
||||
/// None => println!("no name found for peer {}", ssb_id)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_latest_about_message(
|
||||
&mut self,
|
||||
ssb_id: &str,
|
||||
key: &str,
|
||||
) -> Result<Option<String>, GolgiError> {
|
||||
// get about_message_stream
|
||||
let about_message_stream = self.get_about_message_stream(ssb_id).await?;
|
||||
|
||||
// now we have a stream of about messages with most recent at the front
|
||||
// of the vector
|
||||
futures::pin_mut!(about_message_stream);
|
||||
|
||||
// iterate through the vector looking for most recent about message with
|
||||
// the given key
|
||||
let latest_about_message_res: Option<Result<SsbMessageValue, GolgiError>> =
|
||||
about_message_stream
|
||||
// find the first msg that contains the field `key`
|
||||
.find(|res| match res {
|
||||
Ok(msg) => msg.content.get(key).is_some(),
|
||||
Err(_) => false,
|
||||
})
|
||||
.await;
|
||||
|
||||
// Option<Result<SsbMessageValue, GolgiError>> -> Option<SsbMessageValue>
|
||||
let latest_about_message = latest_about_message_res.and_then(|msg| msg.ok());
|
||||
|
||||
// Option<SsbMessageValue> -> Option<String>
|
||||
let latest_about_value = latest_about_message.and_then(|msg| {
|
||||
msg
|
||||
// SsbMessageValue -> Option<&Value>
|
||||
.content
|
||||
.get(key)
|
||||
// Option<&Value> -> <Option<&str>
|
||||
.and_then(|value| value.as_str())
|
||||
// Option<&str> -> Option<String>
|
||||
.map(|value| value.to_string())
|
||||
});
|
||||
|
||||
// return value is either `Ok(Some(String))` or `Ok(None)`
|
||||
Ok(latest_about_value)
|
||||
}
|
||||
|
||||
/// Get the latest `name`, `description` and `image` values for a peer,
|
||||
/// as defined in their `about` type messages.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn profile_info() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// let profile_info = sbot_client.get_profile_info(ssb_id).await?;
|
||||
///
|
||||
/// let name = profile_info.get("name");
|
||||
/// let description = profile_info.get("description");
|
||||
/// let image = profile_info.get("image");
|
||||
///
|
||||
/// match (name, description, image) {
|
||||
/// (Some(name), Some(desc), Some(image)) => {
|
||||
/// println!(
|
||||
/// "peer {} is named {}. their profile image blob reference is {} and they describe themself as follows: {}",
|
||||
/// ssb_id, name, image, desc,
|
||||
/// )
|
||||
/// },
|
||||
/// (_, _, _) => {
|
||||
/// eprintln!("failed to retrieve all profile info values")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_profile_info(
|
||||
&mut self,
|
||||
ssb_id: &str,
|
||||
) -> Result<HashMap<String, String>, GolgiError> {
|
||||
let keys_to_search_for = vec!["name", "description", "image"];
|
||||
self.get_about_info(ssb_id, keys_to_search_for).await
|
||||
}
|
||||
|
||||
/// Get the latest `name` and `image` values for a peer. This method can
|
||||
/// be used to display profile images of a list of users.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn name_and_image_info() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// let profile_info = sbot_client.get_name_and_image(ssb_id).await?;
|
||||
///
|
||||
/// let name = profile_info.get("name");
|
||||
/// let image = profile_info.get("image");
|
||||
///
|
||||
/// match (name, image) {
|
||||
/// (Some(name), Some(image)) => {
|
||||
/// println!(
|
||||
/// "peer {} is named {}. their profile image blob reference is {}.",
|
||||
/// ssb_id, name, image,
|
||||
/// )
|
||||
/// },
|
||||
/// (Some(name), None) => {
|
||||
/// println!(
|
||||
/// "peer {} is named {}. no image blob reference was found for them.",
|
||||
/// ssb_id, name,
|
||||
/// )
|
||||
/// },
|
||||
/// (_, _) => {
|
||||
/// eprintln!("failed to retrieve all profile info values")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
pub async fn get_name_and_image(
|
||||
&mut self,
|
||||
ssb_id: &str,
|
||||
) -> Result<HashMap<String, String>, GolgiError> {
|
||||
let keys_to_search_for = vec!["name", "image"];
|
||||
self.get_about_info(ssb_id, keys_to_search_for).await
|
||||
}
|
||||
|
||||
/// Get the latest values for the provided keys from the `about` type
|
||||
/// messages of a peer. The method will return once a value has been
|
||||
/// found for each key, or once all messages have been checked.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn about_info() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
/// let keys_to_search_for = vec!["name", "description"];
|
||||
///
|
||||
/// let about_info = sbot_client.get_about_info(ssb_id, keys_to_search_for).await?;
|
||||
///
|
||||
/// let name = about_info.get("name");
|
||||
/// let description = about_info.get("description");
|
||||
///
|
||||
/// match (name, description) {
|
||||
/// (Some(name), Some(desc)) => {
|
||||
/// println!(
|
||||
/// "peer {} is named {}. they describe themself as: {}",
|
||||
/// ssb_id, name, desc,
|
||||
/// )
|
||||
/// },
|
||||
/// (Some(name), None) => {
|
||||
/// println!(
|
||||
/// "peer {} is named {}. no description was found for them.",
|
||||
/// ssb_id, name,
|
||||
/// )
|
||||
/// },
|
||||
/// (_, _) => {
|
||||
/// eprintln!("failed to retrieve all profile info values")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_about_info(
|
||||
&mut self,
|
||||
ssb_id: &str,
|
||||
mut keys_to_search_for: Vec<&str>,
|
||||
) -> Result<HashMap<String, String>, GolgiError> {
|
||||
// get about_message_stream
|
||||
let about_message_stream = self.get_about_message_stream(ssb_id).await?;
|
||||
|
||||
// now we have a stream of about messages with most recent at the front
|
||||
// of the vector
|
||||
|
||||
// `pin_mut!` is needed for iteration
|
||||
futures::pin_mut!(about_message_stream);
|
||||
|
||||
let mut profile_info: HashMap<String, String> = HashMap::new();
|
||||
|
||||
// iterate through the stream while it still has more values and
|
||||
// we still have keys we are looking for
|
||||
while let Some(res) = about_message_stream.next().await {
|
||||
// if there are no more keys we are looking for, then we are done
|
||||
if keys_to_search_for.is_empty() {
|
||||
break;
|
||||
}
|
||||
// if there are still keys we are looking for, then continue searching
|
||||
match res {
|
||||
Ok(msg) => {
|
||||
// for each key we are searching for, check if this about
|
||||
// message contains a value for that key
|
||||
for key in &keys_to_search_for.clone() {
|
||||
let about_val = msg.content.get("about").and_then(|val| val.as_str());
|
||||
let option_val = msg
|
||||
.content
|
||||
.get(key)
|
||||
.and_then(|val| val.as_str())
|
||||
.map(|val| val.to_string());
|
||||
match option_val {
|
||||
// only return val if this msg is about the given ssb_id
|
||||
Some(val) if about_val == Some(ssb_id) => {
|
||||
// if a value is found, then insert it
|
||||
profile_info.insert(key.to_string(), val);
|
||||
// remove this key from keys_to_search_for,
|
||||
// since we are no longer searching for it
|
||||
keys_to_search_for.retain(|val| val != key)
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
// skip errors
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(profile_info)
|
||||
}
|
||||
|
||||
/// Get the latest `name` value for a peer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn name_info() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// if let Some(name) = sbot_client.get_name(ssb_id).await? {
|
||||
/// println!("peer {} is named {}", ssb_id, name)
|
||||
/// } else {
|
||||
/// eprintln!("no name found for peer {}", ssb_id)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_name(&mut self, ssb_id: &str) -> Result<Option<String>, GolgiError> {
|
||||
self.get_latest_about_message(ssb_id, "name").await
|
||||
}
|
||||
|
||||
/// Get the latest `description` value for a peer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn description_info() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// if let Some(desc) = sbot_client.get_description(ssb_id).await? {
|
||||
/// println!("peer {} describes themself as follows: {}", ssb_id, desc)
|
||||
/// } else {
|
||||
/// eprintln!("no description found for peer {}", ssb_id)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_description(&mut self, ssb_id: &str) -> Result<Option<String>, GolgiError> {
|
||||
self.get_latest_about_message(ssb_id, "description").await
|
||||
}
|
||||
}
|
432
src/api/friends.rs
Normal file
432
src/api/friends.rs
Normal file
@ -0,0 +1,432 @@
|
||||
//! Define peer relationships and query the social graph.
|
||||
//!
|
||||
//! Implements the following methods:
|
||||
//!
|
||||
//! - [`Sbot::block`]
|
||||
//! - [`Sbot::unblock`]
|
||||
//! - [`Sbot::follow`]
|
||||
//! - [`Sbot::unfollow`]
|
||||
//! - [`Sbot::friends_hops`]
|
||||
//! - [`Sbot::friends_is_blocking`]
|
||||
//! - [`Sbot::friends_is_following`]
|
||||
//! - [`Sbot::get_blocks`]
|
||||
//! - [`Sbot::get_follows`]
|
||||
//! - [`Sbot::set_relationship`]
|
||||
|
||||
use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils};
|
||||
|
||||
// re-export friends-related kuska types
|
||||
pub use kuska_ssb::api::dto::content::{FriendsHops, RelationshipQuery};
|
||||
|
||||
impl Sbot {
|
||||
/// Follow a peer.
|
||||
///
|
||||
/// This is a convenience method to publish a contact message with
|
||||
/// following: `true`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn follow_peer() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// match sbot_client.follow(ssb_id).await {
|
||||
/// Ok(msg_ref) => {
|
||||
/// println!("follow msg reference is: {}", msg_ref)
|
||||
/// },
|
||||
/// Err(e) => eprintln!("failed to follow {}: {}", ssb_id, e)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn follow(&mut self, contact: &str) -> Result<String, GolgiError> {
|
||||
self.set_relationship(contact, Some(true), None).await
|
||||
}
|
||||
|
||||
/// Unfollow a peer.
|
||||
///
|
||||
/// This is a convenience method to publish a contact message with
|
||||
/// following: `false`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn unfollow_peer() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// match sbot_client.unfollow(ssb_id).await {
|
||||
/// Ok(msg_ref) => {
|
||||
/// println!("unfollow msg reference is: {}", msg_ref)
|
||||
/// },
|
||||
/// Err(e) => eprintln!("failed to unfollow {}: {}", ssb_id, e)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn unfollow(&mut self, contact: &str) -> Result<String, GolgiError> {
|
||||
self.set_relationship(contact, Some(false), None).await
|
||||
}
|
||||
|
||||
/// Block a peer.
|
||||
///
|
||||
/// This is a convenience method to publish a contact message with
|
||||
/// following: `false` and blocking: `true`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn block_peer() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// match sbot_client.block(ssb_id).await {
|
||||
/// Ok(msg_ref) => {
|
||||
/// println!("block msg reference is: {}", msg_ref)
|
||||
/// },
|
||||
/// Err(e) => eprintln!("failed to block {}: {}", ssb_id, e)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn block(&mut self, contact: &str) -> Result<String, GolgiError> {
|
||||
// we want to unfollow and block
|
||||
self.set_relationship(contact, Some(false), Some(true))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Unblock a peer.
|
||||
///
|
||||
/// This is a convenience method to publish a contact message with
|
||||
/// blocking: `false`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn unblock_peer() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
///
|
||||
/// match sbot_client.unblock(ssb_id).await {
|
||||
/// Ok(msg_ref) => {
|
||||
/// println!("unblock msg reference is: {}", msg_ref)
|
||||
/// },
|
||||
/// Err(e) => eprintln!("failed to unblock {}: {}", ssb_id, e)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn unblock(&mut self, contact: &str) -> Result<String, GolgiError> {
|
||||
self.set_relationship(contact, None, Some(false)).await
|
||||
}
|
||||
|
||||
/// Publish a contact message defining the relationship for a peer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn relationship() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
/// let following = Some(true);
|
||||
/// // Could also be `None` to only publish the following relationship.
|
||||
/// let blocking = Some(false);
|
||||
///
|
||||
/// match sbot_client.set_relationship(ssb_id, following, blocking).await {
|
||||
/// Ok(msg_ref) => {
|
||||
/// println!("contact msg reference is: {}", msg_ref)
|
||||
/// },
|
||||
/// Err(e) => eprintln!("failed to set relationship for {}: {}", ssb_id, e)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn set_relationship(
|
||||
&mut self,
|
||||
contact: &str,
|
||||
following: Option<bool>,
|
||||
blocking: Option<bool>,
|
||||
) -> Result<String, GolgiError> {
|
||||
let msg = SsbMessageContent::Contact {
|
||||
contact: Some(contact.to_string()),
|
||||
following,
|
||||
blocking,
|
||||
autofollow: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/// Get the follow status of two peers (ie. does one peer follow the other?).
|
||||
///
|
||||
/// A `RelationshipQuery` `struct` must be defined and passed into this method.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, api::friends::RelationshipQuery, sbot::Keystore};
|
||||
///
|
||||
/// async fn relationship() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let peer_a = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
/// let peer_b = "@3QoWCcy46X9a4jTnOl8m3+n1gKfbsukWuODDxNGN0W8=.ed25519";
|
||||
///
|
||||
/// let query = RelationshipQuery {
|
||||
/// source: peer_a.to_string(),
|
||||
/// dest: peer_b.to_string(),
|
||||
/// };
|
||||
///
|
||||
/// match sbot_client.friends_is_following(query).await {
|
||||
/// Ok(following) if following == "true" => {
|
||||
/// println!("{} is following {}", peer_a, peer_b)
|
||||
/// },
|
||||
/// Ok(_) => println!("{} is not following {}", peer_a, peer_b),
|
||||
/// Err(e) => eprintln!("failed to query relationship status for {} and {}: {}", peer_a,
|
||||
/// peer_b, e)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn friends_is_following(
|
||||
&mut self,
|
||||
args: RelationshipQuery,
|
||||
) -> Result<String, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection
|
||||
.client
|
||||
.friends_is_following_req_send(args)
|
||||
.await?;
|
||||
|
||||
utils::get_async(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the block status of two peers (ie. does one peer block the other?).
|
||||
///
|
||||
/// A `RelationshipQuery` `struct` must be defined and passed into this method.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, api::friends::RelationshipQuery, sbot::Keystore};
|
||||
///
|
||||
/// async fn relationship() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let peer_a = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519";
|
||||
/// let peer_b = "@3QoWCcy46X9a4jTnOl8m3+n1gKfbsukWuODDxNGN0W8=.ed25519";
|
||||
///
|
||||
/// let query = RelationshipQuery {
|
||||
/// source: peer_a.to_string(),
|
||||
/// dest: peer_b.to_string(),
|
||||
/// };
|
||||
///
|
||||
/// match sbot_client.friends_is_blocking(query).await {
|
||||
/// Ok(blocking) if blocking == "true" => {
|
||||
/// println!("{} is blocking {}", peer_a, peer_b)
|
||||
/// },
|
||||
/// Ok(_) => println!("{} is not blocking {}", peer_a, peer_b),
|
||||
/// Err(e) => eprintln!("failed to query relationship status for {} and {}: {}", peer_a,
|
||||
/// peer_b, e)
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn friends_is_blocking(
|
||||
&mut self,
|
||||
args: RelationshipQuery,
|
||||
) -> Result<String, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection
|
||||
.client
|
||||
.friends_is_blocking_req_send(args)
|
||||
.await?;
|
||||
|
||||
utils::get_async(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of peers blocked by the local peer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn follows() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let follows = sbot_client.get_blocks().await?;
|
||||
///
|
||||
/// if follows.is_empty() {
|
||||
/// println!("we do not block any peers")
|
||||
/// } else {
|
||||
/// follows.iter().for_each(|peer| println!("we block {}", peer))
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_blocks(&mut self) -> Result<Vec<String>, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection.client.friends_blocks_req_send().await?;
|
||||
utils::get_source_until_eof(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of peers followed by the local peer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn follows() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let follows = sbot_client.get_follows().await?;
|
||||
///
|
||||
/// if follows.is_empty() {
|
||||
/// println!("we do not follow any peers")
|
||||
/// } else {
|
||||
/// follows.iter().for_each(|peer| println!("we follow {}", peer))
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_follows(&mut self) -> Result<Vec<String>, GolgiError> {
|
||||
self.friends_hops(FriendsHops {
|
||||
max: 0,
|
||||
start: None,
|
||||
reverse: Some(false),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of peers following the local peer.
|
||||
///
|
||||
/// NOTE: this method is not currently working as expected.
|
||||
///
|
||||
/// go-sbot does not currently implement the `reverse=True` parameter.
|
||||
/// As a result, the parameter is ignored and this method returns follows
|
||||
/// instead of followers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn followers() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// // let followers = sbot_client.get_followers().await?;
|
||||
///
|
||||
/// // if followers.is_empty() {
|
||||
/// // println!("no peers follow us")
|
||||
/// // } else {
|
||||
/// // followers.iter().for_each(|peer| println!("{} is following us", peer))
|
||||
/// // }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
async fn _get_followers(&mut self) -> Result<Vec<String>, GolgiError> {
|
||||
self.friends_hops(FriendsHops {
|
||||
max: 0,
|
||||
start: None,
|
||||
reverse: Some(true),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of peers within the specified hops range.
|
||||
///
|
||||
/// A `RelationshipQuery` `struct` must be defined and passed into this method.
|
||||
///
|
||||
/// Hops = 0 returns a list of peers followed by the local identity.
|
||||
/// Those peers may or may not be mutual follows (ie. friends).
|
||||
///
|
||||
/// Hops = 1 includes the peers followed by the peers we follow.
|
||||
/// For example, if the local identity follows Aiko and Aiko follows
|
||||
/// Bridgette and Chris, hops = 1 will return a list including the public
|
||||
/// keys for Aiko, Bridgette and Chris (even though Bridgette and Chris
|
||||
/// are not followed by the local identity).
|
||||
///
|
||||
/// When reverse = True, hops = 0 should return a list of peers who
|
||||
/// follow the local identity, ie. followers (but this is not currently
|
||||
/// implemented in go-sbot).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, api::friends::FriendsHops, sbot::Keystore};
|
||||
///
|
||||
/// async fn peers_within_range() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let hops = 2;
|
||||
///
|
||||
/// let query = FriendsHops {
|
||||
/// max: hops,
|
||||
/// reverse: Some(false),
|
||||
/// start: None,
|
||||
/// };
|
||||
///
|
||||
/// let peers = sbot_client.friends_hops(query).await?;
|
||||
///
|
||||
/// if peers.is_empty() {
|
||||
/// println!("no peers found within {} hops", hops)
|
||||
/// } else {
|
||||
/// peers.iter().for_each(|peer| println!("{} is within {} hops", peer, hops))
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn friends_hops(&mut self, args: FriendsHops) -> Result<Vec<String>, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection.client.friends_hops_req_send(args).await?;
|
||||
utils::get_source_until_eof(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
87
src/api/get_subset.rs
Normal file
87
src/api/get_subset.rs
Normal file
@ -0,0 +1,87 @@
|
||||
//! Perform subset queries.
|
||||
//!
|
||||
//! Implements the following methods:
|
||||
//!
|
||||
//! - [`Sbot::get_subset_stream`]
|
||||
|
||||
use async_std::stream::Stream;
|
||||
|
||||
use crate::{error::GolgiError, messages::SsbMessageValue, sbot::Sbot, utils};
|
||||
|
||||
// re-export subset-related kuska types
|
||||
pub use kuska_ssb::api::dto::content::{SubsetQuery, SubsetQueryOptions};
|
||||
|
||||
impl Sbot {
|
||||
/// Make a subset query, as defined by the [Subset replication for SSB specification](https://github.com/ssb-ngi-pointer/ssb-subset-replication-spec).
|
||||
///
|
||||
/// Calls the `partialReplication. getSubset` RPC method.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `query` - A `SubsetQuery` which specifies the desired query filters.
|
||||
/// * `options` - An Option<`SubsetQueryOptions`> which, if provided, adds
|
||||
/// additional specifications to the query, such as page limit and/or
|
||||
/// descending results.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use async_std::stream::StreamExt;
|
||||
/// use golgi::{
|
||||
/// Sbot,
|
||||
/// GolgiError,
|
||||
/// api::get_subset::{
|
||||
/// SubsetQuery,
|
||||
/// SubsetQueryOptions
|
||||
/// },
|
||||
/// sbot::Keystore
|
||||
/// };
|
||||
///
|
||||
/// async fn query() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let post_query = SubsetQuery::Type {
|
||||
/// op: "type".to_string(),
|
||||
/// string: "post".to_string()
|
||||
/// };
|
||||
///
|
||||
/// let post_query_opts = SubsetQueryOptions {
|
||||
/// descending: Some(true),
|
||||
/// keys: None,
|
||||
/// page_limit: Some(5),
|
||||
/// };
|
||||
///
|
||||
/// // Return 5 `post` type messages from any author in descending order.
|
||||
/// let query_stream = sbot_client
|
||||
/// .get_subset_stream(post_query, Some(post_query_opts))
|
||||
/// .await?;
|
||||
///
|
||||
/// // Iterate over the stream and pretty-print each returned message
|
||||
/// // value while ignoring any errors.
|
||||
/// query_stream.for_each(|msg| match msg {
|
||||
/// Ok(val) => println!("{:#?}", val),
|
||||
/// Err(_) => (),
|
||||
/// });
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn get_subset_stream(
|
||||
&mut self,
|
||||
query: SubsetQuery,
|
||||
options: Option<SubsetQueryOptions>,
|
||||
) -> Result<impl Stream<Item = Result<SsbMessageValue, GolgiError>>, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection
|
||||
.client
|
||||
.getsubset_req_send(query, options)
|
||||
.await?;
|
||||
let get_subset_stream = utils::get_source_stream(
|
||||
sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::ssb_message_res_parse,
|
||||
)
|
||||
.await;
|
||||
Ok(get_subset_stream)
|
||||
}
|
||||
}
|
64
src/api/history_stream.rs
Normal file
64
src/api/history_stream.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! Return a history stream.
|
||||
//!
|
||||
//! Implements the following methods:
|
||||
//!
|
||||
//! - [`Sbot::create_history_stream`]
|
||||
|
||||
use async_std::stream::Stream;
|
||||
use kuska_ssb::api::dto::CreateHistoryStreamIn;
|
||||
|
||||
use crate::{
|
||||
error::GolgiError,
|
||||
messages::{SsbMessageKVT, SsbMessageValue},
|
||||
sbot::Sbot,
|
||||
utils,
|
||||
};
|
||||
|
||||
impl Sbot {
|
||||
/// Call the `createHistoryStream` RPC method.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use async_std::stream::StreamExt;
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn history() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let ssb_id = "@zqshk7o2Rpd/OaZ/MxH6xXONgonP1jH+edK9+GZb/NY=.ed25519".to_string();
|
||||
///
|
||||
/// let history_stream = sbot_client.create_history_stream(ssb_id).await?;
|
||||
///
|
||||
/// history_stream.for_each(|msg| {
|
||||
/// match msg {
|
||||
/// Ok(val) => println!("msg value: {:?}", val),
|
||||
/// Err(e) => eprintln!("error: {}", e),
|
||||
/// }
|
||||
/// }).await;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn create_history_stream(
|
||||
&mut self,
|
||||
id: String,
|
||||
//) -> Result<impl Stream<Item = Result<SsbMessageValue, GolgiError>>, GolgiError> {
|
||||
) -> Result<impl Stream<Item = Result<SsbMessageKVT, GolgiError>>, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let args = CreateHistoryStreamIn::new(id).keys_values(true, true);
|
||||
//.limit(10);
|
||||
let req_id = sbot_connection
|
||||
.client
|
||||
.create_history_stream_req_send(&args)
|
||||
.await?;
|
||||
let history_stream = utils::get_source_stream(
|
||||
sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
//utils::ssb_message_res_parse,
|
||||
utils::kvt_res_parse,
|
||||
)
|
||||
.await;
|
||||
Ok(history_stream)
|
||||
}
|
||||
}
|
79
src/api/invite.rs
Normal file
79
src/api/invite.rs
Normal file
@ -0,0 +1,79 @@
|
||||
//! Create and use invite codes.
|
||||
//!
|
||||
//! Implements the following methods:
|
||||
//!
|
||||
//! - [`Sbot::invite_create`]
|
||||
//! - [`Sbot::invite_use`]
|
||||
|
||||
use crate::{error::GolgiError, sbot::Sbot, utils};
|
||||
|
||||
impl Sbot {
|
||||
/// Generate an invite code.
|
||||
///
|
||||
/// Calls the `invite.create` RPC method and returns the code.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn invite_code_generator() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let invite_code = sbot_client.invite_create(5).await?;
|
||||
///
|
||||
/// println!("this invite code can be used 5 times: {}", invite_code);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn invite_create(&mut self, uses: u16) -> Result<String, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection.client.invite_create_req_send(uses).await?;
|
||||
|
||||
utils::get_async(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Use an invite code.
|
||||
///
|
||||
/// Calls the `invite.use` RPC method and returns a reference to the follow
|
||||
/// message.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn invite_code_consumer() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let invite_code = "127.0.0.1:8008:@0iMa+vP7B2aMrV3dzRxlch/iqZn/UM3S3Oo2oVeILY8=.ed25519~ZHNjeajPB/84NjjsrglZInlh46W55RcNDPcffTPgX/Q=";
|
||||
///
|
||||
/// match sbot_client.invite_use(invite_code).await {
|
||||
/// Ok(msg_ref) => println!("consumed invite code. msg reference: {}", msg_ref),
|
||||
/// Err(e) => eprintln!("failed to consume the invite code: {}", e),
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn invite_use(&mut self, invite_code: &str) -> Result<String, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection
|
||||
.client
|
||||
.invite_use_req_send(invite_code)
|
||||
.await?;
|
||||
|
||||
utils::get_async(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
11
src/api/mod.rs
Normal file
11
src/api/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
//! API for interacting with a running go-sbot instance.
|
||||
pub mod about;
|
||||
pub mod friends;
|
||||
pub mod get_subset;
|
||||
pub mod history_stream;
|
||||
pub mod invite;
|
||||
pub mod private;
|
||||
pub mod publish;
|
||||
pub mod whoami;
|
||||
|
||||
pub use crate::sbot::*;
|
66
src/api/private.rs
Normal file
66
src/api/private.rs
Normal file
@ -0,0 +1,66 @@
|
||||
//! Publish Scuttlebutt private messages.
|
||||
//!
|
||||
//! Implements the following method:
|
||||
//!
|
||||
//! - [`Sbot::publish_private`]
|
||||
|
||||
use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils};
|
||||
|
||||
impl Sbot {
|
||||
/// Publish a private message.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msg` - A `PrivateMessage` `struct` whose fields include `text` and
|
||||
/// `recipients`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, messages::SsbMessageContent, sbot::Keystore};
|
||||
///
|
||||
/// async fn publish_a_private_msg() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let text = String::from("Hi friends, I have a super secrect message to share with you about the wonders of intra-cellular transport mechanics.");
|
||||
///
|
||||
/// // We must also include the local identity (public key) here if we wish
|
||||
/// // to be able to read the message. Ie. the sender must be included in
|
||||
/// // the list of recipients.
|
||||
/// let recipients = vec![
|
||||
/// String::from("@OKRij/n7Uu42A0Z75ty0JI0cZxcieD2NyjXrRdYKNOQ=.ed25519"),
|
||||
/// String::from("@Sih4JGgs5oQPXehRyHS5qrYbx/0hQVUqChojX0LNtcQ=.ed25519"),
|
||||
/// String::from("@BVA85B7a/a17v2ZVcLkMgPE+v7X5rQVAHEgQBbCaKMs=.ed25519"),
|
||||
/// ];
|
||||
///
|
||||
/// let msg_ref = sbot_client.publish_private(text, recipients).await?;
|
||||
///
|
||||
/// println!("msg reference for the private msg: {}", msg_ref);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn publish_private(
|
||||
&mut self,
|
||||
text: String,
|
||||
recipients: Vec<String>,
|
||||
) -> Result<String, GolgiError> {
|
||||
let msg = SsbMessageContent::Post {
|
||||
text: text.to_string(),
|
||||
mentions: None,
|
||||
};
|
||||
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection
|
||||
.client
|
||||
.private_publish_req_send(msg, recipients)
|
||||
.await?;
|
||||
|
||||
utils::get_async(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
196
src/api/publish.rs
Normal file
196
src/api/publish.rs
Normal file
@ -0,0 +1,196 @@
|
||||
//! Publish Scuttlebutt messages.
|
||||
//!
|
||||
//! Implements the following methods:
|
||||
//!
|
||||
//! - [`Sbot::publish`]
|
||||
//! - [`Sbot::publish_description`]
|
||||
//! - [`Sbot::publish_image`]
|
||||
//! - [`Sbot::publish_name`]
|
||||
//! - [`Sbot::publish_post`]
|
||||
|
||||
use kuska_ssb::api::dto::content::Image;
|
||||
|
||||
use crate::{error::GolgiError, messages::SsbMessageContent, sbot::Sbot, utils};
|
||||
|
||||
impl Sbot {
|
||||
/// Publish a message.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msg` - A `SsbMessageContent` `enum` whose variants include `Pub`,
|
||||
/// `Post`, `Contact`, `About`, `Channel` and `Vote`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, messages::SsbMessageContent, sbot::Keystore};
|
||||
///
|
||||
/// async fn publish_a_msg() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// // Construct an SSB message of type `post`.
|
||||
/// let post = SsbMessageContent::Post {
|
||||
/// text: "And then those vesicles, filled with the Golgi products, move to the rest of the cell".to_string(),
|
||||
/// mentions: None,
|
||||
/// };
|
||||
///
|
||||
/// let msg_ref = sbot_client.publish(post).await?;
|
||||
///
|
||||
/// println!("msg reference for the golgi post: {}", msg_ref);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn publish(&mut self, msg: SsbMessageContent) -> Result<String, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection.client.publish_req_send(msg).await?;
|
||||
|
||||
utils::get_async(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::string_res_parse,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Publish a post.
|
||||
///
|
||||
/// Convenient wrapper around the `publish` method which constructs and
|
||||
/// publishes a `post` type message appropriately from a string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn publish_a_post() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let text = "The Golgi is located right near the nucleus.";
|
||||
///
|
||||
/// let msg_ref = sbot_client.publish_post(text).await?;
|
||||
///
|
||||
/// println!("msg reference for the golgi post: {}", msg_ref);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn publish_post(&mut self, text: &str) -> Result<String, GolgiError> {
|
||||
let msg = SsbMessageContent::Post {
|
||||
text: text.to_string(),
|
||||
mentions: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/// Publish a description for the local identity.
|
||||
///
|
||||
/// Convenient wrapper around the `publish` method which constructs and
|
||||
/// publishes an `about` type description message appropriately from a string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn publish_a_description() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let description = "The Golgi apparatus was identified by the Italian scientist Camillo Golgi in 1897.";
|
||||
///
|
||||
/// let msg_ref = sbot_client.publish_description(description).await?;
|
||||
///
|
||||
/// println!("msg reference for the golgi description: {}", msg_ref);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn publish_description(&mut self, description: &str) -> Result<String, GolgiError> {
|
||||
let msg = SsbMessageContent::About {
|
||||
about: self.id.to_string(),
|
||||
name: None,
|
||||
title: None,
|
||||
branch: None,
|
||||
image: None,
|
||||
description: Some(description.to_string()),
|
||||
location: None,
|
||||
start_datetime: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/// Publish an image for the local identity.
|
||||
///
|
||||
/// Requires the image to have been added to the local blobstore. The
|
||||
/// ID of the blob (sigil-link in the form `&...=.sha256`) must be provided.
|
||||
///
|
||||
/// Convenient wrapper around the `publish` method which constructs and
|
||||
/// publishes an `about` type name message appropriately from a string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn publish_an_image() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let blob_id = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
||||
///
|
||||
/// let msg_ref = sbot_client.publish_image(blob_id).await?;
|
||||
///
|
||||
/// println!("msg reference: {}", msg_ref);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn publish_image(&mut self, blob_id: &str) -> Result<String, GolgiError> {
|
||||
let msg = SsbMessageContent::About {
|
||||
about: self.id.to_string(),
|
||||
name: None,
|
||||
title: None,
|
||||
branch: None,
|
||||
image: Some(Image::OnlyLink(blob_id.to_string())),
|
||||
description: None,
|
||||
location: None,
|
||||
start_datetime: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/// Publish a name for the local identity.
|
||||
///
|
||||
/// Convenient wrapper around the `publish` method which constructs and
|
||||
/// publishes an `about` type name message appropriately from a string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn publish_a_name() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let name = "glyphski_golgionikus";
|
||||
///
|
||||
/// let msg_ref = sbot_client.publish_name(name).await?;
|
||||
///
|
||||
/// println!("msg reference: {}", msg_ref);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn publish_name(&mut self, name: &str) -> Result<String, GolgiError> {
|
||||
let msg = SsbMessageContent::About {
|
||||
about: self.id.to_string(),
|
||||
name: Some(name.to_string()),
|
||||
title: None,
|
||||
branch: None,
|
||||
image: None,
|
||||
description: None,
|
||||
location: None,
|
||||
start_datetime: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
}
|
45
src/api/whoami.rs
Normal file
45
src/api/whoami.rs
Normal file
@ -0,0 +1,45 @@
|
||||
//! Return the SSB ID of the local sbot instance.
|
||||
//!
|
||||
//! Implements the following methods:
|
||||
//!
|
||||
//! - [`Sbot::whoami`]
|
||||
|
||||
use crate::{error::GolgiError, sbot::Sbot, utils};
|
||||
|
||||
impl Sbot {
|
||||
/// Get the public key of the local identity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{Sbot, GolgiError, sbot::Keystore};
|
||||
///
|
||||
/// async fn fetch_id() -> Result<(), GolgiError> {
|
||||
/// let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
///
|
||||
/// let pub_key = sbot_client.whoami().await?;
|
||||
///
|
||||
/// println!("local ssb id: {}", pub_key);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn whoami(&mut self) -> Result<String, GolgiError> {
|
||||
let mut sbot_connection = self.get_sbot_connection().await?;
|
||||
let req_id = sbot_connection.client.whoami_req_send().await?;
|
||||
|
||||
let result = utils::get_async(
|
||||
&mut sbot_connection.rpc_reader,
|
||||
req_id,
|
||||
utils::json_res_parse,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let id = result
|
||||
.get("id")
|
||||
.ok_or_else(|| GolgiError::Sbot("id key not found on whoami call".to_string()))?
|
||||
.as_str()
|
||||
.ok_or_else(|| GolgiError::Sbot("whoami returned non-string value".to_string()))?;
|
||||
Ok(id.to_string())
|
||||
}
|
||||
}
|
145
src/blobs.rs
Normal file
145
src/blobs.rs
Normal file
@ -0,0 +1,145 @@
|
||||
//! Blob utilities which do not require RPC calls.
|
||||
//!
|
||||
//! Offers the following functions:
|
||||
//!
|
||||
//! - [`get_blob_path()`]
|
||||
//! - [`hash_blob()`]
|
||||
|
||||
use base64;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::error::GolgiError;
|
||||
|
||||
/// Lookup the filepath for a blob.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use golgi::{blobs, GolgiError};
|
||||
///
|
||||
/// fn lookup_blob_path() -> Result<(), GolgiError> {
|
||||
/// let blob_ref = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
||||
///
|
||||
/// let blob_path = blobs::get_blob_path(blob_ref)?;
|
||||
///
|
||||
/// println!("{}", blob_path);
|
||||
///
|
||||
/// // 26/524773dc9e1b5129640f5f20f18bcd42831f415e477f408b9edeba1293d46f
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_blob_path(blob_id: &str) -> Result<String, GolgiError> {
|
||||
// ensure the id starts with the correct sigil link
|
||||
if !blob_id.starts_with('&') {
|
||||
return Err(GolgiError::SigilLink(format!(
|
||||
"incorrect first character, expected '&' sigil: {}",
|
||||
blob_id
|
||||
)));
|
||||
}
|
||||
|
||||
// find the dot index denoting the start of the algorithm definition tag
|
||||
let dot_index = blob_id
|
||||
.rfind('.')
|
||||
.ok_or_else(|| GolgiError::SigilLink(format!("no dot index was found: {}", blob_id)))?;
|
||||
|
||||
// obtain the base64 portion (substring) of the blob id
|
||||
let base64_str = &blob_id[1..dot_index];
|
||||
|
||||
// decode blob substring from base64 (to bytes)
|
||||
let blob_bytes = base64::decode_config(base64_str, base64::STANDARD)?;
|
||||
|
||||
// represent the blob bytes as hex, removing all unnecessary characters
|
||||
let blob_hex = format!("{:02x?}", blob_bytes)
|
||||
.replace('[', "")
|
||||
.replace(']', "")
|
||||
.replace(',', "")
|
||||
.replace(' ', "");
|
||||
|
||||
// split the hex representation of the decoded base64
|
||||
// this is how paths are formatted for the blobstore
|
||||
// e.g. 26/524773dc9e1b5129640f5f20f18bcd42831f415e477f408b9edeba1293d46f
|
||||
// full path would be: `/home/user/.ssb-go/blobs/sha256/26/524773dc...`
|
||||
let blob_path = format!("{}/{}", &blob_hex[..2], &blob_hex[2..]);
|
||||
|
||||
Ok(blob_path)
|
||||
}
|
||||
|
||||
/// Hash the given blob byte slice and return the hex representation and
|
||||
/// blob ID (sigil-link).
|
||||
///
|
||||
/// This function can be used when adding a blob to the local blobstore.
|
||||
pub fn hash_blob(buffer: &[u8]) -> Result<(String, String), GolgiError> {
|
||||
// hash the bytes
|
||||
let hash = Sha256::digest(buffer);
|
||||
|
||||
// generate a hex representation of the hash
|
||||
let hex_hash = format!("{:02x?}", hash)
|
||||
.replace('[', "")
|
||||
.replace(']', "")
|
||||
.replace(',', "")
|
||||
.replace(' ', "");
|
||||
|
||||
// encode the hash
|
||||
let b64_hash = base64::encode(&hash[..]);
|
||||
|
||||
// format the base64 encoding as a blob sigil-link (blob id)
|
||||
let blob_id = format!("&{}.sha256", b64_hash);
|
||||
|
||||
Ok((hex_hash, blob_id))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::blobs;
|
||||
|
||||
/* HAPPY PATH TESTS */
|
||||
|
||||
#[test]
|
||||
fn blob_path() {
|
||||
let blob_ref = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
||||
let blob_path = blobs::get_blob_path(blob_ref);
|
||||
assert!(blob_path.is_ok());
|
||||
assert_eq!(
|
||||
blob_path.unwrap(),
|
||||
"26/524773dc9e1b5129640f5f20f18bcd42831f415e477f408b9edeba1293d46f"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashed_blob() {
|
||||
// pretend this represents a file which has been written to a buffer
|
||||
let buffer = vec![7; 128];
|
||||
let hash_result = blobs::hash_blob(&buffer);
|
||||
assert!(hash_result.is_ok());
|
||||
let (hex_hash, blob_id) = hash_result.unwrap();
|
||||
assert_eq!(
|
||||
hex_hash,
|
||||
"4c1398e54d53e925cff04da532f4bbaf15f75b5981fc76c2072dfdc6491a9fb1"
|
||||
);
|
||||
assert_eq!(
|
||||
blob_id,
|
||||
"&TBOY5U1T6SXP8E2lMvS7rxX3W1mB/HbCBy39xkkan7E=.sha256"
|
||||
);
|
||||
}
|
||||
|
||||
/* SAD PATH TESTS */
|
||||
|
||||
#[test]
|
||||
fn blob_id_without_sigil() {
|
||||
let blob_ref = "JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256";
|
||||
match blobs::get_blob_path(blob_ref) {
|
||||
Err(e) => assert_eq!(e.to_string(), "SSB blob ID error: incorrect first character, expected '&' sigil: JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=.sha256"),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blob_id_without_algo() {
|
||||
let blob_ref = "&JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8=";
|
||||
match blobs::get_blob_path(blob_ref) {
|
||||
Err(e) => assert_eq!(e.to_string(), "SSB blob ID error: no dot index was found: &JlJHc9yeG1EpZA9fIPGLzUKDH0FeR39Ai57euhKT1G8="),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
32
src/error.rs
32
src/error.rs
@ -1,6 +1,7 @@
|
||||
//! Custom error type for `golgi`.
|
||||
//! Custom error type.
|
||||
|
||||
use std::io::Error as IoError;
|
||||
use std::str::Utf8Error;
|
||||
|
||||
use base64::DecodeError;
|
||||
use kuska_handshake::async_std::Error as HandshakeError;
|
||||
@ -33,8 +34,17 @@ pub enum GolgiError {
|
||||
Rpc(RpcError),
|
||||
/// Go-sbot error.
|
||||
Sbot(String),
|
||||
/// SSB sigil-link error.
|
||||
SigilLink(String),
|
||||
/// JSON serialization or deserialization error.
|
||||
SerdeJson(JsonError),
|
||||
/// Error decoding typed SSB message from content.
|
||||
ContentType(String),
|
||||
/// Error decoding UTF8 string from bytes
|
||||
Utf8Parse {
|
||||
/// The underlying parse error.
|
||||
source: Utf8Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::error::Error for GolgiError {
|
||||
@ -47,7 +57,10 @@ impl std::error::Error for GolgiError {
|
||||
GolgiError::Feed(ref err) => Some(err),
|
||||
GolgiError::Rpc(ref err) => Some(err),
|
||||
GolgiError::Sbot(_) => None,
|
||||
GolgiError::SigilLink(_) => None,
|
||||
GolgiError::SerdeJson(ref err) => Some(err),
|
||||
GolgiError::ContentType(_) => None,
|
||||
GolgiError::Utf8Parse { ref source } => Some(source),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,15 +71,24 @@ impl std::fmt::Display for GolgiError {
|
||||
// TODO: add context (what were we trying to decode?)
|
||||
GolgiError::DecodeBase64(_) => write!(f, "Failed to decode base64"),
|
||||
GolgiError::Io { ref context, .. } => write!(f, "IO error: {}", context),
|
||||
GolgiError::Handshake(ref err) => write!(f, "{}", err),
|
||||
GolgiError::Handshake(ref err) => write!(f, "Handshake failure: {}", err),
|
||||
GolgiError::Api(ref err) => write!(f, "SSB API failure: {}", err),
|
||||
GolgiError::Feed(ref err) => write!(f, "SSB feed error: {}", err),
|
||||
// TODO: improve this variant with a context message
|
||||
// then have the core display msg be: "SSB RPC error: {}", context
|
||||
GolgiError::Rpc(ref err) => write!(f, "SSB RPC failure: {}", err),
|
||||
GolgiError::Sbot(ref err) => write!(f, "Sbot returned an error response: {}", err),
|
||||
GolgiError::SigilLink(ref context) => write!(f, "SSB blob ID error: {}", context),
|
||||
GolgiError::SerdeJson(_) => write!(f, "Failed to serialize JSON slice"),
|
||||
//GolgiError::WhoAmI(ref err) => write!(f, "{}", err),
|
||||
GolgiError::ContentType(ref err) => write!(
|
||||
f,
|
||||
"Failed to decode typed message from SSB message content: {}",
|
||||
err
|
||||
),
|
||||
GolgiError::Utf8Parse { source } => {
|
||||
write!(f, "Failed to deserialize UTF8 from bytes: {}", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,3 +128,9 @@ impl From<JsonError> for GolgiError {
|
||||
GolgiError::SerdeJson(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for GolgiError {
|
||||
fn from(err: Utf8Error) -> Self {
|
||||
GolgiError::Utf8Parse { source: err }
|
||||
}
|
||||
}
|
||||
|
61
src/lib.rs
61
src/lib.rs
@ -2,30 +2,77 @@
|
||||
|
||||
//! # golgi
|
||||
//!
|
||||
//! _The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins into membrane-bound vesicles inside the cell before the vesicles are sent to their destination._
|
||||
//! _The Golgi complex (aka. Golgi apparatus or Golgi body) packages proteins
|
||||
//! into membrane-bound vesicles inside the cell before the vesicles are sent
|
||||
//! to their destination._
|
||||
//!
|
||||
//! -----
|
||||
//!
|
||||
//! Golgi is an experimental Scuttlebutt client which uses the [kuska-ssb](https://github.com/Kuska-ssb) libraries and aims to provide a high-level API for interacting with an sbot instance. Development efforts are currently oriented towards [go-sbot](https://github.com/cryptoscope/ssb) interoperability.
|
||||
//! ## Introduction
|
||||
//!
|
||||
//! Golgi is an asynchronous, experimental Scuttlebutt client that aims to
|
||||
//! facilitate Scuttlebutt application development. It provides a high-level
|
||||
//! API for interacting with an sbot instance and uses the
|
||||
//! [kuska-ssb](https://github.com/Kuska-ssb) libraries to make RPC calls.
|
||||
//! Development efforts are currently oriented towards
|
||||
//! [go-sbot](https://github.com/cryptoscope/ssb) interoperability.
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
//! Golgi offers the ability to invoke individual RPC methods while also
|
||||
//! providing a number of convenience methods which may involve multiple RPC
|
||||
//! calls and / or the processing of data received from those calls. The
|
||||
//! [`Sbot`](crate::sbot::Sbot) `struct` is the primary means of interacting
|
||||
//! with the library.
|
||||
//!
|
||||
//! Features include the ability to publish messages of various kinds; to
|
||||
//! retrieve messages (e.g. `about` and `description` messages) and formulate
|
||||
//! queries; to follow, unfollow, block and unblock a peer; to query the social
|
||||
//! graph; and to generate pub invite codes.
|
||||
//!
|
||||
//! Visit the [API modules](crate::api) to view the available methods.
|
||||
//!
|
||||
//! ## Example Usage
|
||||
//!
|
||||
//! Basic usage is demonstrated below. Visit the [examples directory](https://git.coopcloud.tech/golgi-ssb/golgi/src/branch/main/examples) in the `golgi` repository for
|
||||
//! more comprehensive examples.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use golgi::GolgiError;
|
||||
//! use golgi::sbot::Sbot;
|
||||
//! use golgi::{messages::SsbMessageContent, GolgiError, Sbot, sbot::Keystore};
|
||||
//!
|
||||
//! pub async fn run() -> Result<(), GolgiError> {
|
||||
//! let mut sbot_client = Sbot::init(None, None).await?;
|
||||
//! // Attempt to connect to an sbot instance using the default IP address,
|
||||
//! // port and network key (aka. capabilities key).
|
||||
//! let mut sbot_client = Sbot::init(Keystore::Patchwork, None, None).await?;
|
||||
//!
|
||||
//! // Call the `whoami` RPC method to retrieve the public key for the sbot
|
||||
//! // identity.
|
||||
//! let id = sbot_client.whoami().await?;
|
||||
//!
|
||||
//! // Print the public key (identity) to `stdout`.
|
||||
//! println!("{}", id);
|
||||
//!
|
||||
//! // Compose an SSB post message type.
|
||||
//! let post = SsbMessageContent::Post {
|
||||
//! text: "Biology, eh?!".to_string(),
|
||||
//! mentions: None,
|
||||
//! };
|
||||
//!
|
||||
//! // Publish the post.
|
||||
//! let post_msg_reference = sbot_client.publish(post).await?;
|
||||
//!
|
||||
//! // Print the reference (sigil-link) for the published post.
|
||||
//! println!("{}", post_msg_reference);
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub mod api;
|
||||
pub mod blobs;
|
||||
pub mod error;
|
||||
pub mod messages;
|
||||
pub mod sbot;
|
||||
mod utils;
|
||||
pub mod utils;
|
||||
|
||||
pub use crate::error::GolgiError;
|
||||
pub use crate::{error::GolgiError, sbot::Sbot};
|
||||
|
104
src/messages.rs
Normal file
104
src/messages.rs
Normal file
@ -0,0 +1,104 @@
|
||||
//! Message types and conversion methods.
|
||||
|
||||
use kuska_ssb::api::dto::content::TypedMessage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::error::GolgiError;
|
||||
|
||||
/// `SsbMessageContent` is a type alias for `TypedMessage` from the `kuska_ssb` library.
|
||||
/// It is aliased in golgi to fit the naming convention of the other message
|
||||
/// types: `SsbMessageKVT` and `SsbMessageValue`.
|
||||
///
|
||||
/// See the [kuska source code](https://github.com/Kuska-ssb/ssb/blob/master/src/api/dto/content.rs#L103) for the type definition of `TypedMessage`.
|
||||
pub type SsbMessageContent = TypedMessage;
|
||||
|
||||
/// The `value` of an SSB message (the `V` in `KVT`).
|
||||
///
|
||||
/// More information concerning the data model can be found in the
|
||||
/// [`Metadata` documentation](https://spec.scuttlebutt.nz/feed/messages.html#metadata).
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct SsbMessageValue {
|
||||
pub previous: Option<String>,
|
||||
pub author: String,
|
||||
pub sequence: u64,
|
||||
pub timestamp: f64,
|
||||
pub hash: String,
|
||||
pub content: Value,
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
/// Message content types.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum SsbMessageContentType {
|
||||
About,
|
||||
Vote,
|
||||
Post,
|
||||
Contact,
|
||||
Unrecognized,
|
||||
}
|
||||
|
||||
impl SsbMessageValue {
|
||||
/// Get the type field of the message content as an enum, if found.
|
||||
///
|
||||
/// If no `type` field is found or the `type` field is not a string,
|
||||
/// it returns an `Err(GolgiError::ContentType)`.
|
||||
///
|
||||
/// If a `type` field is found but with an unknown string,
|
||||
/// it returns an `Ok(SsbMessageContentType::Unrecognized)`.
|
||||
pub fn get_message_type(&self) -> Result<SsbMessageContentType, GolgiError> {
|
||||
let msg_type = self
|
||||
.content
|
||||
.get("type")
|
||||
.ok_or_else(|| GolgiError::ContentType("type field not found".to_string()))?;
|
||||
let mtype_str: &str = msg_type.as_str().ok_or_else(|| {
|
||||
GolgiError::ContentType("type field value is not a string as expected".to_string())
|
||||
})?;
|
||||
let enum_type = match mtype_str {
|
||||
"about" => SsbMessageContentType::About,
|
||||
"post" => SsbMessageContentType::Post,
|
||||
"vote" => SsbMessageContentType::Vote,
|
||||
"contact" => SsbMessageContentType::Contact,
|
||||
_ => SsbMessageContentType::Unrecognized,
|
||||
};
|
||||
Ok(enum_type)
|
||||
}
|
||||
|
||||
/// Helper function which returns `true` if this message is of the given type,
|
||||
/// and `false` if the type does not match or is not found.
|
||||
pub fn is_message_type(&self, message_type: SsbMessageContentType) -> bool {
|
||||
let self_message_type = self.get_message_type();
|
||||
match self_message_type {
|
||||
Ok(mtype) => mtype == message_type,
|
||||
Err(_err) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the content JSON value into an `SsbMessageContent` `enum`,
|
||||
/// using the `type` field as a tag to select which variant of the `enum`
|
||||
/// to deserialize into.
|
||||
///
|
||||
/// See the [Serde docs on internally-tagged enum representations](https://serde.rs/enum-representations.html#internally-tagged) for further details.
|
||||
pub fn into_ssb_message_content(self) -> Result<SsbMessageContent, GolgiError> {
|
||||
let m: SsbMessageContent = serde_json::from_value(self.content)?;
|
||||
Ok(m)
|
||||
}
|
||||
}
|
||||
|
||||
/// An SSB message represented as a key-value-timestamp (`KVT`).
|
||||
///
|
||||
/// More information concerning the data model can be found in the
|
||||
/// [`Metadata` documentation](https://spec.scuttlebutt.nz/feed/messages.html#metadata).
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct SsbMessageKVT {
|
||||
pub key: String,
|
||||
pub value: SsbMessageValue,
|
||||
pub timestamp: f64,
|
||||
pub rts: Option<f64>,
|
||||
}
|
198
src/sbot.rs
198
src/sbot.rs
@ -1,44 +1,59 @@
|
||||
//! Sbot type and associated methods.
|
||||
|
||||
//! Sbot type and connection-related methods.
|
||||
use async_std::net::TcpStream;
|
||||
|
||||
use kuska_handshake::async_std::BoxStream;
|
||||
use kuska_sodiumoxide::crypto::{auth, sign::ed25519};
|
||||
use kuska_ssb::{
|
||||
api::{
|
||||
dto::{
|
||||
//content::{About, Post},
|
||||
content::{SubsetQuery, TypedMessage},
|
||||
CreateHistoryStreamIn,
|
||||
},
|
||||
ApiCaller,
|
||||
},
|
||||
api::ApiCaller,
|
||||
discovery, keystore,
|
||||
keystore::OwnedIdentity,
|
||||
rpc::{RpcReader, RpcWriter},
|
||||
};
|
||||
|
||||
use crate::error::GolgiError;
|
||||
use crate::utils;
|
||||
|
||||
/// The Scuttlebutt identity, keys and configuration parameters for connecting to a local sbot
|
||||
/// instance, as well as handles for calling RPC methods and receiving responses.
|
||||
/// Keystore selector to specify the location of the secret file.
|
||||
///
|
||||
/// This enum is used when initiating a connection with an sbot instance.
|
||||
pub enum Keystore {
|
||||
/// Patchwork default keystore path: `.ssb/secret` in the user's home directory.
|
||||
Patchwork,
|
||||
/// GoSbot default keystore path: `.ssb-go/secret` in the user's home directory.
|
||||
GoSbot,
|
||||
}
|
||||
|
||||
/// A struct representing a connection with a running sbot.
|
||||
/// A client and an rpc_reader can together be used to make requests to the sbot
|
||||
/// and read the responses.
|
||||
/// Note there can be multiple SbotConnection at the same time.
|
||||
pub struct SbotConnection {
|
||||
/// Client for writing requests to go-bot
|
||||
pub client: ApiCaller<TcpStream>,
|
||||
/// RpcReader object for reading responses from go-sbot
|
||||
pub rpc_reader: RpcReader<TcpStream>,
|
||||
}
|
||||
|
||||
/// Holds the Scuttlebutt identity, keys and configuration parameters for
|
||||
/// connecting to a local sbot and implements all Golgi API methods.
|
||||
pub struct Sbot {
|
||||
id: String,
|
||||
/// The ID (public key value) of the account associated with the local sbot instance.
|
||||
pub id: String,
|
||||
public_key: ed25519::PublicKey,
|
||||
private_key: ed25519::SecretKey,
|
||||
address: String,
|
||||
// aka caps key (scuttleverse identifier)
|
||||
network_id: auth::Key,
|
||||
client: ApiCaller<TcpStream>,
|
||||
rpc_reader: RpcReader<TcpStream>,
|
||||
}
|
||||
|
||||
impl Sbot {
|
||||
/// Initiate a connection with an sbot instance. Define the IP address, port and network key
|
||||
/// for the sbot, then retrieve the public key, private key (secret) and identity from the
|
||||
/// `.ssb-go/secret` file. Open a TCP stream to the sbot and perform the secret handshake. If successful, create a box stream and split it into a writer and reader. Return RPC handles to the sbot as part of the `struct` output.
|
||||
pub async fn init(ip_port: Option<String>, net_id: Option<String>) -> Result<Sbot, GolgiError> {
|
||||
/// Initiate a connection with an sbot instance. Define the IP address,
|
||||
/// port and network key for the sbot, then retrieve the public key,
|
||||
/// private key (secret) and identity from the `.ssb-go/secret` file.
|
||||
pub async fn init(
|
||||
keystore: Keystore,
|
||||
ip_port: Option<String>,
|
||||
net_id: Option<String>,
|
||||
) -> Result<Sbot, GolgiError> {
|
||||
let address = if ip_port.is_none() {
|
||||
"127.0.0.1:8008".to_string()
|
||||
} else {
|
||||
@ -51,23 +66,61 @@ impl Sbot {
|
||||
auth::Key::from_slice(&hex::decode(net_id.unwrap()).unwrap()).unwrap()
|
||||
};
|
||||
|
||||
let OwnedIdentity { pk, sk, id } = keystore::from_gosbot_local()
|
||||
.await
|
||||
.expect("couldn't read local secret");
|
||||
let OwnedIdentity { pk, sk, id } = match keystore {
|
||||
Keystore::Patchwork => keystore::from_patchwork_local()
|
||||
.await
|
||||
.expect("couldn't read local secret"),
|
||||
Keystore::GoSbot => keystore::from_gosbot_local()
|
||||
.await
|
||||
.expect("couldn't read local secret"),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
public_key: pk,
|
||||
private_key: sk,
|
||||
address,
|
||||
network_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new connection with the sbot, using the address, network_id,
|
||||
/// public_key and private_key supplied when Sbot was initialized.
|
||||
///
|
||||
/// Note that a single Sbot can have multiple SbotConnection at the same time.
|
||||
pub async fn get_sbot_connection(&self) -> Result<SbotConnection, GolgiError> {
|
||||
let address = self.address.clone();
|
||||
let network_id = self.network_id.clone();
|
||||
let public_key = self.public_key;
|
||||
let private_key = self.private_key.clone();
|
||||
Sbot::_get_sbot_connection_helper(address, network_id, public_key, private_key).await
|
||||
}
|
||||
|
||||
/// Private helper function which creates a new connection with sbot,
|
||||
/// but with all variables passed as arguments.
|
||||
///
|
||||
/// Open a TCP stream to the sbot and perform the secret handshake. If
|
||||
/// successful, create a box stream and split it into a writer and reader.
|
||||
/// Return RPC handles to the sbot as part of the `struct` output.
|
||||
async fn _get_sbot_connection_helper(
|
||||
address: String,
|
||||
network_id: auth::Key,
|
||||
public_key: ed25519::PublicKey,
|
||||
private_key: ed25519::SecretKey,
|
||||
) -> Result<SbotConnection, GolgiError> {
|
||||
let socket = TcpStream::connect(&address)
|
||||
.await
|
||||
.map_err(|source| GolgiError::Io {
|
||||
source,
|
||||
context: "socket error; failed to initiate tcp stream connection".to_string(),
|
||||
context: "failed to initiate tcp stream connection".to_string(),
|
||||
})?;
|
||||
|
||||
let handshake = kuska_handshake::async_std::handshake_client(
|
||||
&mut &socket,
|
||||
network_id.clone(),
|
||||
pk,
|
||||
sk.clone(),
|
||||
pk,
|
||||
public_key,
|
||||
private_key.clone(),
|
||||
public_key,
|
||||
)
|
||||
.await
|
||||
.map_err(GolgiError::Handshake)?;
|
||||
@ -77,94 +130,7 @@ impl Sbot {
|
||||
|
||||
let rpc_reader = RpcReader::new(box_stream_read);
|
||||
let client = ApiCaller::new(RpcWriter::new(box_stream_write));
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
public_key: pk,
|
||||
private_key: sk,
|
||||
address,
|
||||
network_id,
|
||||
client,
|
||||
rpc_reader,
|
||||
})
|
||||
}
|
||||
|
||||
/// Call the `partialReplication getSubset` RPC method and return a vector
|
||||
/// of messages as KVTs (key, value, timestamp).
|
||||
// TODO: add args for `descending` and `page` (max number of msgs in response)
|
||||
pub async fn getsubset(&mut self, query: SubsetQuery) -> Result<String, GolgiError> {
|
||||
let req_id = self.client.getsubset_req_send(query).await?;
|
||||
|
||||
utils::get_async(&mut self.rpc_reader, req_id, utils::getsubset_res_parse).await
|
||||
}
|
||||
|
||||
/// Call the `whoami` RPC method and return an `id`.
|
||||
pub async fn whoami(&mut self) -> Result<String, GolgiError> {
|
||||
let req_id = self.client.whoami_req_send().await?;
|
||||
|
||||
utils::get_async(&mut self.rpc_reader, req_id, utils::whoami_res_parse)
|
||||
.await
|
||||
.map(|whoami| whoami.id)
|
||||
}
|
||||
|
||||
/// Call the `publish` RPC method and return a message reference.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msg` - A `TypedMessage` `enum` whose variants include `Pub`, `Post`, `Contact`, `About`,
|
||||
/// `Channel` and `Vote`. See the `kuska_ssb` documentation for further details such as field
|
||||
/// names and accepted values for each variant.
|
||||
pub async fn publish(&mut self, msg: TypedMessage) -> Result<String, GolgiError> {
|
||||
let req_id = self.client.publish_req_send(msg).await?;
|
||||
|
||||
utils::get_async(&mut self.rpc_reader, req_id, utils::publish_res_parse).await
|
||||
}
|
||||
|
||||
/// Wrapper for publish which constructs and publishes a post message appropriately from a string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - A reference to a string slice which represents the text to be published in the post
|
||||
pub async fn publish_post(&mut self, text: &str) -> Result<String, GolgiError> {
|
||||
let msg = TypedMessage::Post {
|
||||
text: text.to_string(),
|
||||
mentions: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/// Wrapper for publish which constructs and publishes an about description message appropriately from a string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `description` - A reference to a string slice which represents the text to be published as an about description.
|
||||
pub async fn publish_description(&mut self, description: &str) -> Result<String, GolgiError> {
|
||||
let msg = TypedMessage::About {
|
||||
about: self.id.to_string(),
|
||||
name: None,
|
||||
title: None,
|
||||
branch: None,
|
||||
image: None,
|
||||
description: Some(description.to_string()),
|
||||
location: None,
|
||||
start_datetime: None,
|
||||
};
|
||||
self.publish(msg).await
|
||||
}
|
||||
|
||||
/*
|
||||
pub async fn publish_post(&mut self, post: Post) -> Result<String, GolgiError> {
|
||||
let req_id = self.client.publish_req_send(post).await?;
|
||||
|
||||
utils::get_async(&mut self.rpc_reader, req_id, utils::publish_res_parse).await
|
||||
}
|
||||
*/
|
||||
|
||||
/// Call the `createHistoryStream` RPC method and print the output.
|
||||
async fn create_history_stream(&mut self, id: String) -> Result<(), GolgiError> {
|
||||
let args = CreateHistoryStreamIn::new(id);
|
||||
let req_id = self.client.create_history_stream_req_send(&args).await?;
|
||||
// TODO: we should return a vector of messages instead of printing them
|
||||
utils::print_source_until_eof(&mut self.rpc_reader, req_id, utils::feed_res_parse).await
|
||||
let sbot_connection = SbotConnection { rpc_reader, client };
|
||||
Ok(sbot_connection)
|
||||
}
|
||||
}
|
||||
|
200
src/utils.rs
200
src/utils.rs
@ -1,41 +1,66 @@
|
||||
/*
|
||||
|
||||
use kuska_handshake::async_std::BoxStream;
|
||||
use kuska_sodiumoxide::crypto::sign::ed25519;
|
||||
use kuska_ssb::discovery;
|
||||
use kuska_ssb::keystore;
|
||||
use kuska_ssb::keystore::OwnedIdentity;
|
||||
*/
|
||||
//! Utility methods.
|
||||
use std::fmt::Debug;
|
||||
|
||||
use async_std::io::Read;
|
||||
|
||||
use kuska_ssb::api::dto::WhoAmIOut;
|
||||
use kuska_ssb::feed::Feed;
|
||||
use async_std::{io::Read, net::TcpStream, stream::Stream};
|
||||
use async_stream::stream;
|
||||
use kuska_ssb::rpc::{RecvMsg, RequestNo, RpcReader};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::error::GolgiError;
|
||||
use crate::messages::{SsbMessageKVT, SsbMessageValue};
|
||||
|
||||
pub fn getsubset_res_parse(body: &[u8]) -> Result<String, GolgiError> {
|
||||
// TODO: cleanup with proper error handling etc.
|
||||
Ok(std::str::from_utf8(body).unwrap().to_string())
|
||||
/// Parse an array of bytes (returned by an rpc call) into a `KVT`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `body` - An array of u8 to be parsed.
|
||||
pub fn kvt_res_parse(body: &[u8]) -> Result<SsbMessageKVT, GolgiError> {
|
||||
let value: Value = serde_json::from_slice(body)?;
|
||||
let kvt: SsbMessageKVT = serde_json::from_value(value)?;
|
||||
Ok(kvt)
|
||||
}
|
||||
|
||||
pub fn feed_res_parse(body: &[u8]) -> Result<Feed, GolgiError> {
|
||||
Ok(Feed::from_slice(body)?)
|
||||
/// Parse an array of bytes (returned by an rpc call) into a `String`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `body` - An array of u8 to be parsed.
|
||||
pub fn string_res_parse(body: &[u8]) -> Result<String, GolgiError> {
|
||||
Ok(std::str::from_utf8(body)?.to_string())
|
||||
}
|
||||
|
||||
//pub fn publish_res_parse(body: &[u8]) -> Result<PublishOut, GolgiError> {
|
||||
pub fn publish_res_parse(body: &[u8]) -> Result<String, GolgiError> {
|
||||
//Ok(serde_json::from_slice(body)?)
|
||||
// TODO: cleanup with proper error handling etc.
|
||||
Ok(std::str::from_utf8(body).unwrap().to_string())
|
||||
/// Parse an array of bytes (returned by an rpc call) into a `serde_json::Value`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `body` - An array of u8 to be parsed.
|
||||
pub fn json_res_parse(body: &[u8]) -> Result<Value, GolgiError> {
|
||||
let message: Value = serde_json::from_slice(body)?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
pub fn whoami_res_parse(body: &[u8]) -> Result<WhoAmIOut, GolgiError> {
|
||||
Ok(serde_json::from_slice(body)?)
|
||||
/// Parse an array of bytes (returned by an rpc call) into an `SsbMessageValue`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `body` - An array of u8 to be parsed.
|
||||
pub fn ssb_message_res_parse(body: &[u8]) -> Result<SsbMessageValue, GolgiError> {
|
||||
let message: SsbMessageValue = serde_json::from_slice(body)?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Take in an RPC request number along with a handling function and wait for
|
||||
/// an RPC response which matches the request number. Then, call the handling
|
||||
/// function on the response.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `rpc_reader` - A `RpcReader` which can return Messages in a loop
|
||||
/// * `req_no` - A `RequestNo` of the response to listen for
|
||||
/// * `f` - A function which takes in an array of `u8` and returns a
|
||||
/// `Result<T, GolgiError>`. This is a function which parses the response from
|
||||
/// the `RpcReader`. `T` is a generic type, so this parse function can return
|
||||
/// multiple possible types (`String`, JSON, custom struct etc.)
|
||||
pub async fn get_async<'a, R, T, F>(
|
||||
rpc_reader: &mut RpcReader<R>,
|
||||
req_no: RequestNo,
|
||||
@ -56,12 +81,81 @@ where
|
||||
RecvMsg::ErrorResponse(message) => {
|
||||
return Err(GolgiError::Sbot(message));
|
||||
}
|
||||
RecvMsg::CancelStreamRespose() => {
|
||||
return Err(GolgiError::Sbot(
|
||||
"sbot returned CancelStreamResponse before any content".to_string(),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Take in an RPC request number along with a handling function and call
|
||||
/// the handling function on all RPC responses which match the request number,
|
||||
/// appending the result of each parsed message to a vector until a
|
||||
/// `CancelStreamResponse` is found (marking the end of the stream).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `rpc_reader` - A `RpcReader` which can return Messages in a loop
|
||||
/// * `req_no` - A `RequestNo` of the response to listen for
|
||||
/// * `f` - A function which takes in an array of `u8` and returns a
|
||||
/// `Result<T, GolgiError>`. This is a function which parses the response from
|
||||
/// the `RpcReader`. `T` is a generic type, so this parse function can return
|
||||
/// multiple possible types (`String`, JSON, custom struct etc.)
|
||||
pub async fn get_source_until_eof<'a, R, T, F>(
|
||||
rpc_reader: &mut RpcReader<R>,
|
||||
req_no: RequestNo,
|
||||
f: F,
|
||||
) -> Result<Vec<T>, GolgiError>
|
||||
where
|
||||
R: Read + Unpin,
|
||||
F: Fn(&[u8]) -> Result<T, GolgiError>,
|
||||
T: Debug,
|
||||
{
|
||||
let mut messages: Vec<T> = Vec::new();
|
||||
loop {
|
||||
let (id, msg) = rpc_reader.recv().await?;
|
||||
if id == req_no {
|
||||
match msg {
|
||||
RecvMsg::RpcResponse(_type, body) => {
|
||||
let parsed_response: Result<T, GolgiError> = f(&body);
|
||||
match parsed_response {
|
||||
Ok(parsed_message) => {
|
||||
messages.push(parsed_message);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
RecvMsg::ErrorResponse(message) => {
|
||||
return Err(GolgiError::Sbot(message));
|
||||
}
|
||||
RecvMsg::CancelStreamRespose() => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
/// Take in an RPC request number along with a handling function and call the
|
||||
/// handling function on all responses which match the request number. Then,
|
||||
/// prints out the result of the handling function.
|
||||
///
|
||||
/// This function useful for debugging and only prints the output.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `rpc_reader` - A `RpcReader` which can return Messages in a loop
|
||||
/// * `req_no` - A `RequestNo` of the response to listen for
|
||||
/// * `f` - A function which takes in an array of `u8` and returns a
|
||||
/// `Result<T, GolgiError>`. This is a function which parses the response from
|
||||
/// the `RpcReader`. `T` is a generic type, so this parse function can return
|
||||
/// multiple possible types (`String`, JSON, custom struct etc.)
|
||||
pub async fn print_source_until_eof<'a, R, T, F>(
|
||||
rpc_reader: &mut RpcReader<R>,
|
||||
req_no: RequestNo,
|
||||
@ -90,3 +184,61 @@ where
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Take in an RPC request number along with a handling function (parsing
|
||||
/// results of type `T`) and produce an `async_std::stream::Stream` of results
|
||||
/// of type `T`, where the handling function is called on all `RpcReader`
|
||||
/// responses which match the request number.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `req_no` - A `RequestNo` of the response to listen for
|
||||
/// * `f` - A function which takes in an array of `u8` and returns a
|
||||
/// `Result<T, GolgiError>`. This is a function which parses the response from
|
||||
/// the `RpcReader`. `T` is a generic type, so this parse function can return
|
||||
/// multiple possible types (`String`, JSON, custom struct etc.)
|
||||
pub async fn get_source_stream<'a, F, T>(
|
||||
mut rpc_reader: RpcReader<TcpStream>,
|
||||
req_no: RequestNo,
|
||||
f: F,
|
||||
) -> impl Stream<Item = Result<T, GolgiError>>
|
||||
where
|
||||
F: Fn(&[u8]) -> Result<T, GolgiError>,
|
||||
T: Debug + serde::Deserialize<'a>,
|
||||
{
|
||||
// we use the async_stream::stream macro to allow for creating a stream which calls async functions
|
||||
// see https://users.rust-lang.org/t/how-to-create-async-std-stream-which-calls-async-function-in-poll-next/69760
|
||||
let source_stream = stream! {
|
||||
loop {
|
||||
// get the next message from the rpc_reader
|
||||
let (id, msg) = rpc_reader.recv().await?;
|
||||
let x : i32 = id.clone();
|
||||
// check if the next message from rpc_reader matches the req_no we are looking for
|
||||
// if it matches, then this rpc response is for the given request
|
||||
// and if it doesn't match, then we ignore it
|
||||
if x == req_no {
|
||||
match msg {
|
||||
RecvMsg::RpcResponse(_type, body) => {
|
||||
// parse an item of type T from the message body using the provided
|
||||
// function for parsing
|
||||
let item = f(&body)?;
|
||||
// return Ok(item) as the next value in the stream
|
||||
yield Ok(item)
|
||||
}
|
||||
RecvMsg::ErrorResponse(message) => {
|
||||
// if an error is received
|
||||
// return an Err(err) as the next value in the stream
|
||||
yield Err(GolgiError::Sbot(message.to_string()));
|
||||
}
|
||||
// if we find a CancelStreamResponse
|
||||
// this is the end of the stream
|
||||
RecvMsg::CancelStreamRespose() => break,
|
||||
// if we find an unknown response, we just continue the loop
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// finally return the stream object
|
||||
source_stream
|
||||
}
|
||||
|
Reference in New Issue
Block a user