├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.rst └── src ├── github.rs ├── lib.rs ├── main.rs ├── service.rs └── slack.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | allow: 8 | # Also update indirect dependencies 9 | - dependency-type: all 10 | open-pull-requests-limit: 1024 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | push: 4 | branches: master 5 | 6 | name: Continuous integration 7 | 8 | jobs: 9 | ci: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | rust: 14 | - stable 15 | - beta 16 | - nightly 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | 21 | - uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: ${{ matrix.rust }} 25 | override: true 26 | components: rustfmt, clippy 27 | 28 | - uses: actions-rs/cargo@v1 29 | with: 30 | command: build 31 | 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: test 35 | 36 | - uses: actions-rs/cargo@v1 37 | with: 38 | command: fmt 39 | args: --all -- --check 40 | 41 | - uses: actions-rs/cargo@v1 42 | with: 43 | command: clippy 44 | args: -- -D warnings 45 | 46 | - uses: actions-rs/audit-check@v1 47 | with: 48 | token: ${{ secrets.GITHUB_TOKEN }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.3.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 25 | 26 | [[package]] 27 | name = "backtrace" 28 | version = "0.3.72" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" 31 | dependencies = [ 32 | "addr2line", 33 | "cc", 34 | "cfg-if", 35 | "libc", 36 | "miniz_oxide", 37 | "object", 38 | "rustc-demangle", 39 | ] 40 | 41 | [[package]] 42 | name = "base64" 43 | version = "0.22.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "2.5.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 52 | 53 | [[package]] 54 | name = "bumpalo" 55 | version = "3.16.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 58 | 59 | [[package]] 60 | name = "bytes" 61 | version = "1.6.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 64 | 65 | [[package]] 66 | name = "cc" 67 | version = "1.0.98" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" 70 | 71 | [[package]] 72 | name = "cfg-if" 73 | version = "1.0.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 76 | 77 | [[package]] 78 | name = "dirs-next" 79 | version = "2.0.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 82 | dependencies = [ 83 | "cfg-if", 84 | "dirs-sys-next", 85 | ] 86 | 87 | [[package]] 88 | name = "dirs-sys-next" 89 | version = "0.1.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 92 | dependencies = [ 93 | "libc", 94 | "redox_users", 95 | "winapi", 96 | ] 97 | 98 | [[package]] 99 | name = "fnv" 100 | version = "1.0.7" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 103 | 104 | [[package]] 105 | name = "form_urlencoded" 106 | version = "1.2.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 109 | dependencies = [ 110 | "percent-encoding", 111 | ] 112 | 113 | [[package]] 114 | name = "futures-channel" 115 | version = "0.3.30" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 118 | dependencies = [ 119 | "futures-core", 120 | "futures-sink", 121 | ] 122 | 123 | [[package]] 124 | name = "futures-core" 125 | version = "0.3.30" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 128 | 129 | [[package]] 130 | name = "futures-io" 131 | version = "0.3.30" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 134 | 135 | [[package]] 136 | name = "futures-sink" 137 | version = "0.3.30" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 140 | 141 | [[package]] 142 | name = "futures-task" 143 | version = "0.3.30" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 146 | 147 | [[package]] 148 | name = "futures-util" 149 | version = "0.3.30" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 152 | dependencies = [ 153 | "futures-core", 154 | "futures-io", 155 | "futures-sink", 156 | "futures-task", 157 | "memchr", 158 | "pin-project-lite", 159 | "pin-utils", 160 | "slab", 161 | ] 162 | 163 | [[package]] 164 | name = "getopts" 165 | version = "0.2.21" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 168 | dependencies = [ 169 | "unicode-width", 170 | ] 171 | 172 | [[package]] 173 | name = "getrandom" 174 | version = "0.2.15" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 177 | dependencies = [ 178 | "cfg-if", 179 | "libc", 180 | "wasi", 181 | ] 182 | 183 | [[package]] 184 | name = "gimli" 185 | version = "0.29.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 188 | 189 | [[package]] 190 | name = "hermit-abi" 191 | version = "0.3.9" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 194 | 195 | [[package]] 196 | name = "http" 197 | version = "1.1.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 200 | dependencies = [ 201 | "bytes", 202 | "fnv", 203 | "itoa", 204 | ] 205 | 206 | [[package]] 207 | name = "http-body" 208 | version = "1.0.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 211 | dependencies = [ 212 | "bytes", 213 | "http", 214 | ] 215 | 216 | [[package]] 217 | name = "http-body-util" 218 | version = "0.1.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" 221 | dependencies = [ 222 | "bytes", 223 | "futures-core", 224 | "http", 225 | "http-body", 226 | "pin-project-lite", 227 | ] 228 | 229 | [[package]] 230 | name = "httparse" 231 | version = "1.8.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 234 | 235 | [[package]] 236 | name = "hyper" 237 | version = "1.3.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" 240 | dependencies = [ 241 | "bytes", 242 | "futures-channel", 243 | "futures-util", 244 | "http", 245 | "http-body", 246 | "httparse", 247 | "itoa", 248 | "pin-project-lite", 249 | "smallvec", 250 | "tokio", 251 | "want", 252 | ] 253 | 254 | [[package]] 255 | name = "hyper-rustls" 256 | version = "0.26.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" 259 | dependencies = [ 260 | "futures-util", 261 | "http", 262 | "hyper", 263 | "hyper-util", 264 | "rustls", 265 | "rustls-pki-types", 266 | "tokio", 267 | "tokio-rustls", 268 | "tower-service", 269 | ] 270 | 271 | [[package]] 272 | name = "hyper-util" 273 | version = "0.1.5" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" 276 | dependencies = [ 277 | "bytes", 278 | "futures-channel", 279 | "futures-util", 280 | "http", 281 | "http-body", 282 | "hyper", 283 | "pin-project-lite", 284 | "socket2", 285 | "tokio", 286 | "tower", 287 | "tower-service", 288 | "tracing", 289 | ] 290 | 291 | [[package]] 292 | name = "idna" 293 | version = "0.5.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 296 | dependencies = [ 297 | "unicode-bidi", 298 | "unicode-normalization", 299 | ] 300 | 301 | [[package]] 302 | name = "ipnet" 303 | version = "2.9.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 306 | 307 | [[package]] 308 | name = "itoa" 309 | version = "1.0.11" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 312 | 313 | [[package]] 314 | name = "js-sys" 315 | version = "0.3.69" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 318 | dependencies = [ 319 | "wasm-bindgen", 320 | ] 321 | 322 | [[package]] 323 | name = "libc" 324 | version = "0.2.155" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 327 | 328 | [[package]] 329 | name = "libredox" 330 | version = "0.1.3" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 333 | dependencies = [ 334 | "bitflags", 335 | "libc", 336 | ] 337 | 338 | [[package]] 339 | name = "log" 340 | version = "0.4.21" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 343 | 344 | [[package]] 345 | name = "memchr" 346 | version = "2.7.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 349 | 350 | [[package]] 351 | name = "mime" 352 | version = "0.3.17" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 355 | 356 | [[package]] 357 | name = "miniz_oxide" 358 | version = "0.7.3" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" 361 | dependencies = [ 362 | "adler", 363 | ] 364 | 365 | [[package]] 366 | name = "mio" 367 | version = "0.8.11" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 370 | dependencies = [ 371 | "libc", 372 | "wasi", 373 | "windows-sys 0.48.0", 374 | ] 375 | 376 | [[package]] 377 | name = "num_cpus" 378 | version = "1.16.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 381 | dependencies = [ 382 | "hermit-abi", 383 | "libc", 384 | ] 385 | 386 | [[package]] 387 | name = "object" 388 | version = "0.35.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" 391 | dependencies = [ 392 | "memchr", 393 | ] 394 | 395 | [[package]] 396 | name = "once_cell" 397 | version = "1.19.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 400 | 401 | [[package]] 402 | name = "otp-cop" 403 | version = "0.1.0" 404 | dependencies = [ 405 | "getopts", 406 | "reqwest", 407 | "serde", 408 | "serde_derive", 409 | "serde_json", 410 | "term", 411 | ] 412 | 413 | [[package]] 414 | name = "percent-encoding" 415 | version = "2.3.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 418 | 419 | [[package]] 420 | name = "pin-project" 421 | version = "1.1.5" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 424 | dependencies = [ 425 | "pin-project-internal", 426 | ] 427 | 428 | [[package]] 429 | name = "pin-project-internal" 430 | version = "1.1.5" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 433 | dependencies = [ 434 | "proc-macro2", 435 | "quote", 436 | "syn", 437 | ] 438 | 439 | [[package]] 440 | name = "pin-project-lite" 441 | version = "0.2.14" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 444 | 445 | [[package]] 446 | name = "pin-utils" 447 | version = "0.1.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 450 | 451 | [[package]] 452 | name = "proc-macro2" 453 | version = "1.0.84" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" 456 | dependencies = [ 457 | "unicode-ident", 458 | ] 459 | 460 | [[package]] 461 | name = "quote" 462 | version = "1.0.36" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 465 | dependencies = [ 466 | "proc-macro2", 467 | ] 468 | 469 | [[package]] 470 | name = "redox_users" 471 | version = "0.4.5" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" 474 | dependencies = [ 475 | "getrandom", 476 | "libredox", 477 | "thiserror", 478 | ] 479 | 480 | [[package]] 481 | name = "reqwest" 482 | version = "0.12.4" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" 485 | dependencies = [ 486 | "base64", 487 | "bytes", 488 | "futures-channel", 489 | "futures-core", 490 | "futures-util", 491 | "http", 492 | "http-body", 493 | "http-body-util", 494 | "hyper", 495 | "hyper-rustls", 496 | "hyper-util", 497 | "ipnet", 498 | "js-sys", 499 | "log", 500 | "mime", 501 | "once_cell", 502 | "percent-encoding", 503 | "pin-project-lite", 504 | "rustls", 505 | "rustls-pemfile", 506 | "rustls-pki-types", 507 | "serde", 508 | "serde_json", 509 | "serde_urlencoded", 510 | "sync_wrapper", 511 | "tokio", 512 | "tokio-rustls", 513 | "tower-service", 514 | "url", 515 | "wasm-bindgen", 516 | "wasm-bindgen-futures", 517 | "web-sys", 518 | "webpki-roots", 519 | "winreg", 520 | ] 521 | 522 | [[package]] 523 | name = "ring" 524 | version = "0.17.8" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 527 | dependencies = [ 528 | "cc", 529 | "cfg-if", 530 | "getrandom", 531 | "libc", 532 | "spin", 533 | "untrusted", 534 | "windows-sys 0.52.0", 535 | ] 536 | 537 | [[package]] 538 | name = "rustc-demangle" 539 | version = "0.1.24" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 542 | 543 | [[package]] 544 | name = "rustls" 545 | version = "0.22.4" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" 548 | dependencies = [ 549 | "log", 550 | "ring", 551 | "rustls-pki-types", 552 | "rustls-webpki", 553 | "subtle", 554 | "zeroize", 555 | ] 556 | 557 | [[package]] 558 | name = "rustls-pemfile" 559 | version = "2.1.2" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 562 | dependencies = [ 563 | "base64", 564 | "rustls-pki-types", 565 | ] 566 | 567 | [[package]] 568 | name = "rustls-pki-types" 569 | version = "1.7.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" 572 | 573 | [[package]] 574 | name = "rustls-webpki" 575 | version = "0.102.4" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" 578 | dependencies = [ 579 | "ring", 580 | "rustls-pki-types", 581 | "untrusted", 582 | ] 583 | 584 | [[package]] 585 | name = "rustversion" 586 | version = "1.0.17" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 589 | 590 | [[package]] 591 | name = "ryu" 592 | version = "1.0.18" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 595 | 596 | [[package]] 597 | name = "serde" 598 | version = "1.0.203" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 601 | dependencies = [ 602 | "serde_derive", 603 | ] 604 | 605 | [[package]] 606 | name = "serde_derive" 607 | version = "1.0.203" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 610 | dependencies = [ 611 | "proc-macro2", 612 | "quote", 613 | "syn", 614 | ] 615 | 616 | [[package]] 617 | name = "serde_json" 618 | version = "1.0.117" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 621 | dependencies = [ 622 | "itoa", 623 | "ryu", 624 | "serde", 625 | ] 626 | 627 | [[package]] 628 | name = "serde_urlencoded" 629 | version = "0.7.1" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 632 | dependencies = [ 633 | "form_urlencoded", 634 | "itoa", 635 | "ryu", 636 | "serde", 637 | ] 638 | 639 | [[package]] 640 | name = "slab" 641 | version = "0.4.9" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 644 | dependencies = [ 645 | "autocfg", 646 | ] 647 | 648 | [[package]] 649 | name = "smallvec" 650 | version = "1.13.2" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 653 | 654 | [[package]] 655 | name = "socket2" 656 | version = "0.5.7" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 659 | dependencies = [ 660 | "libc", 661 | "windows-sys 0.52.0", 662 | ] 663 | 664 | [[package]] 665 | name = "spin" 666 | version = "0.9.8" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 669 | 670 | [[package]] 671 | name = "subtle" 672 | version = "2.5.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 675 | 676 | [[package]] 677 | name = "syn" 678 | version = "2.0.66" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 681 | dependencies = [ 682 | "proc-macro2", 683 | "quote", 684 | "unicode-ident", 685 | ] 686 | 687 | [[package]] 688 | name = "sync_wrapper" 689 | version = "0.1.2" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 692 | 693 | [[package]] 694 | name = "term" 695 | version = "0.7.0" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 698 | dependencies = [ 699 | "dirs-next", 700 | "rustversion", 701 | "winapi", 702 | ] 703 | 704 | [[package]] 705 | name = "thiserror" 706 | version = "1.0.61" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 709 | dependencies = [ 710 | "thiserror-impl", 711 | ] 712 | 713 | [[package]] 714 | name = "thiserror-impl" 715 | version = "1.0.61" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 718 | dependencies = [ 719 | "proc-macro2", 720 | "quote", 721 | "syn", 722 | ] 723 | 724 | [[package]] 725 | name = "tinyvec" 726 | version = "1.6.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 729 | dependencies = [ 730 | "tinyvec_macros", 731 | ] 732 | 733 | [[package]] 734 | name = "tinyvec_macros" 735 | version = "0.1.1" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 738 | 739 | [[package]] 740 | name = "tokio" 741 | version = "1.37.0" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 744 | dependencies = [ 745 | "backtrace", 746 | "bytes", 747 | "libc", 748 | "mio", 749 | "num_cpus", 750 | "pin-project-lite", 751 | "socket2", 752 | "windows-sys 0.48.0", 753 | ] 754 | 755 | [[package]] 756 | name = "tokio-rustls" 757 | version = "0.25.0" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" 760 | dependencies = [ 761 | "rustls", 762 | "rustls-pki-types", 763 | "tokio", 764 | ] 765 | 766 | [[package]] 767 | name = "tower" 768 | version = "0.4.13" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 771 | dependencies = [ 772 | "futures-core", 773 | "futures-util", 774 | "pin-project", 775 | "pin-project-lite", 776 | "tokio", 777 | "tower-layer", 778 | "tower-service", 779 | ] 780 | 781 | [[package]] 782 | name = "tower-layer" 783 | version = "0.3.2" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 786 | 787 | [[package]] 788 | name = "tower-service" 789 | version = "0.3.2" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 792 | 793 | [[package]] 794 | name = "tracing" 795 | version = "0.1.40" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 798 | dependencies = [ 799 | "pin-project-lite", 800 | "tracing-core", 801 | ] 802 | 803 | [[package]] 804 | name = "tracing-core" 805 | version = "0.1.32" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 808 | dependencies = [ 809 | "once_cell", 810 | ] 811 | 812 | [[package]] 813 | name = "try-lock" 814 | version = "0.2.5" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 817 | 818 | [[package]] 819 | name = "unicode-bidi" 820 | version = "0.3.15" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 823 | 824 | [[package]] 825 | name = "unicode-ident" 826 | version = "1.0.12" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 829 | 830 | [[package]] 831 | name = "unicode-normalization" 832 | version = "0.1.23" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 835 | dependencies = [ 836 | "tinyvec", 837 | ] 838 | 839 | [[package]] 840 | name = "unicode-width" 841 | version = "0.1.12" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" 844 | 845 | [[package]] 846 | name = "untrusted" 847 | version = "0.9.0" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 850 | 851 | [[package]] 852 | name = "url" 853 | version = "2.5.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 856 | dependencies = [ 857 | "form_urlencoded", 858 | "idna", 859 | "percent-encoding", 860 | ] 861 | 862 | [[package]] 863 | name = "want" 864 | version = "0.3.1" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 867 | dependencies = [ 868 | "try-lock", 869 | ] 870 | 871 | [[package]] 872 | name = "wasi" 873 | version = "0.11.0+wasi-snapshot-preview1" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 876 | 877 | [[package]] 878 | name = "wasm-bindgen" 879 | version = "0.2.92" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 882 | dependencies = [ 883 | "cfg-if", 884 | "wasm-bindgen-macro", 885 | ] 886 | 887 | [[package]] 888 | name = "wasm-bindgen-backend" 889 | version = "0.2.92" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 892 | dependencies = [ 893 | "bumpalo", 894 | "log", 895 | "once_cell", 896 | "proc-macro2", 897 | "quote", 898 | "syn", 899 | "wasm-bindgen-shared", 900 | ] 901 | 902 | [[package]] 903 | name = "wasm-bindgen-futures" 904 | version = "0.4.42" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 907 | dependencies = [ 908 | "cfg-if", 909 | "js-sys", 910 | "wasm-bindgen", 911 | "web-sys", 912 | ] 913 | 914 | [[package]] 915 | name = "wasm-bindgen-macro" 916 | version = "0.2.92" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 919 | dependencies = [ 920 | "quote", 921 | "wasm-bindgen-macro-support", 922 | ] 923 | 924 | [[package]] 925 | name = "wasm-bindgen-macro-support" 926 | version = "0.2.92" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 929 | dependencies = [ 930 | "proc-macro2", 931 | "quote", 932 | "syn", 933 | "wasm-bindgen-backend", 934 | "wasm-bindgen-shared", 935 | ] 936 | 937 | [[package]] 938 | name = "wasm-bindgen-shared" 939 | version = "0.2.92" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 942 | 943 | [[package]] 944 | name = "web-sys" 945 | version = "0.3.69" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 948 | dependencies = [ 949 | "js-sys", 950 | "wasm-bindgen", 951 | ] 952 | 953 | [[package]] 954 | name = "webpki-roots" 955 | version = "0.26.1" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" 958 | dependencies = [ 959 | "rustls-pki-types", 960 | ] 961 | 962 | [[package]] 963 | name = "winapi" 964 | version = "0.3.9" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 967 | dependencies = [ 968 | "winapi-i686-pc-windows-gnu", 969 | "winapi-x86_64-pc-windows-gnu", 970 | ] 971 | 972 | [[package]] 973 | name = "winapi-i686-pc-windows-gnu" 974 | version = "0.4.0" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 977 | 978 | [[package]] 979 | name = "winapi-x86_64-pc-windows-gnu" 980 | version = "0.4.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 983 | 984 | [[package]] 985 | name = "windows-sys" 986 | version = "0.48.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 989 | dependencies = [ 990 | "windows-targets 0.48.5", 991 | ] 992 | 993 | [[package]] 994 | name = "windows-sys" 995 | version = "0.52.0" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 998 | dependencies = [ 999 | "windows-targets 0.52.0", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "windows-targets" 1004 | version = "0.48.5" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1007 | dependencies = [ 1008 | "windows_aarch64_gnullvm 0.48.5", 1009 | "windows_aarch64_msvc 0.48.5", 1010 | "windows_i686_gnu 0.48.5", 1011 | "windows_i686_msvc 0.48.5", 1012 | "windows_x86_64_gnu 0.48.5", 1013 | "windows_x86_64_gnullvm 0.48.5", 1014 | "windows_x86_64_msvc 0.48.5", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "windows-targets" 1019 | version = "0.52.0" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1022 | dependencies = [ 1023 | "windows_aarch64_gnullvm 0.52.0", 1024 | "windows_aarch64_msvc 0.52.0", 1025 | "windows_i686_gnu 0.52.0", 1026 | "windows_i686_msvc 0.52.0", 1027 | "windows_x86_64_gnu 0.52.0", 1028 | "windows_x86_64_gnullvm 0.52.0", 1029 | "windows_x86_64_msvc 0.52.0", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "windows_aarch64_gnullvm" 1034 | version = "0.48.5" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1037 | 1038 | [[package]] 1039 | name = "windows_aarch64_gnullvm" 1040 | version = "0.52.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1043 | 1044 | [[package]] 1045 | name = "windows_aarch64_msvc" 1046 | version = "0.48.5" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1049 | 1050 | [[package]] 1051 | name = "windows_aarch64_msvc" 1052 | version = "0.52.0" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1055 | 1056 | [[package]] 1057 | name = "windows_i686_gnu" 1058 | version = "0.48.5" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1061 | 1062 | [[package]] 1063 | name = "windows_i686_gnu" 1064 | version = "0.52.0" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1067 | 1068 | [[package]] 1069 | name = "windows_i686_msvc" 1070 | version = "0.48.5" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1073 | 1074 | [[package]] 1075 | name = "windows_i686_msvc" 1076 | version = "0.52.0" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1079 | 1080 | [[package]] 1081 | name = "windows_x86_64_gnu" 1082 | version = "0.48.5" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1085 | 1086 | [[package]] 1087 | name = "windows_x86_64_gnu" 1088 | version = "0.52.0" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1091 | 1092 | [[package]] 1093 | name = "windows_x86_64_gnullvm" 1094 | version = "0.48.5" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1097 | 1098 | [[package]] 1099 | name = "windows_x86_64_gnullvm" 1100 | version = "0.52.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1103 | 1104 | [[package]] 1105 | name = "windows_x86_64_msvc" 1106 | version = "0.48.5" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1109 | 1110 | [[package]] 1111 | name = "windows_x86_64_msvc" 1112 | version = "0.52.0" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1115 | 1116 | [[package]] 1117 | name = "winreg" 1118 | version = "0.52.0" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" 1121 | dependencies = [ 1122 | "cfg-if", 1123 | "windows-sys 0.48.0", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "zeroize" 1128 | version = "1.8.1" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1131 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "otp-cop" 3 | version = "0.1.0" 4 | authors = ["Alex Gaynor "] 5 | repository = "https://github.com/alex/otp-cop" 6 | license = "BSD-3-Clause" 7 | edition = "2018" 8 | 9 | [dependencies] 10 | getopts = "0.2" 11 | reqwest = { version = "0.12", features = ["rustls-tls", "blocking"], default-features = false } 12 | serde = "1" 13 | serde_derive = "1" 14 | serde_json = "1" 15 | term = "0.7" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Alex Gaynor and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of rust-asn1 nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | otp-cop 2 | ======= 3 | 4 | At this point, these services all natively have the ability to mandate 2FA, therefore this project is retired. 5 | 6 | Verify that everyone in your organization has 2fa set up. Works with the 7 | following services: 8 | 9 | * Slack 10 | * Github 11 | 12 | To use: 13 | 14 | .. code-block:: console 15 | 16 | $ git clone https://github.com/alex/otp-cop 17 | $ cd otp-cop 18 | $ cargo build 19 | $ ./target/debug/otp-cop 20 | 21 | Now you go and yell at all these people. 22 | 23 | Services 24 | -------- 25 | 26 | Slack 27 | +++++ 28 | 29 | .. code-block:: console 30 | 31 | $ otp-cop --slack-token='' 32 | 33 | You can obtain a token online: https://api.slack.com/web#authentication 34 | 35 | Github 36 | ++++++ 37 | 38 | .. code-block:: console 39 | 40 | $ otp-cop --github-org='' --github-username='' --github-password='' 41 | 42 | You can generate a Github personal access token online: 43 | https://help.github.com/articles/creating-an-access-token-for-command-line-use/. 44 | 45 | The user needs to be an owner of the organization. 46 | 47 | ``otp-cop`` requires the ``read:org`` scope. 48 | 49 | Github Enterprise users must specify the API endpoint URL. 50 | 51 | .. code-block:: console 52 | 53 | $ otp-cop --github-endpoint='' --github-org='' --github-username='' --github-password='' 54 | -------------------------------------------------------------------------------- /src/github.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use reqwest::StatusCode; 4 | 5 | use super::{CreateServiceResult, GetUsersError, GetUsersResult, Service, ServiceFactory, User}; 6 | 7 | const DEFAULT_ENDPOINT: &str = "https://api.github.com"; 8 | 9 | #[derive(Deserialize)] 10 | struct GithubError { 11 | documentation_url: String, 12 | message: String, 13 | } 14 | 15 | #[derive(Deserialize)] 16 | struct GithubUser { 17 | login: String, 18 | } 19 | 20 | pub struct GithubServiceFactory; 21 | 22 | impl ServiceFactory for GithubServiceFactory { 23 | fn add_options(&self, opts: &mut getopts::Options) { 24 | opts.optopt( 25 | "", 26 | "github-endpoint", 27 | &format!("Github API endpoint URL (default: {DEFAULT_ENDPOINT})"), 28 | "endpoint", 29 | ); 30 | opts.optopt("", "github-org", "Gitub organization", "org"); 31 | opts.optopt("", "github-username", "Gitub username", "username"); 32 | opts.optopt("", "github-password", "Github password", "password"); 33 | } 34 | 35 | fn create_service(&self, matches: &getopts::Matches) -> CreateServiceResult { 36 | match ( 37 | matches.opt_str("github-endpoint"), 38 | matches.opt_str("github-org"), 39 | matches.opt_str("github-username"), 40 | matches.opt_str("github-password"), 41 | ) { 42 | (endpoint, Some(org), Some(username), Some(password)) => { 43 | CreateServiceResult::Service(Box::new(GithubService { 44 | endpoint: endpoint.unwrap_or_else(|| DEFAULT_ENDPOINT.to_string()), 45 | org, 46 | username, 47 | password, 48 | })) 49 | } 50 | (None, None, None, None) => CreateServiceResult::None, 51 | (_, org, username, password) => { 52 | let mut missing = vec![]; 53 | if org.is_none() { 54 | missing.push("github-org".to_string()); 55 | } 56 | if username.is_none() { 57 | missing.push("github-username".to_string()); 58 | } 59 | if password.is_none() { 60 | missing.push("github-password".to_string()); 61 | } 62 | CreateServiceResult::MissingArguments(missing) 63 | } 64 | } 65 | } 66 | } 67 | 68 | struct GithubService { 69 | endpoint: String, 70 | org: String, 71 | username: String, 72 | password: String, 73 | } 74 | 75 | impl Service for GithubService { 76 | fn get_users(&self) -> Result { 77 | let client = reqwest::blocking::Client::new(); 78 | 79 | let mut response = client 80 | .get(format!( 81 | "{}/orgs/{}/members?filter=2fa_disabled", 82 | self.endpoint, self.org 83 | )) 84 | .basic_auth(self.username.to_string(), Some(self.password.to_string())) 85 | .header(reqwest::header::USER_AGENT, "otp-cop/0.1.0") 86 | .send() 87 | .unwrap(); 88 | let mut body = String::new(); 89 | response.read_to_string(&mut body).unwrap(); 90 | 91 | match response.status() { 92 | StatusCode::OK => { 93 | let result = serde_json::from_str::>(&body).unwrap(); 94 | 95 | Ok(GetUsersResult { 96 | service_name: "Github".to_string(), 97 | users: result 98 | .iter() 99 | .map(|user| User { 100 | name: user.login.to_string(), 101 | email: None, 102 | details: None, 103 | }) 104 | .collect(), 105 | }) 106 | } 107 | StatusCode::UNPROCESSABLE_ENTITY => { 108 | let result = serde_json::from_str::(&body).unwrap(); 109 | 110 | Err(GetUsersError { 111 | service_name: "Github".to_string(), 112 | error_message: format!( 113 | "{}\n See {}", 114 | result.message, result.documentation_url 115 | ), 116 | }) 117 | } 118 | _ => panic!("Github: unexpected status code"), 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | 4 | pub use crate::github::GithubServiceFactory; 5 | pub use crate::service::{ 6 | CreateServiceResult, GetUsersError, GetUsersResult, Service, ServiceFactory, User, 7 | }; 8 | pub use crate::slack::SlackServiceFactory; 9 | 10 | pub mod github; 11 | pub mod service; 12 | pub mod slack; 13 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::sync::{mpsc, Arc}; 3 | use std::{env, process, thread}; 4 | 5 | use otp_cop::service::{CreateServiceResult, ServiceFactory}; 6 | 7 | struct ParallelIter { 8 | count: usize, 9 | pos: usize, 10 | rx: mpsc::Receiver, 11 | } 12 | 13 | impl Iterator for ParallelIter { 14 | type Item = T; 15 | 16 | fn next(&mut self) -> Option { 17 | if self.pos < self.count { 18 | self.count += 1; 19 | self.rx.recv().ok() 20 | } else { 21 | None 22 | } 23 | } 24 | } 25 | 26 | fn parallel(objs: Vec, f1: F1) -> ParallelIter 27 | where 28 | F1: 'static + Fn(T) -> U + Send + Sync, 29 | T: 'static + Send, 30 | U: 'static + Send, 31 | { 32 | let (tx, rx) = mpsc::channel(); 33 | let count = objs.len(); 34 | let shared_f1 = Arc::new(f1); 35 | for o in objs { 36 | let f1 = shared_f1.clone(); 37 | let tx = tx.clone(); 38 | thread::spawn(move || { 39 | tx.send(f1(o)).unwrap(); 40 | }); 41 | } 42 | 43 | ParallelIter { count, pos: 0, rx } 44 | } 45 | 46 | fn main() { 47 | let service_factories = [ 48 | Box::new(otp_cop::SlackServiceFactory) as Box, 49 | Box::new(otp_cop::GithubServiceFactory) as Box, 50 | ]; 51 | 52 | let mut opts = getopts::Options::new(); 53 | 54 | for factory in service_factories.iter() { 55 | factory.add_options(&mut opts); 56 | } 57 | 58 | let matches = match opts.parse(env::args().skip(1)) { 59 | Ok(matches) => matches, 60 | Err(e) => { 61 | eprintln!("{e}"); 62 | process::exit(1); 63 | } 64 | }; 65 | 66 | let mut services = vec![]; 67 | 68 | for factory in service_factories.iter() { 69 | match factory.create_service(&matches) { 70 | CreateServiceResult::Service(s) => services.push(s), 71 | CreateServiceResult::MissingArguments(args) => { 72 | eprintln!("Missing arguments: {args:?}"); 73 | process::exit(1); 74 | } 75 | CreateServiceResult::None => continue, 76 | } 77 | } 78 | 79 | if services.is_empty() { 80 | eprintln!("{}", opts.usage("otp-cop: ")); 81 | process::exit(1); 82 | } 83 | 84 | let count = services.len(); 85 | let mut error_exit = false; 86 | for (i, result) in parallel(services, |service| service.get_users()).enumerate() { 87 | match result { 88 | Ok(result) => { 89 | let header = format!("{} ({})", result.service_name, result.users.len()); 90 | println!("{header}"); 91 | println!("{}", repeat_char("=", header.len())); 92 | println!(); 93 | if !result.users.is_empty() { 94 | error_exit = true; 95 | } 96 | for user in result.users { 97 | let email = match user.email { 98 | Some(email) => format!(" ({email})"), 99 | None => "".to_string(), 100 | }; 101 | let details = match user.details { 102 | Some(details) => format!(" -- {details}"), 103 | None => "".to_string(), 104 | }; 105 | println!("@{}{email}{details}", user.name); 106 | } 107 | if i + 1 != count { 108 | println!(); 109 | println!(); 110 | } 111 | } 112 | Err(e) => { 113 | error_exit = true; 114 | let mut t = term::stderr().unwrap(); 115 | writeln!(t, "{}", e.service_name).unwrap(); 116 | writeln!(t, "{}", repeat_char("=", e.service_name.len())).unwrap(); 117 | writeln!(t).unwrap(); 118 | t.fg(term::color::RED).unwrap(); 119 | writeln!(t, "{}", e.error_message).unwrap(); 120 | t.reset().unwrap(); 121 | } 122 | } 123 | } 124 | 125 | if error_exit { 126 | process::exit(2); 127 | } 128 | } 129 | 130 | fn repeat_char(c: &'static str, length: usize) -> String { 131 | c.chars().cycle().take(length).collect::() 132 | } 133 | -------------------------------------------------------------------------------- /src/service.rs: -------------------------------------------------------------------------------- 1 | pub struct GetUsersResult { 2 | pub service_name: String, 3 | pub users: Vec, 4 | } 5 | 6 | pub struct GetUsersError { 7 | pub service_name: String, 8 | pub error_message: String, 9 | } 10 | 11 | pub struct User { 12 | pub name: String, 13 | pub email: Option, 14 | pub details: Option, 15 | } 16 | 17 | pub enum CreateServiceResult { 18 | None, 19 | MissingArguments(Vec), 20 | Service(Box), 21 | } 22 | 23 | pub trait ServiceFactory { 24 | fn add_options(&self, _: &mut getopts::Options); 25 | fn create_service(&self, _: &getopts::Matches) -> CreateServiceResult; 26 | } 27 | 28 | pub trait Service: Send + Sync { 29 | fn get_users(&self) -> Result; 30 | } 31 | -------------------------------------------------------------------------------- /src/slack.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use reqwest::StatusCode; 4 | 5 | use super::{CreateServiceResult, GetUsersError, GetUsersResult, Service, ServiceFactory, User}; 6 | 7 | #[derive(Deserialize)] 8 | struct SlackUserListResponse { 9 | ok: bool, 10 | members: Vec, 11 | } 12 | 13 | #[derive(Deserialize)] 14 | struct SlackUser { 15 | name: String, 16 | deleted: bool, 17 | is_bot: Option, 18 | has_2fa: Option, 19 | profile: SlackProfile, 20 | is_owner: Option, 21 | is_admin: Option, 22 | } 23 | 24 | #[derive(Deserialize)] 25 | struct SlackProfile { 26 | email: Option, 27 | } 28 | 29 | pub struct SlackServiceFactory; 30 | 31 | impl ServiceFactory for SlackServiceFactory { 32 | fn add_options(&self, opts: &mut getopts::Options) { 33 | opts.optopt( 34 | "", 35 | "slack-token", 36 | "Slack token (https://api.slack.com/web#authentication)", 37 | "token", 38 | ); 39 | } 40 | 41 | fn create_service(&self, matches: &getopts::Matches) -> CreateServiceResult { 42 | match matches.opt_str("slack-token") { 43 | Some(token) => CreateServiceResult::Service(Box::new(SlackService { token })), 44 | None => CreateServiceResult::None, 45 | } 46 | } 47 | } 48 | 49 | struct SlackService { 50 | token: String, 51 | } 52 | 53 | impl Service for SlackService { 54 | fn get_users(&self) -> Result { 55 | let client = reqwest::blocking::Client::new(); 56 | 57 | let mut response = client 58 | .get(format!( 59 | "https://slack.com/api/users.list?token={}", 60 | self.token 61 | )) 62 | .send() 63 | .unwrap(); 64 | assert_eq!(response.status(), StatusCode::OK); 65 | let mut body = String::new(); 66 | response.read_to_string(&mut body).unwrap(); 67 | 68 | let result = serde_json::from_str::(&body).unwrap(); 69 | assert!(result.ok); 70 | let users = result 71 | .members 72 | .iter() 73 | .filter(|user| !user.deleted && !user.is_bot.unwrap() && !user.has_2fa.unwrap()) 74 | .map(|user| User { 75 | name: user.name.to_string(), 76 | email: Some(user.profile.email.clone().unwrap()), 77 | details: match (user.is_owner.unwrap(), user.is_admin.unwrap()) { 78 | (true, true) => Some("Owner/Admin".to_string()), 79 | (true, false) => Some("Owner".to_string()), 80 | (false, true) => Some("Admin".to_string()), 81 | (false, false) => None, 82 | }, 83 | }) 84 | .collect(); 85 | 86 | Ok(GetUsersResult { 87 | service_name: "Slack".to_string(), 88 | users, 89 | }) 90 | } 91 | } 92 | --------------------------------------------------------------------------------