+348
-26
Cargo.lock
+348
-26
Cargo.lock
···
225
225
]
226
226
227
227
[[package]]
228
+
name = "backtrace-ext"
229
+
version = "0.2.1"
230
+
source = "registry+https://github.com/rust-lang/crates.io-index"
231
+
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
232
+
dependencies = [
233
+
"backtrace",
234
+
]
235
+
236
+
[[package]]
228
237
name = "base-x"
229
238
version = "0.2.11"
230
239
source = "registry+https://github.com/rust-lang/crates.io-index"
···
720
729
]
721
730
722
731
[[package]]
732
+
name = "enum-as-inner"
733
+
version = "0.6.1"
734
+
source = "registry+https://github.com/rust-lang/crates.io-index"
735
+
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
736
+
dependencies = [
737
+
"heck 0.5.0",
738
+
"proc-macro2",
739
+
"quote",
740
+
"syn 2.0.106",
741
+
]
742
+
743
+
[[package]]
723
744
name = "enum_dispatch"
724
745
version = "0.3.13"
725
746
source = "registry+https://github.com/rust-lang/crates.io-index"
···
865
886
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
866
887
867
888
[[package]]
889
+
name = "futures-io"
890
+
version = "0.3.31"
891
+
source = "registry+https://github.com/rust-lang/crates.io-index"
892
+
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
893
+
894
+
[[package]]
868
895
name = "futures-macro"
869
896
version = "0.3.31"
870
897
source = "registry+https://github.com/rust-lang/crates.io-index"
···
972
999
"env_logger",
973
1000
"jacquard",
974
1001
"jetstream",
1002
+
"log",
975
1003
"reqwest",
1004
+
"serde",
976
1005
"serde_json",
977
1006
"tokio",
978
1007
"url",
···
1012
1041
]
1013
1042
1014
1043
[[package]]
1015
-
name = "hashbrown"
1016
-
version = "0.16.0"
1017
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1018
-
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
1019
-
1020
-
[[package]]
1021
1044
name = "heck"
1022
1045
version = "0.4.1"
1023
1046
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1042
1065
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
1043
1066
1044
1067
[[package]]
1068
+
name = "hickory-proto"
1069
+
version = "0.24.4"
1070
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1071
+
checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
1072
+
dependencies = [
1073
+
"async-trait",
1074
+
"cfg-if",
1075
+
"data-encoding",
1076
+
"enum-as-inner",
1077
+
"futures-channel",
1078
+
"futures-io",
1079
+
"futures-util",
1080
+
"idna",
1081
+
"ipnet",
1082
+
"once_cell",
1083
+
"rand 0.8.5",
1084
+
"thiserror 1.0.69",
1085
+
"tinyvec",
1086
+
"tokio",
1087
+
"tracing",
1088
+
"url",
1089
+
]
1090
+
1091
+
[[package]]
1092
+
name = "hickory-resolver"
1093
+
version = "0.24.4"
1094
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1095
+
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
1096
+
dependencies = [
1097
+
"cfg-if",
1098
+
"futures-util",
1099
+
"hickory-proto",
1100
+
"ipconfig",
1101
+
"lru-cache",
1102
+
"once_cell",
1103
+
"parking_lot",
1104
+
"rand 0.8.5",
1105
+
"resolv-conf",
1106
+
"smallvec",
1107
+
"thiserror 1.0.69",
1108
+
"tokio",
1109
+
"tracing",
1110
+
]
1111
+
1112
+
[[package]]
1045
1113
name = "http"
1046
1114
version = "1.3.1"
1047
1115
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1154
1222
"libc",
1155
1223
"percent-encoding",
1156
1224
"pin-project-lite",
1157
-
"socket2",
1225
+
"socket2 0.6.0",
1158
1226
"system-configuration",
1159
1227
"tokio",
1160
1228
"tower-service",
···
1317
1385
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
1318
1386
dependencies = [
1319
1387
"equivalent",
1320
-
"hashbrown 0.16.0",
1388
+
"hashbrown 0.15.5",
1321
1389
"serde",
1322
1390
"serde_core",
1323
1391
]
···
1340
1408
]
1341
1409
1342
1410
[[package]]
1411
+
name = "ipconfig"
1412
+
version = "0.3.2"
1413
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1414
+
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
1415
+
dependencies = [
1416
+
"socket2 0.5.10",
1417
+
"widestring",
1418
+
"windows-sys 0.48.0",
1419
+
"winreg",
1420
+
]
1421
+
1422
+
[[package]]
1343
1423
name = "ipld-core"
1344
1424
version = "0.4.2"
1345
1425
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1367
1447
]
1368
1448
1369
1449
[[package]]
1450
+
name = "is_ci"
1451
+
version = "1.2.0"
1452
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1453
+
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
1454
+
1455
+
[[package]]
1370
1456
name = "is_terminal_polyfill"
1371
1457
version = "1.70.1"
1372
1458
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1389
1475
1390
1476
[[package]]
1391
1477
name = "jacquard"
1392
-
version = "0.1.0"
1478
+
version = "0.2.1"
1393
1479
source = "registry+https://github.com/rust-lang/crates.io-index"
1394
-
checksum = "0e2fb462e7bd414bfd66918bd82eb3ada7c4c7bcb4e47abb9d72ddd32264b49c"
1480
+
checksum = "719fe6fc35f0bd7859a25ba55ebaf55360e3af5b17764dab173202ad734ef5a4"
1395
1481
dependencies = [
1482
+
"async-trait",
1483
+
"bon",
1396
1484
"bytes",
1397
1485
"clap",
1486
+
"hickory-resolver",
1398
1487
"http",
1399
1488
"jacquard-api",
1400
1489
"jacquard-common",
1401
1490
"miette",
1491
+
"percent-encoding",
1402
1492
"reqwest",
1403
1493
"serde",
1404
1494
"serde_html_form",
1405
1495
"serde_ipld_dagcbor",
1406
1496
"serde_json",
1497
+
"smol_str",
1407
1498
"thiserror 2.0.17",
1408
1499
"tokio",
1500
+
"url",
1501
+
"urlencoding",
1409
1502
]
1410
1503
1411
1504
[[package]]
1412
1505
name = "jacquard-api"
1413
-
version = "0.1.0"
1506
+
version = "0.2.0"
1414
1507
source = "registry+https://github.com/rust-lang/crates.io-index"
1415
-
checksum = "9175e40f71219451961f1da8f10c183617fb611e2e474219ce31bc669830c1f1"
1508
+
checksum = "51de55e4de7c7b2a5f5ea67450d8e21bc9388cca719ce3347cb58e236cc90457"
1416
1509
dependencies = [
1417
1510
"bon",
1418
1511
"bytes",
···
1425
1518
1426
1519
[[package]]
1427
1520
name = "jacquard-common"
1428
-
version = "0.1.0"
1521
+
version = "0.2.0"
1429
1522
source = "registry+https://github.com/rust-lang/crates.io-index"
1430
-
checksum = "3b4a7bef3e4fbb0d7c22ad6709544a594a000e78cd514612b584cc001945056d"
1523
+
checksum = "69abd1202e606b0f2cdca38e1daaf98ce09dfe3e29cbae27bcb4cb1f6b4560c4"
1431
1524
dependencies = [
1432
1525
"base64",
1526
+
"bon",
1433
1527
"bytes",
1434
1528
"chrono",
1435
1529
"cid",
···
1441
1535
"multihash",
1442
1536
"num-traits",
1443
1537
"ouroboros",
1444
-
"rand",
1538
+
"rand 0.9.2",
1445
1539
"regex",
1446
1540
"serde",
1447
1541
"serde_html_form",
···
1454
1548
1455
1549
[[package]]
1456
1550
name = "jacquard-derive"
1457
-
version = "0.1.0"
1551
+
version = "0.2.0"
1458
1552
source = "registry+https://github.com/rust-lang/crates.io-index"
1459
-
checksum = "b068f4196f31793f90d682c97b8570bc0bac810d8fb681f6d561937d9bf1510e"
1553
+
checksum = "079cff009d305585a7929ea3ff45aeb5dfb93e18281793e7d79ad03ca75b9513"
1460
1554
dependencies = [
1461
1555
"heck 0.5.0",
1462
1556
"itertools",
···
1559
1653
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
1560
1654
1561
1655
[[package]]
1656
+
name = "linked-hash-map"
1657
+
version = "0.5.6"
1658
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1659
+
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
1660
+
1661
+
[[package]]
1562
1662
name = "linux-raw-sys"
1563
1663
version = "0.11.0"
1564
1664
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1595
1695
]
1596
1696
1597
1697
[[package]]
1698
+
name = "lru-cache"
1699
+
version = "0.1.2"
1700
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1701
+
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
1702
+
dependencies = [
1703
+
"linked-hash-map",
1704
+
]
1705
+
1706
+
[[package]]
1598
1707
name = "lru-slab"
1599
1708
version = "0.1.2"
1600
1709
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1623
1732
source = "registry+https://github.com/rust-lang/crates.io-index"
1624
1733
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
1625
1734
dependencies = [
1735
+
"backtrace",
1736
+
"backtrace-ext",
1626
1737
"cfg-if",
1627
1738
"miette-derive",
1628
-
"unicode-width",
1739
+
"owo-colors",
1740
+
"supports-color",
1741
+
"supports-hyperlinks",
1742
+
"supports-unicode",
1743
+
"terminal_size",
1744
+
"textwrap",
1745
+
"unicode-width 0.1.14",
1629
1746
]
1630
1747
1631
1748
[[package]]
···
1857
1974
]
1858
1975
1859
1976
[[package]]
1977
+
name = "owo-colors"
1978
+
version = "4.2.3"
1979
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1980
+
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
1981
+
1982
+
[[package]]
1860
1983
name = "parking"
1861
1984
version = "2.2.1"
1862
1985
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2017
2140
"quinn-udp",
2018
2141
"rustc-hash",
2019
2142
"rustls",
2020
-
"socket2",
2143
+
"socket2 0.6.0",
2021
2144
"thiserror 2.0.17",
2022
2145
"tokio",
2023
2146
"tracing",
···
2033
2156
"bytes",
2034
2157
"getrandom 0.3.3",
2035
2158
"lru-slab",
2036
-
"rand",
2159
+
"rand 0.9.2",
2037
2160
"ring",
2038
2161
"rustc-hash",
2039
2162
"rustls",
···
2054
2177
"cfg_aliases",
2055
2178
"libc",
2056
2179
"once_cell",
2057
-
"socket2",
2180
+
"socket2 0.6.0",
2058
2181
"tracing",
2059
2182
"windows-sys 0.60.2",
2060
2183
]
···
2073
2196
version = "5.3.0"
2074
2197
source = "registry+https://github.com/rust-lang/crates.io-index"
2075
2198
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
2199
+
2200
+
[[package]]
2201
+
name = "rand"
2202
+
version = "0.8.5"
2203
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2204
+
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
2205
+
dependencies = [
2206
+
"libc",
2207
+
"rand_chacha 0.3.1",
2208
+
"rand_core 0.6.4",
2209
+
]
2076
2210
2077
2211
[[package]]
2078
2212
name = "rand"
···
2080
2214
source = "registry+https://github.com/rust-lang/crates.io-index"
2081
2215
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
2082
2216
dependencies = [
2083
-
"rand_chacha",
2084
-
"rand_core",
2217
+
"rand_chacha 0.9.0",
2218
+
"rand_core 0.9.3",
2219
+
]
2220
+
2221
+
[[package]]
2222
+
name = "rand_chacha"
2223
+
version = "0.3.1"
2224
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2225
+
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
2226
+
dependencies = [
2227
+
"ppv-lite86",
2228
+
"rand_core 0.6.4",
2085
2229
]
2086
2230
2087
2231
[[package]]
···
2091
2235
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
2092
2236
dependencies = [
2093
2237
"ppv-lite86",
2094
-
"rand_core",
2238
+
"rand_core 0.9.3",
2239
+
]
2240
+
2241
+
[[package]]
2242
+
name = "rand_core"
2243
+
version = "0.6.4"
2244
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2245
+
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
2246
+
dependencies = [
2247
+
"getrandom 0.2.16",
2095
2248
]
2096
2249
2097
2250
[[package]]
···
2213
2366
"web-sys",
2214
2367
"webpki-roots",
2215
2368
]
2369
+
2370
+
[[package]]
2371
+
name = "resolv-conf"
2372
+
version = "0.7.5"
2373
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2374
+
checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799"
2216
2375
2217
2376
[[package]]
2218
2377
name = "ring"
···
2570
2729
2571
2730
[[package]]
2572
2731
name = "socket2"
2732
+
version = "0.5.10"
2733
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2734
+
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
2735
+
dependencies = [
2736
+
"libc",
2737
+
"windows-sys 0.52.0",
2738
+
]
2739
+
2740
+
[[package]]
2741
+
name = "socket2"
2573
2742
version = "0.6.0"
2574
2743
source = "registry+https://github.com/rust-lang/crates.io-index"
2575
2744
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
···
2623
2792
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
2624
2793
2625
2794
[[package]]
2795
+
name = "supports-color"
2796
+
version = "3.0.2"
2797
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2798
+
checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"
2799
+
dependencies = [
2800
+
"is_ci",
2801
+
]
2802
+
2803
+
[[package]]
2804
+
name = "supports-hyperlinks"
2805
+
version = "3.1.0"
2806
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2807
+
checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
2808
+
2809
+
[[package]]
2810
+
name = "supports-unicode"
2811
+
version = "3.0.0"
2812
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2813
+
checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
2814
+
2815
+
[[package]]
2626
2816
name = "syn"
2627
2817
version = "1.0.109"
2628
2818
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2702
2892
"once_cell",
2703
2893
"rustix",
2704
2894
"windows-sys 0.61.1",
2895
+
]
2896
+
2897
+
[[package]]
2898
+
name = "terminal_size"
2899
+
version = "0.4.3"
2900
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2901
+
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
2902
+
dependencies = [
2903
+
"rustix",
2904
+
"windows-sys 0.60.2",
2905
+
]
2906
+
2907
+
[[package]]
2908
+
name = "textwrap"
2909
+
version = "0.16.2"
2910
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2911
+
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
2912
+
dependencies = [
2913
+
"unicode-linebreak",
2914
+
"unicode-width 0.2.2",
2705
2915
]
2706
2916
2707
2917
[[package]]
···
2815
3025
"pin-project-lite",
2816
3026
"signal-hook-registry",
2817
3027
"slab",
2818
-
"socket2",
3028
+
"socket2 0.6.0",
2819
3029
"tokio-macros",
2820
3030
"windows-sys 0.59.0",
2821
3031
]
···
2930
3140
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
2931
3141
dependencies = [
2932
3142
"pin-project-lite",
3143
+
"tracing-attributes",
2933
3144
"tracing-core",
2934
3145
]
2935
3146
2936
3147
[[package]]
3148
+
name = "tracing-attributes"
3149
+
version = "0.1.30"
3150
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3151
+
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
3152
+
dependencies = [
3153
+
"proc-macro2",
3154
+
"quote",
3155
+
"syn 2.0.106",
3156
+
]
3157
+
3158
+
[[package]]
2937
3159
name = "tracing-core"
2938
3160
version = "0.1.34"
2939
3161
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2971
3193
"httparse",
2972
3194
"log",
2973
3195
"native-tls",
2974
-
"rand",
3196
+
"rand 0.9.2",
2975
3197
"sha1",
2976
3198
"thiserror 2.0.17",
2977
3199
"url",
···
2991
3213
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
2992
3214
2993
3215
[[package]]
3216
+
name = "unicode-linebreak"
3217
+
version = "0.1.5"
3218
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3219
+
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
3220
+
3221
+
[[package]]
2994
3222
name = "unicode-width"
2995
3223
version = "0.1.14"
2996
3224
source = "registry+https://github.com/rust-lang/crates.io-index"
2997
3225
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
2998
3226
2999
3227
[[package]]
3228
+
name = "unicode-width"
3229
+
version = "0.2.2"
3230
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3231
+
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
3232
+
3233
+
[[package]]
3000
3234
name = "unsigned-varint"
3001
3235
version = "0.8.0"
3002
3236
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3019
3253
"percent-encoding",
3020
3254
"serde",
3021
3255
]
3256
+
3257
+
[[package]]
3258
+
name = "urlencoding"
3259
+
version = "2.1.3"
3260
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3261
+
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
3022
3262
3023
3263
[[package]]
3024
3264
name = "utf-8"
···
3196
3436
]
3197
3437
3198
3438
[[package]]
3439
+
name = "widestring"
3440
+
version = "1.2.0"
3441
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3442
+
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
3443
+
3444
+
[[package]]
3199
3445
name = "windows-core"
3200
3446
version = "0.62.1"
3201
3447
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3291
3537
3292
3538
[[package]]
3293
3539
name = "windows-sys"
3540
+
version = "0.48.0"
3541
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3542
+
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
3543
+
dependencies = [
3544
+
"windows-targets 0.48.5",
3545
+
]
3546
+
3547
+
[[package]]
3548
+
name = "windows-sys"
3294
3549
version = "0.52.0"
3295
3550
source = "registry+https://github.com/rust-lang/crates.io-index"
3296
3551
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
···
3327
3582
3328
3583
[[package]]
3329
3584
name = "windows-targets"
3585
+
version = "0.48.5"
3586
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3587
+
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
3588
+
dependencies = [
3589
+
"windows_aarch64_gnullvm 0.48.5",
3590
+
"windows_aarch64_msvc 0.48.5",
3591
+
"windows_i686_gnu 0.48.5",
3592
+
"windows_i686_msvc 0.48.5",
3593
+
"windows_x86_64_gnu 0.48.5",
3594
+
"windows_x86_64_gnullvm 0.48.5",
3595
+
"windows_x86_64_msvc 0.48.5",
3596
+
]
3597
+
3598
+
[[package]]
3599
+
name = "windows-targets"
3330
3600
version = "0.52.6"
3331
3601
source = "registry+https://github.com/rust-lang/crates.io-index"
3332
3602
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
···
3357
3627
"windows_x86_64_gnullvm 0.53.0",
3358
3628
"windows_x86_64_msvc 0.53.0",
3359
3629
]
3630
+
3631
+
[[package]]
3632
+
name = "windows_aarch64_gnullvm"
3633
+
version = "0.48.5"
3634
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3635
+
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
3360
3636
3361
3637
[[package]]
3362
3638
name = "windows_aarch64_gnullvm"
···
3372
3648
3373
3649
[[package]]
3374
3650
name = "windows_aarch64_msvc"
3651
+
version = "0.48.5"
3652
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3653
+
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
3654
+
3655
+
[[package]]
3656
+
name = "windows_aarch64_msvc"
3375
3657
version = "0.52.6"
3376
3658
source = "registry+https://github.com/rust-lang/crates.io-index"
3377
3659
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
···
3381
3663
version = "0.53.0"
3382
3664
source = "registry+https://github.com/rust-lang/crates.io-index"
3383
3665
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
3666
+
3667
+
[[package]]
3668
+
name = "windows_i686_gnu"
3669
+
version = "0.48.5"
3670
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3671
+
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
3384
3672
3385
3673
[[package]]
3386
3674
name = "windows_i686_gnu"
···
3408
3696
3409
3697
[[package]]
3410
3698
name = "windows_i686_msvc"
3699
+
version = "0.48.5"
3700
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3701
+
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
3702
+
3703
+
[[package]]
3704
+
name = "windows_i686_msvc"
3411
3705
version = "0.52.6"
3412
3706
source = "registry+https://github.com/rust-lang/crates.io-index"
3413
3707
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
···
3420
3714
3421
3715
[[package]]
3422
3716
name = "windows_x86_64_gnu"
3717
+
version = "0.48.5"
3718
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3719
+
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
3720
+
3721
+
[[package]]
3722
+
name = "windows_x86_64_gnu"
3423
3723
version = "0.52.6"
3424
3724
source = "registry+https://github.com/rust-lang/crates.io-index"
3425
3725
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
···
3432
3732
3433
3733
[[package]]
3434
3734
name = "windows_x86_64_gnullvm"
3735
+
version = "0.48.5"
3736
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3737
+
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
3738
+
3739
+
[[package]]
3740
+
name = "windows_x86_64_gnullvm"
3435
3741
version = "0.52.6"
3436
3742
source = "registry+https://github.com/rust-lang/crates.io-index"
3437
3743
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
···
3444
3750
3445
3751
[[package]]
3446
3752
name = "windows_x86_64_msvc"
3753
+
version = "0.48.5"
3754
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3755
+
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
3756
+
3757
+
[[package]]
3758
+
name = "windows_x86_64_msvc"
3447
3759
version = "0.52.6"
3448
3760
source = "registry+https://github.com/rust-lang/crates.io-index"
3449
3761
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
···
3453
3765
version = "0.53.0"
3454
3766
source = "registry+https://github.com/rust-lang/crates.io-index"
3455
3767
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
3768
+
3769
+
[[package]]
3770
+
name = "winreg"
3771
+
version = "0.50.0"
3772
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3773
+
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
3774
+
dependencies = [
3775
+
"cfg-if",
3776
+
"windows-sys 0.48.0",
3777
+
]
3456
3778
3457
3779
[[package]]
3458
3780
name = "wit-bindgen"
+3
-1
Cargo.toml
+3
-1
Cargo.toml
···
6
6
[dependencies]
7
7
clap = { version = "4.5.48", features = ["derive", "env"] }
8
8
env_logger = "0.11.8"
9
-
jacquard = "0.1.0"
9
+
jacquard = "0.2.1"
10
10
jetstream = { path = "../links/jetstream" }
11
+
log = "0.4.28"
11
12
reqwest = { version = "0.12.23", features = ["json"] }
13
+
serde = { version = "1.0.228", features = ["derive"] }
12
14
serde_json = "1.0.145"
13
15
tokio = { version = "1.47.1", features = ["full"] }
14
16
url = "2.5.7"
docs/october-dolly.png
docs/october-dolly.png
This is a binary file and will not be displayed.
+7
license
+7
license
···
1
+
Copyright (c) 2025 @bad-example.com
2
+
3
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+32
readme.md
+32
readme.md
···
1
+
# ๐ Happy hacktober! ๐ง๐ผโโ๏ธ
2
+
3
+

