Return of honkbot, in Rust. Hopefully it won't die all the time.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial commit: honkbot with follower cache and Tangled CI

goose.art ca068142

+4134
+3
.dockerignore
··· 1 + target/ 2 + .git/ 3 + .env
+11
.env.example
··· 1 + BSKY_HANDLE=your-bot.bsky.social 2 + BSKY_PASSWORD=your-app-password 3 + FLICKR_KEY=your-flickr-api-key 4 + # Optional: defaults to https://bsky.social 5 + # BSKY_SERVICE=https://bsky.social 6 + # Optional: defaults to ./fallback_images 7 + # FALLBACK_IMAGE_DIR=fallback_images 8 + # Optional: only reply to followers (default: false) 9 + # FOLLOWS_ONLY=true 10 + # Optional: set log level (default: info) 11 + # RUST_LOG=info
+3
.gitignore
··· 1 + /target 2 + .env 3 + # Fallback images are checked in for Docker builds
+30
.tangled/workflows/build.yml
··· 1 + when: 2 + - event: ["push"] 3 + branch: main 4 + 5 + engine: kubernetes 6 + image: quay.io/buildah/stable:latest 7 + architecture: amd64 8 + 9 + environment: 10 + IMAGE_REGISTRY: atcr.io 11 + IMAGE_USER: goose.art 12 + 13 + steps: 14 + - name: Login to ATCR 15 + command: | 16 + echo "${APP_PASSWORD}" | buildah login \ 17 + -u "${IMAGE_USER}" \ 18 + --password-stdin \ 19 + ${IMAGE_REGISTRY} 20 + 21 + - name: Build and push image 22 + command: | 23 + buildah bud \ 24 + --tag ${IMAGE_REGISTRY}/${IMAGE_USER}/honkbot:${TANGLED_REF_NAME} \ 25 + --tag ${IMAGE_REGISTRY}/${IMAGE_USER}/honkbot:latest \ 26 + --file ./Dockerfile \ 27 + . 28 + 29 + buildah push ${IMAGE_REGISTRY}/${IMAGE_USER}/honkbot:${TANGLED_REF_NAME} 30 + buildah push ${IMAGE_REGISTRY}/${IMAGE_USER}/honkbot:latest
+2854
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "adler2" 7 + version = "2.0.1" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 + 11 + [[package]] 12 + name = "aho-corasick" 13 + version = "1.1.4" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 16 + dependencies = [ 17 + "memchr", 18 + ] 19 + 20 + [[package]] 21 + name = "aligned" 22 + version = "0.4.3" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" 25 + dependencies = [ 26 + "as-slice", 27 + ] 28 + 29 + [[package]] 30 + name = "aligned-vec" 31 + version = "0.6.4" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" 34 + dependencies = [ 35 + "equator", 36 + ] 37 + 38 + [[package]] 39 + name = "anyhow" 40 + version = "1.0.102" 41 + source = "registry+https://github.com/rust-lang/crates.io-index" 42 + checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 43 + 44 + [[package]] 45 + name = "arbitrary" 46 + version = "1.4.2" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" 49 + 50 + [[package]] 51 + name = "arg_enum_proc_macro" 52 + version = "0.3.4" 53 + source = "registry+https://github.com/rust-lang/crates.io-index" 54 + checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" 55 + dependencies = [ 56 + "proc-macro2", 57 + "quote", 58 + "syn", 59 + ] 60 + 61 + [[package]] 62 + name = "arrayvec" 63 + version = "0.7.6" 64 + source = "registry+https://github.com/rust-lang/crates.io-index" 65 + checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 66 + 67 + [[package]] 68 + name = "as-slice" 69 + version = "0.2.1" 70 + source = "registry+https://github.com/rust-lang/crates.io-index" 71 + checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" 72 + dependencies = [ 73 + "stable_deref_trait", 74 + ] 75 + 76 + [[package]] 77 + name = "atomic-waker" 78 + version = "1.1.2" 79 + source = "registry+https://github.com/rust-lang/crates.io-index" 80 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 81 + 82 + [[package]] 83 + name = "autocfg" 84 + version = "1.5.0" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 87 + 88 + [[package]] 89 + name = "av-scenechange" 90 + version = "0.14.1" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" 93 + dependencies = [ 94 + "aligned", 95 + "anyhow", 96 + "arg_enum_proc_macro", 97 + "arrayvec", 98 + "log", 99 + "num-rational", 100 + "num-traits", 101 + "pastey", 102 + "rayon", 103 + "thiserror 2.0.18", 104 + "v_frame", 105 + "y4m", 106 + ] 107 + 108 + [[package]] 109 + name = "av1-grain" 110 + version = "0.2.5" 111 + source = "registry+https://github.com/rust-lang/crates.io-index" 112 + checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" 113 + dependencies = [ 114 + "anyhow", 115 + "arrayvec", 116 + "log", 117 + "nom", 118 + "num-rational", 119 + "v_frame", 120 + ] 121 + 122 + [[package]] 123 + name = "avif-serialize" 124 + version = "0.8.8" 125 + source = "registry+https://github.com/rust-lang/crates.io-index" 126 + checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" 127 + dependencies = [ 128 + "arrayvec", 129 + ] 130 + 131 + [[package]] 132 + name = "base64" 133 + version = "0.22.1" 134 + source = "registry+https://github.com/rust-lang/crates.io-index" 135 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 136 + 137 + [[package]] 138 + name = "bit_field" 139 + version = "0.10.3" 140 + source = "registry+https://github.com/rust-lang/crates.io-index" 141 + checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" 142 + 143 + [[package]] 144 + name = "bitflags" 145 + version = "2.11.0" 146 + source = "registry+https://github.com/rust-lang/crates.io-index" 147 + checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" 148 + 149 + [[package]] 150 + name = "bitstream-io" 151 + version = "4.9.0" 152 + source = "registry+https://github.com/rust-lang/crates.io-index" 153 + checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" 154 + dependencies = [ 155 + "core2", 156 + ] 157 + 158 + [[package]] 159 + name = "block-buffer" 160 + version = "0.10.4" 161 + source = "registry+https://github.com/rust-lang/crates.io-index" 162 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 163 + dependencies = [ 164 + "generic-array", 165 + ] 166 + 167 + [[package]] 168 + name = "built" 169 + version = "0.8.0" 170 + source = "registry+https://github.com/rust-lang/crates.io-index" 171 + checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" 172 + 173 + [[package]] 174 + name = "bumpalo" 175 + version = "3.20.2" 176 + source = "registry+https://github.com/rust-lang/crates.io-index" 177 + checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" 178 + 179 + [[package]] 180 + name = "bytemuck" 181 + version = "1.25.0" 182 + source = "registry+https://github.com/rust-lang/crates.io-index" 183 + checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" 184 + 185 + [[package]] 186 + name = "byteorder" 187 + version = "1.5.0" 188 + source = "registry+https://github.com/rust-lang/crates.io-index" 189 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 190 + 191 + [[package]] 192 + name = "byteorder-lite" 193 + version = "0.1.0" 194 + source = "registry+https://github.com/rust-lang/crates.io-index" 195 + checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 196 + 197 + [[package]] 198 + name = "bytes" 199 + version = "1.11.1" 200 + source = "registry+https://github.com/rust-lang/crates.io-index" 201 + checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" 202 + 203 + [[package]] 204 + name = "cc" 205 + version = "1.2.58" 206 + source = "registry+https://github.com/rust-lang/crates.io-index" 207 + checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" 208 + dependencies = [ 209 + "find-msvc-tools", 210 + "jobserver", 211 + "libc", 212 + "shlex", 213 + ] 214 + 215 + [[package]] 216 + name = "cfg-if" 217 + version = "1.0.4" 218 + source = "registry+https://github.com/rust-lang/crates.io-index" 219 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 220 + 221 + [[package]] 222 + name = "color_quant" 223 + version = "1.1.0" 224 + source = "registry+https://github.com/rust-lang/crates.io-index" 225 + checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 226 + 227 + [[package]] 228 + name = "core-foundation" 229 + version = "0.9.4" 230 + source = "registry+https://github.com/rust-lang/crates.io-index" 231 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 232 + dependencies = [ 233 + "core-foundation-sys", 234 + "libc", 235 + ] 236 + 237 + [[package]] 238 + name = "core-foundation" 239 + version = "0.10.1" 240 + source = "registry+https://github.com/rust-lang/crates.io-index" 241 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 242 + dependencies = [ 243 + "core-foundation-sys", 244 + "libc", 245 + ] 246 + 247 + [[package]] 248 + name = "core-foundation-sys" 249 + version = "0.8.7" 250 + source = "registry+https://github.com/rust-lang/crates.io-index" 251 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 252 + 253 + [[package]] 254 + name = "core2" 255 + version = "0.4.0" 256 + source = "registry+https://github.com/rust-lang/crates.io-index" 257 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 258 + dependencies = [ 259 + "memchr", 260 + ] 261 + 262 + [[package]] 263 + name = "cpufeatures" 264 + version = "0.2.17" 265 + source = "registry+https://github.com/rust-lang/crates.io-index" 266 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 267 + dependencies = [ 268 + "libc", 269 + ] 270 + 271 + [[package]] 272 + name = "crc32fast" 273 + version = "1.5.0" 274 + source = "registry+https://github.com/rust-lang/crates.io-index" 275 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 276 + dependencies = [ 277 + "cfg-if", 278 + ] 279 + 280 + [[package]] 281 + name = "crossbeam-deque" 282 + version = "0.8.6" 283 + source = "registry+https://github.com/rust-lang/crates.io-index" 284 + checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 285 + dependencies = [ 286 + "crossbeam-epoch", 287 + "crossbeam-utils", 288 + ] 289 + 290 + [[package]] 291 + name = "crossbeam-epoch" 292 + version = "0.9.18" 293 + source = "registry+https://github.com/rust-lang/crates.io-index" 294 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 295 + dependencies = [ 296 + "crossbeam-utils", 297 + ] 298 + 299 + [[package]] 300 + name = "crossbeam-utils" 301 + version = "0.8.21" 302 + source = "registry+https://github.com/rust-lang/crates.io-index" 303 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 304 + 305 + [[package]] 306 + name = "crunchy" 307 + version = "0.2.4" 308 + source = "registry+https://github.com/rust-lang/crates.io-index" 309 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 310 + 311 + [[package]] 312 + name = "crypto-common" 313 + version = "0.1.7" 314 + source = "registry+https://github.com/rust-lang/crates.io-index" 315 + checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 316 + dependencies = [ 317 + "generic-array", 318 + "typenum", 319 + ] 320 + 321 + [[package]] 322 + name = "data-encoding" 323 + version = "2.10.0" 324 + source = "registry+https://github.com/rust-lang/crates.io-index" 325 + checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" 326 + 327 + [[package]] 328 + name = "digest" 329 + version = "0.10.7" 330 + source = "registry+https://github.com/rust-lang/crates.io-index" 331 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 332 + dependencies = [ 333 + "block-buffer", 334 + "crypto-common", 335 + ] 336 + 337 + [[package]] 338 + name = "displaydoc" 339 + version = "0.2.5" 340 + source = "registry+https://github.com/rust-lang/crates.io-index" 341 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 342 + dependencies = [ 343 + "proc-macro2", 344 + "quote", 345 + "syn", 346 + ] 347 + 348 + [[package]] 349 + name = "dotenvy" 350 + version = "0.15.7" 351 + source = "registry+https://github.com/rust-lang/crates.io-index" 352 + checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 353 + 354 + [[package]] 355 + name = "either" 356 + version = "1.15.0" 357 + source = "registry+https://github.com/rust-lang/crates.io-index" 358 + checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 359 + 360 + [[package]] 361 + name = "encoding_rs" 362 + version = "0.8.35" 363 + source = "registry+https://github.com/rust-lang/crates.io-index" 364 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 365 + dependencies = [ 366 + "cfg-if", 367 + ] 368 + 369 + [[package]] 370 + name = "equator" 371 + version = "0.4.2" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" 374 + dependencies = [ 375 + "equator-macro", 376 + ] 377 + 378 + [[package]] 379 + name = "equator-macro" 380 + version = "0.4.2" 381 + source = "registry+https://github.com/rust-lang/crates.io-index" 382 + checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" 383 + dependencies = [ 384 + "proc-macro2", 385 + "quote", 386 + "syn", 387 + ] 388 + 389 + [[package]] 390 + name = "equivalent" 391 + version = "1.0.2" 392 + source = "registry+https://github.com/rust-lang/crates.io-index" 393 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 394 + 395 + [[package]] 396 + name = "errno" 397 + version = "0.3.14" 398 + source = "registry+https://github.com/rust-lang/crates.io-index" 399 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 400 + dependencies = [ 401 + "libc", 402 + "windows-sys 0.61.2", 403 + ] 404 + 405 + [[package]] 406 + name = "exr" 407 + version = "1.74.0" 408 + source = "registry+https://github.com/rust-lang/crates.io-index" 409 + checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" 410 + dependencies = [ 411 + "bit_field", 412 + "half", 413 + "lebe", 414 + "miniz_oxide", 415 + "rayon-core", 416 + "smallvec", 417 + "zune-inflate", 418 + ] 419 + 420 + [[package]] 421 + name = "fastrand" 422 + version = "2.3.0" 423 + source = "registry+https://github.com/rust-lang/crates.io-index" 424 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 425 + 426 + [[package]] 427 + name = "fax" 428 + version = "0.2.6" 429 + source = "registry+https://github.com/rust-lang/crates.io-index" 430 + checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" 431 + dependencies = [ 432 + "fax_derive", 433 + ] 434 + 435 + [[package]] 436 + name = "fax_derive" 437 + version = "0.2.0" 438 + source = "registry+https://github.com/rust-lang/crates.io-index" 439 + checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" 440 + dependencies = [ 441 + "proc-macro2", 442 + "quote", 443 + "syn", 444 + ] 445 + 446 + [[package]] 447 + name = "fdeflate" 448 + version = "0.3.7" 449 + source = "registry+https://github.com/rust-lang/crates.io-index" 450 + checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 451 + dependencies = [ 452 + "simd-adler32", 453 + ] 454 + 455 + [[package]] 456 + name = "find-msvc-tools" 457 + version = "0.1.9" 458 + source = "registry+https://github.com/rust-lang/crates.io-index" 459 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" 460 + 461 + [[package]] 462 + name = "flate2" 463 + version = "1.1.9" 464 + source = "registry+https://github.com/rust-lang/crates.io-index" 465 + checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" 466 + dependencies = [ 467 + "crc32fast", 468 + "miniz_oxide", 469 + ] 470 + 471 + [[package]] 472 + name = "fnv" 473 + version = "1.0.7" 474 + source = "registry+https://github.com/rust-lang/crates.io-index" 475 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 476 + 477 + [[package]] 478 + name = "foldhash" 479 + version = "0.1.5" 480 + source = "registry+https://github.com/rust-lang/crates.io-index" 481 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 482 + 483 + [[package]] 484 + name = "foreign-types" 485 + version = "0.3.2" 486 + source = "registry+https://github.com/rust-lang/crates.io-index" 487 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 488 + dependencies = [ 489 + "foreign-types-shared", 490 + ] 491 + 492 + [[package]] 493 + name = "foreign-types-shared" 494 + version = "0.1.1" 495 + source = "registry+https://github.com/rust-lang/crates.io-index" 496 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 497 + 498 + [[package]] 499 + name = "form_urlencoded" 500 + version = "1.2.2" 501 + source = "registry+https://github.com/rust-lang/crates.io-index" 502 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 503 + dependencies = [ 504 + "percent-encoding", 505 + ] 506 + 507 + [[package]] 508 + name = "futures-channel" 509 + version = "0.3.32" 510 + source = "registry+https://github.com/rust-lang/crates.io-index" 511 + checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" 512 + dependencies = [ 513 + "futures-core", 514 + ] 515 + 516 + [[package]] 517 + name = "futures-core" 518 + version = "0.3.32" 519 + source = "registry+https://github.com/rust-lang/crates.io-index" 520 + checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 521 + 522 + [[package]] 523 + name = "futures-macro" 524 + version = "0.3.32" 525 + source = "registry+https://github.com/rust-lang/crates.io-index" 526 + checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" 527 + dependencies = [ 528 + "proc-macro2", 529 + "quote", 530 + "syn", 531 + ] 532 + 533 + [[package]] 534 + name = "futures-sink" 535 + version = "0.3.32" 536 + source = "registry+https://github.com/rust-lang/crates.io-index" 537 + checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" 538 + 539 + [[package]] 540 + name = "futures-task" 541 + version = "0.3.32" 542 + source = "registry+https://github.com/rust-lang/crates.io-index" 543 + checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" 544 + 545 + [[package]] 546 + name = "futures-util" 547 + version = "0.3.32" 548 + source = "registry+https://github.com/rust-lang/crates.io-index" 549 + checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" 550 + dependencies = [ 551 + "futures-core", 552 + "futures-macro", 553 + "futures-sink", 554 + "futures-task", 555 + "pin-project-lite", 556 + "slab", 557 + ] 558 + 559 + [[package]] 560 + name = "generic-array" 561 + version = "0.14.7" 562 + source = "registry+https://github.com/rust-lang/crates.io-index" 563 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 564 + dependencies = [ 565 + "typenum", 566 + "version_check", 567 + ] 568 + 569 + [[package]] 570 + name = "getrandom" 571 + version = "0.2.17" 572 + source = "registry+https://github.com/rust-lang/crates.io-index" 573 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 574 + dependencies = [ 575 + "cfg-if", 576 + "libc", 577 + "wasi", 578 + ] 579 + 580 + [[package]] 581 + name = "getrandom" 582 + version = "0.3.4" 583 + source = "registry+https://github.com/rust-lang/crates.io-index" 584 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 585 + dependencies = [ 586 + "cfg-if", 587 + "libc", 588 + "r-efi 5.3.0", 589 + "wasip2", 590 + ] 591 + 592 + [[package]] 593 + name = "getrandom" 594 + version = "0.4.2" 595 + source = "registry+https://github.com/rust-lang/crates.io-index" 596 + checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" 597 + dependencies = [ 598 + "cfg-if", 599 + "libc", 600 + "r-efi 6.0.0", 601 + "wasip2", 602 + "wasip3", 603 + ] 604 + 605 + [[package]] 606 + name = "gif" 607 + version = "0.14.1" 608 + source = "registry+https://github.com/rust-lang/crates.io-index" 609 + checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" 610 + dependencies = [ 611 + "color_quant", 612 + "weezl", 613 + ] 614 + 615 + [[package]] 616 + name = "h2" 617 + version = "0.4.13" 618 + source = "registry+https://github.com/rust-lang/crates.io-index" 619 + checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" 620 + dependencies = [ 621 + "atomic-waker", 622 + "bytes", 623 + "fnv", 624 + "futures-core", 625 + "futures-sink", 626 + "http", 627 + "indexmap", 628 + "slab", 629 + "tokio", 630 + "tokio-util", 631 + "tracing", 632 + ] 633 + 634 + [[package]] 635 + name = "half" 636 + version = "2.7.1" 637 + source = "registry+https://github.com/rust-lang/crates.io-index" 638 + checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" 639 + dependencies = [ 640 + "cfg-if", 641 + "crunchy", 642 + "zerocopy", 643 + ] 644 + 645 + [[package]] 646 + name = "hashbrown" 647 + version = "0.15.5" 648 + source = "registry+https://github.com/rust-lang/crates.io-index" 649 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 650 + dependencies = [ 651 + "foldhash", 652 + ] 653 + 654 + [[package]] 655 + name = "hashbrown" 656 + version = "0.16.1" 657 + source = "registry+https://github.com/rust-lang/crates.io-index" 658 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 659 + 660 + [[package]] 661 + name = "heck" 662 + version = "0.5.0" 663 + source = "registry+https://github.com/rust-lang/crates.io-index" 664 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 665 + 666 + [[package]] 667 + name = "honkbot" 668 + version = "0.1.0" 669 + dependencies = [ 670 + "anyhow", 671 + "dotenvy", 672 + "futures-util", 673 + "image", 674 + "rand 0.8.5", 675 + "reqwest", 676 + "serde", 677 + "serde_json", 678 + "tokio", 679 + "tokio-tungstenite", 680 + "tracing", 681 + "tracing-subscriber", 682 + "url", 683 + "urlencoding", 684 + ] 685 + 686 + [[package]] 687 + name = "http" 688 + version = "1.4.0" 689 + source = "registry+https://github.com/rust-lang/crates.io-index" 690 + checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 691 + dependencies = [ 692 + "bytes", 693 + "itoa", 694 + ] 695 + 696 + [[package]] 697 + name = "http-body" 698 + version = "1.0.1" 699 + source = "registry+https://github.com/rust-lang/crates.io-index" 700 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 701 + dependencies = [ 702 + "bytes", 703 + "http", 704 + ] 705 + 706 + [[package]] 707 + name = "http-body-util" 708 + version = "0.1.3" 709 + source = "registry+https://github.com/rust-lang/crates.io-index" 710 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 711 + dependencies = [ 712 + "bytes", 713 + "futures-core", 714 + "http", 715 + "http-body", 716 + "pin-project-lite", 717 + ] 718 + 719 + [[package]] 720 + name = "httparse" 721 + version = "1.10.1" 722 + source = "registry+https://github.com/rust-lang/crates.io-index" 723 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 724 + 725 + [[package]] 726 + name = "hyper" 727 + version = "1.9.0" 728 + source = "registry+https://github.com/rust-lang/crates.io-index" 729 + checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" 730 + dependencies = [ 731 + "atomic-waker", 732 + "bytes", 733 + "futures-channel", 734 + "futures-core", 735 + "h2", 736 + "http", 737 + "http-body", 738 + "httparse", 739 + "itoa", 740 + "pin-project-lite", 741 + "smallvec", 742 + "tokio", 743 + "want", 744 + ] 745 + 746 + [[package]] 747 + name = "hyper-rustls" 748 + version = "0.27.7" 749 + source = "registry+https://github.com/rust-lang/crates.io-index" 750 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 751 + dependencies = [ 752 + "http", 753 + "hyper", 754 + "hyper-util", 755 + "rustls", 756 + "rustls-pki-types", 757 + "tokio", 758 + "tokio-rustls", 759 + "tower-service", 760 + ] 761 + 762 + [[package]] 763 + name = "hyper-tls" 764 + version = "0.6.0" 765 + source = "registry+https://github.com/rust-lang/crates.io-index" 766 + checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 767 + dependencies = [ 768 + "bytes", 769 + "http-body-util", 770 + "hyper", 771 + "hyper-util", 772 + "native-tls", 773 + "tokio", 774 + "tokio-native-tls", 775 + "tower-service", 776 + ] 777 + 778 + [[package]] 779 + name = "hyper-util" 780 + version = "0.1.20" 781 + source = "registry+https://github.com/rust-lang/crates.io-index" 782 + checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 783 + dependencies = [ 784 + "base64", 785 + "bytes", 786 + "futures-channel", 787 + "futures-util", 788 + "http", 789 + "http-body", 790 + "hyper", 791 + "ipnet", 792 + "libc", 793 + "percent-encoding", 794 + "pin-project-lite", 795 + "socket2", 796 + "system-configuration", 797 + "tokio", 798 + "tower-service", 799 + "tracing", 800 + "windows-registry", 801 + ] 802 + 803 + [[package]] 804 + name = "icu_collections" 805 + version = "2.2.0" 806 + source = "registry+https://github.com/rust-lang/crates.io-index" 807 + checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" 808 + dependencies = [ 809 + "displaydoc", 810 + "potential_utf", 811 + "utf8_iter", 812 + "yoke", 813 + "zerofrom", 814 + "zerovec", 815 + ] 816 + 817 + [[package]] 818 + name = "icu_locale_core" 819 + version = "2.2.0" 820 + source = "registry+https://github.com/rust-lang/crates.io-index" 821 + checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" 822 + dependencies = [ 823 + "displaydoc", 824 + "litemap", 825 + "tinystr", 826 + "writeable", 827 + "zerovec", 828 + ] 829 + 830 + [[package]] 831 + name = "icu_normalizer" 832 + version = "2.2.0" 833 + source = "registry+https://github.com/rust-lang/crates.io-index" 834 + checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" 835 + dependencies = [ 836 + "icu_collections", 837 + "icu_normalizer_data", 838 + "icu_properties", 839 + "icu_provider", 840 + "smallvec", 841 + "zerovec", 842 + ] 843 + 844 + [[package]] 845 + name = "icu_normalizer_data" 846 + version = "2.2.0" 847 + source = "registry+https://github.com/rust-lang/crates.io-index" 848 + checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" 849 + 850 + [[package]] 851 + name = "icu_properties" 852 + version = "2.2.0" 853 + source = "registry+https://github.com/rust-lang/crates.io-index" 854 + checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" 855 + dependencies = [ 856 + "icu_collections", 857 + "icu_locale_core", 858 + "icu_properties_data", 859 + "icu_provider", 860 + "zerotrie", 861 + "zerovec", 862 + ] 863 + 864 + [[package]] 865 + name = "icu_properties_data" 866 + version = "2.2.0" 867 + source = "registry+https://github.com/rust-lang/crates.io-index" 868 + checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" 869 + 870 + [[package]] 871 + name = "icu_provider" 872 + version = "2.2.0" 873 + source = "registry+https://github.com/rust-lang/crates.io-index" 874 + checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" 875 + dependencies = [ 876 + "displaydoc", 877 + "icu_locale_core", 878 + "writeable", 879 + "yoke", 880 + "zerofrom", 881 + "zerotrie", 882 + "zerovec", 883 + ] 884 + 885 + [[package]] 886 + name = "id-arena" 887 + version = "2.3.0" 888 + source = "registry+https://github.com/rust-lang/crates.io-index" 889 + checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" 890 + 891 + [[package]] 892 + name = "idna" 893 + version = "1.1.0" 894 + source = "registry+https://github.com/rust-lang/crates.io-index" 895 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 896 + dependencies = [ 897 + "idna_adapter", 898 + "smallvec", 899 + "utf8_iter", 900 + ] 901 + 902 + [[package]] 903 + name = "idna_adapter" 904 + version = "1.2.1" 905 + source = "registry+https://github.com/rust-lang/crates.io-index" 906 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 907 + dependencies = [ 908 + "icu_normalizer", 909 + "icu_properties", 910 + ] 911 + 912 + [[package]] 913 + name = "image" 914 + version = "0.25.10" 915 + source = "registry+https://github.com/rust-lang/crates.io-index" 916 + checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" 917 + dependencies = [ 918 + "bytemuck", 919 + "byteorder-lite", 920 + "color_quant", 921 + "exr", 922 + "gif", 923 + "image-webp", 924 + "moxcms", 925 + "num-traits", 926 + "png", 927 + "qoi", 928 + "ravif", 929 + "rayon", 930 + "rgb", 931 + "tiff", 932 + "zune-core", 933 + "zune-jpeg", 934 + ] 935 + 936 + [[package]] 937 + name = "image-webp" 938 + version = "0.2.4" 939 + source = "registry+https://github.com/rust-lang/crates.io-index" 940 + checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" 941 + dependencies = [ 942 + "byteorder-lite", 943 + "quick-error", 944 + ] 945 + 946 + [[package]] 947 + name = "imgref" 948 + version = "1.12.0" 949 + source = "registry+https://github.com/rust-lang/crates.io-index" 950 + checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" 951 + 952 + [[package]] 953 + name = "indexmap" 954 + version = "2.13.0" 955 + source = "registry+https://github.com/rust-lang/crates.io-index" 956 + checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" 957 + dependencies = [ 958 + "equivalent", 959 + "hashbrown 0.16.1", 960 + "serde", 961 + "serde_core", 962 + ] 963 + 964 + [[package]] 965 + name = "interpolate_name" 966 + version = "0.2.4" 967 + source = "registry+https://github.com/rust-lang/crates.io-index" 968 + checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" 969 + dependencies = [ 970 + "proc-macro2", 971 + "quote", 972 + "syn", 973 + ] 974 + 975 + [[package]] 976 + name = "ipnet" 977 + version = "2.12.0" 978 + source = "registry+https://github.com/rust-lang/crates.io-index" 979 + checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" 980 + 981 + [[package]] 982 + name = "iri-string" 983 + version = "0.7.12" 984 + source = "registry+https://github.com/rust-lang/crates.io-index" 985 + checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" 986 + dependencies = [ 987 + "memchr", 988 + "serde", 989 + ] 990 + 991 + [[package]] 992 + name = "itertools" 993 + version = "0.14.0" 994 + source = "registry+https://github.com/rust-lang/crates.io-index" 995 + checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 996 + dependencies = [ 997 + "either", 998 + ] 999 + 1000 + [[package]] 1001 + name = "itoa" 1002 + version = "1.0.18" 1003 + source = "registry+https://github.com/rust-lang/crates.io-index" 1004 + checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" 1005 + 1006 + [[package]] 1007 + name = "jobserver" 1008 + version = "0.1.34" 1009 + source = "registry+https://github.com/rust-lang/crates.io-index" 1010 + checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 1011 + dependencies = [ 1012 + "getrandom 0.3.4", 1013 + "libc", 1014 + ] 1015 + 1016 + [[package]] 1017 + name = "js-sys" 1018 + version = "0.3.94" 1019 + source = "registry+https://github.com/rust-lang/crates.io-index" 1020 + checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" 1021 + dependencies = [ 1022 + "cfg-if", 1023 + "futures-util", 1024 + "once_cell", 1025 + "wasm-bindgen", 1026 + ] 1027 + 1028 + [[package]] 1029 + name = "lazy_static" 1030 + version = "1.5.0" 1031 + source = "registry+https://github.com/rust-lang/crates.io-index" 1032 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1033 + 1034 + [[package]] 1035 + name = "leb128fmt" 1036 + version = "0.1.0" 1037 + source = "registry+https://github.com/rust-lang/crates.io-index" 1038 + checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 1039 + 1040 + [[package]] 1041 + name = "lebe" 1042 + version = "0.5.3" 1043 + source = "registry+https://github.com/rust-lang/crates.io-index" 1044 + checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" 1045 + 1046 + [[package]] 1047 + name = "libc" 1048 + version = "0.2.184" 1049 + source = "registry+https://github.com/rust-lang/crates.io-index" 1050 + checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" 1051 + 1052 + [[package]] 1053 + name = "libfuzzer-sys" 1054 + version = "0.4.12" 1055 + source = "registry+https://github.com/rust-lang/crates.io-index" 1056 + checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" 1057 + dependencies = [ 1058 + "arbitrary", 1059 + "cc", 1060 + ] 1061 + 1062 + [[package]] 1063 + name = "linux-raw-sys" 1064 + version = "0.12.1" 1065 + source = "registry+https://github.com/rust-lang/crates.io-index" 1066 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 1067 + 1068 + [[package]] 1069 + name = "litemap" 1070 + version = "0.8.2" 1071 + source = "registry+https://github.com/rust-lang/crates.io-index" 1072 + checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" 1073 + 1074 + [[package]] 1075 + name = "lock_api" 1076 + version = "0.4.14" 1077 + source = "registry+https://github.com/rust-lang/crates.io-index" 1078 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1079 + dependencies = [ 1080 + "scopeguard", 1081 + ] 1082 + 1083 + [[package]] 1084 + name = "log" 1085 + version = "0.4.29" 1086 + source = "registry+https://github.com/rust-lang/crates.io-index" 1087 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 1088 + 1089 + [[package]] 1090 + name = "loop9" 1091 + version = "0.1.5" 1092 + source = "registry+https://github.com/rust-lang/crates.io-index" 1093 + checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" 1094 + dependencies = [ 1095 + "imgref", 1096 + ] 1097 + 1098 + [[package]] 1099 + name = "matchers" 1100 + version = "0.2.0" 1101 + source = "registry+https://github.com/rust-lang/crates.io-index" 1102 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 1103 + dependencies = [ 1104 + "regex-automata", 1105 + ] 1106 + 1107 + [[package]] 1108 + name = "maybe-rayon" 1109 + version = "0.1.1" 1110 + source = "registry+https://github.com/rust-lang/crates.io-index" 1111 + checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" 1112 + dependencies = [ 1113 + "cfg-if", 1114 + "rayon", 1115 + ] 1116 + 1117 + [[package]] 1118 + name = "memchr" 1119 + version = "2.8.0" 1120 + source = "registry+https://github.com/rust-lang/crates.io-index" 1121 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 1122 + 1123 + [[package]] 1124 + name = "mime" 1125 + version = "0.3.17" 1126 + source = "registry+https://github.com/rust-lang/crates.io-index" 1127 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1128 + 1129 + [[package]] 1130 + name = "miniz_oxide" 1131 + version = "0.8.9" 1132 + source = "registry+https://github.com/rust-lang/crates.io-index" 1133 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1134 + dependencies = [ 1135 + "adler2", 1136 + "simd-adler32", 1137 + ] 1138 + 1139 + [[package]] 1140 + name = "mio" 1141 + version = "1.2.0" 1142 + source = "registry+https://github.com/rust-lang/crates.io-index" 1143 + checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" 1144 + dependencies = [ 1145 + "libc", 1146 + "wasi", 1147 + "windows-sys 0.61.2", 1148 + ] 1149 + 1150 + [[package]] 1151 + name = "moxcms" 1152 + version = "0.8.1" 1153 + source = "registry+https://github.com/rust-lang/crates.io-index" 1154 + checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" 1155 + dependencies = [ 1156 + "num-traits", 1157 + "pxfm", 1158 + ] 1159 + 1160 + [[package]] 1161 + name = "native-tls" 1162 + version = "0.2.18" 1163 + source = "registry+https://github.com/rust-lang/crates.io-index" 1164 + checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" 1165 + dependencies = [ 1166 + "libc", 1167 + "log", 1168 + "openssl", 1169 + "openssl-probe", 1170 + "openssl-sys", 1171 + "schannel", 1172 + "security-framework", 1173 + "security-framework-sys", 1174 + "tempfile", 1175 + ] 1176 + 1177 + [[package]] 1178 + name = "new_debug_unreachable" 1179 + version = "1.0.6" 1180 + source = "registry+https://github.com/rust-lang/crates.io-index" 1181 + checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 1182 + 1183 + [[package]] 1184 + name = "nom" 1185 + version = "8.0.0" 1186 + source = "registry+https://github.com/rust-lang/crates.io-index" 1187 + checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" 1188 + dependencies = [ 1189 + "memchr", 1190 + ] 1191 + 1192 + [[package]] 1193 + name = "noop_proc_macro" 1194 + version = "0.3.0" 1195 + source = "registry+https://github.com/rust-lang/crates.io-index" 1196 + checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" 1197 + 1198 + [[package]] 1199 + name = "nu-ansi-term" 1200 + version = "0.50.3" 1201 + source = "registry+https://github.com/rust-lang/crates.io-index" 1202 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 1203 + dependencies = [ 1204 + "windows-sys 0.61.2", 1205 + ] 1206 + 1207 + [[package]] 1208 + name = "num-bigint" 1209 + version = "0.4.6" 1210 + source = "registry+https://github.com/rust-lang/crates.io-index" 1211 + checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1212 + dependencies = [ 1213 + "num-integer", 1214 + "num-traits", 1215 + ] 1216 + 1217 + [[package]] 1218 + name = "num-derive" 1219 + version = "0.4.2" 1220 + source = "registry+https://github.com/rust-lang/crates.io-index" 1221 + checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 1222 + dependencies = [ 1223 + "proc-macro2", 1224 + "quote", 1225 + "syn", 1226 + ] 1227 + 1228 + [[package]] 1229 + name = "num-integer" 1230 + version = "0.1.46" 1231 + source = "registry+https://github.com/rust-lang/crates.io-index" 1232 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1233 + dependencies = [ 1234 + "num-traits", 1235 + ] 1236 + 1237 + [[package]] 1238 + name = "num-rational" 1239 + version = "0.4.2" 1240 + source = "registry+https://github.com/rust-lang/crates.io-index" 1241 + checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 1242 + dependencies = [ 1243 + "num-bigint", 1244 + "num-integer", 1245 + "num-traits", 1246 + ] 1247 + 1248 + [[package]] 1249 + name = "num-traits" 1250 + version = "0.2.19" 1251 + source = "registry+https://github.com/rust-lang/crates.io-index" 1252 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1253 + dependencies = [ 1254 + "autocfg", 1255 + ] 1256 + 1257 + [[package]] 1258 + name = "once_cell" 1259 + version = "1.21.4" 1260 + source = "registry+https://github.com/rust-lang/crates.io-index" 1261 + checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" 1262 + 1263 + [[package]] 1264 + name = "openssl" 1265 + version = "0.10.76" 1266 + source = "registry+https://github.com/rust-lang/crates.io-index" 1267 + checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" 1268 + dependencies = [ 1269 + "bitflags", 1270 + "cfg-if", 1271 + "foreign-types", 1272 + "libc", 1273 + "once_cell", 1274 + "openssl-macros", 1275 + "openssl-sys", 1276 + ] 1277 + 1278 + [[package]] 1279 + name = "openssl-macros" 1280 + version = "0.1.1" 1281 + source = "registry+https://github.com/rust-lang/crates.io-index" 1282 + checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1283 + dependencies = [ 1284 + "proc-macro2", 1285 + "quote", 1286 + "syn", 1287 + ] 1288 + 1289 + [[package]] 1290 + name = "openssl-probe" 1291 + version = "0.2.1" 1292 + source = "registry+https://github.com/rust-lang/crates.io-index" 1293 + checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 1294 + 1295 + [[package]] 1296 + name = "openssl-sys" 1297 + version = "0.9.112" 1298 + source = "registry+https://github.com/rust-lang/crates.io-index" 1299 + checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" 1300 + dependencies = [ 1301 + "cc", 1302 + "libc", 1303 + "pkg-config", 1304 + "vcpkg", 1305 + ] 1306 + 1307 + [[package]] 1308 + name = "parking_lot" 1309 + version = "0.12.5" 1310 + source = "registry+https://github.com/rust-lang/crates.io-index" 1311 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 1312 + dependencies = [ 1313 + "lock_api", 1314 + "parking_lot_core", 1315 + ] 1316 + 1317 + [[package]] 1318 + name = "parking_lot_core" 1319 + version = "0.9.12" 1320 + source = "registry+https://github.com/rust-lang/crates.io-index" 1321 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 1322 + dependencies = [ 1323 + "cfg-if", 1324 + "libc", 1325 + "redox_syscall", 1326 + "smallvec", 1327 + "windows-link", 1328 + ] 1329 + 1330 + [[package]] 1331 + name = "paste" 1332 + version = "1.0.15" 1333 + source = "registry+https://github.com/rust-lang/crates.io-index" 1334 + checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1335 + 1336 + [[package]] 1337 + name = "pastey" 1338 + version = "0.1.1" 1339 + source = "registry+https://github.com/rust-lang/crates.io-index" 1340 + checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" 1341 + 1342 + [[package]] 1343 + name = "percent-encoding" 1344 + version = "2.3.2" 1345 + source = "registry+https://github.com/rust-lang/crates.io-index" 1346 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1347 + 1348 + [[package]] 1349 + name = "pin-project-lite" 1350 + version = "0.2.17" 1351 + source = "registry+https://github.com/rust-lang/crates.io-index" 1352 + checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 1353 + 1354 + [[package]] 1355 + name = "pkg-config" 1356 + version = "0.3.32" 1357 + source = "registry+https://github.com/rust-lang/crates.io-index" 1358 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1359 + 1360 + [[package]] 1361 + name = "png" 1362 + version = "0.18.1" 1363 + source = "registry+https://github.com/rust-lang/crates.io-index" 1364 + checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" 1365 + dependencies = [ 1366 + "bitflags", 1367 + "crc32fast", 1368 + "fdeflate", 1369 + "flate2", 1370 + "miniz_oxide", 1371 + ] 1372 + 1373 + [[package]] 1374 + name = "potential_utf" 1375 + version = "0.1.5" 1376 + source = "registry+https://github.com/rust-lang/crates.io-index" 1377 + checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" 1378 + dependencies = [ 1379 + "zerovec", 1380 + ] 1381 + 1382 + [[package]] 1383 + name = "ppv-lite86" 1384 + version = "0.2.21" 1385 + source = "registry+https://github.com/rust-lang/crates.io-index" 1386 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1387 + dependencies = [ 1388 + "zerocopy", 1389 + ] 1390 + 1391 + [[package]] 1392 + name = "prettyplease" 1393 + version = "0.2.37" 1394 + source = "registry+https://github.com/rust-lang/crates.io-index" 1395 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 1396 + dependencies = [ 1397 + "proc-macro2", 1398 + "syn", 1399 + ] 1400 + 1401 + [[package]] 1402 + name = "proc-macro2" 1403 + version = "1.0.106" 1404 + source = "registry+https://github.com/rust-lang/crates.io-index" 1405 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 1406 + dependencies = [ 1407 + "unicode-ident", 1408 + ] 1409 + 1410 + [[package]] 1411 + name = "profiling" 1412 + version = "1.0.17" 1413 + source = "registry+https://github.com/rust-lang/crates.io-index" 1414 + checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" 1415 + dependencies = [ 1416 + "profiling-procmacros", 1417 + ] 1418 + 1419 + [[package]] 1420 + name = "profiling-procmacros" 1421 + version = "1.0.17" 1422 + source = "registry+https://github.com/rust-lang/crates.io-index" 1423 + checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" 1424 + dependencies = [ 1425 + "quote", 1426 + "syn", 1427 + ] 1428 + 1429 + [[package]] 1430 + name = "pxfm" 1431 + version = "0.1.28" 1432 + source = "registry+https://github.com/rust-lang/crates.io-index" 1433 + checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" 1434 + 1435 + [[package]] 1436 + name = "qoi" 1437 + version = "0.4.1" 1438 + source = "registry+https://github.com/rust-lang/crates.io-index" 1439 + checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" 1440 + dependencies = [ 1441 + "bytemuck", 1442 + ] 1443 + 1444 + [[package]] 1445 + name = "quick-error" 1446 + version = "2.0.1" 1447 + source = "registry+https://github.com/rust-lang/crates.io-index" 1448 + checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 1449 + 1450 + [[package]] 1451 + name = "quote" 1452 + version = "1.0.45" 1453 + source = "registry+https://github.com/rust-lang/crates.io-index" 1454 + checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 1455 + dependencies = [ 1456 + "proc-macro2", 1457 + ] 1458 + 1459 + [[package]] 1460 + name = "r-efi" 1461 + version = "5.3.0" 1462 + source = "registry+https://github.com/rust-lang/crates.io-index" 1463 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1464 + 1465 + [[package]] 1466 + name = "r-efi" 1467 + version = "6.0.0" 1468 + source = "registry+https://github.com/rust-lang/crates.io-index" 1469 + checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" 1470 + 1471 + [[package]] 1472 + name = "rand" 1473 + version = "0.8.5" 1474 + source = "registry+https://github.com/rust-lang/crates.io-index" 1475 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1476 + dependencies = [ 1477 + "libc", 1478 + "rand_chacha 0.3.1", 1479 + "rand_core 0.6.4", 1480 + ] 1481 + 1482 + [[package]] 1483 + name = "rand" 1484 + version = "0.9.2" 1485 + source = "registry+https://github.com/rust-lang/crates.io-index" 1486 + checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 1487 + dependencies = [ 1488 + "rand_chacha 0.9.0", 1489 + "rand_core 0.9.5", 1490 + ] 1491 + 1492 + [[package]] 1493 + name = "rand_chacha" 1494 + version = "0.3.1" 1495 + source = "registry+https://github.com/rust-lang/crates.io-index" 1496 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1497 + dependencies = [ 1498 + "ppv-lite86", 1499 + "rand_core 0.6.4", 1500 + ] 1501 + 1502 + [[package]] 1503 + name = "rand_chacha" 1504 + version = "0.9.0" 1505 + source = "registry+https://github.com/rust-lang/crates.io-index" 1506 + checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1507 + dependencies = [ 1508 + "ppv-lite86", 1509 + "rand_core 0.9.5", 1510 + ] 1511 + 1512 + [[package]] 1513 + name = "rand_core" 1514 + version = "0.6.4" 1515 + source = "registry+https://github.com/rust-lang/crates.io-index" 1516 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1517 + dependencies = [ 1518 + "getrandom 0.2.17", 1519 + ] 1520 + 1521 + [[package]] 1522 + name = "rand_core" 1523 + version = "0.9.5" 1524 + source = "registry+https://github.com/rust-lang/crates.io-index" 1525 + checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" 1526 + dependencies = [ 1527 + "getrandom 0.3.4", 1528 + ] 1529 + 1530 + [[package]] 1531 + name = "rav1e" 1532 + version = "0.8.1" 1533 + source = "registry+https://github.com/rust-lang/crates.io-index" 1534 + checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" 1535 + dependencies = [ 1536 + "aligned-vec", 1537 + "arbitrary", 1538 + "arg_enum_proc_macro", 1539 + "arrayvec", 1540 + "av-scenechange", 1541 + "av1-grain", 1542 + "bitstream-io", 1543 + "built", 1544 + "cfg-if", 1545 + "interpolate_name", 1546 + "itertools", 1547 + "libc", 1548 + "libfuzzer-sys", 1549 + "log", 1550 + "maybe-rayon", 1551 + "new_debug_unreachable", 1552 + "noop_proc_macro", 1553 + "num-derive", 1554 + "num-traits", 1555 + "paste", 1556 + "profiling", 1557 + "rand 0.9.2", 1558 + "rand_chacha 0.9.0", 1559 + "simd_helpers", 1560 + "thiserror 2.0.18", 1561 + "v_frame", 1562 + "wasm-bindgen", 1563 + ] 1564 + 1565 + [[package]] 1566 + name = "ravif" 1567 + version = "0.13.0" 1568 + source = "registry+https://github.com/rust-lang/crates.io-index" 1569 + checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" 1570 + dependencies = [ 1571 + "avif-serialize", 1572 + "imgref", 1573 + "loop9", 1574 + "quick-error", 1575 + "rav1e", 1576 + "rayon", 1577 + "rgb", 1578 + ] 1579 + 1580 + [[package]] 1581 + name = "rayon" 1582 + version = "1.11.0" 1583 + source = "registry+https://github.com/rust-lang/crates.io-index" 1584 + checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" 1585 + dependencies = [ 1586 + "either", 1587 + "rayon-core", 1588 + ] 1589 + 1590 + [[package]] 1591 + name = "rayon-core" 1592 + version = "1.13.0" 1593 + source = "registry+https://github.com/rust-lang/crates.io-index" 1594 + checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" 1595 + dependencies = [ 1596 + "crossbeam-deque", 1597 + "crossbeam-utils", 1598 + ] 1599 + 1600 + [[package]] 1601 + name = "redox_syscall" 1602 + version = "0.5.18" 1603 + source = "registry+https://github.com/rust-lang/crates.io-index" 1604 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 1605 + dependencies = [ 1606 + "bitflags", 1607 + ] 1608 + 1609 + [[package]] 1610 + name = "regex-automata" 1611 + version = "0.4.14" 1612 + source = "registry+https://github.com/rust-lang/crates.io-index" 1613 + checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" 1614 + dependencies = [ 1615 + "aho-corasick", 1616 + "memchr", 1617 + "regex-syntax", 1618 + ] 1619 + 1620 + [[package]] 1621 + name = "regex-syntax" 1622 + version = "0.8.10" 1623 + source = "registry+https://github.com/rust-lang/crates.io-index" 1624 + checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" 1625 + 1626 + [[package]] 1627 + name = "reqwest" 1628 + version = "0.12.28" 1629 + source = "registry+https://github.com/rust-lang/crates.io-index" 1630 + checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 1631 + dependencies = [ 1632 + "base64", 1633 + "bytes", 1634 + "encoding_rs", 1635 + "futures-core", 1636 + "h2", 1637 + "http", 1638 + "http-body", 1639 + "http-body-util", 1640 + "hyper", 1641 + "hyper-rustls", 1642 + "hyper-tls", 1643 + "hyper-util", 1644 + "js-sys", 1645 + "log", 1646 + "mime", 1647 + "native-tls", 1648 + "percent-encoding", 1649 + "pin-project-lite", 1650 + "rustls-pki-types", 1651 + "serde", 1652 + "serde_json", 1653 + "serde_urlencoded", 1654 + "sync_wrapper", 1655 + "tokio", 1656 + "tokio-native-tls", 1657 + "tower", 1658 + "tower-http", 1659 + "tower-service", 1660 + "url", 1661 + "wasm-bindgen", 1662 + "wasm-bindgen-futures", 1663 + "web-sys", 1664 + ] 1665 + 1666 + [[package]] 1667 + name = "rgb" 1668 + version = "0.8.53" 1669 + source = "registry+https://github.com/rust-lang/crates.io-index" 1670 + checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" 1671 + 1672 + [[package]] 1673 + name = "ring" 1674 + version = "0.17.14" 1675 + source = "registry+https://github.com/rust-lang/crates.io-index" 1676 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1677 + dependencies = [ 1678 + "cc", 1679 + "cfg-if", 1680 + "getrandom 0.2.17", 1681 + "libc", 1682 + "untrusted", 1683 + "windows-sys 0.52.0", 1684 + ] 1685 + 1686 + [[package]] 1687 + name = "rustix" 1688 + version = "1.1.4" 1689 + source = "registry+https://github.com/rust-lang/crates.io-index" 1690 + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 1691 + dependencies = [ 1692 + "bitflags", 1693 + "errno", 1694 + "libc", 1695 + "linux-raw-sys", 1696 + "windows-sys 0.61.2", 1697 + ] 1698 + 1699 + [[package]] 1700 + name = "rustls" 1701 + version = "0.23.37" 1702 + source = "registry+https://github.com/rust-lang/crates.io-index" 1703 + checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" 1704 + dependencies = [ 1705 + "once_cell", 1706 + "rustls-pki-types", 1707 + "rustls-webpki", 1708 + "subtle", 1709 + "zeroize", 1710 + ] 1711 + 1712 + [[package]] 1713 + name = "rustls-pki-types" 1714 + version = "1.14.0" 1715 + source = "registry+https://github.com/rust-lang/crates.io-index" 1716 + checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" 1717 + dependencies = [ 1718 + "zeroize", 1719 + ] 1720 + 1721 + [[package]] 1722 + name = "rustls-webpki" 1723 + version = "0.103.10" 1724 + source = "registry+https://github.com/rust-lang/crates.io-index" 1725 + checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" 1726 + dependencies = [ 1727 + "ring", 1728 + "rustls-pki-types", 1729 + "untrusted", 1730 + ] 1731 + 1732 + [[package]] 1733 + name = "rustversion" 1734 + version = "1.0.22" 1735 + source = "registry+https://github.com/rust-lang/crates.io-index" 1736 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1737 + 1738 + [[package]] 1739 + name = "ryu" 1740 + version = "1.0.23" 1741 + source = "registry+https://github.com/rust-lang/crates.io-index" 1742 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 1743 + 1744 + [[package]] 1745 + name = "schannel" 1746 + version = "0.1.29" 1747 + source = "registry+https://github.com/rust-lang/crates.io-index" 1748 + checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" 1749 + dependencies = [ 1750 + "windows-sys 0.61.2", 1751 + ] 1752 + 1753 + [[package]] 1754 + name = "scopeguard" 1755 + version = "1.2.0" 1756 + source = "registry+https://github.com/rust-lang/crates.io-index" 1757 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1758 + 1759 + [[package]] 1760 + name = "security-framework" 1761 + version = "3.7.0" 1762 + source = "registry+https://github.com/rust-lang/crates.io-index" 1763 + checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" 1764 + dependencies = [ 1765 + "bitflags", 1766 + "core-foundation 0.10.1", 1767 + "core-foundation-sys", 1768 + "libc", 1769 + "security-framework-sys", 1770 + ] 1771 + 1772 + [[package]] 1773 + name = "security-framework-sys" 1774 + version = "2.17.0" 1775 + source = "registry+https://github.com/rust-lang/crates.io-index" 1776 + checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" 1777 + dependencies = [ 1778 + "core-foundation-sys", 1779 + "libc", 1780 + ] 1781 + 1782 + [[package]] 1783 + name = "semver" 1784 + version = "1.0.27" 1785 + source = "registry+https://github.com/rust-lang/crates.io-index" 1786 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 1787 + 1788 + [[package]] 1789 + name = "serde" 1790 + version = "1.0.228" 1791 + source = "registry+https://github.com/rust-lang/crates.io-index" 1792 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 1793 + dependencies = [ 1794 + "serde_core", 1795 + "serde_derive", 1796 + ] 1797 + 1798 + [[package]] 1799 + name = "serde_core" 1800 + version = "1.0.228" 1801 + source = "registry+https://github.com/rust-lang/crates.io-index" 1802 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 1803 + dependencies = [ 1804 + "serde_derive", 1805 + ] 1806 + 1807 + [[package]] 1808 + name = "serde_derive" 1809 + version = "1.0.228" 1810 + source = "registry+https://github.com/rust-lang/crates.io-index" 1811 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 1812 + dependencies = [ 1813 + "proc-macro2", 1814 + "quote", 1815 + "syn", 1816 + ] 1817 + 1818 + [[package]] 1819 + name = "serde_json" 1820 + version = "1.0.149" 1821 + source = "registry+https://github.com/rust-lang/crates.io-index" 1822 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 1823 + dependencies = [ 1824 + "itoa", 1825 + "memchr", 1826 + "serde", 1827 + "serde_core", 1828 + "zmij", 1829 + ] 1830 + 1831 + [[package]] 1832 + name = "serde_urlencoded" 1833 + version = "0.7.1" 1834 + source = "registry+https://github.com/rust-lang/crates.io-index" 1835 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1836 + dependencies = [ 1837 + "form_urlencoded", 1838 + "itoa", 1839 + "ryu", 1840 + "serde", 1841 + ] 1842 + 1843 + [[package]] 1844 + name = "sha1" 1845 + version = "0.10.6" 1846 + source = "registry+https://github.com/rust-lang/crates.io-index" 1847 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1848 + dependencies = [ 1849 + "cfg-if", 1850 + "cpufeatures", 1851 + "digest", 1852 + ] 1853 + 1854 + [[package]] 1855 + name = "sharded-slab" 1856 + version = "0.1.7" 1857 + source = "registry+https://github.com/rust-lang/crates.io-index" 1858 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1859 + dependencies = [ 1860 + "lazy_static", 1861 + ] 1862 + 1863 + [[package]] 1864 + name = "shlex" 1865 + version = "1.3.0" 1866 + source = "registry+https://github.com/rust-lang/crates.io-index" 1867 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1868 + 1869 + [[package]] 1870 + name = "signal-hook-registry" 1871 + version = "1.4.8" 1872 + source = "registry+https://github.com/rust-lang/crates.io-index" 1873 + checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 1874 + dependencies = [ 1875 + "errno", 1876 + "libc", 1877 + ] 1878 + 1879 + [[package]] 1880 + name = "simd-adler32" 1881 + version = "0.3.9" 1882 + source = "registry+https://github.com/rust-lang/crates.io-index" 1883 + checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" 1884 + 1885 + [[package]] 1886 + name = "simd_helpers" 1887 + version = "0.1.0" 1888 + source = "registry+https://github.com/rust-lang/crates.io-index" 1889 + checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" 1890 + dependencies = [ 1891 + "quote", 1892 + ] 1893 + 1894 + [[package]] 1895 + name = "slab" 1896 + version = "0.4.12" 1897 + source = "registry+https://github.com/rust-lang/crates.io-index" 1898 + checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" 1899 + 1900 + [[package]] 1901 + name = "smallvec" 1902 + version = "1.15.1" 1903 + source = "registry+https://github.com/rust-lang/crates.io-index" 1904 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1905 + 1906 + [[package]] 1907 + name = "socket2" 1908 + version = "0.6.3" 1909 + source = "registry+https://github.com/rust-lang/crates.io-index" 1910 + checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" 1911 + dependencies = [ 1912 + "libc", 1913 + "windows-sys 0.61.2", 1914 + ] 1915 + 1916 + [[package]] 1917 + name = "stable_deref_trait" 1918 + version = "1.2.1" 1919 + source = "registry+https://github.com/rust-lang/crates.io-index" 1920 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1921 + 1922 + [[package]] 1923 + name = "subtle" 1924 + version = "2.6.1" 1925 + source = "registry+https://github.com/rust-lang/crates.io-index" 1926 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1927 + 1928 + [[package]] 1929 + name = "syn" 1930 + version = "2.0.117" 1931 + source = "registry+https://github.com/rust-lang/crates.io-index" 1932 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 1933 + dependencies = [ 1934 + "proc-macro2", 1935 + "quote", 1936 + "unicode-ident", 1937 + ] 1938 + 1939 + [[package]] 1940 + name = "sync_wrapper" 1941 + version = "1.0.2" 1942 + source = "registry+https://github.com/rust-lang/crates.io-index" 1943 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1944 + dependencies = [ 1945 + "futures-core", 1946 + ] 1947 + 1948 + [[package]] 1949 + name = "synstructure" 1950 + version = "0.13.2" 1951 + source = "registry+https://github.com/rust-lang/crates.io-index" 1952 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1953 + dependencies = [ 1954 + "proc-macro2", 1955 + "quote", 1956 + "syn", 1957 + ] 1958 + 1959 + [[package]] 1960 + name = "system-configuration" 1961 + version = "0.7.0" 1962 + source = "registry+https://github.com/rust-lang/crates.io-index" 1963 + checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" 1964 + dependencies = [ 1965 + "bitflags", 1966 + "core-foundation 0.9.4", 1967 + "system-configuration-sys", 1968 + ] 1969 + 1970 + [[package]] 1971 + name = "system-configuration-sys" 1972 + version = "0.6.0" 1973 + source = "registry+https://github.com/rust-lang/crates.io-index" 1974 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1975 + dependencies = [ 1976 + "core-foundation-sys", 1977 + "libc", 1978 + ] 1979 + 1980 + [[package]] 1981 + name = "tempfile" 1982 + version = "3.27.0" 1983 + source = "registry+https://github.com/rust-lang/crates.io-index" 1984 + checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" 1985 + dependencies = [ 1986 + "fastrand", 1987 + "getrandom 0.4.2", 1988 + "once_cell", 1989 + "rustix", 1990 + "windows-sys 0.61.2", 1991 + ] 1992 + 1993 + [[package]] 1994 + name = "thiserror" 1995 + version = "1.0.69" 1996 + source = "registry+https://github.com/rust-lang/crates.io-index" 1997 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1998 + dependencies = [ 1999 + "thiserror-impl 1.0.69", 2000 + ] 2001 + 2002 + [[package]] 2003 + name = "thiserror" 2004 + version = "2.0.18" 2005 + source = "registry+https://github.com/rust-lang/crates.io-index" 2006 + checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 2007 + dependencies = [ 2008 + "thiserror-impl 2.0.18", 2009 + ] 2010 + 2011 + [[package]] 2012 + name = "thiserror-impl" 2013 + version = "1.0.69" 2014 + source = "registry+https://github.com/rust-lang/crates.io-index" 2015 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2016 + dependencies = [ 2017 + "proc-macro2", 2018 + "quote", 2019 + "syn", 2020 + ] 2021 + 2022 + [[package]] 2023 + name = "thiserror-impl" 2024 + version = "2.0.18" 2025 + source = "registry+https://github.com/rust-lang/crates.io-index" 2026 + checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" 2027 + dependencies = [ 2028 + "proc-macro2", 2029 + "quote", 2030 + "syn", 2031 + ] 2032 + 2033 + [[package]] 2034 + name = "thread_local" 2035 + version = "1.1.9" 2036 + source = "registry+https://github.com/rust-lang/crates.io-index" 2037 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 2038 + dependencies = [ 2039 + "cfg-if", 2040 + ] 2041 + 2042 + [[package]] 2043 + name = "tiff" 2044 + version = "0.11.3" 2045 + source = "registry+https://github.com/rust-lang/crates.io-index" 2046 + checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" 2047 + dependencies = [ 2048 + "fax", 2049 + "flate2", 2050 + "half", 2051 + "quick-error", 2052 + "weezl", 2053 + "zune-jpeg", 2054 + ] 2055 + 2056 + [[package]] 2057 + name = "tinystr" 2058 + version = "0.8.3" 2059 + source = "registry+https://github.com/rust-lang/crates.io-index" 2060 + checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" 2061 + dependencies = [ 2062 + "displaydoc", 2063 + "zerovec", 2064 + ] 2065 + 2066 + [[package]] 2067 + name = "tokio" 2068 + version = "1.50.0" 2069 + source = "registry+https://github.com/rust-lang/crates.io-index" 2070 + checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" 2071 + dependencies = [ 2072 + "bytes", 2073 + "libc", 2074 + "mio", 2075 + "parking_lot", 2076 + "pin-project-lite", 2077 + "signal-hook-registry", 2078 + "socket2", 2079 + "tokio-macros", 2080 + "windows-sys 0.61.2", 2081 + ] 2082 + 2083 + [[package]] 2084 + name = "tokio-macros" 2085 + version = "2.6.1" 2086 + source = "registry+https://github.com/rust-lang/crates.io-index" 2087 + checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" 2088 + dependencies = [ 2089 + "proc-macro2", 2090 + "quote", 2091 + "syn", 2092 + ] 2093 + 2094 + [[package]] 2095 + name = "tokio-native-tls" 2096 + version = "0.3.1" 2097 + source = "registry+https://github.com/rust-lang/crates.io-index" 2098 + checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2099 + dependencies = [ 2100 + "native-tls", 2101 + "tokio", 2102 + ] 2103 + 2104 + [[package]] 2105 + name = "tokio-rustls" 2106 + version = "0.26.4" 2107 + source = "registry+https://github.com/rust-lang/crates.io-index" 2108 + checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 2109 + dependencies = [ 2110 + "rustls", 2111 + "tokio", 2112 + ] 2113 + 2114 + [[package]] 2115 + name = "tokio-tungstenite" 2116 + version = "0.24.0" 2117 + source = "registry+https://github.com/rust-lang/crates.io-index" 2118 + checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" 2119 + dependencies = [ 2120 + "futures-util", 2121 + "log", 2122 + "native-tls", 2123 + "tokio", 2124 + "tokio-native-tls", 2125 + "tungstenite", 2126 + ] 2127 + 2128 + [[package]] 2129 + name = "tokio-util" 2130 + version = "0.7.18" 2131 + source = "registry+https://github.com/rust-lang/crates.io-index" 2132 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 2133 + dependencies = [ 2134 + "bytes", 2135 + "futures-core", 2136 + "futures-sink", 2137 + "pin-project-lite", 2138 + "tokio", 2139 + ] 2140 + 2141 + [[package]] 2142 + name = "tower" 2143 + version = "0.5.3" 2144 + source = "registry+https://github.com/rust-lang/crates.io-index" 2145 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 2146 + dependencies = [ 2147 + "futures-core", 2148 + "futures-util", 2149 + "pin-project-lite", 2150 + "sync_wrapper", 2151 + "tokio", 2152 + "tower-layer", 2153 + "tower-service", 2154 + ] 2155 + 2156 + [[package]] 2157 + name = "tower-http" 2158 + version = "0.6.8" 2159 + source = "registry+https://github.com/rust-lang/crates.io-index" 2160 + checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 2161 + dependencies = [ 2162 + "bitflags", 2163 + "bytes", 2164 + "futures-util", 2165 + "http", 2166 + "http-body", 2167 + "iri-string", 2168 + "pin-project-lite", 2169 + "tower", 2170 + "tower-layer", 2171 + "tower-service", 2172 + ] 2173 + 2174 + [[package]] 2175 + name = "tower-layer" 2176 + version = "0.3.3" 2177 + source = "registry+https://github.com/rust-lang/crates.io-index" 2178 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2179 + 2180 + [[package]] 2181 + name = "tower-service" 2182 + version = "0.3.3" 2183 + source = "registry+https://github.com/rust-lang/crates.io-index" 2184 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2185 + 2186 + [[package]] 2187 + name = "tracing" 2188 + version = "0.1.44" 2189 + source = "registry+https://github.com/rust-lang/crates.io-index" 2190 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 2191 + dependencies = [ 2192 + "pin-project-lite", 2193 + "tracing-attributes", 2194 + "tracing-core", 2195 + ] 2196 + 2197 + [[package]] 2198 + name = "tracing-attributes" 2199 + version = "0.1.31" 2200 + source = "registry+https://github.com/rust-lang/crates.io-index" 2201 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 2202 + dependencies = [ 2203 + "proc-macro2", 2204 + "quote", 2205 + "syn", 2206 + ] 2207 + 2208 + [[package]] 2209 + name = "tracing-core" 2210 + version = "0.1.36" 2211 + source = "registry+https://github.com/rust-lang/crates.io-index" 2212 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 2213 + dependencies = [ 2214 + "once_cell", 2215 + "valuable", 2216 + ] 2217 + 2218 + [[package]] 2219 + name = "tracing-log" 2220 + version = "0.2.0" 2221 + source = "registry+https://github.com/rust-lang/crates.io-index" 2222 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 2223 + dependencies = [ 2224 + "log", 2225 + "once_cell", 2226 + "tracing-core", 2227 + ] 2228 + 2229 + [[package]] 2230 + name = "tracing-serde" 2231 + version = "0.2.0" 2232 + source = "registry+https://github.com/rust-lang/crates.io-index" 2233 + checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" 2234 + dependencies = [ 2235 + "serde", 2236 + "tracing-core", 2237 + ] 2238 + 2239 + [[package]] 2240 + name = "tracing-subscriber" 2241 + version = "0.3.23" 2242 + source = "registry+https://github.com/rust-lang/crates.io-index" 2243 + checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" 2244 + dependencies = [ 2245 + "matchers", 2246 + "nu-ansi-term", 2247 + "once_cell", 2248 + "regex-automata", 2249 + "serde", 2250 + "serde_json", 2251 + "sharded-slab", 2252 + "smallvec", 2253 + "thread_local", 2254 + "tracing", 2255 + "tracing-core", 2256 + "tracing-log", 2257 + "tracing-serde", 2258 + ] 2259 + 2260 + [[package]] 2261 + name = "try-lock" 2262 + version = "0.2.5" 2263 + source = "registry+https://github.com/rust-lang/crates.io-index" 2264 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2265 + 2266 + [[package]] 2267 + name = "tungstenite" 2268 + version = "0.24.0" 2269 + source = "registry+https://github.com/rust-lang/crates.io-index" 2270 + checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" 2271 + dependencies = [ 2272 + "byteorder", 2273 + "bytes", 2274 + "data-encoding", 2275 + "http", 2276 + "httparse", 2277 + "log", 2278 + "native-tls", 2279 + "rand 0.8.5", 2280 + "sha1", 2281 + "thiserror 1.0.69", 2282 + "utf-8", 2283 + ] 2284 + 2285 + [[package]] 2286 + name = "typenum" 2287 + version = "1.19.0" 2288 + source = "registry+https://github.com/rust-lang/crates.io-index" 2289 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 2290 + 2291 + [[package]] 2292 + name = "unicode-ident" 2293 + version = "1.0.24" 2294 + source = "registry+https://github.com/rust-lang/crates.io-index" 2295 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 2296 + 2297 + [[package]] 2298 + name = "unicode-xid" 2299 + version = "0.2.6" 2300 + source = "registry+https://github.com/rust-lang/crates.io-index" 2301 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 2302 + 2303 + [[package]] 2304 + name = "untrusted" 2305 + version = "0.9.0" 2306 + source = "registry+https://github.com/rust-lang/crates.io-index" 2307 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2308 + 2309 + [[package]] 2310 + name = "url" 2311 + version = "2.5.8" 2312 + source = "registry+https://github.com/rust-lang/crates.io-index" 2313 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 2314 + dependencies = [ 2315 + "form_urlencoded", 2316 + "idna", 2317 + "percent-encoding", 2318 + "serde", 2319 + ] 2320 + 2321 + [[package]] 2322 + name = "urlencoding" 2323 + version = "2.1.3" 2324 + source = "registry+https://github.com/rust-lang/crates.io-index" 2325 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 2326 + 2327 + [[package]] 2328 + name = "utf-8" 2329 + version = "0.7.6" 2330 + source = "registry+https://github.com/rust-lang/crates.io-index" 2331 + checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2332 + 2333 + [[package]] 2334 + name = "utf8_iter" 2335 + version = "1.0.4" 2336 + source = "registry+https://github.com/rust-lang/crates.io-index" 2337 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2338 + 2339 + [[package]] 2340 + name = "v_frame" 2341 + version = "0.3.9" 2342 + source = "registry+https://github.com/rust-lang/crates.io-index" 2343 + checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" 2344 + dependencies = [ 2345 + "aligned-vec", 2346 + "num-traits", 2347 + "wasm-bindgen", 2348 + ] 2349 + 2350 + [[package]] 2351 + name = "valuable" 2352 + version = "0.1.1" 2353 + source = "registry+https://github.com/rust-lang/crates.io-index" 2354 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2355 + 2356 + [[package]] 2357 + name = "vcpkg" 2358 + version = "0.2.15" 2359 + source = "registry+https://github.com/rust-lang/crates.io-index" 2360 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2361 + 2362 + [[package]] 2363 + name = "version_check" 2364 + version = "0.9.5" 2365 + source = "registry+https://github.com/rust-lang/crates.io-index" 2366 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2367 + 2368 + [[package]] 2369 + name = "want" 2370 + version = "0.3.1" 2371 + source = "registry+https://github.com/rust-lang/crates.io-index" 2372 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2373 + dependencies = [ 2374 + "try-lock", 2375 + ] 2376 + 2377 + [[package]] 2378 + name = "wasi" 2379 + version = "0.11.1+wasi-snapshot-preview1" 2380 + source = "registry+https://github.com/rust-lang/crates.io-index" 2381 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 2382 + 2383 + [[package]] 2384 + name = "wasip2" 2385 + version = "1.0.2+wasi-0.2.9" 2386 + source = "registry+https://github.com/rust-lang/crates.io-index" 2387 + checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" 2388 + dependencies = [ 2389 + "wit-bindgen", 2390 + ] 2391 + 2392 + [[package]] 2393 + name = "wasip3" 2394 + version = "0.4.0+wasi-0.3.0-rc-2026-01-06" 2395 + source = "registry+https://github.com/rust-lang/crates.io-index" 2396 + checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" 2397 + dependencies = [ 2398 + "wit-bindgen", 2399 + ] 2400 + 2401 + [[package]] 2402 + name = "wasm-bindgen" 2403 + version = "0.2.117" 2404 + source = "registry+https://github.com/rust-lang/crates.io-index" 2405 + checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" 2406 + dependencies = [ 2407 + "cfg-if", 2408 + "once_cell", 2409 + "rustversion", 2410 + "wasm-bindgen-macro", 2411 + "wasm-bindgen-shared", 2412 + ] 2413 + 2414 + [[package]] 2415 + name = "wasm-bindgen-futures" 2416 + version = "0.4.67" 2417 + source = "registry+https://github.com/rust-lang/crates.io-index" 2418 + checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" 2419 + dependencies = [ 2420 + "js-sys", 2421 + "wasm-bindgen", 2422 + ] 2423 + 2424 + [[package]] 2425 + name = "wasm-bindgen-macro" 2426 + version = "0.2.117" 2427 + source = "registry+https://github.com/rust-lang/crates.io-index" 2428 + checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" 2429 + dependencies = [ 2430 + "quote", 2431 + "wasm-bindgen-macro-support", 2432 + ] 2433 + 2434 + [[package]] 2435 + name = "wasm-bindgen-macro-support" 2436 + version = "0.2.117" 2437 + source = "registry+https://github.com/rust-lang/crates.io-index" 2438 + checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" 2439 + dependencies = [ 2440 + "bumpalo", 2441 + "proc-macro2", 2442 + "quote", 2443 + "syn", 2444 + "wasm-bindgen-shared", 2445 + ] 2446 + 2447 + [[package]] 2448 + name = "wasm-bindgen-shared" 2449 + version = "0.2.117" 2450 + source = "registry+https://github.com/rust-lang/crates.io-index" 2451 + checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" 2452 + dependencies = [ 2453 + "unicode-ident", 2454 + ] 2455 + 2456 + [[package]] 2457 + name = "wasm-encoder" 2458 + version = "0.244.0" 2459 + source = "registry+https://github.com/rust-lang/crates.io-index" 2460 + checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" 2461 + dependencies = [ 2462 + "leb128fmt", 2463 + "wasmparser", 2464 + ] 2465 + 2466 + [[package]] 2467 + name = "wasm-metadata" 2468 + version = "0.244.0" 2469 + source = "registry+https://github.com/rust-lang/crates.io-index" 2470 + checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" 2471 + dependencies = [ 2472 + "anyhow", 2473 + "indexmap", 2474 + "wasm-encoder", 2475 + "wasmparser", 2476 + ] 2477 + 2478 + [[package]] 2479 + name = "wasmparser" 2480 + version = "0.244.0" 2481 + source = "registry+https://github.com/rust-lang/crates.io-index" 2482 + checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" 2483 + dependencies = [ 2484 + "bitflags", 2485 + "hashbrown 0.15.5", 2486 + "indexmap", 2487 + "semver", 2488 + ] 2489 + 2490 + [[package]] 2491 + name = "web-sys" 2492 + version = "0.3.94" 2493 + source = "registry+https://github.com/rust-lang/crates.io-index" 2494 + checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" 2495 + dependencies = [ 2496 + "js-sys", 2497 + "wasm-bindgen", 2498 + ] 2499 + 2500 + [[package]] 2501 + name = "weezl" 2502 + version = "0.1.12" 2503 + source = "registry+https://github.com/rust-lang/crates.io-index" 2504 + checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" 2505 + 2506 + [[package]] 2507 + name = "windows-link" 2508 + version = "0.2.1" 2509 + source = "registry+https://github.com/rust-lang/crates.io-index" 2510 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 2511 + 2512 + [[package]] 2513 + name = "windows-registry" 2514 + version = "0.6.1" 2515 + source = "registry+https://github.com/rust-lang/crates.io-index" 2516 + checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 2517 + dependencies = [ 2518 + "windows-link", 2519 + "windows-result", 2520 + "windows-strings", 2521 + ] 2522 + 2523 + [[package]] 2524 + name = "windows-result" 2525 + version = "0.4.1" 2526 + source = "registry+https://github.com/rust-lang/crates.io-index" 2527 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 2528 + dependencies = [ 2529 + "windows-link", 2530 + ] 2531 + 2532 + [[package]] 2533 + name = "windows-strings" 2534 + version = "0.5.1" 2535 + source = "registry+https://github.com/rust-lang/crates.io-index" 2536 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 2537 + dependencies = [ 2538 + "windows-link", 2539 + ] 2540 + 2541 + [[package]] 2542 + name = "windows-sys" 2543 + version = "0.52.0" 2544 + source = "registry+https://github.com/rust-lang/crates.io-index" 2545 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2546 + dependencies = [ 2547 + "windows-targets", 2548 + ] 2549 + 2550 + [[package]] 2551 + name = "windows-sys" 2552 + version = "0.61.2" 2553 + source = "registry+https://github.com/rust-lang/crates.io-index" 2554 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 2555 + dependencies = [ 2556 + "windows-link", 2557 + ] 2558 + 2559 + [[package]] 2560 + name = "windows-targets" 2561 + version = "0.52.6" 2562 + source = "registry+https://github.com/rust-lang/crates.io-index" 2563 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2564 + dependencies = [ 2565 + "windows_aarch64_gnullvm", 2566 + "windows_aarch64_msvc", 2567 + "windows_i686_gnu", 2568 + "windows_i686_gnullvm", 2569 + "windows_i686_msvc", 2570 + "windows_x86_64_gnu", 2571 + "windows_x86_64_gnullvm", 2572 + "windows_x86_64_msvc", 2573 + ] 2574 + 2575 + [[package]] 2576 + name = "windows_aarch64_gnullvm" 2577 + version = "0.52.6" 2578 + source = "registry+https://github.com/rust-lang/crates.io-index" 2579 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2580 + 2581 + [[package]] 2582 + name = "windows_aarch64_msvc" 2583 + version = "0.52.6" 2584 + source = "registry+https://github.com/rust-lang/crates.io-index" 2585 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2586 + 2587 + [[package]] 2588 + name = "windows_i686_gnu" 2589 + version = "0.52.6" 2590 + source = "registry+https://github.com/rust-lang/crates.io-index" 2591 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2592 + 2593 + [[package]] 2594 + name = "windows_i686_gnullvm" 2595 + version = "0.52.6" 2596 + source = "registry+https://github.com/rust-lang/crates.io-index" 2597 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2598 + 2599 + [[package]] 2600 + name = "windows_i686_msvc" 2601 + version = "0.52.6" 2602 + source = "registry+https://github.com/rust-lang/crates.io-index" 2603 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2604 + 2605 + [[package]] 2606 + name = "windows_x86_64_gnu" 2607 + version = "0.52.6" 2608 + source = "registry+https://github.com/rust-lang/crates.io-index" 2609 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2610 + 2611 + [[package]] 2612 + name = "windows_x86_64_gnullvm" 2613 + version = "0.52.6" 2614 + source = "registry+https://github.com/rust-lang/crates.io-index" 2615 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2616 + 2617 + [[package]] 2618 + name = "windows_x86_64_msvc" 2619 + version = "0.52.6" 2620 + source = "registry+https://github.com/rust-lang/crates.io-index" 2621 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2622 + 2623 + [[package]] 2624 + name = "wit-bindgen" 2625 + version = "0.51.0" 2626 + source = "registry+https://github.com/rust-lang/crates.io-index" 2627 + checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 2628 + dependencies = [ 2629 + "wit-bindgen-rust-macro", 2630 + ] 2631 + 2632 + [[package]] 2633 + name = "wit-bindgen-core" 2634 + version = "0.51.0" 2635 + source = "registry+https://github.com/rust-lang/crates.io-index" 2636 + checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" 2637 + dependencies = [ 2638 + "anyhow", 2639 + "heck", 2640 + "wit-parser", 2641 + ] 2642 + 2643 + [[package]] 2644 + name = "wit-bindgen-rust" 2645 + version = "0.51.0" 2646 + source = "registry+https://github.com/rust-lang/crates.io-index" 2647 + checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" 2648 + dependencies = [ 2649 + "anyhow", 2650 + "heck", 2651 + "indexmap", 2652 + "prettyplease", 2653 + "syn", 2654 + "wasm-metadata", 2655 + "wit-bindgen-core", 2656 + "wit-component", 2657 + ] 2658 + 2659 + [[package]] 2660 + name = "wit-bindgen-rust-macro" 2661 + version = "0.51.0" 2662 + source = "registry+https://github.com/rust-lang/crates.io-index" 2663 + checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" 2664 + dependencies = [ 2665 + "anyhow", 2666 + "prettyplease", 2667 + "proc-macro2", 2668 + "quote", 2669 + "syn", 2670 + "wit-bindgen-core", 2671 + "wit-bindgen-rust", 2672 + ] 2673 + 2674 + [[package]] 2675 + name = "wit-component" 2676 + version = "0.244.0" 2677 + source = "registry+https://github.com/rust-lang/crates.io-index" 2678 + checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" 2679 + dependencies = [ 2680 + "anyhow", 2681 + "bitflags", 2682 + "indexmap", 2683 + "log", 2684 + "serde", 2685 + "serde_derive", 2686 + "serde_json", 2687 + "wasm-encoder", 2688 + "wasm-metadata", 2689 + "wasmparser", 2690 + "wit-parser", 2691 + ] 2692 + 2693 + [[package]] 2694 + name = "wit-parser" 2695 + version = "0.244.0" 2696 + source = "registry+https://github.com/rust-lang/crates.io-index" 2697 + checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" 2698 + dependencies = [ 2699 + "anyhow", 2700 + "id-arena", 2701 + "indexmap", 2702 + "log", 2703 + "semver", 2704 + "serde", 2705 + "serde_derive", 2706 + "serde_json", 2707 + "unicode-xid", 2708 + "wasmparser", 2709 + ] 2710 + 2711 + [[package]] 2712 + name = "writeable" 2713 + version = "0.6.2" 2714 + source = "registry+https://github.com/rust-lang/crates.io-index" 2715 + checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 2716 + 2717 + [[package]] 2718 + name = "y4m" 2719 + version = "0.8.0" 2720 + source = "registry+https://github.com/rust-lang/crates.io-index" 2721 + checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" 2722 + 2723 + [[package]] 2724 + name = "yoke" 2725 + version = "0.8.2" 2726 + source = "registry+https://github.com/rust-lang/crates.io-index" 2727 + checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" 2728 + dependencies = [ 2729 + "stable_deref_trait", 2730 + "yoke-derive", 2731 + "zerofrom", 2732 + ] 2733 + 2734 + [[package]] 2735 + name = "yoke-derive" 2736 + version = "0.8.2" 2737 + source = "registry+https://github.com/rust-lang/crates.io-index" 2738 + checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" 2739 + dependencies = [ 2740 + "proc-macro2", 2741 + "quote", 2742 + "syn", 2743 + "synstructure", 2744 + ] 2745 + 2746 + [[package]] 2747 + name = "zerocopy" 2748 + version = "0.8.48" 2749 + source = "registry+https://github.com/rust-lang/crates.io-index" 2750 + checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" 2751 + dependencies = [ 2752 + "zerocopy-derive", 2753 + ] 2754 + 2755 + [[package]] 2756 + name = "zerocopy-derive" 2757 + version = "0.8.48" 2758 + source = "registry+https://github.com/rust-lang/crates.io-index" 2759 + checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" 2760 + dependencies = [ 2761 + "proc-macro2", 2762 + "quote", 2763 + "syn", 2764 + ] 2765 + 2766 + [[package]] 2767 + name = "zerofrom" 2768 + version = "0.1.7" 2769 + source = "registry+https://github.com/rust-lang/crates.io-index" 2770 + checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" 2771 + dependencies = [ 2772 + "zerofrom-derive", 2773 + ] 2774 + 2775 + [[package]] 2776 + name = "zerofrom-derive" 2777 + version = "0.1.7" 2778 + source = "registry+https://github.com/rust-lang/crates.io-index" 2779 + checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" 2780 + dependencies = [ 2781 + "proc-macro2", 2782 + "quote", 2783 + "syn", 2784 + "synstructure", 2785 + ] 2786 + 2787 + [[package]] 2788 + name = "zeroize" 2789 + version = "1.8.2" 2790 + source = "registry+https://github.com/rust-lang/crates.io-index" 2791 + checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 2792 + 2793 + [[package]] 2794 + name = "zerotrie" 2795 + version = "0.2.4" 2796 + source = "registry+https://github.com/rust-lang/crates.io-index" 2797 + checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" 2798 + dependencies = [ 2799 + "displaydoc", 2800 + "yoke", 2801 + "zerofrom", 2802 + ] 2803 + 2804 + [[package]] 2805 + name = "zerovec" 2806 + version = "0.11.6" 2807 + source = "registry+https://github.com/rust-lang/crates.io-index" 2808 + checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" 2809 + dependencies = [ 2810 + "yoke", 2811 + "zerofrom", 2812 + "zerovec-derive", 2813 + ] 2814 + 2815 + [[package]] 2816 + name = "zerovec-derive" 2817 + version = "0.11.3" 2818 + source = "registry+https://github.com/rust-lang/crates.io-index" 2819 + checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" 2820 + dependencies = [ 2821 + "proc-macro2", 2822 + "quote", 2823 + "syn", 2824 + ] 2825 + 2826 + [[package]] 2827 + name = "zmij" 2828 + version = "1.0.21" 2829 + source = "registry+https://github.com/rust-lang/crates.io-index" 2830 + checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" 2831 + 2832 + [[package]] 2833 + name = "zune-core" 2834 + version = "0.5.1" 2835 + source = "registry+https://github.com/rust-lang/crates.io-index" 2836 + checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" 2837 + 2838 + [[package]] 2839 + name = "zune-inflate" 2840 + version = "0.2.54" 2841 + source = "registry+https://github.com/rust-lang/crates.io-index" 2842 + checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" 2843 + dependencies = [ 2844 + "simd-adler32", 2845 + ] 2846 + 2847 + [[package]] 2848 + name = "zune-jpeg" 2849 + version = "0.5.15" 2850 + source = "registry+https://github.com/rust-lang/crates.io-index" 2851 + checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" 2852 + dependencies = [ 2853 + "zune-core", 2854 + ]
+20
Cargo.toml
··· 1 + [package] 2 + name = "honkbot" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + tokio = { version = "1", features = ["full"] } 8 + tokio-tungstenite = { version = "0.24", features = ["native-tls"] } 9 + futures-util = "0.3" 10 + reqwest = { version = "0.12", features = ["json"] } 11 + serde = { version = "1", features = ["derive"] } 12 + serde_json = "1" 13 + tracing = "0.1" 14 + tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } 15 + rand = "0.8" 16 + image = "0.25" 17 + dotenvy = "0.15" 18 + url = "2" 19 + anyhow = "1" 20 + urlencoding = "2"
+27
Dockerfile
··· 1 + FROM rust:1-bookworm AS builder 2 + 3 + WORKDIR /app 4 + 5 + # Cache dependency builds 6 + COPY Cargo.toml Cargo.lock ./ 7 + RUN mkdir src && echo 'fn main() {}' > src/main.rs && cargo build --release && rm -rf src 8 + 9 + # Build the real binary 10 + COPY src/ src/ 11 + RUN touch src/main.rs && cargo build --release 12 + 13 + # --- Runtime stage --- 14 + FROM debian:bookworm-slim 15 + 16 + RUN apt-get update && \ 17 + apt-get install -y --no-install-recommends ca-certificates && \ 18 + rm -rf /var/lib/apt/lists/* 19 + 20 + COPY --from=builder /app/target/release/honkbot /usr/local/bin/honkbot 21 + COPY fallback_images/ /app/fallback_images/ 22 + 23 + WORKDIR /app 24 + ENV FALLBACK_IMAGE_DIR=/app/fallback_images 25 + ENV RUST_LOG=info 26 + 27 + ENTRYPOINT ["honkbot"]
+63
README.md
··· 1 + # Honkbot (Rust) 2 + 3 + A single Bluesky bot that listens to the full firehose via [Jetstream](https://github.com/bluesky-social/jetstream) and replies to animal commands with random Flickr images. 4 + 5 + Rewrite of [bskybots/honkbot](https://github.com/QuietImCoding/bskybots/tree/main/honkbot) in Rust for reliability and performance. 6 + 7 + ## Commands 8 + 9 + | Command | Animal | Reply Text | 10 + |-----------|-----------|------------| 11 + | `/honk` | Goose | HONK | 12 + | `/awoo` | Wolf | AWOO | 13 + | `/baaa` | Goat | BAAA | 14 + | `/bork` | Dog/Puppy | BORK | 15 + | `/hiss` | Snake | HISS | 16 + | `/hoot` | Owl | HOOT | 17 + | `/meehh` | Sheep | MEEHH | 18 + | `/meow` | Cat | MEOW | 19 + | `/mumble` | Marmot | MUMBLE | 20 + | `/oink` | Pig | OINK | 21 + | `/ribbit` | Frog | RIBBIT | 22 + | `/squee` | Capybara | SQUEE | 23 + | `/yowl` | Bobcat | YOWL | 24 + 25 + ## Setup 26 + 27 + 1. Copy `.env.example` to `.env` and fill in your credentials: 28 + - `BSKY_HANDLE` — your bot's Bluesky handle 29 + - `BSKY_PASSWORD` — an app password for the bot account 30 + - `FLICKR_KEY` — a Flickr API key ([get one here](https://www.flickr.com/services/api/misc.api_keys.html)) 31 + 32 + 2. Seed fallback images (used when Flickr is down or rate-limited): 33 + ```bash 34 + ./seed_fallback_images.sh 35 + ``` 36 + This downloads ~12 images per category into `fallback_images/`. You can also manually add your own `.jpg`/`.png`/`.webp` files. 37 + 38 + 3. Build and run: 39 + ```bash 40 + cargo build --release 41 + ./target/release/honkbot 42 + ``` 43 + 44 + ## Features 45 + 46 + - **Single binary** handles all 13 animal commands 47 + - **Jetstream WebSocket** for efficient firehose consumption (only receives `app.bsky.feed.post` events) 48 + - **Automatic reconnection** with cursor tracking on disconnect 49 + - **Bounded concurrency** (10 concurrent reply tasks) to avoid flooding APIs 50 + - **Session refresh** with automatic retry on auth failures 51 + - **Flickr fallback images** — if Flickr is down or rate-limited, picks a random image from `fallback_images/<command>/` 52 + - **Image resizing** for images over 1MB (Bluesky blob limit) 53 + - **Structured logging** via `tracing` (set `RUST_LOG=debug` for verbose output) 54 + 55 + ## Architecture 56 + 57 + ``` 58 + Jetstream (WSS) → match post text against triggers → spawn bounded task: 59 + 1. Search Flickr for random animal image 60 + 2. Download & resize if needed 61 + 3. Upload blob to Bluesky 62 + 4. Create reply post with image 63 + ```
+21
docker-compose.prod.yml
··· 1 + services: 2 + honkbot: 3 + image: atcr.io/goose.art/honkbot:latest 4 + restart: unless-stopped 5 + env_file: .env 6 + environment: 7 + - RUST_LOG=info 8 + - FALLBACK_IMAGE_DIR=/app/fallback_images 9 + 10 + watchtower: 11 + image: containrrr/watchtower 12 + restart: unless-stopped 13 + volumes: 14 + - /var/run/docker.sock:/var/run/docker.sock 15 + environment: 16 + # Check for new images every 60 seconds 17 + - WATCHTOWER_POLL_INTERVAL=60 18 + # Only update containers in this compose project 19 + - WATCHTOWER_SCOPE=honkbot 20 + labels: 21 + - "com.centurylinklabs.watchtower.scope=honkbot"
+8
docker-compose.yml
··· 1 + services: 2 + honkbot: 3 + build: . 4 + restart: unless-stopped 5 + env_file: .env 6 + environment: 7 + - RUST_LOG=info 8 + - FALLBACK_IMAGE_DIR=/app/fallback_images
fallback_images/awoo/.gitkeep

