A social internet radio platform built on AT Protocol. atradio.fm
atproto radio
7

Configure Feed

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

feat(cli): atradio terminal TUI radio player

A native Rust client for atradio.fm in cli/ (workspace member `atradio`):

- clap CLI with a synthwave-styled help; default launches the TUI.
Subcommands: search, play, trending, login (app-password + --oauth),
logout, whoami.
- ratatui TUI inspired by the web app: trending/popular/favorites lists,
player bar, fzf-style fuzzy search (/), equalizer + full Rockbox DSP
panel (e), comments (c/a), notifications (n), help (?).
- Audio via rockbox-playback (>=0.4.1) with the full DSP chain (10-band
EQ, tone, crossfeed, PBE, surround, compressor, channel/width).
- atproto via jacquard: password + OAuth login (reads env), PDS writes
for favorites/comments/play-status.
- Public AppView reads + radio-browser search over reqwest.
- Typed fm.atradio.* bindings generated from packages/lexicons via
jacquard-codegen (src/fm_atradio, scripts/gen-lexicons.sh).
- TOML settings persistence (volume + DSP) under ~/.config/atradio.
- Linux-only MPRIS (D-Bus) integration for media keys / desktop panels.

Also: sync the Clojure console (bb.edn + console.clj) with cli:* tasks,
and ignore Rust target/ output.

Note: linking rockbox-playback (GPL-2.0) makes the binary GPL-2.0-or-later.

Tsiry Sandratraina (Jul 16, 2026, 11:25 PM +0300) d0c8d25b 02cd17c7

+17001 -4
+4
.gitignore
··· 13 13 14 14 # rockbox-wasm runtime, mirrored from node_modules by apps/web/vite.config.ts 15 15 apps/web/public/rockbox 16 + 17 + # Rust (cli/ — the atradio TUI crate) 18 + target 19 + **/*.rs.bk
+5613
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 = "alloc-no-stdlib" 22 + version = "2.0.4" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 25 + 26 + [[package]] 27 + name = "alloc-stdlib" 28 + version = "0.2.4" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" 31 + dependencies = [ 32 + "alloc-no-stdlib", 33 + ] 34 + 35 + [[package]] 36 + name = "allocator-api2" 37 + version = "0.2.21" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 40 + 41 + [[package]] 42 + name = "alsa" 43 + version = "0.9.1" 44 + source = "registry+https://github.com/rust-lang/crates.io-index" 45 + checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" 46 + dependencies = [ 47 + "alsa-sys", 48 + "bitflags 2.13.1", 49 + "cfg-if", 50 + "libc", 51 + ] 52 + 53 + [[package]] 54 + name = "alsa-sys" 55 + version = "0.3.1" 56 + source = "registry+https://github.com/rust-lang/crates.io-index" 57 + checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" 58 + dependencies = [ 59 + "libc", 60 + "pkg-config", 61 + ] 62 + 63 + [[package]] 64 + name = "android_system_properties" 65 + version = "0.1.5" 66 + source = "registry+https://github.com/rust-lang/crates.io-index" 67 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 68 + dependencies = [ 69 + "libc", 70 + ] 71 + 72 + [[package]] 73 + name = "anstream" 74 + version = "1.0.0" 75 + source = "registry+https://github.com/rust-lang/crates.io-index" 76 + checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" 77 + dependencies = [ 78 + "anstyle", 79 + "anstyle-parse", 80 + "anstyle-query", 81 + "anstyle-wincon", 82 + "colorchoice", 83 + "is_terminal_polyfill", 84 + "utf8parse", 85 + ] 86 + 87 + [[package]] 88 + name = "anstyle" 89 + version = "1.0.14" 90 + source = "registry+https://github.com/rust-lang/crates.io-index" 91 + checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" 92 + 93 + [[package]] 94 + name = "anstyle-parse" 95 + version = "1.0.0" 96 + source = "registry+https://github.com/rust-lang/crates.io-index" 97 + checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" 98 + dependencies = [ 99 + "utf8parse", 100 + ] 101 + 102 + [[package]] 103 + name = "anstyle-query" 104 + version = "1.1.5" 105 + source = "registry+https://github.com/rust-lang/crates.io-index" 106 + checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 107 + dependencies = [ 108 + "windows-sys 0.61.2", 109 + ] 110 + 111 + [[package]] 112 + name = "anstyle-wincon" 113 + version = "3.0.11" 114 + source = "registry+https://github.com/rust-lang/crates.io-index" 115 + checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 116 + dependencies = [ 117 + "anstyle", 118 + "once_cell_polyfill", 119 + "windows-sys 0.61.2", 120 + ] 121 + 122 + [[package]] 123 + name = "anyhow" 124 + version = "1.0.103" 125 + source = "registry+https://github.com/rust-lang/crates.io-index" 126 + checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" 127 + 128 + [[package]] 129 + name = "async-broadcast" 130 + version = "0.7.2" 131 + source = "registry+https://github.com/rust-lang/crates.io-index" 132 + checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" 133 + dependencies = [ 134 + "event-listener", 135 + "event-listener-strategy", 136 + "futures-core", 137 + "pin-project-lite", 138 + ] 139 + 140 + [[package]] 141 + name = "async-channel" 142 + version = "2.5.0" 143 + source = "registry+https://github.com/rust-lang/crates.io-index" 144 + checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" 145 + dependencies = [ 146 + "concurrent-queue", 147 + "event-listener-strategy", 148 + "futures-core", 149 + "pin-project-lite", 150 + ] 151 + 152 + [[package]] 153 + name = "async-compression" 154 + version = "0.4.42" 155 + source = "registry+https://github.com/rust-lang/crates.io-index" 156 + checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" 157 + dependencies = [ 158 + "compression-codecs", 159 + "compression-core", 160 + "pin-project-lite", 161 + "tokio", 162 + ] 163 + 164 + [[package]] 165 + name = "async-executor" 166 + version = "1.14.0" 167 + source = "registry+https://github.com/rust-lang/crates.io-index" 168 + checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" 169 + dependencies = [ 170 + "async-task", 171 + "concurrent-queue", 172 + "fastrand", 173 + "futures-lite", 174 + "pin-project-lite", 175 + "slab", 176 + ] 177 + 178 + [[package]] 179 + name = "async-fs" 180 + version = "2.2.0" 181 + source = "registry+https://github.com/rust-lang/crates.io-index" 182 + checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" 183 + dependencies = [ 184 + "async-lock", 185 + "blocking", 186 + "futures-lite", 187 + ] 188 + 189 + [[package]] 190 + name = "async-io" 191 + version = "2.6.0" 192 + source = "registry+https://github.com/rust-lang/crates.io-index" 193 + checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" 194 + dependencies = [ 195 + "autocfg", 196 + "cfg-if", 197 + "concurrent-queue", 198 + "futures-io", 199 + "futures-lite", 200 + "parking", 201 + "polling", 202 + "rustix 1.1.4", 203 + "slab", 204 + "windows-sys 0.61.2", 205 + ] 206 + 207 + [[package]] 208 + name = "async-lock" 209 + version = "3.4.2" 210 + source = "registry+https://github.com/rust-lang/crates.io-index" 211 + checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" 212 + dependencies = [ 213 + "event-listener", 214 + "event-listener-strategy", 215 + "pin-project-lite", 216 + ] 217 + 218 + [[package]] 219 + name = "async-process" 220 + version = "2.5.0" 221 + source = "registry+https://github.com/rust-lang/crates.io-index" 222 + checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" 223 + dependencies = [ 224 + "async-channel", 225 + "async-io", 226 + "async-lock", 227 + "async-signal", 228 + "async-task", 229 + "blocking", 230 + "cfg-if", 231 + "event-listener", 232 + "futures-lite", 233 + "rustix 1.1.4", 234 + ] 235 + 236 + [[package]] 237 + name = "async-recursion" 238 + version = "1.1.1" 239 + source = "registry+https://github.com/rust-lang/crates.io-index" 240 + checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" 241 + dependencies = [ 242 + "proc-macro2", 243 + "quote", 244 + "syn", 245 + ] 246 + 247 + [[package]] 248 + name = "async-signal" 249 + version = "0.2.14" 250 + source = "registry+https://github.com/rust-lang/crates.io-index" 251 + checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" 252 + dependencies = [ 253 + "async-io", 254 + "async-lock", 255 + "atomic-waker", 256 + "cfg-if", 257 + "futures-core", 258 + "futures-io", 259 + "rustix 1.1.4", 260 + "signal-hook-registry", 261 + "slab", 262 + "windows-sys 0.61.2", 263 + ] 264 + 265 + [[package]] 266 + name = "async-task" 267 + version = "4.7.1" 268 + source = "registry+https://github.com/rust-lang/crates.io-index" 269 + checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 270 + 271 + [[package]] 272 + name = "async-trait" 273 + version = "0.1.89" 274 + source = "registry+https://github.com/rust-lang/crates.io-index" 275 + checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 276 + dependencies = [ 277 + "proc-macro2", 278 + "quote", 279 + "syn", 280 + ] 281 + 282 + [[package]] 283 + name = "atomic-polyfill" 284 + version = "1.0.3" 285 + source = "registry+https://github.com/rust-lang/crates.io-index" 286 + checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" 287 + dependencies = [ 288 + "critical-section", 289 + ] 290 + 291 + [[package]] 292 + name = "atomic-waker" 293 + version = "1.1.2" 294 + source = "registry+https://github.com/rust-lang/crates.io-index" 295 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 296 + 297 + [[package]] 298 + name = "atradio" 299 + version = "0.1.0" 300 + dependencies = [ 301 + "anyhow", 302 + "chrono", 303 + "clap", 304 + "crossterm", 305 + "directories", 306 + "futures", 307 + "fuzzy-matcher", 308 + "jacquard", 309 + "jacquard-common", 310 + "jacquard-derive", 311 + "jacquard-lexicon", 312 + "mpris-server", 313 + "ratatui", 314 + "reqwest", 315 + "rockbox-playback", 316 + "serde", 317 + "serde_json", 318 + "smol_str", 319 + "tokio", 320 + "toml", 321 + ] 322 + 323 + [[package]] 324 + name = "autocfg" 325 + version = "1.5.1" 326 + source = "registry+https://github.com/rust-lang/crates.io-index" 327 + checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" 328 + 329 + [[package]] 330 + name = "base-x" 331 + version = "0.2.11" 332 + source = "registry+https://github.com/rust-lang/crates.io-index" 333 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 334 + 335 + [[package]] 336 + name = "base16ct" 337 + version = "0.2.0" 338 + source = "registry+https://github.com/rust-lang/crates.io-index" 339 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 340 + 341 + [[package]] 342 + name = "base256emoji" 343 + version = "1.0.2" 344 + source = "registry+https://github.com/rust-lang/crates.io-index" 345 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 346 + dependencies = [ 347 + "const-str", 348 + "match-lookup", 349 + ] 350 + 351 + [[package]] 352 + name = "base45" 353 + version = "3.2.0" 354 + source = "registry+https://github.com/rust-lang/crates.io-index" 355 + checksum = "240e56f4d3c453c36faacb695c535a4d5f8c7d23dac175014f32eb0a71012a03" 356 + 357 + [[package]] 358 + name = "base64" 359 + version = "0.22.1" 360 + source = "registry+https://github.com/rust-lang/crates.io-index" 361 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 362 + 363 + [[package]] 364 + name = "base64ct" 365 + version = "1.8.3" 366 + source = "registry+https://github.com/rust-lang/crates.io-index" 367 + checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" 368 + 369 + [[package]] 370 + name = "bindgen" 371 + version = "0.72.1" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" 374 + dependencies = [ 375 + "bitflags 2.13.1", 376 + "cexpr", 377 + "clang-sys", 378 + "itertools", 379 + "proc-macro2", 380 + "quote", 381 + "regex", 382 + "rustc-hash", 383 + "shlex 1.3.0", 384 + "syn", 385 + ] 386 + 387 + [[package]] 388 + name = "bitflags" 389 + version = "1.3.2" 390 + source = "registry+https://github.com/rust-lang/crates.io-index" 391 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 392 + 393 + [[package]] 394 + name = "bitflags" 395 + version = "2.13.1" 396 + source = "registry+https://github.com/rust-lang/crates.io-index" 397 + checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" 398 + 399 + [[package]] 400 + name = "block-buffer" 401 + version = "0.10.4" 402 + source = "registry+https://github.com/rust-lang/crates.io-index" 403 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 404 + dependencies = [ 405 + "generic-array", 406 + ] 407 + 408 + [[package]] 409 + name = "blocking" 410 + version = "1.6.2" 411 + source = "registry+https://github.com/rust-lang/crates.io-index" 412 + checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" 413 + dependencies = [ 414 + "async-channel", 415 + "async-task", 416 + "futures-io", 417 + "futures-lite", 418 + "piper", 419 + ] 420 + 421 + [[package]] 422 + name = "bon" 423 + version = "3.9.3" 424 + source = "registry+https://github.com/rust-lang/crates.io-index" 425 + checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561" 426 + dependencies = [ 427 + "bon-macros", 428 + "rustversion", 429 + ] 430 + 431 + [[package]] 432 + name = "bon-macros" 433 + version = "3.9.3" 434 + source = "registry+https://github.com/rust-lang/crates.io-index" 435 + checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" 436 + dependencies = [ 437 + "darling", 438 + "ident_case", 439 + "prettyplease", 440 + "proc-macro2", 441 + "quote", 442 + "rustversion", 443 + "syn", 444 + ] 445 + 446 + [[package]] 447 + name = "borrow-or-share" 448 + version = "0.2.4" 449 + source = "registry+https://github.com/rust-lang/crates.io-index" 450 + checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" 451 + 452 + [[package]] 453 + name = "borsh" 454 + version = "1.7.0" 455 + source = "registry+https://github.com/rust-lang/crates.io-index" 456 + checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" 457 + dependencies = [ 458 + "bytes", 459 + "cfg_aliases", 460 + ] 461 + 462 + [[package]] 463 + name = "brotli" 464 + version = "8.0.4" 465 + source = "registry+https://github.com/rust-lang/crates.io-index" 466 + checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" 467 + dependencies = [ 468 + "alloc-no-stdlib", 469 + "alloc-stdlib", 470 + "brotli-decompressor", 471 + ] 472 + 473 + [[package]] 474 + name = "brotli-decompressor" 475 + version = "5.0.3" 476 + source = "registry+https://github.com/rust-lang/crates.io-index" 477 + checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" 478 + dependencies = [ 479 + "alloc-no-stdlib", 480 + "alloc-stdlib", 481 + ] 482 + 483 + [[package]] 484 + name = "bs58" 485 + version = "0.5.1" 486 + source = "registry+https://github.com/rust-lang/crates.io-index" 487 + checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" 488 + dependencies = [ 489 + "tinyvec", 490 + ] 491 + 492 + [[package]] 493 + name = "bumpalo" 494 + version = "3.20.3" 495 + source = "registry+https://github.com/rust-lang/crates.io-index" 496 + checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" 497 + 498 + [[package]] 499 + name = "byteorder" 500 + version = "1.5.0" 501 + source = "registry+https://github.com/rust-lang/crates.io-index" 502 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 503 + 504 + [[package]] 505 + name = "bytes" 506 + version = "1.12.1" 507 + source = "registry+https://github.com/rust-lang/crates.io-index" 508 + checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" 509 + dependencies = [ 510 + "serde", 511 + ] 512 + 513 + [[package]] 514 + name = "cassowary" 515 + version = "0.3.0" 516 + source = "registry+https://github.com/rust-lang/crates.io-index" 517 + checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 518 + 519 + [[package]] 520 + name = "castaway" 521 + version = "0.2.4" 522 + source = "registry+https://github.com/rust-lang/crates.io-index" 523 + checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" 524 + dependencies = [ 525 + "rustversion", 526 + ] 527 + 528 + [[package]] 529 + name = "cbor4ii" 530 + version = "0.2.14" 531 + source = "registry+https://github.com/rust-lang/crates.io-index" 532 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 533 + dependencies = [ 534 + "serde", 535 + ] 536 + 537 + [[package]] 538 + name = "cc" 539 + version = "1.2.67" 540 + source = "registry+https://github.com/rust-lang/crates.io-index" 541 + checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" 542 + dependencies = [ 543 + "find-msvc-tools", 544 + "jobserver", 545 + "libc", 546 + "shlex 2.0.1", 547 + ] 548 + 549 + [[package]] 550 + name = "cesu8" 551 + version = "1.1.0" 552 + source = "registry+https://github.com/rust-lang/crates.io-index" 553 + checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 554 + 555 + [[package]] 556 + name = "cexpr" 557 + version = "0.6.0" 558 + source = "registry+https://github.com/rust-lang/crates.io-index" 559 + checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 560 + dependencies = [ 561 + "nom", 562 + ] 563 + 564 + [[package]] 565 + name = "cfg-if" 566 + version = "1.0.4" 567 + source = "registry+https://github.com/rust-lang/crates.io-index" 568 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 569 + 570 + [[package]] 571 + name = "cfg_aliases" 572 + version = "0.2.2" 573 + source = "registry+https://github.com/rust-lang/crates.io-index" 574 + checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" 575 + 576 + [[package]] 577 + name = "chacha20" 578 + version = "0.10.1" 579 + source = "registry+https://github.com/rust-lang/crates.io-index" 580 + checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" 581 + dependencies = [ 582 + "cfg-if", 583 + "cpufeatures 0.3.0", 584 + "rand_core 0.10.1", 585 + ] 586 + 587 + [[package]] 588 + name = "chrono" 589 + version = "0.4.45" 590 + source = "registry+https://github.com/rust-lang/crates.io-index" 591 + checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" 592 + dependencies = [ 593 + "iana-time-zone", 594 + "js-sys", 595 + "num-traits", 596 + "serde", 597 + "wasm-bindgen", 598 + "windows-link", 599 + ] 600 + 601 + [[package]] 602 + name = "ciborium" 603 + version = "0.2.2" 604 + source = "registry+https://github.com/rust-lang/crates.io-index" 605 + checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 606 + dependencies = [ 607 + "ciborium-io", 608 + "ciborium-ll", 609 + "serde", 610 + ] 611 + 612 + [[package]] 613 + name = "ciborium-io" 614 + version = "0.2.2" 615 + source = "registry+https://github.com/rust-lang/crates.io-index" 616 + checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 617 + 618 + [[package]] 619 + name = "ciborium-ll" 620 + version = "0.2.2" 621 + source = "registry+https://github.com/rust-lang/crates.io-index" 622 + checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 623 + dependencies = [ 624 + "ciborium-io", 625 + "half", 626 + ] 627 + 628 + [[package]] 629 + name = "cid" 630 + version = "0.11.3" 631 + source = "registry+https://github.com/rust-lang/crates.io-index" 632 + checksum = "21a304f95f84d169a6f31c4d0a30d784643aaa0bbc9c1e449a2c23e963ec4971" 633 + dependencies = [ 634 + "multibase", 635 + "multihash", 636 + "serde", 637 + "serde_bytes", 638 + "unsigned-varint", 639 + ] 640 + 641 + [[package]] 642 + name = "clang-sys" 643 + version = "1.8.1" 644 + source = "registry+https://github.com/rust-lang/crates.io-index" 645 + checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 646 + dependencies = [ 647 + "glob", 648 + "libc", 649 + "libloading", 650 + ] 651 + 652 + [[package]] 653 + name = "clap" 654 + version = "4.6.2" 655 + source = "registry+https://github.com/rust-lang/crates.io-index" 656 + checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" 657 + dependencies = [ 658 + "clap_builder", 659 + "clap_derive", 660 + ] 661 + 662 + [[package]] 663 + name = "clap_builder" 664 + version = "4.6.2" 665 + source = "registry+https://github.com/rust-lang/crates.io-index" 666 + checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" 667 + dependencies = [ 668 + "anstream", 669 + "anstyle", 670 + "clap_lex", 671 + "strsim", 672 + ] 673 + 674 + [[package]] 675 + name = "clap_derive" 676 + version = "4.6.1" 677 + source = "registry+https://github.com/rust-lang/crates.io-index" 678 + checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" 679 + dependencies = [ 680 + "heck", 681 + "proc-macro2", 682 + "quote", 683 + "syn", 684 + ] 685 + 686 + [[package]] 687 + name = "clap_lex" 688 + version = "1.1.0" 689 + source = "registry+https://github.com/rust-lang/crates.io-index" 690 + checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" 691 + 692 + [[package]] 693 + name = "cobs" 694 + version = "0.3.0" 695 + source = "registry+https://github.com/rust-lang/crates.io-index" 696 + checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" 697 + dependencies = [ 698 + "thiserror 2.0.18", 699 + ] 700 + 701 + [[package]] 702 + name = "colorchoice" 703 + version = "1.0.5" 704 + source = "registry+https://github.com/rust-lang/crates.io-index" 705 + checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" 706 + 707 + [[package]] 708 + name = "combine" 709 + version = "4.6.7" 710 + source = "registry+https://github.com/rust-lang/crates.io-index" 711 + checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 712 + dependencies = [ 713 + "bytes", 714 + "memchr", 715 + ] 716 + 717 + [[package]] 718 + name = "compact_str" 719 + version = "0.8.2" 720 + source = "registry+https://github.com/rust-lang/crates.io-index" 721 + checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" 722 + dependencies = [ 723 + "castaway", 724 + "cfg-if", 725 + "itoa", 726 + "rustversion", 727 + "ryu", 728 + "static_assertions", 729 + ] 730 + 731 + [[package]] 732 + name = "compression-codecs" 733 + version = "0.4.38" 734 + source = "registry+https://github.com/rust-lang/crates.io-index" 735 + checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" 736 + dependencies = [ 737 + "brotli", 738 + "compression-core", 739 + "flate2", 740 + "memchr", 741 + ] 742 + 743 + [[package]] 744 + name = "compression-core" 745 + version = "0.4.32" 746 + source = "registry+https://github.com/rust-lang/crates.io-index" 747 + checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" 748 + 749 + [[package]] 750 + name = "concurrent-queue" 751 + version = "2.5.0" 752 + source = "registry+https://github.com/rust-lang/crates.io-index" 753 + checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 754 + dependencies = [ 755 + "crossbeam-utils", 756 + ] 757 + 758 + [[package]] 759 + name = "const-oid" 760 + version = "0.9.6" 761 + source = "registry+https://github.com/rust-lang/crates.io-index" 762 + checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 763 + 764 + [[package]] 765 + name = "const-str" 766 + version = "0.4.3" 767 + source = "registry+https://github.com/rust-lang/crates.io-index" 768 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 769 + 770 + [[package]] 771 + name = "cordyceps" 772 + version = "0.3.4" 773 + source = "registry+https://github.com/rust-lang/crates.io-index" 774 + checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" 775 + dependencies = [ 776 + "loom", 777 + "tracing", 778 + ] 779 + 780 + [[package]] 781 + name = "core-foundation" 782 + version = "0.9.4" 783 + source = "registry+https://github.com/rust-lang/crates.io-index" 784 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 785 + dependencies = [ 786 + "core-foundation-sys", 787 + "libc", 788 + ] 789 + 790 + [[package]] 791 + name = "core-foundation" 792 + version = "0.10.1" 793 + source = "registry+https://github.com/rust-lang/crates.io-index" 794 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 795 + dependencies = [ 796 + "core-foundation-sys", 797 + "libc", 798 + ] 799 + 800 + [[package]] 801 + name = "core-foundation-sys" 802 + version = "0.8.7" 803 + source = "registry+https://github.com/rust-lang/crates.io-index" 804 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 805 + 806 + [[package]] 807 + name = "coreaudio-rs" 808 + version = "0.11.3" 809 + source = "registry+https://github.com/rust-lang/crates.io-index" 810 + checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" 811 + dependencies = [ 812 + "bitflags 1.3.2", 813 + "core-foundation-sys", 814 + "coreaudio-sys", 815 + ] 816 + 817 + [[package]] 818 + name = "coreaudio-sys" 819 + version = "0.2.18" 820 + source = "registry+https://github.com/rust-lang/crates.io-index" 821 + checksum = "b9b4739a805a62757a83e5654fa3faabec0442666b263bb2287d5a8185bfd953" 822 + dependencies = [ 823 + "bindgen", 824 + ] 825 + 826 + [[package]] 827 + name = "cpal" 828 + version = "0.15.3" 829 + source = "registry+https://github.com/rust-lang/crates.io-index" 830 + checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" 831 + dependencies = [ 832 + "alsa", 833 + "core-foundation-sys", 834 + "coreaudio-rs", 835 + "dasp_sample", 836 + "jni 0.21.1", 837 + "js-sys", 838 + "libc", 839 + "mach2", 840 + "ndk", 841 + "ndk-context", 842 + "oboe", 843 + "wasm-bindgen", 844 + "wasm-bindgen-futures", 845 + "web-sys", 846 + "windows", 847 + ] 848 + 849 + [[package]] 850 + name = "cpufeatures" 851 + version = "0.2.17" 852 + source = "registry+https://github.com/rust-lang/crates.io-index" 853 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 854 + dependencies = [ 855 + "libc", 856 + ] 857 + 858 + [[package]] 859 + name = "cpufeatures" 860 + version = "0.3.0" 861 + source = "registry+https://github.com/rust-lang/crates.io-index" 862 + checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" 863 + dependencies = [ 864 + "libc", 865 + ] 866 + 867 + [[package]] 868 + name = "crc32fast" 869 + version = "1.5.0" 870 + source = "registry+https://github.com/rust-lang/crates.io-index" 871 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 872 + dependencies = [ 873 + "cfg-if", 874 + ] 875 + 876 + [[package]] 877 + name = "critical-section" 878 + version = "1.2.0" 879 + source = "registry+https://github.com/rust-lang/crates.io-index" 880 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 881 + 882 + [[package]] 883 + name = "crossbeam-channel" 884 + version = "0.5.16" 885 + source = "registry+https://github.com/rust-lang/crates.io-index" 886 + checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" 887 + dependencies = [ 888 + "crossbeam-utils", 889 + ] 890 + 891 + [[package]] 892 + name = "crossbeam-utils" 893 + version = "0.8.22" 894 + source = "registry+https://github.com/rust-lang/crates.io-index" 895 + checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" 896 + 897 + [[package]] 898 + name = "crossterm" 899 + version = "0.28.1" 900 + source = "registry+https://github.com/rust-lang/crates.io-index" 901 + checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 902 + dependencies = [ 903 + "bitflags 2.13.1", 904 + "crossterm_winapi", 905 + "futures-core", 906 + "mio", 907 + "parking_lot", 908 + "rustix 0.38.44", 909 + "signal-hook", 910 + "signal-hook-mio", 911 + "winapi", 912 + ] 913 + 914 + [[package]] 915 + name = "crossterm_winapi" 916 + version = "0.9.1" 917 + source = "registry+https://github.com/rust-lang/crates.io-index" 918 + checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 919 + dependencies = [ 920 + "winapi", 921 + ] 922 + 923 + [[package]] 924 + name = "crunchy" 925 + version = "0.2.4" 926 + source = "registry+https://github.com/rust-lang/crates.io-index" 927 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 928 + 929 + [[package]] 930 + name = "crypto-bigint" 931 + version = "0.5.5" 932 + source = "registry+https://github.com/rust-lang/crates.io-index" 933 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 934 + dependencies = [ 935 + "generic-array", 936 + "rand_core 0.6.4", 937 + "subtle", 938 + "zeroize", 939 + ] 940 + 941 + [[package]] 942 + name = "crypto-common" 943 + version = "0.1.6" 944 + source = "registry+https://github.com/rust-lang/crates.io-index" 945 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 946 + dependencies = [ 947 + "generic-array", 948 + "typenum", 949 + ] 950 + 951 + [[package]] 952 + name = "curve25519-dalek" 953 + version = "4.1.3" 954 + source = "registry+https://github.com/rust-lang/crates.io-index" 955 + checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" 956 + dependencies = [ 957 + "cfg-if", 958 + "cpufeatures 0.2.17", 959 + "curve25519-dalek-derive", 960 + "digest", 961 + "fiat-crypto", 962 + "rustc_version", 963 + "subtle", 964 + "zeroize", 965 + ] 966 + 967 + [[package]] 968 + name = "curve25519-dalek-derive" 969 + version = "0.1.1" 970 + source = "registry+https://github.com/rust-lang/crates.io-index" 971 + checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" 972 + dependencies = [ 973 + "proc-macro2", 974 + "quote", 975 + "syn", 976 + ] 977 + 978 + [[package]] 979 + name = "darling" 980 + version = "0.23.0" 981 + source = "registry+https://github.com/rust-lang/crates.io-index" 982 + checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" 983 + dependencies = [ 984 + "darling_core", 985 + "darling_macro", 986 + ] 987 + 988 + [[package]] 989 + name = "darling_core" 990 + version = "0.23.0" 991 + source = "registry+https://github.com/rust-lang/crates.io-index" 992 + checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" 993 + dependencies = [ 994 + "ident_case", 995 + "proc-macro2", 996 + "quote", 997 + "strsim", 998 + "syn", 999 + ] 1000 + 1001 + [[package]] 1002 + name = "darling_macro" 1003 + version = "0.23.0" 1004 + source = "registry+https://github.com/rust-lang/crates.io-index" 1005 + checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" 1006 + dependencies = [ 1007 + "darling_core", 1008 + "quote", 1009 + "syn", 1010 + ] 1011 + 1012 + [[package]] 1013 + name = "dashmap" 1014 + version = "6.2.1" 1015 + source = "registry+https://github.com/rust-lang/crates.io-index" 1016 + checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" 1017 + dependencies = [ 1018 + "cfg-if", 1019 + "crossbeam-utils", 1020 + "hashbrown 0.14.5", 1021 + "lock_api", 1022 + "once_cell", 1023 + "parking_lot_core", 1024 + ] 1025 + 1026 + [[package]] 1027 + name = "dasp_sample" 1028 + version = "0.11.0" 1029 + source = "registry+https://github.com/rust-lang/crates.io-index" 1030 + checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" 1031 + 1032 + [[package]] 1033 + name = "data-encoding" 1034 + version = "2.11.0" 1035 + source = "registry+https://github.com/rust-lang/crates.io-index" 1036 + checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" 1037 + 1038 + [[package]] 1039 + name = "data-encoding-macro" 1040 + version = "0.1.20" 1041 + source = "registry+https://github.com/rust-lang/crates.io-index" 1042 + checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" 1043 + dependencies = [ 1044 + "data-encoding", 1045 + "data-encoding-macro-internal", 1046 + ] 1047 + 1048 + [[package]] 1049 + name = "data-encoding-macro-internal" 1050 + version = "0.1.18" 1051 + source = "registry+https://github.com/rust-lang/crates.io-index" 1052 + checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" 1053 + dependencies = [ 1054 + "data-encoding", 1055 + "syn", 1056 + ] 1057 + 1058 + [[package]] 1059 + name = "der" 1060 + version = "0.7.10" 1061 + source = "registry+https://github.com/rust-lang/crates.io-index" 1062 + checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 1063 + dependencies = [ 1064 + "const-oid", 1065 + "pem-rfc7468", 1066 + "zeroize", 1067 + ] 1068 + 1069 + [[package]] 1070 + name = "deranged" 1071 + version = "0.5.8" 1072 + source = "registry+https://github.com/rust-lang/crates.io-index" 1073 + checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" 1074 + 1075 + [[package]] 1076 + name = "derive_more" 1077 + version = "1.0.0" 1078 + source = "registry+https://github.com/rust-lang/crates.io-index" 1079 + checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" 1080 + dependencies = [ 1081 + "derive_more-impl", 1082 + ] 1083 + 1084 + [[package]] 1085 + name = "derive_more-impl" 1086 + version = "1.0.0" 1087 + source = "registry+https://github.com/rust-lang/crates.io-index" 1088 + checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" 1089 + dependencies = [ 1090 + "proc-macro2", 1091 + "quote", 1092 + "syn", 1093 + "unicode-xid", 1094 + ] 1095 + 1096 + [[package]] 1097 + name = "diatomic-waker" 1098 + version = "0.2.3" 1099 + source = "registry+https://github.com/rust-lang/crates.io-index" 1100 + checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" 1101 + 1102 + [[package]] 1103 + name = "digest" 1104 + version = "0.10.7" 1105 + source = "registry+https://github.com/rust-lang/crates.io-index" 1106 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 1107 + dependencies = [ 1108 + "block-buffer", 1109 + "const-oid", 1110 + "crypto-common", 1111 + "subtle", 1112 + ] 1113 + 1114 + [[package]] 1115 + name = "directories" 1116 + version = "5.0.1" 1117 + source = "registry+https://github.com/rust-lang/crates.io-index" 1118 + checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" 1119 + dependencies = [ 1120 + "dirs-sys", 1121 + ] 1122 + 1123 + [[package]] 1124 + name = "dirs-sys" 1125 + version = "0.4.1" 1126 + source = "registry+https://github.com/rust-lang/crates.io-index" 1127 + checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 1128 + dependencies = [ 1129 + "libc", 1130 + "option-ext", 1131 + "redox_users", 1132 + "windows-sys 0.48.0", 1133 + ] 1134 + 1135 + [[package]] 1136 + name = "displaydoc" 1137 + version = "0.2.6" 1138 + source = "registry+https://github.com/rust-lang/crates.io-index" 1139 + checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" 1140 + dependencies = [ 1141 + "proc-macro2", 1142 + "quote", 1143 + "syn", 1144 + ] 1145 + 1146 + [[package]] 1147 + name = "ecdsa" 1148 + version = "0.16.9" 1149 + source = "registry+https://github.com/rust-lang/crates.io-index" 1150 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 1151 + dependencies = [ 1152 + "der", 1153 + "digest", 1154 + "elliptic-curve", 1155 + "rfc6979", 1156 + "signature", 1157 + "spki", 1158 + ] 1159 + 1160 + [[package]] 1161 + name = "ed25519" 1162 + version = "2.2.3" 1163 + source = "registry+https://github.com/rust-lang/crates.io-index" 1164 + checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" 1165 + dependencies = [ 1166 + "pkcs8", 1167 + "signature", 1168 + ] 1169 + 1170 + [[package]] 1171 + name = "ed25519-dalek" 1172 + version = "2.2.0" 1173 + source = "registry+https://github.com/rust-lang/crates.io-index" 1174 + checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" 1175 + dependencies = [ 1176 + "curve25519-dalek", 1177 + "ed25519", 1178 + "rand_core 0.6.4", 1179 + "serde", 1180 + "sha2", 1181 + "subtle", 1182 + "zeroize", 1183 + ] 1184 + 1185 + [[package]] 1186 + name = "either" 1187 + version = "1.16.0" 1188 + source = "registry+https://github.com/rust-lang/crates.io-index" 1189 + checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" 1190 + 1191 + [[package]] 1192 + name = "elliptic-curve" 1193 + version = "0.13.8" 1194 + source = "registry+https://github.com/rust-lang/crates.io-index" 1195 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 1196 + dependencies = [ 1197 + "base16ct", 1198 + "crypto-bigint", 1199 + "digest", 1200 + "ff", 1201 + "generic-array", 1202 + "group", 1203 + "hkdf", 1204 + "pem-rfc7468", 1205 + "pkcs8", 1206 + "rand_core 0.6.4", 1207 + "sec1", 1208 + "subtle", 1209 + "zeroize", 1210 + ] 1211 + 1212 + [[package]] 1213 + name = "embedded-io" 1214 + version = "0.4.0" 1215 + source = "registry+https://github.com/rust-lang/crates.io-index" 1216 + checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" 1217 + 1218 + [[package]] 1219 + name = "embedded-io" 1220 + version = "0.6.1" 1221 + source = "registry+https://github.com/rust-lang/crates.io-index" 1222 + checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 1223 + 1224 + [[package]] 1225 + name = "encoding_rs" 1226 + version = "0.8.35" 1227 + source = "registry+https://github.com/rust-lang/crates.io-index" 1228 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 1229 + dependencies = [ 1230 + "cfg-if", 1231 + ] 1232 + 1233 + [[package]] 1234 + name = "endi" 1235 + version = "1.1.1" 1236 + source = "registry+https://github.com/rust-lang/crates.io-index" 1237 + checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" 1238 + 1239 + [[package]] 1240 + name = "enum-as-inner" 1241 + version = "0.6.1" 1242 + source = "registry+https://github.com/rust-lang/crates.io-index" 1243 + checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 1244 + dependencies = [ 1245 + "heck", 1246 + "proc-macro2", 1247 + "quote", 1248 + "syn", 1249 + ] 1250 + 1251 + [[package]] 1252 + name = "enumflags2" 1253 + version = "0.7.12" 1254 + source = "registry+https://github.com/rust-lang/crates.io-index" 1255 + checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" 1256 + dependencies = [ 1257 + "enumflags2_derive", 1258 + "serde", 1259 + ] 1260 + 1261 + [[package]] 1262 + name = "enumflags2_derive" 1263 + version = "0.7.12" 1264 + source = "registry+https://github.com/rust-lang/crates.io-index" 1265 + checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" 1266 + dependencies = [ 1267 + "proc-macro2", 1268 + "quote", 1269 + "syn", 1270 + ] 1271 + 1272 + [[package]] 1273 + name = "equivalent" 1274 + version = "1.0.2" 1275 + source = "registry+https://github.com/rust-lang/crates.io-index" 1276 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 1277 + 1278 + [[package]] 1279 + name = "errno" 1280 + version = "0.3.14" 1281 + source = "registry+https://github.com/rust-lang/crates.io-index" 1282 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 1283 + dependencies = [ 1284 + "libc", 1285 + "windows-sys 0.61.2", 1286 + ] 1287 + 1288 + [[package]] 1289 + name = "event-listener" 1290 + version = "5.4.1" 1291 + source = "registry+https://github.com/rust-lang/crates.io-index" 1292 + checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" 1293 + dependencies = [ 1294 + "concurrent-queue", 1295 + "parking", 1296 + "pin-project-lite", 1297 + ] 1298 + 1299 + [[package]] 1300 + name = "event-listener-strategy" 1301 + version = "0.5.4" 1302 + source = "registry+https://github.com/rust-lang/crates.io-index" 1303 + checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 1304 + dependencies = [ 1305 + "event-listener", 1306 + "pin-project-lite", 1307 + ] 1308 + 1309 + [[package]] 1310 + name = "fastrand" 1311 + version = "2.4.1" 1312 + source = "registry+https://github.com/rust-lang/crates.io-index" 1313 + checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" 1314 + 1315 + [[package]] 1316 + name = "ff" 1317 + version = "0.13.1" 1318 + source = "registry+https://github.com/rust-lang/crates.io-index" 1319 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 1320 + dependencies = [ 1321 + "rand_core 0.6.4", 1322 + "subtle", 1323 + ] 1324 + 1325 + [[package]] 1326 + name = "fiat-crypto" 1327 + version = "0.2.9" 1328 + source = "registry+https://github.com/rust-lang/crates.io-index" 1329 + checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" 1330 + 1331 + [[package]] 1332 + name = "find-msvc-tools" 1333 + version = "0.1.9" 1334 + source = "registry+https://github.com/rust-lang/crates.io-index" 1335 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" 1336 + 1337 + [[package]] 1338 + name = "flate2" 1339 + version = "1.1.9" 1340 + source = "registry+https://github.com/rust-lang/crates.io-index" 1341 + checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" 1342 + dependencies = [ 1343 + "crc32fast", 1344 + "miniz_oxide", 1345 + ] 1346 + 1347 + [[package]] 1348 + name = "fluent-uri" 1349 + version = "0.4.1" 1350 + source = "registry+https://github.com/rust-lang/crates.io-index" 1351 + checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" 1352 + dependencies = [ 1353 + "borrow-or-share", 1354 + "ref-cast", 1355 + "serde", 1356 + ] 1357 + 1358 + [[package]] 1359 + name = "fnv" 1360 + version = "1.0.7" 1361 + source = "registry+https://github.com/rust-lang/crates.io-index" 1362 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 1363 + 1364 + [[package]] 1365 + name = "foldhash" 1366 + version = "0.1.5" 1367 + source = "registry+https://github.com/rust-lang/crates.io-index" 1368 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 1369 + 1370 + [[package]] 1371 + name = "form_urlencoded" 1372 + version = "1.2.2" 1373 + source = "registry+https://github.com/rust-lang/crates.io-index" 1374 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 1375 + dependencies = [ 1376 + "percent-encoding", 1377 + ] 1378 + 1379 + [[package]] 1380 + name = "futf" 1381 + version = "0.1.5" 1382 + source = "registry+https://github.com/rust-lang/crates.io-index" 1383 + checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 1384 + dependencies = [ 1385 + "mac", 1386 + "new_debug_unreachable", 1387 + ] 1388 + 1389 + [[package]] 1390 + name = "futures" 1391 + version = "0.3.32" 1392 + source = "registry+https://github.com/rust-lang/crates.io-index" 1393 + checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" 1394 + dependencies = [ 1395 + "futures-channel", 1396 + "futures-core", 1397 + "futures-executor", 1398 + "futures-io", 1399 + "futures-sink", 1400 + "futures-task", 1401 + "futures-util", 1402 + ] 1403 + 1404 + [[package]] 1405 + name = "futures-buffered" 1406 + version = "0.2.13" 1407 + source = "registry+https://github.com/rust-lang/crates.io-index" 1408 + checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" 1409 + dependencies = [ 1410 + "cordyceps", 1411 + "diatomic-waker", 1412 + "futures-core", 1413 + "pin-project-lite", 1414 + "spin 0.10.1", 1415 + ] 1416 + 1417 + [[package]] 1418 + name = "futures-channel" 1419 + version = "0.3.32" 1420 + source = "registry+https://github.com/rust-lang/crates.io-index" 1421 + checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" 1422 + dependencies = [ 1423 + "futures-core", 1424 + "futures-sink", 1425 + ] 1426 + 1427 + [[package]] 1428 + name = "futures-core" 1429 + version = "0.3.32" 1430 + source = "registry+https://github.com/rust-lang/crates.io-index" 1431 + checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 1432 + 1433 + [[package]] 1434 + name = "futures-executor" 1435 + version = "0.3.32" 1436 + source = "registry+https://github.com/rust-lang/crates.io-index" 1437 + checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" 1438 + dependencies = [ 1439 + "futures-core", 1440 + "futures-task", 1441 + "futures-util", 1442 + ] 1443 + 1444 + [[package]] 1445 + name = "futures-io" 1446 + version = "0.3.32" 1447 + source = "registry+https://github.com/rust-lang/crates.io-index" 1448 + checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" 1449 + 1450 + [[package]] 1451 + name = "futures-lite" 1452 + version = "2.6.1" 1453 + source = "registry+https://github.com/rust-lang/crates.io-index" 1454 + checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" 1455 + dependencies = [ 1456 + "fastrand", 1457 + "futures-core", 1458 + "futures-io", 1459 + "parking", 1460 + "pin-project-lite", 1461 + ] 1462 + 1463 + [[package]] 1464 + name = "futures-macro" 1465 + version = "0.3.32" 1466 + source = "registry+https://github.com/rust-lang/crates.io-index" 1467 + checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" 1468 + dependencies = [ 1469 + "proc-macro2", 1470 + "quote", 1471 + "syn", 1472 + ] 1473 + 1474 + [[package]] 1475 + name = "futures-sink" 1476 + version = "0.3.32" 1477 + source = "registry+https://github.com/rust-lang/crates.io-index" 1478 + checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" 1479 + 1480 + [[package]] 1481 + name = "futures-task" 1482 + version = "0.3.32" 1483 + source = "registry+https://github.com/rust-lang/crates.io-index" 1484 + checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" 1485 + 1486 + [[package]] 1487 + name = "futures-util" 1488 + version = "0.3.32" 1489 + source = "registry+https://github.com/rust-lang/crates.io-index" 1490 + checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" 1491 + dependencies = [ 1492 + "futures-channel", 1493 + "futures-core", 1494 + "futures-io", 1495 + "futures-macro", 1496 + "futures-sink", 1497 + "futures-task", 1498 + "memchr", 1499 + "pin-project-lite", 1500 + "slab", 1501 + ] 1502 + 1503 + [[package]] 1504 + name = "fuzzy-matcher" 1505 + version = "0.3.7" 1506 + source = "registry+https://github.com/rust-lang/crates.io-index" 1507 + checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 1508 + dependencies = [ 1509 + "thread_local", 1510 + ] 1511 + 1512 + [[package]] 1513 + name = "generator" 1514 + version = "0.8.9" 1515 + source = "registry+https://github.com/rust-lang/crates.io-index" 1516 + checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" 1517 + dependencies = [ 1518 + "cc", 1519 + "cfg-if", 1520 + "libc", 1521 + "log", 1522 + "rustversion", 1523 + "windows-link", 1524 + "windows-result 0.4.1", 1525 + ] 1526 + 1527 + [[package]] 1528 + name = "generic-array" 1529 + version = "0.14.9" 1530 + source = "registry+https://github.com/rust-lang/crates.io-index" 1531 + checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 1532 + dependencies = [ 1533 + "typenum", 1534 + "version_check", 1535 + "zeroize", 1536 + ] 1537 + 1538 + [[package]] 1539 + name = "getrandom" 1540 + version = "0.2.17" 1541 + source = "registry+https://github.com/rust-lang/crates.io-index" 1542 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 1543 + dependencies = [ 1544 + "cfg-if", 1545 + "js-sys", 1546 + "libc", 1547 + "wasi", 1548 + "wasm-bindgen", 1549 + ] 1550 + 1551 + [[package]] 1552 + name = "getrandom" 1553 + version = "0.3.4" 1554 + source = "registry+https://github.com/rust-lang/crates.io-index" 1555 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 1556 + dependencies = [ 1557 + "cfg-if", 1558 + "js-sys", 1559 + "libc", 1560 + "r-efi 5.3.0", 1561 + "wasip2", 1562 + "wasm-bindgen", 1563 + ] 1564 + 1565 + [[package]] 1566 + name = "getrandom" 1567 + version = "0.4.3" 1568 + source = "registry+https://github.com/rust-lang/crates.io-index" 1569 + checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" 1570 + dependencies = [ 1571 + "cfg-if", 1572 + "js-sys", 1573 + "libc", 1574 + "r-efi 6.0.0", 1575 + "rand_core 0.10.1", 1576 + "wasm-bindgen", 1577 + ] 1578 + 1579 + [[package]] 1580 + name = "glob" 1581 + version = "0.3.3" 1582 + source = "registry+https://github.com/rust-lang/crates.io-index" 1583 + checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1584 + 1585 + [[package]] 1586 + name = "gloo-storage" 1587 + version = "0.3.0" 1588 + source = "registry+https://github.com/rust-lang/crates.io-index" 1589 + checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" 1590 + dependencies = [ 1591 + "gloo-utils", 1592 + "js-sys", 1593 + "serde", 1594 + "serde_json", 1595 + "thiserror 1.0.69", 1596 + "wasm-bindgen", 1597 + "web-sys", 1598 + ] 1599 + 1600 + [[package]] 1601 + name = "gloo-utils" 1602 + version = "0.2.0" 1603 + source = "registry+https://github.com/rust-lang/crates.io-index" 1604 + checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" 1605 + dependencies = [ 1606 + "js-sys", 1607 + "serde", 1608 + "serde_json", 1609 + "wasm-bindgen", 1610 + "web-sys", 1611 + ] 1612 + 1613 + [[package]] 1614 + name = "group" 1615 + version = "0.13.0" 1616 + source = "registry+https://github.com/rust-lang/crates.io-index" 1617 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 1618 + dependencies = [ 1619 + "ff", 1620 + "rand_core 0.6.4", 1621 + "subtle", 1622 + ] 1623 + 1624 + [[package]] 1625 + name = "h2" 1626 + version = "0.4.15" 1627 + source = "registry+https://github.com/rust-lang/crates.io-index" 1628 + checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" 1629 + dependencies = [ 1630 + "atomic-waker", 1631 + "bytes", 1632 + "fnv", 1633 + "futures-core", 1634 + "futures-sink", 1635 + "http", 1636 + "indexmap", 1637 + "slab", 1638 + "tokio", 1639 + "tokio-util", 1640 + "tracing", 1641 + ] 1642 + 1643 + [[package]] 1644 + name = "half" 1645 + version = "2.7.1" 1646 + source = "registry+https://github.com/rust-lang/crates.io-index" 1647 + checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" 1648 + dependencies = [ 1649 + "cfg-if", 1650 + "crunchy", 1651 + "zerocopy", 1652 + ] 1653 + 1654 + [[package]] 1655 + name = "hash32" 1656 + version = "0.2.1" 1657 + source = "registry+https://github.com/rust-lang/crates.io-index" 1658 + checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" 1659 + dependencies = [ 1660 + "byteorder", 1661 + ] 1662 + 1663 + [[package]] 1664 + name = "hashbrown" 1665 + version = "0.14.5" 1666 + source = "registry+https://github.com/rust-lang/crates.io-index" 1667 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1668 + 1669 + [[package]] 1670 + name = "hashbrown" 1671 + version = "0.15.5" 1672 + source = "registry+https://github.com/rust-lang/crates.io-index" 1673 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 1674 + dependencies = [ 1675 + "allocator-api2", 1676 + "equivalent", 1677 + "foldhash", 1678 + ] 1679 + 1680 + [[package]] 1681 + name = "hashbrown" 1682 + version = "0.17.1" 1683 + source = "registry+https://github.com/rust-lang/crates.io-index" 1684 + checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" 1685 + 1686 + [[package]] 1687 + name = "heapless" 1688 + version = "0.7.17" 1689 + source = "registry+https://github.com/rust-lang/crates.io-index" 1690 + checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" 1691 + dependencies = [ 1692 + "atomic-polyfill", 1693 + "hash32", 1694 + "rustc_version", 1695 + "serde", 1696 + "spin 0.9.9", 1697 + "stable_deref_trait", 1698 + ] 1699 + 1700 + [[package]] 1701 + name = "heck" 1702 + version = "0.5.0" 1703 + source = "registry+https://github.com/rust-lang/crates.io-index" 1704 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1705 + 1706 + [[package]] 1707 + name = "hermit-abi" 1708 + version = "0.5.2" 1709 + source = "registry+https://github.com/rust-lang/crates.io-index" 1710 + checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 1711 + 1712 + [[package]] 1713 + name = "hex" 1714 + version = "0.4.3" 1715 + source = "registry+https://github.com/rust-lang/crates.io-index" 1716 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1717 + 1718 + [[package]] 1719 + name = "hickory-proto" 1720 + version = "0.24.4" 1721 + source = "registry+https://github.com/rust-lang/crates.io-index" 1722 + checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" 1723 + dependencies = [ 1724 + "async-trait", 1725 + "cfg-if", 1726 + "data-encoding", 1727 + "enum-as-inner", 1728 + "futures-channel", 1729 + "futures-io", 1730 + "futures-util", 1731 + "idna", 1732 + "ipnet", 1733 + "once_cell", 1734 + "rand 0.8.7", 1735 + "thiserror 1.0.69", 1736 + "tinyvec", 1737 + "tokio", 1738 + "tracing", 1739 + "url", 1740 + ] 1741 + 1742 + [[package]] 1743 + name = "hickory-resolver" 1744 + version = "0.24.4" 1745 + source = "registry+https://github.com/rust-lang/crates.io-index" 1746 + checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" 1747 + dependencies = [ 1748 + "cfg-if", 1749 + "futures-util", 1750 + "hickory-proto", 1751 + "ipconfig", 1752 + "lru-cache", 1753 + "once_cell", 1754 + "parking_lot", 1755 + "rand 0.8.7", 1756 + "resolv-conf", 1757 + "smallvec", 1758 + "thiserror 1.0.69", 1759 + "tokio", 1760 + "tracing", 1761 + ] 1762 + 1763 + [[package]] 1764 + name = "hkdf" 1765 + version = "0.12.4" 1766 + source = "registry+https://github.com/rust-lang/crates.io-index" 1767 + checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 1768 + dependencies = [ 1769 + "hmac", 1770 + ] 1771 + 1772 + [[package]] 1773 + name = "hmac" 1774 + version = "0.12.1" 1775 + source = "registry+https://github.com/rust-lang/crates.io-index" 1776 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1777 + dependencies = [ 1778 + "digest", 1779 + ] 1780 + 1781 + [[package]] 1782 + name = "html5ever" 1783 + version = "0.27.0" 1784 + source = "registry+https://github.com/rust-lang/crates.io-index" 1785 + checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" 1786 + dependencies = [ 1787 + "log", 1788 + "mac", 1789 + "markup5ever", 1790 + "proc-macro2", 1791 + "quote", 1792 + "syn", 1793 + ] 1794 + 1795 + [[package]] 1796 + name = "http" 1797 + version = "1.4.2" 1798 + source = "registry+https://github.com/rust-lang/crates.io-index" 1799 + checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" 1800 + dependencies = [ 1801 + "bytes", 1802 + "itoa", 1803 + ] 1804 + 1805 + [[package]] 1806 + name = "http-body" 1807 + version = "1.1.0" 1808 + source = "registry+https://github.com/rust-lang/crates.io-index" 1809 + checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" 1810 + dependencies = [ 1811 + "bytes", 1812 + "http", 1813 + ] 1814 + 1815 + [[package]] 1816 + name = "http-body-util" 1817 + version = "0.1.4" 1818 + source = "registry+https://github.com/rust-lang/crates.io-index" 1819 + checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" 1820 + dependencies = [ 1821 + "bytes", 1822 + "futures-core", 1823 + "http", 1824 + "http-body", 1825 + "pin-project-lite", 1826 + ] 1827 + 1828 + [[package]] 1829 + name = "httparse" 1830 + version = "1.10.1" 1831 + source = "registry+https://github.com/rust-lang/crates.io-index" 1832 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1833 + 1834 + [[package]] 1835 + name = "hyper" 1836 + version = "1.10.1" 1837 + source = "registry+https://github.com/rust-lang/crates.io-index" 1838 + checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" 1839 + dependencies = [ 1840 + "atomic-waker", 1841 + "bytes", 1842 + "futures-channel", 1843 + "futures-core", 1844 + "h2", 1845 + "http", 1846 + "http-body", 1847 + "httparse", 1848 + "itoa", 1849 + "pin-project-lite", 1850 + "smallvec", 1851 + "tokio", 1852 + "want", 1853 + ] 1854 + 1855 + [[package]] 1856 + name = "hyper-rustls" 1857 + version = "0.27.9" 1858 + source = "registry+https://github.com/rust-lang/crates.io-index" 1859 + checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" 1860 + dependencies = [ 1861 + "http", 1862 + "hyper", 1863 + "hyper-util", 1864 + "rustls", 1865 + "rustls-native-certs", 1866 + "tokio", 1867 + "tokio-rustls", 1868 + "tower-service", 1869 + "webpki-roots", 1870 + ] 1871 + 1872 + [[package]] 1873 + name = "hyper-util" 1874 + version = "0.1.20" 1875 + source = "registry+https://github.com/rust-lang/crates.io-index" 1876 + checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 1877 + dependencies = [ 1878 + "base64", 1879 + "bytes", 1880 + "futures-channel", 1881 + "futures-util", 1882 + "http", 1883 + "http-body", 1884 + "hyper", 1885 + "ipnet", 1886 + "libc", 1887 + "percent-encoding", 1888 + "pin-project-lite", 1889 + "socket2", 1890 + "system-configuration", 1891 + "tokio", 1892 + "tower-service", 1893 + "tracing", 1894 + "windows-registry", 1895 + ] 1896 + 1897 + [[package]] 1898 + name = "iana-time-zone" 1899 + version = "0.1.65" 1900 + source = "registry+https://github.com/rust-lang/crates.io-index" 1901 + checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" 1902 + dependencies = [ 1903 + "android_system_properties", 1904 + "core-foundation-sys", 1905 + "iana-time-zone-haiku", 1906 + "js-sys", 1907 + "log", 1908 + "wasm-bindgen", 1909 + "windows-core 0.62.2", 1910 + ] 1911 + 1912 + [[package]] 1913 + name = "iana-time-zone-haiku" 1914 + version = "0.1.2" 1915 + source = "registry+https://github.com/rust-lang/crates.io-index" 1916 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1917 + dependencies = [ 1918 + "cc", 1919 + ] 1920 + 1921 + [[package]] 1922 + name = "icu_collections" 1923 + version = "2.2.0" 1924 + source = "registry+https://github.com/rust-lang/crates.io-index" 1925 + checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" 1926 + dependencies = [ 1927 + "displaydoc", 1928 + "potential_utf", 1929 + "utf8_iter", 1930 + "yoke", 1931 + "zerofrom", 1932 + "zerovec", 1933 + ] 1934 + 1935 + [[package]] 1936 + name = "icu_locale_core" 1937 + version = "2.2.0" 1938 + source = "registry+https://github.com/rust-lang/crates.io-index" 1939 + checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" 1940 + dependencies = [ 1941 + "displaydoc", 1942 + "litemap", 1943 + "tinystr", 1944 + "writeable", 1945 + "zerovec", 1946 + ] 1947 + 1948 + [[package]] 1949 + name = "icu_normalizer" 1950 + version = "2.2.0" 1951 + source = "registry+https://github.com/rust-lang/crates.io-index" 1952 + checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" 1953 + dependencies = [ 1954 + "icu_collections", 1955 + "icu_normalizer_data", 1956 + "icu_properties", 1957 + "icu_provider", 1958 + "smallvec", 1959 + "zerovec", 1960 + ] 1961 + 1962 + [[package]] 1963 + name = "icu_normalizer_data" 1964 + version = "2.2.0" 1965 + source = "registry+https://github.com/rust-lang/crates.io-index" 1966 + checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" 1967 + 1968 + [[package]] 1969 + name = "icu_properties" 1970 + version = "2.2.0" 1971 + source = "registry+https://github.com/rust-lang/crates.io-index" 1972 + checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" 1973 + dependencies = [ 1974 + "icu_collections", 1975 + "icu_locale_core", 1976 + "icu_properties_data", 1977 + "icu_provider", 1978 + "zerotrie", 1979 + "zerovec", 1980 + ] 1981 + 1982 + [[package]] 1983 + name = "icu_properties_data" 1984 + version = "2.2.0" 1985 + source = "registry+https://github.com/rust-lang/crates.io-index" 1986 + checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" 1987 + 1988 + [[package]] 1989 + name = "icu_provider" 1990 + version = "2.2.0" 1991 + source = "registry+https://github.com/rust-lang/crates.io-index" 1992 + checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" 1993 + dependencies = [ 1994 + "displaydoc", 1995 + "icu_locale_core", 1996 + "writeable", 1997 + "yoke", 1998 + "zerofrom", 1999 + "zerotrie", 2000 + "zerovec", 2001 + ] 2002 + 2003 + [[package]] 2004 + name = "ident_case" 2005 + version = "1.0.1" 2006 + source = "registry+https://github.com/rust-lang/crates.io-index" 2007 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 2008 + 2009 + [[package]] 2010 + name = "idna" 2011 + version = "1.1.0" 2012 + source = "registry+https://github.com/rust-lang/crates.io-index" 2013 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 2014 + dependencies = [ 2015 + "idna_adapter", 2016 + "smallvec", 2017 + "utf8_iter", 2018 + ] 2019 + 2020 + [[package]] 2021 + name = "idna_adapter" 2022 + version = "1.2.2" 2023 + source = "registry+https://github.com/rust-lang/crates.io-index" 2024 + checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" 2025 + dependencies = [ 2026 + "icu_normalizer", 2027 + "icu_properties", 2028 + ] 2029 + 2030 + [[package]] 2031 + name = "indexmap" 2032 + version = "2.14.0" 2033 + source = "registry+https://github.com/rust-lang/crates.io-index" 2034 + checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" 2035 + dependencies = [ 2036 + "equivalent", 2037 + "hashbrown 0.17.1", 2038 + ] 2039 + 2040 + [[package]] 2041 + name = "indoc" 2042 + version = "2.0.7" 2043 + source = "registry+https://github.com/rust-lang/crates.io-index" 2044 + checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" 2045 + dependencies = [ 2046 + "rustversion", 2047 + ] 2048 + 2049 + [[package]] 2050 + name = "instability" 2051 + version = "0.3.12" 2052 + source = "registry+https://github.com/rust-lang/crates.io-index" 2053 + checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" 2054 + dependencies = [ 2055 + "darling", 2056 + "indoc", 2057 + "proc-macro2", 2058 + "quote", 2059 + "syn", 2060 + ] 2061 + 2062 + [[package]] 2063 + name = "inventory" 2064 + version = "0.3.24" 2065 + source = "registry+https://github.com/rust-lang/crates.io-index" 2066 + checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" 2067 + dependencies = [ 2068 + "rustversion", 2069 + ] 2070 + 2071 + [[package]] 2072 + name = "ipconfig" 2073 + version = "0.3.4" 2074 + source = "registry+https://github.com/rust-lang/crates.io-index" 2075 + checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" 2076 + dependencies = [ 2077 + "socket2", 2078 + "widestring", 2079 + "windows-registry", 2080 + "windows-result 0.4.1", 2081 + "windows-sys 0.61.2", 2082 + ] 2083 + 2084 + [[package]] 2085 + name = "ipld-core" 2086 + version = "0.4.3" 2087 + source = "registry+https://github.com/rust-lang/crates.io-index" 2088 + checksum = "090f624976d72f0b0bb71b86d58dc16c15e069193067cb3a3a09d655246cbbda" 2089 + dependencies = [ 2090 + "cid", 2091 + "serde", 2092 + "serde_bytes", 2093 + ] 2094 + 2095 + [[package]] 2096 + name = "ipnet" 2097 + version = "2.12.0" 2098 + source = "registry+https://github.com/rust-lang/crates.io-index" 2099 + checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" 2100 + 2101 + [[package]] 2102 + name = "is_terminal_polyfill" 2103 + version = "1.70.2" 2104 + source = "registry+https://github.com/rust-lang/crates.io-index" 2105 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 2106 + 2107 + [[package]] 2108 + name = "itertools" 2109 + version = "0.13.0" 2110 + source = "registry+https://github.com/rust-lang/crates.io-index" 2111 + checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 2112 + dependencies = [ 2113 + "either", 2114 + ] 2115 + 2116 + [[package]] 2117 + name = "itoa" 2118 + version = "1.0.18" 2119 + source = "registry+https://github.com/rust-lang/crates.io-index" 2120 + checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" 2121 + 2122 + [[package]] 2123 + name = "jacquard" 2124 + version = "0.12.1" 2125 + source = "registry+https://github.com/rust-lang/crates.io-index" 2126 + checksum = "b5055461df426c9d0ac379bcf8f59177ff4c29b4ea2b23bd98c773061b863bbe" 2127 + dependencies = [ 2128 + "bytes", 2129 + "getrandom 0.2.17", 2130 + "gloo-storage", 2131 + "http", 2132 + "jacquard-api", 2133 + "jacquard-common", 2134 + "jacquard-derive", 2135 + "jacquard-identity", 2136 + "jacquard-oauth", 2137 + "jose-jwk", 2138 + "miette", 2139 + "regex", 2140 + "regex-lite", 2141 + "reqwest", 2142 + "serde", 2143 + "serde_html_form", 2144 + "serde_json", 2145 + "smol_str", 2146 + "thiserror 2.0.18", 2147 + "tokio", 2148 + "trait-variant", 2149 + "webpage", 2150 + ] 2151 + 2152 + [[package]] 2153 + name = "jacquard-api" 2154 + version = "0.12.1" 2155 + source = "registry+https://github.com/rust-lang/crates.io-index" 2156 + checksum = "b5c803a3c097e3ef8aea63747b4fe3fc9e339cd18272dd0366b1d10dd90d5c3f" 2157 + dependencies = [ 2158 + "jacquard-common", 2159 + "jacquard-derive", 2160 + "jacquard-lexicon", 2161 + "miette", 2162 + "serde", 2163 + "thiserror 2.0.18", 2164 + ] 2165 + 2166 + [[package]] 2167 + name = "jacquard-common" 2168 + version = "0.12.1" 2169 + source = "registry+https://github.com/rust-lang/crates.io-index" 2170 + checksum = "fec8b6661e6fcfb4d8b9eb1132503be71bc3e3c6f6ff15fa5500ec62655f6da7" 2171 + dependencies = [ 2172 + "base64", 2173 + "bon", 2174 + "bytes", 2175 + "chrono", 2176 + "ciborium", 2177 + "ciborium-io", 2178 + "cid", 2179 + "fluent-uri", 2180 + "getrandom 0.2.17", 2181 + "getrandom 0.3.4", 2182 + "hashbrown 0.15.5", 2183 + "http", 2184 + "ipld-core", 2185 + "k256", 2186 + "maitake-sync", 2187 + "miette", 2188 + "multibase", 2189 + "multihash", 2190 + "oxilangtag", 2191 + "p256", 2192 + "phf", 2193 + "postcard", 2194 + "rand 0.9.5", 2195 + "regex", 2196 + "regex-automata", 2197 + "regex-lite", 2198 + "reqwest", 2199 + "rustversion", 2200 + "serde", 2201 + "serde_bytes", 2202 + "serde_html_form", 2203 + "serde_ipld_dagcbor", 2204 + "serde_json", 2205 + "signature", 2206 + "smol_str", 2207 + "spin 0.10.1", 2208 + "thiserror 2.0.18", 2209 + "tokio", 2210 + "tokio-util", 2211 + "trait-variant", 2212 + "unicode-segmentation", 2213 + ] 2214 + 2215 + [[package]] 2216 + name = "jacquard-derive" 2217 + version = "0.12.1" 2218 + source = "registry+https://github.com/rust-lang/crates.io-index" 2219 + checksum = "e41b82a9a72c0d2b907a5e0734e0692615e717cda903502c6c034ae6f3ddddf7" 2220 + dependencies = [ 2221 + "heck", 2222 + "jacquard-lexicon", 2223 + "proc-macro2", 2224 + "quote", 2225 + "syn", 2226 + ] 2227 + 2228 + [[package]] 2229 + name = "jacquard-identity" 2230 + version = "0.12.1" 2231 + source = "registry+https://github.com/rust-lang/crates.io-index" 2232 + checksum = "2c0a89c960d6c2245f26642830ecf85155eaf7395e5da8794173a78854f0e22b" 2233 + dependencies = [ 2234 + "bon", 2235 + "bytes", 2236 + "hickory-resolver", 2237 + "http", 2238 + "jacquard-common", 2239 + "jacquard-lexicon", 2240 + "miette", 2241 + "mini-moka-wasm", 2242 + "n0-future", 2243 + "reqwest", 2244 + "serde", 2245 + "serde_html_form", 2246 + "serde_json", 2247 + "thiserror 2.0.18", 2248 + "tokio", 2249 + "trait-variant", 2250 + ] 2251 + 2252 + [[package]] 2253 + name = "jacquard-lexicon" 2254 + version = "0.12.1" 2255 + source = "registry+https://github.com/rust-lang/crates.io-index" 2256 + checksum = "160049c269e3d7ec56f130736d25d61953fc6216da660c8b5e102f6b052dd3a1" 2257 + dependencies = [ 2258 + "cid", 2259 + "dashmap", 2260 + "heck", 2261 + "inventory", 2262 + "jacquard-common", 2263 + "miette", 2264 + "multihash", 2265 + "prettyplease", 2266 + "proc-macro2", 2267 + "quote", 2268 + "serde", 2269 + "serde_ipld_dagcbor", 2270 + "serde_json", 2271 + "serde_path_to_error", 2272 + "serde_repr", 2273 + "serde_with", 2274 + "sha2", 2275 + "syn", 2276 + "thiserror 2.0.18", 2277 + "unicode-segmentation", 2278 + ] 2279 + 2280 + [[package]] 2281 + name = "jacquard-oauth" 2282 + version = "0.12.1" 2283 + source = "registry+https://github.com/rust-lang/crates.io-index" 2284 + checksum = "f5da2643242cbe906e707712117eefcc37b351a5ff12909f944ef556e523bfd9" 2285 + dependencies = [ 2286 + "base64", 2287 + "bytes", 2288 + "chrono", 2289 + "dashmap", 2290 + "ed25519-dalek", 2291 + "elliptic-curve", 2292 + "http", 2293 + "jacquard-common", 2294 + "jacquard-identity", 2295 + "jose-jwa", 2296 + "jose-jwk", 2297 + "k256", 2298 + "miette", 2299 + "p256", 2300 + "p384", 2301 + "rand 0.8.7", 2302 + "reqwest", 2303 + "serde", 2304 + "serde_html_form", 2305 + "serde_json", 2306 + "sha2", 2307 + "smallvec", 2308 + "smol_str", 2309 + "thiserror 2.0.18", 2310 + "tokio", 2311 + "trait-variant", 2312 + "webbrowser", 2313 + ] 2314 + 2315 + [[package]] 2316 + name = "jni" 2317 + version = "0.21.1" 2318 + source = "registry+https://github.com/rust-lang/crates.io-index" 2319 + checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 2320 + dependencies = [ 2321 + "cesu8", 2322 + "cfg-if", 2323 + "combine", 2324 + "jni-sys 0.3.1", 2325 + "log", 2326 + "thiserror 1.0.69", 2327 + "walkdir", 2328 + "windows-sys 0.45.0", 2329 + ] 2330 + 2331 + [[package]] 2332 + name = "jni" 2333 + version = "0.22.4" 2334 + source = "registry+https://github.com/rust-lang/crates.io-index" 2335 + checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" 2336 + dependencies = [ 2337 + "cfg-if", 2338 + "combine", 2339 + "jni-macros", 2340 + "jni-sys 0.4.1", 2341 + "log", 2342 + "simd_cesu8", 2343 + "thiserror 2.0.18", 2344 + "walkdir", 2345 + "windows-link", 2346 + ] 2347 + 2348 + [[package]] 2349 + name = "jni-macros" 2350 + version = "0.22.4" 2351 + source = "registry+https://github.com/rust-lang/crates.io-index" 2352 + checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" 2353 + dependencies = [ 2354 + "proc-macro2", 2355 + "quote", 2356 + "rustc_version", 2357 + "simd_cesu8", 2358 + "syn", 2359 + ] 2360 + 2361 + [[package]] 2362 + name = "jni-sys" 2363 + version = "0.3.1" 2364 + source = "registry+https://github.com/rust-lang/crates.io-index" 2365 + checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" 2366 + dependencies = [ 2367 + "jni-sys 0.4.1", 2368 + ] 2369 + 2370 + [[package]] 2371 + name = "jni-sys" 2372 + version = "0.4.1" 2373 + source = "registry+https://github.com/rust-lang/crates.io-index" 2374 + checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" 2375 + dependencies = [ 2376 + "jni-sys-macros", 2377 + ] 2378 + 2379 + [[package]] 2380 + name = "jni-sys-macros" 2381 + version = "0.4.1" 2382 + source = "registry+https://github.com/rust-lang/crates.io-index" 2383 + checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" 2384 + dependencies = [ 2385 + "quote", 2386 + "syn", 2387 + ] 2388 + 2389 + [[package]] 2390 + name = "jobserver" 2391 + version = "0.1.35" 2392 + source = "registry+https://github.com/rust-lang/crates.io-index" 2393 + checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" 2394 + dependencies = [ 2395 + "getrandom 0.4.3", 2396 + "libc", 2397 + ] 2398 + 2399 + [[package]] 2400 + name = "jose-b64" 2401 + version = "0.1.2" 2402 + source = "registry+https://github.com/rust-lang/crates.io-index" 2403 + checksum = "bec69375368709666b21c76965ce67549f2d2db7605f1f8707d17c9656801b56" 2404 + dependencies = [ 2405 + "base64ct", 2406 + "serde", 2407 + "subtle", 2408 + "zeroize", 2409 + ] 2410 + 2411 + [[package]] 2412 + name = "jose-jwa" 2413 + version = "0.1.2" 2414 + source = "registry+https://github.com/rust-lang/crates.io-index" 2415 + checksum = "9ab78e053fe886a351d67cf0d194c000f9d0dcb92906eb34d853d7e758a4b3a7" 2416 + dependencies = [ 2417 + "serde", 2418 + ] 2419 + 2420 + [[package]] 2421 + name = "jose-jwk" 2422 + version = "0.1.2" 2423 + source = "registry+https://github.com/rust-lang/crates.io-index" 2424 + checksum = "280fa263807fe0782ecb6f2baadc28dffc04e00558a58e33bfdb801d11fd58e7" 2425 + dependencies = [ 2426 + "jose-b64", 2427 + "jose-jwa", 2428 + "p256", 2429 + "p384", 2430 + "rsa", 2431 + "serde", 2432 + "zeroize", 2433 + ] 2434 + 2435 + [[package]] 2436 + name = "js-sys" 2437 + version = "0.3.103" 2438 + source = "registry+https://github.com/rust-lang/crates.io-index" 2439 + checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" 2440 + dependencies = [ 2441 + "cfg-if", 2442 + "futures-util", 2443 + "wasm-bindgen", 2444 + ] 2445 + 2446 + [[package]] 2447 + name = "k256" 2448 + version = "0.13.4" 2449 + source = "registry+https://github.com/rust-lang/crates.io-index" 2450 + checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 2451 + dependencies = [ 2452 + "cfg-if", 2453 + "ecdsa", 2454 + "elliptic-curve", 2455 + "once_cell", 2456 + "sha2", 2457 + "signature", 2458 + ] 2459 + 2460 + [[package]] 2461 + name = "lazy_static" 2462 + version = "1.5.0" 2463 + source = "registry+https://github.com/rust-lang/crates.io-index" 2464 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 2465 + dependencies = [ 2466 + "spin 0.9.9", 2467 + ] 2468 + 2469 + [[package]] 2470 + name = "libc" 2471 + version = "0.2.186" 2472 + source = "registry+https://github.com/rust-lang/crates.io-index" 2473 + checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" 2474 + 2475 + [[package]] 2476 + name = "libloading" 2477 + version = "0.8.9" 2478 + source = "registry+https://github.com/rust-lang/crates.io-index" 2479 + checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" 2480 + dependencies = [ 2481 + "cfg-if", 2482 + "windows-link", 2483 + ] 2484 + 2485 + [[package]] 2486 + name = "libm" 2487 + version = "0.2.16" 2488 + source = "registry+https://github.com/rust-lang/crates.io-index" 2489 + checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" 2490 + 2491 + [[package]] 2492 + name = "libredox" 2493 + version = "0.1.18" 2494 + source = "registry+https://github.com/rust-lang/crates.io-index" 2495 + checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" 2496 + dependencies = [ 2497 + "libc", 2498 + ] 2499 + 2500 + [[package]] 2501 + name = "linked-hash-map" 2502 + version = "0.5.6" 2503 + source = "registry+https://github.com/rust-lang/crates.io-index" 2504 + checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 2505 + 2506 + [[package]] 2507 + name = "linux-raw-sys" 2508 + version = "0.4.15" 2509 + source = "registry+https://github.com/rust-lang/crates.io-index" 2510 + checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 2511 + 2512 + [[package]] 2513 + name = "linux-raw-sys" 2514 + version = "0.12.1" 2515 + source = "registry+https://github.com/rust-lang/crates.io-index" 2516 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 2517 + 2518 + [[package]] 2519 + name = "litemap" 2520 + version = "0.8.2" 2521 + source = "registry+https://github.com/rust-lang/crates.io-index" 2522 + checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" 2523 + 2524 + [[package]] 2525 + name = "lock_api" 2526 + version = "0.4.14" 2527 + source = "registry+https://github.com/rust-lang/crates.io-index" 2528 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 2529 + dependencies = [ 2530 + "scopeguard", 2531 + ] 2532 + 2533 + [[package]] 2534 + name = "log" 2535 + version = "0.4.33" 2536 + source = "registry+https://github.com/rust-lang/crates.io-index" 2537 + checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" 2538 + 2539 + [[package]] 2540 + name = "loom" 2541 + version = "0.7.2" 2542 + source = "registry+https://github.com/rust-lang/crates.io-index" 2543 + checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 2544 + dependencies = [ 2545 + "cfg-if", 2546 + "generator", 2547 + "scoped-tls", 2548 + "tracing", 2549 + "tracing-subscriber", 2550 + ] 2551 + 2552 + [[package]] 2553 + name = "lru" 2554 + version = "0.12.5" 2555 + source = "registry+https://github.com/rust-lang/crates.io-index" 2556 + checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 2557 + dependencies = [ 2558 + "hashbrown 0.15.5", 2559 + ] 2560 + 2561 + [[package]] 2562 + name = "lru-cache" 2563 + version = "0.1.2" 2564 + source = "registry+https://github.com/rust-lang/crates.io-index" 2565 + checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 2566 + dependencies = [ 2567 + "linked-hash-map", 2568 + ] 2569 + 2570 + [[package]] 2571 + name = "lru-slab" 2572 + version = "0.1.2" 2573 + source = "registry+https://github.com/rust-lang/crates.io-index" 2574 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 2575 + 2576 + [[package]] 2577 + name = "mac" 2578 + version = "0.1.1" 2579 + source = "registry+https://github.com/rust-lang/crates.io-index" 2580 + checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 2581 + 2582 + [[package]] 2583 + name = "mach2" 2584 + version = "0.4.3" 2585 + source = "registry+https://github.com/rust-lang/crates.io-index" 2586 + checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" 2587 + dependencies = [ 2588 + "libc", 2589 + ] 2590 + 2591 + [[package]] 2592 + name = "maitake-sync" 2593 + version = "0.1.2" 2594 + source = "registry+https://github.com/rust-lang/crates.io-index" 2595 + checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5" 2596 + dependencies = [ 2597 + "cordyceps", 2598 + "loom", 2599 + "mycelium-bitfield", 2600 + "pin-project", 2601 + "portable-atomic", 2602 + ] 2603 + 2604 + [[package]] 2605 + name = "markup5ever" 2606 + version = "0.12.1" 2607 + source = "registry+https://github.com/rust-lang/crates.io-index" 2608 + checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" 2609 + dependencies = [ 2610 + "log", 2611 + "phf", 2612 + "phf_codegen", 2613 + "string_cache", 2614 + "string_cache_codegen", 2615 + "tendril", 2616 + ] 2617 + 2618 + [[package]] 2619 + name = "markup5ever_rcdom" 2620 + version = "0.3.0" 2621 + source = "registry+https://github.com/rust-lang/crates.io-index" 2622 + checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" 2623 + dependencies = [ 2624 + "html5ever", 2625 + "markup5ever", 2626 + "tendril", 2627 + "xml5ever", 2628 + ] 2629 + 2630 + [[package]] 2631 + name = "match-lookup" 2632 + version = "0.1.2" 2633 + source = "registry+https://github.com/rust-lang/crates.io-index" 2634 + checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" 2635 + dependencies = [ 2636 + "proc-macro2", 2637 + "quote", 2638 + "syn", 2639 + ] 2640 + 2641 + [[package]] 2642 + name = "matchers" 2643 + version = "0.2.0" 2644 + source = "registry+https://github.com/rust-lang/crates.io-index" 2645 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 2646 + dependencies = [ 2647 + "regex-automata", 2648 + ] 2649 + 2650 + [[package]] 2651 + name = "memchr" 2652 + version = "2.8.3" 2653 + source = "registry+https://github.com/rust-lang/crates.io-index" 2654 + checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" 2655 + 2656 + [[package]] 2657 + name = "memoffset" 2658 + version = "0.9.1" 2659 + source = "registry+https://github.com/rust-lang/crates.io-index" 2660 + checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 2661 + dependencies = [ 2662 + "autocfg", 2663 + ] 2664 + 2665 + [[package]] 2666 + name = "miette" 2667 + version = "7.6.0" 2668 + source = "registry+https://github.com/rust-lang/crates.io-index" 2669 + checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" 2670 + dependencies = [ 2671 + "cfg-if", 2672 + "miette-derive", 2673 + "unicode-width 0.1.14", 2674 + ] 2675 + 2676 + [[package]] 2677 + name = "miette-derive" 2678 + version = "7.6.0" 2679 + source = "registry+https://github.com/rust-lang/crates.io-index" 2680 + checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" 2681 + dependencies = [ 2682 + "proc-macro2", 2683 + "quote", 2684 + "syn", 2685 + ] 2686 + 2687 + [[package]] 2688 + name = "mime" 2689 + version = "0.3.17" 2690 + source = "registry+https://github.com/rust-lang/crates.io-index" 2691 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 2692 + 2693 + [[package]] 2694 + name = "mini-moka-wasm" 2695 + version = "0.10.99" 2696 + source = "registry+https://github.com/rust-lang/crates.io-index" 2697 + checksum = "0102b9a2ad50fa47ca89eead2316c8222285ecfbd3f69ce99564fbe4253866e8" 2698 + dependencies = [ 2699 + "crossbeam-channel", 2700 + "crossbeam-utils", 2701 + "dashmap", 2702 + "smallvec", 2703 + "tagptr", 2704 + "triomphe", 2705 + ] 2706 + 2707 + [[package]] 2708 + name = "minimal-lexical" 2709 + version = "0.2.1" 2710 + source = "registry+https://github.com/rust-lang/crates.io-index" 2711 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 2712 + 2713 + [[package]] 2714 + name = "miniz_oxide" 2715 + version = "0.8.9" 2716 + source = "registry+https://github.com/rust-lang/crates.io-index" 2717 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 2718 + dependencies = [ 2719 + "adler2", 2720 + "simd-adler32", 2721 + ] 2722 + 2723 + [[package]] 2724 + name = "mio" 2725 + version = "1.2.2" 2726 + source = "registry+https://github.com/rust-lang/crates.io-index" 2727 + checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" 2728 + dependencies = [ 2729 + "libc", 2730 + "log", 2731 + "wasi", 2732 + "windows-sys 0.61.2", 2733 + ] 2734 + 2735 + [[package]] 2736 + name = "mpris-server" 2737 + version = "0.8.1" 2738 + source = "registry+https://github.com/rust-lang/crates.io-index" 2739 + checksum = "058bc2227727af394f34aa51da3e36aeecf2c808f39315d35f754872660750ae" 2740 + dependencies = [ 2741 + "async-channel", 2742 + "futures-channel", 2743 + "serde", 2744 + "trait-variant", 2745 + "zbus", 2746 + ] 2747 + 2748 + [[package]] 2749 + name = "multibase" 2750 + version = "0.9.3" 2751 + source = "registry+https://github.com/rust-lang/crates.io-index" 2752 + checksum = "7e0e4a371cbf1dfd666b658ba137763edb23c45beb43cfe369b5593cd6b437b6" 2753 + dependencies = [ 2754 + "base-x", 2755 + "base256emoji", 2756 + "base45", 2757 + "data-encoding", 2758 + "data-encoding-macro", 2759 + ] 2760 + 2761 + [[package]] 2762 + name = "multihash" 2763 + version = "0.19.5" 2764 + source = "registry+https://github.com/rust-lang/crates.io-index" 2765 + checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447" 2766 + dependencies = [ 2767 + "serde", 2768 + "unsigned-varint", 2769 + ] 2770 + 2771 + [[package]] 2772 + name = "mycelium-bitfield" 2773 + version = "0.1.5" 2774 + source = "registry+https://github.com/rust-lang/crates.io-index" 2775 + checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" 2776 + 2777 + [[package]] 2778 + name = "n0-future" 2779 + version = "0.1.3" 2780 + source = "registry+https://github.com/rust-lang/crates.io-index" 2781 + checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" 2782 + dependencies = [ 2783 + "cfg_aliases", 2784 + "derive_more", 2785 + "futures-buffered", 2786 + "futures-lite", 2787 + "futures-util", 2788 + "js-sys", 2789 + "pin-project", 2790 + "send_wrapper", 2791 + "tokio", 2792 + "tokio-util", 2793 + "wasm-bindgen", 2794 + "wasm-bindgen-futures", 2795 + "web-time", 2796 + ] 2797 + 2798 + [[package]] 2799 + name = "ndk" 2800 + version = "0.8.0" 2801 + source = "registry+https://github.com/rust-lang/crates.io-index" 2802 + checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" 2803 + dependencies = [ 2804 + "bitflags 2.13.1", 2805 + "jni-sys 0.3.1", 2806 + "log", 2807 + "ndk-sys", 2808 + "num_enum", 2809 + "thiserror 1.0.69", 2810 + ] 2811 + 2812 + [[package]] 2813 + name = "ndk-context" 2814 + version = "0.1.1" 2815 + source = "registry+https://github.com/rust-lang/crates.io-index" 2816 + checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 2817 + 2818 + [[package]] 2819 + name = "ndk-sys" 2820 + version = "0.5.0+25.2.9519653" 2821 + source = "registry+https://github.com/rust-lang/crates.io-index" 2822 + checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" 2823 + dependencies = [ 2824 + "jni-sys 0.3.1", 2825 + ] 2826 + 2827 + [[package]] 2828 + name = "new_debug_unreachable" 2829 + version = "1.0.6" 2830 + source = "registry+https://github.com/rust-lang/crates.io-index" 2831 + checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 2832 + 2833 + [[package]] 2834 + name = "nix" 2835 + version = "0.29.0" 2836 + source = "registry+https://github.com/rust-lang/crates.io-index" 2837 + checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 2838 + dependencies = [ 2839 + "bitflags 2.13.1", 2840 + "cfg-if", 2841 + "cfg_aliases", 2842 + "libc", 2843 + "memoffset", 2844 + ] 2845 + 2846 + [[package]] 2847 + name = "nom" 2848 + version = "7.1.3" 2849 + source = "registry+https://github.com/rust-lang/crates.io-index" 2850 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 2851 + dependencies = [ 2852 + "memchr", 2853 + "minimal-lexical", 2854 + ] 2855 + 2856 + [[package]] 2857 + name = "nu-ansi-term" 2858 + version = "0.50.3" 2859 + source = "registry+https://github.com/rust-lang/crates.io-index" 2860 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 2861 + dependencies = [ 2862 + "windows-sys 0.61.2", 2863 + ] 2864 + 2865 + [[package]] 2866 + name = "num-bigint-dig" 2867 + version = "0.8.6" 2868 + source = "registry+https://github.com/rust-lang/crates.io-index" 2869 + checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" 2870 + dependencies = [ 2871 + "lazy_static", 2872 + "libm", 2873 + "num-integer", 2874 + "num-iter", 2875 + "num-traits", 2876 + "rand 0.8.7", 2877 + "smallvec", 2878 + "zeroize", 2879 + ] 2880 + 2881 + [[package]] 2882 + name = "num-conv" 2883 + version = "0.2.2" 2884 + source = "registry+https://github.com/rust-lang/crates.io-index" 2885 + checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" 2886 + 2887 + [[package]] 2888 + name = "num-derive" 2889 + version = "0.4.2" 2890 + source = "registry+https://github.com/rust-lang/crates.io-index" 2891 + checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 2892 + dependencies = [ 2893 + "proc-macro2", 2894 + "quote", 2895 + "syn", 2896 + ] 2897 + 2898 + [[package]] 2899 + name = "num-integer" 2900 + version = "0.1.46" 2901 + source = "registry+https://github.com/rust-lang/crates.io-index" 2902 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 2903 + dependencies = [ 2904 + "num-traits", 2905 + ] 2906 + 2907 + [[package]] 2908 + name = "num-iter" 2909 + version = "0.1.46" 2910 + source = "registry+https://github.com/rust-lang/crates.io-index" 2911 + checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" 2912 + dependencies = [ 2913 + "num-integer", 2914 + "num-traits", 2915 + ] 2916 + 2917 + [[package]] 2918 + name = "num-traits" 2919 + version = "0.2.19" 2920 + source = "registry+https://github.com/rust-lang/crates.io-index" 2921 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 2922 + dependencies = [ 2923 + "autocfg", 2924 + "libm", 2925 + ] 2926 + 2927 + [[package]] 2928 + name = "num_enum" 2929 + version = "0.7.6" 2930 + source = "registry+https://github.com/rust-lang/crates.io-index" 2931 + checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" 2932 + dependencies = [ 2933 + "num_enum_derive", 2934 + "rustversion", 2935 + ] 2936 + 2937 + [[package]] 2938 + name = "num_enum_derive" 2939 + version = "0.7.6" 2940 + source = "registry+https://github.com/rust-lang/crates.io-index" 2941 + checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" 2942 + dependencies = [ 2943 + "proc-macro-crate", 2944 + "proc-macro2", 2945 + "quote", 2946 + "syn", 2947 + ] 2948 + 2949 + [[package]] 2950 + name = "objc2" 2951 + version = "0.6.4" 2952 + source = "registry+https://github.com/rust-lang/crates.io-index" 2953 + checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" 2954 + dependencies = [ 2955 + "objc2-encode", 2956 + ] 2957 + 2958 + [[package]] 2959 + name = "objc2-encode" 2960 + version = "4.1.0" 2961 + source = "registry+https://github.com/rust-lang/crates.io-index" 2962 + checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 2963 + 2964 + [[package]] 2965 + name = "objc2-foundation" 2966 + version = "0.3.2" 2967 + source = "registry+https://github.com/rust-lang/crates.io-index" 2968 + checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" 2969 + dependencies = [ 2970 + "bitflags 2.13.1", 2971 + "objc2", 2972 + ] 2973 + 2974 + [[package]] 2975 + name = "oboe" 2976 + version = "0.6.1" 2977 + source = "registry+https://github.com/rust-lang/crates.io-index" 2978 + checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" 2979 + dependencies = [ 2980 + "jni 0.21.1", 2981 + "ndk", 2982 + "ndk-context", 2983 + "num-derive", 2984 + "num-traits", 2985 + "oboe-sys", 2986 + ] 2987 + 2988 + [[package]] 2989 + name = "oboe-sys" 2990 + version = "0.6.1" 2991 + source = "registry+https://github.com/rust-lang/crates.io-index" 2992 + checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" 2993 + dependencies = [ 2994 + "cc", 2995 + ] 2996 + 2997 + [[package]] 2998 + name = "once_cell" 2999 + version = "1.21.4" 3000 + source = "registry+https://github.com/rust-lang/crates.io-index" 3001 + checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" 3002 + 3003 + [[package]] 3004 + name = "once_cell_polyfill" 3005 + version = "1.70.2" 3006 + source = "registry+https://github.com/rust-lang/crates.io-index" 3007 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 3008 + 3009 + [[package]] 3010 + name = "openssl-probe" 3011 + version = "0.2.1" 3012 + source = "registry+https://github.com/rust-lang/crates.io-index" 3013 + checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 3014 + 3015 + [[package]] 3016 + name = "option-ext" 3017 + version = "0.2.0" 3018 + source = "registry+https://github.com/rust-lang/crates.io-index" 3019 + checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 3020 + 3021 + [[package]] 3022 + name = "ordered-stream" 3023 + version = "0.2.0" 3024 + source = "registry+https://github.com/rust-lang/crates.io-index" 3025 + checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" 3026 + dependencies = [ 3027 + "futures-core", 3028 + "pin-project-lite", 3029 + ] 3030 + 3031 + [[package]] 3032 + name = "oxilangtag" 3033 + version = "0.1.6" 3034 + source = "registry+https://github.com/rust-lang/crates.io-index" 3035 + checksum = "5d3b4eb570abd4a1dcb062c31fd37b832264d9dc7292c3e69acfe926c87b063f" 3036 + dependencies = [ 3037 + "serde", 3038 + ] 3039 + 3040 + [[package]] 3041 + name = "p256" 3042 + version = "0.13.2" 3043 + source = "registry+https://github.com/rust-lang/crates.io-index" 3044 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 3045 + dependencies = [ 3046 + "ecdsa", 3047 + "elliptic-curve", 3048 + "primeorder", 3049 + "sha2", 3050 + ] 3051 + 3052 + [[package]] 3053 + name = "p384" 3054 + version = "0.13.1" 3055 + source = "registry+https://github.com/rust-lang/crates.io-index" 3056 + checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" 3057 + dependencies = [ 3058 + "ecdsa", 3059 + "elliptic-curve", 3060 + "primeorder", 3061 + "sha2", 3062 + ] 3063 + 3064 + [[package]] 3065 + name = "parking" 3066 + version = "2.2.1" 3067 + source = "registry+https://github.com/rust-lang/crates.io-index" 3068 + checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 3069 + 3070 + [[package]] 3071 + name = "parking_lot" 3072 + version = "0.12.5" 3073 + source = "registry+https://github.com/rust-lang/crates.io-index" 3074 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 3075 + dependencies = [ 3076 + "lock_api", 3077 + "parking_lot_core", 3078 + ] 3079 + 3080 + [[package]] 3081 + name = "parking_lot_core" 3082 + version = "0.9.12" 3083 + source = "registry+https://github.com/rust-lang/crates.io-index" 3084 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 3085 + dependencies = [ 3086 + "cfg-if", 3087 + "libc", 3088 + "redox_syscall", 3089 + "smallvec", 3090 + "windows-link", 3091 + ] 3092 + 3093 + [[package]] 3094 + name = "paste" 3095 + version = "1.0.15" 3096 + source = "registry+https://github.com/rust-lang/crates.io-index" 3097 + checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 3098 + 3099 + [[package]] 3100 + name = "pem-rfc7468" 3101 + version = "0.7.0" 3102 + source = "registry+https://github.com/rust-lang/crates.io-index" 3103 + checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 3104 + dependencies = [ 3105 + "base64ct", 3106 + ] 3107 + 3108 + [[package]] 3109 + name = "percent-encoding" 3110 + version = "2.3.2" 3111 + source = "registry+https://github.com/rust-lang/crates.io-index" 3112 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 3113 + 3114 + [[package]] 3115 + name = "phf" 3116 + version = "0.11.3" 3117 + source = "registry+https://github.com/rust-lang/crates.io-index" 3118 + checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" 3119 + dependencies = [ 3120 + "phf_macros", 3121 + "phf_shared", 3122 + ] 3123 + 3124 + [[package]] 3125 + name = "phf_codegen" 3126 + version = "0.11.3" 3127 + source = "registry+https://github.com/rust-lang/crates.io-index" 3128 + checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" 3129 + dependencies = [ 3130 + "phf_generator", 3131 + "phf_shared", 3132 + ] 3133 + 3134 + [[package]] 3135 + name = "phf_generator" 3136 + version = "0.11.3" 3137 + source = "registry+https://github.com/rust-lang/crates.io-index" 3138 + checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 3139 + dependencies = [ 3140 + "phf_shared", 3141 + "rand 0.8.7", 3142 + ] 3143 + 3144 + [[package]] 3145 + name = "phf_macros" 3146 + version = "0.11.3" 3147 + source = "registry+https://github.com/rust-lang/crates.io-index" 3148 + checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" 3149 + dependencies = [ 3150 + "phf_generator", 3151 + "phf_shared", 3152 + "proc-macro2", 3153 + "quote", 3154 + "syn", 3155 + ] 3156 + 3157 + [[package]] 3158 + name = "phf_shared" 3159 + version = "0.11.3" 3160 + source = "registry+https://github.com/rust-lang/crates.io-index" 3161 + checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 3162 + dependencies = [ 3163 + "siphasher", 3164 + ] 3165 + 3166 + [[package]] 3167 + name = "pin-project" 3168 + version = "1.1.13" 3169 + source = "registry+https://github.com/rust-lang/crates.io-index" 3170 + checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" 3171 + dependencies = [ 3172 + "pin-project-internal", 3173 + ] 3174 + 3175 + [[package]] 3176 + name = "pin-project-internal" 3177 + version = "1.1.13" 3178 + source = "registry+https://github.com/rust-lang/crates.io-index" 3179 + checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" 3180 + dependencies = [ 3181 + "proc-macro2", 3182 + "quote", 3183 + "syn", 3184 + ] 3185 + 3186 + [[package]] 3187 + name = "pin-project-lite" 3188 + version = "0.2.17" 3189 + source = "registry+https://github.com/rust-lang/crates.io-index" 3190 + checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 3191 + 3192 + [[package]] 3193 + name = "piper" 3194 + version = "0.2.5" 3195 + source = "registry+https://github.com/rust-lang/crates.io-index" 3196 + checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" 3197 + dependencies = [ 3198 + "atomic-waker", 3199 + "fastrand", 3200 + "futures-io", 3201 + ] 3202 + 3203 + [[package]] 3204 + name = "pkcs1" 3205 + version = "0.7.5" 3206 + source = "registry+https://github.com/rust-lang/crates.io-index" 3207 + checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 3208 + dependencies = [ 3209 + "der", 3210 + "pkcs8", 3211 + "spki", 3212 + ] 3213 + 3214 + [[package]] 3215 + name = "pkcs8" 3216 + version = "0.10.2" 3217 + source = "registry+https://github.com/rust-lang/crates.io-index" 3218 + checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 3219 + dependencies = [ 3220 + "der", 3221 + "spki", 3222 + ] 3223 + 3224 + [[package]] 3225 + name = "pkg-config" 3226 + version = "0.3.33" 3227 + source = "registry+https://github.com/rust-lang/crates.io-index" 3228 + checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" 3229 + 3230 + [[package]] 3231 + name = "polling" 3232 + version = "3.11.0" 3233 + source = "registry+https://github.com/rust-lang/crates.io-index" 3234 + checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" 3235 + dependencies = [ 3236 + "cfg-if", 3237 + "concurrent-queue", 3238 + "hermit-abi", 3239 + "pin-project-lite", 3240 + "rustix 1.1.4", 3241 + "windows-sys 0.61.2", 3242 + ] 3243 + 3244 + [[package]] 3245 + name = "portable-atomic" 3246 + version = "1.13.1" 3247 + source = "registry+https://github.com/rust-lang/crates.io-index" 3248 + checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" 3249 + 3250 + [[package]] 3251 + name = "postcard" 3252 + version = "1.1.3" 3253 + source = "registry+https://github.com/rust-lang/crates.io-index" 3254 + checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" 3255 + dependencies = [ 3256 + "cobs", 3257 + "embedded-io 0.4.0", 3258 + "embedded-io 0.6.1", 3259 + "heapless", 3260 + "serde", 3261 + ] 3262 + 3263 + [[package]] 3264 + name = "potential_utf" 3265 + version = "0.1.5" 3266 + source = "registry+https://github.com/rust-lang/crates.io-index" 3267 + checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" 3268 + dependencies = [ 3269 + "zerovec", 3270 + ] 3271 + 3272 + [[package]] 3273 + name = "powerfmt" 3274 + version = "0.2.0" 3275 + source = "registry+https://github.com/rust-lang/crates.io-index" 3276 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 3277 + 3278 + [[package]] 3279 + name = "ppv-lite86" 3280 + version = "0.2.21" 3281 + source = "registry+https://github.com/rust-lang/crates.io-index" 3282 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 3283 + dependencies = [ 3284 + "zerocopy", 3285 + ] 3286 + 3287 + [[package]] 3288 + name = "precomputed-hash" 3289 + version = "0.1.1" 3290 + source = "registry+https://github.com/rust-lang/crates.io-index" 3291 + checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 3292 + 3293 + [[package]] 3294 + name = "prettyplease" 3295 + version = "0.2.37" 3296 + source = "registry+https://github.com/rust-lang/crates.io-index" 3297 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 3298 + dependencies = [ 3299 + "proc-macro2", 3300 + "syn", 3301 + ] 3302 + 3303 + [[package]] 3304 + name = "primeorder" 3305 + version = "0.13.6" 3306 + source = "registry+https://github.com/rust-lang/crates.io-index" 3307 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 3308 + dependencies = [ 3309 + "elliptic-curve", 3310 + ] 3311 + 3312 + [[package]] 3313 + name = "proc-macro-crate" 3314 + version = "3.5.0" 3315 + source = "registry+https://github.com/rust-lang/crates.io-index" 3316 + checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" 3317 + dependencies = [ 3318 + "toml_edit 0.25.13+spec-1.1.0", 3319 + ] 3320 + 3321 + [[package]] 3322 + name = "proc-macro2" 3323 + version = "1.0.106" 3324 + source = "registry+https://github.com/rust-lang/crates.io-index" 3325 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 3326 + dependencies = [ 3327 + "unicode-ident", 3328 + ] 3329 + 3330 + [[package]] 3331 + name = "quinn" 3332 + version = "0.11.11" 3333 + source = "registry+https://github.com/rust-lang/crates.io-index" 3334 + checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" 3335 + dependencies = [ 3336 + "bytes", 3337 + "cfg_aliases", 3338 + "pin-project-lite", 3339 + "quinn-proto", 3340 + "quinn-udp", 3341 + "rustc-hash", 3342 + "rustls", 3343 + "socket2", 3344 + "thiserror 2.0.18", 3345 + "tokio", 3346 + "tracing", 3347 + "web-time", 3348 + ] 3349 + 3350 + [[package]] 3351 + name = "quinn-proto" 3352 + version = "0.11.16" 3353 + source = "registry+https://github.com/rust-lang/crates.io-index" 3354 + checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" 3355 + dependencies = [ 3356 + "bytes", 3357 + "getrandom 0.4.3", 3358 + "lru-slab", 3359 + "rand 0.10.2", 3360 + "rand_pcg", 3361 + "ring", 3362 + "rustc-hash", 3363 + "rustls", 3364 + "rustls-pki-types", 3365 + "slab", 3366 + "thiserror 2.0.18", 3367 + "tinyvec", 3368 + "tracing", 3369 + "web-time", 3370 + ] 3371 + 3372 + [[package]] 3373 + name = "quinn-udp" 3374 + version = "0.5.15" 3375 + source = "registry+https://github.com/rust-lang/crates.io-index" 3376 + checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" 3377 + dependencies = [ 3378 + "cfg_aliases", 3379 + "libc", 3380 + "once_cell", 3381 + "socket2", 3382 + "tracing", 3383 + "windows-sys 0.61.2", 3384 + ] 3385 + 3386 + [[package]] 3387 + name = "quote" 3388 + version = "1.0.46" 3389 + source = "registry+https://github.com/rust-lang/crates.io-index" 3390 + checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" 3391 + dependencies = [ 3392 + "proc-macro2", 3393 + ] 3394 + 3395 + [[package]] 3396 + name = "r-efi" 3397 + version = "5.3.0" 3398 + source = "registry+https://github.com/rust-lang/crates.io-index" 3399 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 3400 + 3401 + [[package]] 3402 + name = "r-efi" 3403 + version = "6.0.0" 3404 + source = "registry+https://github.com/rust-lang/crates.io-index" 3405 + checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" 3406 + 3407 + [[package]] 3408 + name = "rand" 3409 + version = "0.8.7" 3410 + source = "registry+https://github.com/rust-lang/crates.io-index" 3411 + checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" 3412 + dependencies = [ 3413 + "libc", 3414 + "rand_chacha 0.3.1", 3415 + "rand_core 0.6.4", 3416 + ] 3417 + 3418 + [[package]] 3419 + name = "rand" 3420 + version = "0.9.5" 3421 + source = "registry+https://github.com/rust-lang/crates.io-index" 3422 + checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" 3423 + dependencies = [ 3424 + "rand_chacha 0.9.0", 3425 + "rand_core 0.9.5", 3426 + ] 3427 + 3428 + [[package]] 3429 + name = "rand" 3430 + version = "0.10.2" 3431 + source = "registry+https://github.com/rust-lang/crates.io-index" 3432 + checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" 3433 + dependencies = [ 3434 + "chacha20", 3435 + "getrandom 0.4.3", 3436 + "rand_core 0.10.1", 3437 + ] 3438 + 3439 + [[package]] 3440 + name = "rand_chacha" 3441 + version = "0.3.1" 3442 + source = "registry+https://github.com/rust-lang/crates.io-index" 3443 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 3444 + dependencies = [ 3445 + "ppv-lite86", 3446 + "rand_core 0.6.4", 3447 + ] 3448 + 3449 + [[package]] 3450 + name = "rand_chacha" 3451 + version = "0.9.0" 3452 + source = "registry+https://github.com/rust-lang/crates.io-index" 3453 + checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 3454 + dependencies = [ 3455 + "ppv-lite86", 3456 + "rand_core 0.9.5", 3457 + ] 3458 + 3459 + [[package]] 3460 + name = "rand_core" 3461 + version = "0.6.4" 3462 + source = "registry+https://github.com/rust-lang/crates.io-index" 3463 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 3464 + dependencies = [ 3465 + "getrandom 0.2.17", 3466 + ] 3467 + 3468 + [[package]] 3469 + name = "rand_core" 3470 + version = "0.9.5" 3471 + source = "registry+https://github.com/rust-lang/crates.io-index" 3472 + checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" 3473 + dependencies = [ 3474 + "getrandom 0.3.4", 3475 + ] 3476 + 3477 + [[package]] 3478 + name = "rand_core" 3479 + version = "0.10.1" 3480 + source = "registry+https://github.com/rust-lang/crates.io-index" 3481 + checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" 3482 + 3483 + [[package]] 3484 + name = "rand_pcg" 3485 + version = "0.10.2" 3486 + source = "registry+https://github.com/rust-lang/crates.io-index" 3487 + checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" 3488 + dependencies = [ 3489 + "rand_core 0.10.1", 3490 + ] 3491 + 3492 + [[package]] 3493 + name = "ratatui" 3494 + version = "0.29.0" 3495 + source = "registry+https://github.com/rust-lang/crates.io-index" 3496 + checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 3497 + dependencies = [ 3498 + "bitflags 2.13.1", 3499 + "cassowary", 3500 + "compact_str", 3501 + "crossterm", 3502 + "indoc", 3503 + "instability", 3504 + "itertools", 3505 + "lru", 3506 + "paste", 3507 + "strum", 3508 + "unicode-segmentation", 3509 + "unicode-truncate", 3510 + "unicode-width 0.2.0", 3511 + ] 3512 + 3513 + [[package]] 3514 + name = "redox_syscall" 3515 + version = "0.5.18" 3516 + source = "registry+https://github.com/rust-lang/crates.io-index" 3517 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 3518 + dependencies = [ 3519 + "bitflags 2.13.1", 3520 + ] 3521 + 3522 + [[package]] 3523 + name = "redox_users" 3524 + version = "0.4.6" 3525 + source = "registry+https://github.com/rust-lang/crates.io-index" 3526 + checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 3527 + dependencies = [ 3528 + "getrandom 0.2.17", 3529 + "libredox", 3530 + "thiserror 1.0.69", 3531 + ] 3532 + 3533 + [[package]] 3534 + name = "ref-cast" 3535 + version = "1.0.25" 3536 + source = "registry+https://github.com/rust-lang/crates.io-index" 3537 + checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" 3538 + dependencies = [ 3539 + "ref-cast-impl", 3540 + ] 3541 + 3542 + [[package]] 3543 + name = "ref-cast-impl" 3544 + version = "1.0.25" 3545 + source = "registry+https://github.com/rust-lang/crates.io-index" 3546 + checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" 3547 + dependencies = [ 3548 + "proc-macro2", 3549 + "quote", 3550 + "syn", 3551 + ] 3552 + 3553 + [[package]] 3554 + name = "regex" 3555 + version = "1.13.1" 3556 + source = "registry+https://github.com/rust-lang/crates.io-index" 3557 + checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" 3558 + dependencies = [ 3559 + "aho-corasick", 3560 + "memchr", 3561 + "regex-automata", 3562 + "regex-syntax", 3563 + ] 3564 + 3565 + [[package]] 3566 + name = "regex-automata" 3567 + version = "0.4.16" 3568 + source = "registry+https://github.com/rust-lang/crates.io-index" 3569 + checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" 3570 + dependencies = [ 3571 + "aho-corasick", 3572 + "memchr", 3573 + "regex-syntax", 3574 + ] 3575 + 3576 + [[package]] 3577 + name = "regex-lite" 3578 + version = "0.1.9" 3579 + source = "registry+https://github.com/rust-lang/crates.io-index" 3580 + checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" 3581 + 3582 + [[package]] 3583 + name = "regex-syntax" 3584 + version = "0.8.11" 3585 + source = "registry+https://github.com/rust-lang/crates.io-index" 3586 + checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" 3587 + 3588 + [[package]] 3589 + name = "reqwest" 3590 + version = "0.12.28" 3591 + source = "registry+https://github.com/rust-lang/crates.io-index" 3592 + checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 3593 + dependencies = [ 3594 + "base64", 3595 + "bytes", 3596 + "encoding_rs", 3597 + "futures-channel", 3598 + "futures-core", 3599 + "futures-util", 3600 + "h2", 3601 + "http", 3602 + "http-body", 3603 + "http-body-util", 3604 + "hyper", 3605 + "hyper-rustls", 3606 + "hyper-util", 3607 + "js-sys", 3608 + "log", 3609 + "mime", 3610 + "percent-encoding", 3611 + "pin-project-lite", 3612 + "quinn", 3613 + "rustls", 3614 + "rustls-native-certs", 3615 + "rustls-pki-types", 3616 + "serde", 3617 + "serde_json", 3618 + "serde_urlencoded", 3619 + "sync_wrapper", 3620 + "tokio", 3621 + "tokio-rustls", 3622 + "tokio-util", 3623 + "tower", 3624 + "tower-http", 3625 + "tower-service", 3626 + "url", 3627 + "wasm-bindgen", 3628 + "wasm-bindgen-futures", 3629 + "wasm-streams", 3630 + "web-sys", 3631 + "webpki-roots", 3632 + ] 3633 + 3634 + [[package]] 3635 + name = "resolv-conf" 3636 + version = "0.7.6" 3637 + source = "registry+https://github.com/rust-lang/crates.io-index" 3638 + checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" 3639 + 3640 + [[package]] 3641 + name = "rfc6979" 3642 + version = "0.4.0" 3643 + source = "registry+https://github.com/rust-lang/crates.io-index" 3644 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 3645 + dependencies = [ 3646 + "hmac", 3647 + "subtle", 3648 + ] 3649 + 3650 + [[package]] 3651 + name = "ring" 3652 + version = "0.17.14" 3653 + source = "registry+https://github.com/rust-lang/crates.io-index" 3654 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 3655 + dependencies = [ 3656 + "cc", 3657 + "cfg-if", 3658 + "getrandom 0.2.17", 3659 + "libc", 3660 + "untrusted", 3661 + "windows-sys 0.52.0", 3662 + ] 3663 + 3664 + [[package]] 3665 + name = "rockbox-codecs" 3666 + version = "0.2.1" 3667 + source = "registry+https://github.com/rust-lang/crates.io-index" 3668 + checksum = "96e85d18d48da42648993df4067ad25dabe38a255dee7ab808292cb71978591c" 3669 + dependencies = [ 3670 + "cc", 3671 + "rockbox-metadata", 3672 + ] 3673 + 3674 + [[package]] 3675 + name = "rockbox-dsp" 3676 + version = "0.3.0" 3677 + source = "registry+https://github.com/rust-lang/crates.io-index" 3678 + checksum = "413a59a41ed9e936c283b28d494c22d19e42c1043292a920b84b3ff0bd5af6c2" 3679 + dependencies = [ 3680 + "cc", 3681 + ] 3682 + 3683 + [[package]] 3684 + name = "rockbox-metadata" 3685 + version = "0.1.0" 3686 + source = "registry+https://github.com/rust-lang/crates.io-index" 3687 + checksum = "a4d438a91e9bb4fe2243acfef1c135a229b6a64f95f48b0d7eaf7a936e5abb92" 3688 + dependencies = [ 3689 + "cc", 3690 + ] 3691 + 3692 + [[package]] 3693 + name = "rockbox-playback" 3694 + version = "0.4.1" 3695 + source = "registry+https://github.com/rust-lang/crates.io-index" 3696 + checksum = "e813d4ac60af94b8cdeed199a4d6eec687e34901776083ea929a984b31939c48" 3697 + dependencies = [ 3698 + "cpal", 3699 + "reqwest", 3700 + "rockbox-codecs", 3701 + "rockbox-dsp", 3702 + "rockbox-metadata", 3703 + "tempfile", 3704 + ] 3705 + 3706 + [[package]] 3707 + name = "rsa" 3708 + version = "0.9.10" 3709 + source = "registry+https://github.com/rust-lang/crates.io-index" 3710 + checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" 3711 + dependencies = [ 3712 + "const-oid", 3713 + "digest", 3714 + "num-bigint-dig", 3715 + "num-integer", 3716 + "num-traits", 3717 + "pkcs1", 3718 + "pkcs8", 3719 + "rand_core 0.6.4", 3720 + "signature", 3721 + "spki", 3722 + "subtle", 3723 + "zeroize", 3724 + ] 3725 + 3726 + [[package]] 3727 + name = "rustc-hash" 3728 + version = "2.1.3" 3729 + source = "registry+https://github.com/rust-lang/crates.io-index" 3730 + checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" 3731 + 3732 + [[package]] 3733 + name = "rustc_version" 3734 + version = "0.4.1" 3735 + source = "registry+https://github.com/rust-lang/crates.io-index" 3736 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 3737 + dependencies = [ 3738 + "semver", 3739 + ] 3740 + 3741 + [[package]] 3742 + name = "rustix" 3743 + version = "0.38.44" 3744 + source = "registry+https://github.com/rust-lang/crates.io-index" 3745 + checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 3746 + dependencies = [ 3747 + "bitflags 2.13.1", 3748 + "errno", 3749 + "libc", 3750 + "linux-raw-sys 0.4.15", 3751 + "windows-sys 0.59.0", 3752 + ] 3753 + 3754 + [[package]] 3755 + name = "rustix" 3756 + version = "1.1.4" 3757 + source = "registry+https://github.com/rust-lang/crates.io-index" 3758 + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 3759 + dependencies = [ 3760 + "bitflags 2.13.1", 3761 + "errno", 3762 + "libc", 3763 + "linux-raw-sys 0.12.1", 3764 + "windows-sys 0.61.2", 3765 + ] 3766 + 3767 + [[package]] 3768 + name = "rustls" 3769 + version = "0.23.42" 3770 + source = "registry+https://github.com/rust-lang/crates.io-index" 3771 + checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" 3772 + dependencies = [ 3773 + "once_cell", 3774 + "ring", 3775 + "rustls-pki-types", 3776 + "rustls-webpki", 3777 + "subtle", 3778 + "zeroize", 3779 + ] 3780 + 3781 + [[package]] 3782 + name = "rustls-native-certs" 3783 + version = "0.8.4" 3784 + source = "registry+https://github.com/rust-lang/crates.io-index" 3785 + checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" 3786 + dependencies = [ 3787 + "openssl-probe", 3788 + "rustls-pki-types", 3789 + "schannel", 3790 + "security-framework", 3791 + ] 3792 + 3793 + [[package]] 3794 + name = "rustls-pki-types" 3795 + version = "1.15.0" 3796 + source = "registry+https://github.com/rust-lang/crates.io-index" 3797 + checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" 3798 + dependencies = [ 3799 + "web-time", 3800 + "zeroize", 3801 + ] 3802 + 3803 + [[package]] 3804 + name = "rustls-webpki" 3805 + version = "0.103.13" 3806 + source = "registry+https://github.com/rust-lang/crates.io-index" 3807 + checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" 3808 + dependencies = [ 3809 + "ring", 3810 + "rustls-pki-types", 3811 + "untrusted", 3812 + ] 3813 + 3814 + [[package]] 3815 + name = "rustversion" 3816 + version = "1.0.23" 3817 + source = "registry+https://github.com/rust-lang/crates.io-index" 3818 + checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" 3819 + 3820 + [[package]] 3821 + name = "ryu" 3822 + version = "1.0.23" 3823 + source = "registry+https://github.com/rust-lang/crates.io-index" 3824 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 3825 + 3826 + [[package]] 3827 + name = "same-file" 3828 + version = "1.0.6" 3829 + source = "registry+https://github.com/rust-lang/crates.io-index" 3830 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 3831 + dependencies = [ 3832 + "winapi-util", 3833 + ] 3834 + 3835 + [[package]] 3836 + name = "schannel" 3837 + version = "0.1.29" 3838 + source = "registry+https://github.com/rust-lang/crates.io-index" 3839 + checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" 3840 + dependencies = [ 3841 + "windows-sys 0.61.2", 3842 + ] 3843 + 3844 + [[package]] 3845 + name = "scoped-tls" 3846 + version = "1.0.1" 3847 + source = "registry+https://github.com/rust-lang/crates.io-index" 3848 + checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 3849 + 3850 + [[package]] 3851 + name = "scopeguard" 3852 + version = "1.2.0" 3853 + source = "registry+https://github.com/rust-lang/crates.io-index" 3854 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 3855 + 3856 + [[package]] 3857 + name = "sec1" 3858 + version = "0.7.3" 3859 + source = "registry+https://github.com/rust-lang/crates.io-index" 3860 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 3861 + dependencies = [ 3862 + "base16ct", 3863 + "der", 3864 + "generic-array", 3865 + "pkcs8", 3866 + "subtle", 3867 + "zeroize", 3868 + ] 3869 + 3870 + [[package]] 3871 + name = "security-framework" 3872 + version = "3.7.0" 3873 + source = "registry+https://github.com/rust-lang/crates.io-index" 3874 + checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" 3875 + dependencies = [ 3876 + "bitflags 2.13.1", 3877 + "core-foundation 0.10.1", 3878 + "core-foundation-sys", 3879 + "libc", 3880 + "security-framework-sys", 3881 + ] 3882 + 3883 + [[package]] 3884 + name = "security-framework-sys" 3885 + version = "2.17.0" 3886 + source = "registry+https://github.com/rust-lang/crates.io-index" 3887 + checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" 3888 + dependencies = [ 3889 + "core-foundation-sys", 3890 + "libc", 3891 + ] 3892 + 3893 + [[package]] 3894 + name = "semver" 3895 + version = "1.0.28" 3896 + source = "registry+https://github.com/rust-lang/crates.io-index" 3897 + checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" 3898 + 3899 + [[package]] 3900 + name = "send_wrapper" 3901 + version = "0.6.0" 3902 + source = "registry+https://github.com/rust-lang/crates.io-index" 3903 + checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" 3904 + 3905 + [[package]] 3906 + name = "serde" 3907 + version = "1.0.228" 3908 + source = "registry+https://github.com/rust-lang/crates.io-index" 3909 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 3910 + dependencies = [ 3911 + "serde_core", 3912 + "serde_derive", 3913 + ] 3914 + 3915 + [[package]] 3916 + name = "serde_bytes" 3917 + version = "0.11.19" 3918 + source = "registry+https://github.com/rust-lang/crates.io-index" 3919 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 3920 + dependencies = [ 3921 + "serde", 3922 + "serde_core", 3923 + ] 3924 + 3925 + [[package]] 3926 + name = "serde_core" 3927 + version = "1.0.228" 3928 + source = "registry+https://github.com/rust-lang/crates.io-index" 3929 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 3930 + dependencies = [ 3931 + "serde_derive", 3932 + ] 3933 + 3934 + [[package]] 3935 + name = "serde_derive" 3936 + version = "1.0.228" 3937 + source = "registry+https://github.com/rust-lang/crates.io-index" 3938 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 3939 + dependencies = [ 3940 + "proc-macro2", 3941 + "quote", 3942 + "syn", 3943 + ] 3944 + 3945 + [[package]] 3946 + name = "serde_html_form" 3947 + version = "0.3.2" 3948 + source = "registry+https://github.com/rust-lang/crates.io-index" 3949 + checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" 3950 + dependencies = [ 3951 + "form_urlencoded", 3952 + "indexmap", 3953 + "itoa", 3954 + "serde_core", 3955 + ] 3956 + 3957 + [[package]] 3958 + name = "serde_ipld_dagcbor" 3959 + version = "0.6.4" 3960 + source = "registry+https://github.com/rust-lang/crates.io-index" 3961 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 3962 + dependencies = [ 3963 + "cbor4ii", 3964 + "ipld-core", 3965 + "scopeguard", 3966 + "serde", 3967 + ] 3968 + 3969 + [[package]] 3970 + name = "serde_json" 3971 + version = "1.0.150" 3972 + source = "registry+https://github.com/rust-lang/crates.io-index" 3973 + checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" 3974 + dependencies = [ 3975 + "itoa", 3976 + "memchr", 3977 + "serde", 3978 + "serde_core", 3979 + "zmij", 3980 + ] 3981 + 3982 + [[package]] 3983 + name = "serde_path_to_error" 3984 + version = "0.1.20" 3985 + source = "registry+https://github.com/rust-lang/crates.io-index" 3986 + checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 3987 + dependencies = [ 3988 + "itoa", 3989 + "serde", 3990 + "serde_core", 3991 + ] 3992 + 3993 + [[package]] 3994 + name = "serde_repr" 3995 + version = "0.1.20" 3996 + source = "registry+https://github.com/rust-lang/crates.io-index" 3997 + checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 3998 + dependencies = [ 3999 + "proc-macro2", 4000 + "quote", 4001 + "syn", 4002 + ] 4003 + 4004 + [[package]] 4005 + name = "serde_spanned" 4006 + version = "0.6.9" 4007 + source = "registry+https://github.com/rust-lang/crates.io-index" 4008 + checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 4009 + dependencies = [ 4010 + "serde", 4011 + ] 4012 + 4013 + [[package]] 4014 + name = "serde_urlencoded" 4015 + version = "0.7.1" 4016 + source = "registry+https://github.com/rust-lang/crates.io-index" 4017 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 4018 + dependencies = [ 4019 + "form_urlencoded", 4020 + "itoa", 4021 + "ryu", 4022 + "serde", 4023 + ] 4024 + 4025 + [[package]] 4026 + name = "serde_with" 4027 + version = "3.21.0" 4028 + source = "registry+https://github.com/rust-lang/crates.io-index" 4029 + checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" 4030 + dependencies = [ 4031 + "base64", 4032 + "bs58", 4033 + "chrono", 4034 + "hex", 4035 + "serde_core", 4036 + "serde_json", 4037 + "serde_with_macros", 4038 + "time", 4039 + ] 4040 + 4041 + [[package]] 4042 + name = "serde_with_macros" 4043 + version = "3.21.0" 4044 + source = "registry+https://github.com/rust-lang/crates.io-index" 4045 + checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" 4046 + dependencies = [ 4047 + "darling", 4048 + "proc-macro2", 4049 + "quote", 4050 + "syn", 4051 + ] 4052 + 4053 + [[package]] 4054 + name = "sha1" 4055 + version = "0.10.7" 4056 + source = "registry+https://github.com/rust-lang/crates.io-index" 4057 + checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" 4058 + dependencies = [ 4059 + "cfg-if", 4060 + "cpufeatures 0.2.17", 4061 + "digest", 4062 + ] 4063 + 4064 + [[package]] 4065 + name = "sha2" 4066 + version = "0.10.9" 4067 + source = "registry+https://github.com/rust-lang/crates.io-index" 4068 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 4069 + dependencies = [ 4070 + "cfg-if", 4071 + "cpufeatures 0.2.17", 4072 + "digest", 4073 + ] 4074 + 4075 + [[package]] 4076 + name = "sharded-slab" 4077 + version = "0.1.7" 4078 + source = "registry+https://github.com/rust-lang/crates.io-index" 4079 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 4080 + dependencies = [ 4081 + "lazy_static", 4082 + ] 4083 + 4084 + [[package]] 4085 + name = "shlex" 4086 + version = "1.3.0" 4087 + source = "registry+https://github.com/rust-lang/crates.io-index" 4088 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 4089 + 4090 + [[package]] 4091 + name = "shlex" 4092 + version = "2.0.1" 4093 + source = "registry+https://github.com/rust-lang/crates.io-index" 4094 + checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" 4095 + 4096 + [[package]] 4097 + name = "signal-hook" 4098 + version = "0.3.18" 4099 + source = "registry+https://github.com/rust-lang/crates.io-index" 4100 + checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 4101 + dependencies = [ 4102 + "libc", 4103 + "signal-hook-registry", 4104 + ] 4105 + 4106 + [[package]] 4107 + name = "signal-hook-mio" 4108 + version = "0.2.5" 4109 + source = "registry+https://github.com/rust-lang/crates.io-index" 4110 + checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" 4111 + dependencies = [ 4112 + "libc", 4113 + "mio", 4114 + "signal-hook", 4115 + ] 4116 + 4117 + [[package]] 4118 + name = "signal-hook-registry" 4119 + version = "1.4.8" 4120 + source = "registry+https://github.com/rust-lang/crates.io-index" 4121 + checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 4122 + dependencies = [ 4123 + "errno", 4124 + "libc", 4125 + ] 4126 + 4127 + [[package]] 4128 + name = "signature" 4129 + version = "2.2.0" 4130 + source = "registry+https://github.com/rust-lang/crates.io-index" 4131 + checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 4132 + dependencies = [ 4133 + "digest", 4134 + "rand_core 0.6.4", 4135 + ] 4136 + 4137 + [[package]] 4138 + name = "simd-adler32" 4139 + version = "0.3.10" 4140 + source = "registry+https://github.com/rust-lang/crates.io-index" 4141 + checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" 4142 + 4143 + [[package]] 4144 + name = "simd_cesu8" 4145 + version = "1.2.0" 4146 + source = "registry+https://github.com/rust-lang/crates.io-index" 4147 + checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" 4148 + dependencies = [ 4149 + "rustc_version", 4150 + "simdutf8", 4151 + ] 4152 + 4153 + [[package]] 4154 + name = "simdutf8" 4155 + version = "0.1.5" 4156 + source = "registry+https://github.com/rust-lang/crates.io-index" 4157 + checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 4158 + 4159 + [[package]] 4160 + name = "siphasher" 4161 + version = "1.0.3" 4162 + source = "registry+https://github.com/rust-lang/crates.io-index" 4163 + checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" 4164 + 4165 + [[package]] 4166 + name = "slab" 4167 + version = "0.4.12" 4168 + source = "registry+https://github.com/rust-lang/crates.io-index" 4169 + checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" 4170 + 4171 + [[package]] 4172 + name = "smallvec" 4173 + version = "1.15.2" 4174 + source = "registry+https://github.com/rust-lang/crates.io-index" 4175 + checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" 4176 + 4177 + [[package]] 4178 + name = "smol_str" 4179 + version = "0.3.6" 4180 + source = "registry+https://github.com/rust-lang/crates.io-index" 4181 + checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" 4182 + dependencies = [ 4183 + "borsh", 4184 + "serde_core", 4185 + ] 4186 + 4187 + [[package]] 4188 + name = "socket2" 4189 + version = "0.6.5" 4190 + source = "registry+https://github.com/rust-lang/crates.io-index" 4191 + checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" 4192 + dependencies = [ 4193 + "libc", 4194 + "windows-sys 0.61.2", 4195 + ] 4196 + 4197 + [[package]] 4198 + name = "spin" 4199 + version = "0.9.9" 4200 + source = "registry+https://github.com/rust-lang/crates.io-index" 4201 + checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" 4202 + dependencies = [ 4203 + "lock_api", 4204 + ] 4205 + 4206 + [[package]] 4207 + name = "spin" 4208 + version = "0.10.1" 4209 + source = "registry+https://github.com/rust-lang/crates.io-index" 4210 + checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" 4211 + 4212 + [[package]] 4213 + name = "spki" 4214 + version = "0.7.3" 4215 + source = "registry+https://github.com/rust-lang/crates.io-index" 4216 + checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 4217 + dependencies = [ 4218 + "base64ct", 4219 + "der", 4220 + ] 4221 + 4222 + [[package]] 4223 + name = "stable_deref_trait" 4224 + version = "1.2.1" 4225 + source = "registry+https://github.com/rust-lang/crates.io-index" 4226 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 4227 + 4228 + [[package]] 4229 + name = "static_assertions" 4230 + version = "1.1.0" 4231 + source = "registry+https://github.com/rust-lang/crates.io-index" 4232 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 4233 + 4234 + [[package]] 4235 + name = "string_cache" 4236 + version = "0.8.9" 4237 + source = "registry+https://github.com/rust-lang/crates.io-index" 4238 + checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" 4239 + dependencies = [ 4240 + "new_debug_unreachable", 4241 + "parking_lot", 4242 + "phf_shared", 4243 + "precomputed-hash", 4244 + "serde", 4245 + ] 4246 + 4247 + [[package]] 4248 + name = "string_cache_codegen" 4249 + version = "0.5.4" 4250 + source = "registry+https://github.com/rust-lang/crates.io-index" 4251 + checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" 4252 + dependencies = [ 4253 + "phf_generator", 4254 + "phf_shared", 4255 + "proc-macro2", 4256 + "quote", 4257 + ] 4258 + 4259 + [[package]] 4260 + name = "strsim" 4261 + version = "0.11.1" 4262 + source = "registry+https://github.com/rust-lang/crates.io-index" 4263 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 4264 + 4265 + [[package]] 4266 + name = "strum" 4267 + version = "0.26.3" 4268 + source = "registry+https://github.com/rust-lang/crates.io-index" 4269 + checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 4270 + dependencies = [ 4271 + "strum_macros", 4272 + ] 4273 + 4274 + [[package]] 4275 + name = "strum_macros" 4276 + version = "0.26.4" 4277 + source = "registry+https://github.com/rust-lang/crates.io-index" 4278 + checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 4279 + dependencies = [ 4280 + "heck", 4281 + "proc-macro2", 4282 + "quote", 4283 + "rustversion", 4284 + "syn", 4285 + ] 4286 + 4287 + [[package]] 4288 + name = "subtle" 4289 + version = "2.6.1" 4290 + source = "registry+https://github.com/rust-lang/crates.io-index" 4291 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 4292 + 4293 + [[package]] 4294 + name = "syn" 4295 + version = "2.0.119" 4296 + source = "registry+https://github.com/rust-lang/crates.io-index" 4297 + checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" 4298 + dependencies = [ 4299 + "proc-macro2", 4300 + "quote", 4301 + "unicode-ident", 4302 + ] 4303 + 4304 + [[package]] 4305 + name = "sync_wrapper" 4306 + version = "1.0.2" 4307 + source = "registry+https://github.com/rust-lang/crates.io-index" 4308 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 4309 + dependencies = [ 4310 + "futures-core", 4311 + ] 4312 + 4313 + [[package]] 4314 + name = "synstructure" 4315 + version = "0.13.2" 4316 + source = "registry+https://github.com/rust-lang/crates.io-index" 4317 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 4318 + dependencies = [ 4319 + "proc-macro2", 4320 + "quote", 4321 + "syn", 4322 + ] 4323 + 4324 + [[package]] 4325 + name = "system-configuration" 4326 + version = "0.7.0" 4327 + source = "registry+https://github.com/rust-lang/crates.io-index" 4328 + checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" 4329 + dependencies = [ 4330 + "bitflags 2.13.1", 4331 + "core-foundation 0.9.4", 4332 + "system-configuration-sys", 4333 + ] 4334 + 4335 + [[package]] 4336 + name = "system-configuration-sys" 4337 + version = "0.6.0" 4338 + source = "registry+https://github.com/rust-lang/crates.io-index" 4339 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 4340 + dependencies = [ 4341 + "core-foundation-sys", 4342 + "libc", 4343 + ] 4344 + 4345 + [[package]] 4346 + name = "tagptr" 4347 + version = "0.2.0" 4348 + source = "registry+https://github.com/rust-lang/crates.io-index" 4349 + checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 4350 + 4351 + [[package]] 4352 + name = "tempfile" 4353 + version = "3.27.0" 4354 + source = "registry+https://github.com/rust-lang/crates.io-index" 4355 + checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" 4356 + dependencies = [ 4357 + "fastrand", 4358 + "getrandom 0.4.3", 4359 + "once_cell", 4360 + "rustix 1.1.4", 4361 + "windows-sys 0.61.2", 4362 + ] 4363 + 4364 + [[package]] 4365 + name = "tendril" 4366 + version = "0.4.3" 4367 + source = "registry+https://github.com/rust-lang/crates.io-index" 4368 + checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 4369 + dependencies = [ 4370 + "futf", 4371 + "mac", 4372 + "utf-8", 4373 + ] 4374 + 4375 + [[package]] 4376 + name = "thiserror" 4377 + version = "1.0.69" 4378 + source = "registry+https://github.com/rust-lang/crates.io-index" 4379 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 4380 + dependencies = [ 4381 + "thiserror-impl 1.0.69", 4382 + ] 4383 + 4384 + [[package]] 4385 + name = "thiserror" 4386 + version = "2.0.18" 4387 + source = "registry+https://github.com/rust-lang/crates.io-index" 4388 + checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 4389 + dependencies = [ 4390 + "thiserror-impl 2.0.18", 4391 + ] 4392 + 4393 + [[package]] 4394 + name = "thiserror-impl" 4395 + version = "1.0.69" 4396 + source = "registry+https://github.com/rust-lang/crates.io-index" 4397 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 4398 + dependencies = [ 4399 + "proc-macro2", 4400 + "quote", 4401 + "syn", 4402 + ] 4403 + 4404 + [[package]] 4405 + name = "thiserror-impl" 4406 + version = "2.0.18" 4407 + source = "registry+https://github.com/rust-lang/crates.io-index" 4408 + checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" 4409 + dependencies = [ 4410 + "proc-macro2", 4411 + "quote", 4412 + "syn", 4413 + ] 4414 + 4415 + [[package]] 4416 + name = "thread_local" 4417 + version = "1.1.10" 4418 + source = "registry+https://github.com/rust-lang/crates.io-index" 4419 + checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" 4420 + dependencies = [ 4421 + "cfg-if", 4422 + ] 4423 + 4424 + [[package]] 4425 + name = "time" 4426 + version = "0.3.53" 4427 + source = "registry+https://github.com/rust-lang/crates.io-index" 4428 + checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" 4429 + dependencies = [ 4430 + "deranged", 4431 + "num-conv", 4432 + "powerfmt", 4433 + "serde_core", 4434 + "time-core", 4435 + ] 4436 + 4437 + [[package]] 4438 + name = "time-core" 4439 + version = "0.1.9" 4440 + source = "registry+https://github.com/rust-lang/crates.io-index" 4441 + checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" 4442 + 4443 + [[package]] 4444 + name = "tinystr" 4445 + version = "0.8.3" 4446 + source = "registry+https://github.com/rust-lang/crates.io-index" 4447 + checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" 4448 + dependencies = [ 4449 + "displaydoc", 4450 + "zerovec", 4451 + ] 4452 + 4453 + [[package]] 4454 + name = "tinyvec" 4455 + version = "1.12.0" 4456 + source = "registry+https://github.com/rust-lang/crates.io-index" 4457 + checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" 4458 + dependencies = [ 4459 + "tinyvec_macros", 4460 + ] 4461 + 4462 + [[package]] 4463 + name = "tinyvec_macros" 4464 + version = "0.1.1" 4465 + source = "registry+https://github.com/rust-lang/crates.io-index" 4466 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 4467 + 4468 + [[package]] 4469 + name = "tokio" 4470 + version = "1.52.4" 4471 + source = "registry+https://github.com/rust-lang/crates.io-index" 4472 + checksum = "317fafbbe3f02fc663dad00ea6186197de963cd4190e86a26d8d0fae095539af" 4473 + dependencies = [ 4474 + "bytes", 4475 + "libc", 4476 + "mio", 4477 + "pin-project-lite", 4478 + "socket2", 4479 + "tokio-macros", 4480 + "windows-sys 0.61.2", 4481 + ] 4482 + 4483 + [[package]] 4484 + name = "tokio-macros" 4485 + version = "2.7.0" 4486 + source = "registry+https://github.com/rust-lang/crates.io-index" 4487 + checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" 4488 + dependencies = [ 4489 + "proc-macro2", 4490 + "quote", 4491 + "syn", 4492 + ] 4493 + 4494 + [[package]] 4495 + name = "tokio-rustls" 4496 + version = "0.26.4" 4497 + source = "registry+https://github.com/rust-lang/crates.io-index" 4498 + checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 4499 + dependencies = [ 4500 + "rustls", 4501 + "tokio", 4502 + ] 4503 + 4504 + [[package]] 4505 + name = "tokio-util" 4506 + version = "0.7.18" 4507 + source = "registry+https://github.com/rust-lang/crates.io-index" 4508 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 4509 + dependencies = [ 4510 + "bytes", 4511 + "futures-core", 4512 + "futures-sink", 4513 + "futures-util", 4514 + "pin-project-lite", 4515 + "tokio", 4516 + ] 4517 + 4518 + [[package]] 4519 + name = "toml" 4520 + version = "0.8.23" 4521 + source = "registry+https://github.com/rust-lang/crates.io-index" 4522 + checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 4523 + dependencies = [ 4524 + "serde", 4525 + "serde_spanned", 4526 + "toml_datetime 0.6.11", 4527 + "toml_edit 0.22.27", 4528 + ] 4529 + 4530 + [[package]] 4531 + name = "toml_datetime" 4532 + version = "0.6.11" 4533 + source = "registry+https://github.com/rust-lang/crates.io-index" 4534 + checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 4535 + dependencies = [ 4536 + "serde", 4537 + ] 4538 + 4539 + [[package]] 4540 + name = "toml_datetime" 4541 + version = "1.1.1+spec-1.1.0" 4542 + source = "registry+https://github.com/rust-lang/crates.io-index" 4543 + checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" 4544 + dependencies = [ 4545 + "serde_core", 4546 + ] 4547 + 4548 + [[package]] 4549 + name = "toml_edit" 4550 + version = "0.22.27" 4551 + source = "registry+https://github.com/rust-lang/crates.io-index" 4552 + checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 4553 + dependencies = [ 4554 + "indexmap", 4555 + "serde", 4556 + "serde_spanned", 4557 + "toml_datetime 0.6.11", 4558 + "toml_write", 4559 + "winnow 0.7.15", 4560 + ] 4561 + 4562 + [[package]] 4563 + name = "toml_edit" 4564 + version = "0.25.13+spec-1.1.0" 4565 + source = "registry+https://github.com/rust-lang/crates.io-index" 4566 + checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" 4567 + dependencies = [ 4568 + "indexmap", 4569 + "toml_datetime 1.1.1+spec-1.1.0", 4570 + "toml_parser", 4571 + "winnow 1.0.4", 4572 + ] 4573 + 4574 + [[package]] 4575 + name = "toml_parser" 4576 + version = "1.1.2+spec-1.1.0" 4577 + source = "registry+https://github.com/rust-lang/crates.io-index" 4578 + checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" 4579 + dependencies = [ 4580 + "winnow 1.0.4", 4581 + ] 4582 + 4583 + [[package]] 4584 + name = "toml_write" 4585 + version = "0.1.2" 4586 + source = "registry+https://github.com/rust-lang/crates.io-index" 4587 + checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 4588 + 4589 + [[package]] 4590 + name = "tower" 4591 + version = "0.5.3" 4592 + source = "registry+https://github.com/rust-lang/crates.io-index" 4593 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 4594 + dependencies = [ 4595 + "futures-core", 4596 + "futures-util", 4597 + "pin-project-lite", 4598 + "sync_wrapper", 4599 + "tokio", 4600 + "tower-layer", 4601 + "tower-service", 4602 + ] 4603 + 4604 + [[package]] 4605 + name = "tower-http" 4606 + version = "0.6.11" 4607 + source = "registry+https://github.com/rust-lang/crates.io-index" 4608 + checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" 4609 + dependencies = [ 4610 + "async-compression", 4611 + "bitflags 2.13.1", 4612 + "bytes", 4613 + "futures-core", 4614 + "futures-util", 4615 + "http", 4616 + "http-body", 4617 + "http-body-util", 4618 + "pin-project-lite", 4619 + "tokio", 4620 + "tokio-util", 4621 + "tower", 4622 + "tower-layer", 4623 + "tower-service", 4624 + "url", 4625 + ] 4626 + 4627 + [[package]] 4628 + name = "tower-layer" 4629 + version = "0.3.3" 4630 + source = "registry+https://github.com/rust-lang/crates.io-index" 4631 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 4632 + 4633 + [[package]] 4634 + name = "tower-service" 4635 + version = "0.3.3" 4636 + source = "registry+https://github.com/rust-lang/crates.io-index" 4637 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 4638 + 4639 + [[package]] 4640 + name = "tracing" 4641 + version = "0.1.44" 4642 + source = "registry+https://github.com/rust-lang/crates.io-index" 4643 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 4644 + dependencies = [ 4645 + "pin-project-lite", 4646 + "tracing-attributes", 4647 + "tracing-core", 4648 + ] 4649 + 4650 + [[package]] 4651 + name = "tracing-attributes" 4652 + version = "0.1.31" 4653 + source = "registry+https://github.com/rust-lang/crates.io-index" 4654 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 4655 + dependencies = [ 4656 + "proc-macro2", 4657 + "quote", 4658 + "syn", 4659 + ] 4660 + 4661 + [[package]] 4662 + name = "tracing-core" 4663 + version = "0.1.36" 4664 + source = "registry+https://github.com/rust-lang/crates.io-index" 4665 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 4666 + dependencies = [ 4667 + "once_cell", 4668 + "valuable", 4669 + ] 4670 + 4671 + [[package]] 4672 + name = "tracing-log" 4673 + version = "0.2.0" 4674 + source = "registry+https://github.com/rust-lang/crates.io-index" 4675 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 4676 + dependencies = [ 4677 + "log", 4678 + "once_cell", 4679 + "tracing-core", 4680 + ] 4681 + 4682 + [[package]] 4683 + name = "tracing-subscriber" 4684 + version = "0.3.23" 4685 + source = "registry+https://github.com/rust-lang/crates.io-index" 4686 + checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" 4687 + dependencies = [ 4688 + "matchers", 4689 + "nu-ansi-term", 4690 + "once_cell", 4691 + "regex-automata", 4692 + "sharded-slab", 4693 + "smallvec", 4694 + "thread_local", 4695 + "tracing", 4696 + "tracing-core", 4697 + "tracing-log", 4698 + ] 4699 + 4700 + [[package]] 4701 + name = "trait-variant" 4702 + version = "0.1.2" 4703 + source = "registry+https://github.com/rust-lang/crates.io-index" 4704 + checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" 4705 + dependencies = [ 4706 + "proc-macro2", 4707 + "quote", 4708 + "syn", 4709 + ] 4710 + 4711 + [[package]] 4712 + name = "triomphe" 4713 + version = "0.1.16" 4714 + source = "registry+https://github.com/rust-lang/crates.io-index" 4715 + checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" 4716 + 4717 + [[package]] 4718 + name = "try-lock" 4719 + version = "0.2.5" 4720 + source = "registry+https://github.com/rust-lang/crates.io-index" 4721 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 4722 + 4723 + [[package]] 4724 + name = "typenum" 4725 + version = "1.20.1" 4726 + source = "registry+https://github.com/rust-lang/crates.io-index" 4727 + checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" 4728 + 4729 + [[package]] 4730 + name = "uds_windows" 4731 + version = "1.2.1" 4732 + source = "registry+https://github.com/rust-lang/crates.io-index" 4733 + checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" 4734 + dependencies = [ 4735 + "memoffset", 4736 + "tempfile", 4737 + "windows-sys 0.61.2", 4738 + ] 4739 + 4740 + [[package]] 4741 + name = "unicode-ident" 4742 + version = "1.0.24" 4743 + source = "registry+https://github.com/rust-lang/crates.io-index" 4744 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 4745 + 4746 + [[package]] 4747 + name = "unicode-segmentation" 4748 + version = "1.13.3" 4749 + source = "registry+https://github.com/rust-lang/crates.io-index" 4750 + checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" 4751 + 4752 + [[package]] 4753 + name = "unicode-truncate" 4754 + version = "1.1.0" 4755 + source = "registry+https://github.com/rust-lang/crates.io-index" 4756 + checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 4757 + dependencies = [ 4758 + "itertools", 4759 + "unicode-segmentation", 4760 + "unicode-width 0.1.14", 4761 + ] 4762 + 4763 + [[package]] 4764 + name = "unicode-width" 4765 + version = "0.1.14" 4766 + source = "registry+https://github.com/rust-lang/crates.io-index" 4767 + checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 4768 + 4769 + [[package]] 4770 + name = "unicode-width" 4771 + version = "0.2.0" 4772 + source = "registry+https://github.com/rust-lang/crates.io-index" 4773 + checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 4774 + 4775 + [[package]] 4776 + name = "unicode-xid" 4777 + version = "0.2.6" 4778 + source = "registry+https://github.com/rust-lang/crates.io-index" 4779 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 4780 + 4781 + [[package]] 4782 + name = "unsigned-varint" 4783 + version = "0.8.0" 4784 + source = "registry+https://github.com/rust-lang/crates.io-index" 4785 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 4786 + 4787 + [[package]] 4788 + name = "untrusted" 4789 + version = "0.9.0" 4790 + source = "registry+https://github.com/rust-lang/crates.io-index" 4791 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 4792 + 4793 + [[package]] 4794 + name = "url" 4795 + version = "2.5.8" 4796 + source = "registry+https://github.com/rust-lang/crates.io-index" 4797 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 4798 + dependencies = [ 4799 + "form_urlencoded", 4800 + "idna", 4801 + "percent-encoding", 4802 + "serde", 4803 + ] 4804 + 4805 + [[package]] 4806 + name = "utf-8" 4807 + version = "0.7.6" 4808 + source = "registry+https://github.com/rust-lang/crates.io-index" 4809 + checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 4810 + 4811 + [[package]] 4812 + name = "utf8_iter" 4813 + version = "1.0.4" 4814 + source = "registry+https://github.com/rust-lang/crates.io-index" 4815 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 4816 + 4817 + [[package]] 4818 + name = "utf8parse" 4819 + version = "0.2.2" 4820 + source = "registry+https://github.com/rust-lang/crates.io-index" 4821 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 4822 + 4823 + [[package]] 4824 + name = "valuable" 4825 + version = "0.1.1" 4826 + source = "registry+https://github.com/rust-lang/crates.io-index" 4827 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 4828 + 4829 + [[package]] 4830 + name = "version_check" 4831 + version = "0.9.5" 4832 + source = "registry+https://github.com/rust-lang/crates.io-index" 4833 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 4834 + 4835 + [[package]] 4836 + name = "walkdir" 4837 + version = "2.5.0" 4838 + source = "registry+https://github.com/rust-lang/crates.io-index" 4839 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 4840 + dependencies = [ 4841 + "same-file", 4842 + "winapi-util", 4843 + ] 4844 + 4845 + [[package]] 4846 + name = "want" 4847 + version = "0.3.1" 4848 + source = "registry+https://github.com/rust-lang/crates.io-index" 4849 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 4850 + dependencies = [ 4851 + "try-lock", 4852 + ] 4853 + 4854 + [[package]] 4855 + name = "wasi" 4856 + version = "0.11.1+wasi-snapshot-preview1" 4857 + source = "registry+https://github.com/rust-lang/crates.io-index" 4858 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 4859 + 4860 + [[package]] 4861 + name = "wasip2" 4862 + version = "1.0.4+wasi-0.2.12" 4863 + source = "registry+https://github.com/rust-lang/crates.io-index" 4864 + checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" 4865 + dependencies = [ 4866 + "wit-bindgen", 4867 + ] 4868 + 4869 + [[package]] 4870 + name = "wasm-bindgen" 4871 + version = "0.2.126" 4872 + source = "registry+https://github.com/rust-lang/crates.io-index" 4873 + checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" 4874 + dependencies = [ 4875 + "cfg-if", 4876 + "once_cell", 4877 + "rustversion", 4878 + "wasm-bindgen-macro", 4879 + "wasm-bindgen-shared", 4880 + ] 4881 + 4882 + [[package]] 4883 + name = "wasm-bindgen-futures" 4884 + version = "0.4.76" 4885 + source = "registry+https://github.com/rust-lang/crates.io-index" 4886 + checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" 4887 + dependencies = [ 4888 + "js-sys", 4889 + "wasm-bindgen", 4890 + ] 4891 + 4892 + [[package]] 4893 + name = "wasm-bindgen-macro" 4894 + version = "0.2.126" 4895 + source = "registry+https://github.com/rust-lang/crates.io-index" 4896 + checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" 4897 + dependencies = [ 4898 + "quote", 4899 + "wasm-bindgen-macro-support", 4900 + ] 4901 + 4902 + [[package]] 4903 + name = "wasm-bindgen-macro-support" 4904 + version = "0.2.126" 4905 + source = "registry+https://github.com/rust-lang/crates.io-index" 4906 + checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" 4907 + dependencies = [ 4908 + "bumpalo", 4909 + "proc-macro2", 4910 + "quote", 4911 + "syn", 4912 + "wasm-bindgen-shared", 4913 + ] 4914 + 4915 + [[package]] 4916 + name = "wasm-bindgen-shared" 4917 + version = "0.2.126" 4918 + source = "registry+https://github.com/rust-lang/crates.io-index" 4919 + checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" 4920 + dependencies = [ 4921 + "unicode-ident", 4922 + ] 4923 + 4924 + [[package]] 4925 + name = "wasm-streams" 4926 + version = "0.4.2" 4927 + source = "registry+https://github.com/rust-lang/crates.io-index" 4928 + checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 4929 + dependencies = [ 4930 + "futures-util", 4931 + "js-sys", 4932 + "wasm-bindgen", 4933 + "wasm-bindgen-futures", 4934 + "web-sys", 4935 + ] 4936 + 4937 + [[package]] 4938 + name = "web-sys" 4939 + version = "0.3.103" 4940 + source = "registry+https://github.com/rust-lang/crates.io-index" 4941 + checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" 4942 + dependencies = [ 4943 + "js-sys", 4944 + "wasm-bindgen", 4945 + ] 4946 + 4947 + [[package]] 4948 + name = "web-time" 4949 + version = "1.1.0" 4950 + source = "registry+https://github.com/rust-lang/crates.io-index" 4951 + checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 4952 + dependencies = [ 4953 + "js-sys", 4954 + "wasm-bindgen", 4955 + ] 4956 + 4957 + [[package]] 4958 + name = "webbrowser" 4959 + version = "1.2.1" 4960 + source = "registry+https://github.com/rust-lang/crates.io-index" 4961 + checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" 4962 + dependencies = [ 4963 + "core-foundation 0.10.1", 4964 + "jni 0.22.4", 4965 + "log", 4966 + "ndk-context", 4967 + "objc2", 4968 + "objc2-foundation", 4969 + "url", 4970 + "web-sys", 4971 + ] 4972 + 4973 + [[package]] 4974 + name = "webpage" 4975 + version = "2.0.1" 4976 + source = "registry+https://github.com/rust-lang/crates.io-index" 4977 + checksum = "70862efc041d46e6bbaa82bb9c34ae0596d090e86cbd14bd9e93b36ee6802eac" 4978 + dependencies = [ 4979 + "html5ever", 4980 + "markup5ever_rcdom", 4981 + "serde_json", 4982 + "url", 4983 + ] 4984 + 4985 + [[package]] 4986 + name = "webpki-roots" 4987 + version = "1.0.8" 4988 + source = "registry+https://github.com/rust-lang/crates.io-index" 4989 + checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" 4990 + dependencies = [ 4991 + "rustls-pki-types", 4992 + ] 4993 + 4994 + [[package]] 4995 + name = "widestring" 4996 + version = "1.2.1" 4997 + source = "registry+https://github.com/rust-lang/crates.io-index" 4998 + checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 4999 + 5000 + [[package]] 5001 + name = "winapi" 5002 + version = "0.3.9" 5003 + source = "registry+https://github.com/rust-lang/crates.io-index" 5004 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 5005 + dependencies = [ 5006 + "winapi-i686-pc-windows-gnu", 5007 + "winapi-x86_64-pc-windows-gnu", 5008 + ] 5009 + 5010 + [[package]] 5011 + name = "winapi-i686-pc-windows-gnu" 5012 + version = "0.4.0" 5013 + source = "registry+https://github.com/rust-lang/crates.io-index" 5014 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 5015 + 5016 + [[package]] 5017 + name = "winapi-util" 5018 + version = "0.1.11" 5019 + source = "registry+https://github.com/rust-lang/crates.io-index" 5020 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 5021 + dependencies = [ 5022 + "windows-sys 0.61.2", 5023 + ] 5024 + 5025 + [[package]] 5026 + name = "winapi-x86_64-pc-windows-gnu" 5027 + version = "0.4.0" 5028 + source = "registry+https://github.com/rust-lang/crates.io-index" 5029 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 5030 + 5031 + [[package]] 5032 + name = "windows" 5033 + version = "0.54.0" 5034 + source = "registry+https://github.com/rust-lang/crates.io-index" 5035 + checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" 5036 + dependencies = [ 5037 + "windows-core 0.54.0", 5038 + "windows-targets 0.52.6", 5039 + ] 5040 + 5041 + [[package]] 5042 + name = "windows-core" 5043 + version = "0.54.0" 5044 + source = "registry+https://github.com/rust-lang/crates.io-index" 5045 + checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" 5046 + dependencies = [ 5047 + "windows-result 0.1.2", 5048 + "windows-targets 0.52.6", 5049 + ] 5050 + 5051 + [[package]] 5052 + name = "windows-core" 5053 + version = "0.62.2" 5054 + source = "registry+https://github.com/rust-lang/crates.io-index" 5055 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 5056 + dependencies = [ 5057 + "windows-implement", 5058 + "windows-interface", 5059 + "windows-link", 5060 + "windows-result 0.4.1", 5061 + "windows-strings", 5062 + ] 5063 + 5064 + [[package]] 5065 + name = "windows-implement" 5066 + version = "0.60.2" 5067 + source = "registry+https://github.com/rust-lang/crates.io-index" 5068 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 5069 + dependencies = [ 5070 + "proc-macro2", 5071 + "quote", 5072 + "syn", 5073 + ] 5074 + 5075 + [[package]] 5076 + name = "windows-interface" 5077 + version = "0.59.3" 5078 + source = "registry+https://github.com/rust-lang/crates.io-index" 5079 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 5080 + dependencies = [ 5081 + "proc-macro2", 5082 + "quote", 5083 + "syn", 5084 + ] 5085 + 5086 + [[package]] 5087 + name = "windows-link" 5088 + version = "0.2.1" 5089 + source = "registry+https://github.com/rust-lang/crates.io-index" 5090 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 5091 + 5092 + [[package]] 5093 + name = "windows-registry" 5094 + version = "0.6.1" 5095 + source = "registry+https://github.com/rust-lang/crates.io-index" 5096 + checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 5097 + dependencies = [ 5098 + "windows-link", 5099 + "windows-result 0.4.1", 5100 + "windows-strings", 5101 + ] 5102 + 5103 + [[package]] 5104 + name = "windows-result" 5105 + version = "0.1.2" 5106 + source = "registry+https://github.com/rust-lang/crates.io-index" 5107 + checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" 5108 + dependencies = [ 5109 + "windows-targets 0.52.6", 5110 + ] 5111 + 5112 + [[package]] 5113 + name = "windows-result" 5114 + version = "0.4.1" 5115 + source = "registry+https://github.com/rust-lang/crates.io-index" 5116 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 5117 + dependencies = [ 5118 + "windows-link", 5119 + ] 5120 + 5121 + [[package]] 5122 + name = "windows-strings" 5123 + version = "0.5.1" 5124 + source = "registry+https://github.com/rust-lang/crates.io-index" 5125 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 5126 + dependencies = [ 5127 + "windows-link", 5128 + ] 5129 + 5130 + [[package]] 5131 + name = "windows-sys" 5132 + version = "0.45.0" 5133 + source = "registry+https://github.com/rust-lang/crates.io-index" 5134 + checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 5135 + dependencies = [ 5136 + "windows-targets 0.42.2", 5137 + ] 5138 + 5139 + [[package]] 5140 + name = "windows-sys" 5141 + version = "0.48.0" 5142 + source = "registry+https://github.com/rust-lang/crates.io-index" 5143 + checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 5144 + dependencies = [ 5145 + "windows-targets 0.48.5", 5146 + ] 5147 + 5148 + [[package]] 5149 + name = "windows-sys" 5150 + version = "0.52.0" 5151 + source = "registry+https://github.com/rust-lang/crates.io-index" 5152 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 5153 + dependencies = [ 5154 + "windows-targets 0.52.6", 5155 + ] 5156 + 5157 + [[package]] 5158 + name = "windows-sys" 5159 + version = "0.59.0" 5160 + source = "registry+https://github.com/rust-lang/crates.io-index" 5161 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 5162 + dependencies = [ 5163 + "windows-targets 0.52.6", 5164 + ] 5165 + 5166 + [[package]] 5167 + name = "windows-sys" 5168 + version = "0.61.2" 5169 + source = "registry+https://github.com/rust-lang/crates.io-index" 5170 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 5171 + dependencies = [ 5172 + "windows-link", 5173 + ] 5174 + 5175 + [[package]] 5176 + name = "windows-targets" 5177 + version = "0.42.2" 5178 + source = "registry+https://github.com/rust-lang/crates.io-index" 5179 + checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 5180 + dependencies = [ 5181 + "windows_aarch64_gnullvm 0.42.2", 5182 + "windows_aarch64_msvc 0.42.2", 5183 + "windows_i686_gnu 0.42.2", 5184 + "windows_i686_msvc 0.42.2", 5185 + "windows_x86_64_gnu 0.42.2", 5186 + "windows_x86_64_gnullvm 0.42.2", 5187 + "windows_x86_64_msvc 0.42.2", 5188 + ] 5189 + 5190 + [[package]] 5191 + name = "windows-targets" 5192 + version = "0.48.5" 5193 + source = "registry+https://github.com/rust-lang/crates.io-index" 5194 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 5195 + dependencies = [ 5196 + "windows_aarch64_gnullvm 0.48.5", 5197 + "windows_aarch64_msvc 0.48.5", 5198 + "windows_i686_gnu 0.48.5", 5199 + "windows_i686_msvc 0.48.5", 5200 + "windows_x86_64_gnu 0.48.5", 5201 + "windows_x86_64_gnullvm 0.48.5", 5202 + "windows_x86_64_msvc 0.48.5", 5203 + ] 5204 + 5205 + [[package]] 5206 + name = "windows-targets" 5207 + version = "0.52.6" 5208 + source = "registry+https://github.com/rust-lang/crates.io-index" 5209 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 5210 + dependencies = [ 5211 + "windows_aarch64_gnullvm 0.52.6", 5212 + "windows_aarch64_msvc 0.52.6", 5213 + "windows_i686_gnu 0.52.6", 5214 + "windows_i686_gnullvm", 5215 + "windows_i686_msvc 0.52.6", 5216 + "windows_x86_64_gnu 0.52.6", 5217 + "windows_x86_64_gnullvm 0.52.6", 5218 + "windows_x86_64_msvc 0.52.6", 5219 + ] 5220 + 5221 + [[package]] 5222 + name = "windows_aarch64_gnullvm" 5223 + version = "0.42.2" 5224 + source = "registry+https://github.com/rust-lang/crates.io-index" 5225 + checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 5226 + 5227 + [[package]] 5228 + name = "windows_aarch64_gnullvm" 5229 + version = "0.48.5" 5230 + source = "registry+https://github.com/rust-lang/crates.io-index" 5231 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 5232 + 5233 + [[package]] 5234 + name = "windows_aarch64_gnullvm" 5235 + version = "0.52.6" 5236 + source = "registry+https://github.com/rust-lang/crates.io-index" 5237 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 5238 + 5239 + [[package]] 5240 + name = "windows_aarch64_msvc" 5241 + version = "0.42.2" 5242 + source = "registry+https://github.com/rust-lang/crates.io-index" 5243 + checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 5244 + 5245 + [[package]] 5246 + name = "windows_aarch64_msvc" 5247 + version = "0.48.5" 5248 + source = "registry+https://github.com/rust-lang/crates.io-index" 5249 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 5250 + 5251 + [[package]] 5252 + name = "windows_aarch64_msvc" 5253 + version = "0.52.6" 5254 + source = "registry+https://github.com/rust-lang/crates.io-index" 5255 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 5256 + 5257 + [[package]] 5258 + name = "windows_i686_gnu" 5259 + version = "0.42.2" 5260 + source = "registry+https://github.com/rust-lang/crates.io-index" 5261 + checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 5262 + 5263 + [[package]] 5264 + name = "windows_i686_gnu" 5265 + version = "0.48.5" 5266 + source = "registry+https://github.com/rust-lang/crates.io-index" 5267 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 5268 + 5269 + [[package]] 5270 + name = "windows_i686_gnu" 5271 + version = "0.52.6" 5272 + source = "registry+https://github.com/rust-lang/crates.io-index" 5273 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 5274 + 5275 + [[package]] 5276 + name = "windows_i686_gnullvm" 5277 + version = "0.52.6" 5278 + source = "registry+https://github.com/rust-lang/crates.io-index" 5279 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 5280 + 5281 + [[package]] 5282 + name = "windows_i686_msvc" 5283 + version = "0.42.2" 5284 + source = "registry+https://github.com/rust-lang/crates.io-index" 5285 + checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 5286 + 5287 + [[package]] 5288 + name = "windows_i686_msvc" 5289 + version = "0.48.5" 5290 + source = "registry+https://github.com/rust-lang/crates.io-index" 5291 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 5292 + 5293 + [[package]] 5294 + name = "windows_i686_msvc" 5295 + version = "0.52.6" 5296 + source = "registry+https://github.com/rust-lang/crates.io-index" 5297 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 5298 + 5299 + [[package]] 5300 + name = "windows_x86_64_gnu" 5301 + version = "0.42.2" 5302 + source = "registry+https://github.com/rust-lang/crates.io-index" 5303 + checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 5304 + 5305 + [[package]] 5306 + name = "windows_x86_64_gnu" 5307 + version = "0.48.5" 5308 + source = "registry+https://github.com/rust-lang/crates.io-index" 5309 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 5310 + 5311 + [[package]] 5312 + name = "windows_x86_64_gnu" 5313 + version = "0.52.6" 5314 + source = "registry+https://github.com/rust-lang/crates.io-index" 5315 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 5316 + 5317 + [[package]] 5318 + name = "windows_x86_64_gnullvm" 5319 + version = "0.42.2" 5320 + source = "registry+https://github.com/rust-lang/crates.io-index" 5321 + checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 5322 + 5323 + [[package]] 5324 + name = "windows_x86_64_gnullvm" 5325 + version = "0.48.5" 5326 + source = "registry+https://github.com/rust-lang/crates.io-index" 5327 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 5328 + 5329 + [[package]] 5330 + name = "windows_x86_64_gnullvm" 5331 + version = "0.52.6" 5332 + source = "registry+https://github.com/rust-lang/crates.io-index" 5333 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 5334 + 5335 + [[package]] 5336 + name = "windows_x86_64_msvc" 5337 + version = "0.42.2" 5338 + source = "registry+https://github.com/rust-lang/crates.io-index" 5339 + checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 5340 + 5341 + [[package]] 5342 + name = "windows_x86_64_msvc" 5343 + version = "0.48.5" 5344 + source = "registry+https://github.com/rust-lang/crates.io-index" 5345 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 5346 + 5347 + [[package]] 5348 + name = "windows_x86_64_msvc" 5349 + version = "0.52.6" 5350 + source = "registry+https://github.com/rust-lang/crates.io-index" 5351 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 5352 + 5353 + [[package]] 5354 + name = "winnow" 5355 + version = "0.7.15" 5356 + source = "registry+https://github.com/rust-lang/crates.io-index" 5357 + checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" 5358 + dependencies = [ 5359 + "memchr", 5360 + ] 5361 + 5362 + [[package]] 5363 + name = "winnow" 5364 + version = "1.0.4" 5365 + source = "registry+https://github.com/rust-lang/crates.io-index" 5366 + checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" 5367 + dependencies = [ 5368 + "memchr", 5369 + ] 5370 + 5371 + [[package]] 5372 + name = "wit-bindgen" 5373 + version = "0.57.1" 5374 + source = "registry+https://github.com/rust-lang/crates.io-index" 5375 + checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" 5376 + 5377 + [[package]] 5378 + name = "writeable" 5379 + version = "0.6.3" 5380 + source = "registry+https://github.com/rust-lang/crates.io-index" 5381 + checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" 5382 + 5383 + [[package]] 5384 + name = "xdg-home" 5385 + version = "1.3.0" 5386 + source = "registry+https://github.com/rust-lang/crates.io-index" 5387 + checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" 5388 + dependencies = [ 5389 + "libc", 5390 + "windows-sys 0.59.0", 5391 + ] 5392 + 5393 + [[package]] 5394 + name = "xml5ever" 5395 + version = "0.18.1" 5396 + source = "registry+https://github.com/rust-lang/crates.io-index" 5397 + checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" 5398 + dependencies = [ 5399 + "log", 5400 + "mac", 5401 + "markup5ever", 5402 + ] 5403 + 5404 + [[package]] 5405 + name = "yoke" 5406 + version = "0.8.3" 5407 + source = "registry+https://github.com/rust-lang/crates.io-index" 5408 + checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" 5409 + dependencies = [ 5410 + "stable_deref_trait", 5411 + "yoke-derive", 5412 + "zerofrom", 5413 + ] 5414 + 5415 + [[package]] 5416 + name = "yoke-derive" 5417 + version = "0.8.2" 5418 + source = "registry+https://github.com/rust-lang/crates.io-index" 5419 + checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" 5420 + dependencies = [ 5421 + "proc-macro2", 5422 + "quote", 5423 + "syn", 5424 + "synstructure", 5425 + ] 5426 + 5427 + [[package]] 5428 + name = "zbus" 5429 + version = "4.4.0" 5430 + source = "registry+https://github.com/rust-lang/crates.io-index" 5431 + checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" 5432 + dependencies = [ 5433 + "async-broadcast", 5434 + "async-executor", 5435 + "async-fs", 5436 + "async-io", 5437 + "async-lock", 5438 + "async-process", 5439 + "async-recursion", 5440 + "async-task", 5441 + "async-trait", 5442 + "blocking", 5443 + "enumflags2", 5444 + "event-listener", 5445 + "futures-core", 5446 + "futures-sink", 5447 + "futures-util", 5448 + "hex", 5449 + "nix", 5450 + "ordered-stream", 5451 + "rand 0.8.7", 5452 + "serde", 5453 + "serde_repr", 5454 + "sha1", 5455 + "static_assertions", 5456 + "tracing", 5457 + "uds_windows", 5458 + "windows-sys 0.52.0", 5459 + "xdg-home", 5460 + "zbus_macros", 5461 + "zbus_names", 5462 + "zvariant", 5463 + ] 5464 + 5465 + [[package]] 5466 + name = "zbus_macros" 5467 + version = "4.4.0" 5468 + source = "registry+https://github.com/rust-lang/crates.io-index" 5469 + checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" 5470 + dependencies = [ 5471 + "proc-macro-crate", 5472 + "proc-macro2", 5473 + "quote", 5474 + "syn", 5475 + "zvariant_utils", 5476 + ] 5477 + 5478 + [[package]] 5479 + name = "zbus_names" 5480 + version = "3.0.0" 5481 + source = "registry+https://github.com/rust-lang/crates.io-index" 5482 + checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" 5483 + dependencies = [ 5484 + "serde", 5485 + "static_assertions", 5486 + "zvariant", 5487 + ] 5488 + 5489 + [[package]] 5490 + name = "zerocopy" 5491 + version = "0.8.54" 5492 + source = "registry+https://github.com/rust-lang/crates.io-index" 5493 + checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" 5494 + dependencies = [ 5495 + "zerocopy-derive", 5496 + ] 5497 + 5498 + [[package]] 5499 + name = "zerocopy-derive" 5500 + version = "0.8.54" 5501 + source = "registry+https://github.com/rust-lang/crates.io-index" 5502 + checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" 5503 + dependencies = [ 5504 + "proc-macro2", 5505 + "quote", 5506 + "syn", 5507 + ] 5508 + 5509 + [[package]] 5510 + name = "zerofrom" 5511 + version = "0.1.8" 5512 + source = "registry+https://github.com/rust-lang/crates.io-index" 5513 + checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" 5514 + dependencies = [ 5515 + "zerofrom-derive", 5516 + ] 5517 + 5518 + [[package]] 5519 + name = "zerofrom-derive" 5520 + version = "0.1.7" 5521 + source = "registry+https://github.com/rust-lang/crates.io-index" 5522 + checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" 5523 + dependencies = [ 5524 + "proc-macro2", 5525 + "quote", 5526 + "syn", 5527 + "synstructure", 5528 + ] 5529 + 5530 + [[package]] 5531 + name = "zeroize" 5532 + version = "1.9.0" 5533 + source = "registry+https://github.com/rust-lang/crates.io-index" 5534 + checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" 5535 + dependencies = [ 5536 + "serde", 5537 + ] 5538 + 5539 + [[package]] 5540 + name = "zerotrie" 5541 + version = "0.2.4" 5542 + source = "registry+https://github.com/rust-lang/crates.io-index" 5543 + checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" 5544 + dependencies = [ 5545 + "displaydoc", 5546 + "yoke", 5547 + "zerofrom", 5548 + ] 5549 + 5550 + [[package]] 5551 + name = "zerovec" 5552 + version = "0.11.6" 5553 + source = "registry+https://github.com/rust-lang/crates.io-index" 5554 + checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" 5555 + dependencies = [ 5556 + "yoke", 5557 + "zerofrom", 5558 + "zerovec-derive", 5559 + ] 5560 + 5561 + [[package]] 5562 + name = "zerovec-derive" 5563 + version = "0.11.3" 5564 + source = "registry+https://github.com/rust-lang/crates.io-index" 5565 + checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" 5566 + dependencies = [ 5567 + "proc-macro2", 5568 + "quote", 5569 + "syn", 5570 + ] 5571 + 5572 + [[package]] 5573 + name = "zmij" 5574 + version = "1.0.23" 5575 + source = "registry+https://github.com/rust-lang/crates.io-index" 5576 + checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" 5577 + 5578 + [[package]] 5579 + name = "zvariant" 5580 + version = "4.2.0" 5581 + source = "registry+https://github.com/rust-lang/crates.io-index" 5582 + checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" 5583 + dependencies = [ 5584 + "endi", 5585 + "enumflags2", 5586 + "serde", 5587 + "static_assertions", 5588 + "zvariant_derive", 5589 + ] 5590 + 5591 + [[package]] 5592 + name = "zvariant_derive" 5593 + version = "4.2.0" 5594 + source = "registry+https://github.com/rust-lang/crates.io-index" 5595 + checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" 5596 + dependencies = [ 5597 + "proc-macro-crate", 5598 + "proc-macro2", 5599 + "quote", 5600 + "syn", 5601 + "zvariant_utils", 5602 + ] 5603 + 5604 + [[package]] 5605 + name = "zvariant_utils" 5606 + version = "2.1.0" 5607 + source = "registry+https://github.com/rust-lang/crates.io-index" 5608 + checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" 5609 + dependencies = [ 5610 + "proc-macro2", 5611 + "quote", 5612 + "syn", 5613 + ]
+59 -2
cli/Cargo.toml
··· 1 1 [package] 2 - name = "cli" 2 + name = "atradio" 3 3 version = "0.1.0" 4 - edition = "2024" 4 + edition = "2021" 5 + description = "atradio.fm in your terminal — a synthwave TUI radio player on the AT Protocol" 6 + # NOTE: rockbox-playback is GPL-2.0-or-later; linking it makes this binary GPL. 7 + license = "GPL-2.0-or-later" 8 + authors.workspace = true 9 + repository.workspace = true 10 + 11 + [[bin]] 12 + name = "atradio" 13 + path = "src/main.rs" 5 14 6 15 [dependencies] 16 + # atproto (auth + PDS writes). Minimal `api` feature = com.atproto only 17 + # (createSession + repo.putRecord); we skip the 646-lexicon Bluesky/community 18 + # codegen. `loopback` enables the OAuth browser login flow. 19 + jacquard = { version = "0.12", default-features = false, features = [ 20 + "api", 21 + "dns", 22 + "derive", 23 + "cache", 24 + "loopback", 25 + ] } 26 + # Direct deps so the code generated from our lexicons (which references these by 27 + # crate name) resolves. Same versions jacquard already pulls in. 28 + jacquard-common = "0.12" 29 + jacquard-lexicon = "0.12" 30 + jacquard-derive = "0.12" 31 + 32 + # audio engine + DSP (GPL-2.0) 33 + rockbox-playback = ">=0.4.1" 34 + 35 + # async runtime + HTTP (AppView reads, radio-browser search, ICY) 36 + tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time"] } 37 + reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "stream"] } 38 + serde = { version = "1", features = ["derive"] } 39 + serde_json = "1" 40 + futures = "0.3" 41 + 42 + # CLI + TUI 43 + clap = { version = "4.5", features = ["derive"] } 44 + ratatui = "0.29" 45 + crossterm = { version = "0.28", features = ["event-stream"] } 46 + fuzzy-matcher = "0.3" 47 + 48 + # misc 49 + anyhow = "1" 50 + directories = "5" 51 + chrono = { version = "0.4", features = ["clock"] } 52 + smol_str = { version = "0.3", features = ["serde"] } 53 + toml = "0.8" 54 + 55 + # Linux-only: expose the player over MPRIS (D-Bus) for media-key / desktop 56 + # integration. No-op on macOS/Windows. 57 + [target.'cfg(target_os = "linux")'.dependencies] 58 + mpris-server = "0.8" 59 + 60 + [features] 61 + default = ["fm_atradio"] 62 + # Gates the code generated from packages/lexicons/lexicons/atradio. 63 + fm_atradio = []
+92
cli/README.md
··· 1 + # atradio 2 + 3 + `atradio.fm` in your terminal — a synthwave TUI radio player on the AT Protocol. 4 + 5 + A native Rust client for [atradio.fm](https://atradio.fm): browse trending / 6 + popular stations, fuzzy-search the whole radio-browser directory, play live 7 + streams with a full Rockbox DSP/equalizer chain, and — when signed in — 8 + favorite stations and post comments to your own PDS. 9 + 10 + ## Build 11 + 12 + ```bash 13 + cargo build --release # from the repo root, or from cli/ 14 + ./target/release/atradio # launch the TUI 15 + ``` 16 + 17 + Building compiles the vendored Rockbox codecs, so a C toolchain is required 18 + (clang/gcc). macOS uses CoreAudio; Linux needs ALSA dev headers. 19 + 20 + > **License note:** this crate links `rockbox-playback` (GPL-2.0-or-later), so 21 + > the compiled `atradio` binary is GPL-2.0-or-later. 22 + 23 + ## Usage 24 + 25 + ```bash 26 + atradio # interactive TUI (default) 27 + atradio search lofi # search radio-browser, print results 28 + atradio play "jazz" # headless: play the top hit for a query… 29 + atradio play https://…/stream # …or a stream URL directly 30 + atradio trending # trending stations from the AppView 31 + atradio login # sign in with an app password (env), or: 32 + atradio login --oauth # sign in via the browser (OAuth) 33 + atradio whoami # show the signed-in account 34 + atradio logout 35 + ``` 36 + 37 + ## Signing in 38 + 39 + Reads / posts to the AppView are public; **favoriting and commenting require a 40 + session.** Two ways to authenticate: 41 + 42 + - **App password** — set env vars, then `atradio login`: 43 + ```bash 44 + export ATPROTO_IDENTIFIER="you.bsky.social" 45 + export ATPROTO_APP_PASSWORD="xxxx-xxxx-xxxx-xxxx" 46 + ``` 47 + - **OAuth** — `atradio login --oauth you.bsky.social` opens your PDS in the 48 + browser. 49 + 50 + The session + a small profile cache are stored under 51 + `~/.config/atradio/` (also `settings.toml` for volume + DSP). 52 + 53 + ## Keybindings (TUI) 54 + 55 + | Key | Action | 56 + | --- | --- | 57 + | `↑`/`↓` `j`/`k` | move selection | 58 + | `←`/`→` `Tab` | switch list / home tab | 59 + | `Enter` | play the selected station | 60 + | `Space` | play / pause | 61 + | `+` / `-` | volume up / down (or adjust the focused DSP value) | 62 + | `m` | mute | 63 + | `/` | fuzzy station search (fzf-style) | 64 + | `f` | favorite the selected/current station | 65 + | `c` / `a` | comments / add a comment | 66 + | `n` | notifications | 67 + | `e` | equalizer & DSP settings | 68 + | `h` · `?` | home · help | 69 + | `q` / `Esc` | quit / close overlay | 70 + 71 + ## Equalizer & DSP 72 + 73 + Press `e` for the full Rockbox chain, mirroring the web app: a 10-band 74 + equalizer, bass/treble tone, crossfeed, perceptual bass, Haas surround, a 75 + compressor, and channel mode / stereo width. Changes apply live and persist to 76 + `settings.toml`. 77 + 78 + ## Platform notes 79 + 80 + - **Linux:** the player is exposed over **MPRIS** (D-Bus), so media keys and 81 + desktop panels / `playerctl` can see now-playing and drive play/pause/stop. 82 + 83 + ## Lexicon bindings 84 + 85 + The typed `fm.atradio.*` records/queries in `src/fm_atradio/` and 86 + `src/builder_types.rs` are **generated** from the lexicon JSON in 87 + `packages/lexicons/lexicons/atradio` via jacquard's codegen. Regenerate with: 88 + 89 + ```bash 90 + cargo install jacquard-lexgen # provides `jacquard-codegen` 91 + bash scripts/gen-lexicons.sh 92 + ```
+13
cli/scripts/gen-lexicons.sh
··· 1 + #!/usr/bin/env bash 2 + # Regenerate the Rust bindings in src/{builder_types.rs,fm_atradio*} from the 3 + # atradio lexicon JSON. Requires the jacquard codegen binary: 4 + # cargo install jacquard-lexgen # provides `jacquard-codegen` 5 + set -euo pipefail 6 + here="$(cd "$(dirname "$0")/.." && pwd)" 7 + lexicons="$here/../packages/lexicons/lexicons/atradio" 8 + out="$(mktemp -d)" 9 + jacquard-codegen -i "$lexicons" -o "$out" 10 + cp "$out/builder_types.rs" "$here/src/builder_types.rs" 11 + cp "$out/fm_atradio.rs" "$here/src/fm_atradio.rs" 12 + rm -rf "$here/src/fm_atradio" && cp -r "$out/fm_atradio" "$here/src/fm_atradio" 13 + echo "regenerated lexicon bindings in $here/src"
+156
cli/src/appview/client.rs
··· 1 + //! Thin async client over the public atradio.fm AppView XRPC (`fm.atradio.*`). 2 + //! Everything here is unauthenticated JSON-over-HTTP. 3 + 4 + use anyhow::{Context, Result}; 5 + use serde::de::DeserializeOwned; 6 + 7 + use super::models::*; 8 + 9 + #[derive(Clone)] 10 + pub struct AppView { 11 + http: reqwest::Client, 12 + base: String, 13 + } 14 + 15 + impl AppView { 16 + pub fn new(base: impl Into<String>) -> Self { 17 + let http = reqwest::Client::builder() 18 + .user_agent(concat!("atradio-cli/", env!("CARGO_PKG_VERSION"))) 19 + .build() 20 + .expect("reqwest client"); 21 + Self { 22 + http, 23 + base: base.into().trim_end_matches('/').to_string(), 24 + } 25 + } 26 + 27 + async fn query<T: DeserializeOwned>(&self, nsid: &str, params: &[(&str, String)]) -> Result<T> { 28 + let url = format!("{}/xrpc/{}", self.base, nsid); 29 + let filtered: Vec<(&str, String)> = params 30 + .iter() 31 + .filter(|(_, v)| !v.is_empty()) 32 + .cloned() 33 + .collect(); 34 + let res = self 35 + .http 36 + .get(&url) 37 + .query(&filtered) 38 + .send() 39 + .await 40 + .with_context(|| format!("GET {nsid}"))?; 41 + let status = res.status(); 42 + let body = res.text().await.unwrap_or_default(); 43 + if !status.is_success() { 44 + anyhow::bail!("{nsid} -> {status}: {body}"); 45 + } 46 + serde_json::from_str(&body).with_context(|| format!("decode {nsid}: {body}")) 47 + } 48 + 49 + /// Newest stations platform-wide. 50 + pub async fn recent_stations(&self, limit: u32) -> Result<Vec<StationView>> { 51 + let out: RecentStationsOutput = self 52 + .query("fm.atradio.getRecentStations", &[("limit", limit.to_string())]) 53 + .await?; 54 + Ok(out.items) 55 + } 56 + 57 + /// Most-favorited stations platform-wide. 58 + pub async fn popular_stations(&self, limit: u32) -> Result<Vec<PopularItem>> { 59 + let out: PopularStationsOutput = self 60 + .query("fm.atradio.getPopularStations", &[("limit", limit.to_string())]) 61 + .await?; 62 + Ok(out.items) 63 + } 64 + 65 + /// Platform-wide "who's listening" feed. 66 + pub async fn global_recently_played(&self, limit: u32) -> Result<Vec<PlayView>> { 67 + let out: PlayListOutput = self 68 + .query( 69 + "fm.atradio.getGlobalRecentlyPlayed", 70 + &[("limit", limit.to_string())], 71 + ) 72 + .await?; 73 + Ok(out.items) 74 + } 75 + 76 + /// An actor's own recently played stations (one per station). 77 + pub async fn recently_played(&self, actor: &str, limit: u32) -> Result<Vec<PlayView>> { 78 + let out: PlayListOutput = self 79 + .query( 80 + "fm.atradio.getRecentlyPlayed", 81 + &[("actor", actor.into()), ("limit", limit.to_string())], 82 + ) 83 + .await?; 84 + Ok(out.items) 85 + } 86 + 87 + /// An actor's favorited stations. 88 + pub async fn favorites(&self, actor: &str, limit: u32) -> Result<StationListOutput> { 89 + self.query( 90 + "fm.atradio.getFavorites", 91 + &[("actor", actor.into()), ("limit", limit.to_string())], 92 + ) 93 + .await 94 + } 95 + 96 + /// An actor's own created (custom) stations. 97 + pub async fn stations(&self, actor: &str, limit: u32) -> Result<StationListOutput> { 98 + self.query( 99 + "fm.atradio.getStations", 100 + &[("actor", actor.into()), ("limit", limit.to_string())], 101 + ) 102 + .await 103 + } 104 + 105 + /// Comments on a station, newest first. 106 + pub async fn comments(&self, station_id: &str, limit: u32) -> Result<CommentListOutput> { 107 + self.query( 108 + "fm.atradio.getComments", 109 + &[("station", station_id.into()), ("limit", limit.to_string())], 110 + ) 111 + .await 112 + } 113 + 114 + /// Unique-listener counts for up to 100 station ids. 115 + pub async fn listener_counts(&self, station_ids: &[String]) -> Result<Vec<ListenerCount>> { 116 + if station_ids.is_empty() { 117 + return Ok(Vec::new()); 118 + } 119 + let out: ListenerCountsOutput = self 120 + .query( 121 + "fm.atradio.getListenerCounts", 122 + &[("stations", station_ids.join(","))], 123 + ) 124 + .await?; 125 + Ok(out.counts) 126 + } 127 + 128 + /// An actor's notifications (mentions + comments on their stations). 129 + pub async fn notifications(&self, actor: &str, limit: u32) -> Result<NotificationListOutput> { 130 + self.query( 131 + "fm.atradio.getNotifications", 132 + &[("actor", actor.into()), ("limit", limit.to_string())], 133 + ) 134 + .await 135 + } 136 + 137 + /// Advance the actor's last-seen notification marker. 138 + pub async fn update_seen(&self, actor: &str) -> Result<u32> { 139 + let url = format!("{}/xrpc/fm.atradio.updateSeen", self.base); 140 + let res = self 141 + .http 142 + .post(&url) 143 + .json(&serde_json::json!({ "actor": actor })) 144 + .send() 145 + .await 146 + .context("updateSeen")?; 147 + #[derive(serde::Deserialize)] 148 + #[serde(rename_all = "camelCase")] 149 + struct Out { 150 + #[serde(default)] 151 + unread_count: u32, 152 + } 153 + let out: Out = res.json().await.context("decode updateSeen")?; 154 + Ok(out.unread_count) 155 + } 156 + }
+9
cli/src/appview/mod.rs
··· 1 + // The AppView client mirrors the full public XRPC surface; not every endpoint 2 + // is wired into the TUI yet, so allow the unused ones to stand as API. 3 + #![allow(dead_code)] 4 + 5 + pub mod client; 6 + pub mod models; 7 + 8 + pub use client::AppView; 9 + pub use models::*;
+213
cli/src/appview/models.rs
··· 1 + //! Wire types for the atradio.fm AppView (`fm.atradio.*` XRPC), mirroring 2 + //! `packages/lexicons/src/types.ts`. All responses are plain JSON. 3 + 4 + use serde::{Deserialize, Serialize}; 5 + 6 + /// The canonical, self-contained station snapshot (`fm.atradio.defs#stationInfo`). 7 + #[derive(Clone, Debug, Default, Serialize, Deserialize)] 8 + #[serde(rename_all = "camelCase")] 9 + pub struct StationInfo { 10 + /// `rb:<uuid>` | `tunein:<id>` | `custom:<rkey>`. 11 + pub station_id: String, 12 + pub name: String, 13 + pub stream_url: String, 14 + /// `radio-browser` | `tunein` | `custom`. 15 + #[serde(default)] 16 + pub source: String, 17 + #[serde(default, skip_serializing_if = "Option::is_none")] 18 + pub description: Option<String>, 19 + #[serde(default, skip_serializing_if = "Option::is_none")] 20 + pub genre: Option<String>, 21 + #[serde(default, skip_serializing_if = "Option::is_none")] 22 + pub homepage: Option<String>, 23 + #[serde(default, skip_serializing_if = "Option::is_none")] 24 + pub logo: Option<String>, 25 + #[serde(default, skip_serializing_if = "Option::is_none")] 26 + pub country: Option<String>, 27 + #[serde(default, skip_serializing_if = "Option::is_none")] 28 + pub language: Option<String>, 29 + #[serde(default, skip_serializing_if = "Option::is_none")] 30 + pub bitrate: Option<u32>, 31 + #[serde(default, skip_serializing_if = "Option::is_none")] 32 + pub codec: Option<String>, 33 + #[serde(default, skip_serializing_if = "Vec::is_empty")] 34 + pub tags: Vec<String>, 35 + } 36 + 37 + impl StationInfo { 38 + pub fn subtitle(&self) -> String { 39 + let mut parts: Vec<String> = Vec::new(); 40 + if let Some(g) = self.genre.as_ref().filter(|g| !g.is_empty()) { 41 + parts.push(g.clone()); 42 + } 43 + if let Some(c) = self.country.as_ref().filter(|c| !c.is_empty()) { 44 + parts.push(c.clone()); 45 + } 46 + if let Some(br) = self.bitrate.filter(|b| *b > 0) { 47 + parts.push(format!("{br}k")); 48 + } 49 + parts.join(" · ") 50 + } 51 + } 52 + 53 + #[derive(Clone, Debug, Default, Serialize, Deserialize)] 54 + #[serde(rename_all = "camelCase")] 55 + pub struct ActorInfo { 56 + pub did: String, 57 + #[serde(default)] 58 + pub handle: Option<String>, 59 + #[serde(default)] 60 + pub display_name: Option<String>, 61 + #[serde(default)] 62 + pub avatar: Option<String>, 63 + } 64 + 65 + impl ActorInfo { 66 + pub fn name(&self) -> String { 67 + self.display_name 68 + .clone() 69 + .or_else(|| self.handle.clone()) 70 + .unwrap_or_else(|| "someone".to_string()) 71 + } 72 + } 73 + 74 + #[derive(Clone, Debug, Deserialize)] 75 + #[serde(rename_all = "camelCase")] 76 + pub struct StationView { 77 + #[serde(default)] 78 + pub uri: String, 79 + pub station: StationInfo, 80 + #[serde(default)] 81 + pub created_at: Option<String>, 82 + } 83 + 84 + #[derive(Clone, Debug, Default, Deserialize)] 85 + #[serde(rename_all = "camelCase")] 86 + pub struct StationListOutput { 87 + #[serde(default)] 88 + pub cursor: Option<String>, 89 + #[serde(default)] 90 + pub total: u32, 91 + #[serde(default)] 92 + pub items: Vec<StationView>, 93 + } 94 + 95 + #[derive(Clone, Debug, Default, Deserialize)] 96 + pub struct RecentStationsOutput { 97 + #[serde(default)] 98 + pub items: Vec<StationView>, 99 + } 100 + 101 + #[derive(Clone, Debug, Deserialize)] 102 + pub struct PopularItem { 103 + pub station: StationInfo, 104 + #[serde(default)] 105 + pub count: u32, 106 + } 107 + 108 + #[derive(Clone, Debug, Default, Deserialize)] 109 + pub struct PopularStationsOutput { 110 + #[serde(default)] 111 + pub items: Vec<PopularItem>, 112 + } 113 + 114 + #[derive(Clone, Debug, Deserialize)] 115 + #[serde(rename_all = "camelCase")] 116 + pub struct PlayView { 117 + pub station: StationInfo, 118 + #[serde(default)] 119 + pub played_at: Option<String>, 120 + #[serde(default)] 121 + pub actor: Option<ActorInfo>, 122 + } 123 + 124 + #[derive(Clone, Debug, Default, Deserialize)] 125 + #[serde(rename_all = "camelCase")] 126 + pub struct PlayListOutput { 127 + #[serde(default)] 128 + pub cursor: Option<String>, 129 + #[serde(default)] 130 + pub items: Vec<PlayView>, 131 + } 132 + 133 + #[derive(Clone, Debug, Deserialize)] 134 + #[serde(rename_all = "camelCase")] 135 + pub struct ListenerCount { 136 + pub station_id: String, 137 + #[serde(default)] 138 + pub listeners: u32, 139 + } 140 + 141 + #[derive(Clone, Debug, Default, Deserialize)] 142 + pub struct ListenerCountsOutput { 143 + #[serde(default)] 144 + pub counts: Vec<ListenerCount>, 145 + } 146 + 147 + #[derive(Clone, Debug, Deserialize)] 148 + #[serde(rename_all = "camelCase")] 149 + pub struct Gif { 150 + pub url: String, 151 + #[serde(default)] 152 + pub preview_url: Option<String>, 153 + #[serde(default)] 154 + pub alt: Option<String>, 155 + } 156 + 157 + #[derive(Clone, Debug, Deserialize)] 158 + #[serde(rename_all = "camelCase")] 159 + pub struct CommentView { 160 + #[serde(default)] 161 + pub uri: String, 162 + #[serde(default)] 163 + pub author: Option<ActorInfo>, 164 + #[serde(default)] 165 + pub station: Option<StationInfo>, 166 + #[serde(default)] 167 + pub text: String, 168 + #[serde(default)] 169 + pub gif: Option<Gif>, 170 + #[serde(default)] 171 + pub created_at: Option<String>, 172 + } 173 + 174 + #[derive(Clone, Debug, Default, Deserialize)] 175 + #[serde(rename_all = "camelCase")] 176 + pub struct CommentListOutput { 177 + #[serde(default)] 178 + pub cursor: Option<String>, 179 + #[serde(default)] 180 + pub total: u32, 181 + #[serde(default)] 182 + pub items: Vec<CommentView>, 183 + } 184 + 185 + #[derive(Clone, Debug, Deserialize)] 186 + #[serde(rename_all = "camelCase")] 187 + pub struct NotificationView { 188 + #[serde(default)] 189 + pub uri: String, 190 + /// `mention` | `comment`. 191 + #[serde(default)] 192 + pub reason: String, 193 + pub author: ActorInfo, 194 + #[serde(default)] 195 + pub station: Option<StationInfo>, 196 + #[serde(default)] 197 + pub text: Option<String>, 198 + #[serde(default)] 199 + pub created_at: Option<String>, 200 + #[serde(default)] 201 + pub is_read: bool, 202 + } 203 + 204 + #[derive(Clone, Debug, Default, Deserialize)] 205 + #[serde(rename_all = "camelCase")] 206 + pub struct NotificationListOutput { 207 + #[serde(default)] 208 + pub cursor: Option<String>, 209 + #[serde(default)] 210 + pub unread_count: u32, 211 + #[serde(default)] 212 + pub items: Vec<NotificationView>, 213 + }
+261
cli/src/atproto/mod.rs
··· 1 + //! atproto integration via jacquard: app-password + OAuth login, and record 2 + //! writes to the user's PDS (favorites, comments, play status). 3 + //! 4 + //! Reads are served by the public AppView ([`crate::appview`]); this module is 5 + //! only for identity and writes, which require an authenticated session. 6 + 7 + pub mod profile; 8 + 9 + use std::path::PathBuf; 10 + use std::sync::Arc; 11 + 12 + use anyhow::{anyhow, Context, Result}; 13 + use jacquard::client::credential_session::{ 14 + CredentialLoginOptions, CredentialResumeResult, CredentialSession, 15 + }; 16 + use jacquard::client::{Agent, AgentSessionExt, FileAuthStore}; 17 + use jacquard::common::session::SessionHint; 18 + use jacquard::identity::JacquardResolver; 19 + use jacquard::types::string::{Datetime, UriValue}; 20 + use jacquard_common::types::recordkey::{RecordKey, Rkey}; 21 + 22 + use crate::appview::StationInfo; 23 + use crate::fm_atradio::actor::status::Status as ActorStatus; 24 + use crate::fm_atradio::comment::Comment; 25 + use crate::fm_atradio::favorite::Favorite; 26 + use crate::fm_atradio::StationInfo as GenStation; 27 + 28 + pub use profile::Profile; 29 + 30 + /// The atproto client: owns credential material and a handle resolver. 31 + type Resolver = JacquardResolver<reqwest::Client>; 32 + 33 + #[derive(Clone)] 34 + pub struct Atproto { 35 + store: Arc<FileAuthStore>, 36 + resolver: Arc<Resolver>, 37 + session_path: PathBuf, 38 + } 39 + 40 + impl Atproto { 41 + pub fn new(session_path: PathBuf) -> Self { 42 + let store = Arc::new(FileAuthStore::new( 43 + session_path.to_string_lossy().to_string(), 44 + )); 45 + let resolver = Arc::new(JacquardResolver::new( 46 + reqwest::Client::new(), 47 + Default::default(), 48 + )); 49 + Self { 50 + store, 51 + resolver, 52 + session_path, 53 + } 54 + } 55 + 56 + /// The locally-cached identity, if logged in. 57 + pub fn profile(&self) -> Option<Profile> { 58 + Profile::load(&self.session_path) 59 + } 60 + 61 + /// The `actor` to use for personalized AppView reads (handle or DID). 62 + pub fn actor(&self) -> Option<String> { 63 + self.profile().map(|p| p.handle) 64 + } 65 + 66 + pub fn is_logged_in(&self) -> bool { 67 + self.profile().is_some() 68 + } 69 + 70 + /// Log out: drop the cached session + profile. 71 + pub fn logout(&self) { 72 + Profile::clear(&self.session_path); 73 + let _ = std::fs::remove_file(&self.session_path); 74 + } 75 + 76 + // ---- auth ------------------------------------------------------------ 77 + 78 + /// Log in with an app password. Persists the session + profile. 79 + pub async fn login_password(&self, identifier: &str, password: &str) -> Result<Profile> { 80 + let session = CredentialSession::new(self.store.clone(), self.resolver.clone()); 81 + let hint = SessionHint::from_optional_input(Some(identifier)); 82 + 83 + let auth = match session.resume(&hint).await.map_err(to_anyhow)? { 84 + CredentialResumeResult::Resumed(auth) => auth, 85 + CredentialResumeResult::LoginRequired(challenge) => session 86 + .login_from_challenge( 87 + challenge, 88 + CredentialLoginOptions { 89 + password: password.to_string().into(), 90 + identifier: Some(identifier.to_string().into()), 91 + allow_takendown: None, 92 + auth_factor_token: None, 93 + pds: None, 94 + }, 95 + ) 96 + .await 97 + .map_err(to_anyhow)?, 98 + }; 99 + 100 + let profile = Profile { 101 + did: auth.did.to_string(), 102 + handle: auth.handle.to_string(), 103 + pds: auth.pds.as_ref().map(|u| u.to_string()), 104 + method: "password".into(), 105 + }; 106 + profile.save(&self.session_path)?; 107 + Ok(profile) 108 + } 109 + 110 + /// Log in via OAuth, opening the browser to the user's PDS. Persists the 111 + /// session + profile. 112 + pub async fn login_oauth(&self, input: Option<&str>) -> Result<Profile> { 113 + use jacquard::oauth::atproto::AtprotoClientMetadata; 114 + use jacquard::oauth::client::OAuthClient; 115 + use jacquard::oauth::loopback::LoopbackConfig; 116 + use jacquard::oauth::scopes::Scopes; 117 + use jacquard::oauth::types::AuthorizeOptions; 118 + 119 + let client_data = jacquard::oauth::session::ClientData { 120 + keyset: None, 121 + config: AtprotoClientMetadata::default_localhost(), 122 + }; 123 + // OAuthClient takes an owned store; make a fresh file-backed one at the 124 + // same path so it shares the session with the credential path. 125 + let store = FileAuthStore::new(self.session_path.to_string_lossy().to_string()); 126 + let oauth = OAuthClient::new(store, client_data, reqwest::Client::new()); 127 + let hint = SessionHint::from_optional_input(input); 128 + let scopes = Scopes::builder().atproto().build().map_err(to_anyhow)?; 129 + 130 + let session = match oauth 131 + .resume_or_login_with_local_server( 132 + &hint, 133 + AuthorizeOptions::default().with_scopes(scopes.clone()), 134 + LoopbackConfig::default(), 135 + ) 136 + .await 137 + .map_err(to_anyhow)? 138 + { 139 + Some(session) => session, 140 + None => { 141 + let who = input.ok_or_else(|| { 142 + anyhow!("pass a handle, DID, or PDS URL to start OAuth login") 143 + })?; 144 + oauth 145 + .login_with_local_server( 146 + who.to_string(), 147 + AuthorizeOptions::default().with_scopes(scopes), 148 + LoopbackConfig::default(), 149 + ) 150 + .await 151 + .map_err(to_anyhow)? 152 + } 153 + }; 154 + 155 + let agent: Agent<_> = Agent::from(session); 156 + let (did, handle) = agent 157 + .info() 158 + .await 159 + .ok_or_else(|| anyhow!("OAuth session missing identity"))?; 160 + let profile = Profile { 161 + did: did.to_string(), 162 + handle: handle.map(|h| h.to_string()).unwrap_or_default(), 163 + pds: None, 164 + method: "oauth".into(), 165 + }; 166 + profile.save(&self.session_path)?; 167 + Ok(profile) 168 + } 169 + 170 + // ---- writes (require a resumed app-password session) ----------------- 171 + 172 + /// Resume the stored app-password session into an authenticated agent. 173 + async fn agent(&self) -> Result<Agent<CredentialSession<FileAuthStore, Resolver>>> { 174 + let session = CredentialSession::new(self.store.clone(), self.resolver.clone()); 175 + let actor = self.actor(); 176 + let hint = SessionHint::from_optional_input(actor.as_deref()); 177 + match session.resume(&hint).await.map_err(to_anyhow)? { 178 + CredentialResumeResult::Resumed(_) => Ok(Agent::from(session)), 179 + CredentialResumeResult::LoginRequired(_) => Err(anyhow!( 180 + "not signed in — run `atradio login` with an app password to post" 181 + )), 182 + } 183 + } 184 + 185 + /// Favorite a station. Returns the created record URI. 186 + pub async fn favorite(&self, station: &StationInfo) -> Result<String> { 187 + let record = Favorite::new() 188 + .station(gen_station(station)) 189 + .created_at(Datetime::now()) 190 + .build(); 191 + let out = self 192 + .agent() 193 + .await? 194 + .create_record(record, None) 195 + .await 196 + .map_err(to_anyhow) 197 + .context("create favorite")?; 198 + Ok(out.uri.to_string()) 199 + } 200 + 201 + /// Post a comment on a station. Returns the created record URI. 202 + pub async fn comment(&self, station: &StationInfo, text: &str) -> Result<String> { 203 + let record = Comment::new() 204 + .station(gen_station(station)) 205 + .text(text.to_string()) 206 + .created_at(Datetime::now()) 207 + .build(); 208 + let out = self 209 + .agent() 210 + .await? 211 + .create_record(record, None) 212 + .await 213 + .map_err(to_anyhow) 214 + .context("create comment")?; 215 + Ok(out.uri.to_string()) 216 + } 217 + 218 + /// Update the actor's play-status singleton (so you appear in the 219 + /// recently-played / listener-count feeds). 220 + pub async fn set_play_status(&self, station: &StationInfo) -> Result<()> { 221 + let record = ActorStatus::new() 222 + .station(gen_station(station)) 223 + .played_at(Datetime::now()) 224 + .build(); 225 + let rkey: RecordKey<Rkey> = "self".parse().map_err(|e| anyhow!("rkey: {e}"))?; 226 + self.agent() 227 + .await? 228 + .put_record(rkey, record) 229 + .await 230 + .map_err(to_anyhow) 231 + .context("update play status")?; 232 + Ok(()) 233 + } 234 + } 235 + 236 + /// Convert our wire `StationInfo` into the generated (lexicon) `StationInfo`. 237 + fn gen_station(s: &StationInfo) -> GenStation { 238 + GenStation::new() 239 + .station_id(s.station_id.clone()) 240 + .name(s.name.clone()) 241 + .stream_url(parse_uri(&s.stream_url).unwrap_or_else(|| UriValue::Any(s.stream_url.clone().into()))) 242 + .source(s.source.clone()) 243 + .maybe_description(s.description.clone().map(Into::into)) 244 + .maybe_genre(s.genre.clone().map(Into::into)) 245 + .maybe_homepage(s.homepage.as_deref().and_then(parse_uri)) 246 + .maybe_logo(s.logo.as_deref().and_then(parse_uri)) 247 + .maybe_country(s.country.clone().map(Into::into)) 248 + .maybe_language(s.language.clone().map(Into::into)) 249 + .maybe_codec(s.codec.clone().map(Into::into)) 250 + .bitrate(s.bitrate.map(|b| b as i64)) 251 + .build() 252 + } 253 + 254 + /// Parse a URL string into a lexicon `UriValue`, dropping anything invalid. 255 + fn parse_uri(s: &str) -> Option<UriValue> { 256 + UriValue::new_owned(s).ok() 257 + } 258 + 259 + fn to_anyhow<E: std::fmt::Display>(e: E) -> anyhow::Error { 260 + anyhow!("{e}") 261 + }
+42
cli/src/atproto/profile.rs
··· 1 + //! A tiny locally-cached identity, written on login so `whoami` and 2 + //! personalized AppView reads work without a network round-trip. 3 + 4 + use std::path::{Path, PathBuf}; 5 + 6 + use anyhow::Result; 7 + use serde::{Deserialize, Serialize}; 8 + 9 + #[derive(Clone, Debug, Serialize, Deserialize)] 10 + pub struct Profile { 11 + pub did: String, 12 + pub handle: String, 13 + #[serde(default)] 14 + pub pds: Option<String>, 15 + /// "password" | "oauth". 16 + #[serde(default)] 17 + pub method: String, 18 + } 19 + 20 + impl Profile { 21 + fn path(session_path: &Path) -> PathBuf { 22 + session_path.with_file_name("profile.json") 23 + } 24 + 25 + pub fn load(session_path: &Path) -> Option<Profile> { 26 + let bytes = std::fs::read(Self::path(session_path)).ok()?; 27 + serde_json::from_slice(&bytes).ok() 28 + } 29 + 30 + pub fn save(&self, session_path: &Path) -> Result<()> { 31 + let p = Self::path(session_path); 32 + if let Some(dir) = p.parent() { 33 + std::fs::create_dir_all(dir)?; 34 + } 35 + std::fs::write(p, serde_json::to_vec_pretty(self)?)?; 36 + Ok(()) 37 + } 38 + 39 + pub fn clear(session_path: &Path) { 40 + let _ = std::fs::remove_file(Self::path(session_path)); 41 + } 42 + }
+45
cli/src/builder_types.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + /// Marker type indicating a builder field has been set 7 + pub struct Set<T>(pub T); 8 + impl<T> Set<T> { 9 + /// Extract the inner value 10 + #[inline] 11 + pub fn into_inner(self) -> T { 12 + self.0 13 + } 14 + } 15 + 16 + /// Marker type indicating a builder field has not been set 17 + pub struct Unset; 18 + /// Trait indicating a builder field is set (has a value) 19 + 20 + #[jacquard_common::deps::codegen::rustversion::attr( 21 + since(1.78.0), 22 + diagnostic::on_unimplemented( 23 + message = "the field `{Self}` was not set, but this method requires it to be set", 24 + label = "the field `{Self}` was not set" 25 + ) 26 + )] 27 + pub trait IsSet: private::Sealed {} 28 + /// Trait indicating a builder field is unset (no value yet) 29 + 30 + #[jacquard_common::deps::codegen::rustversion::attr( 31 + since(1.78.0), 32 + diagnostic::on_unimplemented( 33 + message = "the field `{Self}` was already set, but this method requires it to be unset", 34 + label = "the field `{Self}` was already set" 35 + ) 36 + )] 37 + pub trait IsUnset: private::Sealed {} 38 + impl<T> IsSet for Set<T> {} 39 + impl IsUnset for Unset {} 40 + mod private { 41 + /// Sealed trait to prevent external implementations 42 + pub trait Sealed {} 43 + impl<T> Sealed for super::Set<T> {} 44 + impl Sealed for super::Unset {} 45 + }
+211
cli/src/cli.rs
··· 1 + //! Command-line surface. With no subcommand, the interactive TUI launches. 2 + 3 + use anyhow::Result; 4 + use clap::{Parser, Subcommand}; 5 + 6 + use crate::appview::AppView; 7 + use crate::atproto::Atproto; 8 + use crate::config::Config; 9 + use crate::radio::RadioBrowser; 10 + use crate::theme; 11 + 12 + #[derive(Parser)] 13 + #[command( 14 + name = "atradio", 15 + version, 16 + about = "atradio.fm in your terminal — a synthwave radio player on the AT Protocol", 17 + long_about = "atradio.fm in your terminal.\n\nRun with no arguments to open the interactive TUI. \ 18 + Credentials for posting are read from ATPROTO_IDENTIFIER and ATPROTO_APP_PASSWORD, \ 19 + or run `atradio login`.", 20 + styles = theme::clap_styles(), 21 + arg_required_else_help = false 22 + )] 23 + pub struct Cli { 24 + #[command(subcommand)] 25 + pub command: Option<Command>, 26 + } 27 + 28 + #[derive(Subcommand)] 29 + pub enum Command { 30 + /// Launch the interactive TUI (default when no command is given). 31 + Tui, 32 + 33 + /// Search stations on radio-browser and print the results. 34 + Search { 35 + /// Free-text query (station name). 36 + query: Vec<String>, 37 + /// Max results. 38 + #[arg(short, long, default_value_t = 20)] 39 + limit: u32, 40 + }, 41 + 42 + /// Play a station stream URL directly (headless, no TUI). 43 + Play { 44 + /// A stream URL, or a radio-browser search query to play the top hit. 45 + target: Vec<String>, 46 + }, 47 + 48 + /// Show trending / recent stations from the AppView. 49 + Trending { 50 + #[arg(short, long, default_value_t = 20)] 51 + limit: u32, 52 + }, 53 + 54 + /// Sign in so you can favorite and comment. 55 + Login { 56 + /// Handle, DID, or PDS URL (optional; falls back to env / prompt). 57 + identifier: Option<String>, 58 + /// Use the browser OAuth flow instead of an app password. 59 + #[arg(long)] 60 + oauth: bool, 61 + }, 62 + 63 + /// Sign out (forget the stored session). 64 + Logout, 65 + 66 + /// Show the currently signed-in account. 67 + Whoami, 68 + } 69 + 70 + pub async fn run(cli: Cli) -> Result<()> { 71 + let config = Config::from_env(); 72 + 73 + match cli.command.unwrap_or(Command::Tui) { 74 + Command::Tui => crate::tui::run(config).await, 75 + Command::Search { query, limit } => cmd_search(query.join(" "), limit).await, 76 + Command::Play { target } => cmd_play(target.join(" "), config).await, 77 + Command::Trending { limit } => cmd_trending(limit, &config).await, 78 + Command::Login { identifier, oauth } => cmd_login(identifier, oauth, &config).await, 79 + Command::Logout => cmd_logout(&config), 80 + Command::Whoami => cmd_whoami(&config), 81 + } 82 + } 83 + 84 + async fn cmd_search(query: String, limit: u32) -> Result<()> { 85 + if query.trim().is_empty() { 86 + anyhow::bail!("provide a search query, e.g. `atradio search lofi`"); 87 + } 88 + let rb = RadioBrowser::new(); 89 + let results = rb.search(&query, limit).await?; 90 + if results.is_empty() { 91 + println!("No stations found for “{query}”."); 92 + return Ok(()); 93 + } 94 + for (i, s) in results.iter().enumerate() { 95 + let sub = s.subtitle(); 96 + println!( 97 + "{:>2}. {}{}", 98 + i + 1, 99 + s.name, 100 + if sub.is_empty() { 101 + String::new() 102 + } else { 103 + format!(" ({sub})") 104 + } 105 + ); 106 + println!(" {}", s.stream_url); 107 + } 108 + Ok(()) 109 + } 110 + 111 + async fn cmd_trending(limit: u32, config: &Config) -> Result<()> { 112 + let av = AppView::new(&config.appview_url); 113 + let items = av.recent_stations(limit).await?; 114 + if items.is_empty() { 115 + println!("Nothing trending right now."); 116 + return Ok(()); 117 + } 118 + for (i, v) in items.iter().enumerate() { 119 + println!("{:>2}. {}", i + 1, v.station.name); 120 + } 121 + Ok(()) 122 + } 123 + 124 + async fn cmd_play(target: String, _config: Config) -> Result<()> { 125 + if target.trim().is_empty() { 126 + anyhow::bail!("provide a stream URL or search query"); 127 + } 128 + // Resolve the URL: use it directly if it looks like one, else search. 129 + let url = if target.starts_with("http://") || target.starts_with("https://") { 130 + target.clone() 131 + } else { 132 + let rb = RadioBrowser::new(); 133 + let hits = rb.search(&target, 1).await?; 134 + let s = hits 135 + .into_iter() 136 + .next() 137 + .ok_or_else(|| anyhow::anyhow!("no station found for “{target}”"))?; 138 + println!("▶ {}", s.name); 139 + s.stream_url 140 + }; 141 + 142 + let player = std::sync::Arc::new(crate::player::Player::new()?); 143 + #[cfg(target_os = "linux")] 144 + crate::mpris::spawn(player.clone()); 145 + player.play_url(&url); 146 + println!("Playing {url}\nPress Ctrl-C to stop."); 147 + 148 + // Poll and print now-playing until interrupted. 149 + let mut last = String::new(); 150 + loop { 151 + tokio::time::sleep(std::time::Duration::from_millis(500)).await; 152 + let np = player.now_playing(); 153 + if let Some(line) = np.line() { 154 + if line != last { 155 + println!("♪ {line}"); 156 + last = line; 157 + } 158 + } 159 + } 160 + } 161 + 162 + async fn cmd_login(identifier: Option<String>, oauth: bool, config: &Config) -> Result<()> { 163 + let at = Atproto::new(config.session_path.clone()); 164 + 165 + let profile = if oauth { 166 + let hint = identifier.or_else(|| config.identifier.clone()); 167 + at.login_oauth(hint.as_deref()).await? 168 + } else { 169 + let id = identifier 170 + .or_else(|| config.identifier.clone()) 171 + .ok_or_else(|| { 172 + anyhow::anyhow!( 173 + "provide a handle/DID (arg or ATPROTO_IDENTIFIER), or use --oauth" 174 + ) 175 + })?; 176 + let pw = config.app_password.clone().ok_or_else(|| { 177 + anyhow::anyhow!("set ATPROTO_APP_PASSWORD to sign in with an app password, or use --oauth") 178 + })?; 179 + at.login_password(&id, &pw).await? 180 + }; 181 + 182 + println!("✓ Signed in as @{} ({})", profile.handle, profile.did); 183 + Ok(()) 184 + } 185 + 186 + fn cmd_logout(config: &Config) -> Result<()> { 187 + let at = Atproto::new(config.session_path.clone()); 188 + if at.is_logged_in() { 189 + at.logout(); 190 + println!("Signed out."); 191 + } else { 192 + println!("Not signed in."); 193 + } 194 + Ok(()) 195 + } 196 + 197 + fn cmd_whoami(config: &Config) -> Result<()> { 198 + let at = Atproto::new(config.session_path.clone()); 199 + match at.profile() { 200 + Some(p) => { 201 + println!("@{}", p.handle); 202 + println!(" did: {}", p.did); 203 + println!(" via: {}", if p.method.is_empty() { "password" } else { &p.method }); 204 + if let Some(pds) = p.pds { 205 + println!(" pds: {pds}"); 206 + } 207 + } 208 + None => println!("Not signed in. Run `atradio login`."), 209 + } 210 + Ok(()) 211 + }
+57
cli/src/config.rs
··· 1 + //! Runtime configuration, sourced from env vars with sane defaults. 2 + 3 + use std::path::PathBuf; 4 + 5 + /// Production AppView base URL (matches `apps/web/src/lib/appview.ts`). 6 + const DEFAULT_APPVIEW: &str = "https://api.atradio.fm"; 7 + 8 + #[derive(Clone, Debug)] 9 + pub struct Config { 10 + /// AppView base URL, e.g. `https://api.atradio.fm` (no trailing slash). 11 + pub appview_url: String, 12 + /// atproto identifier (handle / DID / email) for password auth, if set. 13 + pub identifier: Option<String>, 14 + /// atproto app password, if set. 15 + pub app_password: Option<String>, 16 + /// Where the persisted session token lives. 17 + pub session_path: PathBuf, 18 + } 19 + 20 + impl Config { 21 + pub fn from_env() -> Self { 22 + let appview_url = std::env::var("ATRADIO_APPVIEW_URL") 23 + .or_else(|_| std::env::var("APPVIEW_URL")) 24 + .or_else(|_| std::env::var("VITE_APPVIEW_URL")) 25 + .unwrap_or_else(|_| DEFAULT_APPVIEW.to_string()) 26 + .trim_end_matches('/') 27 + .to_string(); 28 + 29 + let identifier = env_nonempty("ATPROTO_IDENTIFIER") 30 + .or_else(|| env_nonempty("ATRADIO_IDENTIFIER")) 31 + .or_else(|| env_nonempty("BLUESKY_IDENTIFIER")); 32 + 33 + let app_password = env_nonempty("ATPROTO_APP_PASSWORD") 34 + .or_else(|| env_nonempty("ATPROTO_PASSWORD")) 35 + .or_else(|| env_nonempty("ATRADIO_APP_PASSWORD")) 36 + .or_else(|| env_nonempty("BLUESKY_APP_PASSWORD")); 37 + 38 + Self { 39 + appview_url, 40 + identifier, 41 + app_password, 42 + session_path: session_path(), 43 + } 44 + } 45 + 46 + } 47 + 48 + fn env_nonempty(key: &str) -> Option<String> { 49 + std::env::var(key).ok().filter(|v| !v.trim().is_empty()) 50 + } 51 + 52 + /// `~/.config/atradio/session.json` (XDG on Linux, App Support on macOS). 53 + fn session_path() -> PathBuf { 54 + directories::ProjectDirs::from("fm", "atradio", "atradio") 55 + .map(|d| d.config_dir().join("session.json")) 56 + .unwrap_or_else(|| PathBuf::from(".atradio-session.json")) 57 + }
+2466
cli/src/fm_atradio.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.defs 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + //! Generated bindings for the `fm.atradio` Lexicon namespace/module. 9 + pub mod actor; 10 + pub mod audio; 11 + pub mod comment; 12 + pub mod favorite; 13 + pub mod get_comments; 14 + pub mod get_favorites; 15 + pub mod get_global_recently_played; 16 + pub mod get_listener_counts; 17 + pub mod get_notifications; 18 + pub mod get_recently_played; 19 + pub mod get_stations; 20 + pub mod reaction; 21 + pub mod station; 22 + pub mod update_seen; 23 + 24 + 25 + #[allow(unused_imports)] 26 + use alloc::collections::BTreeMap; 27 + 28 + #[allow(unused_imports)] 29 + use core::marker::PhantomData; 30 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 31 + 32 + #[allow(unused_imports)] 33 + use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 34 + use jacquard_common::deps::smol_str::SmolStr; 35 + use jacquard_common::types::ident::AtIdentifier; 36 + use jacquard_common::types::string::{Did, AtUri, Datetime, UriValue}; 37 + use jacquard_common::types::value::Data; 38 + use jacquard_derive::IntoStatic; 39 + use jacquard_lexicon::lexicon::LexiconDoc; 40 + use jacquard_lexicon::schema::LexiconSchema; 41 + 42 + #[allow(unused_imports)] 43 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 44 + use serde::{Serialize, Deserialize}; 45 + use crate::fm_atradio::comment::Gif; 46 + use crate::fm_atradio::comment::Mention; 47 + use crate::fm_atradio; 48 + /// A minimal public snapshot of an actor. 49 + 50 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 51 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 52 + pub struct ActorInfo<S: BosStr = DefaultStr> { 53 + ///Avatar URL. 54 + #[serde(skip_serializing_if = "Option::is_none")] 55 + pub avatar: Option<UriValue<S>>, 56 + pub did: Did<S>, 57 + #[serde(skip_serializing_if = "Option::is_none")] 58 + pub display_name: Option<S>, 59 + #[serde(skip_serializing_if = "Option::is_none")] 60 + pub handle: Option<AtIdentifier<S>>, 61 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 62 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 63 + } 64 + 65 + /// A comment on a station, paired with its record uri and author. 66 + 67 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 68 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 69 + pub struct CommentView<S: BosStr = DefaultStr> { 70 + #[serde(skip_serializing_if = "Option::is_none")] 71 + pub author: Option<fm_atradio::ActorInfo<S>>, 72 + pub created_at: Datetime, 73 + #[serde(skip_serializing_if = "Option::is_none")] 74 + pub facets: Option<Vec<Mention<S>>>, 75 + #[serde(skip_serializing_if = "Option::is_none")] 76 + pub gif: Option<Gif<S>>, 77 + pub station: fm_atradio::StationInfo<S>, 78 + pub text: S, 79 + pub uri: AtUri<S>, 80 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 81 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 82 + } 83 + 84 + /// Unique-listener count for a single station. 85 + 86 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 87 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 88 + pub struct ListenerCount<S: BosStr = DefaultStr> { 89 + ///Number of distinct actors who have played this station. 90 + pub listeners: i64, 91 + pub station_id: S, 92 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 93 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 94 + } 95 + 96 + /// A single notification for an actor. 97 + 98 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 99 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 100 + pub struct NotificationView<S: BosStr = DefaultStr> { 101 + pub author: fm_atradio::ActorInfo<S>, 102 + pub created_at: Datetime, 103 + pub is_read: bool, 104 + ///Why this notification exists: mention | comment. 105 + pub reason: S, 106 + #[serde(skip_serializing_if = "Option::is_none")] 107 + pub station: Option<fm_atradio::StationInfo<S>>, 108 + ///Snapshot of the comment text. 109 + #[serde(skip_serializing_if = "Option::is_none")] 110 + pub text: Option<S>, 111 + ///URI of the subject record (the comment). 112 + pub uri: AtUri<S>, 113 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 114 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 115 + } 116 + 117 + /// A single play event: a station an actor listened to at a point in time. 118 + 119 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 120 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 121 + pub struct PlayView<S: BosStr = DefaultStr> { 122 + #[serde(skip_serializing_if = "Option::is_none")] 123 + pub actor: Option<fm_atradio::ActorInfo<S>>, 124 + pub played_at: Datetime, 125 + pub station: fm_atradio::StationInfo<S>, 126 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 127 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 128 + } 129 + 130 + /// A self-contained snapshot of a radio station. 131 + 132 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 133 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 134 + pub struct StationInfo<S: BosStr = DefaultStr> { 135 + #[serde(skip_serializing_if = "Option::is_none")] 136 + pub bitrate: Option<i64>, 137 + #[serde(skip_serializing_if = "Option::is_none")] 138 + pub codec: Option<S>, 139 + #[serde(skip_serializing_if = "Option::is_none")] 140 + pub country: Option<S>, 141 + #[serde(skip_serializing_if = "Option::is_none")] 142 + pub description: Option<S>, 143 + #[serde(skip_serializing_if = "Option::is_none")] 144 + pub genre: Option<S>, 145 + #[serde(skip_serializing_if = "Option::is_none")] 146 + pub homepage: Option<UriValue<S>>, 147 + #[serde(skip_serializing_if = "Option::is_none")] 148 + pub language: Option<S>, 149 + ///Logo/artwork URL. 150 + #[serde(skip_serializing_if = "Option::is_none")] 151 + pub logo: Option<UriValue<S>>, 152 + pub name: S, 153 + ///Provider: radio-browser | tunein | custom. 154 + pub source: S, 155 + ///Source-prefixed stable id, e.g. rb:uuid, tunein:s123, custom:xyz. 156 + pub station_id: S, 157 + ///Direct, playable stream URL. 158 + pub stream_url: UriValue<S>, 159 + #[serde(skip_serializing_if = "Option::is_none")] 160 + pub tags: Option<Vec<S>>, 161 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 162 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 163 + } 164 + 165 + /// A station snapshot paired with its record uri (query output item). 166 + 167 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 168 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 169 + pub struct StationView<S: BosStr = DefaultStr> { 170 + pub created_at: Datetime, 171 + pub station: fm_atradio::StationInfo<S>, 172 + pub uri: AtUri<S>, 173 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 174 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 175 + } 176 + 177 + impl<S: BosStr> LexiconSchema for ActorInfo<S> { 178 + fn nsid() -> &'static str { 179 + "fm.atradio.defs" 180 + } 181 + fn def_name() -> &'static str { 182 + "actorInfo" 183 + } 184 + fn lexicon_doc() -> LexiconDoc<'static> { 185 + lexicon_doc_fm_atradio_defs() 186 + } 187 + fn validate(&self) -> Result<(), ConstraintError> { 188 + Ok(()) 189 + } 190 + } 191 + 192 + impl<S: BosStr> LexiconSchema for CommentView<S> { 193 + fn nsid() -> &'static str { 194 + "fm.atradio.defs" 195 + } 196 + fn def_name() -> &'static str { 197 + "commentView" 198 + } 199 + fn lexicon_doc() -> LexiconDoc<'static> { 200 + lexicon_doc_fm_atradio_defs() 201 + } 202 + fn validate(&self) -> Result<(), ConstraintError> { 203 + Ok(()) 204 + } 205 + } 206 + 207 + impl<S: BosStr> LexiconSchema for ListenerCount<S> { 208 + fn nsid() -> &'static str { 209 + "fm.atradio.defs" 210 + } 211 + fn def_name() -> &'static str { 212 + "listenerCount" 213 + } 214 + fn lexicon_doc() -> LexiconDoc<'static> { 215 + lexicon_doc_fm_atradio_defs() 216 + } 217 + fn validate(&self) -> Result<(), ConstraintError> { 218 + { 219 + let value = &self.listeners; 220 + if *value < 0i64 { 221 + return Err(ConstraintError::Minimum { 222 + path: ValidationPath::from_field("listeners"), 223 + min: 0i64, 224 + actual: *value, 225 + }); 226 + } 227 + } 228 + Ok(()) 229 + } 230 + } 231 + 232 + impl<S: BosStr> LexiconSchema for NotificationView<S> { 233 + fn nsid() -> &'static str { 234 + "fm.atradio.defs" 235 + } 236 + fn def_name() -> &'static str { 237 + "notificationView" 238 + } 239 + fn lexicon_doc() -> LexiconDoc<'static> { 240 + lexicon_doc_fm_atradio_defs() 241 + } 242 + fn validate(&self) -> Result<(), ConstraintError> { 243 + Ok(()) 244 + } 245 + } 246 + 247 + impl<S: BosStr> LexiconSchema for PlayView<S> { 248 + fn nsid() -> &'static str { 249 + "fm.atradio.defs" 250 + } 251 + fn def_name() -> &'static str { 252 + "playView" 253 + } 254 + fn lexicon_doc() -> LexiconDoc<'static> { 255 + lexicon_doc_fm_atradio_defs() 256 + } 257 + fn validate(&self) -> Result<(), ConstraintError> { 258 + Ok(()) 259 + } 260 + } 261 + 262 + impl<S: BosStr> LexiconSchema for StationInfo<S> { 263 + fn nsid() -> &'static str { 264 + "fm.atradio.defs" 265 + } 266 + fn def_name() -> &'static str { 267 + "stationInfo" 268 + } 269 + fn lexicon_doc() -> LexiconDoc<'static> { 270 + lexicon_doc_fm_atradio_defs() 271 + } 272 + fn validate(&self) -> Result<(), ConstraintError> { 273 + if let Some(ref value) = self.bitrate { 274 + if *value < 0i64 { 275 + return Err(ConstraintError::Minimum { 276 + path: ValidationPath::from_field("bitrate"), 277 + min: 0i64, 278 + actual: *value, 279 + }); 280 + } 281 + } 282 + if let Some(ref value) = self.description { 283 + #[allow(unused_comparisons)] 284 + if <str>::len(value.as_ref()) > 2000usize { 285 + return Err(ConstraintError::MaxLength { 286 + path: ValidationPath::from_field("description"), 287 + max: 2000usize, 288 + actual: <str>::len(value.as_ref()), 289 + }); 290 + } 291 + } 292 + if let Some(ref value) = self.genre { 293 + #[allow(unused_comparisons)] 294 + if <str>::len(value.as_ref()) > 256usize { 295 + return Err(ConstraintError::MaxLength { 296 + path: ValidationPath::from_field("genre"), 297 + max: 256usize, 298 + actual: <str>::len(value.as_ref()), 299 + }); 300 + } 301 + } 302 + { 303 + let value = &self.name; 304 + #[allow(unused_comparisons)] 305 + if <str>::len(value.as_ref()) > 512usize { 306 + return Err(ConstraintError::MaxLength { 307 + path: ValidationPath::from_field("name"), 308 + max: 512usize, 309 + actual: <str>::len(value.as_ref()), 310 + }); 311 + } 312 + } 313 + { 314 + let value = &self.name; 315 + #[allow(unused_comparisons)] 316 + if <str>::len(value.as_ref()) < 1usize { 317 + return Err(ConstraintError::MinLength { 318 + path: ValidationPath::from_field("name"), 319 + min: 1usize, 320 + actual: <str>::len(value.as_ref()), 321 + }); 322 + } 323 + } 324 + { 325 + let value = &self.station_id; 326 + #[allow(unused_comparisons)] 327 + if <str>::len(value.as_ref()) > 512usize { 328 + return Err(ConstraintError::MaxLength { 329 + path: ValidationPath::from_field("station_id"), 330 + max: 512usize, 331 + actual: <str>::len(value.as_ref()), 332 + }); 333 + } 334 + } 335 + Ok(()) 336 + } 337 + } 338 + 339 + impl<S: BosStr> LexiconSchema for StationView<S> { 340 + fn nsid() -> &'static str { 341 + "fm.atradio.defs" 342 + } 343 + fn def_name() -> &'static str { 344 + "stationView" 345 + } 346 + fn lexicon_doc() -> LexiconDoc<'static> { 347 + lexicon_doc_fm_atradio_defs() 348 + } 349 + fn validate(&self) -> Result<(), ConstraintError> { 350 + Ok(()) 351 + } 352 + } 353 + 354 + pub mod actor_info_state { 355 + 356 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 357 + #[allow(unused)] 358 + use ::core::marker::PhantomData; 359 + mod sealed { 360 + pub trait Sealed {} 361 + } 362 + /// State trait tracking which required fields have been set 363 + pub trait State: sealed::Sealed { 364 + type Did; 365 + } 366 + /// Empty state - all required fields are unset 367 + pub struct Empty(()); 368 + impl sealed::Sealed for Empty {} 369 + impl State for Empty { 370 + type Did = Unset; 371 + } 372 + ///State transition - sets the `did` field to Set 373 + pub struct SetDid<St: State = Empty>(PhantomData<fn() -> St>); 374 + impl<St: State> sealed::Sealed for SetDid<St> {} 375 + impl<St: State> State for SetDid<St> { 376 + type Did = Set<members::did>; 377 + } 378 + /// Marker types for field names 379 + #[allow(non_camel_case_types)] 380 + pub mod members { 381 + ///Marker type for the `did` field 382 + pub struct did(()); 383 + } 384 + } 385 + 386 + /// Builder for constructing an instance of this type. 387 + pub struct ActorInfoBuilder<St: actor_info_state::State, S: BosStr = DefaultStr> { 388 + _state: PhantomData<fn() -> St>, 389 + _fields: (Option<UriValue<S>>, Option<Did<S>>, Option<S>, Option<AtIdentifier<S>>), 390 + _type: PhantomData<fn() -> S>, 391 + } 392 + 393 + impl ActorInfo<DefaultStr> { 394 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 395 + pub fn new() -> ActorInfoBuilder<actor_info_state::Empty, DefaultStr> { 396 + ActorInfoBuilder::new() 397 + } 398 + } 399 + 400 + impl<S: BosStr> ActorInfo<S> { 401 + /// Create a new builder for this type 402 + pub fn builder() -> ActorInfoBuilder<actor_info_state::Empty, S> { 403 + ActorInfoBuilder::builder() 404 + } 405 + } 406 + 407 + impl ActorInfoBuilder<actor_info_state::Empty, DefaultStr> { 408 + /// Create a new builder with all fields unset, using the default string type, if needed 409 + pub fn new() -> Self { 410 + ActorInfoBuilder { 411 + _state: PhantomData, 412 + _fields: (None, None, None, None), 413 + _type: PhantomData, 414 + } 415 + } 416 + } 417 + 418 + impl<S: BosStr> ActorInfoBuilder<actor_info_state::Empty, S> { 419 + /// Create a new builder with all fields unset 420 + pub fn builder() -> Self { 421 + ActorInfoBuilder { 422 + _state: PhantomData, 423 + _fields: (None, None, None, None), 424 + _type: PhantomData, 425 + } 426 + } 427 + } 428 + 429 + impl<St: actor_info_state::State, S: BosStr> ActorInfoBuilder<St, S> { 430 + /// Set the `avatar` field (optional) 431 + pub fn avatar(mut self, value: impl Into<Option<UriValue<S>>>) -> Self { 432 + self._fields.0 = value.into(); 433 + self 434 + } 435 + /// Set the `avatar` field to an Option value (optional) 436 + pub fn maybe_avatar(mut self, value: Option<UriValue<S>>) -> Self { 437 + self._fields.0 = value; 438 + self 439 + } 440 + } 441 + 442 + impl<St, S: BosStr> ActorInfoBuilder<St, S> 443 + where 444 + St: actor_info_state::State, 445 + St::Did: actor_info_state::IsUnset, 446 + { 447 + /// Set the `did` field (required) 448 + pub fn did( 449 + mut self, 450 + value: impl Into<Did<S>>, 451 + ) -> ActorInfoBuilder<actor_info_state::SetDid<St>, S> { 452 + self._fields.1 = Option::Some(value.into()); 453 + ActorInfoBuilder { 454 + _state: PhantomData, 455 + _fields: self._fields, 456 + _type: PhantomData, 457 + } 458 + } 459 + } 460 + 461 + impl<St: actor_info_state::State, S: BosStr> ActorInfoBuilder<St, S> { 462 + /// Set the `displayName` field (optional) 463 + pub fn display_name(mut self, value: impl Into<Option<S>>) -> Self { 464 + self._fields.2 = value.into(); 465 + self 466 + } 467 + /// Set the `displayName` field to an Option value (optional) 468 + pub fn maybe_display_name(mut self, value: Option<S>) -> Self { 469 + self._fields.2 = value; 470 + self 471 + } 472 + } 473 + 474 + impl<St: actor_info_state::State, S: BosStr> ActorInfoBuilder<St, S> { 475 + /// Set the `handle` field (optional) 476 + pub fn handle(mut self, value: impl Into<Option<AtIdentifier<S>>>) -> Self { 477 + self._fields.3 = value.into(); 478 + self 479 + } 480 + /// Set the `handle` field to an Option value (optional) 481 + pub fn maybe_handle(mut self, value: Option<AtIdentifier<S>>) -> Self { 482 + self._fields.3 = value; 483 + self 484 + } 485 + } 486 + 487 + impl<St, S: BosStr> ActorInfoBuilder<St, S> 488 + where 489 + St: actor_info_state::State, 490 + St::Did: actor_info_state::IsSet, 491 + { 492 + /// Build the final struct. 493 + pub fn build(self) -> ActorInfo<S> { 494 + ActorInfo { 495 + avatar: self._fields.0, 496 + did: self._fields.1.unwrap(), 497 + display_name: self._fields.2, 498 + handle: self._fields.3, 499 + extra_data: Default::default(), 500 + } 501 + } 502 + /// Build the final struct with custom extra_data. 503 + pub fn build_with_data( 504 + self, 505 + extra_data: BTreeMap<SmolStr, Data<S>>, 506 + ) -> ActorInfo<S> { 507 + ActorInfo { 508 + avatar: self._fields.0, 509 + did: self._fields.1.unwrap(), 510 + display_name: self._fields.2, 511 + handle: self._fields.3, 512 + extra_data: Some(extra_data), 513 + } 514 + } 515 + } 516 + 517 + fn lexicon_doc_fm_atradio_defs() -> LexiconDoc<'static> { 518 + #[allow(unused_imports)] 519 + use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 520 + use jacquard_lexicon::lexicon::*; 521 + use alloc::collections::BTreeMap; 522 + LexiconDoc { 523 + lexicon: Lexicon::Lexicon1, 524 + id: CowStr::new_static("fm.atradio.defs"), 525 + defs: { 526 + let mut map = BTreeMap::new(); 527 + map.insert( 528 + SmolStr::new_static("actorInfo"), 529 + LexUserType::Object(LexObject { 530 + description: Some( 531 + CowStr::new_static("A minimal public snapshot of an actor."), 532 + ), 533 + required: Some(vec![SmolStr::new_static("did")]), 534 + properties: { 535 + #[allow(unused_mut)] 536 + let mut map = BTreeMap::new(); 537 + map.insert( 538 + SmolStr::new_static("avatar"), 539 + LexObjectProperty::String(LexString { 540 + description: Some(CowStr::new_static("Avatar URL.")), 541 + format: Some(LexStringFormat::Uri), 542 + ..Default::default() 543 + }), 544 + ); 545 + map.insert( 546 + SmolStr::new_static("did"), 547 + LexObjectProperty::String(LexString { 548 + format: Some(LexStringFormat::Did), 549 + ..Default::default() 550 + }), 551 + ); 552 + map.insert( 553 + SmolStr::new_static("displayName"), 554 + LexObjectProperty::String(LexString { ..Default::default() }), 555 + ); 556 + map.insert( 557 + SmolStr::new_static("handle"), 558 + LexObjectProperty::String(LexString { 559 + format: Some(LexStringFormat::AtIdentifier), 560 + ..Default::default() 561 + }), 562 + ); 563 + map 564 + }, 565 + ..Default::default() 566 + }), 567 + ); 568 + map.insert( 569 + SmolStr::new_static("commentView"), 570 + LexUserType::Object(LexObject { 571 + description: Some( 572 + CowStr::new_static( 573 + "A comment on a station, paired with its record uri and author.", 574 + ), 575 + ), 576 + required: Some( 577 + vec![ 578 + SmolStr::new_static("uri"), SmolStr::new_static("station"), 579 + SmolStr::new_static("text"), SmolStr::new_static("createdAt") 580 + ], 581 + ), 582 + properties: { 583 + #[allow(unused_mut)] 584 + let mut map = BTreeMap::new(); 585 + map.insert( 586 + SmolStr::new_static("author"), 587 + LexObjectProperty::Ref(LexRef { 588 + r#ref: CowStr::new_static("fm.atradio.defs#actorInfo"), 589 + ..Default::default() 590 + }), 591 + ); 592 + map.insert( 593 + SmolStr::new_static("createdAt"), 594 + LexObjectProperty::String(LexString { 595 + format: Some(LexStringFormat::Datetime), 596 + ..Default::default() 597 + }), 598 + ); 599 + map.insert( 600 + SmolStr::new_static("facets"), 601 + LexObjectProperty::Array(LexArray { 602 + items: LexArrayItem::Ref(LexRef { 603 + r#ref: CowStr::new_static("fm.atradio.comment#mention"), 604 + ..Default::default() 605 + }), 606 + ..Default::default() 607 + }), 608 + ); 609 + map.insert( 610 + SmolStr::new_static("gif"), 611 + LexObjectProperty::Ref(LexRef { 612 + r#ref: CowStr::new_static("fm.atradio.comment#gif"), 613 + ..Default::default() 614 + }), 615 + ); 616 + map.insert( 617 + SmolStr::new_static("station"), 618 + LexObjectProperty::Ref(LexRef { 619 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 620 + ..Default::default() 621 + }), 622 + ); 623 + map.insert( 624 + SmolStr::new_static("text"), 625 + LexObjectProperty::String(LexString { ..Default::default() }), 626 + ); 627 + map.insert( 628 + SmolStr::new_static("uri"), 629 + LexObjectProperty::String(LexString { 630 + format: Some(LexStringFormat::AtUri), 631 + ..Default::default() 632 + }), 633 + ); 634 + map 635 + }, 636 + ..Default::default() 637 + }), 638 + ); 639 + map.insert( 640 + SmolStr::new_static("listenerCount"), 641 + LexUserType::Object(LexObject { 642 + description: Some( 643 + CowStr::new_static("Unique-listener count for a single station."), 644 + ), 645 + required: Some( 646 + vec![ 647 + SmolStr::new_static("stationId"), 648 + SmolStr::new_static("listeners") 649 + ], 650 + ), 651 + properties: { 652 + #[allow(unused_mut)] 653 + let mut map = BTreeMap::new(); 654 + map.insert( 655 + SmolStr::new_static("listeners"), 656 + LexObjectProperty::Integer(LexInteger { 657 + minimum: Some(0i64), 658 + ..Default::default() 659 + }), 660 + ); 661 + map.insert( 662 + SmolStr::new_static("stationId"), 663 + LexObjectProperty::String(LexString { ..Default::default() }), 664 + ); 665 + map 666 + }, 667 + ..Default::default() 668 + }), 669 + ); 670 + map.insert( 671 + SmolStr::new_static("notificationView"), 672 + LexUserType::Object(LexObject { 673 + description: Some( 674 + CowStr::new_static("A single notification for an actor."), 675 + ), 676 + required: Some( 677 + vec![ 678 + SmolStr::new_static("uri"), SmolStr::new_static("reason"), 679 + SmolStr::new_static("author"), 680 + SmolStr::new_static("createdAt"), 681 + SmolStr::new_static("isRead") 682 + ], 683 + ), 684 + properties: { 685 + #[allow(unused_mut)] 686 + let mut map = BTreeMap::new(); 687 + map.insert( 688 + SmolStr::new_static("author"), 689 + LexObjectProperty::Ref(LexRef { 690 + r#ref: CowStr::new_static("fm.atradio.defs#actorInfo"), 691 + ..Default::default() 692 + }), 693 + ); 694 + map.insert( 695 + SmolStr::new_static("createdAt"), 696 + LexObjectProperty::String(LexString { 697 + format: Some(LexStringFormat::Datetime), 698 + ..Default::default() 699 + }), 700 + ); 701 + map.insert( 702 + SmolStr::new_static("isRead"), 703 + LexObjectProperty::Boolean(LexBoolean { 704 + ..Default::default() 705 + }), 706 + ); 707 + map.insert( 708 + SmolStr::new_static("reason"), 709 + LexObjectProperty::String(LexString { 710 + description: Some( 711 + CowStr::new_static( 712 + "Why this notification exists: mention | comment.", 713 + ), 714 + ), 715 + ..Default::default() 716 + }), 717 + ); 718 + map.insert( 719 + SmolStr::new_static("station"), 720 + LexObjectProperty::Ref(LexRef { 721 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 722 + ..Default::default() 723 + }), 724 + ); 725 + map.insert( 726 + SmolStr::new_static("text"), 727 + LexObjectProperty::String(LexString { 728 + description: Some( 729 + CowStr::new_static("Snapshot of the comment text."), 730 + ), 731 + ..Default::default() 732 + }), 733 + ); 734 + map.insert( 735 + SmolStr::new_static("uri"), 736 + LexObjectProperty::String(LexString { 737 + description: Some( 738 + CowStr::new_static( 739 + "URI of the subject record (the comment).", 740 + ), 741 + ), 742 + format: Some(LexStringFormat::AtUri), 743 + ..Default::default() 744 + }), 745 + ); 746 + map 747 + }, 748 + ..Default::default() 749 + }), 750 + ); 751 + map.insert( 752 + SmolStr::new_static("playView"), 753 + LexUserType::Object(LexObject { 754 + description: Some( 755 + CowStr::new_static( 756 + "A single play event: a station an actor listened to at a point in time.", 757 + ), 758 + ), 759 + required: Some( 760 + vec![ 761 + SmolStr::new_static("station"), 762 + SmolStr::new_static("playedAt") 763 + ], 764 + ), 765 + properties: { 766 + #[allow(unused_mut)] 767 + let mut map = BTreeMap::new(); 768 + map.insert( 769 + SmolStr::new_static("actor"), 770 + LexObjectProperty::Ref(LexRef { 771 + r#ref: CowStr::new_static("fm.atradio.defs#actorInfo"), 772 + ..Default::default() 773 + }), 774 + ); 775 + map.insert( 776 + SmolStr::new_static("playedAt"), 777 + LexObjectProperty::String(LexString { 778 + format: Some(LexStringFormat::Datetime), 779 + ..Default::default() 780 + }), 781 + ); 782 + map.insert( 783 + SmolStr::new_static("station"), 784 + LexObjectProperty::Ref(LexRef { 785 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 786 + ..Default::default() 787 + }), 788 + ); 789 + map 790 + }, 791 + ..Default::default() 792 + }), 793 + ); 794 + map.insert( 795 + SmolStr::new_static("stationInfo"), 796 + LexUserType::Object(LexObject { 797 + description: Some( 798 + CowStr::new_static( 799 + "A self-contained snapshot of a radio station.", 800 + ), 801 + ), 802 + required: Some( 803 + vec![ 804 + SmolStr::new_static("stationId"), 805 + SmolStr::new_static("name"), 806 + SmolStr::new_static("streamUrl"), 807 + SmolStr::new_static("source") 808 + ], 809 + ), 810 + properties: { 811 + #[allow(unused_mut)] 812 + let mut map = BTreeMap::new(); 813 + map.insert( 814 + SmolStr::new_static("bitrate"), 815 + LexObjectProperty::Integer(LexInteger { 816 + minimum: Some(0i64), 817 + ..Default::default() 818 + }), 819 + ); 820 + map.insert( 821 + SmolStr::new_static("codec"), 822 + LexObjectProperty::String(LexString { ..Default::default() }), 823 + ); 824 + map.insert( 825 + SmolStr::new_static("country"), 826 + LexObjectProperty::String(LexString { ..Default::default() }), 827 + ); 828 + map.insert( 829 + SmolStr::new_static("description"), 830 + LexObjectProperty::String(LexString { 831 + max_length: Some(2000usize), 832 + ..Default::default() 833 + }), 834 + ); 835 + map.insert( 836 + SmolStr::new_static("genre"), 837 + LexObjectProperty::String(LexString { 838 + max_length: Some(256usize), 839 + ..Default::default() 840 + }), 841 + ); 842 + map.insert( 843 + SmolStr::new_static("homepage"), 844 + LexObjectProperty::String(LexString { 845 + format: Some(LexStringFormat::Uri), 846 + ..Default::default() 847 + }), 848 + ); 849 + map.insert( 850 + SmolStr::new_static("language"), 851 + LexObjectProperty::String(LexString { ..Default::default() }), 852 + ); 853 + map.insert( 854 + SmolStr::new_static("logo"), 855 + LexObjectProperty::String(LexString { 856 + description: Some(CowStr::new_static("Logo/artwork URL.")), 857 + format: Some(LexStringFormat::Uri), 858 + ..Default::default() 859 + }), 860 + ); 861 + map.insert( 862 + SmolStr::new_static("name"), 863 + LexObjectProperty::String(LexString { 864 + min_length: Some(1usize), 865 + max_length: Some(512usize), 866 + ..Default::default() 867 + }), 868 + ); 869 + map.insert( 870 + SmolStr::new_static("source"), 871 + LexObjectProperty::String(LexString { 872 + description: Some( 873 + CowStr::new_static( 874 + "Provider: radio-browser | tunein | custom.", 875 + ), 876 + ), 877 + ..Default::default() 878 + }), 879 + ); 880 + map.insert( 881 + SmolStr::new_static("stationId"), 882 + LexObjectProperty::String(LexString { 883 + description: Some( 884 + CowStr::new_static( 885 + "Source-prefixed stable id, e.g. rb:uuid, tunein:s123, custom:xyz.", 886 + ), 887 + ), 888 + max_length: Some(512usize), 889 + ..Default::default() 890 + }), 891 + ); 892 + map.insert( 893 + SmolStr::new_static("streamUrl"), 894 + LexObjectProperty::String(LexString { 895 + description: Some( 896 + CowStr::new_static("Direct, playable stream URL."), 897 + ), 898 + format: Some(LexStringFormat::Uri), 899 + ..Default::default() 900 + }), 901 + ); 902 + map.insert( 903 + SmolStr::new_static("tags"), 904 + LexObjectProperty::Array(LexArray { 905 + items: LexArrayItem::String(LexString { 906 + ..Default::default() 907 + }), 908 + ..Default::default() 909 + }), 910 + ); 911 + map 912 + }, 913 + ..Default::default() 914 + }), 915 + ); 916 + map.insert( 917 + SmolStr::new_static("stationView"), 918 + LexUserType::Object(LexObject { 919 + description: Some( 920 + CowStr::new_static( 921 + "A station snapshot paired with its record uri (query output item).", 922 + ), 923 + ), 924 + required: Some( 925 + vec![ 926 + SmolStr::new_static("uri"), SmolStr::new_static("station"), 927 + SmolStr::new_static("createdAt") 928 + ], 929 + ), 930 + properties: { 931 + #[allow(unused_mut)] 932 + let mut map = BTreeMap::new(); 933 + map.insert( 934 + SmolStr::new_static("createdAt"), 935 + LexObjectProperty::String(LexString { 936 + format: Some(LexStringFormat::Datetime), 937 + ..Default::default() 938 + }), 939 + ); 940 + map.insert( 941 + SmolStr::new_static("station"), 942 + LexObjectProperty::Ref(LexRef { 943 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 944 + ..Default::default() 945 + }), 946 + ); 947 + map.insert( 948 + SmolStr::new_static("uri"), 949 + LexObjectProperty::String(LexString { 950 + format: Some(LexStringFormat::AtUri), 951 + ..Default::default() 952 + }), 953 + ); 954 + map 955 + }, 956 + ..Default::default() 957 + }), 958 + ); 959 + map 960 + }, 961 + ..Default::default() 962 + } 963 + } 964 + 965 + pub mod comment_view_state { 966 + 967 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 968 + #[allow(unused)] 969 + use ::core::marker::PhantomData; 970 + mod sealed { 971 + pub trait Sealed {} 972 + } 973 + /// State trait tracking which required fields have been set 974 + pub trait State: sealed::Sealed { 975 + type CreatedAt; 976 + type Station; 977 + type Text; 978 + type Uri; 979 + } 980 + /// Empty state - all required fields are unset 981 + pub struct Empty(()); 982 + impl sealed::Sealed for Empty {} 983 + impl State for Empty { 984 + type CreatedAt = Unset; 985 + type Station = Unset; 986 + type Text = Unset; 987 + type Uri = Unset; 988 + } 989 + ///State transition - sets the `created_at` field to Set 990 + pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>); 991 + impl<St: State> sealed::Sealed for SetCreatedAt<St> {} 992 + impl<St: State> State for SetCreatedAt<St> { 993 + type CreatedAt = Set<members::created_at>; 994 + type Station = St::Station; 995 + type Text = St::Text; 996 + type Uri = St::Uri; 997 + } 998 + ///State transition - sets the `station` field to Set 999 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 1000 + impl<St: State> sealed::Sealed for SetStation<St> {} 1001 + impl<St: State> State for SetStation<St> { 1002 + type CreatedAt = St::CreatedAt; 1003 + type Station = Set<members::station>; 1004 + type Text = St::Text; 1005 + type Uri = St::Uri; 1006 + } 1007 + ///State transition - sets the `text` field to Set 1008 + pub struct SetText<St: State = Empty>(PhantomData<fn() -> St>); 1009 + impl<St: State> sealed::Sealed for SetText<St> {} 1010 + impl<St: State> State for SetText<St> { 1011 + type CreatedAt = St::CreatedAt; 1012 + type Station = St::Station; 1013 + type Text = Set<members::text>; 1014 + type Uri = St::Uri; 1015 + } 1016 + ///State transition - sets the `uri` field to Set 1017 + pub struct SetUri<St: State = Empty>(PhantomData<fn() -> St>); 1018 + impl<St: State> sealed::Sealed for SetUri<St> {} 1019 + impl<St: State> State for SetUri<St> { 1020 + type CreatedAt = St::CreatedAt; 1021 + type Station = St::Station; 1022 + type Text = St::Text; 1023 + type Uri = Set<members::uri>; 1024 + } 1025 + /// Marker types for field names 1026 + #[allow(non_camel_case_types)] 1027 + pub mod members { 1028 + ///Marker type for the `created_at` field 1029 + pub struct created_at(()); 1030 + ///Marker type for the `station` field 1031 + pub struct station(()); 1032 + ///Marker type for the `text` field 1033 + pub struct text(()); 1034 + ///Marker type for the `uri` field 1035 + pub struct uri(()); 1036 + } 1037 + } 1038 + 1039 + /// Builder for constructing an instance of this type. 1040 + pub struct CommentViewBuilder<St: comment_view_state::State, S: BosStr = DefaultStr> { 1041 + _state: PhantomData<fn() -> St>, 1042 + _fields: ( 1043 + Option<fm_atradio::ActorInfo<S>>, 1044 + Option<Datetime>, 1045 + Option<Vec<Mention<S>>>, 1046 + Option<Gif<S>>, 1047 + Option<fm_atradio::StationInfo<S>>, 1048 + Option<S>, 1049 + Option<AtUri<S>>, 1050 + ), 1051 + _type: PhantomData<fn() -> S>, 1052 + } 1053 + 1054 + impl CommentView<DefaultStr> { 1055 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 1056 + pub fn new() -> CommentViewBuilder<comment_view_state::Empty, DefaultStr> { 1057 + CommentViewBuilder::new() 1058 + } 1059 + } 1060 + 1061 + impl<S: BosStr> CommentView<S> { 1062 + /// Create a new builder for this type 1063 + pub fn builder() -> CommentViewBuilder<comment_view_state::Empty, S> { 1064 + CommentViewBuilder::builder() 1065 + } 1066 + } 1067 + 1068 + impl CommentViewBuilder<comment_view_state::Empty, DefaultStr> { 1069 + /// Create a new builder with all fields unset, using the default string type, if needed 1070 + pub fn new() -> Self { 1071 + CommentViewBuilder { 1072 + _state: PhantomData, 1073 + _fields: (None, None, None, None, None, None, None), 1074 + _type: PhantomData, 1075 + } 1076 + } 1077 + } 1078 + 1079 + impl<S: BosStr> CommentViewBuilder<comment_view_state::Empty, S> { 1080 + /// Create a new builder with all fields unset 1081 + pub fn builder() -> Self { 1082 + CommentViewBuilder { 1083 + _state: PhantomData, 1084 + _fields: (None, None, None, None, None, None, None), 1085 + _type: PhantomData, 1086 + } 1087 + } 1088 + } 1089 + 1090 + impl<St: comment_view_state::State, S: BosStr> CommentViewBuilder<St, S> { 1091 + /// Set the `author` field (optional) 1092 + pub fn author(mut self, value: impl Into<Option<fm_atradio::ActorInfo<S>>>) -> Self { 1093 + self._fields.0 = value.into(); 1094 + self 1095 + } 1096 + /// Set the `author` field to an Option value (optional) 1097 + pub fn maybe_author(mut self, value: Option<fm_atradio::ActorInfo<S>>) -> Self { 1098 + self._fields.0 = value; 1099 + self 1100 + } 1101 + } 1102 + 1103 + impl<St, S: BosStr> CommentViewBuilder<St, S> 1104 + where 1105 + St: comment_view_state::State, 1106 + St::CreatedAt: comment_view_state::IsUnset, 1107 + { 1108 + /// Set the `createdAt` field (required) 1109 + pub fn created_at( 1110 + mut self, 1111 + value: impl Into<Datetime>, 1112 + ) -> CommentViewBuilder<comment_view_state::SetCreatedAt<St>, S> { 1113 + self._fields.1 = Option::Some(value.into()); 1114 + CommentViewBuilder { 1115 + _state: PhantomData, 1116 + _fields: self._fields, 1117 + _type: PhantomData, 1118 + } 1119 + } 1120 + } 1121 + 1122 + impl<St: comment_view_state::State, S: BosStr> CommentViewBuilder<St, S> { 1123 + /// Set the `facets` field (optional) 1124 + pub fn facets(mut self, value: impl Into<Option<Vec<Mention<S>>>>) -> Self { 1125 + self._fields.2 = value.into(); 1126 + self 1127 + } 1128 + /// Set the `facets` field to an Option value (optional) 1129 + pub fn maybe_facets(mut self, value: Option<Vec<Mention<S>>>) -> Self { 1130 + self._fields.2 = value; 1131 + self 1132 + } 1133 + } 1134 + 1135 + impl<St: comment_view_state::State, S: BosStr> CommentViewBuilder<St, S> { 1136 + /// Set the `gif` field (optional) 1137 + pub fn gif(mut self, value: impl Into<Option<Gif<S>>>) -> Self { 1138 + self._fields.3 = value.into(); 1139 + self 1140 + } 1141 + /// Set the `gif` field to an Option value (optional) 1142 + pub fn maybe_gif(mut self, value: Option<Gif<S>>) -> Self { 1143 + self._fields.3 = value; 1144 + self 1145 + } 1146 + } 1147 + 1148 + impl<St, S: BosStr> CommentViewBuilder<St, S> 1149 + where 1150 + St: comment_view_state::State, 1151 + St::Station: comment_view_state::IsUnset, 1152 + { 1153 + /// Set the `station` field (required) 1154 + pub fn station( 1155 + mut self, 1156 + value: impl Into<fm_atradio::StationInfo<S>>, 1157 + ) -> CommentViewBuilder<comment_view_state::SetStation<St>, S> { 1158 + self._fields.4 = Option::Some(value.into()); 1159 + CommentViewBuilder { 1160 + _state: PhantomData, 1161 + _fields: self._fields, 1162 + _type: PhantomData, 1163 + } 1164 + } 1165 + } 1166 + 1167 + impl<St, S: BosStr> CommentViewBuilder<St, S> 1168 + where 1169 + St: comment_view_state::State, 1170 + St::Text: comment_view_state::IsUnset, 1171 + { 1172 + /// Set the `text` field (required) 1173 + pub fn text( 1174 + mut self, 1175 + value: impl Into<S>, 1176 + ) -> CommentViewBuilder<comment_view_state::SetText<St>, S> { 1177 + self._fields.5 = Option::Some(value.into()); 1178 + CommentViewBuilder { 1179 + _state: PhantomData, 1180 + _fields: self._fields, 1181 + _type: PhantomData, 1182 + } 1183 + } 1184 + } 1185 + 1186 + impl<St, S: BosStr> CommentViewBuilder<St, S> 1187 + where 1188 + St: comment_view_state::State, 1189 + St::Uri: comment_view_state::IsUnset, 1190 + { 1191 + /// Set the `uri` field (required) 1192 + pub fn uri( 1193 + mut self, 1194 + value: impl Into<AtUri<S>>, 1195 + ) -> CommentViewBuilder<comment_view_state::SetUri<St>, S> { 1196 + self._fields.6 = Option::Some(value.into()); 1197 + CommentViewBuilder { 1198 + _state: PhantomData, 1199 + _fields: self._fields, 1200 + _type: PhantomData, 1201 + } 1202 + } 1203 + } 1204 + 1205 + impl<St, S: BosStr> CommentViewBuilder<St, S> 1206 + where 1207 + St: comment_view_state::State, 1208 + St::CreatedAt: comment_view_state::IsSet, 1209 + St::Station: comment_view_state::IsSet, 1210 + St::Text: comment_view_state::IsSet, 1211 + St::Uri: comment_view_state::IsSet, 1212 + { 1213 + /// Build the final struct. 1214 + pub fn build(self) -> CommentView<S> { 1215 + CommentView { 1216 + author: self._fields.0, 1217 + created_at: self._fields.1.unwrap(), 1218 + facets: self._fields.2, 1219 + gif: self._fields.3, 1220 + station: self._fields.4.unwrap(), 1221 + text: self._fields.5.unwrap(), 1222 + uri: self._fields.6.unwrap(), 1223 + extra_data: Default::default(), 1224 + } 1225 + } 1226 + /// Build the final struct with custom extra_data. 1227 + pub fn build_with_data( 1228 + self, 1229 + extra_data: BTreeMap<SmolStr, Data<S>>, 1230 + ) -> CommentView<S> { 1231 + CommentView { 1232 + author: self._fields.0, 1233 + created_at: self._fields.1.unwrap(), 1234 + facets: self._fields.2, 1235 + gif: self._fields.3, 1236 + station: self._fields.4.unwrap(), 1237 + text: self._fields.5.unwrap(), 1238 + uri: self._fields.6.unwrap(), 1239 + extra_data: Some(extra_data), 1240 + } 1241 + } 1242 + } 1243 + 1244 + pub mod listener_count_state { 1245 + 1246 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1247 + #[allow(unused)] 1248 + use ::core::marker::PhantomData; 1249 + mod sealed { 1250 + pub trait Sealed {} 1251 + } 1252 + /// State trait tracking which required fields have been set 1253 + pub trait State: sealed::Sealed { 1254 + type Listeners; 1255 + type StationId; 1256 + } 1257 + /// Empty state - all required fields are unset 1258 + pub struct Empty(()); 1259 + impl sealed::Sealed for Empty {} 1260 + impl State for Empty { 1261 + type Listeners = Unset; 1262 + type StationId = Unset; 1263 + } 1264 + ///State transition - sets the `listeners` field to Set 1265 + pub struct SetListeners<St: State = Empty>(PhantomData<fn() -> St>); 1266 + impl<St: State> sealed::Sealed for SetListeners<St> {} 1267 + impl<St: State> State for SetListeners<St> { 1268 + type Listeners = Set<members::listeners>; 1269 + type StationId = St::StationId; 1270 + } 1271 + ///State transition - sets the `station_id` field to Set 1272 + pub struct SetStationId<St: State = Empty>(PhantomData<fn() -> St>); 1273 + impl<St: State> sealed::Sealed for SetStationId<St> {} 1274 + impl<St: State> State for SetStationId<St> { 1275 + type Listeners = St::Listeners; 1276 + type StationId = Set<members::station_id>; 1277 + } 1278 + /// Marker types for field names 1279 + #[allow(non_camel_case_types)] 1280 + pub mod members { 1281 + ///Marker type for the `listeners` field 1282 + pub struct listeners(()); 1283 + ///Marker type for the `station_id` field 1284 + pub struct station_id(()); 1285 + } 1286 + } 1287 + 1288 + /// Builder for constructing an instance of this type. 1289 + pub struct ListenerCountBuilder< 1290 + St: listener_count_state::State, 1291 + S: BosStr = DefaultStr, 1292 + > { 1293 + _state: PhantomData<fn() -> St>, 1294 + _fields: (Option<i64>, Option<S>), 1295 + _type: PhantomData<fn() -> S>, 1296 + } 1297 + 1298 + impl ListenerCount<DefaultStr> { 1299 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 1300 + pub fn new() -> ListenerCountBuilder<listener_count_state::Empty, DefaultStr> { 1301 + ListenerCountBuilder::new() 1302 + } 1303 + } 1304 + 1305 + impl<S: BosStr> ListenerCount<S> { 1306 + /// Create a new builder for this type 1307 + pub fn builder() -> ListenerCountBuilder<listener_count_state::Empty, S> { 1308 + ListenerCountBuilder::builder() 1309 + } 1310 + } 1311 + 1312 + impl ListenerCountBuilder<listener_count_state::Empty, DefaultStr> { 1313 + /// Create a new builder with all fields unset, using the default string type, if needed 1314 + pub fn new() -> Self { 1315 + ListenerCountBuilder { 1316 + _state: PhantomData, 1317 + _fields: (None, None), 1318 + _type: PhantomData, 1319 + } 1320 + } 1321 + } 1322 + 1323 + impl<S: BosStr> ListenerCountBuilder<listener_count_state::Empty, S> { 1324 + /// Create a new builder with all fields unset 1325 + pub fn builder() -> Self { 1326 + ListenerCountBuilder { 1327 + _state: PhantomData, 1328 + _fields: (None, None), 1329 + _type: PhantomData, 1330 + } 1331 + } 1332 + } 1333 + 1334 + impl<St, S: BosStr> ListenerCountBuilder<St, S> 1335 + where 1336 + St: listener_count_state::State, 1337 + St::Listeners: listener_count_state::IsUnset, 1338 + { 1339 + /// Set the `listeners` field (required) 1340 + pub fn listeners( 1341 + mut self, 1342 + value: impl Into<i64>, 1343 + ) -> ListenerCountBuilder<listener_count_state::SetListeners<St>, S> { 1344 + self._fields.0 = Option::Some(value.into()); 1345 + ListenerCountBuilder { 1346 + _state: PhantomData, 1347 + _fields: self._fields, 1348 + _type: PhantomData, 1349 + } 1350 + } 1351 + } 1352 + 1353 + impl<St, S: BosStr> ListenerCountBuilder<St, S> 1354 + where 1355 + St: listener_count_state::State, 1356 + St::StationId: listener_count_state::IsUnset, 1357 + { 1358 + /// Set the `stationId` field (required) 1359 + pub fn station_id( 1360 + mut self, 1361 + value: impl Into<S>, 1362 + ) -> ListenerCountBuilder<listener_count_state::SetStationId<St>, S> { 1363 + self._fields.1 = Option::Some(value.into()); 1364 + ListenerCountBuilder { 1365 + _state: PhantomData, 1366 + _fields: self._fields, 1367 + _type: PhantomData, 1368 + } 1369 + } 1370 + } 1371 + 1372 + impl<St, S: BosStr> ListenerCountBuilder<St, S> 1373 + where 1374 + St: listener_count_state::State, 1375 + St::Listeners: listener_count_state::IsSet, 1376 + St::StationId: listener_count_state::IsSet, 1377 + { 1378 + /// Build the final struct. 1379 + pub fn build(self) -> ListenerCount<S> { 1380 + ListenerCount { 1381 + listeners: self._fields.0.unwrap(), 1382 + station_id: self._fields.1.unwrap(), 1383 + extra_data: Default::default(), 1384 + } 1385 + } 1386 + /// Build the final struct with custom extra_data. 1387 + pub fn build_with_data( 1388 + self, 1389 + extra_data: BTreeMap<SmolStr, Data<S>>, 1390 + ) -> ListenerCount<S> { 1391 + ListenerCount { 1392 + listeners: self._fields.0.unwrap(), 1393 + station_id: self._fields.1.unwrap(), 1394 + extra_data: Some(extra_data), 1395 + } 1396 + } 1397 + } 1398 + 1399 + pub mod notification_view_state { 1400 + 1401 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1402 + #[allow(unused)] 1403 + use ::core::marker::PhantomData; 1404 + mod sealed { 1405 + pub trait Sealed {} 1406 + } 1407 + /// State trait tracking which required fields have been set 1408 + pub trait State: sealed::Sealed { 1409 + type Author; 1410 + type CreatedAt; 1411 + type IsRead; 1412 + type Reason; 1413 + type Uri; 1414 + } 1415 + /// Empty state - all required fields are unset 1416 + pub struct Empty(()); 1417 + impl sealed::Sealed for Empty {} 1418 + impl State for Empty { 1419 + type Author = Unset; 1420 + type CreatedAt = Unset; 1421 + type IsRead = Unset; 1422 + type Reason = Unset; 1423 + type Uri = Unset; 1424 + } 1425 + ///State transition - sets the `author` field to Set 1426 + pub struct SetAuthor<St: State = Empty>(PhantomData<fn() -> St>); 1427 + impl<St: State> sealed::Sealed for SetAuthor<St> {} 1428 + impl<St: State> State for SetAuthor<St> { 1429 + type Author = Set<members::author>; 1430 + type CreatedAt = St::CreatedAt; 1431 + type IsRead = St::IsRead; 1432 + type Reason = St::Reason; 1433 + type Uri = St::Uri; 1434 + } 1435 + ///State transition - sets the `created_at` field to Set 1436 + pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>); 1437 + impl<St: State> sealed::Sealed for SetCreatedAt<St> {} 1438 + impl<St: State> State for SetCreatedAt<St> { 1439 + type Author = St::Author; 1440 + type CreatedAt = Set<members::created_at>; 1441 + type IsRead = St::IsRead; 1442 + type Reason = St::Reason; 1443 + type Uri = St::Uri; 1444 + } 1445 + ///State transition - sets the `is_read` field to Set 1446 + pub struct SetIsRead<St: State = Empty>(PhantomData<fn() -> St>); 1447 + impl<St: State> sealed::Sealed for SetIsRead<St> {} 1448 + impl<St: State> State for SetIsRead<St> { 1449 + type Author = St::Author; 1450 + type CreatedAt = St::CreatedAt; 1451 + type IsRead = Set<members::is_read>; 1452 + type Reason = St::Reason; 1453 + type Uri = St::Uri; 1454 + } 1455 + ///State transition - sets the `reason` field to Set 1456 + pub struct SetReason<St: State = Empty>(PhantomData<fn() -> St>); 1457 + impl<St: State> sealed::Sealed for SetReason<St> {} 1458 + impl<St: State> State for SetReason<St> { 1459 + type Author = St::Author; 1460 + type CreatedAt = St::CreatedAt; 1461 + type IsRead = St::IsRead; 1462 + type Reason = Set<members::reason>; 1463 + type Uri = St::Uri; 1464 + } 1465 + ///State transition - sets the `uri` field to Set 1466 + pub struct SetUri<St: State = Empty>(PhantomData<fn() -> St>); 1467 + impl<St: State> sealed::Sealed for SetUri<St> {} 1468 + impl<St: State> State for SetUri<St> { 1469 + type Author = St::Author; 1470 + type CreatedAt = St::CreatedAt; 1471 + type IsRead = St::IsRead; 1472 + type Reason = St::Reason; 1473 + type Uri = Set<members::uri>; 1474 + } 1475 + /// Marker types for field names 1476 + #[allow(non_camel_case_types)] 1477 + pub mod members { 1478 + ///Marker type for the `author` field 1479 + pub struct author(()); 1480 + ///Marker type for the `created_at` field 1481 + pub struct created_at(()); 1482 + ///Marker type for the `is_read` field 1483 + pub struct is_read(()); 1484 + ///Marker type for the `reason` field 1485 + pub struct reason(()); 1486 + ///Marker type for the `uri` field 1487 + pub struct uri(()); 1488 + } 1489 + } 1490 + 1491 + /// Builder for constructing an instance of this type. 1492 + pub struct NotificationViewBuilder< 1493 + St: notification_view_state::State, 1494 + S: BosStr = DefaultStr, 1495 + > { 1496 + _state: PhantomData<fn() -> St>, 1497 + _fields: ( 1498 + Option<fm_atradio::ActorInfo<S>>, 1499 + Option<Datetime>, 1500 + Option<bool>, 1501 + Option<S>, 1502 + Option<fm_atradio::StationInfo<S>>, 1503 + Option<S>, 1504 + Option<AtUri<S>>, 1505 + ), 1506 + _type: PhantomData<fn() -> S>, 1507 + } 1508 + 1509 + impl NotificationView<DefaultStr> { 1510 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 1511 + pub fn new() -> NotificationViewBuilder<notification_view_state::Empty, DefaultStr> { 1512 + NotificationViewBuilder::new() 1513 + } 1514 + } 1515 + 1516 + impl<S: BosStr> NotificationView<S> { 1517 + /// Create a new builder for this type 1518 + pub fn builder() -> NotificationViewBuilder<notification_view_state::Empty, S> { 1519 + NotificationViewBuilder::builder() 1520 + } 1521 + } 1522 + 1523 + impl NotificationViewBuilder<notification_view_state::Empty, DefaultStr> { 1524 + /// Create a new builder with all fields unset, using the default string type, if needed 1525 + pub fn new() -> Self { 1526 + NotificationViewBuilder { 1527 + _state: PhantomData, 1528 + _fields: (None, None, None, None, None, None, None), 1529 + _type: PhantomData, 1530 + } 1531 + } 1532 + } 1533 + 1534 + impl<S: BosStr> NotificationViewBuilder<notification_view_state::Empty, S> { 1535 + /// Create a new builder with all fields unset 1536 + pub fn builder() -> Self { 1537 + NotificationViewBuilder { 1538 + _state: PhantomData, 1539 + _fields: (None, None, None, None, None, None, None), 1540 + _type: PhantomData, 1541 + } 1542 + } 1543 + } 1544 + 1545 + impl<St, S: BosStr> NotificationViewBuilder<St, S> 1546 + where 1547 + St: notification_view_state::State, 1548 + St::Author: notification_view_state::IsUnset, 1549 + { 1550 + /// Set the `author` field (required) 1551 + pub fn author( 1552 + mut self, 1553 + value: impl Into<fm_atradio::ActorInfo<S>>, 1554 + ) -> NotificationViewBuilder<notification_view_state::SetAuthor<St>, S> { 1555 + self._fields.0 = Option::Some(value.into()); 1556 + NotificationViewBuilder { 1557 + _state: PhantomData, 1558 + _fields: self._fields, 1559 + _type: PhantomData, 1560 + } 1561 + } 1562 + } 1563 + 1564 + impl<St, S: BosStr> NotificationViewBuilder<St, S> 1565 + where 1566 + St: notification_view_state::State, 1567 + St::CreatedAt: notification_view_state::IsUnset, 1568 + { 1569 + /// Set the `createdAt` field (required) 1570 + pub fn created_at( 1571 + mut self, 1572 + value: impl Into<Datetime>, 1573 + ) -> NotificationViewBuilder<notification_view_state::SetCreatedAt<St>, S> { 1574 + self._fields.1 = Option::Some(value.into()); 1575 + NotificationViewBuilder { 1576 + _state: PhantomData, 1577 + _fields: self._fields, 1578 + _type: PhantomData, 1579 + } 1580 + } 1581 + } 1582 + 1583 + impl<St, S: BosStr> NotificationViewBuilder<St, S> 1584 + where 1585 + St: notification_view_state::State, 1586 + St::IsRead: notification_view_state::IsUnset, 1587 + { 1588 + /// Set the `isRead` field (required) 1589 + pub fn is_read( 1590 + mut self, 1591 + value: impl Into<bool>, 1592 + ) -> NotificationViewBuilder<notification_view_state::SetIsRead<St>, S> { 1593 + self._fields.2 = Option::Some(value.into()); 1594 + NotificationViewBuilder { 1595 + _state: PhantomData, 1596 + _fields: self._fields, 1597 + _type: PhantomData, 1598 + } 1599 + } 1600 + } 1601 + 1602 + impl<St, S: BosStr> NotificationViewBuilder<St, S> 1603 + where 1604 + St: notification_view_state::State, 1605 + St::Reason: notification_view_state::IsUnset, 1606 + { 1607 + /// Set the `reason` field (required) 1608 + pub fn reason( 1609 + mut self, 1610 + value: impl Into<S>, 1611 + ) -> NotificationViewBuilder<notification_view_state::SetReason<St>, S> { 1612 + self._fields.3 = Option::Some(value.into()); 1613 + NotificationViewBuilder { 1614 + _state: PhantomData, 1615 + _fields: self._fields, 1616 + _type: PhantomData, 1617 + } 1618 + } 1619 + } 1620 + 1621 + impl<St: notification_view_state::State, S: BosStr> NotificationViewBuilder<St, S> { 1622 + /// Set the `station` field (optional) 1623 + pub fn station( 1624 + mut self, 1625 + value: impl Into<Option<fm_atradio::StationInfo<S>>>, 1626 + ) -> Self { 1627 + self._fields.4 = value.into(); 1628 + self 1629 + } 1630 + /// Set the `station` field to an Option value (optional) 1631 + pub fn maybe_station(mut self, value: Option<fm_atradio::StationInfo<S>>) -> Self { 1632 + self._fields.4 = value; 1633 + self 1634 + } 1635 + } 1636 + 1637 + impl<St: notification_view_state::State, S: BosStr> NotificationViewBuilder<St, S> { 1638 + /// Set the `text` field (optional) 1639 + pub fn text(mut self, value: impl Into<Option<S>>) -> Self { 1640 + self._fields.5 = value.into(); 1641 + self 1642 + } 1643 + /// Set the `text` field to an Option value (optional) 1644 + pub fn maybe_text(mut self, value: Option<S>) -> Self { 1645 + self._fields.5 = value; 1646 + self 1647 + } 1648 + } 1649 + 1650 + impl<St, S: BosStr> NotificationViewBuilder<St, S> 1651 + where 1652 + St: notification_view_state::State, 1653 + St::Uri: notification_view_state::IsUnset, 1654 + { 1655 + /// Set the `uri` field (required) 1656 + pub fn uri( 1657 + mut self, 1658 + value: impl Into<AtUri<S>>, 1659 + ) -> NotificationViewBuilder<notification_view_state::SetUri<St>, S> { 1660 + self._fields.6 = Option::Some(value.into()); 1661 + NotificationViewBuilder { 1662 + _state: PhantomData, 1663 + _fields: self._fields, 1664 + _type: PhantomData, 1665 + } 1666 + } 1667 + } 1668 + 1669 + impl<St, S: BosStr> NotificationViewBuilder<St, S> 1670 + where 1671 + St: notification_view_state::State, 1672 + St::Author: notification_view_state::IsSet, 1673 + St::CreatedAt: notification_view_state::IsSet, 1674 + St::IsRead: notification_view_state::IsSet, 1675 + St::Reason: notification_view_state::IsSet, 1676 + St::Uri: notification_view_state::IsSet, 1677 + { 1678 + /// Build the final struct. 1679 + pub fn build(self) -> NotificationView<S> { 1680 + NotificationView { 1681 + author: self._fields.0.unwrap(), 1682 + created_at: self._fields.1.unwrap(), 1683 + is_read: self._fields.2.unwrap(), 1684 + reason: self._fields.3.unwrap(), 1685 + station: self._fields.4, 1686 + text: self._fields.5, 1687 + uri: self._fields.6.unwrap(), 1688 + extra_data: Default::default(), 1689 + } 1690 + } 1691 + /// Build the final struct with custom extra_data. 1692 + pub fn build_with_data( 1693 + self, 1694 + extra_data: BTreeMap<SmolStr, Data<S>>, 1695 + ) -> NotificationView<S> { 1696 + NotificationView { 1697 + author: self._fields.0.unwrap(), 1698 + created_at: self._fields.1.unwrap(), 1699 + is_read: self._fields.2.unwrap(), 1700 + reason: self._fields.3.unwrap(), 1701 + station: self._fields.4, 1702 + text: self._fields.5, 1703 + uri: self._fields.6.unwrap(), 1704 + extra_data: Some(extra_data), 1705 + } 1706 + } 1707 + } 1708 + 1709 + pub mod play_view_state { 1710 + 1711 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1712 + #[allow(unused)] 1713 + use ::core::marker::PhantomData; 1714 + mod sealed { 1715 + pub trait Sealed {} 1716 + } 1717 + /// State trait tracking which required fields have been set 1718 + pub trait State: sealed::Sealed { 1719 + type PlayedAt; 1720 + type Station; 1721 + } 1722 + /// Empty state - all required fields are unset 1723 + pub struct Empty(()); 1724 + impl sealed::Sealed for Empty {} 1725 + impl State for Empty { 1726 + type PlayedAt = Unset; 1727 + type Station = Unset; 1728 + } 1729 + ///State transition - sets the `played_at` field to Set 1730 + pub struct SetPlayedAt<St: State = Empty>(PhantomData<fn() -> St>); 1731 + impl<St: State> sealed::Sealed for SetPlayedAt<St> {} 1732 + impl<St: State> State for SetPlayedAt<St> { 1733 + type PlayedAt = Set<members::played_at>; 1734 + type Station = St::Station; 1735 + } 1736 + ///State transition - sets the `station` field to Set 1737 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 1738 + impl<St: State> sealed::Sealed for SetStation<St> {} 1739 + impl<St: State> State for SetStation<St> { 1740 + type PlayedAt = St::PlayedAt; 1741 + type Station = Set<members::station>; 1742 + } 1743 + /// Marker types for field names 1744 + #[allow(non_camel_case_types)] 1745 + pub mod members { 1746 + ///Marker type for the `played_at` field 1747 + pub struct played_at(()); 1748 + ///Marker type for the `station` field 1749 + pub struct station(()); 1750 + } 1751 + } 1752 + 1753 + /// Builder for constructing an instance of this type. 1754 + pub struct PlayViewBuilder<St: play_view_state::State, S: BosStr = DefaultStr> { 1755 + _state: PhantomData<fn() -> St>, 1756 + _fields: ( 1757 + Option<fm_atradio::ActorInfo<S>>, 1758 + Option<Datetime>, 1759 + Option<fm_atradio::StationInfo<S>>, 1760 + ), 1761 + _type: PhantomData<fn() -> S>, 1762 + } 1763 + 1764 + impl PlayView<DefaultStr> { 1765 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 1766 + pub fn new() -> PlayViewBuilder<play_view_state::Empty, DefaultStr> { 1767 + PlayViewBuilder::new() 1768 + } 1769 + } 1770 + 1771 + impl<S: BosStr> PlayView<S> { 1772 + /// Create a new builder for this type 1773 + pub fn builder() -> PlayViewBuilder<play_view_state::Empty, S> { 1774 + PlayViewBuilder::builder() 1775 + } 1776 + } 1777 + 1778 + impl PlayViewBuilder<play_view_state::Empty, DefaultStr> { 1779 + /// Create a new builder with all fields unset, using the default string type, if needed 1780 + pub fn new() -> Self { 1781 + PlayViewBuilder { 1782 + _state: PhantomData, 1783 + _fields: (None, None, None), 1784 + _type: PhantomData, 1785 + } 1786 + } 1787 + } 1788 + 1789 + impl<S: BosStr> PlayViewBuilder<play_view_state::Empty, S> { 1790 + /// Create a new builder with all fields unset 1791 + pub fn builder() -> Self { 1792 + PlayViewBuilder { 1793 + _state: PhantomData, 1794 + _fields: (None, None, None), 1795 + _type: PhantomData, 1796 + } 1797 + } 1798 + } 1799 + 1800 + impl<St: play_view_state::State, S: BosStr> PlayViewBuilder<St, S> { 1801 + /// Set the `actor` field (optional) 1802 + pub fn actor(mut self, value: impl Into<Option<fm_atradio::ActorInfo<S>>>) -> Self { 1803 + self._fields.0 = value.into(); 1804 + self 1805 + } 1806 + /// Set the `actor` field to an Option value (optional) 1807 + pub fn maybe_actor(mut self, value: Option<fm_atradio::ActorInfo<S>>) -> Self { 1808 + self._fields.0 = value; 1809 + self 1810 + } 1811 + } 1812 + 1813 + impl<St, S: BosStr> PlayViewBuilder<St, S> 1814 + where 1815 + St: play_view_state::State, 1816 + St::PlayedAt: play_view_state::IsUnset, 1817 + { 1818 + /// Set the `playedAt` field (required) 1819 + pub fn played_at( 1820 + mut self, 1821 + value: impl Into<Datetime>, 1822 + ) -> PlayViewBuilder<play_view_state::SetPlayedAt<St>, S> { 1823 + self._fields.1 = Option::Some(value.into()); 1824 + PlayViewBuilder { 1825 + _state: PhantomData, 1826 + _fields: self._fields, 1827 + _type: PhantomData, 1828 + } 1829 + } 1830 + } 1831 + 1832 + impl<St, S: BosStr> PlayViewBuilder<St, S> 1833 + where 1834 + St: play_view_state::State, 1835 + St::Station: play_view_state::IsUnset, 1836 + { 1837 + /// Set the `station` field (required) 1838 + pub fn station( 1839 + mut self, 1840 + value: impl Into<fm_atradio::StationInfo<S>>, 1841 + ) -> PlayViewBuilder<play_view_state::SetStation<St>, S> { 1842 + self._fields.2 = Option::Some(value.into()); 1843 + PlayViewBuilder { 1844 + _state: PhantomData, 1845 + _fields: self._fields, 1846 + _type: PhantomData, 1847 + } 1848 + } 1849 + } 1850 + 1851 + impl<St, S: BosStr> PlayViewBuilder<St, S> 1852 + where 1853 + St: play_view_state::State, 1854 + St::PlayedAt: play_view_state::IsSet, 1855 + St::Station: play_view_state::IsSet, 1856 + { 1857 + /// Build the final struct. 1858 + pub fn build(self) -> PlayView<S> { 1859 + PlayView { 1860 + actor: self._fields.0, 1861 + played_at: self._fields.1.unwrap(), 1862 + station: self._fields.2.unwrap(), 1863 + extra_data: Default::default(), 1864 + } 1865 + } 1866 + /// Build the final struct with custom extra_data. 1867 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> PlayView<S> { 1868 + PlayView { 1869 + actor: self._fields.0, 1870 + played_at: self._fields.1.unwrap(), 1871 + station: self._fields.2.unwrap(), 1872 + extra_data: Some(extra_data), 1873 + } 1874 + } 1875 + } 1876 + 1877 + pub mod station_info_state { 1878 + 1879 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1880 + #[allow(unused)] 1881 + use ::core::marker::PhantomData; 1882 + mod sealed { 1883 + pub trait Sealed {} 1884 + } 1885 + /// State trait tracking which required fields have been set 1886 + pub trait State: sealed::Sealed { 1887 + type Name; 1888 + type Source; 1889 + type StationId; 1890 + type StreamUrl; 1891 + } 1892 + /// Empty state - all required fields are unset 1893 + pub struct Empty(()); 1894 + impl sealed::Sealed for Empty {} 1895 + impl State for Empty { 1896 + type Name = Unset; 1897 + type Source = Unset; 1898 + type StationId = Unset; 1899 + type StreamUrl = Unset; 1900 + } 1901 + ///State transition - sets the `name` field to Set 1902 + pub struct SetName<St: State = Empty>(PhantomData<fn() -> St>); 1903 + impl<St: State> sealed::Sealed for SetName<St> {} 1904 + impl<St: State> State for SetName<St> { 1905 + type Name = Set<members::name>; 1906 + type Source = St::Source; 1907 + type StationId = St::StationId; 1908 + type StreamUrl = St::StreamUrl; 1909 + } 1910 + ///State transition - sets the `source` field to Set 1911 + pub struct SetSource<St: State = Empty>(PhantomData<fn() -> St>); 1912 + impl<St: State> sealed::Sealed for SetSource<St> {} 1913 + impl<St: State> State for SetSource<St> { 1914 + type Name = St::Name; 1915 + type Source = Set<members::source>; 1916 + type StationId = St::StationId; 1917 + type StreamUrl = St::StreamUrl; 1918 + } 1919 + ///State transition - sets the `station_id` field to Set 1920 + pub struct SetStationId<St: State = Empty>(PhantomData<fn() -> St>); 1921 + impl<St: State> sealed::Sealed for SetStationId<St> {} 1922 + impl<St: State> State for SetStationId<St> { 1923 + type Name = St::Name; 1924 + type Source = St::Source; 1925 + type StationId = Set<members::station_id>; 1926 + type StreamUrl = St::StreamUrl; 1927 + } 1928 + ///State transition - sets the `stream_url` field to Set 1929 + pub struct SetStreamUrl<St: State = Empty>(PhantomData<fn() -> St>); 1930 + impl<St: State> sealed::Sealed for SetStreamUrl<St> {} 1931 + impl<St: State> State for SetStreamUrl<St> { 1932 + type Name = St::Name; 1933 + type Source = St::Source; 1934 + type StationId = St::StationId; 1935 + type StreamUrl = Set<members::stream_url>; 1936 + } 1937 + /// Marker types for field names 1938 + #[allow(non_camel_case_types)] 1939 + pub mod members { 1940 + ///Marker type for the `name` field 1941 + pub struct name(()); 1942 + ///Marker type for the `source` field 1943 + pub struct source(()); 1944 + ///Marker type for the `station_id` field 1945 + pub struct station_id(()); 1946 + ///Marker type for the `stream_url` field 1947 + pub struct stream_url(()); 1948 + } 1949 + } 1950 + 1951 + /// Builder for constructing an instance of this type. 1952 + pub struct StationInfoBuilder<St: station_info_state::State, S: BosStr = DefaultStr> { 1953 + _state: PhantomData<fn() -> St>, 1954 + _fields: ( 1955 + Option<i64>, 1956 + Option<S>, 1957 + Option<S>, 1958 + Option<S>, 1959 + Option<S>, 1960 + Option<UriValue<S>>, 1961 + Option<S>, 1962 + Option<UriValue<S>>, 1963 + Option<S>, 1964 + Option<S>, 1965 + Option<S>, 1966 + Option<UriValue<S>>, 1967 + Option<Vec<S>>, 1968 + ), 1969 + _type: PhantomData<fn() -> S>, 1970 + } 1971 + 1972 + impl StationInfo<DefaultStr> { 1973 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 1974 + pub fn new() -> StationInfoBuilder<station_info_state::Empty, DefaultStr> { 1975 + StationInfoBuilder::new() 1976 + } 1977 + } 1978 + 1979 + impl<S: BosStr> StationInfo<S> { 1980 + /// Create a new builder for this type 1981 + pub fn builder() -> StationInfoBuilder<station_info_state::Empty, S> { 1982 + StationInfoBuilder::builder() 1983 + } 1984 + } 1985 + 1986 + impl StationInfoBuilder<station_info_state::Empty, DefaultStr> { 1987 + /// Create a new builder with all fields unset, using the default string type, if needed 1988 + pub fn new() -> Self { 1989 + StationInfoBuilder { 1990 + _state: PhantomData, 1991 + _fields: ( 1992 + None, 1993 + None, 1994 + None, 1995 + None, 1996 + None, 1997 + None, 1998 + None, 1999 + None, 2000 + None, 2001 + None, 2002 + None, 2003 + None, 2004 + None, 2005 + ), 2006 + _type: PhantomData, 2007 + } 2008 + } 2009 + } 2010 + 2011 + impl<S: BosStr> StationInfoBuilder<station_info_state::Empty, S> { 2012 + /// Create a new builder with all fields unset 2013 + pub fn builder() -> Self { 2014 + StationInfoBuilder { 2015 + _state: PhantomData, 2016 + _fields: ( 2017 + None, 2018 + None, 2019 + None, 2020 + None, 2021 + None, 2022 + None, 2023 + None, 2024 + None, 2025 + None, 2026 + None, 2027 + None, 2028 + None, 2029 + None, 2030 + ), 2031 + _type: PhantomData, 2032 + } 2033 + } 2034 + } 2035 + 2036 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2037 + /// Set the `bitrate` field (optional) 2038 + pub fn bitrate(mut self, value: impl Into<Option<i64>>) -> Self { 2039 + self._fields.0 = value.into(); 2040 + self 2041 + } 2042 + /// Set the `bitrate` field to an Option value (optional) 2043 + pub fn maybe_bitrate(mut self, value: Option<i64>) -> Self { 2044 + self._fields.0 = value; 2045 + self 2046 + } 2047 + } 2048 + 2049 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2050 + /// Set the `codec` field (optional) 2051 + pub fn codec(mut self, value: impl Into<Option<S>>) -> Self { 2052 + self._fields.1 = value.into(); 2053 + self 2054 + } 2055 + /// Set the `codec` field to an Option value (optional) 2056 + pub fn maybe_codec(mut self, value: Option<S>) -> Self { 2057 + self._fields.1 = value; 2058 + self 2059 + } 2060 + } 2061 + 2062 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2063 + /// Set the `country` field (optional) 2064 + pub fn country(mut self, value: impl Into<Option<S>>) -> Self { 2065 + self._fields.2 = value.into(); 2066 + self 2067 + } 2068 + /// Set the `country` field to an Option value (optional) 2069 + pub fn maybe_country(mut self, value: Option<S>) -> Self { 2070 + self._fields.2 = value; 2071 + self 2072 + } 2073 + } 2074 + 2075 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2076 + /// Set the `description` field (optional) 2077 + pub fn description(mut self, value: impl Into<Option<S>>) -> Self { 2078 + self._fields.3 = value.into(); 2079 + self 2080 + } 2081 + /// Set the `description` field to an Option value (optional) 2082 + pub fn maybe_description(mut self, value: Option<S>) -> Self { 2083 + self._fields.3 = value; 2084 + self 2085 + } 2086 + } 2087 + 2088 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2089 + /// Set the `genre` field (optional) 2090 + pub fn genre(mut self, value: impl Into<Option<S>>) -> Self { 2091 + self._fields.4 = value.into(); 2092 + self 2093 + } 2094 + /// Set the `genre` field to an Option value (optional) 2095 + pub fn maybe_genre(mut self, value: Option<S>) -> Self { 2096 + self._fields.4 = value; 2097 + self 2098 + } 2099 + } 2100 + 2101 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2102 + /// Set the `homepage` field (optional) 2103 + pub fn homepage(mut self, value: impl Into<Option<UriValue<S>>>) -> Self { 2104 + self._fields.5 = value.into(); 2105 + self 2106 + } 2107 + /// Set the `homepage` field to an Option value (optional) 2108 + pub fn maybe_homepage(mut self, value: Option<UriValue<S>>) -> Self { 2109 + self._fields.5 = value; 2110 + self 2111 + } 2112 + } 2113 + 2114 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2115 + /// Set the `language` field (optional) 2116 + pub fn language(mut self, value: impl Into<Option<S>>) -> Self { 2117 + self._fields.6 = value.into(); 2118 + self 2119 + } 2120 + /// Set the `language` field to an Option value (optional) 2121 + pub fn maybe_language(mut self, value: Option<S>) -> Self { 2122 + self._fields.6 = value; 2123 + self 2124 + } 2125 + } 2126 + 2127 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2128 + /// Set the `logo` field (optional) 2129 + pub fn logo(mut self, value: impl Into<Option<UriValue<S>>>) -> Self { 2130 + self._fields.7 = value.into(); 2131 + self 2132 + } 2133 + /// Set the `logo` field to an Option value (optional) 2134 + pub fn maybe_logo(mut self, value: Option<UriValue<S>>) -> Self { 2135 + self._fields.7 = value; 2136 + self 2137 + } 2138 + } 2139 + 2140 + impl<St, S: BosStr> StationInfoBuilder<St, S> 2141 + where 2142 + St: station_info_state::State, 2143 + St::Name: station_info_state::IsUnset, 2144 + { 2145 + /// Set the `name` field (required) 2146 + pub fn name( 2147 + mut self, 2148 + value: impl Into<S>, 2149 + ) -> StationInfoBuilder<station_info_state::SetName<St>, S> { 2150 + self._fields.8 = Option::Some(value.into()); 2151 + StationInfoBuilder { 2152 + _state: PhantomData, 2153 + _fields: self._fields, 2154 + _type: PhantomData, 2155 + } 2156 + } 2157 + } 2158 + 2159 + impl<St, S: BosStr> StationInfoBuilder<St, S> 2160 + where 2161 + St: station_info_state::State, 2162 + St::Source: station_info_state::IsUnset, 2163 + { 2164 + /// Set the `source` field (required) 2165 + pub fn source( 2166 + mut self, 2167 + value: impl Into<S>, 2168 + ) -> StationInfoBuilder<station_info_state::SetSource<St>, S> { 2169 + self._fields.9 = Option::Some(value.into()); 2170 + StationInfoBuilder { 2171 + _state: PhantomData, 2172 + _fields: self._fields, 2173 + _type: PhantomData, 2174 + } 2175 + } 2176 + } 2177 + 2178 + impl<St, S: BosStr> StationInfoBuilder<St, S> 2179 + where 2180 + St: station_info_state::State, 2181 + St::StationId: station_info_state::IsUnset, 2182 + { 2183 + /// Set the `stationId` field (required) 2184 + pub fn station_id( 2185 + mut self, 2186 + value: impl Into<S>, 2187 + ) -> StationInfoBuilder<station_info_state::SetStationId<St>, S> { 2188 + self._fields.10 = Option::Some(value.into()); 2189 + StationInfoBuilder { 2190 + _state: PhantomData, 2191 + _fields: self._fields, 2192 + _type: PhantomData, 2193 + } 2194 + } 2195 + } 2196 + 2197 + impl<St, S: BosStr> StationInfoBuilder<St, S> 2198 + where 2199 + St: station_info_state::State, 2200 + St::StreamUrl: station_info_state::IsUnset, 2201 + { 2202 + /// Set the `streamUrl` field (required) 2203 + pub fn stream_url( 2204 + mut self, 2205 + value: impl Into<UriValue<S>>, 2206 + ) -> StationInfoBuilder<station_info_state::SetStreamUrl<St>, S> { 2207 + self._fields.11 = Option::Some(value.into()); 2208 + StationInfoBuilder { 2209 + _state: PhantomData, 2210 + _fields: self._fields, 2211 + _type: PhantomData, 2212 + } 2213 + } 2214 + } 2215 + 2216 + impl<St: station_info_state::State, S: BosStr> StationInfoBuilder<St, S> { 2217 + /// Set the `tags` field (optional) 2218 + pub fn tags(mut self, value: impl Into<Option<Vec<S>>>) -> Self { 2219 + self._fields.12 = value.into(); 2220 + self 2221 + } 2222 + /// Set the `tags` field to an Option value (optional) 2223 + pub fn maybe_tags(mut self, value: Option<Vec<S>>) -> Self { 2224 + self._fields.12 = value; 2225 + self 2226 + } 2227 + } 2228 + 2229 + impl<St, S: BosStr> StationInfoBuilder<St, S> 2230 + where 2231 + St: station_info_state::State, 2232 + St::Name: station_info_state::IsSet, 2233 + St::Source: station_info_state::IsSet, 2234 + St::StationId: station_info_state::IsSet, 2235 + St::StreamUrl: station_info_state::IsSet, 2236 + { 2237 + /// Build the final struct. 2238 + pub fn build(self) -> StationInfo<S> { 2239 + StationInfo { 2240 + bitrate: self._fields.0, 2241 + codec: self._fields.1, 2242 + country: self._fields.2, 2243 + description: self._fields.3, 2244 + genre: self._fields.4, 2245 + homepage: self._fields.5, 2246 + language: self._fields.6, 2247 + logo: self._fields.7, 2248 + name: self._fields.8.unwrap(), 2249 + source: self._fields.9.unwrap(), 2250 + station_id: self._fields.10.unwrap(), 2251 + stream_url: self._fields.11.unwrap(), 2252 + tags: self._fields.12, 2253 + extra_data: Default::default(), 2254 + } 2255 + } 2256 + /// Build the final struct with custom extra_data. 2257 + pub fn build_with_data( 2258 + self, 2259 + extra_data: BTreeMap<SmolStr, Data<S>>, 2260 + ) -> StationInfo<S> { 2261 + StationInfo { 2262 + bitrate: self._fields.0, 2263 + codec: self._fields.1, 2264 + country: self._fields.2, 2265 + description: self._fields.3, 2266 + genre: self._fields.4, 2267 + homepage: self._fields.5, 2268 + language: self._fields.6, 2269 + logo: self._fields.7, 2270 + name: self._fields.8.unwrap(), 2271 + source: self._fields.9.unwrap(), 2272 + station_id: self._fields.10.unwrap(), 2273 + stream_url: self._fields.11.unwrap(), 2274 + tags: self._fields.12, 2275 + extra_data: Some(extra_data), 2276 + } 2277 + } 2278 + } 2279 + 2280 + pub mod station_view_state { 2281 + 2282 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 2283 + #[allow(unused)] 2284 + use ::core::marker::PhantomData; 2285 + mod sealed { 2286 + pub trait Sealed {} 2287 + } 2288 + /// State trait tracking which required fields have been set 2289 + pub trait State: sealed::Sealed { 2290 + type CreatedAt; 2291 + type Station; 2292 + type Uri; 2293 + } 2294 + /// Empty state - all required fields are unset 2295 + pub struct Empty(()); 2296 + impl sealed::Sealed for Empty {} 2297 + impl State for Empty { 2298 + type CreatedAt = Unset; 2299 + type Station = Unset; 2300 + type Uri = Unset; 2301 + } 2302 + ///State transition - sets the `created_at` field to Set 2303 + pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>); 2304 + impl<St: State> sealed::Sealed for SetCreatedAt<St> {} 2305 + impl<St: State> State for SetCreatedAt<St> { 2306 + type CreatedAt = Set<members::created_at>; 2307 + type Station = St::Station; 2308 + type Uri = St::Uri; 2309 + } 2310 + ///State transition - sets the `station` field to Set 2311 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 2312 + impl<St: State> sealed::Sealed for SetStation<St> {} 2313 + impl<St: State> State for SetStation<St> { 2314 + type CreatedAt = St::CreatedAt; 2315 + type Station = Set<members::station>; 2316 + type Uri = St::Uri; 2317 + } 2318 + ///State transition - sets the `uri` field to Set 2319 + pub struct SetUri<St: State = Empty>(PhantomData<fn() -> St>); 2320 + impl<St: State> sealed::Sealed for SetUri<St> {} 2321 + impl<St: State> State for SetUri<St> { 2322 + type CreatedAt = St::CreatedAt; 2323 + type Station = St::Station; 2324 + type Uri = Set<members::uri>; 2325 + } 2326 + /// Marker types for field names 2327 + #[allow(non_camel_case_types)] 2328 + pub mod members { 2329 + ///Marker type for the `created_at` field 2330 + pub struct created_at(()); 2331 + ///Marker type for the `station` field 2332 + pub struct station(()); 2333 + ///Marker type for the `uri` field 2334 + pub struct uri(()); 2335 + } 2336 + } 2337 + 2338 + /// Builder for constructing an instance of this type. 2339 + pub struct StationViewBuilder<St: station_view_state::State, S: BosStr = DefaultStr> { 2340 + _state: PhantomData<fn() -> St>, 2341 + _fields: (Option<Datetime>, Option<fm_atradio::StationInfo<S>>, Option<AtUri<S>>), 2342 + _type: PhantomData<fn() -> S>, 2343 + } 2344 + 2345 + impl StationView<DefaultStr> { 2346 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 2347 + pub fn new() -> StationViewBuilder<station_view_state::Empty, DefaultStr> { 2348 + StationViewBuilder::new() 2349 + } 2350 + } 2351 + 2352 + impl<S: BosStr> StationView<S> { 2353 + /// Create a new builder for this type 2354 + pub fn builder() -> StationViewBuilder<station_view_state::Empty, S> { 2355 + StationViewBuilder::builder() 2356 + } 2357 + } 2358 + 2359 + impl StationViewBuilder<station_view_state::Empty, DefaultStr> { 2360 + /// Create a new builder with all fields unset, using the default string type, if needed 2361 + pub fn new() -> Self { 2362 + StationViewBuilder { 2363 + _state: PhantomData, 2364 + _fields: (None, None, None), 2365 + _type: PhantomData, 2366 + } 2367 + } 2368 + } 2369 + 2370 + impl<S: BosStr> StationViewBuilder<station_view_state::Empty, S> { 2371 + /// Create a new builder with all fields unset 2372 + pub fn builder() -> Self { 2373 + StationViewBuilder { 2374 + _state: PhantomData, 2375 + _fields: (None, None, None), 2376 + _type: PhantomData, 2377 + } 2378 + } 2379 + } 2380 + 2381 + impl<St, S: BosStr> StationViewBuilder<St, S> 2382 + where 2383 + St: station_view_state::State, 2384 + St::CreatedAt: station_view_state::IsUnset, 2385 + { 2386 + /// Set the `createdAt` field (required) 2387 + pub fn created_at( 2388 + mut self, 2389 + value: impl Into<Datetime>, 2390 + ) -> StationViewBuilder<station_view_state::SetCreatedAt<St>, S> { 2391 + self._fields.0 = Option::Some(value.into()); 2392 + StationViewBuilder { 2393 + _state: PhantomData, 2394 + _fields: self._fields, 2395 + _type: PhantomData, 2396 + } 2397 + } 2398 + } 2399 + 2400 + impl<St, S: BosStr> StationViewBuilder<St, S> 2401 + where 2402 + St: station_view_state::State, 2403 + St::Station: station_view_state::IsUnset, 2404 + { 2405 + /// Set the `station` field (required) 2406 + pub fn station( 2407 + mut self, 2408 + value: impl Into<fm_atradio::StationInfo<S>>, 2409 + ) -> StationViewBuilder<station_view_state::SetStation<St>, S> { 2410 + self._fields.1 = Option::Some(value.into()); 2411 + StationViewBuilder { 2412 + _state: PhantomData, 2413 + _fields: self._fields, 2414 + _type: PhantomData, 2415 + } 2416 + } 2417 + } 2418 + 2419 + impl<St, S: BosStr> StationViewBuilder<St, S> 2420 + where 2421 + St: station_view_state::State, 2422 + St::Uri: station_view_state::IsUnset, 2423 + { 2424 + /// Set the `uri` field (required) 2425 + pub fn uri( 2426 + mut self, 2427 + value: impl Into<AtUri<S>>, 2428 + ) -> StationViewBuilder<station_view_state::SetUri<St>, S> { 2429 + self._fields.2 = Option::Some(value.into()); 2430 + StationViewBuilder { 2431 + _state: PhantomData, 2432 + _fields: self._fields, 2433 + _type: PhantomData, 2434 + } 2435 + } 2436 + } 2437 + 2438 + impl<St, S: BosStr> StationViewBuilder<St, S> 2439 + where 2440 + St: station_view_state::State, 2441 + St::CreatedAt: station_view_state::IsSet, 2442 + St::Station: station_view_state::IsSet, 2443 + St::Uri: station_view_state::IsSet, 2444 + { 2445 + /// Build the final struct. 2446 + pub fn build(self) -> StationView<S> { 2447 + StationView { 2448 + created_at: self._fields.0.unwrap(), 2449 + station: self._fields.1.unwrap(), 2450 + uri: self._fields.2.unwrap(), 2451 + extra_data: Default::default(), 2452 + } 2453 + } 2454 + /// Build the final struct with custom extra_data. 2455 + pub fn build_with_data( 2456 + self, 2457 + extra_data: BTreeMap<SmolStr, Data<S>>, 2458 + ) -> StationView<S> { 2459 + StationView { 2460 + created_at: self._fields.0.unwrap(), 2461 + station: self._fields.1.unwrap(), 2462 + uri: self._fields.2.unwrap(), 2463 + extra_data: Some(extra_data), 2464 + } 2465 + } 2466 + }
+7
cli/src/fm_atradio/actor.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + //! Generated bindings for the `fm.atradio.actor` Lexicon namespace/module. 7 + pub mod status;
+309
cli/src/fm_atradio/actor/status.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.actor.status 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + 15 + #[allow(unused_imports)] 16 + use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 17 + use jacquard_common::deps::smol_str::SmolStr; 18 + use jacquard_common::types::collection::{Collection, RecordError}; 19 + use jacquard_common::types::string::{AtUri, Cid, Datetime}; 20 + use jacquard_common::types::uri::{RecordUri, UriError}; 21 + use jacquard_common::types::value::Data; 22 + use jacquard_common::xrpc::XrpcResp; 23 + use jacquard_derive::{IntoStatic, lexicon}; 24 + use jacquard_lexicon::lexicon::LexiconDoc; 25 + use jacquard_lexicon::schema::LexiconSchema; 26 + 27 + #[allow(unused_imports)] 28 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 29 + use serde::{Serialize, Deserialize}; 30 + use crate::fm_atradio::StationInfo; 31 + /// The actor's current listening status: the station they most recently played and when. A singleton record, overwritten on each play. 32 + 33 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 34 + #[serde( 35 + rename_all = "camelCase", 36 + rename = "fm.atradio.actor.status", 37 + tag = "$type", 38 + bound(deserialize = "S: Deserialize<'de> + BosStr") 39 + )] 40 + pub struct Status<S: BosStr = DefaultStr> { 41 + pub played_at: Datetime, 42 + pub station: StationInfo<S>, 43 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 44 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 45 + } 46 + 47 + /// Typed wrapper for GetRecord response with this collection's record type. 48 + 49 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 50 + #[serde(rename_all = "camelCase")] 51 + pub struct StatusGetRecordOutput<S: BosStr = DefaultStr> { 52 + #[serde(skip_serializing_if = "Option::is_none")] 53 + pub cid: Option<Cid<S>>, 54 + pub uri: AtUri<S>, 55 + pub value: Status<S>, 56 + } 57 + 58 + impl<S: BosStr> Status<S> { 59 + pub fn uri(uri: S) -> Result<RecordUri<S, StatusRecord>, UriError> { 60 + RecordUri::try_from_uri(AtUri::new(uri)?) 61 + } 62 + } 63 + 64 + /// Marker type for deserializing records from this collection. 65 + 66 + #[derive(Debug, Serialize, Deserialize)] 67 + pub struct StatusRecord; 68 + impl XrpcResp for StatusRecord { 69 + const NSID: &'static str = "fm.atradio.actor.status"; 70 + const ENCODING: &'static str = "application/json"; 71 + type Output<S: BosStr> = StatusGetRecordOutput<S>; 72 + type Err = RecordError; 73 + } 74 + 75 + impl<S: BosStr> From<StatusGetRecordOutput<S>> for Status<S> { 76 + fn from(output: StatusGetRecordOutput<S>) -> Self { 77 + output.value 78 + } 79 + } 80 + 81 + impl<S: BosStr> Collection for Status<S> { 82 + const NSID: &'static str = "fm.atradio.actor.status"; 83 + type Record = StatusRecord; 84 + } 85 + 86 + impl Collection for StatusRecord { 87 + const NSID: &'static str = "fm.atradio.actor.status"; 88 + type Record = StatusRecord; 89 + } 90 + 91 + impl<S: BosStr> LexiconSchema for Status<S> { 92 + fn nsid() -> &'static str { 93 + "fm.atradio.actor.status" 94 + } 95 + fn def_name() -> &'static str { 96 + "main" 97 + } 98 + fn lexicon_doc() -> LexiconDoc<'static> { 99 + lexicon_doc_fm_atradio_actor_status() 100 + } 101 + fn validate(&self) -> Result<(), ConstraintError> { 102 + Ok(()) 103 + } 104 + } 105 + 106 + pub mod status_state { 107 + 108 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 109 + #[allow(unused)] 110 + use ::core::marker::PhantomData; 111 + mod sealed { 112 + pub trait Sealed {} 113 + } 114 + /// State trait tracking which required fields have been set 115 + pub trait State: sealed::Sealed { 116 + type PlayedAt; 117 + type Station; 118 + } 119 + /// Empty state - all required fields are unset 120 + pub struct Empty(()); 121 + impl sealed::Sealed for Empty {} 122 + impl State for Empty { 123 + type PlayedAt = Unset; 124 + type Station = Unset; 125 + } 126 + ///State transition - sets the `played_at` field to Set 127 + pub struct SetPlayedAt<St: State = Empty>(PhantomData<fn() -> St>); 128 + impl<St: State> sealed::Sealed for SetPlayedAt<St> {} 129 + impl<St: State> State for SetPlayedAt<St> { 130 + type PlayedAt = Set<members::played_at>; 131 + type Station = St::Station; 132 + } 133 + ///State transition - sets the `station` field to Set 134 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 135 + impl<St: State> sealed::Sealed for SetStation<St> {} 136 + impl<St: State> State for SetStation<St> { 137 + type PlayedAt = St::PlayedAt; 138 + type Station = Set<members::station>; 139 + } 140 + /// Marker types for field names 141 + #[allow(non_camel_case_types)] 142 + pub mod members { 143 + ///Marker type for the `played_at` field 144 + pub struct played_at(()); 145 + ///Marker type for the `station` field 146 + pub struct station(()); 147 + } 148 + } 149 + 150 + /// Builder for constructing an instance of this type. 151 + pub struct StatusBuilder<St: status_state::State, S: BosStr = DefaultStr> { 152 + _state: PhantomData<fn() -> St>, 153 + _fields: (Option<Datetime>, Option<StationInfo<S>>), 154 + _type: PhantomData<fn() -> S>, 155 + } 156 + 157 + impl Status<DefaultStr> { 158 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 159 + pub fn new() -> StatusBuilder<status_state::Empty, DefaultStr> { 160 + StatusBuilder::new() 161 + } 162 + } 163 + 164 + impl<S: BosStr> Status<S> { 165 + /// Create a new builder for this type 166 + pub fn builder() -> StatusBuilder<status_state::Empty, S> { 167 + StatusBuilder::builder() 168 + } 169 + } 170 + 171 + impl StatusBuilder<status_state::Empty, DefaultStr> { 172 + /// Create a new builder with all fields unset, using the default string type, if needed 173 + pub fn new() -> Self { 174 + StatusBuilder { 175 + _state: PhantomData, 176 + _fields: (None, None), 177 + _type: PhantomData, 178 + } 179 + } 180 + } 181 + 182 + impl<S: BosStr> StatusBuilder<status_state::Empty, S> { 183 + /// Create a new builder with all fields unset 184 + pub fn builder() -> Self { 185 + StatusBuilder { 186 + _state: PhantomData, 187 + _fields: (None, None), 188 + _type: PhantomData, 189 + } 190 + } 191 + } 192 + 193 + impl<St, S: BosStr> StatusBuilder<St, S> 194 + where 195 + St: status_state::State, 196 + St::PlayedAt: status_state::IsUnset, 197 + { 198 + /// Set the `playedAt` field (required) 199 + pub fn played_at( 200 + mut self, 201 + value: impl Into<Datetime>, 202 + ) -> StatusBuilder<status_state::SetPlayedAt<St>, S> { 203 + self._fields.0 = Option::Some(value.into()); 204 + StatusBuilder { 205 + _state: PhantomData, 206 + _fields: self._fields, 207 + _type: PhantomData, 208 + } 209 + } 210 + } 211 + 212 + impl<St, S: BosStr> StatusBuilder<St, S> 213 + where 214 + St: status_state::State, 215 + St::Station: status_state::IsUnset, 216 + { 217 + /// Set the `station` field (required) 218 + pub fn station( 219 + mut self, 220 + value: impl Into<StationInfo<S>>, 221 + ) -> StatusBuilder<status_state::SetStation<St>, S> { 222 + self._fields.1 = Option::Some(value.into()); 223 + StatusBuilder { 224 + _state: PhantomData, 225 + _fields: self._fields, 226 + _type: PhantomData, 227 + } 228 + } 229 + } 230 + 231 + impl<St, S: BosStr> StatusBuilder<St, S> 232 + where 233 + St: status_state::State, 234 + St::PlayedAt: status_state::IsSet, 235 + St::Station: status_state::IsSet, 236 + { 237 + /// Build the final struct. 238 + pub fn build(self) -> Status<S> { 239 + Status { 240 + played_at: self._fields.0.unwrap(), 241 + station: self._fields.1.unwrap(), 242 + extra_data: Default::default(), 243 + } 244 + } 245 + /// Build the final struct with custom extra_data. 246 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Status<S> { 247 + Status { 248 + played_at: self._fields.0.unwrap(), 249 + station: self._fields.1.unwrap(), 250 + extra_data: Some(extra_data), 251 + } 252 + } 253 + } 254 + 255 + fn lexicon_doc_fm_atradio_actor_status() -> LexiconDoc<'static> { 256 + #[allow(unused_imports)] 257 + use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 258 + use jacquard_lexicon::lexicon::*; 259 + use alloc::collections::BTreeMap; 260 + LexiconDoc { 261 + lexicon: Lexicon::Lexicon1, 262 + id: CowStr::new_static("fm.atradio.actor.status"), 263 + defs: { 264 + let mut map = BTreeMap::new(); 265 + map.insert( 266 + SmolStr::new_static("main"), 267 + LexUserType::Record(LexRecord { 268 + description: Some( 269 + CowStr::new_static( 270 + "The actor's current listening status: the station they most recently played and when. A singleton record, overwritten on each play.", 271 + ), 272 + ), 273 + key: Some(CowStr::new_static("literal:self")), 274 + record: LexRecordRecord::Object(LexObject { 275 + required: Some( 276 + vec![ 277 + SmolStr::new_static("station"), 278 + SmolStr::new_static("playedAt") 279 + ], 280 + ), 281 + properties: { 282 + #[allow(unused_mut)] 283 + let mut map = BTreeMap::new(); 284 + map.insert( 285 + SmolStr::new_static("playedAt"), 286 + LexObjectProperty::String(LexString { 287 + format: Some(LexStringFormat::Datetime), 288 + ..Default::default() 289 + }), 290 + ); 291 + map.insert( 292 + SmolStr::new_static("station"), 293 + LexObjectProperty::Ref(LexRef { 294 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 295 + ..Default::default() 296 + }), 297 + ); 298 + map 299 + }, 300 + ..Default::default() 301 + }), 302 + ..Default::default() 303 + }), 304 + ); 305 + map 306 + }, 307 + ..Default::default() 308 + } 309 + }
+7
cli/src/fm_atradio/audio.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + //! Generated bindings for the `fm.atradio.audio` Lexicon namespace/module. 7 + pub mod settings;
+1052
cli/src/fm_atradio/audio/settings.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.audio.settings 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + 15 + #[allow(unused_imports)] 16 + use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 17 + use jacquard_common::deps::smol_str::SmolStr; 18 + use jacquard_common::types::collection::{Collection, RecordError}; 19 + use jacquard_common::types::string::{AtUri, Cid, Datetime}; 20 + use jacquard_common::types::uri::{RecordUri, UriError}; 21 + use jacquard_common::types::value::Data; 22 + use jacquard_common::xrpc::XrpcResp; 23 + use jacquard_derive::{IntoStatic, lexicon}; 24 + use jacquard_lexicon::lexicon::LexiconDoc; 25 + use jacquard_lexicon::schema::LexiconSchema; 26 + 27 + #[allow(unused_imports)] 28 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 29 + use serde::{Serialize, Deserialize}; 30 + /// The actor's audio playback settings (10-band EQ + Rockbox DSP chain), a singleton record synced across devices. Gains are integers; the crossfeed direct gain is in tenths of dB, matching the Rockbox convention. 31 + 32 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 33 + #[serde( 34 + rename_all = "camelCase", 35 + rename = "fm.atradio.audio.settings", 36 + tag = "$type", 37 + bound(deserialize = "S: Deserialize<'de> + BosStr") 38 + )] 39 + pub struct Settings<S: BosStr = DefaultStr> { 40 + ///Bass tone control in dB. 41 + #[serde(skip_serializing_if = "Option::is_none")] 42 + pub bass: Option<i64>, 43 + ///Channel mixing mode. 44 + #[serde(skip_serializing_if = "Option::is_none")] 45 + pub channel_mode: Option<SettingsChannelMode<S>>, 46 + ///Compressor ratio (n:1). 47 + #[serde(skip_serializing_if = "Option::is_none")] 48 + pub comp_ratio: Option<i64>, 49 + ///Compressor threshold in dB (0 = off). 50 + #[serde(skip_serializing_if = "Option::is_none")] 51 + pub comp_threshold: Option<i64>, 52 + ///Crossfeed direct-path gain in tenths of dB (<= 0). 53 + #[serde(skip_serializing_if = "Option::is_none")] 54 + pub crossfeed_direct: Option<i64>, 55 + ///Headphone crossfeed mode. 56 + #[serde(skip_serializing_if = "Option::is_none")] 57 + pub crossfeed_mode: Option<SettingsCrossfeedMode<S>>, 58 + ///Whether the 10-band parametric EQ is enabled. 59 + #[serde(skip_serializing_if = "Option::is_none")] 60 + pub eq_enabled: Option<bool>, 61 + ///Per-band EQ gain in dB, one entry per band (60 Hz - 20 kHz). 62 + #[serde(skip_serializing_if = "Option::is_none")] 63 + pub eq_gains: Option<Vec<i64>>, 64 + ///Perceptual Bass Enhancement strength in percent. 65 + #[serde(skip_serializing_if = "Option::is_none")] 66 + pub pbe: Option<i64>, 67 + ///PBE pre-cut in dB (stored positive, applied as a cut). 68 + #[serde(skip_serializing_if = "Option::is_none")] 69 + pub pbe_precut: Option<i64>, 70 + ///Stereo width in percent. 71 + #[serde(skip_serializing_if = "Option::is_none")] 72 + pub stereo_width: Option<i64>, 73 + ///Haas surround balance in percent. 74 + #[serde(skip_serializing_if = "Option::is_none")] 75 + pub surround_balance: Option<i64>, 76 + ///Haas surround delay in ms (0 = off). 77 + #[serde(skip_serializing_if = "Option::is_none")] 78 + pub surround_delay: Option<i64>, 79 + ///Treble tone control in dB. 80 + #[serde(skip_serializing_if = "Option::is_none")] 81 + pub treble: Option<i64>, 82 + pub updated_at: Datetime, 83 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 84 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 85 + } 86 + 87 + /// Channel mixing mode. 88 + 89 + #[derive(Debug, Clone, PartialEq, Eq, Hash)] 90 + pub enum SettingsChannelMode<S: BosStr = DefaultStr> { 91 + Stereo, 92 + Mono, 93 + Custom, 94 + MonoLeft, 95 + MonoRight, 96 + Karaoke, 97 + Swap, 98 + Other(S), 99 + } 100 + 101 + impl<S: BosStr> SettingsChannelMode<S> { 102 + pub fn as_str(&self) -> &str { 103 + match self { 104 + Self::Stereo => "stereo", 105 + Self::Mono => "mono", 106 + Self::Custom => "custom", 107 + Self::MonoLeft => "mono-left", 108 + Self::MonoRight => "mono-right", 109 + Self::Karaoke => "karaoke", 110 + Self::Swap => "swap", 111 + Self::Other(s) => s.as_ref(), 112 + } 113 + } 114 + /// Construct from a string-like value, matching known values. 115 + pub fn from_value(s: S) -> Self { 116 + match s.as_ref() { 117 + "stereo" => Self::Stereo, 118 + "mono" => Self::Mono, 119 + "custom" => Self::Custom, 120 + "mono-left" => Self::MonoLeft, 121 + "mono-right" => Self::MonoRight, 122 + "karaoke" => Self::Karaoke, 123 + "swap" => Self::Swap, 124 + _ => Self::Other(s), 125 + } 126 + } 127 + } 128 + 129 + impl<S: BosStr> core::fmt::Display for SettingsChannelMode<S> { 130 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 131 + write!(f, "{}", self.as_str()) 132 + } 133 + } 134 + 135 + impl<S: BosStr> AsRef<str> for SettingsChannelMode<S> { 136 + fn as_ref(&self) -> &str { 137 + self.as_str() 138 + } 139 + } 140 + 141 + impl<S: BosStr> Serialize for SettingsChannelMode<S> { 142 + fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> 143 + where 144 + Ser: serde::Serializer, 145 + { 146 + serializer.serialize_str(self.as_str()) 147 + } 148 + } 149 + 150 + impl<'de, S: Deserialize<'de> + BosStr> Deserialize<'de> for SettingsChannelMode<S> { 151 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 152 + where 153 + D: serde::Deserializer<'de>, 154 + { 155 + let s = S::deserialize(deserializer)?; 156 + Ok(Self::from_value(s)) 157 + } 158 + } 159 + 160 + impl<S: BosStr + Default> Default for SettingsChannelMode<S> { 161 + fn default() -> Self { 162 + Self::Other(Default::default()) 163 + } 164 + } 165 + 166 + impl<S: BosStr> jacquard_common::IntoStatic for SettingsChannelMode<S> 167 + where 168 + S: BosStr + jacquard_common::IntoStatic, 169 + S::Output: BosStr, 170 + { 171 + type Output = SettingsChannelMode<S::Output>; 172 + fn into_static(self) -> Self::Output { 173 + match self { 174 + SettingsChannelMode::Stereo => SettingsChannelMode::Stereo, 175 + SettingsChannelMode::Mono => SettingsChannelMode::Mono, 176 + SettingsChannelMode::Custom => SettingsChannelMode::Custom, 177 + SettingsChannelMode::MonoLeft => SettingsChannelMode::MonoLeft, 178 + SettingsChannelMode::MonoRight => SettingsChannelMode::MonoRight, 179 + SettingsChannelMode::Karaoke => SettingsChannelMode::Karaoke, 180 + SettingsChannelMode::Swap => SettingsChannelMode::Swap, 181 + SettingsChannelMode::Other(v) => SettingsChannelMode::Other(v.into_static()), 182 + } 183 + } 184 + } 185 + 186 + /// Headphone crossfeed mode. 187 + 188 + #[derive(Debug, Clone, PartialEq, Eq, Hash)] 189 + pub enum SettingsCrossfeedMode<S: BosStr = DefaultStr> { 190 + Off, 191 + Meier, 192 + Custom, 193 + Other(S), 194 + } 195 + 196 + impl<S: BosStr> SettingsCrossfeedMode<S> { 197 + pub fn as_str(&self) -> &str { 198 + match self { 199 + Self::Off => "off", 200 + Self::Meier => "meier", 201 + Self::Custom => "custom", 202 + Self::Other(s) => s.as_ref(), 203 + } 204 + } 205 + /// Construct from a string-like value, matching known values. 206 + pub fn from_value(s: S) -> Self { 207 + match s.as_ref() { 208 + "off" => Self::Off, 209 + "meier" => Self::Meier, 210 + "custom" => Self::Custom, 211 + _ => Self::Other(s), 212 + } 213 + } 214 + } 215 + 216 + impl<S: BosStr> core::fmt::Display for SettingsCrossfeedMode<S> { 217 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 218 + write!(f, "{}", self.as_str()) 219 + } 220 + } 221 + 222 + impl<S: BosStr> AsRef<str> for SettingsCrossfeedMode<S> { 223 + fn as_ref(&self) -> &str { 224 + self.as_str() 225 + } 226 + } 227 + 228 + impl<S: BosStr> Serialize for SettingsCrossfeedMode<S> { 229 + fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> 230 + where 231 + Ser: serde::Serializer, 232 + { 233 + serializer.serialize_str(self.as_str()) 234 + } 235 + } 236 + 237 + impl<'de, S: Deserialize<'de> + BosStr> Deserialize<'de> for SettingsCrossfeedMode<S> { 238 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 239 + where 240 + D: serde::Deserializer<'de>, 241 + { 242 + let s = S::deserialize(deserializer)?; 243 + Ok(Self::from_value(s)) 244 + } 245 + } 246 + 247 + impl<S: BosStr + Default> Default for SettingsCrossfeedMode<S> { 248 + fn default() -> Self { 249 + Self::Other(Default::default()) 250 + } 251 + } 252 + 253 + impl<S: BosStr> jacquard_common::IntoStatic for SettingsCrossfeedMode<S> 254 + where 255 + S: BosStr + jacquard_common::IntoStatic, 256 + S::Output: BosStr, 257 + { 258 + type Output = SettingsCrossfeedMode<S::Output>; 259 + fn into_static(self) -> Self::Output { 260 + match self { 261 + SettingsCrossfeedMode::Off => SettingsCrossfeedMode::Off, 262 + SettingsCrossfeedMode::Meier => SettingsCrossfeedMode::Meier, 263 + SettingsCrossfeedMode::Custom => SettingsCrossfeedMode::Custom, 264 + SettingsCrossfeedMode::Other(v) => { 265 + SettingsCrossfeedMode::Other(v.into_static()) 266 + } 267 + } 268 + } 269 + } 270 + 271 + /// Typed wrapper for GetRecord response with this collection's record type. 272 + 273 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 274 + #[serde(rename_all = "camelCase")] 275 + pub struct SettingsGetRecordOutput<S: BosStr = DefaultStr> { 276 + #[serde(skip_serializing_if = "Option::is_none")] 277 + pub cid: Option<Cid<S>>, 278 + pub uri: AtUri<S>, 279 + pub value: Settings<S>, 280 + } 281 + 282 + impl<S: BosStr> Settings<S> { 283 + pub fn uri(uri: S) -> Result<RecordUri<S, SettingsRecord>, UriError> { 284 + RecordUri::try_from_uri(AtUri::new(uri)?) 285 + } 286 + } 287 + 288 + /// Marker type for deserializing records from this collection. 289 + 290 + #[derive(Debug, Serialize, Deserialize)] 291 + pub struct SettingsRecord; 292 + impl XrpcResp for SettingsRecord { 293 + const NSID: &'static str = "fm.atradio.audio.settings"; 294 + const ENCODING: &'static str = "application/json"; 295 + type Output<S: BosStr> = SettingsGetRecordOutput<S>; 296 + type Err = RecordError; 297 + } 298 + 299 + impl<S: BosStr> From<SettingsGetRecordOutput<S>> for Settings<S> { 300 + fn from(output: SettingsGetRecordOutput<S>) -> Self { 301 + output.value 302 + } 303 + } 304 + 305 + impl<S: BosStr> Collection for Settings<S> { 306 + const NSID: &'static str = "fm.atradio.audio.settings"; 307 + type Record = SettingsRecord; 308 + } 309 + 310 + impl Collection for SettingsRecord { 311 + const NSID: &'static str = "fm.atradio.audio.settings"; 312 + type Record = SettingsRecord; 313 + } 314 + 315 + impl<S: BosStr> LexiconSchema for Settings<S> { 316 + fn nsid() -> &'static str { 317 + "fm.atradio.audio.settings" 318 + } 319 + fn def_name() -> &'static str { 320 + "main" 321 + } 322 + fn lexicon_doc() -> LexiconDoc<'static> { 323 + lexicon_doc_fm_atradio_audio_settings() 324 + } 325 + fn validate(&self) -> Result<(), ConstraintError> { 326 + if let Some(ref value) = self.bass { 327 + if *value > 24i64 { 328 + return Err(ConstraintError::Maximum { 329 + path: ValidationPath::from_field("bass"), 330 + max: 24i64, 331 + actual: *value, 332 + }); 333 + } 334 + } 335 + if let Some(ref value) = self.bass { 336 + if *value < -24i64 { 337 + return Err(ConstraintError::Minimum { 338 + path: ValidationPath::from_field("bass"), 339 + min: -24i64, 340 + actual: *value, 341 + }); 342 + } 343 + } 344 + if let Some(ref value) = self.comp_ratio { 345 + if *value > 10i64 { 346 + return Err(ConstraintError::Maximum { 347 + path: ValidationPath::from_field("comp_ratio"), 348 + max: 10i64, 349 + actual: *value, 350 + }); 351 + } 352 + } 353 + if let Some(ref value) = self.comp_ratio { 354 + if *value < 2i64 { 355 + return Err(ConstraintError::Minimum { 356 + path: ValidationPath::from_field("comp_ratio"), 357 + min: 2i64, 358 + actual: *value, 359 + }); 360 + } 361 + } 362 + if let Some(ref value) = self.comp_threshold { 363 + if *value > 0i64 { 364 + return Err(ConstraintError::Maximum { 365 + path: ValidationPath::from_field("comp_threshold"), 366 + max: 0i64, 367 + actual: *value, 368 + }); 369 + } 370 + } 371 + if let Some(ref value) = self.comp_threshold { 372 + if *value < -30i64 { 373 + return Err(ConstraintError::Minimum { 374 + path: ValidationPath::from_field("comp_threshold"), 375 + min: -30i64, 376 + actual: *value, 377 + }); 378 + } 379 + } 380 + if let Some(ref value) = self.crossfeed_direct { 381 + if *value > 0i64 { 382 + return Err(ConstraintError::Maximum { 383 + path: ValidationPath::from_field("crossfeed_direct"), 384 + max: 0i64, 385 + actual: *value, 386 + }); 387 + } 388 + } 389 + if let Some(ref value) = self.crossfeed_direct { 390 + if *value < -60i64 { 391 + return Err(ConstraintError::Minimum { 392 + path: ValidationPath::from_field("crossfeed_direct"), 393 + min: -60i64, 394 + actual: *value, 395 + }); 396 + } 397 + } 398 + if let Some(ref value) = self.pbe { 399 + if *value > 100i64 { 400 + return Err(ConstraintError::Maximum { 401 + path: ValidationPath::from_field("pbe"), 402 + max: 100i64, 403 + actual: *value, 404 + }); 405 + } 406 + } 407 + if let Some(ref value) = self.pbe { 408 + if *value < 0i64 { 409 + return Err(ConstraintError::Minimum { 410 + path: ValidationPath::from_field("pbe"), 411 + min: 0i64, 412 + actual: *value, 413 + }); 414 + } 415 + } 416 + if let Some(ref value) = self.pbe_precut { 417 + if *value > 24i64 { 418 + return Err(ConstraintError::Maximum { 419 + path: ValidationPath::from_field("pbe_precut"), 420 + max: 24i64, 421 + actual: *value, 422 + }); 423 + } 424 + } 425 + if let Some(ref value) = self.pbe_precut { 426 + if *value < 0i64 { 427 + return Err(ConstraintError::Minimum { 428 + path: ValidationPath::from_field("pbe_precut"), 429 + min: 0i64, 430 + actual: *value, 431 + }); 432 + } 433 + } 434 + if let Some(ref value) = self.stereo_width { 435 + if *value > 255i64 { 436 + return Err(ConstraintError::Maximum { 437 + path: ValidationPath::from_field("stereo_width"), 438 + max: 255i64, 439 + actual: *value, 440 + }); 441 + } 442 + } 443 + if let Some(ref value) = self.stereo_width { 444 + if *value < 0i64 { 445 + return Err(ConstraintError::Minimum { 446 + path: ValidationPath::from_field("stereo_width"), 447 + min: 0i64, 448 + actual: *value, 449 + }); 450 + } 451 + } 452 + if let Some(ref value) = self.surround_balance { 453 + if *value > 100i64 { 454 + return Err(ConstraintError::Maximum { 455 + path: ValidationPath::from_field("surround_balance"), 456 + max: 100i64, 457 + actual: *value, 458 + }); 459 + } 460 + } 461 + if let Some(ref value) = self.surround_balance { 462 + if *value < 0i64 { 463 + return Err(ConstraintError::Minimum { 464 + path: ValidationPath::from_field("surround_balance"), 465 + min: 0i64, 466 + actual: *value, 467 + }); 468 + } 469 + } 470 + if let Some(ref value) = self.surround_delay { 471 + if *value > 30i64 { 472 + return Err(ConstraintError::Maximum { 473 + path: ValidationPath::from_field("surround_delay"), 474 + max: 30i64, 475 + actual: *value, 476 + }); 477 + } 478 + } 479 + if let Some(ref value) = self.surround_delay { 480 + if *value < 0i64 { 481 + return Err(ConstraintError::Minimum { 482 + path: ValidationPath::from_field("surround_delay"), 483 + min: 0i64, 484 + actual: *value, 485 + }); 486 + } 487 + } 488 + if let Some(ref value) = self.treble { 489 + if *value > 24i64 { 490 + return Err(ConstraintError::Maximum { 491 + path: ValidationPath::from_field("treble"), 492 + max: 24i64, 493 + actual: *value, 494 + }); 495 + } 496 + } 497 + if let Some(ref value) = self.treble { 498 + if *value < -24i64 { 499 + return Err(ConstraintError::Minimum { 500 + path: ValidationPath::from_field("treble"), 501 + min: -24i64, 502 + actual: *value, 503 + }); 504 + } 505 + } 506 + Ok(()) 507 + } 508 + } 509 + 510 + pub mod settings_state { 511 + 512 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 513 + #[allow(unused)] 514 + use ::core::marker::PhantomData; 515 + mod sealed { 516 + pub trait Sealed {} 517 + } 518 + /// State trait tracking which required fields have been set 519 + pub trait State: sealed::Sealed { 520 + type UpdatedAt; 521 + } 522 + /// Empty state - all required fields are unset 523 + pub struct Empty(()); 524 + impl sealed::Sealed for Empty {} 525 + impl State for Empty { 526 + type UpdatedAt = Unset; 527 + } 528 + ///State transition - sets the `updated_at` field to Set 529 + pub struct SetUpdatedAt<St: State = Empty>(PhantomData<fn() -> St>); 530 + impl<St: State> sealed::Sealed for SetUpdatedAt<St> {} 531 + impl<St: State> State for SetUpdatedAt<St> { 532 + type UpdatedAt = Set<members::updated_at>; 533 + } 534 + /// Marker types for field names 535 + #[allow(non_camel_case_types)] 536 + pub mod members { 537 + ///Marker type for the `updated_at` field 538 + pub struct updated_at(()); 539 + } 540 + } 541 + 542 + /// Builder for constructing an instance of this type. 543 + pub struct SettingsBuilder<St: settings_state::State, S: BosStr = DefaultStr> { 544 + _state: PhantomData<fn() -> St>, 545 + _fields: ( 546 + Option<i64>, 547 + Option<SettingsChannelMode<S>>, 548 + Option<i64>, 549 + Option<i64>, 550 + Option<i64>, 551 + Option<SettingsCrossfeedMode<S>>, 552 + Option<bool>, 553 + Option<Vec<i64>>, 554 + Option<i64>, 555 + Option<i64>, 556 + Option<i64>, 557 + Option<i64>, 558 + Option<i64>, 559 + Option<i64>, 560 + Option<Datetime>, 561 + ), 562 + _type: PhantomData<fn() -> S>, 563 + } 564 + 565 + impl Settings<DefaultStr> { 566 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 567 + pub fn new() -> SettingsBuilder<settings_state::Empty, DefaultStr> { 568 + SettingsBuilder::new() 569 + } 570 + } 571 + 572 + impl<S: BosStr> Settings<S> { 573 + /// Create a new builder for this type 574 + pub fn builder() -> SettingsBuilder<settings_state::Empty, S> { 575 + SettingsBuilder::builder() 576 + } 577 + } 578 + 579 + impl SettingsBuilder<settings_state::Empty, DefaultStr> { 580 + /// Create a new builder with all fields unset, using the default string type, if needed 581 + pub fn new() -> Self { 582 + SettingsBuilder { 583 + _state: PhantomData, 584 + _fields: ( 585 + None, 586 + None, 587 + None, 588 + None, 589 + None, 590 + None, 591 + None, 592 + None, 593 + None, 594 + None, 595 + None, 596 + None, 597 + None, 598 + None, 599 + None, 600 + ), 601 + _type: PhantomData, 602 + } 603 + } 604 + } 605 + 606 + impl<S: BosStr> SettingsBuilder<settings_state::Empty, S> { 607 + /// Create a new builder with all fields unset 608 + pub fn builder() -> Self { 609 + SettingsBuilder { 610 + _state: PhantomData, 611 + _fields: ( 612 + None, 613 + None, 614 + None, 615 + None, 616 + None, 617 + None, 618 + None, 619 + None, 620 + None, 621 + None, 622 + None, 623 + None, 624 + None, 625 + None, 626 + None, 627 + ), 628 + _type: PhantomData, 629 + } 630 + } 631 + } 632 + 633 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 634 + /// Set the `bass` field (optional) 635 + pub fn bass(mut self, value: impl Into<Option<i64>>) -> Self { 636 + self._fields.0 = value.into(); 637 + self 638 + } 639 + /// Set the `bass` field to an Option value (optional) 640 + pub fn maybe_bass(mut self, value: Option<i64>) -> Self { 641 + self._fields.0 = value; 642 + self 643 + } 644 + } 645 + 646 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 647 + /// Set the `channelMode` field (optional) 648 + pub fn channel_mode( 649 + mut self, 650 + value: impl Into<Option<SettingsChannelMode<S>>>, 651 + ) -> Self { 652 + self._fields.1 = value.into(); 653 + self 654 + } 655 + /// Set the `channelMode` field to an Option value (optional) 656 + pub fn maybe_channel_mode(mut self, value: Option<SettingsChannelMode<S>>) -> Self { 657 + self._fields.1 = value; 658 + self 659 + } 660 + } 661 + 662 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 663 + /// Set the `compRatio` field (optional) 664 + pub fn comp_ratio(mut self, value: impl Into<Option<i64>>) -> Self { 665 + self._fields.2 = value.into(); 666 + self 667 + } 668 + /// Set the `compRatio` field to an Option value (optional) 669 + pub fn maybe_comp_ratio(mut self, value: Option<i64>) -> Self { 670 + self._fields.2 = value; 671 + self 672 + } 673 + } 674 + 675 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 676 + /// Set the `compThreshold` field (optional) 677 + pub fn comp_threshold(mut self, value: impl Into<Option<i64>>) -> Self { 678 + self._fields.3 = value.into(); 679 + self 680 + } 681 + /// Set the `compThreshold` field to an Option value (optional) 682 + pub fn maybe_comp_threshold(mut self, value: Option<i64>) -> Self { 683 + self._fields.3 = value; 684 + self 685 + } 686 + } 687 + 688 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 689 + /// Set the `crossfeedDirect` field (optional) 690 + pub fn crossfeed_direct(mut self, value: impl Into<Option<i64>>) -> Self { 691 + self._fields.4 = value.into(); 692 + self 693 + } 694 + /// Set the `crossfeedDirect` field to an Option value (optional) 695 + pub fn maybe_crossfeed_direct(mut self, value: Option<i64>) -> Self { 696 + self._fields.4 = value; 697 + self 698 + } 699 + } 700 + 701 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 702 + /// Set the `crossfeedMode` field (optional) 703 + pub fn crossfeed_mode( 704 + mut self, 705 + value: impl Into<Option<SettingsCrossfeedMode<S>>>, 706 + ) -> Self { 707 + self._fields.5 = value.into(); 708 + self 709 + } 710 + /// Set the `crossfeedMode` field to an Option value (optional) 711 + pub fn maybe_crossfeed_mode( 712 + mut self, 713 + value: Option<SettingsCrossfeedMode<S>>, 714 + ) -> Self { 715 + self._fields.5 = value; 716 + self 717 + } 718 + } 719 + 720 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 721 + /// Set the `eqEnabled` field (optional) 722 + pub fn eq_enabled(mut self, value: impl Into<Option<bool>>) -> Self { 723 + self._fields.6 = value.into(); 724 + self 725 + } 726 + /// Set the `eqEnabled` field to an Option value (optional) 727 + pub fn maybe_eq_enabled(mut self, value: Option<bool>) -> Self { 728 + self._fields.6 = value; 729 + self 730 + } 731 + } 732 + 733 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 734 + /// Set the `eqGains` field (optional) 735 + pub fn eq_gains(mut self, value: impl Into<Option<Vec<i64>>>) -> Self { 736 + self._fields.7 = value.into(); 737 + self 738 + } 739 + /// Set the `eqGains` field to an Option value (optional) 740 + pub fn maybe_eq_gains(mut self, value: Option<Vec<i64>>) -> Self { 741 + self._fields.7 = value; 742 + self 743 + } 744 + } 745 + 746 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 747 + /// Set the `pbe` field (optional) 748 + pub fn pbe(mut self, value: impl Into<Option<i64>>) -> Self { 749 + self._fields.8 = value.into(); 750 + self 751 + } 752 + /// Set the `pbe` field to an Option value (optional) 753 + pub fn maybe_pbe(mut self, value: Option<i64>) -> Self { 754 + self._fields.8 = value; 755 + self 756 + } 757 + } 758 + 759 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 760 + /// Set the `pbePrecut` field (optional) 761 + pub fn pbe_precut(mut self, value: impl Into<Option<i64>>) -> Self { 762 + self._fields.9 = value.into(); 763 + self 764 + } 765 + /// Set the `pbePrecut` field to an Option value (optional) 766 + pub fn maybe_pbe_precut(mut self, value: Option<i64>) -> Self { 767 + self._fields.9 = value; 768 + self 769 + } 770 + } 771 + 772 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 773 + /// Set the `stereoWidth` field (optional) 774 + pub fn stereo_width(mut self, value: impl Into<Option<i64>>) -> Self { 775 + self._fields.10 = value.into(); 776 + self 777 + } 778 + /// Set the `stereoWidth` field to an Option value (optional) 779 + pub fn maybe_stereo_width(mut self, value: Option<i64>) -> Self { 780 + self._fields.10 = value; 781 + self 782 + } 783 + } 784 + 785 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 786 + /// Set the `surroundBalance` field (optional) 787 + pub fn surround_balance(mut self, value: impl Into<Option<i64>>) -> Self { 788 + self._fields.11 = value.into(); 789 + self 790 + } 791 + /// Set the `surroundBalance` field to an Option value (optional) 792 + pub fn maybe_surround_balance(mut self, value: Option<i64>) -> Self { 793 + self._fields.11 = value; 794 + self 795 + } 796 + } 797 + 798 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 799 + /// Set the `surroundDelay` field (optional) 800 + pub fn surround_delay(mut self, value: impl Into<Option<i64>>) -> Self { 801 + self._fields.12 = value.into(); 802 + self 803 + } 804 + /// Set the `surroundDelay` field to an Option value (optional) 805 + pub fn maybe_surround_delay(mut self, value: Option<i64>) -> Self { 806 + self._fields.12 = value; 807 + self 808 + } 809 + } 810 + 811 + impl<St: settings_state::State, S: BosStr> SettingsBuilder<St, S> { 812 + /// Set the `treble` field (optional) 813 + pub fn treble(mut self, value: impl Into<Option<i64>>) -> Self { 814 + self._fields.13 = value.into(); 815 + self 816 + } 817 + /// Set the `treble` field to an Option value (optional) 818 + pub fn maybe_treble(mut self, value: Option<i64>) -> Self { 819 + self._fields.13 = value; 820 + self 821 + } 822 + } 823 + 824 + impl<St, S: BosStr> SettingsBuilder<St, S> 825 + where 826 + St: settings_state::State, 827 + St::UpdatedAt: settings_state::IsUnset, 828 + { 829 + /// Set the `updatedAt` field (required) 830 + pub fn updated_at( 831 + mut self, 832 + value: impl Into<Datetime>, 833 + ) -> SettingsBuilder<settings_state::SetUpdatedAt<St>, S> { 834 + self._fields.14 = Option::Some(value.into()); 835 + SettingsBuilder { 836 + _state: PhantomData, 837 + _fields: self._fields, 838 + _type: PhantomData, 839 + } 840 + } 841 + } 842 + 843 + impl<St, S: BosStr> SettingsBuilder<St, S> 844 + where 845 + St: settings_state::State, 846 + St::UpdatedAt: settings_state::IsSet, 847 + { 848 + /// Build the final struct. 849 + pub fn build(self) -> Settings<S> { 850 + Settings { 851 + bass: self._fields.0, 852 + channel_mode: self._fields.1, 853 + comp_ratio: self._fields.2, 854 + comp_threshold: self._fields.3, 855 + crossfeed_direct: self._fields.4, 856 + crossfeed_mode: self._fields.5, 857 + eq_enabled: self._fields.6, 858 + eq_gains: self._fields.7, 859 + pbe: self._fields.8, 860 + pbe_precut: self._fields.9, 861 + stereo_width: self._fields.10, 862 + surround_balance: self._fields.11, 863 + surround_delay: self._fields.12, 864 + treble: self._fields.13, 865 + updated_at: self._fields.14.unwrap(), 866 + extra_data: Default::default(), 867 + } 868 + } 869 + /// Build the final struct with custom extra_data. 870 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Settings<S> { 871 + Settings { 872 + bass: self._fields.0, 873 + channel_mode: self._fields.1, 874 + comp_ratio: self._fields.2, 875 + comp_threshold: self._fields.3, 876 + crossfeed_direct: self._fields.4, 877 + crossfeed_mode: self._fields.5, 878 + eq_enabled: self._fields.6, 879 + eq_gains: self._fields.7, 880 + pbe: self._fields.8, 881 + pbe_precut: self._fields.9, 882 + stereo_width: self._fields.10, 883 + surround_balance: self._fields.11, 884 + surround_delay: self._fields.12, 885 + treble: self._fields.13, 886 + updated_at: self._fields.14.unwrap(), 887 + extra_data: Some(extra_data), 888 + } 889 + } 890 + } 891 + 892 + fn lexicon_doc_fm_atradio_audio_settings() -> LexiconDoc<'static> { 893 + #[allow(unused_imports)] 894 + use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 895 + use jacquard_lexicon::lexicon::*; 896 + use alloc::collections::BTreeMap; 897 + LexiconDoc { 898 + lexicon: Lexicon::Lexicon1, 899 + id: CowStr::new_static("fm.atradio.audio.settings"), 900 + defs: { 901 + let mut map = BTreeMap::new(); 902 + map.insert( 903 + SmolStr::new_static("main"), 904 + LexUserType::Record(LexRecord { 905 + description: Some( 906 + CowStr::new_static( 907 + "The actor's audio playback settings (10-band EQ + Rockbox DSP chain), a singleton record synced across devices. Gains are integers; the crossfeed direct gain is in tenths of dB, matching the Rockbox convention.", 908 + ), 909 + ), 910 + key: Some(CowStr::new_static("literal:self")), 911 + record: LexRecordRecord::Object(LexObject { 912 + required: Some(vec![SmolStr::new_static("updatedAt")]), 913 + properties: { 914 + #[allow(unused_mut)] 915 + let mut map = BTreeMap::new(); 916 + map.insert( 917 + SmolStr::new_static("bass"), 918 + LexObjectProperty::Integer(LexInteger { 919 + minimum: Some(-24i64), 920 + maximum: Some(24i64), 921 + ..Default::default() 922 + }), 923 + ); 924 + map.insert( 925 + SmolStr::new_static("channelMode"), 926 + LexObjectProperty::String(LexString { 927 + description: Some( 928 + CowStr::new_static("Channel mixing mode."), 929 + ), 930 + ..Default::default() 931 + }), 932 + ); 933 + map.insert( 934 + SmolStr::new_static("compRatio"), 935 + LexObjectProperty::Integer(LexInteger { 936 + minimum: Some(2i64), 937 + maximum: Some(10i64), 938 + ..Default::default() 939 + }), 940 + ); 941 + map.insert( 942 + SmolStr::new_static("compThreshold"), 943 + LexObjectProperty::Integer(LexInteger { 944 + minimum: Some(-30i64), 945 + maximum: Some(0i64), 946 + ..Default::default() 947 + }), 948 + ); 949 + map.insert( 950 + SmolStr::new_static("crossfeedDirect"), 951 + LexObjectProperty::Integer(LexInteger { 952 + minimum: Some(-60i64), 953 + maximum: Some(0i64), 954 + ..Default::default() 955 + }), 956 + ); 957 + map.insert( 958 + SmolStr::new_static("crossfeedMode"), 959 + LexObjectProperty::String(LexString { 960 + description: Some( 961 + CowStr::new_static("Headphone crossfeed mode."), 962 + ), 963 + ..Default::default() 964 + }), 965 + ); 966 + map.insert( 967 + SmolStr::new_static("eqEnabled"), 968 + LexObjectProperty::Boolean(LexBoolean { 969 + ..Default::default() 970 + }), 971 + ); 972 + map.insert( 973 + SmolStr::new_static("eqGains"), 974 + LexObjectProperty::Array(LexArray { 975 + description: Some( 976 + CowStr::new_static( 977 + "Per-band EQ gain in dB, one entry per band (60 Hz - 20 kHz).", 978 + ), 979 + ), 980 + items: LexArrayItem::Integer(LexInteger { 981 + ..Default::default() 982 + }), 983 + ..Default::default() 984 + }), 985 + ); 986 + map.insert( 987 + SmolStr::new_static("pbe"), 988 + LexObjectProperty::Integer(LexInteger { 989 + minimum: Some(0i64), 990 + maximum: Some(100i64), 991 + ..Default::default() 992 + }), 993 + ); 994 + map.insert( 995 + SmolStr::new_static("pbePrecut"), 996 + LexObjectProperty::Integer(LexInteger { 997 + minimum: Some(0i64), 998 + maximum: Some(24i64), 999 + ..Default::default() 1000 + }), 1001 + ); 1002 + map.insert( 1003 + SmolStr::new_static("stereoWidth"), 1004 + LexObjectProperty::Integer(LexInteger { 1005 + minimum: Some(0i64), 1006 + maximum: Some(255i64), 1007 + ..Default::default() 1008 + }), 1009 + ); 1010 + map.insert( 1011 + SmolStr::new_static("surroundBalance"), 1012 + LexObjectProperty::Integer(LexInteger { 1013 + minimum: Some(0i64), 1014 + maximum: Some(100i64), 1015 + ..Default::default() 1016 + }), 1017 + ); 1018 + map.insert( 1019 + SmolStr::new_static("surroundDelay"), 1020 + LexObjectProperty::Integer(LexInteger { 1021 + minimum: Some(0i64), 1022 + maximum: Some(30i64), 1023 + ..Default::default() 1024 + }), 1025 + ); 1026 + map.insert( 1027 + SmolStr::new_static("treble"), 1028 + LexObjectProperty::Integer(LexInteger { 1029 + minimum: Some(-24i64), 1030 + maximum: Some(24i64), 1031 + ..Default::default() 1032 + }), 1033 + ); 1034 + map.insert( 1035 + SmolStr::new_static("updatedAt"), 1036 + LexObjectProperty::String(LexString { 1037 + format: Some(LexStringFormat::Datetime), 1038 + ..Default::default() 1039 + }), 1040 + ); 1041 + map 1042 + }, 1043 + ..Default::default() 1044 + }), 1045 + ..Default::default() 1046 + }), 1047 + ); 1048 + map 1049 + }, 1050 + ..Default::default() 1051 + } 1052 + }
+1023
cli/src/fm_atradio/comment.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.comment 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + 15 + #[allow(unused_imports)] 16 + use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 17 + use jacquard_common::deps::smol_str::SmolStr; 18 + use jacquard_common::types::collection::{Collection, RecordError}; 19 + use jacquard_common::types::string::{Did, AtUri, Cid, Datetime, UriValue}; 20 + use jacquard_common::types::uri::{RecordUri, UriError}; 21 + use jacquard_common::types::value::Data; 22 + use jacquard_common::xrpc::XrpcResp; 23 + use jacquard_derive::{IntoStatic, lexicon}; 24 + use jacquard_lexicon::lexicon::LexiconDoc; 25 + use jacquard_lexicon::schema::LexiconSchema; 26 + 27 + #[allow(unused_imports)] 28 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 29 + use serde::{Serialize, Deserialize}; 30 + use crate::fm_atradio::StationInfo; 31 + use crate::fm_atradio::comment; 32 + /// An animated GIF embedded in a comment (e.g. from Giphy). 33 + 34 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 35 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 36 + pub struct Gif<S: BosStr = DefaultStr> { 37 + #[serde(skip_serializing_if = "Option::is_none")] 38 + pub alt: Option<S>, 39 + #[serde(skip_serializing_if = "Option::is_none")] 40 + pub height: Option<i64>, 41 + ///Smaller still/preview image URL. 42 + #[serde(skip_serializing_if = "Option::is_none")] 43 + pub preview_url: Option<UriValue<S>>, 44 + ///Direct URL of the animated GIF/MP4. 45 + pub url: UriValue<S>, 46 + #[serde(skip_serializing_if = "Option::is_none")] 47 + pub width: Option<i64>, 48 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 49 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 50 + } 51 + 52 + /// A user's comment on a radio station. 53 + 54 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 55 + #[serde( 56 + rename_all = "camelCase", 57 + rename = "fm.atradio.comment", 58 + tag = "$type", 59 + bound(deserialize = "S: Deserialize<'de> + BosStr") 60 + )] 61 + pub struct Comment<S: BosStr = DefaultStr> { 62 + pub created_at: Datetime, 63 + ///Mentions of other actors, anchored to byte ranges in `text`. 64 + #[serde(skip_serializing_if = "Option::is_none")] 65 + pub facets: Option<Vec<comment::Mention<S>>>, 66 + #[serde(skip_serializing_if = "Option::is_none")] 67 + pub gif: Option<comment::Gif<S>>, 68 + pub station: StationInfo<S>, 69 + ///The comment body (plain text). 70 + pub text: S, 71 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 72 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 73 + } 74 + 75 + /// Typed wrapper for GetRecord response with this collection's record type. 76 + 77 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 78 + #[serde(rename_all = "camelCase")] 79 + pub struct CommentGetRecordOutput<S: BosStr = DefaultStr> { 80 + #[serde(skip_serializing_if = "Option::is_none")] 81 + pub cid: Option<Cid<S>>, 82 + pub uri: AtUri<S>, 83 + pub value: Comment<S>, 84 + } 85 + 86 + /// A mention of another actor within the comment text. 87 + 88 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 89 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 90 + pub struct Mention<S: BosStr = DefaultStr> { 91 + ///Exclusive UTF-8 byte offset of the mention end. 92 + pub byte_end: i64, 93 + ///Inclusive UTF-8 byte offset of the mention start. 94 + pub byte_start: i64, 95 + pub did: Did<S>, 96 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 97 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 98 + } 99 + 100 + impl<S: BosStr> Comment<S> { 101 + pub fn uri(uri: S) -> Result<RecordUri<S, CommentRecord>, UriError> { 102 + RecordUri::try_from_uri(AtUri::new(uri)?) 103 + } 104 + } 105 + 106 + impl<S: BosStr> LexiconSchema for Gif<S> { 107 + fn nsid() -> &'static str { 108 + "fm.atradio.comment" 109 + } 110 + fn def_name() -> &'static str { 111 + "gif" 112 + } 113 + fn lexicon_doc() -> LexiconDoc<'static> { 114 + lexicon_doc_fm_atradio_comment() 115 + } 116 + fn validate(&self) -> Result<(), ConstraintError> { 117 + if let Some(ref value) = self.alt { 118 + #[allow(unused_comparisons)] 119 + if <str>::len(value.as_ref()) > 512usize { 120 + return Err(ConstraintError::MaxLength { 121 + path: ValidationPath::from_field("alt"), 122 + max: 512usize, 123 + actual: <str>::len(value.as_ref()), 124 + }); 125 + } 126 + } 127 + if let Some(ref value) = self.height { 128 + if *value < 0i64 { 129 + return Err(ConstraintError::Minimum { 130 + path: ValidationPath::from_field("height"), 131 + min: 0i64, 132 + actual: *value, 133 + }); 134 + } 135 + } 136 + if let Some(ref value) = self.width { 137 + if *value < 0i64 { 138 + return Err(ConstraintError::Minimum { 139 + path: ValidationPath::from_field("width"), 140 + min: 0i64, 141 + actual: *value, 142 + }); 143 + } 144 + } 145 + Ok(()) 146 + } 147 + } 148 + 149 + /// Marker type for deserializing records from this collection. 150 + 151 + #[derive(Debug, Serialize, Deserialize)] 152 + pub struct CommentRecord; 153 + impl XrpcResp for CommentRecord { 154 + const NSID: &'static str = "fm.atradio.comment"; 155 + const ENCODING: &'static str = "application/json"; 156 + type Output<S: BosStr> = CommentGetRecordOutput<S>; 157 + type Err = RecordError; 158 + } 159 + 160 + impl<S: BosStr> From<CommentGetRecordOutput<S>> for Comment<S> { 161 + fn from(output: CommentGetRecordOutput<S>) -> Self { 162 + output.value 163 + } 164 + } 165 + 166 + impl<S: BosStr> Collection for Comment<S> { 167 + const NSID: &'static str = "fm.atradio.comment"; 168 + type Record = CommentRecord; 169 + } 170 + 171 + impl Collection for CommentRecord { 172 + const NSID: &'static str = "fm.atradio.comment"; 173 + type Record = CommentRecord; 174 + } 175 + 176 + impl<S: BosStr> LexiconSchema for Comment<S> { 177 + fn nsid() -> &'static str { 178 + "fm.atradio.comment" 179 + } 180 + fn def_name() -> &'static str { 181 + "main" 182 + } 183 + fn lexicon_doc() -> LexiconDoc<'static> { 184 + lexicon_doc_fm_atradio_comment() 185 + } 186 + fn validate(&self) -> Result<(), ConstraintError> { 187 + { 188 + let value = &self.text; 189 + #[allow(unused_comparisons)] 190 + if <str>::len(value.as_ref()) > 3000usize { 191 + return Err(ConstraintError::MaxLength { 192 + path: ValidationPath::from_field("text"), 193 + max: 3000usize, 194 + actual: <str>::len(value.as_ref()), 195 + }); 196 + } 197 + } 198 + { 199 + let value = &self.text; 200 + { 201 + let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count(); 202 + if count > 300usize { 203 + return Err(ConstraintError::MaxGraphemes { 204 + path: ValidationPath::from_field("text"), 205 + max: 300usize, 206 + actual: count, 207 + }); 208 + } 209 + } 210 + } 211 + Ok(()) 212 + } 213 + } 214 + 215 + impl<S: BosStr> LexiconSchema for Mention<S> { 216 + fn nsid() -> &'static str { 217 + "fm.atradio.comment" 218 + } 219 + fn def_name() -> &'static str { 220 + "mention" 221 + } 222 + fn lexicon_doc() -> LexiconDoc<'static> { 223 + lexicon_doc_fm_atradio_comment() 224 + } 225 + fn validate(&self) -> Result<(), ConstraintError> { 226 + { 227 + let value = &self.byte_end; 228 + if *value < 0i64 { 229 + return Err(ConstraintError::Minimum { 230 + path: ValidationPath::from_field("byte_end"), 231 + min: 0i64, 232 + actual: *value, 233 + }); 234 + } 235 + } 236 + { 237 + let value = &self.byte_start; 238 + if *value < 0i64 { 239 + return Err(ConstraintError::Minimum { 240 + path: ValidationPath::from_field("byte_start"), 241 + min: 0i64, 242 + actual: *value, 243 + }); 244 + } 245 + } 246 + Ok(()) 247 + } 248 + } 249 + 250 + pub mod gif_state { 251 + 252 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 253 + #[allow(unused)] 254 + use ::core::marker::PhantomData; 255 + mod sealed { 256 + pub trait Sealed {} 257 + } 258 + /// State trait tracking which required fields have been set 259 + pub trait State: sealed::Sealed { 260 + type Url; 261 + } 262 + /// Empty state - all required fields are unset 263 + pub struct Empty(()); 264 + impl sealed::Sealed for Empty {} 265 + impl State for Empty { 266 + type Url = Unset; 267 + } 268 + ///State transition - sets the `url` field to Set 269 + pub struct SetUrl<St: State = Empty>(PhantomData<fn() -> St>); 270 + impl<St: State> sealed::Sealed for SetUrl<St> {} 271 + impl<St: State> State for SetUrl<St> { 272 + type Url = Set<members::url>; 273 + } 274 + /// Marker types for field names 275 + #[allow(non_camel_case_types)] 276 + pub mod members { 277 + ///Marker type for the `url` field 278 + pub struct url(()); 279 + } 280 + } 281 + 282 + /// Builder for constructing an instance of this type. 283 + pub struct GifBuilder<St: gif_state::State, S: BosStr = DefaultStr> { 284 + _state: PhantomData<fn() -> St>, 285 + _fields: ( 286 + Option<S>, 287 + Option<i64>, 288 + Option<UriValue<S>>, 289 + Option<UriValue<S>>, 290 + Option<i64>, 291 + ), 292 + _type: PhantomData<fn() -> S>, 293 + } 294 + 295 + impl Gif<DefaultStr> { 296 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 297 + pub fn new() -> GifBuilder<gif_state::Empty, DefaultStr> { 298 + GifBuilder::new() 299 + } 300 + } 301 + 302 + impl<S: BosStr> Gif<S> { 303 + /// Create a new builder for this type 304 + pub fn builder() -> GifBuilder<gif_state::Empty, S> { 305 + GifBuilder::builder() 306 + } 307 + } 308 + 309 + impl GifBuilder<gif_state::Empty, DefaultStr> { 310 + /// Create a new builder with all fields unset, using the default string type, if needed 311 + pub fn new() -> Self { 312 + GifBuilder { 313 + _state: PhantomData, 314 + _fields: (None, None, None, None, None), 315 + _type: PhantomData, 316 + } 317 + } 318 + } 319 + 320 + impl<S: BosStr> GifBuilder<gif_state::Empty, S> { 321 + /// Create a new builder with all fields unset 322 + pub fn builder() -> Self { 323 + GifBuilder { 324 + _state: PhantomData, 325 + _fields: (None, None, None, None, None), 326 + _type: PhantomData, 327 + } 328 + } 329 + } 330 + 331 + impl<St: gif_state::State, S: BosStr> GifBuilder<St, S> { 332 + /// Set the `alt` field (optional) 333 + pub fn alt(mut self, value: impl Into<Option<S>>) -> Self { 334 + self._fields.0 = value.into(); 335 + self 336 + } 337 + /// Set the `alt` field to an Option value (optional) 338 + pub fn maybe_alt(mut self, value: Option<S>) -> Self { 339 + self._fields.0 = value; 340 + self 341 + } 342 + } 343 + 344 + impl<St: gif_state::State, S: BosStr> GifBuilder<St, S> { 345 + /// Set the `height` field (optional) 346 + pub fn height(mut self, value: impl Into<Option<i64>>) -> Self { 347 + self._fields.1 = value.into(); 348 + self 349 + } 350 + /// Set the `height` field to an Option value (optional) 351 + pub fn maybe_height(mut self, value: Option<i64>) -> Self { 352 + self._fields.1 = value; 353 + self 354 + } 355 + } 356 + 357 + impl<St: gif_state::State, S: BosStr> GifBuilder<St, S> { 358 + /// Set the `previewUrl` field (optional) 359 + pub fn preview_url(mut self, value: impl Into<Option<UriValue<S>>>) -> Self { 360 + self._fields.2 = value.into(); 361 + self 362 + } 363 + /// Set the `previewUrl` field to an Option value (optional) 364 + pub fn maybe_preview_url(mut self, value: Option<UriValue<S>>) -> Self { 365 + self._fields.2 = value; 366 + self 367 + } 368 + } 369 + 370 + impl<St, S: BosStr> GifBuilder<St, S> 371 + where 372 + St: gif_state::State, 373 + St::Url: gif_state::IsUnset, 374 + { 375 + /// Set the `url` field (required) 376 + pub fn url( 377 + mut self, 378 + value: impl Into<UriValue<S>>, 379 + ) -> GifBuilder<gif_state::SetUrl<St>, S> { 380 + self._fields.3 = Option::Some(value.into()); 381 + GifBuilder { 382 + _state: PhantomData, 383 + _fields: self._fields, 384 + _type: PhantomData, 385 + } 386 + } 387 + } 388 + 389 + impl<St: gif_state::State, S: BosStr> GifBuilder<St, S> { 390 + /// Set the `width` field (optional) 391 + pub fn width(mut self, value: impl Into<Option<i64>>) -> Self { 392 + self._fields.4 = value.into(); 393 + self 394 + } 395 + /// Set the `width` field to an Option value (optional) 396 + pub fn maybe_width(mut self, value: Option<i64>) -> Self { 397 + self._fields.4 = value; 398 + self 399 + } 400 + } 401 + 402 + impl<St, S: BosStr> GifBuilder<St, S> 403 + where 404 + St: gif_state::State, 405 + St::Url: gif_state::IsSet, 406 + { 407 + /// Build the final struct. 408 + pub fn build(self) -> Gif<S> { 409 + Gif { 410 + alt: self._fields.0, 411 + height: self._fields.1, 412 + preview_url: self._fields.2, 413 + url: self._fields.3.unwrap(), 414 + width: self._fields.4, 415 + extra_data: Default::default(), 416 + } 417 + } 418 + /// Build the final struct with custom extra_data. 419 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Gif<S> { 420 + Gif { 421 + alt: self._fields.0, 422 + height: self._fields.1, 423 + preview_url: self._fields.2, 424 + url: self._fields.3.unwrap(), 425 + width: self._fields.4, 426 + extra_data: Some(extra_data), 427 + } 428 + } 429 + } 430 + 431 + fn lexicon_doc_fm_atradio_comment() -> LexiconDoc<'static> { 432 + #[allow(unused_imports)] 433 + use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 434 + use jacquard_lexicon::lexicon::*; 435 + use alloc::collections::BTreeMap; 436 + LexiconDoc { 437 + lexicon: Lexicon::Lexicon1, 438 + id: CowStr::new_static("fm.atradio.comment"), 439 + defs: { 440 + let mut map = BTreeMap::new(); 441 + map.insert( 442 + SmolStr::new_static("gif"), 443 + LexUserType::Object(LexObject { 444 + description: Some( 445 + CowStr::new_static( 446 + "An animated GIF embedded in a comment (e.g. from Giphy).", 447 + ), 448 + ), 449 + required: Some(vec![SmolStr::new_static("url")]), 450 + properties: { 451 + #[allow(unused_mut)] 452 + let mut map = BTreeMap::new(); 453 + map.insert( 454 + SmolStr::new_static("alt"), 455 + LexObjectProperty::String(LexString { 456 + max_length: Some(512usize), 457 + ..Default::default() 458 + }), 459 + ); 460 + map.insert( 461 + SmolStr::new_static("height"), 462 + LexObjectProperty::Integer(LexInteger { 463 + minimum: Some(0i64), 464 + ..Default::default() 465 + }), 466 + ); 467 + map.insert( 468 + SmolStr::new_static("previewUrl"), 469 + LexObjectProperty::String(LexString { 470 + description: Some( 471 + CowStr::new_static("Smaller still/preview image URL."), 472 + ), 473 + format: Some(LexStringFormat::Uri), 474 + ..Default::default() 475 + }), 476 + ); 477 + map.insert( 478 + SmolStr::new_static("url"), 479 + LexObjectProperty::String(LexString { 480 + description: Some( 481 + CowStr::new_static("Direct URL of the animated GIF/MP4."), 482 + ), 483 + format: Some(LexStringFormat::Uri), 484 + ..Default::default() 485 + }), 486 + ); 487 + map.insert( 488 + SmolStr::new_static("width"), 489 + LexObjectProperty::Integer(LexInteger { 490 + minimum: Some(0i64), 491 + ..Default::default() 492 + }), 493 + ); 494 + map 495 + }, 496 + ..Default::default() 497 + }), 498 + ); 499 + map.insert( 500 + SmolStr::new_static("main"), 501 + LexUserType::Record(LexRecord { 502 + description: Some( 503 + CowStr::new_static("A user's comment on a radio station."), 504 + ), 505 + key: Some(CowStr::new_static("tid")), 506 + record: LexRecordRecord::Object(LexObject { 507 + required: Some( 508 + vec![ 509 + SmolStr::new_static("station"), SmolStr::new_static("text"), 510 + SmolStr::new_static("createdAt") 511 + ], 512 + ), 513 + properties: { 514 + #[allow(unused_mut)] 515 + let mut map = BTreeMap::new(); 516 + map.insert( 517 + SmolStr::new_static("createdAt"), 518 + LexObjectProperty::String(LexString { 519 + format: Some(LexStringFormat::Datetime), 520 + ..Default::default() 521 + }), 522 + ); 523 + map.insert( 524 + SmolStr::new_static("facets"), 525 + LexObjectProperty::Array(LexArray { 526 + description: Some( 527 + CowStr::new_static( 528 + "Mentions of other actors, anchored to byte ranges in `text`.", 529 + ), 530 + ), 531 + items: LexArrayItem::Ref(LexRef { 532 + r#ref: CowStr::new_static("fm.atradio.comment#mention"), 533 + ..Default::default() 534 + }), 535 + ..Default::default() 536 + }), 537 + ); 538 + map.insert( 539 + SmolStr::new_static("gif"), 540 + LexObjectProperty::Ref(LexRef { 541 + r#ref: CowStr::new_static("fm.atradio.comment#gif"), 542 + ..Default::default() 543 + }), 544 + ); 545 + map.insert( 546 + SmolStr::new_static("station"), 547 + LexObjectProperty::Ref(LexRef { 548 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 549 + ..Default::default() 550 + }), 551 + ); 552 + map.insert( 553 + SmolStr::new_static("text"), 554 + LexObjectProperty::String(LexString { 555 + description: Some( 556 + CowStr::new_static("The comment body (plain text)."), 557 + ), 558 + max_length: Some(3000usize), 559 + max_graphemes: Some(300usize), 560 + ..Default::default() 561 + }), 562 + ); 563 + map 564 + }, 565 + ..Default::default() 566 + }), 567 + ..Default::default() 568 + }), 569 + ); 570 + map.insert( 571 + SmolStr::new_static("mention"), 572 + LexUserType::Object(LexObject { 573 + description: Some( 574 + CowStr::new_static( 575 + "A mention of another actor within the comment text.", 576 + ), 577 + ), 578 + required: Some( 579 + vec![ 580 + SmolStr::new_static("did"), SmolStr::new_static("byteStart"), 581 + SmolStr::new_static("byteEnd") 582 + ], 583 + ), 584 + properties: { 585 + #[allow(unused_mut)] 586 + let mut map = BTreeMap::new(); 587 + map.insert( 588 + SmolStr::new_static("byteEnd"), 589 + LexObjectProperty::Integer(LexInteger { 590 + minimum: Some(0i64), 591 + ..Default::default() 592 + }), 593 + ); 594 + map.insert( 595 + SmolStr::new_static("byteStart"), 596 + LexObjectProperty::Integer(LexInteger { 597 + minimum: Some(0i64), 598 + ..Default::default() 599 + }), 600 + ); 601 + map.insert( 602 + SmolStr::new_static("did"), 603 + LexObjectProperty::String(LexString { 604 + format: Some(LexStringFormat::Did), 605 + ..Default::default() 606 + }), 607 + ); 608 + map 609 + }, 610 + ..Default::default() 611 + }), 612 + ); 613 + map 614 + }, 615 + ..Default::default() 616 + } 617 + } 618 + 619 + pub mod comment_state { 620 + 621 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 622 + #[allow(unused)] 623 + use ::core::marker::PhantomData; 624 + mod sealed { 625 + pub trait Sealed {} 626 + } 627 + /// State trait tracking which required fields have been set 628 + pub trait State: sealed::Sealed { 629 + type CreatedAt; 630 + type Station; 631 + type Text; 632 + } 633 + /// Empty state - all required fields are unset 634 + pub struct Empty(()); 635 + impl sealed::Sealed for Empty {} 636 + impl State for Empty { 637 + type CreatedAt = Unset; 638 + type Station = Unset; 639 + type Text = Unset; 640 + } 641 + ///State transition - sets the `created_at` field to Set 642 + pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>); 643 + impl<St: State> sealed::Sealed for SetCreatedAt<St> {} 644 + impl<St: State> State for SetCreatedAt<St> { 645 + type CreatedAt = Set<members::created_at>; 646 + type Station = St::Station; 647 + type Text = St::Text; 648 + } 649 + ///State transition - sets the `station` field to Set 650 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 651 + impl<St: State> sealed::Sealed for SetStation<St> {} 652 + impl<St: State> State for SetStation<St> { 653 + type CreatedAt = St::CreatedAt; 654 + type Station = Set<members::station>; 655 + type Text = St::Text; 656 + } 657 + ///State transition - sets the `text` field to Set 658 + pub struct SetText<St: State = Empty>(PhantomData<fn() -> St>); 659 + impl<St: State> sealed::Sealed for SetText<St> {} 660 + impl<St: State> State for SetText<St> { 661 + type CreatedAt = St::CreatedAt; 662 + type Station = St::Station; 663 + type Text = Set<members::text>; 664 + } 665 + /// Marker types for field names 666 + #[allow(non_camel_case_types)] 667 + pub mod members { 668 + ///Marker type for the `created_at` field 669 + pub struct created_at(()); 670 + ///Marker type for the `station` field 671 + pub struct station(()); 672 + ///Marker type for the `text` field 673 + pub struct text(()); 674 + } 675 + } 676 + 677 + /// Builder for constructing an instance of this type. 678 + pub struct CommentBuilder<St: comment_state::State, S: BosStr = DefaultStr> { 679 + _state: PhantomData<fn() -> St>, 680 + _fields: ( 681 + Option<Datetime>, 682 + Option<Vec<comment::Mention<S>>>, 683 + Option<comment::Gif<S>>, 684 + Option<StationInfo<S>>, 685 + Option<S>, 686 + ), 687 + _type: PhantomData<fn() -> S>, 688 + } 689 + 690 + impl Comment<DefaultStr> { 691 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 692 + pub fn new() -> CommentBuilder<comment_state::Empty, DefaultStr> { 693 + CommentBuilder::new() 694 + } 695 + } 696 + 697 + impl<S: BosStr> Comment<S> { 698 + /// Create a new builder for this type 699 + pub fn builder() -> CommentBuilder<comment_state::Empty, S> { 700 + CommentBuilder::builder() 701 + } 702 + } 703 + 704 + impl CommentBuilder<comment_state::Empty, DefaultStr> { 705 + /// Create a new builder with all fields unset, using the default string type, if needed 706 + pub fn new() -> Self { 707 + CommentBuilder { 708 + _state: PhantomData, 709 + _fields: (None, None, None, None, None), 710 + _type: PhantomData, 711 + } 712 + } 713 + } 714 + 715 + impl<S: BosStr> CommentBuilder<comment_state::Empty, S> { 716 + /// Create a new builder with all fields unset 717 + pub fn builder() -> Self { 718 + CommentBuilder { 719 + _state: PhantomData, 720 + _fields: (None, None, None, None, None), 721 + _type: PhantomData, 722 + } 723 + } 724 + } 725 + 726 + impl<St, S: BosStr> CommentBuilder<St, S> 727 + where 728 + St: comment_state::State, 729 + St::CreatedAt: comment_state::IsUnset, 730 + { 731 + /// Set the `createdAt` field (required) 732 + pub fn created_at( 733 + mut self, 734 + value: impl Into<Datetime>, 735 + ) -> CommentBuilder<comment_state::SetCreatedAt<St>, S> { 736 + self._fields.0 = Option::Some(value.into()); 737 + CommentBuilder { 738 + _state: PhantomData, 739 + _fields: self._fields, 740 + _type: PhantomData, 741 + } 742 + } 743 + } 744 + 745 + impl<St: comment_state::State, S: BosStr> CommentBuilder<St, S> { 746 + /// Set the `facets` field (optional) 747 + pub fn facets(mut self, value: impl Into<Option<Vec<comment::Mention<S>>>>) -> Self { 748 + self._fields.1 = value.into(); 749 + self 750 + } 751 + /// Set the `facets` field to an Option value (optional) 752 + pub fn maybe_facets(mut self, value: Option<Vec<comment::Mention<S>>>) -> Self { 753 + self._fields.1 = value; 754 + self 755 + } 756 + } 757 + 758 + impl<St: comment_state::State, S: BosStr> CommentBuilder<St, S> { 759 + /// Set the `gif` field (optional) 760 + pub fn gif(mut self, value: impl Into<Option<comment::Gif<S>>>) -> Self { 761 + self._fields.2 = value.into(); 762 + self 763 + } 764 + /// Set the `gif` field to an Option value (optional) 765 + pub fn maybe_gif(mut self, value: Option<comment::Gif<S>>) -> Self { 766 + self._fields.2 = value; 767 + self 768 + } 769 + } 770 + 771 + impl<St, S: BosStr> CommentBuilder<St, S> 772 + where 773 + St: comment_state::State, 774 + St::Station: comment_state::IsUnset, 775 + { 776 + /// Set the `station` field (required) 777 + pub fn station( 778 + mut self, 779 + value: impl Into<StationInfo<S>>, 780 + ) -> CommentBuilder<comment_state::SetStation<St>, S> { 781 + self._fields.3 = Option::Some(value.into()); 782 + CommentBuilder { 783 + _state: PhantomData, 784 + _fields: self._fields, 785 + _type: PhantomData, 786 + } 787 + } 788 + } 789 + 790 + impl<St, S: BosStr> CommentBuilder<St, S> 791 + where 792 + St: comment_state::State, 793 + St::Text: comment_state::IsUnset, 794 + { 795 + /// Set the `text` field (required) 796 + pub fn text( 797 + mut self, 798 + value: impl Into<S>, 799 + ) -> CommentBuilder<comment_state::SetText<St>, S> { 800 + self._fields.4 = Option::Some(value.into()); 801 + CommentBuilder { 802 + _state: PhantomData, 803 + _fields: self._fields, 804 + _type: PhantomData, 805 + } 806 + } 807 + } 808 + 809 + impl<St, S: BosStr> CommentBuilder<St, S> 810 + where 811 + St: comment_state::State, 812 + St::CreatedAt: comment_state::IsSet, 813 + St::Station: comment_state::IsSet, 814 + St::Text: comment_state::IsSet, 815 + { 816 + /// Build the final struct. 817 + pub fn build(self) -> Comment<S> { 818 + Comment { 819 + created_at: self._fields.0.unwrap(), 820 + facets: self._fields.1, 821 + gif: self._fields.2, 822 + station: self._fields.3.unwrap(), 823 + text: self._fields.4.unwrap(), 824 + extra_data: Default::default(), 825 + } 826 + } 827 + /// Build the final struct with custom extra_data. 828 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Comment<S> { 829 + Comment { 830 + created_at: self._fields.0.unwrap(), 831 + facets: self._fields.1, 832 + gif: self._fields.2, 833 + station: self._fields.3.unwrap(), 834 + text: self._fields.4.unwrap(), 835 + extra_data: Some(extra_data), 836 + } 837 + } 838 + } 839 + 840 + pub mod mention_state { 841 + 842 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 843 + #[allow(unused)] 844 + use ::core::marker::PhantomData; 845 + mod sealed { 846 + pub trait Sealed {} 847 + } 848 + /// State trait tracking which required fields have been set 849 + pub trait State: sealed::Sealed { 850 + type ByteEnd; 851 + type ByteStart; 852 + type Did; 853 + } 854 + /// Empty state - all required fields are unset 855 + pub struct Empty(()); 856 + impl sealed::Sealed for Empty {} 857 + impl State for Empty { 858 + type ByteEnd = Unset; 859 + type ByteStart = Unset; 860 + type Did = Unset; 861 + } 862 + ///State transition - sets the `byte_end` field to Set 863 + pub struct SetByteEnd<St: State = Empty>(PhantomData<fn() -> St>); 864 + impl<St: State> sealed::Sealed for SetByteEnd<St> {} 865 + impl<St: State> State for SetByteEnd<St> { 866 + type ByteEnd = Set<members::byte_end>; 867 + type ByteStart = St::ByteStart; 868 + type Did = St::Did; 869 + } 870 + ///State transition - sets the `byte_start` field to Set 871 + pub struct SetByteStart<St: State = Empty>(PhantomData<fn() -> St>); 872 + impl<St: State> sealed::Sealed for SetByteStart<St> {} 873 + impl<St: State> State for SetByteStart<St> { 874 + type ByteEnd = St::ByteEnd; 875 + type ByteStart = Set<members::byte_start>; 876 + type Did = St::Did; 877 + } 878 + ///State transition - sets the `did` field to Set 879 + pub struct SetDid<St: State = Empty>(PhantomData<fn() -> St>); 880 + impl<St: State> sealed::Sealed for SetDid<St> {} 881 + impl<St: State> State for SetDid<St> { 882 + type ByteEnd = St::ByteEnd; 883 + type ByteStart = St::ByteStart; 884 + type Did = Set<members::did>; 885 + } 886 + /// Marker types for field names 887 + #[allow(non_camel_case_types)] 888 + pub mod members { 889 + ///Marker type for the `byte_end` field 890 + pub struct byte_end(()); 891 + ///Marker type for the `byte_start` field 892 + pub struct byte_start(()); 893 + ///Marker type for the `did` field 894 + pub struct did(()); 895 + } 896 + } 897 + 898 + /// Builder for constructing an instance of this type. 899 + pub struct MentionBuilder<St: mention_state::State, S: BosStr = DefaultStr> { 900 + _state: PhantomData<fn() -> St>, 901 + _fields: (Option<i64>, Option<i64>, Option<Did<S>>), 902 + _type: PhantomData<fn() -> S>, 903 + } 904 + 905 + impl Mention<DefaultStr> { 906 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 907 + pub fn new() -> MentionBuilder<mention_state::Empty, DefaultStr> { 908 + MentionBuilder::new() 909 + } 910 + } 911 + 912 + impl<S: BosStr> Mention<S> { 913 + /// Create a new builder for this type 914 + pub fn builder() -> MentionBuilder<mention_state::Empty, S> { 915 + MentionBuilder::builder() 916 + } 917 + } 918 + 919 + impl MentionBuilder<mention_state::Empty, DefaultStr> { 920 + /// Create a new builder with all fields unset, using the default string type, if needed 921 + pub fn new() -> Self { 922 + MentionBuilder { 923 + _state: PhantomData, 924 + _fields: (None, None, None), 925 + _type: PhantomData, 926 + } 927 + } 928 + } 929 + 930 + impl<S: BosStr> MentionBuilder<mention_state::Empty, S> { 931 + /// Create a new builder with all fields unset 932 + pub fn builder() -> Self { 933 + MentionBuilder { 934 + _state: PhantomData, 935 + _fields: (None, None, None), 936 + _type: PhantomData, 937 + } 938 + } 939 + } 940 + 941 + impl<St, S: BosStr> MentionBuilder<St, S> 942 + where 943 + St: mention_state::State, 944 + St::ByteEnd: mention_state::IsUnset, 945 + { 946 + /// Set the `byteEnd` field (required) 947 + pub fn byte_end( 948 + mut self, 949 + value: impl Into<i64>, 950 + ) -> MentionBuilder<mention_state::SetByteEnd<St>, S> { 951 + self._fields.0 = Option::Some(value.into()); 952 + MentionBuilder { 953 + _state: PhantomData, 954 + _fields: self._fields, 955 + _type: PhantomData, 956 + } 957 + } 958 + } 959 + 960 + impl<St, S: BosStr> MentionBuilder<St, S> 961 + where 962 + St: mention_state::State, 963 + St::ByteStart: mention_state::IsUnset, 964 + { 965 + /// Set the `byteStart` field (required) 966 + pub fn byte_start( 967 + mut self, 968 + value: impl Into<i64>, 969 + ) -> MentionBuilder<mention_state::SetByteStart<St>, S> { 970 + self._fields.1 = Option::Some(value.into()); 971 + MentionBuilder { 972 + _state: PhantomData, 973 + _fields: self._fields, 974 + _type: PhantomData, 975 + } 976 + } 977 + } 978 + 979 + impl<St, S: BosStr> MentionBuilder<St, S> 980 + where 981 + St: mention_state::State, 982 + St::Did: mention_state::IsUnset, 983 + { 984 + /// Set the `did` field (required) 985 + pub fn did( 986 + mut self, 987 + value: impl Into<Did<S>>, 988 + ) -> MentionBuilder<mention_state::SetDid<St>, S> { 989 + self._fields.2 = Option::Some(value.into()); 990 + MentionBuilder { 991 + _state: PhantomData, 992 + _fields: self._fields, 993 + _type: PhantomData, 994 + } 995 + } 996 + } 997 + 998 + impl<St, S: BosStr> MentionBuilder<St, S> 999 + where 1000 + St: mention_state::State, 1001 + St::ByteEnd: mention_state::IsSet, 1002 + St::ByteStart: mention_state::IsSet, 1003 + St::Did: mention_state::IsSet, 1004 + { 1005 + /// Build the final struct. 1006 + pub fn build(self) -> Mention<S> { 1007 + Mention { 1008 + byte_end: self._fields.0.unwrap(), 1009 + byte_start: self._fields.1.unwrap(), 1010 + did: self._fields.2.unwrap(), 1011 + extra_data: Default::default(), 1012 + } 1013 + } 1014 + /// Build the final struct with custom extra_data. 1015 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Mention<S> { 1016 + Mention { 1017 + byte_end: self._fields.0.unwrap(), 1018 + byte_start: self._fields.1.unwrap(), 1019 + did: self._fields.2.unwrap(), 1020 + extra_data: Some(extra_data), 1021 + } 1022 + } 1023 + }
+329
cli/src/fm_atradio/favorite.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.favorite 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + 15 + #[allow(unused_imports)] 16 + use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 17 + use jacquard_common::deps::smol_str::SmolStr; 18 + use jacquard_common::types::collection::{Collection, RecordError}; 19 + use jacquard_common::types::string::{AtUri, Cid, Datetime}; 20 + use jacquard_common::types::uri::{RecordUri, UriError}; 21 + use jacquard_common::types::value::Data; 22 + use jacquard_common::xrpc::XrpcResp; 23 + use jacquard_derive::{IntoStatic, lexicon}; 24 + use jacquard_lexicon::lexicon::LexiconDoc; 25 + use jacquard_lexicon::schema::LexiconSchema; 26 + 27 + #[allow(unused_imports)] 28 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 29 + use serde::{Serialize, Deserialize}; 30 + use crate::fm_atradio::StationInfo; 31 + /// A favorited radio station. 32 + 33 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 34 + #[serde( 35 + rename_all = "camelCase", 36 + rename = "fm.atradio.favorite", 37 + tag = "$type", 38 + bound(deserialize = "S: Deserialize<'de> + BosStr") 39 + )] 40 + pub struct Favorite<S: BosStr = DefaultStr> { 41 + pub created_at: Datetime, 42 + pub station: StationInfo<S>, 43 + #[serde(skip_serializing_if = "Option::is_none")] 44 + pub subject: Option<Data<S>>, 45 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 46 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 47 + } 48 + 49 + /// Typed wrapper for GetRecord response with this collection's record type. 50 + 51 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 52 + #[serde(rename_all = "camelCase")] 53 + pub struct FavoriteGetRecordOutput<S: BosStr = DefaultStr> { 54 + #[serde(skip_serializing_if = "Option::is_none")] 55 + pub cid: Option<Cid<S>>, 56 + pub uri: AtUri<S>, 57 + pub value: Favorite<S>, 58 + } 59 + 60 + impl<S: BosStr> Favorite<S> { 61 + pub fn uri(uri: S) -> Result<RecordUri<S, FavoriteRecord>, UriError> { 62 + RecordUri::try_from_uri(AtUri::new(uri)?) 63 + } 64 + } 65 + 66 + /// Marker type for deserializing records from this collection. 67 + 68 + #[derive(Debug, Serialize, Deserialize)] 69 + pub struct FavoriteRecord; 70 + impl XrpcResp for FavoriteRecord { 71 + const NSID: &'static str = "fm.atradio.favorite"; 72 + const ENCODING: &'static str = "application/json"; 73 + type Output<S: BosStr> = FavoriteGetRecordOutput<S>; 74 + type Err = RecordError; 75 + } 76 + 77 + impl<S: BosStr> From<FavoriteGetRecordOutput<S>> for Favorite<S> { 78 + fn from(output: FavoriteGetRecordOutput<S>) -> Self { 79 + output.value 80 + } 81 + } 82 + 83 + impl<S: BosStr> Collection for Favorite<S> { 84 + const NSID: &'static str = "fm.atradio.favorite"; 85 + type Record = FavoriteRecord; 86 + } 87 + 88 + impl Collection for FavoriteRecord { 89 + const NSID: &'static str = "fm.atradio.favorite"; 90 + type Record = FavoriteRecord; 91 + } 92 + 93 + impl<S: BosStr> LexiconSchema for Favorite<S> { 94 + fn nsid() -> &'static str { 95 + "fm.atradio.favorite" 96 + } 97 + fn def_name() -> &'static str { 98 + "main" 99 + } 100 + fn lexicon_doc() -> LexiconDoc<'static> { 101 + lexicon_doc_fm_atradio_favorite() 102 + } 103 + fn validate(&self) -> Result<(), ConstraintError> { 104 + Ok(()) 105 + } 106 + } 107 + 108 + pub mod favorite_state { 109 + 110 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 111 + #[allow(unused)] 112 + use ::core::marker::PhantomData; 113 + mod sealed { 114 + pub trait Sealed {} 115 + } 116 + /// State trait tracking which required fields have been set 117 + pub trait State: sealed::Sealed { 118 + type CreatedAt; 119 + type Station; 120 + } 121 + /// Empty state - all required fields are unset 122 + pub struct Empty(()); 123 + impl sealed::Sealed for Empty {} 124 + impl State for Empty { 125 + type CreatedAt = Unset; 126 + type Station = Unset; 127 + } 128 + ///State transition - sets the `created_at` field to Set 129 + pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>); 130 + impl<St: State> sealed::Sealed for SetCreatedAt<St> {} 131 + impl<St: State> State for SetCreatedAt<St> { 132 + type CreatedAt = Set<members::created_at>; 133 + type Station = St::Station; 134 + } 135 + ///State transition - sets the `station` field to Set 136 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 137 + impl<St: State> sealed::Sealed for SetStation<St> {} 138 + impl<St: State> State for SetStation<St> { 139 + type CreatedAt = St::CreatedAt; 140 + type Station = Set<members::station>; 141 + } 142 + /// Marker types for field names 143 + #[allow(non_camel_case_types)] 144 + pub mod members { 145 + ///Marker type for the `created_at` field 146 + pub struct created_at(()); 147 + ///Marker type for the `station` field 148 + pub struct station(()); 149 + } 150 + } 151 + 152 + /// Builder for constructing an instance of this type. 153 + pub struct FavoriteBuilder<St: favorite_state::State, S: BosStr = DefaultStr> { 154 + _state: PhantomData<fn() -> St>, 155 + _fields: (Option<Datetime>, Option<StationInfo<S>>, Option<Data<S>>), 156 + _type: PhantomData<fn() -> S>, 157 + } 158 + 159 + impl Favorite<DefaultStr> { 160 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 161 + pub fn new() -> FavoriteBuilder<favorite_state::Empty, DefaultStr> { 162 + FavoriteBuilder::new() 163 + } 164 + } 165 + 166 + impl<S: BosStr> Favorite<S> { 167 + /// Create a new builder for this type 168 + pub fn builder() -> FavoriteBuilder<favorite_state::Empty, S> { 169 + FavoriteBuilder::builder() 170 + } 171 + } 172 + 173 + impl FavoriteBuilder<favorite_state::Empty, DefaultStr> { 174 + /// Create a new builder with all fields unset, using the default string type, if needed 175 + pub fn new() -> Self { 176 + FavoriteBuilder { 177 + _state: PhantomData, 178 + _fields: (None, None, None), 179 + _type: PhantomData, 180 + } 181 + } 182 + } 183 + 184 + impl<S: BosStr> FavoriteBuilder<favorite_state::Empty, S> { 185 + /// Create a new builder with all fields unset 186 + pub fn builder() -> Self { 187 + FavoriteBuilder { 188 + _state: PhantomData, 189 + _fields: (None, None, None), 190 + _type: PhantomData, 191 + } 192 + } 193 + } 194 + 195 + impl<St, S: BosStr> FavoriteBuilder<St, S> 196 + where 197 + St: favorite_state::State, 198 + St::CreatedAt: favorite_state::IsUnset, 199 + { 200 + /// Set the `createdAt` field (required) 201 + pub fn created_at( 202 + mut self, 203 + value: impl Into<Datetime>, 204 + ) -> FavoriteBuilder<favorite_state::SetCreatedAt<St>, S> { 205 + self._fields.0 = Option::Some(value.into()); 206 + FavoriteBuilder { 207 + _state: PhantomData, 208 + _fields: self._fields, 209 + _type: PhantomData, 210 + } 211 + } 212 + } 213 + 214 + impl<St, S: BosStr> FavoriteBuilder<St, S> 215 + where 216 + St: favorite_state::State, 217 + St::Station: favorite_state::IsUnset, 218 + { 219 + /// Set the `station` field (required) 220 + pub fn station( 221 + mut self, 222 + value: impl Into<StationInfo<S>>, 223 + ) -> FavoriteBuilder<favorite_state::SetStation<St>, S> { 224 + self._fields.1 = Option::Some(value.into()); 225 + FavoriteBuilder { 226 + _state: PhantomData, 227 + _fields: self._fields, 228 + _type: PhantomData, 229 + } 230 + } 231 + } 232 + 233 + impl<St: favorite_state::State, S: BosStr> FavoriteBuilder<St, S> { 234 + /// Set the `subject` field (optional) 235 + pub fn subject(mut self, value: impl Into<Option<Data<S>>>) -> Self { 236 + self._fields.2 = value.into(); 237 + self 238 + } 239 + /// Set the `subject` field to an Option value (optional) 240 + pub fn maybe_subject(mut self, value: Option<Data<S>>) -> Self { 241 + self._fields.2 = value; 242 + self 243 + } 244 + } 245 + 246 + impl<St, S: BosStr> FavoriteBuilder<St, S> 247 + where 248 + St: favorite_state::State, 249 + St::CreatedAt: favorite_state::IsSet, 250 + St::Station: favorite_state::IsSet, 251 + { 252 + /// Build the final struct. 253 + pub fn build(self) -> Favorite<S> { 254 + Favorite { 255 + created_at: self._fields.0.unwrap(), 256 + station: self._fields.1.unwrap(), 257 + subject: self._fields.2, 258 + extra_data: Default::default(), 259 + } 260 + } 261 + /// Build the final struct with custom extra_data. 262 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Favorite<S> { 263 + Favorite { 264 + created_at: self._fields.0.unwrap(), 265 + station: self._fields.1.unwrap(), 266 + subject: self._fields.2, 267 + extra_data: Some(extra_data), 268 + } 269 + } 270 + } 271 + 272 + fn lexicon_doc_fm_atradio_favorite() -> LexiconDoc<'static> { 273 + #[allow(unused_imports)] 274 + use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 275 + use jacquard_lexicon::lexicon::*; 276 + use alloc::collections::BTreeMap; 277 + LexiconDoc { 278 + lexicon: Lexicon::Lexicon1, 279 + id: CowStr::new_static("fm.atradio.favorite"), 280 + defs: { 281 + let mut map = BTreeMap::new(); 282 + map.insert( 283 + SmolStr::new_static("main"), 284 + LexUserType::Record(LexRecord { 285 + description: Some(CowStr::new_static("A favorited radio station.")), 286 + key: Some(CowStr::new_static("tid")), 287 + record: LexRecordRecord::Object(LexObject { 288 + required: Some( 289 + vec![ 290 + SmolStr::new_static("station"), 291 + SmolStr::new_static("createdAt") 292 + ], 293 + ), 294 + properties: { 295 + #[allow(unused_mut)] 296 + let mut map = BTreeMap::new(); 297 + map.insert( 298 + SmolStr::new_static("createdAt"), 299 + LexObjectProperty::String(LexString { 300 + format: Some(LexStringFormat::Datetime), 301 + ..Default::default() 302 + }), 303 + ); 304 + map.insert( 305 + SmolStr::new_static("station"), 306 + LexObjectProperty::Ref(LexRef { 307 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 308 + ..Default::default() 309 + }), 310 + ); 311 + map.insert( 312 + SmolStr::new_static("subject"), 313 + LexObjectProperty::Ref(LexRef { 314 + r#ref: CowStr::new_static("com.atproto.repo.strongRef"), 315 + ..Default::default() 316 + }), 317 + ); 318 + map 319 + }, 320 + ..Default::default() 321 + }), 322 + ..Default::default() 323 + }), 324 + ); 325 + map 326 + }, 327 + ..Default::default() 328 + } 329 + }
+204
cli/src/fm_atradio/get_comments.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.getComments 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::value::Data; 16 + use jacquard_derive::IntoStatic; 17 + use serde::{Serialize, Deserialize}; 18 + use crate::fm_atradio::CommentView; 19 + 20 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 21 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 22 + pub struct GetComments<S: BosStr = DefaultStr> { 23 + #[serde(skip_serializing_if = "Option::is_none")] 24 + pub cursor: Option<S>, 25 + /// (min: 1, max: 100) 26 + #[serde(skip_serializing_if = "Option::is_none")] 27 + pub limit: Option<i64>, 28 + pub station: S, 29 + } 30 + 31 + 32 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 33 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 34 + pub struct GetCommentsOutput<S: BosStr = DefaultStr> { 35 + #[serde(skip_serializing_if = "Option::is_none")] 36 + pub cursor: Option<S>, 37 + pub items: Vec<CommentView<S>>, 38 + pub total: i64, 39 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 40 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 41 + } 42 + 43 + /** Response marker for the `fm.atradio.getComments` query. 44 + 45 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `GetCommentsOutput<S>` for this endpoint.*/ 46 + pub struct GetCommentsResponse; 47 + impl jacquard_common::xrpc::XrpcResp for GetCommentsResponse { 48 + const NSID: &'static str = "fm.atradio.getComments"; 49 + const ENCODING: &'static str = "application/json"; 50 + type Output<S: BosStr> = GetCommentsOutput<S>; 51 + type Err = jacquard_common::xrpc::GenericError; 52 + } 53 + 54 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for GetComments<S> { 55 + const NSID: &'static str = "fm.atradio.getComments"; 56 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 57 + type Response = GetCommentsResponse; 58 + } 59 + 60 + /** Endpoint marker for the `fm.atradio.getComments` query. 61 + 62 + Path: `/xrpc/fm.atradio.getComments`. The request payload type is `GetComments<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 63 + pub struct GetCommentsRequest; 64 + impl jacquard_common::xrpc::XrpcEndpoint for GetCommentsRequest { 65 + const PATH: &'static str = "/xrpc/fm.atradio.getComments"; 66 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 67 + type Request<S: BosStr> = GetComments<S>; 68 + type Response = GetCommentsResponse; 69 + } 70 + 71 + pub mod get_comments_state { 72 + 73 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 74 + #[allow(unused)] 75 + use ::core::marker::PhantomData; 76 + mod sealed { 77 + pub trait Sealed {} 78 + } 79 + /// State trait tracking which required fields have been set 80 + pub trait State: sealed::Sealed { 81 + type Station; 82 + } 83 + /// Empty state - all required fields are unset 84 + pub struct Empty(()); 85 + impl sealed::Sealed for Empty {} 86 + impl State for Empty { 87 + type Station = Unset; 88 + } 89 + ///State transition - sets the `station` field to Set 90 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 91 + impl<St: State> sealed::Sealed for SetStation<St> {} 92 + impl<St: State> State for SetStation<St> { 93 + type Station = Set<members::station>; 94 + } 95 + /// Marker types for field names 96 + #[allow(non_camel_case_types)] 97 + pub mod members { 98 + ///Marker type for the `station` field 99 + pub struct station(()); 100 + } 101 + } 102 + 103 + /// Builder for constructing an instance of this type. 104 + pub struct GetCommentsBuilder<St: get_comments_state::State, S: BosStr = DefaultStr> { 105 + _state: PhantomData<fn() -> St>, 106 + _fields: (Option<S>, Option<i64>, Option<S>), 107 + _type: PhantomData<fn() -> S>, 108 + } 109 + 110 + impl GetComments<DefaultStr> { 111 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 112 + pub fn new() -> GetCommentsBuilder<get_comments_state::Empty, DefaultStr> { 113 + GetCommentsBuilder::new() 114 + } 115 + } 116 + 117 + impl<S: BosStr> GetComments<S> { 118 + /// Create a new builder for this type 119 + pub fn builder() -> GetCommentsBuilder<get_comments_state::Empty, S> { 120 + GetCommentsBuilder::builder() 121 + } 122 + } 123 + 124 + impl GetCommentsBuilder<get_comments_state::Empty, DefaultStr> { 125 + /// Create a new builder with all fields unset, using the default string type, if needed 126 + pub fn new() -> Self { 127 + GetCommentsBuilder { 128 + _state: PhantomData, 129 + _fields: (None, None, None), 130 + _type: PhantomData, 131 + } 132 + } 133 + } 134 + 135 + impl<S: BosStr> GetCommentsBuilder<get_comments_state::Empty, S> { 136 + /// Create a new builder with all fields unset 137 + pub fn builder() -> Self { 138 + GetCommentsBuilder { 139 + _state: PhantomData, 140 + _fields: (None, None, None), 141 + _type: PhantomData, 142 + } 143 + } 144 + } 145 + 146 + impl<St: get_comments_state::State, S: BosStr> GetCommentsBuilder<St, S> { 147 + /// Set the `cursor` field (optional) 148 + pub fn cursor(mut self, value: impl Into<Option<S>>) -> Self { 149 + self._fields.0 = value.into(); 150 + self 151 + } 152 + /// Set the `cursor` field to an Option value (optional) 153 + pub fn maybe_cursor(mut self, value: Option<S>) -> Self { 154 + self._fields.0 = value; 155 + self 156 + } 157 + } 158 + 159 + impl<St: get_comments_state::State, S: BosStr> GetCommentsBuilder<St, S> { 160 + /// Set the `limit` field (optional) 161 + pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self { 162 + self._fields.1 = value.into(); 163 + self 164 + } 165 + /// Set the `limit` field to an Option value (optional) 166 + pub fn maybe_limit(mut self, value: Option<i64>) -> Self { 167 + self._fields.1 = value; 168 + self 169 + } 170 + } 171 + 172 + impl<St, S: BosStr> GetCommentsBuilder<St, S> 173 + where 174 + St: get_comments_state::State, 175 + St::Station: get_comments_state::IsUnset, 176 + { 177 + /// Set the `station` field (required) 178 + pub fn station( 179 + mut self, 180 + value: impl Into<S>, 181 + ) -> GetCommentsBuilder<get_comments_state::SetStation<St>, S> { 182 + self._fields.2 = Option::Some(value.into()); 183 + GetCommentsBuilder { 184 + _state: PhantomData, 185 + _fields: self._fields, 186 + _type: PhantomData, 187 + } 188 + } 189 + } 190 + 191 + impl<St, S: BosStr> GetCommentsBuilder<St, S> 192 + where 193 + St: get_comments_state::State, 194 + St::Station: get_comments_state::IsSet, 195 + { 196 + /// Build the final struct. 197 + pub fn build(self) -> GetComments<S> { 198 + GetComments { 199 + cursor: self._fields.0, 200 + limit: self._fields.1, 201 + station: self._fields.2.unwrap(), 202 + } 203 + } 204 + }
+261
cli/src/fm_atradio/get_favorites.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.getFavorites 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::ident::AtIdentifier; 16 + use jacquard_common::types::value::Data; 17 + use jacquard_derive::IntoStatic; 18 + use serde::{Serialize, Deserialize}; 19 + use crate::fm_atradio::StationView; 20 + 21 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 22 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 23 + pub struct GetFavorites<S: BosStr = DefaultStr> { 24 + pub actor: AtIdentifier<S>, 25 + #[serde(skip_serializing_if = "Option::is_none")] 26 + pub cursor: Option<S>, 27 + /// (min: 1, max: 100) 28 + #[serde(skip_serializing_if = "Option::is_none")] 29 + pub limit: Option<i64>, 30 + #[serde(skip_serializing_if = "Option::is_none")] 31 + pub q: Option<S>, 32 + #[serde(skip_serializing_if = "Option::is_none")] 33 + pub sort: Option<S>, 34 + #[serde(skip_serializing_if = "Option::is_none")] 35 + pub source: Option<S>, 36 + } 37 + 38 + 39 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 40 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 41 + pub struct GetFavoritesOutput<S: BosStr = DefaultStr> { 42 + #[serde(skip_serializing_if = "Option::is_none")] 43 + pub cursor: Option<S>, 44 + pub items: Vec<StationView<S>>, 45 + ///Total number of records for the actor. 46 + pub total: i64, 47 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 48 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 49 + } 50 + 51 + /** Response marker for the `fm.atradio.getFavorites` query. 52 + 53 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `GetFavoritesOutput<S>` for this endpoint.*/ 54 + pub struct GetFavoritesResponse; 55 + impl jacquard_common::xrpc::XrpcResp for GetFavoritesResponse { 56 + const NSID: &'static str = "fm.atradio.getFavorites"; 57 + const ENCODING: &'static str = "application/json"; 58 + type Output<S: BosStr> = GetFavoritesOutput<S>; 59 + type Err = jacquard_common::xrpc::GenericError; 60 + } 61 + 62 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for GetFavorites<S> { 63 + const NSID: &'static str = "fm.atradio.getFavorites"; 64 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 65 + type Response = GetFavoritesResponse; 66 + } 67 + 68 + /** Endpoint marker for the `fm.atradio.getFavorites` query. 69 + 70 + Path: `/xrpc/fm.atradio.getFavorites`. The request payload type is `GetFavorites<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 71 + pub struct GetFavoritesRequest; 72 + impl jacquard_common::xrpc::XrpcEndpoint for GetFavoritesRequest { 73 + const PATH: &'static str = "/xrpc/fm.atradio.getFavorites"; 74 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 75 + type Request<S: BosStr> = GetFavorites<S>; 76 + type Response = GetFavoritesResponse; 77 + } 78 + 79 + pub mod get_favorites_state { 80 + 81 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 82 + #[allow(unused)] 83 + use ::core::marker::PhantomData; 84 + mod sealed { 85 + pub trait Sealed {} 86 + } 87 + /// State trait tracking which required fields have been set 88 + pub trait State: sealed::Sealed { 89 + type Actor; 90 + } 91 + /// Empty state - all required fields are unset 92 + pub struct Empty(()); 93 + impl sealed::Sealed for Empty {} 94 + impl State for Empty { 95 + type Actor = Unset; 96 + } 97 + ///State transition - sets the `actor` field to Set 98 + pub struct SetActor<St: State = Empty>(PhantomData<fn() -> St>); 99 + impl<St: State> sealed::Sealed for SetActor<St> {} 100 + impl<St: State> State for SetActor<St> { 101 + type Actor = Set<members::actor>; 102 + } 103 + /// Marker types for field names 104 + #[allow(non_camel_case_types)] 105 + pub mod members { 106 + ///Marker type for the `actor` field 107 + pub struct actor(()); 108 + } 109 + } 110 + 111 + /// Builder for constructing an instance of this type. 112 + pub struct GetFavoritesBuilder<St: get_favorites_state::State, S: BosStr = DefaultStr> { 113 + _state: PhantomData<fn() -> St>, 114 + _fields: ( 115 + Option<AtIdentifier<S>>, 116 + Option<S>, 117 + Option<i64>, 118 + Option<S>, 119 + Option<S>, 120 + Option<S>, 121 + ), 122 + _type: PhantomData<fn() -> S>, 123 + } 124 + 125 + impl GetFavorites<DefaultStr> { 126 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 127 + pub fn new() -> GetFavoritesBuilder<get_favorites_state::Empty, DefaultStr> { 128 + GetFavoritesBuilder::new() 129 + } 130 + } 131 + 132 + impl<S: BosStr> GetFavorites<S> { 133 + /// Create a new builder for this type 134 + pub fn builder() -> GetFavoritesBuilder<get_favorites_state::Empty, S> { 135 + GetFavoritesBuilder::builder() 136 + } 137 + } 138 + 139 + impl GetFavoritesBuilder<get_favorites_state::Empty, DefaultStr> { 140 + /// Create a new builder with all fields unset, using the default string type, if needed 141 + pub fn new() -> Self { 142 + GetFavoritesBuilder { 143 + _state: PhantomData, 144 + _fields: (None, None, None, None, None, None), 145 + _type: PhantomData, 146 + } 147 + } 148 + } 149 + 150 + impl<S: BosStr> GetFavoritesBuilder<get_favorites_state::Empty, S> { 151 + /// Create a new builder with all fields unset 152 + pub fn builder() -> Self { 153 + GetFavoritesBuilder { 154 + _state: PhantomData, 155 + _fields: (None, None, None, None, None, None), 156 + _type: PhantomData, 157 + } 158 + } 159 + } 160 + 161 + impl<St, S: BosStr> GetFavoritesBuilder<St, S> 162 + where 163 + St: get_favorites_state::State, 164 + St::Actor: get_favorites_state::IsUnset, 165 + { 166 + /// Set the `actor` field (required) 167 + pub fn actor( 168 + mut self, 169 + value: impl Into<AtIdentifier<S>>, 170 + ) -> GetFavoritesBuilder<get_favorites_state::SetActor<St>, S> { 171 + self._fields.0 = Option::Some(value.into()); 172 + GetFavoritesBuilder { 173 + _state: PhantomData, 174 + _fields: self._fields, 175 + _type: PhantomData, 176 + } 177 + } 178 + } 179 + 180 + impl<St: get_favorites_state::State, S: BosStr> GetFavoritesBuilder<St, S> { 181 + /// Set the `cursor` field (optional) 182 + pub fn cursor(mut self, value: impl Into<Option<S>>) -> Self { 183 + self._fields.1 = value.into(); 184 + self 185 + } 186 + /// Set the `cursor` field to an Option value (optional) 187 + pub fn maybe_cursor(mut self, value: Option<S>) -> Self { 188 + self._fields.1 = value; 189 + self 190 + } 191 + } 192 + 193 + impl<St: get_favorites_state::State, S: BosStr> GetFavoritesBuilder<St, S> { 194 + /// Set the `limit` field (optional) 195 + pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self { 196 + self._fields.2 = value.into(); 197 + self 198 + } 199 + /// Set the `limit` field to an Option value (optional) 200 + pub fn maybe_limit(mut self, value: Option<i64>) -> Self { 201 + self._fields.2 = value; 202 + self 203 + } 204 + } 205 + 206 + impl<St: get_favorites_state::State, S: BosStr> GetFavoritesBuilder<St, S> { 207 + /// Set the `q` field (optional) 208 + pub fn q(mut self, value: impl Into<Option<S>>) -> Self { 209 + self._fields.3 = value.into(); 210 + self 211 + } 212 + /// Set the `q` field to an Option value (optional) 213 + pub fn maybe_q(mut self, value: Option<S>) -> Self { 214 + self._fields.3 = value; 215 + self 216 + } 217 + } 218 + 219 + impl<St: get_favorites_state::State, S: BosStr> GetFavoritesBuilder<St, S> { 220 + /// Set the `sort` field (optional) 221 + pub fn sort(mut self, value: impl Into<Option<S>>) -> Self { 222 + self._fields.4 = value.into(); 223 + self 224 + } 225 + /// Set the `sort` field to an Option value (optional) 226 + pub fn maybe_sort(mut self, value: Option<S>) -> Self { 227 + self._fields.4 = value; 228 + self 229 + } 230 + } 231 + 232 + impl<St: get_favorites_state::State, S: BosStr> GetFavoritesBuilder<St, S> { 233 + /// Set the `source` field (optional) 234 + pub fn source(mut self, value: impl Into<Option<S>>) -> Self { 235 + self._fields.5 = value.into(); 236 + self 237 + } 238 + /// Set the `source` field to an Option value (optional) 239 + pub fn maybe_source(mut self, value: Option<S>) -> Self { 240 + self._fields.5 = value; 241 + self 242 + } 243 + } 244 + 245 + impl<St, S: BosStr> GetFavoritesBuilder<St, S> 246 + where 247 + St: get_favorites_state::State, 248 + St::Actor: get_favorites_state::IsSet, 249 + { 250 + /// Build the final struct. 251 + pub fn build(self) -> GetFavorites<S> { 252 + GetFavorites { 253 + actor: self._fields.0.unwrap(), 254 + cursor: self._fields.1, 255 + limit: self._fields.2, 256 + q: self._fields.3, 257 + sort: self._fields.4, 258 + source: self._fields.5, 259 + } 260 + } 261 + }
+188
cli/src/fm_atradio/get_global_recently_played.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.getGlobalRecentlyPlayed 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::value::Data; 16 + use jacquard_derive::IntoStatic; 17 + use serde::{Serialize, Deserialize}; 18 + use crate::fm_atradio::PlayView; 19 + 20 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 21 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 22 + pub struct GetGlobalRecentlyPlayed<S: BosStr = DefaultStr> { 23 + #[serde(skip_serializing_if = "Option::is_none")] 24 + pub cursor: Option<S>, 25 + /// (min: 1, max: 100) 26 + #[serde(skip_serializing_if = "Option::is_none")] 27 + pub limit: Option<i64>, 28 + } 29 + 30 + 31 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 32 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 33 + pub struct GetGlobalRecentlyPlayedOutput<S: BosStr = DefaultStr> { 34 + #[serde(skip_serializing_if = "Option::is_none")] 35 + pub cursor: Option<S>, 36 + pub items: Vec<PlayView<S>>, 37 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 38 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 39 + } 40 + 41 + /** Response marker for the `fm.atradio.getGlobalRecentlyPlayed` query. 42 + 43 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `GetGlobalRecentlyPlayedOutput<S>` for this endpoint.*/ 44 + pub struct GetGlobalRecentlyPlayedResponse; 45 + impl jacquard_common::xrpc::XrpcResp for GetGlobalRecentlyPlayedResponse { 46 + const NSID: &'static str = "fm.atradio.getGlobalRecentlyPlayed"; 47 + const ENCODING: &'static str = "application/json"; 48 + type Output<S: BosStr> = GetGlobalRecentlyPlayedOutput<S>; 49 + type Err = jacquard_common::xrpc::GenericError; 50 + } 51 + 52 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for GetGlobalRecentlyPlayed<S> { 53 + const NSID: &'static str = "fm.atradio.getGlobalRecentlyPlayed"; 54 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 55 + type Response = GetGlobalRecentlyPlayedResponse; 56 + } 57 + 58 + /** Endpoint marker for the `fm.atradio.getGlobalRecentlyPlayed` query. 59 + 60 + Path: `/xrpc/fm.atradio.getGlobalRecentlyPlayed`. The request payload type is `GetGlobalRecentlyPlayed<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 61 + pub struct GetGlobalRecentlyPlayedRequest; 62 + impl jacquard_common::xrpc::XrpcEndpoint for GetGlobalRecentlyPlayedRequest { 63 + const PATH: &'static str = "/xrpc/fm.atradio.getGlobalRecentlyPlayed"; 64 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 65 + type Request<S: BosStr> = GetGlobalRecentlyPlayed<S>; 66 + type Response = GetGlobalRecentlyPlayedResponse; 67 + } 68 + 69 + pub mod get_global_recently_played_state { 70 + 71 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 72 + #[allow(unused)] 73 + use ::core::marker::PhantomData; 74 + mod sealed { 75 + pub trait Sealed {} 76 + } 77 + /// State trait tracking which required fields have been set 78 + pub trait State: sealed::Sealed {} 79 + /// Empty state - all required fields are unset 80 + pub struct Empty(()); 81 + impl sealed::Sealed for Empty {} 82 + impl State for Empty {} 83 + /// Marker types for field names 84 + #[allow(non_camel_case_types)] 85 + pub mod members {} 86 + } 87 + 88 + /// Builder for constructing an instance of this type. 89 + pub struct GetGlobalRecentlyPlayedBuilder< 90 + St: get_global_recently_played_state::State, 91 + S: BosStr = DefaultStr, 92 + > { 93 + _state: PhantomData<fn() -> St>, 94 + _fields: (Option<S>, Option<i64>), 95 + _type: PhantomData<fn() -> S>, 96 + } 97 + 98 + impl GetGlobalRecentlyPlayed<DefaultStr> { 99 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 100 + pub fn new() -> GetGlobalRecentlyPlayedBuilder< 101 + get_global_recently_played_state::Empty, 102 + DefaultStr, 103 + > { 104 + GetGlobalRecentlyPlayedBuilder::new() 105 + } 106 + } 107 + 108 + impl<S: BosStr> GetGlobalRecentlyPlayed<S> { 109 + /// Create a new builder for this type 110 + pub fn builder() -> GetGlobalRecentlyPlayedBuilder< 111 + get_global_recently_played_state::Empty, 112 + S, 113 + > { 114 + GetGlobalRecentlyPlayedBuilder::builder() 115 + } 116 + } 117 + 118 + impl GetGlobalRecentlyPlayedBuilder< 119 + get_global_recently_played_state::Empty, 120 + DefaultStr, 121 + > { 122 + /// Create a new builder with all fields unset, using the default string type, if needed 123 + pub fn new() -> Self { 124 + GetGlobalRecentlyPlayedBuilder { 125 + _state: PhantomData, 126 + _fields: (None, None), 127 + _type: PhantomData, 128 + } 129 + } 130 + } 131 + 132 + impl< 133 + S: BosStr, 134 + > GetGlobalRecentlyPlayedBuilder<get_global_recently_played_state::Empty, S> { 135 + /// Create a new builder with all fields unset 136 + pub fn builder() -> Self { 137 + GetGlobalRecentlyPlayedBuilder { 138 + _state: PhantomData, 139 + _fields: (None, None), 140 + _type: PhantomData, 141 + } 142 + } 143 + } 144 + 145 + impl< 146 + St: get_global_recently_played_state::State, 147 + S: BosStr, 148 + > GetGlobalRecentlyPlayedBuilder<St, S> { 149 + /// Set the `cursor` field (optional) 150 + pub fn cursor(mut self, value: impl Into<Option<S>>) -> Self { 151 + self._fields.0 = value.into(); 152 + self 153 + } 154 + /// Set the `cursor` field to an Option value (optional) 155 + pub fn maybe_cursor(mut self, value: Option<S>) -> Self { 156 + self._fields.0 = value; 157 + self 158 + } 159 + } 160 + 161 + impl< 162 + St: get_global_recently_played_state::State, 163 + S: BosStr, 164 + > GetGlobalRecentlyPlayedBuilder<St, S> { 165 + /// Set the `limit` field (optional) 166 + pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self { 167 + self._fields.1 = value.into(); 168 + self 169 + } 170 + /// Set the `limit` field to an Option value (optional) 171 + pub fn maybe_limit(mut self, value: Option<i64>) -> Self { 172 + self._fields.1 = value; 173 + self 174 + } 175 + } 176 + 177 + impl<St, S: BosStr> GetGlobalRecentlyPlayedBuilder<St, S> 178 + where 179 + St: get_global_recently_played_state::State, 180 + { 181 + /// Build the final struct. 182 + pub fn build(self) -> GetGlobalRecentlyPlayed<S> { 183 + GetGlobalRecentlyPlayed { 184 + cursor: self._fields.0, 185 + limit: self._fields.1, 186 + } 187 + } 188 + }
+174
cli/src/fm_atradio/get_listener_counts.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.getListenerCounts 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::value::Data; 16 + use jacquard_derive::IntoStatic; 17 + use serde::{Serialize, Deserialize}; 18 + use crate::fm_atradio::ListenerCount; 19 + 20 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 21 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 22 + pub struct GetListenerCounts<S: BosStr = DefaultStr> { 23 + pub stations: S, 24 + } 25 + 26 + 27 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 28 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 29 + pub struct GetListenerCountsOutput<S: BosStr = DefaultStr> { 30 + pub counts: Vec<ListenerCount<S>>, 31 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 32 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 33 + } 34 + 35 + /** Response marker for the `fm.atradio.getListenerCounts` query. 36 + 37 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `GetListenerCountsOutput<S>` for this endpoint.*/ 38 + pub struct GetListenerCountsResponse; 39 + impl jacquard_common::xrpc::XrpcResp for GetListenerCountsResponse { 40 + const NSID: &'static str = "fm.atradio.getListenerCounts"; 41 + const ENCODING: &'static str = "application/json"; 42 + type Output<S: BosStr> = GetListenerCountsOutput<S>; 43 + type Err = jacquard_common::xrpc::GenericError; 44 + } 45 + 46 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for GetListenerCounts<S> { 47 + const NSID: &'static str = "fm.atradio.getListenerCounts"; 48 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 49 + type Response = GetListenerCountsResponse; 50 + } 51 + 52 + /** Endpoint marker for the `fm.atradio.getListenerCounts` query. 53 + 54 + Path: `/xrpc/fm.atradio.getListenerCounts`. The request payload type is `GetListenerCounts<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 55 + pub struct GetListenerCountsRequest; 56 + impl jacquard_common::xrpc::XrpcEndpoint for GetListenerCountsRequest { 57 + const PATH: &'static str = "/xrpc/fm.atradio.getListenerCounts"; 58 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 59 + type Request<S: BosStr> = GetListenerCounts<S>; 60 + type Response = GetListenerCountsResponse; 61 + } 62 + 63 + pub mod get_listener_counts_state { 64 + 65 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 66 + #[allow(unused)] 67 + use ::core::marker::PhantomData; 68 + mod sealed { 69 + pub trait Sealed {} 70 + } 71 + /// State trait tracking which required fields have been set 72 + pub trait State: sealed::Sealed { 73 + type Stations; 74 + } 75 + /// Empty state - all required fields are unset 76 + pub struct Empty(()); 77 + impl sealed::Sealed for Empty {} 78 + impl State for Empty { 79 + type Stations = Unset; 80 + } 81 + ///State transition - sets the `stations` field to Set 82 + pub struct SetStations<St: State = Empty>(PhantomData<fn() -> St>); 83 + impl<St: State> sealed::Sealed for SetStations<St> {} 84 + impl<St: State> State for SetStations<St> { 85 + type Stations = Set<members::stations>; 86 + } 87 + /// Marker types for field names 88 + #[allow(non_camel_case_types)] 89 + pub mod members { 90 + ///Marker type for the `stations` field 91 + pub struct stations(()); 92 + } 93 + } 94 + 95 + /// Builder for constructing an instance of this type. 96 + pub struct GetListenerCountsBuilder< 97 + St: get_listener_counts_state::State, 98 + S: BosStr = DefaultStr, 99 + > { 100 + _state: PhantomData<fn() -> St>, 101 + _fields: (Option<S>,), 102 + _type: PhantomData<fn() -> S>, 103 + } 104 + 105 + impl GetListenerCounts<DefaultStr> { 106 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 107 + pub fn new() -> GetListenerCountsBuilder< 108 + get_listener_counts_state::Empty, 109 + DefaultStr, 110 + > { 111 + GetListenerCountsBuilder::new() 112 + } 113 + } 114 + 115 + impl<S: BosStr> GetListenerCounts<S> { 116 + /// Create a new builder for this type 117 + pub fn builder() -> GetListenerCountsBuilder<get_listener_counts_state::Empty, S> { 118 + GetListenerCountsBuilder::builder() 119 + } 120 + } 121 + 122 + impl GetListenerCountsBuilder<get_listener_counts_state::Empty, DefaultStr> { 123 + /// Create a new builder with all fields unset, using the default string type, if needed 124 + pub fn new() -> Self { 125 + GetListenerCountsBuilder { 126 + _state: PhantomData, 127 + _fields: (None,), 128 + _type: PhantomData, 129 + } 130 + } 131 + } 132 + 133 + impl<S: BosStr> GetListenerCountsBuilder<get_listener_counts_state::Empty, S> { 134 + /// Create a new builder with all fields unset 135 + pub fn builder() -> Self { 136 + GetListenerCountsBuilder { 137 + _state: PhantomData, 138 + _fields: (None,), 139 + _type: PhantomData, 140 + } 141 + } 142 + } 143 + 144 + impl<St, S: BosStr> GetListenerCountsBuilder<St, S> 145 + where 146 + St: get_listener_counts_state::State, 147 + St::Stations: get_listener_counts_state::IsUnset, 148 + { 149 + /// Set the `stations` field (required) 150 + pub fn stations( 151 + mut self, 152 + value: impl Into<S>, 153 + ) -> GetListenerCountsBuilder<get_listener_counts_state::SetStations<St>, S> { 154 + self._fields.0 = Option::Some(value.into()); 155 + GetListenerCountsBuilder { 156 + _state: PhantomData, 157 + _fields: self._fields, 158 + _type: PhantomData, 159 + } 160 + } 161 + } 162 + 163 + impl<St, S: BosStr> GetListenerCountsBuilder<St, S> 164 + where 165 + St: get_listener_counts_state::State, 166 + St::Stations: get_listener_counts_state::IsSet, 167 + { 168 + /// Build the final struct. 169 + pub fn build(self) -> GetListenerCounts<S> { 170 + GetListenerCounts { 171 + stations: self._fields.0.unwrap(), 172 + } 173 + } 174 + }
+209
cli/src/fm_atradio/get_notifications.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.getNotifications 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::ident::AtIdentifier; 16 + use jacquard_common::types::value::Data; 17 + use jacquard_derive::IntoStatic; 18 + use serde::{Serialize, Deserialize}; 19 + use crate::fm_atradio::NotificationView; 20 + 21 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 22 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 23 + pub struct GetNotifications<S: BosStr = DefaultStr> { 24 + pub actor: AtIdentifier<S>, 25 + #[serde(skip_serializing_if = "Option::is_none")] 26 + pub cursor: Option<S>, 27 + /// (min: 1, max: 100) 28 + #[serde(skip_serializing_if = "Option::is_none")] 29 + pub limit: Option<i64>, 30 + } 31 + 32 + 33 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 34 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 35 + pub struct GetNotificationsOutput<S: BosStr = DefaultStr> { 36 + #[serde(skip_serializing_if = "Option::is_none")] 37 + pub cursor: Option<S>, 38 + pub items: Vec<NotificationView<S>>, 39 + ///Number of notifications newer than the actor's last-seen time. 40 + pub unread_count: i64, 41 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 42 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 43 + } 44 + 45 + /** Response marker for the `fm.atradio.getNotifications` query. 46 + 47 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `GetNotificationsOutput<S>` for this endpoint.*/ 48 + pub struct GetNotificationsResponse; 49 + impl jacquard_common::xrpc::XrpcResp for GetNotificationsResponse { 50 + const NSID: &'static str = "fm.atradio.getNotifications"; 51 + const ENCODING: &'static str = "application/json"; 52 + type Output<S: BosStr> = GetNotificationsOutput<S>; 53 + type Err = jacquard_common::xrpc::GenericError; 54 + } 55 + 56 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for GetNotifications<S> { 57 + const NSID: &'static str = "fm.atradio.getNotifications"; 58 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 59 + type Response = GetNotificationsResponse; 60 + } 61 + 62 + /** Endpoint marker for the `fm.atradio.getNotifications` query. 63 + 64 + Path: `/xrpc/fm.atradio.getNotifications`. The request payload type is `GetNotifications<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 65 + pub struct GetNotificationsRequest; 66 + impl jacquard_common::xrpc::XrpcEndpoint for GetNotificationsRequest { 67 + const PATH: &'static str = "/xrpc/fm.atradio.getNotifications"; 68 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 69 + type Request<S: BosStr> = GetNotifications<S>; 70 + type Response = GetNotificationsResponse; 71 + } 72 + 73 + pub mod get_notifications_state { 74 + 75 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 76 + #[allow(unused)] 77 + use ::core::marker::PhantomData; 78 + mod sealed { 79 + pub trait Sealed {} 80 + } 81 + /// State trait tracking which required fields have been set 82 + pub trait State: sealed::Sealed { 83 + type Actor; 84 + } 85 + /// Empty state - all required fields are unset 86 + pub struct Empty(()); 87 + impl sealed::Sealed for Empty {} 88 + impl State for Empty { 89 + type Actor = Unset; 90 + } 91 + ///State transition - sets the `actor` field to Set 92 + pub struct SetActor<St: State = Empty>(PhantomData<fn() -> St>); 93 + impl<St: State> sealed::Sealed for SetActor<St> {} 94 + impl<St: State> State for SetActor<St> { 95 + type Actor = Set<members::actor>; 96 + } 97 + /// Marker types for field names 98 + #[allow(non_camel_case_types)] 99 + pub mod members { 100 + ///Marker type for the `actor` field 101 + pub struct actor(()); 102 + } 103 + } 104 + 105 + /// Builder for constructing an instance of this type. 106 + pub struct GetNotificationsBuilder< 107 + St: get_notifications_state::State, 108 + S: BosStr = DefaultStr, 109 + > { 110 + _state: PhantomData<fn() -> St>, 111 + _fields: (Option<AtIdentifier<S>>, Option<S>, Option<i64>), 112 + _type: PhantomData<fn() -> S>, 113 + } 114 + 115 + impl GetNotifications<DefaultStr> { 116 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 117 + pub fn new() -> GetNotificationsBuilder<get_notifications_state::Empty, DefaultStr> { 118 + GetNotificationsBuilder::new() 119 + } 120 + } 121 + 122 + impl<S: BosStr> GetNotifications<S> { 123 + /// Create a new builder for this type 124 + pub fn builder() -> GetNotificationsBuilder<get_notifications_state::Empty, S> { 125 + GetNotificationsBuilder::builder() 126 + } 127 + } 128 + 129 + impl GetNotificationsBuilder<get_notifications_state::Empty, DefaultStr> { 130 + /// Create a new builder with all fields unset, using the default string type, if needed 131 + pub fn new() -> Self { 132 + GetNotificationsBuilder { 133 + _state: PhantomData, 134 + _fields: (None, None, None), 135 + _type: PhantomData, 136 + } 137 + } 138 + } 139 + 140 + impl<S: BosStr> GetNotificationsBuilder<get_notifications_state::Empty, S> { 141 + /// Create a new builder with all fields unset 142 + pub fn builder() -> Self { 143 + GetNotificationsBuilder { 144 + _state: PhantomData, 145 + _fields: (None, None, None), 146 + _type: PhantomData, 147 + } 148 + } 149 + } 150 + 151 + impl<St, S: BosStr> GetNotificationsBuilder<St, S> 152 + where 153 + St: get_notifications_state::State, 154 + St::Actor: get_notifications_state::IsUnset, 155 + { 156 + /// Set the `actor` field (required) 157 + pub fn actor( 158 + mut self, 159 + value: impl Into<AtIdentifier<S>>, 160 + ) -> GetNotificationsBuilder<get_notifications_state::SetActor<St>, S> { 161 + self._fields.0 = Option::Some(value.into()); 162 + GetNotificationsBuilder { 163 + _state: PhantomData, 164 + _fields: self._fields, 165 + _type: PhantomData, 166 + } 167 + } 168 + } 169 + 170 + impl<St: get_notifications_state::State, S: BosStr> GetNotificationsBuilder<St, S> { 171 + /// Set the `cursor` field (optional) 172 + pub fn cursor(mut self, value: impl Into<Option<S>>) -> Self { 173 + self._fields.1 = value.into(); 174 + self 175 + } 176 + /// Set the `cursor` field to an Option value (optional) 177 + pub fn maybe_cursor(mut self, value: Option<S>) -> Self { 178 + self._fields.1 = value; 179 + self 180 + } 181 + } 182 + 183 + impl<St: get_notifications_state::State, S: BosStr> GetNotificationsBuilder<St, S> { 184 + /// Set the `limit` field (optional) 185 + pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self { 186 + self._fields.2 = value.into(); 187 + self 188 + } 189 + /// Set the `limit` field to an Option value (optional) 190 + pub fn maybe_limit(mut self, value: Option<i64>) -> Self { 191 + self._fields.2 = value; 192 + self 193 + } 194 + } 195 + 196 + impl<St, S: BosStr> GetNotificationsBuilder<St, S> 197 + where 198 + St: get_notifications_state::State, 199 + St::Actor: get_notifications_state::IsSet, 200 + { 201 + /// Build the final struct. 202 + pub fn build(self) -> GetNotifications<S> { 203 + GetNotifications { 204 + actor: self._fields.0.unwrap(), 205 + cursor: self._fields.1, 206 + limit: self._fields.2, 207 + } 208 + } 209 + }
+210
cli/src/fm_atradio/get_recently_played.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.getRecentlyPlayed 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::ident::AtIdentifier; 16 + use jacquard_common::types::value::Data; 17 + use jacquard_derive::IntoStatic; 18 + use serde::{Serialize, Deserialize}; 19 + use crate::fm_atradio::PlayView; 20 + 21 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 22 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 23 + pub struct GetRecentlyPlayed<S: BosStr = DefaultStr> { 24 + pub actor: AtIdentifier<S>, 25 + #[serde(skip_serializing_if = "Option::is_none")] 26 + pub cursor: Option<S>, 27 + /// (min: 1, max: 100) 28 + #[serde(skip_serializing_if = "Option::is_none")] 29 + pub limit: Option<i64>, 30 + } 31 + 32 + 33 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 34 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 35 + pub struct GetRecentlyPlayedOutput<S: BosStr = DefaultStr> { 36 + #[serde(skip_serializing_if = "Option::is_none")] 37 + pub cursor: Option<S>, 38 + pub items: Vec<PlayView<S>>, 39 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 40 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 41 + } 42 + 43 + /** Response marker for the `fm.atradio.getRecentlyPlayed` query. 44 + 45 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `GetRecentlyPlayedOutput<S>` for this endpoint.*/ 46 + pub struct GetRecentlyPlayedResponse; 47 + impl jacquard_common::xrpc::XrpcResp for GetRecentlyPlayedResponse { 48 + const NSID: &'static str = "fm.atradio.getRecentlyPlayed"; 49 + const ENCODING: &'static str = "application/json"; 50 + type Output<S: BosStr> = GetRecentlyPlayedOutput<S>; 51 + type Err = jacquard_common::xrpc::GenericError; 52 + } 53 + 54 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for GetRecentlyPlayed<S> { 55 + const NSID: &'static str = "fm.atradio.getRecentlyPlayed"; 56 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 57 + type Response = GetRecentlyPlayedResponse; 58 + } 59 + 60 + /** Endpoint marker for the `fm.atradio.getRecentlyPlayed` query. 61 + 62 + Path: `/xrpc/fm.atradio.getRecentlyPlayed`. The request payload type is `GetRecentlyPlayed<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 63 + pub struct GetRecentlyPlayedRequest; 64 + impl jacquard_common::xrpc::XrpcEndpoint for GetRecentlyPlayedRequest { 65 + const PATH: &'static str = "/xrpc/fm.atradio.getRecentlyPlayed"; 66 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 67 + type Request<S: BosStr> = GetRecentlyPlayed<S>; 68 + type Response = GetRecentlyPlayedResponse; 69 + } 70 + 71 + pub mod get_recently_played_state { 72 + 73 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 74 + #[allow(unused)] 75 + use ::core::marker::PhantomData; 76 + mod sealed { 77 + pub trait Sealed {} 78 + } 79 + /// State trait tracking which required fields have been set 80 + pub trait State: sealed::Sealed { 81 + type Actor; 82 + } 83 + /// Empty state - all required fields are unset 84 + pub struct Empty(()); 85 + impl sealed::Sealed for Empty {} 86 + impl State for Empty { 87 + type Actor = Unset; 88 + } 89 + ///State transition - sets the `actor` field to Set 90 + pub struct SetActor<St: State = Empty>(PhantomData<fn() -> St>); 91 + impl<St: State> sealed::Sealed for SetActor<St> {} 92 + impl<St: State> State for SetActor<St> { 93 + type Actor = Set<members::actor>; 94 + } 95 + /// Marker types for field names 96 + #[allow(non_camel_case_types)] 97 + pub mod members { 98 + ///Marker type for the `actor` field 99 + pub struct actor(()); 100 + } 101 + } 102 + 103 + /// Builder for constructing an instance of this type. 104 + pub struct GetRecentlyPlayedBuilder< 105 + St: get_recently_played_state::State, 106 + S: BosStr = DefaultStr, 107 + > { 108 + _state: PhantomData<fn() -> St>, 109 + _fields: (Option<AtIdentifier<S>>, Option<S>, Option<i64>), 110 + _type: PhantomData<fn() -> S>, 111 + } 112 + 113 + impl GetRecentlyPlayed<DefaultStr> { 114 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 115 + pub fn new() -> GetRecentlyPlayedBuilder< 116 + get_recently_played_state::Empty, 117 + DefaultStr, 118 + > { 119 + GetRecentlyPlayedBuilder::new() 120 + } 121 + } 122 + 123 + impl<S: BosStr> GetRecentlyPlayed<S> { 124 + /// Create a new builder for this type 125 + pub fn builder() -> GetRecentlyPlayedBuilder<get_recently_played_state::Empty, S> { 126 + GetRecentlyPlayedBuilder::builder() 127 + } 128 + } 129 + 130 + impl GetRecentlyPlayedBuilder<get_recently_played_state::Empty, DefaultStr> { 131 + /// Create a new builder with all fields unset, using the default string type, if needed 132 + pub fn new() -> Self { 133 + GetRecentlyPlayedBuilder { 134 + _state: PhantomData, 135 + _fields: (None, None, None), 136 + _type: PhantomData, 137 + } 138 + } 139 + } 140 + 141 + impl<S: BosStr> GetRecentlyPlayedBuilder<get_recently_played_state::Empty, S> { 142 + /// Create a new builder with all fields unset 143 + pub fn builder() -> Self { 144 + GetRecentlyPlayedBuilder { 145 + _state: PhantomData, 146 + _fields: (None, None, None), 147 + _type: PhantomData, 148 + } 149 + } 150 + } 151 + 152 + impl<St, S: BosStr> GetRecentlyPlayedBuilder<St, S> 153 + where 154 + St: get_recently_played_state::State, 155 + St::Actor: get_recently_played_state::IsUnset, 156 + { 157 + /// Set the `actor` field (required) 158 + pub fn actor( 159 + mut self, 160 + value: impl Into<AtIdentifier<S>>, 161 + ) -> GetRecentlyPlayedBuilder<get_recently_played_state::SetActor<St>, S> { 162 + self._fields.0 = Option::Some(value.into()); 163 + GetRecentlyPlayedBuilder { 164 + _state: PhantomData, 165 + _fields: self._fields, 166 + _type: PhantomData, 167 + } 168 + } 169 + } 170 + 171 + impl<St: get_recently_played_state::State, S: BosStr> GetRecentlyPlayedBuilder<St, S> { 172 + /// Set the `cursor` field (optional) 173 + pub fn cursor(mut self, value: impl Into<Option<S>>) -> Self { 174 + self._fields.1 = value.into(); 175 + self 176 + } 177 + /// Set the `cursor` field to an Option value (optional) 178 + pub fn maybe_cursor(mut self, value: Option<S>) -> Self { 179 + self._fields.1 = value; 180 + self 181 + } 182 + } 183 + 184 + impl<St: get_recently_played_state::State, S: BosStr> GetRecentlyPlayedBuilder<St, S> { 185 + /// Set the `limit` field (optional) 186 + pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self { 187 + self._fields.2 = value.into(); 188 + self 189 + } 190 + /// Set the `limit` field to an Option value (optional) 191 + pub fn maybe_limit(mut self, value: Option<i64>) -> Self { 192 + self._fields.2 = value; 193 + self 194 + } 195 + } 196 + 197 + impl<St, S: BosStr> GetRecentlyPlayedBuilder<St, S> 198 + where 199 + St: get_recently_played_state::State, 200 + St::Actor: get_recently_played_state::IsSet, 201 + { 202 + /// Build the final struct. 203 + pub fn build(self) -> GetRecentlyPlayed<S> { 204 + GetRecentlyPlayed { 205 + actor: self._fields.0.unwrap(), 206 + cursor: self._fields.1, 207 + limit: self._fields.2, 208 + } 209 + } 210 + }
+261
cli/src/fm_atradio/get_stations.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.getStations 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::ident::AtIdentifier; 16 + use jacquard_common::types::value::Data; 17 + use jacquard_derive::IntoStatic; 18 + use serde::{Serialize, Deserialize}; 19 + use crate::fm_atradio::StationView; 20 + 21 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 22 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 23 + pub struct GetStations<S: BosStr = DefaultStr> { 24 + pub actor: AtIdentifier<S>, 25 + #[serde(skip_serializing_if = "Option::is_none")] 26 + pub cursor: Option<S>, 27 + /// (min: 1, max: 100) 28 + #[serde(skip_serializing_if = "Option::is_none")] 29 + pub limit: Option<i64>, 30 + #[serde(skip_serializing_if = "Option::is_none")] 31 + pub q: Option<S>, 32 + #[serde(skip_serializing_if = "Option::is_none")] 33 + pub sort: Option<S>, 34 + #[serde(skip_serializing_if = "Option::is_none")] 35 + pub source: Option<S>, 36 + } 37 + 38 + 39 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 40 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 41 + pub struct GetStationsOutput<S: BosStr = DefaultStr> { 42 + #[serde(skip_serializing_if = "Option::is_none")] 43 + pub cursor: Option<S>, 44 + pub items: Vec<StationView<S>>, 45 + ///Total number of records for the actor. 46 + pub total: i64, 47 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 48 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 49 + } 50 + 51 + /** Response marker for the `fm.atradio.getStations` query. 52 + 53 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `GetStationsOutput<S>` for this endpoint.*/ 54 + pub struct GetStationsResponse; 55 + impl jacquard_common::xrpc::XrpcResp for GetStationsResponse { 56 + const NSID: &'static str = "fm.atradio.getStations"; 57 + const ENCODING: &'static str = "application/json"; 58 + type Output<S: BosStr> = GetStationsOutput<S>; 59 + type Err = jacquard_common::xrpc::GenericError; 60 + } 61 + 62 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for GetStations<S> { 63 + const NSID: &'static str = "fm.atradio.getStations"; 64 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 65 + type Response = GetStationsResponse; 66 + } 67 + 68 + /** Endpoint marker for the `fm.atradio.getStations` query. 69 + 70 + Path: `/xrpc/fm.atradio.getStations`. The request payload type is `GetStations<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 71 + pub struct GetStationsRequest; 72 + impl jacquard_common::xrpc::XrpcEndpoint for GetStationsRequest { 73 + const PATH: &'static str = "/xrpc/fm.atradio.getStations"; 74 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 75 + type Request<S: BosStr> = GetStations<S>; 76 + type Response = GetStationsResponse; 77 + } 78 + 79 + pub mod get_stations_state { 80 + 81 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 82 + #[allow(unused)] 83 + use ::core::marker::PhantomData; 84 + mod sealed { 85 + pub trait Sealed {} 86 + } 87 + /// State trait tracking which required fields have been set 88 + pub trait State: sealed::Sealed { 89 + type Actor; 90 + } 91 + /// Empty state - all required fields are unset 92 + pub struct Empty(()); 93 + impl sealed::Sealed for Empty {} 94 + impl State for Empty { 95 + type Actor = Unset; 96 + } 97 + ///State transition - sets the `actor` field to Set 98 + pub struct SetActor<St: State = Empty>(PhantomData<fn() -> St>); 99 + impl<St: State> sealed::Sealed for SetActor<St> {} 100 + impl<St: State> State for SetActor<St> { 101 + type Actor = Set<members::actor>; 102 + } 103 + /// Marker types for field names 104 + #[allow(non_camel_case_types)] 105 + pub mod members { 106 + ///Marker type for the `actor` field 107 + pub struct actor(()); 108 + } 109 + } 110 + 111 + /// Builder for constructing an instance of this type. 112 + pub struct GetStationsBuilder<St: get_stations_state::State, S: BosStr = DefaultStr> { 113 + _state: PhantomData<fn() -> St>, 114 + _fields: ( 115 + Option<AtIdentifier<S>>, 116 + Option<S>, 117 + Option<i64>, 118 + Option<S>, 119 + Option<S>, 120 + Option<S>, 121 + ), 122 + _type: PhantomData<fn() -> S>, 123 + } 124 + 125 + impl GetStations<DefaultStr> { 126 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 127 + pub fn new() -> GetStationsBuilder<get_stations_state::Empty, DefaultStr> { 128 + GetStationsBuilder::new() 129 + } 130 + } 131 + 132 + impl<S: BosStr> GetStations<S> { 133 + /// Create a new builder for this type 134 + pub fn builder() -> GetStationsBuilder<get_stations_state::Empty, S> { 135 + GetStationsBuilder::builder() 136 + } 137 + } 138 + 139 + impl GetStationsBuilder<get_stations_state::Empty, DefaultStr> { 140 + /// Create a new builder with all fields unset, using the default string type, if needed 141 + pub fn new() -> Self { 142 + GetStationsBuilder { 143 + _state: PhantomData, 144 + _fields: (None, None, None, None, None, None), 145 + _type: PhantomData, 146 + } 147 + } 148 + } 149 + 150 + impl<S: BosStr> GetStationsBuilder<get_stations_state::Empty, S> { 151 + /// Create a new builder with all fields unset 152 + pub fn builder() -> Self { 153 + GetStationsBuilder { 154 + _state: PhantomData, 155 + _fields: (None, None, None, None, None, None), 156 + _type: PhantomData, 157 + } 158 + } 159 + } 160 + 161 + impl<St, S: BosStr> GetStationsBuilder<St, S> 162 + where 163 + St: get_stations_state::State, 164 + St::Actor: get_stations_state::IsUnset, 165 + { 166 + /// Set the `actor` field (required) 167 + pub fn actor( 168 + mut self, 169 + value: impl Into<AtIdentifier<S>>, 170 + ) -> GetStationsBuilder<get_stations_state::SetActor<St>, S> { 171 + self._fields.0 = Option::Some(value.into()); 172 + GetStationsBuilder { 173 + _state: PhantomData, 174 + _fields: self._fields, 175 + _type: PhantomData, 176 + } 177 + } 178 + } 179 + 180 + impl<St: get_stations_state::State, S: BosStr> GetStationsBuilder<St, S> { 181 + /// Set the `cursor` field (optional) 182 + pub fn cursor(mut self, value: impl Into<Option<S>>) -> Self { 183 + self._fields.1 = value.into(); 184 + self 185 + } 186 + /// Set the `cursor` field to an Option value (optional) 187 + pub fn maybe_cursor(mut self, value: Option<S>) -> Self { 188 + self._fields.1 = value; 189 + self 190 + } 191 + } 192 + 193 + impl<St: get_stations_state::State, S: BosStr> GetStationsBuilder<St, S> { 194 + /// Set the `limit` field (optional) 195 + pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self { 196 + self._fields.2 = value.into(); 197 + self 198 + } 199 + /// Set the `limit` field to an Option value (optional) 200 + pub fn maybe_limit(mut self, value: Option<i64>) -> Self { 201 + self._fields.2 = value; 202 + self 203 + } 204 + } 205 + 206 + impl<St: get_stations_state::State, S: BosStr> GetStationsBuilder<St, S> { 207 + /// Set the `q` field (optional) 208 + pub fn q(mut self, value: impl Into<Option<S>>) -> Self { 209 + self._fields.3 = value.into(); 210 + self 211 + } 212 + /// Set the `q` field to an Option value (optional) 213 + pub fn maybe_q(mut self, value: Option<S>) -> Self { 214 + self._fields.3 = value; 215 + self 216 + } 217 + } 218 + 219 + impl<St: get_stations_state::State, S: BosStr> GetStationsBuilder<St, S> { 220 + /// Set the `sort` field (optional) 221 + pub fn sort(mut self, value: impl Into<Option<S>>) -> Self { 222 + self._fields.4 = value.into(); 223 + self 224 + } 225 + /// Set the `sort` field to an Option value (optional) 226 + pub fn maybe_sort(mut self, value: Option<S>) -> Self { 227 + self._fields.4 = value; 228 + self 229 + } 230 + } 231 + 232 + impl<St: get_stations_state::State, S: BosStr> GetStationsBuilder<St, S> { 233 + /// Set the `source` field (optional) 234 + pub fn source(mut self, value: impl Into<Option<S>>) -> Self { 235 + self._fields.5 = value.into(); 236 + self 237 + } 238 + /// Set the `source` field to an Option value (optional) 239 + pub fn maybe_source(mut self, value: Option<S>) -> Self { 240 + self._fields.5 = value; 241 + self 242 + } 243 + } 244 + 245 + impl<St, S: BosStr> GetStationsBuilder<St, S> 246 + where 247 + St: get_stations_state::State, 248 + St::Actor: get_stations_state::IsSet, 249 + { 250 + /// Build the final struct. 251 + pub fn build(self) -> GetStations<S> { 252 + GetStations { 253 + actor: self._fields.0.unwrap(), 254 + cursor: self._fields.1, 255 + limit: self._fields.2, 256 + q: self._fields.3, 257 + sort: self._fields.4, 258 + source: self._fields.5, 259 + } 260 + } 261 + }
+381
cli/src/fm_atradio/reaction.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.reaction 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + 15 + #[allow(unused_imports)] 16 + use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 17 + use jacquard_common::deps::smol_str::SmolStr; 18 + use jacquard_common::types::collection::{Collection, RecordError}; 19 + use jacquard_common::types::string::{AtUri, Cid, Datetime}; 20 + use jacquard_common::types::uri::{RecordUri, UriError}; 21 + use jacquard_common::types::value::Data; 22 + use jacquard_common::xrpc::XrpcResp; 23 + use jacquard_derive::{IntoStatic, lexicon}; 24 + use jacquard_lexicon::lexicon::LexiconDoc; 25 + use jacquard_lexicon::schema::LexiconSchema; 26 + 27 + #[allow(unused_imports)] 28 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 29 + use serde::{Serialize, Deserialize}; 30 + use crate::fm_atradio::StationInfo; 31 + /// An ephemeral emoji reaction to a radio station. 32 + 33 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 34 + #[serde( 35 + rename_all = "camelCase", 36 + rename = "fm.atradio.reaction", 37 + tag = "$type", 38 + bound(deserialize = "S: Deserialize<'de> + BosStr") 39 + )] 40 + pub struct Reaction<S: BosStr = DefaultStr> { 41 + pub created_at: Datetime, 42 + ///A single emoji. 43 + pub emoji: S, 44 + pub station: StationInfo<S>, 45 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 46 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 47 + } 48 + 49 + /// Typed wrapper for GetRecord response with this collection's record type. 50 + 51 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 52 + #[serde(rename_all = "camelCase")] 53 + pub struct ReactionGetRecordOutput<S: BosStr = DefaultStr> { 54 + #[serde(skip_serializing_if = "Option::is_none")] 55 + pub cid: Option<Cid<S>>, 56 + pub uri: AtUri<S>, 57 + pub value: Reaction<S>, 58 + } 59 + 60 + impl<S: BosStr> Reaction<S> { 61 + pub fn uri(uri: S) -> Result<RecordUri<S, ReactionRecord>, UriError> { 62 + RecordUri::try_from_uri(AtUri::new(uri)?) 63 + } 64 + } 65 + 66 + /// Marker type for deserializing records from this collection. 67 + 68 + #[derive(Debug, Serialize, Deserialize)] 69 + pub struct ReactionRecord; 70 + impl XrpcResp for ReactionRecord { 71 + const NSID: &'static str = "fm.atradio.reaction"; 72 + const ENCODING: &'static str = "application/json"; 73 + type Output<S: BosStr> = ReactionGetRecordOutput<S>; 74 + type Err = RecordError; 75 + } 76 + 77 + impl<S: BosStr> From<ReactionGetRecordOutput<S>> for Reaction<S> { 78 + fn from(output: ReactionGetRecordOutput<S>) -> Self { 79 + output.value 80 + } 81 + } 82 + 83 + impl<S: BosStr> Collection for Reaction<S> { 84 + const NSID: &'static str = "fm.atradio.reaction"; 85 + type Record = ReactionRecord; 86 + } 87 + 88 + impl Collection for ReactionRecord { 89 + const NSID: &'static str = "fm.atradio.reaction"; 90 + type Record = ReactionRecord; 91 + } 92 + 93 + impl<S: BosStr> LexiconSchema for Reaction<S> { 94 + fn nsid() -> &'static str { 95 + "fm.atradio.reaction" 96 + } 97 + fn def_name() -> &'static str { 98 + "main" 99 + } 100 + fn lexicon_doc() -> LexiconDoc<'static> { 101 + lexicon_doc_fm_atradio_reaction() 102 + } 103 + fn validate(&self) -> Result<(), ConstraintError> { 104 + { 105 + let value = &self.emoji; 106 + #[allow(unused_comparisons)] 107 + if <str>::len(value.as_ref()) > 32usize { 108 + return Err(ConstraintError::MaxLength { 109 + path: ValidationPath::from_field("emoji"), 110 + max: 32usize, 111 + actual: <str>::len(value.as_ref()), 112 + }); 113 + } 114 + } 115 + { 116 + let value = &self.emoji; 117 + { 118 + let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count(); 119 + if count > 4usize { 120 + return Err(ConstraintError::MaxGraphemes { 121 + path: ValidationPath::from_field("emoji"), 122 + max: 4usize, 123 + actual: count, 124 + }); 125 + } 126 + } 127 + } 128 + Ok(()) 129 + } 130 + } 131 + 132 + pub mod reaction_state { 133 + 134 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 135 + #[allow(unused)] 136 + use ::core::marker::PhantomData; 137 + mod sealed { 138 + pub trait Sealed {} 139 + } 140 + /// State trait tracking which required fields have been set 141 + pub trait State: sealed::Sealed { 142 + type CreatedAt; 143 + type Emoji; 144 + type Station; 145 + } 146 + /// Empty state - all required fields are unset 147 + pub struct Empty(()); 148 + impl sealed::Sealed for Empty {} 149 + impl State for Empty { 150 + type CreatedAt = Unset; 151 + type Emoji = Unset; 152 + type Station = Unset; 153 + } 154 + ///State transition - sets the `created_at` field to Set 155 + pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>); 156 + impl<St: State> sealed::Sealed for SetCreatedAt<St> {} 157 + impl<St: State> State for SetCreatedAt<St> { 158 + type CreatedAt = Set<members::created_at>; 159 + type Emoji = St::Emoji; 160 + type Station = St::Station; 161 + } 162 + ///State transition - sets the `emoji` field to Set 163 + pub struct SetEmoji<St: State = Empty>(PhantomData<fn() -> St>); 164 + impl<St: State> sealed::Sealed for SetEmoji<St> {} 165 + impl<St: State> State for SetEmoji<St> { 166 + type CreatedAt = St::CreatedAt; 167 + type Emoji = Set<members::emoji>; 168 + type Station = St::Station; 169 + } 170 + ///State transition - sets the `station` field to Set 171 + pub struct SetStation<St: State = Empty>(PhantomData<fn() -> St>); 172 + impl<St: State> sealed::Sealed for SetStation<St> {} 173 + impl<St: State> State for SetStation<St> { 174 + type CreatedAt = St::CreatedAt; 175 + type Emoji = St::Emoji; 176 + type Station = Set<members::station>; 177 + } 178 + /// Marker types for field names 179 + #[allow(non_camel_case_types)] 180 + pub mod members { 181 + ///Marker type for the `created_at` field 182 + pub struct created_at(()); 183 + ///Marker type for the `emoji` field 184 + pub struct emoji(()); 185 + ///Marker type for the `station` field 186 + pub struct station(()); 187 + } 188 + } 189 + 190 + /// Builder for constructing an instance of this type. 191 + pub struct ReactionBuilder<St: reaction_state::State, S: BosStr = DefaultStr> { 192 + _state: PhantomData<fn() -> St>, 193 + _fields: (Option<Datetime>, Option<S>, Option<StationInfo<S>>), 194 + _type: PhantomData<fn() -> S>, 195 + } 196 + 197 + impl Reaction<DefaultStr> { 198 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 199 + pub fn new() -> ReactionBuilder<reaction_state::Empty, DefaultStr> { 200 + ReactionBuilder::new() 201 + } 202 + } 203 + 204 + impl<S: BosStr> Reaction<S> { 205 + /// Create a new builder for this type 206 + pub fn builder() -> ReactionBuilder<reaction_state::Empty, S> { 207 + ReactionBuilder::builder() 208 + } 209 + } 210 + 211 + impl ReactionBuilder<reaction_state::Empty, DefaultStr> { 212 + /// Create a new builder with all fields unset, using the default string type, if needed 213 + pub fn new() -> Self { 214 + ReactionBuilder { 215 + _state: PhantomData, 216 + _fields: (None, None, None), 217 + _type: PhantomData, 218 + } 219 + } 220 + } 221 + 222 + impl<S: BosStr> ReactionBuilder<reaction_state::Empty, S> { 223 + /// Create a new builder with all fields unset 224 + pub fn builder() -> Self { 225 + ReactionBuilder { 226 + _state: PhantomData, 227 + _fields: (None, None, None), 228 + _type: PhantomData, 229 + } 230 + } 231 + } 232 + 233 + impl<St, S: BosStr> ReactionBuilder<St, S> 234 + where 235 + St: reaction_state::State, 236 + St::CreatedAt: reaction_state::IsUnset, 237 + { 238 + /// Set the `createdAt` field (required) 239 + pub fn created_at( 240 + mut self, 241 + value: impl Into<Datetime>, 242 + ) -> ReactionBuilder<reaction_state::SetCreatedAt<St>, S> { 243 + self._fields.0 = Option::Some(value.into()); 244 + ReactionBuilder { 245 + _state: PhantomData, 246 + _fields: self._fields, 247 + _type: PhantomData, 248 + } 249 + } 250 + } 251 + 252 + impl<St, S: BosStr> ReactionBuilder<St, S> 253 + where 254 + St: reaction_state::State, 255 + St::Emoji: reaction_state::IsUnset, 256 + { 257 + /// Set the `emoji` field (required) 258 + pub fn emoji( 259 + mut self, 260 + value: impl Into<S>, 261 + ) -> ReactionBuilder<reaction_state::SetEmoji<St>, S> { 262 + self._fields.1 = Option::Some(value.into()); 263 + ReactionBuilder { 264 + _state: PhantomData, 265 + _fields: self._fields, 266 + _type: PhantomData, 267 + } 268 + } 269 + } 270 + 271 + impl<St, S: BosStr> ReactionBuilder<St, S> 272 + where 273 + St: reaction_state::State, 274 + St::Station: reaction_state::IsUnset, 275 + { 276 + /// Set the `station` field (required) 277 + pub fn station( 278 + mut self, 279 + value: impl Into<StationInfo<S>>, 280 + ) -> ReactionBuilder<reaction_state::SetStation<St>, S> { 281 + self._fields.2 = Option::Some(value.into()); 282 + ReactionBuilder { 283 + _state: PhantomData, 284 + _fields: self._fields, 285 + _type: PhantomData, 286 + } 287 + } 288 + } 289 + 290 + impl<St, S: BosStr> ReactionBuilder<St, S> 291 + where 292 + St: reaction_state::State, 293 + St::CreatedAt: reaction_state::IsSet, 294 + St::Emoji: reaction_state::IsSet, 295 + St::Station: reaction_state::IsSet, 296 + { 297 + /// Build the final struct. 298 + pub fn build(self) -> Reaction<S> { 299 + Reaction { 300 + created_at: self._fields.0.unwrap(), 301 + emoji: self._fields.1.unwrap(), 302 + station: self._fields.2.unwrap(), 303 + extra_data: Default::default(), 304 + } 305 + } 306 + /// Build the final struct with custom extra_data. 307 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Reaction<S> { 308 + Reaction { 309 + created_at: self._fields.0.unwrap(), 310 + emoji: self._fields.1.unwrap(), 311 + station: self._fields.2.unwrap(), 312 + extra_data: Some(extra_data), 313 + } 314 + } 315 + } 316 + 317 + fn lexicon_doc_fm_atradio_reaction() -> LexiconDoc<'static> { 318 + #[allow(unused_imports)] 319 + use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 320 + use jacquard_lexicon::lexicon::*; 321 + use alloc::collections::BTreeMap; 322 + LexiconDoc { 323 + lexicon: Lexicon::Lexicon1, 324 + id: CowStr::new_static("fm.atradio.reaction"), 325 + defs: { 326 + let mut map = BTreeMap::new(); 327 + map.insert( 328 + SmolStr::new_static("main"), 329 + LexUserType::Record(LexRecord { 330 + description: Some( 331 + CowStr::new_static( 332 + "An ephemeral emoji reaction to a radio station.", 333 + ), 334 + ), 335 + key: Some(CowStr::new_static("tid")), 336 + record: LexRecordRecord::Object(LexObject { 337 + required: Some( 338 + vec![ 339 + SmolStr::new_static("station"), 340 + SmolStr::new_static("emoji"), 341 + SmolStr::new_static("createdAt") 342 + ], 343 + ), 344 + properties: { 345 + #[allow(unused_mut)] 346 + let mut map = BTreeMap::new(); 347 + map.insert( 348 + SmolStr::new_static("createdAt"), 349 + LexObjectProperty::String(LexString { 350 + format: Some(LexStringFormat::Datetime), 351 + ..Default::default() 352 + }), 353 + ); 354 + map.insert( 355 + SmolStr::new_static("emoji"), 356 + LexObjectProperty::String(LexString { 357 + description: Some(CowStr::new_static("A single emoji.")), 358 + max_length: Some(32usize), 359 + max_graphemes: Some(4usize), 360 + ..Default::default() 361 + }), 362 + ); 363 + map.insert( 364 + SmolStr::new_static("station"), 365 + LexObjectProperty::Ref(LexRef { 366 + r#ref: CowStr::new_static("fm.atradio.defs#stationInfo"), 367 + ..Default::default() 368 + }), 369 + ); 370 + map 371 + }, 372 + ..Default::default() 373 + }), 374 + ..Default::default() 375 + }), 376 + ); 377 + map 378 + }, 379 + ..Default::default() 380 + } 381 + }
+531
cli/src/fm_atradio/station.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.station 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr}; 14 + 15 + #[allow(unused_imports)] 16 + use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 17 + use jacquard_common::deps::smol_str::SmolStr; 18 + use jacquard_common::types::collection::{Collection, RecordError}; 19 + use jacquard_common::types::string::{AtUri, Cid, Datetime, UriValue}; 20 + use jacquard_common::types::uri::{RecordUri, UriError}; 21 + use jacquard_common::types::value::Data; 22 + use jacquard_common::xrpc::XrpcResp; 23 + use jacquard_derive::{IntoStatic, lexicon}; 24 + use jacquard_lexicon::lexicon::LexiconDoc; 25 + use jacquard_lexicon::schema::LexiconSchema; 26 + 27 + #[allow(unused_imports)] 28 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 29 + use serde::{Serialize, Deserialize}; 30 + /// A user-defined radio station. 31 + 32 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 33 + #[serde( 34 + rename_all = "camelCase", 35 + rename = "fm.atradio.station", 36 + tag = "$type", 37 + bound(deserialize = "S: Deserialize<'de> + BosStr") 38 + )] 39 + pub struct Station<S: BosStr = DefaultStr> { 40 + pub created_at: Datetime, 41 + #[serde(skip_serializing_if = "Option::is_none")] 42 + pub description: Option<S>, 43 + #[serde(skip_serializing_if = "Option::is_none")] 44 + pub genre: Option<S>, 45 + #[serde(skip_serializing_if = "Option::is_none")] 46 + pub homepage: Option<UriValue<S>>, 47 + ///Logo/artwork URL. 48 + #[serde(skip_serializing_if = "Option::is_none")] 49 + pub logo: Option<UriValue<S>>, 50 + pub name: S, 51 + ///Direct, playable stream URL. 52 + pub stream_url: UriValue<S>, 53 + #[serde(skip_serializing_if = "Option::is_none")] 54 + pub tags: Option<Vec<S>>, 55 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 56 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 57 + } 58 + 59 + /// Typed wrapper for GetRecord response with this collection's record type. 60 + 61 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 62 + #[serde(rename_all = "camelCase")] 63 + pub struct StationGetRecordOutput<S: BosStr = DefaultStr> { 64 + #[serde(skip_serializing_if = "Option::is_none")] 65 + pub cid: Option<Cid<S>>, 66 + pub uri: AtUri<S>, 67 + pub value: Station<S>, 68 + } 69 + 70 + impl<S: BosStr> Station<S> { 71 + pub fn uri(uri: S) -> Result<RecordUri<S, StationRecord>, UriError> { 72 + RecordUri::try_from_uri(AtUri::new(uri)?) 73 + } 74 + } 75 + 76 + /// Marker type for deserializing records from this collection. 77 + 78 + #[derive(Debug, Serialize, Deserialize)] 79 + pub struct StationRecord; 80 + impl XrpcResp for StationRecord { 81 + const NSID: &'static str = "fm.atradio.station"; 82 + const ENCODING: &'static str = "application/json"; 83 + type Output<S: BosStr> = StationGetRecordOutput<S>; 84 + type Err = RecordError; 85 + } 86 + 87 + impl<S: BosStr> From<StationGetRecordOutput<S>> for Station<S> { 88 + fn from(output: StationGetRecordOutput<S>) -> Self { 89 + output.value 90 + } 91 + } 92 + 93 + impl<S: BosStr> Collection for Station<S> { 94 + const NSID: &'static str = "fm.atradio.station"; 95 + type Record = StationRecord; 96 + } 97 + 98 + impl Collection for StationRecord { 99 + const NSID: &'static str = "fm.atradio.station"; 100 + type Record = StationRecord; 101 + } 102 + 103 + impl<S: BosStr> LexiconSchema for Station<S> { 104 + fn nsid() -> &'static str { 105 + "fm.atradio.station" 106 + } 107 + fn def_name() -> &'static str { 108 + "main" 109 + } 110 + fn lexicon_doc() -> LexiconDoc<'static> { 111 + lexicon_doc_fm_atradio_station() 112 + } 113 + fn validate(&self) -> Result<(), ConstraintError> { 114 + if let Some(ref value) = self.description { 115 + #[allow(unused_comparisons)] 116 + if <str>::len(value.as_ref()) > 2000usize { 117 + return Err(ConstraintError::MaxLength { 118 + path: ValidationPath::from_field("description"), 119 + max: 2000usize, 120 + actual: <str>::len(value.as_ref()), 121 + }); 122 + } 123 + } 124 + if let Some(ref value) = self.genre { 125 + #[allow(unused_comparisons)] 126 + if <str>::len(value.as_ref()) > 256usize { 127 + return Err(ConstraintError::MaxLength { 128 + path: ValidationPath::from_field("genre"), 129 + max: 256usize, 130 + actual: <str>::len(value.as_ref()), 131 + }); 132 + } 133 + } 134 + { 135 + let value = &self.name; 136 + #[allow(unused_comparisons)] 137 + if <str>::len(value.as_ref()) > 512usize { 138 + return Err(ConstraintError::MaxLength { 139 + path: ValidationPath::from_field("name"), 140 + max: 512usize, 141 + actual: <str>::len(value.as_ref()), 142 + }); 143 + } 144 + } 145 + { 146 + let value = &self.name; 147 + #[allow(unused_comparisons)] 148 + if <str>::len(value.as_ref()) < 1usize { 149 + return Err(ConstraintError::MinLength { 150 + path: ValidationPath::from_field("name"), 151 + min: 1usize, 152 + actual: <str>::len(value.as_ref()), 153 + }); 154 + } 155 + } 156 + Ok(()) 157 + } 158 + } 159 + 160 + pub mod station_state { 161 + 162 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 163 + #[allow(unused)] 164 + use ::core::marker::PhantomData; 165 + mod sealed { 166 + pub trait Sealed {} 167 + } 168 + /// State trait tracking which required fields have been set 169 + pub trait State: sealed::Sealed { 170 + type CreatedAt; 171 + type Name; 172 + type StreamUrl; 173 + } 174 + /// Empty state - all required fields are unset 175 + pub struct Empty(()); 176 + impl sealed::Sealed for Empty {} 177 + impl State for Empty { 178 + type CreatedAt = Unset; 179 + type Name = Unset; 180 + type StreamUrl = Unset; 181 + } 182 + ///State transition - sets the `created_at` field to Set 183 + pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>); 184 + impl<St: State> sealed::Sealed for SetCreatedAt<St> {} 185 + impl<St: State> State for SetCreatedAt<St> { 186 + type CreatedAt = Set<members::created_at>; 187 + type Name = St::Name; 188 + type StreamUrl = St::StreamUrl; 189 + } 190 + ///State transition - sets the `name` field to Set 191 + pub struct SetName<St: State = Empty>(PhantomData<fn() -> St>); 192 + impl<St: State> sealed::Sealed for SetName<St> {} 193 + impl<St: State> State for SetName<St> { 194 + type CreatedAt = St::CreatedAt; 195 + type Name = Set<members::name>; 196 + type StreamUrl = St::StreamUrl; 197 + } 198 + ///State transition - sets the `stream_url` field to Set 199 + pub struct SetStreamUrl<St: State = Empty>(PhantomData<fn() -> St>); 200 + impl<St: State> sealed::Sealed for SetStreamUrl<St> {} 201 + impl<St: State> State for SetStreamUrl<St> { 202 + type CreatedAt = St::CreatedAt; 203 + type Name = St::Name; 204 + type StreamUrl = Set<members::stream_url>; 205 + } 206 + /// Marker types for field names 207 + #[allow(non_camel_case_types)] 208 + pub mod members { 209 + ///Marker type for the `created_at` field 210 + pub struct created_at(()); 211 + ///Marker type for the `name` field 212 + pub struct name(()); 213 + ///Marker type for the `stream_url` field 214 + pub struct stream_url(()); 215 + } 216 + } 217 + 218 + /// Builder for constructing an instance of this type. 219 + pub struct StationBuilder<St: station_state::State, S: BosStr = DefaultStr> { 220 + _state: PhantomData<fn() -> St>, 221 + _fields: ( 222 + Option<Datetime>, 223 + Option<S>, 224 + Option<S>, 225 + Option<UriValue<S>>, 226 + Option<UriValue<S>>, 227 + Option<S>, 228 + Option<UriValue<S>>, 229 + Option<Vec<S>>, 230 + ), 231 + _type: PhantomData<fn() -> S>, 232 + } 233 + 234 + impl Station<DefaultStr> { 235 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 236 + pub fn new() -> StationBuilder<station_state::Empty, DefaultStr> { 237 + StationBuilder::new() 238 + } 239 + } 240 + 241 + impl<S: BosStr> Station<S> { 242 + /// Create a new builder for this type 243 + pub fn builder() -> StationBuilder<station_state::Empty, S> { 244 + StationBuilder::builder() 245 + } 246 + } 247 + 248 + impl StationBuilder<station_state::Empty, DefaultStr> { 249 + /// Create a new builder with all fields unset, using the default string type, if needed 250 + pub fn new() -> Self { 251 + StationBuilder { 252 + _state: PhantomData, 253 + _fields: (None, None, None, None, None, None, None, None), 254 + _type: PhantomData, 255 + } 256 + } 257 + } 258 + 259 + impl<S: BosStr> StationBuilder<station_state::Empty, S> { 260 + /// Create a new builder with all fields unset 261 + pub fn builder() -> Self { 262 + StationBuilder { 263 + _state: PhantomData, 264 + _fields: (None, None, None, None, None, None, None, None), 265 + _type: PhantomData, 266 + } 267 + } 268 + } 269 + 270 + impl<St, S: BosStr> StationBuilder<St, S> 271 + where 272 + St: station_state::State, 273 + St::CreatedAt: station_state::IsUnset, 274 + { 275 + /// Set the `createdAt` field (required) 276 + pub fn created_at( 277 + mut self, 278 + value: impl Into<Datetime>, 279 + ) -> StationBuilder<station_state::SetCreatedAt<St>, S> { 280 + self._fields.0 = Option::Some(value.into()); 281 + StationBuilder { 282 + _state: PhantomData, 283 + _fields: self._fields, 284 + _type: PhantomData, 285 + } 286 + } 287 + } 288 + 289 + impl<St: station_state::State, S: BosStr> StationBuilder<St, S> { 290 + /// Set the `description` field (optional) 291 + pub fn description(mut self, value: impl Into<Option<S>>) -> Self { 292 + self._fields.1 = value.into(); 293 + self 294 + } 295 + /// Set the `description` field to an Option value (optional) 296 + pub fn maybe_description(mut self, value: Option<S>) -> Self { 297 + self._fields.1 = value; 298 + self 299 + } 300 + } 301 + 302 + impl<St: station_state::State, S: BosStr> StationBuilder<St, S> { 303 + /// Set the `genre` field (optional) 304 + pub fn genre(mut self, value: impl Into<Option<S>>) -> Self { 305 + self._fields.2 = value.into(); 306 + self 307 + } 308 + /// Set the `genre` field to an Option value (optional) 309 + pub fn maybe_genre(mut self, value: Option<S>) -> Self { 310 + self._fields.2 = value; 311 + self 312 + } 313 + } 314 + 315 + impl<St: station_state::State, S: BosStr> StationBuilder<St, S> { 316 + /// Set the `homepage` field (optional) 317 + pub fn homepage(mut self, value: impl Into<Option<UriValue<S>>>) -> Self { 318 + self._fields.3 = value.into(); 319 + self 320 + } 321 + /// Set the `homepage` field to an Option value (optional) 322 + pub fn maybe_homepage(mut self, value: Option<UriValue<S>>) -> Self { 323 + self._fields.3 = value; 324 + self 325 + } 326 + } 327 + 328 + impl<St: station_state::State, S: BosStr> StationBuilder<St, S> { 329 + /// Set the `logo` field (optional) 330 + pub fn logo(mut self, value: impl Into<Option<UriValue<S>>>) -> Self { 331 + self._fields.4 = value.into(); 332 + self 333 + } 334 + /// Set the `logo` field to an Option value (optional) 335 + pub fn maybe_logo(mut self, value: Option<UriValue<S>>) -> Self { 336 + self._fields.4 = value; 337 + self 338 + } 339 + } 340 + 341 + impl<St, S: BosStr> StationBuilder<St, S> 342 + where 343 + St: station_state::State, 344 + St::Name: station_state::IsUnset, 345 + { 346 + /// Set the `name` field (required) 347 + pub fn name( 348 + mut self, 349 + value: impl Into<S>, 350 + ) -> StationBuilder<station_state::SetName<St>, S> { 351 + self._fields.5 = Option::Some(value.into()); 352 + StationBuilder { 353 + _state: PhantomData, 354 + _fields: self._fields, 355 + _type: PhantomData, 356 + } 357 + } 358 + } 359 + 360 + impl<St, S: BosStr> StationBuilder<St, S> 361 + where 362 + St: station_state::State, 363 + St::StreamUrl: station_state::IsUnset, 364 + { 365 + /// Set the `streamUrl` field (required) 366 + pub fn stream_url( 367 + mut self, 368 + value: impl Into<UriValue<S>>, 369 + ) -> StationBuilder<station_state::SetStreamUrl<St>, S> { 370 + self._fields.6 = Option::Some(value.into()); 371 + StationBuilder { 372 + _state: PhantomData, 373 + _fields: self._fields, 374 + _type: PhantomData, 375 + } 376 + } 377 + } 378 + 379 + impl<St: station_state::State, S: BosStr> StationBuilder<St, S> { 380 + /// Set the `tags` field (optional) 381 + pub fn tags(mut self, value: impl Into<Option<Vec<S>>>) -> Self { 382 + self._fields.7 = value.into(); 383 + self 384 + } 385 + /// Set the `tags` field to an Option value (optional) 386 + pub fn maybe_tags(mut self, value: Option<Vec<S>>) -> Self { 387 + self._fields.7 = value; 388 + self 389 + } 390 + } 391 + 392 + impl<St, S: BosStr> StationBuilder<St, S> 393 + where 394 + St: station_state::State, 395 + St::CreatedAt: station_state::IsSet, 396 + St::Name: station_state::IsSet, 397 + St::StreamUrl: station_state::IsSet, 398 + { 399 + /// Build the final struct. 400 + pub fn build(self) -> Station<S> { 401 + Station { 402 + created_at: self._fields.0.unwrap(), 403 + description: self._fields.1, 404 + genre: self._fields.2, 405 + homepage: self._fields.3, 406 + logo: self._fields.4, 407 + name: self._fields.5.unwrap(), 408 + stream_url: self._fields.6.unwrap(), 409 + tags: self._fields.7, 410 + extra_data: Default::default(), 411 + } 412 + } 413 + /// Build the final struct with custom extra_data. 414 + pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> Station<S> { 415 + Station { 416 + created_at: self._fields.0.unwrap(), 417 + description: self._fields.1, 418 + genre: self._fields.2, 419 + homepage: self._fields.3, 420 + logo: self._fields.4, 421 + name: self._fields.5.unwrap(), 422 + stream_url: self._fields.6.unwrap(), 423 + tags: self._fields.7, 424 + extra_data: Some(extra_data), 425 + } 426 + } 427 + } 428 + 429 + fn lexicon_doc_fm_atradio_station() -> LexiconDoc<'static> { 430 + #[allow(unused_imports)] 431 + use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 432 + use jacquard_lexicon::lexicon::*; 433 + use alloc::collections::BTreeMap; 434 + LexiconDoc { 435 + lexicon: Lexicon::Lexicon1, 436 + id: CowStr::new_static("fm.atradio.station"), 437 + defs: { 438 + let mut map = BTreeMap::new(); 439 + map.insert( 440 + SmolStr::new_static("main"), 441 + LexUserType::Record(LexRecord { 442 + description: Some( 443 + CowStr::new_static("A user-defined radio station."), 444 + ), 445 + key: Some(CowStr::new_static("tid")), 446 + record: LexRecordRecord::Object(LexObject { 447 + required: Some( 448 + vec![ 449 + SmolStr::new_static("name"), 450 + SmolStr::new_static("streamUrl"), 451 + SmolStr::new_static("createdAt") 452 + ], 453 + ), 454 + properties: { 455 + #[allow(unused_mut)] 456 + let mut map = BTreeMap::new(); 457 + map.insert( 458 + SmolStr::new_static("createdAt"), 459 + LexObjectProperty::String(LexString { 460 + format: Some(LexStringFormat::Datetime), 461 + ..Default::default() 462 + }), 463 + ); 464 + map.insert( 465 + SmolStr::new_static("description"), 466 + LexObjectProperty::String(LexString { 467 + max_length: Some(2000usize), 468 + ..Default::default() 469 + }), 470 + ); 471 + map.insert( 472 + SmolStr::new_static("genre"), 473 + LexObjectProperty::String(LexString { 474 + max_length: Some(256usize), 475 + ..Default::default() 476 + }), 477 + ); 478 + map.insert( 479 + SmolStr::new_static("homepage"), 480 + LexObjectProperty::String(LexString { 481 + format: Some(LexStringFormat::Uri), 482 + ..Default::default() 483 + }), 484 + ); 485 + map.insert( 486 + SmolStr::new_static("logo"), 487 + LexObjectProperty::String(LexString { 488 + description: Some(CowStr::new_static("Logo/artwork URL.")), 489 + format: Some(LexStringFormat::Uri), 490 + ..Default::default() 491 + }), 492 + ); 493 + map.insert( 494 + SmolStr::new_static("name"), 495 + LexObjectProperty::String(LexString { 496 + min_length: Some(1usize), 497 + max_length: Some(512usize), 498 + ..Default::default() 499 + }), 500 + ); 501 + map.insert( 502 + SmolStr::new_static("streamUrl"), 503 + LexObjectProperty::String(LexString { 504 + description: Some( 505 + CowStr::new_static("Direct, playable stream URL."), 506 + ), 507 + format: Some(LexStringFormat::Uri), 508 + ..Default::default() 509 + }), 510 + ); 511 + map.insert( 512 + SmolStr::new_static("tags"), 513 + LexObjectProperty::Array(LexArray { 514 + items: LexArrayItem::String(LexString { 515 + ..Default::default() 516 + }), 517 + ..Default::default() 518 + }), 519 + ); 520 + map 521 + }, 522 + ..Default::default() 523 + }), 524 + ..Default::default() 525 + }), 526 + ); 527 + map 528 + }, 529 + ..Default::default() 530 + } 531 + }
+205
cli/src/fm_atradio/update_seen.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: fm.atradio.updateSeen 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[allow(unused_imports)] 9 + use alloc::collections::BTreeMap; 10 + 11 + #[allow(unused_imports)] 12 + use core::marker::PhantomData; 13 + use jacquard_common::{BosStr, DefaultStr, FromStaticStr}; 14 + use jacquard_common::deps::smol_str::SmolStr; 15 + use jacquard_common::types::ident::AtIdentifier; 16 + use jacquard_common::types::string::Datetime; 17 + use jacquard_common::types::value::Data; 18 + use jacquard_derive::IntoStatic; 19 + use serde::{Serialize, Deserialize}; 20 + 21 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 22 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 23 + pub struct UpdateSeen<S: BosStr = DefaultStr> { 24 + ///DID or handle of the recipient. 25 + pub actor: AtIdentifier<S>, 26 + ///Time to mark seen up to (defaults to now). 27 + #[serde(skip_serializing_if = "Option::is_none")] 28 + pub seen_at: Option<Datetime>, 29 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 30 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 31 + } 32 + 33 + 34 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 35 + #[serde(rename_all = "camelCase", bound(deserialize = "S: Deserialize<'de> + BosStr"))] 36 + pub struct UpdateSeenOutput<S: BosStr = DefaultStr> { 37 + pub unread_count: i64, 38 + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] 39 + pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>, 40 + } 41 + 42 + /** Response marker for the `fm.atradio.updateSeen` procedure. 43 + 44 + Implements `jacquard_common::xrpc::XrpcResp`; successful bodies decode as `Self::Output<S>`, which is `UpdateSeenOutput<S>` for this endpoint.*/ 45 + pub struct UpdateSeenResponse; 46 + impl jacquard_common::xrpc::XrpcResp for UpdateSeenResponse { 47 + const NSID: &'static str = "fm.atradio.updateSeen"; 48 + const ENCODING: &'static str = "application/json"; 49 + type Output<S: BosStr> = UpdateSeenOutput<S>; 50 + type Err = jacquard_common::xrpc::GenericError; 51 + } 52 + 53 + impl<S: BosStr> jacquard_common::xrpc::XrpcRequest for UpdateSeen<S> { 54 + const NSID: &'static str = "fm.atradio.updateSeen"; 55 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Procedure( 56 + "application/json", 57 + ); 58 + type Response = UpdateSeenResponse; 59 + } 60 + 61 + /** Endpoint marker for the `fm.atradio.updateSeen` procedure. 62 + 63 + Path: `/xrpc/fm.atradio.updateSeen`. The request payload type is `UpdateSeen<S>`; send that request with `jacquard::Client` or use this marker through lower-level `XrpcEndpoint` APIs.*/ 64 + pub struct UpdateSeenRequest; 65 + impl jacquard_common::xrpc::XrpcEndpoint for UpdateSeenRequest { 66 + const PATH: &'static str = "/xrpc/fm.atradio.updateSeen"; 67 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Procedure( 68 + "application/json", 69 + ); 70 + type Request<S: BosStr> = UpdateSeen<S>; 71 + type Response = UpdateSeenResponse; 72 + } 73 + 74 + pub mod update_seen_state { 75 + 76 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 77 + #[allow(unused)] 78 + use ::core::marker::PhantomData; 79 + mod sealed { 80 + pub trait Sealed {} 81 + } 82 + /// State trait tracking which required fields have been set 83 + pub trait State: sealed::Sealed { 84 + type Actor; 85 + } 86 + /// Empty state - all required fields are unset 87 + pub struct Empty(()); 88 + impl sealed::Sealed for Empty {} 89 + impl State for Empty { 90 + type Actor = Unset; 91 + } 92 + ///State transition - sets the `actor` field to Set 93 + pub struct SetActor<St: State = Empty>(PhantomData<fn() -> St>); 94 + impl<St: State> sealed::Sealed for SetActor<St> {} 95 + impl<St: State> State for SetActor<St> { 96 + type Actor = Set<members::actor>; 97 + } 98 + /// Marker types for field names 99 + #[allow(non_camel_case_types)] 100 + pub mod members { 101 + ///Marker type for the `actor` field 102 + pub struct actor(()); 103 + } 104 + } 105 + 106 + /// Builder for constructing an instance of this type. 107 + pub struct UpdateSeenBuilder<St: update_seen_state::State, S: BosStr = DefaultStr> { 108 + _state: PhantomData<fn() -> St>, 109 + _fields: (Option<AtIdentifier<S>>, Option<Datetime>), 110 + _type: PhantomData<fn() -> S>, 111 + } 112 + 113 + impl UpdateSeen<DefaultStr> { 114 + /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed 115 + pub fn new() -> UpdateSeenBuilder<update_seen_state::Empty, DefaultStr> { 116 + UpdateSeenBuilder::new() 117 + } 118 + } 119 + 120 + impl<S: BosStr> UpdateSeen<S> { 121 + /// Create a new builder for this type 122 + pub fn builder() -> UpdateSeenBuilder<update_seen_state::Empty, S> { 123 + UpdateSeenBuilder::builder() 124 + } 125 + } 126 + 127 + impl UpdateSeenBuilder<update_seen_state::Empty, DefaultStr> { 128 + /// Create a new builder with all fields unset, using the default string type, if needed 129 + pub fn new() -> Self { 130 + UpdateSeenBuilder { 131 + _state: PhantomData, 132 + _fields: (None, None), 133 + _type: PhantomData, 134 + } 135 + } 136 + } 137 + 138 + impl<S: BosStr> UpdateSeenBuilder<update_seen_state::Empty, S> { 139 + /// Create a new builder with all fields unset 140 + pub fn builder() -> Self { 141 + UpdateSeenBuilder { 142 + _state: PhantomData, 143 + _fields: (None, None), 144 + _type: PhantomData, 145 + } 146 + } 147 + } 148 + 149 + impl<St, S: BosStr> UpdateSeenBuilder<St, S> 150 + where 151 + St: update_seen_state::State, 152 + St::Actor: update_seen_state::IsUnset, 153 + { 154 + /// Set the `actor` field (required) 155 + pub fn actor( 156 + mut self, 157 + value: impl Into<AtIdentifier<S>>, 158 + ) -> UpdateSeenBuilder<update_seen_state::SetActor<St>, S> { 159 + self._fields.0 = Option::Some(value.into()); 160 + UpdateSeenBuilder { 161 + _state: PhantomData, 162 + _fields: self._fields, 163 + _type: PhantomData, 164 + } 165 + } 166 + } 167 + 168 + impl<St: update_seen_state::State, S: BosStr> UpdateSeenBuilder<St, S> { 169 + /// Set the `seenAt` field (optional) 170 + pub fn seen_at(mut self, value: impl Into<Option<Datetime>>) -> Self { 171 + self._fields.1 = value.into(); 172 + self 173 + } 174 + /// Set the `seenAt` field to an Option value (optional) 175 + pub fn maybe_seen_at(mut self, value: Option<Datetime>) -> Self { 176 + self._fields.1 = value; 177 + self 178 + } 179 + } 180 + 181 + impl<St, S: BosStr> UpdateSeenBuilder<St, S> 182 + where 183 + St: update_seen_state::State, 184 + St::Actor: update_seen_state::IsSet, 185 + { 186 + /// Build the final struct. 187 + pub fn build(self) -> UpdateSeen<S> { 188 + UpdateSeen { 189 + actor: self._fields.0.unwrap(), 190 + seen_at: self._fields.1, 191 + extra_data: Default::default(), 192 + } 193 + } 194 + /// Build the final struct with custom extra_data. 195 + pub fn build_with_data( 196 + self, 197 + extra_data: BTreeMap<SmolStr, Data<S>>, 198 + ) -> UpdateSeen<S> { 199 + UpdateSeen { 200 + actor: self._fields.0.unwrap(), 201 + seen_at: self._fields.1, 202 + extra_data: Some(extra_data), 203 + } 204 + } 205 + }
+31 -2
cli/src/main.rs
··· 1 - fn main() { 2 - println!("Hello, world!"); 1 + extern crate alloc; 2 + 3 + // --- generated from packages/lexicons/lexicons/atradio (see scripts/gen-lexicons.sh) --- 4 + #[allow(unused_imports, dead_code, clippy::all)] 5 + pub mod builder_types; 6 + #[cfg(feature = "fm_atradio")] 7 + #[allow(unused_imports, dead_code, clippy::all)] 8 + pub mod fm_atradio; 9 + 10 + // --- hand-written app modules --- 11 + mod appview; 12 + mod atproto; 13 + mod cli; 14 + mod config; 15 + #[cfg(target_os = "linux")] 16 + mod mpris; 17 + mod player; 18 + mod radio; 19 + mod settings; 20 + mod theme; 21 + mod tui; 22 + 23 + use clap::Parser; 24 + 25 + #[tokio::main] 26 + async fn main() { 27 + let args = cli::Cli::parse(); 28 + if let Err(err) = cli::run(args).await { 29 + eprintln!("\x1b[31merror:\x1b[0m {err:#}"); 30 + std::process::exit(1); 31 + } 3 32 }
+109
cli/src/mpris.rs
··· 1 + //! MPRIS (org.mpris.MediaPlayer2) D-Bus integration — Linux only. 2 + //! 3 + //! Exposes the running player to the desktop so media keys, GNOME/KDE panels, 4 + //! `playerctl`, etc. can see "now playing" and drive play/pause/stop/volume. 5 + //! 6 + //! The `mpris_server::Player` is single-threaded (`!Send`), so it runs on its 7 + //! own OS thread with a current-thread Tokio runtime + `LocalSet`. It shares 8 + //! the audio engine through an `Arc<crate::player::Player>` (which is 9 + //! `Send + Sync`), polling it once a second to publish metadata + status. 10 + 11 + use std::sync::Arc; 12 + use std::time::Duration; 13 + 14 + use mpris_server::{Metadata, PlaybackStatus, Player as MprisPlayer, Time}; 15 + 16 + use crate::player::{Player, State}; 17 + 18 + /// Start the MPRIS server in the background. Best-effort: any failure (e.g. no 19 + /// session bus) is logged to stderr and otherwise ignored. 20 + pub fn spawn(player: Arc<Player>) { 21 + std::thread::Builder::new() 22 + .name("atradio-mpris".into()) 23 + .spawn(move || { 24 + let rt = match tokio::runtime::Builder::new_current_thread() 25 + .enable_all() 26 + .build() 27 + { 28 + Ok(rt) => rt, 29 + Err(e) => { 30 + eprintln!("mpris: runtime: {e}"); 31 + return; 32 + } 33 + }; 34 + let local = tokio::task::LocalSet::new(); 35 + local.block_on(&rt, serve(player)); 36 + }) 37 + .ok(); 38 + } 39 + 40 + async fn serve(player: Arc<Player>) { 41 + let mpris = match MprisPlayer::builder("fm.atradio") 42 + .identity("atradio.fm") 43 + .can_play(true) 44 + .can_pause(true) 45 + .can_control(true) 46 + .can_go_next(false) 47 + .can_go_previous(false) 48 + .can_seek(false) 49 + .build() 50 + .await 51 + { 52 + Ok(p) => p, 53 + Err(e) => { 54 + eprintln!("mpris: no session bus ({e}); desktop integration off"); 55 + return; 56 + } 57 + }; 58 + 59 + // Wire desktop controls back to the engine. 60 + let p = player.clone(); 61 + mpris.connect_play_pause(move |_| p.toggle()); 62 + let p = player.clone(); 63 + mpris.connect_play(move |_| p.play()); 64 + let p = player.clone(); 65 + mpris.connect_pause(move |_| p.toggle()); 66 + let p = player.clone(); 67 + mpris.connect_stop(move |_| p.stop()); 68 + let p = player.clone(); 69 + mpris.connect_set_volume(move |_, v| p.set_volume(v as f32)); 70 + 71 + tokio::task::spawn_local(mpris.run()); 72 + 73 + // Publish state + metadata on change. 74 + let mut last_state: Option<State> = None; 75 + let mut last_line = String::new(); 76 + loop { 77 + tokio::time::sleep(Duration::from_secs(1)).await; 78 + let np = player.now_playing(); 79 + 80 + if last_state != Some(np.state) { 81 + let status = match np.state { 82 + State::Playing => PlaybackStatus::Playing, 83 + State::Paused => PlaybackStatus::Paused, 84 + State::Stopped => PlaybackStatus::Stopped, 85 + }; 86 + let _ = mpris.set_playback_status(status).await; 87 + last_state = Some(np.state); 88 + } 89 + 90 + let line = np.line().unwrap_or_default(); 91 + if line != last_line { 92 + let mut b = Metadata::builder(); 93 + if !np.title.trim().is_empty() { 94 + b = b.title(np.title.clone()); 95 + } else if !line.is_empty() { 96 + b = b.title(line.clone()); 97 + } 98 + if !np.artist.trim().is_empty() { 99 + b = b.artist([np.artist.clone()]); 100 + } 101 + if !np.station.trim().is_empty() { 102 + b = b.album(np.station.clone()); 103 + } 104 + b = b.length(Time::from_secs(0)); // live stream: unknown length 105 + let _ = mpris.set_metadata(b.build()).await; 106 + last_line = line; 107 + } 108 + } 109 + }
+168
cli/src/player/dsp.rs
··· 1 + //! The Rockbox DSP chain, modelled to match the web app's audio settings 2 + //! (`apps/web/src/atoms/audioSettings.ts` + `fm.atradio.audio.settings`). 3 + 4 + use rockbox_playback::{ 5 + BassEnhancement, ChannelMode, Compressor, Crossfeed, CrossfeedMode, EqBand, Equalizer, Player, 6 + Surround, ToneControls, EQ_BAND_FREQUENCIES, EQ_BANDS, 7 + }; 8 + 9 + /// Band centre frequencies (Hz), from the engine: 32 … 16 000. 10 + pub const EQ_FREQS: [i32; EQ_BANDS] = EQ_BAND_FREQUENCIES; 11 + 12 + /// The full user-facing DSP state. Defaults mirror `DEFAULT_AUDIO_SETTINGS`. 13 + #[derive(Clone, Debug)] 14 + pub struct AudioSettings { 15 + pub eq_enabled: bool, 16 + /// Per-band gain in dB (−24 … 24), one per [`EQ_BANDS`]. 17 + pub eq_gains: [f32; EQ_BANDS], 18 + pub bass: i32, // dB 19 + pub treble: i32, // dB 20 + pub crossfeed_mode: CrossfeedMode, 21 + pub crossfeed_direct: f32, // dB (≤ 0) 22 + pub pbe: i32, // 0 … 100 % 23 + pub pbe_precut: i32, // dB cut (≥ 0) 24 + pub surround_delay: i32, // ms (0 = off) 25 + pub surround_balance: i32, // % 26 + pub comp_threshold: i32, // dB (0 = off) 27 + pub comp_ratio: i32, // 2 … 10 28 + pub channel_mode: ChannelMode, 29 + pub stereo_width: i32, // % 30 + } 31 + 32 + impl Default for AudioSettings { 33 + fn default() -> Self { 34 + Self { 35 + eq_enabled: false, 36 + eq_gains: [0.0; EQ_BANDS], 37 + bass: 0, 38 + treble: 0, 39 + crossfeed_mode: CrossfeedMode::Off, 40 + crossfeed_direct: -1.5, 41 + pbe: 0, 42 + pbe_precut: 0, 43 + surround_delay: 0, 44 + surround_balance: 35, 45 + comp_threshold: 0, 46 + comp_ratio: 2, 47 + channel_mode: ChannelMode::Stereo, 48 + stereo_width: 100, 49 + } 50 + } 51 + } 52 + 53 + impl AudioSettings { 54 + /// Push the whole chain to the engine. Safe to call live. 55 + pub fn apply(&self, p: &Player) { 56 + // Equalizer. 57 + p.set_eq_enabled(self.eq_enabled); 58 + for (i, &gain) in self.eq_gains.iter().enumerate() { 59 + p.set_eq_band( 60 + i, 61 + EqBand { 62 + cutoff_hz: EQ_FREQS[i], 63 + q: 1.0, 64 + gain_db: gain, 65 + }, 66 + ); 67 + } 68 + // Tone. 69 + p.set_tone(ToneControls { 70 + bass_db: self.bass, 71 + treble_db: self.treble, 72 + bass_cutoff_hz: 0, 73 + treble_cutoff_hz: 0, 74 + }); 75 + // Crossfeed (direct gain is in tenths of a dB). 76 + p.set_crossfeed(Crossfeed { 77 + mode: self.crossfeed_mode, 78 + direct_gain: (self.crossfeed_direct * 10.0).round() as i32, 79 + ..Default::default() 80 + }); 81 + // Perceptual bass enhancement (precut is tenths of a dB, ≤ 0). 82 + p.set_bass_enhancement(BassEnhancement { 83 + strength: self.pbe, 84 + precut: -(self.pbe_precut * 10), 85 + }); 86 + // Haas surround. 87 + p.set_surround(Surround { 88 + delay_ms: self.surround_delay, 89 + balance: self.surround_balance, 90 + cutoff_low_hz: 0, 91 + cutoff_high_hz: 0, 92 + }); 93 + // Compressor. 94 + p.set_compressor(Compressor { 95 + threshold_db: self.comp_threshold, 96 + makeup_gain: 1, 97 + ratio: ratio_index(self.comp_ratio), 98 + knee: 2, 99 + attack_ms: 5, 100 + release_ms: 200, 101 + }); 102 + // Channel / stereo width. 103 + p.set_channel_mode(self.channel_mode); 104 + p.set_stereo_width(self.stereo_width); 105 + } 106 + 107 + /// Build the full [`Equalizer`] value (for one-shot pushes). 108 + pub fn equalizer(&self) -> Equalizer { 109 + Equalizer { 110 + enabled: self.eq_enabled, 111 + precut_db: 0.0, 112 + bands: self 113 + .eq_gains 114 + .iter() 115 + .enumerate() 116 + .map(|(i, &g)| EqBand { 117 + cutoff_hz: EQ_FREQS[i], 118 + q: 1.0, 119 + gain_db: g, 120 + }) 121 + .collect(), 122 + } 123 + } 124 + } 125 + 126 + /// Map a plain N:1 ratio to the engine's ratio index. 127 + fn ratio_index(ratio: i32) -> i32 { 128 + match ratio { 129 + r if r <= 2 => 0, // 2:1 130 + r if r <= 4 => 1, // 4:1 131 + r if r <= 6 => 2, // 6:1 132 + _ => 3, // 10:1 133 + } 134 + } 135 + 136 + /// The DSP "rows" shown in the equalizer/settings panel, in order. 137 + pub const CROSSFEED_MODES: [CrossfeedMode; 3] = 138 + [CrossfeedMode::Off, CrossfeedMode::Meier, CrossfeedMode::Custom]; 139 + 140 + pub const CHANNEL_MODES: [ChannelMode; 7] = [ 141 + ChannelMode::Stereo, 142 + ChannelMode::Mono, 143 + ChannelMode::Custom, 144 + ChannelMode::MonoLeft, 145 + ChannelMode::MonoRight, 146 + ChannelMode::Karaoke, 147 + ChannelMode::Swap, 148 + ]; 149 + 150 + pub fn crossfeed_label(m: CrossfeedMode) -> &'static str { 151 + match m { 152 + CrossfeedMode::Off => "Off", 153 + CrossfeedMode::Meier => "Meier", 154 + CrossfeedMode::Custom => "Custom", 155 + } 156 + } 157 + 158 + pub fn channel_label(m: ChannelMode) -> &'static str { 159 + match m { 160 + ChannelMode::Stereo => "Stereo", 161 + ChannelMode::Mono => "Mono", 162 + ChannelMode::Custom => "Custom", 163 + ChannelMode::MonoLeft => "Mono L", 164 + ChannelMode::MonoRight => "Mono R", 165 + ChannelMode::Karaoke => "Karaoke", 166 + ChannelMode::Swap => "Swap", 167 + } 168 + }
+149
cli/src/player/mod.rs
··· 1 + //! Thin wrapper around the Rockbox playback engine. One instance per process. 2 + #![allow(dead_code)] // full transport surface exposed; not all keys are bound yet 3 + 4 + pub mod dsp; 5 + 6 + use std::sync::Mutex; 7 + use std::time::Duration; 8 + 9 + use anyhow::Result; 10 + use rockbox_playback::{PlaybackState, Player as RbPlayer}; 11 + 12 + pub use dsp::AudioSettings; 13 + 14 + pub struct Player { 15 + rb: RbPlayer, 16 + /// Last non-zero volume, so mute/unmute can restore it. 17 + last_volume: Mutex<f32>, 18 + } 19 + 20 + /// A flattened snapshot for the UI, derived from the engine's `Status`. 21 + #[derive(Clone, Debug, Default)] 22 + pub struct NowPlaying { 23 + pub state: State, 24 + pub position: Duration, 25 + /// ICY `StreamTitle` current-song title. 26 + pub title: String, 27 + pub artist: String, 28 + /// ICY `icy-name` — the station name. 29 + pub station: String, 30 + pub bitrate: u32, 31 + pub sample_rate: u32, 32 + } 33 + 34 + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 35 + pub enum State { 36 + #[default] 37 + Stopped, 38 + Playing, 39 + Paused, 40 + } 41 + 42 + impl NowPlaying { 43 + /// A single "now playing" line, best-effort from ICY metadata. 44 + pub fn line(&self) -> Option<String> { 45 + match (self.artist.trim(), self.title.trim()) { 46 + ("", "") => None, 47 + ("", t) => Some(t.to_string()), 48 + (a, "") => Some(a.to_string()), 49 + (a, t) => Some(format!("{a} — {t}")), 50 + } 51 + } 52 + } 53 + 54 + impl Player { 55 + pub fn new() -> Result<Self> { 56 + let rb = RbPlayer::new().map_err(|e| anyhow::anyhow!("audio engine: {e}"))?; 57 + rb.set_volume(0.8); 58 + Ok(Self { 59 + rb, 60 + last_volume: Mutex::new(0.8), 61 + }) 62 + } 63 + 64 + /// Borrow the raw engine (for direct DSP setters). 65 + pub fn engine(&self) -> &RbPlayer { 66 + &self.rb 67 + } 68 + 69 + /// Start (or restart) playback of a single stream URL. 70 + pub fn play_url(&self, url: &str) { 71 + self.rb.set_queue([url.to_string()]); 72 + self.rb.play(); 73 + } 74 + 75 + pub fn toggle(&self) { 76 + self.rb.toggle(); 77 + } 78 + 79 + pub fn play(&self) { 80 + self.rb.play(); 81 + } 82 + 83 + pub fn stop(&self) { 84 + self.rb.stop(); 85 + } 86 + 87 + pub fn set_volume(&self, vol: f32) { 88 + let v = vol.clamp(0.0, 1.0); 89 + if v > 0.0 { 90 + *self.last_volume.lock().unwrap() = v; 91 + } 92 + self.rb.set_volume(v); 93 + } 94 + 95 + pub fn volume(&self) -> f32 { 96 + self.rb.volume() 97 + } 98 + 99 + pub fn is_muted(&self) -> bool { 100 + self.rb.volume() <= 0.0 101 + } 102 + 103 + pub fn toggle_mute(&self) { 104 + if self.is_muted() { 105 + let restore = *self.last_volume.lock().unwrap(); 106 + self.rb.set_volume(if restore > 0.0 { restore } else { 0.8 }); 107 + } else { 108 + *self.last_volume.lock().unwrap() = self.rb.volume(); 109 + self.rb.set_volume(0.0); 110 + } 111 + } 112 + 113 + /// Nudge the volume by a delta (for +/- keys). 114 + pub fn bump_volume(&self, delta: f32) { 115 + self.set_volume(self.rb.volume() + delta); 116 + } 117 + 118 + pub fn apply_dsp(&self, settings: &AudioSettings) { 119 + settings.apply(&self.rb); 120 + } 121 + 122 + pub fn now_playing(&self) -> NowPlaying { 123 + let st = self.rb.status(); 124 + let state = match st.state { 125 + PlaybackState::Playing => State::Playing, 126 + PlaybackState::Paused => State::Paused, 127 + PlaybackState::Stopped => State::Stopped, 128 + }; 129 + let (title, artist, station, bitrate, sample_rate) = match st.metadata.as_ref() { 130 + Some(m) => ( 131 + m.title.clone(), 132 + m.artist.clone(), 133 + m.album.clone(), 134 + m.bitrate, 135 + m.sample_rate, 136 + ), 137 + None => (String::new(), String::new(), String::new(), 0, 0), 138 + }; 139 + NowPlaying { 140 + state, 141 + position: st.position, 142 + title, 143 + artist, 144 + station, 145 + bitrate, 146 + sample_rate, 147 + } 148 + } 149 + }
+194
cli/src/radio/browser.rs
··· 1 + //! radio-browser.info search, normalized into our `StationInfo` shape. 2 + //! Mirrors the logic in `apps/web/src/lib/api/radioBrowser.ts`. 3 + #![allow(dead_code)] // by_tag / helpers are API for category browsing 4 + 5 + use anyhow::{Context, Result}; 6 + use serde::Deserialize; 7 + 8 + use crate::appview::StationInfo; 9 + 10 + /// Discovery endpoint that lists the mirror servers. 11 + const SERVERS_URL: &str = "https://all.api.radio-browser.info/json/servers"; 12 + /// Fallback mirror if discovery fails. 13 + const FALLBACK_BASE: &str = "https://de1.api.radio-browser.info"; 14 + 15 + #[derive(Clone)] 16 + pub struct RadioBrowser { 17 + http: reqwest::Client, 18 + base: std::sync::Arc<tokio::sync::RwLock<Option<String>>>, 19 + } 20 + 21 + #[derive(Deserialize)] 22 + struct Server { 23 + name: String, 24 + } 25 + 26 + #[derive(Deserialize)] 27 + struct RbStation { 28 + stationuuid: String, 29 + #[serde(default)] 30 + name: String, 31 + #[serde(default)] 32 + url: String, 33 + #[serde(default)] 34 + url_resolved: String, 35 + #[serde(default)] 36 + favicon: String, 37 + #[serde(default)] 38 + homepage: String, 39 + #[serde(default)] 40 + tags: String, 41 + #[serde(default)] 42 + country: String, 43 + #[serde(default)] 44 + language: String, 45 + #[serde(default)] 46 + codec: String, 47 + #[serde(default)] 48 + bitrate: u32, 49 + } 50 + 51 + impl RadioBrowser { 52 + pub fn new() -> Self { 53 + let http = reqwest::Client::builder() 54 + .user_agent(concat!("atradio-cli/", env!("CARGO_PKG_VERSION"))) 55 + .build() 56 + .expect("reqwest client"); 57 + Self { 58 + http, 59 + base: std::sync::Arc::new(tokio::sync::RwLock::new(None)), 60 + } 61 + } 62 + 63 + /// Resolve (and cache) a working mirror base URL. 64 + async fn base(&self) -> String { 65 + if let Some(b) = self.base.read().await.clone() { 66 + return b; 67 + } 68 + let resolved = self 69 + .discover() 70 + .await 71 + .unwrap_or_else(|_| FALLBACK_BASE.to_string()); 72 + *self.base.write().await = Some(resolved.clone()); 73 + resolved 74 + } 75 + 76 + async fn discover(&self) -> Result<String> { 77 + let servers: Vec<Server> = self 78 + .http 79 + .get(SERVERS_URL) 80 + .send() 81 + .await? 82 + .json() 83 + .await 84 + .context("radio-browser servers")?; 85 + servers 86 + .into_iter() 87 + .next() 88 + .map(|s| format!("https://{}", s.name)) 89 + .context("no radio-browser mirrors") 90 + } 91 + 92 + /// Search stations by name, clickcount-ordered. 93 + pub async fn search(&self, query: &str, limit: u32) -> Result<Vec<StationInfo>> { 94 + let base = self.base().await; 95 + let url = format!("{base}/json/stations/search"); 96 + let rows: Vec<RbStation> = self 97 + .http 98 + .get(&url) 99 + .query(&[ 100 + ("name", query), 101 + ("limit", &limit.to_string()), 102 + ("hidebroken", "true"), 103 + ("order", "clickcount"), 104 + ("reverse", "true"), 105 + ]) 106 + .send() 107 + .await? 108 + .json() 109 + .await 110 + .context("radio-browser search")?; 111 + Ok(rows.into_iter().map(to_station).collect()) 112 + } 113 + 114 + /// Browse stations by tag/genre. 115 + pub async fn by_tag(&self, tag: &str, limit: u32) -> Result<Vec<StationInfo>> { 116 + let base = self.base().await; 117 + let url = format!("{base}/json/stations/bytag/{}", urlencode(tag)); 118 + let rows: Vec<RbStation> = self 119 + .http 120 + .get(&url) 121 + .query(&[ 122 + ("limit", limit.to_string()), 123 + ("hidebroken", "true".into()), 124 + ("order", "clickcount".into()), 125 + ("reverse", "true".into()), 126 + ]) 127 + .send() 128 + .await? 129 + .json() 130 + .await 131 + .context("radio-browser bytag")?; 132 + Ok(rows.into_iter().map(to_station).collect()) 133 + } 134 + 135 + /// Register a play (radio-browser click counter). Best-effort. 136 + pub async fn register_click(&self, station_id: &str) { 137 + let Some(uuid) = station_id.strip_prefix("rb:") else { 138 + return; 139 + }; 140 + let base = self.base().await; 141 + let url = format!("{base}/json/url/{uuid}"); 142 + let _ = self.http.get(&url).send().await; 143 + } 144 + } 145 + 146 + impl Default for RadioBrowser { 147 + fn default() -> Self { 148 + Self::new() 149 + } 150 + } 151 + 152 + fn to_station(s: RbStation) -> StationInfo { 153 + let tags: Vec<String> = s 154 + .tags 155 + .split(',') 156 + .map(|t| t.trim().to_string()) 157 + .filter(|t| !t.is_empty()) 158 + .collect(); 159 + let stream_url = if s.url_resolved.is_empty() { 160 + s.url 161 + } else { 162 + s.url_resolved 163 + }; 164 + StationInfo { 165 + station_id: format!("rb:{}", s.stationuuid), 166 + name: s.name.trim().to_string(), 167 + stream_url, 168 + source: "radio-browser".into(), 169 + genre: tags.first().cloned(), 170 + homepage: opt(s.homepage), 171 + logo: opt(s.favicon), 172 + country: opt(s.country), 173 + language: opt(s.language), 174 + bitrate: (s.bitrate > 0).then_some(s.bitrate), 175 + codec: opt(s.codec), 176 + tags, 177 + description: None, 178 + } 179 + } 180 + 181 + fn opt(s: String) -> Option<String> { 182 + let t = s.trim(); 183 + (!t.is_empty()).then(|| t.to_string()) 184 + } 185 + 186 + fn urlencode(s: &str) -> String { 187 + s.chars() 188 + .map(|c| match c { 189 + 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => c.to_string(), 190 + ' ' => "%20".to_string(), 191 + _ => format!("%{:02X}", c as u32), 192 + }) 193 + .collect() 194 + }
+3
cli/src/radio/mod.rs
··· 1 + pub mod browser; 2 + 3 + pub use browser::RadioBrowser;
+162
cli/src/settings.rs
··· 1 + //! Persistent, TOML-backed user settings (volume + the full DSP chain), stored 2 + //! next to the session at `~/.config/atradio/settings.toml`. 3 + 4 + use std::path::{Path, PathBuf}; 5 + 6 + use serde::{Deserialize, Serialize}; 7 + 8 + use crate::player::dsp::AudioSettings; 9 + 10 + /// Serializable mirror of [`AudioSettings`] plus player prefs. Enums are stored 11 + /// as lowercase strings to keep the file human-editable. 12 + #[derive(Clone, Debug, Serialize, Deserialize)] 13 + #[serde(default)] 14 + pub struct Settings { 15 + pub volume: f32, 16 + 17 + pub eq_enabled: bool, 18 + pub eq_gains: Vec<f32>, 19 + pub bass: i32, 20 + pub treble: i32, 21 + pub crossfeed_mode: String, 22 + pub crossfeed_direct: f32, 23 + pub pbe: i32, 24 + pub pbe_precut: i32, 25 + pub surround_delay: i32, 26 + pub surround_balance: i32, 27 + pub comp_threshold: i32, 28 + pub comp_ratio: i32, 29 + pub channel_mode: String, 30 + pub stereo_width: i32, 31 + } 32 + 33 + impl Default for Settings { 34 + fn default() -> Self { 35 + let d = AudioSettings::default(); 36 + Self { 37 + volume: 0.8, 38 + eq_enabled: d.eq_enabled, 39 + eq_gains: d.eq_gains.to_vec(), 40 + bass: d.bass, 41 + treble: d.treble, 42 + crossfeed_mode: crossfeed_to_str(d.crossfeed_mode).into(), 43 + crossfeed_direct: d.crossfeed_direct, 44 + pbe: d.pbe, 45 + pbe_precut: d.pbe_precut, 46 + surround_delay: d.surround_delay, 47 + surround_balance: d.surround_balance, 48 + comp_threshold: d.comp_threshold, 49 + comp_ratio: d.comp_ratio, 50 + channel_mode: channel_to_str(d.channel_mode).into(), 51 + stereo_width: d.stereo_width, 52 + } 53 + } 54 + } 55 + 56 + impl Settings { 57 + fn path(session_path: &Path) -> PathBuf { 58 + session_path.with_file_name("settings.toml") 59 + } 60 + 61 + pub fn load(session_path: &Path) -> Self { 62 + std::fs::read_to_string(Self::path(session_path)) 63 + .ok() 64 + .and_then(|s| toml::from_str(&s).ok()) 65 + .unwrap_or_default() 66 + } 67 + 68 + pub fn save(&self, session_path: &Path) { 69 + let path = Self::path(session_path); 70 + if let Some(dir) = path.parent() { 71 + let _ = std::fs::create_dir_all(dir); 72 + } 73 + if let Ok(s) = toml::to_string_pretty(self) { 74 + let _ = std::fs::write(path, s); 75 + } 76 + } 77 + 78 + /// Build the runtime [`AudioSettings`] from the persisted values. 79 + pub fn audio(&self) -> AudioSettings { 80 + let mut a = AudioSettings::default(); 81 + a.eq_enabled = self.eq_enabled; 82 + for (i, slot) in a.eq_gains.iter_mut().enumerate() { 83 + if let Some(g) = self.eq_gains.get(i) { 84 + *slot = *g; 85 + } 86 + } 87 + a.bass = self.bass; 88 + a.treble = self.treble; 89 + a.crossfeed_mode = str_to_crossfeed(&self.crossfeed_mode); 90 + a.crossfeed_direct = self.crossfeed_direct; 91 + a.pbe = self.pbe; 92 + a.pbe_precut = self.pbe_precut; 93 + a.surround_delay = self.surround_delay; 94 + a.surround_balance = self.surround_balance; 95 + a.comp_threshold = self.comp_threshold; 96 + a.comp_ratio = self.comp_ratio; 97 + a.channel_mode = str_to_channel(&self.channel_mode); 98 + a.stereo_width = self.stereo_width; 99 + a 100 + } 101 + 102 + /// Capture the current runtime DSP + volume back into the persisted shape. 103 + pub fn update_from(&mut self, audio: &AudioSettings, volume: f32) { 104 + self.volume = volume; 105 + self.eq_enabled = audio.eq_enabled; 106 + self.eq_gains = audio.eq_gains.to_vec(); 107 + self.bass = audio.bass; 108 + self.treble = audio.treble; 109 + self.crossfeed_mode = crossfeed_to_str(audio.crossfeed_mode).into(); 110 + self.crossfeed_direct = audio.crossfeed_direct; 111 + self.pbe = audio.pbe; 112 + self.pbe_precut = audio.pbe_precut; 113 + self.surround_delay = audio.surround_delay; 114 + self.surround_balance = audio.surround_balance; 115 + self.comp_threshold = audio.comp_threshold; 116 + self.comp_ratio = audio.comp_ratio; 117 + self.channel_mode = channel_to_str(audio.channel_mode).into(); 118 + self.stereo_width = audio.stereo_width; 119 + } 120 + } 121 + 122 + use rockbox_playback::{ChannelMode, CrossfeedMode}; 123 + 124 + fn crossfeed_to_str(m: CrossfeedMode) -> &'static str { 125 + match m { 126 + CrossfeedMode::Off => "off", 127 + CrossfeedMode::Meier => "meier", 128 + CrossfeedMode::Custom => "custom", 129 + } 130 + } 131 + 132 + fn str_to_crossfeed(s: &str) -> CrossfeedMode { 133 + match s { 134 + "meier" => CrossfeedMode::Meier, 135 + "custom" => CrossfeedMode::Custom, 136 + _ => CrossfeedMode::Off, 137 + } 138 + } 139 + 140 + fn channel_to_str(m: ChannelMode) -> &'static str { 141 + match m { 142 + ChannelMode::Stereo => "stereo", 143 + ChannelMode::Mono => "mono", 144 + ChannelMode::Custom => "custom", 145 + ChannelMode::MonoLeft => "mono-left", 146 + ChannelMode::MonoRight => "mono-right", 147 + ChannelMode::Karaoke => "karaoke", 148 + ChannelMode::Swap => "swap", 149 + } 150 + } 151 + 152 + fn str_to_channel(s: &str) -> ChannelMode { 153 + match s { 154 + "mono" => ChannelMode::Mono, 155 + "custom" => ChannelMode::Custom, 156 + "mono-left" => ChannelMode::MonoLeft, 157 + "mono-right" => ChannelMode::MonoRight, 158 + "karaoke" => ChannelMode::Karaoke, 159 + "swap" => ChannelMode::Swap, 160 + _ => ChannelMode::Stereo, 161 + } 162 + }
+61
cli/src/theme.rs
··· 1 + //! The atradio.fm "synthwave" palette, mirrored from the web app 2 + //! (`apps/web/src/styles/index.css`). Despite the historical `pink`/`magenta` 3 + //! names, the scheme is a teal/green/cyan/indigo set on a near-black bg. 4 + //! 5 + //! The full palette is exported for consistency even where a colour isn't yet 6 + //! referenced by a widget. 7 + #![allow(dead_code)] 8 + 9 + use clap::builder::styling::{AnsiColor, Color, Style, Styles}; 10 + use ratatui::style::Color as TuiColor; 11 + 12 + // ---- raw palette (RGB) ---------------------------------------------------- 13 + 14 + pub const BG: TuiColor = TuiColor::Rgb(0x0a, 0x0e, 0x12); 15 + pub const SURFACE: TuiColor = TuiColor::Rgb(0x11, 0x18, 0x21); 16 + pub const PANEL: TuiColor = TuiColor::Rgb(0x18, 0x22, 0x2d); 17 + 18 + /// Primary — teal. 19 + pub const TEAL: TuiColor = TuiColor::Rgb(0x00, 0xe8, 0xc6); 20 + /// Highlight — green. 21 + pub const GREEN: TuiColor = TuiColor::Rgb(0x64, 0xe8, 0x82); 22 + /// Secondary — cyan. 23 + pub const CYAN: TuiColor = TuiColor::Rgb(0x00, 0xc6, 0xe8); 24 + /// Accent — indigo. 25 + pub const INDIGO: TuiColor = TuiColor::Rgb(0x82, 0x64, 0xff); 26 + /// Link — orange. 27 + pub const ORANGE: TuiColor = TuiColor::Rgb(0xff, 0xa0, 0x64); 28 + 29 + pub const FG: TuiColor = TuiColor::Rgb(0xe8, 0xee, 0xf4); 30 + pub const MUTED: TuiColor = TuiColor::Rgb(0x8b, 0x98, 0xa6); 31 + pub const DANGER: TuiColor = TuiColor::Rgb(0xff, 0x64, 0x64); 32 + 33 + /// Faint border grey (foreground @ ~12%). 34 + pub const BORDER: TuiColor = TuiColor::Rgb(0x2a, 0x33, 0x3d); 35 + 36 + // ---- clap styled help ----------------------------------------------------- 37 + 38 + /// Colored, styled `--help` output matching the palette. 39 + pub fn clap_styles() -> Styles { 40 + let teal = Color::Rgb(clap::builder::styling::RgbColor(0x00, 0xe8, 0xc6)); 41 + let green = Color::Rgb(clap::builder::styling::RgbColor(0x64, 0xe8, 0x82)); 42 + let cyan = Color::Rgb(clap::builder::styling::RgbColor(0x00, 0xc6, 0xe8)); 43 + let orange = Color::Rgb(clap::builder::styling::RgbColor(0xff, 0xa0, 0x64)); 44 + 45 + Styles::styled() 46 + .header(Style::new().bold().fg_color(Some(teal))) 47 + .usage(Style::new().bold().fg_color(Some(cyan))) 48 + .literal(Style::new().bold().fg_color(Some(green))) 49 + .placeholder(Style::new().fg_color(Some(orange))) 50 + .valid(Style::new().fg_color(Some(green))) 51 + .invalid( 52 + Style::new() 53 + .bold() 54 + .fg_color(Some(Color::Ansi(AnsiColor::Red))), 55 + ) 56 + .error( 57 + Style::new() 58 + .bold() 59 + .fg_color(Some(Color::Ansi(AnsiColor::Red))), 60 + ) 61 + }
+193
cli/src/tui/dsp_rows.rs
··· 1 + //! The equalizer/DSP panel rows: a flat, ordered list of adjustable controls 2 + //! mapping the web app's audio settings onto the Rockbox chain. 3 + 4 + use rockbox_playback::EQ_BANDS; 5 + 6 + use crate::player::dsp::{ 7 + channel_label, crossfeed_label, AudioSettings, CHANNEL_MODES, CROSSFEED_MODES, EQ_FREQS, 8 + }; 9 + 10 + /// A single adjustable row. 11 + pub struct Row { 12 + pub label: String, 13 + pub value: String, 14 + /// 0.0..=1.0 fill for a bar, or None for enable/mode toggles. 15 + pub fill: Option<f32>, 16 + } 17 + 18 + /// Build the ordered rows for rendering. 19 + pub fn rows(s: &AudioSettings) -> Vec<Row> { 20 + let mut v = Vec::with_capacity(14 + EQ_BANDS); 21 + 22 + v.push(Row { 23 + label: "Equalizer".into(), 24 + value: if s.eq_enabled { "On" } else { "Off" }.into(), 25 + fill: None, 26 + }); 27 + for (i, &hz) in EQ_FREQS.iter().enumerate() { 28 + v.push(Row { 29 + label: format!(" {}", freq_label(hz)), 30 + value: format!("{:+.0} dB", s.eq_gains[i]), 31 + fill: Some(norm(s.eq_gains[i] as f32, -24.0, 24.0)), 32 + }); 33 + } 34 + v.push(Row { 35 + label: "Bass".into(), 36 + value: format!("{:+} dB", s.bass), 37 + fill: Some(norm(s.bass as f32, -24.0, 24.0)), 38 + }); 39 + v.push(Row { 40 + label: "Treble".into(), 41 + value: format!("{:+} dB", s.treble), 42 + fill: Some(norm(s.treble as f32, -24.0, 24.0)), 43 + }); 44 + v.push(Row { 45 + label: "Crossfeed".into(), 46 + value: crossfeed_label(s.crossfeed_mode).into(), 47 + fill: None, 48 + }); 49 + v.push(Row { 50 + label: " Direct gain".into(), 51 + value: format!("{:.1} dB", s.crossfeed_direct), 52 + fill: Some(norm(s.crossfeed_direct, -6.0, 0.0)), 53 + }); 54 + v.push(Row { 55 + label: "Perceptual bass".into(), 56 + value: format!("{}%", s.pbe), 57 + fill: Some(norm(s.pbe as f32, 0.0, 100.0)), 58 + }); 59 + v.push(Row { 60 + label: " Pre-cut".into(), 61 + value: format!("-{} dB", s.pbe_precut), 62 + fill: Some(norm(s.pbe_precut as f32, 0.0, 24.0)), 63 + }); 64 + v.push(Row { 65 + label: "Surround delay".into(), 66 + value: format!("{} ms", s.surround_delay), 67 + fill: Some(norm(s.surround_delay as f32, 0.0, 30.0)), 68 + }); 69 + v.push(Row { 70 + label: " Balance".into(), 71 + value: format!("{}%", s.surround_balance), 72 + fill: Some(norm(s.surround_balance as f32, 0.0, 100.0)), 73 + }); 74 + v.push(Row { 75 + label: "Compressor thr.".into(), 76 + value: format!("{} dB", s.comp_threshold), 77 + fill: Some(norm(s.comp_threshold as f32, -30.0, 0.0)), 78 + }); 79 + v.push(Row { 80 + label: " Ratio".into(), 81 + value: format!("{}:1", s.comp_ratio), 82 + fill: Some(norm(s.comp_ratio as f32, 2.0, 10.0)), 83 + }); 84 + v.push(Row { 85 + label: "Channel mode".into(), 86 + value: channel_label(s.channel_mode).into(), 87 + fill: None, 88 + }); 89 + v.push(Row { 90 + label: " Stereo width".into(), 91 + value: format!("{}%", s.stereo_width), 92 + fill: Some(norm(s.stereo_width as f32, 0.0, 200.0)), 93 + }); 94 + 95 + v 96 + } 97 + 98 + /// Total number of rows (kept in sync with `rows`). 99 + pub fn row_count() -> usize { 100 + 14 + EQ_BANDS 101 + } 102 + 103 + /// Adjust the given row by `dir` (+1 / -1). Returns true if changed. 104 + pub fn adjust(s: &mut AudioSettings, row: usize, dir: i32) -> bool { 105 + let eq_end = 1 + EQ_BANDS; 106 + match row { 107 + 0 => { 108 + s.eq_enabled = !s.eq_enabled; 109 + true 110 + } 111 + r if r >= 1 && r < eq_end => { 112 + let band = r - 1; 113 + s.eq_gains[band] = (s.eq_gains[band] + dir as f32).clamp(-24.0, 24.0); 114 + true 115 + } 116 + r => adjust_tail(s, r - eq_end, dir), 117 + } 118 + } 119 + 120 + fn adjust_tail(s: &mut AudioSettings, idx: usize, dir: i32) -> bool { 121 + match idx { 122 + 0 => { 123 + s.bass = (s.bass + dir).clamp(-24, 24); 124 + } 125 + 1 => { 126 + s.treble = (s.treble + dir).clamp(-24, 24); 127 + } 128 + 2 => { 129 + cycle_crossfeed(s, dir); 130 + } 131 + 3 => { 132 + s.crossfeed_direct = (s.crossfeed_direct + dir as f32 * 0.5).clamp(-6.0, 0.0); 133 + } 134 + 4 => { 135 + s.pbe = (s.pbe + dir * 5).clamp(0, 100); 136 + } 137 + 5 => { 138 + s.pbe_precut = (s.pbe_precut + dir).clamp(0, 24); 139 + } 140 + 6 => { 141 + s.surround_delay = (s.surround_delay + dir).clamp(0, 30); 142 + } 143 + 7 => { 144 + s.surround_balance = (s.surround_balance + dir * 5).clamp(0, 100); 145 + } 146 + 8 => { 147 + s.comp_threshold = (s.comp_threshold + dir).clamp(-30, 0); 148 + } 149 + 9 => { 150 + s.comp_ratio = (s.comp_ratio + dir).clamp(2, 10); 151 + } 152 + 10 => { 153 + cycle_channel(s, dir); 154 + } 155 + 11 => { 156 + s.stereo_width = (s.stereo_width + dir * 5).clamp(0, 200); 157 + } 158 + _ => return false, 159 + } 160 + true 161 + } 162 + 163 + fn cycle_crossfeed(s: &mut AudioSettings, dir: i32) { 164 + let cur = CROSSFEED_MODES 165 + .iter() 166 + .position(|m| *m == s.crossfeed_mode) 167 + .unwrap_or(0); 168 + let n = CROSSFEED_MODES.len(); 169 + let next = ((cur as i32 + dir).rem_euclid(n as i32)) as usize; 170 + s.crossfeed_mode = CROSSFEED_MODES[next]; 171 + } 172 + 173 + fn cycle_channel(s: &mut AudioSettings, dir: i32) { 174 + let cur = CHANNEL_MODES 175 + .iter() 176 + .position(|m| *m == s.channel_mode) 177 + .unwrap_or(0); 178 + let n = CHANNEL_MODES.len(); 179 + let next = ((cur as i32 + dir).rem_euclid(n as i32)) as usize; 180 + s.channel_mode = CHANNEL_MODES[next]; 181 + } 182 + 183 + fn norm(v: f32, lo: f32, hi: f32) -> f32 { 184 + ((v - lo) / (hi - lo)).clamp(0.0, 1.0) 185 + } 186 + 187 + fn freq_label(hz: i32) -> String { 188 + if hz >= 1000 { 189 + format!("{}kHz", hz / 1000) 190 + } else { 191 + format!("{hz}Hz") 192 + } 193 + }
+549
cli/src/tui/mod.rs
··· 1 + //! The interactive terminal UI: an async render/event loop over ratatui. 2 + 3 + mod dsp_rows; 4 + mod state; 5 + mod ui; 6 + 7 + use std::io::{self, Stdout}; 8 + use std::sync::Arc; 9 + use std::time::Duration; 10 + 11 + use anyhow::Result; 12 + use crossterm::event::{ 13 + DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode, KeyEventKind, KeyModifiers, 14 + }; 15 + use crossterm::execute; 16 + use crossterm::terminal::{ 17 + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, 18 + }; 19 + use futures::StreamExt; 20 + use ratatui::backend::CrosstermBackend; 21 + use ratatui::Terminal; 22 + use tokio::sync::mpsc; 23 + 24 + use crate::appview::{AppView, StationInfo}; 25 + use crate::atproto::Atproto; 26 + use crate::config::Config; 27 + use crate::player::{Player, State as PlayState}; 28 + use crate::radio::RadioBrowser; 29 + use state::{App, Overlay, View}; 30 + 31 + type Term = Terminal<CrosstermBackend<Stdout>>; 32 + 33 + /// Background → UI messages (results of async fetches). 34 + enum Msg { 35 + Trending(Vec<StationInfo>), 36 + Popular(Vec<StationInfo>), 37 + Favorites(Vec<StationInfo>), 38 + SearchResults { query: String, items: Vec<StationInfo> }, 39 + Comments(Vec<crate::appview::CommentView>), 40 + Notifications { items: Vec<crate::appview::NotificationView>, unread: u32 }, 41 + Toast(String), 42 + } 43 + 44 + pub fn status_glyph(state: PlayState) -> &'static str { 45 + match state { 46 + PlayState::Playing => "▶", 47 + PlayState::Paused => "⏸", 48 + PlayState::Stopped => "⏹", 49 + } 50 + } 51 + 52 + pub async fn run(config: Config) -> Result<()> { 53 + let atproto = Atproto::new(config.session_path.clone()); 54 + let profile = atproto.profile(); 55 + let logged_in = profile.is_some(); 56 + let handle = profile.as_ref().map(|p| p.handle.clone()); 57 + 58 + let player = Arc::new(Player::new()?); 59 + #[cfg(target_os = "linux")] 60 + crate::mpris::spawn(player.clone()); 61 + let appview = AppView::new(&config.appview_url); 62 + let browser = RadioBrowser::new(); 63 + let atproto = Arc::new(atproto); 64 + 65 + // Load persisted settings (volume + DSP) and push them to the engine. 66 + let mut settings = crate::settings::Settings::load(&config.session_path); 67 + let mut app = App::new(logged_in, handle); 68 + app.dsp = settings.audio(); 69 + player.set_volume(settings.volume); 70 + player.apply_dsp(&app.dsp); 71 + 72 + // Set up the terminal. 73 + let mut term = setup_terminal()?; 74 + 75 + let (tx, mut rx) = mpsc::unbounded_channel::<Msg>(); 76 + 77 + // Kick off initial loads. 78 + spawn_load_trending(&appview, &tx); 79 + spawn_load_popular(&appview, &tx); 80 + if let Some(actor) = atproto.actor() { 81 + spawn_load_favorites(&appview, &tx, actor.clone()); 82 + spawn_load_notifications(&appview, &tx, actor); 83 + } 84 + 85 + let mut events = EventStream::new(); 86 + let mut ticker = tokio::time::interval(Duration::from_millis(250)); 87 + 88 + let res = loop { 89 + // Render. 90 + let np = player.now_playing(); 91 + app.volume = player.volume(); 92 + app.muted = player.is_muted(); 93 + if let Err(e) = term.draw(|f| ui::draw(f, &app, &np)) { 94 + break Err(e.into()); 95 + } 96 + if app.should_quit { 97 + break Ok(()); 98 + } 99 + 100 + tokio::select! { 101 + maybe_ev = events.next() => { 102 + if let Some(Ok(ev)) = maybe_ev { 103 + handle_event(ev, &mut app, &player, &appview, &browser, &atproto, &tx); 104 + } 105 + } 106 + Some(msg) = rx.recv() => { 107 + apply_msg(msg, &mut app); 108 + } 109 + _ = ticker.tick() => { 110 + app.toast.tick(); 111 + // Fire a debounced search fetch if the query changed. 112 + if app.search_dirty { 113 + app.search_dirty = false; 114 + let q = app.search_query.clone(); 115 + if !q.trim().is_empty() { 116 + spawn_search(&browser, &tx, q); 117 + } 118 + } 119 + } 120 + } 121 + }; 122 + 123 + // Persist volume + DSP on exit. 124 + settings.update_from(&app.dsp, player.volume()); 125 + settings.save(&config.session_path); 126 + 127 + restore_terminal(&mut term)?; 128 + res 129 + } 130 + 131 + fn apply_msg(msg: Msg, app: &mut App) { 132 + match msg { 133 + Msg::Trending(items) => { 134 + app.trending = items; 135 + app.clamp_selection(); 136 + } 137 + Msg::Popular(items) => app.popular = items, 138 + Msg::Favorites(items) => app.favorites = items, 139 + Msg::SearchResults { query, items } => { 140 + // Ignore stale responses. 141 + if query == app.search_query || app.search_query.is_empty() { 142 + app.search_results = items; 143 + app.search_selected = 0; 144 + } 145 + } 146 + Msg::Comments(items) => { 147 + app.comments = items; 148 + app.comments_selected = 0; 149 + } 150 + Msg::Notifications { items, unread } => { 151 + app.notifications = items; 152 + app.unread = unread; 153 + } 154 + Msg::Toast(t) => app.toast.set(t), 155 + } 156 + } 157 + 158 + #[allow(clippy::too_many_arguments)] 159 + fn handle_event( 160 + ev: Event, 161 + app: &mut App, 162 + player: &Arc<Player>, 163 + appview: &AppView, 164 + browser: &RadioBrowser, 165 + atproto: &Arc<Atproto>, 166 + tx: &mpsc::UnboundedSender<Msg>, 167 + ) { 168 + let Event::Key(key) = ev else { return }; 169 + if key.kind == KeyEventKind::Release { 170 + return; 171 + } 172 + 173 + // Ctrl-C always quits. 174 + if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') { 175 + app.should_quit = true; 176 + return; 177 + } 178 + 179 + match app.overlay { 180 + Overlay::Search => return handle_search_keys(key.code, app, player, atproto, tx), 181 + Overlay::Compose => return handle_compose_keys(key.code, app, atproto, tx), 182 + Overlay::None => {} 183 + } 184 + 185 + match key.code { 186 + KeyCode::Char('q') => app.should_quit = true, 187 + KeyCode::Esc => { 188 + if app.view != View::Home { 189 + app.view = View::Home; 190 + } else { 191 + app.should_quit = true; 192 + } 193 + } 194 + KeyCode::Char('/') => { 195 + app.overlay = Overlay::Search; 196 + app.search_selected = 0; 197 + } 198 + KeyCode::Char('h') => app.view = View::Home, 199 + KeyCode::Char('e') => app.view = View::Dsp, 200 + KeyCode::Char('?') => app.view = View::Help, 201 + KeyCode::Char('n') => { 202 + app.view = View::Notifications; 203 + if let Some(actor) = atproto.actor() { 204 + spawn_load_notifications(appview, tx, actor.clone()); 205 + let av = appview.clone(); 206 + let tx2 = tx.clone(); 207 + tokio::spawn(async move { 208 + let _ = av.update_seen(&actor).await; 209 + let _ = tx2; // badge clears on next reload 210 + }); 211 + app.unread = 0; 212 + } 213 + } 214 + KeyCode::Char('c') => { 215 + app.view = View::Comments; 216 + if let Some(s) = app.current.clone() { 217 + spawn_load_comments(appview, tx, s.station_id); 218 + } 219 + } 220 + KeyCode::Char('a') => { 221 + if app.current.is_some() { 222 + app.overlay = Overlay::Compose; 223 + app.compose_text.clear(); 224 + } else { 225 + app.toast.set("Play a station first to comment."); 226 + } 227 + } 228 + KeyCode::Char(' ') => { 229 + player.toggle(); 230 + } 231 + KeyCode::Char('m') => { 232 + player.toggle_mute(); 233 + } 234 + KeyCode::Char('+') | KeyCode::Char('=') => { 235 + if app.view == View::Dsp { 236 + if dsp_rows::adjust(&mut app.dsp, app.dsp_row, 1) { 237 + player.apply_dsp(&app.dsp); 238 + } 239 + } else { 240 + player.bump_volume(0.05); 241 + } 242 + } 243 + KeyCode::Char('-') | KeyCode::Char('_') => { 244 + if app.view == View::Dsp { 245 + if dsp_rows::adjust(&mut app.dsp, app.dsp_row, -1) { 246 + player.apply_dsp(&app.dsp); 247 + } 248 + } else { 249 + player.bump_volume(-0.05); 250 + } 251 + } 252 + KeyCode::Char('f') => favorite_selected(app, atproto, tx), 253 + KeyCode::Up | KeyCode::Char('k') => move_up(app), 254 + KeyCode::Down | KeyCode::Char('j') => move_down(app), 255 + KeyCode::Left => { 256 + if app.view == View::Home { 257 + app.home_tab = app.home_tab.prev(); 258 + app.selected = 0; 259 + } 260 + } 261 + KeyCode::Right | KeyCode::Tab => { 262 + if app.view == View::Home { 263 + app.home_tab = app.home_tab.next(); 264 + app.selected = 0; 265 + } 266 + } 267 + KeyCode::Enter => play_selected(app, player, browser, atproto, appview, tx), 268 + _ => {} 269 + } 270 + } 271 + 272 + fn move_up(app: &mut App) { 273 + match app.view { 274 + View::Dsp => { 275 + app.dsp_row = app.dsp_row.saturating_sub(1); 276 + } 277 + View::Home => { 278 + app.selected = app.selected.saturating_sub(1); 279 + } 280 + _ => {} 281 + } 282 + } 283 + 284 + fn move_down(app: &mut App) { 285 + match app.view { 286 + View::Dsp => { 287 + if app.dsp_row + 1 < dsp_rows::row_count() { 288 + app.dsp_row += 1; 289 + } 290 + } 291 + View::Home => { 292 + let len = app.active_list().len(); 293 + if len > 0 && app.selected + 1 < len { 294 + app.selected += 1; 295 + } 296 + } 297 + _ => {} 298 + } 299 + } 300 + 301 + fn play_selected( 302 + app: &mut App, 303 + player: &Arc<Player>, 304 + browser: &RadioBrowser, 305 + atproto: &Arc<Atproto>, 306 + _appview: &AppView, 307 + tx: &mpsc::UnboundedSender<Msg>, 308 + ) { 309 + if app.view != View::Home { 310 + return; 311 + } 312 + let Some(station) = app.selected_station().cloned() else { 313 + return; 314 + }; 315 + start_playing(app, player, browser, atproto, tx, station); 316 + } 317 + 318 + fn start_playing( 319 + app: &mut App, 320 + player: &Arc<Player>, 321 + browser: &RadioBrowser, 322 + atproto: &Arc<Atproto>, 323 + tx: &mpsc::UnboundedSender<Msg>, 324 + station: StationInfo, 325 + ) { 326 + player.play_url(&station.stream_url); 327 + app.toast.set(format!("▶ {}", station.name)); 328 + app.current = Some(station.clone()); 329 + 330 + // Register the play with radio-browser (click count) and the AppView 331 + // (actor status → recently-played), best-effort. 332 + let b = browser.clone(); 333 + let sid = station.station_id.clone(); 334 + tokio::spawn(async move { b.register_click(&sid).await }); 335 + 336 + if atproto.is_logged_in() { 337 + let at = atproto.clone(); 338 + let s = station.clone(); 339 + let tx2 = tx.clone(); 340 + tokio::spawn(async move { 341 + if let Err(e) = at.set_play_status(&s).await { 342 + let _ = tx2.send(Msg::Toast(format!("play status: {e}"))); 343 + } 344 + }); 345 + } 346 + } 347 + 348 + fn favorite_selected(app: &mut App, atproto: &Arc<Atproto>, tx: &mpsc::UnboundedSender<Msg>) { 349 + let station = app 350 + .selected_station() 351 + .cloned() 352 + .or_else(|| app.current.clone()); 353 + let Some(station) = station else { 354 + return; 355 + }; 356 + if !atproto.is_logged_in() { 357 + app.toast.set("Sign in first: run `atradio login`."); 358 + return; 359 + } 360 + let at = atproto.clone(); 361 + let tx = tx.clone(); 362 + let name = station.name.clone(); 363 + tokio::spawn(async move { 364 + let msg = match at.favorite(&station).await { 365 + Ok(_) => format!("★ Favorited {name}"), 366 + Err(e) => format!("favorite failed: {e}"), 367 + }; 368 + let _ = tx.send(Msg::Toast(msg)); 369 + }); 370 + } 371 + 372 + fn handle_search_keys( 373 + code: KeyCode, 374 + app: &mut App, 375 + player: &Arc<Player>, 376 + atproto: &Arc<Atproto>, 377 + tx: &mpsc::UnboundedSender<Msg>, 378 + ) { 379 + match code { 380 + KeyCode::Esc => { 381 + app.overlay = Overlay::None; 382 + } 383 + KeyCode::Enter => { 384 + let ranked = app.ranked_search(); 385 + if let Some((_, s)) = ranked.get(app.search_selected) { 386 + let station = (*s).clone(); 387 + app.overlay = Overlay::None; 388 + // Reuse the play path (no browser handle here; play directly). 389 + player.play_url(&station.stream_url); 390 + app.toast.set(format!("▶ {}", station.name)); 391 + app.current = Some(station.clone()); 392 + if atproto.is_logged_in() { 393 + let at = atproto.clone(); 394 + let tx2 = tx.clone(); 395 + tokio::spawn(async move { 396 + let _ = at.set_play_status(&station).await; 397 + let _ = tx2; 398 + }); 399 + } 400 + } 401 + } 402 + KeyCode::Up => { 403 + app.search_selected = app.search_selected.saturating_sub(1); 404 + } 405 + KeyCode::Down => { 406 + let len = app.ranked_search().len(); 407 + if len > 0 && app.search_selected + 1 < len { 408 + app.search_selected += 1; 409 + } 410 + } 411 + KeyCode::Backspace => { 412 + app.search_query.pop(); 413 + app.search_dirty = true; 414 + } 415 + KeyCode::Char(c) => { 416 + app.search_query.push(c); 417 + app.search_dirty = true; 418 + } 419 + _ => {} 420 + } 421 + } 422 + 423 + fn handle_compose_keys( 424 + code: KeyCode, 425 + app: &mut App, 426 + atproto: &Arc<Atproto>, 427 + tx: &mpsc::UnboundedSender<Msg>, 428 + ) { 429 + match code { 430 + KeyCode::Esc => { 431 + app.overlay = Overlay::None; 432 + app.compose_text.clear(); 433 + } 434 + KeyCode::Enter => { 435 + let text = app.compose_text.trim().to_string(); 436 + let station = app.current.clone(); 437 + app.overlay = Overlay::None; 438 + app.compose_text.clear(); 439 + if text.is_empty() { 440 + return; 441 + } 442 + let Some(station) = station else { return }; 443 + if !atproto.is_logged_in() { 444 + app.toast.set("Sign in first: run `atradio login`."); 445 + return; 446 + } 447 + let at = atproto.clone(); 448 + let tx = tx.clone(); 449 + tokio::spawn(async move { 450 + let msg = match at.comment(&station, &text).await { 451 + Ok(_) => "💬 Comment posted".to_string(), 452 + Err(e) => format!("comment failed: {e}"), 453 + }; 454 + let _ = tx.send(Msg::Toast(msg)); 455 + }); 456 + } 457 + KeyCode::Backspace => { 458 + app.compose_text.pop(); 459 + } 460 + KeyCode::Char(c) => { 461 + app.compose_text.push(c); 462 + } 463 + _ => {} 464 + } 465 + } 466 + 467 + // ---- async fetch spawners ------------------------------------------------ 468 + 469 + fn spawn_load_trending(appview: &AppView, tx: &mpsc::UnboundedSender<Msg>) { 470 + let av = appview.clone(); 471 + let tx = tx.clone(); 472 + tokio::spawn(async move { 473 + if let Ok(items) = av.recent_stations(50).await { 474 + let _ = tx.send(Msg::Trending(items.into_iter().map(|v| v.station).collect())); 475 + } 476 + }); 477 + } 478 + 479 + fn spawn_load_popular(appview: &AppView, tx: &mpsc::UnboundedSender<Msg>) { 480 + let av = appview.clone(); 481 + let tx = tx.clone(); 482 + tokio::spawn(async move { 483 + if let Ok(items) = av.popular_stations(50).await { 484 + let _ = tx.send(Msg::Popular(items.into_iter().map(|p| p.station).collect())); 485 + } 486 + }); 487 + } 488 + 489 + fn spawn_load_favorites(appview: &AppView, tx: &mpsc::UnboundedSender<Msg>, actor: String) { 490 + let av = appview.clone(); 491 + let tx = tx.clone(); 492 + tokio::spawn(async move { 493 + if let Ok(out) = av.favorites(&actor, 100).await { 494 + let _ = tx.send(Msg::Favorites(out.items.into_iter().map(|v| v.station).collect())); 495 + } 496 + }); 497 + } 498 + 499 + fn spawn_load_comments(appview: &AppView, tx: &mpsc::UnboundedSender<Msg>, station_id: String) { 500 + let av = appview.clone(); 501 + let tx = tx.clone(); 502 + tokio::spawn(async move { 503 + if let Ok(out) = av.comments(&station_id, 100).await { 504 + let _ = tx.send(Msg::Comments(out.items)); 505 + } 506 + }); 507 + } 508 + 509 + fn spawn_load_notifications(appview: &AppView, tx: &mpsc::UnboundedSender<Msg>, actor: String) { 510 + let av = appview.clone(); 511 + let tx = tx.clone(); 512 + tokio::spawn(async move { 513 + if let Ok(out) = av.notifications(&actor, 50).await { 514 + let _ = tx.send(Msg::Notifications { 515 + items: out.items, 516 + unread: out.unread_count, 517 + }); 518 + } 519 + }); 520 + } 521 + 522 + fn spawn_search(browser: &RadioBrowser, tx: &mpsc::UnboundedSender<Msg>, query: String) { 523 + let b = browser.clone(); 524 + let tx = tx.clone(); 525 + tokio::spawn(async move { 526 + if let Ok(items) = b.search(&query, 40).await { 527 + let _ = tx.send(Msg::SearchResults { query, items }); 528 + } 529 + }); 530 + } 531 + 532 + // ---- terminal setup / teardown ------------------------------------------- 533 + 534 + fn setup_terminal() -> Result<Term> { 535 + enable_raw_mode()?; 536 + let mut stdout = io::stdout(); 537 + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; 538 + let backend = CrosstermBackend::new(stdout); 539 + let mut term = Terminal::new(backend)?; 540 + term.clear()?; 541 + Ok(term) 542 + } 543 + 544 + fn restore_terminal(term: &mut Term) -> Result<()> { 545 + disable_raw_mode()?; 546 + execute!(term.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?; 547 + term.show_cursor()?; 548 + Ok(()) 549 + }
+217
cli/src/tui/state.rs
··· 1 + //! TUI application state. 2 + 3 + use fuzzy_matcher::skim::SkimMatcherV2; 4 + use fuzzy_matcher::FuzzyMatcher; 5 + 6 + use crate::appview::{CommentView, NotificationView, StationInfo}; 7 + use crate::player::AudioSettings; 8 + 9 + /// Which full-screen view is active. 10 + #[derive(Clone, Copy, PartialEq, Eq)] 11 + pub enum View { 12 + /// The home/browse screen (trending + popular + favorites). 13 + Home, 14 + /// The equalizer / DSP settings panel. 15 + Dsp, 16 + /// Comments for the current station. 17 + Comments, 18 + /// Notifications. 19 + Notifications, 20 + /// Keybindings help. 21 + Help, 22 + } 23 + 24 + /// A transient overlay drawn on top of the active view. 25 + #[derive(Clone, Copy, PartialEq, Eq)] 26 + pub enum Overlay { 27 + None, 28 + /// fzf-style fuzzy station search. 29 + Search, 30 + /// Comment composer. 31 + Compose, 32 + } 33 + 34 + /// Home has several horizontally-laid lists; this tracks focus. 35 + #[derive(Clone, Copy, PartialEq, Eq)] 36 + pub enum HomeTab { 37 + Trending, 38 + Popular, 39 + Favorites, 40 + } 41 + 42 + impl HomeTab { 43 + pub fn all() -> [HomeTab; 3] { 44 + [HomeTab::Trending, HomeTab::Popular, HomeTab::Favorites] 45 + } 46 + pub fn label(self) -> &'static str { 47 + match self { 48 + HomeTab::Trending => "Trending", 49 + HomeTab::Popular => "Popular", 50 + HomeTab::Favorites => "Favorites", 51 + } 52 + } 53 + pub fn next(self) -> HomeTab { 54 + match self { 55 + HomeTab::Trending => HomeTab::Popular, 56 + HomeTab::Popular => HomeTab::Favorites, 57 + HomeTab::Favorites => HomeTab::Trending, 58 + } 59 + } 60 + pub fn prev(self) -> HomeTab { 61 + match self { 62 + HomeTab::Trending => HomeTab::Favorites, 63 + HomeTab::Popular => HomeTab::Trending, 64 + HomeTab::Favorites => HomeTab::Popular, 65 + } 66 + } 67 + } 68 + 69 + /// A short-lived status message shown in the footer. 70 + #[derive(Clone, Default)] 71 + pub struct Toast { 72 + pub text: String, 73 + /// Remaining ticks before it clears. 74 + pub ttl: u8, 75 + } 76 + 77 + impl Toast { 78 + pub fn set(&mut self, text: impl Into<String>) { 79 + self.text = text.into(); 80 + self.ttl = 6; // ~3s at 500ms/tick 81 + } 82 + pub fn tick(&mut self) { 83 + if self.ttl > 0 { 84 + self.ttl -= 1; 85 + if self.ttl == 0 { 86 + self.text.clear(); 87 + } 88 + } 89 + } 90 + } 91 + 92 + pub struct App { 93 + pub view: View, 94 + pub overlay: Overlay, 95 + pub should_quit: bool, 96 + 97 + // ---- home lists ---- 98 + pub home_tab: HomeTab, 99 + pub trending: Vec<StationInfo>, 100 + pub popular: Vec<StationInfo>, 101 + pub favorites: Vec<StationInfo>, 102 + pub selected: usize, 103 + 104 + // ---- search overlay ---- 105 + pub search_query: String, 106 + pub search_results: Vec<StationInfo>, 107 + pub search_selected: usize, 108 + /// Set when the query changed and a fetch should fire. 109 + pub search_dirty: bool, 110 + 111 + // ---- compose overlay ---- 112 + pub compose_text: String, 113 + 114 + // ---- comments / notifications ---- 115 + pub comments: Vec<CommentView>, 116 + pub comments_selected: usize, 117 + pub notifications: Vec<NotificationView>, 118 + pub unread: u32, 119 + 120 + // ---- player (mirrored from the engine for rendering) ---- 121 + pub current: Option<StationInfo>, 122 + pub volume: f32, 123 + pub muted: bool, 124 + 125 + // ---- DSP ---- 126 + pub dsp: AudioSettings, 127 + pub dsp_row: usize, 128 + 129 + // ---- identity ---- 130 + pub logged_in: bool, 131 + pub handle: Option<String>, 132 + 133 + pub toast: Toast, 134 + matcher: SkimMatcherV2, 135 + } 136 + 137 + impl App { 138 + pub fn new(logged_in: bool, handle: Option<String>) -> Self { 139 + Self { 140 + view: View::Home, 141 + overlay: Overlay::None, 142 + should_quit: false, 143 + home_tab: HomeTab::Trending, 144 + trending: Vec::new(), 145 + popular: Vec::new(), 146 + favorites: Vec::new(), 147 + selected: 0, 148 + search_query: String::new(), 149 + search_results: Vec::new(), 150 + search_selected: 0, 151 + search_dirty: false, 152 + compose_text: String::new(), 153 + comments: Vec::new(), 154 + comments_selected: 0, 155 + notifications: Vec::new(), 156 + unread: 0, 157 + current: None, 158 + volume: 0.8, 159 + muted: false, 160 + dsp: AudioSettings::default(), 161 + dsp_row: 0, 162 + logged_in, 163 + handle, 164 + toast: Toast::default(), 165 + matcher: SkimMatcherV2::default(), 166 + } 167 + } 168 + 169 + /// The station list backing the active home tab. 170 + pub fn active_list(&self) -> &[StationInfo] { 171 + match self.home_tab { 172 + HomeTab::Trending => &self.trending, 173 + HomeTab::Popular => &self.popular, 174 + HomeTab::Favorites => &self.favorites, 175 + } 176 + } 177 + 178 + pub fn selected_station(&self) -> Option<&StationInfo> { 179 + self.active_list().get(self.selected) 180 + } 181 + 182 + /// Re-rank cached search results against the current query (fzf-style). 183 + /// Returns ranked references for rendering. 184 + pub fn ranked_search(&self) -> Vec<(i64, &StationInfo)> { 185 + if self.search_query.is_empty() { 186 + return self 187 + .search_results 188 + .iter() 189 + .map(|s| (0i64, s)) 190 + .collect(); 191 + } 192 + let mut scored: Vec<(i64, &StationInfo)> = self 193 + .search_results 194 + .iter() 195 + .filter_map(|s| { 196 + self.matcher 197 + .fuzzy_match(&s.name, &self.search_query) 198 + .map(|score| (score, s)) 199 + }) 200 + .collect(); 201 + scored.sort_by(|a, b| b.0.cmp(&a.0)); 202 + scored 203 + } 204 + 205 + pub fn clamp_selection(&mut self) { 206 + let len = self.active_list().len(); 207 + if len == 0 { 208 + self.selected = 0; 209 + } else if self.selected >= len { 210 + self.selected = len - 1; 211 + } 212 + } 213 + 214 + pub fn volume_pct(&self) -> u16 { 215 + (self.volume * 100.0).round() as u16 216 + } 217 + }
+550
cli/src/tui/ui.rs
··· 1 + //! Rendering. Synthwave palette, laid out to echo the web app: a top bar, the 2 + //! active view, a persistent player bar, and a footer of keybindings. 3 + 4 + use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; 5 + use ratatui::style::{Modifier, Style}; 6 + use ratatui::text::{Line, Span}; 7 + use ratatui::widgets::{Block, BorderType, Borders, Clear, Gauge, Paragraph}; 8 + use ratatui::Frame; 9 + 10 + use super::dsp_rows; 11 + use super::state::{App, HomeTab, Overlay, View}; 12 + use crate::appview::StationInfo; 13 + use crate::player::NowPlaying; 14 + use crate::theme; 15 + 16 + pub fn draw(f: &mut Frame, app: &App, np: &NowPlaying) { 17 + let root = Layout::default() 18 + .direction(Direction::Vertical) 19 + .constraints([ 20 + Constraint::Length(1), // top bar 21 + Constraint::Min(3), // content 22 + Constraint::Length(4), // player bar 23 + Constraint::Length(1), // footer 24 + ]) 25 + .split(f.area()); 26 + 27 + // Background fill. 28 + f.render_widget(Block::default().style(Style::default().bg(theme::BG)), f.area()); 29 + 30 + draw_topbar(f, root[0], app); 31 + match app.view { 32 + View::Home => draw_home(f, root[1], app), 33 + View::Dsp => draw_dsp(f, root[1], app), 34 + View::Comments => draw_comments(f, root[1], app), 35 + View::Notifications => draw_notifications(f, root[1], app), 36 + View::Help => draw_help(f, root[1]), 37 + } 38 + draw_player(f, root[2], app, np); 39 + draw_footer(f, root[3], app); 40 + 41 + match app.overlay { 42 + Overlay::Search => draw_search(f, f.area(), app), 43 + Overlay::Compose => draw_compose(f, f.area(), app), 44 + Overlay::None => {} 45 + } 46 + } 47 + 48 + fn draw_topbar(f: &mut Frame, area: Rect, app: &App) { 49 + let cols = Layout::default() 50 + .direction(Direction::Horizontal) 51 + .constraints([Constraint::Min(10), Constraint::Length(28)]) 52 + .split(area); 53 + 54 + let brand = Line::from(vec![ 55 + Span::styled("atradio", Style::default().fg(theme::TEAL).add_modifier(Modifier::BOLD)), 56 + Span::styled(".fm", Style::default().fg(theme::CYAN).add_modifier(Modifier::BOLD)), 57 + Span::styled(" · social radio in your terminal", Style::default().fg(theme::MUTED)), 58 + ]); 59 + f.render_widget( 60 + Paragraph::new(brand).style(Style::default().bg(theme::SURFACE)), 61 + cols[0], 62 + ); 63 + 64 + let who = match &app.handle { 65 + Some(h) => Line::from(vec![ 66 + bell(app.unread), 67 + Span::styled(format!(" @{h} "), Style::default().fg(theme::GREEN)), 68 + ]), 69 + None => Line::from(vec![ 70 + bell(app.unread), 71 + Span::styled(" guest ", Style::default().fg(theme::MUTED)), 72 + ]), 73 + }; 74 + f.render_widget( 75 + Paragraph::new(who) 76 + .alignment(Alignment::Right) 77 + .style(Style::default().bg(theme::SURFACE)), 78 + cols[1], 79 + ); 80 + } 81 + 82 + fn bell(unread: u32) -> Span<'static> { 83 + if unread > 0 { 84 + Span::styled( 85 + format!("🔔{unread}"), 86 + Style::default().fg(theme::ORANGE).add_modifier(Modifier::BOLD), 87 + ) 88 + } else { 89 + Span::styled("🔔", Style::default().fg(theme::MUTED)) 90 + } 91 + } 92 + 93 + fn panel(title: &str, focused: bool) -> Block<'_> { 94 + let border = if focused { theme::TEAL } else { theme::BORDER }; 95 + Block::default() 96 + .borders(Borders::ALL) 97 + .border_type(BorderType::Rounded) 98 + .border_style(Style::default().fg(border)) 99 + .title(Span::styled( 100 + format!(" {title} "), 101 + Style::default().fg(if focused { theme::TEAL } else { theme::MUTED }), 102 + )) 103 + .style(Style::default().bg(theme::BG)) 104 + } 105 + 106 + fn draw_home(f: &mut Frame, area: Rect, app: &App) { 107 + // Tabs row + list. 108 + let rows = Layout::default() 109 + .direction(Direction::Vertical) 110 + .constraints([Constraint::Length(1), Constraint::Min(3)]) 111 + .split(area); 112 + 113 + // Tabs. 114 + let mut spans: Vec<Span> = Vec::new(); 115 + for t in HomeTab::all() { 116 + let active = t == app.home_tab; 117 + let style = if active { 118 + Style::default().fg(theme::BG).bg(theme::TEAL).add_modifier(Modifier::BOLD) 119 + } else { 120 + Style::default().fg(theme::MUTED) 121 + }; 122 + spans.push(Span::styled(format!(" {} ", t.label()), style)); 123 + spans.push(Span::raw(" ")); 124 + } 125 + f.render_widget(Paragraph::new(Line::from(spans)), rows[0]); 126 + 127 + let list = app.active_list(); 128 + let block = panel(app.home_tab.label(), true); 129 + let inner = block.inner(rows[1]); 130 + f.render_widget(block, rows[1]); 131 + 132 + if list.is_empty() { 133 + let msg = match app.home_tab { 134 + HomeTab::Favorites if !app.logged_in => { 135 + "Sign in to see your favorites (run `atradio login`)." 136 + } 137 + HomeTab::Favorites => "No favorites yet. Press f on a station to favorite it.", 138 + _ => "Loading…", 139 + }; 140 + f.render_widget( 141 + Paragraph::new(msg).style(Style::default().fg(theme::MUTED)), 142 + inner, 143 + ); 144 + return; 145 + } 146 + render_station_list(f, inner, list, app.selected, app.current.as_ref()); 147 + } 148 + 149 + fn render_station_list( 150 + f: &mut Frame, 151 + area: Rect, 152 + list: &[StationInfo], 153 + selected: usize, 154 + current: Option<&StationInfo>, 155 + ) { 156 + let height = area.height as usize; 157 + let start = selected.saturating_sub(height.saturating_sub(1)); 158 + let mut lines: Vec<Line> = Vec::new(); 159 + for (i, s) in list.iter().enumerate().skip(start).take(height) { 160 + let is_sel = i == selected; 161 + let is_cur = current.map(|c| c.station_id == s.station_id).unwrap_or(false); 162 + let marker = if is_cur { "♪ " } else if is_sel { "› " } else { " " }; 163 + let name_style = if is_sel { 164 + Style::default().fg(theme::BG).bg(theme::TEAL).add_modifier(Modifier::BOLD) 165 + } else if is_cur { 166 + Style::default().fg(theme::GREEN) 167 + } else { 168 + Style::default().fg(theme::FG) 169 + }; 170 + let sub = s.subtitle(); 171 + let mut spans = vec![ 172 + Span::styled(marker, Style::default().fg(theme::CYAN)), 173 + Span::styled(truncate(&s.name, 40), name_style), 174 + ]; 175 + if !sub.is_empty() { 176 + spans.push(Span::styled(format!(" {sub}"), Style::default().fg(theme::MUTED))); 177 + } 178 + lines.push(Line::from(spans)); 179 + } 180 + f.render_widget(Paragraph::new(lines), area); 181 + } 182 + 183 + fn draw_dsp(f: &mut Frame, area: Rect, app: &App) { 184 + let block = panel("Equalizer & DSP", true); 185 + let inner = block.inner(area); 186 + f.render_widget(block, area); 187 + 188 + let rows = dsp_rows::rows(&app.dsp); 189 + let height = inner.height as usize; 190 + let start = app.dsp_row.saturating_sub(height.saturating_sub(1)); 191 + 192 + let mut y = inner.y; 193 + for (i, row) in rows.iter().enumerate().skip(start).take(height) { 194 + let is_sel = i == app.dsp_row; 195 + let line_area = Rect::new(inner.x, y, inner.width, 1); 196 + y += 1; 197 + 198 + let label_w = 18u16.min(inner.width); 199 + let label_style = if is_sel { 200 + Style::default().fg(theme::TEAL).add_modifier(Modifier::BOLD) 201 + } else { 202 + Style::default().fg(theme::FG) 203 + }; 204 + let label_area = Rect::new(line_area.x, line_area.y, label_w, 1); 205 + f.render_widget( 206 + Paragraph::new(Line::from(vec![ 207 + Span::styled(if is_sel { "› " } else { " " }, Style::default().fg(theme::CYAN)), 208 + Span::styled(truncate(&row.label, label_w as usize - 2), label_style), 209 + ])), 210 + label_area, 211 + ); 212 + 213 + let rest = Rect::new( 214 + line_area.x + label_w, 215 + line_area.y, 216 + line_area.width.saturating_sub(label_w), 217 + 1, 218 + ); 219 + match row.fill { 220 + Some(fill) => { 221 + let bar_w = rest.width.saturating_sub(9); 222 + let bar_area = Rect::new(rest.x, rest.y, bar_w, 1); 223 + let g = Gauge::default() 224 + .gauge_style(Style::default().fg(if is_sel { theme::TEAL } else { theme::INDIGO })) 225 + .ratio(fill as f64) 226 + .label(""); 227 + f.render_widget(g, bar_area); 228 + let val_area = Rect::new(rest.x + bar_w, rest.y, rest.width.saturating_sub(bar_w), 1); 229 + f.render_widget( 230 + Paragraph::new(Span::styled( 231 + format!(" {}", row.value), 232 + Style::default().fg(theme::MUTED), 233 + )), 234 + val_area, 235 + ); 236 + } 237 + None => { 238 + let style = Style::default() 239 + .fg(if row.value == "Off" { theme::MUTED } else { theme::GREEN }); 240 + f.render_widget(Paragraph::new(Span::styled(row.value.clone(), style)), rest); 241 + } 242 + } 243 + } 244 + } 245 + 246 + fn draw_comments(f: &mut Frame, area: Rect, app: &App) { 247 + let title = app 248 + .current 249 + .as_ref() 250 + .map(|s| format!("Comments · {}", s.name)) 251 + .unwrap_or_else(|| "Comments".into()); 252 + let block = panel(&title, true); 253 + let inner = block.inner(area); 254 + f.render_widget(block, area); 255 + 256 + if app.current.is_none() { 257 + f.render_widget( 258 + Paragraph::new("Play a station first, then press c to see its comments.") 259 + .style(Style::default().fg(theme::MUTED)), 260 + inner, 261 + ); 262 + return; 263 + } 264 + if app.comments.is_empty() { 265 + f.render_widget( 266 + Paragraph::new("No comments yet. Press a to add one.") 267 + .style(Style::default().fg(theme::MUTED)), 268 + inner, 269 + ); 270 + return; 271 + } 272 + let mut lines: Vec<Line> = Vec::new(); 273 + for c in &app.comments { 274 + let who = c 275 + .author 276 + .as_ref() 277 + .map(|a| a.name()) 278 + .unwrap_or_else(|| "someone".into()); 279 + lines.push(Line::from(vec![ 280 + Span::styled(format!("{who} "), Style::default().fg(theme::CYAN).add_modifier(Modifier::BOLD)), 281 + Span::styled(c.text.clone(), Style::default().fg(theme::FG)), 282 + ])); 283 + } 284 + f.render_widget(Paragraph::new(lines), inner); 285 + } 286 + 287 + fn draw_notifications(f: &mut Frame, area: Rect, app: &App) { 288 + let block = panel("Notifications", true); 289 + let inner = block.inner(area); 290 + f.render_widget(block, area); 291 + 292 + if app.notifications.is_empty() { 293 + // Match the web app's empty copy, centered. 294 + let msg = "Nothing yet. Mentions and comments on your stations show up here."; 295 + let vy = inner.y + inner.height / 2; 296 + let line_area = Rect::new(inner.x, vy, inner.width, 1); 297 + f.render_widget( 298 + Paragraph::new(msg) 299 + .alignment(Alignment::Center) 300 + .style(Style::default().fg(theme::MUTED)), 301 + line_area, 302 + ); 303 + return; 304 + } 305 + let mut lines: Vec<Line> = Vec::new(); 306 + for n in &app.notifications { 307 + let verb = if n.reason == "mention" { "mentioned you" } else { "commented on your station" }; 308 + let dot = if n.is_read { " " } else { "• " }; 309 + lines.push(Line::from(vec![ 310 + Span::styled(dot, Style::default().fg(theme::ORANGE)), 311 + Span::styled(format!("{} ", n.author.name()), Style::default().fg(theme::GREEN).add_modifier(Modifier::BOLD)), 312 + Span::styled(verb, Style::default().fg(theme::FG)), 313 + ])); 314 + if let Some(t) = &n.text { 315 + lines.push(Line::from(Span::styled( 316 + format!(" {}", truncate(t, inner.width as usize - 4)), 317 + Style::default().fg(theme::MUTED), 318 + ))); 319 + } 320 + } 321 + f.render_widget(Paragraph::new(lines), inner); 322 + } 323 + 324 + fn draw_help(f: &mut Frame, area: Rect) { 325 + let block = panel("Keybindings", true); 326 + let inner = block.inner(area); 327 + f.render_widget(block, area); 328 + 329 + let keys = [ 330 + ("↑/↓ j/k", "move selection"), 331 + ("←/→ Tab", "switch list / tab"), 332 + ("Enter", "play selected station"), 333 + ("Space", "play / pause"), 334 + ("+/-", "volume up / down (or adjust DSP value)"), 335 + ("m", "mute"), 336 + ("/", "fuzzy station search"), 337 + ("f", "favorite the selected/current station"), 338 + ("c", "comments for the current station"), 339 + ("a", "add a comment"), 340 + ("n", "notifications"), 341 + ("e", "equalizer & DSP settings"), 342 + ("h", "home"), 343 + ("?", "this help"), 344 + ("q / Esc", "quit / close overlay"), 345 + ]; 346 + let mut lines: Vec<Line> = Vec::new(); 347 + for (k, d) in keys { 348 + lines.push(Line::from(vec![ 349 + Span::styled(format!(" {k:<10}"), Style::default().fg(theme::TEAL).add_modifier(Modifier::BOLD)), 350 + Span::styled(d, Style::default().fg(theme::FG)), 351 + ])); 352 + } 353 + f.render_widget(Paragraph::new(lines), inner); 354 + } 355 + 356 + fn draw_player(f: &mut Frame, area: Rect, app: &App, np: &NowPlaying) { 357 + let block = Block::default() 358 + .borders(Borders::TOP) 359 + .border_style(Style::default().fg(theme::BORDER)) 360 + .style(Style::default().bg(theme::SURFACE)); 361 + let inner = block.inner(area); 362 + f.render_widget(block, area); 363 + 364 + let rows = Layout::default() 365 + .direction(Direction::Vertical) 366 + .constraints([Constraint::Length(1), Constraint::Length(1), Constraint::Length(1)]) 367 + .split(inner); 368 + 369 + match &app.current { 370 + Some(s) => { 371 + let state = crate::tui::status_glyph(np.state); 372 + f.render_widget( 373 + Paragraph::new(Line::from(vec![ 374 + Span::styled(format!("{state} "), Style::default().fg(theme::GREEN)), 375 + Span::styled(truncate(&s.name, 48), Style::default().fg(theme::FG).add_modifier(Modifier::BOLD)), 376 + Span::styled(format!(" {}", s.subtitle()), Style::default().fg(theme::MUTED)), 377 + ])), 378 + rows[0], 379 + ); 380 + let now = np.line().unwrap_or_else(|| "—".into()); 381 + f.render_widget( 382 + Paragraph::new(Line::from(vec![ 383 + Span::styled("♪ ", Style::default().fg(theme::CYAN)), 384 + Span::styled(truncate(&now, 60), Style::default().fg(theme::CYAN)), 385 + ])), 386 + rows[1], 387 + ); 388 + } 389 + None => { 390 + f.render_widget( 391 + Paragraph::new("Nothing playing — press Enter on a station, or / to search.") 392 + .style(Style::default().fg(theme::MUTED)), 393 + rows[0], 394 + ); 395 + } 396 + } 397 + 398 + // Volume line. 399 + let vol = (app.volume_pct()).min(100); 400 + let muted = app.muted; 401 + let vol_label = if muted { "muted".to_string() } else { format!("{vol}%") }; 402 + f.render_widget( 403 + Paragraph::new(Line::from(vec![ 404 + Span::styled("vol ", Style::default().fg(theme::MUTED)), 405 + Span::styled(volume_bar(vol), Style::default().fg(if muted { theme::MUTED } else { theme::TEAL })), 406 + Span::styled(format!(" {vol_label}"), Style::default().fg(theme::MUTED)), 407 + ])), 408 + rows[2], 409 + ); 410 + } 411 + 412 + fn draw_footer(f: &mut Frame, area: Rect, app: &App) { 413 + if app.toast.ttl > 0 && !app.toast.text.is_empty() { 414 + f.render_widget( 415 + Paragraph::new(Span::styled( 416 + format!(" {}", app.toast.text), 417 + Style::default().fg(theme::GREEN), 418 + )) 419 + .style(Style::default().bg(theme::BG)), 420 + area, 421 + ); 422 + return; 423 + } 424 + let hint = " /=search e=eq c=comments n=notifs f=fav Space=play/pause ?=help q=quit"; 425 + f.render_widget( 426 + Paragraph::new(Span::styled(hint, Style::default().fg(theme::MUTED))) 427 + .style(Style::default().bg(theme::BG)), 428 + area, 429 + ); 430 + } 431 + 432 + fn draw_search(f: &mut Frame, area: Rect, app: &App) { 433 + let popup = centered(area, 70, 60); 434 + f.render_widget(Clear, popup); 435 + let block = panel("Search stations (fuzzy)", true); 436 + let inner = block.inner(popup); 437 + f.render_widget(block, popup); 438 + 439 + let rows = Layout::default() 440 + .direction(Direction::Vertical) 441 + .constraints([Constraint::Length(1), Constraint::Length(1), Constraint::Min(1)]) 442 + .split(inner); 443 + 444 + f.render_widget( 445 + Paragraph::new(Line::from(vec![ 446 + Span::styled("/ ", Style::default().fg(theme::TEAL).add_modifier(Modifier::BOLD)), 447 + Span::styled(&app.search_query, Style::default().fg(theme::FG)), 448 + Span::styled("█", Style::default().fg(theme::TEAL)), 449 + ])), 450 + rows[0], 451 + ); 452 + f.render_widget( 453 + Paragraph::new(Span::styled( 454 + "type to filter · ↑↓ to move · Enter to play · Esc to close", 455 + Style::default().fg(theme::MUTED), 456 + )), 457 + rows[1], 458 + ); 459 + 460 + let ranked = app.ranked_search(); 461 + let height = rows[2].height as usize; 462 + let start = app.search_selected.saturating_sub(height.saturating_sub(1)); 463 + let mut lines: Vec<Line> = Vec::new(); 464 + for (i, (_, s)) in ranked.iter().enumerate().skip(start).take(height) { 465 + let is_sel = i == app.search_selected; 466 + let style = if is_sel { 467 + Style::default().fg(theme::BG).bg(theme::TEAL).add_modifier(Modifier::BOLD) 468 + } else { 469 + Style::default().fg(theme::FG) 470 + }; 471 + let sub = s.subtitle(); 472 + let mut spans = vec![ 473 + Span::styled(if is_sel { "› " } else { " " }, Style::default().fg(theme::CYAN)), 474 + Span::styled(truncate(&s.name, 44), style), 475 + ]; 476 + if !sub.is_empty() { 477 + spans.push(Span::styled(format!(" {sub}"), Style::default().fg(theme::MUTED))); 478 + } 479 + lines.push(Line::from(spans)); 480 + } 481 + if lines.is_empty() { 482 + lines.push(Line::from(Span::styled( 483 + " no matches", 484 + Style::default().fg(theme::MUTED), 485 + ))); 486 + } 487 + f.render_widget(Paragraph::new(lines), rows[2]); 488 + } 489 + 490 + fn draw_compose(f: &mut Frame, area: Rect, app: &App) { 491 + let popup = centered(area, 60, 30); 492 + f.render_widget(Clear, popup); 493 + let title = app 494 + .current 495 + .as_ref() 496 + .map(|s| format!("Comment on {}", s.name)) 497 + .unwrap_or_else(|| "Comment".into()); 498 + let block = panel(&title, true); 499 + let inner = block.inner(popup); 500 + f.render_widget(block, popup); 501 + 502 + let rows = Layout::default() 503 + .direction(Direction::Vertical) 504 + .constraints([Constraint::Min(1), Constraint::Length(1)]) 505 + .split(inner); 506 + f.render_widget( 507 + Paragraph::new(Line::from(vec![ 508 + Span::styled(&app.compose_text, Style::default().fg(theme::FG)), 509 + Span::styled("█", Style::default().fg(theme::TEAL)), 510 + ])) 511 + .wrap(ratatui::widgets::Wrap { trim: false }), 512 + rows[0], 513 + ); 514 + f.render_widget( 515 + Paragraph::new(Span::styled( 516 + "Enter to post · Esc to cancel", 517 + Style::default().fg(theme::MUTED), 518 + )), 519 + rows[1], 520 + ); 521 + } 522 + 523 + // ---- helpers ------------------------------------------------------------- 524 + 525 + fn centered(area: Rect, pct_w: u16, pct_h: u16) -> Rect { 526 + let w = area.width * pct_w / 100; 527 + let h = area.height * pct_h / 100; 528 + let x = area.x + (area.width - w) / 2; 529 + let y = area.y + (area.height - h) / 2; 530 + Rect::new(x, y, w, h) 531 + } 532 + 533 + fn volume_bar(pct: u16) -> String { 534 + let filled = (pct as usize * 16 / 100).min(16); 535 + let mut s = String::new(); 536 + for i in 0..16 { 537 + s.push(if i < filled { '▮' } else { '▯' }); 538 + } 539 + s 540 + } 541 + 542 + fn truncate(s: &str, max: usize) -> String { 543 + if s.chars().count() <= max { 544 + s.to_string() 545 + } else { 546 + let mut out: String = s.chars().take(max.saturating_sub(1)).collect(); 547 + out.push('…'); 548 + out 549 + } 550 + }
+6
tools/console/bb.edn
··· 27 27 28 28 lexicons:gen {:doc "Generate lexicon JSON from Pkl" :task (shell {:dir (str root "/packages/lexicons")} "bun" "run" "pkl:gen")} 29 29 30 + cli:build {:doc "Build the atradio CLI (release)" :task (shell {:dir (str root "/cli")} "cargo" "build" "--release")} 31 + cli:run {:doc "Run the atradio TUI" :task (shell {:dir (str root "/cli")} "cargo" "run")} 32 + cli:login {:doc "Sign in via the atradio CLI" :task (shell {:dir (str root "/cli")} "cargo" "run" "--" "login")} 33 + cli:whoami {:doc "Show the signed-in account" :task (shell {:dir (str root "/cli")} "cargo" "run" "--" "whoami")} 34 + cli:gen {:doc "Regenerate the CLI's Rust lexicon bindings" :task (shell {:dir (str root "/cli")} "bash" "scripts/gen-lexicons.sh")} 35 + 30 36 repl {:doc "Launch the interactive Clojure + rebel console" 31 37 :task (clojure "-M:console")}}}
+17
tools/console/src/atradio/console.clj
··· 25 25 (defn- web! [& args] (run* "apps/web" args)) 26 26 (defn- api! [& args] (run* "apps/api" args)) 27 27 (defn- lex! [& args] (run* "packages/lexicons" args)) 28 + (defn- cli! [& args] (run* "cli" args)) 28 29 29 30 ;; ---- workspace-wide (turbo) ---- 30 31 (defn install "Install all workspace deps" [] (root! "bun" "install")) ··· 53 54 ;; ---- lexicons ---- 54 55 (defn gen-lexicons "Generate lexicon JSON from Pkl" [] (lex! "bun" "run" "pkl:gen")) 55 56 57 + ;; ---- cli / TUI (the `atradio` Rust crate) ---- 58 + (defn cli-build "Build the atradio CLI (release)" [] (cli! "cargo" "build" "--release")) 59 + (defn cli-run "Run the atradio TUI" [] (cli! "cargo" "run" "--")) 60 + (defn cli-search "Search stations: (cli-search \"lofi\")" [q] (cli! "cargo" "run" "--" "search" q)) 61 + (defn cli-play "Play a stream URL or query: (cli-play \"jazz\")" [t] (cli! "cargo" "run" "--" "play" t)) 62 + (defn cli-login "Sign in (app password via env, or --oauth)" [] (cli! "cargo" "run" "--" "login")) 63 + (defn cli-whoami "Show the signed-in account" [] (cli! "cargo" "run" "--" "whoami")) 64 + (defn cli-gen-lexicons "Regenerate the CLI's Rust lexicon bindings" [] (cli! "bash" "scripts/gen-lexicons.sh")) 65 + 56 66 (def ^:private commands 57 67 [["(install)" "Install all workspace deps"] 58 68 ["(dev)" "Run every app in dev"] ··· 73 83 ["(db-up)" "Start local Postgres (docker)"] 74 84 ["(db-down)" "Stop local Postgres (docker)"] 75 85 ["(gen-lexicons)" "Generate lexicon JSON from Pkl"] 86 + ["(cli-build)" "Build the atradio CLI (release)"] 87 + ["(cli-run)" "Run the atradio TUI"] 88 + ["(cli-search q)" "Search stations from the CLI"] 89 + ["(cli-play t)" "Play a stream URL or query"] 90 + ["(cli-login)" "Sign in via the CLI"] 91 + ["(cli-whoami)" "Show the signed-in account"] 92 + ["(cli-gen-lexicons)" "Regenerate CLI lexicon bindings"] 76 93 ["(help)" "Show this list"]]) 77 94 78 95 (defn help "List all console commands" []