announcing good-first-issue tags added on @tangled.sh (not affiliated with tangled!)

Compare changes

Choose any two refs to compare.

+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
··· 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

This is a binary file and will not be displayed.

+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
··· 1 + # ๐ŸŽƒ Happy hacktober! ๐Ÿง™๐Ÿผโ€โ™€๏ธ 2 + 3 + ![tangle's dolly as a witchy pumpkin](./docs/october-dolly.png) 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.
+352 -52
src/main.rs
··· 1 1 use clap::Parser; 2 - use url::Url; 3 2 use jacquard::{ 4 - api::app_bsky::{ 5 - feed::post::Post, 6 - richtext::facet::{Facet, ByteSlice}, 7 - }, 8 - api::com_atproto::{ 9 - server::create_session::CreateSession, 10 - 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, 11 8 }, 12 - client::{AuthenticatedClient, Session, XrpcClient}, 9 + client::{BasicClient, Session}, 13 10 types::{ 14 - datetime::Datetime, 15 - ident::AtIdentifier, 16 - language::Language, 17 - collection::Collection, 18 - value::Data, 11 + collection::Collection, datetime::Datetime, ident::AtIdentifier, language::Language, 12 + string::AtUri, value::Data, 19 13 }, 20 14 }; 15 + use jetstream::{ 16 + JetstreamCompression, JetstreamConfig, JetstreamConnector, 17 + events::{CommitOp, Cursor, EventKind, JetstreamEvent}, 18 + exports::Nsid, 19 + }; 20 + use url::Url; 21 21 22 + use serde::Deserialize; 23 + use std::time::Duration; 22 24 23 25 type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; 24 26 ··· 34 36 /// app password for the bot user 35 37 #[arg(short, long, env = "BOT_APP_PASSWORD")] 36 38 app_password: String, 39 + /// lightweight firehose 40 + #[arg(short, long, env = "BOT_JETSTREAM_URL")] 41 + #[clap(default_value = "wss://jetstream1.us-east.fire.hose.cam/subscribe")] 42 + jetstream_url: Url, 43 + /// optional: we can pick up from a past jetstream cursor 44 + /// 45 + /// the default is to just live-tail 46 + /// 47 + /// warning: setting this can lead to rapid bot posting 48 + #[arg(long)] 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>, 37 56 } 38 57 39 - #[tokio::main] 40 - async fn main() -> Result<()> { 41 - env_logger::init(); 42 - let args = Args::parse(); 58 + struct IssueDetails { 59 + repo_full_name: String, 60 + repo_url: String, 61 + title: String, 62 + issues_url: String, 63 + } 43 64 65 + /// com.bad-example.identity.resolveMiniDoc bit we care about 66 + #[derive(Deserialize)] 67 + struct MiniDocResponse { 68 + handle: String, 69 + } 44 70 45 - // Create HTTP client 46 - let pds_uri = args.pds.as_str().trim_end_matches('/').to_string().into(); 47 - let mut client = AuthenticatedClient::new(reqwest::Client::new(), pds_uri); 71 + /// com.atproto.repo.getRecord wraps the record in a `value` key 72 + #[derive(Deserialize)] 73 + struct GetRecordResonse<T> { 74 + value: T, 75 + } 48 76 49 - // Create session 50 - let session = Session::from( 51 - client 52 - .send( 53 - CreateSession::new() 54 - .identifier(&args.identifier) 55 - .password(args.app_password) 56 - .build(), 57 - ) 58 - .await? 59 - .into_output()?, 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}"# 60 232 ); 61 233 62 - println!("logged in as {} ({})", session.handle, session.did); 63 - client.set_session(session); 234 + let pre_len = 9; 235 + 236 + let repo_feature = serde_json::json!({ 237 + "$type": "app.bsky.richtext.facet#link", 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; 64 251 65 - let text = "hello from tangled.org/@bad-example.com/hacktober-bot/"; 66 - let link_feature = serde_json::json!({ 252 + let repo_issues_feature = serde_json::json!({ 67 253 "$type": "app.bsky.richtext.facet#link", 68 - "uri": "https://tangled.org/@bad-example.com/hacktober-bot/", 254 + "uri": issues_url, 69 255 }); 70 - let link_facet = Facet { 71 - features: vec![Data::from_json(&link_feature)?], 256 + let issues_facet = Facet { 257 + features: vec![Data::from_json(&repo_issues_feature)?], 72 258 index: ByteSlice { 73 - byte_start: 11, 74 - byte_end: 54, 259 + byte_start: title_starts_at, 260 + byte_end: title_starts_at + title.len() as i64, 75 261 extra_data: Default::default(), 76 262 }, 77 263 extra_data: Default::default(), ··· 81 267 let post = Post { 82 268 created_at: Datetime::now(), 83 269 langs: Some(vec![Language::new("en")?]), 84 - text: text.into(), 85 - facets: Some(vec![link_facet]), 270 + text: message.into(), 271 + facets: Some(vec![repo_facet, issues_facet]), 86 272 embed: Default::default(), 87 273 entities: Default::default(), 88 274 labels: Default::default(), ··· 93 279 94 280 let json = serde_json::to_value(post)?; 95 281 let data = Data::from_json(&json)?; 96 - let identifier = AtIdentifier::new(&args.identifier)?; 97 282 98 - println!("\nposting..."); 99 - let wat = client 100 - .send(CreateRecord::new() 101 - .repo(identifier) 102 - .collection(Post::nsid()) 103 - .record(data) 104 - .build()) 283 + log::info!("\nposting..."); 284 + client 285 + .send( 286 + CreateRecord::new() 287 + .repo(identifier.clone()) 288 + .collection(Post::nsid()) 289 + .record(data) 290 + .build(), 291 + ) 105 292 .await? 106 293 .into_output()?; 107 294 108 - println!("\ngot: {wat:?}"); 295 + Ok(()) 296 + } 297 + 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 + } 311 + } 312 + } 313 + 314 + #[tokio::main] 315 + async fn main() -> Result<()> { 316 + env_logger::init(); 317 + let args = Args::parse(); 318 + 319 + // Create HTTP client and session 320 + let client = BasicClient::new(args.pds); 321 + let bot_id = AtIdentifier::new(&args.identifier)?; 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()?; 334 + 335 + let jetstream_config: JetstreamConfig = JetstreamConfig { 336 + endpoint: args.jetstream_url.to_string(), 337 + wanted_collections: vec![Nsid::new("sh.tangled.label.op".to_string())?], 338 + user_agent: Some("hacktober_bot".to_string()), 339 + compression: JetstreamCompression::Zstd, 340 + replay_on_reconnect: true, 341 + channel_size: 1024, // buffer up to ~1s of jetstream events 342 + ..Default::default() 343 + }; 344 + let mut receiver = JetstreamConnector::new(jetstream_config)? 345 + .connect_cursor(args.jetstream_cursor.map(Cursor::from_raw_u64)) 346 + .await?; 347 + 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 + } 352 + 353 + log::info!("receiving jetstream messages..."); 354 + loop { 355 + let Some(event) = receiver.recv().await else { 356 + log::error!("consumer: could not receive event, bailing"); 357 + break; 358 + }; 359 + let cursor = event.cursor; 360 + 361 + let CreateLabelRecord { add: adds, subject } = match event_to_create_label(event) { 362 + Ok(clr) => clr, 363 + Err(e) => { 364 + log::debug!("ignoring unparseable event (at {cursor:?}): {e}"); 365 + continue; 366 + } 367 + }; 368 + 369 + let issue_details = match extract_issue_info(&slingshot_client, adds, subject.clone()).await 370 + { 371 + Ok(deets) => deets, 372 + Err(e) => { 373 + log::warn!("failed to extract issue details (at {cursor:?}): {e}"); 374 + continue; 375 + } 376 + }; 377 + 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: 387 + 388 + good-first-issue label added for {repo_full_name} ({repo_url}): 389 + 390 + > {title} ({issues_url})"# 391 + ); 392 + continue; 393 + } 394 + 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?; 400 + 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 + }; 408 + } 109 409 110 410 Ok(()) 111 411 }