This is a binary file and will not be displayed.

fallback_images/awoo/1.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/10.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/11.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/12.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/2.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/3.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/4.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/5.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/6.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/7.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/8.jpg

This is a binary file and will not be displayed.

fallback_images/awoo/9.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/.gitkeep

This is a binary file and will not be displayed.

fallback_images/baaa/1.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/10.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/11.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/12.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/2.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/3.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/4.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/5.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/6.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/7.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/8.jpg

This is a binary file and will not be displayed.

fallback_images/baaa/9.jpg

This is a binary file and will not be displayed.

fallback_images/bork/.gitkeep

This is a binary file and will not be displayed.

fallback_images/bork/1.jpg

This is a binary file and will not be displayed.

fallback_images/bork/10.jpg

This is a binary file and will not be displayed.

fallback_images/bork/11.jpg

This is a binary file and will not be displayed.

fallback_images/bork/12.jpg

This is a binary file and will not be displayed.

fallback_images/bork/2.jpg

This is a binary file and will not be displayed.

fallback_images/bork/3.jpg

This is a binary file and will not be displayed.

fallback_images/bork/4.jpg

This is a binary file and will not be displayed.

fallback_images/bork/5.jpg

This is a binary file and will not be displayed.

fallback_images/bork/6.jpg

This is a binary file and will not be displayed.

