this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+791 -17
.github
workflows
.tangled
workflows
src
+96
.github/workflows/docker-publish.yml
···
··· 1 + name: Docker 2 + 3 + # This workflow uses actions that are not certified by GitHub. 4 + # They are provided by a third-party and are governed by 5 + # separate terms of service, privacy policy, and support 6 + # documentation. 7 + 8 + on: 9 + push: 10 + branches: [ "master" ] 11 + # Publish semver tags as releases. 12 + tags: [ 'v*.*.*' ] 13 + pull_request: 14 + branches: [ "master" ] 15 + 16 + env: 17 + # Use docker.io for Docker Hub if empty 18 + REGISTRY: ghcr.io 19 + # github.repository as <account>/<repo> 20 + IMAGE_NAME: ${{ github.repository }} 21 + 22 + 23 + jobs: 24 + build: 25 + 26 + runs-on: ubuntu-latest 27 + permissions: 28 + contents: read 29 + packages: write 30 + # This is used to complete the identity challenge 31 + # with sigstore/fulcio when running outside of PRs. 32 + id-token: write 33 + 34 + steps: 35 + - name: Checkout repository 36 + uses: actions/checkout@v4 37 + 38 + # Install the cosign tool except on PR 39 + # https://github.com/sigstore/cosign-installer 40 + - name: Install cosign 41 + if: github.event_name != 'pull_request' 42 + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 43 + with: 44 + cosign-release: 'v2.2.4' 45 + 46 + # Set up BuildKit Docker container builder to be able to build 47 + # multi-platform images and export cache 48 + # https://github.com/docker/setup-buildx-action 49 + - name: Set up Docker Buildx 50 + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 51 + 52 + # Login against a Docker registry except on PR 53 + # https://github.com/docker/login-action 54 + - name: Log into registry ${{ env.REGISTRY }} 55 + if: github.event_name != 'pull_request' 56 + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 57 + with: 58 + registry: ${{ env.REGISTRY }} 59 + username: ${{ github.actor }} 60 + password: ${{ secrets.GITHUB_TOKEN }} 61 + 62 + # Extract metadata (tags, labels) for Docker 63 + # https://github.com/docker/metadata-action 64 + - name: Extract Docker metadata 65 + id: meta 66 + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 67 + with: 68 + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 69 + 70 + # Build and push Docker image with Buildx (don't push on PR) 71 + # https://github.com/docker/build-push-action 72 + - name: Build and push Docker image 73 + id: build-and-push 74 + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 75 + with: 76 + context: . 77 + push: ${{ github.event_name != 'pull_request' }} 78 + tags: ${{ steps.meta.outputs.tags }} 79 + labels: ${{ steps.meta.outputs.labels }} 80 + cache-from: type=gha 81 + cache-to: type=gha,mode=max 82 + 83 + # Sign the resulting Docker image digest except on PRs. 84 + # This will only write to the public Rekor transparency log when the Docker 85 + # repository is public to avoid leaking data. If you would like to publish 86 + # transparency data even for private images, pass --force to cosign below. 87 + # https://github.com/sigstore/cosign 88 + - name: Sign the published Docker image 89 + if: ${{ github.event_name != 'pull_request' }} 90 + env: 91 + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable 92 + TAGS: ${{ steps.meta.outputs.tags }} 93 + DIGEST: ${{ steps.build-and-push.outputs.digest }} 94 + # This step uses the identity token to provision an ephemeral certificate 95 + # against the sigstore community Fulcio instance. 96 + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
+13
.tangled/workflows/build.yml
···
··· 1 + when: 2 + - event: ["push"] 3 + branch: ["master"] 4 + 5 + engine: "nixery" 6 + 7 + dependencies: 8 + nixpkgs: 9 + - cargo 10 + 11 + steps: 12 + - name: "Build Rust project" 13 + command: "cargo build"
+333 -14
Cargo.lock
··· 30 ] 31 32 [[package]] 33 name = "anyhow" 34 - version = "1.0.99" 35 source = "registry+https://github.com/rust-lang/crates.io-index" 36 - checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" 37 38 [[package]] 39 name = "async-trait" ··· 145 146 [[package]] 147 name = "cc" 148 - version = "1.2.37" 149 source = "registry+https://github.com/rust-lang/crates.io-index" 150 - checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" 151 dependencies = [ 152 "find-msvc-tools", 153 "jobserver", ··· 162 checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 163 164 [[package]] 165 name = "core-foundation" 166 version = "0.9.4" 167 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 187 ] 188 189 [[package]] 190 name = "crypto-common" 191 version = "0.1.6" 192 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 320 "anyhow", 321 "async-trait", 322 "dotenv", 323 "reqwest", 324 "rocketman", 325 "serde_json", 326 "tokio", 327 ] ··· 369 ] 370 371 [[package]] 372 name = "fastrand" 373 version = "2.3.0" 374 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 376 377 [[package]] 378 name = "find-msvc-tools" 379 - version = "0.1.1" 380 source = "registry+https://github.com/rust-lang/crates.io-index" 381 - checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" 382 383 [[package]] 384 name = "flume" ··· 476 ] 477 478 [[package]] 479 name = "generic-array" 480 version = "0.14.7" 481 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 537 538 [[package]] 539 name = "hashbrown" 540 - version = "0.15.5" 541 source = "registry+https://github.com/rust-lang/crates.io-index" 542 - checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 543 544 [[package]] 545 name = "http" ··· 623 "http 1.3.1", 624 "hyper", 625 "hyper-util", 626 - "rustls 0.23.31", 627 "rustls-pki-types", 628 "tokio", 629 "tokio-rustls 0.26.3", ··· 787 788 [[package]] 789 name = "indexmap" 790 - version = "2.11.3" 791 source = "registry+https://github.com/rust-lang/crates.io-index" 792 - checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" 793 dependencies = [ 794 "equivalent", 795 "hashbrown", ··· 889 checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 890 891 [[package]] 892 name = "memchr" 893 version = "2.7.5" 894 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 928 "libc", 929 "wasi 0.11.1+wasi-snapshot-preview1", 930 "windows-sys 0.59.0", 931 ] 932 933 [[package]] ··· 1025 ] 1026 1027 [[package]] 1028 name = "percent-encoding" 1029 version = "2.3.2" 1030 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1137 ] 1138 1139 [[package]] 1140 name = "reqwest" 1141 version = "0.12.23" 1142 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1221 checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 1222 1223 [[package]] 1224 name = "rustix" 1225 version = "1.1.2" 1226 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1247 1248 [[package]] 1249 name = "rustls" 1250 - version = "0.23.31" 1251 source = "registry+https://github.com/rust-lang/crates.io-index" 1252 - checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" 1253 dependencies = [ 1254 "once_cell", 1255 "rustls-pki-types", ··· 1331 ] 1332 1333 [[package]] 1334 name = "scopeguard" 1335 version = "1.2.0" 1336 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1368 "core-foundation-sys", 1369 "libc", 1370 ] 1371 1372 [[package]] 1373 name = "serde" ··· 1552 ] 1553 1554 [[package]] 1555 name = "tempfile" 1556 version = "3.22.0" 1557 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1658 source = "registry+https://github.com/rust-lang/crates.io-index" 1659 checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" 1660 dependencies = [ 1661 - "rustls 0.23.31", 1662 "tokio", 1663 ] 1664 ··· 1785 source = "registry+https://github.com/rust-lang/crates.io-index" 1786 checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 1787 dependencies = [ 1788 "nu-ansi-term", 1789 "sharded-slab", 1790 "smallvec", 1791 "thread_local", 1792 "tracing-core", 1793 "tracing-log", 1794 ] ··· 1862 checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1863 1864 [[package]] 1865 name = "valuable" 1866 version = "0.1.1" 1867 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2001 checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2002 2003 [[package]] 2004 name = "windows-link" 2005 version = "0.1.3" 2006 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2011 version = "0.2.0" 2012 source = "registry+https://github.com/rust-lang/crates.io-index" 2013 checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" 2014 2015 [[package]] 2016 name = "windows-registry" ··· 2082 "windows_x86_64_gnu", 2083 "windows_x86_64_gnullvm", 2084 "windows_x86_64_msvc", 2085 ] 2086 2087 [[package]]
··· 30 ] 31 32 [[package]] 33 + name = "aho-corasick" 34 + version = "1.1.3" 35 + source = "registry+https://github.com/rust-lang/crates.io-index" 36 + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 37 + dependencies = [ 38 + "memchr", 39 + ] 40 + 41 + [[package]] 42 name = "anyhow" 43 + version = "1.0.100" 44 source = "registry+https://github.com/rust-lang/crates.io-index" 45 + checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 46 + 47 + [[package]] 48 + name = "async-lock" 49 + version = "3.4.1" 50 + source = "registry+https://github.com/rust-lang/crates.io-index" 51 + checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" 52 + dependencies = [ 53 + "event-listener", 54 + "event-listener-strategy", 55 + "pin-project-lite", 56 + ] 57 58 [[package]] 59 name = "async-trait" ··· 165 166 [[package]] 167 name = "cc" 168 + version = "1.2.38" 169 source = "registry+https://github.com/rust-lang/crates.io-index" 170 + checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" 171 dependencies = [ 172 "find-msvc-tools", 173 "jobserver", ··· 182 checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 183 184 [[package]] 185 + name = "concurrent-queue" 186 + version = "2.5.0" 187 + source = "registry+https://github.com/rust-lang/crates.io-index" 188 + checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 189 + dependencies = [ 190 + "crossbeam-utils", 191 + ] 192 + 193 + [[package]] 194 name = "core-foundation" 195 version = "0.9.4" 196 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 216 ] 217 218 [[package]] 219 + name = "crossbeam-channel" 220 + version = "0.5.15" 221 + source = "registry+https://github.com/rust-lang/crates.io-index" 222 + checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 223 + dependencies = [ 224 + "crossbeam-utils", 225 + ] 226 + 227 + [[package]] 228 + name = "crossbeam-epoch" 229 + version = "0.9.18" 230 + source = "registry+https://github.com/rust-lang/crates.io-index" 231 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 232 + dependencies = [ 233 + "crossbeam-utils", 234 + ] 235 + 236 + [[package]] 237 + name = "crossbeam-utils" 238 + version = "0.8.21" 239 + source = "registry+https://github.com/rust-lang/crates.io-index" 240 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 241 + 242 + [[package]] 243 name = "crypto-common" 244 version = "0.1.6" 245 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 373 "anyhow", 374 "async-trait", 375 "dotenv", 376 + "lazy_static", 377 + "moka", 378 "reqwest", 379 "rocketman", 380 + "serde", 381 "serde_json", 382 "tokio", 383 ] ··· 425 ] 426 427 [[package]] 428 + name = "event-listener" 429 + version = "5.4.1" 430 + source = "registry+https://github.com/rust-lang/crates.io-index" 431 + checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" 432 + dependencies = [ 433 + "concurrent-queue", 434 + "parking", 435 + "pin-project-lite", 436 + ] 437 + 438 + [[package]] 439 + name = "event-listener-strategy" 440 + version = "0.5.4" 441 + source = "registry+https://github.com/rust-lang/crates.io-index" 442 + checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 443 + dependencies = [ 444 + "event-listener", 445 + "pin-project-lite", 446 + ] 447 + 448 + [[package]] 449 name = "fastrand" 450 version = "2.3.0" 451 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 453 454 [[package]] 455 name = "find-msvc-tools" 456 + version = "0.1.2" 457 source = "registry+https://github.com/rust-lang/crates.io-index" 458 + checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" 459 460 [[package]] 461 name = "flume" ··· 553 ] 554 555 [[package]] 556 + name = "generator" 557 + version = "0.8.7" 558 + source = "registry+https://github.com/rust-lang/crates.io-index" 559 + checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" 560 + dependencies = [ 561 + "cc", 562 + "cfg-if", 563 + "libc", 564 + "log", 565 + "rustversion", 566 + "windows", 567 + ] 568 + 569 + [[package]] 570 name = "generic-array" 571 version = "0.14.7" 572 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 628 629 [[package]] 630 name = "hashbrown" 631 + version = "0.16.0" 632 source = "registry+https://github.com/rust-lang/crates.io-index" 633 + checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 634 635 [[package]] 636 name = "http" ··· 714 "http 1.3.1", 715 "hyper", 716 "hyper-util", 717 + "rustls 0.23.32", 718 "rustls-pki-types", 719 "tokio", 720 "tokio-rustls 0.26.3", ··· 878 879 [[package]] 880 name = "indexmap" 881 + version = "2.11.4" 882 source = "registry+https://github.com/rust-lang/crates.io-index" 883 + checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 884 dependencies = [ 885 "equivalent", 886 "hashbrown", ··· 980 checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 981 982 [[package]] 983 + name = "loom" 984 + version = "0.7.2" 985 + source = "registry+https://github.com/rust-lang/crates.io-index" 986 + checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 987 + dependencies = [ 988 + "cfg-if", 989 + "generator", 990 + "scoped-tls", 991 + "tracing", 992 + "tracing-subscriber", 993 + ] 994 + 995 + [[package]] 996 + name = "matchers" 997 + version = "0.2.0" 998 + source = "registry+https://github.com/rust-lang/crates.io-index" 999 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 1000 + dependencies = [ 1001 + "regex-automata", 1002 + ] 1003 + 1004 + [[package]] 1005 name = "memchr" 1006 version = "2.7.5" 1007 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1041 "libc", 1042 "wasi 0.11.1+wasi-snapshot-preview1", 1043 "windows-sys 0.59.0", 1044 + ] 1045 + 1046 + [[package]] 1047 + name = "moka" 1048 + version = "0.12.10" 1049 + source = "registry+https://github.com/rust-lang/crates.io-index" 1050 + checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" 1051 + dependencies = [ 1052 + "async-lock", 1053 + "crossbeam-channel", 1054 + "crossbeam-epoch", 1055 + "crossbeam-utils", 1056 + "event-listener", 1057 + "futures-util", 1058 + "loom", 1059 + "parking_lot", 1060 + "portable-atomic", 1061 + "rustc_version", 1062 + "smallvec", 1063 + "tagptr", 1064 + "thiserror", 1065 + "uuid", 1066 ] 1067 1068 [[package]] ··· 1160 ] 1161 1162 [[package]] 1163 + name = "parking" 1164 + version = "2.2.1" 1165 + source = "registry+https://github.com/rust-lang/crates.io-index" 1166 + checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1167 + 1168 + [[package]] 1169 + name = "parking_lot" 1170 + version = "0.12.4" 1171 + source = "registry+https://github.com/rust-lang/crates.io-index" 1172 + checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 1173 + dependencies = [ 1174 + "lock_api", 1175 + "parking_lot_core", 1176 + ] 1177 + 1178 + [[package]] 1179 + name = "parking_lot_core" 1180 + version = "0.9.11" 1181 + source = "registry+https://github.com/rust-lang/crates.io-index" 1182 + checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 1183 + dependencies = [ 1184 + "cfg-if", 1185 + "libc", 1186 + "redox_syscall", 1187 + "smallvec", 1188 + "windows-targets", 1189 + ] 1190 + 1191 + [[package]] 1192 name = "percent-encoding" 1193 version = "2.3.2" 1194 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1301 ] 1302 1303 [[package]] 1304 + name = "redox_syscall" 1305 + version = "0.5.17" 1306 + source = "registry+https://github.com/rust-lang/crates.io-index" 1307 + checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" 1308 + dependencies = [ 1309 + "bitflags", 1310 + ] 1311 + 1312 + [[package]] 1313 + name = "regex-automata" 1314 + version = "0.4.10" 1315 + source = "registry+https://github.com/rust-lang/crates.io-index" 1316 + checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" 1317 + dependencies = [ 1318 + "aho-corasick", 1319 + "memchr", 1320 + "regex-syntax", 1321 + ] 1322 + 1323 + [[package]] 1324 + name = "regex-syntax" 1325 + version = "0.8.6" 1326 + source = "registry+https://github.com/rust-lang/crates.io-index" 1327 + checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" 1328 + 1329 + [[package]] 1330 name = "reqwest" 1331 version = "0.12.23" 1332 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1411 checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 1412 1413 [[package]] 1414 + name = "rustc_version" 1415 + version = "0.4.1" 1416 + source = "registry+https://github.com/rust-lang/crates.io-index" 1417 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1418 + dependencies = [ 1419 + "semver", 1420 + ] 1421 + 1422 + [[package]] 1423 name = "rustix" 1424 version = "1.1.2" 1425 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1446 1447 [[package]] 1448 name = "rustls" 1449 + version = "0.23.32" 1450 source = "registry+https://github.com/rust-lang/crates.io-index" 1451 + checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" 1452 dependencies = [ 1453 "once_cell", 1454 "rustls-pki-types", ··· 1530 ] 1531 1532 [[package]] 1533 + name = "scoped-tls" 1534 + version = "1.0.1" 1535 + source = "registry+https://github.com/rust-lang/crates.io-index" 1536 + checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1537 + 1538 + [[package]] 1539 name = "scopeguard" 1540 version = "1.2.0" 1541 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1573 "core-foundation-sys", 1574 "libc", 1575 ] 1576 + 1577 + [[package]] 1578 + name = "semver" 1579 + version = "1.0.27" 1580 + source = "registry+https://github.com/rust-lang/crates.io-index" 1581 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 1582 1583 [[package]] 1584 name = "serde" ··· 1763 ] 1764 1765 [[package]] 1766 + name = "tagptr" 1767 + version = "0.2.0" 1768 + source = "registry+https://github.com/rust-lang/crates.io-index" 1769 + checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 1770 + 1771 + [[package]] 1772 name = "tempfile" 1773 version = "3.22.0" 1774 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1875 source = "registry+https://github.com/rust-lang/crates.io-index" 1876 checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" 1877 dependencies = [ 1878 + "rustls 0.23.32", 1879 "tokio", 1880 ] 1881 ··· 2002 source = "registry+https://github.com/rust-lang/crates.io-index" 2003 checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 2004 dependencies = [ 2005 + "matchers", 2006 "nu-ansi-term", 2007 + "once_cell", 2008 + "regex-automata", 2009 "sharded-slab", 2010 "smallvec", 2011 "thread_local", 2012 + "tracing", 2013 "tracing-core", 2014 "tracing-log", 2015 ] ··· 2083 checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2084 2085 [[package]] 2086 + name = "uuid" 2087 + version = "1.18.1" 2088 + source = "registry+https://github.com/rust-lang/crates.io-index" 2089 + checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" 2090 + dependencies = [ 2091 + "getrandom 0.3.3", 2092 + "js-sys", 2093 + "wasm-bindgen", 2094 + ] 2095 + 2096 + [[package]] 2097 name = "valuable" 2098 version = "0.1.1" 2099 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2233 checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2234 2235 [[package]] 2236 + name = "windows" 2237 + version = "0.61.3" 2238 + source = "registry+https://github.com/rust-lang/crates.io-index" 2239 + checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 2240 + dependencies = [ 2241 + "windows-collections", 2242 + "windows-core", 2243 + "windows-future", 2244 + "windows-link 0.1.3", 2245 + "windows-numerics", 2246 + ] 2247 + 2248 + [[package]] 2249 + name = "windows-collections" 2250 + version = "0.2.0" 2251 + source = "registry+https://github.com/rust-lang/crates.io-index" 2252 + checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 2253 + dependencies = [ 2254 + "windows-core", 2255 + ] 2256 + 2257 + [[package]] 2258 + name = "windows-core" 2259 + version = "0.61.2" 2260 + source = "registry+https://github.com/rust-lang/crates.io-index" 2261 + checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 2262 + dependencies = [ 2263 + "windows-implement", 2264 + "windows-interface", 2265 + "windows-link 0.1.3", 2266 + "windows-result", 2267 + "windows-strings", 2268 + ] 2269 + 2270 + [[package]] 2271 + name = "windows-future" 2272 + version = "0.2.1" 2273 + source = "registry+https://github.com/rust-lang/crates.io-index" 2274 + checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 2275 + dependencies = [ 2276 + "windows-core", 2277 + "windows-link 0.1.3", 2278 + "windows-threading", 2279 + ] 2280 + 2281 + [[package]] 2282 + name = "windows-implement" 2283 + version = "0.60.0" 2284 + source = "registry+https://github.com/rust-lang/crates.io-index" 2285 + checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 2286 + dependencies = [ 2287 + "proc-macro2", 2288 + "quote", 2289 + "syn", 2290 + ] 2291 + 2292 + [[package]] 2293 + name = "windows-interface" 2294 + version = "0.59.1" 2295 + source = "registry+https://github.com/rust-lang/crates.io-index" 2296 + checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 2297 + dependencies = [ 2298 + "proc-macro2", 2299 + "quote", 2300 + "syn", 2301 + ] 2302 + 2303 + [[package]] 2304 name = "windows-link" 2305 version = "0.1.3" 2306 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2311 version = "0.2.0" 2312 source = "registry+https://github.com/rust-lang/crates.io-index" 2313 checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" 2314 + 2315 + [[package]] 2316 + name = "windows-numerics" 2317 + version = "0.2.0" 2318 + source = "registry+https://github.com/rust-lang/crates.io-index" 2319 + checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 2320 + dependencies = [ 2321 + "windows-core", 2322 + "windows-link 0.1.3", 2323 + ] 2324 2325 [[package]] 2326 name = "windows-registry" ··· 2392 "windows_x86_64_gnu", 2393 "windows_x86_64_gnullvm", 2394 "windows_x86_64_msvc", 2395 + ] 2396 + 2397 + [[package]] 2398 + name = "windows-threading" 2399 + version = "0.1.0" 2400 + source = "registry+https://github.com/rust-lang/crates.io-index" 2401 + checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 2402 + dependencies = [ 2403 + "windows-link 0.1.3", 2404 ] 2405 2406 [[package]]
+4 -1
Cargo.toml
··· 6 [dependencies] 7 anyhow = "1.0.99" 8 async-trait = "0.1.89" 9 reqwest = { version = "0.12.23", features = ["json"] } 10 - rocketman = "0.2.3" 11 serde_json = "1.0.145" 12 dotenv = "0.15.0" 13 tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
··· 6 [dependencies] 7 anyhow = "1.0.99" 8 async-trait = "0.1.89" 9 + lazy_static = "1.4.0" 10 + moka = { version = "0.12", features = ["future"] } 11 reqwest = { version = "0.12.23", features = ["json"] } 12 + rocketman = "0.2.5" 13 + serde = { version = "1.0", features = ["derive"] } 14 serde_json = "1.0.145" 15 dotenv = "0.15.0" 16 tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
+21
LICENSE
···
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 teal computing, LLC 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+29 -2
src/main.rs
··· 5 connection::JetstreamConnection, handler, ingestion::LexiconIngestor, 6 options::JetstreamOptions, types::event::Event, 7 }; 8 - use serde_json::{Value, json}; 9 use std::{ 10 collections::HashMap, 11 sync::{Arc, Mutex}, 12 }; 13 14 #[tokio::main] 15 async fn main() { ··· 20 let opts = JetstreamOptions::builder() 21 // your EXACT nsids 22 .wanted_collections(vec!["fm.teal.alpha.feed.play".to_string()]) 23 .build(); 24 // create the jetstream connector 25 let jetstream = JetstreamConnection::new(opts); ··· 79 let client = Client::new(); 80 let url = std::env::var("DISCORD_WEBHOOK_URL") 81 .expect("DISCORD_WEBHOOK_URL environment variable must be set"); 82 // Safely extract track name and artist from the record 83 let track_info = message 84 .commit ··· 92 }) 93 .unwrap_or_else(|| "unknown track".to_string()); 94 95 let payload = json!({ 96 - "content": format!("{} is listening to {}", message.did, track_info) 97 }); 98 let response = client.post(url).json(&payload).send().await?; 99
··· 5 connection::JetstreamConnection, handler, ingestion::LexiconIngestor, 6 options::JetstreamOptions, types::event::Event, 7 }; 8 + use serde_json::{json, Value}; 9 use std::{ 10 collections::HashMap, 11 sync::{Arc, Mutex}, 12 }; 13 + 14 + mod resolve; 15 16 #[tokio::main] 17 async fn main() { ··· 22 let opts = JetstreamOptions::builder() 23 // your EXACT nsids 24 .wanted_collections(vec!["fm.teal.alpha.feed.play".to_string()]) 25 + .ws_url(rocketman::endpoints::JetstreamEndpoints::Custom( 26 + "wss://jetstream1.us-east.fire.hose.cam/subscribe".to_string(), 27 + )) 28 .build(); 29 // create the jetstream connector 30 let jetstream = JetstreamConnection::new(opts); ··· 84 let client = Client::new(); 85 let url = std::env::var("DISCORD_WEBHOOK_URL") 86 .expect("DISCORD_WEBHOOK_URL environment variable must be set"); 87 + 88 + // Get resolver app view URL from environment 89 + let resolver_app_view = std::env::var("RESOLVER_APP_VIEW") 90 + .unwrap_or_else(|_| "https://bsky.social".to_string()); 91 + 92 // Safely extract track name and artist from the record 93 let track_info = message 94 .commit ··· 102 }) 103 .unwrap_or_else(|| "unknown track".to_string()); 104 105 + let submission_client_agent = message 106 + .commit 107 + .as_ref() 108 + .and_then(|commit| commit.record.as_ref()) 109 + .and_then(|record| record.get("submissionClientAgent")?.as_str()); 110 + 111 + // Resolve the handle from the DID 112 + let handle = match resolve::resolve_identity(&message.did, &resolver_app_view).await { 113 + Ok(resolved) => resolved.identity, 114 + Err(e) => { 115 + eprintln!("Failed to resolve handle for DID {}: {}", message.did, e); 116 + // Fallback to showing the DID if resolution fails 117 + message.did.clone() 118 + } 119 + }; 120 + 121 let payload = json!({ 122 + "content": format!("{} is listening to {} via `{}`", handle, track_info, submission_client_agent.unwrap_or("unknown client")), 123 + "allowed_mentions": { "parse": [] }, 124 }); 125 let response = client.post(url).json(&payload).send().await?; 126
+295
src/resolve.rs
···
··· 1 + // parts rewritten from https://github.com/mary-ext/atcute/blob/trunk/packages/oauth/browser-client/ 2 + // from https://github.com/espeon/geranium/blob/main/src/resolve.rs 3 + // MIT License 4 + 5 + use lazy_static::lazy_static; 6 + use moka::future::Cache; 7 + use serde::{Deserialize, Serialize}; 8 + use std::time::Duration; 9 + 10 + // Cache for handle resolution - maps handle to DID 11 + type HandleCache = Cache<String, String>; 12 + 13 + // Cache for DID documents - maps DID to DidDocument 14 + type DidDocumentCache = Cache<String, DidDocument>; 15 + 16 + // Global cache instances 17 + lazy_static::lazy_static! { 18 + static ref HANDLE_CACHE: HandleCache = Cache::builder() 19 + .time_to_live(Duration::from_secs(3600)) // 1 hour TTL 20 + .max_capacity(10000) 21 + .build(); 22 + 23 + static ref DID_DOCUMENT_CACHE: DidDocumentCache = Cache::builder() 24 + .time_to_live(Duration::from_secs(3600)) // 1 hour TTL 25 + .max_capacity(10000) 26 + .build(); 27 + } 28 + 29 + // should be same as regex /^did:[a-z]+:[\S\s]+/ 30 + fn is_did(did: &str) -> bool { 31 + let parts: Vec<&str> = did.split(':').collect(); 32 + 33 + if parts.len() != 3 { 34 + // must have exactly 3 parts: "did", method, and identifier 35 + return false; 36 + } 37 + 38 + if parts[0] != "did" { 39 + // first part must be "did" 40 + return false; 41 + } 42 + 43 + if !parts[1].chars().all(|c| c.is_ascii_lowercase()) { 44 + // method must be all lowercase 45 + return false; 46 + } 47 + 48 + if parts[2].is_empty() { 49 + // identifier can't be empty 50 + return false; 51 + } 52 + 53 + true 54 + } 55 + 56 + fn is_valid_domain(domain: &str) -> bool { 57 + // Check if empty or too long 58 + if domain.is_empty() || domain.len() > 253 { 59 + return false; 60 + } 61 + 62 + // Split into labels 63 + let labels: Vec<&str> = domain.split('.').collect(); 64 + 65 + // Must have at least 2 labels 66 + if labels.len() < 2 { 67 + return false; 68 + } 69 + 70 + // Check each label 71 + for label in labels { 72 + // Label length check 73 + if label.is_empty() || label.len() > 63 { 74 + return false; 75 + } 76 + 77 + // Must not start or end with hyphen 78 + if label.starts_with('-') || label.ends_with('-') { 79 + return false; 80 + } 81 + 82 + // Check characters 83 + if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { 84 + return false; 85 + } 86 + } 87 + 88 + true 89 + } 90 + 91 + async fn resolve_handle(handle: &str, resolver_app_view: &str) -> Result<String, reqwest::Error> { 92 + // Check cache first 93 + if let Some(cached_did) = HANDLE_CACHE.get(handle).await { 94 + println!("๐ŸŽฏ Cache HIT for handle: {} -> {}", handle, cached_did); 95 + return Ok(cached_did); 96 + } 97 + 98 + println!("โŒ Cache MISS for handle: {}, resolving from API", handle); 99 + 100 + // If not in cache, resolve from API 101 + let res = reqwest::get(format!( 102 + "{}/xrpc/com.atproto.identity.resolveHandle?handle={}", 103 + resolver_app_view, handle 104 + )) 105 + .await? 106 + .json::<ResolvedHandle>() 107 + .await?; 108 + 109 + let did = res.did; 110 + 111 + // Cache the result 112 + HANDLE_CACHE.insert(handle.to_string(), did.clone()).await; 113 + println!("๐Ÿ’พ Cached handle resolution: {} -> {}", handle, did); 114 + 115 + Ok(did) 116 + } 117 + 118 + async fn get_did_doc(did: &str) -> Result<DidDocument, reqwest::Error> { 119 + // Check cache first 120 + if let Some(cached_doc) = DID_DOCUMENT_CACHE.get(did).await { 121 + println!("๐ŸŽฏ Cache HIT for DID document: {}", did); 122 + return Ok(cached_doc); 123 + } 124 + 125 + println!("โŒ Cache MISS for DID document: {}, resolving from API", did); 126 + 127 + // If not in cache, resolve from API 128 + // get the specific did spec 129 + // did:plc:abcd1e -> plc 130 + let parts: Vec<&str> = did.split(':').collect(); 131 + let spec = parts[1]; 132 + let doc = match spec { 133 + "plc" => { 134 + println!("๐Ÿ“ก Fetching DID document from PLC directory for: {}", did); 135 + let res: DidDocument = reqwest::get(format!("https://plc.directory/{}", did)) 136 + .await? 137 + .error_for_status()? 138 + .json() 139 + .await?; 140 + res 141 + } 142 + "web" => { 143 + if !is_valid_domain(parts[2]) { 144 + todo!("Error for domain in did:web is not valid"); 145 + }; 146 + let ident = parts[2]; 147 + println!("๐Ÿ“ก Fetching DID document from web domain: {}", ident); 148 + let res = reqwest::get(format!("https://{}/.well-known/did.json", ident)) 149 + .await? 150 + .error_for_status()? 151 + .json() 152 + .await?; 153 + res 154 + } 155 + _ => todo!("Identifier not supported"), 156 + }; 157 + 158 + // Cache the result 159 + DID_DOCUMENT_CACHE.insert(did.to_string(), doc.clone()).await; 160 + println!("๐Ÿ’พ Cached DID document: {}", did); 161 + 162 + Ok(doc) 163 + } 164 + 165 + fn get_pds_endpoint(doc: &DidDocument) -> Option<DidDocumentService> { 166 + get_service_endpoint(doc, "#atproto_pds", "AtprotoPersonalDataServer") 167 + } 168 + 169 + fn get_service_endpoint( 170 + doc: &DidDocument, 171 + svc_id: &str, 172 + svc_type: &str, 173 + ) -> Option<DidDocumentService> { 174 + doc.service 175 + .iter() 176 + .find(|svc| svc.id == svc_id && svc._type == svc_type) 177 + .cloned() 178 + } 179 + 180 + fn extract_handle_from_doc(doc: &DidDocument) -> Option<String> { 181 + // Look through alsoKnownAs list for at:// URLs 182 + for also_known_as in &doc.also_known_as { 183 + if also_known_as.starts_with("at://") { 184 + // Extract handle from "at://handle.domain" format 185 + let handle = also_known_as.strip_prefix("at://")?; 186 + println!("๐ŸŽฏ Found handle in alsoKnownAs: {} -> {}", also_known_as, handle); 187 + return Some(handle.to_string()); 188 + } 189 + } 190 + None 191 + } 192 + 193 + pub async fn resolve_identity( 194 + id: &str, 195 + resolver_app_view: &str, 196 + ) -> Result<ResolvedIdentity, reqwest::Error> { 197 + println!("๐Ÿ” Resolving identity: {}", id); 198 + 199 + // is our identifier a did 200 + let did = if is_did(id) { 201 + println!("โœ… Input is already a DID: {}", id); 202 + id 203 + } else { 204 + println!("๐Ÿ”— Input is a handle, resolving to DID: {}", id); 205 + // our id must be either invalid or a handle 206 + if let Ok(res) = resolve_handle(id, resolver_app_view).await { 207 + &res.clone() 208 + } else { 209 + todo!("Error type for could not resolve handle") 210 + } 211 + }; 212 + 213 + let doc = get_did_doc(did).await?; 214 + let pds = get_pds_endpoint(&doc); 215 + 216 + if pds.is_none() { 217 + todo!("Error for could not find PDS") 218 + } 219 + 220 + // Extract handle from alsoKnownAs list 221 + let handle = extract_handle_from_doc(&doc).unwrap_or_else(|| { 222 + println!("โš ๏ธ No handle found in alsoKnownAs, using original input: {}", id); 223 + id.to_string() 224 + }); 225 + 226 + println!("โœ… Successfully resolved identity: {} -> {} (handle: {}) (PDS: {})", 227 + id, did, handle, pds.as_ref().unwrap().service_endpoint); 228 + 229 + return Ok(ResolvedIdentity { 230 + did: did.to_owned(), 231 + doc, 232 + identity: handle, 233 + pds: pds.unwrap().service_endpoint, 234 + }); 235 + } 236 + 237 + /// Clear all cached handle resolutions and DID documents 238 + pub async fn clear_cache() { 239 + HANDLE_CACHE.invalidate_all(); 240 + DID_DOCUMENT_CACHE.invalidate_all(); 241 + } 242 + 243 + /// Get cache statistics for monitoring 244 + pub async fn get_cache_stats() -> (u64, u64) { 245 + let handle_count = HANDLE_CACHE.entry_count(); 246 + let did_doc_count = DID_DOCUMENT_CACHE.entry_count(); 247 + (handle_count, did_doc_count) 248 + } 249 + 250 + // want this to be reusable on case of scope expansion :( 251 + #[allow(dead_code)] 252 + #[derive(Serialize, Deserialize, Debug)] 253 + pub struct ResolvedIdentity { 254 + pub did: String, 255 + pub doc: DidDocument, 256 + pub identity: String, 257 + // should prob be url type but not really needed rn 258 + pub pds: String, 259 + } 260 + 261 + #[derive(Serialize, Deserialize, Debug)] 262 + struct ResolvedHandle { 263 + did: String, 264 + } 265 + 266 + #[derive(Serialize, Deserialize, Debug, Clone)] 267 + pub struct DidDocument { 268 + #[serde(alias = "@context")] 269 + pub _context: Vec<String>, 270 + pub id: String, 271 + #[serde(alias = "alsoKnownAs")] 272 + pub also_known_as: Vec<String>, 273 + #[serde(alias = "verificationMethod")] 274 + pub verification_method: Vec<DidDocumentVerificationMethod>, 275 + pub service: Vec<DidDocumentService>, 276 + } 277 + 278 + #[derive(Serialize, Deserialize, Debug, Clone)] 279 + pub struct DidDocumentVerificationMethod { 280 + pub id: String, 281 + #[serde(alias = "type")] 282 + pub _type: String, 283 + pub controller: String, 284 + #[serde(alias = "publicKeyMultibase")] 285 + pub public_key_multibase: String, 286 + } 287 + 288 + #[derive(Serialize, Deserialize, Debug, Clone)] 289 + pub struct DidDocumentService { 290 + pub id: String, 291 + #[serde(alias = "type")] 292 + pub _type: String, 293 + #[serde(alias = "serviceEndpoint")] 294 + pub service_endpoint: String, 295 + }