4
+
5
+
[This bot](https://bsky.app/profile/hacktober.tngl.sh) listens to the [jetstream](github.com/bluesky-social/jetstream) firehose, filters for labels added to issues on [tangled.org](https://tangled.org/), checks if they are the official [`good-first-issue`](https://tangled.org/goodfirstissues) label, and then [posts](https://bsky.app/profile/hacktober.tngl.sh/post/3m2oflabdmc2u) about it!
6
+
7
+
8
+
### It's made with:
9
+
10
+
- [jacquard](https://docs.rs/jacquard/latest/jacquard/): auth and posting
11
+
- [microcosm slingshot](https://slingshot.microcosm.blue/): identity resolution and record fetching
12
+
- [microcosm jetstream](https://tangled.org/@microcosm.blue/microcosm-rs/tree/main/jetstream): firehose listener
13
+
- [tangled's](https://tangled.org/) PDS hosts the bot's account!
14
+
15
+
### It's made by:
16
+
17
+
- [@bad-example.com](https://bsky.app/profile/bad-example.com): [ko-fi](https://ko-fi.com/bad_example), [github sponsors](https://github.com/sponsors/uniphil/)
18
+
19
+
20
+
### It would be nice if this bot would:
21
+
22
+
- [ ] pull [OG repo images](https://bsky.app/profile/oppi.li/post/3m2orohxal22j) so it can post with an external link embed
23
+
24
+
### It would be nice if this bot *could*:
25
+
26
+
- [ ] link directly to the issue, instead of the repo's all-issues page. i don't think it's possible right now because the issue page URL needs the issue id number, which is only kept in tangled's appview at the moment.
27
+
- [ ] reply to its posts when issues are closed as complete! again currently the open/closed state for tangled is only in the appview, so this is not currently possible to detect.
28
+
29
+
30
+
### Things to watch out for if you hack on it
31
+
32
+
- [ ] The microcosm jetstream package isn't published, so this currently uses a horrible local path reference for it, and that reference uses a very old folder name that you won't get by default from cloning [microcosm-rs](https://tangled.org/@microcosm.blue/microcosm-rs). If you rename microcosm-rs's folder name to `links`, it should work! or ping me and i'll fix it.
+299
-198
src/main.rs
+299
-198
src/main.rs
···
1
1
use clap::Parser;
2
-
use url::Url;
3
-
use jetstream::{
4
-
JetstreamCompression, JetstreamConfig, JetstreamConnector,
5
-
events::{CommitOp, Cursor, EventKind},
6
-
exports::Nsid,
7
-
};
8
2
use jacquard::{
9
-
api::app_bsky::{
10
-
feed::post::Post,
11
-
richtext::facet::{Facet, ByteSlice},
12
-
},
13
-
api::com_atproto::{
14
-
server::create_session::CreateSession,
15
-
repo::create_record::CreateRecord,
3
+
api::{
4
+
app_bsky::feed::post::Post,
5
+
app_bsky::richtext::facet::{ByteSlice, Facet},
6
+
com_atproto::repo::create_record::CreateRecord,
7
+
com_atproto::server::create_session::CreateSession,
16
8
},
17
-
client::{HttpClient, AuthenticatedClient, Session, XrpcClient},
9
+
client::{BasicClient, Session},
18
10
types::{
19
-
datetime::Datetime,
20
-
ident::AtIdentifier,
21
-
language::Language,
22
-
collection::Collection,
23
-
value::Data,
24
-
string::AtUri,
11
+
collection::Collection, datetime::Datetime, ident::AtIdentifier, language::Language,
12
+
string::AtUri, value::Data,
25
13
},
26
14
};
15
+
use jetstream::{
16
+
JetstreamCompression, JetstreamConfig, JetstreamConnector,
17
+
events::{CommitOp, Cursor, EventKind, JetstreamEvent},
18
+
exports::Nsid,
19
+
};
20
+
use url::Url;
21
+
22
+
use serde::Deserialize;
27
23
use std::time::Duration;
28
24
29
25
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
···
51
47
/// warning: setting this can lead to rapid bot posting
52
48
#[arg(long)]
53
49
jetstream_cursor: Option<u64>,
50
+
/// don't actually post
51
+
#[arg(long, action)]
52
+
dry_run: bool,
53
+
/// send a checkin to this url every 5 mins
54
+
#[arg(long)]
55
+
healthcheck_ping: Option<Url>,
54
56
}
55
57
56
-
async fn post_link<C: HttpClient>(client: &AuthenticatedClient<C>, identifier: &AtIdentifier<'_>, pre_text: &str, link: Url) -> Result<()> {
57
-
let link_text = link.to_string();
58
-
let text = format!("{pre_text} {link_text}");
59
-
let link_feature = serde_json::json!({
58
+
struct IssueDetails {
59
+
repo_full_name: String,
60
+
repo_url: String,
61
+
title: String,
62
+
issues_url: String,
63
+
}
64
+
65
+
/// com.bad-example.identity.resolveMiniDoc bit we care about
66
+
#[derive(Deserialize)]
67
+
struct MiniDocResponse {
68
+
handle: String,
69
+
}
70
+
71
+
/// com.atproto.repo.getRecord wraps the record in a `value` key
72
+
#[derive(Deserialize)]
73
+
struct GetRecordResonse<T> {
74
+
value: T,
75
+
}
76
+
77
+
/// part of CreateLabelRecord: key is the label reference (ie for "good-first-issue")
78
+
#[derive(Deserialize)]
79
+
struct AddLabel {
80
+
key: String,
81
+
}
82
+
83
+
/// tangled's record for adding labels to an issue
84
+
#[derive(Deserialize)]
85
+
struct CreateLabelRecord {
86
+
add: Vec<AddLabel>,
87
+
subject: String,
88
+
}
89
+
90
+
/// tangled issue record
91
+
#[derive(Deserialize)]
92
+
struct IssueRecord {
93
+
title: String,
94
+
repo: String,
95
+
}
96
+
97
+
/// tangled repo record
98
+
#[derive(Deserialize)]
99
+
struct RepoRecord {
100
+
name: String,
101
+
}
102
+
103
+
/// get some atproto record content (from slingshot)
104
+
async fn get_record<T: for<'a> Deserialize<'a>>(
105
+
client: &reqwest::Client,
106
+
at_uri: &str,
107
+
) -> Result<T> {
108
+
let mut url: Url = "https://slingshot.microcosm.blue".parse()?;
109
+
url.set_path("/xrpc/com.bad-example.repo.getUriRecord");
110
+
url.query_pairs_mut().append_pair("at_uri", at_uri);
111
+
let GetRecordResonse { value } = client
112
+
.get(url)
113
+
.send()
114
+
.await?
115
+
.error_for_status()?
116
+
.json()
117
+
.await?;
118
+
Ok(value)
119
+
}
120
+
121
+
/// try to resolve a bidirectionally verified handle from an identifier (via slingshot)
122
+
async fn get_handle(client: &reqwest::Client, identifier: &str) -> Result<Option<String>> {
123
+
let mut url: Url = "https://slingshot.microcosm.blue".parse()?;
124
+
url.set_path("/xrpc/com.bad-example.identity.resolveMiniDoc");
125
+
url.query_pairs_mut().append_pair("identifier", identifier);
126
+
let MiniDocResponse { handle } = client
127
+
.get(url)
128
+
.send()
129
+
.await?
130
+
.error_for_status()?
131
+
.json()
132
+
.await?;
133
+
if handle == "handle.invalid" {
134
+
Ok(None)
135
+
} else {
136
+
Ok(Some(handle))
137
+
}
138
+
}
139
+
140
+
fn event_to_create_label<T: for<'a> Deserialize<'a>>(event: JetstreamEvent) -> Result<T> {
141
+
if event.kind != EventKind::Commit {
142
+
return Err("not a commit".into());
143
+
}
144
+
let commit = event.commit.ok_or("commit event missing commit data")?;
145
+
if commit.operation != CommitOp::Create {
146
+
return Err("not a create event".into());
147
+
}
148
+
149
+
let raw = commit.record.ok_or("commit missing record")?;
150
+
151
+
// todo: delete post if label is removed
152
+
// delete sample: at://did:plc:hdhoaan3xa3jiuq4fg4mefid/sh.tangled.label.op/3m2jvx4c6wf22
153
+
// tldr: has a "delete" array just like "add" on the same op collection
154
+
Ok(serde_json::from_str(raw.get())?)
155
+
}
156
+
157
+
async fn extract_issue_info(
158
+
client: &reqwest::Client,
159
+
adds: Vec<AddLabel>,
160
+
subject: String,
161
+
) -> Result<IssueDetails> {
162
+
let mut added_good_first_issue = false;
163
+
for added in adds {
164
+
if added.key
165
+
== "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/good-first-issue"
166
+
{
167
+
log::info!("found a good first issue label!!");
168
+
added_good_first_issue = true;
169
+
break; // inner
170
+
}
171
+
log::debug!("found a label but it wasn't good-first-issue, ignoring...");
172
+
}
173
+
if !added_good_first_issue {
174
+
return Err("good-first-issue label not found in added labels".into());
175
+
}
176
+
177
+
let IssueRecord { title, repo } = match get_record(client, &subject).await {
178
+
Ok(m) => m,
179
+
Err(e) => return Err(format!("failed to get issue record: {e} for {subject}").into()),
180
+
};
181
+
182
+
let Ok(repo_uri) = AtUri::new(&repo) else {
183
+
return Err("failed to parse repo to aturi for {subject}".into());
184
+
};
185
+
186
+
let RepoRecord { name: repo_name } = match get_record(client, &repo).await {
187
+
Ok(m) => m,
188
+
Err(e) => return Err(format!("failed to get repo record: {e} for {subject}").into()),
189
+
};
190
+
191
+
let nice_tangled_repo_id = match repo_uri.authority() {
192
+
AtIdentifier::Handle(h) => format!("@{h}"),
193
+
AtIdentifier::Did(did) => match get_handle(client, did.as_str()).await {
194
+
Err(e) => {
195
+
return Err(format!(
196
+
"failed to get mini doc from repo identifier: {e} for {subject}"
197
+
)
198
+
.into());
199
+
}
200
+
Ok(None) => did.to_string(),
201
+
Ok(Some(h)) => format!("@{h}"),
202
+
},
203
+
};
204
+
205
+
let repo_full_name = format!("{nice_tangled_repo_id}/{repo_name}");
206
+
let repo_url = format!("https://tangled.org/{nice_tangled_repo_id}/{repo_name}");
207
+
208
+
let issues_url = format!("https://tangled.org/{nice_tangled_repo_id}/{repo_name}/issues");
209
+
210
+
Ok(IssueDetails {
211
+
repo_full_name,
212
+
repo_url,
213
+
title,
214
+
issues_url,
215
+
})
216
+
}
217
+
218
+
async fn post(
219
+
client: &BasicClient,
220
+
identifier: &AtIdentifier<'_>,
221
+
IssueDetails {
222
+
repo_full_name,
223
+
repo_url,
224
+
title,
225
+
issues_url,
226
+
}: &IssueDetails,
227
+
) -> Result<()> {
228
+
let message = format!(
229
+
r#"New from {repo_full_name}:
230
+
231
+
> {title}"#
232
+
);
233
+
234
+
let pre_len = 9;
235
+
236
+
let repo_feature = serde_json::json!({
60
237
"$type": "app.bsky.richtext.facet#link",
61
-
"uri": link_text,
238
+
"uri": repo_url,
239
+
});
240
+
let repo_facet = Facet {
241
+
features: vec![Data::from_json(&repo_feature)?],
242
+
index: ByteSlice {
243
+
byte_start: pre_len,
244
+
byte_end: pre_len + repo_full_name.len() as i64,
245
+
extra_data: Default::default(),
246
+
},
247
+
extra_data: Default::default(),
248
+
};
249
+
250
+
let title_starts_at = pre_len + (repo_full_name.len() + 5) as i64;
251
+
252
+
let repo_issues_feature = serde_json::json!({
253
+
"$type": "app.bsky.richtext.facet#link",
254
+
"uri": issues_url,
62
255
});
63
-
let link_facet = Facet {
64
-
features: vec![Data::from_json(&link_feature)?],
256
+
let issues_facet = Facet {
257
+
features: vec![Data::from_json(&repo_issues_feature)?],
65
258
index: ByteSlice {
66
-
byte_start: (pre_text.len() + 1) as i64,
67
-
byte_end: text.len() as i64,
259
+
byte_start: title_starts_at,
260
+
byte_end: title_starts_at + title.len() as i64,
68
261
extra_data: Default::default(),
69
262
},
70
263
extra_data: Default::default(),
···
74
267
let post = Post {
75
268
created_at: Datetime::now(),
76
269
langs: Some(vec![Language::new("en")?]),
77
-
text: text.into(),
78
-
facets: Some(vec![link_facet]),
270
+
text: message.into(),
271
+
facets: Some(vec![repo_facet, issues_facet]),
79
272
embed: Default::default(),
80
273
entities: Default::default(),
81
274
labels: Default::default(),
···
87
280
let json = serde_json::to_value(post)?;
88
281
let data = Data::from_json(&json)?;
89
282
90
-
println!("\nposting...");
283
+
log::info!("\nposting...");
91
284
client
92
-
.send(CreateRecord::new()
93
-
.repo(identifier.clone())
94
-
.collection(Post::nsid())
95
-
.record(data)
96
-
.build())
285
+
.send(
286
+
CreateRecord::new()
287
+
.repo(identifier.clone())
288
+
.collection(Post::nsid())
289
+
.record(data)
290
+
.build(),
291
+
)
97
292
.await?
98
293
.into_output()?;
99
294
100
295
Ok(())
101
296
}
102
297
103
-
async fn get_record(client: &reqwest::Client, at_uri: &str) -> Result<serde_json::Map<String, serde_json::Value>> {
104
-
let mut url: Url = "https://slingshot.microcosm.blue".parse()?;
105
-
url.set_path("/xrpc/com.bad-example.repo.getUriRecord");
106
-
url.query_pairs_mut().append_pair("at_uri", at_uri);
107
-
let record_map = client
108
-
.get(url)
109
-
.send()
110
-
.await?
111
-
.error_for_status()?
112
-
.json::<serde_json::Value>()
113
-
.await?
114
-
.as_object()
115
-
.ok_or("get_record response was not a json object")?
116
-
.get("value")
117
-
.ok_or("get_record response obj did not have 'value' key")?
118
-
.as_object()
119
-
.ok_or("get_record response.value was not an object")?
120
-
.clone();
121
-
Ok(record_map)
122
-
}
123
-
124
-
async fn get_handle(client: &reqwest::Client, identifier: &str) -> Result<Option<String>> {
125
-
let mut url: Url = "https://slingshot.microcosm.blue".parse()?;
126
-
url.set_path("/xrpc/com.bad-example.identity.resolveMiniDoc");
127
-
url.query_pairs_mut().append_pair("identifier", identifier);
128
-
let handle = client
129
-
.get(url)
130
-
.send()
131
-
.await?
132
-
.error_for_status()?
133
-
.json::<serde_json::Value>()
134
-
.await?
135
-
.as_object()
136
-
.ok_or("minidoc response was not a json object")?
137
-
.get("handle")
138
-
.ok_or("minidoc response obj did not have 'handle' key")?
139
-
.as_str()
140
-
.ok_or("minidoc handle was not a string")?
141
-
.to_string();
142
-
if handle == "handle.invalid" {
143
-
Ok(None)
144
-
} else {
145
-
Ok(Some(handle))
298
+
async fn hc_ping(url: Url, client: reqwest::Client) {
299
+
let mut interval = tokio::time::interval(Duration::from_secs(5 * 60));
300
+
loop {
301
+
interval.tick().await;
302
+
log::trace!("sending healthcheck ping...");
303
+
if let Err(e) = client
304
+
.get(url.clone())
305
+
.send()
306
+
.await
307
+
.and_then(reqwest::Response::error_for_status)
308
+
{
309
+
log::warn!("error sending healthcheck ping: {e}");
310
+
}
146
311
}
147
312
}
148
313
···
152
317
let args = Args::parse();
153
318
154
319
// Create HTTP client and session
155
-
let pds_uri = args.pds.as_str().trim_end_matches('/').to_string().into();
156
-
let mut client = AuthenticatedClient::new(reqwest::Client::new(), pds_uri);
320
+
let client = BasicClient::new(args.pds);
157
321
let bot_id = AtIdentifier::new(&args.identifier)?;
158
-
let session = Session::from(
159
-
client
160
-
.send(
161
-
CreateSession::new()
162
-
.identifier(&bot_id.to_string())
163
-
.password(args.app_password)
164
-
.build(),
165
-
)
166
-
.await?
167
-
.into_output()?,
168
-
);
169
-
println!("logged in as {} ({})", session.handle, session.did);
170
-
client.set_session(session);
322
+
let create_session = CreateSession::new()
323
+
.identifier(bot_id.to_string())
324
+
.password(&args.app_password)
325
+
.build();
326
+
let session = Session::from(client.send(create_session.clone()).await?.into_output()?);
327
+
log::debug!("logged in as {} ({})", session.handle, session.did);
328
+
client.set_session(session).await?;
329
+
330
+
let slingshot_client = reqwest::Client::builder()
331
+
.user_agent("hacktober_bot")
332
+
.timeout(Duration::from_secs(9))
333
+
.build()?;
171
334
172
335
let jetstream_config: JetstreamConfig = JetstreamConfig {
173
336
endpoint: args.jetstream_url.to_string(),
···
178
341
channel_size: 1024, // buffer up to ~1s of jetstream events
179
342
..Default::default()
180
343
};
181
-
182
344
let mut receiver = JetstreamConnector::new(jetstream_config)?
183
345
.connect_cursor(args.jetstream_cursor.map(Cursor::from_raw_u64))
184
346
.await?;
185
347
186
-
let req_client = reqwest::Client::builder()
187
-
.user_agent("hacktober_bot")
188
-
.timeout(Duration::from_secs(9))
189
-
.build()?;
348
+
if let Some(hc) = args.healthcheck_ping {
349
+
log::info!("starting healthcheck ping task...");
350
+
tokio::task::spawn(hc_ping(hc.clone(), slingshot_client.clone()));
351
+
}
190
352
191
-
println!("receiving jetstream messages...");
353
+
log::info!("receiving jetstream messages...");
192
354
loop {
193
-
let Some(event) = receiver.recv().await else {
194
-
eprintln!("consumer: could not receive event, bailing");
355
+
let Some(event) = receiver.recv().await else {
356
+
log::error!("consumer: could not receive event, bailing");
195
357
break;
196
358
};
197
-
if event.kind != EventKind::Commit {
198
-
continue;
199
-
}
200
-
let Some(ref commit) = event.commit else {
201
-
eprintln!("consumer: commit event missing commit data, ignoring");
202
-
continue;
203
-
};
204
-
if commit.operation != CommitOp::Create {
205
-
continue;
206
-
}
207
-
let Some(ref record) = commit.record else {
208
-
eprintln!("consumer: commit update/delete missing record, ignoring");
209
-
continue;
210
-
};
211
-
let jv: serde_json::Value = match record.get().parse() {
212
-
Ok(v) => v,
213
-
Err(e) => {
214
-
eprintln!("consumer: record failed to parse, ignoring: {e}");
215
-
continue;
216
-
}
217
-
};
218
-
let serde_json::Value::Object(o) = jv else {
219
-
eprintln!("record was not an object, ignoring");
220
-
continue;
221
-
};
222
-
let Some(serde_json::Value::Array(adds)) = o.get("add") else {
223
-
eprintln!("op did not have label added or was not an array");
224
-
continue;
225
-
};
226
-
let mut added_good_first_issue = false;
227
-
for added in adds {
228
-
let serde_json::Value::Object(a) = added else {
229
-
eprintln!("added item was not an obj");
230
-
continue;
231
-
};
232
-
let Some(serde_json::Value::String(key)) = a.get("key") else {
233
-
eprintln!("added was missing key prop");
234
-
continue;
235
-
};
236
-
if key == "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/good-first-issue" {
237
-
println!("found a good first issue label!! {:?}", event.cursor);
238
-
added_good_first_issue = true;
239
-
break;
240
-
}
241
-
eprintln!("found a label but it wasn't good-first-issue, ignoring...");
242
-
}
243
-
if !added_good_first_issue {
244
-
continue;
245
-
}
246
-
let Some(serde_json::Value::String(subject)) = o.get("subject") else {
247
-
eprintln!("could not find `subject` string for the good-first-issue label");
248
-
continue;
249
-
};
359
+
let cursor = event.cursor;
250
360
251
-
let issue_record = match get_record(&req_client, subject).await {
252
-
Ok(m) => m,
361
+
let CreateLabelRecord { add: adds, subject } = match event_to_create_label(event) {
362
+
Ok(clr) => clr,
253
363
Err(e) => {
254
-
eprintln!("failed to get issue record: {e} for {subject}");
364
+
log::debug!("ignoring unparseable event (at {cursor:?}): {e}");
255
365
continue;
256
366
}
257
367
};
258
-
let Some(serde_json::Value::String(title)) = issue_record.get("title") else {
259
-
eprintln!("failed to get title from issue for {subject}");
260
-
continue;
261
-
};
262
-
let Some(serde_json::Value::String(repo)) = issue_record.get("repo") else {
263
-
eprintln!("failed to get repo from issue for {subject}");
264
-
continue;
265
-
};
266
368
267
-
let Ok(repo_uri) = AtUri::new(repo) else {
268
-
eprintln!("failed to parse repo to aturi for {subject}");
269
-
continue;
270
-
};
271
-
272
-
let repo_record = match get_record(&req_client, repo).await {
273
-
Ok(m) => m,
369
+
let issue_details = match extract_issue_info(&slingshot_client, adds, subject.clone()).await
370
+
{
371
+
Ok(deets) => deets,
274
372
Err(e) => {
275
-
eprintln!("failed to get repo record: {e} for {subject}");
373
+
log::warn!("failed to extract issue details (at {cursor:?}): {e}");
276
374
continue;
277
375
}
278
376
};
279
-
let Some(serde_json::Value::String(name)) = repo_record.get("name") else {
280
-
eprintln!("failed to get name for repo for {subject}");
281
-
continue;
282
-
};
283
377
284
-
let nice_tangled_repo_id = match repo_uri.authority() {
285
-
AtIdentifier::Handle(h) => format!("@{h}"),
286
-
AtIdentifier::Did(did) => match get_handle(&req_client, did.as_str()).await {
287
-
Err(e) => {
288
-
eprintln!("failed to get mini doc from repo identifier: {e} for {subject}");
289
-
continue;
290
-
}
291
-
Ok(None) => did.to_string(),
292
-
Ok(Some(h)) => format!("@{h}"),
293
-
}
294
-
};
378
+
if args.dry_run {
379
+
let IssueDetails {
380
+
repo_full_name,
381
+
repo_url,
382
+
title,
383
+
issues_url,
384
+
} = issue_details;
385
+
log::info!(
386
+
r#"--dry-run, but would have posted:
295
387
296
-
let issues_url = format!("https://tangled.org/{nice_tangled_repo_id}/{name}/issues");
388
+
good-first-issue label added for {repo_full_name} ({repo_url}):
297
389
298
-
println!("hi: {issues_url}");
390
+
> {title} ({issues_url})"#
391
+
);
392
+
continue;
393
+
}
299
394
300
-
// let message = format!(r#""#);
395
+
if let Err(e) = post(&client, &bot_id, &issue_details).await {
396
+
log::warn!("failed to post for {subject}: {e}, refreshing session for one retry...");
397
+
let session = Session::from(client.send(create_session.clone()).await?.into_output()?);
398
+
log::debug!("logged in as {} ({})", session.handle, session.did);
399
+
client.set_session(session).await?;
301
400
302
-
eprintln!("got {subject} {title:?} for {name}");
401
+
if let Err(e) = post(&client, &bot_id, &issue_details).await {
402
+
log::error!(
403
+
"failed to post after a session refresh: {e:?}, something is wrong. bye."
404
+
);
405
+
break;
406
+
}
407
+
};
303
408
}
304
-
305
-
// let u2: Url = "https://bad-example.com".parse()?;
306
-
// let bot_id = AtIdentifier::new(&args.identifier)?;
307
-
// post_link(&client, &bot_id, "link test 2: ", u2).await?;
308
409
309
410
Ok(())
310
411
}