fallback_images/bork/7.jpg

This is a binary file and will not be displayed.

fallback_images/bork/8.jpg

This is a binary file and will not be displayed.

fallback_images/bork/9.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/.gitkeep

This is a binary file and will not be displayed.

fallback_images/hiss/1.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/10.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/11.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/12.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/2.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/3.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/4.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/5.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/6.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/7.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/8.jpg

This is a binary file and will not be displayed.

fallback_images/hiss/9.jpg

This is a binary file and will not be displayed.

fallback_images/honk/.gitkeep

This is a binary file and will not be displayed.

fallback_images/honk/1.jpg

This is a binary file and will not be displayed.

fallback_images/honk/10.jpg

This is a binary file and will not be displayed.

fallback_images/honk/11.jpg

This is a binary file and will not be displayed.

fallback_images/honk/12.jpg

This is a binary file and will not be displayed.

fallback_images/honk/2.jpg

This is a binary file and will not be displayed.

fallback_images/honk/3.jpg

This is a binary file and will not be displayed.

fallback_images/honk/4.jpg

This is a binary file and will not be displayed.

fallback_images/honk/5.jpg

This is a binary file and will not be displayed.

fallback_images/honk/6.jpg

This is a binary file and will not be displayed.

fallback_images/honk/7.jpg

This is a binary file and will not be displayed.

fallback_images/honk/8.jpg

This is a binary file and will not be displayed.

fallback_images/honk/9.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/.gitkeep

This is a binary file and will not be displayed.

fallback_images/hoot/1.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/10.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/11.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/12.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/2.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/3.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/4.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/5.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/6.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/7.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/8.jpg

This is a binary file and will not be displayed.

fallback_images/hoot/9.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/.gitkeep

This is a binary file and will not be displayed.

fallback_images/meehh/1.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/10.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/11.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/12.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/2.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/3.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/4.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/5.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/6.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/7.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/8.jpg

This is a binary file and will not be displayed.

fallback_images/meehh/9.jpg

This is a binary file and will not be displayed.

fallback_images/meow/.gitkeep

This is a binary file and will not be displayed.

fallback_images/meow/1.jpg

This is a binary file and will not be displayed.

fallback_images/meow/10.jpg

This is a binary file and will not be displayed.

fallback_images/meow/11.jpg

This is a binary file and will not be displayed.

fallback_images/meow/12.jpg

This is a binary file and will not be displayed.

fallback_images/meow/2.jpg

This is a binary file and will not be displayed.

fallback_images/meow/3.jpg

This is a binary file and will not be displayed.

fallback_images/meow/4.jpg

This is a binary file and will not be displayed.

fallback_images/meow/5.jpg

This is a binary file and will not be displayed.

fallback_images/meow/6.jpg

This is a binary file and will not be displayed.

fallback_images/meow/7.jpg

This is a binary file and will not be displayed.

fallback_images/meow/8.jpg

This is a binary file and will not be displayed.

fallback_images/meow/9.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/.gitkeep

This is a binary file and will not be displayed.

fallback_images/mumble/1.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/10.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/11.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/12.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/2.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/3.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/4.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/5.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/6.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/7.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/8.jpg

This is a binary file and will not be displayed.

fallback_images/mumble/9.jpg

This is a binary file and will not be displayed.

fallback_images/oink/.gitkeep

This is a binary file and will not be displayed.

fallback_images/oink/1.jpg

This is a binary file and will not be displayed.

fallback_images/oink/10.jpg

This is a binary file and will not be displayed.

fallback_images/oink/11.jpg

This is a binary file and will not be displayed.

fallback_images/oink/12.jpg

This is a binary file and will not be displayed.

fallback_images/oink/2.jpg

This is a binary file and will not be displayed.

fallback_images/oink/3.jpg

This is a binary file and will not be displayed.

fallback_images/oink/4.jpg

This is a binary file and will not be displayed.

fallback_images/oink/5.jpg

This is a binary file and will not be displayed.

fallback_images/oink/6.jpg

This is a binary file and will not be displayed.

fallback_images/oink/7.jpg

This is a binary file and will not be displayed.

fallback_images/oink/8.jpg

This is a binary file and will not be displayed.

fallback_images/oink/9.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/.gitkeep

This is a binary file and will not be displayed.

fallback_images/ribbit/1.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/10.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/11.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/12.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/2.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/3.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/4.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/5.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/6.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/7.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/8.jpg

This is a binary file and will not be displayed.

fallback_images/ribbit/9.jpg

This is a binary file and will not be displayed.

fallback_images/squee/.gitkeep

This is a binary file and will not be displayed.

fallback_images/squee/1.jpg

This is a binary file and will not be displayed.

fallback_images/squee/10.jpg

This is a binary file and will not be displayed.

fallback_images/squee/11.jpg

This is a binary file and will not be displayed.

fallback_images/squee/12.jpg

This is a binary file and will not be displayed.

fallback_images/squee/2.jpg

This is a binary file and will not be displayed.

fallback_images/squee/3.jpg

This is a binary file and will not be displayed.

fallback_images/squee/4.jpg

This is a binary file and will not be displayed.

fallback_images/squee/5.jpg

This is a binary file and will not be displayed.

fallback_images/squee/6.jpg

This is a binary file and will not be displayed.

fallback_images/squee/7.jpg

This is a binary file and will not be displayed.

fallback_images/squee/8.jpg

This is a binary file and will not be displayed.

fallback_images/squee/9.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/.gitkeep

This is a binary file and will not be displayed.

fallback_images/yowl/1.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/10.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/11.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/12.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/2.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/3.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/4.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/5.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/6.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/7.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/8.jpg

This is a binary file and will not be displayed.

fallback_images/yowl/9.jpg

This is a binary file and will not be displayed.

+134
seed_fallback_images.sh
··· 1 + #!/usr/bin/env bash 2 + # 3 + # Downloads 12 fallback images per animal category from Flickr. 4 + # Requires FLICKR_KEY env var (or .env file with it). 5 + # 6 + # Usage: 7 + # export FLICKR_KEY=your_key 8 + # ./seed_fallback_images.sh 9 + # 10 + # Images are saved to fallback_images/<category>/<n>.jpg 11 + # Images over 1MB are resized down (requires ImageMagick's mogrify). 12 + 13 + set -euo pipefail 14 + 15 + if [ -f .env ]; then 16 + source .env 17 + fi 18 + 19 + if [ -z "${FLICKR_KEY:-}" ]; then 20 + echo "ERROR: FLICKR_KEY env var is required" 21 + echo " export FLICKR_KEY=your_flickr_api_key" 22 + exit 1 23 + fi 24 + 25 + IMAGES_PER_CATEGORY=12 26 + BASE_DIR="fallback_images" 27 + 28 + # category:search_text pairs 29 + CATEGORIES=" 30 + honk:goose geese bird 31 + awoo:pup wolves wolf 32 + baaa:kid goats goat 33 + bork:puppy puppies dog 34 + hiss:snake reptile hiss 35 + hoot:owlet owls owl 36 + meehh:lamb sheep lambs 37 + meow:kitty kitties cat 38 + mumble:marmot marmots rodent nature 39 + oink:pig piggy oink farm 40 + ribbit:frog frogs amphibian nature 41 + squee:capybaras rodent capybaras nature 42 + yowl:lynx bobcats bobcat 43 + " 44 + 45 + download_images() { 46 + local category="$1" 47 + local search_text="$2" 48 + local dir="$BASE_DIR/$category" 49 + mkdir -p "$dir" 50 + 51 + existing=$(find "$dir" -name '*.jpg' 2>/dev/null | wc -l | tr -d ' ') 52 + if [ "$existing" -ge "$IMAGES_PER_CATEGORY" ]; then 53 + echo "[$category] Already has $existing images, skipping" 54 + return 55 + fi 56 + 57 + echo "[$category] Searching Flickr for: $search_text" 58 + 59 + local encoded_text 60 + encoded_text=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$search_text'))") 61 + 62 + local search_url="https://api.flickr.com/services/rest?api_key=${FLICKR_KEY}&method=flickr.photos.search&text=${encoded_text}&sort=interestingness-desc&content_type=0&format=json&nojsoncallback=1&per_page=100" 63 + 64 + local photo_ids 65 + photo_ids=$(curl -sf "$search_url" | jq -r '.photos.photo[].id' | shuf | head -n "$IMAGES_PER_CATEGORY") 66 + 67 + if [ -z "$photo_ids" ]; then 68 + echo "[$category] WARNING: No photos found!" 69 + return 70 + fi 71 + 72 + local i=1 73 + for photo_id in $photo_ids; do 74 + local sizes_url="https://api.flickr.com/services/rest?api_key=${FLICKR_KEY}&method=flickr.photos.getSizes&format=json&nojsoncallback=1&photo_id=${photo_id}" 75 + local sizes_json 76 + sizes_json=$(curl -sf "$sizes_url" 2>/dev/null || true) 77 + 78 + if [ -z "$sizes_json" ]; then 79 + echo " [$category/$i] Failed to get sizes for $photo_id, skipping" 80 + continue 81 + fi 82 + 83 + # Pick 4th from end (medium-large), same logic as the original bash bots 84 + local image_url 85 + image_url=$(echo "$sizes_json" | jq -cr '.sizes.size[].source' | tail -n 4 | head -n 1) 86 + 87 + if [ -z "$image_url" ]; then 88 + echo " [$category/$i] No suitable size for $photo_id, skipping" 89 + continue 90 + fi 91 + 92 + local outfile="$dir/${i}.jpg" 93 + if curl -sf -o "$outfile" "$image_url" 2>/dev/null; then 94 + # Resize if over 1MB 95 + local size_kb 96 + size_kb=$(du -k "$outfile" | awk '{print $1}') 97 + if [ "$size_kb" -gt 1000 ]; then 98 + if command -v mogrify &>/dev/null; then 99 + mogrify -define jpeg:extent=900KB "$outfile" 100 + echo " [$category/$i] Downloaded and resized (was ${size_kb}KB)" 101 + else 102 + echo " [$category/$i] Downloaded (${size_kb}KB, >1MB but mogrify not available)" 103 + fi 104 + else 105 + echo " [$category/$i] Downloaded (${size_kb}KB)" 106 + fi 107 + i=$((i + 1)) 108 + else 109 + echo " [$category/$i] Failed to download $image_url" 110 + fi 111 + 112 + # Be nice to Flickr 113 + sleep 0.2 114 + done 115 + 116 + local final_count 117 + final_count=$(find "$dir" -name '*.jpg' | wc -l | tr -d ' ') 118 + echo "[$category] Done: $final_count images in $dir" 119 + } 120 + 121 + echo "Seeding fallback images ($IMAGES_PER_CATEGORY per category)..." 122 + echo "" 123 + 124 + echo "$CATEGORIES" | while IFS= read -r line; do 125 + # Skip blank lines 126 + [ -z "$line" ] && continue 127 + category="${line%%:*}" 128 + search_text="${line#*:}" 129 + download_images "$category" "$search_text" 130 + echo "" 131 + done 132 + 133 + echo "Done! Total images:" 134 + find "$BASE_DIR" -name '*.jpg' | wc -l | tr -d ' '
+960
src/main.rs
··· 1 + use futures_util::StreamExt; 2 + use rand::seq::SliceRandom; 3 + use serde::Deserialize; 4 + use std::collections::HashSet; 5 + use std::io::Cursor; 6 + use std::path::{Path, PathBuf}; 7 + use std::sync::Arc; 8 + use std::time::Duration; 9 + use tokio::sync::{RwLock, Semaphore}; 10 + use tokio_tungstenite::tungstenite::Message; 11 + use tracing::{error, info, warn}; 12 + 13 + // --- Command configuration --- 14 + 15 + struct BotCommand { 16 + trigger: &'static str, // e.g. "/honk" 17 + reply_text: &'static str, 18 + flickr_search: &'static str, 19 + alt_text: &'static str, 20 + fallback_folder: &'static str, // e.g. "honk" 21 + } 22 + 23 + const COMMANDS: &[BotCommand] = &[ 24 + BotCommand { 25 + trigger: "/honk", 26 + reply_text: "HONK", 27 + flickr_search: "goose geese bird", 28 + alt_text: "A goose", 29 + fallback_folder: "honk", 30 + }, 31 + BotCommand { 32 + trigger: "/awoo", 33 + reply_text: "AWOO", 34 + flickr_search: "pup wolves wolf", 35 + alt_text: "A wolf", 36 + fallback_folder: "awoo", 37 + }, 38 + BotCommand { 39 + trigger: "/baaa", 40 + reply_text: "BAAA", 41 + flickr_search: "kid goats goat", 42 + alt_text: "A goat", 43 + fallback_folder: "baaa", 44 + }, 45 + BotCommand { 46 + trigger: "/bork", 47 + reply_text: "BORK", 48 + flickr_search: "puppy puppies dog", 49 + alt_text: "A dog", 50 + fallback_folder: "bork", 51 + }, 52 + BotCommand { 53 + trigger: "/hiss", 54 + reply_text: "HISS", 55 + flickr_search: "snake reptile hiss", 56 + alt_text: "A snake", 57 + fallback_folder: "hiss", 58 + }, 59 + BotCommand { 60 + trigger: "/hoot", 61 + reply_text: "HOOT", 62 + flickr_search: "owlet owls owl", 63 + alt_text: "An owl", 64 + fallback_folder: "hoot", 65 + }, 66 + BotCommand { 67 + trigger: "/meehh", 68 + reply_text: "MEEHH", 69 + flickr_search: "lamb sheep lambs", 70 + alt_text: "A lamb", 71 + fallback_folder: "meehh", 72 + }, 73 + BotCommand { 74 + trigger: "/meow", 75 + reply_text: "MEOW", 76 + flickr_search: "kitty kitties cat", 77 + alt_text: "A cat", 78 + fallback_folder: "meow", 79 + }, 80 + BotCommand { 81 + trigger: "/mumble", 82 + reply_text: "MUMBLE", 83 + flickr_search: "marmot marmots rodent nature", 84 + alt_text: "A marmot", 85 + fallback_folder: "mumble", 86 + }, 87 + BotCommand { 88 + trigger: "/oink", 89 + reply_text: "OINK", 90 + flickr_search: "pig piggy oink farm", 91 + alt_text: "A pig", 92 + fallback_folder: "oink", 93 + }, 94 + BotCommand { 95 + trigger: "/ribbit", 96 + reply_text: "RIBBIT", 97 + flickr_search: "frog frogs amphibian nature", 98 + alt_text: "A frog", 99 + fallback_folder: "ribbit", 100 + }, 101 + BotCommand { 102 + trigger: "/squee", 103 + reply_text: "SQUEE", 104 + flickr_search: "capybaras rodent capybaras nature", 105 + alt_text: "A capybara", 106 + fallback_folder: "squee", 107 + }, 108 + BotCommand { 109 + trigger: "/yowl", 110 + reply_text: "YOWL", 111 + flickr_search: "lynx bobcats bobcat", 112 + alt_text: "A bobcat", 113 + fallback_folder: "yowl", 114 + }, 115 + ]; 116 + 117 + // --- Jetstream types --- 118 + 119 + #[derive(Debug, Deserialize)] 120 + struct JetstreamEvent { 121 + did: Option<String>, 122 + time_us: Option<u64>, 123 + #[allow(dead_code)] 124 + kind: Option<String>, 125 + commit: Option<JetstreamCommit>, 126 + } 127 + 128 + #[derive(Debug, Deserialize)] 129 + struct JetstreamCommit { 130 + #[allow(dead_code)] 131 + rev: Option<String>, 132 + operation: Option<String>, 133 + collection: Option<String>, 134 + rkey: Option<String>, 135 + record: Option<serde_json::Value>, 136 + cid: Option<String>, 137 + } 138 + 139 + // --- Flickr types --- 140 + 141 + #[derive(Debug, Deserialize)] 142 + struct FlickrPhoto { 143 + id: String, 144 + } 145 + 146 + #[derive(Debug, Deserialize)] 147 + struct FlickrPhotosResult { 148 + photo: Vec<FlickrPhoto>, 149 + } 150 + 151 + #[derive(Debug, Deserialize)] 152 + struct FlickrSearchResponse { 153 + photos: FlickrPhotosResult, 154 + } 155 + 156 + #[derive(Debug, Deserialize)] 157 + struct FlickrSize { 158 + source: String, 159 + #[allow(dead_code)] 160 + label: Option<String>, 161 + } 162 + 163 + #[derive(Debug, Deserialize)] 164 + struct FlickrSizes { 165 + size: Vec<FlickrSize>, 166 + } 167 + 168 + #[derive(Debug, Deserialize)] 169 + struct FlickrSizesResponse { 170 + sizes: FlickrSizes, 171 + } 172 + 173 + // --- Constellation (follower check) --- 174 + 175 + #[derive(Debug, Deserialize)] 176 + struct ConstellationBacklinks { 177 + total: u64, 178 + } 179 + 180 + /// Check if `follower_did` follows the bot (`bot_did`) using Constellation's getBacklinks. 181 + /// Returns true if a follow record exists, false otherwise. 182 + async fn is_follower_constellation( 183 + http: &reqwest::Client, 184 + bot_did: &str, 185 + follower_did: &str, 186 + ) -> anyhow::Result<bool> { 187 + let url = format!( 188 + "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject={}&source=app.bsky.graph.follow:subject&did={}&limit=1", 189 + urlencoding::encode(bot_did), 190 + urlencoding::encode(follower_did), 191 + ); 192 + 193 + let resp: ConstellationBacklinks = http.get(&url).send().await?.json().await?; 194 + Ok(resp.total > 0) 195 + } 196 + 197 + // --- Follower cache --- 198 + 199 + /// In-memory cache of follower DIDs, updated in real-time via the firehose. 200 + /// Falls back to Constellation API for DIDs not yet seen in the firehose. 201 + struct FollowerCache { 202 + /// DIDs known to be followers 203 + followers: RwLock<HashSet<String>>, 204 + /// DIDs known to NOT be followers (to avoid repeated Constellation lookups) 205 + non_followers: RwLock<HashSet<String>>, 206 + } 207 + 208 + impl FollowerCache { 209 + fn new() -> Arc<Self> { 210 + Arc::new(Self { 211 + followers: RwLock::new(HashSet::new()), 212 + non_followers: RwLock::new(HashSet::new()), 213 + }) 214 + } 215 + 216 + /// Record a new follow from the firehose 217 + async fn add_follower(&self, did: &str) { 218 + self.non_followers.write().await.remove(did); 219 + self.followers.write().await.insert(did.to_string()); 220 + info!(did = %did, "Follower cache: added follower"); 221 + } 222 + 223 + /// Record an unfollow from the firehose 224 + async fn remove_follower(&self, did: &str) { 225 + self.followers.write().await.remove(did); 226 + self.non_followers.write().await.insert(did.to_string()); 227 + info!(did = %did, "Follower cache: removed follower"); 228 + } 229 + 230 + /// Check if a DID is a follower, consulting cache first then Constellation. 231 + async fn is_follower( 232 + &self, 233 + http: &reqwest::Client, 234 + bot_did: &str, 235 + did: &str, 236 + ) -> anyhow::Result<bool> { 237 + // Check positive cache 238 + if self.followers.read().await.contains(did) { 239 + return Ok(true); 240 + } 241 + 242 + // Check negative cache 243 + if self.non_followers.read().await.contains(did) { 244 + return Ok(false); 245 + } 246 + 247 + // Not in cache — fall back to Constellation and cache the result 248 + let result = is_follower_constellation(http, bot_did, did).await?; 249 + if result { 250 + self.followers.write().await.insert(did.to_string()); 251 + } else { 252 + self.non_followers.write().await.insert(did.to_string()); 253 + } 254 + Ok(result) 255 + } 256 + } 257 + 258 + // Bluesky API uses serde_json::json! for record construction 259 + 260 + // --- Bluesky session --- 261 + 262 + #[derive(Debug, Deserialize)] 263 + struct Session { 264 + #[serde(rename = "accessJwt")] 265 + access_jwt: String, 266 + did: String, 267 + } 268 + 269 + struct BskyClient { 270 + http: reqwest::Client, 271 + service: String, 272 + access_jwt: tokio::sync::RwLock<String>, 273 + did: String, 274 + handle: String, 275 + password: String, 276 + } 277 + 278 + impl BskyClient { 279 + async fn login( 280 + http: &reqwest::Client, 281 + service: &str, 282 + handle: &str, 283 + password: &str, 284 + ) -> anyhow::Result<Session> { 285 + let resp = http 286 + .post(format!("{}/xrpc/com.atproto.server.createSession", service)) 287 + .json(&serde_json::json!({ 288 + "identifier": handle, 289 + "password": password, 290 + })) 291 + .send() 292 + .await?; 293 + 294 + if !resp.status().is_success() { 295 + let status = resp.status(); 296 + let body = resp.text().await.unwrap_or_default(); 297 + anyhow::bail!("Login failed ({}): {}", status, body); 298 + } 299 + 300 + Ok(resp.json::<Session>().await?) 301 + } 302 + 303 + async fn new(service: String, handle: String, password: String) -> anyhow::Result<Arc<Self>> { 304 + let http = reqwest::Client::new(); 305 + let session = Self::login(&http, &service, &handle, &password).await?; 306 + info!(did = %session.did, "Logged in to Bluesky"); 307 + 308 + Ok(Arc::new(Self { 309 + http, 310 + service, 311 + access_jwt: tokio::sync::RwLock::new(session.access_jwt), 312 + did: session.did, 313 + handle, 314 + password, 315 + })) 316 + } 317 + 318 + async fn refresh_session(&self) -> anyhow::Result<()> { 319 + let session = Self::login(&self.http, &self.service, &self.handle, &self.password).await?; 320 + let mut jwt = self.access_jwt.write().await; 321 + *jwt = session.access_jwt; 322 + info!("Refreshed Bluesky session"); 323 + Ok(()) 324 + } 325 + 326 + async fn upload_blob(&self, data: &[u8], mime: &str) -> anyhow::Result<serde_json::Value> { 327 + let jwt = self.access_jwt.read().await.clone(); 328 + let resp = self 329 + .http 330 + .post(format!( 331 + "{}/xrpc/com.atproto.repo.uploadBlob", 332 + self.service 333 + )) 334 + .header("Authorization", format!("Bearer {}", jwt)) 335 + .header("Content-Type", mime) 336 + .body(data.to_vec()) 337 + .send() 338 + .await?; 339 + 340 + if !resp.status().is_success() { 341 + let status = resp.status(); 342 + let body = resp.text().await.unwrap_or_default(); 343 + anyhow::bail!("Upload blob failed ({}): {}", status, body); 344 + } 345 + 346 + let body: serde_json::Value = resp.json().await?; 347 + Ok(body["blob"].clone()) 348 + } 349 + 350 + async fn create_record(&self, request: serde_json::Value) -> anyhow::Result<serde_json::Value> { 351 + let jwt = self.access_jwt.read().await.clone(); 352 + let resp = self 353 + .http 354 + .post(format!( 355 + "{}/xrpc/com.atproto.repo.createRecord", 356 + self.service 357 + )) 358 + .header("Authorization", format!("Bearer {}", jwt)) 359 + .json(&request) 360 + .send() 361 + .await?; 362 + 363 + if !resp.status().is_success() { 364 + let status = resp.status(); 365 + let body = resp.text().await.unwrap_or_default(); 366 + anyhow::bail!("Create record failed ({}): {}", status, body); 367 + } 368 + 369 + Ok(resp.json::<serde_json::Value>().await?) 370 + } 371 + } 372 + 373 + // --- Flickr --- 374 + 375 + async fn fetch_random_flickr_image( 376 + http: &reqwest::Client, 377 + flickr_key: &str, 378 + search_text: &str, 379 + ) -> anyhow::Result<Vec<u8>> { 380 + // Search for photos 381 + let search_url = format!( 382 + "https://api.flickr.com/services/rest?api_key={}&method=flickr.photos.search&text={}&sort=interestingness-desc&content_type=0&format=json&nojsoncallback=1", 383 + flickr_key, 384 + urlencoding::encode(search_text) 385 + ); 386 + 387 + let resp: FlickrSearchResponse = http.get(&search_url).send().await?.json().await?; 388 + 389 + if resp.photos.photo.is_empty() { 390 + anyhow::bail!("No photos found for search: {}", search_text); 391 + } 392 + 393 + // Pick a random photo 394 + let photo = resp 395 + .photos 396 + .photo 397 + .choose(&mut rand::thread_rng()) 398 + .ok_or_else(|| anyhow::anyhow!("Failed to pick random photo"))?; 399 + 400 + // Get photo sizes 401 + let sizes_url = format!( 402 + "https://api.flickr.com/services/rest?api_key={}&method=flickr.photos.getSizes&format=json&nojsoncallback=1&photo_id={}", 403 + flickr_key, photo.id 404 + ); 405 + 406 + let sizes_resp: FlickrSizesResponse = http.get(&sizes_url).send().await?.json().await?; 407 + 408 + // Pick a medium-large size (similar to bash: tail -n 4 | head -n 1 picks 4th from end) 409 + let sizes = &sizes_resp.sizes.size; 410 + if sizes.is_empty() { 411 + anyhow::bail!("No sizes available for photo {}", photo.id); 412 + } 413 + let idx = if sizes.len() >= 4 { 414 + sizes.len() - 4 415 + } else { 416 + 0 417 + }; 418 + let image_url = &sizes[idx].source; 419 + 420 + // Download image 421 + let image_bytes = http.get(image_url).send().await?.bytes().await?; 422 + 423 + // Resize if > 1MB (Bluesky blob limit) 424 + let mut data = image_bytes.to_vec(); 425 + if data.len() > 1_000_000 { 426 + info!( 427 + size = data.len(), 428 + "Image too large, resizing to fit under 1MB" 429 + ); 430 + data = resize_image(&data, 1_000_000)?; 431 + } 432 + 433 + Ok(data) 434 + } 435 + 436 + fn resize_image(data: &[u8], max_bytes: usize) -> anyhow::Result<Vec<u8>> { 437 + let img = image::load_from_memory(data)?; 438 + 439 + // Iteratively reduce quality/size 440 + let mut quality = 85u8; 441 + let mut current = img.clone(); 442 + 443 + loop { 444 + let mut buf = Vec::new(); 445 + let mut cursor = Cursor::new(&mut buf); 446 + current.write_to(&mut cursor, image::ImageFormat::Jpeg)?; 447 + 448 + if buf.len() <= max_bytes || quality <= 10 { 449 + return Ok(buf); 450 + } 451 + 452 + // Reduce quality or dimensions 453 + if quality > 20 { 454 + quality -= 10; 455 + let mut buf2 = Vec::new(); 456 + { 457 + let mut encoder = 458 + image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf2, quality); 459 + encoder.encode_image(&current)?; 460 + } 461 + if buf2.len() <= max_bytes { 462 + return Ok(buf2); 463 + } 464 + } 465 + 466 + // Scale down by 75% 467 + let new_w = (current.width() * 3) / 4; 468 + let new_h = (current.height() * 3) / 4; 469 + if new_w < 100 || new_h < 100 { 470 + // Just return what we have at lowest quality 471 + let mut buf2 = Vec::new(); 472 + let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf2, 10); 473 + encoder.encode_image(&current)?; 474 + return Ok(buf2); 475 + } 476 + current = current.resize(new_w, new_h, image::imageops::FilterType::Lanczos3); 477 + } 478 + } 479 + 480 + // --- Fallback images --- 481 + 482 + fn pick_random_fallback_image(fallback_dir: &Path, folder: &str) -> anyhow::Result<Vec<u8>> { 483 + let dir = fallback_dir.join(folder); 484 + let entries: Vec<PathBuf> = std::fs::read_dir(&dir)? 485 + .filter_map(|e| e.ok()) 486 + .map(|e| e.path()) 487 + .filter(|p| { 488 + p.extension() 489 + .and_then(|ext| ext.to_str()) 490 + .map(|ext| matches!(ext.to_lowercase().as_str(), "jpg" | "jpeg" | "png" | "webp")) 491 + .unwrap_or(false) 492 + }) 493 + .collect(); 494 + 495 + if entries.is_empty() { 496 + anyhow::bail!("No fallback images in {}", dir.display()); 497 + } 498 + 499 + let path = entries 500 + .choose(&mut rand::thread_rng()) 501 + .ok_or_else(|| anyhow::anyhow!("Failed to pick fallback image"))?; 502 + 503 + let data = std::fs::read(path)?; 504 + info!( 505 + path = %path.display(), 506 + "Using fallback image" 507 + ); 508 + Ok(data) 509 + } 510 + 511 + // --- Main logic --- 512 + 513 + async fn handle_command( 514 + bsky: Arc<BskyClient>, 515 + flickr_key: String, 516 + fallback_dir: PathBuf, 517 + post_uri: String, 518 + post_cid: String, 519 + command: &'static BotCommand, 520 + ) { 521 + let http = reqwest::Client::new(); 522 + 523 + // Fetch random image from Flickr, fall back to local images on failure 524 + let image_data: Vec<u8> = match fetch_random_flickr_image(&http, &flickr_key, command.flickr_search).await { 525 + Ok(data) => data, 526 + Err(e) => { 527 + warn!( 528 + command = command.trigger, 529 + uri = %post_uri, 530 + error = %e, 531 + "Flickr fetch failed, trying fallback images" 532 + ); 533 + match pick_random_fallback_image(&fallback_dir, command.fallback_folder) { 534 + Ok(data) => data, 535 + Err(e2) => { 536 + error!( 537 + command = command.trigger, 538 + uri = %post_uri, 539 + flickr_error = %e, 540 + fallback_error = %e2, 541 + "Both Flickr and fallback images failed" 542 + ); 543 + return; 544 + } 545 + } 546 + } 547 + }; 548 + 549 + // Upload blob to Bluesky 550 + let blob = match bsky.upload_blob(&image_data, "image/jpeg").await { 551 + Ok(b) => b, 552 + Err(e) => { 553 + // Try refreshing session and retrying once 554 + warn!(error = %e, "Blob upload failed, refreshing session and retrying"); 555 + if let Err(e2) = bsky.refresh_session().await { 556 + error!(error = %e2, "Session refresh failed"); 557 + return; 558 + } 559 + match bsky.upload_blob(&image_data, "image/jpeg").await { 560 + Ok(b) => b, 561 + Err(e2) => { 562 + error!(error = %e2, "Blob upload failed after retry"); 563 + return; 564 + } 565 + } 566 + } 567 + }; 568 + 569 + // Create reply post 570 + let now = chrono_now(); 571 + let record = serde_json::json!({ 572 + "repo": bsky.did, 573 + "collection": "app.bsky.feed.post", 574 + "record": { 575 + "$type": "app.bsky.feed.post", 576 + "text": command.reply_text, 577 + "createdAt": now, 578 + "reply": { 579 + "root": { 580 + "uri": post_uri, 581 + "cid": post_cid, 582 + }, 583 + "parent": { 584 + "uri": post_uri, 585 + "cid": post_cid, 586 + } 587 + }, 588 + "embed": { 589 + "$type": "app.bsky.embed.images", 590 + "images": [{ 591 + "alt": command.alt_text, 592 + "image": blob, 593 + }] 594 + } 595 + } 596 + }); 597 + 598 + match bsky.create_record(record).await { 599 + Ok(_) => { 600 + info!( 601 + command = command.trigger, 602 + uri = %post_uri, 603 + "{} reply sent", command.reply_text 604 + ); 605 + } 606 + Err(e) => { 607 + // Try refreshing and retrying once 608 + warn!(error = %e, "Create record failed, refreshing session"); 609 + if let Err(e2) = bsky.refresh_session().await { 610 + error!(error = %e2, "Session refresh failed"); 611 + return; 612 + } 613 + // Re-upload blob with new session 614 + let blob2 = match bsky.upload_blob(&image_data, "image/jpeg").await { 615 + Ok(b) => b, 616 + Err(e2) => { 617 + error!(error = %e2, "Blob re-upload failed after session refresh"); 618 + return; 619 + } 620 + }; 621 + let record2 = serde_json::json!({ 622 + "repo": bsky.did, 623 + "collection": "app.bsky.feed.post", 624 + "record": { 625 + "$type": "app.bsky.feed.post", 626 + "text": command.reply_text, 627 + "createdAt": chrono_now(), 628 + "reply": { 629 + "root": { 630 + "uri": post_uri, 631 + "cid": post_cid, 632 + }, 633 + "parent": { 634 + "uri": post_uri, 635 + "cid": post_cid, 636 + } 637 + }, 638 + "embed": { 639 + "$type": "app.bsky.embed.images", 640 + "images": [{ 641 + "alt": command.alt_text, 642 + "image": blob2, 643 + }] 644 + } 645 + } 646 + }); 647 + match bsky.create_record(record2).await { 648 + Ok(_) => { 649 + info!( 650 + command = command.trigger, 651 + uri = %post_uri, 652 + "{} reply sent (after retry)", command.reply_text 653 + ); 654 + } 655 + Err(e2) => { 656 + error!( 657 + command = command.trigger, 658 + uri = %post_uri, 659 + error = %e2, 660 + "Create record failed after retry" 661 + ); 662 + } 663 + } 664 + } 665 + } 666 + } 667 + 668 + fn chrono_now() -> String { 669 + use std::time::SystemTime; 670 + let d = SystemTime::now() 671 + .duration_since(SystemTime::UNIX_EPOCH) 672 + .unwrap(); 673 + let secs = d.as_secs(); 674 + let millis = d.subsec_millis(); 675 + // Format as ISO 8601 676 + let days_since_epoch = secs / 86400; 677 + let time_of_day = secs % 86400; 678 + let hours = time_of_day / 3600; 679 + let minutes = (time_of_day % 3600) / 60; 680 + let seconds = time_of_day % 60; 681 + 682 + // Simple date calculation 683 + let mut y = 1970i64; 684 + let mut remaining = days_since_epoch as i64; 685 + loop { 686 + let days_in_year = if is_leap(y) { 366 } else { 365 }; 687 + if remaining < days_in_year { 688 + break; 689 + } 690 + remaining -= days_in_year; 691 + y += 1; 692 + } 693 + let month_days = if is_leap(y) { 694 + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 695 + } else { 696 + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 697 + }; 698 + let mut m = 0; 699 + for (i, &days) in month_days.iter().enumerate() { 700 + if remaining < days { 701 + m = i; 702 + break; 703 + } 704 + remaining -= days; 705 + } 706 + 707 + format!( 708 + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", 709 + y, 710 + m + 1, 711 + remaining + 1, 712 + hours, 713 + minutes, 714 + seconds, 715 + millis 716 + ) 717 + } 718 + 719 + fn is_leap(y: i64) -> bool { 720 + (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 721 + } 722 + 723 + fn find_matching_command(text: &str) -> Option<&'static BotCommand> { 724 + let lower = text.to_lowercase(); 725 + COMMANDS.iter().find(|cmd| lower.contains(cmd.trigger)) 726 + } 727 + 728 + async fn connect_and_stream( 729 + bsky: Arc<BskyClient>, 730 + flickr_key: String, 731 + fallback_dir: PathBuf, 732 + follows_only: bool, 733 + semaphore: Arc<Semaphore>, 734 + follower_cache: Arc<FollowerCache>, 735 + ) -> anyhow::Result<()> { 736 + let jetstream_url = "wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post&wantedCollections=app.bsky.graph.follow"; 737 + 738 + info!("Connecting to Jetstream..."); 739 + let (ws_stream, _) = tokio_tungstenite::connect_async(jetstream_url).await?; 740 + info!("Connected to Jetstream"); 741 + 742 + let (_write, mut read) = ws_stream.split(); 743 + 744 + let mut last_cursor: u64 = 0; 745 + 746 + while let Some(msg) = read.next().await { 747 + let msg = match msg { 748 + Ok(m) => m, 749 + Err(e) => { 750 + error!(error = %e, "WebSocket read error"); 751 + return Err(e.into()); 752 + } 753 + }; 754 + 755 + let text = match msg { 756 + Message::Text(t) => t, 757 + Message::Ping(_) => continue, 758 + Message::Pong(_) => continue, 759 + Message::Close(_) => { 760 + warn!("WebSocket closed by server"); 761 + return Ok(()); 762 + } 763 + _ => continue, 764 + }; 765 + 766 + // Parse event 767 + let event: JetstreamEvent = match serde_json::from_str(&text) { 768 + Ok(e) => e, 769 + Err(_) => continue, 770 + }; 771 + 772 + // Track cursor for reconnection 773 + if let Some(t) = event.time_us { 774 + last_cursor = t; 775 + } 776 + 777 + // Only care about commit events with create operation on posts 778 + let commit = match &event.commit { 779 + Some(c) => c, 780 + None => continue, 781 + }; 782 + 783 + // Handle follow/unfollow events for our follower cache 784 + if commit.collection.as_deref() == Some("app.bsky.graph.follow") { 785 + if let Some(did) = &event.did { 786 + // Check if this follow targets our bot 787 + let is_about_us = commit 788 + .record 789 + .as_ref() 790 + .and_then(|r| r.get("subject")) 791 + .and_then(|s| s.as_str()) 792 + == Some(&bsky.did); 793 + 794 + if is_about_us { 795 + match commit.operation.as_deref() { 796 + Some("create") => { 797 + follower_cache.add_follower(did).await; 798 + } 799 + Some("delete") => { 800 + follower_cache.remove_follower(did).await; 801 + } 802 + _ => {} 803 + } 804 + } 805 + } 806 + continue; 807 + } 808 + 809 + if commit.operation.as_deref() != Some("create") { 810 + continue; 811 + } 812 + if commit.collection.as_deref() != Some("app.bsky.feed.post") { 813 + continue; 814 + } 815 + 816 + // Check the post text for a trigger 817 + let record = match &commit.record { 818 + Some(r) => r, 819 + None => continue, 820 + }; 821 + 822 + let post_text = match record.get("text").and_then(|t| t.as_str()) { 823 + Some(t) => t, 824 + None => continue, 825 + }; 826 + 827 + let command = match find_matching_command(post_text) { 828 + Some(c) => c, 829 + None => continue, 830 + }; 831 + 832 + let did = match &event.did { 833 + Some(d) => d.clone(), 834 + None => continue, 835 + }; 836 + let rkey = match &commit.rkey { 837 + Some(r) => r.clone(), 838 + None => continue, 839 + }; 840 + let cid = match &commit.cid { 841 + Some(c) => c.clone(), 842 + None => continue, 843 + }; 844 + 845 + let post_uri = format!("at://{}/app.bsky.feed.post/{}", did, rkey); 846 + 847 + // Check if the poster follows the bot (if FOLLOWS_ONLY is enabled) 848 + if follows_only { 849 + match follower_cache.is_follower(&bsky.http, &bsky.did, &did).await { 850 + Ok(true) => {} 851 + Ok(false) => { 852 + info!( 853 + command = command.trigger, 854 + poster = %did, 855 + "Ignoring command from non-follower" 856 + ); 857 + continue; 858 + } 859 + Err(e) => { 860 + warn!( 861 + command = command.trigger, 862 + poster = %did, 863 + error = %e, 864 + "Follower check failed, skipping" 865 + ); 866 + continue; 867 + } 868 + } 869 + } 870 + 871 + info!( 872 + command = command.trigger, 873 + uri = %post_uri, 874 + "Matched command, queuing reply" 875 + ); 876 + 877 + // Spawn bounded task 878 + let bsky = bsky.clone(); 879 + let flickr_key = flickr_key.clone(); 880 + let fallback_dir = fallback_dir.clone(); 881 + let permit = semaphore.clone().acquire_owned().await?; 882 + 883 + tokio::spawn(async move { 884 + handle_command(bsky, flickr_key, fallback_dir, post_uri, cid, command).await; 885 + drop(permit); 886 + }); 887 + } 888 + 889 + warn!(cursor = last_cursor, "Jetstream stream ended"); 890 + Ok(()) 891 + } 892 + 893 + #[tokio::main] 894 + async fn main() -> anyhow::Result<()> { 895 + // Initialize logging 896 + tracing_subscriber::fmt() 897 + .with_env_filter( 898 + tracing_subscriber::EnvFilter::try_from_default_env() 899 + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), 900 + ) 901 + .init(); 902 + 903 + // Load .env 904 + dotenvy::dotenv().ok(); 905 + 906 + let bsky_handle = 907 + std::env::var("BSKY_HANDLE").expect("BSKY_HANDLE env var required"); 908 + let bsky_password = 909 + std::env::var("BSKY_PASSWORD").expect("BSKY_PASSWORD env var required"); 910 + let flickr_key = 911 + std::env::var("FLICKR_KEY").expect("FLICKR_KEY env var required"); 912 + let bsky_service = std::env::var("BSKY_SERVICE") 913 + .unwrap_or_else(|_| "https://bsky.social".to_string()); 914 + let follows_only = std::env::var("FOLLOWS_ONLY") 915 + .map(|v| matches!(v.to_lowercase().as_str(), "1" | "true" | "yes")) 916 + .unwrap_or(false); 917 + 918 + // Resolve fallback image directory (default: ./fallback_images) 919 + let fallback_dir = PathBuf::from( 920 + std::env::var("FALLBACK_IMAGE_DIR").unwrap_or_else(|_| "fallback_images".to_string()), 921 + ); 922 + let fallback_dir = std::fs::canonicalize(&fallback_dir).unwrap_or(fallback_dir); 923 + 924 + info!( 925 + handle = %bsky_handle, 926 + service = %bsky_service, 927 + fallback_dir = %fallback_dir.display(), 928 + follows_only = follows_only, 929 + commands = COMMANDS.len(), 930 + "Starting honkbot" 931 + ); 932 + 933 + // Login to Bluesky 934 + let bsky: Arc<BskyClient> = BskyClient::new(bsky_service, bsky_handle, bsky_password).await?; 935 + 936 + // Limit concurrent reply tasks (don't flood APIs) 937 + let semaphore = Arc::new(Semaphore::new(10)); 938 + 939 + // Follower cache — updated in real-time via firehose follow events 940 + let follower_cache = FollowerCache::new(); 941 + 942 + // Main loop with automatic reconnection 943 + loop { 944 + match connect_and_stream(bsky.clone(), flickr_key.clone(), fallback_dir.clone(), follows_only, semaphore.clone(), follower_cache.clone()).await { 945 + Ok(()) => { 946 + warn!("Stream ended normally, reconnecting in 5s..."); 947 + } 948 + Err(e) => { 949 + error!(error = %e, "Stream error, reconnecting in 5s..."); 950 + } 951 + } 952 + 953 + // Refresh session before reconnecting 954 + if let Err(e) = bsky.refresh_session().await { 955 + error!(error = %e, "Failed to refresh session before reconnect"); 956 + } 957 + 958 + tokio::time::sleep(Duration::from_secs(5)).await; 959 + } 960 + }